2011-11-01 [colin] 3.7.10cvs68
[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         prev_dir = g_get_current_dir();
2326
2327         if (g_chdir(dir) < 0) {
2328                 FILE_OP_ERROR(dir, "chdir");
2329                 g_free(prev_dir);
2330                 return -1;
2331         }
2332
2333         if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2334                 g_warning("failed to open directory: %s\n", dir);
2335                 g_free(prev_dir);
2336                 return -1;
2337         }
2338
2339         while ((dir_name = g_dir_read_name(dp)) != NULL) {
2340                 file_no = to_number(dir_name);
2341                 if (file_no > 0 && first <= file_no && file_no <= last) {
2342                         if (is_dir_exist(dir_name))
2343                                 continue;
2344                         if (claws_unlink(dir_name) < 0)
2345                                 FILE_OP_ERROR(dir_name, "unlink");
2346                 }
2347         }
2348
2349         g_dir_close(dp);
2350
2351         if (g_chdir(prev_dir) < 0) {
2352                 FILE_OP_ERROR(prev_dir, "chdir");
2353                 g_free(prev_dir);
2354                 return -1;
2355         }
2356
2357         g_free(prev_dir);
2358
2359         return 0;
2360 }
2361
2362 gint remove_numbered_files_not_in_list(const gchar *dir, GSList *numberlist)
2363 {
2364         GDir *dp;
2365         const gchar *dir_name;
2366         gchar *prev_dir;
2367         gint file_no;
2368
2369         prev_dir = g_get_current_dir();
2370
2371         if (g_chdir(dir) < 0) {
2372                 FILE_OP_ERROR(dir, "chdir");
2373                 g_free(prev_dir);
2374                 return -1;
2375         }
2376
2377         if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2378                 FILE_OP_ERROR(dir, "opendir");
2379                 g_free(prev_dir);
2380                 return -1;
2381         }
2382
2383         while ((dir_name = g_dir_read_name(dp)) != NULL) {
2384                 file_no = to_number(dir_name);
2385                 if (file_no > 0 && (g_slist_find(numberlist, GINT_TO_POINTER(file_no)) == NULL)) {
2386                         debug_print("removing unwanted file %d from %s\n", file_no, dir);
2387                         if (is_dir_exist(dir_name))
2388                                 continue;
2389                         if (claws_unlink(dir_name) < 0)
2390                                 FILE_OP_ERROR(dir_name, "unlink");
2391                 }
2392         }
2393
2394         g_dir_close(dp);
2395
2396         if (g_chdir(prev_dir) < 0) {
2397                 FILE_OP_ERROR(prev_dir, "chdir");
2398                 g_free(prev_dir);
2399                 return -1;
2400         }
2401
2402         g_free(prev_dir);
2403
2404         return 0;
2405 }
2406
2407 gint remove_all_numbered_files(const gchar *dir)
2408 {
2409         return remove_numbered_files(dir, 0, UINT_MAX);
2410 }
2411
2412 gint remove_dir_recursive(const gchar *dir)
2413 {
2414         struct stat s;
2415         GDir *dp;
2416         const gchar *dir_name;
2417         gchar *prev_dir;
2418
2419         if (g_stat(dir, &s) < 0) {
2420                 FILE_OP_ERROR(dir, "stat");
2421                 if (ENOENT == errno) return 0;
2422                 return -1;
2423         }
2424
2425         if (!S_ISDIR(s.st_mode)) {
2426                 if (claws_unlink(dir) < 0) {
2427                         FILE_OP_ERROR(dir, "unlink");
2428                         return -1;
2429                 }
2430
2431                 return 0;
2432         }
2433
2434         prev_dir = g_get_current_dir();
2435         /* g_print("prev_dir = %s\n", prev_dir); */
2436
2437         if (!path_cmp(prev_dir, dir)) {
2438                 g_free(prev_dir);
2439                 if (g_chdir("..") < 0) {
2440                         FILE_OP_ERROR(dir, "chdir");
2441                         return -1;
2442                 }
2443                 prev_dir = g_get_current_dir();
2444         }
2445
2446         if (g_chdir(dir) < 0) {
2447                 FILE_OP_ERROR(dir, "chdir");
2448                 g_free(prev_dir);
2449                 return -1;
2450         }
2451
2452         if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2453                 g_warning("failed to open directory: %s\n", dir);
2454                 g_chdir(prev_dir);
2455                 g_free(prev_dir);
2456                 return -1;
2457         }
2458
2459         /* remove all files in the directory */
2460         while ((dir_name = g_dir_read_name(dp)) != NULL) {
2461                 /* g_print("removing %s\n", dir_name); */
2462
2463                 if (is_dir_exist(dir_name)) {
2464                         if (remove_dir_recursive(dir_name) < 0) {
2465                                 g_warning("can't remove directory\n");
2466                                 return -1;
2467                         }
2468                 } else {
2469                         if (claws_unlink(dir_name) < 0)
2470                                 FILE_OP_ERROR(dir_name, "unlink");
2471                 }
2472         }
2473
2474         g_dir_close(dp);
2475
2476         if (g_chdir(prev_dir) < 0) {
2477                 FILE_OP_ERROR(prev_dir, "chdir");
2478                 g_free(prev_dir);
2479                 return -1;
2480         }
2481
2482         g_free(prev_dir);
2483
2484         if (g_rmdir(dir) < 0) {
2485                 FILE_OP_ERROR(dir, "rmdir");
2486                 return -1;
2487         }
2488
2489         return 0;
2490 }
2491
2492 gint rename_force(const gchar *oldpath, const gchar *newpath)
2493 {
2494 #ifndef G_OS_UNIX
2495         if (!is_file_entry_exist(oldpath)) {
2496                 errno = ENOENT;
2497                 return -1;
2498         }
2499         if (is_file_exist(newpath)) {
2500                 if (claws_unlink(newpath) < 0)
2501                         FILE_OP_ERROR(newpath, "unlink");
2502         }
2503 #endif
2504         return g_rename(oldpath, newpath);
2505 }
2506
2507 /*
2508  * Append src file body to the tail of dest file.
2509  * Now keep_backup has no effects.
2510  */
2511 gint append_file(const gchar *src, const gchar *dest, gboolean keep_backup)
2512 {
2513         FILE *src_fp, *dest_fp;
2514         gint n_read;
2515         gchar buf[BUFSIZ];
2516
2517         gboolean err = FALSE;
2518
2519         if ((src_fp = g_fopen(src, "rb")) == NULL) {
2520                 FILE_OP_ERROR(src, "g_fopen");
2521                 return -1;
2522         }
2523
2524         if ((dest_fp = g_fopen(dest, "ab")) == NULL) {
2525                 FILE_OP_ERROR(dest, "g_fopen");
2526                 fclose(src_fp);
2527                 return -1;
2528         }
2529
2530         if (change_file_mode_rw(dest_fp, dest) < 0) {
2531                 FILE_OP_ERROR(dest, "chmod");
2532                 g_warning("can't change file mode\n");
2533         }
2534
2535         while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
2536                 if (n_read < sizeof(buf) && ferror(src_fp))
2537                         break;
2538                 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2539                         g_warning("writing to %s failed.\n", dest);
2540                         fclose(dest_fp);
2541                         fclose(src_fp);
2542                         claws_unlink(dest);
2543                         return -1;
2544                 }
2545         }
2546
2547         if (ferror(src_fp)) {
2548                 FILE_OP_ERROR(src, "fread");
2549                 err = TRUE;
2550         }
2551         fclose(src_fp);
2552         if (fclose(dest_fp) == EOF) {
2553                 FILE_OP_ERROR(dest, "fclose");
2554                 err = TRUE;
2555         }
2556
2557         if (err) {
2558                 claws_unlink(dest);
2559                 return -1;
2560         }
2561
2562         return 0;
2563 }
2564
2565 gint copy_file(const gchar *src, const gchar *dest, gboolean keep_backup)
2566 {
2567         FILE *src_fp, *dest_fp;
2568         gint n_read;
2569         gchar buf[BUFSIZ];
2570         gchar *dest_bak = NULL;
2571         gboolean err = FALSE;
2572
2573         if ((src_fp = g_fopen(src, "rb")) == NULL) {
2574                 FILE_OP_ERROR(src, "g_fopen");
2575                 return -1;
2576         }
2577         if (is_file_exist(dest)) {
2578                 dest_bak = g_strconcat(dest, ".bak", NULL);
2579                 if (rename_force(dest, dest_bak) < 0) {
2580                         FILE_OP_ERROR(dest, "rename");
2581                         fclose(src_fp);
2582                         g_free(dest_bak);
2583                         return -1;
2584                 }
2585         }
2586
2587         if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2588                 FILE_OP_ERROR(dest, "g_fopen");
2589                 fclose(src_fp);
2590                 if (dest_bak) {
2591                         if (rename_force(dest_bak, dest) < 0)
2592                                 FILE_OP_ERROR(dest_bak, "rename");
2593                         g_free(dest_bak);
2594                 }
2595                 return -1;
2596         }
2597
2598         if (change_file_mode_rw(dest_fp, dest) < 0) {
2599                 FILE_OP_ERROR(dest, "chmod");
2600                 g_warning("can't change file mode\n");
2601         }
2602
2603         while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
2604                 if (n_read < sizeof(buf) && ferror(src_fp))
2605                         break;
2606                 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2607                         g_warning("writing to %s failed.\n", dest);
2608                         fclose(dest_fp);
2609                         fclose(src_fp);
2610                         claws_unlink(dest);
2611                         if (dest_bak) {
2612                                 if (rename_force(dest_bak, dest) < 0)
2613                                         FILE_OP_ERROR(dest_bak, "rename");
2614                                 g_free(dest_bak);
2615                         }
2616                         return -1;
2617                 }
2618         }
2619
2620         if (ferror(src_fp)) {
2621                 FILE_OP_ERROR(src, "fread");
2622                 err = TRUE;
2623         }
2624         fclose(src_fp);
2625         if (fclose(dest_fp) == EOF) {
2626                 FILE_OP_ERROR(dest, "fclose");
2627                 err = TRUE;
2628         }
2629
2630         if (err) {
2631                 claws_unlink(dest);
2632                 if (dest_bak) {
2633                         if (rename_force(dest_bak, dest) < 0)
2634                                 FILE_OP_ERROR(dest_bak, "rename");
2635                         g_free(dest_bak);
2636                 }
2637                 return -1;
2638         }
2639
2640         if (keep_backup == FALSE && dest_bak)
2641                 claws_unlink(dest_bak);
2642
2643         g_free(dest_bak);
2644
2645         return 0;
2646 }
2647
2648 gint move_file(const gchar *src, const gchar *dest, gboolean overwrite)
2649 {
2650         if (overwrite == FALSE && is_file_exist(dest)) {
2651                 g_warning("move_file(): file %s already exists.", dest);
2652                 return -1;
2653         }
2654
2655         if (rename_force(src, dest) == 0) return 0;
2656
2657         if (EXDEV != errno) {
2658                 FILE_OP_ERROR(src, "rename");
2659                 return -1;
2660         }
2661
2662         if (copy_file(src, dest, FALSE) < 0) return -1;
2663
2664         claws_unlink(src);
2665
2666         return 0;
2667 }
2668
2669 gint copy_file_part_to_fp(FILE *fp, off_t offset, size_t length, FILE *dest_fp)
2670 {
2671         gint n_read;
2672         gint bytes_left, to_read;
2673         gchar buf[BUFSIZ];
2674
2675         if (fseek(fp, offset, SEEK_SET) < 0) {
2676                 perror("fseek");
2677                 return -1;
2678         }
2679
2680         bytes_left = length;
2681         to_read = MIN(bytes_left, sizeof(buf));
2682
2683         while ((n_read = fread(buf, sizeof(gchar), to_read, fp)) > 0) {
2684                 if (n_read < to_read && ferror(fp))
2685                         break;
2686                 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2687                         return -1;
2688                 }
2689                 bytes_left -= n_read;
2690                 if (bytes_left == 0)
2691                         break;
2692                 to_read = MIN(bytes_left, sizeof(buf));
2693         }
2694
2695         if (ferror(fp)) {
2696                 perror("fread");
2697                 return -1;
2698         }
2699
2700         return 0;
2701 }
2702
2703 gint copy_file_part(FILE *fp, off_t offset, size_t length, const gchar *dest)
2704 {
2705         FILE *dest_fp;
2706         gboolean err = FALSE;
2707
2708         if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2709                 FILE_OP_ERROR(dest, "g_fopen");
2710                 return -1;
2711         }
2712
2713         if (change_file_mode_rw(dest_fp, dest) < 0) {
2714                 FILE_OP_ERROR(dest, "chmod");
2715                 g_warning("can't change file mode\n");
2716         }
2717
2718         if (copy_file_part_to_fp(fp, offset, length, dest_fp) < 0)
2719                 err = TRUE;
2720
2721         if (!err && fclose(dest_fp) == EOF) {
2722                 FILE_OP_ERROR(dest, "fclose");
2723                 err = TRUE;
2724         }
2725
2726         if (err) {
2727                 g_warning("writing to %s failed.\n", dest);
2728                 claws_unlink(dest);
2729                 return -1;
2730         }
2731
2732         return 0;
2733 }
2734
2735 /* convert line endings into CRLF. If the last line doesn't end with
2736  * linebreak, add it.
2737  */
2738 gchar *canonicalize_str(const gchar *str)
2739 {
2740         const gchar *p;
2741         guint new_len = 0;
2742         gchar *out, *outp;
2743
2744         for (p = str; *p != '\0'; ++p) {
2745                 if (*p != '\r') {
2746                         ++new_len;
2747                         if (*p == '\n')
2748                                 ++new_len;
2749                 }
2750         }
2751         if (p == str || *(p - 1) != '\n')
2752                 new_len += 2;
2753
2754         out = outp = g_malloc(new_len + 1);
2755         for (p = str; *p != '\0'; ++p) {
2756                 if (*p != '\r') {
2757                         if (*p == '\n')
2758                                 *outp++ = '\r';
2759                         *outp++ = *p;
2760                 }
2761         }
2762         if (p == str || *(p - 1) != '\n') {
2763                 *outp++ = '\r';
2764                 *outp++ = '\n';
2765         }
2766         *outp = '\0';
2767
2768         return out;
2769 }
2770
2771 gint canonicalize_file(const gchar *src, const gchar *dest)
2772 {
2773         FILE *src_fp, *dest_fp;
2774         gchar buf[BUFFSIZE];
2775         gint len;
2776         gboolean err = FALSE;
2777         gboolean last_linebreak = FALSE;
2778
2779         if (src == NULL || dest == NULL)
2780                 return -1;
2781
2782         if ((src_fp = g_fopen(src, "rb")) == NULL) {
2783                 FILE_OP_ERROR(src, "g_fopen");
2784                 return -1;
2785         }
2786
2787         if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2788                 FILE_OP_ERROR(dest, "g_fopen");
2789                 fclose(src_fp);
2790                 return -1;
2791         }
2792
2793         if (change_file_mode_rw(dest_fp, dest) < 0) {
2794                 FILE_OP_ERROR(dest, "chmod");
2795                 g_warning("can't change file mode\n");
2796         }
2797
2798         while (fgets(buf, sizeof(buf), src_fp) != NULL) {
2799                 gint r = 0;
2800
2801                 len = strlen(buf);
2802                 if (len == 0) break;
2803                 last_linebreak = FALSE;
2804
2805                 if (buf[len - 1] != '\n') {
2806                         last_linebreak = TRUE;
2807                         r = fputs(buf, dest_fp);
2808                 } else if (len > 1 && buf[len - 1] == '\n' && buf[len - 2] == '\r') {
2809                         r = fputs(buf, dest_fp);
2810                 } else {
2811                         if (len > 1) {
2812                                 r = fwrite(buf, 1, len - 1, dest_fp);
2813                                 if (r != (len -1))
2814                                         r = EOF;
2815                         }
2816                         if (r != EOF)
2817                                 r = fputs("\r\n", dest_fp);
2818                 }
2819
2820                 if (r == EOF) {
2821                         g_warning("writing to %s failed.\n", dest);
2822                         fclose(dest_fp);
2823                         fclose(src_fp);
2824                         claws_unlink(dest);
2825                         return -1;
2826                 }
2827         }
2828
2829         if (last_linebreak == TRUE) {
2830                 if (fputs("\r\n", dest_fp) == EOF)
2831                         err = TRUE;
2832         }
2833
2834         if (ferror(src_fp)) {
2835                 FILE_OP_ERROR(src, "fgets");
2836                 err = TRUE;
2837         }
2838         fclose(src_fp);
2839         if (fclose(dest_fp) == EOF) {
2840                 FILE_OP_ERROR(dest, "fclose");
2841                 err = TRUE;
2842         }
2843
2844         if (err) {
2845                 claws_unlink(dest);
2846                 return -1;
2847         }
2848
2849         return 0;
2850 }
2851
2852 gint canonicalize_file_replace(const gchar *file)
2853 {
2854         gchar *tmp_file;
2855
2856         tmp_file = get_tmp_file();
2857
2858         if (canonicalize_file(file, tmp_file) < 0) {
2859                 g_free(tmp_file);
2860                 return -1;
2861         }
2862
2863         if (move_file(tmp_file, file, TRUE) < 0) {
2864                 g_warning("can't replace %s .\n", file);
2865                 claws_unlink(tmp_file);
2866                 g_free(tmp_file);
2867                 return -1;
2868         }
2869
2870         g_free(tmp_file);
2871         return 0;
2872 }
2873
2874 gchar *normalize_newlines(const gchar *str)
2875 {
2876         const gchar *p = str;
2877         gchar *out, *outp;
2878
2879         out = outp = g_malloc(strlen(str) + 1);
2880         for (p = str; *p != '\0'; ++p) {
2881                 if (*p == '\r') {
2882                         if (*(p + 1) != '\n')
2883                                 *outp++ = '\n';
2884                 } else
2885                         *outp++ = *p;
2886         }
2887
2888         *outp = '\0';
2889
2890         return out;
2891 }
2892
2893 gchar *get_outgoing_rfc2822_str(FILE *fp)
2894 {
2895         gchar buf[BUFFSIZE];
2896         GString *str;
2897         gchar *ret;
2898
2899         str = g_string_new(NULL);
2900
2901         /* output header part */
2902         while (fgets(buf, sizeof(buf), fp) != NULL) {
2903                 strretchomp(buf);
2904                 if (!g_ascii_strncasecmp(buf, "Bcc:", 4)) {
2905                         gint next;
2906
2907                         for (;;) {
2908                                 next = fgetc(fp);
2909                                 if (next == EOF)
2910                                         break;
2911                                 else if (next != ' ' && next != '\t') {
2912                                         ungetc(next, fp);
2913                                         break;
2914                                 }
2915                                 if (fgets(buf, sizeof(buf), fp) == NULL)
2916                                         break;
2917                         }
2918                 } else {
2919                         g_string_append(str, buf);
2920                         g_string_append(str, "\r\n");
2921                         if (buf[0] == '\0')
2922                                 break;
2923                 }
2924         }
2925
2926         /* output body part */
2927         while (fgets(buf, sizeof(buf), fp) != NULL) {
2928                 strretchomp(buf);
2929                 if (buf[0] == '.')
2930                         g_string_append_c(str, '.');
2931                 g_string_append(str, buf);
2932                 g_string_append(str, "\r\n");
2933         }
2934
2935         ret = str->str;
2936         g_string_free(str, FALSE);
2937
2938         return ret;
2939 }
2940
2941 /*
2942  * Create a new boundary in a way that it is very unlikely that this
2943  * will occur in the following text.  It would be easy to ensure
2944  * uniqueness if everything is either quoted-printable or base64
2945  * encoded (note that conversion is allowed), but because MIME bodies
2946  * may be nested, it may happen that the same boundary has already
2947  * been used.
2948  *
2949  *   boundary := 0*69<bchars> bcharsnospace
2950  *   bchars := bcharsnospace / " "
2951  *   bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" /
2952  *                  "+" / "_" / "," / "-" / "." /
2953  *                  "/" / ":" / "=" / "?"
2954  *
2955  * some special characters removed because of buggy MTAs
2956  */
2957
2958 gchar *generate_mime_boundary(const gchar *prefix)
2959 {
2960         static gchar tbl[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
2961                              "abcdefghijklmnopqrstuvwxyz"
2962                              "1234567890+_./=";
2963         gchar buf_uniq[24];
2964         gint i;
2965
2966         for (i = 0; i < sizeof(buf_uniq) - 1; i++)
2967                 buf_uniq[i] = tbl[g_random_int_range(0, sizeof(tbl) - 1)];
2968         buf_uniq[i] = '\0';
2969
2970         return g_strdup_printf("%s_/%s", prefix ? prefix : "MP",
2971                                buf_uniq);
2972 }
2973
2974 gint change_file_mode_rw(FILE *fp, const gchar *file)
2975 {
2976 #if HAVE_FCHMOD
2977         return fchmod(fileno(fp), S_IRUSR|S_IWUSR);
2978 #else
2979         return g_chmod(file, S_IRUSR|S_IWUSR);
2980 #endif
2981 }
2982
2983 FILE *my_tmpfile(void)
2984 {
2985 #if HAVE_MKSTEMP || defined(G_OS_WIN32)
2986         const gchar suffix[] = ".XXXXXX";
2987         const gchar *tmpdir;
2988         guint tmplen;
2989         const gchar *progname;
2990         guint proglen;
2991         gchar *fname;
2992         gint fd;
2993         FILE *fp;
2994 #ifndef G_OS_WIN32
2995         gchar buf[2]="\0";
2996 #endif
2997
2998         tmpdir = get_tmp_dir();
2999         tmplen = strlen(tmpdir);
3000         progname = g_get_prgname();
3001         if (progname == NULL)
3002                 progname = "claws-mail";
3003         proglen = strlen(progname);
3004         Xalloca(fname, tmplen + 1 + proglen + sizeof(suffix),
3005                 return tmpfile());
3006
3007         memcpy(fname, tmpdir, tmplen);
3008         fname[tmplen] = G_DIR_SEPARATOR;
3009         memcpy(fname + tmplen + 1, progname, proglen);
3010         memcpy(fname + tmplen + 1 + proglen, suffix, sizeof(suffix));
3011
3012         fd = mkstemp(fname);
3013         if (fd < 0)
3014                 return tmpfile();
3015
3016 #ifndef G_OS_WIN32
3017         claws_unlink(fname);
3018         
3019         /* verify that we can write in the file after unlinking */
3020         if (write(fd, buf, 1) < 0) {
3021                 close(fd);
3022                 return tmpfile();
3023         }
3024         
3025 #endif
3026
3027         fp = fdopen(fd, "w+b");
3028         if (!fp)
3029                 close(fd);
3030         else {
3031                 rewind(fp);
3032                 return fp;
3033         }
3034
3035 #endif /* HAVE_MKSTEMP || G_OS_WIN32 */
3036
3037         return tmpfile();
3038 }
3039
3040 FILE *get_tmpfile_in_dir(const gchar *dir, gchar **filename)
3041 {
3042         int fd;
3043 #ifdef G_OS_WIN32
3044         char *template = g_strdup_printf ("%s%cclaws.XXXXXX",
3045                                           dir, G_DIR_SEPARATOR);
3046         fd = mkstemp_name(template, filename);
3047         g_free(template);
3048 #else
3049         *filename = g_strdup_printf("%s%cclaws.XXXXXX", dir, G_DIR_SEPARATOR);
3050         fd = mkstemp(*filename);
3051 #endif
3052         return fdopen(fd, "w+");
3053 }
3054
3055 FILE *str_open_as_stream(const gchar *str)
3056 {
3057         FILE *fp;
3058         size_t len;
3059
3060         cm_return_val_if_fail(str != NULL, NULL);
3061
3062         fp = my_tmpfile();
3063         if (!fp) {
3064                 FILE_OP_ERROR("str_open_as_stream", "my_tmpfile");
3065                 return NULL;
3066         }
3067
3068         len = strlen(str);
3069         if (len == 0) return fp;
3070
3071         if (fwrite(str, 1, len, fp) != len) {
3072                 FILE_OP_ERROR("str_open_as_stream", "fwrite");
3073                 fclose(fp);
3074                 return NULL;
3075         }
3076
3077         rewind(fp);
3078         return fp;
3079 }
3080
3081 gint str_write_to_file(const gchar *str, const gchar *file)
3082 {
3083         FILE *fp;
3084         size_t len;
3085
3086         cm_return_val_if_fail(str != NULL, -1);
3087         cm_return_val_if_fail(file != NULL, -1);
3088
3089         if ((fp = g_fopen(file, "wb")) == NULL) {
3090                 FILE_OP_ERROR(file, "g_fopen");
3091                 return -1;
3092         }
3093
3094         len = strlen(str);
3095         if (len == 0) {
3096                 fclose(fp);
3097                 return 0;
3098         }
3099
3100         if (fwrite(str, 1, len, fp) != len) {
3101                 FILE_OP_ERROR(file, "fwrite");
3102                 fclose(fp);
3103                 claws_unlink(file);
3104                 return -1;
3105         }
3106
3107         if (fclose(fp) == EOF) {
3108                 FILE_OP_ERROR(file, "fclose");
3109                 claws_unlink(file);
3110                 return -1;
3111         }
3112
3113         return 0;
3114 }
3115
3116 static gchar *file_read_stream_to_str_full(FILE *fp, gboolean recode)
3117 {
3118         GByteArray *array;
3119         guchar buf[BUFSIZ];
3120         gint n_read;
3121         gchar *str;
3122
3123         cm_return_val_if_fail(fp != NULL, NULL);
3124
3125         array = g_byte_array_new();
3126
3127         while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
3128                 if (n_read < sizeof(buf) && ferror(fp))
3129                         break;
3130                 g_byte_array_append(array, buf, n_read);
3131         }
3132
3133         if (ferror(fp)) {
3134                 FILE_OP_ERROR("file stream", "fread");
3135                 g_byte_array_free(array, TRUE);
3136                 return NULL;
3137         }
3138
3139         buf[0] = '\0';
3140         g_byte_array_append(array, buf, 1);
3141         str = (gchar *)array->data;
3142         g_byte_array_free(array, FALSE);
3143
3144         if (recode && !g_utf8_validate(str, -1, NULL)) {
3145                 const gchar *src_codeset, *dest_codeset;
3146                 gchar *tmp = NULL;
3147                 src_codeset = conv_get_locale_charset_str();
3148                 dest_codeset = CS_UTF_8;
3149                 tmp = conv_codeset_strdup(str, src_codeset, dest_codeset);
3150                 g_free(str);
3151                 str = tmp;
3152         }
3153
3154         return str;
3155 }
3156
3157 static gchar *file_read_to_str_full(const gchar *file, gboolean recode)
3158 {
3159         FILE *fp;
3160         gchar *str;
3161         struct stat s;
3162 #ifndef G_OS_WIN32
3163         gint fd, err;
3164         struct timeval timeout = {1, 0};
3165         fd_set fds;
3166         int fflags = 0;
3167 #endif
3168
3169         cm_return_val_if_fail(file != NULL, NULL);
3170
3171         if (g_stat(file, &s) != 0) {
3172                 FILE_OP_ERROR(file, "stat");
3173                 return NULL;
3174         }
3175         if (S_ISDIR(s.st_mode)) {
3176                 g_warning("%s: is a directory\n", file);
3177                 return NULL;
3178         }
3179
3180 #ifdef G_OS_WIN32
3181         fp = g_fopen (file, "rb");
3182         if (fp == NULL) {
3183                 FILE_OP_ERROR(file, "open");
3184                 return NULL;
3185         }
3186 #else     
3187         /* test whether the file is readable without blocking */
3188         fd = g_open(file, O_RDONLY | O_NONBLOCK, 0);
3189         if (fd == -1) {
3190                 FILE_OP_ERROR(file, "open");
3191                 return NULL;
3192         }
3193
3194         FD_ZERO(&fds);
3195         FD_SET(fd, &fds);
3196
3197         /* allow for one second */
3198         err = select(fd+1, &fds, NULL, NULL, &timeout);
3199         if (err <= 0 || !FD_ISSET(fd, &fds)) {
3200                 if (err < 0) {
3201                         FILE_OP_ERROR(file, "select");
3202                 } else {
3203                         g_warning("%s: doesn't seem readable\n", file);
3204                 }
3205                 close(fd);
3206                 return NULL;
3207         }
3208         
3209         /* Now clear O_NONBLOCK */
3210         if ((fflags = fcntl(fd, F_GETFL)) < 0) {
3211                 FILE_OP_ERROR(file, "fcntl (F_GETFL)");
3212                 close(fd);
3213                 return NULL;
3214         }
3215         if (fcntl(fd, F_SETFL, (fflags & ~O_NONBLOCK)) < 0) {
3216                 FILE_OP_ERROR(file, "fcntl (F_SETFL)");
3217                 close(fd);
3218                 return NULL;
3219         }
3220         
3221         /* get the FILE pointer */
3222         fp = fdopen(fd, "rb");
3223
3224         if (fp == NULL) {
3225                 FILE_OP_ERROR(file, "fdopen");
3226                 close(fd); /* if fp isn't NULL, we'll use fclose instead! */
3227                 return NULL;
3228         }
3229 #endif
3230
3231         str = file_read_stream_to_str_full(fp, recode);
3232
3233         fclose(fp);
3234
3235         return str;
3236 }
3237
3238 gchar *file_read_to_str(const gchar *file)
3239 {
3240         return file_read_to_str_full(file, TRUE);
3241 }
3242 gchar *file_read_stream_to_str(FILE *fp)
3243 {
3244         return file_read_stream_to_str_full(fp, TRUE);
3245 }
3246
3247 gchar *file_read_to_str_no_recode(const gchar *file)
3248 {
3249         return file_read_to_str_full(file, FALSE);
3250 }
3251 gchar *file_read_stream_to_str_no_recode(FILE *fp)
3252 {
3253         return file_read_stream_to_str_full(fp, FALSE);
3254 }
3255
3256 char *fgets_crlf(char *buf, int size, FILE *stream)
3257 {
3258         gboolean is_cr = FALSE;
3259         gboolean last_was_cr = FALSE;
3260         int c = 0;
3261         char *cs;
3262
3263         cs = buf;
3264         while (--size > 0 && (c = getc(stream)) != EOF)
3265         {
3266                 *cs++ = c;
3267                 is_cr = (c == '\r');
3268                 if (c == '\n') {
3269                         break;
3270                 }
3271                 if (last_was_cr) {
3272                         *(--cs) = '\n';
3273                         cs++;
3274                         ungetc(c, stream);
3275                         break;
3276                 }
3277                 last_was_cr = is_cr;
3278         }
3279         if (c == EOF && cs == buf)
3280                 return NULL;
3281
3282         *cs = '\0';
3283
3284         return buf;     
3285 }
3286
3287 static gint execute_async(gchar *const argv[])
3288 {
3289         cm_return_val_if_fail(argv != NULL && argv[0] != NULL, -1);
3290
3291         if (g_spawn_async(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH,
3292                           NULL, NULL, NULL, FALSE) == FALSE) {
3293                 g_warning("Couldn't execute command: %s\n", argv[0]);
3294                 return -1;
3295         }
3296
3297         return 0;
3298 }
3299
3300 static gint execute_sync(gchar *const argv[])
3301 {
3302         gint status;
3303
3304         cm_return_val_if_fail(argv != NULL && argv[0] != NULL, -1);
3305
3306 #ifdef G_OS_UNIX
3307         if (g_spawn_sync(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH,
3308                          NULL, NULL, NULL, NULL, &status, NULL) == FALSE) {
3309                 g_warning("Couldn't execute command: %s\n", argv[0]);
3310                 return -1;
3311         }
3312
3313         if (WIFEXITED(status))
3314                 return WEXITSTATUS(status);
3315         else
3316                 return -1;
3317 #else
3318         if (g_spawn_sync(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH| 
3319                          G_SPAWN_CHILD_INHERITS_STDIN|G_SPAWN_LEAVE_DESCRIPTORS_OPEN,
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         return status;
3326 #endif
3327 }
3328
3329 gint execute_command_line(const gchar *cmdline, gboolean async)
3330 {
3331         gchar **argv;
3332         gint ret;
3333
3334         debug_print("execute_command_line(): executing: %s\n", cmdline?cmdline:"(null)");
3335
3336         argv = strsplit_with_quote(cmdline, " ", 0);
3337
3338         if (async)
3339                 ret = execute_async(argv);
3340         else
3341                 ret = execute_sync(argv);
3342
3343         g_strfreev(argv);
3344
3345         return ret;
3346 }
3347
3348 gchar *get_command_output(const gchar *cmdline)
3349 {
3350         gchar *child_stdout;
3351         gint status;
3352
3353         cm_return_val_if_fail(cmdline != NULL, NULL);
3354
3355         debug_print("get_command_output(): executing: %s\n", cmdline);
3356
3357         if (g_spawn_command_line_sync(cmdline, &child_stdout, NULL, &status,
3358                                       NULL) == FALSE) {
3359                 g_warning("Couldn't execute command: %s\n", cmdline);
3360                 return NULL;
3361         }
3362
3363         return child_stdout;
3364 }
3365 #ifndef MAEMO
3366 static gint is_unchanged_uri_char(char c)
3367 {
3368         switch (c) {
3369                 case '(':
3370                 case ')':
3371                         return 0;
3372                 default:
3373                         return 1;
3374         }
3375 }
3376
3377 static void encode_uri(gchar *encoded_uri, gint bufsize, const gchar *uri)
3378 {
3379         int i;
3380         int k;
3381
3382         k = 0;
3383         for(i = 0; i < strlen(uri) ; i++) {
3384                 if (is_unchanged_uri_char(uri[i])) {
3385                         if (k + 2 >= bufsize)
3386                                 break;
3387                         encoded_uri[k++] = uri[i];
3388                 }
3389                 else {
3390                         char * hexa = "0123456789ABCDEF";
3391
3392                         if (k + 4 >= bufsize)
3393                                 break;
3394                         encoded_uri[k++] = '%';
3395                         encoded_uri[k++] = hexa[uri[i] / 16];
3396                         encoded_uri[k++] = hexa[uri[i] % 16];
3397                 }
3398         }
3399         encoded_uri[k] = 0;
3400 }
3401 #endif
3402 gint open_uri(const gchar *uri, const gchar *cmdline)
3403 {
3404 #ifndef MAEMO
3405 #ifndef G_OS_WIN32
3406         gchar buf[BUFFSIZE];
3407         gchar *p;
3408         gchar encoded_uri[BUFFSIZE];
3409         cm_return_val_if_fail(uri != NULL, -1);
3410
3411         /* an option to choose whether to use encode_uri or not ? */
3412         encode_uri(encoded_uri, BUFFSIZE, uri);
3413
3414         if (cmdline &&
3415             (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
3416             !strchr(p + 2, '%'))
3417                 g_snprintf(buf, sizeof(buf), cmdline, encoded_uri);
3418         else {
3419                 if (cmdline)
3420                         g_warning("Open URI command-line is invalid "
3421                                   "(there must be only one '%%s'): %s",
3422                                   cmdline);
3423                 g_snprintf(buf, sizeof(buf), DEFAULT_BROWSER_CMD, encoded_uri);
3424         }
3425
3426         execute_command_line(buf, TRUE);
3427 #else
3428         ShellExecute(NULL, "open", uri, NULL, NULL, SW_SHOW);
3429 #endif
3430 #else
3431         extern osso_context_t *get_osso_context(void);
3432         osso_rpc_run_with_defaults(get_osso_context(), "osso_browser",
3433                                         OSSO_BROWSER_OPEN_NEW_WINDOW_REQ, NULL, 
3434                                         DBUS_TYPE_STRING, uri, DBUS_TYPE_INVALID);
3435 #endif
3436         return 0;
3437 }
3438
3439 gint open_txt_editor(const gchar *filepath, const gchar *cmdline)
3440 {
3441         gchar buf[BUFFSIZE];
3442         gchar *p;
3443
3444         cm_return_val_if_fail(filepath != NULL, -1);
3445
3446         if (cmdline &&
3447             (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
3448             !strchr(p + 2, '%'))
3449                 g_snprintf(buf, sizeof(buf), cmdline, filepath);
3450         else {
3451                 if (cmdline)
3452                         g_warning("Open Text Editor command-line is invalid "
3453                                   "(there must be only one '%%s'): %s",
3454                                   cmdline);
3455                 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, filepath);
3456         }
3457
3458         execute_command_line(buf, TRUE);
3459
3460         return 0;
3461 }
3462
3463 time_t remote_tzoffset_sec(const gchar *zone)
3464 {
3465         static gchar ustzstr[] = "PSTPDTMSTMDTCSTCDTESTEDT";
3466         gchar zone3[4];
3467         gchar *p;
3468         gchar c;
3469         gint iustz;
3470         gint offset;
3471         time_t remoteoffset;
3472
3473         strncpy(zone3, zone, 3);
3474         zone3[3] = '\0';
3475         remoteoffset = 0;
3476
3477         if (sscanf(zone, "%c%d", &c, &offset) == 2 &&
3478             (c == '+' || c == '-')) {
3479                 remoteoffset = ((offset / 100) * 60 + (offset % 100)) * 60;
3480                 if (c == '-')
3481                         remoteoffset = -remoteoffset;
3482         } else if (!strncmp(zone, "UT" , 2) ||
3483                    !strncmp(zone, "GMT", 2)) {
3484                 remoteoffset = 0;
3485         } else if (strlen(zone3) == 3) {
3486                 for (p = ustzstr; *p != '\0'; p += 3) {
3487                         if (!g_ascii_strncasecmp(p, zone3, 3)) {
3488                                 iustz = ((gint)(p - ustzstr) / 3 + 1) / 2 - 8;
3489                                 remoteoffset = iustz * 3600;
3490                                 break;
3491                         }
3492                 }
3493                 if (*p == '\0')
3494                         return -1;
3495         } else if (strlen(zone3) == 1) {
3496                 switch (zone[0]) {
3497                 case 'Z': remoteoffset =   0; break;
3498                 case 'A': remoteoffset =  -1; break;
3499                 case 'B': remoteoffset =  -2; break;
3500                 case 'C': remoteoffset =  -3; break;
3501                 case 'D': remoteoffset =  -4; break;
3502                 case 'E': remoteoffset =  -5; break;
3503                 case 'F': remoteoffset =  -6; break;
3504                 case 'G': remoteoffset =  -7; break;
3505                 case 'H': remoteoffset =  -8; break;
3506                 case 'I': remoteoffset =  -9; break;
3507                 case 'K': remoteoffset = -10; break; /* J is not used */
3508                 case 'L': remoteoffset = -11; break;
3509                 case 'M': remoteoffset = -12; break;
3510                 case 'N': remoteoffset =   1; break;
3511                 case 'O': remoteoffset =   2; break;
3512                 case 'P': remoteoffset =   3; break;
3513                 case 'Q': remoteoffset =   4; break;
3514                 case 'R': remoteoffset =   5; break;
3515                 case 'S': remoteoffset =   6; break;
3516                 case 'T': remoteoffset =   7; break;
3517                 case 'U': remoteoffset =   8; break;
3518                 case 'V': remoteoffset =   9; break;
3519                 case 'W': remoteoffset =  10; break;
3520                 case 'X': remoteoffset =  11; break;
3521                 case 'Y': remoteoffset =  12; break;
3522                 default:  remoteoffset =   0; break;
3523                 }
3524                 remoteoffset = remoteoffset * 3600;
3525         } else
3526                 return -1;
3527
3528         return remoteoffset;
3529 }
3530
3531 time_t tzoffset_sec(time_t *now)
3532 {
3533         struct tm gmt, *lt;
3534         gint off;
3535         struct tm buf1, buf2;
3536 #ifdef G_OS_WIN32
3537         if (now && *now < 0)
3538                 return 0;
3539 #endif  
3540         gmt = *gmtime_r(now, &buf1);
3541         lt = localtime_r(now, &buf2);
3542
3543         off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
3544
3545         if (lt->tm_year < gmt.tm_year)
3546                 off -= 24 * 60;
3547         else if (lt->tm_year > gmt.tm_year)
3548                 off += 24 * 60;
3549         else if (lt->tm_yday < gmt.tm_yday)
3550                 off -= 24 * 60;
3551         else if (lt->tm_yday > gmt.tm_yday)
3552                 off += 24 * 60;
3553
3554         if (off >= 24 * 60)             /* should be impossible */
3555                 off = 23 * 60 + 59;     /* if not, insert silly value */
3556         if (off <= -24 * 60)
3557                 off = -(23 * 60 + 59);
3558
3559         return off * 60;
3560 }
3561
3562 /* calculate timezone offset */
3563 gchar *tzoffset(time_t *now)
3564 {
3565         static gchar offset_string[6];
3566         struct tm gmt, *lt;
3567         gint off;
3568         gchar sign = '+';
3569         struct tm buf1, buf2;
3570 #ifdef G_OS_WIN32
3571         if (now && *now < 0)
3572                 return 0;
3573 #endif
3574         gmt = *gmtime_r(now, &buf1);
3575         lt = localtime_r(now, &buf2);
3576
3577         off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
3578
3579         if (lt->tm_year < gmt.tm_year)
3580                 off -= 24 * 60;
3581         else if (lt->tm_year > gmt.tm_year)
3582                 off += 24 * 60;
3583         else if (lt->tm_yday < gmt.tm_yday)
3584                 off -= 24 * 60;
3585         else if (lt->tm_yday > gmt.tm_yday)
3586                 off += 24 * 60;
3587
3588         if (off < 0) {
3589                 sign = '-';
3590                 off = -off;
3591         }
3592
3593         if (off >= 24 * 60)             /* should be impossible */
3594                 off = 23 * 60 + 59;     /* if not, insert silly value */
3595
3596         sprintf(offset_string, "%c%02d%02d", sign, off / 60, off % 60);
3597
3598         return offset_string;
3599 }
3600
3601 void get_rfc822_date(gchar *buf, gint len)
3602 {
3603         struct tm *lt;
3604         time_t t;
3605         gchar day[4], mon[4];
3606         gint dd, hh, mm, ss, yyyy;
3607         struct tm buf1;
3608         gchar buf2[BUFFSIZE];
3609
3610         t = time(NULL);
3611         lt = localtime_r(&t, &buf1);
3612
3613         sscanf(asctime_r(lt, buf2), "%3s %3s %d %d:%d:%d %d\n",
3614                day, mon, &dd, &hh, &mm, &ss, &yyyy);
3615
3616         g_snprintf(buf, len, "%s, %d %s %d %02d:%02d:%02d %s",
3617                    day, dd, mon, yyyy, hh, mm, ss, tzoffset(&t));
3618 }
3619
3620 void debug_set_mode(gboolean mode)
3621 {
3622         debug_mode = mode;
3623 }
3624
3625 gboolean debug_get_mode(void)
3626 {
3627         return debug_mode;
3628 }
3629
3630 void debug_print_real(const gchar *format, ...)
3631 {
3632         va_list args;
3633         gchar buf[BUFFSIZE];
3634
3635         if (!debug_mode) return;
3636
3637         va_start(args, format);
3638         g_vsnprintf(buf, sizeof(buf), format, args);
3639         va_end(args);
3640
3641         g_print("%s", buf);
3642 }
3643
3644
3645 const char * debug_srcname(const char *file)
3646 {
3647         const char *s = strrchr (file, '/');
3648         return s? s+1:file;
3649 }
3650
3651
3652 void * subject_table_lookup(GHashTable *subject_table, gchar * subject)
3653 {
3654         if (subject == NULL)
3655                 subject = "";
3656         else
3657                 subject += subject_get_prefix_length(subject);
3658
3659         return g_hash_table_lookup(subject_table, subject);
3660 }
3661
3662 void subject_table_insert(GHashTable *subject_table, gchar * subject,
3663                           void * data)
3664 {
3665         if (subject == NULL || *subject == 0)
3666                 return;
3667         subject += subject_get_prefix_length(subject);
3668         g_hash_table_insert(subject_table, subject, data);
3669 }
3670
3671 void subject_table_remove(GHashTable *subject_table, gchar * subject)
3672 {
3673         if (subject == NULL)
3674                 return;
3675
3676         subject += subject_get_prefix_length(subject);
3677         g_hash_table_remove(subject_table, subject);
3678 }
3679
3680 #ifndef G_OS_WIN32
3681 static regex_t u_regex;
3682 static gboolean u_init_;
3683 #endif
3684
3685 void utils_free_regex(void)
3686 {
3687 #ifndef G_OS_WIN32
3688         if (u_init_) {
3689                 regfree(&u_regex);
3690                 u_init_ = FALSE;
3691         }
3692 #endif
3693 }
3694
3695 /*!
3696  *\brief        Check if a string is prefixed with known (combinations)
3697  *              of prefixes. The function assumes that each prefix
3698  *              is terminated by zero or exactly _one_ space.
3699  *
3700  *\param        str String to check for a prefixes
3701  *
3702  *\return       int Number of chars in the prefix that should be skipped
3703  *              for a "clean" subject line. If no prefix was found, 0
3704  *              is returned.
3705  */
3706 int subject_get_prefix_length(const gchar *subject)
3707 {
3708 #ifndef G_OS_WIN32
3709         /*!< Array with allowable reply prefixes regexps. */
3710         static const gchar * const prefixes[] = {
3711                 "Re\\:",                        /* "Re:" */
3712                 "Re\\[[1-9][0-9]*\\]\\:",       /* "Re[XXX]:" (non-conforming news mail clients) */
3713                 "Antw\\:",                      /* "Antw:" (Dutch / German Outlook) */
3714                 "Aw\\:",                        /* "Aw:"   (German) */
3715                 "Antwort\\:",                   /* "Antwort:" (German Lotus Notes) */
3716                 "Res\\:",                       /* "Res:" (Spanish/Brazilian Outlook) */
3717                 "Fw\\:",                        /* "Fw:" Forward */
3718                 "Fwd\\:",                       /* "Fwd:" Forward */
3719                 "Enc\\:",                       /* "Enc:" Forward (Brazilian Outlook) */
3720                 "Odp\\:",                       /* "Odp:" Re (Polish Outlook) */
3721                 "Rif\\:",                       /* "Rif:" (Italian Outlook) */
3722                 "Sv\\:",                        /* "Sv" (Norwegian) */
3723                 "Vs\\:",                        /* "Vs" (Norwegian) */
3724                 "Ad\\:",                        /* "Ad" (Norwegian) */
3725                 "\347\255\224\345\244\215\\:",  /* "Re" (Chinese, UTF-8) */
3726                 "R\303\251f\\. \\:",            /* "Réf. :" (French Lotus Notes) */
3727                 "Re \\:",                       /* "Re :" (French Yahoo Mail) */
3728                 /* add more */
3729         };
3730         const int PREFIXES = sizeof prefixes / sizeof prefixes[0];
3731         int n;
3732         regmatch_t pos;
3733
3734         if (!subject) return 0;
3735         if (!*subject) return 0;
3736
3737         if (!u_init_) {
3738                 GString *s = g_string_new("");
3739
3740                 for (n = 0; n < PREFIXES; n++)
3741                         /* Terminate each prefix regexpression by a
3742                          * "\ ?" (zero or ONE space), and OR them */
3743                         g_string_append_printf(s, "(%s\\ ?)%s",
3744                                           prefixes[n],
3745                                           n < PREFIXES - 1 ?
3746                                           "|" : "");
3747
3748                 g_string_prepend(s, "(");
3749                 g_string_append(s, ")+");       /* match at least once */
3750                 g_string_prepend(s, "^\\ *");   /* from beginning of line */
3751
3752
3753                 /* We now have something like "^\ *((PREFIX1\ ?)|(PREFIX2\ ?))+"
3754                  * TODO: Should this be       "^\ *(((PREFIX1)|(PREFIX2))\ ?)+" ??? */
3755                 if (regcomp(&u_regex, s->str, REG_EXTENDED | REG_ICASE)) {
3756                         debug_print("Error compiling regexp %s\n", s->str);
3757                         g_string_free(s, TRUE);
3758                         return 0;
3759                 } else {
3760                         u_init_ = TRUE;
3761                         g_string_free(s, TRUE);
3762                 }
3763         }
3764
3765         if (!regexec(&u_regex, subject, 1, &pos, 0) && pos.rm_so != -1)
3766                 return pos.rm_eo;
3767         else
3768                 return 0;
3769 #else
3770         /*!< Array with allowable reply prefixes regexps. */
3771         static const gchar * const prefixes[] = {
3772                 "re:",                  /* "Re:" */
3773                 "antw:",                        /* "Antw:" (Dutch / German Outlook) */
3774                 "aw:",                  /* "Aw:"   (German) */
3775                 "antwort:",                     /* "Antwort:" (German Lotus Notes) */
3776                 "res:",                 /* "Res:" (Spanish/Brazilian Outlook) */
3777                 "fw:",                  /* "Fw:" Forward */
3778                 "fwd:",                 /* "Fwd:" Forward */
3779                 "enc:",                 /* "Enc:" Forward (Brazilian Outlook) */
3780                 "odp:",                 /* "Odp:" Re (Polish Outlook) */
3781                 "rif:",                 /* "Rif:" (Italian Outlook) */
3782                 "sv:",                  /* "Sv" (Norwegian) */
3783                 "vs:",                  /* "Vs" (Norwegian) */
3784                 "ad:",                  /* "Ad" (Norwegian) */
3785                 "R\303\251f. :",        /* "Réf. :" (French Lotus Notes) */
3786                 "Re :",                 /* "Re :" (French Yahoo Mail) */
3787                 /* add more */
3788         };
3789         const int PREFIXES = sizeof prefixes / sizeof prefixes[0];
3790         int n;
3791
3792         if (!subject) return 0;
3793         if (!*subject) return 0;
3794
3795         for (n = 0; n < PREFIXES; n++) {
3796                 int len = strlen(prefixes[n]);
3797                 if (!strncasecmp(subject, prefixes[n], len)) {
3798                         if (subject[len] == ' ')
3799                                 return len+1;
3800                         else
3801                                 return len;
3802                 }
3803         }
3804         return 0;
3805 #endif
3806 }
3807 static guint g_stricase_hash(gconstpointer gptr)
3808 {
3809         guint hash_result = 0;
3810         const char *str;
3811
3812         for (str = gptr; str && *str; str++) {
3813                 hash_result += toupper(*str);
3814         }
3815
3816         return hash_result;
3817 }
3818
3819 static gint g_stricase_equal(gconstpointer gptr1, gconstpointer gptr2)
3820 {
3821         const char *str1 = gptr1;
3822         const char *str2 = gptr2;
3823
3824         return !strcasecmp(str1, str2);
3825 }
3826
3827 gint g_int_compare(gconstpointer a, gconstpointer b)
3828 {
3829         return GPOINTER_TO_INT(a) - GPOINTER_TO_INT(b);
3830 }
3831
3832 gchar *generate_msgid(gchar *buf, gint len, gchar *user_addr)
3833 {
3834         struct tm *lt;
3835         time_t t;
3836         gchar *addr;
3837         struct tm buft;
3838
3839         t = time(NULL);
3840         lt = localtime_r(&t, &buft);
3841
3842         if (user_addr != NULL)
3843               addr = g_strdup_printf(".%s", user_addr);
3844         else if (strlen(buf) != 0)
3845               addr = g_strdup_printf("@%s", buf);
3846         else
3847               addr = g_strdup_printf("@%s", get_domain_name());
3848
3849         /* Replace all @ but the last one in addr, with underscores.
3850          * RFC 2822 States that msg-id syntax only allows one @.
3851          */
3852         while (strchr(addr, '@') != NULL && strchr(addr, '@') != strrchr(addr, '@'))
3853                 *(strchr(addr, '@')) = '_';
3854
3855         g_snprintf(buf, len, "%04d%02d%02d%02d%02d%02d.%08x%s",
3856                    lt->tm_year + 1900, lt->tm_mon + 1,
3857                    lt->tm_mday, lt->tm_hour,
3858                    lt->tm_min, lt->tm_sec,
3859                    (guint) rand(), addr);
3860
3861         g_free(addr);
3862         return buf;
3863 }
3864
3865 /*
3866    quote_cmd_argument()
3867
3868    return a quoted string safely usable in argument of a command.
3869
3870    code is extracted and adapted from etPan! project -- DINH V. Hoà.
3871 */
3872
3873 gint quote_cmd_argument(gchar * result, guint size,
3874                         const gchar * path)
3875 {
3876         const gchar * p;
3877         gchar * result_p;
3878         guint remaining;
3879
3880         result_p = result;
3881         remaining = size;
3882
3883         for(p = path ; * p != '\0' ; p ++) {
3884
3885                 if (isalnum((guchar)*p) || (* p == '/')) {
3886                         if (remaining > 0) {
3887                                 * result_p = * p;
3888                                 result_p ++;
3889                                 remaining --;
3890                         }
3891                         else {
3892                                 result[size - 1] = '\0';
3893                                 return -1;
3894                         }
3895                 }
3896                 else {
3897                         if (remaining >= 2) {
3898                                 * result_p = '\\';
3899                                 result_p ++;
3900                                 * result_p = * p;
3901                                 result_p ++;
3902                                 remaining -= 2;
3903                         }
3904                         else {
3905                                 result[size - 1] = '\0';
3906                                 return -1;
3907                         }
3908                 }
3909         }
3910         if (remaining > 0) {
3911                 * result_p = '\0';
3912         }
3913         else {
3914                 result[size - 1] = '\0';
3915                 return -1;
3916         }
3917
3918         return 0;
3919 }
3920
3921 typedef struct
3922 {
3923         GNode           *parent;
3924         GNodeMapFunc     func;
3925         gpointer         data;
3926 } GNodeMapData;
3927
3928 static void g_node_map_recursive(GNode *node, gpointer data)
3929 {
3930         GNodeMapData *mapdata = (GNodeMapData *) data;
3931         GNode *newnode;
3932         GNodeMapData newmapdata;
3933         gpointer newdata;
3934
3935         newdata = mapdata->func(node->data, mapdata->data);
3936         if (newdata != NULL) {
3937                 newnode = g_node_new(newdata);
3938                 g_node_append(mapdata->parent, newnode);
3939
3940                 newmapdata.parent = newnode;
3941                 newmapdata.func = mapdata->func;
3942                 newmapdata.data = mapdata->data;
3943
3944                 g_node_children_foreach(node, G_TRAVERSE_ALL, g_node_map_recursive, &newmapdata);
3945         }
3946 }
3947
3948 GNode *g_node_map(GNode *node, GNodeMapFunc func, gpointer data)
3949 {
3950         GNode *root;
3951         GNodeMapData mapdata;
3952
3953         cm_return_val_if_fail(node != NULL, NULL);
3954         cm_return_val_if_fail(func != NULL, NULL);
3955
3956         root = g_node_new(func(node->data, data));
3957
3958         mapdata.parent = root;
3959         mapdata.func = func;
3960         mapdata.data = data;
3961
3962         g_node_children_foreach(node, G_TRAVERSE_ALL, g_node_map_recursive, &mapdata);
3963
3964         return root;
3965 }
3966
3967 #define HEX_TO_INT(val, hex)                    \
3968 {                                               \
3969         gchar c = hex;                          \
3970                                                 \
3971         if ('0' <= c && c <= '9') {             \
3972                 val = c - '0';                  \
3973         } else if ('a' <= c && c <= 'f') {      \
3974                 val = c - 'a' + 10;             \
3975         } else if ('A' <= c && c <= 'F') {      \
3976                 val = c - 'A' + 10;             \
3977         } else {                                \
3978                 val = -1;                       \
3979         }                                       \
3980 }
3981
3982 gboolean get_hex_value(guchar *out, gchar c1, gchar c2)
3983 {
3984         gint hi, lo;
3985
3986         HEX_TO_INT(hi, c1);
3987         HEX_TO_INT(lo, c2);
3988
3989         if (hi == -1 || lo == -1)
3990                 return FALSE;
3991
3992         *out = (hi << 4) + lo;
3993         return TRUE;
3994 }
3995
3996 #define INT_TO_HEX(hex, val)            \
3997 {                                       \
3998         if ((val) < 10)                 \
3999                 hex = '0' + (val);      \
4000         else                            \
4001                 hex = 'A' + (val) - 10; \
4002 }
4003
4004 void get_hex_str(gchar *out, guchar ch)
4005 {
4006         gchar hex;
4007
4008         INT_TO_HEX(hex, ch >> 4);
4009         *out++ = hex;
4010         INT_TO_HEX(hex, ch & 0x0f);
4011         *out++ = hex;
4012 }
4013
4014 #undef REF_DEBUG
4015 #ifndef REF_DEBUG
4016 #define G_PRINT_REF 1 == 1 ? (void) 0 : (void)
4017 #else
4018 #define G_PRINT_REF g_print
4019 #endif
4020
4021 /*!
4022  *\brief        Register ref counted pointer. It is based on GBoxed, so should
4023  *              work with anything that uses the GType system. The semantics
4024  *              are similar to a C++ auto pointer, with the exception that
4025  *              C doesn't have automatic closure (calling destructors) when
4026  *              exiting a block scope.
4027  *              Use the \ref G_TYPE_AUTO_POINTER macro instead of calling this
4028  *              function directly.
4029  *
4030  *\return       GType A GType type.
4031  */
4032 GType g_auto_pointer_register(void)
4033 {
4034         static GType auto_pointer_type;
4035         if (!auto_pointer_type)
4036                 auto_pointer_type =
4037                         g_boxed_type_register_static
4038                                 ("G_TYPE_AUTO_POINTER",
4039                                  (GBoxedCopyFunc) g_auto_pointer_copy,
4040                                  (GBoxedFreeFunc) g_auto_pointer_free);
4041         return auto_pointer_type;
4042 }
4043
4044 /*!
4045  *\brief        Structure with g_new() allocated pointer guarded by the
4046  *              auto pointer
4047  */
4048 typedef struct AutoPointerRef {
4049         void          (*free) (gpointer);
4050         gpointer        pointer;
4051         glong           cnt;
4052 } AutoPointerRef;
4053
4054 /*!
4055  *\brief        The auto pointer opaque structure that references the
4056  *              pointer guard block.
4057  */
4058 typedef struct AutoPointer {
4059         AutoPointerRef *ref;
4060         gpointer        ptr; /*!< access to protected pointer */
4061 } AutoPointer;
4062
4063 /*!
4064  *\brief        Creates an auto pointer for a g_new()ed pointer. Example:
4065  *
4066  *\code
4067  *
4068  *              ... tell gtk_list_store it should use a G_TYPE_AUTO_POINTER
4069  *              ... when assigning, copying and freeing storage elements
4070  *
4071  *              gtk_list_store_new(N_S_COLUMNS,
4072  *                                 G_TYPE_AUTO_POINTER,
4073  *                                 -1);
4074  *
4075  *
4076  *              Template *precious_data = g_new0(Template, 1);
4077  *              g_pointer protect = g_auto_pointer_new(precious_data);
4078  *
4079  *              gtk_list_store_set(container, &iter,
4080  *                                 S_DATA, protect,
4081  *                                 -1);
4082  *
4083  *              ... the gtk_list_store has copied the pointer and
4084  *              ... incremented its reference count, we should free
4085  *              ... the auto pointer (in C++ a destructor would do
4086  *              ... this for us when leaving block scope)
4087  *
4088  *              g_auto_pointer_free(protect);
4089  *
4090  *              ... gtk_list_store_set() now manages the data. When
4091  *              ... *explicitly* requesting a pointer from the list
4092  *              ... store, don't forget you get a copy that should be
4093  *              ... freed with g_auto_pointer_free() eventually.
4094  *
4095  *\endcode
4096  *
4097  *\param        pointer Pointer to be guarded.
4098  *
4099  *\return       GAuto * Pointer that should be used in containers with
4100  *              GType support.
4101  */
4102 GAuto *g_auto_pointer_new(gpointer p)
4103 {
4104         AutoPointerRef *ref;
4105         AutoPointer    *ptr;
4106
4107         if (p == NULL)
4108                 return NULL;
4109
4110         ref = g_new0(AutoPointerRef, 1);
4111         ptr = g_new0(AutoPointer, 1);
4112
4113         ref->pointer = p;
4114         ref->free = g_free;
4115         ref->cnt = 1;
4116
4117         ptr->ref = ref;
4118         ptr->ptr = p;
4119
4120 #ifdef REF_DEBUG
4121         G_PRINT_REF ("XXXX ALLOC(%lx)\n", p);
4122 #endif
4123         return ptr;
4124 }
4125
4126 /*!
4127  *\brief        Allocate an autopointer using the passed \a free function to
4128  *              free the guarded pointer
4129  */
4130 GAuto *g_auto_pointer_new_with_free(gpointer p, GFreeFunc free_)
4131 {
4132         AutoPointer *aptr;
4133
4134         if (p == NULL)
4135                 return NULL;
4136
4137         aptr = g_auto_pointer_new(p);
4138         aptr->ref->free = free_;
4139         return aptr;
4140 }
4141
4142 gpointer g_auto_pointer_get_ptr(GAuto *auto_ptr)
4143 {
4144         if (auto_ptr == NULL)
4145                 return NULL;
4146         return ((AutoPointer *) auto_ptr)->ptr;
4147 }
4148
4149 /*!
4150  *\brief        Copies an auto pointer by. It's mostly not necessary
4151  *              to call this function directly, unless you copy/assign
4152  *              the guarded pointer.
4153  *
4154  *\param        auto_ptr Auto pointer returned by previous call to
4155  *              g_auto_pointer_new_XXX()
4156  *
4157  *\return       gpointer An auto pointer
4158  */
4159 GAuto *g_auto_pointer_copy(GAuto *auto_ptr)
4160 {
4161         AutoPointer     *ptr;
4162         AutoPointerRef  *ref;
4163         AutoPointer     *newp;
4164
4165         if (auto_ptr == NULL)
4166                 return NULL;
4167
4168         ptr = auto_ptr;
4169         ref = ptr->ref;
4170         newp = g_new0(AutoPointer, 1);
4171
4172         newp->ref = ref;
4173         newp->ptr = ref->pointer;
4174         ++(ref->cnt);
4175
4176 #ifdef REF_DEBUG
4177         G_PRINT_REF ("XXXX COPY(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4178 #endif
4179         return newp;
4180 }
4181
4182 /*!
4183  *\brief        Free an auto pointer
4184  */
4185 void g_auto_pointer_free(GAuto *auto_ptr)
4186 {
4187         AutoPointer     *ptr;
4188         AutoPointerRef  *ref;
4189
4190         if (auto_ptr == NULL)
4191                 return;
4192
4193         ptr = auto_ptr;
4194         ref = ptr->ref;
4195
4196         if (--(ref->cnt) == 0) {
4197 #ifdef REF_DEBUG
4198                 G_PRINT_REF ("XXXX FREE(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4199 #endif
4200                 ref->free(ref->pointer);
4201                 g_free(ref);
4202         }
4203 #ifdef REF_DEBUG
4204         else
4205                 G_PRINT_REF ("XXXX DEREF(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4206 #endif
4207         g_free(ptr);
4208 }
4209
4210 void replace_returns(gchar *str)
4211 {
4212         if (!str)
4213                 return;
4214
4215         while (strstr(str, "\n")) {
4216                 *strstr(str, "\n") = ' ';
4217         }
4218         while (strstr(str, "\r")) {
4219                 *strstr(str, "\r") = ' ';
4220         }
4221 }
4222
4223 /* get_uri_part() - retrieves a URI starting from scanpos.
4224                     Returns TRUE if succesful */
4225 gboolean get_uri_part(const gchar *start, const gchar *scanpos,
4226                              const gchar **bp, const gchar **ep, gboolean hdr)
4227 {
4228         const gchar *ep_;
4229         gint parenthese_cnt = 0;
4230
4231         cm_return_val_if_fail(start != NULL, FALSE);
4232         cm_return_val_if_fail(scanpos != NULL, FALSE);
4233         cm_return_val_if_fail(bp != NULL, FALSE);
4234         cm_return_val_if_fail(ep != NULL, FALSE);
4235
4236         *bp = scanpos;
4237
4238         /* find end point of URI */
4239         for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
4240                 if (!g_ascii_isgraph(*(const guchar *)ep_) ||
4241                     !IS_ASCII(*(const guchar *)ep_) ||
4242                     strchr("[]{}<>\"", *ep_)) {
4243                         break;
4244                 } else if (strchr("(", *ep_)) {
4245                         parenthese_cnt++;
4246                 } else if (strchr(")", *ep_)) {
4247                         if (parenthese_cnt > 0)
4248                                 parenthese_cnt--;
4249                         else
4250                                 break;
4251                 }
4252         }
4253
4254         /* no punctuation at end of string */
4255
4256         /* FIXME: this stripping of trailing punctuations may bite with other URIs.
4257          * should pass some URI type to this function and decide on that whether
4258          * to perform punctuation stripping */
4259
4260 #define IS_REAL_PUNCT(ch)       (g_ascii_ispunct(ch) && !strchr("/?=-_)", ch))
4261
4262         for (; ep_ - 1 > scanpos + 1 &&
4263                IS_REAL_PUNCT(*(ep_ - 1));
4264              ep_--)
4265                 ;
4266
4267 #undef IS_REAL_PUNCT
4268
4269         *ep = ep_;
4270
4271         return TRUE;
4272 }
4273
4274 gchar *make_uri_string(const gchar *bp, const gchar *ep)
4275 {
4276         while (bp && *bp && g_ascii_isspace(*bp))
4277                 bp++;
4278         return g_strndup(bp, ep - bp);
4279 }
4280
4281 /* valid mail address characters */
4282 #define IS_RFC822_CHAR(ch) \
4283         (IS_ASCII(ch) && \
4284          (ch) > 32   && \
4285          (ch) != 127 && \
4286          !g_ascii_isspace(ch) && \
4287          !strchr("(),;<>\"", (ch)))
4288
4289 /* alphabet and number within 7bit ASCII */
4290 #define IS_ASCII_ALNUM(ch)      (IS_ASCII(ch) && g_ascii_isalnum(ch))
4291 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
4292
4293 static GHashTable *create_domain_tab(void)
4294 {
4295         static const gchar *toplvl_domains [] = {
4296             "museum", "aero",
4297             "arpa", "coop", "info", "name", "biz", "com", "edu", "gov",
4298             "int", "mil", "net", "org", "ac", "ad", "ae", "af", "ag",
4299             "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au",
4300             "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi",
4301             "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
4302             "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl",
4303             "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz", "de",
4304             "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er",
4305             "es", "et", "eu", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd",
4306             "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
4307             "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
4308             "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir",
4309             "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
4310             "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc",
4311             "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
4312             "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq",
4313             "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na",
4314             "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu",
4315             "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm",
4316             "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru",
4317             "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj",
4318             "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
4319             "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to",
4320             "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um",
4321             "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu",
4322             "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw"
4323         };
4324         gint n;
4325         GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
4326
4327         cm_return_val_if_fail(htab, NULL);
4328         for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++)
4329                 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
4330         return htab;
4331 }
4332
4333 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
4334 {
4335         const gint MAX_LVL_DOM_NAME_LEN = 6;
4336         gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
4337         const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
4338         register gchar *p;
4339
4340         if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
4341                 return FALSE;
4342
4343         for (p = buf; p < m &&  first < last; *p++ = *first++)
4344                 ;
4345         *p = 0;
4346
4347         return g_hash_table_lookup(tab, buf) != NULL;
4348 }
4349
4350 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
4351 gboolean get_email_part(const gchar *start, const gchar *scanpos,
4352                                const gchar **bp, const gchar **ep, gboolean hdr)
4353 {
4354         /* more complex than the uri part because we need to scan back and forward starting from
4355          * the scan position. */
4356         gboolean result = FALSE;
4357         const gchar *bp_ = NULL;
4358         const gchar *ep_ = NULL;
4359         static GHashTable *dom_tab;
4360         const gchar *last_dot = NULL;
4361         const gchar *prelast_dot = NULL;
4362         const gchar *last_tld_char = NULL;
4363
4364         /* the informative part of the email address (describing the name
4365          * of the email address owner) may contain quoted parts. the
4366          * closure stack stores the last encountered quotes. */
4367         gchar closure_stack[128];
4368         gchar *ptr = closure_stack;
4369
4370         cm_return_val_if_fail(start != NULL, FALSE);
4371         cm_return_val_if_fail(scanpos != NULL, FALSE);
4372         cm_return_val_if_fail(bp != NULL, FALSE);
4373         cm_return_val_if_fail(ep != NULL, FALSE);
4374
4375         if (hdr) {
4376                 const gchar *start_quote = NULL;
4377                 const gchar *end_quote = NULL;
4378 search_again:
4379                 /* go to the real start */
4380                 if (start[0] == ',')
4381                         start++;
4382                 if (start[0] == ';')
4383                         start++;
4384                 while (start[0] == '\n' || start[0] == '\r')
4385                         start++;
4386                 while (start[0] == ' ' || start[0] == '\t')
4387                         start++;
4388
4389                 *bp = start;
4390                 
4391                 /* check if there are quotes (to skip , in them) */
4392                 if (*start == '"') {
4393                         start_quote = start;
4394                         start++;
4395                         end_quote = strstr(start, "\"");
4396                 } else {
4397                         start_quote = NULL;
4398                         end_quote = NULL;
4399                 }
4400                 
4401                 /* skip anything between quotes */
4402                 if (start_quote && end_quote) {
4403                         start = end_quote;
4404                         
4405                 } 
4406
4407                 /* find end (either , or ; or end of line) */
4408                 if (strstr(start, ",") && strstr(start, ";"))
4409                         *ep = strstr(start,",") < strstr(start, ";")
4410                                 ? strstr(start, ",") : strstr(start, ";");
4411                 else if (strstr(start, ","))
4412                         *ep = strstr(start, ",");
4413                 else if (strstr(start, ";"))
4414                         *ep = strstr(start, ";");
4415                 else
4416                         *ep = start+strlen(start);
4417
4418                 /* go back to real start */
4419                 if (start_quote && end_quote) {
4420                         start = start_quote;
4421                 }
4422
4423                 /* check there's still an @ in that, or search
4424                  * further if possible */
4425                 if (strstr(start, "@") && strstr(start, "@") < *ep)
4426                         return TRUE;
4427                 else if (*ep < start+strlen(start)) {
4428                         start = *ep;
4429                         goto search_again;
4430                 } else if (start_quote && strstr(start, "\"") && strstr(start, "\"") < *ep) {
4431                         *bp = start_quote;
4432                         return TRUE;
4433                 } else
4434                         return FALSE;
4435         }
4436
4437         if (!dom_tab)
4438                 dom_tab = create_domain_tab();
4439         cm_return_val_if_fail(dom_tab, FALSE);
4440
4441         /* scan start of address */
4442         for (bp_ = scanpos - 1;
4443              bp_ >= start && IS_RFC822_CHAR(*(const guchar *)bp_); bp_--)
4444                 ;
4445
4446         /* TODO: should start with an alnum? */
4447         bp_++;
4448         for (; bp_ < scanpos && !IS_ASCII_ALNUM(*(const guchar *)bp_); bp_++)
4449                 ;
4450
4451         if (bp_ != scanpos) {
4452                 /* scan end of address */
4453                 for (ep_ = scanpos + 1;
4454                      *ep_ && IS_RFC822_CHAR(*(const guchar *)ep_); ep_++)
4455                         if (*ep_ == '.') {
4456                                 prelast_dot = last_dot;
4457                                 last_dot = ep_;
4458                                 if (*(last_dot + 1) == '.') {
4459                                         if (prelast_dot == NULL)
4460                                                 return FALSE;
4461                                         last_dot = prelast_dot;
4462                                         break;
4463                                 }
4464                         }
4465
4466                 /* TODO: really should terminate with an alnum? */
4467                 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*(const guchar *)ep_);
4468                      --ep_)
4469                         ;
4470                 ep_++;
4471
4472                 if (last_dot == NULL)
4473                         return FALSE;
4474                 if (last_dot >= ep_)
4475                         last_dot = prelast_dot;
4476                 if (last_dot == NULL || (scanpos + 1 >= last_dot))
4477                         return FALSE;
4478                 last_dot++;
4479
4480                 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
4481                         if (*last_tld_char == '?')
4482                                 break;
4483
4484                 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
4485                         result = TRUE;
4486
4487                 *ep = ep_;
4488                 *bp = bp_;
4489         }
4490
4491         if (!result) return FALSE;
4492
4493         if (*ep_ && bp_ != start && *(bp_ - 1) == '"' && *(ep_) == '"'
4494         && *(ep_ + 1) == ' ' && *(ep_ + 2) == '<'
4495         && IS_RFC822_CHAR(*(ep_ + 3))) {
4496                 /* this informative part with an @ in it is
4497                  * followed by the email address */
4498                 ep_ += 3;
4499
4500                 /* go to matching '>' (or next non-rfc822 char, like \n) */
4501                 for (; *ep_ != '>' && *ep != '\0' && IS_RFC822_CHAR(*ep_); ep_++)
4502                         ;
4503
4504                 /* include the bracket */
4505                 if (*ep_ == '>') ep_++;
4506
4507                 /* include the leading quote */
4508                 bp_--;
4509
4510                 *ep = ep_;
4511                 *bp = bp_;
4512                 return TRUE;
4513         }
4514
4515         /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
4516         if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
4517                 return FALSE;
4518
4519         /* see if this is <bracketed>; in this case we also scan for the informative part. */
4520         if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
4521                 return TRUE;
4522
4523 #define FULL_STACK()    ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
4524 #define IN_STACK()      (ptr > closure_stack)
4525 /* has underrun check */
4526 #define POP_STACK()     if(IN_STACK()) --ptr
4527 /* has overrun check */
4528 #define PUSH_STACK(c)   if(!FULL_STACK()) *ptr++ = (c); else return TRUE
4529 /* has underrun check */
4530 #define PEEK_STACK()    (IN_STACK() ? *(ptr - 1) : 0)
4531
4532         ep_++;
4533
4534         /* scan for the informative part. */
4535         for (bp_ -= 2; bp_ >= start; bp_--) {
4536                 /* if closure on the stack keep scanning */
4537                 if (PEEK_STACK() == *bp_) {
4538                         POP_STACK();
4539                         continue;
4540                 }
4541                 if (!IN_STACK() && (*bp_ == '\'' || *bp_ == '"')) {
4542                         PUSH_STACK(*bp_);
4543                         continue;
4544                 }
4545
4546                 /* if nothing in the closure stack, do the special conditions
4547                  * the following if..else expression simply checks whether
4548                  * a token is acceptable. if not acceptable, the clause
4549                  * should terminate the loop with a 'break' */
4550                 if (!PEEK_STACK()) {
4551                         if (*bp_ == '-'
4552                         && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
4553                         && (((bp_ + 1) < ep_)    && isalnum(*(bp_ + 1)))) {
4554                                 /* hyphens are allowed, but only in
4555                                    between alnums */
4556                         } else if (strchr(" \"'", *bp_)) {
4557                                 /* but anything not being a punctiation
4558                                    is ok */
4559                         } else {
4560                                 break; /* anything else is rejected */
4561                         }
4562                 }
4563         }
4564
4565         bp_++;
4566
4567         /* scan forward (should start with an alnum) */
4568         for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
4569                 ;
4570 #undef PEEK_STACK
4571 #undef PUSH_STACK
4572 #undef POP_STACK
4573 #undef IN_STACK
4574 #undef FULL_STACK
4575
4576
4577         *bp = bp_;
4578         *ep = ep_;
4579
4580         return result;
4581 }
4582
4583 #undef IS_QUOTE
4584 #undef IS_ASCII_ALNUM
4585 #undef IS_RFC822_CHAR
4586
4587 gchar *make_email_string(const gchar *bp, const gchar *ep)
4588 {
4589         /* returns a mailto: URI; mailto: is also used to detect the
4590          * uri type later on in the button_pressed signal handler */
4591         gchar *tmp;
4592         gchar *result;
4593
4594         tmp = g_strndup(bp, ep - bp);
4595         result = g_strconcat("mailto:", tmp, NULL);
4596         g_free(tmp);
4597
4598         return result;
4599 }
4600
4601 gchar *make_http_string(const gchar *bp, const gchar *ep)
4602 {
4603         /* returns an http: URI; */
4604         gchar *tmp;
4605         gchar *result;
4606
4607         while (bp && *bp && g_ascii_isspace(*bp))
4608                 bp++;
4609         tmp = g_strndup(bp, ep - bp);
4610         result = g_strconcat("http://", tmp, NULL);
4611         g_free(tmp);
4612
4613         return result;
4614 }
4615
4616 static gchar *mailcap_get_command_in_file(const gchar *path, const gchar *type, const gchar *file_to_open)
4617 {
4618         FILE *fp = g_fopen(path, "rb");
4619         gchar buf[BUFFSIZE];
4620         gchar *result = NULL;
4621         if (!fp)
4622                 return NULL;
4623         while (fgets(buf, sizeof (buf), fp) != NULL) {
4624                 gchar **parts = g_strsplit(buf, ";", 3);
4625                 gchar *trimmed = parts[0];
4626                 while (trimmed[0] == ' ' || trimmed[0] == '\t')
4627                         trimmed++;
4628                 while (trimmed[strlen(trimmed)-1] == ' ' || trimmed[strlen(trimmed)-1] == '\t')
4629                         trimmed[strlen(trimmed)-1] = '\0';
4630
4631                 if (!strcmp(trimmed, type)) {
4632                         gboolean needsterminal = FALSE;
4633                         if (parts[2] && strstr(parts[2], "needsterminal")) {
4634                                 needsterminal = TRUE;
4635                         }
4636                         if (parts[2] && strstr(parts[2], "test=")) {
4637                                 gchar *orig_testcmd = g_strdup(strstr(parts[2], "test=")+5);
4638                                 gchar *testcmd = orig_testcmd;
4639                                 if (strstr(testcmd,";"))
4640                                         *(strstr(testcmd,";")) = '\0';
4641                                 while (testcmd[0] == ' ' || testcmd[0] == '\t')
4642                                         testcmd++;
4643                                 while (testcmd[strlen(testcmd)-1] == '\n')
4644                                         testcmd[strlen(testcmd)-1] = '\0';
4645                                 while (testcmd[strlen(testcmd)-1] == '\r')
4646                                         testcmd[strlen(testcmd)-1] = '\0';
4647                                 while (testcmd[strlen(testcmd)-1] == ' ' || testcmd[strlen(testcmd)-1] == '\t')
4648                                         testcmd[strlen(testcmd)-1] = '\0';
4649                                         
4650                                 if (strstr(testcmd, "%s")) {
4651                                         gchar *tmp = g_strdup_printf(testcmd, file_to_open);
4652                                         gint res = system(tmp);
4653                                         g_free(tmp);
4654                                         g_free(orig_testcmd);
4655                                         
4656                                         if (res != 0) {
4657                                                 g_strfreev(parts);
4658                                                 continue;
4659                                         }
4660                                 } else {
4661                                         gint res = system(testcmd);
4662                                         g_free(orig_testcmd);
4663                                         
4664                                         if (res != 0) {
4665                                                 g_strfreev(parts);
4666                                                 continue;
4667                                         }
4668                                 }
4669                         }
4670                         
4671                         trimmed = parts[1];
4672                         while (trimmed[0] == ' ' || trimmed[0] == '\t')
4673                                 trimmed++;
4674                         while (trimmed[strlen(trimmed)-1] == '\n')
4675                                 trimmed[strlen(trimmed)-1] = '\0';
4676                         while (trimmed[strlen(trimmed)-1] == '\r')
4677                                 trimmed[strlen(trimmed)-1] = '\0';
4678                         while (trimmed[strlen(trimmed)-1] == ' ' || trimmed[strlen(trimmed)-1] == '\t')
4679                                 trimmed[strlen(trimmed)-1] = '\0';
4680                         result = g_strdup(trimmed);
4681                         g_strfreev(parts);
4682                         fclose(fp);
4683                         /* if there are no single quotes around %s, add them.
4684                          * '.*%s.*' is ok, as in display 'png:%s'
4685                          */
4686                         if (strstr(result, "%s") 
4687                         && !(strstr(result, "'") < strstr(result,"%s") &&
4688                              strstr(strstr(result,"%s"), "'"))) {
4689                                 gchar *start = g_strdup(result);
4690                                 gchar *end = g_strdup(strstr(result, "%s")+2);
4691                                 gchar *tmp;
4692                                 *strstr(start, "%s") = '\0';
4693                                 tmp = g_strconcat(start,"'%s'",end, NULL);
4694                                 g_free(start);
4695                                 g_free(end);
4696                                 g_free(result);
4697                                 result = tmp;
4698                         }
4699                         if (needsterminal) {
4700                                 gchar *tmp = g_strdup_printf("xterm -e %s", result);
4701                                 g_free(result);
4702                                 result = tmp;
4703                         }
4704                         return result;
4705                 }
4706                 g_strfreev(parts);
4707         }
4708         fclose(fp);
4709         return NULL;
4710 }
4711 gchar *mailcap_get_command_for_type(const gchar *type, const gchar *file_to_open)
4712 {
4713         gchar *result = NULL;
4714         gchar *path = NULL;
4715         if (type == NULL)
4716                 return NULL;
4717         path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap", NULL);
4718         result = mailcap_get_command_in_file(path, type, file_to_open);
4719         g_free(path);
4720         if (result)
4721                 return result;
4722         result = mailcap_get_command_in_file("/etc/mailcap", type, file_to_open);
4723         return result;
4724 }
4725
4726 void mailcap_update_default(const gchar *type, const gchar *command)
4727 {
4728         gchar *path = NULL, *outpath = NULL;
4729         path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap", NULL);
4730         outpath = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap.new", NULL);
4731         FILE *fp = g_fopen(path, "rb");
4732         FILE *outfp = g_fopen(outpath, "wb");
4733         gchar buf[BUFFSIZE];
4734         gboolean err = FALSE;
4735
4736         if (!outfp) {
4737                 g_free(path);
4738                 g_free(outpath);
4739                 fclose(fp);
4740                 return;
4741         }
4742         while (fp && fgets(buf, sizeof (buf), fp) != NULL) {
4743                 gchar **parts = g_strsplit(buf, ";", 3);
4744                 gchar *trimmed = parts[0];
4745                 while (trimmed[0] == ' ')
4746                         trimmed++;
4747                 while (trimmed[strlen(trimmed)-1] == ' ')
4748                         trimmed[strlen(trimmed)-1] = '\0';
4749
4750                 if (!strcmp(trimmed, type)) {
4751                         g_strfreev(parts);
4752                         continue;
4753                 }
4754                 else {
4755                         if(fputs(buf, outfp) == EOF) {
4756                                 err = TRUE;
4757                                 break;
4758                         }
4759                 }
4760                 g_strfreev(parts);
4761         }
4762         if (fprintf(outfp, "%s; %s\n", type, command) < 0)
4763                 err = TRUE;
4764
4765         if (fp)
4766                 fclose(fp);
4767
4768         if (fclose(outfp) == EOF)
4769                 err = TRUE;
4770                 
4771         if (!err)
4772                 g_rename(outpath, path);
4773
4774         g_free(path);
4775         g_free(outpath);
4776 }
4777
4778 gint copy_dir(const gchar *src, const gchar *dst)
4779 {
4780         GDir *dir;
4781         const gchar *name;
4782
4783         if ((dir = g_dir_open(src, 0, NULL)) == NULL) {
4784                 g_warning("failed to open directory: %s\n", src);
4785                 return -1;
4786         }
4787
4788         if (make_dir(dst) < 0)
4789                 return -1;
4790
4791         while ((name = g_dir_read_name(dir)) != NULL) {
4792                 gchar *old_file, *new_file;
4793                 old_file = g_strconcat(src, G_DIR_SEPARATOR_S, name, NULL);
4794                 new_file = g_strconcat(dst, G_DIR_SEPARATOR_S, name, NULL);
4795                 debug_print("copying: %s -> %s\n", old_file, new_file);
4796                 if (g_file_test(old_file, G_FILE_TEST_IS_REGULAR)) {
4797                         gint r = copy_file(old_file, new_file, TRUE);
4798                         if (r < 0) {
4799                                 g_dir_close(dir);
4800                                 return r;
4801                         }
4802                 }
4803 #ifndef G_OS_WIN32
4804                 /* Windows has no symlinks.  Or well, Vista seems to
4805                    have something like this but the semantics might be
4806                    different.  Thus we don't use it under Windows. */
4807                  else if (g_file_test(old_file, G_FILE_TEST_IS_SYMLINK)) {
4808                         GError *error;
4809                         gint r = 0;
4810                         gchar *target = g_file_read_link(old_file, &error);
4811                         if (target)
4812                                 r = symlink(target, new_file);
4813                         g_free(target);
4814                         if (r < 0) {
4815                                 g_dir_close(dir);
4816                                 return r;
4817                         }
4818                  }
4819 #endif /*G_OS_WIN32*/
4820                 else if (g_file_test(old_file, G_FILE_TEST_IS_DIR)) {
4821                         gint r = copy_dir(old_file, new_file);
4822                         if (r < 0) {
4823                                 g_dir_close(dir);
4824                                 return r;
4825                         }
4826                 }
4827         }
4828         g_dir_close(dir);
4829         return 0;
4830 }
4831
4832 /* crude test to see if a file is an email. */
4833 gboolean file_is_email (const gchar *filename)
4834 {
4835         FILE *fp = NULL;
4836         gchar buffer[2048];
4837         gint i = 0;
4838         gint score = 0;
4839         if (filename == NULL)
4840                 return FALSE;
4841         if ((fp = g_fopen(filename, "rb")) == NULL)
4842                 return FALSE;
4843         while (i < 60 && score < 3
4844                && fgets(buffer, sizeof (buffer), fp) > 0) {
4845                 if (!strncmp(buffer, "From:", strlen("From:")))
4846                         score++;
4847                 if (!strncmp(buffer, "To:", strlen("To:")))
4848                         score++;
4849                 if (!strncmp(buffer, "Subject:", strlen("Subject:")))
4850                         score++;
4851                 i++;
4852         }
4853         fclose(fp);
4854         return (score >= 3);
4855 }
4856
4857 gboolean sc_g_list_bigger(GList *list, gint max)
4858 {
4859         GList *cur = list;
4860         int i = 0;
4861         while (cur && i <= max+1) {
4862                 i++;
4863                 cur = cur->next;
4864         }
4865         return (i > max);
4866 }
4867
4868 gboolean sc_g_slist_bigger(GSList *list, gint max)
4869 {
4870         GSList *cur = list;
4871         int i = 0;
4872         while (cur && i <= max+1) {
4873                 i++;
4874                 cur = cur->next;
4875         }
4876         return (i > max);
4877 }
4878
4879 const gchar *daynames[] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL};
4880 const gchar *monthnames[] = {NULL, NULL, NULL, NULL, NULL, NULL, 
4881                              NULL, NULL, NULL, NULL, NULL, NULL};
4882 const gchar *s_daynames[] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL};
4883 const gchar *s_monthnames[] = {NULL, NULL, NULL, NULL, NULL, NULL, 
4884                              NULL, NULL, NULL, NULL, NULL, NULL};
4885
4886 gint daynames_len[] =     {0,0,0,0,0,0,0};
4887 gint monthnames_len[] =   {0,0,0,0,0,0,
4888                                  0,0,0,0,0,0};
4889 gint s_daynames_len[] =   {0,0,0,0,0,0,0};
4890 gint s_monthnames_len[] = {0,0,0,0,0,0,
4891                                  0,0,0,0,0,0};
4892 const gchar *s_am_up = NULL;
4893 const gchar *s_pm_up = NULL;
4894 const gchar *s_am_low = NULL;
4895 const gchar *s_pm_low = NULL;
4896
4897 gint s_am_up_len = 0;
4898 gint s_pm_up_len = 0;
4899 gint s_am_low_len = 0;
4900 gint s_pm_low_len = 0;
4901
4902 const gchar *def_loc_format = NULL;
4903 const gchar *date_loc_format = NULL;
4904 const gchar *time_loc_format = NULL;
4905 const gchar *time_am_pm = NULL;
4906
4907 static gboolean time_names_init_done = FALSE;
4908
4909 static void init_time_names(void)
4910 {
4911         int i = 0;
4912
4913         daynames[0] = Q_("Complete day name for use by strftime|Sunday");
4914         daynames[1] = Q_("Complete day name for use by strftime|Monday");
4915         daynames[2] = Q_("Complete day name for use by strftime|Tuesday");
4916         daynames[3] = Q_("Complete day name for use by strftime|Wednesday");
4917         daynames[4] = Q_("Complete day name for use by strftime|Thursday");
4918         daynames[5] = Q_("Complete day name for use by strftime|Friday");
4919         daynames[6] = Q_("Complete day name for use by strftime|Saturday");
4920
4921         monthnames[0] = Q_("Complete month name for use by strftime|January");
4922         monthnames[1] = Q_("Complete month name for use by strftime|February");
4923         monthnames[2] = Q_("Complete month name for use by strftime|March");
4924         monthnames[3] = Q_("Complete month name for use by strftime|April");
4925         monthnames[4] = Q_("Complete month name for use by strftime|May");
4926         monthnames[5] = Q_("Complete month name for use by strftime|June");
4927         monthnames[6] = Q_("Complete month name for use by strftime|July");
4928         monthnames[7] = Q_("Complete month name for use by strftime|August");
4929         monthnames[8] = Q_("Complete month name for use by strftime|September");
4930         monthnames[9] = Q_("Complete month name for use by strftime|October");
4931         monthnames[10] = Q_("Complete month name for use by strftime|November");
4932         monthnames[11] = Q_("Complete month name for use by strftime|December");
4933
4934         s_daynames[0] = Q_("Abbr. day name for use by strftime|Sun");
4935         s_daynames[1] = Q_("Abbr. day name for use by strftime|Mon");
4936         s_daynames[2] = Q_("Abbr. day name for use by strftime|Tue");
4937         s_daynames[3] = Q_("Abbr. day name for use by strftime|Wed");
4938         s_daynames[4] = Q_("Abbr. day name for use by strftime|Thu");
4939         s_daynames[5] = Q_("Abbr. day name for use by strftime|Fri");
4940         s_daynames[6] = Q_("Abbr. day name for use by strftime|Sat");
4941         
4942         s_monthnames[0] = Q_("Abbr. month name for use by strftime|Jan");
4943         s_monthnames[1] = Q_("Abbr. month name for use by strftime|Feb");
4944         s_monthnames[2] = Q_("Abbr. month name for use by strftime|Mar");
4945         s_monthnames[3] = Q_("Abbr. month name for use by strftime|Apr");
4946         s_monthnames[4] = Q_("Abbr. month name for use by strftime|May");
4947         s_monthnames[5] = Q_("Abbr. month name for use by strftime|Jun");
4948         s_monthnames[6] = Q_("Abbr. month name for use by strftime|Jul");
4949         s_monthnames[7] = Q_("Abbr. month name for use by strftime|Aug");
4950         s_monthnames[8] = Q_("Abbr. month name for use by strftime|Sep");
4951         s_monthnames[9] = Q_("Abbr. month name for use by strftime|Oct");
4952         s_monthnames[10] = Q_("Abbr. month name for use by strftime|Nov");
4953         s_monthnames[11] = Q_("Abbr. month name for use by strftime|Dec");
4954
4955         for (i = 0; i < 7; i++) {
4956                 daynames_len[i] = strlen(daynames[i]);
4957                 s_daynames_len[i] = strlen(s_daynames[i]);
4958         }
4959         for (i = 0; i < 12; i++) {
4960                 monthnames_len[i] = strlen(monthnames[i]);
4961                 s_monthnames_len[i] = strlen(s_monthnames[i]);
4962         }
4963
4964         s_am_up = Q_("For use by strftime (morning)|AM");
4965         s_pm_up = Q_("For use by strftime (afternoon)|PM");
4966         s_am_low = Q_("For use by strftime (morning, lowercase)|am");
4967         s_pm_low = Q_("For use by strftime (afternoon, lowercase)|pm");
4968         
4969         s_am_up_len = strlen(s_am_up);
4970         s_pm_up_len = strlen(s_pm_up);
4971         s_am_low_len = strlen(s_am_low);
4972         s_pm_low_len = strlen(s_pm_low);
4973         
4974         def_loc_format = Q_("For use by strftime (default date+time format)|%a %b %e %H:%M:%S %Y");
4975         date_loc_format = Q_("For use by strftime (default date format)|%m/%d/%y");
4976         time_loc_format = Q_("For use by strftime (default time format)|%H:%M:%S");
4977
4978         time_am_pm = Q_("For use by strftime (default 12-hour time format)|%I:%M:%S %p");
4979
4980         time_names_init_done = TRUE;
4981 }
4982
4983 #define CHECK_SIZE() {                  \
4984         total_done += len;              \
4985         if (total_done >= buflen) {     \
4986                 buf[buflen-1] = '\0';   \
4987                 return 0;               \
4988         }                               \
4989 }
4990
4991 size_t fast_strftime(gchar *buf, gint buflen, const gchar *format, struct tm *lt)
4992 {
4993         gchar *curpos = buf;
4994         gint total_done = 0;
4995         gchar subbuf[64], subfmt[64];
4996         static time_t last_tzset = (time_t)0;
4997         
4998         if (!time_names_init_done)
4999                 init_time_names();
5000         
5001         if (format == NULL || lt == NULL)
5002                 return 0;
5003                 
5004         if (last_tzset != time(NULL)) {
5005                 tzset();
5006                 last_tzset = time(NULL);
5007         }
5008         while(*format) {
5009                 if (*format == '%') {
5010                         gint len = 0, tmp = 0;
5011                         format++;
5012                         switch(*format) {
5013                         case '%':
5014                                 len = 1; CHECK_SIZE();
5015                                 *curpos = '%';
5016                                 break;
5017                         case 'a':
5018                                 len = s_daynames_len[lt->tm_wday]; CHECK_SIZE();
5019                                 strncpy2(curpos, s_daynames[lt->tm_wday], buflen - total_done);
5020                                 break;
5021                         case 'A':
5022                                 len = daynames_len[lt->tm_wday]; CHECK_SIZE();
5023                                 strncpy2(curpos, daynames[lt->tm_wday], buflen - total_done);
5024                                 break;
5025                         case 'b':
5026                         case 'h':
5027                                 len = s_monthnames_len[lt->tm_mon]; CHECK_SIZE();
5028                                 strncpy2(curpos, s_monthnames[lt->tm_mon], buflen - total_done);
5029                                 break;
5030                         case 'B':
5031                                 len = monthnames_len[lt->tm_mon]; CHECK_SIZE();
5032                                 strncpy2(curpos, monthnames[lt->tm_mon], buflen - total_done);
5033                                 break;
5034                         case 'c':
5035                                 fast_strftime(subbuf, 64, def_loc_format, lt);
5036                                 len = strlen(subbuf); CHECK_SIZE();
5037                                 strncpy2(curpos, subbuf, buflen - total_done);
5038                                 break;
5039                         case 'C':
5040                                 total_done += 2; CHECK_SIZE();
5041                                 tmp = (lt->tm_year + 1900)/100;
5042                                 *curpos++ = '0'+(tmp / 10);
5043                                 *curpos++ = '0'+(tmp % 10);
5044                                 break;
5045                         case 'd':
5046                                 total_done += 2; CHECK_SIZE();
5047                                 *curpos++ = '0'+(lt->tm_mday / 10);
5048                                 *curpos++ = '0'+(lt->tm_mday % 10);
5049                                 break;
5050                         case 'D':
5051                                 total_done += 8; CHECK_SIZE();
5052                                 *curpos++ = '0'+((lt->tm_mon+1) / 10);
5053                                 *curpos++ = '0'+((lt->tm_mon+1) % 10);
5054                                 *curpos++ = '/';
5055                                 *curpos++ = '0'+(lt->tm_mday / 10);
5056                                 *curpos++ = '0'+(lt->tm_mday % 10);
5057                                 *curpos++ = '/';
5058                                 tmp = lt->tm_year%100;
5059                                 *curpos++ = '0'+(tmp / 10);
5060                                 *curpos++ = '0'+(tmp % 10);
5061                                 break;
5062                         case 'e':
5063                                 len = 2; CHECK_SIZE();
5064                                 snprintf(curpos, buflen - total_done, "%2d", lt->tm_mday);
5065                                 break;
5066                         case 'F':
5067                                 len = 10; CHECK_SIZE();
5068                                 snprintf(curpos, buflen - total_done, "%4d-%02d-%02d", 
5069                                         lt->tm_year + 1900, lt->tm_mon +1, lt->tm_mday);
5070                                 break;
5071                         case 'H':
5072                                 total_done += 2; CHECK_SIZE();
5073                                 *curpos++ = '0'+(lt->tm_hour / 10);
5074                                 *curpos++ = '0'+(lt->tm_hour % 10);
5075                                 break;
5076                         case 'I':
5077                                 total_done += 2; CHECK_SIZE();
5078                                 tmp = lt->tm_hour;
5079                                 if (tmp > 12)
5080                                         tmp -= 12;
5081                                 else if (tmp == 0)
5082                                         tmp = 12;
5083                                 *curpos++ = '0'+(tmp / 10);
5084                                 *curpos++ = '0'+(tmp % 10);
5085                                 break;
5086                         case 'j':
5087                                 len = 3; CHECK_SIZE();
5088                                 snprintf(curpos, buflen - total_done, "%03d", lt->tm_yday+1);
5089                                 break;
5090                         case 'k':
5091                                 len = 2; CHECK_SIZE();
5092                                 snprintf(curpos, buflen - total_done, "%2d", lt->tm_hour);
5093                                 break;
5094                         case 'l':
5095                                 len = 2; CHECK_SIZE();
5096                                 tmp = lt->tm_hour;
5097                                 if (tmp > 12)
5098                                         tmp -= 12;
5099                                 else if (tmp == 0)
5100                                         tmp = 12;
5101                                 snprintf(curpos, buflen - total_done, "%2d", tmp);
5102                                 break;
5103                         case 'm':
5104                                 total_done += 2; CHECK_SIZE();
5105                                 tmp = lt->tm_mon + 1;
5106                                 *curpos++ = '0'+(tmp / 10);
5107                                 *curpos++ = '0'+(tmp % 10);
5108                                 break;
5109                         case 'M':
5110                                 total_done += 2; CHECK_SIZE();
5111                                 *curpos++ = '0'+(lt->tm_min / 10);
5112                                 *curpos++ = '0'+(lt->tm_min % 10);
5113                                 break;
5114                         case 'n':
5115                                 len = 1; CHECK_SIZE();
5116                                 *curpos = '\n';
5117                                 break;
5118                         case 'p':
5119                                 if (lt->tm_hour >= 12) {
5120                                         len = s_pm_up_len; CHECK_SIZE();
5121                                         snprintf(curpos, buflen-total_done, "%s", s_pm_up);
5122                                 } else {
5123                                         len = s_am_up_len; CHECK_SIZE();
5124                                         snprintf(curpos, buflen-total_done, "%s", s_am_up);
5125                                 }
5126                                 break;
5127                         case 'P':
5128                                 if (lt->tm_hour >= 12) {
5129                                         len = s_pm_low_len; CHECK_SIZE();
5130                                         snprintf(curpos, buflen-total_done, "%s", s_pm_low);
5131                                 } else {
5132                                         len = s_am_low_len; CHECK_SIZE();
5133                                         snprintf(curpos, buflen-total_done, "%s", s_am_low);
5134                                 }
5135                                 break;
5136                         case 'r':
5137                                 fast_strftime(subbuf, 64, time_am_pm, lt);
5138                                 len = strlen(subbuf); CHECK_SIZE();
5139                                 strncpy2(curpos, subbuf, buflen - total_done);
5140                                 break;
5141                         case 'R':
5142                                 total_done += 5; CHECK_SIZE();
5143                                 *curpos++ = '0'+(lt->tm_hour / 10);
5144                                 *curpos++ = '0'+(lt->tm_hour % 10);
5145                                 *curpos++ = ':';
5146                                 *curpos++ = '0'+(lt->tm_min / 10);
5147                                 *curpos++ = '0'+(lt->tm_min % 10);
5148                                 break;
5149                         case 's':
5150                                 snprintf(subbuf, 64, "%ld", mktime(lt));
5151                                 len = strlen(subbuf); CHECK_SIZE();
5152                                 strncpy2(curpos, subbuf, buflen - total_done);
5153                                 break;
5154                         case 'S':
5155                                 total_done += 2; CHECK_SIZE();
5156                                 *curpos++ = '0'+(lt->tm_sec / 10);
5157                                 *curpos++ = '0'+(lt->tm_sec % 10);
5158                                 break;
5159                         case 't':
5160                                 len = 1; CHECK_SIZE();
5161                                 *curpos = '\t';
5162                                 break;
5163                         case 'T':
5164                                 total_done += 8; CHECK_SIZE();
5165                                 *curpos++ = '0'+(lt->tm_hour / 10);
5166                                 *curpos++ = '0'+(lt->tm_hour % 10);
5167                                 *curpos++ = ':';
5168                                 *curpos++ = '0'+(lt->tm_min / 10);
5169                                 *curpos++ = '0'+(lt->tm_min % 10);
5170                                 *curpos++ = ':';
5171                                 *curpos++ = '0'+(lt->tm_sec / 10);
5172                                 *curpos++ = '0'+(lt->tm_sec % 10);
5173                                 break;
5174                         case 'u':
5175                                 len = 1; CHECK_SIZE();
5176                                 snprintf(curpos, buflen - total_done, "%d", lt->tm_wday == 0 ? 7: lt->tm_wday);
5177                                 break;
5178                         case 'w':
5179                                 len = 1; CHECK_SIZE();
5180                                 snprintf(curpos, buflen - total_done, "%d", lt->tm_wday);
5181                                 break;
5182                         case 'x':
5183                                 fast_strftime(subbuf, 64, date_loc_format, lt);
5184                                 len = strlen(subbuf); CHECK_SIZE();
5185                                 strncpy2(curpos, subbuf, buflen - total_done);
5186                                 break;
5187                         case 'X':
5188                                 fast_strftime(subbuf, 64, time_loc_format, lt);
5189                                 len = strlen(subbuf); CHECK_SIZE();
5190                                 strncpy2(curpos, subbuf, buflen - total_done);
5191                                 break;
5192                         case 'y':
5193                                 total_done += 2; CHECK_SIZE();
5194                                 tmp = lt->tm_year%100;
5195                                 *curpos++ = '0'+(tmp / 10);
5196                                 *curpos++ = '0'+(tmp % 10);
5197                                 break;
5198                         case 'Y':
5199                                 len = 4; CHECK_SIZE();
5200                                 snprintf(curpos, buflen - total_done, "%4d", lt->tm_year + 1900);
5201                                 break;
5202                         case 'G':
5203                         case 'g':
5204                         case 'U':
5205                         case 'V':
5206                         case 'W':
5207                         case 'z':
5208                         case 'Z':
5209                         case '+':
5210                                 /* let these complicated ones be done with the libc */
5211                                 snprintf(subfmt, 64, "%%%c", *format);
5212                                 strftime(subbuf, 64, subfmt, lt);
5213                                 len = strlen(subbuf); CHECK_SIZE();
5214                                 strncpy2(curpos, subbuf, buflen - total_done);
5215                                 break;
5216                         case 'E':
5217                         case 'O':
5218                                 /* let these complicated modifiers be done with the libc */
5219                                 snprintf(subfmt, 64, "%%%c%c", *format, *(format+1));
5220                                 strftime(subbuf, 64, subfmt, lt);
5221                                 len = strlen(subbuf); CHECK_SIZE();
5222                                 strncpy2(curpos, subbuf, buflen - total_done);
5223                                 format++;
5224                                 break;
5225                         default:
5226                                 if (format && *format)
5227                                         g_warning("format error (%c)", *format);
5228                                 *curpos = '\0';
5229                                 return total_done;
5230                         }
5231                         curpos += len;
5232                         format++;
5233                 } else {
5234                         int len = 1; CHECK_SIZE();
5235                         *curpos++ = *format++; 
5236                 }
5237         }
5238         *curpos++ = '\0';
5239         return total_done;
5240 }
5241
5242 gboolean prefs_common_get_use_shred(void);
5243
5244
5245 #ifdef G_OS_WIN32
5246 #define WEXITSTATUS(x) (x)
5247 #endif
5248
5249 int claws_unlink(const gchar *filename) 
5250 {
5251         struct stat s;
5252         static int found_shred = -1;
5253         static const gchar *args[4];
5254
5255         if (filename == NULL)
5256                 return 0;
5257
5258         if (prefs_common_get_use_shred()) {
5259                 if (found_shred == -1) {
5260                         /* init */
5261                         args[0] = g_find_program_in_path("shred");
5262                         debug_print("found shred: %s\n", args[0]);
5263                         found_shred = (args[0] != NULL) ? 1:0;
5264                         args[1] = "-f";
5265                         args[3] = NULL;
5266                 }
5267                 if (found_shred == 1) {
5268                         if (g_stat(filename, &s) == 0 && S_ISREG(s.st_mode)) {
5269                                 if (s.st_nlink == 1) {
5270                                         gint status=0;
5271                                         args[2] = filename;
5272                                         g_spawn_sync(NULL, (gchar **)args, NULL, 0,
5273                                          NULL, NULL, NULL, NULL, &status, NULL);
5274                                         debug_print("%s %s exited with status %d\n",
5275                                                 args[0], filename, WEXITSTATUS(status));
5276                                         if (truncate(filename, 0) < 0)
5277                                                 g_warning("couln't truncate");
5278                                 }
5279                         }
5280                 }
5281         }
5282         return g_unlink(filename);
5283 }