2 * This program is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or (at
5 * your option) any later version.
7 * This program is distributed in the hope that it will be useful, but
8 * WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10 * General Public License for more details.
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
17 * Copyright © 2006 Pierre Habouzit
20 * Copyright notice from original mutt:
21 * Copyright (C) 1996-2000 Michael R. Elkins <me@mutt.org>
22 * Copyright (C) 1999-2000 Thomas Roessler <roessler@does-not-exist.org>
24 * This file is part of mutt-ng, see http://www.muttng.org/.
25 * It's licensed under the GNU General Public License,
26 * please see the file GPL in the top level source directory.
37 /* FIXME: ugly to the max */
40 static int compare_stat(struct stat *osb, struct stat *nsb)
42 if (osb->st_dev != nsb->st_dev || osb->st_ino != nsb->st_ino
43 || osb->st_rdev != nsb->st_rdev)
51 /****************************************************************************/
53 /****************************************************************************/
55 int safe_open(const char *path, int flags)
62 if ((fd = open(path, flags, 0666)) < 0)
65 /* make sure the file is not symlink */
66 if (lstat (path, &osb) < 0 || fstat(fd, &nsb) < 0
67 || compare_stat(&osb, &nsb) == -1)
77 int safe_symlink(const char *oldpath, const char *newpath)
81 if (!oldpath || !newpath)
84 if (unlink(newpath) < 0 && errno != ENOENT)
87 if (oldpath[0] == '/') {
88 if (symlink(oldpath, newpath) < 0)
91 char abs_oldpath[_POSIX_PATH_MAX];
94 if (!getcwd(abs_oldpath, sizeof(abs_oldpath)))
97 len = m_strcat(abs_oldpath, sizeof(abs_oldpath), "/");
98 if (len >= ssizeof(abs_oldpath))
101 len += m_strcpy(abs_oldpath + len, sizeof(abs_oldpath) - len, oldpath);
102 if (len >= ssizeof(abs_oldpath))
105 if (symlink (abs_oldpath, newpath) < 0)
109 if (stat(oldpath, &osb) < 0
110 || stat(newpath, &nsb) < 0
111 || compare_stat(&osb, &nsb) < 0)
121 * This function is supposed to do nfs-safe renaming of files.
123 * Warning: We don't check whether src and target are equal.
125 int safe_rename(const char *src, const char *target)
127 struct stat ssb, tsb;
132 if (link(src, target) != 0) {
134 * Coda does not allow cross-directory links, but tells
135 * us it's a cross-filesystem linking attempt.
137 * However, the Coda rename call is allegedly safe to use.
139 * With other file systems, rename should just fail when
140 * the files reside on different file systems, so it's safe
146 return rename(src, target);
151 /* Stat both links and check if they are equal. */
153 if (stat(src, &ssb) < 0 || stat(target, &tsb) < 0) {
158 * pretend that the link failed because the target file
162 if (compare_stat(&ssb, &tsb) == -1) {
168 * Unlink the original link. Should we really ignore the return
176 void mutt_unlink(const char *s)
180 if (lstat(s, &sb) == 0 && S_ISREG(sb.st_mode)) {
184 /* Defend against symlink attacks */
186 if ((fd = open(s, O_RDWR | O_NOFOLLOW)) < 0)
189 if ((fstat(fd, &sb2) != 0) || !S_ISREG(sb2.st_mode)
190 || (sb.st_dev != sb2.st_dev) || (sb.st_ino != sb2.st_ino))
196 if ((f = fdopen(fd, "r+"))) {
200 p_clear(buf, countof(buf));
201 while (sb.st_size > 0) {
202 fwrite(buf, 1, MIN(ssizeof(buf), sb.st_size), f);
203 sb.st_size -= MIN(ssizeof(buf), sb.st_size);
211 /****************************************************************************/
213 /****************************************************************************/
215 /* when opening files for writing, make sure the file doesn't already exist
216 to avoid race conditions. */
217 FILE *safe_fopen(const char *path, const char *mode)
219 /* first set the current umask */
222 if (mode[0] == 'w') {
224 int flags = O_CREAT | O_EXCL | O_NOFOLLOW;
226 flags |= (mode[1] == '+' ? O_RDWR : O_WRONLY);
228 if ((fd = safe_open(path, flags)) < 0)
231 return fdopen (fd, mode);
234 return fopen(path, mode);
237 /* If rfc1524_expand_command() is used on a recv'd message, then
238 * the filename doesn't exist yet, but if its used while sending a message,
239 * then we need to rename the existing file.
241 * This function returns 0 on successful move, 1 on old file doesn't exist,
242 * 2 on new file already exists, and 3 on other failure.
245 /* note on access(2) use: No dangling symlink problems here due to
248 int mutt_rename_file(char *oldfile, char *newfile)
252 if (access(oldfile, F_OK) != 0)
254 if (access(newfile, F_OK) == 0)
257 ofp = fopen(oldfile, "r");
261 nfp = safe_fopen(newfile, "w");
267 mutt_copy_stream(ofp, nfp);
270 mutt_unlink(oldfile);
274 /* Read a line from ``fp'' into the dynamically allocated ``s'',
275 * increasing ``s'' if necessary. The ending "\n" or "\r\n" is removed.
276 * If a line ends with "\", this char and the linefeed is removed,
277 * and the next line is read too.
279 char *mutt_read_line(char *s, ssize_t *size, FILE * fp, int *line)
285 s = p_new(char, STRING);
290 if (fgets(s + offset, *size - offset, fp) == NULL) {
294 if ((ch = strchr(s + offset, '\n')) != NULL) {
297 if (ch > s && *(ch - 1) == '\r')
299 if (ch == s || *(ch - 1) != '\\')
305 c = getc (fp); /* This is kind of a hack. We want to know if the
306 char at the current point in the input stream is EOF.
307 feof() will only tell us if we've already hit EOF, not
308 if the next character is EOF. So, we need to read in
309 the next character and manually check if it is EOF. */
311 /* The last line of fp isn't \n terminated */
315 ungetc (c, fp); /* undo our dammage */
316 /* There wasn't room for the line -- increase ``s'' */
317 offset = *size - 1; /* overwrite the terminating 0 */
319 p_realloc(&s, *size);
325 int mutt_copy_stream(FILE *fin, FILE *fout)
330 while ((l = fread(buf, 1, sizeof(buf), fin)) > 0) {
331 if (fwrite(buf, 1, l, fout) != l)
338 int mutt_copy_bytes(FILE *in, FILE *out, ssize_t size)
343 size_t chunk = MIN(size, ssizeof(buf));
345 if ((chunk = fread(buf, 1, chunk, in)) < 1)
347 if (fwrite(buf, 1, chunk, out) != chunk) {
357 /****************************************************************************/
358 /* path manipulations */
359 /****************************************************************************/
361 const char *mutt_basename(const char *f)
363 const char *p = strrchr(f, '/');
364 return p ? p + 1 : f;
367 ssize_t m_dirname(char *dst, ssize_t dlen, const char *path)
369 ssize_t plen = m_strlen(path);
371 while (plen > 0 && path[plen - 1] == '/')
374 while (plen > 0 && path[plen - 1] != '/')
377 while (plen > 0 && path[plen - 1] == '/')
381 return m_strncpy(dst, dlen, path, plen);
384 return m_strcpy(dst, dlen, "/");
385 return m_strcpy(dst, dlen, ".");
389 mutt_concat_path(char *d, ssize_t n, const char *dir, const char *fname)
393 pos = m_strcpy(d, n, dir);
397 if (pos && d[pos - 1] != '/')
400 m_strcpy(d + pos, n - pos, fname);
405 void mutt_sanitize_filename(char *s, short slash)
407 static char safe_chars[] =
408 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+@{}._-:%/";
414 if ((slash && *s == '/') || !strchr (safe_chars, *s))
419 /* prepare a file name to survive the shell's quoting rules.
420 * From the Unix programming FAQ by way of Liviu.
423 /* FIXME: API is very wrong */
424 ssize_t mutt_quote_filename(char *d, ssize_t l, const char *s)
433 /* leave some space for the trailing characters. */
438 for (i = 0; j < l && s[i]; i++) {
439 if (s[i] == '\'' || s[i] == '`') {
455 ssize_t m_file_fmt(char *dst, ssize_t n, const char *fmt, const char *src)
461 for (p = strchr(fmt, '%'); p; p = strchr(fmt, '%')) {
463 pos += m_strncpy(dst + pos, n - pos, fmt, p - fmt);
464 pos += m_strcpy(dst + pos, n - pos, src);
468 pos += m_strncpy(dst + pos, n - pos, fmt, p + 1 - fmt);
469 fmt = p + 1 + (p[1] == '%');
472 pos += m_strcpy(dst + pos, n - pos, fmt);
475 pos += snprintf(dst + pos, n - pos, " %s", src);
481 m_quotefile_fmt(char *dst, ssize_t n, const char *fmt, const char *src)
483 char tmp[LONG_STRING];
484 mutt_quote_filename(tmp, sizeof(tmp), src);
485 return m_file_fmt(dst, n, fmt, tmp);
489 m_tempftplize(char *dst, ssize_t dlen, const char *fmt, const char *s)
493 while ((p = strchr(fmt, '/'))) {
498 return m_strcpy(dst, dlen, s);
500 for (p = strchr(fmt, '%'); p; p = strchr(p, '%')) {
502 return m_file_fmt(dst, dlen, fmt, s);
504 p += 1 + (p[1] == '%');
507 p = strrchr(fmt, '.');
509 return snprintf(dst, dlen, "%s%s", s, p);
511 return snprintf(dst, dlen, "%s.%s", s, fmt);
515 int m_tempfd(char *dst, ssize_t n, const char *dir, const char *fmt)
517 char raw[_POSIX_PATH_MAX], tpl[_POSIX_PATH_MAX];
518 const char *path = fmt ? tpl : raw;
522 snprintf(raw, sizeof(raw), "%s/madmutt-%04x-%04x-%08x",
523 dir, (int)getuid(), (int)getpid(), (int)rand());
526 m_tempftplize(tpl, sizeof(tpl), fmt, raw);
529 fd = open(path, O_CREAT | O_EXCL | O_RDWR | O_NOFOLLOW, 0600);
537 m_strcpy(dst, n, path);
541 FILE *m_tempfile(char *dst, ssize_t n, const char *dir, const char *fmt)
543 int fd = m_tempfd(dst, n, dir, fmt);
544 return fd < 0 ? NULL : fdopen(fd, "w+");
547 /****************************************************************************/
549 /****************************************************************************/
551 /* Decrease a file's modification time by 1 second */
552 time_t m_decrease_mtime(const char *path, struct stat *st)
559 if (stat(path, &_st) == -1)
564 if ((mtime = st->st_mtime) == time(NULL)) {
567 utim.modtime = mtime;