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