begin to rework mailcap parsing a "bit".
[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 #ifndef O_NOFOLLOW
42 #  define O_NOFOLLOW  0
43 #endif
44
45 /* FIXME: ugly to the max */
46 extern short Umask;
47
48 static int compare_stat(struct stat *osb, struct stat *nsb)
49 {
50     if (osb->st_dev  != nsb->st_dev || osb->st_ino != nsb->st_ino
51     || osb->st_rdev != nsb->st_rdev)
52     {
53         return -1;
54     }
55
56     return 0;
57 }
58
59 /****************************************************************************/
60 /* fd ops                                                                   */
61 /****************************************************************************/
62
63 int safe_open(const char *path, int flags)
64 {
65     struct stat osb, nsb;
66     int fd;
67
68     umask(Umask);
69
70     if ((fd = open(path, flags, 0666)) < 0)
71         return fd;
72
73     /* make sure the file is not symlink */
74     if (lstat (path, &osb) < 0 || fstat(fd, &nsb) < 0
75     ||  compare_stat(&osb, &nsb) == -1)
76     {
77         close(fd);
78         return -1;
79     }
80
81     return (fd);
82 }
83
84
85 int safe_symlink(const char *oldpath, const char *newpath)
86 {
87     struct stat osb, nsb;
88
89     if (!oldpath || !newpath)
90         return -1;
91
92     if (unlink(newpath) < 0 && errno != ENOENT)
93         return -1;
94
95     if (oldpath[0] == '/') {
96         if (symlink(oldpath, newpath) < 0)
97             return -1;
98     } else {
99         char abs_oldpath[_POSIX_PATH_MAX];
100         ssize_t len;
101
102         if (!getcwd(abs_oldpath, sizeof(abs_oldpath)))
103             return -1;
104
105         len = m_strcat(abs_oldpath, sizeof(abs_oldpath), "/");
106         if (len >= ssizeof(abs_oldpath))
107             return -1;
108
109         len += m_strcpy(abs_oldpath + len, sizeof(abs_oldpath) - len, oldpath);
110         if (len >= ssizeof(abs_oldpath))
111             return -1;
112
113         if (symlink (abs_oldpath, newpath) < 0)
114             return -1;
115     }
116
117     if (stat(oldpath, &osb) < 0
118     ||  stat(newpath, &nsb) < 0
119     ||  compare_stat(&osb, &nsb) < 0)
120     {
121         unlink (newpath);
122         return -1;
123     }
124
125     return 0;
126 }
127
128 /*
129  * This function is supposed to do nfs-safe renaming of files.
130  *
131  * Warning: We don't check whether src and target are equal.
132  */
133 int safe_rename(const char *src, const char *target)
134 {
135     struct stat ssb, tsb;
136
137     if (!src || !target)
138         return -1;
139
140     if (link(src, target) != 0) {
141         /*
142          * Coda does not allow cross-directory links, but tells
143          * us it's a cross-filesystem linking attempt.
144          *
145          * However, the Coda rename call is allegedly safe to use.
146          *
147          * With other file systems, rename should just fail when 
148          * the files reside on different file systems, so it's safe
149          * to try it here.
150          *
151          */
152
153         if (errno == EXDEV)
154             return rename(src, target);
155
156         return -1;
157     }
158
159     /* Stat both links and check if they are equal. */
160
161     if (stat(src, &ssb) < 0 || stat(target, &tsb) < 0) {
162         return -1;
163     }
164
165     /* 
166      * pretend that the link failed because the target file
167      * did already exist.
168      */
169
170     if (compare_stat(&ssb, &tsb) == -1) {
171         errno = EEXIST;
172         return -1;
173     }
174
175     /*
176      * Unlink the original link.  Should we really ignore the return
177      * value here? XXX
178      */
179
180     unlink(src);
181     return 0;
182 }
183
184 void mutt_unlink(const char *s)
185 {
186     struct stat sb, sb2;
187
188     if (lstat(s, &sb) == 0 && S_ISREG(sb.st_mode)) {
189         int fd;
190         FILE *f;
191
192         /* Defend against symlink attacks */
193
194         if ((fd = open(s, O_RDWR | O_NOFOLLOW)) < 0)
195             return;
196
197         if ((fstat(fd, &sb2) != 0) || !S_ISREG(sb2.st_mode)
198         || (sb.st_dev != sb2.st_dev) || (sb.st_ino != sb2.st_ino))
199         {
200             close (fd);
201             return;
202         }
203
204         if ((f = fdopen(fd, "r+"))) {
205             char buf[BUFSIZ];
206             unlink(s);
207
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);
212             }
213             fclose (f);
214         }
215     }
216 }
217
218
219 /****************************************************************************/
220 /* FILE* ops                                                                */
221 /****************************************************************************/
222
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)
226 {
227     /* first set the current umask */
228     umask(Umask);
229
230     if (mode[0] == 'w') {
231         int fd;
232         int flags = O_CREAT | O_EXCL | O_NOFOLLOW;
233
234         flags |= (mode[1] == '+' ? O_RDWR : O_WRONLY);
235
236         if ((fd = safe_open(path, flags)) < 0)
237             return (NULL);
238
239         return fdopen (fd, mode);
240     }
241
242     return fopen(path, mode);
243 }
244
245 int safe_fclose(FILE **f)
246 {
247     int r = 0;
248
249     if (*f)
250         r = fclose (*f);
251     *f = NULL;
252     return r;
253 }
254
255 /* If rfc1524_expand_command() is used on a recv'd message, then
256  * the filename doesn't exist yet, but if its used while sending a message,
257  * then we need to rename the existing file.
258  *
259  * This function returns 0 on successful move, 1 on old file doesn't exist,
260  * 2 on new file already exists, and 3 on other failure.
261  */
262
263 /* note on access(2) use: No dangling symlink problems here due to
264  * safe_fopen().
265  */
266 int mutt_rename_file(char *oldfile, char *newfile)
267 {
268     FILE *ofp, *nfp;
269
270     if (access(oldfile, F_OK) != 0)
271         return 1;
272     if (access(newfile, F_OK) == 0)
273         return 2;
274
275     ofp = fopen(oldfile, "r");
276     if (!ofp)
277         return 3;
278
279     nfp = safe_fopen(newfile, "w");
280     if (!nfp) {
281         fclose (ofp);
282         return 3;
283     }
284
285     mutt_copy_stream(ofp, nfp);
286     fclose(nfp);
287     fclose(ofp);
288     mutt_unlink(oldfile);
289     return 0;
290 }
291
292 /* Read a line from ``fp'' into the dynamically allocated ``s'',
293  * increasing ``s'' if necessary. The ending "\n" or "\r\n" is removed.
294  * If a line ends with "\", this char and the linefeed is removed,
295  * and the next line is read too.
296  */
297 char *mutt_read_line(char *s, ssize_t *size, FILE * fp, int *line)
298 {
299     ssize_t offset = 0;
300     char *ch;
301
302     if (!s) {
303         s = p_new(char, STRING);
304         *size = STRING;
305     }
306
307     for (;;) {
308         if (fgets(s + offset, *size - offset, fp) == NULL) {
309             p_delete(&s);
310             return NULL;
311         }
312         if ((ch = strchr(s + offset, '\n')) != NULL) {
313             (*line)++;
314             *ch = 0;
315             if (ch > s && *(ch - 1) == '\r')
316                 *--ch = 0;
317             if (ch == s || *(ch - 1) != '\\')
318                 return s;
319             offset = ch - s - 1;
320         } else {
321             int c;
322
323             c = getc (fp);            /* This is kind of a hack. We want to know if the
324                                          char at the current point in the input stream is EOF.
325                                          feof() will only tell us if we've already hit EOF, not
326                                          if the next character is EOF. So, we need to read in
327                                          the next character and manually check if it is EOF. */
328             if (c == EOF) {
329                 /* The last line of fp isn't \n terminated */
330                 (*line)++;
331                 return s;
332             } else {
333                 ungetc (c, fp);         /* undo our dammage */
334                 /* There wasn't room for the line -- increase ``s'' */
335                 offset = *size - 1;     /* overwrite the terminating 0 */
336                 *size += STRING;
337                 p_realloc(&s, *size);
338             }
339         }
340     }
341 }
342
343 int mutt_copy_stream(FILE *fin, FILE *fout)
344 {
345     char buf[BUFSIZ];
346     size_t l;
347
348     while ((l = fread(buf, 1, sizeof(buf), fin)) > 0) {
349         if (fwrite(buf, 1, l, fout) != l)
350             return -1;
351     }
352
353     return 0;
354 }
355
356 int mutt_copy_bytes(FILE *in, FILE *out, ssize_t size)
357 {
358     char buf[BUFSIZ];
359
360     while (size > 0) {
361         size_t chunk = MIN(size, ssizeof(buf));
362
363         if ((chunk = fread(buf, 1, chunk, in)) < 1)
364             break;
365         if (fwrite(buf, 1, chunk, out) != chunk) {
366             return -1;
367         }
368         size -= chunk;
369     }
370
371     return 0;
372 }
373
374
375 /****************************************************************************/
376 /* ligben-like funcs                                                        */
377 /****************************************************************************/
378
379 const char *mutt_basename(const char *f)
380 {
381     const char *p = strrchr(f, '/');
382     return p ? p + 1 : f;
383 }
384
385
386 char *
387 mutt_concat_path(char *d, ssize_t n, const char *dir, const char *fname)
388 {
389     int pos;
390
391     pos = m_strcpy(d, n, dir);
392     if (pos >= n - 1)
393         return d;
394
395     if (pos && d[pos - 1] != '/')
396         d[pos++] = '/';
397
398     m_strcpy(d + pos, n - pos, fname);
399     return d;
400 }
401
402
403 void mutt_sanitize_filename(char *s, short slash)
404 {
405     static char safe_chars[] =
406         "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+@{}._-:%/";
407
408     if (!s)
409         return;
410
411     for (; *s; s++) {
412         if ((slash && *s == '/') || !strchr (safe_chars, *s))
413             *s = '_';
414     }
415 }
416
417 /* prepare a file name to survive the shell's quoting rules.
418  * From the Unix programming FAQ by way of Liviu.
419  */
420
421 /* FIXME: API is very wrong */
422 ssize_t mutt_quote_filename(char *d, ssize_t l, const char *s)
423 {
424     ssize_t i, j = 0;
425
426     if (!s) {
427         *d = '\0';
428         return 0;
429     }
430
431     /* leave some space for the trailing characters. */
432     l -= 6;
433
434     d[j++] = '\'';
435
436     for (i = 0; j < l && s[i]; i++) {
437         if (s[i] == '\'' || s[i] == '`') {
438             d[j++] = '\'';
439             d[j++] = '\\';
440             d[j++] = s[i];
441             d[j++] = '\'';
442         } else {
443             d[j++] = s[i];
444         }
445     }
446
447     d[j++] = '\'';
448     d[j] = '\0';
449
450     return j;
451 }