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