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