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