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