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