2009-01-09 [colin] 3.7.0cvs19
[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, "SHGetFolderPathW");
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_utf16[MAX_PATH] = "";
1797         static gchar *home_dir_utf8 = NULL;
1798         if (home_dir_utf16[0] == '\0') {
1799                 if (w32_shgetfolderpath
1800                             (NULL, CSIDL_APPDATA|CSIDL_FLAG_CREATE,
1801                              NULL, 0, home_dir_utf16) < 0)
1802                                 strcpy (home_dir_utf16, "C:\\Sylpheed");
1803                 home_dir_utf8 = g_utf16_to_utf8 ((const gunichar *)home_dir_utf16, -1, NULL, NULL, NULL);
1804         }
1805         return home_dir_utf8;
1806 #else
1807         static const gchar *homeenv = NULL;
1808
1809         if (homeenv)
1810                 return homeenv;
1811
1812         if (!homeenv && g_getenv("HOME") != NULL)
1813                 homeenv = g_strdup(g_getenv("HOME"));
1814         if (!homeenv)
1815                 homeenv = g_get_home_dir();
1816
1817         return homeenv;
1818 #endif
1819 }
1820
1821 static gchar *claws_rc_dir = NULL;
1822 static gboolean rc_dir_alt = FALSE;
1823 const gchar *get_rc_dir(void)
1824 {
1825
1826         if (!claws_rc_dir)
1827                 claws_rc_dir = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
1828                                      RC_DIR, NULL);
1829
1830         return claws_rc_dir;
1831 }
1832
1833 void set_rc_dir(const gchar *dir)
1834 {
1835         if (claws_rc_dir != NULL) {
1836                 g_print("Error: rc_dir already set\n");
1837         } else {
1838                 rc_dir_alt = TRUE;
1839                 if (g_path_is_absolute(dir))
1840                         claws_rc_dir = g_strdup(dir);
1841                 else {
1842                         claws_rc_dir = g_strconcat(g_get_current_dir(),
1843                                 G_DIR_SEPARATOR_S, dir, NULL);
1844                 }
1845                 debug_print("set rc_dir to %s\n", claws_rc_dir);
1846                 if (!is_dir_exist(claws_rc_dir)) {
1847                         if (make_dir_hier(claws_rc_dir) != 0) {
1848                                 g_print("Error: can't create %s\n",
1849                                 claws_rc_dir);
1850                         }
1851                 }
1852         }
1853 }
1854
1855 gboolean rc_dir_is_alt(void) {
1856         return rc_dir_alt;
1857 }
1858
1859 const gchar *get_mail_base_dir(void)
1860 {
1861         return get_home_dir();
1862 }
1863
1864 #ifdef MAEMO
1865 const gchar *prefs_common_get_data_root(void);
1866 gchar *last_data_root = NULL;
1867 #endif
1868
1869 const gchar *get_news_cache_dir(void)
1870 {
1871         static gchar *news_cache_dir = NULL;
1872 #ifdef MAEMO
1873         const gchar *data_root = prefs_common_get_data_root();
1874         if (strcmp2(data_root, last_data_root)) {
1875                 g_free(news_cache_dir);
1876                 news_cache_dir = NULL;
1877         }
1878 #endif
1879         if (!news_cache_dir)
1880 #ifndef MAEMO
1881                 news_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1882                                              NEWS_CACHE_DIR, NULL);
1883 #else
1884         {
1885                 if (data_root) {
1886                         news_cache_dir = g_strconcat(data_root, G_DIR_SEPARATOR_S,
1887                                              "Claws", G_DIR_SEPARATOR_S, 
1888                                              g_get_user_name(), G_DIR_SEPARATOR_S,
1889                                              NEWS_CACHE_DIR, NULL);
1890                         g_free(last_data_root);
1891                         last_data_root = g_strdup(last_data_root);
1892                 } else {
1893                         news_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1894                                              NEWS_CACHE_DIR, NULL);
1895                         g_free(last_data_root);
1896                         last_data_root = NULL;
1897                 }
1898         }
1899 #endif
1900         return news_cache_dir;
1901 }
1902
1903 const gchar *get_imap_cache_dir(void)
1904 {
1905         static gchar *imap_cache_dir = NULL;
1906 #ifdef MAEMO
1907         const gchar *data_root = prefs_common_get_data_root();
1908         if (strcmp2(data_root, last_data_root)) {
1909                 g_free(imap_cache_dir);
1910                 imap_cache_dir = NULL;
1911         }
1912 #endif
1913
1914         if (!imap_cache_dir)
1915 #ifndef MAEMO
1916                 imap_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1917                                              IMAP_CACHE_DIR, NULL);
1918 #else
1919         {
1920                 if (data_root) {
1921                         imap_cache_dir = g_strconcat(data_root, G_DIR_SEPARATOR_S,
1922                                              "Claws", G_DIR_SEPARATOR_S, 
1923                                              g_get_user_name(), G_DIR_SEPARATOR_S,
1924                                              IMAP_CACHE_DIR, NULL);
1925                         g_free(last_data_root);
1926                         last_data_root = g_strdup(last_data_root);
1927                 } else {
1928                         imap_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1929                                              IMAP_CACHE_DIR, NULL);
1930                         g_free(last_data_root);
1931                         last_data_root = NULL;
1932                 }
1933         }
1934 #endif
1935
1936         return imap_cache_dir;
1937 }
1938
1939 const gchar *get_mime_tmp_dir(void)
1940 {
1941         static gchar *mime_tmp_dir = NULL;
1942
1943         if (!mime_tmp_dir)
1944                 mime_tmp_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1945                                            MIME_TMP_DIR, NULL);
1946
1947         return mime_tmp_dir;
1948 }
1949
1950 const gchar *get_template_dir(void)
1951 {
1952         static gchar *template_dir = NULL;
1953
1954         if (!template_dir)
1955                 template_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1956                                            TEMPLATE_DIR, NULL);
1957
1958         return template_dir;
1959 }
1960
1961 #ifdef G_OS_WIN32
1962 const gchar *get_cert_file(void)
1963 {
1964         const gchar *cert_file = NULL;
1965         if (!cert_file)
1966                 cert_file = g_strconcat(w32_get_module_dir(),
1967                                  "\\share\\claws-mail\\",
1968                                 "ca-certificates.crt",
1969                                 NULL);  
1970         return cert_file;
1971 }
1972 #endif
1973
1974 /* Return the default directory for Plugins. */
1975 const gchar *get_plugin_dir(void)
1976 {
1977 #ifdef G_OS_WIN32
1978         static gchar *plugin_dir = NULL;
1979
1980         if (!plugin_dir)
1981                 plugin_dir = g_strconcat(w32_get_module_dir(),
1982                                          "\\lib\\claws-mail\\plugins\\",
1983                                          NULL);
1984         return plugin_dir;
1985 #else
1986         if (is_dir_exist(PLUGINDIR))
1987                 return PLUGINDIR;
1988         else {
1989                 static gchar *plugin_dir = NULL;
1990                 if (!plugin_dir)
1991                         plugin_dir = g_strconcat(get_rc_dir(), 
1992                                 G_DIR_SEPARATOR_S, "plugins", 
1993                                 G_DIR_SEPARATOR_S, NULL);
1994                 return plugin_dir;                      
1995         }
1996 #endif
1997 }
1998
1999
2000 #ifdef G_OS_WIN32
2001 /* Return the default directory for Themes. */
2002 const gchar *get_themes_dir(void)
2003 {
2004         static gchar *themes_dir = NULL;
2005
2006         if (!themes_dir)
2007                 themes_dir = g_strconcat(w32_get_module_dir(),
2008                                          "\\share\\claws-mail\\themes",
2009                                          NULL);
2010         return themes_dir;
2011 }
2012 #endif
2013
2014 const gchar *get_tmp_dir(void)
2015 {
2016         static gchar *tmp_dir = NULL;
2017
2018         if (!tmp_dir)
2019                 tmp_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
2020                                       TMP_DIR, NULL);
2021
2022         return tmp_dir;
2023 }
2024
2025 gchar *get_tmp_file(void)
2026 {
2027         gchar *tmp_file;
2028         static guint32 id = 0;
2029
2030         tmp_file = g_strdup_printf("%s%ctmpfile.%08x",
2031                                    get_tmp_dir(), G_DIR_SEPARATOR, id++);
2032
2033         return tmp_file;
2034 }
2035
2036 const gchar *get_domain_name(void)
2037 {
2038 #ifdef G_OS_UNIX
2039         static gchar *domain_name = NULL;
2040
2041         if (!domain_name) {
2042                 struct hostent *hp;
2043                 char hostname[256];
2044
2045                 if (gethostname(hostname, sizeof(hostname)) != 0) {
2046                         perror("gethostname");
2047                         domain_name = "unknown";
2048                 } else {
2049                         hostname[sizeof(hostname) - 1] = '\0';
2050                         if ((hp = my_gethostbyname(hostname)) == NULL) {
2051                                 perror("gethostbyname");
2052                                 domain_name = g_strdup(hostname);
2053                         } else {
2054                                 domain_name = g_strdup(hp->h_name);
2055                         }
2056                 }
2057                 debug_print("domain name = %s\n", domain_name);
2058         }
2059
2060         return domain_name;
2061 #else
2062         return "unknown";
2063 #endif
2064 }
2065
2066 off_t get_file_size(const gchar *file)
2067 {
2068         struct stat s;
2069
2070         if (g_stat(file, &s) < 0) {
2071                 FILE_OP_ERROR(file, "stat");
2072                 return -1;
2073         }
2074
2075         return s.st_size;
2076 }
2077
2078 time_t get_file_mtime(const gchar *file)
2079 {
2080         struct stat s;
2081
2082         if (g_stat(file, &s) < 0) {
2083                 FILE_OP_ERROR(file, "stat");
2084                 return -1;
2085         }
2086
2087         return s.st_mtime;
2088 }
2089
2090 off_t get_file_size_as_crlf(const gchar *file)
2091 {
2092         FILE *fp;
2093         off_t size = 0;
2094         gchar buf[BUFFSIZE];
2095
2096         if ((fp = g_fopen(file, "rb")) == NULL) {
2097                 FILE_OP_ERROR(file, "fopen");
2098                 return -1;
2099         }
2100
2101         while (fgets(buf, sizeof(buf), fp) != NULL) {
2102                 strretchomp(buf);
2103                 size += strlen(buf) + 2;
2104         }
2105
2106         if (ferror(fp)) {
2107                 FILE_OP_ERROR(file, "fgets");
2108                 size = -1;
2109         }
2110
2111         fclose(fp);
2112
2113         return size;
2114 }
2115
2116 gboolean file_exist(const gchar *file, gboolean allow_fifo)
2117 {
2118         struct stat s;
2119
2120         if (file == NULL)
2121                 return FALSE;
2122
2123         if (g_stat(file, &s) < 0) {
2124                 if (ENOENT != errno) FILE_OP_ERROR(file, "stat");
2125                 return FALSE;
2126         }
2127
2128         if (S_ISREG(s.st_mode) || (allow_fifo && S_ISFIFO(s.st_mode)))
2129                 return TRUE;
2130
2131         return FALSE;
2132 }
2133
2134
2135 /* Test on whether FILE is a relative file name. This is
2136  * straightforward for Unix but more complex for Windows. */
2137 gboolean is_relative_filename(const gchar *file)
2138 {
2139         if (!file)
2140                 return TRUE;
2141 #ifdef G_OS_WIN32
2142         if ( *file == '\\' && file[1] == '\\' && strchr (file+2, '\\') )
2143                 return FALSE; /* Prefixed with a hostname - this can't
2144                                * be a relative name. */
2145
2146         if ( ((*file >= 'a' && *file <= 'z')
2147               || (*file >= 'A' && *file <= 'Z'))
2148              && file[1] == ':')
2149                 file += 2;  /* Skip drive letter. */
2150
2151         return !(*file == '\\' || *file == '/');
2152 #else
2153         return !(*file == G_DIR_SEPARATOR);
2154 #endif
2155 }
2156
2157
2158 gboolean is_dir_exist(const gchar *dir)
2159 {
2160         if (dir == NULL)
2161                 return FALSE;
2162
2163         return g_file_test(dir, G_FILE_TEST_IS_DIR);
2164 }
2165
2166 gboolean is_file_entry_exist(const gchar *file)
2167 {
2168         if (file == NULL)
2169                 return FALSE;
2170
2171         return g_file_test(file, G_FILE_TEST_EXISTS);
2172 }
2173
2174 gboolean dirent_is_regular_file(struct dirent *d)
2175 {
2176 #if !defined(G_OS_WIN32) && !defined(MAEMO) && defined(HAVE_DIRENT_D_TYPE)
2177         if (d->d_type == DT_REG)
2178                 return TRUE;
2179         else if (d->d_type != DT_UNKNOWN)
2180                 return FALSE;
2181 #endif
2182
2183         return g_file_test(d->d_name, G_FILE_TEST_IS_REGULAR);
2184 }
2185
2186 gint change_dir(const gchar *dir)
2187 {
2188         gchar *prevdir = NULL;
2189
2190         if (debug_mode)
2191                 prevdir = g_get_current_dir();
2192
2193         if (g_chdir(dir) < 0) {
2194                 FILE_OP_ERROR(dir, "chdir");
2195                 if (debug_mode) g_free(prevdir);
2196                 return -1;
2197         } else if (debug_mode) {
2198                 gchar *cwd;
2199
2200                 cwd = g_get_current_dir();
2201                 if (strcmp(prevdir, cwd) != 0)
2202                         g_print("current dir: %s\n", cwd);
2203                 g_free(cwd);
2204                 g_free(prevdir);
2205         }
2206
2207         return 0;
2208 }
2209
2210 gint make_dir(const gchar *dir)
2211 {
2212         if (g_mkdir(dir, S_IRWXU) < 0) {
2213                 FILE_OP_ERROR(dir, "mkdir");
2214                 return -1;
2215         }
2216         if (g_chmod(dir, S_IRWXU) < 0)
2217                 FILE_OP_ERROR(dir, "chmod");
2218
2219         return 0;
2220 }
2221
2222 gint make_dir_hier(const gchar *dir)
2223 {
2224         gchar *parent_dir;
2225         const gchar *p;
2226
2227         for (p = dir; (p = strchr(p, G_DIR_SEPARATOR)) != NULL; p++) {
2228                 parent_dir = g_strndup(dir, p - dir);
2229                 if (*parent_dir != '\0') {
2230                         if (!is_dir_exist(parent_dir)) {
2231                                 if (make_dir(parent_dir) < 0) {
2232                                         g_free(parent_dir);
2233                                         return -1;
2234                                 }
2235                         }
2236                 }
2237                 g_free(parent_dir);
2238         }
2239
2240         if (!is_dir_exist(dir)) {
2241                 if (make_dir(dir) < 0)
2242                         return -1;
2243         }
2244
2245         return 0;
2246 }
2247
2248 gint remove_all_files(const gchar *dir)
2249 {
2250         GDir *dp;
2251         const gchar *dir_name;
2252         gchar *prev_dir;
2253
2254         prev_dir = g_get_current_dir();
2255
2256         if (g_chdir(dir) < 0) {
2257                 FILE_OP_ERROR(dir, "chdir");
2258                 g_free(prev_dir);
2259                 return -1;
2260         }
2261
2262         if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2263                 g_warning("failed to open directory: %s\n", dir);
2264                 g_free(prev_dir);
2265                 return -1;
2266         }
2267
2268         while ((dir_name = g_dir_read_name(dp)) != NULL) {
2269                 if (claws_unlink(dir_name) < 0)
2270                         FILE_OP_ERROR(dir_name, "unlink");
2271         }
2272
2273         g_dir_close(dp);
2274
2275         if (g_chdir(prev_dir) < 0) {
2276                 FILE_OP_ERROR(prev_dir, "chdir");
2277                 g_free(prev_dir);
2278                 return -1;
2279         }
2280
2281         g_free(prev_dir);
2282
2283         return 0;
2284 }
2285
2286 gint remove_numbered_files(const gchar *dir, guint first, guint last)
2287 {
2288         GDir *dp;
2289         const gchar *dir_name;
2290         gchar *prev_dir;
2291         gint file_no;
2292
2293         prev_dir = g_get_current_dir();
2294
2295         if (g_chdir(dir) < 0) {
2296                 FILE_OP_ERROR(dir, "chdir");
2297                 g_free(prev_dir);
2298                 return -1;
2299         }
2300
2301         if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2302                 g_warning("failed to open directory: %s\n", dir);
2303                 g_free(prev_dir);
2304                 return -1;
2305         }
2306
2307         while ((dir_name = g_dir_read_name(dp)) != NULL) {
2308                 file_no = to_number(dir_name);
2309                 if (file_no > 0 && first <= file_no && file_no <= last) {
2310                         if (is_dir_exist(dir_name))
2311                                 continue;
2312                         if (claws_unlink(dir_name) < 0)
2313                                 FILE_OP_ERROR(dir_name, "unlink");
2314                 }
2315         }
2316
2317         g_dir_close(dp);
2318
2319         if (g_chdir(prev_dir) < 0) {
2320                 FILE_OP_ERROR(prev_dir, "chdir");
2321                 g_free(prev_dir);
2322                 return -1;
2323         }
2324
2325         g_free(prev_dir);
2326
2327         return 0;
2328 }
2329
2330 gint remove_numbered_files_not_in_list(const gchar *dir, GSList *numberlist)
2331 {
2332         GDir *dp;
2333         const gchar *dir_name;
2334         gchar *prev_dir;
2335         gint file_no;
2336
2337         prev_dir = g_get_current_dir();
2338
2339         if (g_chdir(dir) < 0) {
2340                 FILE_OP_ERROR(dir, "chdir");
2341                 g_free(prev_dir);
2342                 return -1;
2343         }
2344
2345         if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2346                 FILE_OP_ERROR(dir, "opendir");
2347                 g_free(prev_dir);
2348                 return -1;
2349         }
2350
2351         while ((dir_name = g_dir_read_name(dp)) != NULL) {
2352                 file_no = to_number(dir_name);
2353                 if (file_no > 0 && (g_slist_find(numberlist, GINT_TO_POINTER(file_no)) == NULL)) {
2354                         debug_print("removing unwanted file %d from %s\n", file_no, dir);
2355                         if (is_dir_exist(dir_name))
2356                                 continue;
2357                         if (claws_unlink(dir_name) < 0)
2358                                 FILE_OP_ERROR(dir_name, "unlink");
2359                 }
2360         }
2361
2362         g_dir_close(dp);
2363
2364         if (g_chdir(prev_dir) < 0) {
2365                 FILE_OP_ERROR(prev_dir, "chdir");
2366                 g_free(prev_dir);
2367                 return -1;
2368         }
2369
2370         g_free(prev_dir);
2371
2372         return 0;
2373 }
2374
2375 gint remove_all_numbered_files(const gchar *dir)
2376 {
2377         return remove_numbered_files(dir, 0, UINT_MAX);
2378 }
2379
2380 gint remove_dir_recursive(const gchar *dir)
2381 {
2382         struct stat s;
2383         GDir *dp;
2384         const gchar *dir_name;
2385         gchar *prev_dir;
2386
2387         if (g_stat(dir, &s) < 0) {
2388                 FILE_OP_ERROR(dir, "stat");
2389                 if (ENOENT == errno) return 0;
2390                 return -1;
2391         }
2392
2393         if (!S_ISDIR(s.st_mode)) {
2394                 if (claws_unlink(dir) < 0) {
2395                         FILE_OP_ERROR(dir, "unlink");
2396                         return -1;
2397                 }
2398
2399                 return 0;
2400         }
2401
2402         prev_dir = g_get_current_dir();
2403         /* g_print("prev_dir = %s\n", prev_dir); */
2404
2405         if (!path_cmp(prev_dir, dir)) {
2406                 g_free(prev_dir);
2407                 if (g_chdir("..") < 0) {
2408                         FILE_OP_ERROR(dir, "chdir");
2409                         return -1;
2410                 }
2411                 prev_dir = g_get_current_dir();
2412         }
2413
2414         if (g_chdir(dir) < 0) {
2415                 FILE_OP_ERROR(dir, "chdir");
2416                 g_free(prev_dir);
2417                 return -1;
2418         }
2419
2420         if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2421                 g_warning("failed to open directory: %s\n", dir);
2422                 g_chdir(prev_dir);
2423                 g_free(prev_dir);
2424                 return -1;
2425         }
2426
2427         /* remove all files in the directory */
2428         while ((dir_name = g_dir_read_name(dp)) != NULL) {
2429                 /* g_print("removing %s\n", dir_name); */
2430
2431                 if (is_dir_exist(dir_name)) {
2432                         if (remove_dir_recursive(dir_name) < 0) {
2433                                 g_warning("can't remove directory\n");
2434                                 return -1;
2435                         }
2436                 } else {
2437                         if (claws_unlink(dir_name) < 0)
2438                                 FILE_OP_ERROR(dir_name, "unlink");
2439                 }
2440         }
2441
2442         g_dir_close(dp);
2443
2444         if (g_chdir(prev_dir) < 0) {
2445                 FILE_OP_ERROR(prev_dir, "chdir");
2446                 g_free(prev_dir);
2447                 return -1;
2448         }
2449
2450         g_free(prev_dir);
2451
2452         if (g_rmdir(dir) < 0) {
2453                 FILE_OP_ERROR(dir, "rmdir");
2454                 return -1;
2455         }
2456
2457         return 0;
2458 }
2459
2460 gint rename_force(const gchar *oldpath, const gchar *newpath)
2461 {
2462 #ifndef G_OS_UNIX
2463         if (!is_file_entry_exist(oldpath)) {
2464                 errno = ENOENT;
2465                 return -1;
2466         }
2467         if (is_file_exist(newpath)) {
2468                 if (claws_unlink(newpath) < 0)
2469                         FILE_OP_ERROR(newpath, "unlink");
2470         }
2471 #endif
2472         return g_rename(oldpath, newpath);
2473 }
2474
2475 /*
2476  * Append src file body to the tail of dest file.
2477  * Now keep_backup has no effects.
2478  */
2479 gint append_file(const gchar *src, const gchar *dest, gboolean keep_backup)
2480 {
2481         FILE *src_fp, *dest_fp;
2482         gint n_read;
2483         gchar buf[BUFSIZ];
2484
2485         gboolean err = FALSE;
2486
2487         if ((src_fp = g_fopen(src, "rb")) == NULL) {
2488                 FILE_OP_ERROR(src, "fopen");
2489                 return -1;
2490         }
2491
2492         if ((dest_fp = g_fopen(dest, "ab")) == NULL) {
2493                 FILE_OP_ERROR(dest, "fopen");
2494                 fclose(src_fp);
2495                 return -1;
2496         }
2497
2498         if (change_file_mode_rw(dest_fp, dest) < 0) {
2499                 FILE_OP_ERROR(dest, "chmod");
2500                 g_warning("can't change file mode\n");
2501         }
2502
2503         while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
2504                 if (n_read < sizeof(buf) && ferror(src_fp))
2505                         break;
2506                 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2507                         g_warning("writing to %s failed.\n", dest);
2508                         fclose(dest_fp);
2509                         fclose(src_fp);
2510                         claws_unlink(dest);
2511                         return -1;
2512                 }
2513         }
2514
2515         if (ferror(src_fp)) {
2516                 FILE_OP_ERROR(src, "fread");
2517                 err = TRUE;
2518         }
2519         fclose(src_fp);
2520         if (fclose(dest_fp) == EOF) {
2521                 FILE_OP_ERROR(dest, "fclose");
2522                 err = TRUE;
2523         }
2524
2525         if (err) {
2526                 claws_unlink(dest);
2527                 return -1;
2528         }
2529
2530         return 0;
2531 }
2532
2533 gint copy_file(const gchar *src, const gchar *dest, gboolean keep_backup)
2534 {
2535         FILE *src_fp, *dest_fp;
2536         gint n_read;
2537         gchar buf[BUFSIZ];
2538         gchar *dest_bak = NULL;
2539         gboolean err = FALSE;
2540
2541         if ((src_fp = g_fopen(src, "rb")) == NULL) {
2542                 FILE_OP_ERROR(src, "fopen");
2543                 return -1;
2544         }
2545         if (is_file_exist(dest)) {
2546                 dest_bak = g_strconcat(dest, ".bak", NULL);
2547                 if (rename_force(dest, dest_bak) < 0) {
2548                         FILE_OP_ERROR(dest, "rename");
2549                         fclose(src_fp);
2550                         g_free(dest_bak);
2551                         return -1;
2552                 }
2553         }
2554
2555         if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2556                 FILE_OP_ERROR(dest, "fopen");
2557                 fclose(src_fp);
2558                 if (dest_bak) {
2559                         if (rename_force(dest_bak, dest) < 0)
2560                                 FILE_OP_ERROR(dest_bak, "rename");
2561                         g_free(dest_bak);
2562                 }
2563                 return -1;
2564         }
2565
2566         if (change_file_mode_rw(dest_fp, dest) < 0) {
2567                 FILE_OP_ERROR(dest, "chmod");
2568                 g_warning("can't change file mode\n");
2569         }
2570
2571         while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
2572                 if (n_read < sizeof(buf) && ferror(src_fp))
2573                         break;
2574                 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2575                         g_warning("writing to %s failed.\n", dest);
2576                         fclose(dest_fp);
2577                         fclose(src_fp);
2578                         claws_unlink(dest);
2579                         if (dest_bak) {
2580                                 if (rename_force(dest_bak, dest) < 0)
2581                                         FILE_OP_ERROR(dest_bak, "rename");
2582                                 g_free(dest_bak);
2583                         }
2584                         return -1;
2585                 }
2586         }
2587
2588         if (ferror(src_fp)) {
2589                 FILE_OP_ERROR(src, "fread");
2590                 err = TRUE;
2591         }
2592         fclose(src_fp);
2593         if (fclose(dest_fp) == EOF) {
2594                 FILE_OP_ERROR(dest, "fclose");
2595                 err = TRUE;
2596         }
2597
2598         if (err) {
2599                 claws_unlink(dest);
2600                 if (dest_bak) {
2601                         if (rename_force(dest_bak, dest) < 0)
2602                                 FILE_OP_ERROR(dest_bak, "rename");
2603                         g_free(dest_bak);
2604                 }
2605                 return -1;
2606         }
2607
2608         if (keep_backup == FALSE && dest_bak)
2609                 claws_unlink(dest_bak);
2610
2611         g_free(dest_bak);
2612
2613         return 0;
2614 }
2615
2616 gint move_file(const gchar *src, const gchar *dest, gboolean overwrite)
2617 {
2618         if (overwrite == FALSE && is_file_exist(dest)) {
2619                 g_warning("move_file(): file %s already exists.", dest);
2620                 return -1;
2621         }
2622
2623         if (rename_force(src, dest) == 0) return 0;
2624
2625         if (EXDEV != errno) {
2626                 FILE_OP_ERROR(src, "rename");
2627                 return -1;
2628         }
2629
2630         if (copy_file(src, dest, FALSE) < 0) return -1;
2631
2632         claws_unlink(src);
2633
2634         return 0;
2635 }
2636
2637 gint copy_file_part_to_fp(FILE *fp, off_t offset, size_t length, FILE *dest_fp)
2638 {
2639         gint n_read;
2640         gint bytes_left, to_read;
2641         gchar buf[BUFSIZ];
2642
2643         if (fseek(fp, offset, SEEK_SET) < 0) {
2644                 perror("fseek");
2645                 return -1;
2646         }
2647
2648         bytes_left = length;
2649         to_read = MIN(bytes_left, sizeof(buf));
2650
2651         while ((n_read = fread(buf, sizeof(gchar), to_read, fp)) > 0) {
2652                 if (n_read < to_read && ferror(fp))
2653                         break;
2654                 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2655                         return -1;
2656                 }
2657                 bytes_left -= n_read;
2658                 if (bytes_left == 0)
2659                         break;
2660                 to_read = MIN(bytes_left, sizeof(buf));
2661         }
2662
2663         if (ferror(fp)) {
2664                 perror("fread");
2665                 return -1;
2666         }
2667
2668         return 0;
2669 }
2670
2671 gint copy_file_part(FILE *fp, off_t offset, size_t length, const gchar *dest)
2672 {
2673         FILE *dest_fp;
2674         gboolean err = FALSE;
2675
2676         if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2677                 FILE_OP_ERROR(dest, "fopen");
2678                 return -1;
2679         }
2680
2681         if (change_file_mode_rw(dest_fp, dest) < 0) {
2682                 FILE_OP_ERROR(dest, "chmod");
2683                 g_warning("can't change file mode\n");
2684         }
2685
2686         if (copy_file_part_to_fp(fp, offset, length, dest_fp) < 0)
2687                 err = TRUE;
2688
2689         if (!err && fclose(dest_fp) == EOF) {
2690                 FILE_OP_ERROR(dest, "fclose");
2691                 err = TRUE;
2692         }
2693
2694         if (err) {
2695                 g_warning("writing to %s failed.\n", dest);
2696                 claws_unlink(dest);
2697                 return -1;
2698         }
2699
2700         return 0;
2701 }
2702
2703 /* convert line endings into CRLF. If the last line doesn't end with
2704  * linebreak, add it.
2705  */
2706 gchar *canonicalize_str(const gchar *str)
2707 {
2708         const gchar *p;
2709         guint new_len = 0;
2710         gchar *out, *outp;
2711
2712         for (p = str; *p != '\0'; ++p) {
2713                 if (*p != '\r') {
2714                         ++new_len;
2715                         if (*p == '\n')
2716                                 ++new_len;
2717                 }
2718         }
2719         if (p == str || *(p - 1) != '\n')
2720                 new_len += 2;
2721
2722         out = outp = g_malloc(new_len + 1);
2723         for (p = str; *p != '\0'; ++p) {
2724                 if (*p != '\r') {
2725                         if (*p == '\n')
2726                                 *outp++ = '\r';
2727                         *outp++ = *p;
2728                 }
2729         }
2730         if (p == str || *(p - 1) != '\n') {
2731                 *outp++ = '\r';
2732                 *outp++ = '\n';
2733         }
2734         *outp = '\0';
2735
2736         return out;
2737 }
2738
2739 gint canonicalize_file(const gchar *src, const gchar *dest)
2740 {
2741         FILE *src_fp, *dest_fp;
2742         gchar buf[BUFFSIZE];
2743         gint len;
2744         gboolean err = FALSE;
2745         gboolean last_linebreak = FALSE;
2746
2747         if ((src_fp = g_fopen(src, "rb")) == NULL) {
2748                 FILE_OP_ERROR(src, "fopen");
2749                 return -1;
2750         }
2751
2752         if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2753                 FILE_OP_ERROR(dest, "fopen");
2754                 fclose(src_fp);
2755                 return -1;
2756         }
2757
2758         if (change_file_mode_rw(dest_fp, dest) < 0) {
2759                 FILE_OP_ERROR(dest, "chmod");
2760                 g_warning("can't change file mode\n");
2761         }
2762
2763         while (fgets(buf, sizeof(buf), src_fp) != NULL) {
2764                 gint r = 0;
2765
2766                 len = strlen(buf);
2767                 if (len == 0) break;
2768                 last_linebreak = FALSE;
2769
2770                 if (buf[len - 1] != '\n') {
2771                         last_linebreak = TRUE;
2772                         r = fputs(buf, dest_fp);
2773                 } else if (len > 1 && buf[len - 1] == '\n' && buf[len - 2] == '\r') {
2774                         r = fputs(buf, dest_fp);
2775                 } else {
2776                         if (len > 1) {
2777                                 r = fwrite(buf, 1, len - 1, dest_fp);
2778                                 if (r != (len -1))
2779                                         r = EOF;
2780                         }
2781                         if (r != EOF)
2782                                 r = fputs("\r\n", dest_fp);
2783                 }
2784
2785                 if (r == EOF) {
2786                         g_warning("writing to %s failed.\n", dest);
2787                         fclose(dest_fp);
2788                         fclose(src_fp);
2789                         claws_unlink(dest);
2790                         return -1;
2791                 }
2792         }
2793
2794         if (last_linebreak == TRUE) {
2795                 if (fputs("\r\n", dest_fp) == EOF)
2796                         err = TRUE;
2797         }
2798
2799         if (ferror(src_fp)) {
2800                 FILE_OP_ERROR(src, "fgets");
2801                 err = TRUE;
2802         }
2803         fclose(src_fp);
2804         if (fclose(dest_fp) == EOF) {
2805                 FILE_OP_ERROR(dest, "fclose");
2806                 err = TRUE;
2807         }
2808
2809         if (err) {
2810                 claws_unlink(dest);
2811                 return -1;
2812         }
2813
2814         return 0;
2815 }
2816
2817 gint canonicalize_file_replace(const gchar *file)
2818 {
2819         gchar *tmp_file;
2820
2821         tmp_file = get_tmp_file();
2822
2823         if (canonicalize_file(file, tmp_file) < 0) {
2824                 g_free(tmp_file);
2825                 return -1;
2826         }
2827
2828         if (move_file(tmp_file, file, TRUE) < 0) {
2829                 g_warning("can't replace %s .\n", file);
2830                 claws_unlink(tmp_file);
2831                 g_free(tmp_file);
2832                 return -1;
2833         }
2834
2835         g_free(tmp_file);
2836         return 0;
2837 }
2838
2839 gchar *normalize_newlines(const gchar *str)
2840 {
2841         const gchar *p = str;
2842         gchar *out, *outp;
2843
2844         out = outp = g_malloc(strlen(str) + 1);
2845         for (p = str; *p != '\0'; ++p) {
2846                 if (*p == '\r') {
2847                         if (*(p + 1) != '\n')
2848                                 *outp++ = '\n';
2849                 } else
2850                         *outp++ = *p;
2851         }
2852
2853         *outp = '\0';
2854
2855         return out;
2856 }
2857
2858 gchar *get_outgoing_rfc2822_str(FILE *fp)
2859 {
2860         gchar buf[BUFFSIZE];
2861         GString *str;
2862         gchar *ret;
2863
2864         str = g_string_new(NULL);
2865
2866         /* output header part */
2867         while (fgets(buf, sizeof(buf), fp) != NULL) {
2868                 strretchomp(buf);
2869                 if (!g_ascii_strncasecmp(buf, "Bcc:", 4)) {
2870                         gint next;
2871
2872                         for (;;) {
2873                                 next = fgetc(fp);
2874                                 if (next == EOF)
2875                                         break;
2876                                 else if (next != ' ' && next != '\t') {
2877                                         ungetc(next, fp);
2878                                         break;
2879                                 }
2880                                 if (fgets(buf, sizeof(buf), fp) == NULL)
2881                                         break;
2882                         }
2883                 } else {
2884                         g_string_append(str, buf);
2885                         g_string_append(str, "\r\n");
2886                         if (buf[0] == '\0')
2887                                 break;
2888                 }
2889         }
2890
2891         /* output body part */
2892         while (fgets(buf, sizeof(buf), fp) != NULL) {
2893                 strretchomp(buf);
2894                 if (buf[0] == '.')
2895                         g_string_append_c(str, '.');
2896                 g_string_append(str, buf);
2897                 g_string_append(str, "\r\n");
2898         }
2899
2900         ret = str->str;
2901         g_string_free(str, FALSE);
2902
2903         return ret;
2904 }
2905
2906 /*
2907  * Create a new boundary in a way that it is very unlikely that this
2908  * will occur in the following text.  It would be easy to ensure
2909  * uniqueness if everything is either quoted-printable or base64
2910  * encoded (note that conversion is allowed), but because MIME bodies
2911  * may be nested, it may happen that the same boundary has already
2912  * been used.
2913  *
2914  *   boundary := 0*69<bchars> bcharsnospace
2915  *   bchars := bcharsnospace / " "
2916  *   bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" /
2917  *                  "+" / "_" / "," / "-" / "." /
2918  *                  "/" / ":" / "=" / "?"
2919  *
2920  * some special characters removed because of buggy MTAs
2921  */
2922
2923 gchar *generate_mime_boundary(const gchar *prefix)
2924 {
2925         static gchar tbl[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
2926                              "abcdefghijklmnopqrstuvwxyz"
2927                              "1234567890+_./=";
2928         gchar buf_uniq[24];
2929         gint i;
2930
2931         for (i = 0; i < sizeof(buf_uniq) - 1; i++)
2932                 buf_uniq[i] = tbl[g_random_int_range(0, sizeof(tbl) - 1)];
2933         buf_uniq[i] = '\0';
2934
2935         return g_strdup_printf("%s_/%s", prefix ? prefix : "MP",
2936                                buf_uniq);
2937 }
2938
2939 gint change_file_mode_rw(FILE *fp, const gchar *file)
2940 {
2941 #if HAVE_FCHMOD
2942         return fchmod(fileno(fp), S_IRUSR|S_IWUSR);
2943 #else
2944         return g_chmod(file, S_IRUSR|S_IWUSR);
2945 #endif
2946 }
2947
2948 FILE *my_tmpfile(void)
2949 {
2950 #if HAVE_MKSTEMP || defined(G_OS_WIN32)
2951         const gchar suffix[] = ".XXXXXX";
2952         const gchar *tmpdir;
2953         guint tmplen;
2954         const gchar *progname;
2955         guint proglen;
2956         gchar *fname;
2957         gint fd;
2958         FILE *fp;
2959 #ifndef G_OS_WIN32
2960         gchar buf[2]="\0";
2961 #endif
2962
2963         tmpdir = get_tmp_dir();
2964         tmplen = strlen(tmpdir);
2965         progname = g_get_prgname();
2966         if (progname == NULL)
2967                 progname = "claws-mail";
2968         proglen = strlen(progname);
2969         Xalloca(fname, tmplen + 1 + proglen + sizeof(suffix),
2970                 return tmpfile());
2971
2972         memcpy(fname, tmpdir, tmplen);
2973         fname[tmplen] = G_DIR_SEPARATOR;
2974         memcpy(fname + tmplen + 1, progname, proglen);
2975         memcpy(fname + tmplen + 1 + proglen, suffix, sizeof(suffix));
2976
2977         fd = mkstemp(fname);
2978         if (fd < 0)
2979                 return tmpfile();
2980
2981 #ifndef G_OS_WIN32
2982         claws_unlink(fname);
2983         
2984         /* verify that we can write in the file after unlinking */
2985         if (write(fd, buf, 1) < 0) {
2986                 close(fd);
2987                 return tmpfile();
2988         }
2989         
2990 #endif
2991
2992         fp = fdopen(fd, "w+b");
2993         if (!fp)
2994                 close(fd);
2995         else {
2996                 rewind(fp);
2997                 return fp;
2998         }
2999
3000 #endif /* HAVE_MKSTEMP || G_OS_WIN32 */
3001
3002         return tmpfile();
3003 }
3004
3005 FILE *get_tmpfile_in_dir(const gchar *dir, gchar **filename)
3006 {
3007         int fd;
3008 #ifdef G_OS_WIN32
3009         char *template = g_strdup_printf ("%s%cclaws.XXXXXX",
3010                                           dir, G_DIR_SEPARATOR);
3011         fd = mkstemp_name(template, filename);
3012         g_free(template);
3013 #else
3014         *filename = g_strdup_printf("%s%cclaws.XXXXXX", dir, G_DIR_SEPARATOR);
3015         fd = mkstemp(*filename);
3016 #endif
3017         return fdopen(fd, "w+");
3018 }
3019
3020 FILE *str_open_as_stream(const gchar *str)
3021 {
3022         FILE *fp;
3023         size_t len;
3024
3025         g_return_val_if_fail(str != NULL, NULL);
3026
3027         fp = my_tmpfile();
3028         if (!fp) {
3029                 FILE_OP_ERROR("str_open_as_stream", "my_tmpfile");
3030                 return NULL;
3031         }
3032
3033         len = strlen(str);
3034         if (len == 0) return fp;
3035
3036         if (fwrite(str, 1, len, fp) != len) {
3037                 FILE_OP_ERROR("str_open_as_stream", "fwrite");
3038                 fclose(fp);
3039                 return NULL;
3040         }
3041
3042         rewind(fp);
3043         return fp;
3044 }
3045
3046 gint str_write_to_file(const gchar *str, const gchar *file)
3047 {
3048         FILE *fp;
3049         size_t len;
3050
3051         g_return_val_if_fail(str != NULL, -1);
3052         g_return_val_if_fail(file != NULL, -1);
3053
3054         if ((fp = g_fopen(file, "wb")) == NULL) {
3055                 FILE_OP_ERROR(file, "fopen");
3056                 return -1;
3057         }
3058
3059         len = strlen(str);
3060         if (len == 0) {
3061                 fclose(fp);
3062                 return 0;
3063         }
3064
3065         if (fwrite(str, 1, len, fp) != len) {
3066                 FILE_OP_ERROR(file, "fwrite");
3067                 fclose(fp);
3068                 claws_unlink(file);
3069                 return -1;
3070         }
3071
3072         if (fclose(fp) == EOF) {
3073                 FILE_OP_ERROR(file, "fclose");
3074                 claws_unlink(file);
3075                 return -1;
3076         }
3077
3078         return 0;
3079 }
3080
3081 static gchar *file_read_stream_to_str_full(FILE *fp, gboolean recode)
3082 {
3083         GByteArray *array;
3084         guchar buf[BUFSIZ];
3085         gint n_read;
3086         gchar *str;
3087
3088         g_return_val_if_fail(fp != NULL, NULL);
3089
3090         array = g_byte_array_new();
3091
3092         while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
3093                 if (n_read < sizeof(buf) && ferror(fp))
3094                         break;
3095                 g_byte_array_append(array, buf, n_read);
3096         }
3097
3098         if (ferror(fp)) {
3099                 FILE_OP_ERROR("file stream", "fread");
3100                 g_byte_array_free(array, TRUE);
3101                 return NULL;
3102         }
3103
3104         buf[0] = '\0';
3105         g_byte_array_append(array, buf, 1);
3106         str = (gchar *)array->data;
3107         g_byte_array_free(array, FALSE);
3108
3109         if (recode && !g_utf8_validate(str, -1, NULL)) {
3110                 const gchar *src_codeset, *dest_codeset;
3111                 gchar *tmp = NULL;
3112                 src_codeset = conv_get_locale_charset_str();
3113                 dest_codeset = CS_UTF_8;
3114                 tmp = conv_codeset_strdup(str, src_codeset, dest_codeset);
3115                 g_free(str);
3116                 str = tmp;
3117         }
3118
3119         return str;
3120 }
3121
3122 static gchar *file_read_to_str_full(const gchar *file, gboolean recode)
3123 {
3124         FILE *fp;
3125         gchar *str;
3126         struct stat s;
3127 #ifndef G_OS_WIN32
3128         gint fd, err;
3129         struct timeval timeout = {1, 0};
3130         fd_set fds;
3131         int fflags = 0;
3132 #endif
3133
3134         g_return_val_if_fail(file != NULL, NULL);
3135
3136         if (g_stat(file, &s) != 0) {
3137                 FILE_OP_ERROR(file, "stat");
3138                 return NULL;
3139         }
3140         if (S_ISDIR(s.st_mode)) {
3141                 g_warning("%s: is a directory\n", file);
3142                 return NULL;
3143         }
3144
3145 #ifdef G_OS_WIN32
3146         fp = fopen (file, "rb");
3147         if (fp == NULL) {
3148                 FILE_OP_ERROR(file, "open");
3149                 return NULL;
3150         }
3151 #else     
3152         /* test whether the file is readable without blocking */
3153         fd = open(file, O_RDONLY | O_NONBLOCK);
3154         if (fd == -1) {
3155                 FILE_OP_ERROR(file, "open");
3156                 return NULL;
3157         }
3158
3159         FD_ZERO(&fds);
3160         FD_SET(fd, &fds);
3161
3162         /* allow for one second */
3163         err = select(fd+1, &fds, NULL, NULL, &timeout);
3164         if (err <= 0 || !FD_ISSET(fd, &fds)) {
3165                 if (err < 0) {
3166                         FILE_OP_ERROR(file, "select");
3167                 } else {
3168                         g_warning("%s: doesn't seem readable\n", file);
3169                 }
3170                 close(fd);
3171                 return NULL;
3172         }
3173         
3174         /* Now clear O_NONBLOCK */
3175         if ((fflags = fcntl(fd, F_GETFL)) < 0) {
3176                 FILE_OP_ERROR(file, "fcntl (F_GETFL)");
3177                 close(fd);
3178                 return NULL;
3179         }
3180         if (fcntl(fd, F_SETFL, (fflags & ~O_NONBLOCK)) < 0) {
3181                 FILE_OP_ERROR(file, "fcntl (F_SETFL)");
3182                 close(fd);
3183                 return NULL;
3184         }
3185         
3186         /* get the FILE pointer */
3187         fp = fdopen(fd, "rb");
3188
3189         if (fp == NULL) {
3190                 FILE_OP_ERROR(file, "fdopen");
3191                 close(fd); /* if fp isn't NULL, we'll use fclose instead! */
3192                 return NULL;
3193         }
3194 #endif
3195
3196         str = file_read_stream_to_str_full(fp, recode);
3197
3198         fclose(fp);
3199
3200         return str;
3201 }
3202
3203 gchar *file_read_to_str(const gchar *file)
3204 {
3205         return file_read_to_str_full(file, TRUE);
3206 }
3207 gchar *file_read_stream_to_str(FILE *fp)
3208 {
3209         return file_read_stream_to_str_full(fp, TRUE);
3210 }
3211
3212 gchar *file_read_to_str_no_recode(const gchar *file)
3213 {
3214         return file_read_to_str_full(file, FALSE);
3215 }
3216 gchar *file_read_stream_to_str_no_recode(FILE *fp)
3217 {
3218         return file_read_stream_to_str_full(fp, FALSE);
3219 }
3220
3221 char *fgets_crlf(char *buf, int size, FILE *stream)
3222 {
3223         gboolean is_cr = FALSE;
3224         gboolean last_was_cr = FALSE;
3225         int c = 0;
3226         char *cs;
3227
3228         cs = buf;
3229         while (--size > 0 && (c = getc(stream)) != EOF)
3230         {
3231                 *cs++ = c;
3232                 is_cr = (c == '\r');
3233                 if (c == '\n') {
3234                         break;
3235                 }
3236                 if (last_was_cr) {
3237                         *(--cs) = '\n';
3238                         cs++;
3239                         ungetc(c, stream);
3240                         break;
3241                 }
3242                 last_was_cr = is_cr;
3243         }
3244         if (c == EOF && cs == buf)
3245                 return NULL;
3246
3247         *cs = '\0';
3248
3249         return buf;     
3250 }
3251
3252 static gint execute_async(gchar *const argv[])
3253 {
3254         g_return_val_if_fail(argv != NULL && argv[0] != NULL, -1);
3255
3256         if (g_spawn_async(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH,
3257                           NULL, NULL, NULL, FALSE) == FALSE) {
3258                 g_warning("Couldn't execute command: %s\n", argv[0]);
3259                 return -1;
3260         }
3261
3262         return 0;
3263 }
3264
3265 static gint execute_sync(gchar *const argv[])
3266 {
3267         gint status;
3268
3269         g_return_val_if_fail(argv != NULL && argv[0] != NULL, -1);
3270
3271         if (g_spawn_sync(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH,
3272                          NULL, NULL, NULL, NULL, &status, NULL) == FALSE) {
3273                 g_warning("Couldn't execute command: %s\n", argv[0]);
3274                 return -1;
3275         }
3276
3277 #ifdef G_OS_UNIX
3278         if (WIFEXITED(status))
3279                 return WEXITSTATUS(status);
3280         else
3281                 return -1;
3282 #else
3283         return status;
3284 #endif
3285 }
3286
3287 gint execute_command_line(const gchar *cmdline, gboolean async)
3288 {
3289         gchar **argv;
3290         gint ret;
3291
3292         debug_print("execute_command_line(): executing: %s\n", cmdline?cmdline:"(null)");
3293
3294         argv = strsplit_with_quote(cmdline, " ", 0);
3295
3296         if (async)
3297                 ret = execute_async(argv);
3298         else
3299                 ret = execute_sync(argv);
3300
3301         g_strfreev(argv);
3302
3303         return ret;
3304 }
3305
3306 gchar *get_command_output(const gchar *cmdline)
3307 {
3308         gchar *child_stdout;
3309         gint status;
3310
3311         g_return_val_if_fail(cmdline != NULL, NULL);
3312
3313         debug_print("get_command_output(): executing: %s\n", cmdline);
3314
3315         if (g_spawn_command_line_sync(cmdline, &child_stdout, NULL, &status,
3316                                       NULL) == FALSE) {
3317                 g_warning("Couldn't execute command: %s\n", cmdline);
3318                 return NULL;
3319         }
3320
3321         return child_stdout;
3322 }
3323 #ifndef MAEMO
3324 static gint is_unchanged_uri_char(char c)
3325 {
3326         switch (c) {
3327                 case '(':
3328                 case ')':
3329                         return 0;
3330                 default:
3331                         return 1;
3332         }
3333 }
3334
3335 static void encode_uri(gchar *encoded_uri, gint bufsize, const gchar *uri)
3336 {
3337         int i;
3338         int k;
3339
3340         k = 0;
3341         for(i = 0; i < strlen(uri) ; i++) {
3342                 if (is_unchanged_uri_char(uri[i])) {
3343                         if (k + 2 >= bufsize)
3344                                 break;
3345                         encoded_uri[k++] = uri[i];
3346                 }
3347                 else {
3348                         char * hexa = "0123456789ABCDEF";
3349
3350                         if (k + 4 >= bufsize)
3351                                 break;
3352                         encoded_uri[k++] = '%';
3353                         encoded_uri[k++] = hexa[uri[i] / 16];
3354                         encoded_uri[k++] = hexa[uri[i] % 16];
3355                 }
3356         }
3357         encoded_uri[k] = 0;
3358 }
3359 #endif
3360 gint open_uri(const gchar *uri, const gchar *cmdline)
3361 {
3362 #ifndef MAEMO
3363 #ifndef G_OS_WIN32
3364         gchar buf[BUFFSIZE];
3365         gchar *p;
3366         gchar encoded_uri[BUFFSIZE];
3367         g_return_val_if_fail(uri != NULL, -1);
3368
3369         /* an option to choose whether to use encode_uri or not ? */
3370         encode_uri(encoded_uri, BUFFSIZE, uri);
3371
3372         if (cmdline &&
3373             (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
3374             !strchr(p + 2, '%'))
3375                 g_snprintf(buf, sizeof(buf), cmdline, encoded_uri);
3376         else {
3377                 if (cmdline)
3378                         g_warning("Open URI command-line is invalid "
3379                                   "(there must be only one '%%s'): %s",
3380                                   cmdline);
3381                 g_snprintf(buf, sizeof(buf), DEFAULT_BROWSER_CMD, encoded_uri);
3382         }
3383
3384         execute_command_line(buf, TRUE);
3385 #else
3386         ShellExecute(NULL, "open", uri, NULL, NULL, SW_SHOW);
3387 #endif
3388 #else
3389         extern osso_context_t *get_osso_context(void);
3390         osso_rpc_run_with_defaults(get_osso_context(), "osso_browser",
3391                                         OSSO_BROWSER_OPEN_NEW_WINDOW_REQ, NULL, 
3392                                         DBUS_TYPE_STRING, uri, DBUS_TYPE_INVALID);
3393 #endif
3394         return 0;
3395 }
3396
3397 gint open_txt_editor(const gchar *filepath, const gchar *cmdline)
3398 {
3399         gchar buf[BUFFSIZE];
3400         gchar *p;
3401
3402         g_return_val_if_fail(filepath != NULL, -1);
3403
3404         if (cmdline &&
3405             (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
3406             !strchr(p + 2, '%'))
3407                 g_snprintf(buf, sizeof(buf), cmdline, filepath);
3408         else {
3409                 if (cmdline)
3410                         g_warning("Open Text Editor command-line is invalid "
3411                                   "(there must be only one '%%s'): %s",
3412                                   cmdline);
3413                 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, filepath);
3414         }
3415
3416         execute_command_line(buf, TRUE);
3417
3418         return 0;
3419 }
3420
3421 time_t remote_tzoffset_sec(const gchar *zone)
3422 {
3423         static gchar ustzstr[] = "PSTPDTMSTMDTCSTCDTESTEDT";
3424         gchar zone3[4];
3425         gchar *p;
3426         gchar c;
3427         gint iustz;
3428         gint offset;
3429         time_t remoteoffset;
3430
3431         strncpy(zone3, zone, 3);
3432         zone3[3] = '\0';
3433         remoteoffset = 0;
3434
3435         if (sscanf(zone, "%c%d", &c, &offset) == 2 &&
3436             (c == '+' || c == '-')) {
3437                 remoteoffset = ((offset / 100) * 60 + (offset % 100)) * 60;
3438                 if (c == '-')
3439                         remoteoffset = -remoteoffset;
3440         } else if (!strncmp(zone, "UT" , 2) ||
3441                    !strncmp(zone, "GMT", 2)) {
3442                 remoteoffset = 0;
3443         } else if (strlen(zone3) == 3) {
3444                 for (p = ustzstr; *p != '\0'; p += 3) {
3445                         if (!g_ascii_strncasecmp(p, zone3, 3)) {
3446                                 iustz = ((gint)(p - ustzstr) / 3 + 1) / 2 - 8;
3447                                 remoteoffset = iustz * 3600;
3448                                 break;
3449                         }
3450                 }
3451                 if (*p == '\0')
3452                         return -1;
3453         } else if (strlen(zone3) == 1) {
3454                 switch (zone[0]) {
3455                 case 'Z': remoteoffset =   0; break;
3456                 case 'A': remoteoffset =  -1; break;
3457                 case 'B': remoteoffset =  -2; break;
3458                 case 'C': remoteoffset =  -3; break;
3459                 case 'D': remoteoffset =  -4; break;
3460                 case 'E': remoteoffset =  -5; break;
3461                 case 'F': remoteoffset =  -6; break;
3462                 case 'G': remoteoffset =  -7; break;
3463                 case 'H': remoteoffset =  -8; break;
3464                 case 'I': remoteoffset =  -9; break;
3465                 case 'K': remoteoffset = -10; break; /* J is not used */
3466                 case 'L': remoteoffset = -11; break;
3467                 case 'M': remoteoffset = -12; break;
3468                 case 'N': remoteoffset =   1; break;
3469                 case 'O': remoteoffset =   2; break;
3470                 case 'P': remoteoffset =   3; break;
3471                 case 'Q': remoteoffset =   4; break;
3472                 case 'R': remoteoffset =   5; break;
3473                 case 'S': remoteoffset =   6; break;
3474                 case 'T': remoteoffset =   7; break;
3475                 case 'U': remoteoffset =   8; break;
3476                 case 'V': remoteoffset =   9; break;
3477                 case 'W': remoteoffset =  10; break;
3478                 case 'X': remoteoffset =  11; break;
3479                 case 'Y': remoteoffset =  12; break;
3480                 default:  remoteoffset =   0; break;
3481                 }
3482                 remoteoffset = remoteoffset * 3600;
3483         } else
3484                 return -1;
3485
3486         return remoteoffset;
3487 }
3488
3489 time_t tzoffset_sec(time_t *now)
3490 {
3491         struct tm gmt, *lt;
3492         gint off;
3493 #ifndef G_OS_WIN32
3494         struct tm buf1, buf2;
3495 #endif
3496 #ifdef G_OS_WIN32
3497         if (now && *now < 0)
3498                 return 0;
3499 #endif  
3500         gmt = *gmtime_r(now, &buf1);
3501         lt = localtime_r(now, &buf2);
3502
3503         off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
3504
3505         if (lt->tm_year < gmt.tm_year)
3506                 off -= 24 * 60;
3507         else if (lt->tm_year > gmt.tm_year)
3508                 off += 24 * 60;
3509         else if (lt->tm_yday < gmt.tm_yday)
3510                 off -= 24 * 60;
3511         else if (lt->tm_yday > gmt.tm_yday)
3512                 off += 24 * 60;
3513
3514         if (off >= 24 * 60)             /* should be impossible */
3515                 off = 23 * 60 + 59;     /* if not, insert silly value */
3516         if (off <= -24 * 60)
3517                 off = -(23 * 60 + 59);
3518
3519         return off * 60;
3520 }
3521
3522 /* calculate timezone offset */
3523 gchar *tzoffset(time_t *now)
3524 {
3525         static gchar offset_string[6];
3526         struct tm gmt, *lt;
3527         gint off;
3528         gchar sign = '+';
3529 #ifndef G_OS_WIN32
3530         struct tm buf1, buf2;
3531 #endif
3532 #ifdef G_OS_WIN32
3533         if (now && *now < 0)
3534                 return 0;
3535 #endif
3536         gmt = *gmtime_r(now, &buf1);
3537         lt = localtime_r(now, &buf2);
3538
3539         off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
3540
3541         if (lt->tm_year < gmt.tm_year)
3542                 off -= 24 * 60;
3543         else if (lt->tm_year > gmt.tm_year)
3544                 off += 24 * 60;
3545         else if (lt->tm_yday < gmt.tm_yday)
3546                 off -= 24 * 60;
3547         else if (lt->tm_yday > gmt.tm_yday)
3548                 off += 24 * 60;
3549
3550         if (off < 0) {
3551                 sign = '-';
3552                 off = -off;
3553         }
3554
3555         if (off >= 24 * 60)             /* should be impossible */
3556                 off = 23 * 60 + 59;     /* if not, insert silly value */
3557
3558         sprintf(offset_string, "%c%02d%02d", sign, off / 60, off % 60);
3559
3560         return offset_string;
3561 }
3562
3563 void get_rfc822_date(gchar *buf, gint len)
3564 {
3565         struct tm *lt;
3566         time_t t;
3567         gchar day[4], mon[4];
3568         gint dd, hh, mm, ss, yyyy;
3569 #ifndef G_OS_WIN32
3570         struct tm buf1;
3571         gchar buf2[BUFFSIZE];
3572 #endif
3573
3574         t = time(NULL);
3575         lt = localtime_r(&t, &buf1);
3576
3577         sscanf(asctime_r(lt, buf2), "%3s %3s %d %d:%d:%d %d\n",
3578                day, mon, &dd, &hh, &mm, &ss, &yyyy);
3579
3580         g_snprintf(buf, len, "%s, %d %s %d %02d:%02d:%02d %s",
3581                    day, dd, mon, yyyy, hh, mm, ss, tzoffset(&t));
3582 }
3583
3584 void debug_set_mode(gboolean mode)
3585 {
3586         debug_mode = mode;
3587 }
3588
3589 gboolean debug_get_mode(void)
3590 {
3591         return debug_mode;
3592 }
3593
3594 void debug_print_real(const gchar *format, ...)
3595 {
3596         va_list args;
3597         gchar buf[BUFFSIZE];
3598
3599         if (!debug_mode) return;
3600
3601         va_start(args, format);
3602         g_vsnprintf(buf, sizeof(buf), format, args);
3603         va_end(args);
3604
3605         g_print("%s", buf);
3606 }
3607
3608
3609 const char * debug_srcname(const char *file)
3610 {
3611         const char *s = strrchr (file, '/');
3612         return s? s+1:file;
3613 }
3614
3615
3616 void * subject_table_lookup(GHashTable *subject_table, gchar * subject)
3617 {
3618         if (subject == NULL)
3619                 subject = "";
3620         else
3621                 subject += subject_get_prefix_length(subject);
3622
3623         return g_hash_table_lookup(subject_table, subject);
3624 }
3625
3626 void subject_table_insert(GHashTable *subject_table, gchar * subject,
3627                           void * data)
3628 {
3629         if (subject == NULL || *subject == 0)
3630                 return;
3631         subject += subject_get_prefix_length(subject);
3632         g_hash_table_insert(subject_table, subject, data);
3633 }
3634
3635 void subject_table_remove(GHashTable *subject_table, gchar * subject)
3636 {
3637         if (subject == NULL)
3638                 return;
3639
3640         subject += subject_get_prefix_length(subject);
3641         g_hash_table_remove(subject_table, subject);
3642 }
3643
3644 /*!
3645  *\brief        Check if a string is prefixed with known (combinations)
3646  *              of prefixes. The function assumes that each prefix
3647  *              is terminated by zero or exactly _one_ space.
3648  *
3649  *\param        str String to check for a prefixes
3650  *
3651  *\return       int Number of chars in the prefix that should be skipped
3652  *              for a "clean" subject line. If no prefix was found, 0
3653  *              is returned.
3654  */
3655  
3656 #ifndef G_OS_WIN32
3657 static regex_t u_regex;
3658 static gboolean u_init_;
3659 #endif
3660
3661 void utils_free_regex(void)
3662 {
3663 #ifndef G_OS_WIN32
3664         if (u_init_) {
3665                 regfree(&u_regex);
3666                 u_init_ = FALSE;
3667         }
3668 #endif
3669 }
3670
3671 int subject_get_prefix_length(const gchar *subject)
3672 {
3673 #ifndef G_OS_WIN32
3674         /*!< Array with allowable reply prefixes regexps. */
3675         static const gchar * const prefixes[] = {
3676                 "Re\\:",                        /* "Re:" */
3677                 "Re\\[[1-9][0-9]*\\]\\:",       /* "Re[XXX]:" (non-conforming news mail clients) */
3678                 "Antw\\:",                      /* "Antw:" (Dutch / German Outlook) */
3679                 "Aw\\:",                        /* "Aw:"   (German) */
3680                 "Antwort\\:",                   /* "Antwort:" (German Lotus Notes) */
3681                 "Res\\:",                       /* "Res:" (Spanish/Brazilian Outlook) */
3682                 "Fw\\:",                        /* "Fw:" Forward */
3683                 "Fwd\\:",                       /* "Fwd:" Forward */
3684                 "Enc\\:",                       /* "Enc:" Forward (Brazilian Outlook) */
3685                 "Odp\\:",                       /* "Odp:" Re (Polish Outlook) */
3686                 "Rif\\:",                       /* "Rif:" (Italian Outlook) */
3687                 "Sv\\:",                        /* "Sv" (Norwegian) */
3688                 "Vs\\:",                        /* "Vs" (Norwegian) */
3689                 "Ad\\:",                        /* "Ad" (Norwegian) */
3690                 "\347\255\224\345\244\215\\:"   /* "Re" (Chinese, UTF-8) */
3691                 /* add more */
3692         };
3693         const int PREFIXES = sizeof prefixes / sizeof prefixes[0];
3694         int n;
3695         regmatch_t pos;
3696
3697         if (!subject) return 0;
3698         if (!*subject) return 0;
3699
3700         if (!u_init_) {
3701                 GString *s = g_string_new("");
3702
3703                 for (n = 0; n < PREFIXES; n++)
3704                         /* Terminate each prefix regexpression by a
3705                          * "\ ?" (zero or ONE space), and OR them */
3706                         g_string_append_printf(s, "(%s\\ ?)%s",
3707                                           prefixes[n],
3708                                           n < PREFIXES - 1 ?
3709                                           "|" : "");
3710
3711                 g_string_prepend(s, "(");
3712                 g_string_append(s, ")+");       /* match at least once */
3713                 g_string_prepend(s, "^\\ *");   /* from beginning of line */
3714
3715
3716                 /* We now have something like "^\ *((PREFIX1\ ?)|(PREFIX2\ ?))+"
3717                  * TODO: Should this be       "^\ *(((PREFIX1)|(PREFIX2))\ ?)+" ??? */
3718                 if (regcomp(&u_regex, s->str, REG_EXTENDED | REG_ICASE)) {
3719                         debug_print("Error compiling regexp %s\n", s->str);
3720                         g_string_free(s, TRUE);
3721                         return 0;
3722                 } else {
3723                         u_init_ = TRUE;
3724                         g_string_free(s, TRUE);
3725                 }
3726         }
3727
3728         if (!regexec(&u_regex, subject, 1, &pos, 0) && pos.rm_so != -1)
3729                 return pos.rm_eo;
3730         else
3731                 return 0;
3732 #else
3733         /*!< Array with allowable reply prefixes regexps. */
3734         static const gchar * const prefixes[] = {
3735                 "re:",                  /* "Re:" */
3736                 "antw:",                        /* "Antw:" (Dutch / German Outlook) */
3737                 "aw:",                  /* "Aw:"   (German) */
3738                 "antwort:",                     /* "Antwort:" (German Lotus Notes) */
3739                 "res:",                 /* "Res:" (Spanish/Brazilian Outlook) */
3740                 "fw:",                  /* "Fw:" Forward */
3741                 "fwd:",                 /* "Fwd:" Forward */
3742                 "enc:",                 /* "Enc:" Forward (Brazilian Outlook) */
3743                 "odp:",                 /* "Odp:" Re (Polish Outlook) */
3744                 "rif:",                 /* "Rif:" (Italian Outlook) */
3745                 "sv:",                  /* "Sv" (Norwegian) */
3746                 "vs:",                  /* "Vs" (Norwegian) */
3747                 "ad:",                  /* "Ad" (Norwegian) */
3748                 /* add more */
3749         };
3750         const int PREFIXES = sizeof prefixes / sizeof prefixes[0];
3751         int n;
3752
3753         if (!subject) return 0;
3754         if (!*subject) return 0;
3755
3756         for (n = 0; n < PREFIXES; n++) {
3757                 int len = strlen(prefixes[n]);
3758                 if (!strncasecmp(subject, prefixes[n], len))
3759                         return len;
3760         }
3761         return 0;
3762 #endif
3763 }
3764 static guint g_stricase_hash(gconstpointer gptr)
3765 {
3766         guint hash_result = 0;
3767         const char *str;
3768
3769         for (str = gptr; str && *str; str++) {
3770                 hash_result += toupper(*str);
3771         }
3772
3773         return hash_result;
3774 }
3775
3776 static gint g_stricase_equal(gconstpointer gptr1, gconstpointer gptr2)
3777 {
3778         const char *str1 = gptr1;
3779         const char *str2 = gptr2;
3780
3781         return !strcasecmp(str1, str2);
3782 }
3783
3784 gint g_int_compare(gconstpointer a, gconstpointer b)
3785 {
3786         return GPOINTER_TO_INT(a) - GPOINTER_TO_INT(b);
3787 }
3788
3789 gchar *generate_msgid(gchar *buf, gint len)
3790 {
3791         struct tm *lt;
3792         time_t t;
3793         gchar *addr;
3794 #ifndef G_OS_WIN32
3795         struct tm buft;
3796 #endif
3797
3798         t = time(NULL);
3799         lt = localtime_r(&t, &buft);
3800
3801         if (strcmp(buf, "") == 0) {
3802                 addr = g_strconcat("@", get_domain_name(), NULL);
3803         }
3804         else {
3805                 addr = g_strconcat("@", buf, NULL);
3806         }
3807
3808         g_snprintf(buf, len, "%04d%02d%02d%02d%02d%02d.%08x%s",
3809                    lt->tm_year + 1900, lt->tm_mon + 1,
3810                    lt->tm_mday, lt->tm_hour,
3811                    lt->tm_min, lt->tm_sec,
3812                    (guint) rand(), addr);
3813
3814         g_free(addr);
3815         return buf;
3816 }
3817
3818 /*
3819    quote_cmd_argument()
3820
3821    return a quoted string safely usable in argument of a command.
3822
3823    code is extracted and adapted from etPan! project -- DINH V. Hoà.
3824 */
3825
3826 gint quote_cmd_argument(gchar * result, guint size,
3827                         const gchar * path)
3828 {
3829         const gchar * p;
3830         gchar * result_p;
3831         guint remaining;
3832
3833         result_p = result;
3834         remaining = size;
3835
3836         for(p = path ; * p != '\0' ; p ++) {
3837
3838                 if (isalnum((guchar)*p) || (* p == '/')) {
3839                         if (remaining > 0) {
3840                                 * result_p = * p;
3841                                 result_p ++;
3842                                 remaining --;
3843                         }
3844                         else {
3845                                 result[size - 1] = '\0';
3846                                 return -1;
3847                         }
3848                 }
3849                 else {
3850                         if (remaining >= 2) {
3851                                 * result_p = '\\';
3852                                 result_p ++;
3853                                 * result_p = * p;
3854                                 result_p ++;
3855                                 remaining -= 2;
3856                         }
3857                         else {
3858                                 result[size - 1] = '\0';
3859                                 return -1;
3860                         }
3861                 }
3862         }
3863         if (remaining > 0) {
3864                 * result_p = '\0';
3865         }
3866         else {
3867                 result[size - 1] = '\0';
3868                 return -1;
3869         }
3870
3871         return 0;
3872 }
3873
3874 typedef struct
3875 {
3876         GNode           *parent;
3877         GNodeMapFunc     func;
3878         gpointer         data;
3879 } GNodeMapData;
3880
3881 static void g_node_map_recursive(GNode *node, gpointer data)
3882 {
3883         GNodeMapData *mapdata = (GNodeMapData *) data;
3884         GNode *newnode;
3885         GNodeMapData newmapdata;
3886         gpointer newdata;
3887
3888         newdata = mapdata->func(node->data, mapdata->data);
3889         if (newdata != NULL) {
3890                 newnode = g_node_new(newdata);
3891                 g_node_append(mapdata->parent, newnode);
3892
3893                 newmapdata.parent = newnode;
3894                 newmapdata.func = mapdata->func;
3895                 newmapdata.data = mapdata->data;
3896
3897                 g_node_children_foreach(node, G_TRAVERSE_ALL, g_node_map_recursive, &newmapdata);
3898         }
3899 }
3900
3901 GNode *g_node_map(GNode *node, GNodeMapFunc func, gpointer data)
3902 {
3903         GNode *root;
3904         GNodeMapData mapdata;
3905
3906         g_return_val_if_fail(node != NULL, NULL);
3907         g_return_val_if_fail(func != NULL, NULL);
3908
3909         root = g_node_new(func(node->data, data));
3910
3911         mapdata.parent = root;
3912         mapdata.func = func;
3913         mapdata.data = data;
3914
3915         g_node_children_foreach(node, G_TRAVERSE_ALL, g_node_map_recursive, &mapdata);
3916
3917         return root;
3918 }
3919
3920 #define HEX_TO_INT(val, hex)                    \
3921 {                                               \
3922         gchar c = hex;                          \
3923                                                 \
3924         if ('0' <= c && c <= '9') {             \
3925                 val = c - '0';                  \
3926         } else if ('a' <= c && c <= 'f') {      \
3927                 val = c - 'a' + 10;             \
3928         } else if ('A' <= c && c <= 'F') {      \
3929                 val = c - 'A' + 10;             \
3930         } else {                                \
3931                 val = -1;                       \
3932         }                                       \
3933 }
3934
3935 gboolean get_hex_value(guchar *out, gchar c1, gchar c2)
3936 {
3937         gint hi, lo;
3938
3939         HEX_TO_INT(hi, c1);
3940         HEX_TO_INT(lo, c2);
3941
3942         if (hi == -1 || lo == -1)
3943                 return FALSE;
3944
3945         *out = (hi << 4) + lo;
3946         return TRUE;
3947 }
3948
3949 #define INT_TO_HEX(hex, val)            \
3950 {                                       \
3951         if ((val) < 10)                 \
3952                 hex = '0' + (val);      \
3953         else                            \
3954                 hex = 'A' + (val) - 10; \
3955 }
3956
3957 void get_hex_str(gchar *out, guchar ch)
3958 {
3959         gchar hex;
3960
3961         INT_TO_HEX(hex, ch >> 4);
3962         *out++ = hex;
3963         INT_TO_HEX(hex, ch & 0x0f);
3964         *out++ = hex;
3965 }
3966
3967 #undef REF_DEBUG
3968 #ifndef REF_DEBUG
3969 #define G_PRINT_REF 1 == 1 ? (void) 0 : (void)
3970 #else
3971 #define G_PRINT_REF g_print
3972 #endif
3973
3974 /*!
3975  *\brief        Register ref counted pointer. It is based on GBoxed, so should
3976  *              work with anything that uses the GType system. The semantics
3977  *              are similar to a C++ auto pointer, with the exception that
3978  *              C doesn't have automatic closure (calling destructors) when
3979  *              exiting a block scope.
3980  *              Use the \ref G_TYPE_AUTO_POINTER macro instead of calling this
3981  *              function directly.
3982  *
3983  *\return       GType A GType type.
3984  */
3985 GType g_auto_pointer_register(void)
3986 {
3987         static GType auto_pointer_type;
3988         if (!auto_pointer_type)
3989                 auto_pointer_type =
3990                         g_boxed_type_register_static
3991                                 ("G_TYPE_AUTO_POINTER",
3992                                  (GBoxedCopyFunc) g_auto_pointer_copy,
3993                                  (GBoxedFreeFunc) g_auto_pointer_free);
3994         return auto_pointer_type;
3995 }
3996
3997 /*!
3998  *\brief        Structure with g_new() allocated pointer guarded by the
3999  *              auto pointer
4000  */
4001 typedef struct AutoPointerRef {
4002         void          (*free) (gpointer);
4003         gpointer        pointer;
4004         glong           cnt;
4005 } AutoPointerRef;
4006
4007 /*!
4008  *\brief        The auto pointer opaque structure that references the
4009  *              pointer guard block.
4010  */
4011 typedef struct AutoPointer {
4012         AutoPointerRef *ref;
4013         gpointer        ptr; /*!< access to protected pointer */
4014 } AutoPointer;
4015
4016 /*!
4017  *\brief        Creates an auto pointer for a g_new()ed pointer. Example:
4018  *
4019  *\code
4020  *
4021  *              ... tell gtk_list_store it should use a G_TYPE_AUTO_POINTER
4022  *              ... when assigning, copying and freeing storage elements
4023  *
4024  *              gtk_list_store_new(N_S_COLUMNS,
4025  *                                 G_TYPE_AUTO_POINTER,
4026  *                                 -1);
4027  *
4028  *
4029  *              Template *precious_data = g_new0(Template, 1);
4030  *              g_pointer protect = g_auto_pointer_new(precious_data);
4031  *
4032  *              gtk_list_store_set(container, &iter,
4033  *                                 S_DATA, protect,
4034  *                                 -1);
4035  *
4036  *              ... the gtk_list_store has copied the pointer and
4037  *              ... incremented its reference count, we should free
4038  *              ... the auto pointer (in C++ a destructor would do
4039  *              ... this for us when leaving block scope)
4040  *
4041  *              g_auto_pointer_free(protect);
4042  *
4043  *              ... gtk_list_store_set() now manages the data. When
4044  *              ... *explicitly* requesting a pointer from the list
4045  *              ... store, don't forget you get a copy that should be
4046  *              ... freed with g_auto_pointer_free() eventually.
4047  *
4048  *\endcode
4049  *
4050  *\param        pointer Pointer to be guarded.
4051  *
4052  *\return       GAuto * Pointer that should be used in containers with
4053  *              GType support.
4054  */
4055 GAuto *g_auto_pointer_new(gpointer p)
4056 {
4057         AutoPointerRef *ref;
4058         AutoPointer    *ptr;
4059
4060         if (p == NULL)
4061                 return NULL;
4062
4063         ref = g_new0(AutoPointerRef, 1);
4064         ptr = g_new0(AutoPointer, 1);
4065
4066         ref->pointer = p;
4067         ref->free = g_free;
4068         ref->cnt = 1;
4069
4070         ptr->ref = ref;
4071         ptr->ptr = p;
4072
4073 #ifdef REF_DEBUG
4074         G_PRINT_REF ("XXXX ALLOC(%lx)\n", p);
4075 #endif
4076         return ptr;
4077 }
4078
4079 /*!
4080  *\brief        Allocate an autopointer using the passed \a free function to
4081  *              free the guarded pointer
4082  */
4083 GAuto *g_auto_pointer_new_with_free(gpointer p, GFreeFunc free_)
4084 {
4085         AutoPointer *aptr;
4086
4087         if (p == NULL)
4088                 return NULL;
4089
4090         aptr = g_auto_pointer_new(p);
4091         aptr->ref->free = free_;
4092         return aptr;
4093 }
4094
4095 gpointer g_auto_pointer_get_ptr(GAuto *auto_ptr)
4096 {
4097         if (auto_ptr == NULL)
4098                 return NULL;
4099         return ((AutoPointer *) auto_ptr)->ptr;
4100 }
4101
4102 /*!
4103  *\brief        Copies an auto pointer by. It's mostly not necessary
4104  *              to call this function directly, unless you copy/assign
4105  *              the guarded pointer.
4106  *
4107  *\param        auto_ptr Auto pointer returned by previous call to
4108  *              g_auto_pointer_new_XXX()
4109  *
4110  *\return       gpointer An auto pointer
4111  */
4112 GAuto *g_auto_pointer_copy(GAuto *auto_ptr)
4113 {
4114         AutoPointer     *ptr;
4115         AutoPointerRef  *ref;
4116         AutoPointer     *newp;
4117
4118         if (auto_ptr == NULL)
4119                 return NULL;
4120
4121         ptr = auto_ptr;
4122         ref = ptr->ref;
4123         newp = g_new0(AutoPointer, 1);
4124
4125         newp->ref = ref;
4126         newp->ptr = ref->pointer;
4127         ++(ref->cnt);
4128
4129 #ifdef REF_DEBUG
4130         G_PRINT_REF ("XXXX COPY(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4131 #endif
4132         return newp;
4133 }
4134
4135 /*!
4136  *\brief        Free an auto pointer
4137  */
4138 void g_auto_pointer_free(GAuto *auto_ptr)
4139 {
4140         AutoPointer     *ptr;
4141         AutoPointerRef  *ref;
4142
4143         if (auto_ptr == NULL)
4144                 return;
4145
4146         ptr = auto_ptr;
4147         ref = ptr->ref;
4148
4149         if (--(ref->cnt) == 0) {
4150 #ifdef REF_DEBUG
4151                 G_PRINT_REF ("XXXX FREE(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4152 #endif
4153                 ref->free(ref->pointer);
4154                 g_free(ref);
4155         }
4156 #ifdef REF_DEBUG
4157         else
4158                 G_PRINT_REF ("XXXX DEREF(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4159 #endif
4160         g_free(ptr);
4161 }
4162
4163 void replace_returns(gchar *str)
4164 {
4165         if (!str)
4166                 return;
4167
4168         while (strstr(str, "\n")) {
4169                 *strstr(str, "\n") = ' ';
4170         }
4171         while (strstr(str, "\r")) {
4172                 *strstr(str, "\r") = ' ';
4173         }
4174 }
4175
4176 /* get_uri_part() - retrieves a URI starting from scanpos.
4177                     Returns TRUE if succesful */
4178 gboolean get_uri_part(const gchar *start, const gchar *scanpos,
4179                              const gchar **bp, const gchar **ep, gboolean hdr)
4180 {
4181         const gchar *ep_;
4182         gint parenthese_cnt = 0;
4183
4184         g_return_val_if_fail(start != NULL, FALSE);
4185         g_return_val_if_fail(scanpos != NULL, FALSE);
4186         g_return_val_if_fail(bp != NULL, FALSE);
4187         g_return_val_if_fail(ep != NULL, FALSE);
4188
4189         *bp = scanpos;
4190
4191         /* find end point of URI */
4192         for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
4193                 if (!g_ascii_isgraph(*(const guchar *)ep_) ||
4194                     !IS_ASCII(*(const guchar *)ep_) ||
4195                     strchr("[]{}<>\"", *ep_)) {
4196                         break;
4197                 } else if (strchr("(", *ep_)) {
4198                         parenthese_cnt++;
4199                 } else if (strchr(")", *ep_)) {
4200                         if (parenthese_cnt > 0)
4201                                 parenthese_cnt--;
4202                         else
4203                                 break;
4204                 }
4205         }
4206
4207         /* no punctuation at end of string */
4208
4209         /* FIXME: this stripping of trailing punctuations may bite with other URIs.
4210          * should pass some URI type to this function and decide on that whether
4211          * to perform punctuation stripping */
4212
4213 #define IS_REAL_PUNCT(ch)       (g_ascii_ispunct(ch) && !strchr("/?=-)", ch))
4214
4215         for (; ep_ - 1 > scanpos + 1 &&
4216                IS_REAL_PUNCT(*(ep_ - 1));
4217              ep_--)
4218                 ;
4219
4220 #undef IS_REAL_PUNCT
4221
4222         *ep = ep_;
4223
4224         return TRUE;
4225 }
4226
4227 gchar *make_uri_string(const gchar *bp, const gchar *ep)
4228 {
4229         while (bp && *bp && g_ascii_isspace(*bp))
4230                 bp++;
4231         return g_strndup(bp, ep - bp);
4232 }
4233
4234 /* valid mail address characters */
4235 #define IS_RFC822_CHAR(ch) \
4236         (IS_ASCII(ch) && \
4237          (ch) > 32   && \
4238          (ch) != 127 && \
4239          !g_ascii_isspace(ch) && \
4240          !strchr("(),;<>\"", (ch)))
4241
4242 /* alphabet and number within 7bit ASCII */
4243 #define IS_ASCII_ALNUM(ch)      (IS_ASCII(ch) && g_ascii_isalnum(ch))
4244 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
4245
4246 static GHashTable *create_domain_tab(void)
4247 {
4248         static const gchar *toplvl_domains [] = {
4249             "museum", "aero",
4250             "arpa", "coop", "info", "name", "biz", "com", "edu", "gov",
4251             "int", "mil", "net", "org", "ac", "ad", "ae", "af", "ag",
4252             "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au",
4253             "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi",
4254             "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
4255             "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl",
4256             "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz", "de",
4257             "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er",
4258             "es", "et", "eu", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd",
4259             "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
4260             "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
4261             "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir",
4262             "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
4263             "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc",
4264             "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
4265             "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq",
4266             "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na",
4267             "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu",
4268             "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm",
4269             "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru",
4270             "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj",
4271             "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
4272             "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to",
4273             "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um",
4274             "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu",
4275             "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw"
4276         };
4277         gint n;
4278         GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
4279
4280         g_return_val_if_fail(htab, NULL);
4281         for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++)
4282                 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
4283         return htab;
4284 }
4285
4286 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
4287 {
4288         const gint MAX_LVL_DOM_NAME_LEN = 6;
4289         gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
4290         const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
4291         register gchar *p;
4292
4293         if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
4294                 return FALSE;
4295
4296         for (p = buf; p < m &&  first < last; *p++ = *first++)
4297                 ;
4298         *p = 0;
4299
4300         return g_hash_table_lookup(tab, buf) != NULL;
4301 }
4302
4303 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
4304 gboolean get_email_part(const gchar *start, const gchar *scanpos,
4305                                const gchar **bp, const gchar **ep, gboolean hdr)
4306 {
4307         /* more complex than the uri part because we need to scan back and forward starting from
4308          * the scan position. */
4309         gboolean result = FALSE;
4310         const gchar *bp_ = NULL;
4311         const gchar *ep_ = NULL;
4312         static GHashTable *dom_tab;
4313         const gchar *last_dot = NULL;
4314         const gchar *prelast_dot = NULL;
4315         const gchar *last_tld_char = NULL;
4316
4317         /* the informative part of the email address (describing the name
4318          * of the email address owner) may contain quoted parts. the
4319          * closure stack stores the last encountered quotes. */
4320         gchar closure_stack[128];
4321         gchar *ptr = closure_stack;
4322
4323         g_return_val_if_fail(start != NULL, FALSE);
4324         g_return_val_if_fail(scanpos != NULL, FALSE);
4325         g_return_val_if_fail(bp != NULL, FALSE);
4326         g_return_val_if_fail(ep != NULL, FALSE);
4327
4328         if (hdr) {
4329                 const gchar *start_quote = NULL;
4330                 const gchar *end_quote = NULL;
4331 search_again:
4332                 /* go to the real start */
4333                 if (start[0] == ',')
4334                         start++;
4335                 if (start[0] == ';')
4336                         start++;
4337                 while (start[0] == '\n' || start[0] == '\r')
4338                         start++;
4339                 while (start[0] == ' ' || start[0] == '\t')
4340                         start++;
4341
4342                 *bp = start;
4343                 
4344                 /* check if there are quotes (to skip , in them) */
4345                 if (*start == '"') {
4346                         start_quote = start;
4347                         start++;
4348                         end_quote = strstr(start, "\"");
4349                 } else {
4350                         start_quote = NULL;
4351                         end_quote = NULL;
4352                 }
4353                 
4354                 /* skip anything between quotes */
4355                 if (start_quote && end_quote) {
4356                         start = end_quote;
4357                         
4358                 } 
4359
4360                 /* find end (either , or ; or end of line) */
4361                 if (strstr(start, ",") && strstr(start, ";"))
4362                         *ep = strstr(start,",") < strstr(start, ";")
4363                                 ? strstr(start, ",") : strstr(start, ";");
4364                 else if (strstr(start, ","))
4365                         *ep = strstr(start, ",");
4366                 else if (strstr(start, ";"))
4367                         *ep = strstr(start, ";");
4368                 else
4369                         *ep = start+strlen(start);
4370
4371                 /* go back to real start */
4372                 if (start_quote && end_quote) {
4373                         start = start_quote;
4374                 }
4375
4376                 /* check there's still an @ in that, or search
4377                  * further if possible */
4378                 if (strstr(start, "@") && strstr(start, "@") < *ep)
4379                         return TRUE;
4380                 else if (*ep < start+strlen(start)) {
4381                         start = *ep;
4382                         goto search_again;
4383                 } else if (start_quote && strstr(start, "\"") && strstr(start, "\"") < *ep) {
4384                         *bp = start_quote;
4385                         return TRUE;
4386                 } else
4387                         return FALSE;
4388         }
4389
4390         if (!dom_tab)
4391                 dom_tab = create_domain_tab();
4392         g_return_val_if_fail(dom_tab, FALSE);
4393
4394         /* scan start of address */
4395         for (bp_ = scanpos - 1;
4396              bp_ >= start && IS_RFC822_CHAR(*(const guchar *)bp_); bp_--)
4397                 ;
4398
4399         /* TODO: should start with an alnum? */
4400         bp_++;
4401         for (; bp_ < scanpos && !IS_ASCII_ALNUM(*(const guchar *)bp_); bp_++)
4402                 ;
4403
4404         if (bp_ != scanpos) {
4405                 /* scan end of address */
4406                 for (ep_ = scanpos + 1;
4407                      *ep_ && IS_RFC822_CHAR(*(const guchar *)ep_); ep_++)
4408                         if (*ep_ == '.') {
4409                                 prelast_dot = last_dot;
4410                                 last_dot = ep_;
4411                                 if (*(last_dot + 1) == '.') {
4412                                         if (prelast_dot == NULL)
4413                                                 return FALSE;
4414                                         last_dot = prelast_dot;
4415                                         break;
4416                                 }
4417                         }
4418
4419                 /* TODO: really should terminate with an alnum? */
4420                 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*(const guchar *)ep_);
4421                      --ep_)
4422                         ;
4423                 ep_++;
4424
4425                 if (last_dot == NULL)
4426                         return FALSE;
4427                 if (last_dot >= ep_)
4428                         last_dot = prelast_dot;
4429                 if (last_dot == NULL || (scanpos + 1 >= last_dot))
4430                         return FALSE;
4431                 last_dot++;
4432
4433                 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
4434                         if (*last_tld_char == '?')
4435                                 break;
4436
4437                 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
4438                         result = TRUE;
4439
4440                 *ep = ep_;
4441                 *bp = bp_;
4442         }
4443
4444         if (!result) return FALSE;
4445
4446         if (*ep_ && bp_ != start && *(bp_ - 1) == '"' && *(ep_) == '"'
4447         && *(ep_ + 1) == ' ' && *(ep_ + 2) == '<'
4448         && IS_RFC822_CHAR(*(ep_ + 3))) {
4449                 /* this informative part with an @ in it is
4450                  * followed by the email address */
4451                 ep_ += 3;
4452
4453                 /* go to matching '>' (or next non-rfc822 char, like \n) */
4454                 for (; *ep_ != '>' && *ep != '\0' && IS_RFC822_CHAR(*ep_); ep_++)
4455                         ;
4456
4457                 /* include the bracket */
4458                 if (*ep_ == '>') ep_++;
4459
4460                 /* include the leading quote */
4461                 bp_--;
4462
4463                 *ep = ep_;
4464                 *bp = bp_;
4465                 return TRUE;
4466         }
4467
4468         /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
4469         if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
4470                 return FALSE;
4471
4472         /* see if this is <bracketed>; in this case we also scan for the informative part. */
4473         if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
4474                 return TRUE;
4475
4476 #define FULL_STACK()    ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
4477 #define IN_STACK()      (ptr > closure_stack)
4478 /* has underrun check */
4479 #define POP_STACK()     if(IN_STACK()) --ptr
4480 /* has overrun check */
4481 #define PUSH_STACK(c)   if(!FULL_STACK()) *ptr++ = (c); else return TRUE
4482 /* has underrun check */
4483 #define PEEK_STACK()    (IN_STACK() ? *(ptr - 1) : 0)
4484
4485         ep_++;
4486
4487         /* scan for the informative part. */
4488         for (bp_ -= 2; bp_ >= start; bp_--) {
4489                 /* if closure on the stack keep scanning */
4490                 if (PEEK_STACK() == *bp_) {
4491                         POP_STACK();
4492                         continue;
4493                 }
4494                 if (!IN_STACK() && (*bp_ == '\'' || *bp_ == '"')) {
4495                         PUSH_STACK(*bp_);
4496                         continue;
4497                 }
4498
4499                 /* if nothing in the closure stack, do the special conditions
4500                  * the following if..else expression simply checks whether
4501                  * a token is acceptable. if not acceptable, the clause
4502                  * should terminate the loop with a 'break' */
4503                 if (!PEEK_STACK()) {
4504                         if (*bp_ == '-'
4505                         && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
4506                         && (((bp_ + 1) < ep_)    && isalnum(*(bp_ + 1)))) {
4507                                 /* hyphens are allowed, but only in
4508                                    between alnums */
4509                         } else if (strchr(" \"'", *bp_)) {
4510                                 /* but anything not being a punctiation
4511                                    is ok */
4512                         } else {
4513                                 break; /* anything else is rejected */
4514                         }
4515                 }
4516         }
4517
4518         bp_++;
4519
4520         /* scan forward (should start with an alnum) */
4521         for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
4522                 ;
4523 #undef PEEK_STACK
4524 #undef PUSH_STACK
4525 #undef POP_STACK
4526 #undef IN_STACK
4527 #undef FULL_STACK
4528
4529
4530         *bp = bp_;
4531         *ep = ep_;
4532
4533         return result;
4534 }
4535
4536 #undef IS_QUOTE
4537 #undef IS_ASCII_ALNUM
4538 #undef IS_RFC822_CHAR
4539
4540 gchar *make_email_string(const gchar *bp, const gchar *ep)
4541 {
4542         /* returns a mailto: URI; mailto: is also used to detect the
4543          * uri type later on in the button_pressed signal handler */
4544         gchar *tmp;
4545         gchar *result;
4546
4547         tmp = g_strndup(bp, ep - bp);
4548         result = g_strconcat("mailto:", tmp, NULL);
4549         g_free(tmp);
4550
4551         return result;
4552 }
4553
4554 gchar *make_http_string(const gchar *bp, const gchar *ep)
4555 {
4556         /* returns an http: URI; */
4557         gchar *tmp;
4558         gchar *result;
4559
4560         while (bp && *bp && g_ascii_isspace(*bp))
4561                 bp++;
4562         tmp = g_strndup(bp, ep - bp);
4563         result = g_strconcat("http://", tmp, NULL);
4564         g_free(tmp);
4565
4566         return result;
4567 }
4568
4569 static gchar *mailcap_get_command_in_file(const gchar *path, const gchar *type, const gchar *file_to_open)
4570 {
4571         FILE *fp = fopen(path, "rb");
4572         gchar buf[BUFFSIZE];
4573         gchar *result = NULL;
4574         if (!fp)
4575                 return NULL;
4576         while (fgets(buf, sizeof (buf), fp) != NULL) {
4577                 gchar **parts = g_strsplit(buf, ";", 3);
4578                 gchar *trimmed = parts[0];
4579                 while (trimmed[0] == ' ' || trimmed[0] == '\t')
4580                         trimmed++;
4581                 while (trimmed[strlen(trimmed)-1] == ' ' || trimmed[strlen(trimmed)-1] == '\t')
4582                         trimmed[strlen(trimmed)-1] = '\0';
4583
4584                 if (!strcmp(trimmed, type)) {
4585                         gboolean needsterminal = FALSE;
4586                         if (parts[2] && strstr(parts[2], "needsterminal")) {
4587                                 needsterminal = TRUE;
4588                         }
4589                         if (parts[2] && strstr(parts[2], "test=")) {
4590                                 gchar *orig_testcmd = g_strdup(strstr(parts[2], "test=")+5);
4591                                 gchar *testcmd = orig_testcmd;
4592                                 if (strstr(testcmd,";"))
4593                                         *(strstr(testcmd,";")) = '\0';
4594                                 while (testcmd[0] == ' ' || testcmd[0] == '\t')
4595                                         testcmd++;
4596                                 while (testcmd[strlen(testcmd)-1] == '\n')
4597                                         testcmd[strlen(testcmd)-1] = '\0';
4598                                 while (testcmd[strlen(testcmd)-1] == '\r')
4599                                         testcmd[strlen(testcmd)-1] = '\0';
4600                                 while (testcmd[strlen(testcmd)-1] == ' ' || testcmd[strlen(testcmd)-1] == '\t')
4601                                         testcmd[strlen(testcmd)-1] = '\0';
4602                                         
4603                                 if (strstr(testcmd, "%s")) {
4604                                         gchar *tmp = g_strdup_printf(testcmd, file_to_open);
4605                                         gint res = system(tmp);
4606                                         g_free(tmp);
4607                                         g_free(orig_testcmd);
4608                                         
4609                                         if (res != 0) {
4610                                                 g_strfreev(parts);
4611                                                 continue;
4612                                         }
4613                                 } else {
4614                                         gint res = system(testcmd);
4615                                         g_free(orig_testcmd);
4616                                         
4617                                         if (res != 0) {
4618                                                 g_strfreev(parts);
4619                                                 continue;
4620                                         }
4621                                 }
4622                         }
4623                         
4624                         trimmed = parts[1];
4625                         while (trimmed[0] == ' ' || trimmed[0] == '\t')
4626                                 trimmed++;
4627                         while (trimmed[strlen(trimmed)-1] == '\n')
4628                                 trimmed[strlen(trimmed)-1] = '\0';
4629                         while (trimmed[strlen(trimmed)-1] == '\r')
4630                                 trimmed[strlen(trimmed)-1] = '\0';
4631                         while (trimmed[strlen(trimmed)-1] == ' ' || trimmed[strlen(trimmed)-1] == '\t')
4632                                 trimmed[strlen(trimmed)-1] = '\0';
4633                         result = g_strdup(trimmed);
4634                         g_strfreev(parts);
4635                         fclose(fp);
4636                         /* if there are no single quotes around %s, add them.
4637                          * '.*%s.*' is ok, as in display 'png:%s'
4638                          */
4639                         if (strstr(result, "%s") 
4640                         && !(strstr(result, "'") < strstr(result,"%s") &&
4641                              strstr(strstr(result,"%s"), "'"))) {
4642                                 gchar *start = g_strdup(result);
4643                                 gchar *end = g_strdup(strstr(result, "%s")+2);
4644                                 gchar *tmp;
4645                                 *strstr(start, "%s") = '\0';
4646                                 tmp = g_strconcat(start,"'%s'",end, NULL);
4647                                 g_free(start);
4648                                 g_free(end);
4649                                 g_free(result);
4650                                 result = tmp;
4651                         }
4652                         if (needsterminal) {
4653                                 gchar *tmp = g_strdup_printf("xterm -e %s", result);
4654                                 g_free(result);
4655                                 result = tmp;
4656                         }
4657                         return result;
4658                 }
4659                 g_strfreev(parts);
4660         }
4661         fclose(fp);
4662         return NULL;
4663 }
4664 gchar *mailcap_get_command_for_type(const gchar *type, const gchar *file_to_open)
4665 {
4666         gchar *result = NULL;
4667         gchar *path = NULL;
4668         if (type == NULL)
4669                 return NULL;
4670         path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap", NULL);
4671         result = mailcap_get_command_in_file(path, type, file_to_open);
4672         g_free(path);
4673         if (result)
4674                 return result;
4675         result = mailcap_get_command_in_file("/etc/mailcap", type, file_to_open);
4676         return result;
4677 }
4678
4679 void mailcap_update_default(const gchar *type, const gchar *command)
4680 {
4681         gchar *path = NULL, *outpath = NULL;
4682         path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap", NULL);
4683         outpath = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap.new", NULL);
4684         FILE *fp = fopen(path, "rb");
4685         FILE *outfp = fopen(outpath, "wb");
4686         gchar buf[BUFFSIZE];
4687         gboolean err = FALSE;
4688
4689         if (!outfp) {
4690                 g_free(path);
4691                 g_free(outpath);
4692                 fclose(fp);
4693                 return;
4694         }
4695         while (fp && fgets(buf, sizeof (buf), fp) != NULL) {
4696                 gchar **parts = g_strsplit(buf, ";", 3);
4697                 gchar *trimmed = parts[0];
4698                 while (trimmed[0] == ' ')
4699                         trimmed++;
4700                 while (trimmed[strlen(trimmed)-1] == ' ')
4701                         trimmed[strlen(trimmed)-1] = '\0';
4702
4703                 if (!strcmp(trimmed, type)) {
4704                         g_strfreev(parts);
4705                         continue;
4706                 }
4707                 else {
4708                         if(fputs(buf, outfp) == EOF) {
4709                                 err = TRUE;
4710                                 break;
4711                         }
4712                 }
4713                 g_strfreev(parts);
4714         }
4715         if (fprintf(outfp, "%s; %s\n", type, command) < 0)
4716                 err = TRUE;
4717
4718         if (fp)
4719                 fclose(fp);
4720
4721         if (fclose(outfp) == EOF)
4722                 err = TRUE;
4723                 
4724         if (!err)
4725                 g_rename(outpath, path);
4726
4727         g_free(path);
4728         g_free(outpath);
4729 }
4730
4731 gint copy_dir(const gchar *src, const gchar *dst)
4732 {
4733         GDir *dir;
4734         const gchar *name;
4735
4736         if ((dir = g_dir_open(src, 0, NULL)) == NULL) {
4737                 g_warning("failed to open directory: %s\n", src);
4738                 return -1;
4739         }
4740
4741         if (make_dir(dst) < 0)
4742                 return -1;
4743
4744         while ((name = g_dir_read_name(dir)) != NULL) {
4745                 gchar *old_file, *new_file;
4746                 old_file = g_strconcat(src, G_DIR_SEPARATOR_S, name, NULL);
4747                 new_file = g_strconcat(dst, G_DIR_SEPARATOR_S, name, NULL);
4748                 debug_print("copying: %s -> %s\n", old_file, new_file);
4749                 if (g_file_test(old_file, G_FILE_TEST_IS_REGULAR)) {
4750                         gint r = copy_file(old_file, new_file, TRUE);
4751                         if (r < 0) {
4752                                 g_dir_close(dir);
4753                                 return r;
4754                         }
4755                 }
4756 #ifndef G_OS_WIN32
4757                 /* Windows has no symlinks.  Or well, Vista seems to
4758                    have something like this but the semantics might be
4759                    different.  Thus we don't use it under Windows. */
4760                  else if (g_file_test(old_file, G_FILE_TEST_IS_SYMLINK)) {
4761                         GError *error;
4762                         gint r = 0;
4763                         gchar *target = g_file_read_link(old_file, &error);
4764                         if (target)
4765                                 r = symlink(target, new_file);
4766                         g_free(target);
4767                         if (r < 0) {
4768                                 g_dir_close(dir);
4769                                 return r;
4770                         }
4771                  }
4772 #endif /*G_OS_WIN32*/
4773                 else if (g_file_test(old_file, G_FILE_TEST_IS_DIR)) {
4774                         gint r = copy_dir(old_file, new_file);
4775                         if (r < 0) {
4776                                 g_dir_close(dir);
4777                                 return r;
4778                         }
4779                 }
4780         }
4781         g_dir_close(dir);
4782         return 0;
4783 }
4784
4785 /* crude test to see if a file is an email. */
4786 gboolean file_is_email (const gchar *filename)
4787 {
4788         FILE *fp = NULL;
4789         gchar buffer[2048];
4790         gint i = 0;
4791         gint score = 0;
4792         if (filename == NULL)
4793                 return FALSE;
4794         if ((fp = g_fopen(filename, "rb")) == NULL)
4795                 return FALSE;
4796         while (i < 60 && score < 3
4797                && fgets(buffer, sizeof (buffer), fp) > 0) {
4798                 if (!strncmp(buffer, "From:", strlen("From:")))
4799                         score++;
4800                 if (!strncmp(buffer, "To:", strlen("To:")))
4801                         score++;
4802                 if (!strncmp(buffer, "Subject:", strlen("Subject:")))
4803                         score++;
4804                 i++;
4805         }
4806         fclose(fp);
4807         return (score >= 3);
4808 }
4809
4810 gboolean sc_g_list_bigger(GList *list, gint max)
4811 {
4812         GList *cur = list;
4813         int i = 0;
4814         while (cur && i <= max+1) {
4815                 i++;
4816                 cur = cur->next;
4817         }
4818         return (i > max);
4819 }
4820
4821 gboolean sc_g_slist_bigger(GSList *list, gint max)
4822 {
4823         GSList *cur = list;
4824         int i = 0;
4825         while (cur && i <= max+1) {
4826                 i++;
4827                 cur = cur->next;
4828         }
4829         return (i > max);
4830 }
4831
4832 const gchar *daynames[] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL};
4833 const gchar *monthnames[] = {NULL, NULL, NULL, NULL, NULL, NULL, 
4834                              NULL, NULL, NULL, NULL, NULL, NULL};
4835 const gchar *s_daynames[] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL};
4836 const gchar *s_monthnames[] = {NULL, NULL, NULL, NULL, NULL, NULL, 
4837                              NULL, NULL, NULL, NULL, NULL, NULL};
4838
4839 gint daynames_len[] =     {0,0,0,0,0,0,0};
4840 gint monthnames_len[] =   {0,0,0,0,0,0,
4841                                  0,0,0,0,0,0};
4842 gint s_daynames_len[] =   {0,0,0,0,0,0,0};
4843 gint s_monthnames_len[] = {0,0,0,0,0,0,
4844                                  0,0,0,0,0,0};
4845 const gchar *s_am_up = NULL;
4846 const gchar *s_pm_up = NULL;
4847 const gchar *s_am_low = NULL;
4848 const gchar *s_pm_low = NULL;
4849
4850 gint s_am_up_len = 0;
4851 gint s_pm_up_len = 0;
4852 gint s_am_low_len = 0;
4853 gint s_pm_low_len = 0;
4854
4855 const gchar *def_loc_format = NULL;
4856 const gchar *date_loc_format = NULL;
4857 const gchar *time_loc_format = NULL;
4858 const gchar *time_am_pm = NULL;
4859
4860 static gboolean time_names_init_done = FALSE;
4861
4862 static void init_time_names(void)
4863 {
4864         int i = 0;
4865
4866         daynames[0] = Q_("Complete day name for use by strftime|Sunday");
4867         daynames[1] = Q_("Complete day name for use by strftime|Monday");
4868         daynames[2] = Q_("Complete day name for use by strftime|Tuesday");
4869         daynames[3] = Q_("Complete day name for use by strftime|Wednesday");
4870         daynames[4] = Q_("Complete day name for use by strftime|Thursday");
4871         daynames[5] = Q_("Complete day name for use by strftime|Friday");
4872         daynames[6] = Q_("Complete day name for use by strftime|Saturday");
4873
4874         monthnames[0] = Q_("Complete month name for use by strftime|January");
4875         monthnames[1] = Q_("Complete month name for use by strftime|February");
4876         monthnames[2] = Q_("Complete month name for use by strftime|March");
4877         monthnames[3] = Q_("Complete month name for use by strftime|April");
4878         monthnames[4] = Q_("Complete month name for use by strftime|May");
4879         monthnames[5] = Q_("Complete month name for use by strftime|June");
4880         monthnames[6] = Q_("Complete month name for use by strftime|July");
4881         monthnames[7] = Q_("Complete month name for use by strftime|August");
4882         monthnames[8] = Q_("Complete month name for use by strftime|September");
4883         monthnames[9] = Q_("Complete month name for use by strftime|October");
4884         monthnames[10] = Q_("Complete month name for use by strftime|November");
4885         monthnames[11] = Q_("Complete month name for use by strftime|December");
4886
4887         s_daynames[0] = Q_("Abbr. day name for use by strftime|Sun");
4888         s_daynames[1] = Q_("Abbr. day name for use by strftime|Mon");
4889         s_daynames[2] = Q_("Abbr. day name for use by strftime|Tue");
4890         s_daynames[3] = Q_("Abbr. day name for use by strftime|Wed");
4891         s_daynames[4] = Q_("Abbr. day name for use by strftime|Thu");
4892         s_daynames[5] = Q_("Abbr. day name for use by strftime|Fri");
4893         s_daynames[6] = Q_("Abbr. day name for use by strftime|Sat");
4894         
4895         s_monthnames[0] = Q_("Abbr. month name for use by strftime|Jan");
4896         s_monthnames[1] = Q_("Abbr. month name for use by strftime|Feb");
4897         s_monthnames[2] = Q_("Abbr. month name for use by strftime|Mar");
4898         s_monthnames[3] = Q_("Abbr. month name for use by strftime|Apr");
4899         s_monthnames[4] = Q_("Abbr. month name for use by strftime|May");
4900         s_monthnames[5] = Q_("Abbr. month name for use by strftime|Jun");
4901         s_monthnames[6] = Q_("Abbr. month name for use by strftime|Jul");
4902         s_monthnames[7] = Q_("Abbr. month name for use by strftime|Aug");
4903         s_monthnames[8] = Q_("Abbr. month name for use by strftime|Sep");
4904         s_monthnames[9] = Q_("Abbr. month name for use by strftime|Oct");
4905         s_monthnames[10] = Q_("Abbr. month name for use by strftime|Nov");
4906         s_monthnames[11] = Q_("Abbr. month name for use by strftime|Dec");
4907
4908         for (i = 0; i < 7; i++) {
4909                 daynames_len[i] = strlen(daynames[i]);
4910                 s_daynames_len[i] = strlen(s_daynames[i]);
4911         }
4912         for (i = 0; i < 12; i++) {
4913                 monthnames_len[i] = strlen(monthnames[i]);
4914                 s_monthnames_len[i] = strlen(s_monthnames[i]);
4915         }
4916
4917         s_am_up = Q_("For use by strftime (morning)|AM");
4918         s_pm_up = Q_("For use by strftime (afternoon)|PM");
4919         s_am_low = Q_("For use by strftime (morning, lowercase)|am");
4920         s_pm_low = Q_("For use by strftime (afternoon, lowercase)|pm");
4921         
4922         s_am_up_len = strlen(s_am_up);
4923         s_pm_up_len = strlen(s_pm_up);
4924         s_am_low_len = strlen(s_am_low);
4925         s_pm_low_len = strlen(s_pm_low);
4926         
4927         def_loc_format = Q_("For use by strftime (default date+time format)|%a %b %e %H:%M:%S %Y");
4928         date_loc_format = Q_("For use by strftime (default date format)|%m/%d/%y");
4929         time_loc_format = Q_("For use by strftime (default time format)|%H:%M:%S");
4930
4931         time_am_pm = Q_("For use by strftime (default 12-hour time format)|%I:%M:%S %p");
4932
4933         time_names_init_done = TRUE;
4934 }
4935
4936 #define CHECK_SIZE() {                  \
4937         total_done += len;              \
4938         if (total_done >= buflen) {     \
4939                 buf[buflen-1] = '\0';   \
4940                 return 0;               \
4941         }                               \
4942 }
4943
4944 size_t fast_strftime(gchar *buf, gint buflen, const gchar *format, struct tm *lt)
4945 {
4946         gchar *curpos = buf;
4947         gint total_done = 0;
4948         gchar subbuf[64], subfmt[64];
4949         static time_t last_tzset = (time_t)0;
4950         
4951         if (!time_names_init_done)
4952                 init_time_names();
4953         
4954         if (format == NULL || lt == NULL)
4955                 return 0;
4956                 
4957         if (last_tzset != time(NULL)) {
4958                 tzset();
4959                 last_tzset = time(NULL);
4960         }
4961         while(*format) {
4962                 if (*format == '%') {
4963                         gint len = 0, tmp = 0;
4964                         format++;
4965                         switch(*format) {
4966                         case '%':
4967                                 len = 1; CHECK_SIZE();
4968                                 *curpos = '%';
4969                                 break;
4970                         case 'a':
4971                                 len = s_daynames_len[lt->tm_wday]; CHECK_SIZE();
4972                                 strncpy2(curpos, s_daynames[lt->tm_wday], buflen - total_done);
4973                                 break;
4974                         case 'A':
4975                                 len = daynames_len[lt->tm_wday]; CHECK_SIZE();
4976                                 strncpy2(curpos, daynames[lt->tm_wday], buflen - total_done);
4977                                 break;
4978                         case 'b':
4979                         case 'h':
4980                                 len = s_monthnames_len[lt->tm_mon]; CHECK_SIZE();
4981                                 strncpy2(curpos, s_monthnames[lt->tm_mon], buflen - total_done);
4982                                 break;
4983                         case 'B':
4984                                 len = monthnames_len[lt->tm_mon]; CHECK_SIZE();
4985                                 strncpy2(curpos, monthnames[lt->tm_mon], buflen - total_done);
4986                                 break;
4987                         case 'c':
4988                                 fast_strftime(subbuf, 64, def_loc_format, lt);
4989                                 len = strlen(subbuf); CHECK_SIZE();
4990                                 strncpy2(curpos, subbuf, buflen - total_done);
4991                                 break;
4992                         case 'C':
4993                                 total_done += 2; CHECK_SIZE();
4994                                 tmp = (lt->tm_year + 1900)/100;
4995                                 *curpos++ = '0'+(tmp / 10);
4996                                 *curpos++ = '0'+(tmp % 10);
4997                                 break;
4998                         case 'd':
4999                                 total_done += 2; CHECK_SIZE();
5000                                 *curpos++ = '0'+(lt->tm_mday / 10);
5001                                 *curpos++ = '0'+(lt->tm_mday % 10);
5002                                 break;
5003                         case 'D':
5004                                 total_done += 8; CHECK_SIZE();
5005                                 *curpos++ = '0'+((lt->tm_mon+1) / 10);
5006                                 *curpos++ = '0'+((lt->tm_mon+1) % 10);
5007                                 *curpos++ = '/';
5008                                 *curpos++ = '0'+(lt->tm_mday / 10);
5009                                 *curpos++ = '0'+(lt->tm_mday % 10);
5010                                 *curpos++ = '/';
5011                                 tmp = lt->tm_year%100;
5012                                 *curpos++ = '0'+(tmp / 10);
5013                                 *curpos++ = '0'+(tmp % 10);
5014                                 break;
5015                         case 'e':
5016                                 len = 2; CHECK_SIZE();
5017                                 snprintf(curpos, buflen - total_done, "%2d", lt->tm_mday);
5018                                 break;
5019                         case 'F':
5020                                 len = 10; CHECK_SIZE();
5021                                 snprintf(curpos, buflen - total_done, "%4d-%02d-%02d", 
5022                                         lt->tm_year + 1900, lt->tm_mon +1, lt->tm_mday);
5023                                 break;
5024                         case 'H':
5025                                 total_done += 2; CHECK_SIZE();
5026                                 *curpos++ = '0'+(lt->tm_hour / 10);
5027                                 *curpos++ = '0'+(lt->tm_hour % 10);
5028                                 break;
5029                         case 'I':
5030                                 total_done += 2; CHECK_SIZE();
5031                                 tmp = lt->tm_hour;
5032                                 if (tmp > 12)
5033                                         tmp -= 12;
5034                                 else if (tmp == 0)
5035                                         tmp = 12;
5036                                 *curpos++ = '0'+(tmp / 10);
5037                                 *curpos++ = '0'+(tmp % 10);
5038                                 break;
5039                         case 'j':
5040                                 len = 3; CHECK_SIZE();
5041                                 snprintf(curpos, buflen - total_done, "%03d", lt->tm_yday+1);
5042                                 break;
5043                         case 'k':
5044                                 len = 2; CHECK_SIZE();
5045                                 snprintf(curpos, buflen - total_done, "%2d", lt->tm_hour);
5046                                 break;
5047                         case 'l':
5048                                 len = 2; CHECK_SIZE();
5049                                 tmp = lt->tm_hour;
5050                                 if (tmp > 12)
5051                                         tmp -= 12;
5052                                 else if (tmp == 0)
5053                                         tmp = 12;
5054                                 snprintf(curpos, buflen - total_done, "%2d", tmp);
5055                                 break;
5056                         case 'm':
5057                                 total_done += 2; CHECK_SIZE();
5058                                 tmp = lt->tm_mon + 1;
5059                                 *curpos++ = '0'+(tmp / 10);
5060                                 *curpos++ = '0'+(tmp % 10);
5061                                 break;
5062                         case 'M':
5063                                 total_done += 2; CHECK_SIZE();
5064                                 *curpos++ = '0'+(lt->tm_min / 10);
5065                                 *curpos++ = '0'+(lt->tm_min % 10);
5066                                 break;
5067                         case 'n':
5068                                 len = 1; CHECK_SIZE();
5069                                 *curpos = '\n';
5070                                 break;
5071                         case 'p':
5072                                 if (lt->tm_hour >= 12) {
5073                                         len = s_pm_up_len; CHECK_SIZE();
5074                                         snprintf(curpos, buflen-total_done, "%s", s_pm_up);
5075                                 } else {
5076                                         len = s_am_up_len; CHECK_SIZE();
5077                                         snprintf(curpos, buflen-total_done, "%s", s_am_up);
5078                                 }
5079                                 break;
5080                         case 'P':
5081                                 if (lt->tm_hour >= 12) {
5082                                         len = s_pm_low_len; CHECK_SIZE();
5083                                         snprintf(curpos, buflen-total_done, "%s", s_pm_low);
5084                                 } else {
5085                                         len = s_am_low_len; CHECK_SIZE();
5086                                         snprintf(curpos, buflen-total_done, "%s", s_am_low);
5087                                 }
5088                                 break;
5089                         case 'r':
5090                                 fast_strftime(subbuf, 64, time_am_pm, lt);
5091                                 len = strlen(subbuf); CHECK_SIZE();
5092                                 strncpy2(curpos, subbuf, buflen - total_done);
5093                                 break;
5094                         case 'R':
5095                                 total_done += 5; CHECK_SIZE();
5096                                 *curpos++ = '0'+(lt->tm_hour / 10);
5097                                 *curpos++ = '0'+(lt->tm_hour % 10);
5098                                 *curpos++ = ':';
5099                                 *curpos++ = '0'+(lt->tm_min / 10);
5100                                 *curpos++ = '0'+(lt->tm_min % 10);
5101                                 break;
5102                         case 's':
5103                                 snprintf(subbuf, 64, "%ld", mktime(lt));
5104                                 len = strlen(subbuf); CHECK_SIZE();
5105                                 strncpy2(curpos, subbuf, buflen - total_done);
5106                                 break;
5107                         case 'S':
5108                                 total_done += 2; CHECK_SIZE();
5109                                 *curpos++ = '0'+(lt->tm_sec / 10);
5110                                 *curpos++ = '0'+(lt->tm_sec % 10);
5111                                 break;
5112                         case 't':
5113                                 len = 1; CHECK_SIZE();
5114                                 *curpos = '\t';
5115                                 break;
5116                         case 'T':
5117                                 total_done += 8; CHECK_SIZE();
5118                                 *curpos++ = '0'+(lt->tm_hour / 10);
5119                                 *curpos++ = '0'+(lt->tm_hour % 10);
5120                                 *curpos++ = ':';
5121                                 *curpos++ = '0'+(lt->tm_min / 10);
5122                                 *curpos++ = '0'+(lt->tm_min % 10);
5123                                 *curpos++ = ':';
5124                                 *curpos++ = '0'+(lt->tm_sec / 10);
5125                                 *curpos++ = '0'+(lt->tm_sec % 10);
5126                                 break;
5127                         case 'u':
5128                                 len = 1; CHECK_SIZE();
5129                                 snprintf(curpos, buflen - total_done, "%d", lt->tm_wday == 0 ? 7: lt->tm_wday);
5130                                 break;
5131                         case 'w':
5132                                 len = 1; CHECK_SIZE();
5133                                 snprintf(curpos, buflen - total_done, "%d", lt->tm_wday);
5134                                 break;
5135                         case 'x':
5136                                 fast_strftime(subbuf, 64, date_loc_format, lt);
5137                                 len = strlen(subbuf); CHECK_SIZE();
5138                                 strncpy2(curpos, subbuf, buflen - total_done);
5139                                 break;
5140                         case 'X':
5141                                 fast_strftime(subbuf, 64, time_loc_format, lt);
5142                                 len = strlen(subbuf); CHECK_SIZE();
5143                                 strncpy2(curpos, subbuf, buflen - total_done);
5144                                 break;
5145                         case 'y':
5146                                 total_done += 2; CHECK_SIZE();
5147                                 tmp = lt->tm_year%100;
5148                                 *curpos++ = '0'+(tmp / 10);
5149                                 *curpos++ = '0'+(tmp % 10);
5150                                 break;
5151                         case 'Y':
5152                                 len = 4; CHECK_SIZE();
5153                                 snprintf(curpos, buflen - total_done, "%4d", lt->tm_year + 1900);
5154                                 break;
5155                         case 'G':
5156                         case 'g':
5157                         case 'U':
5158                         case 'V':
5159                         case 'W':
5160                         case 'z':
5161                         case 'Z':
5162                         case '+':
5163                                 /* let these complicated ones be done with the libc */
5164                                 snprintf(subfmt, 64, "%%%c", *format);
5165                                 strftime(subbuf, 64, subfmt, lt);
5166                                 len = strlen(subbuf); CHECK_SIZE();
5167                                 strncpy2(curpos, subbuf, buflen - total_done);
5168                                 break;
5169                         case 'E':
5170                         case 'O':
5171                                 /* let these complicated modifiers be done with the libc */
5172                                 snprintf(subfmt, 64, "%%%c%c", *format, *(format+1));
5173                                 strftime(subbuf, 64, subfmt, lt);
5174                                 len = strlen(subbuf); CHECK_SIZE();
5175                                 strncpy2(curpos, subbuf, buflen - total_done);
5176                                 format++;
5177                                 break;
5178                         default:
5179                                 if (format && *format)
5180                                         g_warning("format error (%c)", *format);
5181                                 *curpos = '\0';
5182                                 return total_done;
5183                         }
5184                         curpos += len;
5185                         format++;
5186                 } else {
5187                         int len = 1; CHECK_SIZE();
5188                         *curpos++ = *format++; 
5189                 }
5190         }
5191         *curpos++ = '\0';
5192         return total_done;
5193 }
5194
5195 gboolean prefs_common_get_use_shred(void);
5196
5197
5198 #ifdef G_OS_WIN32
5199 #define WEXITSTATUS(x) (x)
5200 #endif
5201
5202 int claws_unlink(const gchar *filename) 
5203 {
5204         struct stat s;
5205         static int found_shred = -1;
5206         static const gchar *args[4];
5207
5208         if (filename == NULL)
5209                 return 0;
5210
5211         if (prefs_common_get_use_shred()) {
5212                 if (found_shred == -1) {
5213                         /* init */
5214                         args[0] = g_find_program_in_path("shred");
5215                         debug_print("found shred: %s\n", args[0]);
5216                         found_shred = (args[0] != NULL) ? 1:0;
5217                         args[1] = "-f";
5218                         args[3] = NULL;
5219                 }
5220                 if (found_shred == 1) {
5221                         if (g_stat(filename, &s) == 0 && S_ISREG(s.st_mode)) {
5222                                 if (s.st_nlink == 1) {
5223                                         gint status=0;
5224                                         args[2] = filename;
5225                                         g_spawn_sync(NULL, (gchar **)args, NULL, 0,
5226                                          NULL, NULL, NULL, NULL, &status, NULL);
5227                                         debug_print("%s %s exited with status %d\n",
5228                                                 args[0], filename, WEXITSTATUS(status));
5229                                         if (truncate(filename, 0) < 0)
5230                                                 g_warning("couln't truncate");
5231                                 }
5232                         }
5233                 }
5234         }
5235         return g_unlink(filename);
5236 }