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