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