]> git.llucax.com Git - software/mutt-debian.git/blob - lib.c
Remove maildir-mtime patch, upstream has a different implementation (though with...
[software/mutt-debian.git] / lib.c
1 /*
2  * Copyright (C) 1996-2000,2007 Michael R. Elkins <me@mutt.org>
3  * Copyright (C) 1999-2004,2006-7 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., 51 Franklin Street, Fifth Floor,
20  *     Boston, MA  02110-1301, 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 #define _LIB_C 1
30
31 #if HAVE_CONFIG_H
32 # include "config.h"
33 #endif
34
35 #include <string.h>
36 #include <ctype.h>
37 #include <unistd.h>
38 #include <stdlib.h>
39 #include <sys/wait.h>
40 #include <errno.h>
41 #include <sys/stat.h>
42 #include <fcntl.h>
43 #include <pwd.h>
44 #include <sys/types.h>
45 #include <dirent.h>
46
47 #ifdef HAVE_SYSEXITS_H
48 #include <sysexits.h>
49 #else /* Make sure EX_OK is defined <philiph@pobox.com> */
50 #define EX_OK 0
51 #endif
52
53 #include "lib.h"
54
55
56 static struct sysexits
57 {
58   int v;
59   const char *str;
60
61 sysexits_h[] = 
62 {
63 #ifdef EX_USAGE
64   { 0xff & EX_USAGE, "Bad usage." },
65 #endif
66 #ifdef EX_DATAERR
67   { 0xff & EX_DATAERR, "Data format error." },
68 #endif
69 #ifdef EX_NOINPUT
70   { 0xff & EX_NOINPUT, "Cannot open input." },
71 #endif
72 #ifdef EX_NOUSER
73   { 0xff & EX_NOUSER, "User unknown." },
74 #endif
75 #ifdef EX_NOHOST
76   { 0xff & EX_NOHOST, "Host unknown." },
77 #endif
78 #ifdef EX_UNAVAILABLE
79   { 0xff & EX_UNAVAILABLE, "Service unavailable." },
80 #endif
81 #ifdef EX_SOFTWARE
82   { 0xff & EX_SOFTWARE, "Internal error." },
83 #endif
84 #ifdef EX_OSERR
85   { 0xff & EX_OSERR, "Operating system error." },
86 #endif
87 #ifdef EX_OSFILE
88   { 0xff & EX_OSFILE, "System file missing." },
89 #endif
90 #ifdef EX_CANTCREAT
91   { 0xff & EX_CANTCREAT, "Can't create output." },
92 #endif
93 #ifdef EX_IOERR
94   { 0xff & EX_IOERR, "I/O error." },
95 #endif
96 #ifdef EX_TEMPFAIL
97   { 0xff & EX_TEMPFAIL, "Deferred." },
98 #endif
99 #ifdef EX_PROTOCOL
100   { 0xff & EX_PROTOCOL, "Remote protocol error." },
101 #endif
102 #ifdef EX_NOPERM
103   { 0xff & EX_NOPERM, "Insufficient permission." },
104 #endif
105 #ifdef EX_CONFIG
106   { 0xff & EX_NOPERM, "Local configuration error." },
107 #endif
108   { S_ERR, "Exec error." },
109   { -1, NULL}
110 };
111
112 void mutt_nocurses_error (const char *fmt, ...)
113 {
114   va_list ap;
115
116   va_start (ap, fmt);
117   vfprintf (stderr, fmt, ap);
118   va_end (ap);
119   fputc ('\n', stderr);
120 }
121
122 void *safe_calloc (size_t nmemb, size_t size)
123 {
124   void *p;
125
126   if (!nmemb || !size)
127     return NULL;
128
129   if (((size_t) -1) / nmemb <= size)
130   {
131     mutt_error _("Integer overflow -- can't allocate memory!");
132     sleep (1);
133     mutt_exit (1);
134   }
135   
136   if (!(p = calloc (nmemb, size)))
137   {
138     mutt_error _("Out of memory!");
139     sleep (1);
140     mutt_exit (1);
141   }
142   return p;
143 }
144
145 void *safe_malloc (size_t siz)
146 {
147   void *p;
148
149   if (siz == 0)
150     return 0;
151   if ((p = (void *) malloc (siz)) == 0) /* __MEM_CHECKED__ */
152   {
153     mutt_error _("Out of memory!");
154     sleep (1);
155     mutt_exit (1);
156   }
157   return (p);
158 }
159
160 void safe_realloc (void *ptr, size_t siz)
161 {
162   void *r;
163   void **p = (void **)ptr;
164
165   if (siz == 0)
166   {
167     if (*p)
168     {
169       free (*p);                        /* __MEM_CHECKED__ */
170       *p = NULL;
171     }
172     return;
173   }
174
175   if (*p)
176     r = (void *) realloc (*p, siz);     /* __MEM_CHECKED__ */
177   else
178   {
179     /* realloc(NULL, nbytes) doesn't seem to work under SunOS 4.1.x  --- __MEM_CHECKED__ */
180     r = (void *) malloc (siz);          /* __MEM_CHECKED__ */
181   }
182
183   if (!r)
184   {
185     mutt_error _("Out of memory!");
186     sleep (1);
187     mutt_exit (1);
188   }
189
190   *p = r;
191 }
192
193 void safe_free (void *ptr)      /* __SAFE_FREE_CHECKED__ */
194 {
195   void **p = (void **)ptr;
196   if (*p)
197   {
198     free (*p);                          /* __MEM_CHECKED__ */
199     *p = 0;
200   }
201 }
202
203 int safe_fclose (FILE **f)
204 {
205   int r = 0;
206   
207   if (*f)
208     r = fclose (*f);
209       
210   *f = NULL;
211   return r;
212 }
213
214 int safe_fsync_close (FILE **f)
215 {
216   int r = 0;
217
218   if (*f)
219   {
220     if (fflush (*f) || fsync (fileno (*f)))
221     {
222       r = -1;
223       safe_fclose (f);
224     }
225     else
226       r = safe_fclose (f);
227   }
228
229   return r;
230 }
231
232 char *safe_strdup (const char *s)
233 {
234   char *p;
235   size_t l;
236
237   if (!s || !*s)
238     return 0;
239   l = strlen (s) + 1;
240   p = (char *)safe_malloc (l);
241   memcpy (p, s, l);
242   return (p);
243 }
244
245 char *safe_strcat (char *d, size_t l, const char *s)
246 {
247   char *p = d;
248
249   if (!l) 
250     return d;
251
252   l--; /* Space for the trailing '\0'. */
253   
254   for (; *d && l; l--)
255     d++;
256   for (; *s && l; l--)
257     *d++ = *s++;
258
259   *d = '\0';
260   
261   return p;
262 }
263
264 char *safe_strncat (char *d, size_t l, const char *s, size_t sl)
265 {
266   char *p = d;
267
268   if (!l)
269     return d;
270   
271   l--; /* Space for the trailing '\0'. */
272   
273   for (; *d && l; l--)
274     d++;
275   for (; *s && l && sl; l--, sl--)
276     *d++ = *s++;
277
278   *d = '\0';
279   
280   return p;
281 }
282
283
284 void mutt_str_replace (char **p, const char *s)
285 {
286   FREE (p);             /* __FREE_CHECKED__ */
287   *p = safe_strdup (s);
288 }
289
290 void mutt_str_adjust (char **p)
291 {
292   if (!p || !*p) return;
293   safe_realloc (p, strlen (*p) + 1);
294 }
295
296 /* convert all characters in the string to lowercase */
297 char *mutt_strlower (char *s)
298 {
299   char *p = s;
300
301   while (*p)
302   {
303     *p = tolower ((unsigned char) *p);
304     p++;
305   }
306
307   return (s);
308 }
309
310 void mutt_unlink (const char *s)
311 {
312   int fd;
313   int flags;
314   FILE *f;
315   struct stat sb, sb2;
316   char buf[2048];
317
318   /* Defend against symlink attacks */
319   
320 #ifdef O_NOFOLLOW 
321   flags = O_RDWR | O_NOFOLLOW;
322 #else
323   flags = O_RDWR;
324 #endif
325   
326   if (lstat (s, &sb) == 0 && S_ISREG(sb.st_mode))
327   {
328     if ((fd = open (s, flags)) < 0)
329       return;
330     
331     if ((fstat (fd, &sb2) != 0) || !S_ISREG (sb2.st_mode) 
332         || (sb.st_dev != sb2.st_dev) || (sb.st_ino != sb2.st_ino))
333     {
334       close (fd);
335       return;
336     }
337     
338     if ((f = fdopen (fd, "r+")))
339     {
340       unlink (s);
341       memset (buf, 0, sizeof (buf));
342       while (sb.st_size > 0)
343       {
344         fwrite (buf, 1, MIN (sizeof (buf), sb.st_size), f);
345         sb.st_size -= MIN (sizeof (buf), sb.st_size);
346       }
347       safe_fclose (&f);
348     }
349   }
350 }
351
352 int mutt_copy_bytes (FILE *in, FILE *out, size_t size)
353 {
354   char buf[2048];
355   size_t chunk;
356
357   while (size > 0)
358   {
359     chunk = (size > sizeof (buf)) ? sizeof (buf) : size;
360     if ((chunk = fread (buf, 1, chunk, in)) < 1)
361       break;
362     if (fwrite (buf, 1, chunk, out) != chunk)
363     {
364       /* dprint (1, (debugfile, "mutt_copy_bytes(): fwrite() returned short byte count\n")); */
365       return (-1);
366     }
367     size -= chunk;
368   }
369
370   return 0;
371 }
372
373 int mutt_copy_stream (FILE *fin, FILE *fout)
374 {
375   size_t l;
376   char buf[LONG_STRING];
377
378   while ((l = fread (buf, 1, sizeof (buf), fin)) > 0)
379   {
380     if (fwrite (buf, 1, l, fout) != l)
381       return (-1);
382   }
383
384   return 0;
385 }
386
387 int 
388 compare_stat (struct stat *osb, struct stat *nsb)
389 {
390   if (osb->st_dev != nsb->st_dev || osb->st_ino != nsb->st_ino ||
391       osb->st_rdev != nsb->st_rdev)
392   {
393     return -1;
394   }
395
396   return 0;
397 }
398
399 int safe_symlink(const char *oldpath, const char *newpath)
400 {
401   struct stat osb, nsb;
402
403   if(!oldpath || !newpath)
404     return -1;
405   
406   if(unlink(newpath) == -1 && errno != ENOENT)
407     return -1;
408   
409   if (oldpath[0] == '/')
410   {
411     if (symlink (oldpath, newpath) == -1)
412       return -1;
413   }
414   else
415   {
416     char abs_oldpath[_POSIX_PATH_MAX];
417
418     if ((getcwd (abs_oldpath, sizeof abs_oldpath) == NULL) ||
419         (strlen (abs_oldpath) + 1 + strlen (oldpath) + 1 > sizeof abs_oldpath))
420     return -1;
421   
422     strcat (abs_oldpath, "/");          /* __STRCAT_CHECKED__ */
423     strcat (abs_oldpath, oldpath);      /* __STRCAT_CHECKED__ */
424     if (symlink (abs_oldpath, newpath) == -1)
425       return -1;
426   }
427
428   if(stat(oldpath, &osb) == -1 || stat(newpath, &nsb) == -1
429      || compare_stat(&osb, &nsb) == -1)
430   {
431     unlink(newpath);
432     return -1;
433   }
434   
435   return 0;
436 }
437
438
439
440 /* 
441  * This function is supposed to do nfs-safe renaming of files.
442  * 
443  * Warning: We don't check whether src and target are equal.
444  */
445
446 int safe_rename (const char *src, const char *target)
447 {
448   struct stat ssb, tsb;
449
450   if (!src || !target)
451     return -1;
452
453   if (link (src, target) != 0)
454   {
455
456     /*
457      * Coda does not allow cross-directory links, but tells
458      * us it's a cross-filesystem linking attempt.
459      * 
460      * However, the Coda rename call is allegedly safe to use.
461      * 
462      * With other file systems, rename should just fail when 
463      * the files reside on different file systems, so it's safe
464      * to try it here.
465      *
466      */
467     
468     dprint (1, (debugfile, "safe_rename: link (%s, %s) failed: %s (%d)\n", src, target, strerror (errno), errno));
469
470     /*
471      * FUSE may return ENOSYS. VFAT may return EPERM. FreeBSD's
472      * msdosfs may return EOPNOTSUPP.  ENOTSUP can also appear.
473      */
474     if (errno == EXDEV || errno == ENOSYS || errno == EPERM
475 #ifdef ENOTSUP
476         || errno == ENOTSUP
477 #endif
478 #ifdef EOPNOTSUPP
479         || errno == EOPNOTSUPP
480 #endif
481         )
482     {
483       dprint (1, (debugfile, "safe_rename: trying rename...\n"));
484       if (rename (src, target) == -1) 
485       {
486         dprint (1, (debugfile, "safe_rename: rename (%s, %s) failed: %s (%d)\n", src, target, strerror (errno), errno));
487         return -1;
488       }
489       dprint (1, (debugfile, "safe_rename: rename succeeded.\n"));
490     
491       return 0;
492     }
493
494     return -1;
495   }
496
497   /*
498    * Stat both links and check if they are equal.
499    */
500   
501   if (lstat (src, &ssb) == -1)
502   {
503     dprint (1, (debugfile, "safe_rename: can't stat %s: %s (%d)\n",
504                 src, strerror (errno), errno));
505     return -1;
506   }
507   
508   if (lstat (target, &tsb) == -1)
509   {
510     dprint (1, (debugfile, "safe_rename: can't stat %s: %s (%d)\n",
511                 src, strerror (errno), errno));
512     return -1;
513   }
514
515   /* 
516    * pretend that the link failed because the target file
517    * did already exist.
518    */
519
520   if (compare_stat (&ssb, &tsb) == -1)
521   {
522     dprint (1, (debugfile, "safe_rename: stat blocks for %s and %s diverge; pretending EEXIST.\n", src, target));
523     errno = EEXIST;
524     return -1;
525   }
526
527   /*
528    * Unlink the original link.  Should we really ignore the return
529    * value here? XXX
530    */
531
532   if (unlink (src) == -1) 
533   {
534     dprint (1, (debugfile, "safe_rename: unlink (%s) failed: %s (%d)\n",
535                 src, strerror (errno), errno));
536   }
537   
538
539   return 0;
540 }
541
542
543 /* Create a temporary directory next to a file name */
544
545 static int mutt_mkwrapdir (const char *path, char *newfile, size_t nflen, 
546                     char *newdir, size_t ndlen)
547 {
548   const char *basename;
549   char parent[_POSIX_PATH_MAX];
550   char *p;
551   int rv;
552
553   strfcpy (parent, NONULL (path), sizeof (parent));
554   
555   if ((p = strrchr (parent, '/')))
556   {
557     *p = '\0';
558     basename = p + 1;
559   }
560   else
561   {
562     strfcpy (parent, ".", sizeof (parent));
563     basename = path;
564   }
565
566   do 
567   {
568     snprintf (newdir, ndlen, "%s/%s", parent, ".muttXXXXXX");
569     mktemp (newdir);
570   } 
571   while ((rv = mkdir (newdir, 0700)) == -1 && errno == EEXIST);
572   
573   if (rv == -1)
574     return -1;
575   
576   snprintf (newfile, nflen, "%s/%s", newdir, NONULL(basename));
577   return 0;  
578 }
579
580 /* remove a directory and everything under it */
581 int mutt_rmtree (const char* path)
582 {
583   DIR* dirp;
584   struct dirent* de;
585   char cur[_POSIX_PATH_MAX];
586   struct stat statbuf;
587   int rc = 0;
588
589   if (!(dirp = opendir (path)))
590   {
591     dprint (1, (debugfile, "mutt_rmtree: error opening directory %s\n", path));
592     return -1;
593   }
594   while ((de = readdir (dirp)))
595   {
596     if (!strcmp (".", de->d_name) || !strcmp ("..", de->d_name))
597       continue;
598
599     snprintf (cur, sizeof (cur), "%s/%s", path, de->d_name);
600     /* XXX make nonrecursive version */
601
602     if (stat(cur, &statbuf) == -1)
603     {
604       rc = 1;
605       continue;
606     }
607
608     if (S_ISDIR (statbuf.st_mode))
609       rc |= mutt_rmtree (cur);
610     else
611       rc |= unlink (cur);
612   }
613   closedir (dirp);
614
615   rc |= rmdir (path);
616
617   return rc;
618 }
619
620 static int mutt_put_file_in_place (const char *path, const char *safe_file, const char *safe_dir)
621 {
622   int rv;
623   
624   rv = safe_rename (safe_file, path);
625   unlink (safe_file);
626   rmdir (safe_dir);
627   return rv;
628 }
629
630 int safe_open (const char *path, int flags)
631 {
632   struct stat osb, nsb;
633   int fd;
634
635   if (flags & O_EXCL) 
636   {
637     char safe_file[_POSIX_PATH_MAX];
638     char safe_dir[_POSIX_PATH_MAX];
639
640     if (mutt_mkwrapdir (path, safe_file, sizeof (safe_file),
641                         safe_dir, sizeof (safe_dir)) == -1)
642       return -1;
643     
644     if ((fd = open (safe_file, flags, 0600)) < 0)
645     {
646       rmdir (safe_dir);
647       return fd;
648     }
649
650     /* NFS and I believe cygwin do not handle movement of open files well */
651     close (fd);
652     if (mutt_put_file_in_place (path, safe_file, safe_dir) == -1)
653       return -1;
654   }
655
656   if ((fd = open (path, flags & ~O_EXCL, 0600)) < 0)
657     return fd;
658     
659   /* make sure the file is not symlink */
660   if (lstat (path, &osb) < 0 || fstat (fd, &nsb) < 0 ||
661       compare_stat(&osb, &nsb) == -1)
662   {
663 /*    dprint (1, (debugfile, "safe_open(): %s is a symlink!\n", path)); */
664     close (fd);
665     return (-1);
666   }
667
668   return (fd);
669 }
670
671 /* when opening files for writing, make sure the file doesn't already exist
672  * to avoid race conditions.
673  */
674 FILE *safe_fopen (const char *path, const char *mode)
675 {
676   if (mode[0] == 'w')
677   {
678     int fd;
679     int flags = O_CREAT | O_EXCL;
680
681 #ifdef O_NOFOLLOW
682     flags |= O_NOFOLLOW;
683 #endif
684
685     if (mode[1] == '+')
686       flags |= O_RDWR;
687     else
688       flags |= O_WRONLY;
689
690     if ((fd = safe_open (path, flags)) < 0)
691       return (NULL);
692
693     return (fdopen (fd, mode));
694   }
695   else
696     return (fopen (path, mode));
697 }
698
699 static char safe_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+@{}._-:%/";
700
701 void mutt_sanitize_filename (char *f, short slash)
702 {
703   if (!f) return;
704
705   for (; *f; f++)
706   {
707     if ((slash && *f == '/') || !strchr (safe_chars, *f))
708       *f = '_';
709   }
710 }
711
712 /* these characters must be escaped in regular expressions */
713
714 static char rx_special_chars[] = "^.[$()|*+?{\\";
715
716 int mutt_rx_sanitize_string (char *dest, size_t destlen, const char *src)
717 {
718   while (*src && --destlen > 2)
719   {
720     if (strchr (rx_special_chars, *src))
721     {
722       *dest++ = '\\';
723       destlen--;
724     }
725     *dest++ = *src++;
726   }
727   
728   *dest = '\0';
729   
730   if (*src)
731     return -1;
732   else
733     return 0;
734 }
735
736 /* Read a line from ``fp'' into the dynamically allocated ``s'',
737  * increasing ``s'' if necessary. The ending "\n" or "\r\n" is removed.
738  * If a line ends with "\", this char and the linefeed is removed,
739  * and the next line is read too.
740  */
741 char *mutt_read_line (char *s, size_t *size, FILE *fp, int *line, int flags)
742 {
743   size_t offset = 0;
744   char *ch;
745
746   if (!s)
747   {
748     s = safe_malloc (STRING);
749     *size = STRING;
750   }
751
752   FOREVER
753   {
754     if (fgets (s + offset, *size - offset, fp) == NULL)
755     {
756       FREE (&s);
757       return NULL;
758     }
759     if ((ch = strchr (s + offset, '\n')) != NULL)
760     {
761       (*line)++;
762       if (flags & M_EOL)
763         return s;
764       *ch = 0;
765       if (ch > s && *(ch - 1) == '\r')
766         *--ch = 0;
767       if (!(flags & M_CONT) || ch == s || *(ch - 1) != '\\')
768         return s;
769       offset = ch - s - 1;
770     }
771     else
772     {
773       int c;
774       c = getc (fp); /* This is kind of a hack. We want to know if the
775                         char at the current point in the input stream is EOF.
776                         feof() will only tell us if we've already hit EOF, not
777                         if the next character is EOF. So, we need to read in
778                         the next character and manually check if it is EOF. */
779       if (c == EOF)
780       {
781         /* The last line of fp isn't \n terminated */
782         (*line)++;
783         return s;
784       }
785       else
786       {
787         ungetc (c, fp); /* undo our dammage */
788         /* There wasn't room for the line -- increase ``s'' */
789         offset = *size - 1; /* overwrite the terminating 0 */
790         *size += STRING;
791         safe_realloc (&s, *size);
792       }
793     }
794   }
795 }
796
797 char *
798 mutt_substrcpy (char *dest, const char *beg, const char *end, size_t destlen)
799 {
800   size_t len;
801
802   len = end - beg;
803   if (len > destlen - 1)
804     len = destlen - 1;
805   memcpy (dest, beg, len);
806   dest[len] = 0;
807   return dest;
808 }
809
810 char *mutt_substrdup (const char *begin, const char *end)
811 {
812   size_t len;
813   char *p;
814
815   if (end)
816     len = end - begin;
817   else
818     len = strlen (begin);
819
820   p = safe_malloc (len + 1);
821   memcpy (p, begin, len);
822   p[len] = 0;
823   return p;
824 }
825
826 /* prepare a file name to survive the shell's quoting rules.
827  * From the Unix programming FAQ by way of Liviu.
828  */
829
830 size_t mutt_quote_filename (char *d, size_t l, const char *f)
831 {
832   size_t i, j = 0;
833
834   if(!f) 
835   {
836     *d = '\0';
837     return 0;
838   }
839
840   /* leave some space for the trailing characters. */
841   l -= 6;
842   
843   d[j++] = '\'';
844   
845   for(i = 0; j < l && f[i]; i++)
846   {
847     if(f[i] == '\'' || f[i] == '`')
848     {
849       d[j++] = '\'';
850       d[j++] = '\\';
851       d[j++] = f[i];
852       d[j++] = '\'';
853     }
854     else
855       d[j++] = f[i];
856   }
857   
858   d[j++] = '\'';
859   d[j]   = '\0';
860   
861   return j;
862 }
863
864 /* NULL-pointer aware string comparison functions */
865
866 int mutt_strcmp(const char *a, const char *b)
867 {
868   return strcmp(NONULL(a), NONULL(b));
869 }
870
871 int mutt_strcasecmp(const char *a, const char *b)
872 {
873   return strcasecmp(NONULL(a), NONULL(b));
874 }
875
876 int mutt_strncmp(const char *a, const char *b, size_t l)
877 {
878   return strncmp(NONULL(a), NONULL(b), l);
879 }
880
881 int mutt_strncasecmp(const char *a, const char *b, size_t l)
882 {
883   return strncasecmp(NONULL(a), NONULL(b), l);
884 }
885
886 size_t mutt_strlen(const char *a)
887 {
888   return a ? strlen (a) : 0;
889 }
890
891 int mutt_strcoll(const char *a, const char *b)
892 {
893   return strcoll(NONULL(a), NONULL(b));
894 }
895
896 const char *mutt_stristr (const char *haystack, const char *needle)
897 {
898   const char *p, *q;
899
900   if (!haystack)
901     return NULL;
902   if (!needle)
903     return (haystack);
904
905   while (*(p = haystack))
906   {
907     for (q = needle;
908          *p && *q &&
909            tolower ((unsigned char) *p) == tolower ((unsigned char) *q);
910          p++, q++)
911       ;
912     if (!*q)
913       return (haystack);
914     haystack++;
915   }
916   return NULL;
917 }
918
919 char *mutt_skip_whitespace (char *p)
920 {
921   SKIPWS (p);
922   return p;
923 }
924
925 void mutt_remove_trailing_ws (char *s)
926 {
927   char *p;
928   
929   for (p = s + mutt_strlen (s) - 1 ; p >= s && ISSPACE (*p) ; p--)
930     *p = 0;
931 }
932
933 /*
934  * Write the concatened pathname (dir + "/" + fname) into dst.
935  * The slash is ommitted when dir or fname is of 0 length.
936  * Returns NULL on error or a pointer to dst otherwise.
937  */
938 char *mutt_concatn_path (char *dst, size_t dstlen,
939     const char *dir, size_t dirlen, const char *fname, size_t fnamelen)
940 {
941   size_t req;
942   size_t offset = 0;
943
944   if (dstlen == 0)
945     return NULL; /* probably should not mask errors like this */
946
947   /* size check */
948   req = dirlen + fnamelen + 1; /* +1 for the trailing nul */
949   if (dirlen && fnamelen)
950     req++; /* when both components are non-nul, we add a "/" in between */
951   if (req > dstlen) { /* check for condition where the dst length is too short */
952     /* Two options here:
953      * 1) assert(0) or return NULL to signal error
954      * 2) copy as much of the path as will fit
955      * It doesn't appear that the return value is actually checked anywhere mutt_concat_path()
956      * is called, so we should just copy set dst to nul and let the calling function fail later.
957      */
958     dst[0] = 0; /* safe since we bail out early if dstlen == 0 */
959     return NULL;
960   }
961
962   if (dirlen) { /* when dir is not empty */
963     memcpy(dst, dir, dirlen);
964     offset = dirlen;
965     if (fnamelen)
966       dst[offset++] = '/';
967   }
968   if (fnamelen) { /* when fname is not empty */
969     memcpy(dst + offset, fname, fnamelen);
970     offset += fnamelen;
971   }
972   dst[offset] = 0;
973   return dst;
974 }
975
976 char *mutt_concat_path (char *d, const char *dir, const char *fname, size_t l)
977 {
978   const char *fmt = "%s/%s";
979   
980   if (!*fname || (*dir && dir[strlen(dir)-1] == '/'))
981     fmt = "%s%s";
982   
983   snprintf (d, l, fmt, dir, fname);
984   return d;
985 }
986
987 const char *mutt_basename (const char *f)
988 {
989   const char *p = strrchr (f, '/');
990   if (p)
991     return p + 1;
992   else
993     return f;
994 }
995
996 const char *
997 mutt_strsysexit(int e)
998 {
999   int i;
1000   
1001   for(i = 0; sysexits_h[i].str; i++)
1002   {
1003     if(e == sysexits_h[i].v)
1004       break;
1005   }
1006   
1007   return sysexits_h[i].str;
1008 }
1009
1010 int mutt_atos (const char *str, short *dst)
1011 {
1012   int rc;
1013   long res;
1014   short tmp;
1015   short *t = dst ? dst : &tmp;
1016
1017   *t = 0;
1018
1019   if ((rc = mutt_atol (str, &res)) < 0)
1020     return rc;
1021   if ((short) res != res)
1022     return -2;
1023
1024   *t = (short) res;
1025   return 0;
1026 }
1027
1028 int mutt_atoi (const char *str, int *dst)
1029 {
1030   int rc;
1031   long res;
1032   int tmp;
1033   int *t = dst ? dst : &tmp;
1034
1035   *t = 0;
1036
1037   if ((rc = mutt_atol (str, &res)) < 0)
1038     return rc;
1039   if ((int) res != res)
1040     return -2;
1041
1042   *t = (int) res;
1043   return 0;
1044 }
1045
1046 int mutt_atol (const char *str, long *dst)
1047 {
1048   long r;
1049   long *res = dst ? dst : &r;
1050   char *e = NULL;
1051
1052   /* no input: 0 */
1053   if (!str || !*str)
1054   {
1055     *res = 0;
1056     return 0;
1057   }
1058
1059   *res = strtol (str, &e, 10);
1060   if (e && *e != '\0')
1061     return -1;
1062   return 0;
1063 }