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