Fix bug #1901 ‘More descriptive error msgs when attempting to delete a folder’
[claws.git] / src / common / utils.c
1 /*
2  * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2015 Hiroyuki Yamamoto & The Claws Mail Team
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 3 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program. If not, see <http://www.gnu.org/licenses/>.
17  *
18  * The code of the g_utf8_substring function below is owned by
19  * Matthias Clasen <matthiasc@src.gnome.org>/<mclasen@redhat.com>
20  * and is got from GLIB 2.30: https://git.gnome.org/browse/glib/commit/
21  *  ?h=glib-2-30&id=9eb65dd3ed5e1a9638595cbe10699c7606376511
22  *
23  * GLib 2.30 is licensed under GPL v2 or later and:
24  * Copyright (C) 1999 Tom Tromey
25  * Copyright (C) 2000 Red Hat, Inc.
26  *
27  * https://git.gnome.org/browse/glib/tree/glib/gutf8.c
28  *  ?h=glib-2-30&id=9eb65dd3ed5e1a9638595cbe10699c7606376511
29  */
30
31 #ifdef HAVE_CONFIG_H
32 #  include "config.h"
33 #include "claws-features.h"
34 #endif
35
36 #include "defs.h"
37
38 #include <glib.h>
39 #include <gio/gio.h>
40
41 #include <glib/gi18n.h>
42
43 #ifdef USE_PTHREAD
44 #include <pthread.h>
45 #endif
46
47 #include <stdio.h>
48 #include <string.h>
49 #include <ctype.h>
50 #include <errno.h>
51 #include <sys/param.h>
52
53 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
54 #  include <wchar.h>
55 #  include <wctype.h>
56 #endif
57 #include <stdlib.h>
58 #include <sys/stat.h>
59 #include <unistd.h>
60 #include <stdarg.h>
61 #include <sys/types.h>
62 #if HAVE_SYS_WAIT_H
63 #  include <sys/wait.h>
64 #endif
65 #include <dirent.h>
66 #include <time.h>
67 #include <regex.h>
68
69 #ifdef G_OS_UNIX
70 #include <sys/utsname.h>
71 #endif
72
73 #include <fcntl.h>
74
75 #ifdef G_OS_WIN32
76 #  include <direct.h>
77 #  include <io.h>
78 #  include <w32lib.h>
79 #endif
80
81 #include "utils.h"
82 #include "socket.h"
83 #include "../codeconv.h"
84
85 #define BUFFSIZE        8192
86
87 static gboolean debug_mode = FALSE;
88 #ifdef G_OS_WIN32
89 static GSList *tempfiles=NULL;
90 #endif
91
92 /* Return true if we are running as root.  This function should beused
93    instead of getuid () == 0.  */
94 gboolean superuser_p (void)
95 {
96 #ifdef G_OS_WIN32
97   return w32_is_administrator ();
98 #else
99   return !getuid();
100 #endif  
101 }
102
103
104
105 #if !defined(G_OS_UNIX)
106 gint g_chdir(const gchar *path)
107 {
108 #ifdef G_OS_WIN32
109         if (G_WIN32_HAVE_WIDECHAR_API()) {
110                 wchar_t *wpath;
111                 gint retval;
112                 gint save_errno;
113
114                 wpath = g_utf8_to_utf16(path, -1, NULL, NULL, NULL);
115                 if (wpath == NULL) {
116                         errno = EINVAL;
117                         return -1;
118                 }
119
120                 retval = _wchdir(wpath);
121                 save_errno = errno;
122
123                 g_free(wpath);
124
125                 errno = save_errno;
126                 return retval;
127         } else {
128                 gchar *cp_path;
129                 gint retval;
130                 gint save_errno;
131
132                 cp_path = g_locale_from_utf8(path, -1, NULL, NULL, NULL);
133                 if (cp_path == NULL) {
134                         errno = EINVAL;
135                         return -1;
136                 }
137
138                 retval = chdir(cp_path);
139                 save_errno = errno;
140
141                 g_free(cp_path);
142
143                 errno = save_errno;
144                 return retval;
145         }
146 #else
147         return chdir(path);
148 #endif
149 }
150
151 gint g_chmod(const gchar *path, gint mode)
152 {
153 #ifdef G_OS_WIN32
154         if (G_WIN32_HAVE_WIDECHAR_API()) {
155                 wchar_t *wpath;
156                 gint retval;
157                 gint save_errno;
158
159                 wpath = g_utf8_to_utf16(path, -1, NULL, NULL, NULL);
160                 if (wpath == NULL) {
161                         errno = EINVAL;
162                         return -1;
163                 }
164
165                 retval = _wchmod(wpath, mode);
166                 save_errno = errno;
167
168                 g_free(wpath);
169
170                 errno = save_errno;
171                 return retval;
172         } else {
173                 gchar *cp_path;
174                 gint retval;
175                 gint save_errno;
176
177                 cp_path = g_locale_from_utf8(path, -1, NULL, NULL, NULL);
178                 if (cp_path == NULL) {
179                         errno = EINVAL;
180                         return -1;
181                 }
182
183                 retval = chmod(cp_path, mode);
184                 save_errno = errno;
185
186                 g_free(cp_path);
187
188                 errno = save_errno;
189                 return retval;
190         }
191 #else
192         return chmod(path, mode);
193 #endif
194 }
195
196 FILE* g_fopen(const gchar *filename, const gchar *mode)
197 {
198 #ifdef G_OS_WIN32
199         char *name = g_win32_locale_filename_from_utf8(filename);
200         FILE* fp = fopen(name, mode);
201         g_free(name);
202         return fp;
203 #else
204         return fopen(filename, mode);
205 #endif
206 }
207 int g_open(const gchar *filename, int flags, int mode)
208 {
209 #ifdef G_OS_WIN32
210         char *name = g_win32_locale_filename_from_utf8(filename);
211         int fd = open(name, flags, mode);
212         g_free(name);
213         return fd;
214 #else
215         return open(filename, flags, mode);
216 #endif
217 }
218 #endif /* G_OS_UNIX */
219
220
221 #ifdef G_OS_WIN32
222 gint mkstemp_name(gchar *template, gchar **name_used)
223 {
224         static gulong count=0; /* W32-_mktemp only supports up to 27
225                                   tempfiles... */
226         int tmpfd;
227
228         *name_used = g_strdup_printf("%s.%ld",_mktemp(template),count++);
229         tmpfd = g_open (*name_used, (O_CREAT | O_RDWR | O_BINARY),
230                                     (S_IRUSR | S_IWUSR));
231
232         tempfiles=g_slist_append(tempfiles, g_strdup(*name_used));
233         if (tmpfd<0) {
234                 perror(g_strdup_printf("cant create %s",*name_used));
235                 return -1;
236         }
237         else
238                 return tmpfd;
239 }
240 #endif /* G_OS_WIN32 */
241
242 #ifdef G_OS_WIN32
243 gint mkstemp(gchar *template)
244 {
245         gchar *dummyname;
246         gint res = mkstemp_name(template, &dummyname);
247         g_free(dummyname);
248         return res;
249 }
250 #endif /* G_OS_WIN32 */
251
252 GSList *slist_copy_deep(GSList *list, GCopyFunc func)
253 {
254 #if GLIB_CHECK_VERSION(2, 34, 0)
255         return g_slist_copy_deep(list, func, NULL);
256 #else
257         GSList *res = g_slist_copy(list);
258         GSList *walk = res;
259         while (walk) {
260                 walk->data = func(walk->data, NULL);
261                 walk = walk->next;
262         }
263         return res;
264 #endif
265 }
266
267 void list_free_strings(GList *list)
268 {
269         list = g_list_first(list);
270
271         while (list != NULL) {
272                 g_free(list->data);
273                 list = list->next;
274         }
275 }
276
277 void slist_free_strings(GSList *list)
278 {
279         while (list != NULL) {
280                 g_free(list->data);
281                 list = list->next;
282         }
283 }
284
285 void slist_free_strings_full(GSList *list)
286 {
287 #if GLIB_CHECK_VERSION(2,28,0)
288         g_slist_free_full(list, (GDestroyNotify)g_free);
289 #else
290         g_slist_foreach(list, (GFunc)g_free, NULL);
291         g_slist_free(list);
292 #endif
293 }
294
295 static void hash_free_strings_func(gpointer key, gpointer value, gpointer data)
296 {
297         g_free(key);
298 }
299
300 void hash_free_strings(GHashTable *table)
301 {
302         g_hash_table_foreach(table, hash_free_strings_func, NULL);
303 }
304
305 gint str_case_equal(gconstpointer v, gconstpointer v2)
306 {
307         return g_ascii_strcasecmp((const gchar *)v, (const gchar *)v2) == 0;
308 }
309
310 guint str_case_hash(gconstpointer key)
311 {
312         const gchar *p = key;
313         guint h = *p;
314
315         if (h) {
316                 h = g_ascii_tolower(h);
317                 for (p += 1; *p != '\0'; p++)
318                         h = (h << 5) - h + g_ascii_tolower(*p);
319         }
320
321         return h;
322 }
323
324 void ptr_array_free_strings(GPtrArray *array)
325 {
326         gint i;
327         gchar *str;
328
329         cm_return_if_fail(array != NULL);
330
331         for (i = 0; i < array->len; i++) {
332                 str = g_ptr_array_index(array, i);
333                 g_free(str);
334         }
335 }
336
337 gint to_number(const gchar *nstr)
338 {
339         register const gchar *p;
340
341         if (*nstr == '\0') return -1;
342
343         for (p = nstr; *p != '\0'; p++)
344                 if (!g_ascii_isdigit(*p)) return -1;
345
346         return atoi(nstr);
347 }
348
349 /* convert integer into string,
350    nstr must be not lower than 11 characters length */
351 gchar *itos_buf(gchar *nstr, gint n)
352 {
353         g_snprintf(nstr, 11, "%d", n);
354         return nstr;
355 }
356
357 /* convert integer into string */
358 gchar *itos(gint n)
359 {
360         static gchar nstr[11];
361
362         return itos_buf(nstr, n);
363 }
364
365 #define divide(num,divisor,i,d)         \
366 {                                       \
367         i = num >> divisor;             \
368         d = num & ((1<<divisor)-1);     \
369         d = (d*100) >> divisor;         \
370 }
371
372
373 /*!
374  * \brief Convert a given size in bytes in a human-readable string
375  *
376  * \param size  The size expressed in bytes to convert in string
377  * \return      The string that respresents the size in an human-readable way
378  */
379 gchar *to_human_readable(goffset size)
380 {
381         static gchar str[14];
382         static gchar *b_format = NULL, *kb_format = NULL, 
383                      *mb_format = NULL, *gb_format = NULL;
384         register int t = 0, r = 0;
385         if (b_format == NULL) {
386                 b_format  = _("%dB");
387                 kb_format = _("%d.%02dKB");
388                 mb_format = _("%d.%02dMB");
389                 gb_format = _("%.2fGB");
390         }
391         
392         if (size < (goffset)1024) {
393                 g_snprintf(str, sizeof(str), b_format, (gint)size);
394                 return str;
395         } else if (size >> 10 < (goffset)1024) {
396                 divide(size, 10, t, r);
397                 g_snprintf(str, sizeof(str), kb_format, t, r);
398                 return str;
399         } else if (size >> 20 < (goffset)1024) {
400                 divide(size, 20, t, r);
401                 g_snprintf(str, sizeof(str), mb_format, t, r);
402                 return str;
403         } else {
404                 g_snprintf(str, sizeof(str), gb_format, (gfloat)(size >> 30));
405                 return str;
406         }
407 }
408
409 /* strcmp with NULL-checking */
410 gint strcmp2(const gchar *s1, const gchar *s2)
411 {
412         if (s1 == NULL || s2 == NULL)
413                 return -1;
414         else
415                 return strcmp(s1, s2);
416 }
417 /* strstr with NULL-checking */
418 gchar *strstr2(const gchar *s1, const gchar *s2)
419 {
420         if (s1 == NULL || s2 == NULL)
421                 return NULL;
422         else
423                 return strstr(s1, s2);
424 }
425 /* compare paths */
426 gint path_cmp(const gchar *s1, const gchar *s2)
427 {
428         gint len1, len2;
429         int rc;
430 #ifdef G_OS_WIN32
431         gchar *s1buf, *s2buf;
432 #endif
433
434         if (s1 == NULL || s2 == NULL) return -1;
435         if (*s1 == '\0' || *s2 == '\0') return -1;
436
437 #ifdef G_OS_WIN32
438         s1buf = g_strdup (s1);
439         s2buf = g_strdup (s2);
440         subst_char (s1buf, '/', G_DIR_SEPARATOR);
441         subst_char (s2buf, '/', G_DIR_SEPARATOR);
442         s1 = s1buf;
443         s2 = s2buf;
444 #endif /* !G_OS_WIN32 */
445
446         len1 = strlen(s1);
447         len2 = strlen(s2);
448
449         if (s1[len1 - 1] == G_DIR_SEPARATOR) len1--;
450         if (s2[len2 - 1] == G_DIR_SEPARATOR) len2--;
451
452         rc = strncmp(s1, s2, MAX(len1, len2));
453 #ifdef G_OS_WIN32
454         g_free (s1buf);
455         g_free (s2buf);
456 #endif /* !G_OS_WIN32 */
457         return rc;
458 }
459
460 /* remove trailing return code */
461 gchar *strretchomp(gchar *str)
462 {
463         register gchar *s;
464
465         if (!*str) return str;
466
467         for (s = str + strlen(str) - 1;
468              s >= str && (*s == '\n' || *s == '\r');
469              s--)
470                 *s = '\0';
471
472         return str;
473 }
474
475 /* remove trailing character */
476 gchar *strtailchomp(gchar *str, gchar tail_char)
477 {
478         register gchar *s;
479
480         if (!*str) return str;
481         if (tail_char == '\0') return str;
482
483         for (s = str + strlen(str) - 1; s >= str && *s == tail_char; s--)
484                 *s = '\0';
485
486         return str;
487 }
488
489 /* remove CR (carriage return) */
490 gchar *strcrchomp(gchar *str)
491 {
492         register gchar *s;
493
494         if (!*str) return str;
495
496         s = str + strlen(str) - 1;
497         if (*s == '\n' && s > str && *(s - 1) == '\r') {
498                 *(s - 1) = '\n';
499                 *s = '\0';
500         }
501
502         return str;
503 }
504
505 gint file_strip_crs(const gchar *file)
506 {
507         FILE *fp = NULL, *outfp = NULL;
508         gchar buf[4096];
509         gchar *out = get_tmp_file();
510         if (file == NULL)
511                 goto freeout;
512
513         fp = g_fopen(file, "rb");
514         if (!fp)
515                 goto freeout;
516
517         outfp = g_fopen(out, "wb");
518         if (!outfp) {
519                 fclose(fp);
520                 goto freeout;
521         }
522
523         while (fgets(buf, sizeof (buf), fp) != NULL) {
524                 strcrchomp(buf);
525                 if (fputs(buf, outfp) == EOF) {
526                         fclose(fp);
527                         fclose(outfp);
528                         goto unlinkout;
529                 }
530         }
531
532         fclose(fp);
533         if (fclose(outfp) == EOF) {
534                 goto unlinkout;
535         }
536         
537         if (move_file(out, file, TRUE) < 0)
538                 goto unlinkout;
539         
540         g_free(out);
541         return 0;
542 unlinkout:
543         claws_unlink(out);
544 freeout:
545         g_free(out);
546         return -1;
547 }
548
549 /* Similar to `strstr' but this function ignores the case of both strings.  */
550 gchar *strcasestr(const gchar *haystack, const gchar *needle)
551 {
552         size_t haystack_len = strlen(haystack);
553
554         return strncasestr(haystack, haystack_len, needle);
555 }
556
557 gchar *strncasestr(const gchar *haystack, gint haystack_len, const gchar *needle)
558 {
559         register size_t needle_len;
560
561         needle_len   = strlen(needle);
562
563         if (haystack_len < needle_len || needle_len == 0)
564                 return NULL;
565
566         while (haystack_len >= needle_len) {
567                 if (!g_ascii_strncasecmp(haystack, needle, needle_len))
568                         return (gchar *)haystack;
569                 else {
570                         haystack++;
571                         haystack_len--;
572                 }
573         }
574
575         return NULL;
576 }
577
578 gpointer my_memmem(gconstpointer haystack, size_t haystacklen,
579                    gconstpointer needle, size_t needlelen)
580 {
581         const gchar *haystack_ = (const gchar *)haystack;
582         const gchar *needle_ = (const gchar *)needle;
583         const gchar *haystack_cur = (const gchar *)haystack;
584         size_t haystack_left = haystacklen;
585
586         if (needlelen == 1)
587                 return memchr(haystack_, *needle_, haystacklen);
588
589         while ((haystack_cur = memchr(haystack_cur, *needle_, haystack_left))
590                != NULL) {
591                 if (haystacklen - (haystack_cur - haystack_) < needlelen)
592                         break;
593                 if (memcmp(haystack_cur + 1, needle_ + 1, needlelen - 1) == 0)
594                         return (gpointer)haystack_cur;
595                 else{
596                         haystack_cur++;
597                         haystack_left = haystacklen - (haystack_cur - haystack_);
598                 }
599         }
600
601         return NULL;
602 }
603
604 /* Copy no more than N characters of SRC to DEST, with NULL terminating.  */
605 gchar *strncpy2(gchar *dest, const gchar *src, size_t n)
606 {
607         register const gchar *s = src;
608         register gchar *d = dest;
609
610         while (--n && *s)
611                 *d++ = *s++;
612         *d = '\0';
613
614         return dest;
615 }
616
617
618 /* Examine if next block is non-ASCII string */
619 gboolean is_next_nonascii(const gchar *s)
620 {
621         const gchar *p;
622
623         /* skip head space */
624         for (p = s; *p != '\0' && g_ascii_isspace(*p); p++)
625                 ;
626         for (; *p != '\0' && !g_ascii_isspace(*p); p++) {
627                 if (*(guchar *)p > 127 || *(guchar *)p < 32)
628                         return TRUE;
629         }
630
631         return FALSE;
632 }
633
634 gint get_next_word_len(const gchar *s)
635 {
636         gint len = 0;
637
638         for (; *s != '\0' && !g_ascii_isspace(*s); s++, len++)
639                 ;
640
641         return len;
642 }
643
644 static void trim_subject_for_compare(gchar *str)
645 {
646         gchar *srcp;
647
648         eliminate_parenthesis(str, '[', ']');
649         eliminate_parenthesis(str, '(', ')');
650         g_strstrip(str);
651
652         srcp = str + subject_get_prefix_length(str);
653         if (srcp != str)
654                 memmove(str, srcp, strlen(srcp) + 1);
655 }
656
657 static void trim_subject_for_sort(gchar *str)
658 {
659         gchar *srcp;
660
661         g_strstrip(str);
662
663         srcp = str + subject_get_prefix_length(str);
664         if (srcp != str)
665                 memmove(str, srcp, strlen(srcp) + 1);
666 }
667
668 /* compare subjects */
669 gint subject_compare(const gchar *s1, const gchar *s2)
670 {
671         gchar *str1, *str2;
672
673         if (!s1 || !s2) return -1;
674         if (!*s1 || !*s2) return -1;
675
676         Xstrdup_a(str1, s1, return -1);
677         Xstrdup_a(str2, s2, return -1);
678
679         trim_subject_for_compare(str1);
680         trim_subject_for_compare(str2);
681
682         if (!*str1 || !*str2) return -1;
683
684         return strcmp(str1, str2);
685 }
686
687 gint subject_compare_for_sort(const gchar *s1, const gchar *s2)
688 {
689         gchar *str1, *str2;
690
691         if (!s1 || !s2) return -1;
692
693         Xstrdup_a(str1, s1, return -1);
694         Xstrdup_a(str2, s2, return -1);
695
696         trim_subject_for_sort(str1);
697         trim_subject_for_sort(str2);
698
699         return g_utf8_collate(str1, str2);
700 }
701
702 void trim_subject(gchar *str)
703 {
704         register gchar *srcp;
705         gchar op, cl;
706         gint in_brace;
707
708         g_strstrip(str);
709
710         srcp = str + subject_get_prefix_length(str);
711
712         if (*srcp == '[') {
713                 op = '[';
714                 cl = ']';
715         } else if (*srcp == '(') {
716                 op = '(';
717                 cl = ')';
718         } else
719                 op = 0;
720
721         if (op) {
722                 ++srcp;
723                 in_brace = 1;
724                 while (*srcp) {
725                         if (*srcp == op)
726                                 in_brace++;
727                         else if (*srcp == cl)
728                                 in_brace--;
729                         srcp++;
730                         if (in_brace == 0)
731                                 break;
732                 }
733         }
734         while (g_ascii_isspace(*srcp)) srcp++;
735         memmove(str, srcp, strlen(srcp) + 1);
736 }
737
738 void eliminate_parenthesis(gchar *str, gchar op, gchar cl)
739 {
740         register gchar *srcp, *destp;
741         gint in_brace;
742
743         destp = str;
744
745         while ((destp = strchr(destp, op))) {
746                 in_brace = 1;
747                 srcp = destp + 1;
748                 while (*srcp) {
749                         if (*srcp == op)
750                                 in_brace++;
751                         else if (*srcp == cl)
752                                 in_brace--;
753                         srcp++;
754                         if (in_brace == 0)
755                                 break;
756                 }
757                 while (g_ascii_isspace(*srcp)) srcp++;
758                 memmove(destp, srcp, strlen(srcp) + 1);
759         }
760 }
761
762 void extract_parenthesis(gchar *str, gchar op, gchar cl)
763 {
764         register gchar *srcp, *destp;
765         gint in_brace;
766
767         destp = str;
768
769         while ((srcp = strchr(destp, op))) {
770                 if (destp > str)
771                         *destp++ = ' ';
772                 memmove(destp, srcp + 1, strlen(srcp));
773                 in_brace = 1;
774                 while(*destp) {
775                         if (*destp == op)
776                                 in_brace++;
777                         else if (*destp == cl)
778                                 in_brace--;
779
780                         if (in_brace == 0)
781                                 break;
782
783                         destp++;
784                 }
785         }
786         *destp = '\0';
787 }
788
789 static void extract_parenthesis_with_skip_quote(gchar *str, gchar quote_chr,
790                                          gchar op, gchar cl)
791 {
792         register gchar *srcp, *destp;
793         gint in_brace;
794         gboolean in_quote = FALSE;
795
796         destp = str;
797
798         while ((srcp = strchr_with_skip_quote(destp, quote_chr, op))) {
799                 if (destp > str)
800                         *destp++ = ' ';
801                 memmove(destp, srcp + 1, strlen(srcp));
802                 in_brace = 1;
803                 while(*destp) {
804                         if (*destp == op && !in_quote)
805                                 in_brace++;
806                         else if (*destp == cl && !in_quote)
807                                 in_brace--;
808                         else if (*destp == quote_chr)
809                                 in_quote ^= TRUE;
810
811                         if (in_brace == 0)
812                                 break;
813
814                         destp++;
815                 }
816         }
817         *destp = '\0';
818 }
819
820 void extract_quote(gchar *str, gchar quote_chr)
821 {
822         register gchar *p;
823
824         if ((str = strchr(str, quote_chr))) {
825                 p = str;
826                 while ((p = strchr(p + 1, quote_chr)) && (p[-1] == '\\')) {
827                         memmove(p - 1, p, strlen(p) + 1);
828                         p--;
829                 }
830                 if(p) {
831                         *p = '\0';
832                         memmove(str, str + 1, p - str);
833                 }
834         }
835 }
836
837 /* Returns a newly allocated string with all quote_chr not at the beginning
838    or the end of str escaped with '\' or the given str if not required. */
839 gchar *escape_internal_quotes(gchar *str, gchar quote_chr)
840 {
841         register gchar *p, *q;
842         gchar *qstr;
843         int k = 0, l = 0;
844
845         if (str == NULL || *str == '\0')
846                 return str;
847
848         /* search for unescaped quote_chr */
849         p = str;
850         if (*p == quote_chr)
851                 ++p, ++l;
852         while (*p) {
853                 if (*p == quote_chr && *(p - 1) != '\\' && *(p + 1) != '\0')
854                         ++k;
855                 ++p, ++l;
856         }
857         if (!k) /* nothing to escape */
858                 return str;
859
860         /* unescaped quote_chr found */
861         qstr = g_malloc(l + k + 1);
862         p = str;
863         q = qstr;
864         if (*p == quote_chr) {
865                 *q = quote_chr;
866                 ++p, ++q;
867         }
868         while (*p) {
869                 if (*p == quote_chr && *(p - 1) != '\\' && *(p + 1) != '\0')
870                         *q++ = '\\';
871                 *q++ = *p++;
872         }
873         *q = '\0';
874
875         return qstr;
876 }
877
878 void eliminate_address_comment(gchar *str)
879 {
880         register gchar *srcp, *destp;
881         gint in_brace;
882
883         destp = str;
884
885         while ((destp = strchr(destp, '"'))) {
886                 if ((srcp = strchr(destp + 1, '"'))) {
887                         srcp++;
888                         if (*srcp == '@') {
889                                 destp = srcp + 1;
890                         } else {
891                                 while (g_ascii_isspace(*srcp)) srcp++;
892                                 memmove(destp, srcp, strlen(srcp) + 1);
893                         }
894                 } else {
895                         *destp = '\0';
896                         break;
897                 }
898         }
899
900         destp = str;
901
902         while ((destp = strchr_with_skip_quote(destp, '"', '('))) {
903                 in_brace = 1;
904                 srcp = destp + 1;
905                 while (*srcp) {
906                         if (*srcp == '(')
907                                 in_brace++;
908                         else if (*srcp == ')')
909                                 in_brace--;
910                         srcp++;
911                         if (in_brace == 0)
912                                 break;
913                 }
914                 while (g_ascii_isspace(*srcp)) srcp++;
915                 memmove(destp, srcp, strlen(srcp) + 1);
916         }
917 }
918
919 gchar *strchr_with_skip_quote(const gchar *str, gint quote_chr, gint c)
920 {
921         gboolean in_quote = FALSE;
922
923         while (*str) {
924                 if (*str == c && !in_quote)
925                         return (gchar *)str;
926                 if (*str == quote_chr)
927                         in_quote ^= TRUE;
928                 str++;
929         }
930
931         return NULL;
932 }
933
934 void extract_address(gchar *str)
935 {
936         cm_return_if_fail(str != NULL);
937         eliminate_address_comment(str);
938         if (strchr_with_skip_quote(str, '"', '<'))
939                 extract_parenthesis_with_skip_quote(str, '"', '<', '>');
940         g_strstrip(str);
941 }
942
943 void extract_list_id_str(gchar *str)
944 {
945         if (strchr_with_skip_quote(str, '"', '<'))
946                 extract_parenthesis_with_skip_quote(str, '"', '<', '>');
947         g_strstrip(str);
948 }
949
950 static GSList *address_list_append_real(GSList *addr_list, const gchar *str, gboolean removecomments)
951 {
952         gchar *work;
953         gchar *workp;
954
955         if (!str) return addr_list;
956
957         Xstrdup_a(work, str, return addr_list);
958
959         if (removecomments)
960                 eliminate_address_comment(work);
961         workp = work;
962
963         while (workp && *workp) {
964                 gchar *p, *next;
965
966                 if ((p = strchr_with_skip_quote(workp, '"', ','))) {
967                         *p = '\0';
968                         next = p + 1;
969                 } else
970                         next = NULL;
971
972                 if (removecomments && strchr_with_skip_quote(workp, '"', '<'))
973                         extract_parenthesis_with_skip_quote
974                                 (workp, '"', '<', '>');
975
976                 g_strstrip(workp);
977                 if (*workp)
978                         addr_list = g_slist_append(addr_list, g_strdup(workp));
979
980                 workp = next;
981         }
982
983         return addr_list;
984 }
985
986 GSList *address_list_append(GSList *addr_list, const gchar *str)
987 {
988         return address_list_append_real(addr_list, str, TRUE);
989 }
990
991 GSList *address_list_append_with_comments(GSList *addr_list, const gchar *str)
992 {
993         return address_list_append_real(addr_list, str, FALSE);
994 }
995
996 GSList *references_list_prepend(GSList *msgid_list, const gchar *str)
997 {
998         const gchar *strp;
999
1000         if (!str) return msgid_list;
1001         strp = str;
1002
1003         while (strp && *strp) {
1004                 const gchar *start, *end;
1005                 gchar *msgid;
1006
1007                 if ((start = strchr(strp, '<')) != NULL) {
1008                         end = strchr(start + 1, '>');
1009                         if (!end) break;
1010                 } else
1011                         break;
1012
1013                 msgid = g_strndup(start + 1, end - start - 1);
1014                 g_strstrip(msgid);
1015                 if (*msgid)
1016                         msgid_list = g_slist_prepend(msgid_list, msgid);
1017                 else
1018                         g_free(msgid);
1019
1020                 strp = end + 1;
1021         }
1022
1023         return msgid_list;
1024 }
1025
1026 GSList *references_list_append(GSList *msgid_list, const gchar *str)
1027 {
1028         GSList *list;
1029
1030         list = references_list_prepend(NULL, str);
1031         list = g_slist_reverse(list);
1032         msgid_list = g_slist_concat(msgid_list, list);
1033
1034         return msgid_list;
1035 }
1036
1037 GSList *newsgroup_list_append(GSList *group_list, const gchar *str)
1038 {
1039         gchar *work;
1040         gchar *workp;
1041
1042         if (!str) return group_list;
1043
1044         Xstrdup_a(work, str, return group_list);
1045
1046         workp = work;
1047
1048         while (workp && *workp) {
1049                 gchar *p, *next;
1050
1051                 if ((p = strchr_with_skip_quote(workp, '"', ','))) {
1052                         *p = '\0';
1053                         next = p + 1;
1054                 } else
1055                         next = NULL;
1056
1057                 g_strstrip(workp);
1058                 if (*workp)
1059                         group_list = g_slist_append(group_list,
1060                                                     g_strdup(workp));
1061
1062                 workp = next;
1063         }
1064
1065         return group_list;
1066 }
1067
1068 GList *add_history(GList *list, const gchar *str)
1069 {
1070         GList *old;
1071         gchar *oldstr;
1072
1073         cm_return_val_if_fail(str != NULL, list);
1074
1075         old = g_list_find_custom(list, (gpointer)str, (GCompareFunc)strcmp2);
1076         if (old) {
1077                 oldstr = old->data;
1078                 list = g_list_remove(list, old->data);
1079                 g_free(oldstr);
1080         } else if (g_list_length(list) >= MAX_HISTORY_SIZE) {
1081                 GList *last;
1082
1083                 last = g_list_last(list);
1084                 if (last) {
1085                         oldstr = last->data;
1086                         list = g_list_remove(list, last->data);
1087                         g_free(oldstr);
1088                 }
1089         }
1090
1091         list = g_list_prepend(list, g_strdup(str));
1092
1093         return list;
1094 }
1095
1096 void remove_return(gchar *str)
1097 {
1098         register gchar *p = str;
1099
1100         while (*p) {
1101                 if (*p == '\n' || *p == '\r')
1102                         memmove(p, p + 1, strlen(p));
1103                 else
1104                         p++;
1105         }
1106 }
1107
1108 void remove_space(gchar *str)
1109 {
1110         register gchar *p = str;
1111         register gint spc;
1112
1113         while (*p) {
1114                 spc = 0;
1115                 while (g_ascii_isspace(*(p + spc)))
1116                         spc++;
1117                 if (spc)
1118                         memmove(p, p + spc, strlen(p + spc) + 1);
1119                 else
1120                         p++;
1121         }
1122 }
1123
1124 void unfold_line(gchar *str)
1125 {
1126         register gchar *p = str;
1127         register gint spc;
1128
1129         while (*p) {
1130                 if (*p == '\n' || *p == '\r') {
1131                         *p++ = ' ';
1132                         spc = 0;
1133                         while (g_ascii_isspace(*(p + spc)))
1134                                 spc++;
1135                         if (spc)
1136                                 memmove(p, p + spc, strlen(p + spc) + 1);
1137                 } else
1138                         p++;
1139         }
1140 }
1141
1142 void subst_char(gchar *str, gchar orig, gchar subst)
1143 {
1144         register gchar *p = str;
1145
1146         while (*p) {
1147                 if (*p == orig)
1148                         *p = subst;
1149                 p++;
1150         }
1151 }
1152
1153 void subst_chars(gchar *str, gchar *orig, gchar subst)
1154 {
1155         register gchar *p = str;
1156
1157         while (*p) {
1158                 if (strchr(orig, *p) != NULL)
1159                         *p = subst;
1160                 p++;
1161         }
1162 }
1163
1164 void subst_for_filename(gchar *str)
1165 {
1166         if (!str)
1167                 return;
1168 #ifdef G_OS_WIN32
1169         subst_chars(str, "\t\r\n\\/*:", '_');
1170 #else
1171         subst_chars(str, "\t\r\n\\/*", '_');
1172 #endif
1173 }
1174
1175 void subst_for_shellsafe_filename(gchar *str)
1176 {
1177         if (!str)
1178                 return;
1179         subst_for_filename(str);
1180         subst_chars(str, " \"'|&;()<>'!{}[]",'_');
1181 }
1182
1183 gboolean is_ascii_str(const gchar *str)
1184 {
1185         const guchar *p = (const guchar *)str;
1186
1187         while (*p != '\0') {
1188                 if (*p != '\t' && *p != ' ' &&
1189                     *p != '\r' && *p != '\n' &&
1190                     (*p < 32 || *p >= 127))
1191                         return FALSE;
1192                 p++;
1193         }
1194
1195         return TRUE;
1196 }
1197
1198 static const gchar * line_has_quote_char_last(const gchar * str, const gchar *quote_chars)
1199 {
1200         gchar * position = NULL;
1201         gchar * tmp_pos = NULL;
1202         int i;
1203
1204         if (quote_chars == NULL)
1205                 return NULL;
1206
1207         for (i = 0; i < strlen(quote_chars); i++) {
1208                 tmp_pos = strrchr (str, quote_chars[i]);
1209                 if(position == NULL
1210                    || (tmp_pos != NULL && position <= tmp_pos) )
1211                         position = tmp_pos;
1212         }
1213         return position;
1214 }
1215
1216 gint get_quote_level(const gchar *str, const gchar *quote_chars)
1217 {
1218         const gchar *first_pos;
1219         const gchar *last_pos;
1220         const gchar *p = str;
1221         gint quote_level = -1;
1222
1223         /* speed up line processing by only searching to the last '>' */
1224         if ((first_pos = line_has_quote_char(str, quote_chars)) != NULL) {
1225                 /* skip a line if it contains a '<' before the initial '>' */
1226                 if (memchr(str, '<', first_pos - str) != NULL)
1227                         return -1;
1228                 last_pos = line_has_quote_char_last(first_pos, quote_chars);
1229         } else
1230                 return -1;
1231
1232         while (p <= last_pos) {
1233                 while (p < last_pos) {
1234                         if (g_ascii_isspace(*p))
1235                                 p++;
1236                         else
1237                                 break;
1238                 }
1239
1240                 if (strchr(quote_chars, *p))
1241                         quote_level++;
1242                 else if (*p != '-' && !g_ascii_isspace(*p) && p <= last_pos) {
1243                         /* any characters are allowed except '-','<' and space */
1244                         while (*p != '-' && *p != '<'
1245                                && !strchr(quote_chars, *p)
1246                                && !g_ascii_isspace(*p)
1247                                && p < last_pos)
1248                                 p++;
1249                         if (strchr(quote_chars, *p))
1250                                 quote_level++;
1251                         else
1252                                 break;
1253                 }
1254
1255                 p++;
1256         }
1257
1258         return quote_level;
1259 }
1260
1261 gint check_line_length(const gchar *str, gint max_chars, gint *line)
1262 {
1263         const gchar *p = str, *q;
1264         gint cur_line = 0, len;
1265
1266         while ((q = strchr(p, '\n')) != NULL) {
1267                 len = q - p + 1;
1268                 if (len > max_chars) {
1269                         if (line)
1270                                 *line = cur_line;
1271                         return -1;
1272                 }
1273                 p = q + 1;
1274                 ++cur_line;
1275         }
1276
1277         len = strlen(p);
1278         if (len > max_chars) {
1279                 if (line)
1280                         *line = cur_line;
1281                 return -1;
1282         }
1283
1284         return 0;
1285 }
1286
1287 const gchar * line_has_quote_char(const gchar * str, const gchar *quote_chars)
1288 {
1289         gchar * position = NULL;
1290         gchar * tmp_pos = NULL;
1291         int i;
1292
1293         if (quote_chars == NULL)
1294                 return FALSE;
1295
1296         for (i = 0; i < strlen(quote_chars); i++) {
1297                 tmp_pos = strchr (str,  quote_chars[i]);
1298                 if(position == NULL
1299                    || (tmp_pos != NULL && position >= tmp_pos) )
1300                         position = tmp_pos;
1301         }
1302         return position;
1303 }
1304
1305 static gchar *strstr_with_skip_quote(const gchar *haystack, const gchar *needle)
1306 {
1307         register guint haystack_len, needle_len;
1308         gboolean in_squote = FALSE, in_dquote = FALSE;
1309
1310         haystack_len = strlen(haystack);
1311         needle_len   = strlen(needle);
1312
1313         if (haystack_len < needle_len || needle_len == 0)
1314                 return NULL;
1315
1316         while (haystack_len >= needle_len) {
1317                 if (!in_squote && !in_dquote &&
1318                     !strncmp(haystack, needle, needle_len))
1319                         return (gchar *)haystack;
1320
1321                 /* 'foo"bar"' -> foo"bar"
1322                    "foo'bar'" -> foo'bar' */
1323                 if (*haystack == '\'') {
1324                         if (in_squote)
1325                                 in_squote = FALSE;
1326                         else if (!in_dquote)
1327                                 in_squote = TRUE;
1328                 } else if (*haystack == '\"') {
1329                         if (in_dquote)
1330                                 in_dquote = FALSE;
1331                         else if (!in_squote)
1332                                 in_dquote = TRUE;
1333                 } else if (*haystack == '\\') {
1334                         haystack++;
1335                         haystack_len--;
1336                 }
1337
1338                 haystack++;
1339                 haystack_len--;
1340         }
1341
1342         return NULL;
1343 }
1344
1345 gchar **strsplit_with_quote(const gchar *str, const gchar *delim,
1346                             gint max_tokens)
1347 {
1348         GSList *string_list = NULL, *slist;
1349         gchar **str_array, *s, *new_str;
1350         guint i, n = 1, len;
1351
1352         cm_return_val_if_fail(str != NULL, NULL);
1353         cm_return_val_if_fail(delim != NULL, NULL);
1354
1355         if (max_tokens < 1)
1356                 max_tokens = G_MAXINT;
1357
1358         s = strstr_with_skip_quote(str, delim);
1359         if (s) {
1360                 guint delimiter_len = strlen(delim);
1361
1362                 do {
1363                         len = s - str;
1364                         new_str = g_strndup(str, len);
1365
1366                         if (new_str[0] == '\'' || new_str[0] == '\"') {
1367                                 if (new_str[len - 1] == new_str[0]) {
1368                                         new_str[len - 1] = '\0';
1369                                         memmove(new_str, new_str + 1, len - 1);
1370                                 }
1371                         }
1372                         string_list = g_slist_prepend(string_list, new_str);
1373                         n++;
1374                         str = s + delimiter_len;
1375                         s = strstr_with_skip_quote(str, delim);
1376                 } while (--max_tokens && s);
1377         }
1378
1379         if (*str) {
1380                 new_str = g_strdup(str);
1381                 if (new_str[0] == '\'' || new_str[0] == '\"') {
1382                         len = strlen(str);
1383                         if (new_str[len - 1] == new_str[0]) {
1384                                 new_str[len - 1] = '\0';
1385                                 memmove(new_str, new_str + 1, len - 1);
1386                         }
1387                 }
1388                 string_list = g_slist_prepend(string_list, new_str);
1389                 n++;
1390         }
1391
1392         str_array = g_new(gchar*, n);
1393
1394         i = n - 1;
1395
1396         str_array[i--] = NULL;
1397         for (slist = string_list; slist; slist = slist->next)
1398                 str_array[i--] = slist->data;
1399
1400         g_slist_free(string_list);
1401
1402         return str_array;
1403 }
1404
1405 gchar *get_abbrev_newsgroup_name(const gchar *group, gint len)
1406 {
1407         gchar *abbrev_group;
1408         gchar *ap;
1409         const gchar *p = group;
1410         const gchar *last;
1411
1412         cm_return_val_if_fail(group != NULL, NULL);
1413
1414         last = group + strlen(group);
1415         abbrev_group = ap = g_malloc(strlen(group) + 1);
1416
1417         while (*p) {
1418                 while (*p == '.')
1419                         *ap++ = *p++;
1420                 if ((ap - abbrev_group) + (last - p) > len && strchr(p, '.')) {
1421                         *ap++ = *p++;
1422                         while (*p != '.') p++;
1423                 } else {
1424                         strcpy(ap, p);
1425                         return abbrev_group;
1426                 }
1427         }
1428
1429         *ap = '\0';
1430         return abbrev_group;
1431 }
1432
1433 gchar *trim_string(const gchar *str, gint len)
1434 {
1435         const gchar *p = str;
1436         gint mb_len;
1437         gchar *new_str;
1438         gint new_len = 0;
1439
1440         if (!str) return NULL;
1441         if (strlen(str) <= len)
1442                 return g_strdup(str);
1443         if (g_utf8_validate(str, -1, NULL) == FALSE)
1444                 return g_strdup(str);
1445
1446         while (*p != '\0') {
1447                 mb_len = g_utf8_skip[*(guchar *)p];
1448                 if (mb_len == 0)
1449                         break;
1450                 else if (new_len + mb_len > len)
1451                         break;
1452
1453                 new_len += mb_len;
1454                 p += mb_len;
1455         }
1456
1457         Xstrndup_a(new_str, str, new_len, return g_strdup(str));
1458         return g_strconcat(new_str, "...", NULL);
1459 }
1460
1461 GList *uri_list_extract_filenames(const gchar *uri_list)
1462 {
1463         GList *result = NULL;
1464         const gchar *p, *q;
1465         gchar *escaped_utf8uri;
1466
1467         p = uri_list;
1468
1469         while (p) {
1470                 if (*p != '#') {
1471                         while (g_ascii_isspace(*p)) p++;
1472                         if (!strncmp(p, "file:", 5)) {
1473                                 q = p;
1474                                 q += 5;
1475                                 while (*q && *q != '\n' && *q != '\r') q++;
1476
1477                                 if (q > p) {
1478                                         gchar *file, *locale_file = NULL;
1479                                         q--;
1480                                         while (q > p && g_ascii_isspace(*q))
1481                                                 q--;
1482                                         Xalloca(escaped_utf8uri, q - p + 2,
1483                                                 return result);
1484                                         Xalloca(file, q - p + 2,
1485                                                 return result);
1486                                         *file = '\0';
1487                                         strncpy(escaped_utf8uri, p, q - p + 1);
1488                                         escaped_utf8uri[q - p + 1] = '\0';
1489                                         decode_uri(file, escaped_utf8uri);
1490                     /*
1491                      * g_filename_from_uri() rejects escaped/locale encoded uri
1492                      * string which come from Nautilus.
1493                      */
1494 #ifndef G_OS_WIN32
1495                                         if (g_utf8_validate(file, -1, NULL))
1496                                                 locale_file
1497                                                         = conv_codeset_strdup(
1498                                                                 file + 5,
1499                                                                 CS_UTF_8,
1500                                                                 conv_get_locale_charset_str());
1501                                         if (!locale_file)
1502                                                 locale_file = g_strdup(file + 5);
1503 #else
1504                                         locale_file = g_filename_from_uri(file, NULL, NULL);
1505 #endif
1506                                         result = g_list_append(result, locale_file);
1507                                 }
1508                         }
1509                 }
1510                 p = strchr(p, '\n');
1511                 if (p) p++;
1512         }
1513
1514         return result;
1515 }
1516
1517 /* Converts two-digit hexadecimal to decimal.  Used for unescaping escaped
1518  * characters
1519  */
1520 static gint axtoi(const gchar *hexstr)
1521 {
1522         gint hi, lo, result;
1523
1524         hi = hexstr[0];
1525         if ('0' <= hi && hi <= '9') {
1526                 hi -= '0';
1527         } else
1528                 if ('a' <= hi && hi <= 'f') {
1529                         hi -= ('a' - 10);
1530                 } else
1531                         if ('A' <= hi && hi <= 'F') {
1532                                 hi -= ('A' - 10);
1533                         }
1534
1535         lo = hexstr[1];
1536         if ('0' <= lo && lo <= '9') {
1537                 lo -= '0';
1538         } else
1539                 if ('a' <= lo && lo <= 'f') {
1540                         lo -= ('a'-10);
1541                 } else
1542                         if ('A' <= lo && lo <= 'F') {
1543                                 lo -= ('A' - 10);
1544                         }
1545         result = lo + (16 * hi);
1546         return result;
1547 }
1548
1549 gboolean is_uri_string(const gchar *str)
1550 {
1551         while (str && *str && g_ascii_isspace(*str))
1552                 str++;
1553         return (g_ascii_strncasecmp(str, "http://", 7) == 0 ||
1554                 g_ascii_strncasecmp(str, "https://", 8) == 0 ||
1555                 g_ascii_strncasecmp(str, "ftp://", 6) == 0 ||
1556                 g_ascii_strncasecmp(str, "www.", 4) == 0);
1557 }
1558
1559 gchar *get_uri_path(const gchar *uri)
1560 {
1561         while (uri && *uri && g_ascii_isspace(*uri))
1562                 uri++;
1563         if (g_ascii_strncasecmp(uri, "http://", 7) == 0)
1564                 return (gchar *)(uri + 7);
1565         else if (g_ascii_strncasecmp(uri, "https://", 8) == 0)
1566                 return (gchar *)(uri + 8);
1567         else if (g_ascii_strncasecmp(uri, "ftp://", 6) == 0)
1568                 return (gchar *)(uri + 6);
1569         else
1570                 return (gchar *)uri;
1571 }
1572
1573 gint get_uri_len(const gchar *str)
1574 {
1575         const gchar *p;
1576
1577         if (is_uri_string(str)) {
1578                 for (p = str; *p != '\0'; p++) {
1579                         if (!g_ascii_isgraph(*p) || strchr("()<>\"", *p))
1580                                 break;
1581                 }
1582                 return p - str;
1583         }
1584
1585         return 0;
1586 }
1587
1588 /* Decodes URL-Encoded strings (i.e. strings in which spaces are replaced by
1589  * plusses, and escape characters are used)
1590  */
1591 void decode_uri_with_plus(gchar *decoded_uri, const gchar *encoded_uri, gboolean with_plus)
1592 {
1593         gchar *dec = decoded_uri;
1594         const gchar *enc = encoded_uri;
1595
1596         while (*enc) {
1597                 if (*enc == '%') {
1598                         enc++;
1599                         if (isxdigit((guchar)enc[0]) &&
1600                             isxdigit((guchar)enc[1])) {
1601                                 *dec = axtoi(enc);
1602                                 dec++;
1603                                 enc += 2;
1604                         }
1605                 } else {
1606                         if (with_plus && *enc == '+')
1607                                 *dec = ' ';
1608                         else
1609                                 *dec = *enc;
1610                         dec++;
1611                         enc++;
1612                 }
1613         }
1614
1615         *dec = '\0';
1616 }
1617
1618 void decode_uri(gchar *decoded_uri, const gchar *encoded_uri)
1619 {
1620         decode_uri_with_plus(decoded_uri, encoded_uri, TRUE);
1621 }
1622
1623 static gchar *decode_uri_gdup(const gchar *encoded_uri)
1624 {
1625     gchar *buffer = g_malloc(strlen(encoded_uri)+1);
1626     decode_uri_with_plus(buffer, encoded_uri, FALSE);
1627     return buffer;
1628 }
1629
1630 gint scan_mailto_url(const gchar *mailto, gchar **from, gchar **to, gchar **cc, gchar **bcc,
1631                      gchar **subject, gchar **body, gchar ***attach, gchar **inreplyto)
1632 {
1633         gchar *tmp_mailto;
1634         gchar *p;
1635         const gchar *forbidden_uris[] = { ".gnupg/",
1636                                           "/etc/passwd",
1637                                           "/etc/shadow",
1638                                           ".ssh/",
1639                                           "../",
1640                                           NULL };
1641         gint num_attach = 0;
1642         gchar **my_att = NULL;
1643
1644         Xstrdup_a(tmp_mailto, mailto, return -1);
1645
1646         if (!strncmp(tmp_mailto, "mailto:", 7))
1647                 tmp_mailto += 7;
1648
1649         p = strchr(tmp_mailto, '?');
1650         if (p) {
1651                 *p = '\0';
1652                 p++;
1653         }
1654
1655         if (to && !*to)
1656                 *to = decode_uri_gdup(tmp_mailto);
1657
1658         my_att = g_malloc(sizeof(char *));
1659         my_att[0] = NULL;
1660
1661         while (p) {
1662                 gchar *field, *value;
1663
1664                 field = p;
1665
1666                 p = strchr(p, '=');
1667                 if (!p) break;
1668                 *p = '\0';
1669                 p++;
1670
1671                 value = p;
1672
1673                 p = strchr(p, '&');
1674                 if (p) {
1675                         *p = '\0';
1676                         p++;
1677                 }
1678
1679                 if (*value == '\0') continue;
1680
1681                 if (from && !g_ascii_strcasecmp(field, "from")) {
1682                         if (!*from) {
1683                                 *from = decode_uri_gdup(value);
1684                         } else {
1685                                 gchar *tmp = decode_uri_gdup(value);
1686                                 gchar *new_from = g_strdup_printf("%s, %s", *from, tmp);
1687                                 g_free(*from);
1688                                 *from = new_from;
1689                         }
1690                 } else if (cc && !g_ascii_strcasecmp(field, "cc")) {
1691                         if (!*cc) {
1692                                 *cc = decode_uri_gdup(value);
1693                         } else {
1694                                 gchar *tmp = decode_uri_gdup(value);
1695                                 gchar *new_cc = g_strdup_printf("%s, %s", *cc, tmp);
1696                                 g_free(*cc);
1697                                 *cc = new_cc;
1698                         }
1699                 } else if (bcc && !g_ascii_strcasecmp(field, "bcc")) {
1700                         if (!*bcc) {
1701                                 *bcc = decode_uri_gdup(value);
1702                         } else {
1703                                 gchar *tmp = decode_uri_gdup(value);
1704                                 gchar *new_bcc = g_strdup_printf("%s, %s", *bcc, tmp);
1705                                 g_free(*bcc);
1706                                 *bcc = new_bcc;
1707                         }
1708                 } else if (subject && !*subject &&
1709                            !g_ascii_strcasecmp(field, "subject")) {
1710                         *subject = decode_uri_gdup(value);
1711                 } else if (body && !*body && !g_ascii_strcasecmp(field, "body")) {
1712                         *body = decode_uri_gdup(value);
1713                 } else if (body && !*body && !g_ascii_strcasecmp(field, "insert")) {
1714                         gchar *tmp = decode_uri_gdup(value);
1715                         if (!g_file_get_contents(tmp, body, NULL, NULL)) {
1716                                 g_warning("couldn't set insert file '%s' in body", value);
1717                         }
1718                         g_free(tmp);
1719                         tmp = NULL;
1720                 } else if (attach && !g_ascii_strcasecmp(field, "attach")) {
1721                         int i = 0;
1722                         gchar *tmp = decode_uri_gdup(value);
1723                         for (; forbidden_uris[i]; i++) {
1724                                 if (strstr(tmp, forbidden_uris[i])) {
1725                                         g_print("Refusing to attach '%s', potential private data leak\n",
1726                                                         tmp);
1727                                         g_free(tmp);
1728                                         tmp = NULL;
1729                                         break;
1730                                 }
1731                         }
1732                         if (tmp) {
1733                                 /* attach is correct */
1734                                 num_attach++;
1735                                 my_att = g_realloc(my_att, (sizeof(char *))*(num_attach+1));
1736                                 my_att[num_attach-1] = tmp;
1737                                 my_att[num_attach] = NULL;
1738                         }
1739                 } else if (inreplyto && !*inreplyto &&
1740                            !g_ascii_strcasecmp(field, "in-reply-to")) {
1741                         *inreplyto = decode_uri_gdup(value);
1742                 }
1743         }
1744
1745         if (attach)
1746                 *attach = my_att;
1747         return 0;
1748 }
1749
1750
1751 #ifdef G_OS_WIN32
1752 #include <windows.h>
1753 #ifndef CSIDL_APPDATA
1754 #define CSIDL_APPDATA 0x001a
1755 #endif
1756 #ifndef CSIDL_LOCAL_APPDATA
1757 #define CSIDL_LOCAL_APPDATA 0x001c
1758 #endif
1759 #ifndef CSIDL_FLAG_CREATE
1760 #define CSIDL_FLAG_CREATE 0x8000
1761 #endif
1762 #define DIM(v)               (sizeof(v)/sizeof((v)[0]))
1763
1764 #define RTLD_LAZY 0
1765 const char *
1766 w32_strerror (int w32_errno)
1767 {
1768   static char strerr[256];
1769   int ec = (int)GetLastError ();
1770
1771   if (w32_errno == 0)
1772     w32_errno = ec;
1773   FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM, NULL, w32_errno,
1774                  MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
1775                  strerr, DIM (strerr)-1, NULL);
1776   return strerr;
1777 }
1778
1779 static __inline__ void *
1780 dlopen (const char * name, int flag)
1781 {
1782   void * hd = LoadLibrary (name);
1783   return hd;
1784 }
1785
1786 static __inline__ void *
1787 dlsym (void * hd, const char * sym)
1788 {
1789   if (hd && sym)
1790     {
1791       void * fnc = GetProcAddress (hd, sym);
1792       if (!fnc)
1793         return NULL;
1794       return fnc;
1795     }
1796   return NULL;
1797 }
1798
1799
1800 static __inline__ const char *
1801 dlerror (void)
1802 {
1803   return w32_strerror (0);
1804 }
1805
1806
1807 static __inline__ int
1808 dlclose (void * hd)
1809 {
1810   if (hd)
1811     {
1812       FreeLibrary (hd);
1813       return 0;
1814     }
1815   return -1;
1816 }
1817
1818 static HRESULT
1819 w32_shgetfolderpath (HWND a, int b, HANDLE c, DWORD d, LPSTR e)
1820 {
1821   static int initialized;
1822   static HRESULT (WINAPI * func)(HWND,int,HANDLE,DWORD,LPSTR);
1823
1824   if (!initialized)
1825     {
1826       static char *dllnames[] = { "shell32.dll", "shfolder.dll", NULL };
1827       void *handle;
1828       int i;
1829
1830       initialized = 1;
1831
1832       for (i=0, handle = NULL; !handle && dllnames[i]; i++)
1833         {
1834           handle = dlopen (dllnames[i], RTLD_LAZY);
1835           if (handle)
1836             {
1837               func = dlsym (handle, "SHGetFolderPathW");
1838               if (!func)
1839                 {
1840                   dlclose (handle);
1841                   handle = NULL;
1842                 }
1843             }
1844         }
1845     }
1846
1847   if (func)
1848     return func (a,b,c,d,e);
1849   else
1850     return -1;
1851 }
1852
1853 /* Returns a static string with the directroy from which the module
1854    has been loaded.  Returns an empty string on error. */
1855 static char *w32_get_module_dir(void)
1856 {
1857         static char *moddir;
1858
1859         if (!moddir) {
1860                 char name[MAX_PATH+10];
1861                 char *p;
1862
1863                 if ( !GetModuleFileNameA (0, name, sizeof (name)-10) )
1864                         *name = 0;
1865                 else {
1866                         p = strrchr (name, '\\');
1867                         if (p)
1868                                 *p = 0;
1869                         else
1870                                 *name = 0;
1871                 }
1872                 moddir = g_strdup (name);
1873         }
1874         return moddir;
1875 }
1876 #endif /* G_OS_WIN32 */
1877
1878 /* Return a static string with the locale dir. */
1879 const gchar *get_locale_dir(void)
1880 {
1881         static gchar *loc_dir;
1882
1883 #ifdef G_OS_WIN32
1884         if (!loc_dir)
1885                 loc_dir = g_strconcat(w32_get_module_dir(), G_DIR_SEPARATOR_S,
1886                                       "\\share\\locale", NULL);
1887 #endif
1888         if (!loc_dir)
1889                 loc_dir = LOCALEDIR;
1890         
1891         return loc_dir;
1892 }
1893
1894
1895 const gchar *get_home_dir(void)
1896 {
1897 #ifdef G_OS_WIN32
1898         static char home_dir_utf16[MAX_PATH] = "";
1899         static gchar *home_dir_utf8 = NULL;
1900         if (home_dir_utf16[0] == '\0') {
1901                 if (w32_shgetfolderpath
1902                             (NULL, CSIDL_APPDATA|CSIDL_FLAG_CREATE,
1903                              NULL, 0, home_dir_utf16) < 0)
1904                                 strcpy (home_dir_utf16, "C:\\Sylpheed");
1905                 home_dir_utf8 = g_utf16_to_utf8 ((const gunichar *)home_dir_utf16, -1, NULL, NULL, NULL);
1906         }
1907         return home_dir_utf8;
1908 #else
1909         static const gchar *homeenv = NULL;
1910
1911         if (homeenv)
1912                 return homeenv;
1913
1914         if (!homeenv && g_getenv("HOME") != NULL)
1915                 homeenv = g_strdup(g_getenv("HOME"));
1916         if (!homeenv)
1917                 homeenv = g_get_home_dir();
1918
1919         return homeenv;
1920 #endif
1921 }
1922
1923 static gchar *claws_rc_dir = NULL;
1924 static gboolean rc_dir_alt = FALSE;
1925 const gchar *get_rc_dir(void)
1926 {
1927
1928         if (!claws_rc_dir) {
1929                 claws_rc_dir = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
1930                                      RC_DIR, NULL);
1931                 debug_print("using default rc_dir %s\n", claws_rc_dir);
1932         }
1933         return claws_rc_dir;
1934 }
1935
1936 void set_rc_dir(const gchar *dir)
1937 {
1938         gchar *canonical_dir;
1939         if (claws_rc_dir != NULL) {
1940                 g_print("Error: rc_dir already set\n");
1941         } else {
1942                 int err = cm_canonicalize_filename(dir, &canonical_dir);
1943                 int len;
1944
1945                 if (err) {
1946                         g_print("Error looking for %s: %d(%s)\n",
1947                                 dir, -err, g_strerror(-err));
1948                         exit(0);
1949                 }
1950                 rc_dir_alt = TRUE;
1951
1952                 claws_rc_dir = canonical_dir;
1953                 
1954                 len = strlen(claws_rc_dir);
1955                 if (claws_rc_dir[len - 1] == G_DIR_SEPARATOR)
1956                         claws_rc_dir[len - 1] = '\0';
1957                 
1958                 debug_print("set rc_dir to %s\n", claws_rc_dir);
1959                 if (!is_dir_exist(claws_rc_dir)) {
1960                         if (make_dir_hier(claws_rc_dir) != 0) {
1961                                 g_print("Error: can't create %s\n",
1962                                 claws_rc_dir);
1963                                 exit(0);
1964                         }
1965                 }
1966         }
1967 }
1968
1969 gboolean rc_dir_is_alt(void) {
1970         return rc_dir_alt;
1971 }
1972
1973 const gchar *get_mail_base_dir(void)
1974 {
1975         return get_home_dir();
1976 }
1977
1978 const gchar *get_news_cache_dir(void)
1979 {
1980         static gchar *news_cache_dir = NULL;
1981         if (!news_cache_dir)
1982                 news_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1983                                              NEWS_CACHE_DIR, NULL);
1984
1985         return news_cache_dir;
1986 }
1987
1988 const gchar *get_imap_cache_dir(void)
1989 {
1990         static gchar *imap_cache_dir = NULL;
1991
1992         if (!imap_cache_dir)
1993                 imap_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1994                                              IMAP_CACHE_DIR, NULL);
1995
1996         return imap_cache_dir;
1997 }
1998
1999 const gchar *get_mime_tmp_dir(void)
2000 {
2001         static gchar *mime_tmp_dir = NULL;
2002
2003         if (!mime_tmp_dir)
2004                 mime_tmp_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
2005                                            MIME_TMP_DIR, NULL);
2006
2007         return mime_tmp_dir;
2008 }
2009
2010 const gchar *get_template_dir(void)
2011 {
2012         static gchar *template_dir = NULL;
2013
2014         if (!template_dir)
2015                 template_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
2016                                            TEMPLATE_DIR, NULL);
2017
2018         return template_dir;
2019 }
2020
2021 #ifdef G_OS_WIN32
2022 const gchar *get_cert_file(void)
2023 {
2024         const gchar *cert_file = NULL;
2025         if (!cert_file)
2026                 cert_file = g_strconcat(w32_get_module_dir(),
2027                                  "\\share\\claws-mail\\",
2028                                 "ca-certificates.crt",
2029                                 NULL);  
2030         return cert_file;
2031 }
2032 #endif
2033
2034 /* Return the filepath of the claws-mail.desktop file */
2035 const gchar *get_desktop_file(void)
2036 {
2037 #ifdef DESKTOPFILEPATH
2038   return DESKTOPFILEPATH;
2039 #else
2040   return NULL;
2041 #endif
2042 }
2043
2044 /* Return the default directory for Plugins. */
2045 const gchar *get_plugin_dir(void)
2046 {
2047 #ifdef G_OS_WIN32
2048         static gchar *plugin_dir = NULL;
2049
2050         if (!plugin_dir)
2051                 plugin_dir = g_strconcat(w32_get_module_dir(),
2052                                          "\\lib\\claws-mail\\plugins\\",
2053                                          NULL);
2054         return plugin_dir;
2055 #else
2056         if (is_dir_exist(PLUGINDIR))
2057                 return PLUGINDIR;
2058         else {
2059                 static gchar *plugin_dir = NULL;
2060                 if (!plugin_dir)
2061                         plugin_dir = g_strconcat(get_rc_dir(), 
2062                                 G_DIR_SEPARATOR_S, "plugins", 
2063                                 G_DIR_SEPARATOR_S, NULL);
2064                 return plugin_dir;                      
2065         }
2066 #endif
2067 }
2068
2069
2070 #ifdef G_OS_WIN32
2071 /* Return the default directory for Themes. */
2072 const gchar *get_themes_dir(void)
2073 {
2074         static gchar *themes_dir = NULL;
2075
2076         if (!themes_dir)
2077                 themes_dir = g_strconcat(w32_get_module_dir(),
2078                                          "\\share\\claws-mail\\themes",
2079                                          NULL);
2080         return themes_dir;
2081 }
2082 #endif
2083
2084 const gchar *get_tmp_dir(void)
2085 {
2086         static gchar *tmp_dir = NULL;
2087
2088         if (!tmp_dir)
2089                 tmp_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
2090                                       TMP_DIR, NULL);
2091
2092         return tmp_dir;
2093 }
2094
2095 gchar *get_tmp_file(void)
2096 {
2097         gchar *tmp_file;
2098         static guint32 id = 0;
2099
2100         tmp_file = g_strdup_printf("%s%ctmpfile.%08x",
2101                                    get_tmp_dir(), G_DIR_SEPARATOR, id++);
2102
2103         return tmp_file;
2104 }
2105
2106 const gchar *get_domain_name(void)
2107 {
2108 #ifdef G_OS_UNIX
2109         static gchar *domain_name = NULL;
2110
2111         if (!domain_name) {
2112                 struct hostent *hp;
2113                 char hostname[256];
2114
2115                 if (gethostname(hostname, sizeof(hostname)) != 0) {
2116                         perror("gethostname");
2117                         domain_name = "localhost";
2118                 } else {
2119                         hostname[sizeof(hostname) - 1] = '\0';
2120                         if ((hp = my_gethostbyname(hostname)) == NULL) {
2121                                 perror("gethostbyname");
2122                                 domain_name = g_strdup(hostname);
2123                         } else {
2124                                 domain_name = g_strdup(hp->h_name);
2125                         }
2126                 }
2127                 debug_print("domain name = %s\n", domain_name);
2128         }
2129
2130         return domain_name;
2131 #else
2132         return "localhost";
2133 #endif
2134 }
2135
2136 off_t get_file_size(const gchar *file)
2137 {
2138         GStatBuf s;
2139
2140         if (g_stat(file, &s) < 0) {
2141                 FILE_OP_ERROR(file, "stat");
2142                 return -1;
2143         }
2144
2145         return s.st_size;
2146 }
2147
2148 time_t get_file_mtime(const gchar *file)
2149 {
2150         GStatBuf s;
2151
2152         if (g_stat(file, &s) < 0) {
2153                 FILE_OP_ERROR(file, "stat");
2154                 return -1;
2155         }
2156
2157         return s.st_mtime;
2158 }
2159
2160 off_t get_file_size_as_crlf(const gchar *file)
2161 {
2162         FILE *fp;
2163         off_t size = 0;
2164         gchar buf[BUFFSIZE];
2165
2166         if ((fp = g_fopen(file, "rb")) == NULL) {
2167                 FILE_OP_ERROR(file, "g_fopen");
2168                 return -1;
2169         }
2170
2171         while (fgets(buf, sizeof(buf), fp) != NULL) {
2172                 strretchomp(buf);
2173                 size += strlen(buf) + 2;
2174         }
2175
2176         if (ferror(fp)) {
2177                 FILE_OP_ERROR(file, "fgets");
2178                 size = -1;
2179         }
2180
2181         fclose(fp);
2182
2183         return size;
2184 }
2185
2186 gboolean file_exist(const gchar *file, gboolean allow_fifo)
2187 {
2188         GStatBuf s;
2189
2190         if (file == NULL)
2191                 return FALSE;
2192
2193         if (g_stat(file, &s) < 0) {
2194                 if (ENOENT != errno) FILE_OP_ERROR(file, "stat");
2195                 return FALSE;
2196         }
2197
2198         if (S_ISREG(s.st_mode) || (allow_fifo && S_ISFIFO(s.st_mode)))
2199                 return TRUE;
2200
2201         return FALSE;
2202 }
2203
2204
2205 /* Test on whether FILE is a relative file name. This is
2206  * straightforward for Unix but more complex for Windows. */
2207 gboolean is_relative_filename(const gchar *file)
2208 {
2209         if (!file)
2210                 return TRUE;
2211 #ifdef G_OS_WIN32
2212         if ( *file == '\\' && file[1] == '\\' && strchr (file+2, '\\') )
2213                 return FALSE; /* Prefixed with a hostname - this can't
2214                                * be a relative name. */
2215
2216         if ( ((*file >= 'a' && *file <= 'z')
2217               || (*file >= 'A' && *file <= 'Z'))
2218              && file[1] == ':')
2219                 file += 2;  /* Skip drive letter. */
2220
2221         return !(*file == '\\' || *file == '/');
2222 #else
2223         return !(*file == G_DIR_SEPARATOR);
2224 #endif
2225 }
2226
2227
2228 gboolean is_dir_exist(const gchar *dir)
2229 {
2230         if (dir == NULL)
2231                 return FALSE;
2232
2233         return g_file_test(dir, G_FILE_TEST_IS_DIR);
2234 }
2235
2236 gboolean is_file_entry_exist(const gchar *file)
2237 {
2238         if (file == NULL)
2239                 return FALSE;
2240
2241         return g_file_test(file, G_FILE_TEST_EXISTS);
2242 }
2243
2244 gboolean dirent_is_regular_file(struct dirent *d)
2245 {
2246 #if !defined(G_OS_WIN32) && defined(HAVE_DIRENT_D_TYPE)
2247         if (d->d_type == DT_REG)
2248                 return TRUE;
2249         else if (d->d_type != DT_UNKNOWN)
2250                 return FALSE;
2251 #endif
2252
2253         return g_file_test(d->d_name, G_FILE_TEST_IS_REGULAR);
2254 }
2255
2256 gint change_dir(const gchar *dir)
2257 {
2258         gchar *prevdir = NULL;
2259
2260         if (debug_mode)
2261                 prevdir = g_get_current_dir();
2262
2263         if (g_chdir(dir) < 0) {
2264                 FILE_OP_ERROR(dir, "chdir");
2265                 if (debug_mode) g_free(prevdir);
2266                 return -1;
2267         } else if (debug_mode) {
2268                 gchar *cwd;
2269
2270                 cwd = g_get_current_dir();
2271                 if (strcmp(prevdir, cwd) != 0)
2272                         g_print("current dir: %s\n", cwd);
2273                 g_free(cwd);
2274                 g_free(prevdir);
2275         }
2276
2277         return 0;
2278 }
2279
2280 gint make_dir(const gchar *dir)
2281 {
2282         if (g_mkdir(dir, S_IRWXU) < 0) {
2283                 FILE_OP_ERROR(dir, "mkdir");
2284                 return -1;
2285         }
2286         if (g_chmod(dir, S_IRWXU) < 0)
2287                 FILE_OP_ERROR(dir, "chmod");
2288
2289         return 0;
2290 }
2291
2292 gint make_dir_hier(const gchar *dir)
2293 {
2294         gchar *parent_dir;
2295         const gchar *p;
2296
2297         for (p = dir; (p = strchr(p, G_DIR_SEPARATOR)) != NULL; p++) {
2298                 parent_dir = g_strndup(dir, p - dir);
2299                 if (*parent_dir != '\0') {
2300                         if (!is_dir_exist(parent_dir)) {
2301                                 if (make_dir(parent_dir) < 0) {
2302                                         g_free(parent_dir);
2303                                         return -1;
2304                                 }
2305                         }
2306                 }
2307                 g_free(parent_dir);
2308         }
2309
2310         if (!is_dir_exist(dir)) {
2311                 if (make_dir(dir) < 0)
2312                         return -1;
2313         }
2314
2315         return 0;
2316 }
2317
2318 gint remove_all_files(const gchar *dir)
2319 {
2320         GDir *dp;
2321         const gchar *dir_name;
2322         gchar *prev_dir;
2323
2324         prev_dir = g_get_current_dir();
2325
2326         if (g_chdir(dir) < 0) {
2327                 FILE_OP_ERROR(dir, "chdir");
2328                 g_free(prev_dir);
2329                 return -1;
2330         }
2331
2332         if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2333                 g_warning("failed to open directory: %s", dir);
2334                 g_free(prev_dir);
2335                 return -1;
2336         }
2337
2338         while ((dir_name = g_dir_read_name(dp)) != NULL) {
2339                 if (claws_unlink(dir_name) < 0)
2340                         FILE_OP_ERROR(dir_name, "unlink");
2341         }
2342
2343         g_dir_close(dp);
2344
2345         if (g_chdir(prev_dir) < 0) {
2346                 FILE_OP_ERROR(prev_dir, "chdir");
2347                 g_free(prev_dir);
2348                 return -1;
2349         }
2350
2351         g_free(prev_dir);
2352
2353         return 0;
2354 }
2355
2356 gint remove_numbered_files(const gchar *dir, guint first, guint last)
2357 {
2358         GDir *dp;
2359         const gchar *dir_name;
2360         gchar *prev_dir;
2361         gint file_no;
2362
2363         if (first == last) {
2364                 /* Skip all the dir reading part. */
2365                 gchar *filename = g_strdup_printf("%s%s%u", dir, G_DIR_SEPARATOR_S, first);
2366                 if (is_dir_exist(filename)) {
2367                         /* a numbered directory with this name exists,
2368                          * remove the dot-file instead */
2369                         g_free(filename);
2370                         filename = g_strdup_printf("%s%s.%u", dir, G_DIR_SEPARATOR_S, first);
2371                 }
2372                 if (claws_unlink(filename) < 0) {
2373                         FILE_OP_ERROR(filename, "unlink");
2374                         g_free(filename);
2375                         return -1;
2376                 }
2377                 g_free(filename);
2378                 return 0;
2379         }
2380
2381         prev_dir = g_get_current_dir();
2382
2383         if (g_chdir(dir) < 0) {
2384                 FILE_OP_ERROR(dir, "chdir");
2385                 g_free(prev_dir);
2386                 return -1;
2387         }
2388
2389         if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2390                 g_warning("failed to open directory: %s", dir);
2391                 g_free(prev_dir);
2392                 return -1;
2393         }
2394
2395         while ((dir_name = g_dir_read_name(dp)) != NULL) {
2396                 file_no = to_number(dir_name);
2397                 if (file_no > 0 && first <= file_no && file_no <= last) {
2398                         if (is_dir_exist(dir_name)) {
2399                                 gchar *dot_file = g_strdup_printf(".%s", dir_name);
2400                                 if (is_file_exist(dot_file) && claws_unlink(dot_file) < 0) {
2401                                         FILE_OP_ERROR(dot_file, "unlink");
2402                                 }
2403                                 g_free(dot_file);
2404                                 continue;
2405                         }
2406                         if (claws_unlink(dir_name) < 0)
2407                                 FILE_OP_ERROR(dir_name, "unlink");
2408                 }
2409         }
2410
2411         g_dir_close(dp);
2412
2413         if (g_chdir(prev_dir) < 0) {
2414                 FILE_OP_ERROR(prev_dir, "chdir");
2415                 g_free(prev_dir);
2416                 return -1;
2417         }
2418
2419         g_free(prev_dir);
2420
2421         return 0;
2422 }
2423
2424 gint remove_numbered_files_not_in_list(const gchar *dir, GSList *numberlist)
2425 {
2426         GDir *dp;
2427         const gchar *dir_name;
2428         gchar *prev_dir;
2429         gint file_no;
2430         GHashTable *wanted_files;
2431         GSList *cur;
2432
2433         if (numberlist == NULL)
2434             return 0;
2435
2436         prev_dir = g_get_current_dir();
2437
2438         if (g_chdir(dir) < 0) {
2439                 FILE_OP_ERROR(dir, "chdir");
2440                 g_free(prev_dir);
2441                 return -1;
2442         }
2443
2444         if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2445                 FILE_OP_ERROR(dir, "opendir");
2446                 g_free(prev_dir);
2447                 return -1;
2448         }
2449
2450         wanted_files = g_hash_table_new(g_direct_hash, g_direct_equal);
2451         for (cur = numberlist; cur != NULL; cur = cur->next) {
2452                 /* numberlist->data is expected to be GINT_TO_POINTER */
2453                 g_hash_table_insert(wanted_files, cur->data, GINT_TO_POINTER(1));
2454         }
2455
2456         while ((dir_name = g_dir_read_name(dp)) != NULL) {
2457                 file_no = to_number(dir_name);
2458                 if (is_dir_exist(dir_name))
2459                         continue;
2460                 if (file_no > 0 && g_hash_table_lookup(wanted_files, GINT_TO_POINTER(file_no)) == NULL) {
2461                         debug_print("removing unwanted file %d from %s\n", file_no, dir);
2462                         if (is_dir_exist(dir_name)) {
2463                                 gchar *dot_file = g_strdup_printf(".%s", dir_name);
2464                                 if (is_file_exist(dot_file) && claws_unlink(dot_file) < 0) {
2465                                         FILE_OP_ERROR(dot_file, "unlink");
2466                                 }
2467                                 g_free(dot_file);
2468                                 continue;
2469                         }
2470                         if (claws_unlink(dir_name) < 0)
2471                                 FILE_OP_ERROR(dir_name, "unlink");
2472                 }
2473         }
2474
2475         g_dir_close(dp);
2476         g_hash_table_destroy(wanted_files);
2477
2478         if (g_chdir(prev_dir) < 0) {
2479                 FILE_OP_ERROR(prev_dir, "chdir");
2480                 g_free(prev_dir);
2481                 return -1;
2482         }
2483
2484         g_free(prev_dir);
2485
2486         return 0;
2487 }
2488
2489 gint remove_all_numbered_files(const gchar *dir)
2490 {
2491         return remove_numbered_files(dir, 0, UINT_MAX);
2492 }
2493
2494 gint remove_dir_recursive(const gchar *dir)
2495 {
2496         GStatBuf s;
2497         GDir *dp;
2498         const gchar *dir_name;
2499         gchar *prev_dir;
2500
2501         if (g_stat(dir, &s) < 0) {
2502                 FILE_OP_ERROR(dir, "stat");
2503                 if (ENOENT == errno) return 0;
2504                 return -(errno);
2505         }
2506
2507         if (!S_ISDIR(s.st_mode)) {
2508                 if (claws_unlink(dir) < 0) {
2509                         FILE_OP_ERROR(dir, "unlink");
2510                         return -(errno);
2511                 }
2512
2513                 return 0;
2514         }
2515
2516         prev_dir = g_get_current_dir();
2517         /* g_print("prev_dir = %s\n", prev_dir); */
2518
2519         if (!path_cmp(prev_dir, dir)) {
2520                 g_free(prev_dir);
2521                 if (g_chdir("..") < 0) {
2522                         FILE_OP_ERROR(dir, "chdir");
2523                         return -(errno);
2524                 }
2525                 prev_dir = g_get_current_dir();
2526         }
2527
2528         if (g_chdir(dir) < 0) {
2529                 FILE_OP_ERROR(dir, "chdir");
2530                 g_free(prev_dir);
2531                 return -(errno);
2532         }
2533
2534         if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2535                 g_warning("failed to open directory: %s", dir);
2536                 g_chdir(prev_dir);
2537                 g_free(prev_dir);
2538                 return -(errno);
2539         }
2540
2541         /* remove all files in the directory */
2542         while ((dir_name = g_dir_read_name(dp)) != NULL) {
2543                 /* g_print("removing %s\n", dir_name); */
2544
2545                 if (is_dir_exist(dir_name)) {
2546                         gint ret;
2547
2548                         if ((ret = remove_dir_recursive(dir_name)) < 0) {
2549                                 g_warning("can't remove directory: %s", dir_name);
2550                                 return ret;
2551                         }
2552                 } else {
2553                         if (claws_unlink(dir_name) < 0)
2554                                 FILE_OP_ERROR(dir_name, "unlink");
2555                 }
2556         }
2557
2558         g_dir_close(dp);
2559
2560         if (g_chdir(prev_dir) < 0) {
2561                 FILE_OP_ERROR(prev_dir, "chdir");
2562                 g_free(prev_dir);
2563                 return -(errno);
2564         }
2565
2566         g_free(prev_dir);
2567
2568         if (g_rmdir(dir) < 0) {
2569                 FILE_OP_ERROR(dir, "rmdir");
2570                 return -(errno);
2571         }
2572
2573         return 0;
2574 }
2575
2576 gint rename_force(const gchar *oldpath, const gchar *newpath)
2577 {
2578 #ifndef G_OS_UNIX
2579         if (!is_file_entry_exist(oldpath)) {
2580                 errno = ENOENT;
2581                 return -1;
2582         }
2583         if (is_file_exist(newpath)) {
2584                 if (claws_unlink(newpath) < 0)
2585                         FILE_OP_ERROR(newpath, "unlink");
2586         }
2587 #endif
2588         return g_rename(oldpath, newpath);
2589 }
2590
2591 /*
2592  * Append src file body to the tail of dest file.
2593  * Now keep_backup has no effects.
2594  */
2595 gint append_file(const gchar *src, const gchar *dest, gboolean keep_backup)
2596 {
2597         FILE *src_fp, *dest_fp;
2598         gint n_read;
2599         gchar buf[BUFSIZ];
2600
2601         gboolean err = FALSE;
2602
2603         if ((src_fp = g_fopen(src, "rb")) == NULL) {
2604                 FILE_OP_ERROR(src, "g_fopen");
2605                 return -1;
2606         }
2607
2608         if ((dest_fp = g_fopen(dest, "ab")) == NULL) {
2609                 FILE_OP_ERROR(dest, "g_fopen");
2610                 fclose(src_fp);
2611                 return -1;
2612         }
2613
2614         if (change_file_mode_rw(dest_fp, dest) < 0) {
2615                 FILE_OP_ERROR(dest, "chmod");
2616                 g_warning("can't change file mode: %s", dest);
2617         }
2618
2619         while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
2620                 if (n_read < sizeof(buf) && ferror(src_fp))
2621                         break;
2622                 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2623                         g_warning("writing to %s failed.", dest);
2624                         fclose(dest_fp);
2625                         fclose(src_fp);
2626                         claws_unlink(dest);
2627                         return -1;
2628                 }
2629         }
2630
2631         if (ferror(src_fp)) {
2632                 FILE_OP_ERROR(src, "fread");
2633                 err = TRUE;
2634         }
2635         fclose(src_fp);
2636         if (fclose(dest_fp) == EOF) {
2637                 FILE_OP_ERROR(dest, "fclose");
2638                 err = TRUE;
2639         }
2640
2641         if (err) {
2642                 claws_unlink(dest);
2643                 return -1;
2644         }
2645
2646         return 0;
2647 }
2648
2649 gint copy_file(const gchar *src, const gchar *dest, gboolean keep_backup)
2650 {
2651         FILE *src_fp, *dest_fp;
2652         gint n_read;
2653         gchar buf[BUFSIZ];
2654         gchar *dest_bak = NULL;
2655         gboolean err = FALSE;
2656
2657         if ((src_fp = g_fopen(src, "rb")) == NULL) {
2658                 FILE_OP_ERROR(src, "g_fopen");
2659                 return -1;
2660         }
2661         if (is_file_exist(dest)) {
2662                 dest_bak = g_strconcat(dest, ".bak", NULL);
2663                 if (rename_force(dest, dest_bak) < 0) {
2664                         FILE_OP_ERROR(dest, "rename");
2665                         fclose(src_fp);
2666                         g_free(dest_bak);
2667                         return -1;
2668                 }
2669         }
2670
2671         if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2672                 FILE_OP_ERROR(dest, "g_fopen");
2673                 fclose(src_fp);
2674                 if (dest_bak) {
2675                         if (rename_force(dest_bak, dest) < 0)
2676                                 FILE_OP_ERROR(dest_bak, "rename");
2677                         g_free(dest_bak);
2678                 }
2679                 return -1;
2680         }
2681
2682         if (change_file_mode_rw(dest_fp, dest) < 0) {
2683                 FILE_OP_ERROR(dest, "chmod");
2684                 g_warning("can't change file mode: %s", dest);
2685         }
2686
2687         while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
2688                 if (n_read < sizeof(buf) && ferror(src_fp))
2689                         break;
2690                 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2691                         g_warning("writing to %s failed.", dest);
2692                         fclose(dest_fp);
2693                         fclose(src_fp);
2694                         claws_unlink(dest);
2695                         if (dest_bak) {
2696                                 if (rename_force(dest_bak, dest) < 0)
2697                                         FILE_OP_ERROR(dest_bak, "rename");
2698                                 g_free(dest_bak);
2699                         }
2700                         return -1;
2701                 }
2702         }
2703
2704         if (ferror(src_fp)) {
2705                 FILE_OP_ERROR(src, "fread");
2706                 err = TRUE;
2707         }
2708         fclose(src_fp);
2709         if (fclose(dest_fp) == EOF) {
2710                 FILE_OP_ERROR(dest, "fclose");
2711                 err = TRUE;
2712         }
2713
2714         if (err) {
2715                 claws_unlink(dest);
2716                 if (dest_bak) {
2717                         if (rename_force(dest_bak, dest) < 0)
2718                                 FILE_OP_ERROR(dest_bak, "rename");
2719                         g_free(dest_bak);
2720                 }
2721                 return -1;
2722         }
2723
2724         if (keep_backup == FALSE && dest_bak)
2725                 claws_unlink(dest_bak);
2726
2727         g_free(dest_bak);
2728
2729         return 0;
2730 }
2731
2732 gint move_file(const gchar *src, const gchar *dest, gboolean overwrite)
2733 {
2734         if (overwrite == FALSE && is_file_exist(dest)) {
2735                 g_warning("move_file(): file %s already exists.", dest);
2736                 return -1;
2737         }
2738
2739         if (rename_force(src, dest) == 0) return 0;
2740
2741         if (EXDEV != errno) {
2742                 FILE_OP_ERROR(src, "rename");
2743                 return -1;
2744         }
2745
2746         if (copy_file(src, dest, FALSE) < 0) return -1;
2747
2748         claws_unlink(src);
2749
2750         return 0;
2751 }
2752
2753 gint copy_file_part_to_fp(FILE *fp, off_t offset, size_t length, FILE *dest_fp)
2754 {
2755         gint n_read;
2756         gint bytes_left, to_read;
2757         gchar buf[BUFSIZ];
2758
2759         if (fseek(fp, offset, SEEK_SET) < 0) {
2760                 perror("fseek");
2761                 return -1;
2762         }
2763
2764         bytes_left = length;
2765         to_read = MIN(bytes_left, sizeof(buf));
2766
2767         while ((n_read = fread(buf, sizeof(gchar), to_read, fp)) > 0) {
2768                 if (n_read < to_read && ferror(fp))
2769                         break;
2770                 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2771                         return -1;
2772                 }
2773                 bytes_left -= n_read;
2774                 if (bytes_left == 0)
2775                         break;
2776                 to_read = MIN(bytes_left, sizeof(buf));
2777         }
2778
2779         if (ferror(fp)) {
2780                 perror("fread");
2781                 return -1;
2782         }
2783
2784         return 0;
2785 }
2786
2787 gint copy_file_part(FILE *fp, off_t offset, size_t length, const gchar *dest)
2788 {
2789         FILE *dest_fp;
2790         gboolean err = FALSE;
2791
2792         if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2793                 FILE_OP_ERROR(dest, "g_fopen");
2794                 return -1;
2795         }
2796
2797         if (change_file_mode_rw(dest_fp, dest) < 0) {
2798                 FILE_OP_ERROR(dest, "chmod");
2799                 g_warning("can't change file mode: %s", dest);
2800         }
2801
2802         if (copy_file_part_to_fp(fp, offset, length, dest_fp) < 0)
2803                 err = TRUE;
2804
2805         if (!err && fclose(dest_fp) == EOF) {
2806                 FILE_OP_ERROR(dest, "fclose");
2807                 err = TRUE;
2808         }
2809
2810         if (err) {
2811                 g_warning("writing to %s failed.", dest);
2812                 claws_unlink(dest);
2813                 return -1;
2814         }
2815
2816         return 0;
2817 }
2818
2819 /* convert line endings into CRLF. If the last line doesn't end with
2820  * linebreak, add it.
2821  */
2822 gchar *canonicalize_str(const gchar *str)
2823 {
2824         const gchar *p;
2825         guint new_len = 0;
2826         gchar *out, *outp;
2827
2828         for (p = str; *p != '\0'; ++p) {
2829                 if (*p != '\r') {
2830                         ++new_len;
2831                         if (*p == '\n')
2832                                 ++new_len;
2833                 }
2834         }
2835         if (p == str || *(p - 1) != '\n')
2836                 new_len += 2;
2837
2838         out = outp = g_malloc(new_len + 1);
2839         for (p = str; *p != '\0'; ++p) {
2840                 if (*p != '\r') {
2841                         if (*p == '\n')
2842                                 *outp++ = '\r';
2843                         *outp++ = *p;
2844                 }
2845         }
2846         if (p == str || *(p - 1) != '\n') {
2847                 *outp++ = '\r';
2848                 *outp++ = '\n';
2849         }
2850         *outp = '\0';
2851
2852         return out;
2853 }
2854
2855 gint canonicalize_file(const gchar *src, const gchar *dest)
2856 {
2857         FILE *src_fp, *dest_fp;
2858         gchar buf[BUFFSIZE];
2859         gint len;
2860         gboolean err = FALSE;
2861         gboolean last_linebreak = FALSE;
2862
2863         if (src == NULL || dest == NULL)
2864                 return -1;
2865
2866         if ((src_fp = g_fopen(src, "rb")) == NULL) {
2867                 FILE_OP_ERROR(src, "g_fopen");
2868                 return -1;
2869         }
2870
2871         if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2872                 FILE_OP_ERROR(dest, "g_fopen");
2873                 fclose(src_fp);
2874                 return -1;
2875         }
2876
2877         if (change_file_mode_rw(dest_fp, dest) < 0) {
2878                 FILE_OP_ERROR(dest, "chmod");
2879                 g_warning("can't change file mode: %s", dest);
2880         }
2881
2882         while (fgets(buf, sizeof(buf), src_fp) != NULL) {
2883                 gint r = 0;
2884
2885                 len = strlen(buf);
2886                 if (len == 0) break;
2887                 last_linebreak = FALSE;
2888
2889                 if (buf[len - 1] != '\n') {
2890                         last_linebreak = TRUE;
2891                         r = fputs(buf, dest_fp);
2892                 } else if (len > 1 && buf[len - 1] == '\n' && buf[len - 2] == '\r') {
2893                         r = fputs(buf, dest_fp);
2894                 } else {
2895                         if (len > 1) {
2896                                 r = fwrite(buf, 1, len - 1, dest_fp);
2897                                 if (r != (len -1))
2898                                         r = EOF;
2899                         }
2900                         if (r != EOF)
2901                                 r = fputs("\r\n", dest_fp);
2902                 }
2903
2904                 if (r == EOF) {
2905                         g_warning("writing to %s failed.", dest);
2906                         fclose(dest_fp);
2907                         fclose(src_fp);
2908                         claws_unlink(dest);
2909                         return -1;
2910                 }
2911         }
2912
2913         if (last_linebreak == TRUE) {
2914                 if (fputs("\r\n", dest_fp) == EOF)
2915                         err = TRUE;
2916         }
2917
2918         if (ferror(src_fp)) {
2919                 FILE_OP_ERROR(src, "fgets");
2920                 err = TRUE;
2921         }
2922         fclose(src_fp);
2923         if (fclose(dest_fp) == EOF) {
2924                 FILE_OP_ERROR(dest, "fclose");
2925                 err = TRUE;
2926         }
2927
2928         if (err) {
2929                 claws_unlink(dest);
2930                 return -1;
2931         }
2932
2933         return 0;
2934 }
2935
2936 gint canonicalize_file_replace(const gchar *file)
2937 {
2938         gchar *tmp_file;
2939
2940         tmp_file = get_tmp_file();
2941
2942         if (canonicalize_file(file, tmp_file) < 0) {
2943                 g_free(tmp_file);
2944                 return -1;
2945         }
2946
2947         if (move_file(tmp_file, file, TRUE) < 0) {
2948                 g_warning("can't replace file: %s", file);
2949                 claws_unlink(tmp_file);
2950                 g_free(tmp_file);
2951                 return -1;
2952         }
2953
2954         g_free(tmp_file);
2955         return 0;
2956 }
2957
2958 gchar *normalize_newlines(const gchar *str)
2959 {
2960         const gchar *p;
2961         gchar *out, *outp;
2962
2963         out = outp = g_malloc(strlen(str) + 1);
2964         for (p = str; *p != '\0'; ++p) {
2965                 if (*p == '\r') {
2966                         if (*(p + 1) != '\n')
2967                                 *outp++ = '\n';
2968                 } else
2969                         *outp++ = *p;
2970         }
2971
2972         *outp = '\0';
2973
2974         return out;
2975 }
2976
2977 gchar *get_outgoing_rfc2822_str(FILE *fp)
2978 {
2979         gchar buf[BUFFSIZE];
2980         GString *str;
2981         gchar *ret;
2982
2983         str = g_string_new(NULL);
2984
2985         /* output header part */
2986         while (fgets(buf, sizeof(buf), fp) != NULL) {
2987                 strretchomp(buf);
2988                 if (!g_ascii_strncasecmp(buf, "Bcc:", 4)) {
2989                         gint next;
2990
2991                         for (;;) {
2992                                 next = fgetc(fp);
2993                                 if (next == EOF)
2994                                         break;
2995                                 else if (next != ' ' && next != '\t') {
2996                                         ungetc(next, fp);
2997                                         break;
2998                                 }
2999                                 if (fgets(buf, sizeof(buf), fp) == NULL)
3000                                         break;
3001                         }
3002                 } else {
3003                         g_string_append(str, buf);
3004                         g_string_append(str, "\r\n");
3005                         if (buf[0] == '\0')
3006                                 break;
3007                 }
3008         }
3009
3010         /* output body part */
3011         while (fgets(buf, sizeof(buf), fp) != NULL) {
3012                 strretchomp(buf);
3013                 if (buf[0] == '.')
3014                         g_string_append_c(str, '.');
3015                 g_string_append(str, buf);
3016                 g_string_append(str, "\r\n");
3017         }
3018
3019         ret = str->str;
3020         g_string_free(str, FALSE);
3021
3022         return ret;
3023 }
3024
3025 /*
3026  * Create a new boundary in a way that it is very unlikely that this
3027  * will occur in the following text.  It would be easy to ensure
3028  * uniqueness if everything is either quoted-printable or base64
3029  * encoded (note that conversion is allowed), but because MIME bodies
3030  * may be nested, it may happen that the same boundary has already
3031  * been used.
3032  *
3033  *   boundary := 0*69<bchars> bcharsnospace
3034  *   bchars := bcharsnospace / " "
3035  *   bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" /
3036  *                  "+" / "_" / "," / "-" / "." /
3037  *                  "/" / ":" / "=" / "?"
3038  *
3039  * some special characters removed because of buggy MTAs
3040  */
3041
3042 gchar *generate_mime_boundary(const gchar *prefix)
3043 {
3044         static gchar tbl[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
3045                              "abcdefghijklmnopqrstuvwxyz"
3046                              "1234567890+_./=";
3047         gchar buf_uniq[24];
3048         gint i;
3049
3050         for (i = 0; i < sizeof(buf_uniq) - 1; i++)
3051                 buf_uniq[i] = tbl[g_random_int_range(0, sizeof(tbl) - 1)];
3052         buf_uniq[i] = '\0';
3053
3054         return g_strdup_printf("%s_/%s", prefix ? prefix : "MP",
3055                                buf_uniq);
3056 }
3057
3058 gint change_file_mode_rw(FILE *fp, const gchar *file)
3059 {
3060 #if HAVE_FCHMOD
3061         return fchmod(fileno(fp), S_IRUSR|S_IWUSR);
3062 #else
3063         return g_chmod(file, S_IRUSR|S_IWUSR);
3064 #endif
3065 }
3066
3067 FILE *my_tmpfile(void)
3068 {
3069 #if HAVE_MKSTEMP || defined(G_OS_WIN32)
3070         const gchar suffix[] = ".XXXXXX";
3071         const gchar *tmpdir;
3072         guint tmplen;
3073         const gchar *progname;
3074         guint proglen;
3075         gchar *fname;
3076         gint fd;
3077         FILE *fp;
3078 #ifndef G_OS_WIN32
3079         gchar buf[2]="\0";
3080 #endif
3081
3082         tmpdir = get_tmp_dir();
3083         tmplen = strlen(tmpdir);
3084         progname = g_get_prgname();
3085         if (progname == NULL)
3086                 progname = "claws-mail";
3087         proglen = strlen(progname);
3088         Xalloca(fname, tmplen + 1 + proglen + sizeof(suffix),
3089                 return tmpfile());
3090
3091         memcpy(fname, tmpdir, tmplen);
3092         fname[tmplen] = G_DIR_SEPARATOR;
3093         memcpy(fname + tmplen + 1, progname, proglen);
3094         memcpy(fname + tmplen + 1 + proglen, suffix, sizeof(suffix));
3095
3096         fd = mkstemp(fname);
3097         if (fd < 0)
3098                 return tmpfile();
3099
3100 #ifndef G_OS_WIN32
3101         claws_unlink(fname);
3102         
3103         /* verify that we can write in the file after unlinking */
3104         if (write(fd, buf, 1) < 0) {
3105                 close(fd);
3106                 return tmpfile();
3107         }
3108         
3109 #endif
3110
3111         fp = fdopen(fd, "w+b");
3112         if (!fp)
3113                 close(fd);
3114         else {
3115                 rewind(fp);
3116                 return fp;
3117         }
3118
3119 #endif /* HAVE_MKSTEMP || G_OS_WIN32 */
3120
3121         return tmpfile();
3122 }
3123
3124 FILE *get_tmpfile_in_dir(const gchar *dir, gchar **filename)
3125 {
3126         int fd;
3127 #ifdef G_OS_WIN32
3128         char *template = g_strdup_printf ("%s%cclaws.XXXXXX",
3129                                           dir, G_DIR_SEPARATOR);
3130         fd = mkstemp_name(template, filename);
3131         g_free(template);
3132 #else
3133         *filename = g_strdup_printf("%s%cclaws.XXXXXX", dir, G_DIR_SEPARATOR);
3134         fd = mkstemp(*filename);
3135         if (fd < 0)
3136                 return NULL;
3137 #endif
3138         return fdopen(fd, "w+");
3139 }
3140
3141 FILE *str_open_as_stream(const gchar *str)
3142 {
3143         FILE *fp;
3144         size_t len;
3145
3146         cm_return_val_if_fail(str != NULL, NULL);
3147
3148         fp = my_tmpfile();
3149         if (!fp) {
3150                 FILE_OP_ERROR("str_open_as_stream", "my_tmpfile");
3151                 return NULL;
3152         }
3153
3154         len = strlen(str);
3155         if (len == 0) return fp;
3156
3157         if (fwrite(str, 1, len, fp) != len) {
3158                 FILE_OP_ERROR("str_open_as_stream", "fwrite");
3159                 fclose(fp);
3160                 return NULL;
3161         }
3162
3163         rewind(fp);
3164         return fp;
3165 }
3166
3167 gint str_write_to_file(const gchar *str, const gchar *file)
3168 {
3169         FILE *fp;
3170         size_t len;
3171
3172         cm_return_val_if_fail(str != NULL, -1);
3173         cm_return_val_if_fail(file != NULL, -1);
3174
3175         if ((fp = g_fopen(file, "wb")) == NULL) {
3176                 FILE_OP_ERROR(file, "g_fopen");
3177                 return -1;
3178         }
3179
3180         len = strlen(str);
3181         if (len == 0) {
3182                 fclose(fp);
3183                 return 0;
3184         }
3185
3186         if (fwrite(str, 1, len, fp) != len) {
3187                 FILE_OP_ERROR(file, "fwrite");
3188                 fclose(fp);
3189                 claws_unlink(file);
3190                 return -1;
3191         }
3192
3193         if (fclose(fp) == EOF) {
3194                 FILE_OP_ERROR(file, "fclose");
3195                 claws_unlink(file);
3196                 return -1;
3197         }
3198
3199         return 0;
3200 }
3201
3202 static gchar *file_read_stream_to_str_full(FILE *fp, gboolean recode)
3203 {
3204         GByteArray *array;
3205         guchar buf[BUFSIZ];
3206         gint n_read;
3207         gchar *str;
3208
3209         cm_return_val_if_fail(fp != NULL, NULL);
3210
3211         array = g_byte_array_new();
3212
3213         while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
3214                 if (n_read < sizeof(buf) && ferror(fp))
3215                         break;
3216                 g_byte_array_append(array, buf, n_read);
3217         }
3218
3219         if (ferror(fp)) {
3220                 FILE_OP_ERROR("file stream", "fread");
3221                 g_byte_array_free(array, TRUE);
3222                 return NULL;
3223         }
3224
3225         buf[0] = '\0';
3226         g_byte_array_append(array, buf, 1);
3227         str = (gchar *)array->data;
3228         g_byte_array_free(array, FALSE);
3229
3230         if (recode && !g_utf8_validate(str, -1, NULL)) {
3231                 const gchar *src_codeset, *dest_codeset;
3232                 gchar *tmp = NULL;
3233                 src_codeset = conv_get_locale_charset_str();
3234                 dest_codeset = CS_UTF_8;
3235                 tmp = conv_codeset_strdup(str, src_codeset, dest_codeset);
3236                 g_free(str);
3237                 str = tmp;
3238         }
3239
3240         return str;
3241 }
3242
3243 static gchar *file_read_to_str_full(const gchar *file, gboolean recode)
3244 {
3245         FILE *fp;
3246         gchar *str;
3247         GStatBuf s;
3248 #ifndef G_OS_WIN32
3249         gint fd, err;
3250         struct timeval timeout = {1, 0};
3251         fd_set fds;
3252         int fflags = 0;
3253 #endif
3254
3255         cm_return_val_if_fail(file != NULL, NULL);
3256
3257         if (g_stat(file, &s) != 0) {
3258                 FILE_OP_ERROR(file, "stat");
3259                 return NULL;
3260         }
3261         if (S_ISDIR(s.st_mode)) {
3262                 g_warning("%s: is a directory", file);
3263                 return NULL;
3264         }
3265
3266 #ifdef G_OS_WIN32
3267         fp = g_fopen (file, "rb");
3268         if (fp == NULL) {
3269                 FILE_OP_ERROR(file, "open");
3270                 return NULL;
3271         }
3272 #else     
3273         /* test whether the file is readable without blocking */
3274         fd = g_open(file, O_RDONLY | O_NONBLOCK, 0);
3275         if (fd == -1) {
3276                 FILE_OP_ERROR(file, "open");
3277                 return NULL;
3278         }
3279
3280         FD_ZERO(&fds);
3281         FD_SET(fd, &fds);
3282
3283         /* allow for one second */
3284         err = select(fd+1, &fds, NULL, NULL, &timeout);
3285         if (err <= 0 || !FD_ISSET(fd, &fds)) {
3286                 if (err < 0) {
3287                         FILE_OP_ERROR(file, "select");
3288                 } else {
3289                         g_warning("%s: doesn't seem readable", file);
3290                 }
3291                 close(fd);
3292                 return NULL;
3293         }
3294         
3295         /* Now clear O_NONBLOCK */
3296         if ((fflags = fcntl(fd, F_GETFL)) < 0) {
3297                 FILE_OP_ERROR(file, "fcntl (F_GETFL)");
3298                 close(fd);
3299                 return NULL;
3300         }
3301         if (fcntl(fd, F_SETFL, (fflags & ~O_NONBLOCK)) < 0) {
3302                 FILE_OP_ERROR(file, "fcntl (F_SETFL)");
3303                 close(fd);
3304                 return NULL;
3305         }
3306         
3307         /* get the FILE pointer */
3308         fp = fdopen(fd, "rb");
3309
3310         if (fp == NULL) {
3311                 FILE_OP_ERROR(file, "fdopen");
3312                 close(fd); /* if fp isn't NULL, we'll use fclose instead! */
3313                 return NULL;
3314         }
3315 #endif
3316
3317         str = file_read_stream_to_str_full(fp, recode);
3318
3319         fclose(fp);
3320
3321         return str;
3322 }
3323
3324 gchar *file_read_to_str(const gchar *file)
3325 {
3326         return file_read_to_str_full(file, TRUE);
3327 }
3328 gchar *file_read_stream_to_str(FILE *fp)
3329 {
3330         return file_read_stream_to_str_full(fp, TRUE);
3331 }
3332
3333 gchar *file_read_to_str_no_recode(const gchar *file)
3334 {
3335         return file_read_to_str_full(file, FALSE);
3336 }
3337 gchar *file_read_stream_to_str_no_recode(FILE *fp)
3338 {
3339         return file_read_stream_to_str_full(fp, FALSE);
3340 }
3341
3342 char *fgets_crlf(char *buf, int size, FILE *stream)
3343 {
3344         gboolean is_cr = FALSE;
3345         gboolean last_was_cr = FALSE;
3346         int c = 0;
3347         char *cs;
3348
3349         cs = buf;
3350         while (--size > 0 && (c = getc(stream)) != EOF)
3351         {
3352                 *cs++ = c;
3353                 is_cr = (c == '\r');
3354                 if (c == '\n') {
3355                         break;
3356                 }
3357                 if (last_was_cr) {
3358                         *(--cs) = '\n';
3359                         cs++;
3360                         ungetc(c, stream);
3361                         break;
3362                 }
3363                 last_was_cr = is_cr;
3364         }
3365         if (c == EOF && cs == buf)
3366                 return NULL;
3367
3368         *cs = '\0';
3369
3370         return buf;     
3371 }
3372
3373 static gint execute_async(gchar *const argv[])
3374 {
3375         cm_return_val_if_fail(argv != NULL && argv[0] != NULL, -1);
3376
3377         if (g_spawn_async(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH,
3378                           NULL, NULL, NULL, FALSE) == FALSE) {
3379                 g_warning("couldn't execute command: %s", argv[0]);
3380                 return -1;
3381         }
3382
3383         return 0;
3384 }
3385
3386 static gint execute_sync(gchar *const argv[])
3387 {
3388         gint status;
3389
3390         cm_return_val_if_fail(argv != NULL && argv[0] != NULL, -1);
3391
3392 #ifdef G_OS_UNIX
3393         if (g_spawn_sync(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH,
3394                          NULL, NULL, NULL, NULL, &status, NULL) == FALSE) {
3395                 g_warning("couldn't execute command: %s", argv[0]);
3396                 return -1;
3397         }
3398
3399         if (WIFEXITED(status))
3400                 return WEXITSTATUS(status);
3401         else
3402                 return -1;
3403 #else
3404         if (g_spawn_sync(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH| 
3405                          G_SPAWN_CHILD_INHERITS_STDIN|G_SPAWN_LEAVE_DESCRIPTORS_OPEN,
3406                          NULL, NULL, NULL, NULL, &status, NULL) == FALSE) {
3407                 g_warning("couldn't execute command: %s", argv[0]);
3408                 return -1;
3409         }
3410
3411         return status;
3412 #endif
3413 }
3414
3415 gint execute_command_line(const gchar *cmdline, gboolean async)
3416 {
3417         gchar **argv;
3418         gint ret;
3419
3420         debug_print("execute_command_line(): executing: %s\n", cmdline?cmdline:"(null)");
3421
3422         argv = strsplit_with_quote(cmdline, " ", 0);
3423
3424         if (async)
3425                 ret = execute_async(argv);
3426         else
3427                 ret = execute_sync(argv);
3428
3429         g_strfreev(argv);
3430
3431         return ret;
3432 }
3433
3434 gchar *get_command_output(const gchar *cmdline)
3435 {
3436         gchar *child_stdout;
3437         gint status;
3438
3439         cm_return_val_if_fail(cmdline != NULL, NULL);
3440
3441         debug_print("get_command_output(): executing: %s\n", cmdline);
3442
3443         if (g_spawn_command_line_sync(cmdline, &child_stdout, NULL, &status,
3444                                       NULL) == FALSE) {
3445                 g_warning("couldn't execute command: %s", cmdline);
3446                 return NULL;
3447         }
3448
3449         return child_stdout;
3450 }
3451
3452 static gint is_unchanged_uri_char(char c)
3453 {
3454         switch (c) {
3455                 case '(':
3456                 case ')':
3457                         return 0;
3458                 default:
3459                         return 1;
3460         }
3461 }
3462
3463 static void encode_uri(gchar *encoded_uri, gint bufsize, const gchar *uri)
3464 {
3465         int i;
3466         int k;
3467
3468         k = 0;
3469         for(i = 0; i < strlen(uri) ; i++) {
3470                 if (is_unchanged_uri_char(uri[i])) {
3471                         if (k + 2 >= bufsize)
3472                                 break;
3473                         encoded_uri[k++] = uri[i];
3474                 }
3475                 else {
3476                         char * hexa = "0123456789ABCDEF";
3477
3478                         if (k + 4 >= bufsize)
3479                                 break;
3480                         encoded_uri[k++] = '%';
3481                         encoded_uri[k++] = hexa[uri[i] / 16];
3482                         encoded_uri[k++] = hexa[uri[i] % 16];
3483                 }
3484         }
3485         encoded_uri[k] = 0;
3486 }
3487
3488 gint open_uri(const gchar *uri, const gchar *cmdline)
3489 {
3490
3491 #ifndef G_OS_WIN32
3492         gchar buf[BUFFSIZE];
3493         gchar *p;
3494         gchar encoded_uri[BUFFSIZE];
3495         cm_return_val_if_fail(uri != NULL, -1);
3496
3497         /* an option to choose whether to use encode_uri or not ? */
3498         encode_uri(encoded_uri, BUFFSIZE, uri);
3499
3500         if (cmdline &&
3501             (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
3502             !strchr(p + 2, '%'))
3503                 g_snprintf(buf, sizeof(buf), cmdline, encoded_uri);
3504         else {
3505                 if (cmdline)
3506                         g_warning("Open URI command-line is invalid "
3507                                   "(there must be only one '%%s'): %s",
3508                                   cmdline);
3509                 g_snprintf(buf, sizeof(buf), DEFAULT_BROWSER_CMD, encoded_uri);
3510         }
3511
3512         execute_command_line(buf, TRUE);
3513 #else
3514         ShellExecute(NULL, "open", uri, NULL, NULL, SW_SHOW);
3515 #endif
3516         return 0;
3517 }
3518
3519 gint open_txt_editor(const gchar *filepath, const gchar *cmdline)
3520 {
3521         gchar buf[BUFFSIZE];
3522         gchar *p;
3523
3524         cm_return_val_if_fail(filepath != NULL, -1);
3525
3526         if (cmdline &&
3527             (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
3528             !strchr(p + 2, '%'))
3529                 g_snprintf(buf, sizeof(buf), cmdline, filepath);
3530         else {
3531                 if (cmdline)
3532                         g_warning("Open Text Editor command-line is invalid "
3533                                   "(there must be only one '%%s'): %s",
3534                                   cmdline);
3535                 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, filepath);
3536         }
3537
3538         execute_command_line(buf, TRUE);
3539
3540         return 0;
3541 }
3542
3543 time_t remote_tzoffset_sec(const gchar *zone)
3544 {
3545         static gchar ustzstr[] = "PSTPDTMSTMDTCSTCDTESTEDT";
3546         gchar zone3[4];
3547         gchar *p;
3548         gchar c;
3549         gint iustz;
3550         gint offset;
3551         time_t remoteoffset;
3552
3553         strncpy(zone3, zone, 3);
3554         zone3[3] = '\0';
3555         remoteoffset = 0;
3556
3557         if (sscanf(zone, "%c%d", &c, &offset) == 2 &&
3558             (c == '+' || c == '-')) {
3559                 remoteoffset = ((offset / 100) * 60 + (offset % 100)) * 60;
3560                 if (c == '-')
3561                         remoteoffset = -remoteoffset;
3562         } else if (!strncmp(zone, "UT" , 2) ||
3563                    !strncmp(zone, "GMT", 2)) {
3564                 remoteoffset = 0;
3565         } else if (strlen(zone3) == 3) {
3566                 for (p = ustzstr; *p != '\0'; p += 3) {
3567                         if (!g_ascii_strncasecmp(p, zone3, 3)) {
3568                                 iustz = ((gint)(p - ustzstr) / 3 + 1) / 2 - 8;
3569                                 remoteoffset = iustz * 3600;
3570                                 break;
3571                         }
3572                 }
3573                 if (*p == '\0')
3574                         return -1;
3575         } else if (strlen(zone3) == 1) {
3576                 switch (zone[0]) {
3577                 case 'Z': remoteoffset =   0; break;
3578                 case 'A': remoteoffset =  -1; break;
3579                 case 'B': remoteoffset =  -2; break;
3580                 case 'C': remoteoffset =  -3; break;
3581                 case 'D': remoteoffset =  -4; break;
3582                 case 'E': remoteoffset =  -5; break;
3583                 case 'F': remoteoffset =  -6; break;
3584                 case 'G': remoteoffset =  -7; break;
3585                 case 'H': remoteoffset =  -8; break;
3586                 case 'I': remoteoffset =  -9; break;
3587                 case 'K': remoteoffset = -10; break; /* J is not used */
3588                 case 'L': remoteoffset = -11; break;
3589                 case 'M': remoteoffset = -12; break;
3590                 case 'N': remoteoffset =   1; break;
3591                 case 'O': remoteoffset =   2; break;
3592                 case 'P': remoteoffset =   3; break;
3593                 case 'Q': remoteoffset =   4; break;
3594                 case 'R': remoteoffset =   5; break;
3595                 case 'S': remoteoffset =   6; break;
3596                 case 'T': remoteoffset =   7; break;
3597                 case 'U': remoteoffset =   8; break;
3598                 case 'V': remoteoffset =   9; break;
3599                 case 'W': remoteoffset =  10; break;
3600                 case 'X': remoteoffset =  11; break;
3601                 case 'Y': remoteoffset =  12; break;
3602                 default:  remoteoffset =   0; break;
3603                 }
3604                 remoteoffset = remoteoffset * 3600;
3605         } else
3606                 return -1;
3607
3608         return remoteoffset;
3609 }
3610
3611 time_t tzoffset_sec(time_t *now)
3612 {
3613         struct tm gmt, *lt;
3614         gint off;
3615         struct tm buf1, buf2;
3616 #ifdef G_OS_WIN32
3617         if (now && *now < 0)
3618                 return 0;
3619 #endif  
3620         gmt = *gmtime_r(now, &buf1);
3621         lt = localtime_r(now, &buf2);
3622
3623         off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
3624
3625         if (lt->tm_year < gmt.tm_year)
3626                 off -= 24 * 60;
3627         else if (lt->tm_year > gmt.tm_year)
3628                 off += 24 * 60;
3629         else if (lt->tm_yday < gmt.tm_yday)
3630                 off -= 24 * 60;
3631         else if (lt->tm_yday > gmt.tm_yday)
3632                 off += 24 * 60;
3633
3634         if (off >= 24 * 60)             /* should be impossible */
3635                 off = 23 * 60 + 59;     /* if not, insert silly value */
3636         if (off <= -24 * 60)
3637                 off = -(23 * 60 + 59);
3638
3639         return off * 60;
3640 }
3641
3642 /* calculate timezone offset */
3643 gchar *tzoffset(time_t *now)
3644 {
3645         static gchar offset_string[6];
3646         struct tm gmt, *lt;
3647         gint off;
3648         gchar sign = '+';
3649         struct tm buf1, buf2;
3650 #ifdef G_OS_WIN32
3651         if (now && *now < 0)
3652                 return 0;
3653 #endif
3654         gmt = *gmtime_r(now, &buf1);
3655         lt = localtime_r(now, &buf2);
3656
3657         off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
3658
3659         if (lt->tm_year < gmt.tm_year)
3660                 off -= 24 * 60;
3661         else if (lt->tm_year > gmt.tm_year)
3662                 off += 24 * 60;
3663         else if (lt->tm_yday < gmt.tm_yday)
3664                 off -= 24 * 60;
3665         else if (lt->tm_yday > gmt.tm_yday)
3666                 off += 24 * 60;
3667
3668         if (off < 0) {
3669                 sign = '-';
3670                 off = -off;
3671         }
3672
3673         if (off >= 24 * 60)             /* should be impossible */
3674                 off = 23 * 60 + 59;     /* if not, insert silly value */
3675
3676         sprintf(offset_string, "%c%02d%02d", sign, off / 60, off % 60);
3677
3678         return offset_string;
3679 }
3680
3681 void get_rfc822_date(gchar *buf, gint len)
3682 {
3683         struct tm *lt;
3684         time_t t;
3685         gchar day[4], mon[4];
3686         gint dd, hh, mm, ss, yyyy;
3687         struct tm buf1;
3688         gchar buf2[BUFFSIZE];
3689
3690         t = time(NULL);
3691         lt = localtime_r(&t, &buf1);
3692
3693         sscanf(asctime_r(lt, buf2), "%3s %3s %d %d:%d:%d %d\n",
3694                day, mon, &dd, &hh, &mm, &ss, &yyyy);
3695
3696         g_snprintf(buf, len, "%s, %d %s %d %02d:%02d:%02d %s",
3697                    day, dd, mon, yyyy, hh, mm, ss, tzoffset(&t));
3698 }
3699
3700 void debug_set_mode(gboolean mode)
3701 {
3702         debug_mode = mode;
3703 }
3704
3705 gboolean debug_get_mode(void)
3706 {
3707         return debug_mode;
3708 }
3709
3710 void debug_print_real(const gchar *format, ...)
3711 {
3712         va_list args;
3713         gchar buf[BUFFSIZE];
3714
3715         if (!debug_mode) return;
3716
3717         va_start(args, format);
3718         g_vsnprintf(buf, sizeof(buf), format, args);
3719         va_end(args);
3720
3721         g_print("%s", buf);
3722 }
3723
3724
3725 const char * debug_srcname(const char *file)
3726 {
3727         const char *s = strrchr (file, '/');
3728         return s? s+1:file;
3729 }
3730
3731
3732 void * subject_table_lookup(GHashTable *subject_table, gchar * subject)
3733 {
3734         if (subject == NULL)
3735                 subject = "";
3736         else
3737                 subject += subject_get_prefix_length(subject);
3738
3739         return g_hash_table_lookup(subject_table, subject);
3740 }
3741
3742 void subject_table_insert(GHashTable *subject_table, gchar * subject,
3743                           void * data)
3744 {
3745         if (subject == NULL || *subject == 0)
3746                 return;
3747         subject += subject_get_prefix_length(subject);
3748         g_hash_table_insert(subject_table, subject, data);
3749 }
3750
3751 void subject_table_remove(GHashTable *subject_table, gchar * subject)
3752 {
3753         if (subject == NULL)
3754                 return;
3755
3756         subject += subject_get_prefix_length(subject);
3757         g_hash_table_remove(subject_table, subject);
3758 }
3759
3760 #ifndef G_OS_WIN32
3761 static regex_t u_regex;
3762 static gboolean u_init_;
3763 #endif
3764
3765 void utils_free_regex(void)
3766 {
3767 #ifndef G_OS_WIN32
3768         if (u_init_) {
3769                 regfree(&u_regex);
3770                 u_init_ = FALSE;
3771         }
3772 #endif
3773 }
3774
3775 /*!
3776  *\brief        Check if a string is prefixed with known (combinations)
3777  *              of prefixes. The function assumes that each prefix
3778  *              is terminated by zero or exactly _one_ space.
3779  *
3780  *\param        str String to check for a prefixes
3781  *
3782  *\return       int Number of chars in the prefix that should be skipped
3783  *              for a "clean" subject line. If no prefix was found, 0
3784  *              is returned.
3785  */
3786 int subject_get_prefix_length(const gchar *subject)
3787 {
3788 #ifndef G_OS_WIN32
3789         /*!< Array with allowable reply prefixes regexps. */
3790         static const gchar * const prefixes[] = {
3791                 "Re\\:",                        /* "Re:" */
3792                 "Re\\[[1-9][0-9]*\\]\\:",       /* "Re[XXX]:" (non-conforming news mail clients) */
3793                 "Antw\\:",                      /* "Antw:" (Dutch / German Outlook) */
3794                 "Aw\\:",                        /* "Aw:"   (German) */
3795                 "Antwort\\:",                   /* "Antwort:" (German Lotus Notes) */
3796                 "Res\\:",                       /* "Res:" (Spanish/Brazilian Outlook) */
3797                 "Fw\\:",                        /* "Fw:" Forward */
3798                 "Fwd\\:",                       /* "Fwd:" Forward */
3799                 "Enc\\:",                       /* "Enc:" Forward (Brazilian Outlook) */
3800                 "Odp\\:",                       /* "Odp:" Re (Polish Outlook) */
3801                 "Rif\\:",                       /* "Rif:" (Italian Outlook) */
3802                 "Sv\\:",                        /* "Sv" (Norwegian) */
3803                 "Vs\\:",                        /* "Vs" (Norwegian) */
3804                 "Ad\\:",                        /* "Ad" (Norwegian) */
3805                 "\347\255\224\345\244\215\\:",  /* "Re" (Chinese, UTF-8) */
3806                 "R\303\251f\\. \\:",            /* "R�f. :" (French Lotus Notes) */
3807                 "Re \\:",                       /* "Re :" (French Yahoo Mail) */
3808                 /* add more */
3809         };
3810         const int PREFIXES = sizeof prefixes / sizeof prefixes[0];
3811         int n;
3812         regmatch_t pos;
3813
3814         if (!subject) return 0;
3815         if (!*subject) return 0;
3816
3817         if (!u_init_) {
3818                 GString *s = g_string_new("");
3819
3820                 for (n = 0; n < PREFIXES; n++)
3821                         /* Terminate each prefix regexpression by a
3822                          * "\ ?" (zero or ONE space), and OR them */
3823                         g_string_append_printf(s, "(%s\\ ?)%s",
3824                                           prefixes[n],
3825                                           n < PREFIXES - 1 ?
3826                                           "|" : "");
3827
3828                 g_string_prepend(s, "(");
3829                 g_string_append(s, ")+");       /* match at least once */
3830                 g_string_prepend(s, "^\\ *");   /* from beginning of line */
3831
3832
3833                 /* We now have something like "^\ *((PREFIX1\ ?)|(PREFIX2\ ?))+"
3834                  * TODO: Should this be       "^\ *(((PREFIX1)|(PREFIX2))\ ?)+" ??? */
3835                 if (regcomp(&u_regex, s->str, REG_EXTENDED | REG_ICASE)) {
3836                         debug_print("Error compiling regexp %s\n", s->str);
3837                         g_string_free(s, TRUE);
3838                         return 0;
3839                 } else {
3840                         u_init_ = TRUE;
3841                         g_string_free(s, TRUE);
3842                 }
3843         }
3844
3845         if (!regexec(&u_regex, subject, 1, &pos, 0) && pos.rm_so != -1)
3846                 return pos.rm_eo;
3847         else
3848                 return 0;
3849 #else
3850         /*!< Array with allowable reply prefixes regexps. */
3851         static const gchar * const prefixes[] = {
3852                 "re:",                  /* "Re:" */
3853                 "antw:",                        /* "Antw:" (Dutch / German Outlook) */
3854                 "aw:",                  /* "Aw:"   (German) */
3855                 "antwort:",                     /* "Antwort:" (German Lotus Notes) */
3856                 "res:",                 /* "Res:" (Spanish/Brazilian Outlook) */
3857                 "fw:",                  /* "Fw:" Forward */
3858                 "fwd:",                 /* "Fwd:" Forward */
3859                 "enc:",                 /* "Enc:" Forward (Brazilian Outlook) */
3860                 "odp:",                 /* "Odp:" Re (Polish Outlook) */
3861                 "rif:",                 /* "Rif:" (Italian Outlook) */
3862                 "sv:",                  /* "Sv" (Norwegian) */
3863                 "vs:",                  /* "Vs" (Norwegian) */
3864                 "ad:",                  /* "Ad" (Norwegian) */
3865                 "R\303\251f. :",        /* "R�f. :" (French Lotus Notes) */
3866                 "Re :",                 /* "Re :" (French Yahoo Mail) */
3867                 /* add more */
3868         };
3869         const int PREFIXES = sizeof prefixes / sizeof prefixes[0];
3870         int n;
3871
3872         if (!subject) return 0;
3873         if (!*subject) return 0;
3874
3875         for (n = 0; n < PREFIXES; n++) {
3876                 int len = strlen(prefixes[n]);
3877                 if (!strncasecmp(subject, prefixes[n], len)) {
3878                         if (subject[len] == ' ')
3879                                 return len+1;
3880                         else
3881                                 return len;
3882                 }
3883         }
3884         return 0;
3885 #endif
3886 }
3887 static guint g_stricase_hash(gconstpointer gptr)
3888 {
3889         guint hash_result = 0;
3890         const char *str;
3891
3892         for (str = gptr; str && *str; str++) {
3893                 hash_result += toupper(*str);
3894         }
3895
3896         return hash_result;
3897 }
3898
3899 static gint g_stricase_equal(gconstpointer gptr1, gconstpointer gptr2)
3900 {
3901         const char *str1 = gptr1;
3902         const char *str2 = gptr2;
3903
3904         return !strcasecmp(str1, str2);
3905 }
3906
3907 gint g_int_compare(gconstpointer a, gconstpointer b)
3908 {
3909         return GPOINTER_TO_INT(a) - GPOINTER_TO_INT(b);
3910 }
3911
3912 gchar *generate_msgid(gchar *buf, gint len, gchar *user_addr)
3913 {
3914         struct tm *lt;
3915         time_t t;
3916         gchar *addr;
3917         struct tm buft;
3918
3919         t = time(NULL);
3920         lt = localtime_r(&t, &buft);
3921
3922         if (user_addr != NULL)
3923               addr = g_strdup_printf(".%s", user_addr);
3924         else if (strlen(buf) != 0)
3925               addr = g_strdup_printf("@%s", buf);
3926         else
3927               addr = g_strdup_printf("@%s", get_domain_name());
3928
3929         /* Replace all @ but the last one in addr, with underscores.
3930          * RFC 2822 States that msg-id syntax only allows one @.
3931          */
3932         while (strchr(addr, '@') != NULL && strchr(addr, '@') != strrchr(addr, '@'))
3933                 *(strchr(addr, '@')) = '_';
3934
3935         g_snprintf(buf, len, "%04d%02d%02d%02d%02d%02d.%08x%s",
3936                    lt->tm_year + 1900, lt->tm_mon + 1,
3937                    lt->tm_mday, lt->tm_hour,
3938                    lt->tm_min, lt->tm_sec,
3939                    (guint) rand(), addr);
3940
3941         g_free(addr);
3942         return buf;
3943 }
3944
3945 /*
3946    quote_cmd_argument()
3947
3948    return a quoted string safely usable in argument of a command.
3949
3950    code is extracted and adapted from etPan! project -- DINH V. Ho�.
3951 */
3952
3953 gint quote_cmd_argument(gchar * result, guint size,
3954                         const gchar * path)
3955 {
3956         const gchar * p;
3957         gchar * result_p;
3958         guint remaining;
3959
3960         result_p = result;
3961         remaining = size;
3962
3963         for(p = path ; * p != '\0' ; p ++) {
3964
3965                 if (isalnum((guchar)*p) || (* p == '/')) {
3966                         if (remaining > 0) {
3967                                 * result_p = * p;
3968                                 result_p ++;
3969                                 remaining --;
3970                         }
3971                         else {
3972                                 result[size - 1] = '\0';
3973                                 return -1;
3974                         }
3975                 }
3976                 else {
3977                         if (remaining >= 2) {
3978                                 * result_p = '\\';
3979                                 result_p ++;
3980                                 * result_p = * p;
3981                                 result_p ++;
3982                                 remaining -= 2;
3983                         }
3984                         else {
3985                                 result[size - 1] = '\0';
3986                                 return -1;
3987                         }
3988                 }
3989         }
3990         if (remaining > 0) {
3991                 * result_p = '\0';
3992         }
3993         else {
3994                 result[size - 1] = '\0';
3995                 return -1;
3996         }
3997
3998         return 0;
3999 }
4000
4001 typedef struct
4002 {
4003         GNode           *parent;
4004         GNodeMapFunc     func;
4005         gpointer         data;
4006 } GNodeMapData;
4007
4008 static void g_node_map_recursive(GNode *node, gpointer data)
4009 {
4010         GNodeMapData *mapdata = (GNodeMapData *) data;
4011         GNode *newnode;
4012         GNodeMapData newmapdata;
4013         gpointer newdata;
4014
4015         newdata = mapdata->func(node->data, mapdata->data);
4016         if (newdata != NULL) {
4017                 newnode = g_node_new(newdata);
4018                 g_node_append(mapdata->parent, newnode);
4019
4020                 newmapdata.parent = newnode;
4021                 newmapdata.func = mapdata->func;
4022                 newmapdata.data = mapdata->data;
4023
4024                 g_node_children_foreach(node, G_TRAVERSE_ALL, g_node_map_recursive, &newmapdata);
4025         }
4026 }
4027
4028 GNode *g_node_map(GNode *node, GNodeMapFunc func, gpointer data)
4029 {
4030         GNode *root;
4031         GNodeMapData mapdata;
4032
4033         cm_return_val_if_fail(node != NULL, NULL);
4034         cm_return_val_if_fail(func != NULL, NULL);
4035
4036         root = g_node_new(func(node->data, data));
4037
4038         mapdata.parent = root;
4039         mapdata.func = func;
4040         mapdata.data = data;
4041
4042         g_node_children_foreach(node, G_TRAVERSE_ALL, g_node_map_recursive, &mapdata);
4043
4044         return root;
4045 }
4046
4047 #define HEX_TO_INT(val, hex)                    \
4048 {                                               \
4049         gchar c = hex;                          \
4050                                                 \
4051         if ('0' <= c && c <= '9') {             \
4052                 val = c - '0';                  \
4053         } else if ('a' <= c && c <= 'f') {      \
4054                 val = c - 'a' + 10;             \
4055         } else if ('A' <= c && c <= 'F') {      \
4056                 val = c - 'A' + 10;             \
4057         } else {                                \
4058                 val = -1;                       \
4059         }                                       \
4060 }
4061
4062 gboolean get_hex_value(guchar *out, gchar c1, gchar c2)
4063 {
4064         gint hi, lo;
4065
4066         HEX_TO_INT(hi, c1);
4067         HEX_TO_INT(lo, c2);
4068
4069         if (hi == -1 || lo == -1)
4070                 return FALSE;
4071
4072         *out = (hi << 4) + lo;
4073         return TRUE;
4074 }
4075
4076 #define INT_TO_HEX(hex, val)            \
4077 {                                       \
4078         if ((val) < 10)                 \
4079                 hex = '0' + (val);      \
4080         else                            \
4081                 hex = 'A' + (val) - 10; \
4082 }
4083
4084 void get_hex_str(gchar *out, guchar ch)
4085 {
4086         gchar hex;
4087
4088         INT_TO_HEX(hex, ch >> 4);
4089         *out++ = hex;
4090         INT_TO_HEX(hex, ch & 0x0f);
4091         *out   = hex;
4092 }
4093
4094 #undef REF_DEBUG
4095 #ifndef REF_DEBUG
4096 #define G_PRINT_REF 1 == 1 ? (void) 0 : (void)
4097 #else
4098 #define G_PRINT_REF g_print
4099 #endif
4100
4101 /*!
4102  *\brief        Register ref counted pointer. It is based on GBoxed, so should
4103  *              work with anything that uses the GType system. The semantics
4104  *              are similar to a C++ auto pointer, with the exception that
4105  *              C doesn't have automatic closure (calling destructors) when
4106  *              exiting a block scope.
4107  *              Use the \ref G_TYPE_AUTO_POINTER macro instead of calling this
4108  *              function directly.
4109  *
4110  *\return       GType A GType type.
4111  */
4112 GType g_auto_pointer_register(void)
4113 {
4114         static GType auto_pointer_type;
4115         if (!auto_pointer_type)
4116                 auto_pointer_type =
4117                         g_boxed_type_register_static
4118                                 ("G_TYPE_AUTO_POINTER",
4119                                  (GBoxedCopyFunc) g_auto_pointer_copy,
4120                                  (GBoxedFreeFunc) g_auto_pointer_free);
4121         return auto_pointer_type;
4122 }
4123
4124 /*!
4125  *\brief        Structure with g_new() allocated pointer guarded by the
4126  *              auto pointer
4127  */
4128 typedef struct AutoPointerRef {
4129         void          (*free) (gpointer);
4130         gpointer        pointer;
4131         glong           cnt;
4132 } AutoPointerRef;
4133
4134 /*!
4135  *\brief        The auto pointer opaque structure that references the
4136  *              pointer guard block.
4137  */
4138 typedef struct AutoPointer {
4139         AutoPointerRef *ref;
4140         gpointer        ptr; /*!< access to protected pointer */
4141 } AutoPointer;
4142
4143 /*!
4144  *\brief        Creates an auto pointer for a g_new()ed pointer. Example:
4145  *
4146  *\code
4147  *
4148  *              ... tell gtk_list_store it should use a G_TYPE_AUTO_POINTER
4149  *              ... when assigning, copying and freeing storage elements
4150  *
4151  *              gtk_list_store_new(N_S_COLUMNS,
4152  *                                 G_TYPE_AUTO_POINTER,
4153  *                                 -1);
4154  *
4155  *
4156  *              Template *precious_data = g_new0(Template, 1);
4157  *              g_pointer protect = g_auto_pointer_new(precious_data);
4158  *
4159  *              gtk_list_store_set(container, &iter,
4160  *                                 S_DATA, protect,
4161  *                                 -1);
4162  *
4163  *              ... the gtk_list_store has copied the pointer and
4164  *              ... incremented its reference count, we should free
4165  *              ... the auto pointer (in C++ a destructor would do
4166  *              ... this for us when leaving block scope)
4167  *
4168  *              g_auto_pointer_free(protect);
4169  *
4170  *              ... gtk_list_store_set() now manages the data. When
4171  *              ... *explicitly* requesting a pointer from the list
4172  *              ... store, don't forget you get a copy that should be
4173  *              ... freed with g_auto_pointer_free() eventually.
4174  *
4175  *\endcode
4176  *
4177  *\param        pointer Pointer to be guarded.
4178  *
4179  *\return       GAuto * Pointer that should be used in containers with
4180  *              GType support.
4181  */
4182 GAuto *g_auto_pointer_new(gpointer p)
4183 {
4184         AutoPointerRef *ref;
4185         AutoPointer    *ptr;
4186
4187         if (p == NULL)
4188                 return NULL;
4189
4190         ref = g_new0(AutoPointerRef, 1);
4191         ptr = g_new0(AutoPointer, 1);
4192
4193         ref->pointer = p;
4194         ref->free = g_free;
4195         ref->cnt = 1;
4196
4197         ptr->ref = ref;
4198         ptr->ptr = p;
4199
4200 #ifdef REF_DEBUG
4201         G_PRINT_REF ("XXXX ALLOC(%lx)\n", p);
4202 #endif
4203         return ptr;
4204 }
4205
4206 /*!
4207  *\brief        Allocate an autopointer using the passed \a free function to
4208  *              free the guarded pointer
4209  */
4210 GAuto *g_auto_pointer_new_with_free(gpointer p, GFreeFunc free_)
4211 {
4212         AutoPointer *aptr;
4213
4214         if (p == NULL)
4215                 return NULL;
4216
4217         aptr = g_auto_pointer_new(p);
4218         aptr->ref->free = free_;
4219         return aptr;
4220 }
4221
4222 gpointer g_auto_pointer_get_ptr(GAuto *auto_ptr)
4223 {
4224         if (auto_ptr == NULL)
4225                 return NULL;
4226         return ((AutoPointer *) auto_ptr)->ptr;
4227 }
4228
4229 /*!
4230  *\brief        Copies an auto pointer by. It's mostly not necessary
4231  *              to call this function directly, unless you copy/assign
4232  *              the guarded pointer.
4233  *
4234  *\param        auto_ptr Auto pointer returned by previous call to
4235  *              g_auto_pointer_new_XXX()
4236  *
4237  *\return       gpointer An auto pointer
4238  */
4239 GAuto *g_auto_pointer_copy(GAuto *auto_ptr)
4240 {
4241         AutoPointer     *ptr;
4242         AutoPointerRef  *ref;
4243         AutoPointer     *newp;
4244
4245         if (auto_ptr == NULL)
4246                 return NULL;
4247
4248         ptr = auto_ptr;
4249         ref = ptr->ref;
4250         newp = g_new0(AutoPointer, 1);
4251
4252         newp->ref = ref;
4253         newp->ptr = ref->pointer;
4254         ++(ref->cnt);
4255
4256 #ifdef REF_DEBUG
4257         G_PRINT_REF ("XXXX COPY(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4258 #endif
4259         return newp;
4260 }
4261
4262 /*!
4263  *\brief        Free an auto pointer
4264  */
4265 void g_auto_pointer_free(GAuto *auto_ptr)
4266 {
4267         AutoPointer     *ptr;
4268         AutoPointerRef  *ref;
4269
4270         if (auto_ptr == NULL)
4271                 return;
4272
4273         ptr = auto_ptr;
4274         ref = ptr->ref;
4275
4276         if (--(ref->cnt) == 0) {
4277 #ifdef REF_DEBUG
4278                 G_PRINT_REF ("XXXX FREE(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4279 #endif
4280                 ref->free(ref->pointer);
4281                 g_free(ref);
4282         }
4283 #ifdef REF_DEBUG
4284         else
4285                 G_PRINT_REF ("XXXX DEREF(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4286 #endif
4287         g_free(ptr);
4288 }
4289
4290 void replace_returns(gchar *str)
4291 {
4292         if (!str)
4293                 return;
4294
4295         while (strstr(str, "\n")) {
4296                 *strstr(str, "\n") = ' ';
4297         }
4298         while (strstr(str, "\r")) {
4299                 *strstr(str, "\r") = ' ';
4300         }
4301 }
4302
4303 /* get_uri_part() - retrieves a URI starting from scanpos.
4304                     Returns TRUE if succesful */
4305 gboolean get_uri_part(const gchar *start, const gchar *scanpos,
4306                              const gchar **bp, const gchar **ep, gboolean hdr)
4307 {
4308         const gchar *ep_;
4309         gint parenthese_cnt = 0;
4310
4311         cm_return_val_if_fail(start != NULL, FALSE);
4312         cm_return_val_if_fail(scanpos != NULL, FALSE);
4313         cm_return_val_if_fail(bp != NULL, FALSE);
4314         cm_return_val_if_fail(ep != NULL, FALSE);
4315
4316         *bp = scanpos;
4317
4318         /* find end point of URI */
4319         for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
4320                 if (!g_ascii_isgraph(*(const guchar *)ep_) ||
4321                     !IS_ASCII(*(const guchar *)ep_) ||
4322                     strchr("[]{}<>\"", *ep_)) {
4323                         break;
4324                 } else if (strchr("(", *ep_)) {
4325                         parenthese_cnt++;
4326                 } else if (strchr(")", *ep_)) {
4327                         if (parenthese_cnt > 0)
4328                                 parenthese_cnt--;
4329                         else
4330                                 break;
4331                 }
4332         }
4333
4334         /* no punctuation at end of string */
4335
4336         /* FIXME: this stripping of trailing punctuations may bite with other URIs.
4337          * should pass some URI type to this function and decide on that whether
4338          * to perform punctuation stripping */
4339
4340 #define IS_REAL_PUNCT(ch)       (g_ascii_ispunct(ch) && !strchr("/?=-_)", ch))
4341
4342         for (; ep_ - 1 > scanpos + 1 &&
4343                IS_REAL_PUNCT(*(ep_ - 1));
4344              ep_--)
4345                 ;
4346
4347 #undef IS_REAL_PUNCT
4348
4349         *ep = ep_;
4350
4351         return TRUE;
4352 }
4353
4354 gchar *make_uri_string(const gchar *bp, const gchar *ep)
4355 {
4356         while (bp && *bp && g_ascii_isspace(*bp))
4357                 bp++;
4358         return g_strndup(bp, ep - bp);
4359 }
4360
4361 /* valid mail address characters */
4362 #define IS_RFC822_CHAR(ch) \
4363         (IS_ASCII(ch) && \
4364          (ch) > 32   && \
4365          (ch) != 127 && \
4366          !g_ascii_isspace(ch) && \
4367          !strchr("(),;<>\"", (ch)))
4368
4369 /* alphabet and number within 7bit ASCII */
4370 #define IS_ASCII_ALNUM(ch)      (IS_ASCII(ch) && g_ascii_isalnum(ch))
4371 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
4372
4373 static GHashTable *create_domain_tab(void)
4374 {
4375         static const gchar *toplvl_domains [] = {
4376             "museum", "aero",
4377             "arpa", "coop", "info", "name", "biz", "com", "edu", "gov",
4378             "int", "mil", "net", "org", "ac", "ad", "ae", "af", "ag",
4379             "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au",
4380             "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi",
4381             "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
4382             "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl",
4383             "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz", "de",
4384             "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er",
4385             "es", "et", "eu", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd",
4386             "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
4387             "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
4388             "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir",
4389             "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
4390             "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc",
4391             "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
4392             "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq",
4393             "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na",
4394             "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu",
4395             "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm",
4396             "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru",
4397             "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj",
4398             "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
4399             "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to",
4400             "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um",
4401             "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu",
4402             "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw"
4403         };
4404         gint n;
4405         GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
4406
4407         cm_return_val_if_fail(htab, NULL);
4408         for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++)
4409                 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
4410         return htab;
4411 }
4412
4413 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
4414 {
4415         const gint MAX_LVL_DOM_NAME_LEN = 6;
4416         gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
4417         const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
4418         register gchar *p;
4419
4420         if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
4421                 return FALSE;
4422
4423         for (p = buf; p < m &&  first < last; *p++ = *first++)
4424                 ;
4425         *p = 0;
4426
4427         return g_hash_table_lookup(tab, buf) != NULL;
4428 }
4429
4430 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
4431 gboolean get_email_part(const gchar *start, const gchar *scanpos,
4432                                const gchar **bp, const gchar **ep, gboolean hdr)
4433 {
4434         /* more complex than the uri part because we need to scan back and forward starting from
4435          * the scan position. */
4436         gboolean result = FALSE;
4437         const gchar *bp_ = NULL;
4438         const gchar *ep_ = NULL;
4439         static GHashTable *dom_tab;
4440         const gchar *last_dot = NULL;
4441         const gchar *prelast_dot = NULL;
4442         const gchar *last_tld_char = NULL;
4443
4444         /* the informative part of the email address (describing the name
4445          * of the email address owner) may contain quoted parts. the
4446          * closure stack stores the last encountered quotes. */
4447         gchar closure_stack[128];
4448         gchar *ptr = closure_stack;
4449
4450         cm_return_val_if_fail(start != NULL, FALSE);
4451         cm_return_val_if_fail(scanpos != NULL, FALSE);
4452         cm_return_val_if_fail(bp != NULL, FALSE);
4453         cm_return_val_if_fail(ep != NULL, FALSE);
4454
4455         if (hdr) {
4456                 const gchar *start_quote = NULL;
4457                 const gchar *end_quote = NULL;
4458 search_again:
4459                 /* go to the real start */
4460                 if (start[0] == ',')
4461                         start++;
4462                 if (start[0] == ';')
4463                         start++;
4464                 while (start[0] == '\n' || start[0] == '\r')
4465                         start++;
4466                 while (start[0] == ' ' || start[0] == '\t')
4467                         start++;
4468
4469                 *bp = start;
4470                 
4471                 /* check if there are quotes (to skip , in them) */
4472                 if (*start == '"') {
4473                         start_quote = start;
4474                         start++;
4475                         end_quote = strstr(start, "\"");
4476                 } else {
4477                         start_quote = NULL;
4478                         end_quote = NULL;
4479                 }
4480                 
4481                 /* skip anything between quotes */
4482                 if (start_quote && end_quote) {
4483                         start = end_quote;
4484                         
4485                 } 
4486
4487                 /* find end (either , or ; or end of line) */
4488                 if (strstr(start, ",") && strstr(start, ";"))
4489                         *ep = strstr(start,",") < strstr(start, ";")
4490                                 ? strstr(start, ",") : strstr(start, ";");
4491                 else if (strstr(start, ","))
4492                         *ep = strstr(start, ",");
4493                 else if (strstr(start, ";"))
4494                         *ep = strstr(start, ";");
4495                 else
4496                         *ep = start+strlen(start);
4497
4498                 /* go back to real start */
4499                 if (start_quote && end_quote) {
4500                         start = start_quote;
4501                 }
4502
4503                 /* check there's still an @ in that, or search
4504                  * further if possible */
4505                 if (strstr(start, "@") && strstr(start, "@") < *ep)
4506                         return TRUE;
4507                 else if (*ep < start+strlen(start)) {
4508                         start = *ep;
4509                         goto search_again;
4510                 } else if (start_quote && strstr(start, "\"") && strstr(start, "\"") < *ep) {
4511                         *bp = start_quote;
4512                         return TRUE;
4513                 } else
4514                         return FALSE;
4515         }
4516
4517         if (!dom_tab)
4518                 dom_tab = create_domain_tab();
4519         cm_return_val_if_fail(dom_tab, FALSE);
4520
4521         /* scan start of address */
4522         for (bp_ = scanpos - 1;
4523              bp_ >= start && IS_RFC822_CHAR(*(const guchar *)bp_); bp_--)
4524                 ;
4525
4526         /* TODO: should start with an alnum? */
4527         bp_++;
4528         for (; bp_ < scanpos && !IS_ASCII_ALNUM(*(const guchar *)bp_); bp_++)
4529                 ;
4530
4531         if (bp_ != scanpos) {
4532                 /* scan end of address */
4533                 for (ep_ = scanpos + 1;
4534                      *ep_ && IS_RFC822_CHAR(*(const guchar *)ep_); ep_++)
4535                         if (*ep_ == '.') {
4536                                 prelast_dot = last_dot;
4537                                 last_dot = ep_;
4538                                 if (*(last_dot + 1) == '.') {
4539                                         if (prelast_dot == NULL)
4540                                                 return FALSE;
4541                                         last_dot = prelast_dot;
4542                                         break;
4543                                 }
4544                         }
4545
4546                 /* TODO: really should terminate with an alnum? */
4547                 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*(const guchar *)ep_);
4548                      --ep_)
4549                         ;
4550                 ep_++;
4551
4552                 if (last_dot == NULL)
4553                         return FALSE;
4554                 if (last_dot >= ep_)
4555                         last_dot = prelast_dot;
4556                 if (last_dot == NULL || (scanpos + 1 >= last_dot))
4557                         return FALSE;
4558                 last_dot++;
4559
4560                 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
4561                         if (*last_tld_char == '?')
4562                                 break;
4563
4564                 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
4565                         result = TRUE;
4566
4567                 *ep = ep_;
4568                 *bp = bp_;
4569         }
4570
4571         if (!result) return FALSE;
4572
4573         if (*ep_ && bp_ != start && *(bp_ - 1) == '"' && *(ep_) == '"'
4574         && *(ep_ + 1) == ' ' && *(ep_ + 2) == '<'
4575         && IS_RFC822_CHAR(*(ep_ + 3))) {
4576                 /* this informative part with an @ in it is
4577                  * followed by the email address */
4578                 ep_ += 3;
4579
4580                 /* go to matching '>' (or next non-rfc822 char, like \n) */
4581                 for (; *ep_ != '>' && *ep != '\0' && IS_RFC822_CHAR(*ep_); ep_++)
4582                         ;
4583
4584                 /* include the bracket */
4585                 if (*ep_ == '>') ep_++;
4586
4587                 /* include the leading quote */
4588                 bp_--;
4589
4590                 *ep = ep_;
4591                 *bp = bp_;
4592                 return TRUE;
4593         }
4594
4595         /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
4596         if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
4597                 return FALSE;
4598
4599         /* see if this is <bracketed>; in this case we also scan for the informative part. */
4600         if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
4601                 return TRUE;
4602
4603 #define FULL_STACK()    ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
4604 #define IN_STACK()      (ptr > closure_stack)
4605 /* has underrun check */
4606 #define POP_STACK()     if(IN_STACK()) --ptr
4607 /* has overrun check */
4608 #define PUSH_STACK(c)   if(!FULL_STACK()) *ptr++ = (c); else return TRUE
4609 /* has underrun check */
4610 #define PEEK_STACK()    (IN_STACK() ? *(ptr - 1) : 0)
4611
4612         ep_++;
4613
4614         /* scan for the informative part. */
4615         for (bp_ -= 2; bp_ >= start; bp_--) {
4616                 /* if closure on the stack keep scanning */
4617                 if (PEEK_STACK() == *bp_) {
4618                         POP_STACK();
4619                         continue;
4620                 }
4621                 if (!IN_STACK() && (*bp_ == '\'' || *bp_ == '"')) {
4622                         PUSH_STACK(*bp_);
4623                         continue;
4624                 }
4625
4626                 /* if nothing in the closure stack, do the special conditions
4627                  * the following if..else expression simply checks whether
4628                  * a token is acceptable. if not acceptable, the clause
4629                  * should terminate the loop with a 'break' */
4630                 if (!PEEK_STACK()) {
4631                         if (*bp_ == '-'
4632                         && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
4633                         && (((bp_ + 1) < ep_)    && isalnum(*(bp_ + 1)))) {
4634                                 /* hyphens are allowed, but only in
4635                                    between alnums */
4636                         } else if (strchr(" \"'", *bp_)) {
4637                                 /* but anything not being a punctiation
4638                                    is ok */
4639                         } else {
4640                                 break; /* anything else is rejected */
4641                         }
4642                 }
4643         }
4644
4645         bp_++;
4646
4647         /* scan forward (should start with an alnum) */
4648         for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
4649                 ;
4650 #undef PEEK_STACK
4651 #undef PUSH_STACK
4652 #undef POP_STACK
4653 #undef IN_STACK
4654 #undef FULL_STACK
4655
4656
4657         *bp = bp_;
4658         *ep = ep_;
4659
4660         return result;
4661 }
4662
4663 #undef IS_QUOTE
4664 #undef IS_ASCII_ALNUM
4665 #undef IS_RFC822_CHAR
4666
4667 gchar *make_email_string(const gchar *bp, const gchar *ep)
4668 {
4669         /* returns a mailto: URI; mailto: is also used to detect the
4670          * uri type later on in the button_pressed signal handler */
4671         gchar *tmp;
4672         gchar *result;
4673
4674         tmp = g_strndup(bp, ep - bp);
4675         result = g_strconcat("mailto:", tmp, NULL);
4676         g_free(tmp);
4677
4678         return result;
4679 }
4680
4681 gchar *make_http_string(const gchar *bp, const gchar *ep)
4682 {
4683         /* returns an http: URI; */
4684         gchar *tmp;
4685         gchar *result;
4686
4687         while (bp && *bp && g_ascii_isspace(*bp))
4688                 bp++;
4689         tmp = g_strndup(bp, ep - bp);
4690         result = g_strconcat("http://", tmp, NULL);
4691         g_free(tmp);
4692
4693         return result;
4694 }
4695
4696 static gchar *mailcap_get_command_in_file(const gchar *path, const gchar *type, const gchar *file_to_open)
4697 {
4698         FILE *fp = g_fopen(path, "rb");
4699         gchar buf[BUFFSIZE];
4700         gchar *result = NULL;
4701         if (!fp)
4702                 return NULL;
4703         while (fgets(buf, sizeof (buf), fp) != NULL) {
4704                 gchar **parts = g_strsplit(buf, ";", 3);
4705                 gchar *trimmed = parts[0];
4706                 while (trimmed[0] == ' ' || trimmed[0] == '\t')
4707                         trimmed++;
4708                 while (trimmed[strlen(trimmed)-1] == ' ' || trimmed[strlen(trimmed)-1] == '\t')
4709                         trimmed[strlen(trimmed)-1] = '\0';
4710
4711                 if (!strcmp(trimmed, type)) {
4712                         gboolean needsterminal = FALSE;
4713                         if (parts[2] && strstr(parts[2], "needsterminal")) {
4714                                 needsterminal = TRUE;
4715                         }
4716                         if (parts[2] && strstr(parts[2], "test=")) {
4717                                 gchar *orig_testcmd = g_strdup(strstr(parts[2], "test=")+5);
4718                                 gchar *testcmd = orig_testcmd;
4719                                 if (strstr(testcmd,";"))
4720                                         *(strstr(testcmd,";")) = '\0';
4721                                 while (testcmd[0] == ' ' || testcmd[0] == '\t')
4722                                         testcmd++;
4723                                 while (testcmd[strlen(testcmd)-1] == '\n')
4724                                         testcmd[strlen(testcmd)-1] = '\0';
4725                                 while (testcmd[strlen(testcmd)-1] == '\r')
4726                                         testcmd[strlen(testcmd)-1] = '\0';
4727                                 while (testcmd[strlen(testcmd)-1] == ' ' || testcmd[strlen(testcmd)-1] == '\t')
4728                                         testcmd[strlen(testcmd)-1] = '\0';
4729                                         
4730                                 if (strstr(testcmd, "%s")) {
4731                                         gchar *tmp = g_strdup_printf(testcmd, file_to_open);
4732                                         gint res = system(tmp);
4733                                         g_free(tmp);
4734                                         g_free(orig_testcmd);
4735                                         
4736                                         if (res != 0) {
4737                                                 g_strfreev(parts);
4738                                                 continue;
4739                                         }
4740                                 } else {
4741                                         gint res = system(testcmd);
4742                                         g_free(orig_testcmd);
4743                                         
4744                                         if (res != 0) {
4745                                                 g_strfreev(parts);
4746                                                 continue;
4747                                         }
4748                                 }
4749                         }
4750                         
4751                         trimmed = parts[1];
4752                         while (trimmed[0] == ' ' || trimmed[0] == '\t')
4753                                 trimmed++;
4754                         while (trimmed[strlen(trimmed)-1] == '\n')
4755                                 trimmed[strlen(trimmed)-1] = '\0';
4756                         while (trimmed[strlen(trimmed)-1] == '\r')
4757                                 trimmed[strlen(trimmed)-1] = '\0';
4758                         while (trimmed[strlen(trimmed)-1] == ' ' || trimmed[strlen(trimmed)-1] == '\t')
4759                                 trimmed[strlen(trimmed)-1] = '\0';
4760                         result = g_strdup(trimmed);
4761                         g_strfreev(parts);
4762                         fclose(fp);
4763                         /* if there are no single quotes around %s, add them.
4764                          * '.*%s.*' is ok, as in display 'png:%s'
4765                          */
4766                         if (strstr(result, "%s") 
4767                         && !(strstr(result, "'") < strstr(result,"%s") &&
4768                              strstr(strstr(result,"%s"), "'"))) {
4769                                 gchar *start = g_strdup(result);
4770                                 gchar *end = g_strdup(strstr(result, "%s")+2);
4771                                 gchar *tmp;
4772                                 *strstr(start, "%s") = '\0';
4773                                 tmp = g_strconcat(start,"'%s'",end, NULL);
4774                                 g_free(start);
4775                                 g_free(end);
4776                                 g_free(result);
4777                                 result = tmp;
4778                         }
4779                         if (needsterminal) {
4780                                 gchar *tmp = g_strdup_printf("xterm -e %s", result);
4781                                 g_free(result);
4782                                 result = tmp;
4783                         }
4784                         return result;
4785                 }
4786                 g_strfreev(parts);
4787         }
4788         fclose(fp);
4789         return NULL;
4790 }
4791 gchar *mailcap_get_command_for_type(const gchar *type, const gchar *file_to_open)
4792 {
4793         gchar *result = NULL;
4794         gchar *path = NULL;
4795         if (type == NULL)
4796                 return NULL;
4797         path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap", NULL);
4798         result = mailcap_get_command_in_file(path, type, file_to_open);
4799         g_free(path);
4800         if (result)
4801                 return result;
4802         result = mailcap_get_command_in_file("/etc/mailcap", type, file_to_open);
4803         return result;
4804 }
4805
4806 void mailcap_update_default(const gchar *type, const gchar *command)
4807 {
4808         gchar *path = NULL, *outpath = NULL;
4809         path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap", NULL);
4810         outpath = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap.new", NULL);
4811         FILE *fp = g_fopen(path, "rb");
4812         FILE *outfp = NULL;
4813         gchar buf[BUFFSIZE];
4814         gboolean err = FALSE;
4815
4816         if (!fp) {
4817                 fp = g_fopen(path, "a");
4818                 if (!fp) {
4819                         g_warning("failed to create file %s", path);
4820                         g_free(path);
4821                         g_free(outpath);
4822                         return;
4823                 }
4824                 fp = g_freopen(path, "rb", fp);
4825                 if (!fp) {
4826                         g_warning("failed to reopen file %s", path);
4827                         g_free(path);
4828                         g_free(outpath);
4829                         return;
4830                 }
4831         }
4832
4833         outfp = g_fopen(outpath, "wb");
4834         if (!outfp) {
4835                 g_warning("failed to create file %s", outpath);
4836                 g_free(path);
4837                 g_free(outpath);
4838                 fclose(fp);
4839                 return;
4840         }
4841         while (fp && fgets(buf, sizeof (buf), fp) != NULL) {
4842                 gchar **parts = g_strsplit(buf, ";", 3);
4843                 gchar *trimmed = parts[0];
4844                 while (trimmed[0] == ' ')
4845                         trimmed++;
4846                 while (trimmed[strlen(trimmed)-1] == ' ')
4847                         trimmed[strlen(trimmed)-1] = '\0';
4848
4849                 if (!strcmp(trimmed, type)) {
4850                         g_strfreev(parts);
4851                         continue;
4852                 }
4853                 else {
4854                         if(fputs(buf, outfp) == EOF) {
4855                                 err = TRUE;
4856                                 break;
4857                         }
4858                 }
4859                 g_strfreev(parts);
4860         }
4861         if (fprintf(outfp, "%s; %s\n", type, command) < 0)
4862                 err = TRUE;
4863
4864         if (fp)
4865                 fclose(fp);
4866
4867         if (fclose(outfp) == EOF)
4868                 err = TRUE;
4869                 
4870         if (!err)
4871                 g_rename(outpath, path);
4872
4873         g_free(path);
4874         g_free(outpath);
4875 }
4876
4877 gint copy_dir(const gchar *src, const gchar *dst)
4878 {
4879         GDir *dir;
4880         const gchar *name;
4881
4882         if ((dir = g_dir_open(src, 0, NULL)) == NULL) {
4883                 g_warning("failed to open directory: %s", src);
4884                 return -1;
4885         }
4886
4887         if (make_dir(dst) < 0)
4888                 return -1;
4889
4890         while ((name = g_dir_read_name(dir)) != NULL) {
4891                 gchar *old_file, *new_file;
4892                 old_file = g_strconcat(src, G_DIR_SEPARATOR_S, name, NULL);
4893                 new_file = g_strconcat(dst, G_DIR_SEPARATOR_S, name, NULL);
4894                 debug_print("copying: %s -> %s\n", old_file, new_file);
4895                 if (g_file_test(old_file, G_FILE_TEST_IS_REGULAR)) {
4896                         gint r = copy_file(old_file, new_file, TRUE);
4897                         if (r < 0) {
4898                                 g_dir_close(dir);
4899                                 return r;
4900                         }
4901                 }
4902 #ifndef G_OS_WIN32
4903                 /* Windows has no symlinks.  Or well, Vista seems to
4904                    have something like this but the semantics might be
4905                    different.  Thus we don't use it under Windows. */
4906                  else if (g_file_test(old_file, G_FILE_TEST_IS_SYMLINK)) {
4907                         GError *error;
4908                         gint r = 0;
4909                         gchar *target = g_file_read_link(old_file, &error);
4910                         if (target)
4911                                 r = symlink(target, new_file);
4912                         g_free(target);
4913                         if (r < 0) {
4914                                 g_dir_close(dir);
4915                                 return r;
4916                         }
4917                  }
4918 #endif /*G_OS_WIN32*/
4919                 else if (g_file_test(old_file, G_FILE_TEST_IS_DIR)) {
4920                         gint r = copy_dir(old_file, new_file);
4921                         if (r < 0) {
4922                                 g_dir_close(dir);
4923                                 return r;
4924                         }
4925                 }
4926         }
4927         g_dir_close(dir);
4928         return 0;
4929 }
4930
4931 /* crude test to see if a file is an email. */
4932 gboolean file_is_email (const gchar *filename)
4933 {
4934         FILE *fp = NULL;
4935         gchar buffer[2048];
4936         gint i = 0;
4937         gint score = 0;
4938         if (filename == NULL)
4939                 return FALSE;
4940         if ((fp = g_fopen(filename, "rb")) == NULL)
4941                 return FALSE;
4942         while (i < 60 && score < 3
4943                && fgets(buffer, sizeof (buffer), fp) > 0) {
4944                 if (!strncmp(buffer, "From:", strlen("From:")))
4945                         score++;
4946                 else if (!strncmp(buffer, "Date:", strlen("Date:")))
4947                         score++;
4948                 else if (!strncmp(buffer, "Message-ID:", strlen("Message-ID:")))
4949                         score++;
4950                 else if (!strncmp(buffer, "Subject:", strlen("Subject:")))
4951                         score++;
4952                 i++;
4953         }
4954         fclose(fp);
4955         return (score >= 3);
4956 }
4957
4958 gboolean sc_g_list_bigger(GList *list, gint max)
4959 {
4960         GList *cur = list;
4961         int i = 0;
4962         while (cur && i <= max+1) {
4963                 i++;
4964                 cur = cur->next;
4965         }
4966         return (i > max);
4967 }
4968
4969 gboolean sc_g_slist_bigger(GSList *list, gint max)
4970 {
4971         GSList *cur = list;
4972         int i = 0;
4973         while (cur && i <= max+1) {
4974                 i++;
4975                 cur = cur->next;
4976         }
4977         return (i > max);
4978 }
4979
4980 const gchar *daynames[] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL};
4981 const gchar *monthnames[] = {NULL, NULL, NULL, NULL, NULL, NULL, 
4982                              NULL, NULL, NULL, NULL, NULL, NULL};
4983 const gchar *s_daynames[] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL};
4984 const gchar *s_monthnames[] = {NULL, NULL, NULL, NULL, NULL, NULL, 
4985                              NULL, NULL, NULL, NULL, NULL, NULL};
4986
4987 gint daynames_len[] =     {0,0,0,0,0,0,0};
4988 gint monthnames_len[] =   {0,0,0,0,0,0,
4989                                  0,0,0,0,0,0};
4990 gint s_daynames_len[] =   {0,0,0,0,0,0,0};
4991 gint s_monthnames_len[] = {0,0,0,0,0,0,
4992                                  0,0,0,0,0,0};
4993 const gchar *s_am_up = NULL;
4994 const gchar *s_pm_up = NULL;
4995 const gchar *s_am_low = NULL;
4996 const gchar *s_pm_low = NULL;
4997
4998 gint s_am_up_len = 0;
4999 gint s_pm_up_len = 0;
5000 gint s_am_low_len = 0;
5001 gint s_pm_low_len = 0;
5002
5003 static gboolean time_names_init_done = FALSE;
5004
5005 static void init_time_names(void)
5006 {
5007         int i = 0;
5008
5009         daynames[0] = C_("Complete day name for use by strftime", "Sunday");
5010         daynames[1] = C_("Complete day name for use by strftime", "Monday");
5011         daynames[2] = C_("Complete day name for use by strftime", "Tuesday");
5012         daynames[3] = C_("Complete day name for use by strftime", "Wednesday");
5013         daynames[4] = C_("Complete day name for use by strftime", "Thursday");
5014         daynames[5] = C_("Complete day name for use by strftime", "Friday");
5015         daynames[6] = C_("Complete day name for use by strftime", "Saturday");
5016
5017         monthnames[0] = C_("Complete month name for use by strftime", "January");
5018         monthnames[1] = C_("Complete month name for use by strftime", "February");
5019         monthnames[2] = C_("Complete month name for use by strftime", "March");
5020         monthnames[3] = C_("Complete month name for use by strftime", "April");
5021         monthnames[4] = C_("Complete month name for use by strftime", "May");
5022         monthnames[5] = C_("Complete month name for use by strftime", "June");
5023         monthnames[6] = C_("Complete month name for use by strftime", "July");
5024         monthnames[7] = C_("Complete month name for use by strftime", "August");
5025         monthnames[8] = C_("Complete month name for use by strftime", "September");
5026         monthnames[9] = C_("Complete month name for use by strftime", "October");
5027         monthnames[10] = C_("Complete month name for use by strftime", "November");
5028         monthnames[11] = C_("Complete month name for use by strftime", "December");
5029
5030         s_daynames[0] = C_("Abbr. day name for use by strftime", "Sun");
5031         s_daynames[1] = C_("Abbr. day name for use by strftime", "Mon");
5032         s_daynames[2] = C_("Abbr. day name for use by strftime", "Tue");
5033         s_daynames[3] = C_("Abbr. day name for use by strftime", "Wed");
5034         s_daynames[4] = C_("Abbr. day name for use by strftime", "Thu");
5035         s_daynames[5] = C_("Abbr. day name for use by strftime", "Fri");
5036         s_daynames[6] = C_("Abbr. day name for use by strftime", "Sat");
5037         
5038         s_monthnames[0] = C_("Abbr. month name for use by strftime", "Jan");
5039         s_monthnames[1] = C_("Abbr. month name for use by strftime", "Feb");
5040         s_monthnames[2] = C_("Abbr. month name for use by strftime", "Mar");
5041         s_monthnames[3] = C_("Abbr. month name for use by strftime", "Apr");
5042         s_monthnames[4] = C_("Abbr. month name for use by strftime", "May");
5043         s_monthnames[5] = C_("Abbr. month name for use by strftime", "Jun");
5044         s_monthnames[6] = C_("Abbr. month name for use by strftime", "Jul");
5045         s_monthnames[7] = C_("Abbr. month name for use by strftime", "Aug");
5046         s_monthnames[8] = C_("Abbr. month name for use by strftime", "Sep");
5047         s_monthnames[9] = C_("Abbr. month name for use by strftime", "Oct");
5048         s_monthnames[10] = C_("Abbr. month name for use by strftime", "Nov");
5049         s_monthnames[11] = C_("Abbr. month name for use by strftime", "Dec");
5050
5051         for (i = 0; i < 7; i++) {
5052                 daynames_len[i] = strlen(daynames[i]);
5053                 s_daynames_len[i] = strlen(s_daynames[i]);
5054         }
5055         for (i = 0; i < 12; i++) {
5056                 monthnames_len[i] = strlen(monthnames[i]);
5057                 s_monthnames_len[i] = strlen(s_monthnames[i]);
5058         }
5059
5060         s_am_up = C_("For use by strftime (morning)", "AM");
5061         s_pm_up = C_("For use by strftime (afternoon)", "PM");
5062         s_am_low = C_("For use by strftime (morning, lowercase)", "am");
5063         s_pm_low = C_("For use by strftime (afternoon, lowercase)", "pm");
5064         
5065         s_am_up_len = strlen(s_am_up);
5066         s_pm_up_len = strlen(s_pm_up);
5067         s_am_low_len = strlen(s_am_low);
5068         s_pm_low_len = strlen(s_pm_low);
5069
5070         time_names_init_done = TRUE;
5071 }
5072
5073 #define CHECK_SIZE() {                  \
5074         total_done += len;              \
5075         if (total_done >= buflen) {     \
5076                 buf[buflen-1] = '\0';   \
5077                 return 0;               \
5078         }                               \
5079 }
5080
5081 size_t fast_strftime(gchar *buf, gint buflen, const gchar *format, struct tm *lt)
5082 {
5083         gchar *curpos = buf;
5084         gint total_done = 0;
5085         gchar subbuf[64], subfmt[64];
5086         static time_t last_tzset = (time_t)0;
5087         
5088         if (!time_names_init_done)
5089                 init_time_names();
5090         
5091         if (format == NULL || lt == NULL)
5092                 return 0;
5093                 
5094         if (last_tzset != time(NULL)) {
5095                 tzset();
5096                 last_tzset = time(NULL);
5097         }
5098         while(*format) {
5099                 if (*format == '%') {
5100                         gint len = 0, tmp = 0;
5101                         format++;
5102                         switch(*format) {
5103                         case '%':
5104                                 len = 1; CHECK_SIZE();
5105                                 *curpos = '%';
5106                                 break;
5107                         case 'a':
5108                                 len = s_daynames_len[lt->tm_wday]; CHECK_SIZE();
5109                                 strncpy2(curpos, s_daynames[lt->tm_wday], buflen - total_done);
5110                                 break;
5111                         case 'A':
5112                                 len = daynames_len[lt->tm_wday]; CHECK_SIZE();
5113                                 strncpy2(curpos, daynames[lt->tm_wday], buflen - total_done);
5114                                 break;
5115                         case 'b':
5116                         case 'h':
5117                                 len = s_monthnames_len[lt->tm_mon]; CHECK_SIZE();
5118                                 strncpy2(curpos, s_monthnames[lt->tm_mon], buflen - total_done);
5119                                 break;
5120                         case 'B':
5121                                 len = monthnames_len[lt->tm_mon]; CHECK_SIZE();
5122                                 strncpy2(curpos, monthnames[lt->tm_mon], buflen - total_done);
5123                                 break;
5124                         case 'c':
5125                                 strftime(subbuf, 64, "%c", lt);
5126                                 len = strlen(subbuf); CHECK_SIZE();
5127                                 strncpy2(curpos, subbuf, buflen - total_done);
5128                                 break;
5129                         case 'C':
5130                                 total_done += 2; CHECK_SIZE();
5131                                 tmp = (lt->tm_year + 1900)/100;
5132                                 *curpos++ = '0'+(tmp / 10);
5133                                 *curpos++ = '0'+(tmp % 10);
5134                                 break;
5135                         case 'd':
5136                                 total_done += 2; CHECK_SIZE();
5137                                 *curpos++ = '0'+(lt->tm_mday / 10);
5138                                 *curpos++ = '0'+(lt->tm_mday % 10);
5139                                 break;
5140                         case 'D':
5141                                 total_done += 8; CHECK_SIZE();
5142                                 *curpos++ = '0'+((lt->tm_mon+1) / 10);
5143                                 *curpos++ = '0'+((lt->tm_mon+1) % 10);
5144                                 *curpos++ = '/';
5145                                 *curpos++ = '0'+(lt->tm_mday / 10);
5146                                 *curpos++ = '0'+(lt->tm_mday % 10);
5147                                 *curpos++ = '/';
5148                                 tmp = lt->tm_year%100;
5149                                 *curpos++ = '0'+(tmp / 10);
5150                                 *curpos++ = '0'+(tmp % 10);
5151                                 break;
5152                         case 'e':
5153                                 len = 2; CHECK_SIZE();
5154                                 snprintf(curpos, buflen - total_done, "%2d", lt->tm_mday);
5155                                 break;
5156                         case 'F':
5157                                 len = 10; CHECK_SIZE();
5158                                 snprintf(curpos, buflen - total_done, "%4d-%02d-%02d", 
5159                                         lt->tm_year + 1900, lt->tm_mon +1, lt->tm_mday);
5160                                 break;
5161                         case 'H':
5162                                 total_done += 2; CHECK_SIZE();
5163                                 *curpos++ = '0'+(lt->tm_hour / 10);
5164                                 *curpos++ = '0'+(lt->tm_hour % 10);
5165                                 break;
5166                         case 'I':
5167                                 total_done += 2; CHECK_SIZE();
5168                                 tmp = lt->tm_hour;
5169                                 if (tmp > 12)
5170                                         tmp -= 12;
5171                                 else if (tmp == 0)
5172                                         tmp = 12;
5173                                 *curpos++ = '0'+(tmp / 10);
5174                                 *curpos++ = '0'+(tmp % 10);
5175                                 break;
5176                         case 'j':
5177                                 len = 3; CHECK_SIZE();
5178                                 snprintf(curpos, buflen - total_done, "%03d", lt->tm_yday+1);
5179                                 break;
5180                         case 'k':
5181                                 len = 2; CHECK_SIZE();
5182                                 snprintf(curpos, buflen - total_done, "%2d", lt->tm_hour);
5183                                 break;
5184                         case 'l':
5185                                 len = 2; CHECK_SIZE();
5186                                 tmp = lt->tm_hour;
5187                                 if (tmp > 12)
5188                                         tmp -= 12;
5189                                 else if (tmp == 0)
5190                                         tmp = 12;
5191                                 snprintf(curpos, buflen - total_done, "%2d", tmp);
5192                                 break;
5193                         case 'm':
5194                                 total_done += 2; CHECK_SIZE();
5195                                 tmp = lt->tm_mon + 1;
5196                                 *curpos++ = '0'+(tmp / 10);
5197                                 *curpos++ = '0'+(tmp % 10);
5198                                 break;
5199                         case 'M':
5200                                 total_done += 2; CHECK_SIZE();
5201                                 *curpos++ = '0'+(lt->tm_min / 10);
5202                                 *curpos++ = '0'+(lt->tm_min % 10);
5203                                 break;
5204                         case 'n':
5205                                 len = 1; CHECK_SIZE();
5206                                 *curpos = '\n';
5207                                 break;
5208                         case 'p':
5209                                 if (lt->tm_hour >= 12) {
5210                                         len = s_pm_up_len; CHECK_SIZE();
5211                                         snprintf(curpos, buflen-total_done, "%s", s_pm_up);
5212                                 } else {
5213                                         len = s_am_up_len; CHECK_SIZE();
5214                                         snprintf(curpos, buflen-total_done, "%s", s_am_up);
5215                                 }
5216                                 break;
5217                         case 'P':
5218                                 if (lt->tm_hour >= 12) {
5219                                         len = s_pm_low_len; CHECK_SIZE();
5220                                         snprintf(curpos, buflen-total_done, "%s", s_pm_low);
5221                                 } else {
5222                                         len = s_am_low_len; CHECK_SIZE();
5223                                         snprintf(curpos, buflen-total_done, "%s", s_am_low);
5224                                 }
5225                                 break;
5226                         case 'r':
5227                                 strftime(subbuf, 64, "%r", lt);
5228                                 len = strlen(subbuf); CHECK_SIZE();
5229                                 strncpy2(curpos, subbuf, buflen - total_done);
5230                                 break;
5231                         case 'R':
5232                                 total_done += 5; CHECK_SIZE();
5233                                 *curpos++ = '0'+(lt->tm_hour / 10);
5234                                 *curpos++ = '0'+(lt->tm_hour % 10);
5235                                 *curpos++ = ':';
5236                                 *curpos++ = '0'+(lt->tm_min / 10);
5237                                 *curpos++ = '0'+(lt->tm_min % 10);
5238                                 break;
5239                         case 's':
5240                                 snprintf(subbuf, 64, "%ld", mktime(lt));
5241                                 len = strlen(subbuf); CHECK_SIZE();
5242                                 strncpy2(curpos, subbuf, buflen - total_done);
5243                                 break;
5244                         case 'S':
5245                                 total_done += 2; CHECK_SIZE();
5246                                 *curpos++ = '0'+(lt->tm_sec / 10);
5247                                 *curpos++ = '0'+(lt->tm_sec % 10);
5248                                 break;
5249                         case 't':
5250                                 len = 1; CHECK_SIZE();
5251                                 *curpos = '\t';
5252                                 break;
5253                         case 'T':
5254                                 total_done += 8; CHECK_SIZE();
5255                                 *curpos++ = '0'+(lt->tm_hour / 10);
5256                                 *curpos++ = '0'+(lt->tm_hour % 10);
5257                                 *curpos++ = ':';
5258                                 *curpos++ = '0'+(lt->tm_min / 10);
5259                                 *curpos++ = '0'+(lt->tm_min % 10);
5260                                 *curpos++ = ':';
5261                                 *curpos++ = '0'+(lt->tm_sec / 10);
5262                                 *curpos++ = '0'+(lt->tm_sec % 10);
5263                                 break;
5264                         case 'u':
5265                                 len = 1; CHECK_SIZE();
5266                                 snprintf(curpos, buflen - total_done, "%d", lt->tm_wday == 0 ? 7: lt->tm_wday);
5267                                 break;
5268                         case 'w':
5269                                 len = 1; CHECK_SIZE();
5270                                 snprintf(curpos, buflen - total_done, "%d", lt->tm_wday);
5271                                 break;
5272                         case 'x':
5273                                 strftime(subbuf, 64, "%x", lt);
5274                                 len = strlen(subbuf); CHECK_SIZE();
5275                                 strncpy2(curpos, subbuf, buflen - total_done);
5276                                 break;
5277                         case 'X':
5278                                 strftime(subbuf, 64, "%X", lt);
5279                                 len = strlen(subbuf); CHECK_SIZE();
5280                                 strncpy2(curpos, subbuf, buflen - total_done);
5281                                 break;
5282                         case 'y':
5283                                 total_done += 2; CHECK_SIZE();
5284                                 tmp = lt->tm_year%100;
5285                                 *curpos++ = '0'+(tmp / 10);
5286                                 *curpos++ = '0'+(tmp % 10);
5287                                 break;
5288                         case 'Y':
5289                                 len = 4; CHECK_SIZE();
5290                                 snprintf(curpos, buflen - total_done, "%4d", lt->tm_year + 1900);
5291                                 break;
5292                         case 'G':
5293                         case 'g':
5294                         case 'U':
5295                         case 'V':
5296                         case 'W':
5297                         case 'z':
5298                         case 'Z':
5299                         case '+':
5300                                 /* let these complicated ones be done with the libc */
5301                                 snprintf(subfmt, 64, "%%%c", *format);
5302                                 strftime(subbuf, 64, subfmt, lt);
5303                                 len = strlen(subbuf); CHECK_SIZE();
5304                                 strncpy2(curpos, subbuf, buflen - total_done);
5305                                 break;
5306                         case 'E':
5307                         case 'O':
5308                                 /* let these complicated modifiers be done with the libc */
5309                                 snprintf(subfmt, 64, "%%%c%c", *format, *(format+1));
5310                                 strftime(subbuf, 64, subfmt, lt);
5311                                 len = strlen(subbuf); CHECK_SIZE();
5312                                 strncpy2(curpos, subbuf, buflen - total_done);
5313                                 format++;
5314                                 break;
5315                         default:
5316                                 g_warning("format error (%c)", *format);
5317                                 *curpos = '\0';
5318                                 return total_done;
5319                         }
5320                         curpos += len;
5321                         format++;
5322                 } else {
5323                         int len = 1; CHECK_SIZE();
5324                         *curpos++ = *format++; 
5325                 }
5326         }
5327         *curpos = '\0';
5328         return total_done;
5329 }
5330
5331 gboolean prefs_common_get_use_shred(void);
5332
5333
5334 #ifdef G_OS_WIN32
5335 #define WEXITSTATUS(x) (x)
5336 #endif
5337
5338 int claws_unlink(const gchar *filename) 
5339 {
5340         GStatBuf s;
5341         static int found_shred = -1;
5342         static const gchar *args[4];
5343
5344         if (filename == NULL)
5345                 return 0;
5346
5347         if (prefs_common_get_use_shred()) {
5348                 if (found_shred == -1) {
5349                         /* init */
5350                         args[0] = g_find_program_in_path("shred");
5351                         debug_print("found shred: %s\n", args[0]);
5352                         found_shred = (args[0] != NULL) ? 1:0;
5353                         args[1] = "-f";
5354                         args[3] = NULL;
5355                 }
5356                 if (found_shred == 1) {
5357                         if (g_stat(filename, &s) == 0 && S_ISREG(s.st_mode)) {
5358                                 if (s.st_nlink == 1) {
5359                                         gint status=0;
5360                                         args[2] = filename;
5361                                         g_spawn_sync(NULL, (gchar **)args, NULL, 0,
5362                                          NULL, NULL, NULL, NULL, &status, NULL);
5363                                         debug_print("%s %s exited with status %d\n",
5364                                                 args[0], filename, WEXITSTATUS(status));
5365                                         if (truncate(filename, 0) < 0)
5366                                                 g_warning("couln't truncate: %s", filename);
5367                                 }
5368                         }
5369                 }
5370         }
5371         return g_unlink(filename);
5372 }
5373
5374 GMutex *cm_mutex_new(void) {
5375 #if GLIB_CHECK_VERSION(2,32,0)
5376         GMutex *m = g_new0(GMutex, 1);
5377         g_mutex_init(m);
5378         return m;
5379 #else
5380         return g_mutex_new();
5381 #endif
5382 }
5383
5384 void cm_mutex_free(GMutex *mutex) {
5385 #if GLIB_CHECK_VERSION(2,32,0)
5386         g_mutex_clear(mutex);
5387         g_free(mutex);
5388 #else
5389         g_mutex_free(mutex);
5390 #endif
5391 }
5392
5393 static gchar *canonical_list_to_file(GSList *list)
5394 {
5395         GString *result = g_string_new(NULL);
5396         GSList *pathlist = g_slist_reverse(g_slist_copy(list));
5397         GSList *cur;
5398         gchar *str;
5399
5400 #ifndef G_OS_WIN32
5401         result = g_string_append(result, G_DIR_SEPARATOR_S);
5402 #else
5403         if (pathlist->data) {
5404                 const gchar *root = (gchar *)pathlist->data;
5405                 if (root[0] != '\0' && g_ascii_isalpha(root[0]) &&
5406                     root[1] == ':') {
5407                         /* drive - don't prepend dir separator */
5408                 } else {
5409                         result = g_string_append(result, G_DIR_SEPARATOR_S);
5410                 }
5411         }
5412 #endif
5413
5414         for (cur = pathlist; cur; cur = cur->next) {
5415                 result = g_string_append(result, (gchar *)cur->data);
5416                 if (cur->next)
5417                         result = g_string_append(result, G_DIR_SEPARATOR_S);
5418         }
5419         g_slist_free(pathlist);
5420
5421         str = result->str;
5422         g_string_free(result, FALSE);
5423
5424         return str;
5425 }
5426
5427 static GSList *cm_split_path(const gchar *filename, int depth)
5428 {
5429         gchar **path_parts;
5430         GSList *canonical_parts = NULL;
5431         GStatBuf st;
5432         int i;
5433         gboolean follow_symlinks = TRUE;
5434
5435         if (depth > 32) {
5436 #ifndef G_OS_WIN32
5437                 errno = ELOOP;
5438 #else
5439                 errno = EINVAL; /* can't happen, no symlink handling */
5440 #endif
5441                 return NULL;
5442         }
5443
5444         if (!g_path_is_absolute(filename)) {
5445                 errno =EINVAL;
5446                 return NULL;
5447         }
5448
5449         path_parts = g_strsplit(filename, G_DIR_SEPARATOR_S, -1);
5450
5451         for (i = 0; path_parts[i] != NULL; i++) {
5452                 if (!strcmp(path_parts[i], ""))
5453                         continue;
5454                 if (!strcmp(path_parts[i], "."))
5455                         continue;
5456                 else if (!strcmp(path_parts[i], "..")) {
5457                         if (i == 0) {
5458                                 errno =ENOTDIR;
5459                                 return NULL;
5460                         }
5461                         else /* Remove the last inserted element */
5462                                 canonical_parts = 
5463                                         g_slist_delete_link(canonical_parts,
5464                                                             canonical_parts);
5465                 } else {
5466                         gchar *tmp_path;
5467
5468                         canonical_parts = g_slist_prepend(canonical_parts,
5469                                                 g_strdup(path_parts[i]));
5470
5471                         tmp_path = canonical_list_to_file(canonical_parts);
5472
5473                         if(g_stat(tmp_path, &st) < 0) {
5474                                 if (errno == ENOENT) {
5475                                         errno = 0;
5476                                         follow_symlinks = FALSE;
5477                                 }
5478                                 if (errno != 0) {
5479                                         g_free(tmp_path);
5480                                         slist_free_strings_full(canonical_parts);
5481                                         g_strfreev(path_parts);
5482
5483                                         return NULL;
5484                                 }
5485                         }
5486 #ifndef G_OS_WIN32
5487                         if (follow_symlinks && g_file_test(tmp_path, G_FILE_TEST_IS_SYMLINK)) {
5488                                 GError *error = NULL;
5489                                 gchar *target = g_file_read_link(tmp_path, &error);
5490
5491                                 if (!g_path_is_absolute(target)) {
5492                                         /* remove the last inserted element */
5493                                         canonical_parts = 
5494                                                 g_slist_delete_link(canonical_parts,
5495                                                             canonical_parts);
5496                                         /* add the target */
5497                                         canonical_parts = g_slist_prepend(canonical_parts,
5498                                                 g_strdup(target));
5499                                         g_free(target);
5500
5501                                         /* and get the new target */
5502                                         target = canonical_list_to_file(canonical_parts);
5503                                 }
5504
5505                                 /* restart from absolute target */
5506                                 slist_free_strings_full(canonical_parts);
5507                                 canonical_parts = NULL;
5508                                 if (!error)
5509                                         canonical_parts = cm_split_path(target, depth + 1);
5510                                 else
5511                                         g_error_free(error);
5512                                 if (canonical_parts == NULL) {
5513                                         g_free(tmp_path);
5514                                         g_strfreev(path_parts);
5515                                         return NULL;
5516                                 }
5517                                 g_free(target);
5518                         }
5519 #endif
5520                         g_free(tmp_path);
5521                 }
5522         }
5523         g_strfreev(path_parts);
5524         return canonical_parts;
5525 }
5526
5527 /*
5528  * Canonicalize a filename, resolving symlinks along the way.
5529  * Returns a negative errno in case of error.
5530  */
5531 int cm_canonicalize_filename(const gchar *filename, gchar **canonical_name) {
5532         GSList *canonical_parts;
5533         gboolean is_absolute;
5534
5535         if (filename == NULL)
5536                 return -EINVAL;
5537         if (canonical_name == NULL)
5538                 return -EINVAL;
5539         *canonical_name = NULL;
5540
5541         is_absolute = g_path_is_absolute(filename);
5542         if (!is_absolute) {
5543                 /* Always work on absolute filenames. */
5544                 gchar *cur = g_get_current_dir();
5545                 gchar *absolute_filename = g_strconcat(cur, G_DIR_SEPARATOR_S,
5546                                                        filename, NULL);
5547                 
5548                 canonical_parts = cm_split_path(absolute_filename, 0);
5549                 g_free(absolute_filename);
5550                 g_free(cur);
5551         } else
5552                 canonical_parts = cm_split_path(filename, 0);
5553
5554         if (canonical_parts == NULL)
5555                 return -errno;
5556
5557         *canonical_name = canonical_list_to_file(canonical_parts);
5558         slist_free_strings_full(canonical_parts);
5559         return 0;
5560 }
5561
5562 /* Returns a decoded base64 string, guaranteed to be null-terminated. */
5563 guchar *g_base64_decode_zero(const gchar *text, gsize *out_len)
5564 {
5565         gchar *tmp = g_base64_decode(text, out_len);
5566         gchar *out = g_strndup(tmp, *out_len);
5567
5568         g_free(tmp);
5569
5570         if (strlen(out) != *out_len) {
5571                 g_warning ("strlen(out) %zd != *out_len %" G_GSIZE_FORMAT, strlen(out), *out_len);
5572         }
5573
5574         return out;
5575 }
5576
5577 #if !GLIB_CHECK_VERSION(2, 30, 0)
5578 /**
5579  * g_utf8_substring:
5580  * @str: a UTF-8 encoded string
5581  * @start_pos: a character offset within @str
5582  * @end_pos: another character offset within @str
5583  *
5584  * Copies a substring out of a UTF-8 encoded string.
5585  * The substring will contain @end_pos - @start_pos
5586  * characters.
5587  *
5588  * Returns: a newly allocated copy of the requested
5589  *     substring. Free with g_free() when no longer needed.
5590  *
5591  * Since: GLIB 2.30
5592  */
5593 gchar *
5594 g_utf8_substring (const gchar *str,
5595                                   glong            start_pos,
5596                                   glong            end_pos)
5597 {
5598   gchar *start, *end, *out;
5599
5600   start = g_utf8_offset_to_pointer (str, start_pos);
5601   end = g_utf8_offset_to_pointer (start, end_pos - start_pos);
5602
5603   out = g_malloc (end - start + 1);
5604   memcpy (out, start, end - start);
5605   out[end - start] = 0;
5606
5607   return out;
5608 }
5609 #endif