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