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