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