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