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