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