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>
45 /* FIXME: ugly to the max */
48 static int compare_stat(struct stat *osb, struct stat *nsb)
50 if (osb->st_dev != nsb->st_dev || osb->st_ino != nsb->st_ino
51 || osb->st_rdev != nsb->st_rdev)
59 /****************************************************************************/
61 /****************************************************************************/
63 int safe_open(const char *path, int flags)
70 if ((fd = open(path, flags, 0666)) < 0)
73 /* make sure the file is not symlink */
74 if (lstat (path, &osb) < 0 || fstat(fd, &nsb) < 0
75 || compare_stat(&osb, &nsb) == -1)
85 int safe_symlink(const char *oldpath, const char *newpath)
89 if (!oldpath || !newpath)
92 if (unlink(newpath) < 0 && errno != ENOENT)
95 if (oldpath[0] == '/') {
96 if (symlink(oldpath, newpath) < 0)
99 char abs_oldpath[_POSIX_PATH_MAX];
102 if (!getcwd(abs_oldpath, sizeof(abs_oldpath)))
105 len = m_strcat(abs_oldpath, sizeof(abs_oldpath), "/");
106 if (len >= ssizeof(abs_oldpath))
109 len += m_strcpy(abs_oldpath + len, sizeof(abs_oldpath) - len, oldpath);
110 if (len >= ssizeof(abs_oldpath))
113 if (symlink (abs_oldpath, newpath) < 0)
117 if (stat(oldpath, &osb) < 0
118 || stat(newpath, &nsb) < 0
119 || compare_stat(&osb, &nsb) < 0)
129 * This function is supposed to do nfs-safe renaming of files.
131 * Warning: We don't check whether src and target are equal.
133 int safe_rename(const char *src, const char *target)
135 struct stat ssb, tsb;
140 if (link(src, target) != 0) {
142 * Coda does not allow cross-directory links, but tells
143 * us it's a cross-filesystem linking attempt.
145 * However, the Coda rename call is allegedly safe to use.
147 * With other file systems, rename should just fail when
148 * the files reside on different file systems, so it's safe
154 return rename(src, target);
159 /* Stat both links and check if they are equal. */
161 if (stat(src, &ssb) < 0 || stat(target, &tsb) < 0) {
166 * pretend that the link failed because the target file
170 if (compare_stat(&ssb, &tsb) == -1) {
176 * Unlink the original link. Should we really ignore the return
184 void mutt_unlink(const char *s)
188 if (lstat(s, &sb) == 0 && S_ISREG(sb.st_mode)) {
192 /* Defend against symlink attacks */
194 if ((fd = open(s, O_RDWR | O_NOFOLLOW)) < 0)
197 if ((fstat(fd, &sb2) != 0) || !S_ISREG(sb2.st_mode)
198 || (sb.st_dev != sb2.st_dev) || (sb.st_ino != sb2.st_ino))
204 if ((f = fdopen(fd, "r+"))) {
208 p_clear(buf, countof(buf));
209 while (sb.st_size > 0) {
210 fwrite(buf, 1, MIN(ssizeof(buf), sb.st_size), f);
211 sb.st_size -= MIN(ssizeof(buf), sb.st_size);
219 /****************************************************************************/
221 /****************************************************************************/
223 /* when opening files for writing, make sure the file doesn't already exist
224 to avoid race conditions. */
225 FILE *safe_fopen(const char *path, const char *mode)
227 /* first set the current umask */
230 if (mode[0] == 'w') {
232 int flags = O_CREAT | O_EXCL | O_NOFOLLOW;
234 flags |= (mode[1] == '+' ? O_RDWR : O_WRONLY);
236 if ((fd = safe_open(path, flags)) < 0)
239 return fdopen (fd, mode);
242 return fopen(path, mode);
245 int safe_fclose(FILE **f)
255 /* Read a line from ``fp'' into the dynamically allocated ``s'',
256 * increasing ``s'' if necessary. The ending "\n" or "\r\n" is removed.
257 * If a line ends with "\", this char and the linefeed is removed,
258 * and the next line is read too.
260 char *mutt_read_line(char *s, ssize_t *size, FILE * fp, int *line)
266 s = p_new(char, STRING);
271 if (fgets(s + offset, *size - offset, fp) == NULL) {
275 if ((ch = strchr(s + offset, '\n')) != NULL) {
278 if (ch > s && *(ch - 1) == '\r')
280 if (ch == s || *(ch - 1) != '\\')
286 c = getc (fp); /* This is kind of a hack. We want to know if the
287 char at the current point in the input stream is EOF.
288 feof() will only tell us if we've already hit EOF, not
289 if the next character is EOF. So, we need to read in
290 the next character and manually check if it is EOF. */
292 /* The last line of fp isn't \n terminated */
296 ungetc (c, fp); /* undo our dammage */
297 /* There wasn't room for the line -- increase ``s'' */
298 offset = *size - 1; /* overwrite the terminating 0 */
300 p_realloc(&s, *size);
306 int mutt_copy_stream(FILE *fin, FILE *fout)
311 while ((l = fread(buf, 1, sizeof(buf), fin)) > 0) {
312 if (fwrite(buf, 1, l, fout) != l)
319 int mutt_copy_bytes(FILE *in, FILE *out, ssize_t size)
324 size_t chunk = MIN(size, ssizeof(buf));
326 if ((chunk = fread(buf, 1, chunk, in)) < 1)
328 if (fwrite(buf, 1, chunk, out) != chunk) {
338 /****************************************************************************/
339 /* ligben-like funcs */
340 /****************************************************************************/
342 const char *mutt_basename(const char *f)
344 const char *p = strrchr(f, '/');
345 return p ? p + 1 : f;
350 mutt_concat_path(char *d, ssize_t n, const char *dir, const char *fname)
354 pos = m_strcpy(d, n, dir);
358 if (pos && d[pos - 1] != '/')
361 m_strcpy(d + pos, n - pos, fname);
366 void mutt_sanitize_filename(char *s, short slash)
368 static char safe_chars[] =
369 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+@{}._-:%/";
375 if ((slash && *s == '/') || !strchr (safe_chars, *s))
380 /* prepare a file name to survive the shell's quoting rules.
381 * From the Unix programming FAQ by way of Liviu.
384 /* FIXME: API is very wrong */
385 ssize_t mutt_quote_filename(char *d, ssize_t l, const char *s)
394 /* leave some space for the trailing characters. */
399 for (i = 0; j < l && s[i]; i++) {
400 if (s[i] == '\'' || s[i] == '`') {