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