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