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