2005-09-07 [colin] 1.9.14cvs2
[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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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. We avoid scanning the message for conflicts and hope the
3039  * best.
3040  *
3041  *   boundary := 0*69<bchars> bcharsnospace
3042  *   bchars := bcharsnospace / " "
3043  *   bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" /
3044  *                    "+" / "_" / "," / "-" / "." /
3045  *                    "/" / ":" / "=" / "?"
3046  *
3047  * some special characters removed because of buggy MTAs
3048  */
3049
3050 gchar *generate_mime_boundary(const gchar *prefix)
3051 {
3052         static gchar tbl[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
3053                              "abcdefghijklmnopqrstuvwxyz"
3054                              "1234567890+_./=";
3055         gchar buf_uniq[17];
3056         gchar buf_date[64];
3057         gint i;
3058
3059         for (i = 0; i < sizeof(buf_uniq) - 1; i++)
3060                 buf_uniq[i] = tbl[g_random_int_range(0, sizeof(tbl) - 1)];
3061         buf_uniq[i] = '\0';
3062
3063         get_rfc822_date(buf_date, sizeof(buf_date));
3064         subst_char(buf_date, ' ', '_');
3065         subst_char(buf_date, ',', '_');
3066         subst_char(buf_date, ':', '_');
3067
3068         return g_strdup_printf("%s_%s_%s", prefix ? prefix : "Multipart",
3069                                buf_date, buf_uniq);
3070 }
3071
3072 gint change_file_mode_rw(FILE *fp, const gchar *file)
3073 {
3074 #if HAVE_FCHMOD
3075         return fchmod(fileno(fp), S_IRUSR|S_IWUSR);
3076 #else
3077         return g_chmod(file, S_IRUSR|S_IWUSR);
3078 #endif
3079 }
3080
3081 FILE *my_tmpfile(void)
3082 {
3083 #if HAVE_MKSTEMP
3084         const gchar suffix[] = ".XXXXXX";
3085         const gchar *tmpdir;
3086         guint tmplen;
3087         const gchar *progname;
3088         guint proglen;
3089         gchar *fname;
3090         gint fd;
3091         FILE *fp;
3092
3093         tmpdir = get_tmp_dir();
3094         tmplen = strlen(tmpdir);
3095         progname = g_get_prgname();
3096         if (progname == NULL)
3097                 progname = "sylpheed-claws";
3098         proglen = strlen(progname);
3099         Xalloca(fname, tmplen + 1 + proglen + sizeof(suffix),
3100                 return tmpfile());
3101
3102         memcpy(fname, tmpdir, tmplen);
3103         fname[tmplen] = G_DIR_SEPARATOR;
3104         memcpy(fname + tmplen + 1, progname, proglen);
3105         memcpy(fname + tmplen + 1 + proglen, suffix, sizeof(suffix));
3106
3107         fd = mkstemp(fname);
3108         if (fd < 0)
3109                 return tmpfile();
3110
3111         g_unlink(fname);
3112
3113         fp = fdopen(fd, "w+b");
3114         if (!fp)
3115                 close(fd);
3116         else
3117                 return fp;
3118 #endif /* HAVE_MKSTEMP */
3119
3120         return tmpfile();
3121 }
3122
3123 FILE *get_tmpfile_in_dir(const gchar *dir, gchar **filename)
3124 {
3125         int fd;
3126         
3127         *filename = g_strdup_printf("%s%csylpheed.XXXXXX", dir, G_DIR_SEPARATOR);
3128         fd = mkstemp(*filename);
3129
3130         return fdopen(fd, "w+");
3131 }
3132
3133 FILE *str_open_as_stream(const gchar *str)
3134 {
3135         FILE *fp;
3136         size_t len;
3137
3138         g_return_val_if_fail(str != NULL, NULL);
3139
3140         fp = my_tmpfile();
3141         if (!fp) {
3142                 FILE_OP_ERROR("str_open_as_stream", "my_tmpfile");
3143                 return NULL;
3144         }
3145
3146         len = strlen(str);
3147         if (len == 0) return fp;
3148
3149         if (fwrite(str, 1, len, fp) != len) {
3150                 FILE_OP_ERROR("str_open_as_stream", "fwrite");
3151                 fclose(fp);
3152                 return NULL;
3153         }
3154
3155         rewind(fp);
3156         return fp;
3157 }
3158
3159 gint str_write_to_file(const gchar *str, const gchar *file)
3160 {
3161         FILE *fp;
3162         size_t len;
3163
3164         g_return_val_if_fail(str != NULL, -1);
3165         g_return_val_if_fail(file != NULL, -1);
3166
3167         if ((fp = g_fopen(file, "wb")) == NULL) {
3168                 FILE_OP_ERROR(file, "fopen");
3169                 return -1;
3170         }
3171
3172         len = strlen(str);
3173         if (len == 0) {
3174                 fclose(fp);
3175                 return 0;
3176         }
3177
3178         if (fwrite(str, 1, len, fp) != len) {
3179                 FILE_OP_ERROR(file, "fwrite");
3180                 fclose(fp);
3181                 g_unlink(file);
3182                 return -1;
3183         }
3184
3185         if (fclose(fp) == EOF) {
3186                 FILE_OP_ERROR(file, "fclose");
3187                 g_unlink(file);
3188                 return -1;
3189         }
3190
3191         return 0;
3192 }
3193
3194 gchar *file_read_to_str(const gchar *file)
3195 {
3196         FILE *fp;
3197         gchar *str;
3198
3199         g_return_val_if_fail(file != NULL, NULL);
3200
3201         if ((fp = g_fopen(file, "rb")) == NULL) {
3202                 FILE_OP_ERROR(file, "fopen");
3203                 return NULL;
3204         }
3205
3206         str = file_read_stream_to_str(fp);
3207
3208         fclose(fp);
3209
3210         return str;
3211 }
3212
3213 gchar *file_read_stream_to_str(FILE *fp)
3214 {
3215         GByteArray *array;
3216         guchar buf[BUFSIZ];
3217         gint n_read;
3218         gchar *str;
3219
3220         g_return_val_if_fail(fp != NULL, NULL);
3221
3222         array = g_byte_array_new();
3223
3224         while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
3225                 if (n_read < sizeof(buf) && ferror(fp))
3226                         break;
3227                 g_byte_array_append(array, buf, n_read);
3228         }
3229
3230         if (ferror(fp)) {
3231                 FILE_OP_ERROR("file stream", "fread");
3232                 g_byte_array_free(array, TRUE);
3233                 return NULL;
3234         }
3235
3236         buf[0] = '\0';
3237         g_byte_array_append(array, buf, 1);
3238         str = (gchar *)array->data;
3239         g_byte_array_free(array, FALSE);
3240
3241         if (!g_utf8_validate(str, -1, NULL)) {
3242                 const gchar *src_codeset, *dest_codeset;
3243                 gchar *tmp = NULL;
3244                 src_codeset = conv_get_locale_charset_str();
3245                 dest_codeset = CS_UTF_8;
3246                 tmp = conv_codeset_strdup(str, src_codeset, dest_codeset);
3247                 g_free(str);
3248                 str = tmp;
3249         }
3250
3251         return str;
3252 }
3253
3254 gint execute_async(gchar *const argv[])
3255 {
3256         g_return_val_if_fail(argv != NULL && argv[0] != NULL, -1);
3257
3258         if (g_spawn_async(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH,
3259                           NULL, NULL, NULL, FALSE) == FALSE) {
3260                 g_warning("Can't execute command: %s\n", argv[0]);
3261                 return -1;
3262         }
3263
3264         return 0;
3265 }
3266
3267 gint execute_sync(gchar *const argv[])
3268 {
3269         gint status;
3270
3271         g_return_val_if_fail(argv != NULL && argv[0] != NULL, -1);
3272
3273         if (g_spawn_sync(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH,
3274                          NULL, NULL, NULL, NULL, &status, NULL) == FALSE) {
3275                 g_warning("Can't execute command: %s\n", argv[0]);
3276                 return -1;
3277         }
3278
3279 #ifdef G_OS_UNIX
3280         if (WIFEXITED(status))
3281                 return WEXITSTATUS(status);
3282         else
3283                 return -1;
3284 #else
3285         return status;
3286 #endif
3287 }
3288
3289 gint execute_command_line(const gchar *cmdline, gboolean async)
3290 {
3291         gchar **argv;
3292         gint ret;
3293
3294         debug_print("execute_command_line(): executing: %s\n", cmdline);
3295
3296         argv = strsplit_with_quote(cmdline, " ", 0);
3297
3298         if (async)
3299                 ret = execute_async(argv);
3300         else
3301                 ret = execute_sync(argv);
3302
3303         g_strfreev(argv);
3304
3305         return ret;
3306 }
3307
3308 gchar *get_command_output(const gchar *cmdline)
3309 {
3310         gchar *child_stdout;
3311         gint status;
3312
3313         g_return_val_if_fail(cmdline != NULL, NULL);
3314
3315         debug_print("get_command_output(): executing: %s\n", cmdline);
3316
3317         if (g_spawn_command_line_sync(cmdline, &child_stdout, NULL, &status,
3318                                       NULL) == FALSE) {
3319                 g_warning("Can't execute command: %s\n", cmdline);
3320                 return NULL;
3321         }
3322
3323         return child_stdout;
3324 }
3325
3326 static gint is_unchanged_uri_char(char c)
3327 {
3328         switch (c) {
3329                 case '(':
3330                 case ')':
3331                 case ',':
3332                         return 0;
3333                 default:
3334                         return 1;
3335         }
3336 }
3337
3338 void encode_uri(gchar *encoded_uri, gint bufsize, const gchar *uri)
3339 {
3340         int i;
3341         int k;
3342
3343         k = 0;
3344         for(i = 0; i < strlen(uri) ; i++) {
3345                 if (is_unchanged_uri_char(uri[i])) {
3346                         if (k + 2 >= bufsize)
3347                                 break;
3348                         encoded_uri[k++] = uri[i];
3349                 }
3350                 else {
3351                         char * hexa = "0123456789ABCDEF";
3352                         
3353                         if (k + 4 >= bufsize)
3354                                 break;
3355                         encoded_uri[k++] = '%';
3356                         encoded_uri[k++] = hexa[uri[i] / 16];
3357                         encoded_uri[k++] = hexa[uri[i] % 16];
3358                 }
3359         }
3360         encoded_uri[k] = 0;
3361 }
3362
3363 gint open_uri(const gchar *uri, const gchar *cmdline)
3364 {
3365         gchar buf[BUFFSIZE];
3366         gchar *p;
3367         gchar encoded_uri[BUFFSIZE];
3368         
3369         g_return_val_if_fail(uri != NULL, -1);
3370
3371         /* an option to choose whether to use encode_uri or not ? */
3372         encode_uri(encoded_uri, BUFFSIZE, uri);
3373         
3374         if (cmdline &&
3375             (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
3376             !strchr(p + 2, '%'))
3377                 g_snprintf(buf, sizeof(buf), cmdline, encoded_uri);
3378         else {
3379                 if (cmdline)
3380                         g_warning("Open URI command line is invalid "
3381                                   "(there must be only one '%%s'): %s",
3382                                   cmdline);
3383                 g_snprintf(buf, sizeof(buf), DEFAULT_BROWSER_CMD, encoded_uri);
3384         }
3385
3386         execute_command_line(buf, TRUE);
3387
3388         return 0;
3389 }
3390
3391 time_t remote_tzoffset_sec(const gchar *zone)
3392 {
3393         static gchar ustzstr[] = "PSTPDTMSTMDTCSTCDTESTEDT";
3394         gchar zone3[4];
3395         gchar *p;
3396         gchar c;
3397         gint iustz;
3398         gint offset;
3399         time_t remoteoffset;
3400
3401         strncpy(zone3, zone, 3);
3402         zone3[3] = '\0';
3403         remoteoffset = 0;
3404
3405         if (sscanf(zone, "%c%d", &c, &offset) == 2 &&
3406             (c == '+' || c == '-')) {
3407                 remoteoffset = ((offset / 100) * 60 + (offset % 100)) * 60;
3408                 if (c == '-')
3409                         remoteoffset = -remoteoffset;
3410         } else if (!strncmp(zone, "UT" , 2) ||
3411                    !strncmp(zone, "GMT", 2)) {
3412                 remoteoffset = 0;
3413         } else if (strlen(zone3) == 3) {
3414                 for (p = ustzstr; *p != '\0'; p += 3) {
3415                         if (!g_ascii_strncasecmp(p, zone3, 3)) {
3416                                 iustz = ((gint)(p - ustzstr) / 3 + 1) / 2 - 8;
3417                                 remoteoffset = iustz * 3600;
3418                                 break;
3419                         }
3420                 }
3421                 if (*p == '\0')
3422                         return -1;
3423         } else if (strlen(zone3) == 1) {
3424                 switch (zone[0]) {
3425                 case 'Z': remoteoffset =   0; break;
3426                 case 'A': remoteoffset =  -1; break;
3427                 case 'B': remoteoffset =  -2; break;
3428                 case 'C': remoteoffset =  -3; break;
3429                 case 'D': remoteoffset =  -4; break;
3430                 case 'E': remoteoffset =  -5; break;
3431                 case 'F': remoteoffset =  -6; break;
3432                 case 'G': remoteoffset =  -7; break;
3433                 case 'H': remoteoffset =  -8; break;
3434                 case 'I': remoteoffset =  -9; break;
3435                 case 'K': remoteoffset = -10; break; /* J is not used */
3436                 case 'L': remoteoffset = -11; break;
3437                 case 'M': remoteoffset = -12; break;
3438                 case 'N': remoteoffset =   1; break;
3439                 case 'O': remoteoffset =   2; break;
3440                 case 'P': remoteoffset =   3; break;
3441                 case 'Q': remoteoffset =   4; break;
3442                 case 'R': remoteoffset =   5; break;
3443                 case 'S': remoteoffset =   6; break;
3444                 case 'T': remoteoffset =   7; break;
3445                 case 'U': remoteoffset =   8; break;
3446                 case 'V': remoteoffset =   9; break;
3447                 case 'W': remoteoffset =  10; break;
3448                 case 'X': remoteoffset =  11; break;
3449                 case 'Y': remoteoffset =  12; break;
3450                 default:  remoteoffset =   0; break;
3451                 }
3452                 remoteoffset = remoteoffset * 3600;
3453         } else
3454                 return -1;
3455
3456         return remoteoffset;
3457 }
3458
3459 time_t tzoffset_sec(time_t *now)
3460 {
3461         struct tm gmt, *lt;
3462         gint off;
3463
3464         gmt = *gmtime(now);
3465         lt = localtime(now);
3466
3467         off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
3468
3469         if (lt->tm_year < gmt.tm_year)
3470                 off -= 24 * 60;
3471         else if (lt->tm_year > gmt.tm_year)
3472                 off += 24 * 60;
3473         else if (lt->tm_yday < gmt.tm_yday)
3474                 off -= 24 * 60;
3475         else if (lt->tm_yday > gmt.tm_yday)
3476                 off += 24 * 60;
3477
3478         if (off >= 24 * 60)             /* should be impossible */
3479                 off = 23 * 60 + 59;     /* if not, insert silly value */
3480         if (off <= -24 * 60)
3481                 off = -(23 * 60 + 59);
3482
3483         return off * 60;
3484 }
3485
3486 /* calculate timezone offset */
3487 gchar *tzoffset(time_t *now)
3488 {
3489         static gchar offset_string[6];
3490         struct tm gmt, *lt;
3491         gint off;
3492         gchar sign = '+';
3493
3494         gmt = *gmtime(now);
3495         lt = localtime(now);
3496
3497         off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
3498
3499         if (lt->tm_year < gmt.tm_year)
3500                 off -= 24 * 60;
3501         else if (lt->tm_year > gmt.tm_year)
3502                 off += 24 * 60;
3503         else if (lt->tm_yday < gmt.tm_yday)
3504                 off -= 24 * 60;
3505         else if (lt->tm_yday > gmt.tm_yday)
3506                 off += 24 * 60;
3507
3508         if (off < 0) {
3509                 sign = '-';
3510                 off = -off;
3511         }
3512
3513         if (off >= 24 * 60)             /* should be impossible */
3514                 off = 23 * 60 + 59;     /* if not, insert silly value */
3515
3516         sprintf(offset_string, "%c%02d%02d", sign, off / 60, off % 60);
3517
3518         return offset_string;
3519 }
3520
3521 void get_rfc822_date(gchar *buf, gint len)
3522 {
3523         struct tm *lt;
3524         time_t t;
3525         gchar day[4], mon[4];
3526         gint dd, hh, mm, ss, yyyy;
3527
3528         t = time(NULL);
3529         lt = localtime(&t);
3530
3531         sscanf(asctime(lt), "%3s %3s %d %d:%d:%d %d\n",
3532                day, mon, &dd, &hh, &mm, &ss, &yyyy);
3533         g_snprintf(buf, len, "%s, %d %s %d %02d:%02d:%02d %s",
3534                    day, dd, mon, yyyy, hh, mm, ss, tzoffset(&t));
3535 }
3536
3537 /* just a wrapper to suppress the warning of gcc about %c */
3538 size_t my_strftime(gchar *s, size_t max, const gchar *format,
3539                    const struct tm *tm)
3540 {
3541         return strftime(s, max, format, tm);
3542 }
3543
3544 void debug_set_mode(gboolean mode)
3545 {
3546         debug_mode = mode;
3547 }
3548
3549 gboolean debug_get_mode(void)
3550 {
3551         return debug_mode;
3552 }
3553
3554 void debug_print_real(const gchar *format, ...)
3555 {
3556         va_list args;
3557         gchar buf[BUFFSIZE];
3558
3559         if (!debug_mode) return;
3560
3561         va_start(args, format);
3562         g_vsnprintf(buf, sizeof(buf), format, args);
3563         va_end(args);
3564
3565         g_print("%s", buf);
3566 }
3567
3568 void * subject_table_lookup(GHashTable *subject_table, gchar * subject)
3569 {
3570         if (subject == NULL)
3571                 subject = "";
3572         else
3573                 subject += subject_get_prefix_length(subject);
3574
3575         return g_hash_table_lookup(subject_table, subject);
3576 }
3577
3578 void subject_table_insert(GHashTable *subject_table, gchar * subject,
3579                           void * data)
3580 {
3581         if (subject == NULL || *subject == 0)
3582                 return;
3583         subject += subject_get_prefix_length(subject);
3584         g_hash_table_insert(subject_table, subject, data);
3585 }
3586
3587 void subject_table_remove(GHashTable *subject_table, gchar * subject)
3588 {
3589         if (subject == NULL)
3590                 return;
3591
3592         subject += subject_get_prefix_length(subject);  
3593         g_hash_table_remove(subject_table, subject);
3594 }
3595
3596 /*!
3597  *\brief        Check if a string is prefixed with known (combinations) 
3598  *              of prefixes. The function assumes that each prefix 
3599  *              is terminated by zero or exactly _one_ space.
3600  *
3601  *\param        str String to check for a prefixes
3602  *
3603  *\return       int Number of chars in the prefix that should be skipped 
3604  *              for a "clean" subject line. If no prefix was found, 0
3605  *              is returned.
3606  */             
3607 int subject_get_prefix_length(const gchar *subject)
3608 {
3609         /*!< Array with allowable reply prefixes regexps. */
3610         static const gchar * const prefixes[] = {
3611                 "Re\\:",                        /* "Re:" */
3612                 "Re\\[[1-9][0-9]*\\]\\:",       /* "Re[XXX]:" (non-conforming news mail clients) */
3613                 "Antw\\:",                      /* "Antw:" (Dutch / German Outlook) */
3614                 "Aw\\:",                        /* "Aw:"   (German) */
3615                 "Antwort\\:",                   /* "Antwort:" (German Lotus Notes) */
3616                 "Res\\:",                       /* "Res:" (Brazilian Outlook) */
3617                 "Fw\\:",                        /* "Fw:" Forward */
3618                 "Enc\\:",                       /* "Enc:" Forward (Brazilian Outlook) */
3619                 "Odp\\:",                       /* "Odp:" Re (Polish Outlook) */
3620                 "Rif\\:"                        /* "Rif:" (Italian Outlook) */
3621                 /* add more */
3622         };
3623         const int PREFIXES = sizeof prefixes / sizeof prefixes[0];
3624         int n;
3625         regmatch_t pos;
3626         static regex_t regex;
3627         static gboolean init_;
3628
3629         if (!subject) return 0;
3630         if (!*subject) return 0;
3631
3632         if (!init_) {
3633                 GString *s = g_string_new("");
3634                 
3635                 for (n = 0; n < PREFIXES; n++)
3636                         /* Terminate each prefix regexpression by a
3637                          * "\ ?" (zero or ONE space), and OR them */
3638                         g_string_append_printf(s, "(%s\\ ?)%s",
3639                                           prefixes[n],
3640                                           n < PREFIXES - 1 ? 
3641                                           "|" : "");
3642                 
3643                 g_string_prepend(s, "(");
3644                 g_string_append(s, ")+");       /* match at least once */
3645                 g_string_prepend(s, "^\\ *");   /* from beginning of line */
3646                 
3647
3648                 /* We now have something like "^\ *((PREFIX1\ ?)|(PREFIX2\ ?))+" 
3649                  * TODO: Should this be       "^\ *(((PREFIX1)|(PREFIX2))\ ?)+" ??? */
3650                 if (regcomp(&regex, s->str, REG_EXTENDED | REG_ICASE)) { 
3651                         debug_print("Error compiling regexp %s\n", s->str);
3652                         g_string_free(s, TRUE);
3653                         return 0;
3654                 } else {
3655                         init_ = TRUE;
3656                         g_string_free(s, TRUE);
3657                 }
3658         }
3659         
3660         if (!regexec(&regex, subject, 1, &pos, 0) && pos.rm_so != -1)
3661                 return pos.rm_eo;
3662         else
3663                 return 0;
3664 }
3665
3666 guint g_stricase_hash(gconstpointer gptr)
3667 {
3668         guint hash_result = 0;
3669         const char *str;
3670
3671         for (str = gptr; str && *str; str++) {
3672                 if (isupper((guchar)*str)) hash_result += (*str + ' ');
3673                 else hash_result += *str;
3674         }
3675
3676         return hash_result;
3677 }
3678
3679 gint g_stricase_equal(gconstpointer gptr1, gconstpointer gptr2)
3680 {
3681         const char *str1 = gptr1;
3682         const char *str2 = gptr2;
3683
3684         return !g_utf8_collate(str1, str2);
3685 }
3686
3687 gint g_int_compare(gconstpointer a, gconstpointer b)
3688 {
3689         return GPOINTER_TO_INT(a) - GPOINTER_TO_INT(b);
3690 }
3691
3692 gchar *generate_msgid(gchar *buf, gint len)
3693 {
3694         struct tm *lt;
3695         time_t t;
3696         gchar *addr;
3697
3698         t = time(NULL);
3699         lt = localtime(&t);
3700
3701         addr = g_strconcat("@", get_domain_name(), NULL);
3702
3703         g_snprintf(buf, len, "%04d%02d%02d%02d%02d%02d.%08x%s",
3704                    lt->tm_year + 1900, lt->tm_mon + 1,
3705                    lt->tm_mday, lt->tm_hour,
3706                    lt->tm_min, lt->tm_sec,
3707                    (guint) rand(), addr);
3708
3709         g_free(addr);
3710         return buf;
3711 }
3712
3713 /*
3714    quote_cmd_argument()
3715    
3716    return a quoted string safely usable in argument of a command.
3717    
3718    code is extracted and adapted from etPan! project -- DINH V. Hoà.
3719 */
3720
3721 gint quote_cmd_argument(gchar * result, guint size,
3722                         const gchar * path)
3723 {
3724         const gchar * p;
3725         gchar * result_p;
3726         guint remaining;
3727
3728         result_p = result;
3729         remaining = size;
3730
3731         for(p = path ; * p != '\0' ; p ++) {
3732
3733                 if (isalnum((guchar)*p) || (* p == '/')) {
3734                         if (remaining > 0) {
3735                                 * result_p = * p;
3736                                 result_p ++; 
3737                                 remaining --;
3738                         }
3739                         else {
3740                                 result[size - 1] = '\0';
3741                                 return -1;
3742                         }
3743                 }
3744                 else { 
3745                         if (remaining >= 2) {
3746                                 * result_p = '\\';
3747                                 result_p ++; 
3748                                 * result_p = * p;
3749                                 result_p ++; 
3750                                 remaining -= 2;
3751                         }
3752                         else {
3753                                 result[size - 1] = '\0';
3754                                 return -1;
3755                         }
3756                 }
3757         }
3758         if (remaining > 0) {
3759                 * result_p = '\0';
3760         }
3761         else {
3762                 result[size - 1] = '\0';
3763                 return -1;
3764         }
3765   
3766         return 0;
3767 }
3768
3769 typedef struct 
3770 {
3771         GNode           *parent;
3772         GNodeMapFunc     func;
3773         gpointer         data;
3774 } GNodeMapData;
3775
3776 static void g_node_map_recursive(GNode *node, gpointer data)
3777 {
3778         GNodeMapData *mapdata = (GNodeMapData *) data;
3779         GNode *newnode;
3780         GNodeMapData newmapdata;
3781         gpointer newdata;
3782
3783         newdata = mapdata->func(node->data, mapdata->data);
3784         if (newdata != NULL) {
3785                 newnode = g_node_new(newdata);
3786                 g_node_append(mapdata->parent, newnode);
3787
3788                 newmapdata.parent = newnode;
3789                 newmapdata.func = mapdata->func;
3790                 newmapdata.data = mapdata->data;
3791
3792                 g_node_children_foreach(node, G_TRAVERSE_ALL, g_node_map_recursive, &newmapdata);
3793         }
3794 }
3795
3796 GNode *g_node_map(GNode *node, GNodeMapFunc func, gpointer data)
3797 {
3798         GNode *root;
3799         GNodeMapData mapdata;
3800
3801         g_return_val_if_fail(node != NULL, NULL);
3802         g_return_val_if_fail(func != NULL, NULL);
3803
3804         root = g_node_new(func(node->data, data));
3805
3806         mapdata.parent = root;
3807         mapdata.func = func;
3808         mapdata.data = data;
3809
3810         g_node_children_foreach(node, G_TRAVERSE_ALL, g_node_map_recursive, &mapdata);
3811
3812         return root;
3813 }
3814
3815 #define HEX_TO_INT(val, hex)                    \
3816 {                                               \
3817         gchar c = hex;                          \
3818                                                 \
3819         if ('0' <= c && c <= '9') {             \
3820                 val = c - '0';                  \
3821         } else if ('a' <= c && c <= 'f') {      \
3822                 val = c - 'a' + 10;             \
3823         } else if ('A' <= c && c <= 'F') {      \
3824                 val = c - 'A' + 10;             \
3825         } else {                                \
3826                 val = -1;                       \
3827         }                                       \
3828 }
3829
3830 gboolean get_hex_value(guchar *out, gchar c1, gchar c2)
3831 {
3832         gint hi, lo;
3833
3834         HEX_TO_INT(hi, c1);
3835         HEX_TO_INT(lo, c2);
3836
3837         if (hi == -1 || lo == -1)
3838                 return FALSE;
3839
3840         *out = (hi << 4) + lo;
3841         return TRUE;
3842 }
3843
3844 #define INT_TO_HEX(hex, val)            \
3845 {                                       \
3846         if ((val) < 10)                 \
3847                 hex = '0' + (val);      \
3848         else                            \
3849                 hex = 'A' + (val) - 10; \
3850 }
3851
3852 void get_hex_str(gchar *out, guchar ch)
3853 {
3854         gchar hex;
3855
3856         INT_TO_HEX(hex, ch >> 4);
3857         *out++ = hex;
3858         INT_TO_HEX(hex, ch & 0x0f);
3859         *out++ = hex;
3860 }
3861
3862 #undef REF_DEBUG
3863 #ifndef REF_DEBUG
3864 #define G_PRINT_REF 1 == 1 ? (void) 0 : (void)
3865 #else
3866 #define G_PRINT_REF g_print
3867 #endif
3868
3869 /*!
3870  *\brief        Register ref counted pointer. It is based on GBoxed, so should
3871  *              work with anything that uses the GType system. The semantics
3872  *              are similar to a C++ auto pointer, with the exception that
3873  *              C doesn't have automatic closure (calling destructors) when 
3874  *              exiting a block scope.
3875  *              Use the \ref G_TYPE_AUTO_POINTER macro instead of calling this
3876  *              function directly.
3877  *
3878  *\return       GType A GType type.
3879  */
3880 GType g_auto_pointer_register(void)
3881 {
3882         static GType auto_pointer_type;
3883         if (!auto_pointer_type)
3884                 auto_pointer_type =
3885                         g_boxed_type_register_static
3886                                 ("G_TYPE_AUTO_POINTER",
3887                                  (GBoxedCopyFunc) g_auto_pointer_copy,
3888                                  (GBoxedFreeFunc) g_auto_pointer_free);
3889         return auto_pointer_type;                                                    
3890 }
3891
3892 /*!
3893  *\brief        Structure with g_new() allocated pointer guarded by the
3894  *              auto pointer
3895  */
3896 typedef struct AutoPointerRef {
3897         void          (*free) (gpointer);
3898         gpointer        pointer;
3899         glong           cnt;
3900 } AutoPointerRef;
3901
3902 /*!
3903  *\brief        The auto pointer opaque structure that references the
3904  *              pointer guard block.
3905  */
3906 typedef struct AutoPointer {
3907         AutoPointerRef *ref;
3908         gpointer        ptr; /*!< access to protected pointer */
3909 } AutoPointer;
3910
3911 /*!
3912  *\brief        Creates an auto pointer for a g_new()ed pointer. Example:
3913  *
3914  *\code 
3915  *
3916  *              ... tell gtk_list_store it should use a G_TYPE_AUTO_POINTER
3917  *              ... when assigning, copying and freeing storage elements
3918  *
3919  *              gtk_list_store_new(N_S_COLUMNS, 
3920  *                                 G_TYPE_AUTO_POINTER,
3921  *                                 -1);
3922  *
3923  *
3924  *              Template *precious_data = g_new0(Template, 1);
3925  *              g_pointer protect = g_auto_pointer_new(precious_data);
3926  *
3927  *              gtk_list_store_set(container, &iter,
3928  *                                 S_DATA, protect,
3929  *                                 -1);
3930  *
3931  *              ... the gtk_list_store has copied the pointer and 
3932  *              ... incremented its reference count, we should free
3933  *              ... the auto pointer (in C++ a destructor would do
3934  *              ... this for us when leaving block scope)
3935  * 
3936  *              g_auto_pointer_free(protect);
3937  *
3938  *              ... gtk_list_store_set() now manages the data. When
3939  *              ... *explicitly* requesting a pointer from the list 
3940  *              ... store, don't forget you get a copy that should be 
3941  *              ... freed with g_auto_pointer_free() eventually.
3942  *
3943  *\endcode
3944  *
3945  *\param        pointer Pointer to be guarded.
3946  *
3947  *\return       GAuto * Pointer that should be used in containers with
3948  *              GType support.
3949  */
3950 GAuto *g_auto_pointer_new(gpointer p)
3951 {
3952         AutoPointerRef *ref;
3953         AutoPointer    *ptr;
3954         
3955         if (p == NULL) 
3956                 return NULL;
3957
3958         ref = g_new0(AutoPointerRef, 1);
3959         ptr = g_new0(AutoPointer, 1);
3960
3961         ref->pointer = p;
3962         ref->free = g_free;
3963         ref->cnt = 1;
3964
3965         ptr->ref = ref;
3966         ptr->ptr = p;
3967
3968 #ifdef REF_DEBUG
3969         G_PRINT_REF ("XXXX ALLOC(%lx)\n", p);
3970 #endif
3971         return ptr;
3972 }
3973
3974 /*!
3975  *\brief        Allocate an autopointer using the passed \a free function to
3976  *              free the guarded pointer
3977  */
3978 GAuto *g_auto_pointer_new_with_free(gpointer p, GFreeFunc free_)
3979 {
3980         AutoPointer *aptr;
3981         
3982         if (p == NULL)
3983                 return NULL;
3984
3985         aptr = g_auto_pointer_new(p);
3986         aptr->ref->free = free_;
3987         return aptr; 
3988 }
3989
3990 gpointer g_auto_pointer_get_ptr(GAuto *auto_ptr)
3991 {
3992         if (auto_ptr == NULL) 
3993                 return NULL;
3994         return ((AutoPointer *) auto_ptr)->ptr; 
3995 }
3996
3997 /*!
3998  *\brief        Copies an auto pointer by. It's mostly not necessary
3999  *              to call this function directly, unless you copy/assign
4000  *              the guarded pointer.
4001  *
4002  *\param        auto_ptr Auto pointer returned by previous call to 
4003  *              g_auto_pointer_new_XXX()
4004  *
4005  *\return       gpointer An auto pointer
4006  */
4007 GAuto *g_auto_pointer_copy(GAuto *auto_ptr)
4008 {
4009         AutoPointer     *ptr;
4010         AutoPointerRef  *ref;
4011         AutoPointer     *newp;
4012
4013         if (auto_ptr == NULL) 
4014                 return NULL;
4015
4016         ptr = auto_ptr;
4017         ref = ptr->ref;
4018         newp = g_new0(AutoPointer, 1);
4019
4020         newp->ref = ref;
4021         newp->ptr = ref->pointer;
4022         ++(ref->cnt);
4023         
4024 #ifdef REF_DEBUG
4025         G_PRINT_REF ("XXXX COPY(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4026 #endif
4027         return newp;
4028 }
4029
4030 /*!
4031  *\brief        Free an auto pointer
4032  */
4033 void g_auto_pointer_free(GAuto *auto_ptr)
4034 {
4035         AutoPointer     *ptr;
4036         AutoPointerRef  *ref;
4037         
4038         if (auto_ptr == NULL)
4039                 return;
4040
4041         ptr = auto_ptr;
4042         ref = ptr->ref;
4043
4044         if (--(ref->cnt) == 0) {
4045 #ifdef REF_DEBUG
4046                 G_PRINT_REF ("XXXX FREE(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4047 #endif
4048                 ref->free(ref->pointer);
4049                 g_free(ref);
4050         } 
4051 #ifdef REF_DEBUG
4052         else
4053                 G_PRINT_REF ("XXXX DEREF(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4054 #endif
4055         g_free(ptr);            
4056 }
4057
4058 void replace_returns(gchar *str)
4059 {
4060         if (!str)
4061                 return;
4062
4063         while (strstr(str, "\n")) {
4064                 *strstr(str, "\n") = ' ';
4065         }
4066         while (strstr(str, "\r")) {
4067                 *strstr(str, "\r") = ' ';
4068         }
4069 }
4070
4071 /* get_uri_part() - retrieves a URI starting from scanpos.
4072                     Returns TRUE if succesful */
4073 gboolean get_uri_part(const gchar *start, const gchar *scanpos,
4074                              const gchar **bp, const gchar **ep)
4075 {
4076         const gchar *ep_;
4077
4078         g_return_val_if_fail(start != NULL, FALSE);
4079         g_return_val_if_fail(scanpos != NULL, FALSE);
4080         g_return_val_if_fail(bp != NULL, FALSE);
4081         g_return_val_if_fail(ep != NULL, FALSE);
4082
4083         *bp = scanpos;
4084
4085         /* find end point of URI */
4086         for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
4087                 if (!isgraph(*(const guchar *)ep_) ||
4088                     !IS_ASCII(*(const guchar *)ep_) ||
4089                     strchr("[]{}()<>\"", *ep_))
4090                         break;
4091         }
4092
4093         /* no punctuation at end of string */
4094
4095         /* FIXME: this stripping of trailing punctuations may bite with other URIs.
4096          * should pass some URI type to this function and decide on that whether
4097          * to perform punctuation stripping */
4098
4099 #define IS_REAL_PUNCT(ch)       (ispunct(ch) && ((ch) != '/')) 
4100
4101         for (; ep_ - 1 > scanpos + 1 &&
4102                IS_REAL_PUNCT(*(const guchar *)(ep_ - 1));
4103              ep_--)
4104                 ;
4105
4106 #undef IS_REAL_PUNCT
4107
4108         *ep = ep_;
4109
4110         return TRUE;            
4111 }
4112
4113 gchar *make_uri_string(const gchar *bp, const gchar *ep)
4114 {
4115         return g_strndup(bp, ep - bp);
4116 }
4117
4118 /* valid mail address characters */
4119 #define IS_RFC822_CHAR(ch) \
4120         (IS_ASCII(ch) && \
4121          (ch) > 32   && \
4122          (ch) != 127 && \
4123          !g_ascii_isspace(ch) && \
4124          !strchr("(),;<>\"", (ch)))
4125
4126 /* alphabet and number within 7bit ASCII */
4127 #define IS_ASCII_ALNUM(ch)      (IS_ASCII(ch) && g_ascii_isalnum(ch))
4128 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
4129
4130 static GHashTable *create_domain_tab(void)
4131 {
4132         static const gchar *toplvl_domains [] = {
4133             "museum", "aero",
4134             "arpa", "coop", "info", "name", "biz", "com", "edu", "gov",
4135             "int", "mil", "net", "org", "ac", "ad", "ae", "af", "ag",
4136             "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au",
4137             "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi",
4138             "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
4139             "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl",
4140             "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz", "de",
4141             "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er",
4142             "es", "et", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd",
4143             "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
4144             "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
4145             "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir",
4146             "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
4147             "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc",
4148             "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
4149             "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq",
4150             "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na",
4151             "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu",
4152             "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm",
4153             "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru",
4154             "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj",
4155             "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
4156             "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to",
4157             "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um",
4158             "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu",
4159             "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw" 
4160         };
4161         gint n;
4162         GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
4163         
4164         g_return_val_if_fail(htab, NULL);
4165         for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++) 
4166                 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
4167         return htab;
4168 }
4169
4170 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
4171 {
4172         const gint MAX_LVL_DOM_NAME_LEN = 6;
4173         gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
4174         const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
4175         register gchar *p;
4176         
4177         if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
4178                 return FALSE;
4179
4180         for (p = buf; p < m &&  first < last; *p++ = *first++)
4181                 ;
4182         *p = 0;
4183
4184         return g_hash_table_lookup(tab, buf) != NULL;
4185 }
4186
4187 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
4188 gboolean get_email_part(const gchar *start, const gchar *scanpos,
4189                                const gchar **bp, const gchar **ep)
4190 {
4191         /* more complex than the uri part because we need to scan back and forward starting from
4192          * the scan position. */
4193         gboolean result = FALSE;
4194         const gchar *bp_ = NULL;
4195         const gchar *ep_ = NULL;
4196         static GHashTable *dom_tab;
4197         const gchar *last_dot = NULL;
4198         const gchar *prelast_dot = NULL;
4199         const gchar *last_tld_char = NULL;
4200         
4201         /* the informative part of the email address (describing the name
4202          * of the email address owner) may contain quoted parts. the
4203          * closure stack stores the last encountered quotes. */
4204         gchar closure_stack[128];
4205         gchar *ptr = closure_stack;
4206
4207         g_return_val_if_fail(start != NULL, FALSE);
4208         g_return_val_if_fail(scanpos != NULL, FALSE);
4209         g_return_val_if_fail(bp != NULL, FALSE);
4210         g_return_val_if_fail(ep != NULL, FALSE);
4211
4212         if (!dom_tab)
4213                 dom_tab = create_domain_tab();
4214         g_return_val_if_fail(dom_tab, FALSE);   
4215
4216         /* scan start of address */
4217         for (bp_ = scanpos - 1;
4218              bp_ >= start && IS_RFC822_CHAR(*(const guchar *)bp_); bp_--)
4219                 ;
4220
4221         /* TODO: should start with an alnum? */
4222         bp_++;
4223         for (; bp_ < scanpos && !IS_ASCII_ALNUM(*(const guchar *)bp_); bp_++)
4224                 ;
4225
4226         if (bp_ != scanpos) {
4227                 /* scan end of address */
4228                 for (ep_ = scanpos + 1;
4229                      *ep_ && IS_RFC822_CHAR(*(const guchar *)ep_); ep_++)
4230                         if (*ep_ == '.') {
4231                                 prelast_dot = last_dot;
4232                                 last_dot = ep_;
4233                                 if (*(last_dot + 1) == '.') {
4234                                         if (prelast_dot == NULL)
4235                                                 return FALSE;
4236                                         last_dot = prelast_dot;
4237                                         break;
4238                                 }
4239                         }
4240
4241                 /* TODO: really should terminate with an alnum? */
4242                 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*(const guchar *)ep_);
4243                      --ep_)
4244                         ;
4245                 ep_++;
4246
4247                 if (last_dot == NULL)
4248                         return FALSE;
4249                 if (last_dot >= ep_)
4250                         last_dot = prelast_dot;
4251                 if (last_dot == NULL || (scanpos + 1 >= last_dot))
4252                         return FALSE;
4253                 last_dot++;
4254
4255                 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
4256                         if (*last_tld_char == '?')
4257                                 break;
4258
4259                 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
4260                         result = TRUE;
4261
4262                 *ep = ep_;
4263                 *bp = bp_;
4264         }
4265
4266         if (!result) return FALSE;
4267
4268         if (*ep_ && *(bp_ - 1) == '"' && *(ep_) == '"' 
4269         && *(ep_ + 1) == ' ' && *(ep_ + 2) == '<'
4270         && IS_RFC822_CHAR(*(ep_ + 3))) {
4271                 /* this informative part with an @ in it is 
4272                  * followed by the email address */
4273                 ep_ += 3;
4274                 
4275                 /* go to matching '>' (or next non-rfc822 char, like \n) */
4276                 for (; *ep_ != '>' && *ep != '\0' && IS_RFC822_CHAR(*ep_); ep_++)
4277                         ;
4278                         
4279                 /* include the bracket */
4280                 if (*ep_ == '>') ep_++;
4281                 
4282                 /* include the leading quote */         
4283                 bp_--;
4284
4285                 *ep = ep_;
4286                 *bp = bp_;
4287                 return TRUE;
4288         }
4289
4290         /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
4291         if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
4292                 return FALSE;
4293
4294         /* see if this is <bracketed>; in this case we also scan for the informative part. */
4295         if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
4296                 return TRUE;
4297
4298 #define FULL_STACK()    ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
4299 #define IN_STACK()      (ptr > closure_stack)
4300 /* has underrun check */
4301 #define POP_STACK()     if(IN_STACK()) --ptr
4302 /* has overrun check */
4303 #define PUSH_STACK(c)   if(!FULL_STACK()) *ptr++ = (c); else return TRUE
4304 /* has underrun check */
4305 #define PEEK_STACK()    (IN_STACK() ? *(ptr - 1) : 0)
4306
4307         ep_++;
4308
4309         /* scan for the informative part. */
4310         for (bp_ -= 2; bp_ >= start; bp_--) {
4311                 /* if closure on the stack keep scanning */
4312                 if (PEEK_STACK() == *bp_) {
4313                         POP_STACK();
4314                         continue;
4315                 }
4316                 if (*bp_ == '\'' || *bp_ == '"') {
4317                         PUSH_STACK(*bp_);
4318                         continue;
4319                 }
4320
4321                 /* if nothing in the closure stack, do the special conditions
4322                  * the following if..else expression simply checks whether 
4323                  * a token is acceptable. if not acceptable, the clause
4324                  * should terminate the loop with a 'break' */
4325                 if (!PEEK_STACK()) {
4326                         if (*bp_ == '-'
4327                         && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
4328                         && (((bp_ + 1) < ep_)    && isalnum(*(bp_ + 1)))) {
4329                                 /* hyphens are allowed, but only in
4330                                    between alnums */
4331                         } else if (!strchr(",;:=?./+<>!&\r\n\t", *bp_)) {
4332                                 /* but anything not being a punctiation
4333                                    is ok */
4334                         } else {
4335                                 break; /* anything else is rejected */
4336                         }
4337                 }
4338         }
4339
4340         bp_++;
4341
4342 #undef PEEK_STACK
4343 #undef PUSH_STACK
4344 #undef POP_STACK
4345 #undef IN_STACK
4346 #undef FULL_STACK
4347
4348         /* scan forward (should start with an alnum) */
4349         for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
4350                 ;
4351
4352         *ep = ep_;
4353         *bp = bp_;
4354         
4355         return result;
4356 }
4357
4358 #undef IS_QUOTE
4359 #undef IS_ASCII_ALNUM
4360 #undef IS_RFC822_CHAR
4361
4362 gchar *make_email_string(const gchar *bp, const gchar *ep)
4363 {
4364         /* returns a mailto: URI; mailto: is also used to detect the
4365          * uri type later on in the button_pressed signal handler */
4366         gchar *tmp;
4367         gchar *result;
4368
4369         tmp = g_strndup(bp, ep - bp);
4370         result = g_strconcat("mailto:", tmp, NULL);
4371         g_free(tmp);
4372
4373         return result;
4374 }
4375
4376 gchar *make_http_string(const gchar *bp, const gchar *ep)
4377 {
4378         /* returns an http: URI; */
4379         gchar *tmp;
4380         gchar *result;
4381
4382         tmp = g_strndup(bp, ep - bp);
4383         result = g_strconcat("http://", tmp, NULL);
4384         g_free(tmp);
4385
4386         return result;
4387 }
4388
4389 static gchar *mailcap_get_command_in_file(const gchar *path, const gchar *type)
4390 {
4391         FILE *fp = fopen(path, "rb");
4392         gchar buf[BUFFSIZE];
4393         gchar *result = NULL;
4394         if (!fp)
4395                 return NULL;
4396         while (fgets(buf, sizeof (buf), fp) != NULL) {
4397                 gchar **parts = g_strsplit(buf, ";", -1);
4398                 gchar *trimmed = parts[0];
4399                 while (trimmed[0] == ' ')
4400                         trimmed++;
4401                 while (trimmed[strlen(trimmed)-1] == ' ') 
4402                         trimmed[strlen(trimmed)-1] = '\0';
4403                         
4404                 if (!strcmp(trimmed, type)) {
4405                         trimmed = parts[1];
4406                         while (trimmed[0] == ' ')
4407                                 trimmed++;
4408                         while (trimmed[strlen(trimmed)-1] == ' ') 
4409                                 trimmed[strlen(trimmed)-1] = '\0';
4410                         while (trimmed[strlen(trimmed)-1] == '\n') 
4411                                 trimmed[strlen(trimmed)-1] = '\0';
4412                         while (trimmed[strlen(trimmed)-1] == '\r') 
4413                                 trimmed[strlen(trimmed)-1] = '\0';
4414                         result = g_strdup(trimmed);
4415                         g_strfreev(parts);
4416                         fclose(fp);
4417                         if (strstr(result, "%s") && !strstr(result, "'%s'")) {
4418                                 gchar *start = g_strdup(result);
4419                                 gchar *end = g_strdup(strstr(result, "%s")+2);
4420                                 gchar *tmp;
4421                                 *strstr(start, "%s") = '\0';
4422                                 tmp = g_strconcat(start,"'%s'",end, NULL);
4423                                 g_free(start);
4424                                 g_free(end);
4425                                 g_free(result);
4426                                 result = tmp;
4427                         }
4428                         return result;
4429                 }
4430                 g_strfreev(parts);
4431         }
4432         fclose(fp);
4433         return NULL;
4434 }
4435 gchar *mailcap_get_command_for_type(const gchar *type) 
4436 {
4437         gchar *result = NULL;
4438         gchar *path = NULL;
4439         path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap", NULL);
4440         result = mailcap_get_command_in_file(path, type);
4441         g_free(path);
4442         if (result)
4443                 return result;
4444         result = mailcap_get_command_in_file("/etc/mailcap", type);
4445         return result;
4446 }