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