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