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