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