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