f00a4ec7263000ec4b531a14a6fbf015c161f287
[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 static gchar *claws_rc_dir = NULL;
1925 static gboolean rc_dir_alt = FALSE;
1926 const gchar *get_rc_dir(void)
1927 {
1928
1929         if (!claws_rc_dir)
1930                 claws_rc_dir = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
1931                                      RC_DIR, NULL);
1932
1933         return claws_rc_dir;
1934 }
1935
1936 void set_rc_dir(const gchar *dir)
1937 {
1938         if (claws_rc_dir != NULL) {
1939                 g_print("Error: rc_dir already set\n");
1940         } else {
1941                 rc_dir_alt = TRUE;
1942                 if (g_path_is_absolute(dir))
1943                         claws_rc_dir = g_strdup(dir);
1944                 else {
1945                         claws_rc_dir = g_strconcat(g_get_current_dir(),
1946                                 G_DIR_SEPARATOR_S, dir, NULL);
1947                 }
1948                 debug_print("set rc_dir to %s\n", claws_rc_dir);
1949                 if (!is_dir_exist(claws_rc_dir)) {
1950                         if (make_dir_hier(claws_rc_dir) != 0) {
1951                                 g_print("Error: can't create %s\n",
1952                                 claws_rc_dir);
1953                         }
1954                 }
1955         }
1956 }
1957
1958 gboolean rc_dir_is_alt(void) {
1959         return rc_dir_alt;
1960 }
1961
1962 const gchar *get_mail_base_dir(void)
1963 {
1964 #ifdef G_OS_WIN32
1965         static gchar *mail_base_dir = NULL;
1966
1967         if (!mail_base_dir)
1968                 mail_base_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1969                                             "Mailboxes", NULL);
1970
1971         return mail_base_dir;
1972 #else
1973         return get_home_dir();
1974 #endif
1975 }
1976
1977 #ifdef MAEMO
1978 const gchar *prefs_common_get_data_root(void);
1979 gchar *last_data_root = NULL;
1980 #endif
1981
1982 const gchar *get_news_cache_dir(void)
1983 {
1984         static gchar *news_cache_dir = NULL;
1985 #ifdef MAEMO
1986         const gchar *data_root = prefs_common_get_data_root();
1987         if (strcmp2(data_root, last_data_root)) {
1988                 g_free(news_cache_dir);
1989                 news_cache_dir = NULL;
1990         }
1991 #endif
1992         if (!news_cache_dir)
1993 #ifndef MAEMO
1994                 news_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1995                                              NEWS_CACHE_DIR, NULL);
1996 #else
1997         {
1998                 if (data_root) {
1999                         news_cache_dir = g_strconcat(data_root, G_DIR_SEPARATOR_S,
2000                                              "Claws", G_DIR_SEPARATOR_S, 
2001                                              g_get_user_name(), G_DIR_SEPARATOR_S,
2002                                              NEWS_CACHE_DIR, NULL);
2003                         g_free(last_data_root);
2004                         last_data_root = g_strdup(last_data_root);
2005                 } else {
2006                         news_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
2007                                              NEWS_CACHE_DIR, NULL);
2008                         g_free(last_data_root);
2009                         last_data_root = NULL;
2010                 }
2011         }
2012 #endif
2013         return news_cache_dir;
2014 }
2015
2016 const gchar *get_imap_cache_dir(void)
2017 {
2018         static gchar *imap_cache_dir = NULL;
2019 #ifdef MAEMO
2020         const gchar *data_root = prefs_common_get_data_root();
2021         if (strcmp2(data_root, last_data_root)) {
2022                 g_free(imap_cache_dir);
2023                 imap_cache_dir = NULL;
2024         }
2025 #endif
2026
2027         if (!imap_cache_dir)
2028 #ifndef MAEMO
2029                 imap_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
2030                                              IMAP_CACHE_DIR, NULL);
2031 #else
2032         {
2033                 if (data_root) {
2034                         imap_cache_dir = g_strconcat(data_root, G_DIR_SEPARATOR_S,
2035                                              "Claws", G_DIR_SEPARATOR_S, 
2036                                              g_get_user_name(), G_DIR_SEPARATOR_S,
2037                                              IMAP_CACHE_DIR, NULL);
2038                         g_free(last_data_root);
2039                         last_data_root = g_strdup(last_data_root);
2040                 } else {
2041                         imap_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
2042                                              IMAP_CACHE_DIR, NULL);
2043                         g_free(last_data_root);
2044                         last_data_root = NULL;
2045                 }
2046         }
2047 #endif
2048
2049         return imap_cache_dir;
2050 }
2051
2052 const gchar *get_mbox_cache_dir(void)
2053 {
2054         static gchar *mbox_cache_dir = NULL;
2055 #ifdef MAEMO
2056         const gchar *data_root = prefs_common_get_data_root();
2057         if (strcmp2(data_root, last_data_root)) {
2058                 g_free(mbox_cache_dir);
2059                 mbox_cache_dir = NULL;
2060         }
2061 #endif
2062         if (!mbox_cache_dir)
2063 #ifndef MAEMO
2064                 mbox_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
2065                                              MBOX_CACHE_DIR, NULL);
2066 #else
2067         {
2068                 if (data_root) {
2069                         mbox_cache_dir = g_strconcat(data_root, G_DIR_SEPARATOR_S,
2070                                              "Claws", G_DIR_SEPARATOR_S, 
2071                                              g_get_user_name(), G_DIR_SEPARATOR_S,
2072                                              MBOX_CACHE_DIR, NULL);
2073                         g_free(last_data_root);
2074                         last_data_root = g_strdup(last_data_root);
2075                 } else {
2076                         mbox_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
2077                                              MBOX_CACHE_DIR, NULL);
2078                         g_free(last_data_root);
2079                         last_data_root = NULL;
2080                 }
2081         }
2082 #endif
2083
2084         return mbox_cache_dir;
2085 }
2086
2087 const gchar *get_mime_tmp_dir(void)
2088 {
2089         static gchar *mime_tmp_dir = NULL;
2090
2091         if (!mime_tmp_dir)
2092                 mime_tmp_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
2093                                            MIME_TMP_DIR, NULL);
2094
2095         return mime_tmp_dir;
2096 }
2097
2098 const gchar *get_template_dir(void)
2099 {
2100         static gchar *template_dir = NULL;
2101
2102         if (!template_dir)
2103                 template_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
2104                                            TEMPLATE_DIR, NULL);
2105
2106         return template_dir;
2107 }
2108
2109 const gchar *get_header_cache_dir(void)
2110 {
2111         static gchar *header_dir = NULL;
2112
2113         if (!header_dir)
2114                 header_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
2115                                          HEADER_CACHE_DIR, NULL);
2116
2117         return header_dir;
2118 }
2119
2120 /* Return the default directory for Plugins. */
2121 const gchar *get_plugin_dir(void)
2122 {
2123 #ifdef G_OS_WIN32
2124         static gchar *plugin_dir = NULL;
2125
2126         if (!plugin_dir)
2127                 plugin_dir = g_strconcat(w32_get_module_dir(),
2128                                          "\\lib\\claws-mail\\plugins\\",
2129                                          NULL);
2130         return plugin_dir;
2131 #else
2132         if (is_dir_exist(PLUGINDIR))
2133                 return PLUGINDIR;
2134         else {
2135                 static gchar *plugin_dir = NULL;
2136                 if (!plugin_dir)
2137                         plugin_dir = g_strconcat(get_rc_dir(), 
2138                                 G_DIR_SEPARATOR_S, "plugins", 
2139                                 G_DIR_SEPARATOR_S, NULL);
2140                 return plugin_dir;                      
2141         }
2142 #endif
2143 }
2144
2145 const gchar *get_tmp_dir(void)
2146 {
2147         static gchar *tmp_dir = NULL;
2148
2149         if (!tmp_dir)
2150                 tmp_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
2151                                       TMP_DIR, NULL);
2152
2153         return tmp_dir;
2154 }
2155
2156 gchar *get_tmp_file(void)
2157 {
2158         gchar *tmp_file;
2159         static guint32 id = 0;
2160
2161         tmp_file = g_strdup_printf("%s%ctmpfile.%08x",
2162                                    get_tmp_dir(), G_DIR_SEPARATOR, id++);
2163
2164         return tmp_file;
2165 }
2166
2167 const gchar *get_domain_name(void)
2168 {
2169 #ifdef G_OS_UNIX
2170         static gchar *domain_name = NULL;
2171
2172         if (!domain_name) {
2173                 struct hostent *hp;
2174                 char hostname[256];
2175
2176                 if (gethostname(hostname, sizeof(hostname)) != 0) {
2177                         perror("gethostname");
2178                         domain_name = "unknown";
2179                 } else {
2180                         hostname[sizeof(hostname) - 1] = '\0';
2181                         if ((hp = my_gethostbyname(hostname)) == NULL) {
2182                                 perror("gethostbyname");
2183                                 domain_name = g_strdup(hostname);
2184                         } else {
2185                                 domain_name = g_strdup(hp->h_name);
2186                         }
2187                 }
2188                 debug_print("domain name = %s\n", domain_name);
2189         }
2190
2191         return domain_name;
2192 #else
2193         return "unknown";
2194 #endif
2195 }
2196
2197 off_t get_file_size(const gchar *file)
2198 {
2199         struct stat s;
2200
2201         if (g_stat(file, &s) < 0) {
2202                 FILE_OP_ERROR(file, "stat");
2203                 return -1;
2204         }
2205
2206         return s.st_size;
2207 }
2208
2209 time_t get_file_mtime(const gchar *file)
2210 {
2211         struct stat s;
2212
2213         if (g_stat(file, &s) < 0) {
2214                 FILE_OP_ERROR(file, "stat");
2215                 return -1;
2216         }
2217
2218         return s.st_mtime;
2219 }
2220
2221 off_t get_file_size_as_crlf(const gchar *file)
2222 {
2223         FILE *fp;
2224         off_t size = 0;
2225         gchar buf[BUFFSIZE];
2226
2227         if ((fp = g_fopen(file, "rb")) == NULL) {
2228                 FILE_OP_ERROR(file, "fopen");
2229                 return -1;
2230         }
2231
2232         while (fgets(buf, sizeof(buf), fp) != NULL) {
2233                 strretchomp(buf);
2234                 size += strlen(buf) + 2;
2235         }
2236
2237         if (ferror(fp)) {
2238                 FILE_OP_ERROR(file, "fgets");
2239                 size = -1;
2240         }
2241
2242         fclose(fp);
2243
2244         return size;
2245 }
2246
2247 off_t get_left_file_size(FILE *fp)
2248 {
2249         glong pos;
2250         glong end;
2251         off_t size;
2252
2253         if ((pos = ftell(fp)) < 0) {
2254                 perror("ftell");
2255                 return -1;
2256         }
2257         if (fseek(fp, 0L, SEEK_END) < 0) {
2258                 perror("fseek");
2259                 return -1;
2260         }
2261         if ((end = ftell(fp)) < 0) {
2262                 perror("fseek");
2263                 return -1;
2264         }
2265         size = end - pos;
2266         if (fseek(fp, pos, SEEK_SET) < 0) {
2267                 perror("fseek");
2268                 return -1;
2269         }
2270
2271         return size;
2272 }
2273
2274 gboolean file_exist(const gchar *file, gboolean allow_fifo)
2275 {
2276         struct stat s;
2277
2278         if (file == NULL)
2279                 return FALSE;
2280
2281         if (g_stat(file, &s) < 0) {
2282                 if (ENOENT != errno) FILE_OP_ERROR(file, "stat");
2283                 return FALSE;
2284         }
2285
2286         if (S_ISREG(s.st_mode) || (allow_fifo && S_ISFIFO(s.st_mode)))
2287                 return TRUE;
2288
2289         return FALSE;
2290 }
2291
2292
2293 /* Test on whether FILE is a relative file name. This is
2294  * straightforward for Unix but more complex for Windows. */
2295 gboolean is_relative_filename(const gchar *file)
2296 {
2297         if (!file)
2298                 return TRUE;
2299 #ifdef G_OS_WIN32
2300         if ( *file == '\\' && file[1] == '\\' && strchr (file+2, '\\') )
2301                 return FALSE; /* Prefixed with a hostname - this can't
2302                                * be a relative name. */
2303
2304         if ( ((*file >= 'a' && *file <= 'z')
2305               || (*file >= 'A' && *file <= 'Z'))
2306              && file[1] == ':')
2307                 file += 2;  /* Skip drive letter. */
2308
2309         return !(*file == '\\' || *file == '/');
2310 #else
2311         return !(*file == G_DIR_SEPARATOR);
2312 #endif
2313 }
2314
2315
2316 gboolean is_dir_exist(const gchar *dir)
2317 {
2318         if (dir == NULL)
2319                 return FALSE;
2320
2321         return g_file_test(dir, G_FILE_TEST_IS_DIR);
2322 }
2323
2324 gboolean is_file_entry_exist(const gchar *file)
2325 {
2326         if (file == NULL)
2327                 return FALSE;
2328
2329         return g_file_test(file, G_FILE_TEST_EXISTS);
2330 }
2331
2332 gboolean dirent_is_regular_file(struct dirent *d)
2333 {
2334 #if !defined(G_OS_WIN32) && !defined(MAEMO) && defined(HAVE_DIRENT_D_TYPE)
2335         if (d->d_type == DT_REG)
2336                 return TRUE;
2337         else if (d->d_type != DT_UNKNOWN)
2338                 return FALSE;
2339 #endif
2340
2341         return g_file_test(d->d_name, G_FILE_TEST_IS_REGULAR);
2342 }
2343
2344 gboolean dirent_is_directory(struct dirent *d)
2345 {
2346 #if !defined(G_OS_WIN32) && !defined(MAEMO) && defined(HAVE_DIRENT_D_TYPE)
2347         if (d->d_type == DT_DIR)
2348                 return TRUE;
2349         else if (d->d_type != DT_UNKNOWN)
2350                 return FALSE;
2351 #endif
2352
2353         return g_file_test(d->d_name, G_FILE_TEST_IS_DIR);
2354 }
2355
2356 gint change_dir(const gchar *dir)
2357 {
2358         gchar *prevdir = NULL;
2359
2360         if (debug_mode)
2361                 prevdir = g_get_current_dir();
2362
2363         if (g_chdir(dir) < 0) {
2364                 FILE_OP_ERROR(dir, "chdir");
2365                 if (debug_mode) g_free(prevdir);
2366                 return -1;
2367         } else if (debug_mode) {
2368                 gchar *cwd;
2369
2370                 cwd = g_get_current_dir();
2371                 if (strcmp(prevdir, cwd) != 0)
2372                         g_print("current dir: %s\n", cwd);
2373                 g_free(cwd);
2374                 g_free(prevdir);
2375         }
2376
2377         return 0;
2378 }
2379
2380 gint make_dir(const gchar *dir)
2381 {
2382         if (g_mkdir(dir, S_IRWXU) < 0) {
2383                 FILE_OP_ERROR(dir, "mkdir");
2384                 return -1;
2385         }
2386         if (g_chmod(dir, S_IRWXU) < 0)
2387                 FILE_OP_ERROR(dir, "chmod");
2388
2389         return 0;
2390 }
2391
2392 gint make_dir_hier(const gchar *dir)
2393 {
2394         gchar *parent_dir;
2395         const gchar *p;
2396
2397         for (p = dir; (p = strchr(p, G_DIR_SEPARATOR)) != NULL; p++) {
2398                 parent_dir = g_strndup(dir, p - dir);
2399                 if (*parent_dir != '\0') {
2400                         if (!is_dir_exist(parent_dir)) {
2401                                 if (make_dir(parent_dir) < 0) {
2402                                         g_free(parent_dir);
2403                                         return -1;
2404                                 }
2405                         }
2406                 }
2407                 g_free(parent_dir);
2408         }
2409
2410         if (!is_dir_exist(dir)) {
2411                 if (make_dir(dir) < 0)
2412                         return -1;
2413         }
2414
2415         return 0;
2416 }
2417
2418 gint remove_all_files(const gchar *dir)
2419 {
2420         GDir *dp;
2421         const gchar *dir_name;
2422         gchar *prev_dir;
2423
2424         prev_dir = g_get_current_dir();
2425
2426         if (g_chdir(dir) < 0) {
2427                 FILE_OP_ERROR(dir, "chdir");
2428                 g_free(prev_dir);
2429                 return -1;
2430         }
2431
2432         if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2433                 g_warning("failed to open directory: %s\n", dir);
2434                 g_free(prev_dir);
2435                 return -1;
2436         }
2437
2438         while ((dir_name = g_dir_read_name(dp)) != NULL) {
2439                 if (g_unlink(dir_name) < 0)
2440                         FILE_OP_ERROR(dir_name, "unlink");
2441         }
2442
2443         g_dir_close(dp);
2444
2445         if (g_chdir(prev_dir) < 0) {
2446                 FILE_OP_ERROR(prev_dir, "chdir");
2447                 g_free(prev_dir);
2448                 return -1;
2449         }
2450
2451         g_free(prev_dir);
2452
2453         return 0;
2454 }
2455
2456 gint remove_numbered_files(const gchar *dir, guint first, guint last)
2457 {
2458         GDir *dp;
2459         const gchar *dir_name;
2460         gchar *prev_dir;
2461         gint file_no;
2462
2463         prev_dir = g_get_current_dir();
2464
2465         if (g_chdir(dir) < 0) {
2466                 FILE_OP_ERROR(dir, "chdir");
2467                 g_free(prev_dir);
2468                 return -1;
2469         }
2470
2471         if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2472                 g_warning("failed to open directory: %s\n", dir);
2473                 g_free(prev_dir);
2474                 return -1;
2475         }
2476
2477         while ((dir_name = g_dir_read_name(dp)) != NULL) {
2478                 file_no = to_number(dir_name);
2479                 if (file_no > 0 && first <= file_no && file_no <= last) {
2480                         if (is_dir_exist(dir_name))
2481                                 continue;
2482                         if (g_unlink(dir_name) < 0)
2483                                 FILE_OP_ERROR(dir_name, "unlink");
2484                 }
2485         }
2486
2487         g_dir_close(dp);
2488
2489         if (g_chdir(prev_dir) < 0) {
2490                 FILE_OP_ERROR(prev_dir, "chdir");
2491                 g_free(prev_dir);
2492                 return -1;
2493         }
2494
2495         g_free(prev_dir);
2496
2497         return 0;
2498 }
2499
2500 gint remove_numbered_files_not_in_list(const gchar *dir, GSList *numberlist)
2501 {
2502         GDir *dp;
2503         const gchar *dir_name;
2504         gchar *prev_dir;
2505         gint file_no;
2506
2507         prev_dir = g_get_current_dir();
2508
2509         if (g_chdir(dir) < 0) {
2510                 FILE_OP_ERROR(dir, "chdir");
2511                 g_free(prev_dir);
2512                 return -1;
2513         }
2514
2515         if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2516                 FILE_OP_ERROR(dir, "opendir");
2517                 g_free(prev_dir);
2518                 return -1;
2519         }
2520
2521         while ((dir_name = g_dir_read_name(dp)) != NULL) {
2522                 file_no = to_number(dir_name);
2523                 if (file_no > 0 && (g_slist_find(numberlist, GINT_TO_POINTER(file_no)) == NULL)) {
2524                         debug_print("removing unwanted file %d from %s\n", file_no, dir);
2525                         if (is_dir_exist(dir_name))
2526                                 continue;
2527                         if (g_unlink(dir_name) < 0)
2528                                 FILE_OP_ERROR(dir_name, "unlink");
2529                 }
2530         }
2531
2532         g_dir_close(dp);
2533
2534         if (g_chdir(prev_dir) < 0) {
2535                 FILE_OP_ERROR(prev_dir, "chdir");
2536                 g_free(prev_dir);
2537                 return -1;
2538         }
2539
2540         g_free(prev_dir);
2541
2542         return 0;
2543 }
2544
2545 gint remove_all_numbered_files(const gchar *dir)
2546 {
2547         return remove_numbered_files(dir, 0, UINT_MAX);
2548 }
2549
2550 gint remove_expired_files(const gchar *dir, guint hours)
2551 {
2552         GDir *dp;
2553         const gchar *dir_name;
2554         struct stat s;
2555         gchar *prev_dir;
2556         gint file_no;
2557         time_t mtime, now, expire_time;
2558
2559         prev_dir = g_get_current_dir();
2560
2561         if (g_chdir(dir) < 0) {
2562                 FILE_OP_ERROR(dir, "chdir");
2563                 g_free(prev_dir);
2564                 return -1;
2565         }
2566
2567         if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2568                 g_warning("failed to open directory: %s\n", dir);
2569                 g_free(prev_dir);
2570                 return -1;
2571         }
2572
2573         now = time(NULL);
2574         expire_time = hours * 60 * 60;
2575
2576         while ((dir_name = g_dir_read_name(dp)) != NULL) {
2577                 file_no = to_number(dir_name);
2578                 if (file_no > 0) {
2579                         if (g_stat(dir_name, &s) < 0) {
2580                                 FILE_OP_ERROR(dir_name, "stat");
2581                                 continue;
2582                         }
2583                         if (S_ISDIR(s.st_mode))
2584                                 continue;
2585                         mtime = MAX(s.st_mtime, s.st_atime);
2586                         if (now - mtime > expire_time) {
2587                                 if (g_unlink(dir_name) < 0)
2588                                         FILE_OP_ERROR(dir_name, "unlink");
2589                         }
2590                 }
2591         }
2592
2593         g_dir_close(dp);
2594
2595         if (g_chdir(prev_dir) < 0) {
2596                 FILE_OP_ERROR(prev_dir, "chdir");
2597                 g_free(prev_dir);
2598                 return -1;
2599         }
2600
2601         g_free(prev_dir);
2602
2603         return 0;
2604 }
2605
2606 gint remove_dir_recursive(const gchar *dir)
2607 {
2608         struct stat s;
2609         GDir *dp;
2610         const gchar *dir_name;
2611         gchar *prev_dir;
2612
2613         if (g_stat(dir, &s) < 0) {
2614                 FILE_OP_ERROR(dir, "stat");
2615                 if (ENOENT == errno) return 0;
2616                 return -1;
2617         }
2618
2619         if (!S_ISDIR(s.st_mode)) {
2620                 if (g_unlink(dir) < 0) {
2621                         FILE_OP_ERROR(dir, "unlink");
2622                         return -1;
2623                 }
2624
2625                 return 0;
2626         }
2627
2628         prev_dir = g_get_current_dir();
2629         /* g_print("prev_dir = %s\n", prev_dir); */
2630
2631         if (!path_cmp(prev_dir, dir)) {
2632                 g_free(prev_dir);
2633                 if (g_chdir("..") < 0) {
2634                         FILE_OP_ERROR(dir, "chdir");
2635                         return -1;
2636                 }
2637                 prev_dir = g_get_current_dir();
2638         }
2639
2640         if (g_chdir(dir) < 0) {
2641                 FILE_OP_ERROR(dir, "chdir");
2642                 g_free(prev_dir);
2643                 return -1;
2644         }
2645
2646         if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2647                 g_warning("failed to open directory: %s\n", dir);
2648                 g_chdir(prev_dir);
2649                 g_free(prev_dir);
2650                 return -1;
2651         }
2652
2653         /* remove all files in the directory */
2654         while ((dir_name = g_dir_read_name(dp)) != NULL) {
2655                 /* g_print("removing %s\n", dir_name); */
2656
2657                 if (is_dir_exist(dir_name)) {
2658                         if (remove_dir_recursive(dir_name) < 0) {
2659                                 g_warning("can't remove directory\n");
2660                                 return -1;
2661                         }
2662                 } else {
2663                         if (g_unlink(dir_name) < 0)
2664                                 FILE_OP_ERROR(dir_name, "unlink");
2665                 }
2666         }
2667
2668         g_dir_close(dp);
2669
2670         if (g_chdir(prev_dir) < 0) {
2671                 FILE_OP_ERROR(prev_dir, "chdir");
2672                 g_free(prev_dir);
2673                 return -1;
2674         }
2675
2676         g_free(prev_dir);
2677
2678         if (g_rmdir(dir) < 0) {
2679                 FILE_OP_ERROR(dir, "rmdir");
2680                 return -1;
2681         }
2682
2683         return 0;
2684 }
2685
2686 gint rename_force(const gchar *oldpath, const gchar *newpath)
2687 {
2688 #ifndef G_OS_UNIX
2689         if (!is_file_entry_exist(oldpath)) {
2690                 errno = ENOENT;
2691                 return -1;
2692         }
2693         if (is_file_exist(newpath)) {
2694                 if (g_unlink(newpath) < 0)
2695                         FILE_OP_ERROR(newpath, "unlink");
2696         }
2697 #endif
2698         return g_rename(oldpath, newpath);
2699 }
2700
2701 /*
2702  * Append src file body to the tail of dest file.
2703  * Now keep_backup has no effects.
2704  */
2705 gint append_file(const gchar *src, const gchar *dest, gboolean keep_backup)
2706 {
2707         FILE *src_fp, *dest_fp;
2708         gint n_read;
2709         gchar buf[BUFSIZ];
2710
2711         gboolean err = FALSE;
2712
2713         if ((src_fp = g_fopen(src, "rb")) == NULL) {
2714                 FILE_OP_ERROR(src, "fopen");
2715                 return -1;
2716         }
2717
2718         if ((dest_fp = g_fopen(dest, "ab")) == NULL) {
2719                 FILE_OP_ERROR(dest, "fopen");
2720                 fclose(src_fp);
2721                 return -1;
2722         }
2723
2724         if (change_file_mode_rw(dest_fp, dest) < 0) {
2725                 FILE_OP_ERROR(dest, "chmod");
2726                 g_warning("can't change file mode\n");
2727         }
2728
2729         while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
2730                 if (n_read < sizeof(buf) && ferror(src_fp))
2731                         break;
2732                 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2733                         g_warning("writing to %s failed.\n", dest);
2734                         fclose(dest_fp);
2735                         fclose(src_fp);
2736                         g_unlink(dest);
2737                         return -1;
2738                 }
2739         }
2740
2741         if (ferror(src_fp)) {
2742                 FILE_OP_ERROR(src, "fread");
2743                 err = TRUE;
2744         }
2745         fclose(src_fp);
2746         if (fclose(dest_fp) == EOF) {
2747                 FILE_OP_ERROR(dest, "fclose");
2748                 err = TRUE;
2749         }
2750
2751         if (err) {
2752                 g_unlink(dest);
2753                 return -1;
2754         }
2755
2756         return 0;
2757 }
2758
2759 gint copy_file(const gchar *src, const gchar *dest, gboolean keep_backup)
2760 {
2761         FILE *src_fp, *dest_fp;
2762         gint n_read;
2763         gchar buf[BUFSIZ];
2764         gchar *dest_bak = NULL;
2765         gboolean err = FALSE;
2766
2767         if ((src_fp = g_fopen(src, "rb")) == NULL) {
2768                 FILE_OP_ERROR(src, "fopen");
2769                 return -1;
2770         }
2771         if (is_file_exist(dest)) {
2772                 dest_bak = g_strconcat(dest, ".bak", NULL);
2773                 if (rename_force(dest, dest_bak) < 0) {
2774                         FILE_OP_ERROR(dest, "rename");
2775                         fclose(src_fp);
2776                         g_free(dest_bak);
2777                         return -1;
2778                 }
2779         }
2780
2781         if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2782                 FILE_OP_ERROR(dest, "fopen");
2783                 fclose(src_fp);
2784                 if (dest_bak) {
2785                         if (rename_force(dest_bak, dest) < 0)
2786                                 FILE_OP_ERROR(dest_bak, "rename");
2787                         g_free(dest_bak);
2788                 }
2789                 return -1;
2790         }
2791
2792         if (change_file_mode_rw(dest_fp, dest) < 0) {
2793                 FILE_OP_ERROR(dest, "chmod");
2794                 g_warning("can't change file mode\n");
2795         }
2796
2797         while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
2798                 if (n_read < sizeof(buf) && ferror(src_fp))
2799                         break;
2800                 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2801                         g_warning("writing to %s failed.\n", dest);
2802                         fclose(dest_fp);
2803                         fclose(src_fp);
2804                         g_unlink(dest);
2805                         if (dest_bak) {
2806                                 if (rename_force(dest_bak, dest) < 0)
2807                                         FILE_OP_ERROR(dest_bak, "rename");
2808                                 g_free(dest_bak);
2809                         }
2810                         return -1;
2811                 }
2812         }
2813
2814         if (ferror(src_fp)) {
2815                 FILE_OP_ERROR(src, "fread");
2816                 err = TRUE;
2817         }
2818         fclose(src_fp);
2819         if (fclose(dest_fp) == EOF) {
2820                 FILE_OP_ERROR(dest, "fclose");
2821                 err = TRUE;
2822         }
2823
2824         if (err) {
2825                 g_unlink(dest);
2826                 if (dest_bak) {
2827                         if (rename_force(dest_bak, dest) < 0)
2828                                 FILE_OP_ERROR(dest_bak, "rename");
2829                         g_free(dest_bak);
2830                 }
2831                 return -1;
2832         }
2833
2834         if (keep_backup == FALSE && dest_bak)
2835                 g_unlink(dest_bak);
2836
2837         g_free(dest_bak);
2838
2839         return 0;
2840 }
2841
2842 gint move_file(const gchar *src, const gchar *dest, gboolean overwrite)
2843 {
2844         if (overwrite == FALSE && is_file_exist(dest)) {
2845                 g_warning("move_file(): file %s already exists.", dest);
2846                 return -1;
2847         }
2848
2849         if (rename_force(src, dest) == 0) return 0;
2850
2851         if (EXDEV != errno) {
2852                 FILE_OP_ERROR(src, "rename");
2853                 return -1;
2854         }
2855
2856         if (copy_file(src, dest, FALSE) < 0) return -1;
2857
2858         g_unlink(src);
2859
2860         return 0;
2861 }
2862
2863 gint copy_file_part_to_fp(FILE *fp, off_t offset, size_t length, FILE *dest_fp)
2864 {
2865         gint n_read;
2866         gint bytes_left, to_read;
2867         gchar buf[BUFSIZ];
2868
2869         if (fseek(fp, offset, SEEK_SET) < 0) {
2870                 perror("fseek");
2871                 return -1;
2872         }
2873
2874         bytes_left = length;
2875         to_read = MIN(bytes_left, sizeof(buf));
2876
2877         while ((n_read = fread(buf, sizeof(gchar), to_read, fp)) > 0) {
2878                 if (n_read < to_read && ferror(fp))
2879                         break;
2880                 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2881                         return -1;
2882                 }
2883                 bytes_left -= n_read;
2884                 if (bytes_left == 0)
2885                         break;
2886                 to_read = MIN(bytes_left, sizeof(buf));
2887         }
2888
2889         if (ferror(fp)) {
2890                 perror("fread");
2891                 return -1;
2892         }
2893
2894         return 0;
2895 }
2896
2897 gint copy_file_part(FILE *fp, off_t offset, size_t length, const gchar *dest)
2898 {
2899         FILE *dest_fp;
2900         gboolean err = FALSE;
2901
2902         if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2903                 FILE_OP_ERROR(dest, "fopen");
2904                 return -1;
2905         }
2906
2907         if (change_file_mode_rw(dest_fp, dest) < 0) {
2908                 FILE_OP_ERROR(dest, "chmod");
2909                 g_warning("can't change file mode\n");
2910         }
2911
2912         if (copy_file_part_to_fp(fp, offset, length, dest_fp) < 0)
2913                 err = TRUE;
2914
2915         if (!err && fclose(dest_fp) == EOF) {
2916                 FILE_OP_ERROR(dest, "fclose");
2917                 err = TRUE;
2918         }
2919
2920         if (err) {
2921                 g_warning("writing to %s failed.\n", dest);
2922                 g_unlink(dest);
2923                 return -1;
2924         }
2925
2926         return 0;
2927 }
2928
2929 /* convert line endings into CRLF. If the last line doesn't end with
2930  * linebreak, add it.
2931  */
2932 gchar *canonicalize_str(const gchar *str)
2933 {
2934         const gchar *p;
2935         guint new_len = 0;
2936         gchar *out, *outp;
2937
2938         for (p = str; *p != '\0'; ++p) {
2939                 if (*p != '\r') {
2940                         ++new_len;
2941                         if (*p == '\n')
2942                                 ++new_len;
2943                 }
2944         }
2945         if (p == str || *(p - 1) != '\n')
2946                 new_len += 2;
2947
2948         out = outp = g_malloc(new_len + 1);
2949         for (p = str; *p != '\0'; ++p) {
2950                 if (*p != '\r') {
2951                         if (*p == '\n')
2952                                 *outp++ = '\r';
2953                         *outp++ = *p;
2954                 }
2955         }
2956         if (p == str || *(p - 1) != '\n') {
2957                 *outp++ = '\r';
2958                 *outp++ = '\n';
2959         }
2960         *outp = '\0';
2961
2962         return out;
2963 }
2964
2965 gint canonicalize_file(const gchar *src, const gchar *dest)
2966 {
2967         FILE *src_fp, *dest_fp;
2968         gchar buf[BUFFSIZE];
2969         gint len;
2970         gboolean err = FALSE;
2971         gboolean last_linebreak = FALSE;
2972
2973         if ((src_fp = g_fopen(src, "rb")) == NULL) {
2974                 FILE_OP_ERROR(src, "fopen");
2975                 return -1;
2976         }
2977
2978         if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2979                 FILE_OP_ERROR(dest, "fopen");
2980                 fclose(src_fp);
2981                 return -1;
2982         }
2983
2984         if (change_file_mode_rw(dest_fp, dest) < 0) {
2985                 FILE_OP_ERROR(dest, "chmod");
2986                 g_warning("can't change file mode\n");
2987         }
2988
2989         while (fgets(buf, sizeof(buf), src_fp) != NULL) {
2990                 gint r = 0;
2991
2992                 len = strlen(buf);
2993                 if (len == 0) break;
2994                 last_linebreak = FALSE;
2995
2996                 if (buf[len - 1] != '\n') {
2997                         last_linebreak = TRUE;
2998                         r = fputs(buf, dest_fp);
2999                 } else if (len > 1 && buf[len - 1] == '\n' && buf[len - 2] == '\r') {
3000                         r = fputs(buf, dest_fp);
3001                 } else {
3002                         if (len > 1) {
3003                                 r = fwrite(buf, 1, len - 1, dest_fp);
3004                                 if (r != (len -1))
3005                                         r = EOF;
3006                         }
3007                         if (r != EOF)
3008                                 r = fputs("\r\n", dest_fp);
3009                 }
3010
3011                 if (r == EOF) {
3012                         g_warning("writing to %s failed.\n", dest);
3013                         fclose(dest_fp);
3014                         fclose(src_fp);
3015                         g_unlink(dest);
3016                         return -1;
3017                 }
3018         }
3019
3020         if (last_linebreak == TRUE) {
3021                 if (fputs("\r\n", dest_fp) == EOF)
3022                         err = TRUE;
3023         }
3024
3025         if (ferror(src_fp)) {
3026                 FILE_OP_ERROR(src, "fgets");
3027                 err = TRUE;
3028         }
3029         fclose(src_fp);
3030         if (fclose(dest_fp) == EOF) {
3031                 FILE_OP_ERROR(dest, "fclose");
3032                 err = TRUE;
3033         }
3034
3035         if (err) {
3036                 g_unlink(dest);
3037                 return -1;
3038         }
3039
3040         return 0;
3041 }
3042
3043 gint canonicalize_file_replace(const gchar *file)
3044 {
3045         gchar *tmp_file;
3046
3047         tmp_file = get_tmp_file();
3048
3049         if (canonicalize_file(file, tmp_file) < 0) {
3050                 g_free(tmp_file);
3051                 return -1;
3052         }
3053
3054         if (move_file(tmp_file, file, TRUE) < 0) {
3055                 g_warning("can't replace %s .\n", file);
3056                 g_unlink(tmp_file);
3057                 g_free(tmp_file);
3058                 return -1;
3059         }
3060
3061         g_free(tmp_file);
3062         return 0;
3063 }
3064
3065 gint uncanonicalize_file(const gchar *src, const gchar *dest)
3066 {
3067         FILE *src_fp, *dest_fp;
3068         gchar buf[BUFFSIZE];
3069         gboolean err = FALSE;
3070
3071         if ((src_fp = g_fopen(src, "rb")) == NULL) {
3072                 FILE_OP_ERROR(src, "fopen");
3073                 return -1;
3074         }
3075
3076         if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
3077                 FILE_OP_ERROR(dest, "fopen");
3078                 fclose(src_fp);
3079                 return -1;
3080         }
3081
3082         if (change_file_mode_rw(dest_fp, dest) < 0) {
3083                 FILE_OP_ERROR(dest, "chmod");
3084                 g_warning("can't change file mode\n");
3085         }
3086
3087         while (fgets(buf, sizeof(buf), src_fp) != NULL) {
3088                 strcrchomp(buf);
3089                 if (fputs(buf, dest_fp) == EOF) {
3090                         g_warning("writing to %s failed.\n", dest);
3091                         fclose(dest_fp);
3092                         fclose(src_fp);
3093                         g_unlink(dest);
3094                         return -1;
3095                 }
3096         }
3097
3098         if (ferror(src_fp)) {
3099                 FILE_OP_ERROR(src, "fgets");
3100                 err = TRUE;
3101         }
3102         fclose(src_fp);
3103         if (fclose(dest_fp) == EOF) {
3104                 FILE_OP_ERROR(dest, "fclose");
3105                 err = TRUE;
3106         }
3107
3108         if (err) {
3109                 g_unlink(dest);
3110                 return -1;
3111         }
3112
3113         return 0;
3114 }
3115
3116 gint uncanonicalize_file_replace(const gchar *file)
3117 {
3118         gchar *tmp_file;
3119
3120         tmp_file = get_tmp_file();
3121
3122         if (uncanonicalize_file(file, tmp_file) < 0) {
3123                 g_free(tmp_file);
3124                 return -1;
3125         }
3126
3127         if (move_file(tmp_file, file, TRUE) < 0) {
3128                 g_warning("can't replace %s .\n", file);
3129                 g_unlink(tmp_file);
3130                 g_free(tmp_file);
3131                 return -1;
3132         }
3133
3134         g_free(tmp_file);
3135         return 0;
3136 }
3137
3138 gchar *normalize_newlines(const gchar *str)
3139 {
3140         const gchar *p = str;
3141         gchar *out, *outp;
3142
3143         out = outp = g_malloc(strlen(str) + 1);
3144         for (p = str; *p != '\0'; ++p) {
3145                 if (*p == '\r') {
3146                         if (*(p + 1) != '\n')
3147                                 *outp++ = '\n';
3148                 } else
3149                         *outp++ = *p;
3150         }
3151
3152         *outp = '\0';
3153
3154         return out;
3155 }
3156
3157 gchar *get_outgoing_rfc2822_str(FILE *fp)
3158 {
3159         gchar buf[BUFFSIZE];
3160         GString *str;
3161         gchar *ret;
3162
3163         str = g_string_new(NULL);
3164
3165         /* output header part */
3166         while (fgets(buf, sizeof(buf), fp) != NULL) {
3167                 strretchomp(buf);
3168                 if (!g_ascii_strncasecmp(buf, "Bcc:", 4)) {
3169                         gint next;
3170
3171                         for (;;) {
3172                                 next = fgetc(fp);
3173                                 if (next == EOF)
3174                                         break;
3175                                 else if (next != ' ' && next != '\t') {
3176                                         ungetc(next, fp);
3177                                         break;
3178                                 }
3179                                 if (fgets(buf, sizeof(buf), fp) == NULL)
3180                                         break;
3181                         }
3182                 } else {
3183                         g_string_append(str, buf);
3184                         g_string_append(str, "\r\n");
3185                         if (buf[0] == '\0')
3186                                 break;
3187                 }
3188         }
3189
3190         /* output body part */
3191         while (fgets(buf, sizeof(buf), fp) != NULL) {
3192                 strretchomp(buf);
3193                 if (buf[0] == '.')
3194                         g_string_append_c(str, '.');
3195                 g_string_append(str, buf);
3196                 g_string_append(str, "\r\n");
3197         }
3198
3199         ret = str->str;
3200         g_string_free(str, FALSE);
3201
3202         return ret;
3203 }
3204
3205 /*
3206  * Create a new boundary in a way that it is very unlikely that this
3207  * will occur in the following text.  It would be easy to ensure
3208  * uniqueness if everything is either quoted-printable or base64
3209  * encoded (note that conversion is allowed), but because MIME bodies
3210  * may be nested, it may happen that the same boundary has already
3211  * been used.
3212  *
3213  *   boundary := 0*69<bchars> bcharsnospace
3214  *   bchars := bcharsnospace / " "
3215  *   bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" /
3216  *                  "+" / "_" / "," / "-" / "." /
3217  *                  "/" / ":" / "=" / "?"
3218  *
3219  * some special characters removed because of buggy MTAs
3220  */
3221
3222 gchar *generate_mime_boundary(const gchar *prefix)
3223 {
3224         static gchar tbl[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
3225                              "abcdefghijklmnopqrstuvwxyz"
3226                              "1234567890+_./=";
3227         gchar buf_uniq[24];
3228         gint i;
3229
3230         for (i = 0; i < sizeof(buf_uniq) - 1; i++)
3231                 buf_uniq[i] = tbl[g_random_int_range(0, sizeof(tbl) - 1)];
3232         buf_uniq[i] = '\0';
3233
3234         return g_strdup_printf("%s_/%s", prefix ? prefix : "MP",
3235                                buf_uniq);
3236 }
3237
3238 gint change_file_mode_rw(FILE *fp, const gchar *file)
3239 {
3240 #if HAVE_FCHMOD
3241         return fchmod(fileno(fp), S_IRUSR|S_IWUSR);
3242 #else
3243         return g_chmod(file, S_IRUSR|S_IWUSR);
3244 #endif
3245 }
3246
3247 FILE *my_tmpfile(void)
3248 {
3249 #if HAVE_MKSTEMP || defined(G_OS_WIN32)
3250         const gchar suffix[] = ".XXXXXX";
3251         const gchar *tmpdir;
3252         guint tmplen;
3253         const gchar *progname;
3254         guint proglen;
3255         gchar *fname;
3256         gint fd;
3257         FILE *fp;
3258         gchar buf[2]="\0";
3259
3260         tmpdir = get_tmp_dir();
3261         tmplen = strlen(tmpdir);
3262         progname = g_get_prgname();
3263         if (progname == NULL)
3264                 progname = "claws-mail";
3265         proglen = strlen(progname);
3266         Xalloca(fname, tmplen + 1 + proglen + sizeof(suffix),
3267                 return tmpfile());
3268
3269         memcpy(fname, tmpdir, tmplen);
3270         fname[tmplen] = G_DIR_SEPARATOR;
3271         memcpy(fname + tmplen + 1, progname, proglen);
3272         memcpy(fname + tmplen + 1 + proglen, suffix, sizeof(suffix));
3273
3274         fd = mkstemp(fname);
3275         if (fd < 0)
3276                 return tmpfile();
3277
3278 #ifndef G_OS_WIN32
3279         g_unlink(fname);
3280         
3281         /* verify that we can write in the file after unlinking */
3282         if (write(fd, buf, 1) < 0) {
3283                 close(fd);
3284                 return tmpfile();
3285         }
3286         
3287 #endif
3288
3289         fp = fdopen(fd, "w+b");
3290         if (!fp)
3291                 close(fd);
3292         else {
3293                 rewind(fp);
3294                 return fp;
3295         }
3296
3297 #endif /* HAVE_MKSTEMP || G_OS_WIN32 */
3298
3299         return tmpfile();
3300 }
3301
3302 FILE *get_tmpfile_in_dir(const gchar *dir, gchar **filename)
3303 {
3304         int fd;
3305 #ifdef G_OS_WIN32
3306         char *template = g_strdup_printf ("%s%cclaws.XXXXXX",
3307                                           dir, G_DIR_SEPARATOR);
3308         fd = mkstemp_name(template, filename);
3309         g_free(template);
3310 #else
3311         *filename = g_strdup_printf("%s%cclaws.XXXXXX", dir, G_DIR_SEPARATOR);
3312         fd = mkstemp(*filename);
3313 #endif
3314         return fdopen(fd, "w+");
3315 }
3316
3317 FILE *str_open_as_stream(const gchar *str)
3318 {
3319         FILE *fp;
3320         size_t len;
3321
3322         g_return_val_if_fail(str != NULL, NULL);
3323
3324         fp = my_tmpfile();
3325         if (!fp) {
3326                 FILE_OP_ERROR("str_open_as_stream", "my_tmpfile");
3327                 return NULL;
3328         }
3329
3330         len = strlen(str);
3331         if (len == 0) return fp;
3332
3333         if (fwrite(str, 1, len, fp) != len) {
3334                 FILE_OP_ERROR("str_open_as_stream", "fwrite");
3335                 fclose(fp);
3336                 return NULL;
3337         }
3338
3339         rewind(fp);
3340         return fp;
3341 }
3342
3343 gint str_write_to_file(const gchar *str, const gchar *file)
3344 {
3345         FILE *fp;
3346         size_t len;
3347
3348         g_return_val_if_fail(str != NULL, -1);
3349         g_return_val_if_fail(file != NULL, -1);
3350
3351         if ((fp = g_fopen(file, "wb")) == NULL) {
3352                 FILE_OP_ERROR(file, "fopen");
3353                 return -1;
3354         }
3355
3356         len = strlen(str);
3357         if (len == 0) {
3358                 fclose(fp);
3359                 return 0;
3360         }
3361
3362         if (fwrite(str, 1, len, fp) != len) {
3363                 FILE_OP_ERROR(file, "fwrite");
3364                 fclose(fp);
3365                 g_unlink(file);
3366                 return -1;
3367         }
3368
3369         if (fclose(fp) == EOF) {
3370                 FILE_OP_ERROR(file, "fclose");
3371                 g_unlink(file);
3372                 return -1;
3373         }
3374
3375         return 0;
3376 }
3377
3378 gchar *file_read_to_str(const gchar *file)
3379 {
3380         FILE *fp;
3381         gchar *str;
3382
3383         g_return_val_if_fail(file != NULL, NULL);
3384
3385         if ((fp = g_fopen(file, "rb")) == NULL) {
3386                 FILE_OP_ERROR(file, "fopen");
3387                 return NULL;
3388         }
3389
3390         str = file_read_stream_to_str(fp);
3391
3392         fclose(fp);
3393
3394         return str;
3395 }
3396
3397 gchar *file_read_stream_to_str(FILE *fp)
3398 {
3399         GByteArray *array;
3400         guchar buf[BUFSIZ];
3401         gint n_read;
3402         gchar *str;
3403
3404         g_return_val_if_fail(fp != NULL, NULL);
3405
3406         array = g_byte_array_new();
3407
3408         while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
3409                 if (n_read < sizeof(buf) && ferror(fp))
3410                         break;
3411                 g_byte_array_append(array, buf, n_read);
3412         }
3413
3414         if (ferror(fp)) {
3415                 FILE_OP_ERROR("file stream", "fread");
3416                 g_byte_array_free(array, TRUE);
3417                 return NULL;
3418         }
3419
3420         buf[0] = '\0';
3421         g_byte_array_append(array, buf, 1);
3422         str = (gchar *)array->data;
3423         g_byte_array_free(array, FALSE);
3424
3425         if (!g_utf8_validate(str, -1, NULL)) {
3426                 const gchar *src_codeset, *dest_codeset;
3427                 gchar *tmp = NULL;
3428                 src_codeset = conv_get_locale_charset_str();
3429                 dest_codeset = CS_UTF_8;
3430                 tmp = conv_codeset_strdup(str, src_codeset, dest_codeset);
3431                 g_free(str);
3432                 str = tmp;
3433         }
3434
3435         return str;
3436 }
3437
3438
3439 char *fgets_crlf(char *buf, int size, FILE *stream)
3440 {
3441         gboolean is_cr = FALSE;
3442         gboolean last_was_cr = FALSE;
3443         int c = 0;
3444         char *cs;
3445
3446         cs = buf;
3447         while (--size > 0 && (c = getc(stream)) != EOF)
3448         {
3449                 *cs++ = c;
3450                 is_cr = (c == '\r');
3451                 if (c == '\n') {
3452                         break;
3453                 }
3454                 if (last_was_cr) {
3455                         *(--cs) = '\n';
3456                         cs++;
3457                         ungetc(c, stream);
3458                         break;
3459                 }
3460                 last_was_cr = is_cr;
3461         }
3462         if (c == EOF && cs == buf)
3463                 return NULL;
3464
3465         *cs = '\0';
3466
3467         return buf;     
3468 }
3469
3470 static gint execute_async(gchar *const argv[])
3471 {
3472         g_return_val_if_fail(argv != NULL && argv[0] != NULL, -1);
3473
3474         if (g_spawn_async(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH,
3475                           NULL, NULL, NULL, FALSE) == FALSE) {
3476                 g_warning("Couldn't execute command: %s\n", argv[0]);
3477                 return -1;
3478         }
3479
3480         return 0;
3481 }
3482
3483 static gint execute_sync(gchar *const argv[])
3484 {
3485         gint status;
3486
3487         g_return_val_if_fail(argv != NULL && argv[0] != NULL, -1);
3488
3489         if (g_spawn_sync(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH,
3490                          NULL, NULL, NULL, NULL, &status, NULL) == FALSE) {
3491                 g_warning("Couldn't execute command: %s\n", argv[0]);
3492                 return -1;
3493         }
3494
3495 #ifdef G_OS_UNIX
3496         if (WIFEXITED(status))
3497                 return WEXITSTATUS(status);
3498         else
3499                 return -1;
3500 #else
3501         return status;
3502 #endif
3503 }
3504
3505 gint execute_command_line(const gchar *cmdline, gboolean async)
3506 {
3507         gchar **argv;
3508         gint ret;
3509
3510         debug_print("execute_command_line(): executing: %s\n", cmdline?cmdline:"(null)");
3511
3512         argv = strsplit_with_quote(cmdline, " ", 0);
3513
3514         if (async)
3515                 ret = execute_async(argv);
3516         else
3517                 ret = execute_sync(argv);
3518
3519         g_strfreev(argv);
3520
3521         return ret;
3522 }
3523
3524 gchar *get_command_output(const gchar *cmdline)
3525 {
3526         gchar *child_stdout;
3527         gint status;
3528
3529         g_return_val_if_fail(cmdline != NULL, NULL);
3530
3531         debug_print("get_command_output(): executing: %s\n", cmdline);
3532
3533         if (g_spawn_command_line_sync(cmdline, &child_stdout, NULL, &status,
3534                                       NULL) == FALSE) {
3535                 g_warning("Couldn't execute command: %s\n", cmdline);
3536                 return NULL;
3537         }
3538
3539         return child_stdout;
3540 }
3541
3542 static gint is_unchanged_uri_char(char c)
3543 {
3544         switch (c) {
3545                 case '(':
3546                 case ')':
3547                         return 0;
3548                 default:
3549                         return 1;
3550         }
3551 }
3552
3553 static void encode_uri(gchar *encoded_uri, gint bufsize, const gchar *uri)
3554 {
3555         int i;
3556         int k;
3557
3558         k = 0;
3559         for(i = 0; i < strlen(uri) ; i++) {
3560                 if (is_unchanged_uri_char(uri[i])) {
3561                         if (k + 2 >= bufsize)
3562                                 break;
3563                         encoded_uri[k++] = uri[i];
3564                 }
3565                 else {
3566                         char * hexa = "0123456789ABCDEF";
3567
3568                         if (k + 4 >= bufsize)
3569                                 break;
3570                         encoded_uri[k++] = '%';
3571                         encoded_uri[k++] = hexa[uri[i] / 16];
3572                         encoded_uri[k++] = hexa[uri[i] % 16];
3573                 }
3574         }
3575         encoded_uri[k] = 0;
3576 }
3577
3578 gint open_uri(const gchar *uri, const gchar *cmdline)
3579 {
3580 #ifndef MAEMO
3581         gchar buf[BUFFSIZE];
3582         gchar *p;
3583         gchar encoded_uri[BUFFSIZE];
3584         g_return_val_if_fail(uri != NULL, -1);
3585
3586         /* an option to choose whether to use encode_uri or not ? */
3587         encode_uri(encoded_uri, BUFFSIZE, uri);
3588
3589         if (cmdline &&
3590             (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
3591             !strchr(p + 2, '%'))
3592                 g_snprintf(buf, sizeof(buf), cmdline, encoded_uri);
3593         else {
3594                 if (cmdline)
3595                         g_warning("Open URI command line is invalid "
3596                                   "(there must be only one '%%s'): %s",
3597                                   cmdline);
3598                 g_snprintf(buf, sizeof(buf), DEFAULT_BROWSER_CMD, encoded_uri);
3599         }
3600
3601         execute_command_line(buf, TRUE);
3602 #else
3603         extern osso_context_t *get_osso_context(void);
3604         osso_rpc_run_with_defaults(get_osso_context(), "osso_browser",
3605                                         OSSO_BROWSER_OPEN_NEW_WINDOW_REQ, NULL, 
3606                                         DBUS_TYPE_STRING, uri, DBUS_TYPE_INVALID);
3607 #endif
3608         return 0;
3609 }
3610
3611 gint open_txt_editor(const gchar *filepath, const gchar *cmdline)
3612 {
3613         gchar buf[BUFFSIZE];
3614         gchar *p;
3615
3616         g_return_val_if_fail(filepath != NULL, -1);
3617
3618         if (cmdline &&
3619             (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
3620             !strchr(p + 2, '%'))
3621                 g_snprintf(buf, sizeof(buf), cmdline, filepath);
3622         else {
3623                 if (cmdline)
3624                         g_warning("Open Text Editor command line is invalid "
3625                                   "(there must be only one '%%s'): %s",
3626                                   cmdline);
3627                 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, filepath);
3628         }
3629
3630         execute_command_line(buf, TRUE);
3631
3632         return 0;
3633 }
3634
3635 time_t remote_tzoffset_sec(const gchar *zone)
3636 {
3637         static gchar ustzstr[] = "PSTPDTMSTMDTCSTCDTESTEDT";
3638         gchar zone3[4];
3639         gchar *p;
3640         gchar c;
3641         gint iustz;
3642         gint offset;
3643         time_t remoteoffset;
3644
3645         strncpy(zone3, zone, 3);
3646         zone3[3] = '\0';
3647         remoteoffset = 0;
3648
3649         if (sscanf(zone, "%c%d", &c, &offset) == 2 &&
3650             (c == '+' || c == '-')) {
3651                 remoteoffset = ((offset / 100) * 60 + (offset % 100)) * 60;
3652                 if (c == '-')
3653                         remoteoffset = -remoteoffset;
3654         } else if (!strncmp(zone, "UT" , 2) ||
3655                    !strncmp(zone, "GMT", 2)) {
3656                 remoteoffset = 0;
3657         } else if (strlen(zone3) == 3) {
3658                 for (p = ustzstr; *p != '\0'; p += 3) {
3659                         if (!g_ascii_strncasecmp(p, zone3, 3)) {
3660                                 iustz = ((gint)(p - ustzstr) / 3 + 1) / 2 - 8;
3661                                 remoteoffset = iustz * 3600;
3662                                 break;
3663                         }
3664                 }
3665                 if (*p == '\0')
3666                         return -1;
3667         } else if (strlen(zone3) == 1) {
3668                 switch (zone[0]) {
3669                 case 'Z': remoteoffset =   0; break;
3670                 case 'A': remoteoffset =  -1; break;
3671                 case 'B': remoteoffset =  -2; break;
3672                 case 'C': remoteoffset =  -3; break;
3673                 case 'D': remoteoffset =  -4; break;
3674                 case 'E': remoteoffset =  -5; break;
3675                 case 'F': remoteoffset =  -6; break;
3676                 case 'G': remoteoffset =  -7; break;
3677                 case 'H': remoteoffset =  -8; break;
3678                 case 'I': remoteoffset =  -9; break;
3679                 case 'K': remoteoffset = -10; break; /* J is not used */
3680                 case 'L': remoteoffset = -11; break;
3681                 case 'M': remoteoffset = -12; break;
3682                 case 'N': remoteoffset =   1; break;
3683                 case 'O': remoteoffset =   2; break;
3684                 case 'P': remoteoffset =   3; break;
3685                 case 'Q': remoteoffset =   4; break;
3686                 case 'R': remoteoffset =   5; break;
3687                 case 'S': remoteoffset =   6; break;
3688                 case 'T': remoteoffset =   7; break;
3689                 case 'U': remoteoffset =   8; break;
3690                 case 'V': remoteoffset =   9; break;
3691                 case 'W': remoteoffset =  10; break;
3692                 case 'X': remoteoffset =  11; break;
3693                 case 'Y': remoteoffset =  12; break;
3694                 default:  remoteoffset =   0; break;
3695                 }
3696                 remoteoffset = remoteoffset * 3600;
3697         } else
3698                 return -1;
3699
3700         return remoteoffset;
3701 }
3702
3703 time_t tzoffset_sec(time_t *now)
3704 {
3705         struct tm gmt, *lt;
3706         gint off;
3707         struct tm buf1, buf2;
3708         
3709         gmt = *gmtime_r(now, &buf1);
3710         lt = localtime_r(now, &buf2);
3711
3712         off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
3713
3714         if (lt->tm_year < gmt.tm_year)
3715                 off -= 24 * 60;
3716         else if (lt->tm_year > gmt.tm_year)
3717                 off += 24 * 60;
3718         else if (lt->tm_yday < gmt.tm_yday)
3719                 off -= 24 * 60;
3720         else if (lt->tm_yday > gmt.tm_yday)
3721                 off += 24 * 60;
3722
3723         if (off >= 24 * 60)             /* should be impossible */
3724                 off = 23 * 60 + 59;     /* if not, insert silly value */
3725         if (off <= -24 * 60)
3726                 off = -(23 * 60 + 59);
3727
3728         return off * 60;
3729 }
3730
3731 /* calculate timezone offset */
3732 gchar *tzoffset(time_t *now)
3733 {
3734         static gchar offset_string[6];
3735         struct tm gmt, *lt;
3736         gint off;
3737         gchar sign = '+';
3738         struct tm buf1, buf2;
3739
3740         gmt = *gmtime_r(now, &buf1);
3741         lt = localtime_r(now, &buf2);
3742
3743         off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
3744
3745         if (lt->tm_year < gmt.tm_year)
3746                 off -= 24 * 60;
3747         else if (lt->tm_year > gmt.tm_year)
3748                 off += 24 * 60;
3749         else if (lt->tm_yday < gmt.tm_yday)
3750                 off -= 24 * 60;
3751         else if (lt->tm_yday > gmt.tm_yday)
3752                 off += 24 * 60;
3753
3754         if (off < 0) {
3755                 sign = '-';
3756                 off = -off;
3757         }
3758
3759         if (off >= 24 * 60)             /* should be impossible */
3760                 off = 23 * 60 + 59;     /* if not, insert silly value */
3761
3762         sprintf(offset_string, "%c%02d%02d", sign, off / 60, off % 60);
3763
3764         return offset_string;
3765 }
3766
3767 void get_rfc822_date(gchar *buf, gint len)
3768 {
3769         struct tm *lt;
3770         time_t t;
3771         gchar day[4], mon[4];
3772         gint dd, hh, mm, ss, yyyy;
3773         struct tm buf1;
3774         gchar buf2[BUFFSIZE];
3775
3776         t = time(NULL);
3777         lt = localtime_r(&t, &buf1);
3778
3779 #ifdef SOLARIS
3780         sscanf(asctime_r(lt, buf2, sizeof(buf2)), "%3s %3s %d %d:%d:%d %d\n",
3781                day, mon, &dd, &hh, &mm, &ss, &yyyy);
3782 #else
3783         sscanf(asctime_r(lt, buf2), "%3s %3s %d %d:%d:%d %d\n",
3784                day, mon, &dd, &hh, &mm, &ss, &yyyy);
3785 #endif
3786         g_snprintf(buf, len, "%s, %d %s %d %02d:%02d:%02d %s",
3787                    day, dd, mon, yyyy, hh, mm, ss, tzoffset(&t));
3788 }
3789
3790 /* just a wrapper to suppress the warning of gcc about %c */
3791 size_t my_strftime(gchar *s, size_t max, const gchar *format,
3792                    const struct tm *tm)
3793 {
3794         return strftime(s, max, format, tm);
3795 }
3796
3797 void debug_set_mode(gboolean mode)
3798 {
3799         debug_mode = mode;
3800 }
3801
3802 gboolean debug_get_mode(void)
3803 {
3804         return debug_mode;
3805 }
3806
3807 void debug_print_real(const gchar *format, ...)
3808 {
3809         va_list args;
3810         gchar buf[BUFFSIZE];
3811
3812         if (!debug_mode) return;
3813
3814         va_start(args, format);
3815         g_vsnprintf(buf, sizeof(buf), format, args);
3816         va_end(args);
3817
3818         g_print("%s", buf);
3819 }
3820
3821
3822 const char * debug_srcname(const char *file)
3823 {
3824         const char *s = strrchr (file, '/');
3825         return s? s+1:file;
3826 }
3827
3828
3829 void * subject_table_lookup(GHashTable *subject_table, gchar * subject)
3830 {
3831         if (subject == NULL)
3832                 subject = "";
3833         else
3834                 subject += subject_get_prefix_length(subject);
3835
3836         return g_hash_table_lookup(subject_table, subject);
3837 }
3838
3839 void subject_table_insert(GHashTable *subject_table, gchar * subject,
3840                           void * data)
3841 {
3842         if (subject == NULL || *subject == 0)
3843                 return;
3844         subject += subject_get_prefix_length(subject);
3845         g_hash_table_insert(subject_table, subject, data);
3846 }
3847
3848 void subject_table_remove(GHashTable *subject_table, gchar * subject)
3849 {
3850         if (subject == NULL)
3851                 return;
3852
3853         subject += subject_get_prefix_length(subject);
3854         g_hash_table_remove(subject_table, subject);
3855 }
3856
3857 /*!
3858  *\brief        Check if a string is prefixed with known (combinations)
3859  *              of prefixes. The function assumes that each prefix
3860  *              is terminated by zero or exactly _one_ space.
3861  *
3862  *\param        str String to check for a prefixes
3863  *
3864  *\return       int Number of chars in the prefix that should be skipped
3865  *              for a "clean" subject line. If no prefix was found, 0
3866  *              is returned.
3867  */
3868 int subject_get_prefix_length(const gchar *subject)
3869 {
3870         /*!< Array with allowable reply prefixes regexps. */
3871         static const gchar * const prefixes[] = {
3872                 "Re\\:",                        /* "Re:" */
3873                 "Re\\[[1-9][0-9]*\\]\\:",       /* "Re[XXX]:" (non-conforming news mail clients) */
3874                 "Antw\\:",                      /* "Antw:" (Dutch / German Outlook) */
3875                 "Aw\\:",                        /* "Aw:"   (German) */
3876                 "Antwort\\:",                   /* "Antwort:" (German Lotus Notes) */
3877                 "Res\\:",                       /* "Res:" (Brazilian Outlook) */
3878                 "Fw\\:",                        /* "Fw:" Forward */
3879                 "Enc\\:",                       /* "Enc:" Forward (Brazilian Outlook) */
3880                 "Odp\\:",                       /* "Odp:" Re (Polish Outlook) */
3881                 "Rif\\:",                       /* "Rif:" (Italian Outlook) */
3882                 "Sv\\:",                        /* "Sv" (Norwegian) */
3883                 "Vs\\:",                        /* "Vs" (Norwegian) */
3884                 "Ad\\:",                        /* "Ad" (Norwegian) */
3885                 "\347\255\224\345\244\215\\:"   /* "Re" (Chinese, UTF-8) */
3886                 /* add more */
3887         };
3888         const int PREFIXES = sizeof prefixes / sizeof prefixes[0];
3889         int n;
3890         regmatch_t pos;
3891         static regex_t regex;
3892         static gboolean init_;
3893
3894         if (!subject) return 0;
3895         if (!*subject) return 0;
3896
3897         if (!init_) {
3898                 GString *s = g_string_new("");
3899
3900                 for (n = 0; n < PREFIXES; n++)
3901                         /* Terminate each prefix regexpression by a
3902                          * "\ ?" (zero or ONE space), and OR them */
3903                         g_string_append_printf(s, "(%s\\ ?)%s",
3904                                           prefixes[n],
3905                                           n < PREFIXES - 1 ?
3906                                           "|" : "");
3907
3908                 g_string_prepend(s, "(");
3909                 g_string_append(s, ")+");       /* match at least once */
3910                 g_string_prepend(s, "^\\ *");   /* from beginning of line */
3911
3912
3913                 /* We now have something like "^\ *((PREFIX1\ ?)|(PREFIX2\ ?))+"
3914                  * TODO: Should this be       "^\ *(((PREFIX1)|(PREFIX2))\ ?)+" ??? */
3915                 if (regcomp(&regex, s->str, REG_EXTENDED | REG_ICASE)) {
3916                         debug_print("Error compiling regexp %s\n", s->str);
3917                         g_string_free(s, TRUE);
3918                         return 0;
3919                 } else {
3920                         init_ = TRUE;
3921                         g_string_free(s, TRUE);
3922                 }
3923         }
3924
3925         if (!regexec(&regex, subject, 1, &pos, 0) && pos.rm_so != -1)
3926                 return pos.rm_eo;
3927         else
3928                 return 0;
3929 }
3930
3931 guint g_stricase_hash(gconstpointer gptr)
3932 {
3933         guint hash_result = 0;
3934         const char *str;
3935
3936         for (str = gptr; str && *str; str++) {
3937                 hash_result += toupper(*str);
3938         }
3939
3940         return hash_result;
3941 }
3942
3943 gint g_stricase_equal(gconstpointer gptr1, gconstpointer gptr2)
3944 {
3945         const char *str1 = gptr1;
3946         const char *str2 = gptr2;
3947
3948         return !strcasecmp(str1, str2);
3949 }
3950
3951 gint g_int_compare(gconstpointer a, gconstpointer b)
3952 {
3953         return GPOINTER_TO_INT(a) - GPOINTER_TO_INT(b);
3954 }
3955
3956 gchar *generate_msgid(gchar *buf, gint len)
3957 {
3958         struct tm *lt;
3959         time_t t;
3960         gchar *addr;
3961         struct tm buft;
3962
3963         t = time(NULL);
3964         lt = localtime_r(&t, &buft);
3965
3966         if (strcmp(buf, "") == 0) {
3967                 addr = g_strconcat("@", get_domain_name(), NULL);
3968         }
3969         else {
3970                 addr = g_strconcat("@", buf, NULL);
3971         }
3972
3973         g_snprintf(buf, len, "%04d%02d%02d%02d%02d%02d.%08x%s",
3974                    lt->tm_year + 1900, lt->tm_mon + 1,
3975                    lt->tm_mday, lt->tm_hour,
3976                    lt->tm_min, lt->tm_sec,
3977                    (guint) rand(), addr);
3978
3979         g_free(addr);
3980         return buf;
3981 }
3982
3983 /*
3984    quote_cmd_argument()
3985
3986    return a quoted string safely usable in argument of a command.
3987
3988    code is extracted and adapted from etPan! project -- DINH V. Hoà.
3989 */
3990
3991 gint quote_cmd_argument(gchar * result, guint size,
3992                         const gchar * path)
3993 {
3994         const gchar * p;
3995         gchar * result_p;
3996         guint remaining;
3997
3998         result_p = result;
3999         remaining = size;
4000
4001         for(p = path ; * p != '\0' ; p ++) {
4002
4003                 if (isalnum((guchar)*p) || (* p == '/')) {
4004                         if (remaining > 0) {
4005                                 * result_p = * p;
4006                                 result_p ++;
4007                                 remaining --;
4008                         }
4009                         else {
4010                                 result[size - 1] = '\0';
4011                                 return -1;
4012                         }
4013                 }
4014                 else {
4015                         if (remaining >= 2) {
4016                                 * result_p = '\\';
4017                                 result_p ++;
4018                                 * result_p = * p;
4019                                 result_p ++;
4020                                 remaining -= 2;
4021                         }
4022                         else {
4023                                 result[size - 1] = '\0';
4024                                 return -1;
4025                         }
4026                 }
4027         }
4028         if (remaining > 0) {
4029                 * result_p = '\0';
4030         }
4031         else {
4032                 result[size - 1] = '\0';
4033                 return -1;
4034         }
4035
4036         return 0;
4037 }
4038
4039 typedef struct
4040 {
4041         GNode           *parent;
4042         GNodeMapFunc     func;
4043         gpointer         data;
4044 } GNodeMapData;
4045
4046 static void g_node_map_recursive(GNode *node, gpointer data)
4047 {
4048         GNodeMapData *mapdata = (GNodeMapData *) data;
4049         GNode *newnode;
4050         GNodeMapData newmapdata;
4051         gpointer newdata;
4052
4053         newdata = mapdata->func(node->data, mapdata->data);
4054         if (newdata != NULL) {
4055                 newnode = g_node_new(newdata);
4056                 g_node_append(mapdata->parent, newnode);
4057
4058                 newmapdata.parent = newnode;
4059                 newmapdata.func = mapdata->func;
4060                 newmapdata.data = mapdata->data;
4061
4062                 g_node_children_foreach(node, G_TRAVERSE_ALL, g_node_map_recursive, &newmapdata);
4063         }
4064 }
4065
4066 GNode *g_node_map(GNode *node, GNodeMapFunc func, gpointer data)
4067 {
4068         GNode *root;
4069         GNodeMapData mapdata;
4070
4071         g_return_val_if_fail(node != NULL, NULL);
4072         g_return_val_if_fail(func != NULL, NULL);
4073
4074         root = g_node_new(func(node->data, data));
4075
4076         mapdata.parent = root;
4077         mapdata.func = func;
4078         mapdata.data = data;
4079
4080         g_node_children_foreach(node, G_TRAVERSE_ALL, g_node_map_recursive, &mapdata);
4081
4082         return root;
4083 }
4084
4085 #define HEX_TO_INT(val, hex)                    \
4086 {                                               \
4087         gchar c = hex;                          \
4088                                                 \
4089         if ('0' <= c && c <= '9') {             \
4090                 val = c - '0';                  \
4091         } else if ('a' <= c && c <= 'f') {      \
4092                 val = c - 'a' + 10;             \
4093         } else if ('A' <= c && c <= 'F') {      \
4094                 val = c - 'A' + 10;             \
4095         } else {                                \
4096                 val = -1;                       \
4097         }                                       \
4098 }
4099
4100 gboolean get_hex_value(guchar *out, gchar c1, gchar c2)
4101 {
4102         gint hi, lo;
4103
4104         HEX_TO_INT(hi, c1);
4105         HEX_TO_INT(lo, c2);
4106
4107         if (hi == -1 || lo == -1)
4108                 return FALSE;
4109
4110         *out = (hi << 4) + lo;
4111         return TRUE;
4112 }
4113
4114 #define INT_TO_HEX(hex, val)            \
4115 {                                       \
4116         if ((val) < 10)                 \
4117                 hex = '0' + (val);      \
4118         else                            \
4119                 hex = 'A' + (val) - 10; \
4120 }
4121
4122 void get_hex_str(gchar *out, guchar ch)
4123 {
4124         gchar hex;
4125
4126         INT_TO_HEX(hex, ch >> 4);
4127         *out++ = hex;
4128         INT_TO_HEX(hex, ch & 0x0f);
4129         *out++ = hex;
4130 }
4131
4132 #undef REF_DEBUG
4133 #ifndef REF_DEBUG
4134 #define G_PRINT_REF 1 == 1 ? (void) 0 : (void)
4135 #else
4136 #define G_PRINT_REF g_print
4137 #endif
4138
4139 /*!
4140  *\brief        Register ref counted pointer. It is based on GBoxed, so should
4141  *              work with anything that uses the GType system. The semantics
4142  *              are similar to a C++ auto pointer, with the exception that
4143  *              C doesn't have automatic closure (calling destructors) when
4144  *              exiting a block scope.
4145  *              Use the \ref G_TYPE_AUTO_POINTER macro instead of calling this
4146  *              function directly.
4147  *
4148  *\return       GType A GType type.
4149  */
4150 GType g_auto_pointer_register(void)
4151 {
4152         static GType auto_pointer_type;
4153         if (!auto_pointer_type)
4154                 auto_pointer_type =
4155                         g_boxed_type_register_static
4156                                 ("G_TYPE_AUTO_POINTER",
4157                                  (GBoxedCopyFunc) g_auto_pointer_copy,
4158                                  (GBoxedFreeFunc) g_auto_pointer_free);
4159         return auto_pointer_type;
4160 }
4161
4162 /*!
4163  *\brief        Structure with g_new() allocated pointer guarded by the
4164  *              auto pointer
4165  */
4166 typedef struct AutoPointerRef {
4167         void          (*free) (gpointer);
4168         gpointer        pointer;
4169         glong           cnt;
4170 } AutoPointerRef;
4171
4172 /*!
4173  *\brief        The auto pointer opaque structure that references the
4174  *              pointer guard block.
4175  */
4176 typedef struct AutoPointer {
4177         AutoPointerRef *ref;
4178         gpointer        ptr; /*!< access to protected pointer */
4179 } AutoPointer;
4180
4181 /*!
4182  *\brief        Creates an auto pointer for a g_new()ed pointer. Example:
4183  *
4184  *\code
4185  *
4186  *              ... tell gtk_list_store it should use a G_TYPE_AUTO_POINTER
4187  *              ... when assigning, copying and freeing storage elements
4188  *
4189  *              gtk_list_store_new(N_S_COLUMNS,
4190  *                                 G_TYPE_AUTO_POINTER,
4191  *                                 -1);
4192  *
4193  *
4194  *              Template *precious_data = g_new0(Template, 1);
4195  *              g_pointer protect = g_auto_pointer_new(precious_data);
4196  *
4197  *              gtk_list_store_set(container, &iter,
4198  *                                 S_DATA, protect,
4199  *                                 -1);
4200  *
4201  *              ... the gtk_list_store has copied the pointer and
4202  *              ... incremented its reference count, we should free
4203  *              ... the auto pointer (in C++ a destructor would do
4204  *              ... this for us when leaving block scope)
4205  *
4206  *              g_auto_pointer_free(protect);
4207  *
4208  *              ... gtk_list_store_set() now manages the data. When
4209  *              ... *explicitly* requesting a pointer from the list
4210  *              ... store, don't forget you get a copy that should be
4211  *              ... freed with g_auto_pointer_free() eventually.
4212  *
4213  *\endcode
4214  *
4215  *\param        pointer Pointer to be guarded.
4216  *
4217  *\return       GAuto * Pointer that should be used in containers with
4218  *              GType support.
4219  */
4220 GAuto *g_auto_pointer_new(gpointer p)
4221 {
4222         AutoPointerRef *ref;
4223         AutoPointer    *ptr;
4224
4225         if (p == NULL)
4226                 return NULL;
4227
4228         ref = g_new0(AutoPointerRef, 1);
4229         ptr = g_new0(AutoPointer, 1);
4230
4231         ref->pointer = p;
4232         ref->free = g_free;
4233         ref->cnt = 1;
4234
4235         ptr->ref = ref;
4236         ptr->ptr = p;
4237
4238 #ifdef REF_DEBUG
4239         G_PRINT_REF ("XXXX ALLOC(%lx)\n", p);
4240 #endif
4241         return ptr;
4242 }
4243
4244 /*!
4245  *\brief        Allocate an autopointer using the passed \a free function to
4246  *              free the guarded pointer
4247  */
4248 GAuto *g_auto_pointer_new_with_free(gpointer p, GFreeFunc free_)
4249 {
4250         AutoPointer *aptr;
4251
4252         if (p == NULL)
4253                 return NULL;
4254
4255         aptr = g_auto_pointer_new(p);
4256         aptr->ref->free = free_;
4257         return aptr;
4258 }
4259
4260 gpointer g_auto_pointer_get_ptr(GAuto *auto_ptr)
4261 {
4262         if (auto_ptr == NULL)
4263                 return NULL;
4264         return ((AutoPointer *) auto_ptr)->ptr;
4265 }
4266
4267 /*!
4268  *\brief        Copies an auto pointer by. It's mostly not necessary
4269  *              to call this function directly, unless you copy/assign
4270  *              the guarded pointer.
4271  *
4272  *\param        auto_ptr Auto pointer returned by previous call to
4273  *              g_auto_pointer_new_XXX()
4274  *
4275  *\return       gpointer An auto pointer
4276  */
4277 GAuto *g_auto_pointer_copy(GAuto *auto_ptr)
4278 {
4279         AutoPointer     *ptr;
4280         AutoPointerRef  *ref;
4281         AutoPointer     *newp;
4282
4283         if (auto_ptr == NULL)
4284                 return NULL;
4285
4286         ptr = auto_ptr;
4287         ref = ptr->ref;
4288         newp = g_new0(AutoPointer, 1);
4289
4290         newp->ref = ref;
4291         newp->ptr = ref->pointer;
4292         ++(ref->cnt);
4293
4294 #ifdef REF_DEBUG
4295         G_PRINT_REF ("XXXX COPY(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4296 #endif
4297         return newp;
4298 }
4299
4300 /*!
4301  *\brief        Free an auto pointer
4302  */
4303 void g_auto_pointer_free(GAuto *auto_ptr)
4304 {
4305         AutoPointer     *ptr;
4306         AutoPointerRef  *ref;
4307
4308         if (auto_ptr == NULL)
4309                 return;
4310
4311         ptr = auto_ptr;
4312         ref = ptr->ref;
4313
4314         if (--(ref->cnt) == 0) {
4315 #ifdef REF_DEBUG
4316                 G_PRINT_REF ("XXXX FREE(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4317 #endif
4318                 ref->free(ref->pointer);
4319                 g_free(ref);
4320         }
4321 #ifdef REF_DEBUG
4322         else
4323                 G_PRINT_REF ("XXXX DEREF(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4324 #endif
4325         g_free(ptr);
4326 }
4327
4328 void replace_returns(gchar *str)
4329 {
4330         if (!str)
4331                 return;
4332
4333         while (strstr(str, "\n")) {
4334                 *strstr(str, "\n") = ' ';
4335         }
4336         while (strstr(str, "\r")) {
4337                 *strstr(str, "\r") = ' ';
4338         }
4339 }
4340
4341 /* get_uri_part() - retrieves a URI starting from scanpos.
4342                     Returns TRUE if succesful */
4343 gboolean get_uri_part(const gchar *start, const gchar *scanpos,
4344                              const gchar **bp, const gchar **ep, gboolean hdr)
4345 {
4346         const gchar *ep_;
4347         gint parenthese_cnt = 0;
4348
4349         g_return_val_if_fail(start != NULL, FALSE);
4350         g_return_val_if_fail(scanpos != NULL, FALSE);
4351         g_return_val_if_fail(bp != NULL, FALSE);
4352         g_return_val_if_fail(ep != NULL, FALSE);
4353
4354         *bp = scanpos;
4355
4356         /* find end point of URI */
4357         for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
4358                 if (!g_ascii_isgraph(*(const guchar *)ep_) ||
4359                     !IS_ASCII(*(const guchar *)ep_) ||
4360                     strchr("[]{}<>\"", *ep_)) {
4361                         break;
4362                 } else if (strchr("(", *ep_)) {
4363                         parenthese_cnt++;
4364                 } else if (strchr(")", *ep_)) {
4365                         if (parenthese_cnt > 0)
4366                                 parenthese_cnt--;
4367                         else
4368                                 break;
4369                 }
4370         }
4371
4372         /* no punctuation at end of string */
4373
4374         /* FIXME: this stripping of trailing punctuations may bite with other URIs.
4375          * should pass some URI type to this function and decide on that whether
4376          * to perform punctuation stripping */
4377
4378 #define IS_REAL_PUNCT(ch)       (g_ascii_ispunct(ch) && !strchr("/?=-)", ch))
4379
4380         for (; ep_ - 1 > scanpos + 1 &&
4381                IS_REAL_PUNCT(*(ep_ - 1));
4382              ep_--)
4383                 ;
4384
4385 #undef IS_REAL_PUNCT
4386
4387         *ep = ep_;
4388
4389         return TRUE;
4390 }
4391
4392 gchar *make_uri_string(const gchar *bp, const gchar *ep)
4393 {
4394         while (bp && *bp && g_ascii_isspace(*bp))
4395                 bp++;
4396         return g_strndup(bp, ep - bp);
4397 }
4398
4399 /* valid mail address characters */
4400 #define IS_RFC822_CHAR(ch) \
4401         (IS_ASCII(ch) && \
4402          (ch) > 32   && \
4403          (ch) != 127 && \
4404          !g_ascii_isspace(ch) && \
4405          !strchr("(),;<>\"", (ch)))
4406
4407 /* alphabet and number within 7bit ASCII */
4408 #define IS_ASCII_ALNUM(ch)      (IS_ASCII(ch) && g_ascii_isalnum(ch))
4409 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
4410
4411 static GHashTable *create_domain_tab(void)
4412 {
4413         static const gchar *toplvl_domains [] = {
4414             "museum", "aero",
4415             "arpa", "coop", "info", "name", "biz", "com", "edu", "gov",
4416             "int", "mil", "net", "org", "ac", "ad", "ae", "af", "ag",
4417             "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au",
4418             "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi",
4419             "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
4420             "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl",
4421             "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz", "de",
4422             "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er",
4423             "es", "et", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd",
4424             "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
4425             "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
4426             "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir",
4427             "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
4428             "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc",
4429             "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
4430             "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq",
4431             "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na",
4432             "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu",
4433             "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm",
4434             "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru",
4435             "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj",
4436             "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
4437             "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to",
4438             "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um",
4439             "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu",
4440             "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw"
4441         };
4442         gint n;
4443         GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
4444
4445         g_return_val_if_fail(htab, NULL);
4446         for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++)
4447                 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
4448         return htab;
4449 }
4450
4451 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
4452 {
4453         const gint MAX_LVL_DOM_NAME_LEN = 6;
4454         gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
4455         const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
4456         register gchar *p;
4457
4458         if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
4459                 return FALSE;
4460
4461         for (p = buf; p < m &&  first < last; *p++ = *first++)
4462                 ;
4463         *p = 0;
4464
4465         return g_hash_table_lookup(tab, buf) != NULL;
4466 }
4467
4468 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
4469 gboolean get_email_part(const gchar *start, const gchar *scanpos,
4470                                const gchar **bp, const gchar **ep, gboolean hdr)
4471 {
4472         /* more complex than the uri part because we need to scan back and forward starting from
4473          * the scan position. */
4474         gboolean result = FALSE;
4475         const gchar *bp_ = NULL;
4476         const gchar *ep_ = NULL;
4477         static GHashTable *dom_tab;
4478         const gchar *last_dot = NULL;
4479         const gchar *prelast_dot = NULL;
4480         const gchar *last_tld_char = NULL;
4481
4482         /* the informative part of the email address (describing the name
4483          * of the email address owner) may contain quoted parts. the
4484          * closure stack stores the last encountered quotes. */
4485         gchar closure_stack[128];
4486         gchar *ptr = closure_stack;
4487
4488         g_return_val_if_fail(start != NULL, FALSE);
4489         g_return_val_if_fail(scanpos != NULL, FALSE);
4490         g_return_val_if_fail(bp != NULL, FALSE);
4491         g_return_val_if_fail(ep != NULL, FALSE);
4492
4493         if (hdr) {
4494                 const gchar *start_quote = NULL;
4495                 const gchar *end_quote = NULL;
4496 search_again:
4497                 /* go to the real start */
4498                 if (start[0] == ',')
4499                         start++;
4500                 if (start[0] == ';')
4501                         start++;
4502                 while (start[0] == '\n' || start[0] == '\r')
4503                         start++;
4504                 while (start[0] == ' ' || start[0] == '\t')
4505                         start++;
4506
4507                 *bp = start;
4508                 
4509                 /* check if there are quotes (to skip , in them) */
4510                 if (*start == '"') {
4511                         start_quote = start;
4512                         start++;
4513                         end_quote = strstr(start, "\"");
4514                 } else {
4515                         start_quote = NULL;
4516                         end_quote = NULL;
4517                 }
4518                 
4519                 /* skip anything between quotes */
4520                 if (start_quote && end_quote) {
4521                         start = end_quote;
4522                         
4523                 } 
4524
4525                 /* find end (either , or ; or end of line) */
4526                 if (strstr(start, ",") && strstr(start, ";"))
4527                         *ep = strstr(start,",") < strstr(start, ";")
4528                                 ? strstr(start, ",") : strstr(start, ";");
4529                 else if (strstr(start, ","))
4530                         *ep = strstr(start, ",");
4531                 else if (strstr(start, ";"))
4532                         *ep = strstr(start, ";");
4533                 else
4534                         *ep = start+strlen(start);
4535
4536                 /* go back to real start */
4537                 if (start_quote && end_quote) {
4538                         start = start_quote;
4539                 }
4540
4541                 /* check there's still an @ in that, or search
4542                  * further if possible */
4543                 if (strstr(start, "@") && strstr(start, "@") < *ep)
4544                         return TRUE;
4545                 else if (*ep < start+strlen(start)) {
4546                         start = *ep;
4547                         goto search_again;
4548                 } else if (start_quote && strstr(start, "\"") && strstr(start, "\"") < *ep) {
4549                         *bp = start_quote;
4550                         return TRUE;
4551                 } else
4552                         return FALSE;
4553         }
4554
4555         if (!dom_tab)
4556                 dom_tab = create_domain_tab();
4557         g_return_val_if_fail(dom_tab, FALSE);
4558
4559         /* scan start of address */
4560         for (bp_ = scanpos - 1;
4561              bp_ >= start && IS_RFC822_CHAR(*(const guchar *)bp_); bp_--)
4562                 ;
4563
4564         /* TODO: should start with an alnum? */
4565         bp_++;
4566         for (; bp_ < scanpos && !IS_ASCII_ALNUM(*(const guchar *)bp_); bp_++)
4567                 ;
4568
4569         if (bp_ != scanpos) {
4570                 /* scan end of address */
4571                 for (ep_ = scanpos + 1;
4572                      *ep_ && IS_RFC822_CHAR(*(const guchar *)ep_); ep_++)
4573                         if (*ep_ == '.') {
4574                                 prelast_dot = last_dot;
4575                                 last_dot = ep_;
4576                                 if (*(last_dot + 1) == '.') {
4577                                         if (prelast_dot == NULL)
4578                                                 return FALSE;
4579                                         last_dot = prelast_dot;
4580                                         break;
4581                                 }
4582                         }
4583
4584                 /* TODO: really should terminate with an alnum? */
4585                 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*(const guchar *)ep_);
4586                      --ep_)
4587                         ;
4588                 ep_++;
4589
4590                 if (last_dot == NULL)
4591                         return FALSE;
4592                 if (last_dot >= ep_)
4593                         last_dot = prelast_dot;
4594                 if (last_dot == NULL || (scanpos + 1 >= last_dot))
4595                         return FALSE;
4596                 last_dot++;
4597
4598                 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
4599                         if (*last_tld_char == '?')
4600                                 break;
4601
4602                 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
4603                         result = TRUE;
4604
4605                 *ep = ep_;
4606                 *bp = bp_;
4607         }
4608
4609         if (!result) return FALSE;
4610
4611         if (*ep_ && bp_ != start && *(bp_ - 1) == '"' && *(ep_) == '"'
4612         && *(ep_ + 1) == ' ' && *(ep_ + 2) == '<'
4613         && IS_RFC822_CHAR(*(ep_ + 3))) {
4614                 /* this informative part with an @ in it is
4615                  * followed by the email address */
4616                 ep_ += 3;
4617
4618                 /* go to matching '>' (or next non-rfc822 char, like \n) */
4619                 for (; *ep_ != '>' && *ep != '\0' && IS_RFC822_CHAR(*ep_); ep_++)
4620                         ;
4621
4622                 /* include the bracket */
4623                 if (*ep_ == '>') ep_++;
4624
4625                 /* include the leading quote */
4626                 bp_--;
4627
4628                 *ep = ep_;
4629                 *bp = bp_;
4630                 return TRUE;
4631         }
4632
4633         /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
4634         if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
4635                 return FALSE;
4636
4637         /* see if this is <bracketed>; in this case we also scan for the informative part. */
4638         if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
4639                 return TRUE;
4640
4641 #define FULL_STACK()    ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
4642 #define IN_STACK()      (ptr > closure_stack)
4643 /* has underrun check */
4644 #define POP_STACK()     if(IN_STACK()) --ptr
4645 /* has overrun check */
4646 #define PUSH_STACK(c)   if(!FULL_STACK()) *ptr++ = (c); else return TRUE
4647 /* has underrun check */
4648 #define PEEK_STACK()    (IN_STACK() ? *(ptr - 1) : 0)
4649
4650         ep_++;
4651
4652         /* scan for the informative part. */
4653         for (bp_ -= 2; bp_ >= start; bp_--) {
4654                 /* if closure on the stack keep scanning */
4655                 if (PEEK_STACK() == *bp_) {
4656                         POP_STACK();
4657                         continue;
4658                 }
4659                 if (*bp_ == '\'' || *bp_ == '"') {
4660                         PUSH_STACK(*bp_);
4661                         continue;
4662                 }
4663
4664                 /* if nothing in the closure stack, do the special conditions
4665                  * the following if..else expression simply checks whether
4666                  * a token is acceptable. if not acceptable, the clause
4667                  * should terminate the loop with a 'break' */
4668                 if (!PEEK_STACK()) {
4669                         if (*bp_ == '-'
4670                         && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
4671                         && (((bp_ + 1) < ep_)    && isalnum(*(bp_ + 1)))) {
4672                                 /* hyphens are allowed, but only in
4673                                    between alnums */
4674                         } else if (strchr(" \"'", *bp_)) {
4675                                 /* but anything not being a punctiation
4676                                    is ok */
4677                         } else {
4678                                 break; /* anything else is rejected */
4679                         }
4680                 }
4681         }
4682
4683         bp_++;
4684
4685         /* scan forward (should start with an alnum) */
4686         for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
4687                 ;
4688 #undef PEEK_STACK
4689 #undef PUSH_STACK
4690 #undef POP_STACK
4691 #undef IN_STACK
4692 #undef FULL_STACK
4693
4694
4695         *bp = bp_;
4696         *ep = ep_;
4697
4698         return result;
4699 }
4700
4701 #undef IS_QUOTE
4702 #undef IS_ASCII_ALNUM
4703 #undef IS_RFC822_CHAR
4704
4705 gchar *make_email_string(const gchar *bp, const gchar *ep)
4706 {
4707         /* returns a mailto: URI; mailto: is also used to detect the
4708          * uri type later on in the button_pressed signal handler */
4709         gchar *tmp;
4710         gchar *result;
4711
4712         tmp = g_strndup(bp, ep - bp);
4713         result = g_strconcat("mailto:", tmp, NULL);
4714         g_free(tmp);
4715
4716         return result;
4717 }
4718
4719 gchar *make_http_string(const gchar *bp, const gchar *ep)
4720 {
4721         /* returns an http: URI; */
4722         gchar *tmp;
4723         gchar *result;
4724
4725         while (bp && *bp && g_ascii_isspace(*bp))
4726                 bp++;
4727         tmp = g_strndup(bp, ep - bp);
4728         result = g_strconcat("http://", tmp, NULL);
4729         g_free(tmp);
4730
4731         return result;
4732 }
4733
4734 static gchar *mailcap_get_command_in_file(const gchar *path, const gchar *type, const gchar *file_to_open)
4735 {
4736         FILE *fp = fopen(path, "rb");
4737         gchar buf[BUFFSIZE];
4738         gchar *result = NULL;
4739         if (!fp)
4740                 return NULL;
4741         while (fgets(buf, sizeof (buf), fp) != NULL) {
4742                 gchar **parts = g_strsplit(buf, ";", 3);
4743                 gchar *trimmed = parts[0];
4744                 while (trimmed[0] == ' ' || trimmed[0] == '\t')
4745                         trimmed++;
4746                 while (trimmed[strlen(trimmed)-1] == ' ' || trimmed[strlen(trimmed)-1] == '\t')
4747                         trimmed[strlen(trimmed)-1] = '\0';
4748
4749                 if (!strcmp(trimmed, type)) {
4750                         gboolean needsterminal = FALSE;
4751                         if (parts[2] && strstr(parts[2], "needsterminal")) {
4752                                 needsterminal = TRUE;
4753                         }
4754                         if (parts[2] && strstr(parts[2], "test=")) {
4755                                 gchar *orig_testcmd = g_strdup(strstr(parts[2], "test=")+5);
4756                                 gchar *testcmd = orig_testcmd;
4757                                 if (strstr(testcmd,";"))
4758                                         *(strstr(testcmd,";")) = '\0';
4759                                 while (testcmd[0] == ' ' || testcmd[0] == '\t')
4760                                         testcmd++;
4761                                 while (testcmd[strlen(testcmd)-1] == '\n')
4762                                         testcmd[strlen(testcmd)-1] = '\0';
4763                                 while (testcmd[strlen(testcmd)-1] == '\r')
4764                                         testcmd[strlen(testcmd)-1] = '\0';
4765                                 while (testcmd[strlen(testcmd)-1] == ' ' || testcmd[strlen(testcmd)-1] == '\t')
4766                                         testcmd[strlen(testcmd)-1] = '\0';
4767                                         
4768                                 if (strstr(testcmd, "%s")) {
4769                                         gchar *tmp = g_strdup_printf(testcmd, file_to_open);
4770                                         gint res = system(tmp);
4771                                         g_free(tmp);
4772                                         g_free(orig_testcmd);
4773                                         
4774                                         if (res != 0) {
4775                                                 g_strfreev(parts);
4776                                                 continue;
4777                                         }
4778                                 } else {
4779                                         gint res = system(testcmd);
4780                                         g_free(orig_testcmd);
4781                                         
4782                                         if (res != 0) {
4783                                                 g_strfreev(parts);
4784                                                 continue;
4785                                         }
4786                                 }
4787                         }
4788                         
4789                         trimmed = parts[1];
4790                         while (trimmed[0] == ' ' || trimmed[0] == '\t')
4791                                 trimmed++;
4792                         while (trimmed[strlen(trimmed)-1] == '\n')
4793                                 trimmed[strlen(trimmed)-1] = '\0';
4794                         while (trimmed[strlen(trimmed)-1] == '\r')
4795                                 trimmed[strlen(trimmed)-1] = '\0';
4796                         while (trimmed[strlen(trimmed)-1] == ' ' || trimmed[strlen(trimmed)-1] == '\t')
4797                                 trimmed[strlen(trimmed)-1] = '\0';
4798                         result = g_strdup(trimmed);
4799                         g_strfreev(parts);
4800                         fclose(fp);
4801                         /* if there are no single quotes around %s, add them.
4802                          * '.*%s.*' is ok, as in display 'png:%s'
4803                          */
4804                         if (strstr(result, "%s") 
4805                         && !(strstr(result, "'") < strstr(result,"%s") &&
4806                              strstr(strstr(result,"%s"), "'"))) {
4807                                 gchar *start = g_strdup(result);
4808                                 gchar *end = g_strdup(strstr(result, "%s")+2);
4809                                 gchar *tmp;
4810                                 *strstr(start, "%s") = '\0';
4811                                 tmp = g_strconcat(start,"'%s'",end, NULL);
4812                                 g_free(start);
4813                                 g_free(end);
4814                                 g_free(result);
4815                                 result = tmp;
4816                         }
4817                         if (needsterminal) {
4818                                 gchar *tmp = g_strdup_printf("xterm -e %s", result);
4819                                 g_free(result);
4820                                 result = tmp;
4821                         }
4822                         return result;
4823                 }
4824                 g_strfreev(parts);
4825         }
4826         fclose(fp);
4827         return NULL;
4828 }
4829 gchar *mailcap_get_command_for_type(const gchar *type, const gchar *file_to_open)
4830 {
4831         gchar *result = NULL;
4832         gchar *path = NULL;
4833         if (type == NULL)
4834                 return NULL;
4835         path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap", NULL);
4836         result = mailcap_get_command_in_file(path, type, file_to_open);
4837         g_free(path);
4838         if (result)
4839                 return result;
4840         result = mailcap_get_command_in_file("/etc/mailcap", type, file_to_open);
4841         return result;
4842 }
4843
4844 void mailcap_update_default(const gchar *type, const gchar *command)
4845 {
4846         gchar *path = NULL, *outpath = NULL;
4847         path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap", NULL);
4848         outpath = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap.new", NULL);
4849         FILE *fp = fopen(path, "rb");
4850         FILE *outfp = fopen(outpath, "wb");
4851         gchar buf[BUFFSIZE];
4852         gboolean err = FALSE;
4853
4854         if (!outfp) {
4855                 g_free(path);
4856                 g_free(outpath);
4857                 fclose(fp);
4858                 return;
4859         }
4860         while (fp && fgets(buf, sizeof (buf), fp) != NULL) {
4861                 gchar **parts = g_strsplit(buf, ";", 3);
4862                 gchar *trimmed = parts[0];
4863                 while (trimmed[0] == ' ')
4864                         trimmed++;
4865                 while (trimmed[strlen(trimmed)-1] == ' ')
4866                         trimmed[strlen(trimmed)-1] = '\0';
4867
4868                 if (!strcmp(trimmed, type)) {
4869                         g_strfreev(parts);
4870                         continue;
4871                 }
4872                 else {
4873                         if(fputs(buf, outfp) == EOF) {
4874                                 err = TRUE;
4875                                 break;
4876                         }
4877                 }
4878                 g_strfreev(parts);
4879         }
4880         if (fprintf(outfp, "%s; %s\n", type, command) < 0)
4881                 err = TRUE;
4882
4883         if (fp)
4884                 fclose(fp);
4885
4886         if (fclose(outfp) == EOF)
4887                 err = TRUE;
4888                 
4889         if (!err)
4890                 g_rename(outpath, path);
4891
4892         g_free(path);
4893         g_free(outpath);
4894 }
4895
4896 gint copy_dir(const gchar *src, const gchar *dst)
4897 {
4898         GDir *dir;
4899         const gchar *name;
4900
4901         if ((dir = g_dir_open(src, 0, NULL)) == NULL) {
4902                 g_warning("failed to open directory: %s\n", src);
4903                 return -1;
4904         }
4905
4906         if (make_dir(dst) < 0)
4907                 return -1;
4908
4909         while ((name = g_dir_read_name(dir)) != NULL) {
4910                 gchar *old_file, *new_file;
4911                 old_file = g_strconcat(src, G_DIR_SEPARATOR_S, name, NULL);
4912                 new_file = g_strconcat(dst, G_DIR_SEPARATOR_S, name, NULL);
4913                 debug_print("copying: %s -> %s\n", old_file, new_file);
4914                 if (g_file_test(old_file, G_FILE_TEST_IS_REGULAR)) {
4915                         gint r = copy_file(old_file, new_file, TRUE);
4916                         if (r < 0) {
4917                                 g_dir_close(dir);
4918                                 return r;
4919                         }
4920                 }
4921 #ifndef G_OS_WIN32
4922                 /* Windows has no symlinks.  Or well, Vista seems to
4923                    have something like this but the semantics might be
4924                    different.  Thus we don't use it under Windows. */
4925                  else if (g_file_test(old_file, G_FILE_TEST_IS_SYMLINK)) {
4926                         GError *error;
4927                         gint r = 0;
4928                         gchar *target = g_file_read_link(old_file, &error);
4929                         if (target)
4930                                 r = symlink(target, new_file);
4931                         g_free(target);
4932                         if (r < 0) {
4933                                 g_dir_close(dir);
4934                                 return r;
4935                         }
4936                  }
4937 #endif /*G_OS_WIN32*/
4938                 else if (g_file_test(old_file, G_FILE_TEST_IS_DIR)) {
4939                         gint r = copy_dir(old_file, new_file);
4940                         if (r < 0) {
4941                                 g_dir_close(dir);
4942                                 return r;
4943                         }
4944                 }
4945         }
4946         g_dir_close(dir);
4947         return 0;
4948 }
4949
4950 /* crude test to see if a file is an email. */
4951 gboolean file_is_email (const gchar *filename)
4952 {
4953         FILE *fp = NULL;
4954         gchar buffer[2048];
4955         gint i = 0;
4956         gint score = 0;
4957         if (filename == NULL)
4958                 return FALSE;
4959         if ((fp = g_fopen(filename, "rb")) == NULL)
4960                 return FALSE;
4961         while (i < 60 && score < 3
4962                && fgets(buffer, sizeof (buffer), fp) > 0) {
4963                 if (!strncmp(buffer, "From:", strlen("From:")))
4964                         score++;
4965                 if (!strncmp(buffer, "To:", strlen("To:")))
4966                         score++;
4967                 if (!strncmp(buffer, "Subject:", strlen("Subject:")))
4968                         score++;
4969                 i++;
4970         }
4971         fclose(fp);
4972         return (score >= 3);
4973 }
4974
4975 gboolean sc_g_list_bigger(GList *list, gint max)
4976 {
4977         GList *cur = list;
4978         int i = 0;
4979         while (cur && i <= max+1) {
4980                 i++;
4981                 cur = cur->next;
4982         }
4983         return (i > max);
4984 }
4985
4986 gboolean sc_g_slist_bigger(GSList *list, gint max)
4987 {
4988         GSList *cur = list;
4989         int i = 0;
4990         while (cur && i <= max+1) {
4991                 i++;
4992                 cur = cur->next;
4993         }
4994         return (i > max);
4995 }
4996
4997 const gchar *daynames[] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL};
4998 const gchar *monthnames[] = {NULL, NULL, NULL, NULL, NULL, NULL, 
4999                              NULL, NULL, NULL, NULL, NULL, NULL};
5000 const gchar *s_daynames[] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL};
5001 const gchar *s_monthnames[] = {NULL, NULL, NULL, NULL, NULL, NULL, 
5002                              NULL, NULL, NULL, NULL, NULL, NULL};
5003 const gchar *s_am_up = NULL;
5004 const gchar *s_pm_up = NULL;
5005 const gchar *s_am_low = NULL;
5006 const gchar *s_pm_low = NULL;
5007 const gchar *def_loc_format = NULL;
5008 const gchar *date_loc_format = NULL;
5009 const gchar *time_loc_format = NULL;
5010 const gchar *time_am_pm = NULL;
5011
5012 static gboolean time_names_init_done = FALSE;
5013
5014 static void init_time_names(void)
5015 {
5016         daynames[0] = Q_("Complete day name for use by strftime|Sunday");
5017         daynames[1] = Q_("Complete day name for use by strftime|Monday");
5018         daynames[2] = Q_("Complete day name for use by strftime|Tuesday");
5019         daynames[3] = Q_("Complete day name for use by strftime|Wednesday");
5020         daynames[4] = Q_("Complete day name for use by strftime|Thursday");
5021         daynames[5] = Q_("Complete day name for use by strftime|Friday");
5022         daynames[6] = Q_("Complete day name for use by strftime|Saturday");
5023         
5024         monthnames[0] = Q_("Complete month name for use by strftime|January");
5025         monthnames[1] = Q_("Complete month name for use by strftime|February");
5026         monthnames[2] = Q_("Complete month name for use by strftime|March");
5027         monthnames[3] = Q_("Complete month name for use by strftime|April");
5028         monthnames[4] = Q_("Complete month name for use by strftime|May");
5029         monthnames[5] = Q_("Complete month name for use by strftime|June");
5030         monthnames[6] = Q_("Complete month name for use by strftime|July");
5031         monthnames[7] = Q_("Complete month name for use by strftime|August");
5032         monthnames[8] = Q_("Complete month name for use by strftime|September");
5033         monthnames[9] = Q_("Complete month name for use by strftime|October");
5034         monthnames[10] = Q_("Complete month name for use by strftime|November");
5035         monthnames[11] = Q_("Complete month name for use by strftime|December");
5036
5037         s_daynames[0] = Q_("Abbr. day name for use by strftime|Sun");
5038         s_daynames[1] = Q_("Abbr. day name for use by strftime|Mon");
5039         s_daynames[2] = Q_("Abbr. day name for use by strftime|Tue");
5040         s_daynames[3] = Q_("Abbr. day name for use by strftime|Wed");
5041         s_daynames[4] = Q_("Abbr. day name for use by strftime|Thu");
5042         s_daynames[5] = Q_("Abbr. day name for use by strftime|Fri");
5043         s_daynames[6] = Q_("Abbr. day name for use by strftime|Sat");
5044         
5045         s_monthnames[0] = Q_("Abbr. month name for use by strftime|Jan");
5046         s_monthnames[1] = Q_("Abbr. month name for use by strftime|Feb");
5047         s_monthnames[2] = Q_("Abbr. month name for use by strftime|Mar");
5048         s_monthnames[3] = Q_("Abbr. month name for use by strftime|Apr");
5049         s_monthnames[4] = Q_("Abbr. month name for use by strftime|May");
5050         s_monthnames[5] = Q_("Abbr. month name for use by strftime|Jun");
5051         s_monthnames[6] = Q_("Abbr. month name for use by strftime|Jul");
5052         s_monthnames[7] = Q_("Abbr. month name for use by strftime|Aug");
5053         s_monthnames[8] = Q_("Abbr. month name for use by strftime|Sep");
5054         s_monthnames[9] = Q_("Abbr. month name for use by strftime|Oct");
5055         s_monthnames[10] = Q_("Abbr. month name for use by strftime|Nov");
5056         s_monthnames[11] = Q_("Abbr. month name for use by strftime|Dec");
5057
5058         s_am_up = Q_("For use by strftime (morning)|AM");
5059         s_pm_up = Q_("For use by strftime (afternoon)|PM");
5060         s_am_low = Q_("For use by strftime (morning, lowercase)|am");
5061         s_pm_low = Q_("For use by strftime (afternoon, lowercase)|pm");
5062         
5063         def_loc_format = Q_("For use by strftime (default date+time format)|%a %b %e %H:%M:%S %Y");
5064         date_loc_format = Q_("For use by strftime (default date format)|%m/%d/%y");
5065         time_loc_format = Q_("For use by strftime (default time format)|%H:%M:%S");
5066
5067         time_am_pm = Q_("For use by strftime (default 12-hour time format)|%I:%M:%S %p");
5068
5069         time_names_init_done = TRUE;
5070 }
5071
5072 #define CHECK_SIZE() {                  \
5073         total_done += len;              \
5074         if (total_done >= buflen) {     \
5075                 buf[buflen-1] = '\0';   \
5076                 return 0;               \
5077         }                               \
5078 }
5079
5080 size_t fast_strftime(gchar *buf, gint buflen, const gchar *format, struct tm *lt)
5081 {
5082         gchar *curpos = buf;
5083         gint total_done = 0;
5084         gchar subbuf[64], subfmt[64];
5085         static time_t last_tzset = (time_t)0;
5086         
5087         if (!time_names_init_done)
5088                 init_time_names();
5089         
5090         if (format == NULL || lt == NULL)
5091                 return 0;
5092                 
5093         if (last_tzset != time(NULL)) {
5094                 tzset();
5095                 last_tzset = time(NULL);
5096         }
5097         while(*format) {
5098                 if (*format == '%') {
5099                         gint len = 0, tmp = 0;
5100                         format++;
5101                         switch(*format) {
5102                         case '%':
5103                                 len = 1; CHECK_SIZE();
5104                                 *curpos = '%';
5105                                 break;
5106                         case 'a':
5107                                 len = strlen(s_daynames[lt->tm_wday]); CHECK_SIZE();
5108                                 strncpy2(curpos, s_daynames[lt->tm_wday], buflen - total_done);
5109                                 break;
5110                         case 'A':
5111                                 len = strlen(daynames[lt->tm_wday]); CHECK_SIZE();
5112                                 strncpy2(curpos, daynames[lt->tm_wday], buflen - total_done);
5113                                 break;
5114                         case 'b':
5115                         case 'h':
5116                                 len = strlen(s_monthnames[lt->tm_mon]); CHECK_SIZE();
5117                                 strncpy2(curpos, s_monthnames[lt->tm_mon], buflen - total_done);
5118                                 break;
5119                         case 'B':
5120                                 len = strlen(monthnames[lt->tm_mon]); CHECK_SIZE();
5121                                 strncpy2(curpos, monthnames[lt->tm_mon], buflen - total_done);
5122                                 break;
5123                         case 'c':
5124                                 fast_strftime(subbuf, 64, def_loc_format, lt);
5125                                 len = strlen(subbuf); CHECK_SIZE();
5126                                 strncpy2(curpos, subbuf, buflen - total_done);
5127                                 break;
5128                         case 'C':
5129                                 total_done += 2; CHECK_SIZE();
5130                                 tmp = (lt->tm_year + 1900)/100;
5131                                 *curpos++ = '0'+(tmp / 10);
5132                                 *curpos++ = '0'+(tmp % 10);
5133                                 break;
5134                         case 'd':
5135                                 total_done += 2; CHECK_SIZE();
5136                                 *curpos++ = '0'+(lt->tm_mday / 10);
5137                                 *curpos++ = '0'+(lt->tm_mday % 10);
5138                                 break;
5139                         case 'D':
5140                                 total_done += 8; CHECK_SIZE();
5141                                 *curpos++ = '0'+((lt->tm_mon+1) / 10);
5142                                 *curpos++ = '0'+((lt->tm_mon+1) % 10);
5143                                 *curpos++ = '/';
5144                                 *curpos++ = '0'+(lt->tm_mday / 10);
5145                                 *curpos++ = '0'+(lt->tm_mday % 10);
5146                                 *curpos++ = '/';
5147                                 tmp = lt->tm_year%100;
5148                                 *curpos++ = '0'+(tmp / 10);
5149                                 *curpos++ = '0'+(tmp % 10);
5150                                 break;
5151                         case 'e':
5152                                 len = 2; CHECK_SIZE();
5153                                 snprintf(curpos, buflen - total_done, "%2d", lt->tm_mday);
5154                                 break;
5155                         case 'F':
5156                                 len = 10; CHECK_SIZE();
5157                                 snprintf(curpos, buflen - total_done, "%4d-%02d-%02d", 
5158                                         lt->tm_year + 1900, lt->tm_mon +1, lt->tm_mday);
5159                                 break;
5160                         case 'H':
5161                                 total_done += 2; CHECK_SIZE();
5162                                 *curpos++ = '0'+(lt->tm_hour / 10);
5163                                 *curpos++ = '0'+(lt->tm_hour % 10);
5164                                 break;
5165                         case 'I':
5166                                 total_done += 2; CHECK_SIZE();
5167                                 tmp = lt->tm_hour;
5168                                 if (tmp > 12)
5169                                         tmp -= 12;
5170                                 else if (tmp == 0)
5171                                         tmp = 12;
5172                                 *curpos++ = '0'+(tmp / 10);
5173                                 *curpos++ = '0'+(tmp % 10);
5174                                 break;
5175                         case 'j':
5176                                 len = 3; CHECK_SIZE();
5177                                 snprintf(curpos, buflen - total_done, "%03d", lt->tm_yday+1);
5178                                 break;
5179                         case 'k':
5180                                 len = 2; CHECK_SIZE();
5181                                 snprintf(curpos, buflen - total_done, "%2d", lt->tm_hour);
5182                                 break;
5183                         case 'l':
5184                                 len = 2; CHECK_SIZE();
5185                                 tmp = lt->tm_hour;
5186                                 if (tmp > 12)
5187                                         tmp -= 12;
5188                                 else if (tmp == 0)
5189                                         tmp = 12;
5190                                 snprintf(curpos, buflen - total_done, "%2d", tmp);
5191                                 break;
5192                         case 'm':
5193                                 total_done += 2; CHECK_SIZE();
5194                                 tmp = lt->tm_mon + 1;
5195                                 *curpos++ = '0'+(tmp / 10);
5196                                 *curpos++ = '0'+(tmp % 10);
5197                                 break;
5198                         case 'M':
5199                                 total_done += 2; CHECK_SIZE();
5200                                 *curpos++ = '0'+(lt->tm_min / 10);
5201                                 *curpos++ = '0'+(lt->tm_min % 10);
5202                                 break;
5203                         case 'n':
5204                                 len = 1; CHECK_SIZE();
5205                                 *curpos = '\n';
5206                                 break;
5207                         case 'p':
5208                                 if (lt->tm_hour >= 12) {
5209                                         len = strlen(s_pm_up); CHECK_SIZE();
5210                                         snprintf(curpos, buflen-total_done, s_pm_up);
5211                                 } else {
5212                                         len = strlen(s_am_up); CHECK_SIZE();
5213                                         snprintf(curpos, buflen-total_done, s_am_up);
5214                                 }
5215                                 break;
5216                         case 'P':
5217                                 if (lt->tm_hour >= 12) {
5218                                         len = strlen(s_pm_low); CHECK_SIZE();
5219                                         snprintf(curpos, buflen-total_done, s_pm_low);
5220                                 } else {
5221                                         len = strlen(s_am_low); CHECK_SIZE();
5222                                         snprintf(curpos, buflen-total_done, s_am_low);
5223                                 }
5224                                 break;
5225                         case 'r':
5226                                 fast_strftime(subbuf, 64, time_am_pm, lt);
5227                                 len = strlen(subbuf); CHECK_SIZE();
5228                                 strncpy2(curpos, subbuf, buflen - total_done);
5229                                 break;
5230                         case 'R':
5231                                 total_done += 5; CHECK_SIZE();
5232                                 *curpos++ = '0'+(lt->tm_hour / 10);
5233                                 *curpos++ = '0'+(lt->tm_hour % 10);
5234                                 *curpos++ = ':';
5235                                 *curpos++ = '0'+(lt->tm_min / 10);
5236                                 *curpos++ = '0'+(lt->tm_min % 10);
5237                                 break;
5238                         case 's':
5239                                 snprintf(subbuf, buflen - total_done, "%ld", mktime(lt));
5240                                 len = strlen(subbuf); CHECK_SIZE();
5241                                 strncpy2(curpos, subbuf, buflen - total_done);
5242                                 break;
5243                         case 'S':
5244                                 total_done += 2; CHECK_SIZE();
5245                                 *curpos++ = '0'+(lt->tm_sec / 10);
5246                                 *curpos++ = '0'+(lt->tm_sec % 10);
5247                                 break;
5248                         case 't':
5249                                 len = 1; CHECK_SIZE();
5250                                 *curpos = '\t';
5251                                 break;
5252                         case 'T':
5253                                 total_done += 8; CHECK_SIZE();
5254                                 *curpos++ = '0'+(lt->tm_hour / 10);
5255                                 *curpos++ = '0'+(lt->tm_hour % 10);
5256                                 *curpos++ = ':';
5257                                 *curpos++ = '0'+(lt->tm_min / 10);
5258                                 *curpos++ = '0'+(lt->tm_min % 10);
5259                                 *curpos++ = ':';
5260                                 *curpos++ = '0'+(lt->tm_sec / 10);
5261                                 *curpos++ = '0'+(lt->tm_sec % 10);
5262                                 break;
5263                         case 'u':
5264                                 len = 1; CHECK_SIZE();
5265                                 snprintf(curpos, buflen - total_done, "%d", lt->tm_wday == 0 ? 7: lt->tm_wday);
5266                                 break;
5267                         case 'w':
5268                                 len = 1; CHECK_SIZE();
5269                                 snprintf(curpos, buflen - total_done, "%d", lt->tm_wday);
5270                                 break;
5271                         case 'x':
5272                                 fast_strftime(subbuf, 64, date_loc_format, lt);
5273                                 len = strlen(subbuf); CHECK_SIZE();
5274                                 strncpy2(curpos, subbuf, buflen - total_done);
5275                                 break;
5276                         case 'X':
5277                                 fast_strftime(subbuf, 64, time_loc_format, lt);
5278                                 len = strlen(subbuf); CHECK_SIZE();
5279                                 strncpy2(curpos, subbuf, buflen - total_done);
5280                                 break;
5281                         case 'y':
5282                                 total_done += 2; CHECK_SIZE();
5283                                 tmp = lt->tm_year%100;
5284                                 *curpos++ = '0'+(tmp / 10);
5285                                 *curpos++ = '0'+(tmp % 10);
5286                                 break;
5287                         case 'Y':
5288                                 len = 4; CHECK_SIZE();
5289                                 snprintf(curpos, buflen - total_done, "%4d", lt->tm_year + 1900);
5290                                 break;
5291                         case 'G':
5292                         case 'g':
5293                         case 'U':
5294                         case 'V':
5295                         case 'W':
5296                         case 'z':
5297                         case 'Z':
5298                         case '+':
5299                                 /* let these complicated ones be done with the libc */
5300                                 snprintf(subfmt, 64, "%%%c", *format);
5301                                 strftime(subbuf, 64, subfmt, lt);
5302                                 len = strlen(subbuf); CHECK_SIZE();
5303                                 strncpy2(curpos, subbuf, buflen - total_done);
5304                                 break;
5305                         case 'E':
5306                         case 'O':
5307                                 /* let these complicated modifiers be done with the libc */
5308                                 snprintf(subfmt, 64, "%%%c%c", *format, *(format+1));
5309                                 strftime(subbuf, 64, subfmt, lt);
5310                                 len = strlen(subbuf); CHECK_SIZE();
5311                                 strncpy2(curpos, subbuf, buflen - total_done);
5312                                 format++;
5313                                 break;
5314                         default:
5315                                 g_warning("format error (%c)", *format);
5316                                 *curpos = '\0';
5317                                 return total_done;
5318                         }
5319                         curpos += len;
5320                         format++;
5321                 } else {
5322                         int len = 1; CHECK_SIZE();
5323                         *curpos++ = *format++; 
5324                 }
5325         }
5326         *curpos++ = '\0';
5327         return total_done;
5328 }