a07e9136dd480049fe498b2b9048e0f2a03ce08f
[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
2402         if (numberlist == NULL)
2403             return 0;
2404
2405         prev_dir = g_get_current_dir();
2406
2407         if (g_chdir(dir) < 0) {
2408                 FILE_OP_ERROR(dir, "chdir");
2409                 g_free(prev_dir);
2410                 return -1;
2411         }
2412
2413         if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2414                 FILE_OP_ERROR(dir, "opendir");
2415                 g_free(prev_dir);
2416                 return -1;
2417         }
2418
2419         wanted_files = g_hash_table_new(g_direct_hash, g_direct_equal);
2420         for (cur = numberlist; cur != NULL; cur = cur->next) {
2421                 /* numberlist->data is expected to be GINT_TO_POINTER */
2422                 g_hash_table_insert(wanted_files, cur->data, GINT_TO_POINTER(1));
2423         }
2424
2425         while ((dir_name = g_dir_read_name(dp)) != NULL) {
2426                 file_no = to_number(dir_name);
2427                 if (is_dir_exist(dir_name))
2428                         continue;
2429                 if (file_no > 0 && g_hash_table_lookup(wanted_files, GINT_TO_POINTER(file_no)) == NULL) {
2430                         debug_print("removing unwanted file %d from %s\n", file_no, dir);
2431                         if (is_dir_exist(dir_name)) {
2432                                 gchar *dot_file = g_strdup_printf(".%s", dir_name);
2433                                 if (is_file_exist(dot_file) && claws_unlink(dot_file) < 0) {
2434                                         FILE_OP_ERROR(dot_file, "unlink");
2435                                 }
2436                                 g_free(dot_file);
2437                                 continue;
2438                         }
2439                         if (claws_unlink(dir_name) < 0)
2440                                 FILE_OP_ERROR(dir_name, "unlink");
2441                 }
2442         }
2443
2444         g_dir_close(dp);
2445         g_hash_table_destroy(wanted_files);
2446
2447         if (g_chdir(prev_dir) < 0) {
2448                 FILE_OP_ERROR(prev_dir, "chdir");
2449                 g_free(prev_dir);
2450                 return -1;
2451         }
2452
2453         g_free(prev_dir);
2454
2455         return 0;
2456 }
2457
2458 gint remove_all_numbered_files(const gchar *dir)
2459 {
2460         return remove_numbered_files(dir, 0, UINT_MAX);
2461 }
2462
2463 gint remove_dir_recursive(const gchar *dir)
2464 {
2465         GStatBuf s;
2466         GDir *dp;
2467         const gchar *dir_name;
2468         gchar *prev_dir;
2469
2470         if (g_stat(dir, &s) < 0) {
2471                 FILE_OP_ERROR(dir, "stat");
2472                 if (ENOENT == errno) return 0;
2473                 return -(errno);
2474         }
2475
2476         if (!S_ISDIR(s.st_mode)) {
2477                 if (claws_unlink(dir) < 0) {
2478                         FILE_OP_ERROR(dir, "unlink");
2479                         return -(errno);
2480                 }
2481
2482                 return 0;
2483         }
2484
2485         prev_dir = g_get_current_dir();
2486         /* g_print("prev_dir = %s\n", prev_dir); */
2487
2488         if (!path_cmp(prev_dir, dir)) {
2489                 g_free(prev_dir);
2490                 if (g_chdir("..") < 0) {
2491                         FILE_OP_ERROR(dir, "chdir");
2492                         return -(errno);
2493                 }
2494                 prev_dir = g_get_current_dir();
2495         }
2496
2497         if (g_chdir(dir) < 0) {
2498                 FILE_OP_ERROR(dir, "chdir");
2499                 g_free(prev_dir);
2500                 return -(errno);
2501         }
2502
2503         if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2504                 g_warning("failed to open directory: %s", dir);
2505                 g_chdir(prev_dir);
2506                 g_free(prev_dir);
2507                 return -(errno);
2508         }
2509
2510         /* remove all files in the directory */
2511         while ((dir_name = g_dir_read_name(dp)) != NULL) {
2512                 /* g_print("removing %s\n", dir_name); */
2513
2514                 if (is_dir_exist(dir_name)) {
2515                         gint ret;
2516
2517                         if ((ret = remove_dir_recursive(dir_name)) < 0) {
2518                                 g_warning("can't remove directory: %s", dir_name);
2519                                 return ret;
2520                         }
2521                 } else {
2522                         if (claws_unlink(dir_name) < 0)
2523                                 FILE_OP_ERROR(dir_name, "unlink");
2524                 }
2525         }
2526
2527         g_dir_close(dp);
2528
2529         if (g_chdir(prev_dir) < 0) {
2530                 FILE_OP_ERROR(prev_dir, "chdir");
2531                 g_free(prev_dir);
2532                 return -(errno);
2533         }
2534
2535         g_free(prev_dir);
2536
2537         if (g_rmdir(dir) < 0) {
2538                 FILE_OP_ERROR(dir, "rmdir");
2539                 return -(errno);
2540         }
2541
2542         return 0;
2543 }
2544
2545 gint rename_force(const gchar *oldpath, const gchar *newpath)
2546 {
2547 #ifndef G_OS_UNIX
2548         if (!is_file_entry_exist(oldpath)) {
2549                 errno = ENOENT;
2550                 return -1;
2551         }
2552         if (is_file_exist(newpath)) {
2553                 if (claws_unlink(newpath) < 0)
2554                         FILE_OP_ERROR(newpath, "unlink");
2555         }
2556 #endif
2557         return g_rename(oldpath, newpath);
2558 }
2559
2560 /*
2561  * Append src file body to the tail of dest file.
2562  * Now keep_backup has no effects.
2563  */
2564 gint append_file(const gchar *src, const gchar *dest, gboolean keep_backup)
2565 {
2566         FILE *src_fp, *dest_fp;
2567         gint n_read;
2568         gchar buf[BUFSIZ];
2569
2570         gboolean err = FALSE;
2571
2572         if ((src_fp = g_fopen(src, "rb")) == NULL) {
2573                 FILE_OP_ERROR(src, "g_fopen");
2574                 return -1;
2575         }
2576
2577         if ((dest_fp = g_fopen(dest, "ab")) == NULL) {
2578                 FILE_OP_ERROR(dest, "g_fopen");
2579                 fclose(src_fp);
2580                 return -1;
2581         }
2582
2583         if (change_file_mode_rw(dest_fp, dest) < 0) {
2584                 FILE_OP_ERROR(dest, "chmod");
2585                 g_warning("can't change file mode: %s", dest);
2586         }
2587
2588         while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
2589                 if (n_read < sizeof(buf) && ferror(src_fp))
2590                         break;
2591                 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2592                         g_warning("writing to %s failed.", dest);
2593                         fclose(dest_fp);
2594                         fclose(src_fp);
2595                         claws_unlink(dest);
2596                         return -1;
2597                 }
2598         }
2599
2600         if (ferror(src_fp)) {
2601                 FILE_OP_ERROR(src, "fread");
2602                 err = TRUE;
2603         }
2604         fclose(src_fp);
2605         if (fclose(dest_fp) == EOF) {
2606                 FILE_OP_ERROR(dest, "fclose");
2607                 err = TRUE;
2608         }
2609
2610         if (err) {
2611                 claws_unlink(dest);
2612                 return -1;
2613         }
2614
2615         return 0;
2616 }
2617
2618 gint copy_file(const gchar *src, const gchar *dest, gboolean keep_backup)
2619 {
2620         FILE *src_fp, *dest_fp;
2621         gint n_read;
2622         gchar buf[BUFSIZ];
2623         gchar *dest_bak = NULL;
2624         gboolean err = FALSE;
2625
2626         if ((src_fp = g_fopen(src, "rb")) == NULL) {
2627                 FILE_OP_ERROR(src, "g_fopen");
2628                 return -1;
2629         }
2630         if (is_file_exist(dest)) {
2631                 dest_bak = g_strconcat(dest, ".bak", NULL);
2632                 if (rename_force(dest, dest_bak) < 0) {
2633                         FILE_OP_ERROR(dest, "rename");
2634                         fclose(src_fp);
2635                         g_free(dest_bak);
2636                         return -1;
2637                 }
2638         }
2639
2640         if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2641                 FILE_OP_ERROR(dest, "g_fopen");
2642                 fclose(src_fp);
2643                 if (dest_bak) {
2644                         if (rename_force(dest_bak, dest) < 0)
2645                                 FILE_OP_ERROR(dest_bak, "rename");
2646                         g_free(dest_bak);
2647                 }
2648                 return -1;
2649         }
2650
2651         if (change_file_mode_rw(dest_fp, dest) < 0) {
2652                 FILE_OP_ERROR(dest, "chmod");
2653                 g_warning("can't change file mode: %s", dest);
2654         }
2655
2656         while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
2657                 if (n_read < sizeof(buf) && ferror(src_fp))
2658                         break;
2659                 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2660                         g_warning("writing to %s failed.", dest);
2661                         fclose(dest_fp);
2662                         fclose(src_fp);
2663                         claws_unlink(dest);
2664                         if (dest_bak) {
2665                                 if (rename_force(dest_bak, dest) < 0)
2666                                         FILE_OP_ERROR(dest_bak, "rename");
2667                                 g_free(dest_bak);
2668                         }
2669                         return -1;
2670                 }
2671         }
2672
2673         if (ferror(src_fp)) {
2674                 FILE_OP_ERROR(src, "fread");
2675                 err = TRUE;
2676         }
2677         fclose(src_fp);
2678         if (fclose(dest_fp) == EOF) {
2679                 FILE_OP_ERROR(dest, "fclose");
2680                 err = TRUE;
2681         }
2682
2683         if (err) {
2684                 claws_unlink(dest);
2685                 if (dest_bak) {
2686                         if (rename_force(dest_bak, dest) < 0)
2687                                 FILE_OP_ERROR(dest_bak, "rename");
2688                         g_free(dest_bak);
2689                 }
2690                 return -1;
2691         }
2692
2693         if (keep_backup == FALSE && dest_bak)
2694                 claws_unlink(dest_bak);
2695
2696         g_free(dest_bak);
2697
2698         return 0;
2699 }
2700
2701 gint move_file(const gchar *src, const gchar *dest, gboolean overwrite)
2702 {
2703         if (overwrite == FALSE && is_file_exist(dest)) {
2704                 g_warning("move_file(): file %s already exists.", dest);
2705                 return -1;
2706         }
2707
2708         if (rename_force(src, dest) == 0) return 0;
2709
2710         if (EXDEV != errno) {
2711                 FILE_OP_ERROR(src, "rename");
2712                 return -1;
2713         }
2714
2715         if (copy_file(src, dest, FALSE) < 0) return -1;
2716
2717         claws_unlink(src);
2718
2719         return 0;
2720 }
2721
2722 gint copy_file_part_to_fp(FILE *fp, off_t offset, size_t length, FILE *dest_fp)
2723 {
2724         gint n_read;
2725         gint bytes_left, to_read;
2726         gchar buf[BUFSIZ];
2727
2728         if (fseek(fp, offset, SEEK_SET) < 0) {
2729                 perror("fseek");
2730                 return -1;
2731         }
2732
2733         bytes_left = length;
2734         to_read = MIN(bytes_left, sizeof(buf));
2735
2736         while ((n_read = fread(buf, sizeof(gchar), to_read, fp)) > 0) {
2737                 if (n_read < to_read && ferror(fp))
2738                         break;
2739                 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2740                         return -1;
2741                 }
2742                 bytes_left -= n_read;
2743                 if (bytes_left == 0)
2744                         break;
2745                 to_read = MIN(bytes_left, sizeof(buf));
2746         }
2747
2748         if (ferror(fp)) {
2749                 perror("fread");
2750                 return -1;
2751         }
2752
2753         return 0;
2754 }
2755
2756 gint copy_file_part(FILE *fp, off_t offset, size_t length, const gchar *dest)
2757 {
2758         FILE *dest_fp;
2759         gboolean err = FALSE;
2760
2761         if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2762                 FILE_OP_ERROR(dest, "g_fopen");
2763                 return -1;
2764         }
2765
2766         if (change_file_mode_rw(dest_fp, dest) < 0) {
2767                 FILE_OP_ERROR(dest, "chmod");
2768                 g_warning("can't change file mode: %s", dest);
2769         }
2770
2771         if (copy_file_part_to_fp(fp, offset, length, dest_fp) < 0)
2772                 err = TRUE;
2773
2774         if (!err && fclose(dest_fp) == EOF) {
2775                 FILE_OP_ERROR(dest, "fclose");
2776                 err = TRUE;
2777         }
2778
2779         if (err) {
2780                 g_warning("writing to %s failed.", dest);
2781                 claws_unlink(dest);
2782                 return -1;
2783         }
2784
2785         return 0;
2786 }
2787
2788 /* convert line endings into CRLF. If the last line doesn't end with
2789  * linebreak, add it.
2790  */
2791 gchar *canonicalize_str(const gchar *str)
2792 {
2793         const gchar *p;
2794         guint new_len = 0;
2795         gchar *out, *outp;
2796
2797         for (p = str; *p != '\0'; ++p) {
2798                 if (*p != '\r') {
2799                         ++new_len;
2800                         if (*p == '\n')
2801                                 ++new_len;
2802                 }
2803         }
2804         if (p == str || *(p - 1) != '\n')
2805                 new_len += 2;
2806
2807         out = outp = g_malloc(new_len + 1);
2808         for (p = str; *p != '\0'; ++p) {
2809                 if (*p != '\r') {
2810                         if (*p == '\n')
2811                                 *outp++ = '\r';
2812                         *outp++ = *p;
2813                 }
2814         }
2815         if (p == str || *(p - 1) != '\n') {
2816                 *outp++ = '\r';
2817                 *outp++ = '\n';
2818         }
2819         *outp = '\0';
2820
2821         return out;
2822 }
2823
2824 gint canonicalize_file(const gchar *src, const gchar *dest)
2825 {
2826         FILE *src_fp, *dest_fp;
2827         gchar buf[BUFFSIZE];
2828         gint len;
2829         gboolean err = FALSE;
2830         gboolean last_linebreak = FALSE;
2831
2832         if (src == NULL || dest == NULL)
2833                 return -1;
2834
2835         if ((src_fp = g_fopen(src, "rb")) == NULL) {
2836                 FILE_OP_ERROR(src, "g_fopen");
2837                 return -1;
2838         }
2839
2840         if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2841                 FILE_OP_ERROR(dest, "g_fopen");
2842                 fclose(src_fp);
2843                 return -1;
2844         }
2845
2846         if (change_file_mode_rw(dest_fp, dest) < 0) {
2847                 FILE_OP_ERROR(dest, "chmod");
2848                 g_warning("can't change file mode: %s", dest);
2849         }
2850
2851         while (fgets(buf, sizeof(buf), src_fp) != NULL) {
2852                 gint r = 0;
2853
2854                 len = strlen(buf);
2855                 if (len == 0) break;
2856                 last_linebreak = FALSE;
2857
2858                 if (buf[len - 1] != '\n') {
2859                         last_linebreak = TRUE;
2860                         r = fputs(buf, dest_fp);
2861                 } else if (len > 1 && buf[len - 1] == '\n' && buf[len - 2] == '\r') {
2862                         r = fputs(buf, dest_fp);
2863                 } else {
2864                         if (len > 1) {
2865                                 r = fwrite(buf, 1, len - 1, dest_fp);
2866                                 if (r != (len -1))
2867                                         r = EOF;
2868                         }
2869                         if (r != EOF)
2870                                 r = fputs("\r\n", dest_fp);
2871                 }
2872
2873                 if (r == EOF) {
2874                         g_warning("writing to %s failed.", dest);
2875                         fclose(dest_fp);
2876                         fclose(src_fp);
2877                         claws_unlink(dest);
2878                         return -1;
2879                 }
2880         }
2881
2882         if (last_linebreak == TRUE) {
2883                 if (fputs("\r\n", dest_fp) == EOF)
2884                         err = TRUE;
2885         }
2886
2887         if (ferror(src_fp)) {
2888                 FILE_OP_ERROR(src, "fgets");
2889                 err = TRUE;
2890         }
2891         fclose(src_fp);
2892         if (fclose(dest_fp) == EOF) {
2893                 FILE_OP_ERROR(dest, "fclose");
2894                 err = TRUE;
2895         }
2896
2897         if (err) {
2898                 claws_unlink(dest);
2899                 return -1;
2900         }
2901
2902         return 0;
2903 }
2904
2905 gint canonicalize_file_replace(const gchar *file)
2906 {
2907         gchar *tmp_file;
2908
2909         tmp_file = get_tmp_file();
2910
2911         if (canonicalize_file(file, tmp_file) < 0) {
2912                 g_free(tmp_file);
2913                 return -1;
2914         }
2915
2916         if (move_file(tmp_file, file, TRUE) < 0) {
2917                 g_warning("can't replace file: %s", file);
2918                 claws_unlink(tmp_file);
2919                 g_free(tmp_file);
2920                 return -1;
2921         }
2922
2923         g_free(tmp_file);
2924         return 0;
2925 }
2926
2927 gchar *normalize_newlines(const gchar *str)
2928 {
2929         const gchar *p;
2930         gchar *out, *outp;
2931
2932         out = outp = g_malloc(strlen(str) + 1);
2933         for (p = str; *p != '\0'; ++p) {
2934                 if (*p == '\r') {
2935                         if (*(p + 1) != '\n')
2936                                 *outp++ = '\n';
2937                 } else
2938                         *outp++ = *p;
2939         }
2940
2941         *outp = '\0';
2942
2943         return out;
2944 }
2945
2946 gchar *get_outgoing_rfc2822_str(FILE *fp)
2947 {
2948         gchar buf[BUFFSIZE];
2949         GString *str;
2950         gchar *ret;
2951
2952         str = g_string_new(NULL);
2953
2954         /* output header part */
2955         while (fgets(buf, sizeof(buf), fp) != NULL) {
2956                 strretchomp(buf);
2957                 if (!g_ascii_strncasecmp(buf, "Bcc:", 4)) {
2958                         gint next;
2959
2960                         for (;;) {
2961                                 next = fgetc(fp);
2962                                 if (next == EOF)
2963                                         break;
2964                                 else if (next != ' ' && next != '\t') {
2965                                         ungetc(next, fp);
2966                                         break;
2967                                 }
2968                                 if (fgets(buf, sizeof(buf), fp) == NULL)
2969                                         break;
2970                         }
2971                 } else {
2972                         g_string_append(str, buf);
2973                         g_string_append(str, "\r\n");
2974                         if (buf[0] == '\0')
2975                                 break;
2976                 }
2977         }
2978
2979         /* output body part */
2980         while (fgets(buf, sizeof(buf), fp) != NULL) {
2981                 strretchomp(buf);
2982                 if (buf[0] == '.')
2983                         g_string_append_c(str, '.');
2984                 g_string_append(str, buf);
2985                 g_string_append(str, "\r\n");
2986         }
2987
2988         ret = str->str;
2989         g_string_free(str, FALSE);
2990
2991         return ret;
2992 }
2993
2994 /*
2995  * Create a new boundary in a way that it is very unlikely that this
2996  * will occur in the following text.  It would be easy to ensure
2997  * uniqueness if everything is either quoted-printable or base64
2998  * encoded (note that conversion is allowed), but because MIME bodies
2999  * may be nested, it may happen that the same boundary has already
3000  * been used.
3001  *
3002  *   boundary := 0*69<bchars> bcharsnospace
3003  *   bchars := bcharsnospace / " "
3004  *   bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" /
3005  *                  "+" / "_" / "," / "-" / "." /
3006  *                  "/" / ":" / "=" / "?"
3007  *
3008  * some special characters removed because of buggy MTAs
3009  */
3010
3011 gchar *generate_mime_boundary(const gchar *prefix)
3012 {
3013         static gchar tbl[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
3014                              "abcdefghijklmnopqrstuvwxyz"
3015                              "1234567890+_./=";
3016         gchar buf_uniq[24];
3017         gint i;
3018
3019         for (i = 0; i < sizeof(buf_uniq) - 1; i++)
3020                 buf_uniq[i] = tbl[g_random_int_range(0, sizeof(tbl) - 1)];
3021         buf_uniq[i] = '\0';
3022
3023         return g_strdup_printf("%s_/%s", prefix ? prefix : "MP",
3024                                buf_uniq);
3025 }
3026
3027 gint change_file_mode_rw(FILE *fp, const gchar *file)
3028 {
3029 #if HAVE_FCHMOD
3030         return fchmod(fileno(fp), S_IRUSR|S_IWUSR);
3031 #else
3032         return g_chmod(file, S_IRUSR|S_IWUSR);
3033 #endif
3034 }
3035
3036 FILE *my_tmpfile(void)
3037 {
3038         const gchar suffix[] = ".XXXXXX";
3039         const gchar *tmpdir;
3040         guint tmplen;
3041         const gchar *progname;
3042         guint proglen;
3043         gchar *fname;
3044         gint fd;
3045         FILE *fp;
3046 #ifndef G_OS_WIN32
3047         gchar buf[2]="\0";
3048 #endif
3049
3050         tmpdir = get_tmp_dir();
3051         tmplen = strlen(tmpdir);
3052         progname = g_get_prgname();
3053         if (progname == NULL)
3054                 progname = "claws-mail";
3055         proglen = strlen(progname);
3056         Xalloca(fname, tmplen + 1 + proglen + sizeof(suffix),
3057                 return tmpfile());
3058
3059         memcpy(fname, tmpdir, tmplen);
3060         fname[tmplen] = G_DIR_SEPARATOR;
3061         memcpy(fname + tmplen + 1, progname, proglen);
3062         memcpy(fname + tmplen + 1 + proglen, suffix, sizeof(suffix));
3063
3064         fd = g_mkstemp(fname);
3065         if (fd < 0)
3066                 return tmpfile();
3067
3068 #ifndef G_OS_WIN32
3069         claws_unlink(fname);
3070         
3071         /* verify that we can write in the file after unlinking */
3072         if (write(fd, buf, 1) < 0) {
3073                 close(fd);
3074                 return tmpfile();
3075         }
3076         
3077 #endif
3078
3079         fp = fdopen(fd, "w+b");
3080         if (!fp)
3081                 close(fd);
3082         else {
3083                 rewind(fp);
3084                 return fp;
3085         }
3086
3087         return tmpfile();
3088 }
3089
3090 FILE *get_tmpfile_in_dir(const gchar *dir, gchar **filename)
3091 {
3092         int fd;
3093         *filename = g_strdup_printf("%s%cclaws.XXXXXX", dir, G_DIR_SEPARATOR);
3094         fd = g_mkstemp(*filename);
3095         if (fd < 0)
3096                 return NULL;
3097         return fdopen(fd, "w+");
3098 }
3099
3100 FILE *str_open_as_stream(const gchar *str)
3101 {
3102         FILE *fp;
3103         size_t len;
3104
3105         cm_return_val_if_fail(str != NULL, NULL);
3106
3107         fp = my_tmpfile();
3108         if (!fp) {
3109                 FILE_OP_ERROR("str_open_as_stream", "my_tmpfile");
3110                 return NULL;
3111         }
3112
3113         len = strlen(str);
3114         if (len == 0) return fp;
3115
3116         if (fwrite(str, 1, len, fp) != len) {
3117                 FILE_OP_ERROR("str_open_as_stream", "fwrite");
3118                 fclose(fp);
3119                 return NULL;
3120         }
3121
3122         rewind(fp);
3123         return fp;
3124 }
3125
3126 gint str_write_to_file(const gchar *str, const gchar *file)
3127 {
3128         FILE *fp;
3129         size_t len;
3130
3131         cm_return_val_if_fail(str != NULL, -1);
3132         cm_return_val_if_fail(file != NULL, -1);
3133
3134         if ((fp = g_fopen(file, "wb")) == NULL) {
3135                 FILE_OP_ERROR(file, "g_fopen");
3136                 return -1;
3137         }
3138
3139         len = strlen(str);
3140         if (len == 0) {
3141                 fclose(fp);
3142                 return 0;
3143         }
3144
3145         if (fwrite(str, 1, len, fp) != len) {
3146                 FILE_OP_ERROR(file, "fwrite");
3147                 fclose(fp);
3148                 claws_unlink(file);
3149                 return -1;
3150         }
3151
3152         if (fclose(fp) == EOF) {
3153                 FILE_OP_ERROR(file, "fclose");
3154                 claws_unlink(file);
3155                 return -1;
3156         }
3157
3158         return 0;
3159 }
3160
3161 static gchar *file_read_stream_to_str_full(FILE *fp, gboolean recode)
3162 {
3163         GByteArray *array;
3164         guchar buf[BUFSIZ];
3165         gint n_read;
3166         gchar *str;
3167
3168         cm_return_val_if_fail(fp != NULL, NULL);
3169
3170         array = g_byte_array_new();
3171
3172         while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
3173                 if (n_read < sizeof(buf) && ferror(fp))
3174                         break;
3175                 g_byte_array_append(array, buf, n_read);
3176         }
3177
3178         if (ferror(fp)) {
3179                 FILE_OP_ERROR("file stream", "fread");
3180                 g_byte_array_free(array, TRUE);
3181                 return NULL;
3182         }
3183
3184         buf[0] = '\0';
3185         g_byte_array_append(array, buf, 1);
3186         str = (gchar *)array->data;
3187         g_byte_array_free(array, FALSE);
3188
3189         if (recode && !g_utf8_validate(str, -1, NULL)) {
3190                 const gchar *src_codeset, *dest_codeset;
3191                 gchar *tmp = NULL;
3192                 src_codeset = conv_get_locale_charset_str();
3193                 dest_codeset = CS_UTF_8;
3194                 tmp = conv_codeset_strdup(str, src_codeset, dest_codeset);
3195                 g_free(str);
3196                 str = tmp;
3197         }
3198
3199         return str;
3200 }
3201
3202 static gchar *file_read_to_str_full(const gchar *file, gboolean recode)
3203 {
3204         FILE *fp;
3205         gchar *str;
3206         GStatBuf s;
3207 #ifndef G_OS_WIN32
3208         gint fd, err;
3209         struct timeval timeout = {1, 0};
3210         fd_set fds;
3211         int fflags = 0;
3212 #endif
3213
3214         cm_return_val_if_fail(file != NULL, NULL);
3215
3216         if (g_stat(file, &s) != 0) {
3217                 FILE_OP_ERROR(file, "stat");
3218                 return NULL;
3219         }
3220         if (S_ISDIR(s.st_mode)) {
3221                 g_warning("%s: is a directory", file);
3222                 return NULL;
3223         }
3224
3225 #ifdef G_OS_WIN32
3226         fp = g_fopen (file, "rb");
3227         if (fp == NULL) {
3228                 FILE_OP_ERROR(file, "open");
3229                 return NULL;
3230         }
3231 #else     
3232         /* test whether the file is readable without blocking */
3233         fd = g_open(file, O_RDONLY | O_NONBLOCK, 0);
3234         if (fd == -1) {
3235                 FILE_OP_ERROR(file, "open");
3236                 return NULL;
3237         }
3238
3239         FD_ZERO(&fds);
3240         FD_SET(fd, &fds);
3241
3242         /* allow for one second */
3243         err = select(fd+1, &fds, NULL, NULL, &timeout);
3244         if (err <= 0 || !FD_ISSET(fd, &fds)) {
3245                 if (err < 0) {
3246                         FILE_OP_ERROR(file, "select");
3247                 } else {
3248                         g_warning("%s: doesn't seem readable", file);
3249                 }
3250                 close(fd);
3251                 return NULL;
3252         }
3253         
3254         /* Now clear O_NONBLOCK */
3255         if ((fflags = fcntl(fd, F_GETFL)) < 0) {
3256                 FILE_OP_ERROR(file, "fcntl (F_GETFL)");
3257                 close(fd);
3258                 return NULL;
3259         }
3260         if (fcntl(fd, F_SETFL, (fflags & ~O_NONBLOCK)) < 0) {
3261                 FILE_OP_ERROR(file, "fcntl (F_SETFL)");
3262                 close(fd);
3263                 return NULL;
3264         }
3265         
3266         /* get the FILE pointer */
3267         fp = fdopen(fd, "rb");
3268
3269         if (fp == NULL) {
3270                 FILE_OP_ERROR(file, "fdopen");
3271                 close(fd); /* if fp isn't NULL, we'll use fclose instead! */
3272                 return NULL;
3273         }
3274 #endif
3275
3276         str = file_read_stream_to_str_full(fp, recode);
3277
3278         fclose(fp);
3279
3280         return str;
3281 }
3282
3283 gchar *file_read_to_str(const gchar *file)
3284 {
3285         return file_read_to_str_full(file, TRUE);
3286 }
3287 gchar *file_read_stream_to_str(FILE *fp)
3288 {
3289         return file_read_stream_to_str_full(fp, TRUE);
3290 }
3291
3292 gchar *file_read_to_str_no_recode(const gchar *file)
3293 {
3294         return file_read_to_str_full(file, FALSE);
3295 }
3296 gchar *file_read_stream_to_str_no_recode(FILE *fp)
3297 {
3298         return file_read_stream_to_str_full(fp, FALSE);
3299 }
3300
3301 char *fgets_crlf(char *buf, int size, FILE *stream)
3302 {
3303         gboolean is_cr = FALSE;
3304         gboolean last_was_cr = FALSE;
3305         int c = 0;
3306         char *cs;
3307
3308         cs = buf;
3309         while (--size > 0 && (c = getc(stream)) != EOF)
3310         {
3311                 *cs++ = c;
3312                 is_cr = (c == '\r');
3313                 if (c == '\n') {
3314                         break;
3315                 }
3316                 if (last_was_cr) {
3317                         *(--cs) = '\n';
3318                         cs++;
3319                         ungetc(c, stream);
3320                         break;
3321                 }
3322                 last_was_cr = is_cr;
3323         }
3324         if (c == EOF && cs == buf)
3325                 return NULL;
3326
3327         *cs = '\0';
3328
3329         return buf;     
3330 }
3331
3332 static gint execute_async(gchar *const argv[])
3333 {
3334         cm_return_val_if_fail(argv != NULL && argv[0] != NULL, -1);
3335
3336         if (g_spawn_async(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH,
3337                           NULL, NULL, NULL, FALSE) == FALSE) {
3338                 g_warning("couldn't execute command: %s", argv[0]);
3339                 return -1;
3340         }
3341
3342         return 0;
3343 }
3344
3345 static gint execute_sync(gchar *const argv[])
3346 {
3347         gint status;
3348
3349         cm_return_val_if_fail(argv != NULL && argv[0] != NULL, -1);
3350
3351 #ifdef G_OS_UNIX
3352         if (g_spawn_sync(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH,
3353                          NULL, NULL, NULL, NULL, &status, NULL) == FALSE) {
3354                 g_warning("couldn't execute command: %s", argv[0]);
3355                 return -1;
3356         }
3357
3358         if (WIFEXITED(status))
3359                 return WEXITSTATUS(status);
3360         else
3361                 return -1;
3362 #else
3363         if (g_spawn_sync(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH| 
3364                          G_SPAWN_CHILD_INHERITS_STDIN|G_SPAWN_LEAVE_DESCRIPTORS_OPEN,
3365                          NULL, NULL, NULL, NULL, &status, NULL) == FALSE) {
3366                 g_warning("couldn't execute command: %s", argv[0]);
3367                 return -1;
3368         }
3369
3370         return status;
3371 #endif
3372 }
3373
3374 gint execute_command_line(const gchar *cmdline, gboolean async)
3375 {
3376         gchar **argv;
3377         gint ret;
3378
3379         debug_print("execute_command_line(): executing: %s\n", cmdline?cmdline:"(null)");
3380
3381         argv = strsplit_with_quote(cmdline, " ", 0);
3382
3383         if (async)
3384                 ret = execute_async(argv);
3385         else
3386                 ret = execute_sync(argv);
3387
3388         g_strfreev(argv);
3389
3390         return ret;
3391 }
3392
3393 gchar *get_command_output(const gchar *cmdline)
3394 {
3395         gchar *child_stdout;
3396         gint status;
3397
3398         cm_return_val_if_fail(cmdline != NULL, NULL);
3399
3400         debug_print("get_command_output(): executing: %s\n", cmdline);
3401
3402         if (g_spawn_command_line_sync(cmdline, &child_stdout, NULL, &status,
3403                                       NULL) == FALSE) {
3404                 g_warning("couldn't execute command: %s", cmdline);
3405                 return NULL;
3406         }
3407
3408         return child_stdout;
3409 }
3410
3411 static gint is_unchanged_uri_char(char c)
3412 {
3413         switch (c) {
3414                 case '(':
3415                 case ')':
3416                         return 0;
3417                 default:
3418                         return 1;
3419         }
3420 }
3421
3422 static void encode_uri(gchar *encoded_uri, gint bufsize, const gchar *uri)
3423 {
3424         int i;
3425         int k;
3426
3427         k = 0;
3428         for(i = 0; i < strlen(uri) ; i++) {
3429                 if (is_unchanged_uri_char(uri[i])) {
3430                         if (k + 2 >= bufsize)
3431                                 break;
3432                         encoded_uri[k++] = uri[i];
3433                 }
3434                 else {
3435                         char * hexa = "0123456789ABCDEF";
3436
3437                         if (k + 4 >= bufsize)
3438                                 break;
3439                         encoded_uri[k++] = '%';
3440                         encoded_uri[k++] = hexa[uri[i] / 16];
3441                         encoded_uri[k++] = hexa[uri[i] % 16];
3442                 }
3443         }
3444         encoded_uri[k] = 0;
3445 }
3446
3447 gint open_uri(const gchar *uri, const gchar *cmdline)
3448 {
3449
3450 #ifndef G_OS_WIN32
3451         gchar buf[BUFFSIZE];
3452         gchar *p;
3453         gchar encoded_uri[BUFFSIZE];
3454         cm_return_val_if_fail(uri != NULL, -1);
3455
3456         /* an option to choose whether to use encode_uri or not ? */
3457         encode_uri(encoded_uri, BUFFSIZE, uri);
3458
3459         if (cmdline &&
3460             (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
3461             !strchr(p + 2, '%'))
3462                 g_snprintf(buf, sizeof(buf), cmdline, encoded_uri);
3463         else {
3464                 if (cmdline)
3465                         g_warning("Open URI command-line is invalid "
3466                                   "(there must be only one '%%s'): %s",
3467                                   cmdline);
3468                 g_snprintf(buf, sizeof(buf), DEFAULT_BROWSER_CMD, encoded_uri);
3469         }
3470
3471         execute_command_line(buf, TRUE);
3472 #else
3473         ShellExecute(NULL, "open", uri, NULL, NULL, SW_SHOW);
3474 #endif
3475         return 0;
3476 }
3477
3478 gint open_txt_editor(const gchar *filepath, const gchar *cmdline)
3479 {
3480         gchar buf[BUFFSIZE];
3481         gchar *p;
3482
3483         cm_return_val_if_fail(filepath != NULL, -1);
3484
3485         if (cmdline &&
3486             (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
3487             !strchr(p + 2, '%'))
3488                 g_snprintf(buf, sizeof(buf), cmdline, filepath);
3489         else {
3490                 if (cmdline)
3491                         g_warning("Open Text Editor command-line is invalid "
3492                                   "(there must be only one '%%s'): %s",
3493                                   cmdline);
3494                 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, filepath);
3495         }
3496
3497         execute_command_line(buf, TRUE);
3498
3499         return 0;
3500 }
3501
3502 time_t remote_tzoffset_sec(const gchar *zone)
3503 {
3504         static gchar ustzstr[] = "PSTPDTMSTMDTCSTCDTESTEDT";
3505         gchar zone3[4];
3506         gchar *p;
3507         gchar c;
3508         gint iustz;
3509         gint offset;
3510         time_t remoteoffset;
3511
3512         strncpy(zone3, zone, 3);
3513         zone3[3] = '\0';
3514         remoteoffset = 0;
3515
3516         if (sscanf(zone, "%c%d", &c, &offset) == 2 &&
3517             (c == '+' || c == '-')) {
3518                 remoteoffset = ((offset / 100) * 60 + (offset % 100)) * 60;
3519                 if (c == '-')
3520                         remoteoffset = -remoteoffset;
3521         } else if (!strncmp(zone, "UT" , 2) ||
3522                    !strncmp(zone, "GMT", 2)) {
3523                 remoteoffset = 0;
3524         } else if (strlen(zone3) == 3) {
3525                 for (p = ustzstr; *p != '\0'; p += 3) {
3526                         if (!g_ascii_strncasecmp(p, zone3, 3)) {
3527                                 iustz = ((gint)(p - ustzstr) / 3 + 1) / 2 - 8;
3528                                 remoteoffset = iustz * 3600;
3529                                 break;
3530                         }
3531                 }
3532                 if (*p == '\0')
3533                         return -1;
3534         } else if (strlen(zone3) == 1) {
3535                 switch (zone[0]) {
3536                 case 'Z': remoteoffset =   0; break;
3537                 case 'A': remoteoffset =  -1; break;
3538                 case 'B': remoteoffset =  -2; break;
3539                 case 'C': remoteoffset =  -3; break;
3540                 case 'D': remoteoffset =  -4; break;
3541                 case 'E': remoteoffset =  -5; break;
3542                 case 'F': remoteoffset =  -6; break;
3543                 case 'G': remoteoffset =  -7; break;
3544                 case 'H': remoteoffset =  -8; break;
3545                 case 'I': remoteoffset =  -9; break;
3546                 case 'K': remoteoffset = -10; break; /* J is not used */
3547                 case 'L': remoteoffset = -11; break;
3548                 case 'M': remoteoffset = -12; break;
3549                 case 'N': remoteoffset =   1; break;
3550                 case 'O': remoteoffset =   2; break;
3551                 case 'P': remoteoffset =   3; break;
3552                 case 'Q': remoteoffset =   4; break;
3553                 case 'R': remoteoffset =   5; break;
3554                 case 'S': remoteoffset =   6; break;
3555                 case 'T': remoteoffset =   7; break;
3556                 case 'U': remoteoffset =   8; break;
3557                 case 'V': remoteoffset =   9; break;
3558                 case 'W': remoteoffset =  10; break;
3559                 case 'X': remoteoffset =  11; break;
3560                 case 'Y': remoteoffset =  12; break;
3561                 default:  remoteoffset =   0; break;
3562                 }
3563                 remoteoffset = remoteoffset * 3600;
3564         } else
3565                 return -1;
3566
3567         return remoteoffset;
3568 }
3569
3570 time_t tzoffset_sec(time_t *now)
3571 {
3572         struct tm gmt, *lt;
3573         gint off;
3574         struct tm buf1, buf2;
3575 #ifdef G_OS_WIN32
3576         if (now && *now < 0)
3577                 return 0;
3578 #endif  
3579         gmt = *gmtime_r(now, &buf1);
3580         lt = localtime_r(now, &buf2);
3581
3582         off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
3583
3584         if (lt->tm_year < gmt.tm_year)
3585                 off -= 24 * 60;
3586         else if (lt->tm_year > gmt.tm_year)
3587                 off += 24 * 60;
3588         else if (lt->tm_yday < gmt.tm_yday)
3589                 off -= 24 * 60;
3590         else if (lt->tm_yday > gmt.tm_yday)
3591                 off += 24 * 60;
3592
3593         if (off >= 24 * 60)             /* should be impossible */
3594                 off = 23 * 60 + 59;     /* if not, insert silly value */
3595         if (off <= -24 * 60)
3596                 off = -(23 * 60 + 59);
3597
3598         return off * 60;
3599 }
3600
3601 /* calculate timezone offset */
3602 gchar *tzoffset(time_t *now)
3603 {
3604         static gchar offset_string[6];
3605         struct tm gmt, *lt;
3606         gint off;
3607         gchar sign = '+';
3608         struct tm buf1, buf2;
3609 #ifdef G_OS_WIN32
3610         if (now && *now < 0)
3611                 return 0;
3612 #endif
3613         gmt = *gmtime_r(now, &buf1);
3614         lt = localtime_r(now, &buf2);
3615
3616         off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
3617
3618         if (lt->tm_year < gmt.tm_year)
3619                 off -= 24 * 60;
3620         else if (lt->tm_year > gmt.tm_year)
3621                 off += 24 * 60;
3622         else if (lt->tm_yday < gmt.tm_yday)
3623                 off -= 24 * 60;
3624         else if (lt->tm_yday > gmt.tm_yday)
3625                 off += 24 * 60;
3626
3627         if (off < 0) {
3628                 sign = '-';
3629                 off = -off;
3630         }
3631
3632         if (off >= 24 * 60)             /* should be impossible */
3633                 off = 23 * 60 + 59;     /* if not, insert silly value */
3634
3635         sprintf(offset_string, "%c%02d%02d", sign, off / 60, off % 60);
3636
3637         return offset_string;
3638 }
3639
3640 void get_rfc822_date(gchar *buf, gint len)
3641 {
3642         struct tm *lt;
3643         time_t t;
3644         gchar day[4], mon[4];
3645         gint dd, hh, mm, ss, yyyy;
3646         struct tm buf1;
3647         gchar buf2[BUFFSIZE];
3648
3649         t = time(NULL);
3650         lt = localtime_r(&t, &buf1);
3651
3652         sscanf(asctime_r(lt, buf2), "%3s %3s %d %d:%d:%d %d\n",
3653                day, mon, &dd, &hh, &mm, &ss, &yyyy);
3654
3655         g_snprintf(buf, len, "%s, %d %s %d %02d:%02d:%02d %s",
3656                    day, dd, mon, yyyy, hh, mm, ss, tzoffset(&t));
3657 }
3658
3659 void debug_set_mode(gboolean mode)
3660 {
3661         debug_mode = mode;
3662 }
3663
3664 gboolean debug_get_mode(void)
3665 {
3666         return debug_mode;
3667 }
3668
3669 void debug_print_real(const gchar *format, ...)
3670 {
3671         va_list args;
3672         gchar buf[BUFFSIZE];
3673
3674         if (!debug_mode) return;
3675
3676         va_start(args, format);
3677         g_vsnprintf(buf, sizeof(buf), format, args);
3678         va_end(args);
3679
3680         g_print("%s", buf);
3681 }
3682
3683
3684 const char * debug_srcname(const char *file)
3685 {
3686         const char *s = strrchr (file, '/');
3687         return s? s+1:file;
3688 }
3689
3690
3691 void * subject_table_lookup(GHashTable *subject_table, gchar * subject)
3692 {
3693         if (subject == NULL)
3694                 subject = "";
3695         else
3696                 subject += subject_get_prefix_length(subject);
3697
3698         return g_hash_table_lookup(subject_table, subject);
3699 }
3700
3701 void subject_table_insert(GHashTable *subject_table, gchar * subject,
3702                           void * data)
3703 {
3704         if (subject == NULL || *subject == 0)
3705                 return;
3706         subject += subject_get_prefix_length(subject);
3707         g_hash_table_insert(subject_table, subject, data);
3708 }
3709
3710 void subject_table_remove(GHashTable *subject_table, gchar * subject)
3711 {
3712         if (subject == NULL)
3713                 return;
3714
3715         subject += subject_get_prefix_length(subject);
3716         g_hash_table_remove(subject_table, subject);
3717 }
3718
3719 #ifndef G_OS_WIN32
3720 static regex_t u_regex;
3721 static gboolean u_init_;
3722 #endif
3723
3724 void utils_free_regex(void)
3725 {
3726 #ifndef G_OS_WIN32
3727         if (u_init_) {
3728                 regfree(&u_regex);
3729                 u_init_ = FALSE;
3730         }
3731 #endif
3732 }
3733
3734 /*!
3735  *\brief        Check if a string is prefixed with known (combinations)
3736  *              of prefixes. The function assumes that each prefix
3737  *              is terminated by zero or exactly _one_ space.
3738  *
3739  *\param        str String to check for a prefixes
3740  *
3741  *\return       int Number of chars in the prefix that should be skipped
3742  *              for a "clean" subject line. If no prefix was found, 0
3743  *              is returned.
3744  */
3745 int subject_get_prefix_length(const gchar *subject)
3746 {
3747 #ifndef G_OS_WIN32
3748         /*!< Array with allowable reply prefixes regexps. */
3749         static const gchar * const prefixes[] = {
3750                 "Re\\:",                        /* "Re:" */
3751                 "Re\\[[1-9][0-9]*\\]\\:",       /* "Re[XXX]:" (non-conforming news mail clients) */
3752                 "Antw\\:",                      /* "Antw:" (Dutch / German Outlook) */
3753                 "Aw\\:",                        /* "Aw:"   (German) */
3754                 "Antwort\\:",                   /* "Antwort:" (German Lotus Notes) */
3755                 "Res\\:",                       /* "Res:" (Spanish/Brazilian Outlook) */
3756                 "Fw\\:",                        /* "Fw:" Forward */
3757                 "Fwd\\:",                       /* "Fwd:" Forward */
3758                 "Enc\\:",                       /* "Enc:" Forward (Brazilian Outlook) */
3759                 "Odp\\:",                       /* "Odp:" Re (Polish Outlook) */
3760                 "Rif\\:",                       /* "Rif:" (Italian Outlook) */
3761                 "Sv\\:",                        /* "Sv" (Norwegian) */
3762                 "Vs\\:",                        /* "Vs" (Norwegian) */
3763                 "Ad\\:",                        /* "Ad" (Norwegian) */
3764                 "\347\255\224\345\244\215\\:",  /* "Re" (Chinese, UTF-8) */
3765                 "R\303\251f\\. \\:",            /* "R�f. :" (French Lotus Notes) */
3766                 "Re \\:",                       /* "Re :" (French Yahoo Mail) */
3767                 /* add more */
3768         };
3769         const int PREFIXES = sizeof prefixes / sizeof prefixes[0];
3770         int n;
3771         regmatch_t pos;
3772
3773         if (!subject) return 0;
3774         if (!*subject) return 0;
3775
3776         if (!u_init_) {
3777                 GString *s = g_string_new("");
3778
3779                 for (n = 0; n < PREFIXES; n++)
3780                         /* Terminate each prefix regexpression by a
3781                          * "\ ?" (zero or ONE space), and OR them */
3782                         g_string_append_printf(s, "(%s\\ ?)%s",
3783                                           prefixes[n],
3784                                           n < PREFIXES - 1 ?
3785                                           "|" : "");
3786
3787                 g_string_prepend(s, "(");
3788                 g_string_append(s, ")+");       /* match at least once */
3789                 g_string_prepend(s, "^\\ *");   /* from beginning of line */
3790
3791
3792                 /* We now have something like "^\ *((PREFIX1\ ?)|(PREFIX2\ ?))+"
3793                  * TODO: Should this be       "^\ *(((PREFIX1)|(PREFIX2))\ ?)+" ??? */
3794                 if (regcomp(&u_regex, s->str, REG_EXTENDED | REG_ICASE)) {
3795                         debug_print("Error compiling regexp %s\n", s->str);
3796                         g_string_free(s, TRUE);
3797                         return 0;
3798                 } else {
3799                         u_init_ = TRUE;
3800                         g_string_free(s, TRUE);
3801                 }
3802         }
3803
3804         if (!regexec(&u_regex, subject, 1, &pos, 0) && pos.rm_so != -1)
3805                 return pos.rm_eo;
3806         else
3807                 return 0;
3808 #else
3809         /*!< Array with allowable reply prefixes regexps. */
3810         static const gchar * const prefixes[] = {
3811                 "re:",                  /* "Re:" */
3812                 "antw:",                        /* "Antw:" (Dutch / German Outlook) */
3813                 "aw:",                  /* "Aw:"   (German) */
3814                 "antwort:",                     /* "Antwort:" (German Lotus Notes) */
3815                 "res:",                 /* "Res:" (Spanish/Brazilian Outlook) */
3816                 "fw:",                  /* "Fw:" Forward */
3817                 "fwd:",                 /* "Fwd:" Forward */
3818                 "enc:",                 /* "Enc:" Forward (Brazilian Outlook) */
3819                 "odp:",                 /* "Odp:" Re (Polish Outlook) */
3820                 "rif:",                 /* "Rif:" (Italian Outlook) */
3821                 "sv:",                  /* "Sv" (Norwegian) */
3822                 "vs:",                  /* "Vs" (Norwegian) */
3823                 "ad:",                  /* "Ad" (Norwegian) */
3824                 "R\303\251f. :",        /* "R�f. :" (French Lotus Notes) */
3825                 "Re :",                 /* "Re :" (French Yahoo Mail) */
3826                 /* add more */
3827         };
3828         const int PREFIXES = sizeof prefixes / sizeof prefixes[0];
3829         int n;
3830
3831         if (!subject) return 0;
3832         if (!*subject) return 0;
3833
3834         for (n = 0; n < PREFIXES; n++) {
3835                 int len = strlen(prefixes[n]);
3836                 if (!strncasecmp(subject, prefixes[n], len)) {
3837                         if (subject[len] == ' ')
3838                                 return len+1;
3839                         else
3840                                 return len;
3841                 }
3842         }
3843         return 0;
3844 #endif
3845 }
3846 static guint g_stricase_hash(gconstpointer gptr)
3847 {
3848         guint hash_result = 0;
3849         const char *str;
3850
3851         for (str = gptr; str && *str; str++) {
3852                 hash_result += toupper(*str);
3853         }
3854
3855         return hash_result;
3856 }
3857
3858 static gint g_stricase_equal(gconstpointer gptr1, gconstpointer gptr2)
3859 {
3860         const char *str1 = gptr1;
3861         const char *str2 = gptr2;
3862
3863         return !strcasecmp(str1, str2);
3864 }
3865
3866 gint g_int_compare(gconstpointer a, gconstpointer b)
3867 {
3868         return GPOINTER_TO_INT(a) - GPOINTER_TO_INT(b);
3869 }
3870
3871 gchar *generate_msgid(gchar *buf, gint len, gchar *user_addr)
3872 {
3873         struct tm *lt;
3874         time_t t;
3875         gchar *addr;
3876         struct tm buft;
3877
3878         t = time(NULL);
3879         lt = localtime_r(&t, &buft);
3880
3881         if (user_addr != NULL)
3882               addr = g_strdup_printf(".%s", user_addr);
3883         else if (strlen(buf) != 0)
3884               addr = g_strdup_printf("@%s", buf);
3885         else
3886               addr = g_strdup_printf("@%s", get_domain_name());
3887
3888         /* Replace all @ but the last one in addr, with underscores.
3889          * RFC 2822 States that msg-id syntax only allows one @.
3890          */
3891         while (strchr(addr, '@') != NULL && strchr(addr, '@') != strrchr(addr, '@'))
3892                 *(strchr(addr, '@')) = '_';
3893
3894         g_snprintf(buf, len, "%04d%02d%02d%02d%02d%02d.%08x%s",
3895                    lt->tm_year + 1900, lt->tm_mon + 1,
3896                    lt->tm_mday, lt->tm_hour,
3897                    lt->tm_min, lt->tm_sec,
3898                    (guint) rand(), addr);
3899
3900         g_free(addr);
3901         return buf;
3902 }
3903
3904 /*
3905    quote_cmd_argument()
3906
3907    return a quoted string safely usable in argument of a command.
3908
3909    code is extracted and adapted from etPan! project -- DINH V. Ho�.
3910 */
3911
3912 gint quote_cmd_argument(gchar * result, guint size,
3913                         const gchar * path)
3914 {
3915         const gchar * p;
3916         gchar * result_p;
3917         guint remaining;
3918
3919         result_p = result;
3920         remaining = size;
3921
3922         for(p = path ; * p != '\0' ; p ++) {
3923
3924                 if (isalnum((guchar)*p) || (* p == '/')) {
3925                         if (remaining > 0) {
3926                                 * result_p = * p;
3927                                 result_p ++;
3928                                 remaining --;
3929                         }
3930                         else {
3931                                 result[size - 1] = '\0';
3932                                 return -1;
3933                         }
3934                 }
3935                 else {
3936                         if (remaining >= 2) {
3937                                 * result_p = '\\';
3938                                 result_p ++;
3939                                 * result_p = * p;
3940                                 result_p ++;
3941                                 remaining -= 2;
3942                         }
3943                         else {
3944                                 result[size - 1] = '\0';
3945                                 return -1;
3946                         }
3947                 }
3948         }
3949         if (remaining > 0) {
3950                 * result_p = '\0';
3951         }
3952         else {
3953                 result[size - 1] = '\0';
3954                 return -1;
3955         }
3956
3957         return 0;
3958 }
3959
3960 typedef struct
3961 {
3962         GNode           *parent;
3963         GNodeMapFunc     func;
3964         gpointer         data;
3965 } GNodeMapData;
3966
3967 static void g_node_map_recursive(GNode *node, gpointer data)
3968 {
3969         GNodeMapData *mapdata = (GNodeMapData *) data;
3970         GNode *newnode;
3971         GNodeMapData newmapdata;
3972         gpointer newdata;
3973
3974         newdata = mapdata->func(node->data, mapdata->data);
3975         if (newdata != NULL) {
3976                 newnode = g_node_new(newdata);
3977                 g_node_append(mapdata->parent, newnode);
3978
3979                 newmapdata.parent = newnode;
3980                 newmapdata.func = mapdata->func;
3981                 newmapdata.data = mapdata->data;
3982
3983                 g_node_children_foreach(node, G_TRAVERSE_ALL, g_node_map_recursive, &newmapdata);
3984         }
3985 }
3986
3987 GNode *g_node_map(GNode *node, GNodeMapFunc func, gpointer data)
3988 {
3989         GNode *root;
3990         GNodeMapData mapdata;
3991
3992         cm_return_val_if_fail(node != NULL, NULL);
3993         cm_return_val_if_fail(func != NULL, NULL);
3994
3995         root = g_node_new(func(node->data, data));
3996
3997         mapdata.parent = root;
3998         mapdata.func = func;
3999         mapdata.data = data;
4000
4001         g_node_children_foreach(node, G_TRAVERSE_ALL, g_node_map_recursive, &mapdata);
4002
4003         return root;
4004 }
4005
4006 #define HEX_TO_INT(val, hex)                    \
4007 {                                               \
4008         gchar c = hex;                          \
4009                                                 \
4010         if ('0' <= c && c <= '9') {             \
4011                 val = c - '0';                  \
4012         } else if ('a' <= c && c <= 'f') {      \
4013                 val = c - 'a' + 10;             \
4014         } else if ('A' <= c && c <= 'F') {      \
4015                 val = c - 'A' + 10;             \
4016         } else {                                \
4017                 val = -1;                       \
4018         }                                       \
4019 }
4020
4021 gboolean get_hex_value(guchar *out, gchar c1, gchar c2)
4022 {
4023         gint hi, lo;
4024
4025         HEX_TO_INT(hi, c1);
4026         HEX_TO_INT(lo, c2);
4027
4028         if (hi == -1 || lo == -1)
4029                 return FALSE;
4030
4031         *out = (hi << 4) + lo;
4032         return TRUE;
4033 }
4034
4035 #define INT_TO_HEX(hex, val)            \
4036 {                                       \
4037         if ((val) < 10)                 \
4038                 hex = '0' + (val);      \
4039         else                            \
4040                 hex = 'A' + (val) - 10; \
4041 }
4042
4043 void get_hex_str(gchar *out, guchar ch)
4044 {
4045         gchar hex;
4046
4047         INT_TO_HEX(hex, ch >> 4);
4048         *out++ = hex;
4049         INT_TO_HEX(hex, ch & 0x0f);
4050         *out   = hex;
4051 }
4052
4053 #undef REF_DEBUG
4054 #ifndef REF_DEBUG
4055 #define G_PRINT_REF 1 == 1 ? (void) 0 : (void)
4056 #else
4057 #define G_PRINT_REF g_print
4058 #endif
4059
4060 /*!
4061  *\brief        Register ref counted pointer. It is based on GBoxed, so should
4062  *              work with anything that uses the GType system. The semantics
4063  *              are similar to a C++ auto pointer, with the exception that
4064  *              C doesn't have automatic closure (calling destructors) when
4065  *              exiting a block scope.
4066  *              Use the \ref G_TYPE_AUTO_POINTER macro instead of calling this
4067  *              function directly.
4068  *
4069  *\return       GType A GType type.
4070  */
4071 GType g_auto_pointer_register(void)
4072 {
4073         static GType auto_pointer_type;
4074         if (!auto_pointer_type)
4075                 auto_pointer_type =
4076                         g_boxed_type_register_static
4077                                 ("G_TYPE_AUTO_POINTER",
4078                                  (GBoxedCopyFunc) g_auto_pointer_copy,
4079                                  (GBoxedFreeFunc) g_auto_pointer_free);
4080         return auto_pointer_type;
4081 }
4082
4083 /*!
4084  *\brief        Structure with g_new() allocated pointer guarded by the
4085  *              auto pointer
4086  */
4087 typedef struct AutoPointerRef {
4088         void          (*free) (gpointer);
4089         gpointer        pointer;
4090         glong           cnt;
4091 } AutoPointerRef;
4092
4093 /*!
4094  *\brief        The auto pointer opaque structure that references the
4095  *              pointer guard block.
4096  */
4097 typedef struct AutoPointer {
4098         AutoPointerRef *ref;
4099         gpointer        ptr; /*!< access to protected pointer */
4100 } AutoPointer;
4101
4102 /*!
4103  *\brief        Creates an auto pointer for a g_new()ed pointer. Example:
4104  *
4105  *\code
4106  *
4107  *              ... tell gtk_list_store it should use a G_TYPE_AUTO_POINTER
4108  *              ... when assigning, copying and freeing storage elements
4109  *
4110  *              gtk_list_store_new(N_S_COLUMNS,
4111  *                                 G_TYPE_AUTO_POINTER,
4112  *                                 -1);
4113  *
4114  *
4115  *              Template *precious_data = g_new0(Template, 1);
4116  *              g_pointer protect = g_auto_pointer_new(precious_data);
4117  *
4118  *              gtk_list_store_set(container, &iter,
4119  *                                 S_DATA, protect,
4120  *                                 -1);
4121  *
4122  *              ... the gtk_list_store has copied the pointer and
4123  *              ... incremented its reference count, we should free
4124  *              ... the auto pointer (in C++ a destructor would do
4125  *              ... this for us when leaving block scope)
4126  *
4127  *              g_auto_pointer_free(protect);
4128  *
4129  *              ... gtk_list_store_set() now manages the data. When
4130  *              ... *explicitly* requesting a pointer from the list
4131  *              ... store, don't forget you get a copy that should be
4132  *              ... freed with g_auto_pointer_free() eventually.
4133  *
4134  *\endcode
4135  *
4136  *\param        pointer Pointer to be guarded.
4137  *
4138  *\return       GAuto * Pointer that should be used in containers with
4139  *              GType support.
4140  */
4141 GAuto *g_auto_pointer_new(gpointer p)
4142 {
4143         AutoPointerRef *ref;
4144         AutoPointer    *ptr;
4145
4146         if (p == NULL)
4147                 return NULL;
4148
4149         ref = g_new0(AutoPointerRef, 1);
4150         ptr = g_new0(AutoPointer, 1);
4151
4152         ref->pointer = p;
4153         ref->free = g_free;
4154         ref->cnt = 1;
4155
4156         ptr->ref = ref;
4157         ptr->ptr = p;
4158
4159 #ifdef REF_DEBUG
4160         G_PRINT_REF ("XXXX ALLOC(%lx)\n", p);
4161 #endif
4162         return ptr;
4163 }
4164
4165 /*!
4166  *\brief        Allocate an autopointer using the passed \a free function to
4167  *              free the guarded pointer
4168  */
4169 GAuto *g_auto_pointer_new_with_free(gpointer p, GFreeFunc free_)
4170 {
4171         AutoPointer *aptr;
4172
4173         if (p == NULL)
4174                 return NULL;
4175
4176         aptr = g_auto_pointer_new(p);
4177         aptr->ref->free = free_;
4178         return aptr;
4179 }
4180
4181 gpointer g_auto_pointer_get_ptr(GAuto *auto_ptr)
4182 {
4183         if (auto_ptr == NULL)
4184                 return NULL;
4185         return ((AutoPointer *) auto_ptr)->ptr;
4186 }
4187
4188 /*!
4189  *\brief        Copies an auto pointer by. It's mostly not necessary
4190  *              to call this function directly, unless you copy/assign
4191  *              the guarded pointer.
4192  *
4193  *\param        auto_ptr Auto pointer returned by previous call to
4194  *              g_auto_pointer_new_XXX()
4195  *
4196  *\return       gpointer An auto pointer
4197  */
4198 GAuto *g_auto_pointer_copy(GAuto *auto_ptr)
4199 {
4200         AutoPointer     *ptr;
4201         AutoPointerRef  *ref;
4202         AutoPointer     *newp;
4203
4204         if (auto_ptr == NULL)
4205                 return NULL;
4206
4207         ptr = auto_ptr;
4208         ref = ptr->ref;
4209         newp = g_new0(AutoPointer, 1);
4210
4211         newp->ref = ref;
4212         newp->ptr = ref->pointer;
4213         ++(ref->cnt);
4214
4215 #ifdef REF_DEBUG
4216         G_PRINT_REF ("XXXX COPY(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4217 #endif
4218         return newp;
4219 }
4220
4221 /*!
4222  *\brief        Free an auto pointer
4223  */
4224 void g_auto_pointer_free(GAuto *auto_ptr)
4225 {
4226         AutoPointer     *ptr;
4227         AutoPointerRef  *ref;
4228
4229         if (auto_ptr == NULL)
4230                 return;
4231
4232         ptr = auto_ptr;
4233         ref = ptr->ref;
4234
4235         if (--(ref->cnt) == 0) {
4236 #ifdef REF_DEBUG
4237                 G_PRINT_REF ("XXXX FREE(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4238 #endif
4239                 ref->free(ref->pointer);
4240                 g_free(ref);
4241         }
4242 #ifdef REF_DEBUG
4243         else
4244                 G_PRINT_REF ("XXXX DEREF(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4245 #endif
4246         g_free(ptr);
4247 }
4248
4249 void replace_returns(gchar *str)
4250 {
4251         if (!str)
4252                 return;
4253
4254         while (strstr(str, "\n")) {
4255                 *strstr(str, "\n") = ' ';
4256         }
4257         while (strstr(str, "\r")) {
4258                 *strstr(str, "\r") = ' ';
4259         }
4260 }
4261
4262 /* get_uri_part() - retrieves a URI starting from scanpos.
4263                     Returns TRUE if succesful */
4264 gboolean get_uri_part(const gchar *start, const gchar *scanpos,
4265                              const gchar **bp, const gchar **ep, gboolean hdr)
4266 {
4267         const gchar *ep_;
4268         gint parenthese_cnt = 0;
4269
4270         cm_return_val_if_fail(start != NULL, FALSE);
4271         cm_return_val_if_fail(scanpos != NULL, FALSE);
4272         cm_return_val_if_fail(bp != NULL, FALSE);
4273         cm_return_val_if_fail(ep != NULL, FALSE);
4274
4275         *bp = scanpos;
4276
4277         /* find end point of URI */
4278         for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
4279                 if (!g_ascii_isgraph(*(const guchar *)ep_) ||
4280                     !IS_ASCII(*(const guchar *)ep_) ||
4281                     strchr("[]{}<>\"", *ep_)) {
4282                         break;
4283                 } else if (strchr("(", *ep_)) {
4284                         parenthese_cnt++;
4285                 } else if (strchr(")", *ep_)) {
4286                         if (parenthese_cnt > 0)
4287                                 parenthese_cnt--;
4288                         else
4289                                 break;
4290                 }
4291         }
4292
4293         /* no punctuation at end of string */
4294
4295         /* FIXME: this stripping of trailing punctuations may bite with other URIs.
4296          * should pass some URI type to this function and decide on that whether
4297          * to perform punctuation stripping */
4298
4299 #define IS_REAL_PUNCT(ch)       (g_ascii_ispunct(ch) && !strchr("/?=-_)", ch))
4300
4301         for (; ep_ - 1 > scanpos + 1 &&
4302                IS_REAL_PUNCT(*(ep_ - 1));
4303              ep_--)
4304                 ;
4305
4306 #undef IS_REAL_PUNCT
4307
4308         *ep = ep_;
4309
4310         return TRUE;
4311 }
4312
4313 gchar *make_uri_string(const gchar *bp, const gchar *ep)
4314 {
4315         while (bp && *bp && g_ascii_isspace(*bp))
4316                 bp++;
4317         return g_strndup(bp, ep - bp);
4318 }
4319
4320 /* valid mail address characters */
4321 #define IS_RFC822_CHAR(ch) \
4322         (IS_ASCII(ch) && \
4323          (ch) > 32   && \
4324          (ch) != 127 && \
4325          !g_ascii_isspace(ch) && \
4326          !strchr("(),;<>\"", (ch)))
4327
4328 /* alphabet and number within 7bit ASCII */
4329 #define IS_ASCII_ALNUM(ch)      (IS_ASCII(ch) && g_ascii_isalnum(ch))
4330 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
4331
4332 static GHashTable *create_domain_tab(void)
4333 {
4334         static const gchar *toplvl_domains [] = {
4335             "museum", "aero",
4336             "arpa", "coop", "info", "name", "biz", "com", "edu", "gov",
4337             "int", "mil", "net", "org", "ac", "ad", "ae", "af", "ag",
4338             "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au",
4339             "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi",
4340             "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
4341             "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl",
4342             "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz", "de",
4343             "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er",
4344             "es", "et", "eu", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd",
4345             "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
4346             "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
4347             "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir",
4348             "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
4349             "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc",
4350             "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
4351             "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq",
4352             "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na",
4353             "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu",
4354             "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm",
4355             "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru",
4356             "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj",
4357             "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
4358             "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to",
4359             "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um",
4360             "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu",
4361             "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw"
4362         };
4363         gint n;
4364         GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
4365
4366         cm_return_val_if_fail(htab, NULL);
4367         for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++)
4368                 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
4369         return htab;
4370 }
4371
4372 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
4373 {
4374         const gint MAX_LVL_DOM_NAME_LEN = 6;
4375         gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
4376         const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
4377         register gchar *p;
4378
4379         if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
4380                 return FALSE;
4381
4382         for (p = buf; p < m &&  first < last; *p++ = *first++)
4383                 ;
4384         *p = 0;
4385
4386         return g_hash_table_lookup(tab, buf) != NULL;
4387 }
4388
4389 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
4390 gboolean get_email_part(const gchar *start, const gchar *scanpos,
4391                                const gchar **bp, const gchar **ep, gboolean hdr)
4392 {
4393         /* more complex than the uri part because we need to scan back and forward starting from
4394          * the scan position. */
4395         gboolean result = FALSE;
4396         const gchar *bp_ = NULL;
4397         const gchar *ep_ = NULL;
4398         static GHashTable *dom_tab;
4399         const gchar *last_dot = NULL;
4400         const gchar *prelast_dot = NULL;
4401         const gchar *last_tld_char = NULL;
4402
4403         /* the informative part of the email address (describing the name
4404          * of the email address owner) may contain quoted parts. the
4405          * closure stack stores the last encountered quotes. */
4406         gchar closure_stack[128];
4407         gchar *ptr = closure_stack;
4408
4409         cm_return_val_if_fail(start != NULL, FALSE);
4410         cm_return_val_if_fail(scanpos != NULL, FALSE);
4411         cm_return_val_if_fail(bp != NULL, FALSE);
4412         cm_return_val_if_fail(ep != NULL, FALSE);
4413
4414         if (hdr) {
4415                 const gchar *start_quote = NULL;
4416                 const gchar *end_quote = NULL;
4417 search_again:
4418                 /* go to the real start */
4419                 if (start[0] == ',')
4420                         start++;
4421                 if (start[0] == ';')
4422                         start++;
4423                 while (start[0] == '\n' || start[0] == '\r')
4424                         start++;
4425                 while (start[0] == ' ' || start[0] == '\t')
4426                         start++;
4427
4428                 *bp = start;
4429                 
4430                 /* check if there are quotes (to skip , in them) */
4431                 if (*start == '"') {
4432                         start_quote = start;
4433                         start++;
4434                         end_quote = strstr(start, "\"");
4435                 } else {
4436                         start_quote = NULL;
4437                         end_quote = NULL;
4438                 }
4439                 
4440                 /* skip anything between quotes */
4441                 if (start_quote && end_quote) {
4442                         start = end_quote;
4443                         
4444                 } 
4445
4446                 /* find end (either , or ; or end of line) */
4447                 if (strstr(start, ",") && strstr(start, ";"))
4448                         *ep = strstr(start,",") < strstr(start, ";")
4449                                 ? strstr(start, ",") : strstr(start, ";");
4450                 else if (strstr(start, ","))
4451                         *ep = strstr(start, ",");
4452                 else if (strstr(start, ";"))
4453                         *ep = strstr(start, ";");
4454                 else
4455                         *ep = start+strlen(start);
4456
4457                 /* go back to real start */
4458                 if (start_quote && end_quote) {
4459                         start = start_quote;
4460                 }
4461
4462                 /* check there's still an @ in that, or search
4463                  * further if possible */
4464                 if (strstr(start, "@") && strstr(start, "@") < *ep)
4465                         return TRUE;
4466                 else if (*ep < start+strlen(start)) {
4467                         start = *ep;
4468                         goto search_again;
4469                 } else if (start_quote && strstr(start, "\"") && strstr(start, "\"") < *ep) {
4470                         *bp = start_quote;
4471                         return TRUE;
4472                 } else
4473                         return FALSE;
4474         }
4475
4476         if (!dom_tab)
4477                 dom_tab = create_domain_tab();
4478         cm_return_val_if_fail(dom_tab, FALSE);
4479
4480         /* scan start of address */
4481         for (bp_ = scanpos - 1;
4482              bp_ >= start && IS_RFC822_CHAR(*(const guchar *)bp_); bp_--)
4483                 ;
4484
4485         /* TODO: should start with an alnum? */
4486         bp_++;
4487         for (; bp_ < scanpos && !IS_ASCII_ALNUM(*(const guchar *)bp_); bp_++)
4488                 ;
4489
4490         if (bp_ != scanpos) {
4491                 /* scan end of address */
4492                 for (ep_ = scanpos + 1;
4493                      *ep_ && IS_RFC822_CHAR(*(const guchar *)ep_); ep_++)
4494                         if (*ep_ == '.') {
4495                                 prelast_dot = last_dot;
4496                                 last_dot = ep_;
4497                                 if (*(last_dot + 1) == '.') {
4498                                         if (prelast_dot == NULL)
4499                                                 return FALSE;
4500                                         last_dot = prelast_dot;
4501                                         break;
4502                                 }
4503                         }
4504
4505                 /* TODO: really should terminate with an alnum? */
4506                 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*(const guchar *)ep_);
4507                      --ep_)
4508                         ;
4509                 ep_++;
4510
4511                 if (last_dot == NULL)
4512                         return FALSE;
4513                 if (last_dot >= ep_)
4514                         last_dot = prelast_dot;
4515                 if (last_dot == NULL || (scanpos + 1 >= last_dot))
4516                         return FALSE;
4517                 last_dot++;
4518
4519                 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
4520                         if (*last_tld_char == '?')
4521                                 break;
4522
4523                 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
4524                         result = TRUE;
4525
4526                 *ep = ep_;
4527                 *bp = bp_;
4528         }
4529
4530         if (!result) return FALSE;
4531
4532         if (*ep_ && bp_ != start && *(bp_ - 1) == '"' && *(ep_) == '"'
4533         && *(ep_ + 1) == ' ' && *(ep_ + 2) == '<'
4534         && IS_RFC822_CHAR(*(ep_ + 3))) {
4535                 /* this informative part with an @ in it is
4536                  * followed by the email address */
4537                 ep_ += 3;
4538
4539                 /* go to matching '>' (or next non-rfc822 char, like \n) */
4540                 for (; *ep_ != '>' && *ep != '\0' && IS_RFC822_CHAR(*ep_); ep_++)
4541                         ;
4542
4543                 /* include the bracket */
4544                 if (*ep_ == '>') ep_++;
4545
4546                 /* include the leading quote */
4547                 bp_--;
4548
4549                 *ep = ep_;
4550                 *bp = bp_;
4551                 return TRUE;
4552         }
4553
4554         /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
4555         if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
4556                 return FALSE;
4557
4558         /* see if this is <bracketed>; in this case we also scan for the informative part. */
4559         if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
4560                 return TRUE;
4561
4562 #define FULL_STACK()    ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
4563 #define IN_STACK()      (ptr > closure_stack)
4564 /* has underrun check */
4565 #define POP_STACK()     if(IN_STACK()) --ptr
4566 /* has overrun check */
4567 #define PUSH_STACK(c)   if(!FULL_STACK()) *ptr++ = (c); else return TRUE
4568 /* has underrun check */
4569 #define PEEK_STACK()    (IN_STACK() ? *(ptr - 1) : 0)
4570
4571         ep_++;
4572
4573         /* scan for the informative part. */
4574         for (bp_ -= 2; bp_ >= start; bp_--) {
4575                 /* if closure on the stack keep scanning */
4576                 if (PEEK_STACK() == *bp_) {
4577                         POP_STACK();
4578                         continue;
4579                 }
4580                 if (!IN_STACK() && (*bp_ == '\'' || *bp_ == '"')) {
4581                         PUSH_STACK(*bp_);
4582                         continue;
4583                 }
4584
4585                 /* if nothing in the closure stack, do the special conditions
4586                  * the following if..else expression simply checks whether
4587                  * a token is acceptable. if not acceptable, the clause
4588                  * should terminate the loop with a 'break' */
4589                 if (!PEEK_STACK()) {
4590                         if (*bp_ == '-'
4591                         && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
4592                         && (((bp_ + 1) < ep_)    && isalnum(*(bp_ + 1)))) {
4593                                 /* hyphens are allowed, but only in
4594                                    between alnums */
4595                         } else if (strchr(" \"'", *bp_)) {
4596                                 /* but anything not being a punctiation
4597                                    is ok */
4598                         } else {
4599                                 break; /* anything else is rejected */
4600                         }
4601                 }
4602         }
4603
4604         bp_++;
4605
4606         /* scan forward (should start with an alnum) */
4607         for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
4608                 ;
4609 #undef PEEK_STACK
4610 #undef PUSH_STACK
4611 #undef POP_STACK
4612 #undef IN_STACK
4613 #undef FULL_STACK
4614
4615
4616         *bp = bp_;
4617         *ep = ep_;
4618
4619         return result;
4620 }
4621
4622 #undef IS_QUOTE
4623 #undef IS_ASCII_ALNUM
4624 #undef IS_RFC822_CHAR
4625
4626 gchar *make_email_string(const gchar *bp, const gchar *ep)
4627 {
4628         /* returns a mailto: URI; mailto: is also used to detect the
4629          * uri type later on in the button_pressed signal handler */
4630         gchar *tmp;
4631         gchar *result;
4632
4633         tmp = g_strndup(bp, ep - bp);
4634         result = g_strconcat("mailto:", tmp, NULL);
4635         g_free(tmp);
4636
4637         return result;
4638 }
4639
4640 gchar *make_http_string(const gchar *bp, const gchar *ep)
4641 {
4642         /* returns an http: URI; */
4643         gchar *tmp;
4644         gchar *result;
4645
4646         while (bp && *bp && g_ascii_isspace(*bp))
4647                 bp++;
4648         tmp = g_strndup(bp, ep - bp);
4649         result = g_strconcat("http://", tmp, NULL);
4650         g_free(tmp);
4651
4652         return result;
4653 }
4654
4655 static gchar *mailcap_get_command_in_file(const gchar *path, const gchar *type, const gchar *file_to_open)
4656 {
4657         FILE *fp = g_fopen(path, "rb");
4658         gchar buf[BUFFSIZE];
4659         gchar *result = NULL;
4660         if (!fp)
4661                 return NULL;
4662         while (fgets(buf, sizeof (buf), fp) != NULL) {
4663                 gchar **parts = g_strsplit(buf, ";", 3);
4664                 gchar *trimmed = parts[0];
4665                 while (trimmed[0] == ' ' || trimmed[0] == '\t')
4666                         trimmed++;
4667                 while (trimmed[strlen(trimmed)-1] == ' ' || trimmed[strlen(trimmed)-1] == '\t')
4668                         trimmed[strlen(trimmed)-1] = '\0';
4669
4670                 if (!strcmp(trimmed, type)) {
4671                         gboolean needsterminal = FALSE;
4672                         if (parts[2] && strstr(parts[2], "needsterminal")) {
4673                                 needsterminal = TRUE;
4674                         }
4675                         if (parts[2] && strstr(parts[2], "test=")) {
4676                                 gchar *orig_testcmd = g_strdup(strstr(parts[2], "test=")+5);
4677                                 gchar *testcmd = orig_testcmd;
4678                                 if (strstr(testcmd,";"))
4679                                         *(strstr(testcmd,";")) = '\0';
4680                                 while (testcmd[0] == ' ' || testcmd[0] == '\t')
4681                                         testcmd++;
4682                                 while (testcmd[strlen(testcmd)-1] == '\n')
4683                                         testcmd[strlen(testcmd)-1] = '\0';
4684                                 while (testcmd[strlen(testcmd)-1] == '\r')
4685                                         testcmd[strlen(testcmd)-1] = '\0';
4686                                 while (testcmd[strlen(testcmd)-1] == ' ' || testcmd[strlen(testcmd)-1] == '\t')
4687                                         testcmd[strlen(testcmd)-1] = '\0';
4688                                         
4689                                 if (strstr(testcmd, "%s")) {
4690                                         gchar *tmp = g_strdup_printf(testcmd, file_to_open);
4691                                         gint res = system(tmp);
4692                                         g_free(tmp);
4693                                         g_free(orig_testcmd);
4694                                         
4695                                         if (res != 0) {
4696                                                 g_strfreev(parts);
4697                                                 continue;
4698                                         }
4699                                 } else {
4700                                         gint res = system(testcmd);
4701                                         g_free(orig_testcmd);
4702                                         
4703                                         if (res != 0) {
4704                                                 g_strfreev(parts);
4705                                                 continue;
4706                                         }
4707                                 }
4708                         }
4709                         
4710                         trimmed = parts[1];
4711                         while (trimmed[0] == ' ' || trimmed[0] == '\t')
4712                                 trimmed++;
4713                         while (trimmed[strlen(trimmed)-1] == '\n')
4714                                 trimmed[strlen(trimmed)-1] = '\0';
4715                         while (trimmed[strlen(trimmed)-1] == '\r')
4716                                 trimmed[strlen(trimmed)-1] = '\0';
4717                         while (trimmed[strlen(trimmed)-1] == ' ' || trimmed[strlen(trimmed)-1] == '\t')
4718                                 trimmed[strlen(trimmed)-1] = '\0';
4719                         result = g_strdup(trimmed);
4720                         g_strfreev(parts);
4721                         fclose(fp);
4722                         /* if there are no single quotes around %s, add them.
4723                          * '.*%s.*' is ok, as in display 'png:%s'
4724                          */
4725                         if (strstr(result, "%s") 
4726                         && !(strstr(result, "'") < strstr(result,"%s") &&
4727                              strstr(strstr(result,"%s"), "'"))) {
4728                                 gchar *start = g_strdup(result);
4729                                 gchar *end = g_strdup(strstr(result, "%s")+2);
4730                                 gchar *tmp;
4731                                 *strstr(start, "%s") = '\0';
4732                                 tmp = g_strconcat(start,"'%s'",end, NULL);
4733                                 g_free(start);
4734                                 g_free(end);
4735                                 g_free(result);
4736                                 result = tmp;
4737                         }
4738                         if (needsterminal) {
4739                                 gchar *tmp = g_strdup_printf("xterm -e %s", result);
4740                                 g_free(result);
4741                                 result = tmp;
4742                         }
4743                         return result;
4744                 }
4745                 g_strfreev(parts);
4746         }
4747         fclose(fp);
4748         return NULL;
4749 }
4750 gchar *mailcap_get_command_for_type(const gchar *type, const gchar *file_to_open)
4751 {
4752         gchar *result = NULL;
4753         gchar *path = NULL;
4754         if (type == NULL)
4755                 return NULL;
4756         path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap", NULL);
4757         result = mailcap_get_command_in_file(path, type, file_to_open);
4758         g_free(path);
4759         if (result)
4760                 return result;
4761         result = mailcap_get_command_in_file("/etc/mailcap", type, file_to_open);
4762         return result;
4763 }
4764
4765 void mailcap_update_default(const gchar *type, const gchar *command)
4766 {
4767         gchar *path = NULL, *outpath = NULL;
4768         path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap", NULL);
4769         outpath = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap.new", NULL);
4770         FILE *fp = g_fopen(path, "rb");
4771         FILE *outfp = NULL;
4772         gchar buf[BUFFSIZE];
4773         gboolean err = FALSE;
4774
4775         if (!fp) {
4776                 fp = g_fopen(path, "a");
4777                 if (!fp) {
4778                         g_warning("failed to create file %s", path);
4779                         g_free(path);
4780                         g_free(outpath);
4781                         return;
4782                 }
4783                 fp = g_freopen(path, "rb", fp);
4784                 if (!fp) {
4785                         g_warning("failed to reopen file %s", path);
4786                         g_free(path);
4787                         g_free(outpath);
4788                         return;
4789                 }
4790         }
4791
4792         outfp = g_fopen(outpath, "wb");
4793         if (!outfp) {
4794                 g_warning("failed to create file %s", outpath);
4795                 g_free(path);
4796                 g_free(outpath);
4797                 fclose(fp);
4798                 return;
4799         }
4800         while (fp && fgets(buf, sizeof (buf), fp) != NULL) {
4801                 gchar **parts = g_strsplit(buf, ";", 3);
4802                 gchar *trimmed = parts[0];
4803                 while (trimmed[0] == ' ')
4804                         trimmed++;
4805                 while (trimmed[strlen(trimmed)-1] == ' ')
4806                         trimmed[strlen(trimmed)-1] = '\0';
4807
4808                 if (!strcmp(trimmed, type)) {
4809                         g_strfreev(parts);
4810                         continue;
4811                 }
4812                 else {
4813                         if(fputs(buf, outfp) == EOF) {
4814                                 err = TRUE;
4815                                 break;
4816                         }
4817                 }
4818                 g_strfreev(parts);
4819         }
4820         if (fprintf(outfp, "%s; %s\n", type, command) < 0)
4821                 err = TRUE;
4822
4823         if (fp)
4824                 fclose(fp);
4825
4826         if (fclose(outfp) == EOF)
4827                 err = TRUE;
4828                 
4829         if (!err)
4830                 g_rename(outpath, path);
4831
4832         g_free(path);
4833         g_free(outpath);
4834 }
4835
4836 gint copy_dir(const gchar *src, const gchar *dst)
4837 {
4838         GDir *dir;
4839         const gchar *name;
4840
4841         if ((dir = g_dir_open(src, 0, NULL)) == NULL) {
4842                 g_warning("failed to open directory: %s", src);
4843                 return -1;
4844         }
4845
4846         if (make_dir(dst) < 0)
4847                 return -1;
4848
4849         while ((name = g_dir_read_name(dir)) != NULL) {
4850                 gchar *old_file, *new_file;
4851                 old_file = g_strconcat(src, G_DIR_SEPARATOR_S, name, NULL);
4852                 new_file = g_strconcat(dst, G_DIR_SEPARATOR_S, name, NULL);
4853                 debug_print("copying: %s -> %s\n", old_file, new_file);
4854                 if (g_file_test(old_file, G_FILE_TEST_IS_REGULAR)) {
4855                         gint r = copy_file(old_file, new_file, TRUE);
4856                         if (r < 0) {
4857                                 g_dir_close(dir);
4858                                 return r;
4859                         }
4860                 }
4861 #ifndef G_OS_WIN32
4862                 /* Windows has no symlinks.  Or well, Vista seems to
4863                    have something like this but the semantics might be
4864                    different.  Thus we don't use it under Windows. */
4865                  else if (g_file_test(old_file, G_FILE_TEST_IS_SYMLINK)) {
4866                         GError *error;
4867                         gint r = 0;
4868                         gchar *target = g_file_read_link(old_file, &error);
4869                         if (target)
4870                                 r = symlink(target, new_file);
4871                         g_free(target);
4872                         if (r < 0) {
4873                                 g_dir_close(dir);
4874                                 return r;
4875                         }
4876                  }
4877 #endif /*G_OS_WIN32*/
4878                 else if (g_file_test(old_file, G_FILE_TEST_IS_DIR)) {
4879                         gint r = copy_dir(old_file, new_file);
4880                         if (r < 0) {
4881                                 g_dir_close(dir);
4882                                 return r;
4883                         }
4884                 }
4885         }
4886         g_dir_close(dir);
4887         return 0;
4888 }
4889
4890 /* crude test to see if a file is an email. */
4891 gboolean file_is_email (const gchar *filename)
4892 {
4893         FILE *fp = NULL;
4894         gchar buffer[2048];
4895         gint i = 0;
4896         gint score = 0;
4897         if (filename == NULL)
4898                 return FALSE;
4899         if ((fp = g_fopen(filename, "rb")) == NULL)
4900                 return FALSE;
4901         while (i < 60 && score < 3
4902                && fgets(buffer, sizeof (buffer), fp) > 0) {
4903                 if (!strncmp(buffer, "From:", strlen("From:")))
4904                         score++;
4905                 else if (!strncmp(buffer, "Date:", strlen("Date:")))
4906                         score++;
4907                 else if (!strncmp(buffer, "Message-ID:", strlen("Message-ID:")))
4908                         score++;
4909                 else if (!strncmp(buffer, "Subject:", strlen("Subject:")))
4910                         score++;
4911                 i++;
4912         }
4913         fclose(fp);
4914         return (score >= 3);
4915 }
4916
4917 gboolean sc_g_list_bigger(GList *list, gint max)
4918 {
4919         GList *cur = list;
4920         int i = 0;
4921         while (cur && i <= max+1) {
4922                 i++;
4923                 cur = cur->next;
4924         }
4925         return (i > max);
4926 }
4927
4928 gboolean sc_g_slist_bigger(GSList *list, gint max)
4929 {
4930         GSList *cur = list;
4931         int i = 0;
4932         while (cur && i <= max+1) {
4933                 i++;
4934                 cur = cur->next;
4935         }
4936         return (i > max);
4937 }
4938
4939 const gchar *daynames[] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL};
4940 const gchar *monthnames[] = {NULL, NULL, NULL, NULL, NULL, NULL, 
4941                              NULL, NULL, NULL, NULL, NULL, NULL};
4942 const gchar *s_daynames[] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL};
4943 const gchar *s_monthnames[] = {NULL, NULL, NULL, NULL, NULL, NULL, 
4944                              NULL, NULL, NULL, NULL, NULL, NULL};
4945
4946 gint daynames_len[] =     {0,0,0,0,0,0,0};
4947 gint monthnames_len[] =   {0,0,0,0,0,0,
4948                                  0,0,0,0,0,0};
4949 gint s_daynames_len[] =   {0,0,0,0,0,0,0};
4950 gint s_monthnames_len[] = {0,0,0,0,0,0,
4951                                  0,0,0,0,0,0};
4952 const gchar *s_am_up = NULL;
4953 const gchar *s_pm_up = NULL;
4954 const gchar *s_am_low = NULL;
4955 const gchar *s_pm_low = NULL;
4956
4957 gint s_am_up_len = 0;
4958 gint s_pm_up_len = 0;
4959 gint s_am_low_len = 0;
4960 gint s_pm_low_len = 0;
4961
4962 static gboolean time_names_init_done = FALSE;
4963
4964 static void init_time_names(void)
4965 {
4966         int i = 0;
4967
4968         daynames[0] = C_("Complete day name for use by strftime", "Sunday");
4969         daynames[1] = C_("Complete day name for use by strftime", "Monday");
4970         daynames[2] = C_("Complete day name for use by strftime", "Tuesday");
4971         daynames[3] = C_("Complete day name for use by strftime", "Wednesday");
4972         daynames[4] = C_("Complete day name for use by strftime", "Thursday");
4973         daynames[5] = C_("Complete day name for use by strftime", "Friday");
4974         daynames[6] = C_("Complete day name for use by strftime", "Saturday");
4975
4976         monthnames[0] = C_("Complete month name for use by strftime", "January");
4977         monthnames[1] = C_("Complete month name for use by strftime", "February");
4978         monthnames[2] = C_("Complete month name for use by strftime", "March");
4979         monthnames[3] = C_("Complete month name for use by strftime", "April");
4980         monthnames[4] = C_("Complete month name for use by strftime", "May");
4981         monthnames[5] = C_("Complete month name for use by strftime", "June");
4982         monthnames[6] = C_("Complete month name for use by strftime", "July");
4983         monthnames[7] = C_("Complete month name for use by strftime", "August");
4984         monthnames[8] = C_("Complete month name for use by strftime", "September");
4985         monthnames[9] = C_("Complete month name for use by strftime", "October");
4986         monthnames[10] = C_("Complete month name for use by strftime", "November");
4987         monthnames[11] = C_("Complete month name for use by strftime", "December");
4988
4989         s_daynames[0] = C_("Abbr. day name for use by strftime", "Sun");
4990         s_daynames[1] = C_("Abbr. day name for use by strftime", "Mon");
4991         s_daynames[2] = C_("Abbr. day name for use by strftime", "Tue");
4992         s_daynames[3] = C_("Abbr. day name for use by strftime", "Wed");
4993         s_daynames[4] = C_("Abbr. day name for use by strftime", "Thu");
4994         s_daynames[5] = C_("Abbr. day name for use by strftime", "Fri");
4995         s_daynames[6] = C_("Abbr. day name for use by strftime", "Sat");
4996         
4997         s_monthnames[0] = C_("Abbr. month name for use by strftime", "Jan");
4998         s_monthnames[1] = C_("Abbr. month name for use by strftime", "Feb");
4999         s_monthnames[2] = C_("Abbr. month name for use by strftime", "Mar");
5000         s_monthnames[3] = C_("Abbr. month name for use by strftime", "Apr");
5001         s_monthnames[4] = C_("Abbr. month name for use by strftime", "May");
5002         s_monthnames[5] = C_("Abbr. month name for use by strftime", "Jun");
5003         s_monthnames[6] = C_("Abbr. month name for use by strftime", "Jul");
5004         s_monthnames[7] = C_("Abbr. month name for use by strftime", "Aug");
5005         s_monthnames[8] = C_("Abbr. month name for use by strftime", "Sep");
5006         s_monthnames[9] = C_("Abbr. month name for use by strftime", "Oct");
5007         s_monthnames[10] = C_("Abbr. month name for use by strftime", "Nov");
5008         s_monthnames[11] = C_("Abbr. month name for use by strftime", "Dec");
5009
5010         for (i = 0; i < 7; i++) {
5011                 daynames_len[i] = strlen(daynames[i]);
5012                 s_daynames_len[i] = strlen(s_daynames[i]);
5013         }
5014         for (i = 0; i < 12; i++) {
5015                 monthnames_len[i] = strlen(monthnames[i]);
5016                 s_monthnames_len[i] = strlen(s_monthnames[i]);
5017         }
5018
5019         s_am_up = C_("For use by strftime (morning)", "AM");
5020         s_pm_up = C_("For use by strftime (afternoon)", "PM");
5021         s_am_low = C_("For use by strftime (morning, lowercase)", "am");
5022         s_pm_low = C_("For use by strftime (afternoon, lowercase)", "pm");
5023         
5024         s_am_up_len = strlen(s_am_up);
5025         s_pm_up_len = strlen(s_pm_up);
5026         s_am_low_len = strlen(s_am_low);
5027         s_pm_low_len = strlen(s_pm_low);
5028
5029         time_names_init_done = TRUE;
5030 }
5031
5032 #define CHECK_SIZE() {                  \
5033         total_done += len;              \
5034         if (total_done >= buflen) {     \
5035                 buf[buflen-1] = '\0';   \
5036                 return 0;               \
5037         }                               \
5038 }
5039
5040 size_t fast_strftime(gchar *buf, gint buflen, const gchar *format, struct tm *lt)
5041 {
5042         gchar *curpos = buf;
5043         gint total_done = 0;
5044         gchar subbuf[64], subfmt[64];
5045         static time_t last_tzset = (time_t)0;
5046         
5047         if (!time_names_init_done)
5048                 init_time_names();
5049         
5050         if (format == NULL || lt == NULL)
5051                 return 0;
5052                 
5053         if (last_tzset != time(NULL)) {
5054                 tzset();
5055                 last_tzset = time(NULL);
5056         }
5057         while(*format) {
5058                 if (*format == '%') {
5059                         gint len = 0, tmp = 0;
5060                         format++;
5061                         switch(*format) {
5062                         case '%':
5063                                 len = 1; CHECK_SIZE();
5064                                 *curpos = '%';
5065                                 break;
5066                         case 'a':
5067                                 len = s_daynames_len[lt->tm_wday]; CHECK_SIZE();
5068                                 strncpy2(curpos, s_daynames[lt->tm_wday], buflen - total_done);
5069                                 break;
5070                         case 'A':
5071                                 len = daynames_len[lt->tm_wday]; CHECK_SIZE();
5072                                 strncpy2(curpos, daynames[lt->tm_wday], buflen - total_done);
5073                                 break;
5074                         case 'b':
5075                         case 'h':
5076                                 len = s_monthnames_len[lt->tm_mon]; CHECK_SIZE();
5077                                 strncpy2(curpos, s_monthnames[lt->tm_mon], buflen - total_done);
5078                                 break;
5079                         case 'B':
5080                                 len = monthnames_len[lt->tm_mon]; CHECK_SIZE();
5081                                 strncpy2(curpos, monthnames[lt->tm_mon], buflen - total_done);
5082                                 break;
5083                         case 'c':
5084                                 strftime(subbuf, 64, "%c", lt);
5085                                 len = strlen(subbuf); CHECK_SIZE();
5086                                 strncpy2(curpos, subbuf, buflen - total_done);
5087                                 break;
5088                         case 'C':
5089                                 total_done += 2; CHECK_SIZE();
5090                                 tmp = (lt->tm_year + 1900)/100;
5091                                 *curpos++ = '0'+(tmp / 10);
5092                                 *curpos++ = '0'+(tmp % 10);
5093                                 break;
5094                         case 'd':
5095                                 total_done += 2; CHECK_SIZE();
5096                                 *curpos++ = '0'+(lt->tm_mday / 10);
5097                                 *curpos++ = '0'+(lt->tm_mday % 10);
5098                                 break;
5099                         case 'D':
5100                                 total_done += 8; CHECK_SIZE();
5101                                 *curpos++ = '0'+((lt->tm_mon+1) / 10);
5102                                 *curpos++ = '0'+((lt->tm_mon+1) % 10);
5103                                 *curpos++ = '/';
5104                                 *curpos++ = '0'+(lt->tm_mday / 10);
5105                                 *curpos++ = '0'+(lt->tm_mday % 10);
5106                                 *curpos++ = '/';
5107                                 tmp = lt->tm_year%100;
5108                                 *curpos++ = '0'+(tmp / 10);
5109                                 *curpos++ = '0'+(tmp % 10);
5110                                 break;
5111                         case 'e':
5112                                 len = 2; CHECK_SIZE();
5113                                 snprintf(curpos, buflen - total_done, "%2d", lt->tm_mday);
5114                                 break;
5115                         case 'F':
5116                                 len = 10; CHECK_SIZE();
5117                                 snprintf(curpos, buflen - total_done, "%4d-%02d-%02d", 
5118                                         lt->tm_year + 1900, lt->tm_mon +1, lt->tm_mday);
5119                                 break;
5120                         case 'H':
5121                                 total_done += 2; CHECK_SIZE();
5122                                 *curpos++ = '0'+(lt->tm_hour / 10);
5123                                 *curpos++ = '0'+(lt->tm_hour % 10);
5124                                 break;
5125                         case 'I':
5126                                 total_done += 2; CHECK_SIZE();
5127                                 tmp = lt->tm_hour;
5128                                 if (tmp > 12)
5129                                         tmp -= 12;
5130                                 else if (tmp == 0)
5131                                         tmp = 12;
5132                                 *curpos++ = '0'+(tmp / 10);
5133                                 *curpos++ = '0'+(tmp % 10);
5134                                 break;
5135                         case 'j':
5136                                 len = 3; CHECK_SIZE();
5137                                 snprintf(curpos, buflen - total_done, "%03d", lt->tm_yday+1);
5138                                 break;
5139                         case 'k':
5140                                 len = 2; CHECK_SIZE();
5141                                 snprintf(curpos, buflen - total_done, "%2d", lt->tm_hour);
5142                                 break;
5143                         case 'l':
5144                                 len = 2; CHECK_SIZE();
5145                                 tmp = lt->tm_hour;
5146                                 if (tmp > 12)
5147                                         tmp -= 12;
5148                                 else if (tmp == 0)
5149                                         tmp = 12;
5150                                 snprintf(curpos, buflen - total_done, "%2d", tmp);
5151                                 break;
5152                         case 'm':
5153                                 total_done += 2; CHECK_SIZE();
5154                                 tmp = lt->tm_mon + 1;
5155                                 *curpos++ = '0'+(tmp / 10);
5156                                 *curpos++ = '0'+(tmp % 10);
5157                                 break;
5158                         case 'M':
5159                                 total_done += 2; CHECK_SIZE();
5160                                 *curpos++ = '0'+(lt->tm_min / 10);
5161                                 *curpos++ = '0'+(lt->tm_min % 10);
5162                                 break;
5163                         case 'n':
5164                                 len = 1; CHECK_SIZE();
5165                                 *curpos = '\n';
5166                                 break;
5167                         case 'p':
5168                                 if (lt->tm_hour >= 12) {
5169                                         len = s_pm_up_len; CHECK_SIZE();
5170                                         snprintf(curpos, buflen-total_done, "%s", s_pm_up);
5171                                 } else {
5172                                         len = s_am_up_len; CHECK_SIZE();
5173                                         snprintf(curpos, buflen-total_done, "%s", s_am_up);
5174                                 }
5175                                 break;
5176                         case 'P':
5177                                 if (lt->tm_hour >= 12) {
5178                                         len = s_pm_low_len; CHECK_SIZE();
5179                                         snprintf(curpos, buflen-total_done, "%s", s_pm_low);
5180                                 } else {
5181                                         len = s_am_low_len; CHECK_SIZE();
5182                                         snprintf(curpos, buflen-total_done, "%s", s_am_low);
5183                                 }
5184                                 break;
5185                         case 'r':
5186                                 strftime(subbuf, 64, "%r", lt);
5187                                 len = strlen(subbuf); CHECK_SIZE();
5188                                 strncpy2(curpos, subbuf, buflen - total_done);
5189                                 break;
5190                         case 'R':
5191                                 total_done += 5; CHECK_SIZE();
5192                                 *curpos++ = '0'+(lt->tm_hour / 10);
5193                                 *curpos++ = '0'+(lt->tm_hour % 10);
5194                                 *curpos++ = ':';
5195                                 *curpos++ = '0'+(lt->tm_min / 10);
5196                                 *curpos++ = '0'+(lt->tm_min % 10);
5197                                 break;
5198                         case 's':
5199                                 snprintf(subbuf, 64, "%ld", mktime(lt));
5200                                 len = strlen(subbuf); CHECK_SIZE();
5201                                 strncpy2(curpos, subbuf, buflen - total_done);
5202                                 break;
5203                         case 'S':
5204                                 total_done += 2; CHECK_SIZE();
5205                                 *curpos++ = '0'+(lt->tm_sec / 10);
5206                                 *curpos++ = '0'+(lt->tm_sec % 10);
5207                                 break;
5208                         case 't':
5209                                 len = 1; CHECK_SIZE();
5210                                 *curpos = '\t';
5211                                 break;
5212                         case 'T':
5213                                 total_done += 8; CHECK_SIZE();
5214                                 *curpos++ = '0'+(lt->tm_hour / 10);
5215                                 *curpos++ = '0'+(lt->tm_hour % 10);
5216                                 *curpos++ = ':';
5217                                 *curpos++ = '0'+(lt->tm_min / 10);
5218                                 *curpos++ = '0'+(lt->tm_min % 10);
5219                                 *curpos++ = ':';
5220                                 *curpos++ = '0'+(lt->tm_sec / 10);
5221                                 *curpos++ = '0'+(lt->tm_sec % 10);
5222                                 break;
5223                         case 'u':
5224                                 len = 1; CHECK_SIZE();
5225                                 snprintf(curpos, buflen - total_done, "%d", lt->tm_wday == 0 ? 7: lt->tm_wday);
5226                                 break;
5227                         case 'w':
5228                                 len = 1; CHECK_SIZE();
5229                                 snprintf(curpos, buflen - total_done, "%d", lt->tm_wday);
5230                                 break;
5231                         case 'x':
5232                                 strftime(subbuf, 64, "%x", lt);
5233                                 len = strlen(subbuf); CHECK_SIZE();
5234                                 strncpy2(curpos, subbuf, buflen - total_done);
5235                                 break;
5236                         case 'X':
5237                                 strftime(subbuf, 64, "%X", lt);
5238                                 len = strlen(subbuf); CHECK_SIZE();
5239                                 strncpy2(curpos, subbuf, buflen - total_done);
5240                                 break;
5241                         case 'y':
5242                                 total_done += 2; CHECK_SIZE();
5243                                 tmp = lt->tm_year%100;
5244                                 *curpos++ = '0'+(tmp / 10);
5245                                 *curpos++ = '0'+(tmp % 10);
5246                                 break;
5247                         case 'Y':
5248                                 len = 4; CHECK_SIZE();
5249                                 snprintf(curpos, buflen - total_done, "%4d", lt->tm_year + 1900);
5250                                 break;
5251                         case 'G':
5252                         case 'g':
5253                         case 'U':
5254                         case 'V':
5255                         case 'W':
5256                         case 'z':
5257                         case 'Z':
5258                         case '+':
5259                                 /* let these complicated ones be done with the libc */
5260                                 snprintf(subfmt, 64, "%%%c", *format);
5261                                 strftime(subbuf, 64, subfmt, lt);
5262                                 len = strlen(subbuf); CHECK_SIZE();
5263                                 strncpy2(curpos, subbuf, buflen - total_done);
5264                                 break;
5265                         case 'E':
5266                         case 'O':
5267                                 /* let these complicated modifiers be done with the libc */
5268                                 snprintf(subfmt, 64, "%%%c%c", *format, *(format+1));
5269                                 strftime(subbuf, 64, subfmt, lt);
5270                                 len = strlen(subbuf); CHECK_SIZE();
5271                                 strncpy2(curpos, subbuf, buflen - total_done);
5272                                 format++;
5273                                 break;
5274                         default:
5275                                 g_warning("format error (%c)", *format);
5276                                 *curpos = '\0';
5277                                 return total_done;
5278                         }
5279                         curpos += len;
5280                         format++;
5281                 } else {
5282                         int len = 1; CHECK_SIZE();
5283                         *curpos++ = *format++; 
5284                 }
5285         }
5286         *curpos = '\0';
5287         return total_done;
5288 }
5289
5290 gboolean prefs_common_get_use_shred(void);
5291
5292
5293 #ifdef G_OS_WIN32
5294 #define WEXITSTATUS(x) (x)
5295 #endif
5296
5297 int claws_unlink(const gchar *filename) 
5298 {
5299         GStatBuf s;
5300         static int found_shred = -1;
5301         static const gchar *args[4];
5302
5303         if (filename == NULL)
5304                 return 0;
5305
5306         if (prefs_common_get_use_shred()) {
5307                 if (found_shred == -1) {
5308                         /* init */
5309                         args[0] = g_find_program_in_path("shred");
5310                         debug_print("found shred: %s\n", args[0]);
5311                         found_shred = (args[0] != NULL) ? 1:0;
5312                         args[1] = "-f";
5313                         args[3] = NULL;
5314                 }
5315                 if (found_shred == 1) {
5316                         if (g_stat(filename, &s) == 0 && S_ISREG(s.st_mode)) {
5317                                 if (s.st_nlink == 1) {
5318                                         gint status=0;
5319                                         args[2] = filename;
5320                                         g_spawn_sync(NULL, (gchar **)args, NULL, 0,
5321                                          NULL, NULL, NULL, NULL, &status, NULL);
5322                                         debug_print("%s %s exited with status %d\n",
5323                                                 args[0], filename, WEXITSTATUS(status));
5324                                         if (truncate(filename, 0) < 0)
5325                                                 g_warning("couln't truncate: %s", filename);
5326                                 }
5327                         }
5328                 }
5329         }
5330         return g_unlink(filename);
5331 }
5332
5333 GMutex *cm_mutex_new(void) {
5334 #if GLIB_CHECK_VERSION(2,32,0)
5335         GMutex *m = g_new0(GMutex, 1);
5336         g_mutex_init(m);
5337         return m;
5338 #else
5339         return g_mutex_new();
5340 #endif
5341 }
5342
5343 void cm_mutex_free(GMutex *mutex) {
5344 #if GLIB_CHECK_VERSION(2,32,0)
5345         g_mutex_clear(mutex);
5346         g_free(mutex);
5347 #else
5348         g_mutex_free(mutex);
5349 #endif
5350 }
5351
5352 static gchar *canonical_list_to_file(GSList *list)
5353 {
5354         GString *result = g_string_new(NULL);
5355         GSList *pathlist = g_slist_reverse(g_slist_copy(list));
5356         GSList *cur;
5357         gchar *str;
5358
5359 #ifndef G_OS_WIN32
5360         result = g_string_append(result, G_DIR_SEPARATOR_S);
5361 #else
5362         if (pathlist->data) {
5363                 const gchar *root = (gchar *)pathlist->data;
5364                 if (root[0] != '\0' && g_ascii_isalpha(root[0]) &&
5365                     root[1] == ':') {
5366                         /* drive - don't prepend dir separator */
5367                 } else {
5368                         result = g_string_append(result, G_DIR_SEPARATOR_S);
5369                 }
5370         }
5371 #endif
5372
5373         for (cur = pathlist; cur; cur = cur->next) {
5374                 result = g_string_append(result, (gchar *)cur->data);
5375                 if (cur->next)
5376                         result = g_string_append(result, G_DIR_SEPARATOR_S);
5377         }
5378         g_slist_free(pathlist);
5379
5380         str = result->str;
5381         g_string_free(result, FALSE);
5382
5383         return str;
5384 }
5385
5386 static GSList *cm_split_path(const gchar *filename, int depth)
5387 {
5388         gchar **path_parts;
5389         GSList *canonical_parts = NULL;
5390         GStatBuf st;
5391         int i;
5392         gboolean follow_symlinks = TRUE;
5393
5394         if (depth > 32) {
5395 #ifndef G_OS_WIN32
5396                 errno = ELOOP;
5397 #else
5398                 errno = EINVAL; /* can't happen, no symlink handling */
5399 #endif
5400                 return NULL;
5401         }
5402
5403         if (!g_path_is_absolute(filename)) {
5404                 errno =EINVAL;
5405                 return NULL;
5406         }
5407
5408         path_parts = g_strsplit(filename, G_DIR_SEPARATOR_S, -1);
5409
5410         for (i = 0; path_parts[i] != NULL; i++) {
5411                 if (!strcmp(path_parts[i], ""))
5412                         continue;
5413                 if (!strcmp(path_parts[i], "."))
5414                         continue;
5415                 else if (!strcmp(path_parts[i], "..")) {
5416                         if (i == 0) {
5417                                 errno =ENOTDIR;
5418                                 return NULL;
5419                         }
5420                         else /* Remove the last inserted element */
5421                                 canonical_parts = 
5422                                         g_slist_delete_link(canonical_parts,
5423                                                             canonical_parts);
5424                 } else {
5425                         gchar *tmp_path;
5426
5427                         canonical_parts = g_slist_prepend(canonical_parts,
5428                                                 g_strdup(path_parts[i]));
5429
5430                         tmp_path = canonical_list_to_file(canonical_parts);
5431
5432                         if(g_stat(tmp_path, &st) < 0) {
5433                                 if (errno == ENOENT) {
5434                                         errno = 0;
5435                                         follow_symlinks = FALSE;
5436                                 }
5437                                 if (errno != 0) {
5438                                         g_free(tmp_path);
5439                                         slist_free_strings_full(canonical_parts);
5440                                         g_strfreev(path_parts);
5441
5442                                         return NULL;
5443                                 }
5444                         }
5445 #ifndef G_OS_WIN32
5446                         if (follow_symlinks && g_file_test(tmp_path, G_FILE_TEST_IS_SYMLINK)) {
5447                                 GError *error = NULL;
5448                                 gchar *target = g_file_read_link(tmp_path, &error);
5449
5450                                 if (!g_path_is_absolute(target)) {
5451                                         /* remove the last inserted element */
5452                                         canonical_parts = 
5453                                                 g_slist_delete_link(canonical_parts,
5454                                                             canonical_parts);
5455                                         /* add the target */
5456                                         canonical_parts = g_slist_prepend(canonical_parts,
5457                                                 g_strdup(target));
5458                                         g_free(target);
5459
5460                                         /* and get the new target */
5461                                         target = canonical_list_to_file(canonical_parts);
5462                                 }
5463
5464                                 /* restart from absolute target */
5465                                 slist_free_strings_full(canonical_parts);
5466                                 canonical_parts = NULL;
5467                                 if (!error)
5468                                         canonical_parts = cm_split_path(target, depth + 1);
5469                                 else
5470                                         g_error_free(error);
5471                                 if (canonical_parts == NULL) {
5472                                         g_free(tmp_path);
5473                                         g_strfreev(path_parts);
5474                                         return NULL;
5475                                 }
5476                                 g_free(target);
5477                         }
5478 #endif
5479                         g_free(tmp_path);
5480                 }
5481         }
5482         g_strfreev(path_parts);
5483         return canonical_parts;
5484 }
5485
5486 /*
5487  * Canonicalize a filename, resolving symlinks along the way.
5488  * Returns a negative errno in case of error.
5489  */
5490 int cm_canonicalize_filename(const gchar *filename, gchar **canonical_name) {
5491         GSList *canonical_parts;
5492         gboolean is_absolute;
5493
5494         if (filename == NULL)
5495                 return -EINVAL;
5496         if (canonical_name == NULL)
5497                 return -EINVAL;
5498         *canonical_name = NULL;
5499
5500         is_absolute = g_path_is_absolute(filename);
5501         if (!is_absolute) {
5502                 /* Always work on absolute filenames. */
5503                 gchar *cur = g_get_current_dir();
5504                 gchar *absolute_filename = g_strconcat(cur, G_DIR_SEPARATOR_S,
5505                                                        filename, NULL);
5506                 
5507                 canonical_parts = cm_split_path(absolute_filename, 0);
5508                 g_free(absolute_filename);
5509                 g_free(cur);
5510         } else
5511                 canonical_parts = cm_split_path(filename, 0);
5512
5513         if (canonical_parts == NULL)
5514                 return -errno;
5515
5516         *canonical_name = canonical_list_to_file(canonical_parts);
5517         slist_free_strings_full(canonical_parts);
5518         return 0;
5519 }
5520
5521 /* Returns a decoded base64 string, guaranteed to be null-terminated. */
5522 guchar *g_base64_decode_zero(const gchar *text, gsize *out_len)
5523 {
5524         gchar *tmp = g_base64_decode(text, out_len);
5525         gchar *out = g_strndup(tmp, *out_len);
5526
5527         g_free(tmp);
5528
5529         if (strlen(out) != *out_len) {
5530                 g_warning ("strlen(out) %zd != *out_len %" G_GSIZE_FORMAT, strlen(out), *out_len);
5531         }
5532
5533         return out;
5534 }
5535
5536 #if !GLIB_CHECK_VERSION(2, 30, 0)
5537 /**
5538  * g_utf8_substring:
5539  * @str: a UTF-8 encoded string
5540  * @start_pos: a character offset within @str
5541  * @end_pos: another character offset within @str
5542  *
5543  * Copies a substring out of a UTF-8 encoded string.
5544  * The substring will contain @end_pos - @start_pos
5545  * characters.
5546  *
5547  * Returns: a newly allocated copy of the requested
5548  *     substring. Free with g_free() when no longer needed.
5549  *
5550  * Since: GLIB 2.30
5551  */
5552 gchar *
5553 g_utf8_substring (const gchar *str,
5554                                   glong            start_pos,
5555                                   glong            end_pos)
5556 {
5557   gchar *start, *end, *out;
5558
5559   start = g_utf8_offset_to_pointer (str, start_pos);
5560   end = g_utf8_offset_to_pointer (start, end_pos - start_pos);
5561
5562   out = g_malloc (end - start + 1);
5563   memcpy (out, start, end - start);
5564   out[end - start] = 0;
5565
5566   return out;
5567 }
5568 #endif