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