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"
42 #include "../lib/str.h"
48 /* FIXME: ugly to the max */
51 static int compare_stat(struct stat *osb, struct stat *nsb)
53 if (osb->st_dev != nsb->st_dev || osb->st_ino != nsb->st_ino
54 || osb->st_rdev != nsb->st_rdev)
62 /****************************************************************************/
64 /****************************************************************************/
66 int safe_open(const char *path, int flags)
73 if ((fd = open(path, flags, 0666)) < 0)
76 /* make sure the file is not symlink */
77 if (lstat (path, &osb) < 0 || fstat(fd, &nsb) < 0
78 || compare_stat(&osb, &nsb) == -1)
80 debug_print(1, ("%s is a symlink!\n", path));
89 int safe_symlink(const char *oldpath, const char *newpath)
93 if (!oldpath || !newpath)
96 if (unlink(newpath) < 0 && errno != ENOENT)
99 if (oldpath[0] == '/') {
100 if (symlink(oldpath, newpath) < 0)
103 char abs_oldpath[_POSIX_PATH_MAX];
106 if (!getcwd(abs_oldpath, sizeof(abs_oldpath)))
109 len = m_strcat(abs_oldpath, sizeof(abs_oldpath), "/");
110 if (len >= ssizeof(abs_oldpath))
113 len += m_strcpy(abs_oldpath + len, sizeof(abs_oldpath) - len, oldpath);
114 if (len >= ssizeof(abs_oldpath))
117 if (symlink (abs_oldpath, newpath) < 0)
121 if (stat(oldpath, &osb) < 0
122 || stat(newpath, &nsb) < 0
123 || compare_stat(&osb, &nsb) < 0)
133 * This function is supposed to do nfs-safe renaming of files.
135 * Warning: We don't check whether src and target are equal.
137 int safe_rename(const char *src, const char *target)
139 struct stat ssb, tsb;
144 if (link(src, target) != 0) {
146 * Coda does not allow cross-directory links, but tells
147 * us it's a cross-filesystem linking attempt.
149 * However, the Coda rename call is allegedly safe to use.
151 * With other file systems, rename should just fail when
152 * the files reside on different file systems, so it's safe
158 return rename(src, target);
163 /* Stat both links and check if they are equal. */
165 if (stat(src, &ssb) < 0 || stat(target, &tsb) < 0) {
170 * pretend that the link failed because the target file
174 if (compare_stat(&ssb, &tsb) == -1) {
180 * Unlink the original link. Should we really ignore the return
188 void mutt_unlink(const char *s)
192 if (lstat(s, &sb) == 0 && S_ISREG(sb.st_mode)) {
196 /* Defend against symlink attacks */
198 if ((fd = open(s, O_RDWR | O_NOFOLLOW)) < 0)
201 if ((fstat(fd, &sb2) != 0) || !S_ISREG(sb2.st_mode)
202 || (sb.st_dev != sb2.st_dev) || (sb.st_ino != sb2.st_ino))
208 if ((f = fdopen(fd, "r+"))) {
212 p_clear(buf, sizeof(buf));
213 while (sb.st_size > 0) {
214 fwrite(buf, 1, MIN(ssizeof(buf), sb.st_size), f);
215 sb.st_size -= MIN(ssizeof(buf), sb.st_size);
223 /****************************************************************************/
225 /****************************************************************************/
227 /* when opening files for writing, make sure the file doesn't already exist
228 to avoid race conditions. */
229 FILE *safe_fopen(const char *path, const char *mode)
231 /* first set the current umask */
234 if (mode[0] == 'w') {
236 int flags = O_CREAT | O_EXCL | O_NOFOLLOW;
238 flags |= (mode[1] == '+' ? O_RDWR : O_WRONLY);
240 if ((fd = safe_open(path, flags)) < 0)
243 return fdopen (fd, mode);
246 return fopen(path, mode);
249 int safe_fclose(FILE **f)
259 /* Read a line from ``fp'' into the dynamically allocated ``s'',
260 * increasing ``s'' if necessary. The ending "\n" or "\r\n" is removed.
261 * If a line ends with "\", this char and the linefeed is removed,
262 * and the next line is read too.
264 char *mutt_read_line(char *s, size_t *size, FILE * fp, int *line)
270 s = p_new(char, STRING);
275 if (fgets(s + offset, *size - offset, fp) == NULL) {
279 if ((ch = strchr(s + offset, '\n')) != NULL) {
282 if (ch > s && *(ch - 1) == '\r')
284 if (ch == s || *(ch - 1) != '\\')
290 c = getc (fp); /* This is kind of a hack. We want to know if the
291 char at the current point in the input stream is EOF.
292 feof() will only tell us if we've already hit EOF, not
293 if the next character is EOF. So, we need to read in
294 the next character and manually check if it is EOF. */
296 /* The last line of fp isn't \n terminated */
300 ungetc (c, fp); /* undo our dammage */
301 /* There wasn't room for the line -- increase ``s'' */
302 offset = *size - 1; /* overwrite the terminating 0 */
304 p_realloc(&s, *size);
310 int mutt_copy_stream(FILE *fin, FILE *fout)
315 while ((l = fread(buf, 1, sizeof (buf), fin)) > 0) {
316 if (fwrite(buf, 1, l, fout) != l)
323 int mutt_copy_bytes(FILE *in, FILE *out, size_t size)
328 size_t chunk = MIN(size, sizeof(buf));
330 if ((chunk = fread(buf, 1, chunk, in)) < 1)
332 if (fwrite(buf, 1, chunk, out) != chunk) {
333 debug_print(1, ("fwrite() returned short byte count\n"));
343 /****************************************************************************/
344 /* ligben-like funcs */
345 /****************************************************************************/
347 const char *mutt_basename(const char *f)
349 const char *p = strrchr(f, '/');
350 return p ? p + 1 : f;
355 mutt_concat_path(char *d, ssize_t n, const char *dir, const char *fname)
359 pos = m_strcpy(d, n, dir);
363 if (pos && d[pos - 1] != '/')
366 m_strcpy(d + pos, n - pos, fname);
371 void mutt_sanitize_filename(char *s, short slash)
373 static char safe_chars[] =
374 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+@{}._-:%/";
380 if ((slash && *s == '/') || !strchr (safe_chars, *s))
385 /* prepare a file name to survive the shell's quoting rules.
386 * From the Unix programming FAQ by way of Liviu.
389 /* FIXME: API is very wrong */
390 ssize_t mutt_quote_filename(char *d, ssize_t l, const char *s)
399 /* leave some space for the trailing characters. */
404 for (i = 0; j < l && s[i]; i++) {
405 if (s[i] == '\'' || s[i] == '`') {