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