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>
46 /* FIXME: ugly to the max */
49 static int compare_stat(struct stat *osb, struct stat *nsb)
51 if (osb->st_dev != nsb->st_dev || osb->st_ino != nsb->st_ino
52 || osb->st_rdev != nsb->st_rdev)
60 /****************************************************************************/
62 /****************************************************************************/
64 int safe_open(const char *path, int flags)
71 if ((fd = open(path, flags, 0666)) < 0)
74 /* make sure the file is not symlink */
75 if (lstat (path, &osb) < 0 || fstat(fd, &nsb) < 0
76 || compare_stat(&osb, &nsb) == -1)
78 debug_print(1, ("%s is a symlink!\n", path));
87 int safe_symlink(const char *oldpath, const char *newpath)
91 if (!oldpath || !newpath)
94 if (unlink(newpath) < 0 && errno != ENOENT)
97 if (oldpath[0] == '/') {
98 if (symlink(oldpath, newpath) < 0)
101 char abs_oldpath[_POSIX_PATH_MAX];
104 if (!getcwd(abs_oldpath, sizeof(abs_oldpath)))
107 len = m_strcat(abs_oldpath, sizeof(abs_oldpath), "/");
108 if (len >= ssizeof(abs_oldpath))
111 len += m_strcpy(abs_oldpath + len, sizeof(abs_oldpath) - len, oldpath);
112 if (len >= ssizeof(abs_oldpath))
115 if (symlink (abs_oldpath, newpath) < 0)
119 if (stat(oldpath, &osb) < 0
120 || stat(newpath, &nsb) < 0
121 || compare_stat(&osb, &nsb) < 0)
131 * This function is supposed to do nfs-safe renaming of files.
133 * Warning: We don't check whether src and target are equal.
135 int safe_rename(const char *src, const char *target)
137 struct stat ssb, tsb;
142 if (link(src, target) != 0) {
144 * Coda does not allow cross-directory links, but tells
145 * us it's a cross-filesystem linking attempt.
147 * However, the Coda rename call is allegedly safe to use.
149 * With other file systems, rename should just fail when
150 * the files reside on different file systems, so it's safe
156 return rename(src, target);
161 /* Stat both links and check if they are equal. */
163 if (stat(src, &ssb) < 0 || stat(target, &tsb) < 0) {
168 * pretend that the link failed because the target file
172 if (compare_stat(&ssb, &tsb) == -1) {
178 * Unlink the original link. Should we really ignore the return
186 void mutt_unlink(const char *s)
190 if (lstat(s, &sb) == 0 && S_ISREG(sb.st_mode)) {
194 /* Defend against symlink attacks */
196 if ((fd = open(s, O_RDWR | O_NOFOLLOW)) < 0)
199 if ((fstat(fd, &sb2) != 0) || !S_ISREG(sb2.st_mode)
200 || (sb.st_dev != sb2.st_dev) || (sb.st_ino != sb2.st_ino))
206 if ((f = fdopen(fd, "r+"))) {
210 p_clear(buf, countof(buf));
211 while (sb.st_size > 0) {
212 fwrite(buf, 1, MIN(ssizeof(buf), sb.st_size), f);
213 sb.st_size -= MIN(ssizeof(buf), sb.st_size);
221 /****************************************************************************/
223 /****************************************************************************/
225 /* when opening files for writing, make sure the file doesn't already exist
226 to avoid race conditions. */
227 FILE *safe_fopen(const char *path, const char *mode)
229 /* first set the current umask */
232 if (mode[0] == 'w') {
234 int flags = O_CREAT | O_EXCL | O_NOFOLLOW;
236 flags |= (mode[1] == '+' ? O_RDWR : O_WRONLY);
238 if ((fd = safe_open(path, flags)) < 0)
241 return fdopen (fd, mode);
244 return fopen(path, mode);
247 int safe_fclose(FILE **f)
257 /* Read a line from ``fp'' into the dynamically allocated ``s'',
258 * increasing ``s'' if necessary. The ending "\n" or "\r\n" is removed.
259 * If a line ends with "\", this char and the linefeed is removed,
260 * and the next line is read too.
262 char *mutt_read_line(char *s, size_t *size, FILE * fp, int *line)
268 s = p_new(char, STRING);
273 if (fgets(s + offset, *size - offset, fp) == NULL) {
277 if ((ch = strchr(s + offset, '\n')) != NULL) {
280 if (ch > s && *(ch - 1) == '\r')
282 if (ch == s || *(ch - 1) != '\\')
288 c = getc (fp); /* This is kind of a hack. We want to know if the
289 char at the current point in the input stream is EOF.
290 feof() will only tell us if we've already hit EOF, not
291 if the next character is EOF. So, we need to read in
292 the next character and manually check if it is EOF. */
294 /* The last line of fp isn't \n terminated */
298 ungetc (c, fp); /* undo our dammage */
299 /* There wasn't room for the line -- increase ``s'' */
300 offset = *size - 1; /* overwrite the terminating 0 */
302 p_realloc(&s, *size);
308 int mutt_copy_stream(FILE *fin, FILE *fout)
313 while ((l = fread(buf, 1, sizeof (buf), fin)) > 0) {
314 if (fwrite(buf, 1, l, fout) != l)
321 int mutt_copy_bytes(FILE *in, FILE *out, size_t size)
326 size_t chunk = MIN(size, sizeof(buf));
328 if ((chunk = fread(buf, 1, chunk, in)) < 1)
330 if (fwrite(buf, 1, chunk, out) != chunk) {
331 debug_print(1, ("fwrite() returned short byte count\n"));
341 /****************************************************************************/
342 /* ligben-like funcs */
343 /****************************************************************************/
345 const char *mutt_basename(const char *f)
347 const char *p = strrchr(f, '/');
348 return p ? p + 1 : f;
353 mutt_concat_path(char *d, ssize_t n, const char *dir, const char *fname)
357 pos = m_strcpy(d, n, dir);
361 if (pos && d[pos - 1] != '/')
364 m_strcpy(d + pos, n - pos, fname);
369 void mutt_sanitize_filename(char *s, short slash)
371 static char safe_chars[] =
372 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+@{}._-:%/";
378 if ((slash && *s == '/') || !strchr (safe_chars, *s))
383 /* prepare a file name to survive the shell's quoting rules.
384 * From the Unix programming FAQ by way of Liviu.
387 /* FIXME: API is very wrong */
388 ssize_t mutt_quote_filename(char *d, ssize_t l, const char *s)
397 /* leave some space for the trailing characters. */
402 for (i = 0; j < l && s[i]; i++) {
403 if (s[i] == '\'' || s[i] == '`') {