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