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