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