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   umask(Umask);
367   if ((fd = open (path, flags, 0666)) < 0)
368     return fd;
369
370   /* make sure the file is not symlink */
371   if (lstat (path, &osb) < 0 || fstat (fd, &nsb) < 0 ||
372       compare_stat(&osb, &nsb) == -1)
373   {
374 /*    dprint (1, (debugfile, "safe_open(): %s is a symlink!\n", path)); */
375     close (fd);
376     return (-1);
377   }
378
379   return (fd);
380 }
381
382 /* when opening files for writing, make sure the file doesn't already exist
383  * to avoid race conditions.
384  */
385 FILE *safe_fopen (const char *path, const char *mode)
386 {
387   /* first set the current umask */
388   umask(Umask);
389   if (mode[0] == 'w')
390   {
391     int fd;
392     int flags = O_CREAT | O_EXCL;
393
394 #ifdef O_NOFOLLOW
395     flags |= O_NOFOLLOW;
396 #endif
397
398     if (mode[1] == '+')
399       flags |= O_RDWR;
400     else
401       flags |= O_WRONLY;
402
403     if ((fd = safe_open (path, flags)) < 0)
404       return (NULL);
405
406     return (fdopen (fd, mode));
407   }
408   else
409     return (fopen (path, mode));
410 }
411
412 static char safe_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+@{}._-:%/";
413
414 void mutt_sanitize_filename (char *f, short slash)
415 {
416   if (!f) return;
417
418   for (; *f; f++)
419   {
420     if ((slash && *f == '/') || !strchr (safe_chars, *f))
421       *f = '_';
422   }
423 }
424
425 /* these characters must be escaped in regular expressions */
426
427 static char rx_special_chars[] = "^.[$()|*+?{\\";
428
429 int mutt_rx_sanitize_string (char *dest, size_t destlen, const char *src)
430 {
431   while (*src && --destlen > 2)
432   {
433     if (strchr (rx_special_chars, *src))
434     {
435       *dest++ = '\\';
436       destlen--;
437     }
438     *dest++ = *src++;
439   }
440   
441   *dest = '\0';
442   
443   if (*src)
444     return -1;
445   else
446     return 0;
447 }
448
449 /* Read a line from ``fp'' into the dynamically allocated ``s'',
450  * increasing ``s'' if necessary. The ending "\n" or "\r\n" is removed.
451  * If a line ends with "\", this char and the linefeed is removed,
452  * and the next line is read too.
453  */
454 char *mutt_read_line (char *s, size_t *size, FILE *fp, int *line)
455 {
456   size_t offset = 0;
457   char *ch;
458
459   if (!s)
460   {
461     s = safe_malloc (STRING);
462     *size = STRING;
463   }
464
465   FOREVER
466   {
467     if (fgets (s + offset, *size - offset, fp) == NULL)
468     {
469       FREE (&s);
470       return NULL;
471     }
472     if ((ch = strchr (s + offset, '\n')) != NULL)
473     {
474       (*line)++;
475       *ch = 0;
476       if (ch > s && *(ch - 1) == '\r')
477         *--ch = 0;
478       if (ch == s || *(ch - 1) != '\\')
479         return s;
480       offset = ch - s - 1;
481     }
482     else
483     {
484       int c;
485       c = getc (fp); /* This is kind of a hack. We want to know if the
486                         char at the current point in the input stream is EOF.
487                         feof() will only tell us if we've already hit EOF, not
488                         if the next character is EOF. So, we need to read in
489                         the next character and manually check if it is EOF. */
490       if (c == EOF)
491       {
492         /* The last line of fp isn't \n terminated */
493         (*line)++;
494         return s;
495       }
496       else
497       {
498         ungetc (c, fp); /* undo our dammage */
499         /* There wasn't room for the line -- increase ``s'' */
500         offset = *size - 1; /* overwrite the terminating 0 */
501         *size += STRING;
502         safe_realloc (&s, *size);
503       }
504     }
505   }
506 }
507
508 char *
509 mutt_substrcpy (char *dest, const char *beg, const char *end, size_t destlen)
510 {
511   size_t len;
512
513   len = end - beg;
514   if (len > destlen - 1)
515     len = destlen - 1;
516   memcpy (dest, beg, len);
517   dest[len] = 0;
518   return dest;
519 }
520
521 char *mutt_substrdup (const char *begin, const char *end)
522 {
523   size_t len;
524   char *p;
525
526   if (end)
527     len = end - begin;
528   else
529     len = strlen (begin);
530
531   p = safe_malloc (len + 1);
532   memcpy (p, begin, len);
533   p[len] = 0;
534   return p;
535 }
536
537 /* prepare a file name to survive the shell's quoting rules.
538  * From the Unix programming FAQ by way of Liviu.
539  */
540
541 size_t mutt_quote_filename (char *d, size_t l, const char *f)
542 {
543   size_t i, j = 0;
544
545   if(!f) 
546   {
547     *d = '\0';
548     return 0;
549   }
550
551   /* leave some space for the trailing characters. */
552   l -= 6;
553   
554   d[j++] = '\'';
555   
556   for(i = 0; j < l && f[i]; i++)
557   {
558     if(f[i] == '\'' || f[i] == '`')
559     {
560       d[j++] = '\'';
561       d[j++] = '\\';
562       d[j++] = f[i];
563       d[j++] = '\'';
564     }
565     else
566       d[j++] = f[i];
567   }
568   
569   d[j++] = '\'';
570   d[j]   = '\0';
571   
572   return j;
573 }
574
575 /* NULL-pointer aware string comparison functions */
576
577 int mutt_strcmp(const char *a, const char *b)
578 {
579   return strcmp(NONULL(a), NONULL(b));
580 }
581
582 int mutt_strcasecmp(const char *a, const char *b)
583 {
584   return strcasecmp(NONULL(a), NONULL(b));
585 }
586
587 int mutt_strncmp(const char *a, const char *b, size_t l)
588 {
589   return strncmp(NONULL(a), NONULL(b), l);
590 }
591
592 int mutt_strncasecmp(const char *a, const char *b, size_t l)
593 {
594   return strncasecmp(NONULL(a), NONULL(b), l);
595 }
596
597 size_t mutt_strlen(const char *a)
598 {
599   return a ? strlen (a) : 0;
600 }
601
602 int mutt_strcoll(const char *a, const char *b)
603 {
604   return strcoll(NONULL(a), NONULL(b));
605 }
606
607 const char *mutt_stristr (const char *haystack, const char *needle)
608 {
609   const char *p, *q;
610
611   if (!haystack)
612     return NULL;
613   if (!needle)
614     return (haystack);
615
616   while (*(p = haystack))
617   {
618     for (q = needle;
619          *p && *q &&
620            tolower ((unsigned char) *p) == tolower ((unsigned char) *q);
621          p++, q++)
622       ;
623     if (!*q)
624       return (haystack);
625     haystack++;
626   }
627   return NULL;
628 }
629
630 char *mutt_skip_whitespace (char *p)
631 {
632   SKIPWS (p);
633   return p;
634 }
635
636 void mutt_remove_trailing_ws (char *s)
637 {
638   char *p;
639   
640   for (p = s + mutt_strlen (s) - 1 ; p >= s && ISSPACE (*p) ; p--)
641     *p = 0;
642 }
643
644 char *mutt_concat_path (char *d, const char *dir, const char *fname, size_t l)
645 {
646   const char *fmt = "%s/%s";
647   
648   if (!*fname || (*dir && dir[strlen(dir)-1] == '/'))
649     fmt = "%s%s";
650   
651   snprintf (d, l, fmt, dir, fname);
652   return d;
653 }
654
655 const char *mutt_basename (const char *f)
656 {
657   const char *p = strrchr (f, '/');
658   if (p)
659     return p + 1;
660   else
661     return f;
662 }