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;
369 mutt_concat_path(char *d, ssize_t n, const char *dir, const char *fname)
373 pos = m_strcpy(d, n, dir);
377 if (pos && d[pos - 1] != '/')
380 m_strcpy(d + pos, n - pos, fname);
385 void mutt_sanitize_filename(char *s, short slash)
387 static char safe_chars[] =
388 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+@{}._-:%/";
394 if ((slash && *s == '/') || !strchr (safe_chars, *s))
399 /* prepare a file name to survive the shell's quoting rules.
400 * From the Unix programming FAQ by way of Liviu.
403 /* FIXME: API is very wrong */
404 ssize_t mutt_quote_filename(char *d, ssize_t l, const char *s)
413 /* leave some space for the trailing characters. */
418 for (i = 0; j < l && s[i]; i++) {
419 if (s[i] == '\'' || s[i] == '`') {
435 ssize_t m_file_fmt(char *dst, ssize_t n, const char *fmt, const char *src)
441 for (p = strchr(fmt, '%'); p; p = strchr(fmt, '%')) {
443 pos += m_strncpy(dst + pos, n - pos, fmt, p - fmt);
444 pos += m_strcpy(dst + pos, n - pos, src);
448 pos += m_strncpy(dst + pos, n - pos, fmt, p + 1 - fmt);
449 fmt = p + 1 + (p[1] == '%');
452 pos += m_strcpy(dst + pos, n - pos, fmt);
455 pos += snprintf(dst + pos, n - pos, " %s", src);
461 m_quotefile_fmt(char *dst, ssize_t n, const char *fmt, const char *src)
463 char tmp[LONG_STRING];
464 mutt_quote_filename(tmp, sizeof(tmp), src);
465 return m_file_fmt(dst, n, fmt, tmp);
469 m_tempftplize(char *dst, ssize_t dlen, const char *fmt, const char *s)
473 while ((p = strchr(fmt, '/'))) {
478 return m_strcpy(dst, dlen, s);
480 for (p = strchr(fmt, '%'); p; p = strchr(p, '%')) {
482 return m_file_fmt(dst, dlen, fmt, s);
484 p += 1 + (p[1] == '%');
487 p = strrchr(fmt, '.');
489 return snprintf(dst, dlen, "%s%s", s, p);
491 return snprintf(dst, dlen, "%s.%s", s, fmt);
495 int m_tempfd(char *dst, ssize_t n, const char *dir, const char *fmt)
497 char raw[_POSIX_PATH_MAX], tpl[_POSIX_PATH_MAX];
498 const char *path = fmt ? tpl : raw;
502 snprintf(raw, sizeof(raw), "%s/madmutt-%04x-%04x-%08x",
503 dir, (int)getuid(), (int)getpid(), (int)rand());
506 m_tempftplize(tpl, sizeof(tpl), fmt, raw);
509 fd = open(path, O_CREAT | O_EXCL | O_RDWR | O_NOFOLLOW, 0600);
517 m_strcpy(dst, n, path);
521 FILE *m_tempfile(char *dst, ssize_t n, const char *dir, const char *fmt)
523 int fd = m_tempfd(dst, n, dir, fmt);
524 return fd < 0 ? NULL : fdopen(fd, "w+");
527 /****************************************************************************/
529 /****************************************************************************/
531 /* Decrease a file's modification time by 1 second */
532 time_t m_decrease_mtime(const char *path, struct stat *st)
539 if (stat(path, &_st) == -1)
544 if ((mtime = st->st_mtime) == time(NULL)) {
547 utim.modtime = mtime;