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