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