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