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