more work in the lib-mime. begin to "rewr^H^Had" the code in rfc2231.c
[apps/madmutt.git] / lib-lib / file.c
1 /*
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.
6  *
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.
11  *
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,
15  *  MA 02110-1301, USA.
16  *
17  *  Copyright © 2006 Pierre Habouzit
18  */
19 /*
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>
23  *
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.
27  */
28
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <fcntl.h>
32 #include <errno.h>
33 #include <unistd.h>
34 #include <limits.h>
35
36 #include "macros.h"
37 #include "mem.h"
38 #include "str.h"
39 #include "file.h"
40
41 #include "../lib/debug.h"
42 #include "../lib/str.h"
43
44 #ifndef O_NOFOLLOW
45 #  define O_NOFOLLOW  0
46 #endif
47
48 /* FIXME: ugly to the max */
49 extern short Umask;
50
51 static int compare_stat(struct stat *osb, struct stat *nsb)
52 {
53     if (osb->st_dev  != nsb->st_dev || osb->st_ino != nsb->st_ino
54     || osb->st_rdev != nsb->st_rdev)
55     {
56         return -1;
57     }
58
59     return 0;
60 }
61
62 /****************************************************************************/
63 /* fd ops                                                                   */
64 /****************************************************************************/
65
66 int safe_open(const char *path, int flags)
67 {
68     struct stat osb, nsb;
69     int fd;
70
71     umask(Umask);
72
73     if ((fd = open(path, flags, 0666)) < 0)
74         return fd;
75
76     /* make sure the file is not symlink */
77     if (lstat (path, &osb) < 0 || fstat(fd, &nsb) < 0
78     ||  compare_stat(&osb, &nsb) == -1)
79     {
80         debug_print(1, ("%s is a symlink!\n", path));
81         close(fd);
82         return -1;
83     }
84
85     return (fd);
86 }
87
88
89 int safe_symlink(const char *oldpath, const char *newpath)
90 {
91     struct stat osb, nsb;
92
93     if (!oldpath || !newpath)
94         return -1;
95
96     if (unlink(newpath) < 0 && errno != ENOENT)
97         return -1;
98
99     if (oldpath[0] == '/') {
100         if (symlink(oldpath, newpath) < 0)
101             return -1;
102     } else {
103         char abs_oldpath[_POSIX_PATH_MAX];
104         ssize_t len;
105
106         if (!getcwd(abs_oldpath, sizeof(abs_oldpath)))
107             return -1;
108
109         len = m_strcat(abs_oldpath, sizeof(abs_oldpath), "/");
110         if (len >= sizeof(abs_oldpath))
111             return -1;
112
113         len += m_strcpy(abs_oldpath + len, sizeof(abs_oldpath) - len, oldpath);
114         if (len >= sizeof(abs_oldpath))
115             return -1;
116
117         if (symlink (abs_oldpath, newpath) < 0)
118             return -1;
119     }
120
121     if (stat(oldpath, &osb) < 0
122     ||  stat(newpath, &nsb) < 0
123     ||  compare_stat(&osb, &nsb) < 0)
124     {
125         unlink (newpath);
126         return -1;
127     }
128
129     return 0;
130 }
131
132 /*
133  * This function is supposed to do nfs-safe renaming of files.
134  *
135  * Warning: We don't check whether src and target are equal.
136  */
137 int safe_rename(const char *src, const char *target)
138 {
139     struct stat ssb, tsb;
140
141     if (!src || !target)
142         return -1;
143
144     if (link(src, target) != 0) {
145         /*
146          * Coda does not allow cross-directory links, but tells
147          * us it's a cross-filesystem linking attempt.
148          *
149          * However, the Coda rename call is allegedly safe to use.
150          *
151          * With other file systems, rename should just fail when 
152          * the files reside on different file systems, so it's safe
153          * to try it here.
154          *
155          */
156
157         if (errno == EXDEV)
158             return rename(src, target);
159
160         return -1;
161     }
162
163     /* Stat both links and check if they are equal. */
164
165     if (stat(src, &ssb) < 0 || stat(target, &tsb) < 0) {
166         return -1;
167     }
168
169     /* 
170      * pretend that the link failed because the target file
171      * did already exist.
172      */
173
174     if (compare_stat(&ssb, &tsb) == -1) {
175         errno = EEXIST;
176         return -1;
177     }
178
179     /*
180      * Unlink the original link.  Should we really ignore the return
181      * value here? XXX
182      */
183
184     unlink(src);
185     return 0;
186 }
187
188 void mutt_unlink(const char *s)
189 {
190     struct stat sb, sb2;
191
192     if (lstat(s, &sb) == 0 && S_ISREG(sb.st_mode)) {
193         int fd;
194         FILE *f;
195
196         /* Defend against symlink attacks */
197
198         if ((fd = open(s, O_RDWR | O_NOFOLLOW)) < 0)
199             return;
200
201         if ((fstat(fd, &sb2) != 0) || !S_ISREG(sb2.st_mode)
202         || (sb.st_dev != sb2.st_dev) || (sb.st_ino != sb2.st_ino))
203         {
204             close (fd);
205             return;
206         }
207
208         if ((f = fdopen(fd, "r+"))) {
209             char buf[BUFSIZ];
210             unlink(s);
211
212             p_clear(buf, sizeof(buf));
213             while (sb.st_size > 0) {
214                 fwrite(buf, 1, MIN(sizeof(buf), sb.st_size), f);
215                 sb.st_size -= MIN(sizeof(buf), sb.st_size);
216             }
217             fclose (f);
218         }
219     }
220 }
221
222
223 /****************************************************************************/
224 /* FILE* ops                                                                */
225 /****************************************************************************/
226
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)
230 {
231     /* first set the current umask */
232     umask(Umask);
233
234     if (mode[0] == 'w') {
235         int fd;
236         int flags = O_CREAT | O_EXCL | O_NOFOLLOW;
237
238         flags |= (mode[1] == '+' ? O_RDWR : O_WRONLY);
239
240         if ((fd = safe_open(path, flags)) < 0)
241             return (NULL);
242
243         return fdopen (fd, mode);
244     }
245
246     return fopen(path, mode);
247 }
248
249 int safe_fclose(FILE **f)
250 {
251     int r = 0;
252
253     if (*f)
254         r = fclose (*f);
255     *f = NULL;
256     return r;
257 }
258
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.
263  */
264 char *mutt_read_line(char *s, size_t *size, FILE * fp, int *line)
265 {
266     size_t offset = 0;
267     char *ch;
268
269     if (!s) {
270         s = p_new(char, STRING);
271         *size = STRING;
272     }
273
274     for (;;) {
275         if (fgets(s + offset, *size - offset, fp) == NULL) {
276             p_delete(&s);
277             return NULL;
278         }
279         if ((ch = strchr(s + offset, '\n')) != NULL) {
280             (*line)++;
281             *ch = 0;
282             if (ch > s && *(ch - 1) == '\r')
283                 *--ch = 0;
284             if (ch == s || *(ch - 1) != '\\')
285                 return s;
286             offset = ch - s - 1;
287         } else {
288             int c;
289
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. */
295             if (c == EOF) {
296                 /* The last line of fp isn't \n terminated */
297                 (*line)++;
298                 return s;
299             } else {
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 */
303                 *size += STRING;
304                 p_realloc(&s, *size);
305             }
306         }
307     }
308 }
309
310 int mutt_copy_stream(FILE *fin, FILE *fout)
311 {
312     char buf[BUFSIZ];
313     ssize_t l;
314
315     while ((l = fread(buf, 1, sizeof (buf), fin)) > 0) {
316         if (fwrite(buf, 1, l, fout) != l)
317             return -1;
318     }
319
320     return 0;
321 }
322
323 int mutt_copy_bytes(FILE *in, FILE *out, size_t size)
324 {
325     char buf[BUFSIZ];
326
327     while (size > 0) {
328         ssize_t chunk = MIN(size, sizeof(buf));
329
330         if ((chunk = fread(buf, 1, chunk, in)) < 1)
331             break;
332         if (fwrite(buf, 1, chunk, out) != chunk) {
333             debug_print(1, ("fwrite() returned short byte count\n"));
334             return -1;
335         }
336         size -= chunk;
337     }
338
339     return 0;
340 }
341
342
343 /****************************************************************************/
344 /* ligben-like funcs                                                        */
345 /****************************************************************************/
346
347 const char *mutt_basename(const char *f)
348 {
349     const char *p = strrchr(f, '/');
350     return p ? p + 1 : f;
351 }
352
353
354 char *
355 mutt_concat_path(char *d, ssize_t n, const char *dir, const char *fname)
356 {
357     const char *fmt = "%s/%s";
358
359     if (!*fname || (*dir && dir[m_strlen(dir) - 1] == '/'))
360         fmt = "%s%s";
361
362     snprintf(d, n, fmt, dir, fname);
363     return d;
364 }
365
366
367 void mutt_sanitize_filename(char *s, short slash)
368 {
369     static char safe_chars[] =
370         "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+@{}._-:%/";
371
372     if (!s)
373         return;
374
375     for (; *s; s++) {
376         if ((slash && *s == '/') || !strchr (safe_chars, *s))
377             *s = '_';
378     }
379 }
380
381 /* prepare a file name to survive the shell's quoting rules.
382  * From the Unix programming FAQ by way of Liviu.
383  */
384
385 /* FIXME: API is very wrong */
386 ssize_t mutt_quote_filename(char *d, ssize_t l, const char *s)
387 {
388     size_t i, j = 0;
389
390     if (!s) {
391         *d = '\0';
392         return 0;
393     }
394
395     /* leave some space for the trailing characters. */
396     l -= 6;
397
398     d[j++] = '\'';
399
400     for (i = 0; j < l && s[i]; i++) {
401         if (s[i] == '\'' || s[i] == '`') {
402             d[j++] = '\'';
403             d[j++] = '\\';
404             d[j++] = s[i];
405             d[j++] = '\'';
406         } else {
407             d[j++] = s[i];
408         }
409     }
410
411     d[j++] = '\'';
412     d[j] = '\0';
413
414     return j;
415 }