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