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.
29 #include <sys/types.h>
41 #include "../lib/debug.h"
47 /* FIXME: ugly to the max */
50 static int compare_stat(struct stat *osb, struct stat *nsb)
52 if (osb->st_dev != nsb->st_dev || osb->st_ino != nsb->st_ino
53 || osb->st_rdev != nsb->st_rdev)
61 /****************************************************************************/
63 /****************************************************************************/
65 int safe_open(const char *path, int flags)
72 if ((fd = open(path, flags, 0666)) < 0)
75 /* make sure the file is not symlink */
76 if (lstat (path, &osb) < 0 || fstat(fd, &nsb) < 0
77 || compare_stat(&osb, &nsb) == -1)
79 debug_print(1, ("%s is a symlink!\n", path));
88 int safe_symlink(const char *oldpath, const char *newpath)
92 if (!oldpath || !newpath)
95 if (unlink(newpath) < 0 && errno != ENOENT)
98 if (oldpath[0] == '/') {
99 if (symlink(oldpath, newpath) < 0)
102 char abs_oldpath[_POSIX_PATH_MAX];
105 if (!getcwd(abs_oldpath, sizeof(abs_oldpath)))
108 len = m_strcat(abs_oldpath, sizeof(abs_oldpath), "/");
109 if (len >= ssizeof(abs_oldpath))
112 len += m_strcpy(abs_oldpath + len, sizeof(abs_oldpath) - len, oldpath);
113 if (len >= ssizeof(abs_oldpath))
116 if (symlink (abs_oldpath, newpath) < 0)
120 if (stat(oldpath, &osb) < 0
121 || stat(newpath, &nsb) < 0
122 || compare_stat(&osb, &nsb) < 0)
132 * This function is supposed to do nfs-safe renaming of files.
134 * Warning: We don't check whether src and target are equal.
136 int safe_rename(const char *src, const char *target)
138 struct stat ssb, tsb;
143 if (link(src, target) != 0) {
145 * Coda does not allow cross-directory links, but tells
146 * us it's a cross-filesystem linking attempt.
148 * However, the Coda rename call is allegedly safe to use.
150 * With other file systems, rename should just fail when
151 * the files reside on different file systems, so it's safe
157 return rename(src, target);
162 /* Stat both links and check if they are equal. */
164 if (stat(src, &ssb) < 0 || stat(target, &tsb) < 0) {
169 * pretend that the link failed because the target file
173 if (compare_stat(&ssb, &tsb) == -1) {
179 * Unlink the original link. Should we really ignore the return
187 void mutt_unlink(const char *s)
191 if (lstat(s, &sb) == 0 && S_ISREG(sb.st_mode)) {
195 /* Defend against symlink attacks */
197 if ((fd = open(s, O_RDWR | O_NOFOLLOW)) < 0)
200 if ((fstat(fd, &sb2) != 0) || !S_ISREG(sb2.st_mode)
201 || (sb.st_dev != sb2.st_dev) || (sb.st_ino != sb2.st_ino))
207 if ((f = fdopen(fd, "r+"))) {
211 p_clear(buf, sizeof(buf));
212 while (sb.st_size > 0) {
213 fwrite(buf, 1, MIN(ssizeof(buf), sb.st_size), f);
214 sb.st_size -= MIN(ssizeof(buf), sb.st_size);
222 /****************************************************************************/
224 /****************************************************************************/
226 /* when opening files for writing, make sure the file doesn't already exist
227 to avoid race conditions. */
228 FILE *safe_fopen(const char *path, const char *mode)
230 /* first set the current umask */
233 if (mode[0] == 'w') {
235 int flags = O_CREAT | O_EXCL | O_NOFOLLOW;
237 flags |= (mode[1] == '+' ? O_RDWR : O_WRONLY);
239 if ((fd = safe_open(path, flags)) < 0)
242 return fdopen (fd, mode);
245 return fopen(path, mode);
248 int safe_fclose(FILE **f)
258 /* Read a line from ``fp'' into the dynamically allocated ``s'',
259 * increasing ``s'' if necessary. The ending "\n" or "\r\n" is removed.
260 * If a line ends with "\", this char and the linefeed is removed,
261 * and the next line is read too.
263 char *mutt_read_line(char *s, size_t *size, FILE * fp, int *line)
269 s = p_new(char, STRING);
274 if (fgets(s + offset, *size - offset, fp) == NULL) {
278 if ((ch = strchr(s + offset, '\n')) != NULL) {
281 if (ch > s && *(ch - 1) == '\r')
283 if (ch == s || *(ch - 1) != '\\')
289 c = getc (fp); /* This is kind of a hack. We want to know if the
290 char at the current point in the input stream is EOF.
291 feof() will only tell us if we've already hit EOF, not
292 if the next character is EOF. So, we need to read in
293 the next character and manually check if it is EOF. */
295 /* The last line of fp isn't \n terminated */
299 ungetc (c, fp); /* undo our dammage */
300 /* There wasn't room for the line -- increase ``s'' */
301 offset = *size - 1; /* overwrite the terminating 0 */
303 p_realloc(&s, *size);
309 int mutt_copy_stream(FILE *fin, FILE *fout)
314 while ((l = fread(buf, 1, sizeof (buf), fin)) > 0) {
315 if (fwrite(buf, 1, l, fout) != l)
322 int mutt_copy_bytes(FILE *in, FILE *out, size_t size)
327 size_t chunk = MIN(size, sizeof(buf));
329 if ((chunk = fread(buf, 1, chunk, in)) < 1)
331 if (fwrite(buf, 1, chunk, out) != chunk) {
332 debug_print(1, ("fwrite() returned short byte count\n"));
342 /****************************************************************************/
343 /* ligben-like funcs */
344 /****************************************************************************/
346 const char *mutt_basename(const char *f)
348 const char *p = strrchr(f, '/');
349 return p ? p + 1 : f;
354 mutt_concat_path(char *d, ssize_t n, const char *dir, const char *fname)
358 pos = m_strcpy(d, n, dir);
362 if (pos && d[pos - 1] != '/')
365 m_strcpy(d + pos, n - pos, fname);
370 void mutt_sanitize_filename(char *s, short slash)
372 static char safe_chars[] =
373 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+@{}._-:%/";
379 if ((slash && *s == '/') || !strchr (safe_chars, *s))
384 /* prepare a file name to survive the shell's quoting rules.
385 * From the Unix programming FAQ by way of Liviu.
388 /* FIXME: API is very wrong */
389 ssize_t mutt_quote_filename(char *d, ssize_t l, const char *s)
398 /* leave some space for the trailing characters. */
403 for (i = 0; j < l && s[i]; i++) {
404 if (s[i] == '\'' || s[i] == '`') {