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