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