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