2005-10-19 [colin] 1.9.15cvs76
[claws.git] / src / common / utils.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2005 Hiroyuki Yamamoto & The Sylpheed-Claws Team
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #endif
23
24 #include "defs.h"
25
26 #include <glib.h>
27 #include <glib/gi18n.h>
28 #include <stdio.h>
29 #include <string.h>
30 #include <ctype.h>
31 #include <errno.h>
32
33 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
34 #  include <wchar.h>
35 #  include <wctype.h>
36 #endif
37 #include <stdlib.h>
38 #include <sys/stat.h>
39 #include <unistd.h>
40 #include <stdarg.h>
41 #include <sys/types.h>
42 #if HAVE_SYS_WAIT_H
43 #  include <sys/wait.h>
44 #endif
45 #include <dirent.h>
46 #include <time.h>
47 #include <regex.h>
48 #include <sys/utsname.h>
49
50 #ifdef G_OS_WIN32
51 #  include <direct.h>
52 #  include <io.h>
53 #endif
54
55 #include "utils.h"
56 #include "socket.h"
57 #include "../codeconv.h"
58
59 #define BUFFSIZE        8192
60
61 static gboolean debug_mode = FALSE;
62
63
64 #if !GLIB_CHECK_VERSION(2, 7, 0) && !defined(G_OS_UNIX)
65 gint g_chdir(const gchar *path)
66 {
67 #ifdef G_OS_WIN32
68         if (G_WIN32_HAVE_WIDECHAR_API()) {
69                 wchar_t *wpath;
70                 gint retval;
71                 gint save_errno;
72
73                 wpath = g_utf8_to_utf16(path, -1, NULL, NULL, NULL);
74                 if (wpath == NULL) {
75                         errno = EINVAL;
76                         return -1;
77                 }
78
79                 retval = _wchdir(wpath);
80                 save_errno = errno;
81
82                 g_free(wpath);
83
84                 errno = save_errno;
85                 return retval;
86         } else {
87                 gchar *cp_path;
88                 gint retval;
89                 gint save_errno;
90
91                 cp_path = g_locale_from_utf8(path, -1, NULL, NULL, NULL);
92                 if (cp_path == NULL) {
93                         errno = EINVAL;
94                         return -1;
95                 }
96
97                 retval = chdir(cp_path);
98                 save_errno = errno;
99
100                 g_free(cp_path);
101
102                 errno = save_errno;
103                 return retval;
104         }
105 #else
106         return chdir(path);
107 #endif
108 }
109
110 gint g_chmod(const gchar *path, gint mode)
111 {
112 #ifdef G_OS_WIN32
113         if (G_WIN32_HAVE_WIDECHAR_API()) {
114                 wchar_t *wpath;
115                 gint retval;
116                 gint save_errno;
117
118                 wpath = g_utf8_to_utf16(path, -1, NULL, NULL, NULL);
119                 if (wpath == NULL) {
120                         errno = EINVAL;
121                         return -1;
122                 }
123
124                 retval = _wchmod(wpath, mode);
125                 save_errno = errno;
126
127                 g_free(wpath);
128
129                 errno = save_errno;
130                 return retval;
131         } else {
132                 gchar *cp_path;
133                 gint retval;
134                 gint save_errno;
135
136                 cp_path = g_locale_from_utf8(path, -1, NULL, NULL, NULL);
137                 if (cp_path == NULL) {
138                         errno = EINVAL;
139                         return -1;
140                 }
141
142                 retval = chmod(cp_path, mode);
143                 save_errno = errno;
144
145                 g_free(cp_path);
146
147                 errno = save_errno;
148                 return retval;
149         }
150 #else
151         return chmod(path, mode);
152 #endif
153 }
154 #endif /* GLIB_CHECK_VERSION && G_OS_UNIX */
155
156 void list_free_strings(GList *list)
157 {
158         list = g_list_first(list);
159
160         while (list != NULL) {
161                 g_free(list->data);
162                 list = list->next;
163         }
164 }
165
166 void slist_free_strings(GSList *list)
167 {
168         while (list != NULL) {
169                 g_free(list->data);
170                 list = list->next;
171         }
172 }
173
174 GSList *slist_concat_unique (GSList *first, GSList *second)
175 {
176         GSList *tmp, *ret;
177         if (first == NULL) {
178                 if (second == NULL)
179                         return NULL;
180                 else 
181                         return second;
182         } else if (second == NULL)
183                 return first;
184         ret = first;
185         for (tmp = second; tmp != NULL; tmp = g_slist_next(tmp)) {
186                 if (g_slist_find(ret, tmp->data) == NULL)
187                         ret = g_slist_prepend(ret, tmp->data);
188         }
189         return ret;
190 }
191  
192 static void hash_free_strings_func(gpointer key, gpointer value, gpointer data)
193 {
194         g_free(key);
195 }
196
197 void hash_free_strings(GHashTable *table)
198 {
199         g_hash_table_foreach(table, hash_free_strings_func, NULL);
200 }
201
202 static void hash_free_value_mem_func(gpointer key, gpointer value,
203                                      gpointer data)
204 {
205         g_free(value);
206 }
207
208 void hash_free_value_mem(GHashTable *table)
209 {
210         g_hash_table_foreach(table, hash_free_value_mem_func, NULL);
211 }
212
213 gint str_case_equal(gconstpointer v, gconstpointer v2)
214 {
215         return g_ascii_strcasecmp((const gchar *)v, (const gchar *)v2) == 0;
216 }
217
218 guint str_case_hash(gconstpointer key)
219 {
220         const gchar *p = key;
221         guint h = *p;
222
223         if (h) {
224                 h = g_ascii_tolower(h);
225                 for (p += 1; *p != '\0'; p++)
226                         h = (h << 5) - h + g_ascii_tolower(*p);
227         }
228
229         return h;
230 }
231
232 void ptr_array_free_strings(GPtrArray *array)
233 {
234         gint i;
235         gchar *str;
236
237         g_return_if_fail(array != NULL);
238
239         for (i = 0; i < array->len; i++) {
240                 str = g_ptr_array_index(array, i);
241                 g_free(str);
242         }
243 }
244
245 gboolean str_find(const gchar *haystack, const gchar *needle)
246 {
247         return strstr(haystack, needle) != NULL ? TRUE : FALSE;
248 }
249
250 gboolean str_case_find(const gchar *haystack, const gchar *needle)
251 {
252         return strcasestr(haystack, needle) != NULL ? TRUE : FALSE;
253 }
254
255 gboolean str_find_equal(const gchar *haystack, const gchar *needle)
256 {
257         return strcmp(haystack, needle) == 0;
258 }
259
260 gboolean str_case_find_equal(const gchar *haystack, const gchar *needle)
261 {
262         return g_ascii_strcasecmp(haystack, needle) == 0;
263 }
264
265 gint to_number(const gchar *nstr)
266 {
267         register const gchar *p;
268
269         if (*nstr == '\0') return -1;
270
271         for (p = nstr; *p != '\0'; p++)
272                 if (!g_ascii_isdigit(*p)) return -1;
273
274         return atoi(nstr);
275 }
276
277 /* convert integer into string,
278    nstr must be not lower than 11 characters length */
279 gchar *itos_buf(gchar *nstr, gint n)
280 {
281         g_snprintf(nstr, 11, "%d", n);
282         return nstr;
283 }
284
285 /* convert integer into string */
286 gchar *itos(gint n)
287 {
288         static gchar nstr[11];
289
290         return itos_buf(nstr, n);
291 }
292
293 gchar *to_human_readable(off_t size)
294 {
295         static gchar str[10];
296
297         if (size < 1024)
298                 g_snprintf(str, sizeof(str), _("%dB"), (gint)size);
299         else if (size >> 10 < 1024)
300                 g_snprintf(str, sizeof(str), _("%.1fKB"), (gfloat)size / (1 << 10));
301         else if (size >> 20 < 1024)
302                 g_snprintf(str, sizeof(str), _("%.2fMB"), (gfloat)size / (1 << 20));
303         else
304                 g_snprintf(str, sizeof(str), _("%.2fGB"), (gfloat)size / (1 << 30));
305
306         return str;
307 }
308
309 /* strcmp with NULL-checking */
310 gint strcmp2(const gchar *s1, const gchar *s2)
311 {
312         if (s1 == NULL || s2 == NULL)
313                 return -1;
314         else
315                 return strcmp(s1, s2);
316 }
317 /* strstr with NULL-checking */
318 gchar *strstr2(const gchar *s1, const gchar *s2)
319 {
320         if (s1 == NULL || s2 == NULL)
321                 return NULL;
322         else
323                 return strstr(s1, s2);
324 }
325 /* compare paths */
326 gint path_cmp(const gchar *s1, const gchar *s2)
327 {
328         gint len1, len2;
329
330         if (s1 == NULL || s2 == NULL) return -1;
331         if (*s1 == '\0' || *s2 == '\0') return -1;
332
333         len1 = strlen(s1);
334         len2 = strlen(s2);
335
336         if (s1[len1 - 1] == G_DIR_SEPARATOR) len1--;
337         if (s2[len2 - 1] == G_DIR_SEPARATOR) len2--;
338
339         return strncmp(s1, s2, MAX(len1, len2));
340 }
341
342 /* remove trailing return code */
343 gchar *strretchomp(gchar *str)
344 {
345         register gchar *s;
346
347         if (!*str) return str;
348
349         for (s = str + strlen(str) - 1;
350              s >= str && (*s == '\n' || *s == '\r');
351              s--)
352                 *s = '\0';
353
354         return str;
355 }
356
357 /* remove trailing character */
358 gchar *strtailchomp(gchar *str, gchar tail_char)
359 {
360         register gchar *s;
361
362         if (!*str) return str;
363         if (tail_char == '\0') return str;
364
365         for (s = str + strlen(str) - 1; s >= str && *s == tail_char; s--)
366                 *s = '\0';
367
368         return str;
369 }
370
371 /* remove CR (carriage return) */
372 gchar *strcrchomp(gchar *str)
373 {
374         register gchar *s;
375
376         if (!*str) return str;
377
378         s = str + strlen(str) - 1;
379         if (*s == '\n' && s > str && *(s - 1) == '\r') {
380                 *(s - 1) = '\n';
381                 *s = '\0';
382         }
383
384         return str;
385 }
386
387 void file_strip_crs(const gchar *file) 
388 {
389         FILE *fp = NULL, *outfp = NULL;
390         gchar buf[4096];
391         gchar *out = get_tmp_file();
392         if (file == NULL)
393                 goto freeout;
394         
395         fp = fopen(file, "rb");
396         if (!fp)
397                 goto freeout;
398
399         outfp = fopen(out, "wb");
400         if (!outfp) {
401                 fclose(fp);
402                 goto freeout;
403         }
404
405         while (fgets(buf, sizeof (buf), fp) != NULL) {
406                 strcrchomp(buf);
407                 fputs(buf, outfp);
408         }
409         
410         fclose(fp);
411         fclose(outfp);
412         rename_force(out, file);
413 freeout:
414         g_free(out);
415 }
416
417 /* Similar to `strstr' but this function ignores the case of both strings.  */
418 gchar *strcasestr(const gchar *haystack, const gchar *needle)
419 {
420         register size_t haystack_len, needle_len;
421
422         haystack_len = strlen(haystack);
423         needle_len   = strlen(needle);
424
425         if (haystack_len < needle_len || needle_len == 0)
426                 return NULL;
427
428         while (haystack_len >= needle_len) {
429                 if (!g_ascii_strncasecmp(haystack, needle, needle_len))
430                         return (gchar *)haystack;
431                 else {
432                         haystack++;
433                         haystack_len--;
434                 }
435         }
436
437         return NULL;
438 }
439
440 gpointer my_memmem(gconstpointer haystack, size_t haystacklen,
441                    gconstpointer needle, size_t needlelen)
442 {
443         const gchar *haystack_ = (const gchar *)haystack;
444         const gchar *needle_ = (const gchar *)needle;
445         const gchar *haystack_cur = (const gchar *)haystack;
446
447         if (needlelen == 1)
448                 return memchr(haystack_, *needle_, haystacklen);
449
450         while ((haystack_cur = memchr(haystack_cur, *needle_, haystacklen))
451                != NULL) {
452                 if (haystacklen - (haystack_cur - haystack_) < needlelen)
453                         break;
454                 if (memcmp(haystack_cur + 1, needle_ + 1, needlelen - 1) == 0)
455                         return (gpointer)haystack_cur;
456                 else
457                         haystack_cur++;
458         }
459
460         return NULL;
461 }
462
463 /* Copy no more than N characters of SRC to DEST, with NULL terminating.  */
464 gchar *strncpy2(gchar *dest, const gchar *src, size_t n)
465 {
466         register const gchar *s = src;
467         register gchar *d = dest;
468
469         while (--n && *s)
470                 *d++ = *s++;
471         *d = '\0';
472
473         return dest;
474 }
475
476 #if !HAVE_ISWALNUM
477 int iswalnum(wint_t wc)
478 {
479         return g_ascii_isalnum((int)wc);
480 }
481 #endif
482
483 #if !HAVE_ISWSPACE
484 int iswspace(wint_t wc)
485 {
486         return g_ascii_isspace((int)wc);
487 }
488 #endif
489
490 #if !HAVE_TOWLOWER
491 wint_t towlower(wint_t wc)
492 {
493         if (wc >= L'A' && wc <= L'Z')
494                 return wc + L'a' - L'A';
495
496         return wc;
497 }
498 #endif
499
500 #if !HAVE_WCSLEN
501 size_t wcslen(const wchar_t *s)
502 {
503         size_t len = 0;
504
505         while (*s != L'\0')
506                 ++len, ++s;
507
508         return len;
509 }
510 #endif
511
512 #if !HAVE_WCSCPY
513 /* Copy SRC to DEST.  */
514 wchar_t *wcscpy(wchar_t *dest, const wchar_t *src)
515 {
516         wint_t c;
517         wchar_t *s = dest;
518
519         do {
520                 c = *src++;
521                 *dest++ = c;
522         } while (c != L'\0');
523
524         return s;
525 }
526 #endif
527
528 #if !HAVE_WCSNCPY
529 /* Copy no more than N wide-characters of SRC to DEST.  */
530 wchar_t *wcsncpy (wchar_t *dest, const wchar_t *src, size_t n)
531 {
532         wint_t c;
533         wchar_t *s = dest;
534
535         do {
536                 c = *src++;
537                 *dest++ = c;
538                 if (--n == 0)
539                         return s;
540         } while (c != L'\0');
541
542         /* zero fill */
543         do
544                 *dest++ = L'\0';
545         while (--n > 0);
546
547         return s;
548 }
549 #endif
550
551 /* Duplicate S, returning an identical malloc'd string. */
552 wchar_t *wcsdup(const wchar_t *s)
553 {
554         wchar_t *new_str;
555
556         if (s) {
557                 new_str = g_new(wchar_t, wcslen(s) + 1);
558                 wcscpy(new_str, s);
559         } else
560                 new_str = NULL;
561
562         return new_str;
563 }
564
565 /* Duplicate no more than N wide-characters of S,
566    returning an identical malloc'd string. */
567 wchar_t *wcsndup(const wchar_t *s, size_t n)
568 {
569         wchar_t *new_str;
570
571         if (s) {
572                 new_str = g_new(wchar_t, n + 1);
573                 wcsncpy(new_str, s, n);
574                 new_str[n] = (wchar_t)0;
575         } else
576                 new_str = NULL;
577
578         return new_str;
579 }
580
581 wchar_t *strdup_mbstowcs(const gchar *s)
582 {
583         wchar_t *new_str;
584
585         if (s) {
586                 new_str = g_new(wchar_t, strlen(s) + 1);
587                 if (mbstowcs(new_str, s, strlen(s) + 1) < 0) {
588                         g_free(new_str);
589                         new_str = NULL;
590                 } else
591                         new_str = g_realloc(new_str,
592                                             sizeof(wchar_t) * (wcslen(new_str) + 1));
593         } else
594                 new_str = NULL;
595
596         return new_str;
597 }
598
599 gchar *strdup_wcstombs(const wchar_t *s)
600 {
601         gchar *new_str;
602         size_t len;
603
604         if (s) {
605                 len = wcslen(s) * MB_CUR_MAX + 1;
606                 new_str = g_new(gchar, len);
607                 if (wcstombs(new_str, s, len) < 0) {
608                         g_free(new_str);
609                         new_str = NULL;
610                 } else
611                         new_str = g_realloc(new_str, strlen(new_str) + 1);
612         } else
613                 new_str = NULL;
614
615         return new_str;
616 }
617
618 /* Compare S1 and S2, ignoring case.  */
619 gint wcsncasecmp(const wchar_t *s1, const wchar_t *s2, size_t n)
620 {
621         wint_t c1;
622         wint_t c2;
623
624         while (n--) {
625                 c1 = towlower(*s1++);
626                 c2 = towlower(*s2++);
627                 if (c1 != c2)
628                         return c1 - c2;
629                 else if (c1 == 0 && c2 == 0)
630                         break;
631         }
632
633         return 0;
634 }
635
636 /* Find the first occurrence of NEEDLE in HAYSTACK, ignoring case.  */
637 wchar_t *wcscasestr(const wchar_t *haystack, const wchar_t *needle)
638 {
639         register size_t haystack_len, needle_len;
640
641         haystack_len = wcslen(haystack);
642         needle_len   = wcslen(needle);
643
644         if (haystack_len < needle_len || needle_len == 0)
645                 return NULL;
646
647         while (haystack_len >= needle_len) {
648                 if (!wcsncasecmp(haystack, needle, needle_len))
649                         return (wchar_t *)haystack;
650                 else {
651                         haystack++;
652                         haystack_len--;
653                 }
654         }
655
656         return NULL;
657 }
658
659 gint get_mbs_len(const gchar *s)
660 {
661         const gchar *p = s;
662         gint mb_len;
663         gint len = 0;
664
665         if (!p)
666                 return -1;
667
668         while (*p != '\0') {
669                 mb_len = g_utf8_skip[*(guchar *)p];
670                 if (mb_len == 0)
671                         break;
672                 else
673                         len++;
674
675                 p += mb_len;
676         }
677
678         return len;
679 }
680
681 /* Examine if next block is non-ASCII string */
682 gboolean is_next_nonascii(const gchar *s)
683 {
684         const gchar *p;
685
686         /* skip head space */
687         for (p = s; *p != '\0' && g_ascii_isspace(*p); p++)
688                 ;
689         for (; *p != '\0' && !g_ascii_isspace(*p); p++) {
690                 if (*(guchar *)p > 127 || *(guchar *)p < 32)
691                         return TRUE;
692         }
693
694         return FALSE;
695 }
696
697 gint get_next_word_len(const gchar *s)
698 {
699         gint len = 0;
700
701         for (; *s != '\0' && !g_ascii_isspace(*s); s++, len++)
702                 ;
703
704         return len;
705 }
706
707 /* compare subjects */
708 gint subject_compare(const gchar *s1, const gchar *s2)
709 {
710         gchar *str1, *str2;
711
712         if (!s1 || !s2) return -1;
713         if (!*s1 || !*s2) return -1;
714
715         Xstrdup_a(str1, s1, return -1);
716         Xstrdup_a(str2, s2, return -1);
717
718         trim_subject_for_compare(str1);
719         trim_subject_for_compare(str2);
720
721         if (!*str1 || !*str2) return -1;
722
723         return strcmp(str1, str2);
724 }
725
726 gint subject_compare_for_sort(const gchar *s1, const gchar *s2)
727 {
728         gchar *str1, *str2;
729
730         if (!s1 || !s2) return -1;
731
732         Xstrdup_a(str1, s1, return -1);
733         Xstrdup_a(str2, s2, return -1);
734
735         trim_subject_for_sort(str1);
736         trim_subject_for_sort(str2);
737
738         return g_utf8_collate(str1, str2);
739 }
740
741 void trim_subject_for_compare(gchar *str)
742 {
743         gchar *srcp;
744
745         eliminate_parenthesis(str, '[', ']');
746         eliminate_parenthesis(str, '(', ')');
747         g_strstrip(str);
748
749         srcp = str + subject_get_prefix_length(str);
750         if (srcp != str)
751                 memmove(str, srcp, strlen(srcp) + 1);
752 }
753
754 void trim_subject_for_sort(gchar *str)
755 {
756         gchar *srcp;
757
758         g_strstrip(str);
759
760         srcp = str + subject_get_prefix_length(str);
761         if (srcp != str)        
762                 memmove(str, srcp, strlen(srcp) + 1);
763 }
764
765 void trim_subject(gchar *str)
766 {
767         register gchar *srcp;
768         gchar op, cl;
769         gint in_brace;
770         
771         g_strstrip(str);
772
773         srcp = str + subject_get_prefix_length(str);
774
775         if (*srcp == '[') {
776                 op = '[';
777                 cl = ']';
778         } else if (*srcp == '(') {
779                 op = '(';
780                 cl = ')';
781         } else
782                 op = 0;
783
784         if (op) {
785                 ++srcp;
786                 in_brace = 1;
787                 while (*srcp) {
788                         if (*srcp == op)
789                                 in_brace++;
790                         else if (*srcp == cl)
791                                 in_brace--;
792                         srcp++;
793                         if (in_brace == 0)
794                                 break;
795                 }
796         }
797         while (g_ascii_isspace(*srcp)) srcp++;
798         memmove(str, srcp, strlen(srcp) + 1);
799 }
800
801 void eliminate_parenthesis(gchar *str, gchar op, gchar cl)
802 {
803         register gchar *srcp, *destp;
804         gint in_brace;
805
806         srcp = destp = str;
807
808         while ((destp = strchr(destp, op))) {
809                 in_brace = 1;
810                 srcp = destp + 1;
811                 while (*srcp) {
812                         if (*srcp == op)
813                                 in_brace++;
814                         else if (*srcp == cl)
815                                 in_brace--;
816                         srcp++;
817                         if (in_brace == 0)
818                                 break;
819                 }
820                 while (g_ascii_isspace(*srcp)) srcp++;
821                 memmove(destp, srcp, strlen(srcp) + 1);
822         }
823 }
824
825 void extract_parenthesis(gchar *str, gchar op, gchar cl)
826 {
827         register gchar *srcp, *destp;
828         gint in_brace;
829
830         srcp = destp = str;
831
832         while ((srcp = strchr(destp, op))) {
833                 if (destp > str)
834                         *destp++ = ' ';
835                 memmove(destp, srcp + 1, strlen(srcp));
836                 in_brace = 1;
837                 while(*destp) {
838                         if (*destp == op)
839                                 in_brace++;
840                         else if (*destp == cl)
841                                 in_brace--;
842
843                         if (in_brace == 0)
844                                 break;
845
846                         destp++;
847                 }
848         }
849         *destp = '\0';
850 }
851
852 void extract_parenthesis_with_skip_quote(gchar *str, gchar quote_chr,
853                                          gchar op, gchar cl)
854 {
855         register gchar *srcp, *destp;
856         gint in_brace;
857         gboolean in_quote = FALSE;
858
859         srcp = destp = str;
860
861         while ((srcp = strchr_with_skip_quote(destp, quote_chr, op))) {
862                 if (destp > str)
863                         *destp++ = ' ';
864                 memmove(destp, srcp + 1, strlen(srcp));
865                 in_brace = 1;
866                 while(*destp) {
867                         if (*destp == op && !in_quote)
868                                 in_brace++;
869                         else if (*destp == cl && !in_quote)
870                                 in_brace--;
871                         else if (*destp == quote_chr)
872                                 in_quote ^= TRUE;
873
874                         if (in_brace == 0)
875                                 break;
876
877                         destp++;
878                 }
879         }
880         *destp = '\0';
881 }
882
883 void eliminate_quote(gchar *str, gchar quote_chr)
884 {
885         register gchar *srcp, *destp;
886
887         srcp = destp = str;
888
889         while ((destp = strchr(destp, quote_chr))) {
890                 if ((srcp = strchr(destp + 1, quote_chr))) {
891                         srcp++;
892                         while (g_ascii_isspace(*srcp)) srcp++;
893                         memmove(destp, srcp, strlen(srcp) + 1);
894                 } else {
895                         *destp = '\0';
896                         break;
897                 }
898         }
899 }
900
901 void extract_quote(gchar *str, gchar quote_chr)
902 {
903         register gchar *p;
904
905         if ((str = strchr(str, quote_chr))) {
906                 p = str;
907                 while ((p = strchr(p + 1, quote_chr)) && (p[-1] == '\\')) {
908                         memmove(p - 1, p, strlen(p) + 1);
909                         p--;
910                 }
911                 if(p) {
912                         *p = '\0';
913                         memmove(str, str + 1, p - str);
914                 }
915         }
916 }
917
918 void eliminate_address_comment(gchar *str)
919 {
920         register gchar *srcp, *destp;
921         gint in_brace;
922
923         srcp = destp = str;
924
925         while ((destp = strchr(destp, '"'))) {
926                 if ((srcp = strchr(destp + 1, '"'))) {
927                         srcp++;
928                         if (*srcp == '@') {
929                                 destp = srcp + 1;
930                         } else {
931                                 while (g_ascii_isspace(*srcp)) srcp++;
932                                 memmove(destp, srcp, strlen(srcp) + 1);
933                         }
934                 } else {
935                         *destp = '\0';
936                         break;
937                 }
938         }
939
940         srcp = destp = str;
941
942         while ((destp = strchr_with_skip_quote(destp, '"', '('))) {
943                 in_brace = 1;
944                 srcp = destp + 1;
945                 while (*srcp) {
946                         if (*srcp == '(')
947                                 in_brace++;
948                         else if (*srcp == ')')
949                                 in_brace--;
950                         srcp++;
951                         if (in_brace == 0)
952                                 break;
953                 }
954                 while (g_ascii_isspace(*srcp)) srcp++;
955                 memmove(destp, srcp, strlen(srcp) + 1);
956         }
957 }
958
959 gchar *strchr_with_skip_quote(const gchar *str, gint quote_chr, gint c)
960 {
961         gboolean in_quote = FALSE;
962
963         while (*str) {
964                 if (*str == c && !in_quote)
965                         return (gchar *)str;
966                 if (*str == quote_chr)
967                         in_quote ^= TRUE;
968                 str++;
969         }
970
971         return NULL;
972 }
973
974 gchar *strrchr_with_skip_quote(const gchar *str, gint quote_chr, gint c)
975 {
976         gboolean in_quote = FALSE;
977         const gchar *p;
978
979         p = str + strlen(str) - 1;
980         while (p >= str) {
981                 if (*p == c && !in_quote)
982                         return (gchar *)p;
983                 if (*p == quote_chr)
984                         in_quote ^= TRUE;
985                 p--;
986         }
987
988         return NULL;
989 }
990
991 void extract_address(gchar *str)
992 {
993         eliminate_address_comment(str);
994         if (strchr_with_skip_quote(str, '"', '<'))
995                 extract_parenthesis_with_skip_quote(str, '"', '<', '>');
996         g_strstrip(str);
997 }
998
999 void extract_list_id_str(gchar *str)
1000 {
1001         if (strchr_with_skip_quote(str, '"', '<'))
1002                 extract_parenthesis_with_skip_quote(str, '"', '<', '>');
1003         g_strstrip(str);
1004 }
1005
1006 static GSList *address_list_append_real(GSList *addr_list, const gchar *str, gboolean removecomments)
1007 {
1008         gchar *work;
1009         gchar *workp;
1010
1011         if (!str) return addr_list;
1012
1013         Xstrdup_a(work, str, return addr_list);
1014
1015         if (removecomments)
1016                 eliminate_address_comment(work);
1017         workp = work;
1018
1019         while (workp && *workp) {
1020                 gchar *p, *next;
1021
1022                 if ((p = strchr_with_skip_quote(workp, '"', ','))) {
1023                         *p = '\0';
1024                         next = p + 1;
1025                 } else
1026                         next = NULL;
1027
1028                 if (removecomments && strchr_with_skip_quote(workp, '"', '<'))
1029                         extract_parenthesis_with_skip_quote
1030                                 (workp, '"', '<', '>');
1031
1032                 g_strstrip(workp);
1033                 if (*workp)
1034                         addr_list = g_slist_append(addr_list, g_strdup(workp));
1035
1036                 workp = next;
1037         }
1038
1039         return addr_list;
1040 }
1041
1042 GSList *address_list_append(GSList *addr_list, const gchar *str)
1043 {
1044         return address_list_append_real(addr_list, str, TRUE);
1045 }
1046
1047 GSList *address_list_append_with_comments(GSList *addr_list, const gchar *str)
1048 {
1049         return address_list_append_real(addr_list, str, FALSE);
1050 }
1051
1052 GSList *references_list_prepend(GSList *msgid_list, const gchar *str)
1053 {
1054         const gchar *strp;
1055
1056         if (!str) return msgid_list;
1057         strp = str;
1058
1059         while (strp && *strp) {
1060                 const gchar *start, *end;
1061                 gchar *msgid;
1062
1063                 if ((start = strchr(strp, '<')) != NULL) {
1064                         end = strchr(start + 1, '>');
1065                         if (!end) break;
1066                 } else
1067                         break;
1068
1069                 msgid = g_strndup(start + 1, end - start - 1);
1070                 g_strstrip(msgid);
1071                 if (*msgid)
1072                         msgid_list = g_slist_prepend(msgid_list, msgid);
1073                 else
1074                         g_free(msgid);
1075
1076                 strp = end + 1;
1077         }
1078
1079         return msgid_list;
1080 }
1081
1082 GSList *references_list_append(GSList *msgid_list, const gchar *str)
1083 {
1084         GSList *list;
1085
1086         list = references_list_prepend(NULL, str);
1087         list = g_slist_reverse(list);
1088         msgid_list = g_slist_concat(msgid_list, list);
1089
1090         return msgid_list;
1091 }
1092
1093 GSList *newsgroup_list_append(GSList *group_list, const gchar *str)
1094 {
1095         gchar *work;
1096         gchar *workp;
1097
1098         if (!str) return group_list;
1099
1100         Xstrdup_a(work, str, return group_list);
1101
1102         workp = work;
1103
1104         while (workp && *workp) {
1105                 gchar *p, *next;
1106
1107                 if ((p = strchr_with_skip_quote(workp, '"', ','))) {
1108                         *p = '\0';
1109                         next = p + 1;
1110                 } else
1111                         next = NULL;
1112
1113                 g_strstrip(workp);
1114                 if (*workp)
1115                         group_list = g_slist_append(group_list,
1116                                                     g_strdup(workp));
1117
1118                 workp = next;
1119         }
1120
1121         return group_list;
1122 }
1123
1124 GList *add_history(GList *list, const gchar *str)
1125 {
1126         GList *old;
1127
1128         g_return_val_if_fail(str != NULL, list);
1129
1130         old = g_list_find_custom(list, (gpointer)str, (GCompareFunc)strcmp2);
1131         if (old) {
1132                 g_free(old->data);
1133                 list = g_list_remove(list, old->data);
1134         } else if (g_list_length(list) >= MAX_HISTORY_SIZE) {
1135                 GList *last;
1136
1137                 last = g_list_last(list);
1138                 if (last) {
1139                         g_free(last->data);
1140                         g_list_remove(list, last->data);
1141                 }
1142         }
1143
1144         list = g_list_prepend(list, g_strdup(str));
1145
1146         return list;
1147 }
1148
1149 void remove_return(gchar *str)
1150 {
1151         register gchar *p = str;
1152
1153         while (*p) {
1154                 if (*p == '\n' || *p == '\r')
1155                         memmove(p, p + 1, strlen(p));
1156                 else
1157                         p++;
1158         }
1159 }
1160
1161 void remove_space(gchar *str)
1162 {
1163         register gchar *p = str;
1164         register gint spc;
1165
1166         while (*p) {
1167                 spc = 0;
1168                 while (g_ascii_isspace(*(p + spc)))
1169                         spc++;
1170                 if (spc)
1171                         memmove(p, p + spc, strlen(p + spc) + 1);
1172                 else
1173                         p++;
1174         }
1175 }
1176
1177 void unfold_line(gchar *str)
1178 {
1179         register gchar *p = str;
1180         register gint spc;
1181
1182         while (*p) {
1183                 if (*p == '\n' || *p == '\r') {
1184                         *p++ = ' ';
1185                         spc = 0;
1186                         while (g_ascii_isspace(*(p + spc)))
1187                                 spc++;
1188                         if (spc)
1189                                 memmove(p, p + spc, strlen(p + spc) + 1);
1190                 } else
1191                         p++;
1192         }
1193 }
1194
1195 void subst_char(gchar *str, gchar orig, gchar subst)
1196 {
1197         register gchar *p = str;
1198
1199         while (*p) {
1200                 if (*p == orig)
1201                         *p = subst;
1202                 p++;
1203         }
1204 }
1205
1206 void subst_chars(gchar *str, gchar *orig, gchar subst)
1207 {
1208         register gchar *p = str;
1209
1210         while (*p) {
1211                 if (strchr(orig, *p) != NULL)
1212                         *p = subst;
1213                 p++;
1214         }
1215 }
1216
1217 void subst_for_filename(gchar *str)
1218 {
1219         if (!str)
1220                 return;
1221         subst_chars(str, "\t\r\n\\/*", '_');
1222 }
1223
1224 void subst_for_shellsafe_filename(gchar *str)
1225 {
1226         if (!str)
1227                 return;
1228         subst_for_filename(str);
1229         subst_chars(str, " \"'|&;()<>'!{}[]",'_');
1230 }
1231
1232 gboolean is_header_line(const gchar *str)
1233 {
1234         if (str[0] == ':') return FALSE;
1235
1236         while (*str != '\0' && *str != ' ') {
1237                 if (*str == ':')
1238                         return TRUE;
1239                 str++;
1240         }
1241
1242         return FALSE;
1243 }
1244
1245 gboolean is_ascii_str(const gchar *str)
1246 {
1247         const guchar *p = (const guchar *)str;
1248
1249         while (*p != '\0') {
1250                 if (*p != '\t' && *p != ' ' &&
1251                     *p != '\r' && *p != '\n' &&
1252                     (*p < 32 || *p >= 127))
1253                         return FALSE;
1254                 p++;
1255         }
1256
1257         return TRUE;
1258 }
1259
1260 gint get_quote_level(const gchar *str, const gchar *quote_chars)
1261 {
1262         const gchar *first_pos;
1263         const gchar *last_pos;
1264         const gchar *p = str;
1265         gint quote_level = -1;
1266
1267         /* speed up line processing by only searching to the last '>' */
1268         if ((first_pos = line_has_quote_char(str, quote_chars)) != NULL) {
1269                 /* skip a line if it contains a '<' before the initial '>' */
1270                 if (memchr(str, '<', first_pos - str) != NULL)
1271                         return -1;
1272                 last_pos = line_has_quote_char_last(first_pos, quote_chars);
1273         } else
1274                 return -1;
1275
1276         while (p <= last_pos) {
1277                 while (p < last_pos) {
1278                         if (g_ascii_isspace(*p))
1279                                 p++;
1280                         else
1281                                 break;
1282                 }
1283
1284                 if (strchr(quote_chars, *p))
1285                         quote_level++;
1286                 else if (*p != '-' && !g_ascii_isspace(*p) && p <= last_pos) {
1287                         /* any characters are allowed except '-' and space */
1288                         while (*p != '-' 
1289                                && !strchr(quote_chars, *p) 
1290                                && !g_ascii_isspace(*p) 
1291                                && p < last_pos)
1292                                 p++;
1293                         if (strchr(quote_chars, *p))
1294                                 quote_level++;
1295                         else
1296                                 break;
1297                 }
1298
1299                 p++;
1300         }
1301
1302         return quote_level;
1303 }
1304
1305 gint check_line_length(const gchar *str, gint max_chars, gint *line)
1306 {
1307         const gchar *p = str, *q;
1308         gint cur_line = 0, len;
1309
1310         while ((q = strchr(p, '\n')) != NULL) {
1311                 len = q - p + 1;
1312                 if (len > max_chars) {
1313                         if (line)
1314                                 *line = cur_line;
1315                         return -1;
1316                 }
1317                 p = q + 1;
1318                 ++cur_line;
1319         }
1320
1321         len = strlen(p);
1322         if (len > max_chars) {
1323                 if (line)
1324                         *line = cur_line;
1325                 return -1;
1326         }
1327
1328         return 0;
1329 }
1330
1331 const gchar * line_has_quote_char(const gchar * str, const gchar *quote_chars) 
1332 {
1333         gchar * position = NULL;
1334         gchar * tmp_pos = NULL;
1335         int i;
1336
1337         if (quote_chars == NULL)
1338                 return FALSE;
1339         
1340         for (i = 0; i < strlen(quote_chars); i++) {
1341                 tmp_pos = strchr (str,  quote_chars[i]);
1342                 if(position == NULL 
1343                    || (tmp_pos != NULL && position >= tmp_pos) )
1344                         position = tmp_pos;
1345         }
1346         return position; 
1347 }
1348
1349 const gchar * line_has_quote_char_last(const gchar * str, const gchar *quote_chars) 
1350 {
1351         gchar * position = NULL;
1352         gchar * tmp_pos = NULL;
1353         int i;
1354
1355         if (quote_chars == NULL)
1356                 return FALSE;
1357         
1358         for (i = 0; i < strlen(quote_chars); i++) {
1359                 tmp_pos = strrchr (str, quote_chars[i]);
1360                 if(position == NULL 
1361                    || (tmp_pos != NULL && position <= tmp_pos) )
1362                         position = tmp_pos;
1363         }
1364         return position; 
1365 }
1366
1367 gchar *strstr_with_skip_quote(const gchar *haystack, const gchar *needle)
1368 {
1369         register guint haystack_len, needle_len;
1370         gboolean in_squote = FALSE, in_dquote = FALSE;
1371
1372         haystack_len = strlen(haystack);
1373         needle_len   = strlen(needle);
1374
1375         if (haystack_len < needle_len || needle_len == 0)
1376                 return NULL;
1377
1378         while (haystack_len >= needle_len) {
1379                 if (!in_squote && !in_dquote &&
1380                     !strncmp(haystack, needle, needle_len))
1381                         return (gchar *)haystack;
1382
1383                 /* 'foo"bar"' -> foo"bar"
1384                    "foo'bar'" -> foo'bar' */
1385                 if (*haystack == '\'') {
1386                         if (in_squote)
1387                                 in_squote = FALSE;
1388                         else if (!in_dquote)
1389                                 in_squote = TRUE;
1390                 } else if (*haystack == '\"') {
1391                         if (in_dquote)
1392                                 in_dquote = FALSE;
1393                         else if (!in_squote)
1394                                 in_dquote = TRUE;
1395                 }
1396
1397                 haystack++;
1398                 haystack_len--;
1399         }
1400
1401         return NULL;
1402 }
1403
1404 gchar *strchr_parenthesis_close(const gchar *str, gchar op, gchar cl)
1405 {
1406         const gchar *p;
1407         gchar quote_chr = '"';
1408         gint in_brace;
1409         gboolean in_quote = FALSE;
1410
1411         p = str;
1412
1413         if ((p = strchr_with_skip_quote(p, quote_chr, op))) {
1414                 p++;
1415                 in_brace = 1;
1416                 while (*p) {
1417                         if (*p == op && !in_quote)
1418                                 in_brace++;
1419                         else if (*p == cl && !in_quote)
1420                                 in_brace--;
1421                         else if (*p == quote_chr)
1422                                 in_quote ^= TRUE;
1423
1424                         if (in_brace == 0)
1425                                 return (gchar *)p;
1426
1427                         p++;
1428                 }
1429         }
1430
1431         return NULL;
1432 }
1433
1434 gchar **strsplit_parenthesis(const gchar *str, gchar op, gchar cl,
1435                              gint max_tokens)
1436 {
1437         GSList *string_list = NULL, *slist;
1438         gchar **str_array;
1439         const gchar *s_op, *s_cl;
1440         guint i, n = 1;
1441
1442         g_return_val_if_fail(str != NULL, NULL);
1443
1444         if (max_tokens < 1)
1445                 max_tokens = G_MAXINT;
1446
1447         s_op = strchr_with_skip_quote(str, '"', op);
1448         if (!s_op) return NULL;
1449         str = s_op;
1450         s_cl = strchr_parenthesis_close(str, op, cl);
1451         if (s_cl) {
1452                 do {
1453                         guint len;
1454                         gchar *new_string;
1455
1456                         str++;
1457                         len = s_cl - str;
1458                         new_string = g_new(gchar, len + 1);
1459                         strncpy(new_string, str, len);
1460                         new_string[len] = 0;
1461                         string_list = g_slist_prepend(string_list, new_string);
1462                         n++;
1463                         str = s_cl + 1;
1464
1465                         while (*str && g_ascii_isspace(*str)) str++;
1466                         if (*str != op) {
1467                                 string_list = g_slist_prepend(string_list,
1468                                                               g_strdup(""));
1469                                 n++;
1470                                 s_op = strchr_with_skip_quote(str, '"', op);
1471                                 if (!--max_tokens || !s_op) break;
1472                                 str = s_op;
1473                         } else
1474                                 s_op = str;
1475                         s_cl = strchr_parenthesis_close(str, op, cl);
1476                 } while (--max_tokens && s_cl);
1477         }
1478
1479         str_array = g_new(gchar*, n);
1480
1481         i = n - 1;
1482
1483         str_array[i--] = NULL;
1484         for (slist = string_list; slist; slist = slist->next)
1485                 str_array[i--] = slist->data;
1486
1487         g_slist_free(string_list);
1488
1489         return str_array;
1490 }
1491
1492 gchar **strsplit_with_quote(const gchar *str, const gchar *delim,
1493                             gint max_tokens)
1494 {
1495         GSList *string_list = NULL, *slist;
1496         gchar **str_array, *s, *new_str;
1497         guint i, n = 1, len;
1498
1499         g_return_val_if_fail(str != NULL, NULL);
1500         g_return_val_if_fail(delim != NULL, NULL);
1501
1502         if (max_tokens < 1)
1503                 max_tokens = G_MAXINT;
1504
1505         s = strstr_with_skip_quote(str, delim);
1506         if (s) {
1507                 guint delimiter_len = strlen(delim);
1508
1509                 do {
1510                         len = s - str;
1511                         new_str = g_strndup(str, len);
1512
1513                         if (new_str[0] == '\'' || new_str[0] == '\"') {
1514                                 if (new_str[len - 1] == new_str[0]) {
1515                                         new_str[len - 1] = '\0';
1516                                         memmove(new_str, new_str + 1, len - 1);
1517                                 }
1518                         }
1519                         string_list = g_slist_prepend(string_list, new_str);
1520                         n++;
1521                         str = s + delimiter_len;
1522                         s = strstr_with_skip_quote(str, delim);
1523                 } while (--max_tokens && s);
1524         }
1525
1526         if (*str) {
1527                 new_str = g_strdup(str);
1528                 if (new_str[0] == '\'' || new_str[0] == '\"') {
1529                         len = strlen(str);
1530                         if (new_str[len - 1] == new_str[0]) {
1531                                 new_str[len - 1] = '\0';
1532                                 memmove(new_str, new_str + 1, len - 1);
1533                         }
1534                 }
1535                 string_list = g_slist_prepend(string_list, new_str);
1536                 n++;
1537         }
1538
1539         str_array = g_new(gchar*, n);
1540
1541         i = n - 1;
1542
1543         str_array[i--] = NULL;
1544         for (slist = string_list; slist; slist = slist->next)
1545                 str_array[i--] = slist->data;
1546
1547         g_slist_free(string_list);
1548
1549         return str_array;
1550 }
1551
1552 gchar *get_abbrev_newsgroup_name(const gchar *group, gint len)
1553 {
1554         gchar *abbrev_group;
1555         gchar *ap;
1556         const gchar *p = group;
1557         const gchar *last;
1558
1559         g_return_val_if_fail(group != NULL, NULL);
1560
1561         last = group + strlen(group);
1562         abbrev_group = ap = g_malloc(strlen(group) + 1);
1563
1564         while (*p) {
1565                 while (*p == '.')
1566                         *ap++ = *p++;
1567                 if ((ap - abbrev_group) + (last - p) > len && strchr(p, '.')) {
1568                         *ap++ = *p++;
1569                         while (*p != '.') p++;
1570                 } else {
1571                         strcpy(ap, p);
1572                         return abbrev_group;
1573                 }
1574         }
1575
1576         *ap = '\0';
1577         return abbrev_group;
1578 }
1579
1580 gchar *trim_string(const gchar *str, gint len)
1581 {
1582         const gchar *p = str;
1583         gint mb_len;
1584         gchar *new_str;
1585         gint new_len = 0;
1586
1587         if (!str) return NULL;
1588         if (strlen(str) <= len)
1589                 return g_strdup(str);
1590         if (g_utf8_validate(str, -1, NULL) == FALSE)
1591                 return g_strdup(str);
1592
1593         while (*p != '\0') {
1594                 mb_len = g_utf8_skip[*(guchar *)p];
1595                 if (mb_len == 0)
1596                         break;
1597                 else if (new_len + mb_len > len)
1598                         break;
1599
1600                 new_len += mb_len;
1601                 p += mb_len;
1602         }
1603
1604         Xstrndup_a(new_str, str, new_len, return g_strdup(str));
1605         return g_strconcat(new_str, "...", NULL);
1606 }
1607
1608 GList *uri_list_extract_filenames(const gchar *uri_list)
1609 {
1610         GList *result = NULL;
1611         const gchar *p, *q;
1612         gchar *escaped_utf8uri;
1613
1614         p = uri_list;
1615
1616         while (p) {
1617                 if (*p != '#') {
1618                         while (g_ascii_isspace(*p)) p++;
1619                         if (!strncmp(p, "file:", 5)) {
1620                                 q = p;
1621                                 q += 5;
1622                                 while (*q && *q != '\n' && *q != '\r') q++;
1623
1624                                 if (q > p) {
1625                                         gchar *file, *locale_file = NULL;
1626                                         q--;
1627                                         while (q > p && g_ascii_isspace(*q))
1628                                                 q--;
1629                                         Xalloca(escaped_utf8uri, q - p + 2,
1630                                                 return result);
1631                                         Xalloca(file, q - p + 2,
1632                                                 return result);
1633                                         *file = '\0';
1634                                         strncpy(escaped_utf8uri, p, q - p + 1);
1635                                         escaped_utf8uri[q - p + 1] = '\0';
1636                                         decode_uri(file, escaped_utf8uri);
1637                     /*
1638                      * g_filename_from_uri() rejects escaped/locale encoded uri
1639                      * string which come from Nautilus.
1640                      */
1641                                         if (g_utf8_validate(file, -1, NULL))
1642                                                 locale_file
1643                                                         = conv_codeset_strdup(
1644                                                                 file + 5,
1645                                                                 CS_UTF_8,
1646                                                                 conv_get_locale_charset_str());
1647                                         if (!locale_file)
1648                                                 locale_file = g_strdup(file + 5);
1649                                         result = g_list_append(result, locale_file);
1650                                 }
1651                         }
1652                 }
1653                 p = strchr(p, '\n');
1654                 if (p) p++;
1655         }
1656
1657         return result;
1658 }
1659
1660 /* Converts two-digit hexadecimal to decimal.  Used for unescaping escaped 
1661  * characters
1662  */
1663 static gint axtoi(const gchar *hexstr)
1664 {
1665         gint hi, lo, result;
1666        
1667         hi = hexstr[0];
1668         if ('0' <= hi && hi <= '9') {
1669                 hi -= '0';
1670         } else
1671                 if ('a' <= hi && hi <= 'f') {
1672                         hi -= ('a' - 10);
1673                 } else
1674                         if ('A' <= hi && hi <= 'F') {
1675                                 hi -= ('A' - 10);
1676                         }
1677
1678         lo = hexstr[1];
1679         if ('0' <= lo && lo <= '9') {
1680                 lo -= '0';
1681         } else
1682                 if ('a' <= lo && lo <= 'f') {
1683                         lo -= ('a'-10);
1684                 } else
1685                         if ('A' <= lo && lo <= 'F') {
1686                                 lo -= ('A' - 10);
1687                         }
1688         result = lo + (16 * hi);
1689         return result;
1690 }
1691
1692 gboolean is_uri_string(const gchar *str)
1693 {
1694         return (g_ascii_strncasecmp(str, "http://", 7) == 0 ||
1695                 g_ascii_strncasecmp(str, "https://", 8) == 0 ||
1696                 g_ascii_strncasecmp(str, "ftp://", 6) == 0 ||
1697                 g_ascii_strncasecmp(str, "www.", 4) == 0);
1698 }
1699
1700 gchar *get_uri_path(const gchar *uri)
1701 {
1702         if (g_ascii_strncasecmp(uri, "http://", 7) == 0)
1703                 return (gchar *)(uri + 7);
1704         else if (g_ascii_strncasecmp(uri, "https://", 8) == 0)
1705                 return (gchar *)(uri + 8);
1706         else if (g_ascii_strncasecmp(uri, "ftp://", 6) == 0)
1707                 return (gchar *)(uri + 6);
1708         else
1709                 return (gchar *)uri;
1710 }
1711
1712 gint get_uri_len(const gchar *str)
1713 {
1714         const gchar *p;
1715
1716         if (is_uri_string(str)) {
1717                 for (p = str; *p != '\0'; p++) {
1718                         if (!g_ascii_isgraph(*p) || strchr("()<>\"", *p))
1719                                 break;
1720                 }
1721                 return p - str;
1722         }
1723
1724         return 0;
1725 }
1726
1727 /* Decodes URL-Encoded strings (i.e. strings in which spaces are replaced by
1728  * plusses, and escape characters are used)
1729  */
1730 void decode_uri(gchar *decoded_uri, const gchar *encoded_uri)
1731 {
1732         gchar *dec = decoded_uri;
1733         const gchar *enc = encoded_uri;
1734
1735         while (*enc) {
1736                 if (*enc == '%') {
1737                         enc++;
1738                         if (isxdigit((guchar)enc[0]) &&
1739                             isxdigit((guchar)enc[1])) {
1740                                 *dec = axtoi(enc);
1741                                 dec++;
1742                                 enc += 2;
1743                         }
1744                 } else {
1745                         if (*enc == '+')
1746                                 *dec = ' ';
1747                         else
1748                                 *dec = *enc;
1749                         dec++;
1750                         enc++;
1751                 }
1752         }
1753
1754         *dec = '\0';
1755 }
1756
1757 gint scan_mailto_url(const gchar *mailto, gchar **to, gchar **cc, gchar **bcc,
1758                      gchar **subject, gchar **body)
1759 {
1760         gchar *tmp_mailto;
1761         gchar *p;
1762
1763         Xstrdup_a(tmp_mailto, mailto, return -1);
1764
1765         if (!strncmp(tmp_mailto, "mailto:", 7))
1766                 tmp_mailto += 7;
1767
1768         p = strchr(tmp_mailto, '?');
1769         if (p) {
1770                 *p = '\0';
1771                 p++;
1772         }
1773
1774         if (to && !*to)
1775                 *to = g_strdup(tmp_mailto);
1776
1777         while (p) {
1778                 gchar *field, *value;
1779
1780                 field = p;
1781
1782                 p = strchr(p, '=');
1783                 if (!p) break;
1784                 *p = '\0';
1785                 p++;
1786
1787                 value = p;
1788
1789                 p = strchr(p, '&');
1790                 if (p) {
1791                         *p = '\0';
1792                         p++;
1793                 }
1794
1795                 if (*value == '\0') continue;
1796
1797                 if (cc && !*cc && !g_ascii_strcasecmp(field, "cc")) {
1798                         *cc = g_strdup(value);
1799                 } else if (bcc && !*bcc && !g_ascii_strcasecmp(field, "bcc")) {
1800                         *bcc = g_strdup(value);
1801                 } else if (subject && !*subject &&
1802                            !g_ascii_strcasecmp(field, "subject")) {
1803                         *subject = g_malloc(strlen(value) + 1);
1804                         decode_uri(*subject, value);
1805                 } else if (body && !*body && !g_ascii_strcasecmp(field, "body")) {
1806                         *body = g_malloc(strlen(value) + 1);
1807                         decode_uri(*body, value);
1808                 }
1809         }
1810
1811         return 0;
1812 }
1813
1814 const gchar *get_home_dir(void)
1815 {
1816 #ifdef G_OS_WIN32
1817         static const gchar *home_dir = NULL;
1818
1819         if (!home_dir) {
1820                 home_dir = g_get_home_dir();
1821                 if (!home_dir)
1822                         home_dir = "C:\\Sylpheed";
1823         }
1824
1825         return home_dir;
1826 #else
1827         return g_get_home_dir();
1828 #endif
1829 }
1830
1831 const gchar *get_rc_dir(void)
1832 {
1833         static gchar *rc_dir = NULL;
1834
1835         if (!rc_dir)
1836 #ifdef G_OS_WIN32
1837                 rc_dir = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
1838                                      "Application Data", G_DIR_SEPARATOR_S,
1839                                      RC_DIR, NULL);
1840 #else
1841                 rc_dir = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
1842                                      RC_DIR, NULL);
1843 #endif
1844
1845         return rc_dir;
1846 }
1847
1848 const gchar *get_mail_base_dir(void)
1849 {
1850 #ifdef G_OS_WIN32
1851         static gchar *mail_base_dir = NULL;
1852
1853         if (!mail_base_dir)
1854                 mail_base_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1855                                             "Mailboxes", NULL);
1856
1857         return mail_base_dir;
1858 #else
1859         return get_home_dir();
1860 #endif
1861 }
1862
1863 const gchar *get_news_cache_dir(void)
1864 {
1865         static gchar *news_cache_dir = NULL;
1866
1867         if (!news_cache_dir)
1868                 news_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1869                                              NEWS_CACHE_DIR, NULL);
1870
1871         return news_cache_dir;
1872 }
1873
1874 const gchar *get_imap_cache_dir(void)
1875 {
1876         static gchar *imap_cache_dir = NULL;
1877
1878         if (!imap_cache_dir)
1879                 imap_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1880                                              IMAP_CACHE_DIR, NULL);
1881
1882         return imap_cache_dir;
1883 }
1884
1885 const gchar *get_mbox_cache_dir(void)
1886 {
1887         static gchar *mbox_cache_dir = NULL;
1888
1889         if (!mbox_cache_dir)
1890                 mbox_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1891                                              MBOX_CACHE_DIR, NULL);
1892
1893         return mbox_cache_dir;
1894 }
1895
1896 const gchar *get_mime_tmp_dir(void)
1897 {
1898         static gchar *mime_tmp_dir = NULL;
1899
1900         if (!mime_tmp_dir)
1901                 mime_tmp_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1902                                            MIME_TMP_DIR, NULL);
1903
1904         return mime_tmp_dir;
1905 }
1906
1907 const gchar *get_template_dir(void)
1908 {
1909         static gchar *template_dir = NULL;
1910
1911         if (!template_dir)
1912                 template_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1913                                            TEMPLATE_DIR, NULL);
1914
1915         return template_dir;
1916 }
1917
1918 const gchar *get_header_cache_dir(void)
1919 {
1920         static gchar *header_dir = NULL;
1921
1922         if (!header_dir)
1923                 header_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1924                                          HEADER_CACHE_DIR, NULL);
1925
1926         return header_dir;
1927 }
1928
1929 const gchar *get_tmp_dir(void)
1930 {
1931         static gchar *tmp_dir = NULL;
1932
1933         if (!tmp_dir)
1934                 tmp_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1935                                       TMP_DIR, NULL);
1936
1937         return tmp_dir;
1938 }
1939
1940 gchar *get_tmp_file(void)
1941 {
1942         gchar *tmp_file;
1943         static guint32 id = 0;
1944
1945         tmp_file = g_strdup_printf("%s%ctmpfile.%08x",
1946                                    get_tmp_dir(), G_DIR_SEPARATOR, id++);
1947
1948         return tmp_file;
1949 }
1950
1951 const gchar *get_domain_name(void)
1952 {
1953 #ifdef G_OS_UNIX
1954         static gchar *domain_name = NULL;
1955
1956         if (!domain_name) {
1957                 struct hostent *hp;
1958                 struct utsname uts;
1959
1960                 if (uname(&uts) < 0) {
1961                         perror("uname");
1962                         domain_name = "unknown";
1963                 } else {
1964                         if ((hp = my_gethostbyname(uts.nodename)) == NULL) {
1965                                 perror("gethostbyname");
1966                                 domain_name = g_strdup(uts.nodename);
1967                         } else {
1968                                 domain_name = g_strdup(hp->h_name);
1969                         }
1970                 }
1971
1972                 debug_print("domain name = %s\n", domain_name);
1973         }
1974
1975         return domain_name;
1976 #else
1977         return "unknown";
1978 #endif
1979 }
1980
1981 off_t get_file_size(const gchar *file)
1982 {
1983         struct stat s;
1984
1985         if (g_stat(file, &s) < 0) {
1986                 FILE_OP_ERROR(file, "stat");
1987                 return -1;
1988         }
1989
1990         return s.st_size;
1991 }
1992
1993 off_t get_file_size_as_crlf(const gchar *file)
1994 {
1995         FILE *fp;
1996         off_t size = 0;
1997         gchar buf[BUFFSIZE];
1998
1999         if ((fp = g_fopen(file, "rb")) == NULL) {
2000                 FILE_OP_ERROR(file, "fopen");
2001                 return -1;
2002         }
2003
2004         while (fgets(buf, sizeof(buf), fp) != NULL) {
2005                 strretchomp(buf);
2006                 size += strlen(buf) + 2;
2007         }
2008
2009         if (ferror(fp)) {
2010                 FILE_OP_ERROR(file, "fgets");
2011                 size = -1;
2012         }
2013
2014         fclose(fp);
2015
2016         return size;
2017 }
2018
2019 off_t get_left_file_size(FILE *fp)
2020 {
2021         glong pos;
2022         glong end;
2023         off_t size;
2024
2025         if ((pos = ftell(fp)) < 0) {
2026                 perror("ftell");
2027                 return -1;
2028         }
2029         if (fseek(fp, 0L, SEEK_END) < 0) {
2030                 perror("fseek");
2031                 return -1;
2032         }
2033         if ((end = ftell(fp)) < 0) {
2034                 perror("fseek");
2035                 return -1;
2036         }
2037         size = end - pos;
2038         if (fseek(fp, pos, SEEK_SET) < 0) {
2039                 perror("fseek");
2040                 return -1;
2041         }
2042
2043         return size;
2044 }
2045
2046 gboolean file_exist(const gchar *file, gboolean allow_fifo)
2047 {
2048         struct stat s;
2049
2050         if (file == NULL)
2051                 return FALSE;
2052
2053         if (g_stat(file, &s) < 0) {
2054                 if (ENOENT != errno) FILE_OP_ERROR(file, "stat");
2055                 return FALSE;
2056         }
2057
2058         if (S_ISREG(s.st_mode) || (allow_fifo && S_ISFIFO(s.st_mode)))
2059                 return TRUE;
2060
2061         return FALSE;
2062 }
2063
2064 gboolean is_dir_exist(const gchar *dir)
2065 {
2066         if (dir == NULL)
2067                 return FALSE;
2068
2069         return g_file_test(dir, G_FILE_TEST_IS_DIR);
2070 }
2071
2072 gboolean is_file_entry_exist(const gchar *file)
2073 {
2074         if (file == NULL)
2075                 return FALSE;
2076
2077         return g_file_test(file, G_FILE_TEST_EXISTS);
2078 }
2079
2080 gboolean dirent_is_regular_file(struct dirent *d)
2081 {
2082 #ifdef HAVE_DIRENT_D_TYPE
2083         if (d->d_type == DT_REG)
2084                 return TRUE;
2085         else if (d->d_type != DT_UNKNOWN)
2086                 return FALSE;
2087 #endif
2088
2089         return g_file_test(d->d_name, G_FILE_TEST_IS_REGULAR);
2090 }
2091
2092 gboolean dirent_is_directory(struct dirent *d)
2093 {
2094 #ifdef HAVE_DIRENT_D_TYPE
2095         if (d->d_type == DT_DIR)
2096                 return TRUE;
2097         else if (d->d_type != DT_UNKNOWN)
2098                 return FALSE;
2099 #endif
2100
2101         return g_file_test(d->d_name, G_FILE_TEST_IS_DIR);
2102 }
2103
2104 gint change_dir(const gchar *dir)
2105 {
2106         gchar *prevdir = NULL;
2107
2108         if (debug_mode)
2109                 prevdir = g_get_current_dir();
2110
2111         if (g_chdir(dir) < 0) {
2112                 FILE_OP_ERROR(dir, "chdir");
2113                 if (debug_mode) g_free(prevdir);
2114                 return -1;
2115         } else if (debug_mode) {
2116                 gchar *cwd;
2117
2118                 cwd = g_get_current_dir();
2119                 if (strcmp(prevdir, cwd) != 0)
2120                         g_print("current dir: %s\n", cwd);
2121                 g_free(cwd);
2122                 g_free(prevdir);
2123         }
2124
2125         return 0;
2126 }
2127
2128 gint make_dir(const gchar *dir)
2129 {
2130         if (g_mkdir(dir, S_IRWXU) < 0) {
2131                 FILE_OP_ERROR(dir, "mkdir");
2132                 return -1;
2133         }
2134         if (g_chmod(dir, S_IRWXU) < 0)
2135                 FILE_OP_ERROR(dir, "chmod");
2136
2137         return 0;
2138 }
2139
2140 gint make_dir_hier(const gchar *dir)
2141 {
2142         gchar *parent_dir;
2143         const gchar *p;
2144
2145         for (p = dir; (p = strchr(p, G_DIR_SEPARATOR)) != NULL; p++) {
2146                 parent_dir = g_strndup(dir, p - dir);
2147                 if (*parent_dir != '\0') {
2148                         if (!is_dir_exist(parent_dir)) {
2149                                 if (make_dir(parent_dir) < 0) {
2150                                         g_free(parent_dir);
2151                                         return -1;
2152                                 }
2153                         }
2154                 }
2155                 g_free(parent_dir);
2156         }
2157
2158         if (!is_dir_exist(dir)) {
2159                 if (make_dir(dir) < 0)
2160                         return -1;
2161         }
2162
2163         return 0;
2164 }
2165
2166 gint remove_all_files(const gchar *dir)
2167 {
2168         GDir *dp;
2169         const gchar *dir_name;
2170         gchar *prev_dir;
2171
2172         prev_dir = g_get_current_dir();
2173
2174         if (g_chdir(dir) < 0) {
2175                 FILE_OP_ERROR(dir, "chdir");
2176                 g_free(prev_dir);
2177                 return -1;
2178         }
2179
2180         if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2181                 g_warning("failed to open directory: %s\n", dir);
2182                 g_free(prev_dir);
2183                 return -1;
2184         }
2185
2186         while ((dir_name = g_dir_read_name(dp)) != NULL) {
2187                 if (g_unlink(dir_name) < 0)
2188                         FILE_OP_ERROR(dir_name, "unlink");
2189         }
2190
2191         g_dir_close(dp);
2192
2193         if (g_chdir(prev_dir) < 0) {
2194                 FILE_OP_ERROR(prev_dir, "chdir");
2195                 g_free(prev_dir);
2196                 return -1;
2197         }
2198
2199         g_free(prev_dir);
2200
2201         return 0;
2202 }
2203
2204 gint remove_numbered_files(const gchar *dir, guint first, guint last)
2205 {
2206         GDir *dp;
2207         const gchar *dir_name;
2208         gchar *prev_dir;
2209         gint file_no;
2210
2211         prev_dir = g_get_current_dir();
2212
2213         if (g_chdir(dir) < 0) {
2214                 FILE_OP_ERROR(dir, "chdir");
2215                 g_free(prev_dir);
2216                 return -1;
2217         }
2218
2219         if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2220                 g_warning("failed to open directory: %s\n", dir);
2221                 g_free(prev_dir);
2222                 return -1;
2223         }
2224
2225         while ((dir_name = g_dir_read_name(dp)) != NULL) {
2226                 file_no = to_number(dir_name);
2227                 if (file_no > 0 && first <= file_no && file_no <= last) {
2228                         if (is_dir_exist(dir_name))
2229                                 continue;
2230                         if (g_unlink(dir_name) < 0)
2231                                 FILE_OP_ERROR(dir_name, "unlink");
2232                 }
2233         }
2234
2235         g_dir_close(dp);
2236
2237         if (g_chdir(prev_dir) < 0) {
2238                 FILE_OP_ERROR(prev_dir, "chdir");
2239                 g_free(prev_dir);
2240                 return -1;
2241         }
2242
2243         g_free(prev_dir);
2244
2245         return 0;
2246 }
2247
2248 gint remove_numbered_files_not_in_list(const gchar *dir, GSList *numberlist)
2249 {
2250         GDir *dp;
2251         const gchar *dir_name;
2252         gchar *prev_dir;
2253         gint file_no;
2254
2255         prev_dir = g_get_current_dir();
2256
2257         if (g_chdir(dir) < 0) {
2258                 FILE_OP_ERROR(dir, "chdir");
2259                 g_free(prev_dir);
2260                 return -1;
2261         }
2262
2263         if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2264                 FILE_OP_ERROR(dir, "opendir");
2265                 g_free(prev_dir);
2266                 return -1;
2267         }
2268
2269         while ((dir_name = g_dir_read_name(dp)) != NULL) {
2270                 file_no = to_number(dir_name);
2271                 if (file_no > 0 && (g_slist_find(numberlist, GINT_TO_POINTER(file_no)) == NULL)) {
2272                         debug_print("removing unwanted file %d from %s\n", file_no, dir);
2273                         if (is_dir_exist(dir_name))
2274                                 continue;
2275                         if (g_unlink(dir_name) < 0)
2276                                 FILE_OP_ERROR(dir_name, "unlink");
2277                 }
2278         }
2279
2280         g_dir_close(dp);
2281
2282         if (g_chdir(prev_dir) < 0) {
2283                 FILE_OP_ERROR(prev_dir, "chdir");
2284                 g_free(prev_dir);
2285                 return -1;
2286         }
2287
2288         g_free(prev_dir);
2289
2290         return 0;
2291 }
2292
2293 gint remove_all_numbered_files(const gchar *dir)
2294 {
2295         return remove_numbered_files(dir, 0, UINT_MAX);
2296 }
2297
2298 gint remove_expired_files(const gchar *dir, guint hours)
2299 {
2300         GDir *dp;
2301         const gchar *dir_name;
2302         struct stat s;
2303         gchar *prev_dir;
2304         gint file_no;
2305         time_t mtime, now, expire_time;
2306
2307         prev_dir = g_get_current_dir();
2308
2309         if (g_chdir(dir) < 0) {
2310                 FILE_OP_ERROR(dir, "chdir");
2311                 g_free(prev_dir);
2312                 return -1;
2313         }
2314
2315         if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2316                 g_warning("failed to open directory: %s\n", dir);
2317                 g_free(prev_dir);
2318                 return -1;
2319         }
2320
2321         now = time(NULL);
2322         expire_time = hours * 60 * 60;
2323
2324         while ((dir_name = g_dir_read_name(dp)) != NULL) {
2325                 file_no = to_number(dir_name);
2326                 if (file_no > 0) {
2327                         if (g_stat(dir_name, &s) < 0) {
2328                                 FILE_OP_ERROR(dir_name, "stat");
2329                                 continue;
2330                         }
2331                         if (S_ISDIR(s.st_mode))
2332                                 continue;
2333                         mtime = MAX(s.st_mtime, s.st_atime);
2334                         if (now - mtime > expire_time) {
2335                                 if (g_unlink(dir_name) < 0)
2336                                         FILE_OP_ERROR(dir_name, "unlink");
2337                         }
2338                 }
2339         }
2340
2341         g_dir_close(dp);
2342
2343         if (g_chdir(prev_dir) < 0) {
2344                 FILE_OP_ERROR(prev_dir, "chdir");
2345                 g_free(prev_dir);
2346                 return -1;
2347         }
2348
2349         g_free(prev_dir);
2350
2351         return 0;
2352 }
2353
2354 gint remove_dir_recursive(const gchar *dir)
2355 {
2356         struct stat s;
2357         GDir *dp;
2358         const gchar *dir_name;
2359         gchar *prev_dir;
2360
2361         if (g_stat(dir, &s) < 0) {
2362                 FILE_OP_ERROR(dir, "stat");
2363                 if (ENOENT == errno) return 0;
2364                 return -1;
2365         }
2366
2367         if (!S_ISDIR(s.st_mode)) {
2368                 if (g_unlink(dir) < 0) {
2369                         FILE_OP_ERROR(dir, "unlink");
2370                         return -1;
2371                 }
2372
2373                 return 0;
2374         }
2375
2376         prev_dir = g_get_current_dir();
2377         /* g_print("prev_dir = %s\n", prev_dir); */
2378
2379         if (!path_cmp(prev_dir, dir)) {
2380                 g_free(prev_dir);
2381                 if (g_chdir("..") < 0) {
2382                         FILE_OP_ERROR(dir, "chdir");
2383                         return -1;
2384                 }
2385                 prev_dir = g_get_current_dir();
2386         }
2387
2388         if (g_chdir(dir) < 0) {
2389                 FILE_OP_ERROR(dir, "chdir");
2390                 g_free(prev_dir);
2391                 return -1;
2392         }
2393
2394         if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2395                 g_warning("failed to open directory: %s\n", dir);
2396                 g_chdir(prev_dir);
2397                 g_free(prev_dir);
2398                 return -1;
2399         }
2400
2401         /* remove all files in the directory */
2402         while ((dir_name = g_dir_read_name(dp)) != NULL) {
2403                 /* g_print("removing %s\n", dir_name); */
2404
2405                 if (is_dir_exist(dir_name)) {
2406                         if (remove_dir_recursive(dir_name) < 0) {
2407                                 g_warning("can't remove directory\n");
2408                                 return -1;
2409                         }
2410                 } else {
2411                         if (g_unlink(dir_name) < 0)
2412                                 FILE_OP_ERROR(dir_name, "unlink");
2413                 }
2414         }
2415
2416         g_dir_close(dp);
2417
2418         if (g_chdir(prev_dir) < 0) {
2419                 FILE_OP_ERROR(prev_dir, "chdir");
2420                 g_free(prev_dir);
2421                 return -1;
2422         }
2423
2424         g_free(prev_dir);
2425
2426         if (g_rmdir(dir) < 0) {
2427                 FILE_OP_ERROR(dir, "rmdir");
2428                 return -1;
2429         }
2430
2431         return 0;
2432 }
2433
2434 gint rename_force(const gchar *oldpath, const gchar *newpath)
2435 {
2436 #ifndef G_OS_UNIX
2437         if (!is_file_entry_exist(oldpath)) {
2438                 errno = ENOENT;
2439                 return -1;
2440         }
2441         if (is_file_exist(newpath)) {
2442                 if (g_unlink(newpath) < 0)
2443                         FILE_OP_ERROR(newpath, "unlink");
2444         }
2445 #endif
2446         return g_rename(oldpath, newpath);
2447 }
2448
2449 #if 0
2450 /* this seems to be slower than the stdio version... */
2451 gint copy_file(const gchar *src, const gchar *dest)
2452 {
2453         gint src_fd, dest_fd;
2454         gint n_read;
2455         gint n_write;
2456         gchar buf[BUFSIZ];
2457         gchar *dest_bak = NULL;
2458
2459         if ((src_fd = open(src, O_RDONLY)) < 0) {
2460                 FILE_OP_ERROR(src, "open");
2461                 return -1;
2462         }
2463
2464         if (is_file_exist(dest)) {
2465                 dest_bak = g_strconcat(dest, ".bak", NULL);
2466                 if (rename_force(dest, dest_bak) < 0) {
2467                         FILE_OP_ERROR(dest, "rename");
2468                         close(src_fd);
2469                         g_free(dest_bak);
2470                         return -1;
2471                 }
2472         }
2473
2474         if ((dest_fd = open(dest, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR)) < 0) {
2475                 FILE_OP_ERROR(dest, "open");
2476                 close(src_fd);
2477                 if (dest_bak) {
2478                         if (rename(dest_bak, dest) < 0)
2479                                 FILE_OP_ERROR(dest_bak, "rename");
2480                         g_free(dest_bak);
2481                 }
2482                 return -1;
2483         }
2484
2485         while ((n_read = read(src_fd, buf, sizeof(buf))) > 0) {
2486                 gint len = n_read;
2487                 gchar *bufp = buf;
2488
2489                 while (len > 0) {
2490                         n_write = write(dest_fd, bufp, len);
2491                         if (n_write <= 0) {
2492                                 g_warning("writing to %s failed.\n", dest);
2493                                 close(dest_fd);
2494                                 close(src_fd);
2495                                 g_unlink(dest);
2496                                 if (dest_bak) {
2497                                         if (rename(dest_bak, dest) < 0)
2498                                                 FILE_OP_ERROR(dest_bak, "rename");
2499                                         g_free(dest_bak);
2500                                 }
2501                                 return -1;
2502                         }
2503                         len -= n_write;
2504                         bufp += n_write;
2505                 }
2506         }
2507
2508         close(src_fd);
2509         close(dest_fd);
2510
2511         if (n_read < 0 || get_file_size(src) != get_file_size(dest)) {
2512                 g_warning("File copy from %s to %s failed.\n", src, dest);
2513                 g_unlink(dest);
2514                 if (dest_bak) {
2515                         if (rename(dest_bak, dest) < 0)
2516                                 FILE_OP_ERROR(dest_bak, "rename");
2517                         g_free(dest_bak);
2518                 }
2519                 return -1;
2520         }
2521         g_free(dest_bak);
2522
2523         return 0;
2524 }
2525 #endif
2526
2527
2528 /*
2529  * Append src file body to the tail of dest file.
2530  * Now keep_backup has no effects.
2531  */
2532 gint append_file(const gchar *src, const gchar *dest, gboolean keep_backup)
2533 {
2534         FILE *src_fp, *dest_fp;
2535         gint n_read;
2536         gchar buf[BUFSIZ];
2537
2538         gboolean err = FALSE;
2539
2540         if ((src_fp = g_fopen(src, "rb")) == NULL) {
2541                 FILE_OP_ERROR(src, "fopen");
2542                 return -1;
2543         }
2544         
2545         if ((dest_fp = g_fopen(dest, "ab")) == NULL) {
2546                 FILE_OP_ERROR(dest, "fopen");
2547                 fclose(src_fp);
2548                 return -1;
2549         }
2550
2551         if (change_file_mode_rw(dest_fp, dest) < 0) {
2552                 FILE_OP_ERROR(dest, "chmod");
2553                 g_warning("can't change file mode\n");
2554         }
2555
2556         while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
2557                 if (n_read < sizeof(buf) && ferror(src_fp))
2558                         break;
2559                 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2560                         g_warning("writing to %s failed.\n", dest);
2561                         fclose(dest_fp);
2562                         fclose(src_fp);
2563                         g_unlink(dest);
2564                         return -1;
2565                 }
2566         }
2567
2568         if (ferror(src_fp)) {
2569                 FILE_OP_ERROR(src, "fread");
2570                 err = TRUE;
2571         }
2572         fclose(src_fp);
2573         if (fclose(dest_fp) == EOF) {
2574                 FILE_OP_ERROR(dest, "fclose");
2575                 err = TRUE;
2576         }
2577
2578         if (err) {
2579                 g_unlink(dest);
2580                 return -1;
2581         }
2582
2583         return 0;
2584 }
2585
2586 gint copy_file(const gchar *src, const gchar *dest, gboolean keep_backup)
2587 {
2588         FILE *src_fp, *dest_fp;
2589         gint n_read;
2590         gchar buf[BUFSIZ];
2591         gchar *dest_bak = NULL;
2592         gboolean err = FALSE;
2593
2594         if ((src_fp = g_fopen(src, "rb")) == NULL) {
2595                 FILE_OP_ERROR(src, "fopen");
2596                 return -1;
2597         }
2598         if (is_file_exist(dest)) {
2599                 dest_bak = g_strconcat(dest, ".bak", NULL);
2600                 if (rename_force(dest, dest_bak) < 0) {
2601                         FILE_OP_ERROR(dest, "rename");
2602                         fclose(src_fp);
2603                         g_free(dest_bak);
2604                         return -1;
2605                 }
2606         }
2607
2608         if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2609                 FILE_OP_ERROR(dest, "fopen");
2610                 fclose(src_fp);
2611                 if (dest_bak) {
2612                         if (rename_force(dest_bak, dest) < 0)
2613                                 FILE_OP_ERROR(dest_bak, "rename");
2614                         g_free(dest_bak);
2615                 }
2616                 return -1;
2617         }
2618
2619         if (change_file_mode_rw(dest_fp, dest) < 0) {
2620                 FILE_OP_ERROR(dest, "chmod");
2621                 g_warning("can't change file mode\n");
2622         }
2623
2624         while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
2625                 if (n_read < sizeof(buf) && ferror(src_fp))
2626                         break;
2627                 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2628                         g_warning("writing to %s failed.\n", dest);
2629                         fclose(dest_fp);
2630                         fclose(src_fp);
2631                         g_unlink(dest);
2632                         if (dest_bak) {
2633                                 if (rename_force(dest_bak, dest) < 0)
2634                                         FILE_OP_ERROR(dest_bak, "rename");
2635                                 g_free(dest_bak);
2636                         }
2637                         return -1;
2638                 }
2639         }
2640
2641         if (ferror(src_fp)) {
2642                 FILE_OP_ERROR(src, "fread");
2643                 err = TRUE;
2644         }
2645         fclose(src_fp);
2646         if (fclose(dest_fp) == EOF) {
2647                 FILE_OP_ERROR(dest, "fclose");
2648                 err = TRUE;
2649         }
2650
2651         if (err) {
2652                 g_unlink(dest);
2653                 if (dest_bak) {
2654                         if (rename_force(dest_bak, dest) < 0)
2655                                 FILE_OP_ERROR(dest_bak, "rename");
2656                         g_free(dest_bak);
2657                 }
2658                 return -1;
2659         }
2660
2661         if (keep_backup == FALSE && dest_bak)
2662                 g_unlink(dest_bak);
2663
2664         g_free(dest_bak);
2665
2666         return 0;
2667 }
2668
2669 gint move_file(const gchar *src, const gchar *dest, gboolean overwrite)
2670 {
2671         if (overwrite == FALSE && is_file_exist(dest)) {
2672                 g_warning("move_file(): file %s already exists.", dest);
2673                 return -1;
2674         }
2675
2676         if (rename_force(src, dest) == 0) return 0;
2677
2678         if (EXDEV != errno) {
2679                 FILE_OP_ERROR(src, "rename");
2680                 return -1;
2681         }
2682
2683         if (copy_file(src, dest, FALSE) < 0) return -1;
2684
2685         g_unlink(src);
2686
2687         return 0;
2688 }
2689
2690 gint copy_file_part_to_fp(FILE *fp, off_t offset, size_t length, FILE *dest_fp)
2691 {
2692         gint n_read;
2693         gint bytes_left, to_read;
2694         gchar buf[BUFSIZ];
2695
2696         if (fseek(fp, offset, SEEK_SET) < 0) {
2697                 perror("fseek");
2698                 return -1;
2699         }
2700
2701         bytes_left = length;
2702         to_read = MIN(bytes_left, sizeof(buf));
2703
2704         while ((n_read = fread(buf, sizeof(gchar), to_read, fp)) > 0) {
2705                 if (n_read < to_read && ferror(fp))
2706                         break;
2707                 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2708                         return -1;
2709                 }
2710                 bytes_left -= n_read;
2711                 if (bytes_left == 0)
2712                         break;
2713                 to_read = MIN(bytes_left, sizeof(buf));
2714         }
2715
2716         if (ferror(fp)) {
2717                 perror("fread");
2718                 return -1;
2719         }
2720
2721         return 0;
2722 }
2723
2724 gint copy_file_part(FILE *fp, off_t offset, size_t length, const gchar *dest)
2725 {
2726         FILE *dest_fp;
2727         gboolean err = FALSE;
2728
2729         if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2730                 FILE_OP_ERROR(dest, "fopen");
2731                 return -1;
2732         }
2733
2734         if (change_file_mode_rw(dest_fp, dest) < 0) {
2735                 FILE_OP_ERROR(dest, "chmod");
2736                 g_warning("can't change file mode\n");
2737         }
2738
2739         if (copy_file_part_to_fp(fp, offset, length, dest_fp) < 0)
2740                 err = TRUE;
2741
2742         if (!err && fclose(dest_fp) == EOF) {
2743                 FILE_OP_ERROR(dest, "fclose");
2744                 err = TRUE;
2745         }
2746
2747         if (err) {
2748                 g_warning("writing to %s failed.\n", dest);
2749                 g_unlink(dest);
2750                 return -1;
2751         }
2752
2753         return 0;
2754 }
2755
2756 /* convert line endings into CRLF. If the last line doesn't end with
2757  * linebreak, add it.
2758  */
2759 gchar *canonicalize_str(const gchar *str)
2760 {
2761         const gchar *p;
2762         guint new_len = 0;
2763         gchar *out, *outp;
2764
2765         for (p = str; *p != '\0'; ++p) {
2766                 if (*p != '\r') {
2767                         ++new_len;
2768                         if (*p == '\n')
2769                                 ++new_len;
2770                 }
2771         }
2772         if (p == str || *(p - 1) != '\n')
2773                 new_len += 2;
2774
2775         out = outp = g_malloc(new_len + 1);
2776         for (p = str; *p != '\0'; ++p) {
2777                 if (*p != '\r') {
2778                         if (*p == '\n')
2779                                 *outp++ = '\r';
2780                         *outp++ = *p;
2781                 }
2782         }
2783         if (p == str || *(p - 1) != '\n') {
2784                 *outp++ = '\r';
2785                 *outp++ = '\n';
2786         }
2787         *outp = '\0';
2788
2789         return out;
2790 }
2791
2792 gint canonicalize_file(const gchar *src, const gchar *dest)
2793 {
2794         FILE *src_fp, *dest_fp;
2795         gchar buf[BUFFSIZE];
2796         gint len;
2797         gboolean err = FALSE;
2798         gboolean last_linebreak = FALSE;
2799
2800         if ((src_fp = g_fopen(src, "rb")) == NULL) {
2801                 FILE_OP_ERROR(src, "fopen");
2802                 return -1;
2803         }
2804
2805         if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2806                 FILE_OP_ERROR(dest, "fopen");
2807                 fclose(src_fp);
2808                 return -1;
2809         }
2810
2811         if (change_file_mode_rw(dest_fp, dest) < 0) {
2812                 FILE_OP_ERROR(dest, "chmod");
2813                 g_warning("can't change file mode\n");
2814         }
2815
2816         while (fgets(buf, sizeof(buf), src_fp) != NULL) {
2817                 gint r = 0;
2818
2819                 len = strlen(buf);
2820                 if (len == 0) break;
2821                 last_linebreak = FALSE;
2822
2823                 if (buf[len - 1] != '\n') {
2824                         last_linebreak = TRUE;
2825                         r = fputs(buf, dest_fp);
2826                 } else if (len > 1 && buf[len - 1] == '\n' && buf[len - 2] == '\r') {
2827                         r = fputs(buf, dest_fp);
2828                 } else {
2829                         if (len > 1) {
2830                                 r = fwrite(buf, 1, len - 1, dest_fp);
2831                                 if (r != (len -1))
2832                                         r = EOF;
2833                         }
2834                         if (r != EOF)
2835                                 r = fputs("\r\n", dest_fp);
2836                 }
2837
2838                 if (r == EOF) {
2839                         g_warning("writing to %s failed.\n", dest);
2840                         fclose(dest_fp);
2841                         fclose(src_fp);
2842                         g_unlink(dest);
2843                         return -1;
2844                 }
2845         }
2846
2847         if (last_linebreak == TRUE) {
2848                 if (fputs("\r\n", dest_fp) == EOF)
2849                         err = TRUE;
2850         }
2851
2852         if (ferror(src_fp)) {
2853                 FILE_OP_ERROR(src, "fgets");
2854                 err = TRUE;
2855         }
2856         fclose(src_fp);
2857         if (fclose(dest_fp) == EOF) {
2858                 FILE_OP_ERROR(dest, "fclose");
2859                 err = TRUE;
2860         }
2861
2862         if (err) {
2863                 g_unlink(dest);
2864                 return -1;
2865         }
2866
2867         return 0;
2868 }
2869
2870 gint canonicalize_file_replace(const gchar *file)
2871 {
2872         gchar *tmp_file;
2873
2874         tmp_file = get_tmp_file();
2875
2876         if (canonicalize_file(file, tmp_file) < 0) {
2877                 g_free(tmp_file);
2878                 return -1;
2879         }
2880
2881         if (move_file(tmp_file, file, TRUE) < 0) {
2882                 g_warning("can't replace %s .\n", file);
2883                 g_unlink(tmp_file);
2884                 g_free(tmp_file);
2885                 return -1;
2886         }
2887
2888         g_free(tmp_file);
2889         return 0;
2890 }
2891
2892 gint uncanonicalize_file(const gchar *src, const gchar *dest)
2893 {
2894         FILE *src_fp, *dest_fp;
2895         gchar buf[BUFFSIZE];
2896         gboolean err = FALSE;
2897
2898         if ((src_fp = g_fopen(src, "rb")) == NULL) {
2899                 FILE_OP_ERROR(src, "fopen");
2900                 return -1;
2901         }
2902
2903         if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2904                 FILE_OP_ERROR(dest, "fopen");
2905                 fclose(src_fp);
2906                 return -1;
2907         }
2908
2909         if (change_file_mode_rw(dest_fp, dest) < 0) {
2910                 FILE_OP_ERROR(dest, "chmod");
2911                 g_warning("can't change file mode\n");
2912         }
2913
2914         while (fgets(buf, sizeof(buf), src_fp) != NULL) {
2915                 strcrchomp(buf);
2916                 if (fputs(buf, dest_fp) == EOF) {
2917                         g_warning("writing to %s failed.\n", dest);
2918                         fclose(dest_fp);
2919                         fclose(src_fp);
2920                         g_unlink(dest);
2921                         return -1;
2922                 }
2923         }
2924
2925         if (ferror(src_fp)) {
2926                 FILE_OP_ERROR(src, "fgets");
2927                 err = TRUE;
2928         }
2929         fclose(src_fp);
2930         if (fclose(dest_fp) == EOF) {
2931                 FILE_OP_ERROR(dest, "fclose");
2932                 err = TRUE;
2933         }
2934
2935         if (err) {
2936                 g_unlink(dest);
2937                 return -1;
2938         }
2939
2940         return 0;
2941 }
2942
2943 gint uncanonicalize_file_replace(const gchar *file)
2944 {
2945         gchar *tmp_file;
2946
2947         tmp_file = get_tmp_file();
2948
2949         if (uncanonicalize_file(file, tmp_file) < 0) {
2950                 g_free(tmp_file);
2951                 return -1;
2952         }
2953
2954         if (move_file(tmp_file, file, TRUE) < 0) {
2955                 g_warning("can't replace %s .\n", file);
2956                 g_unlink(tmp_file);
2957                 g_free(tmp_file);
2958                 return -1;
2959         }
2960
2961         g_free(tmp_file);
2962         return 0;
2963 }
2964
2965 gchar *normalize_newlines(const gchar *str)
2966 {
2967         const gchar *p = str;
2968         gchar *out, *outp;
2969
2970         out = outp = g_malloc(strlen(str) + 1);
2971         for (p = str; *p != '\0'; ++p) {
2972                 if (*p == '\r') {
2973                         if (*(p + 1) != '\n')
2974                                 *outp++ = '\n';
2975                 } else
2976                         *outp++ = *p;
2977         }
2978
2979         *outp = '\0';
2980
2981         return out;
2982 }
2983
2984 gchar *get_outgoing_rfc2822_str(FILE *fp)
2985 {
2986         gchar buf[BUFFSIZE];
2987         GString *str;
2988         gchar *ret;
2989
2990         str = g_string_new(NULL);
2991
2992         /* output header part */
2993         while (fgets(buf, sizeof(buf), fp) != NULL) {
2994                 strretchomp(buf);
2995                 if (!g_ascii_strncasecmp(buf, "Bcc:", 4)) {
2996                         gint next;
2997
2998                         for (;;) {
2999                                 next = fgetc(fp);
3000                                 if (next == EOF)
3001                                         break;
3002                                 else if (next != ' ' && next != '\t') {
3003                                         ungetc(next, fp);
3004                                         break;
3005                                 }
3006                                 if (fgets(buf, sizeof(buf), fp) == NULL)
3007                                         break;
3008                         }
3009                 } else {
3010                         g_string_append(str, buf);
3011                         g_string_append(str, "\r\n");
3012                         if (buf[0] == '\0')
3013                                 break;
3014                 }
3015         }
3016
3017         /* output body part */
3018         while (fgets(buf, sizeof(buf), fp) != NULL) {
3019                 strretchomp(buf);
3020                 if (buf[0] == '.')
3021                         g_string_append_c(str, '.');
3022                 g_string_append(str, buf);
3023                 g_string_append(str, "\r\n");
3024         }
3025
3026         ret = str->str;
3027         g_string_free(str, FALSE);
3028
3029         return ret;
3030 }
3031
3032 /*
3033  * Create a new boundary in a way that it is very unlikely that this
3034  * will occur in the following text.  It would be easy to ensure
3035  * uniqueness if everything is either quoted-printable or base64
3036  * encoded (note that conversion is allowed), but because MIME bodies
3037  * may be nested, it may happen that the same boundary has already
3038  * been used. 
3039  *
3040  *   boundary := 0*69<bchars> bcharsnospace
3041  *   bchars := bcharsnospace / " "
3042  *   bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" /
3043  *                    "+" / "_" / "," / "-" / "." /
3044  *                    "/" / ":" / "=" / "?"
3045  *
3046  * some special characters removed because of buggy MTAs
3047  */
3048
3049 gchar *generate_mime_boundary(const gchar *prefix)
3050 {
3051         static gchar tbl[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
3052                              "abcdefghijklmnopqrstuvwxyz"
3053                              "1234567890+_./=";
3054         gchar buf_uniq[24];
3055         gint i;
3056
3057         for (i = 0; i < sizeof(buf_uniq) - 1; i++)
3058                 buf_uniq[i] = tbl[g_random_int_range(0, sizeof(tbl) - 1)];
3059         buf_uniq[i] = '\0';
3060
3061         return g_strdup_printf("%s_%s", prefix ? prefix : "MP",
3062                                buf_uniq);
3063 }
3064
3065 gint change_file_mode_rw(FILE *fp, const gchar *file)
3066 {
3067 #if HAVE_FCHMOD
3068         return fchmod(fileno(fp), S_IRUSR|S_IWUSR);
3069 #else
3070         return g_chmod(file, S_IRUSR|S_IWUSR);
3071 #endif
3072 }
3073
3074 FILE *my_tmpfile(void)
3075 {
3076 #if HAVE_MKSTEMP
3077         const gchar suffix[] = ".XXXXXX";
3078         const gchar *tmpdir;
3079         guint tmplen;
3080         const gchar *progname;
3081         guint proglen;
3082         gchar *fname;
3083         gint fd;
3084         FILE *fp;
3085
3086         tmpdir = get_tmp_dir();
3087         tmplen = strlen(tmpdir);
3088         progname = g_get_prgname();
3089         if (progname == NULL)
3090                 progname = "sylpheed-claws";
3091         proglen = strlen(progname);
3092         Xalloca(fname, tmplen + 1 + proglen + sizeof(suffix),
3093                 return tmpfile());
3094
3095         memcpy(fname, tmpdir, tmplen);
3096         fname[tmplen] = G_DIR_SEPARATOR;
3097         memcpy(fname + tmplen + 1, progname, proglen);
3098         memcpy(fname + tmplen + 1 + proglen, suffix, sizeof(suffix));
3099
3100         fd = mkstemp(fname);
3101         if (fd < 0)
3102                 return tmpfile();
3103
3104         g_unlink(fname);
3105
3106         fp = fdopen(fd, "w+b");
3107         if (!fp)
3108                 close(fd);
3109         else
3110                 return fp;
3111 #endif /* HAVE_MKSTEMP */
3112
3113         return tmpfile();
3114 }
3115
3116 FILE *get_tmpfile_in_dir(const gchar *dir, gchar **filename)
3117 {
3118         int fd;
3119         
3120         *filename = g_strdup_printf("%s%csylpheed.XXXXXX", dir, G_DIR_SEPARATOR);
3121         fd = mkstemp(*filename);
3122
3123         return fdopen(fd, "w+");
3124 }
3125
3126 FILE *str_open_as_stream(const gchar *str)
3127 {
3128         FILE *fp;
3129         size_t len;
3130
3131         g_return_val_if_fail(str != NULL, NULL);
3132
3133         fp = my_tmpfile();
3134         if (!fp) {
3135                 FILE_OP_ERROR("str_open_as_stream", "my_tmpfile");
3136                 return NULL;
3137         }
3138
3139         len = strlen(str);
3140         if (len == 0) return fp;
3141
3142         if (fwrite(str, 1, len, fp) != len) {
3143                 FILE_OP_ERROR("str_open_as_stream", "fwrite");
3144                 fclose(fp);
3145                 return NULL;
3146         }
3147
3148         rewind(fp);
3149         return fp;
3150 }
3151
3152 gint str_write_to_file(const gchar *str, const gchar *file)
3153 {
3154         FILE *fp;
3155         size_t len;
3156
3157         g_return_val_if_fail(str != NULL, -1);
3158         g_return_val_if_fail(file != NULL, -1);
3159
3160         if ((fp = g_fopen(file, "wb")) == NULL) {
3161                 FILE_OP_ERROR(file, "fopen");
3162                 return -1;
3163         }
3164
3165         len = strlen(str);
3166         if (len == 0) {
3167                 fclose(fp);
3168                 return 0;
3169         }
3170
3171         if (fwrite(str, 1, len, fp) != len) {
3172                 FILE_OP_ERROR(file, "fwrite");
3173                 fclose(fp);
3174                 g_unlink(file);
3175                 return -1;
3176         }
3177
3178         if (fclose(fp) == EOF) {
3179                 FILE_OP_ERROR(file, "fclose");
3180                 g_unlink(file);
3181                 return -1;
3182         }
3183
3184         return 0;
3185 }
3186
3187 gchar *file_read_to_str(const gchar *file)
3188 {
3189         FILE *fp;
3190         gchar *str;
3191
3192         g_return_val_if_fail(file != NULL, NULL);
3193
3194         if ((fp = g_fopen(file, "rb")) == NULL) {
3195                 FILE_OP_ERROR(file, "fopen");
3196                 return NULL;
3197         }
3198
3199         str = file_read_stream_to_str(fp);
3200
3201         fclose(fp);
3202
3203         return str;
3204 }
3205
3206 gchar *file_read_stream_to_str(FILE *fp)
3207 {
3208         GByteArray *array;
3209         guchar buf[BUFSIZ];
3210         gint n_read;
3211         gchar *str;
3212
3213         g_return_val_if_fail(fp != NULL, NULL);
3214
3215         array = g_byte_array_new();
3216
3217         while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
3218                 if (n_read < sizeof(buf) && ferror(fp))
3219                         break;
3220                 g_byte_array_append(array, buf, n_read);
3221         }
3222
3223         if (ferror(fp)) {
3224                 FILE_OP_ERROR("file stream", "fread");
3225                 g_byte_array_free(array, TRUE);
3226                 return NULL;
3227         }
3228
3229         buf[0] = '\0';
3230         g_byte_array_append(array, buf, 1);
3231         str = (gchar *)array->data;
3232         g_byte_array_free(array, FALSE);
3233
3234         if (!g_utf8_validate(str, -1, NULL)) {
3235                 const gchar *src_codeset, *dest_codeset;
3236                 gchar *tmp = NULL;
3237                 src_codeset = conv_get_locale_charset_str();
3238                 dest_codeset = CS_UTF_8;
3239                 tmp = conv_codeset_strdup(str, src_codeset, dest_codeset);
3240                 g_free(str);
3241                 str = tmp;
3242         }
3243
3244         return str;
3245 }
3246
3247 gint execute_async(gchar *const argv[])
3248 {
3249         g_return_val_if_fail(argv != NULL && argv[0] != NULL, -1);
3250
3251         if (g_spawn_async(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH,
3252                           NULL, NULL, NULL, FALSE) == FALSE) {
3253                 g_warning("Can't execute command: %s\n", argv[0]);
3254                 return -1;
3255         }
3256
3257         return 0;
3258 }
3259
3260 gint execute_sync(gchar *const argv[])
3261 {
3262         gint status;
3263
3264         g_return_val_if_fail(argv != NULL && argv[0] != NULL, -1);
3265
3266         if (g_spawn_sync(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH,
3267                          NULL, NULL, NULL, NULL, &status, NULL) == FALSE) {
3268                 g_warning("Can't execute command: %s\n", argv[0]);
3269                 return -1;
3270         }
3271
3272 #ifdef G_OS_UNIX
3273         if (WIFEXITED(status))
3274                 return WEXITSTATUS(status);
3275         else
3276                 return -1;
3277 #else
3278         return status;
3279 #endif
3280 }
3281
3282 gint execute_command_line(const gchar *cmdline, gboolean async)
3283 {
3284         gchar **argv;
3285         gint ret;
3286
3287         debug_print("execute_command_line(): executing: %s\n", cmdline);
3288
3289         argv = strsplit_with_quote(cmdline, " ", 0);
3290
3291         if (async)
3292                 ret = execute_async(argv);
3293         else
3294                 ret = execute_sync(argv);
3295
3296         g_strfreev(argv);
3297
3298         return ret;
3299 }
3300
3301 gchar *get_command_output(const gchar *cmdline)
3302 {
3303         gchar *child_stdout;
3304         gint status;
3305
3306         g_return_val_if_fail(cmdline != NULL, NULL);
3307
3308         debug_print("get_command_output(): executing: %s\n", cmdline);
3309
3310         if (g_spawn_command_line_sync(cmdline, &child_stdout, NULL, &status,
3311                                       NULL) == FALSE) {
3312                 g_warning("Can't execute command: %s\n", cmdline);
3313                 return NULL;
3314         }
3315
3316         return child_stdout;
3317 }
3318
3319 static gint is_unchanged_uri_char(char c)
3320 {
3321         switch (c) {
3322                 case '(':
3323                 case ')':
3324                 case ',':
3325                         return 0;
3326                 default:
3327                         return 1;
3328         }
3329 }
3330
3331 void encode_uri(gchar *encoded_uri, gint bufsize, const gchar *uri)
3332 {
3333         int i;
3334         int k;
3335
3336         k = 0;
3337         for(i = 0; i < strlen(uri) ; i++) {
3338                 if (is_unchanged_uri_char(uri[i])) {
3339                         if (k + 2 >= bufsize)
3340                                 break;
3341                         encoded_uri[k++] = uri[i];
3342                 }
3343                 else {
3344                         char * hexa = "0123456789ABCDEF";
3345                         
3346                         if (k + 4 >= bufsize)
3347                                 break;
3348                         encoded_uri[k++] = '%';
3349                         encoded_uri[k++] = hexa[uri[i] / 16];
3350                         encoded_uri[k++] = hexa[uri[i] % 16];
3351                 }
3352         }
3353         encoded_uri[k] = 0;
3354 }
3355
3356 gint open_uri(const gchar *uri, const gchar *cmdline)
3357 {
3358         gchar buf[BUFFSIZE];
3359         gchar *p;
3360         gchar encoded_uri[BUFFSIZE];
3361         
3362         g_return_val_if_fail(uri != NULL, -1);
3363
3364         /* an option to choose whether to use encode_uri or not ? */
3365         encode_uri(encoded_uri, BUFFSIZE, uri);
3366         
3367         if (cmdline &&
3368             (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
3369             !strchr(p + 2, '%'))
3370                 g_snprintf(buf, sizeof(buf), cmdline, encoded_uri);
3371         else {
3372                 if (cmdline)
3373                         g_warning("Open URI command line is invalid "
3374                                   "(there must be only one '%%s'): %s",
3375                                   cmdline);
3376                 g_snprintf(buf, sizeof(buf), DEFAULT_BROWSER_CMD, encoded_uri);
3377         }
3378
3379         execute_command_line(buf, TRUE);
3380
3381         return 0;
3382 }
3383
3384 time_t remote_tzoffset_sec(const gchar *zone)
3385 {
3386         static gchar ustzstr[] = "PSTPDTMSTMDTCSTCDTESTEDT";
3387         gchar zone3[4];
3388         gchar *p;
3389         gchar c;
3390         gint iustz;
3391         gint offset;
3392         time_t remoteoffset;
3393
3394         strncpy(zone3, zone, 3);
3395         zone3[3] = '\0';
3396         remoteoffset = 0;
3397
3398         if (sscanf(zone, "%c%d", &c, &offset) == 2 &&
3399             (c == '+' || c == '-')) {
3400                 remoteoffset = ((offset / 100) * 60 + (offset % 100)) * 60;
3401                 if (c == '-')
3402                         remoteoffset = -remoteoffset;
3403         } else if (!strncmp(zone, "UT" , 2) ||
3404                    !strncmp(zone, "GMT", 2)) {
3405                 remoteoffset = 0;
3406         } else if (strlen(zone3) == 3) {
3407                 for (p = ustzstr; *p != '\0'; p += 3) {
3408                         if (!g_ascii_strncasecmp(p, zone3, 3)) {
3409                                 iustz = ((gint)(p - ustzstr) / 3 + 1) / 2 - 8;
3410                                 remoteoffset = iustz * 3600;
3411                                 break;
3412                         }
3413                 }
3414                 if (*p == '\0')
3415                         return -1;
3416         } else if (strlen(zone3) == 1) {
3417                 switch (zone[0]) {
3418                 case 'Z': remoteoffset =   0; break;
3419                 case 'A': remoteoffset =  -1; break;
3420                 case 'B': remoteoffset =  -2; break;
3421                 case 'C': remoteoffset =  -3; break;
3422                 case 'D': remoteoffset =  -4; break;
3423                 case 'E': remoteoffset =  -5; break;
3424                 case 'F': remoteoffset =  -6; break;
3425                 case 'G': remoteoffset =  -7; break;
3426                 case 'H': remoteoffset =  -8; break;
3427                 case 'I': remoteoffset =  -9; break;
3428                 case 'K': remoteoffset = -10; break; /* J is not used */
3429                 case 'L': remoteoffset = -11; break;
3430                 case 'M': remoteoffset = -12; break;
3431                 case 'N': remoteoffset =   1; break;
3432                 case 'O': remoteoffset =   2; break;
3433                 case 'P': remoteoffset =   3; break;
3434                 case 'Q': remoteoffset =   4; break;
3435                 case 'R': remoteoffset =   5; break;
3436                 case 'S': remoteoffset =   6; break;
3437                 case 'T': remoteoffset =   7; break;
3438                 case 'U': remoteoffset =   8; break;
3439                 case 'V': remoteoffset =   9; break;
3440                 case 'W': remoteoffset =  10; break;
3441                 case 'X': remoteoffset =  11; break;
3442                 case 'Y': remoteoffset =  12; break;
3443                 default:  remoteoffset =   0; break;
3444                 }
3445                 remoteoffset = remoteoffset * 3600;
3446         } else
3447                 return -1;
3448
3449         return remoteoffset;
3450 }
3451
3452 time_t tzoffset_sec(time_t *now)
3453 {
3454         struct tm gmt, *lt;
3455         gint off;
3456
3457         gmt = *gmtime(now);
3458         lt = localtime(now);
3459
3460         off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
3461
3462         if (lt->tm_year < gmt.tm_year)
3463                 off -= 24 * 60;
3464         else if (lt->tm_year > gmt.tm_year)
3465                 off += 24 * 60;
3466         else if (lt->tm_yday < gmt.tm_yday)
3467                 off -= 24 * 60;
3468         else if (lt->tm_yday > gmt.tm_yday)
3469                 off += 24 * 60;
3470
3471         if (off >= 24 * 60)             /* should be impossible */
3472                 off = 23 * 60 + 59;     /* if not, insert silly value */
3473         if (off <= -24 * 60)
3474                 off = -(23 * 60 + 59);
3475
3476         return off * 60;
3477 }
3478
3479 /* calculate timezone offset */
3480 gchar *tzoffset(time_t *now)
3481 {
3482         static gchar offset_string[6];
3483         struct tm gmt, *lt;
3484         gint off;
3485         gchar sign = '+';
3486
3487         gmt = *gmtime(now);
3488         lt = localtime(now);
3489
3490         off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
3491
3492         if (lt->tm_year < gmt.tm_year)
3493                 off -= 24 * 60;
3494         else if (lt->tm_year > gmt.tm_year)
3495                 off += 24 * 60;
3496         else if (lt->tm_yday < gmt.tm_yday)
3497                 off -= 24 * 60;
3498         else if (lt->tm_yday > gmt.tm_yday)
3499                 off += 24 * 60;
3500
3501         if (off < 0) {
3502                 sign = '-';
3503                 off = -off;
3504         }
3505
3506         if (off >= 24 * 60)             /* should be impossible */
3507                 off = 23 * 60 + 59;     /* if not, insert silly value */
3508
3509         sprintf(offset_string, "%c%02d%02d", sign, off / 60, off % 60);
3510
3511         return offset_string;
3512 }
3513
3514 void get_rfc822_date(gchar *buf, gint len)
3515 {
3516         struct tm *lt;
3517         time_t t;
3518         gchar day[4], mon[4];
3519         gint dd, hh, mm, ss, yyyy;
3520
3521         t = time(NULL);
3522         lt = localtime(&t);
3523
3524         sscanf(asctime(lt), "%3s %3s %d %d:%d:%d %d\n",
3525                day, mon, &dd, &hh, &mm, &ss, &yyyy);
3526         g_snprintf(buf, len, "%s, %d %s %d %02d:%02d:%02d %s",
3527                    day, dd, mon, yyyy, hh, mm, ss, tzoffset(&t));
3528 }
3529
3530 /* just a wrapper to suppress the warning of gcc about %c */
3531 size_t my_strftime(gchar *s, size_t max, const gchar *format,
3532                    const struct tm *tm)
3533 {
3534         return strftime(s, max, format, tm);
3535 }
3536
3537 void debug_set_mode(gboolean mode)
3538 {
3539         debug_mode = mode;
3540 }
3541
3542 gboolean debug_get_mode(void)
3543 {
3544         return debug_mode;
3545 }
3546
3547 void debug_print_real(const gchar *format, ...)
3548 {
3549         va_list args;
3550         gchar buf[BUFFSIZE];
3551
3552         if (!debug_mode) return;
3553
3554         va_start(args, format);
3555         g_vsnprintf(buf, sizeof(buf), format, args);
3556         va_end(args);
3557
3558         g_print("%s", buf);
3559 }
3560
3561 void * subject_table_lookup(GHashTable *subject_table, gchar * subject)
3562 {
3563         if (subject == NULL)
3564                 subject = "";
3565         else
3566                 subject += subject_get_prefix_length(subject);
3567
3568         return g_hash_table_lookup(subject_table, subject);
3569 }
3570
3571 void subject_table_insert(GHashTable *subject_table, gchar * subject,
3572                           void * data)
3573 {
3574         if (subject == NULL || *subject == 0)
3575                 return;
3576         subject += subject_get_prefix_length(subject);
3577         g_hash_table_insert(subject_table, subject, data);
3578 }
3579
3580 void subject_table_remove(GHashTable *subject_table, gchar * subject)
3581 {
3582         if (subject == NULL)
3583                 return;
3584
3585         subject += subject_get_prefix_length(subject);  
3586         g_hash_table_remove(subject_table, subject);
3587 }
3588
3589 /*!
3590  *\brief        Check if a string is prefixed with known (combinations) 
3591  *              of prefixes. The function assumes that each prefix 
3592  *              is terminated by zero or exactly _one_ space.
3593  *
3594  *\param        str String to check for a prefixes
3595  *
3596  *\return       int Number of chars in the prefix that should be skipped 
3597  *              for a "clean" subject line. If no prefix was found, 0
3598  *              is returned.
3599  */             
3600 int subject_get_prefix_length(const gchar *subject)
3601 {
3602         /*!< Array with allowable reply prefixes regexps. */
3603         static const gchar * const prefixes[] = {
3604                 "Re\\:",                        /* "Re:" */
3605                 "Re\\[[1-9][0-9]*\\]\\:",       /* "Re[XXX]:" (non-conforming news mail clients) */
3606                 "Antw\\:",                      /* "Antw:" (Dutch / German Outlook) */
3607                 "Aw\\:",                        /* "Aw:"   (German) */
3608                 "Antwort\\:",                   /* "Antwort:" (German Lotus Notes) */
3609                 "Res\\:",                       /* "Res:" (Brazilian Outlook) */
3610                 "Fw\\:",                        /* "Fw:" Forward */
3611                 "Enc\\:",                       /* "Enc:" Forward (Brazilian Outlook) */
3612                 "Odp\\:",                       /* "Odp:" Re (Polish Outlook) */
3613                 "Rif\\:"                        /* "Rif:" (Italian Outlook) */
3614                 /* add more */
3615         };
3616         const int PREFIXES = sizeof prefixes / sizeof prefixes[0];
3617         int n;
3618         regmatch_t pos;
3619         static regex_t regex;
3620         static gboolean init_;