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