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