2b15053f898eb25871cd75456fa0fdea38ea15ff
[apps/madmutt.git] / lib.c
1 /*
2  * Copyright notice from original mutt:
3  * Copyright (C) 1996-2000 Michael R. Elkins <me@mutt.org>
4  * Copyright (C) 1999-2000 Thomas Roessler <roessler@does-not-exist.org>
5  *
6  * This file is part of mutt-ng, see http://www.muttng.org/.
7  * It's licensed under the GNU General Public License,
8  * please see the file GPL in the top level source directory.
9  */
10
11 /*
12  * This file used to contain some more functions, namely those
13  * which are now in muttlib.c.  They have been removed, so we have
14  * some of our "standard" functions in external programs, too.
15  */
16
17 #if HAVE_CONFIG_H
18 # include "config.h"
19 #endif
20
21 #include <string.h>
22 #include <ctype.h>
23 #include <unistd.h>
24 #include <stdlib.h>
25 #include <sys/wait.h>
26 #include <errno.h>
27 #include <sys/stat.h>
28 #include <fcntl.h>
29 #include <pwd.h>
30
31 #include "lib.h"
32
33 extern short Umask;
34
35 void mutt_nocurses_error (const char *fmt, ...)
36 {
37   va_list ap;
38
39   va_start (ap, fmt);
40   vfprintf (stderr, fmt, ap);
41   va_end (ap);
42   fputc ('\n', stderr);
43 }
44
45 int safe_fclose (FILE ** f)
46 {
47   int r = 0;
48
49   if (*f)
50     r = fclose (*f);
51
52   *f = NULL;
53   return r;
54 }
55
56 char *safe_strdup (const char *s)
57 {
58   char *p;
59   size_t l;
60
61   if (!s || !*s)
62     return 0;
63   l = mutt_strlen (s) + 1;
64   p = (char *) safe_malloc (l);
65   memcpy (p, s, l);
66   return (p);
67 }
68
69 char *safe_strcat (char *d, size_t l, const char *s)
70 {
71   char *p = d;
72
73   if (!l)
74     return d;
75
76   l--;                          /* Space for the trailing '\0'. */
77
78   for (; *d && l; l--)
79     d++;
80   for (; *s && l; l--)
81     *d++ = *s++;
82
83   *d = '\0';
84
85   return p;
86 }
87
88 char *safe_strncat (char *d, size_t l, const char *s, size_t sl)
89 {
90   char *p = d;
91
92   if (!l)
93     return d;
94
95   l--;                          /* Space for the trailing '\0'. */
96
97   for (; *d && l; l--)
98     d++;
99   for (; *s && l && sl; l--, sl--)
100     *d++ = *s++;
101
102   *d = '\0';
103
104   return p;
105 }
106
107
108 void mutt_str_replace (char **p, const char *s)
109 {
110   FREE (p);
111   *p = safe_strdup (s);
112 }
113
114 void mutt_str_adjust (char **p)
115 {
116   if (!p || !*p)
117     return;
118   safe_realloc (p, mutt_strlen (*p) + 1);
119 }
120
121 /* convert all characters in the string to lowercase */
122 char *mutt_strlower (char *s)
123 {
124   char *p = s;
125
126   while (*p) {
127     *p = tolower ((unsigned char) *p);
128     p++;
129   }
130
131   return (s);
132 }
133
134 void mutt_unlink (const char *s)
135 {
136   int fd;
137   int flags;
138   FILE *f;
139   struct stat sb, sb2;
140   char buf[2048];
141
142   /* Defend against symlink attacks */
143
144 #ifdef O_NOFOLLOW
145   flags = O_RDWR | O_NOFOLLOW;
146 #else
147   flags = O_RDWR;
148 #endif
149
150   if (lstat (s, &sb) == 0 && S_ISREG (sb.st_mode)) {
151     if ((fd = open (s, flags)) < 0)
152       return;
153
154     if ((fstat (fd, &sb2) != 0) || !S_ISREG (sb2.st_mode)
155         || (sb.st_dev != sb2.st_dev) || (sb.st_ino != sb2.st_ino)) {
156       close (fd);
157       return;
158     }
159
160     if ((f = fdopen (fd, "r+"))) {
161       unlink (s);
162       memset (buf, 0, sizeof (buf));
163       while (sb.st_size > 0) {
164         fwrite (buf, 1, MIN (sizeof (buf), sb.st_size), f);
165         sb.st_size -= MIN (sizeof (buf), sb.st_size);
166       }
167       fclose (f);
168     }
169   }
170 }
171
172 int mutt_copy_bytes (FILE * in, FILE * out, size_t size)
173 {
174   char buf[2048];
175   size_t chunk;
176
177   while (size > 0) {
178     chunk = (size > sizeof (buf)) ? sizeof (buf) : size;
179     if ((chunk = fread (buf, 1, chunk, in)) < 1)
180       break;
181     if (fwrite (buf, 1, chunk, out) != chunk) {
182       /* dprint (1, (debugfile, "mutt_copy_bytes(): fwrite() returned short byte count\n")); */
183       return (-1);
184     }
185     size -= chunk;
186   }
187
188   return 0;
189 }
190
191 int mutt_copy_stream (FILE * fin, FILE * fout)
192 {
193   size_t l;
194   char buf[LONG_STRING];
195
196   while ((l = fread (buf, 1, sizeof (buf), fin)) > 0) {
197     if (fwrite (buf, 1, l, fout) != l)
198       return (-1);
199   }
200
201   return 0;
202 }
203
204 static int compare_stat (struct stat *osb, struct stat *nsb)
205 {
206   if (osb->st_dev != nsb->st_dev || osb->st_ino != nsb->st_ino ||
207       osb->st_rdev != nsb->st_rdev) {
208     return -1;
209   }
210
211   return 0;
212 }
213
214 int safe_symlink (const char *oldpath, const char *newpath)
215 {
216   struct stat osb, nsb;
217
218   if (!oldpath || !newpath)
219     return -1;
220
221   if (unlink (newpath) == -1 && errno != ENOENT)
222     return -1;
223
224   if (oldpath[0] == '/') {
225     if (symlink (oldpath, newpath) == -1)
226       return -1;
227   }
228   else {
229     char abs_oldpath[_POSIX_PATH_MAX];
230
231     if ((getcwd (abs_oldpath, sizeof abs_oldpath) == NULL) ||
232         (mutt_strlen (abs_oldpath) + 1 + mutt_strlen (oldpath) + 1 >
233          sizeof abs_oldpath))
234       return -1;
235
236     strcat (abs_oldpath, "/");  /* __STRCAT_CHECKED__ */
237     strcat (abs_oldpath, oldpath);      /* __STRCAT_CHECKED__ */
238     if (symlink (abs_oldpath, newpath) == -1)
239       return -1;
240   }
241
242   if (stat (oldpath, &osb) == -1 || stat (newpath, &nsb) == -1
243       || compare_stat (&osb, &nsb) == -1) {
244     unlink (newpath);
245     return -1;
246   }
247
248   return 0;
249 }
250
251 /* 
252  * This function is supposed to do nfs-safe renaming of files.
253  * 
254  * Warning: We don't check whether src and target are equal.
255  */
256
257 int safe_rename (const char *src, const char *target)
258 {
259   struct stat ssb, tsb;
260
261   if (!src || !target)
262     return -1;
263
264   if (link (src, target) != 0) {
265
266     /*
267      * Coda does not allow cross-directory links, but tells
268      * us it's a cross-filesystem linking attempt.
269      * 
270      * However, the Coda rename call is allegedly safe to use.
271      * 
272      * With other file systems, rename should just fail when 
273      * the files reside on different file systems, so it's safe
274      * to try it here.
275      *
276      */
277
278     if (errno == EXDEV)
279       return rename (src, target);
280
281     return -1;
282   }
283
284   /*
285    * Stat both links and check if they are equal.
286    */
287
288   if (stat (src, &ssb) == -1) {
289     return -1;
290   }
291
292   if (stat (target, &tsb) == -1) {
293     return -1;
294   }
295
296   /* 
297    * pretend that the link failed because the target file
298    * did already exist.
299    */
300
301   if (compare_stat (&ssb, &tsb) == -1) {
302     errno = EEXIST;
303     return -1;
304   }
305
306   /*
307    * Unlink the original link.  Should we really ignore the return
308    * value here? XXX
309    */
310
311   unlink (src);
312
313   return 0;
314 }
315
316 int safe_open (const char *path, int flags)
317 {
318   struct stat osb, nsb;
319   int fd;
320
321   umask (Umask);
322   if ((fd = open (path, flags, 0666)) < 0)
323     return fd;
324
325   /* make sure the file is not symlink */
326   if (lstat (path, &osb) < 0 || fstat (fd, &nsb) < 0 ||
327       compare_stat (&osb, &nsb) == -1) {
328 /*    dprint (1, (debugfile, "safe_open(): %s is a symlink!\n", path)); */
329     close (fd);
330     return (-1);
331   }
332
333   return (fd);
334 }
335
336 /* when opening files for writing, make sure the file doesn't already exist
337  * to avoid race conditions.
338  */
339 FILE *safe_fopen (const char *path, const char *mode)
340 {
341   /* first set the current umask */
342   umask (Umask);
343   if (mode[0] == 'w') {
344     int fd;
345     int flags = O_CREAT | O_EXCL;
346
347 #ifdef O_NOFOLLOW
348     flags |= O_NOFOLLOW;
349 #endif
350
351     if (mode[1] == '+')
352       flags |= O_RDWR;
353     else
354       flags |= O_WRONLY;
355
356     if ((fd = safe_open (path, flags)) < 0)
357       return (NULL);
358
359     return (fdopen (fd, mode));
360   }
361   else
362     return (fopen (path, mode));
363 }
364
365 static char safe_chars[] =
366   "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+@{}._-:%/";
367
368 void mutt_sanitize_filename (char *f, short slash)
369 {
370   if (!f)
371     return;
372
373   for (; *f; f++) {
374     if ((slash && *f == '/') || !strchr (safe_chars, *f))
375       *f = '_';
376   }
377 }
378
379 /* these characters must be escaped in regular expressions */
380
381 static char rx_special_chars[] = "^.[$()|*+?{\\";
382
383 int mutt_rx_sanitize_string (char *dest, size_t destlen, const char *src)
384 {
385   while (*src && --destlen > 2) {
386     if (strchr (rx_special_chars, *src)) {
387       *dest++ = '\\';
388       destlen--;
389     }
390     *dest++ = *src++;
391   }
392
393   *dest = '\0';
394
395   if (*src)
396     return -1;
397   else
398     return 0;
399 }
400
401 /* Read a line from ``fp'' into the dynamically allocated ``s'',
402  * increasing ``s'' if necessary. The ending "\n" or "\r\n" is removed.
403  * If a line ends with "\", this char and the linefeed is removed,
404  * and the next line is read too.
405  */
406 char *mutt_read_line (char *s, size_t * size, FILE * fp, int *line)
407 {
408   size_t offset = 0;
409   char *ch;
410
411   if (!s) {
412     s = safe_malloc (STRING);
413     *size = STRING;
414   }
415
416   FOREVER {
417     if (fgets (s + offset, *size - offset, fp) == NULL) {
418       FREE (&s);
419       return NULL;
420     }
421     if ((ch = strchr (s + offset, '\n')) != NULL) {
422       (*line)++;
423       *ch = 0;
424       if (ch > s && *(ch - 1) == '\r')
425         *--ch = 0;
426       if (ch == s || *(ch - 1) != '\\')
427         return s;
428       offset = ch - s - 1;
429     }
430     else {
431       int c;
432
433       c = getc (fp);            /* This is kind of a hack. We want to know if the
434                                    char at the current point in the input stream is EOF.
435                                    feof() will only tell us if we've already hit EOF, not
436                                    if the next character is EOF. So, we need to read in
437                                    the next character and manually check if it is EOF. */
438       if (c == EOF) {
439         /* The last line of fp isn't \n terminated */
440         (*line)++;
441         return s;
442       }
443       else {
444         ungetc (c, fp);         /* undo our dammage */
445         /* There wasn't room for the line -- increase ``s'' */
446         offset = *size - 1;     /* overwrite the terminating 0 */
447         *size += STRING;
448         safe_realloc (&s, *size);
449       }
450     }
451   }
452 }
453
454 char *mutt_substrcpy (char *dest, const char *beg, const char *end,
455                       size_t destlen)
456 {
457   size_t len;
458
459   len = end - beg;
460   if (len > destlen - 1)
461     len = destlen - 1;
462   memcpy (dest, beg, len);
463   dest[len] = 0;
464   return dest;
465 }
466
467 char *mutt_substrdup (const char *begin, const char *end)
468 {
469   size_t len;
470   char *p;
471
472   if (end)
473     len = end - begin;
474   else
475     len = mutt_strlen (begin);
476
477   p = safe_malloc (len + 1);
478   memcpy (p, begin, len);
479   p[len] = 0;
480   return p;
481 }
482
483 /* prepare a file name to survive the shell's quoting rules.
484  * From the Unix programming FAQ by way of Liviu.
485  */
486
487 size_t mutt_quote_filename (char *d, size_t l, const char *f)
488 {
489   size_t i, j = 0;
490
491   if (!f) {
492     *d = '\0';
493     return 0;
494   }
495
496   /* leave some space for the trailing characters. */
497   l -= 6;
498
499   d[j++] = '\'';
500
501   for (i = 0; j < l && f[i]; i++) {
502     if (f[i] == '\'' || f[i] == '`') {
503       d[j++] = '\'';
504       d[j++] = '\\';
505       d[j++] = f[i];
506       d[j++] = '\'';
507     }
508     else
509       d[j++] = f[i];
510   }
511
512   d[j++] = '\'';
513   d[j] = '\0';
514
515   return j;
516 }
517
518 /* NULL-pointer aware string comparison functions */
519
520 int mutt_strcmp (const char *a, const char *b)
521 {
522   return strcmp (NONULL (a), NONULL (b));
523 }
524
525 int mutt_strcasecmp (const char *a, const char *b)
526 {
527   return strcasecmp (NONULL (a), NONULL (b));
528 }
529
530 int mutt_strncmp (const char *a, const char *b, size_t l)
531 {
532   return strncmp (NONULL (a), NONULL (b), l);
533 }
534
535 int mutt_strncasecmp (const char *a, const char *b, size_t l)
536 {
537   return strncasecmp (NONULL (a), NONULL (b), l);
538 }
539
540 size_t mutt_strlen (const char *a)
541 {
542   return a ? strlen (a) : 0;
543 }
544
545 int mutt_strcoll (const char *a, const char *b)
546 {
547   return strcoll (NONULL (a), NONULL (b));
548 }
549
550 const char *mutt_stristr (const char *haystack, const char *needle)
551 {
552   const char *p, *q;
553
554   if (!haystack)
555     return NULL;
556   if (!needle)
557     return (haystack);
558
559   while (*(p = haystack)) {
560     for (q = needle;
561          *p && *q &&
562          tolower ((unsigned char) *p) == tolower ((unsigned char) *q);
563          p++, q++);
564     if (!*q)
565       return (haystack);
566     haystack++;
567   }
568   return NULL;
569 }
570
571 char *mutt_skip_whitespace (char *p)
572 {
573   SKIPWS (p);
574   return p;
575 }
576
577 void mutt_remove_trailing_ws (char *s)
578 {
579   char *p;
580
581   for (p = s + mutt_strlen (s) - 1; p >= s && ISSPACE (*p); p--)
582     *p = 0;
583 }
584
585 char *mutt_concat_path (char *d, const char *dir, const char *fname, size_t l)
586 {
587   const char *fmt = "%s/%s";
588
589   if (!*fname || (*dir && dir[mutt_strlen (dir) - 1] == '/'))
590     fmt = "%s%s";
591
592   snprintf (d, l, fmt, dir, fname);
593   return d;
594 }
595
596 const char *mutt_basename (const char *f)
597 {
598   const char *p = strrchr (f, '/');
599
600   if (p)
601     return p + 1;
602   else
603     return f;
604 }