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