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