2009-03-17 [colin] 3.7.1cvs20
[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         if (g_spawn_sync(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH,
3290                          NULL, NULL, NULL, NULL, &status, NULL) == FALSE) {
3291                 g_warning("Couldn't execute command: %s\n", argv[0]);
3292                 return -1;
3293         }
3294
3295 #ifdef G_OS_UNIX
3296         if (WIFEXITED(status))
3297                 return WEXITSTATUS(status);
3298         else
3299                 return -1;
3300 #else
3301         return status;
3302 #endif
3303 }
3304
3305 gint execute_command_line(const gchar *cmdline, gboolean async)
3306 {
3307         gchar **argv;
3308         gint ret;
3309
3310         debug_print("execute_command_line(): executing: %s\n", cmdline?cmdline:"(null)");
3311
3312         argv = strsplit_with_quote(cmdline, " ", 0);
3313
3314         if (async)
3315                 ret = execute_async(argv);
3316         else
3317                 ret = execute_sync(argv);
3318
3319         g_strfreev(argv);
3320
3321         return ret;
3322 }
3323
3324 gchar *get_command_output(const gchar *cmdline)
3325 {
3326         gchar *child_stdout;
3327         gint status;
3328
3329         cm_return_val_if_fail(cmdline != NULL, NULL);
3330
3331         debug_print("get_command_output(): executing: %s\n", cmdline);
3332
3333         if (g_spawn_command_line_sync(cmdline, &child_stdout, NULL, &status,
3334                                       NULL) == FALSE) {
3335                 g_warning("Couldn't execute command: %s\n", cmdline);
3336                 return NULL;
3337         }
3338
3339         return child_stdout;
3340 }
3341 #ifndef MAEMO
3342 static gint is_unchanged_uri_char(char c)
3343 {
3344         switch (c) {
3345                 case '(':
3346                 case ')':
3347                         return 0;
3348                 default:
3349                         return 1;
3350         }
3351 }
3352
3353 static void encode_uri(gchar *encoded_uri, gint bufsize, const gchar *uri)
3354 {
3355         int i;
3356         int k;
3357
3358         k = 0;
3359         for(i = 0; i < strlen(uri) ; i++) {
3360                 if (is_unchanged_uri_char(uri[i])) {
3361                         if (k + 2 >= bufsize)
3362                                 break;
3363                         encoded_uri[k++] = uri[i];
3364                 }
3365                 else {
3366                         char * hexa = "0123456789ABCDEF";
3367
3368                         if (k + 4 >= bufsize)
3369                                 break;
3370                         encoded_uri[k++] = '%';
3371                         encoded_uri[k++] = hexa[uri[i] / 16];
3372                         encoded_uri[k++] = hexa[uri[i] % 16];
3373                 }
3374         }
3375         encoded_uri[k] = 0;
3376 }
3377 #endif
3378 gint open_uri(const gchar *uri, const gchar *cmdline)
3379 {
3380 #ifndef MAEMO
3381 #ifndef G_OS_WIN32
3382         gchar buf[BUFFSIZE];
3383         gchar *p;
3384         gchar encoded_uri[BUFFSIZE];
3385         cm_return_val_if_fail(uri != NULL, -1);
3386
3387         /* an option to choose whether to use encode_uri or not ? */
3388         encode_uri(encoded_uri, BUFFSIZE, uri);
3389
3390         if (cmdline &&
3391             (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
3392             !strchr(p + 2, '%'))
3393                 g_snprintf(buf, sizeof(buf), cmdline, encoded_uri);
3394         else {
3395                 if (cmdline)
3396                         g_warning("Open URI command-line is invalid "
3397                                   "(there must be only one '%%s'): %s",
3398                                   cmdline);
3399                 g_snprintf(buf, sizeof(buf), DEFAULT_BROWSER_CMD, encoded_uri);
3400         }
3401
3402         execute_command_line(buf, TRUE);
3403 #else
3404         ShellExecute(NULL, "open", uri, NULL, NULL, SW_SHOW);
3405 #endif
3406 #else
3407         extern osso_context_t *get_osso_context(void);
3408         osso_rpc_run_with_defaults(get_osso_context(), "osso_browser",
3409                                         OSSO_BROWSER_OPEN_NEW_WINDOW_REQ, NULL, 
3410                                         DBUS_TYPE_STRING, uri, DBUS_TYPE_INVALID);
3411 #endif
3412         return 0;
3413 }
3414
3415 gint open_txt_editor(const gchar *filepath, const gchar *cmdline)
3416 {
3417         gchar buf[BUFFSIZE];
3418         gchar *p;
3419
3420         cm_return_val_if_fail(filepath != NULL, -1);
3421
3422         if (cmdline &&
3423             (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
3424             !strchr(p + 2, '%'))
3425                 g_snprintf(buf, sizeof(buf), cmdline, filepath);
3426         else {
3427                 if (cmdline)
3428                         g_warning("Open Text Editor command-line is invalid "
3429                                   "(there must be only one '%%s'): %s",
3430                                   cmdline);
3431                 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, filepath);
3432         }
3433
3434         execute_command_line(buf, TRUE);
3435
3436         return 0;
3437 }
3438
3439 time_t remote_tzoffset_sec(const gchar *zone)
3440 {
3441         static gchar ustzstr[] = "PSTPDTMSTMDTCSTCDTESTEDT";
3442         gchar zone3[4];
3443         gchar *p;
3444         gchar c;
3445         gint iustz;
3446         gint offset;
3447         time_t remoteoffset;
3448
3449         strncpy(zone3, zone, 3);
3450         zone3[3] = '\0';
3451         remoteoffset = 0;
3452
3453         if (sscanf(zone, "%c%d", &c, &offset) == 2 &&
3454             (c == '+' || c == '-')) {
3455                 remoteoffset = ((offset / 100) * 60 + (offset % 100)) * 60;
3456                 if (c == '-')
3457                         remoteoffset = -remoteoffset;
3458         } else if (!strncmp(zone, "UT" , 2) ||
3459                    !strncmp(zone, "GMT", 2)) {
3460                 remoteoffset = 0;
3461         } else if (strlen(zone3) == 3) {
3462                 for (p = ustzstr; *p != '\0'; p += 3) {
3463                         if (!g_ascii_strncasecmp(p, zone3, 3)) {
3464                                 iustz = ((gint)(p - ustzstr) / 3 + 1) / 2 - 8;
3465                                 remoteoffset = iustz * 3600;
3466                                 break;
3467                         }
3468                 }
3469                 if (*p == '\0')
3470                         return -1;
3471         } else if (strlen(zone3) == 1) {
3472                 switch (zone[0]) {
3473                 case 'Z': remoteoffset =   0; break;
3474                 case 'A': remoteoffset =  -1; break;
3475                 case 'B': remoteoffset =  -2; break;
3476                 case 'C': remoteoffset =  -3; break;
3477                 case 'D': remoteoffset =  -4; break;
3478                 case 'E': remoteoffset =  -5; break;
3479                 case 'F': remoteoffset =  -6; break;
3480                 case 'G': remoteoffset =  -7; break;
3481                 case 'H': remoteoffset =  -8; break;
3482                 case 'I': remoteoffset =  -9; break;
3483                 case 'K': remoteoffset = -10; break; /* J is not used */
3484                 case 'L': remoteoffset = -11; break;
3485                 case 'M': remoteoffset = -12; break;
3486                 case 'N': remoteoffset =   1; break;
3487                 case 'O': remoteoffset =   2; break;
3488                 case 'P': remoteoffset =   3; break;
3489                 case 'Q': remoteoffset =   4; break;
3490                 case 'R': remoteoffset =   5; break;
3491                 case 'S': remoteoffset =   6; break;
3492                 case 'T': remoteoffset =   7; break;
3493                 case 'U': remoteoffset =   8; break;
3494                 case 'V': remoteoffset =   9; break;
3495                 case 'W': remoteoffset =  10; break;
3496                 case 'X': remoteoffset =  11; break;
3497                 case 'Y': remoteoffset =  12; break;
3498                 default:  remoteoffset =   0; break;
3499                 }
3500                 remoteoffset = remoteoffset * 3600;
3501         } else
3502                 return -1;
3503
3504         return remoteoffset;
3505 }
3506
3507 time_t tzoffset_sec(time_t *now)
3508 {
3509         struct tm gmt, *lt;
3510         gint off;
3511 #ifndef G_OS_WIN32
3512         struct tm buf1, buf2;
3513 #endif
3514 #ifdef G_OS_WIN32
3515         if (now && *now < 0)
3516                 return 0;
3517 #endif  
3518         gmt = *gmtime_r(now, &buf1);
3519         lt = localtime_r(now, &buf2);
3520
3521         off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
3522
3523         if (lt->tm_year < gmt.tm_year)
3524                 off -= 24 * 60;
3525         else if (lt->tm_year > gmt.tm_year)
3526                 off += 24 * 60;
3527         else if (lt->tm_yday < gmt.tm_yday)
3528                 off -= 24 * 60;
3529         else if (lt->tm_yday > gmt.tm_yday)
3530                 off += 24 * 60;
3531
3532         if (off >= 24 * 60)             /* should be impossible */
3533                 off = 23 * 60 + 59;     /* if not, insert silly value */
3534         if (off <= -24 * 60)
3535                 off = -(23 * 60 + 59);
3536
3537         return off * 60;
3538 }
3539
3540 /* calculate timezone offset */
3541 gchar *tzoffset(time_t *now)
3542 {
3543         static gchar offset_string[6];
3544         struct tm gmt, *lt;
3545         gint off;
3546         gchar sign = '+';
3547 #ifndef G_OS_WIN32
3548         struct tm buf1, buf2;
3549 #endif
3550 #ifdef G_OS_WIN32
3551         if (now && *now < 0)
3552                 return 0;
3553 #endif
3554         gmt = *gmtime_r(now, &buf1);
3555         lt = localtime_r(now, &buf2);
3556
3557         off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
3558
3559         if (lt->tm_year < gmt.tm_year)
3560                 off -= 24 * 60;
3561         else if (lt->tm_year > gmt.tm_year)
3562                 off += 24 * 60;
3563         else if (lt->tm_yday < gmt.tm_yday)
3564                 off -= 24 * 60;
3565         else if (lt->tm_yday > gmt.tm_yday)
3566                 off += 24 * 60;
3567
3568         if (off < 0) {
3569                 sign = '-';
3570                 off = -off;
3571         }
3572
3573         if (off >= 24 * 60)             /* should be impossible */
3574                 off = 23 * 60 + 59;     /* if not, insert silly value */
3575
3576         sprintf(offset_string, "%c%02d%02d", sign, off / 60, off % 60);
3577
3578         return offset_string;
3579 }
3580
3581 void get_rfc822_date(gchar *buf, gint len)
3582 {
3583         struct tm *lt;
3584         time_t t;
3585         gchar day[4], mon[4];
3586         gint dd, hh, mm, ss, yyyy;
3587 #ifndef G_OS_WIN32
3588         struct tm buf1;
3589         gchar buf2[BUFFSIZE];
3590 #endif
3591
3592         t = time(NULL);
3593         lt = localtime_r(&t, &buf1);
3594
3595         sscanf(asctime_r(lt, buf2), "%3s %3s %d %d:%d:%d %d\n",
3596                day, mon, &dd, &hh, &mm, &ss, &yyyy);
3597
3598         g_snprintf(buf, len, "%s, %d %s %d %02d:%02d:%02d %s",
3599                    day, dd, mon, yyyy, hh, mm, ss, tzoffset(&t));
3600 }
3601
3602 void debug_set_mode(gboolean mode)
3603 {
3604         debug_mode = mode;
3605 }
3606
3607 gboolean debug_get_mode(void)
3608 {
3609         return debug_mode;
3610 }
3611
3612 void debug_print_real(const gchar *format, ...)
3613 {
3614         va_list args;
3615         gchar buf[BUFFSIZE];
3616
3617         if (!debug_mode) return;
3618
3619         va_start(args, format);
3620         g_vsnprintf(buf, sizeof(buf), format, args);
3621         va_end(args);
3622
3623         g_print("%s", buf);
3624 }
3625
3626
3627 const char * debug_srcname(const char *file)
3628 {
3629         const char *s = strrchr (file, '/');
3630         return s? s+1:file;
3631 }
3632
3633
3634 void * subject_table_lookup(GHashTable *subject_table, gchar * subject)
3635 {
3636         if (subject == NULL)
3637                 subject = "";
3638         else
3639                 subject += subject_get_prefix_length(subject);
3640
3641         return g_hash_table_lookup(subject_table, subject);
3642 }
3643
3644 void subject_table_insert(GHashTable *subject_table, gchar * subject,
3645                           void * data)
3646 {
3647         if (subject == NULL || *subject == 0)
3648                 return;
3649         subject += subject_get_prefix_length(subject);
3650         g_hash_table_insert(subject_table, subject, data);
3651 }
3652
3653 void subject_table_remove(GHashTable *subject_table, gchar * subject)
3654 {
3655         if (subject == NULL)
3656                 return;
3657
3658         subject += subject_get_prefix_length(subject);
3659         g_hash_table_remove(subject_table, subject);
3660 }
3661
3662 /*!
3663  *\brief        Check if a string is prefixed with known (combinations)
3664  *              of prefixes. The function assumes that each prefix
3665  *              is terminated by zero or exactly _one_ space.
3666  *
3667  *\param        str String to check for a prefixes
3668  *
3669  *\return       int Number of chars in the prefix that should be skipped
3670  *              for a "clean" subject line. If no prefix was found, 0
3671  *              is returned.
3672  */
3673  
3674 #ifndef G_OS_WIN32
3675 static regex_t u_regex;
3676 static gboolean u_init_;
3677 #endif
3678
3679 void utils_free_regex(void)
3680 {
3681 #ifndef G_OS_WIN32
3682         if (u_init_) {
3683                 regfree(&u_regex);
3684                 u_init_ = FALSE;
3685         }
3686 #endif
3687 }
3688
3689 int subject_get_prefix_length(const gchar *subject)
3690 {
3691 #ifndef G_OS_WIN32
3692         /*!< Array with allowable reply prefixes regexps. */
3693         static const gchar * const prefixes[] = {
3694                 "Re\\:",                        /* "Re:" */
3695                 "Re\\[[1-9][0-9]*\\]\\:",       /* "Re[XXX]:" (non-conforming news mail clients) */
3696                 "Antw\\:",                      /* "Antw:" (Dutch / German Outlook) */
3697                 "Aw\\:",                        /* "Aw:"   (German) */
3698                 "Antwort\\:",                   /* "Antwort:" (German Lotus Notes) */
3699                 "Res\\:",                       /* "Res:" (Spanish/Brazilian Outlook) */
3700                 "Fw\\:",                        /* "Fw:" Forward */
3701                 "Fwd\\:",                       /* "Fwd:" Forward */
3702                 "Enc\\:",                       /* "Enc:" Forward (Brazilian Outlook) */
3703                 "Odp\\:",                       /* "Odp:" Re (Polish Outlook) */
3704                 "Rif\\:",                       /* "Rif:" (Italian Outlook) */
3705                 "Sv\\:",                        /* "Sv" (Norwegian) */
3706                 "Vs\\:",                        /* "Vs" (Norwegian) */
3707                 "Ad\\:",                        /* "Ad" (Norwegian) */
3708                 "\347\255\224\345\244\215\\:"   /* "Re" (Chinese, UTF-8) */
3709                 /* add more */
3710         };
3711         const int PREFIXES = sizeof prefixes / sizeof prefixes[0];
3712         int n;
3713         regmatch_t pos;
3714
3715         if (!subject) return 0;
3716         if (!*subject) return 0;
3717
3718         if (!u_init_) {
3719                 GString *s = g_string_new("");
3720
3721                 for (n = 0; n < PREFIXES; n++)
3722                         /* Terminate each prefix regexpression by a
3723                          * "\ ?" (zero or ONE space), and OR them */
3724                         g_string_append_printf(s, "(%s\\ ?)%s",
3725                                           prefixes[n],
3726                                           n < PREFIXES - 1 ?
3727                                           "|" : "");
3728
3729                 g_string_prepend(s, "(");
3730                 g_string_append(s, ")+");       /* match at least once */
3731                 g_string_prepend(s, "^\\ *");   /* from beginning of line */
3732
3733
3734                 /* We now have something like "^\ *((PREFIX1\ ?)|(PREFIX2\ ?))+"
3735                  * TODO: Should this be       "^\ *(((PREFIX1)|(PREFIX2))\ ?)+" ??? */
3736                 if (regcomp(&u_regex, s->str, REG_EXTENDED | REG_ICASE)) {
3737                         debug_print("Error compiling regexp %s\n", s->str);
3738                         g_string_free(s, TRUE);
3739                         return 0;
3740                 } else {
3741                         u_init_ = TRUE;
3742                         g_string_free(s, TRUE);
3743                 }
3744         }
3745
3746         if (!regexec(&u_regex, subject, 1, &pos, 0) && pos.rm_so != -1)
3747                 return pos.rm_eo;
3748         else
3749                 return 0;
3750 #else
3751         /*!< Array with allowable reply prefixes regexps. */
3752         static const gchar * const prefixes[] = {
3753                 "re:",                  /* "Re:" */
3754                 "antw:",                        /* "Antw:" (Dutch / German Outlook) */
3755                 "aw:",                  /* "Aw:"   (German) */
3756                 "antwort:",                     /* "Antwort:" (German Lotus Notes) */
3757                 "res:",                 /* "Res:" (Spanish/Brazilian Outlook) */
3758                 "fw:",                  /* "Fw:" Forward */
3759                 "fwd:",                 /* "Fwd:" Forward */
3760                 "enc:",                 /* "Enc:" Forward (Brazilian Outlook) */
3761                 "odp:",                 /* "Odp:" Re (Polish Outlook) */
3762                 "rif:",                 /* "Rif:" (Italian Outlook) */
3763                 "sv:",                  /* "Sv" (Norwegian) */
3764                 "vs:",                  /* "Vs" (Norwegian) */
3765                 "ad:",                  /* "Ad" (Norwegian) */
3766                 /* add more */
3767         };
3768         const int PREFIXES = sizeof prefixes / sizeof prefixes[0];
3769         int n;
3770
3771         if (!subject) return 0;
3772         if (!*subject) return 0;
3773
3774         for (n = 0; n < PREFIXES; n++) {
3775                 int len = strlen(prefixes[n]);
3776                 if (!strncasecmp(subject, prefixes[n], len)) {
3777                         if (subject[len] == ' ')
3778                                 return len+1;
3779                         else
3780                                 return len;
3781                 }
3782         }
3783         return 0;
3784 #endif
3785 }
3786 static guint g_stricase_hash(gconstpointer gptr)
3787 {
3788         guint hash_result = 0;
3789         const char *str;
3790
3791         for (str = gptr; str && *str; str++) {
3792                 hash_result += toupper(*str);
3793         }
3794
3795         return hash_result;
3796 }
3797
3798 static gint g_stricase_equal(gconstpointer gptr1, gconstpointer gptr2)
3799 {
3800         const char *str1 = gptr1;
3801         const char *str2 = gptr2;
3802
3803         return !strcasecmp(str1, str2);
3804 }
3805
3806 gint g_int_compare(gconstpointer a, gconstpointer b)
3807 {
3808         return GPOINTER_TO_INT(a) - GPOINTER_TO_INT(b);
3809 }
3810
3811 gchar *generate_msgid(gchar *buf, gint len)
3812 {
3813         struct tm *lt;
3814         time_t t;
3815         gchar *addr;
3816 #ifndef G_OS_WIN32
3817         struct tm buft;
3818 #endif
3819
3820         t = time(NULL);
3821         lt = localtime_r(&t, &buft);
3822
3823         if (strcmp(buf, "") == 0) {
3824                 addr = g_strconcat("@", get_domain_name(), NULL);
3825         }
3826         else {
3827                 addr = g_strconcat("@", buf, NULL);
3828         }
3829
3830         g_snprintf(buf, len, "%04d%02d%02d%02d%02d%02d.%08x%s",
3831                    lt->tm_year + 1900, lt->tm_mon + 1,
3832                    lt->tm_mday, lt->tm_hour,
3833                    lt->tm_min, lt->tm_sec,
3834                    (guint) rand(), addr);
3835
3836         g_free(addr);
3837         return buf;
3838 }
3839
3840 /*
3841    quote_cmd_argument()
3842
3843    return a quoted string safely usable in argument of a command.
3844
3845    code is extracted and adapted from etPan! project -- DINH V. Hoà.
3846 */
3847
3848 gint quote_cmd_argument(gchar * result, guint size,
3849                         const gchar * path)
3850 {
3851         const gchar * p;
3852         gchar * result_p;
3853         guint remaining;
3854
3855         result_p = result;
3856         remaining = size;
3857
3858         for(p = path ; * p != '\0' ; p ++) {
3859
3860                 if (isalnum((guchar)*p) || (* p == '/')) {
3861                         if (remaining > 0) {
3862                                 * result_p = * p;
3863                                 result_p ++;
3864                                 remaining --;
3865                         }
3866                         else {
3867                                 result[size - 1] = '\0';
3868                                 return -1;
3869                         }
3870                 }
3871                 else {
3872                         if (remaining >= 2) {
3873                                 * result_p = '\\';
3874                                 result_p ++;
3875                                 * result_p = * p;
3876                                 result_p ++;
3877                                 remaining -= 2;
3878                         }
3879                         else {
3880                                 result[size - 1] = '\0';
3881                                 return -1;
3882                         }
3883                 }
3884         }
3885         if (remaining > 0) {
3886                 * result_p = '\0';
3887         }
3888         else {
3889                 result[size - 1] = '\0';
3890                 return -1;
3891         }
3892
3893         return 0;
3894 }
3895
3896 typedef struct
3897 {
3898         GNode           *parent;
3899         GNodeMapFunc     func;
3900         gpointer         data;
3901 } GNodeMapData;
3902
3903 static void g_node_map_recursive(GNode *node, gpointer data)
3904 {
3905         GNodeMapData *mapdata = (GNodeMapData *) data;
3906         GNode *newnode;
3907         GNodeMapData newmapdata;
3908         gpointer newdata;
3909
3910         newdata = mapdata->func(node->data, mapdata->data);
3911         if (newdata != NULL) {
3912                 newnode = g_node_new(newdata);
3913                 g_node_append(mapdata->parent, newnode);
3914
3915                 newmapdata.parent = newnode;
3916                 newmapdata.func = mapdata->func;
3917                 newmapdata.data = mapdata->data;
3918
3919                 g_node_children_foreach(node, G_TRAVERSE_ALL, g_node_map_recursive, &newmapdata);
3920         }
3921 }
3922
3923 GNode *g_node_map(GNode *node, GNodeMapFunc func, gpointer data)
3924 {
3925         GNode *root;
3926         GNodeMapData mapdata;
3927
3928         cm_return_val_if_fail(node != NULL, NULL);
3929         cm_return_val_if_fail(func != NULL, NULL);
3930
3931         root = g_node_new(func(node->data, data));
3932
3933         mapdata.parent = root;
3934         mapdata.func = func;
3935         mapdata.data = data;
3936
3937         g_node_children_foreach(node, G_TRAVERSE_ALL, g_node_map_recursive, &mapdata);
3938
3939         return root;
3940 }
3941
3942 #define HEX_TO_INT(val, hex)                    \
3943 {                                               \
3944         gchar c = hex;                          \
3945                                                 \
3946         if ('0' <= c && c <= '9') {             \
3947                 val = c - '0';                  \
3948         } else if ('a' <= c && c <= 'f') {      \
3949                 val = c - 'a' + 10;             \
3950         } else if ('A' <= c && c <= 'F') {      \
3951                 val = c - 'A' + 10;             \
3952         } else {                                \
3953                 val = -1;                       \
3954         }                                       \
3955 }
3956
3957 gboolean get_hex_value(guchar *out, gchar c1, gchar c2)
3958 {
3959         gint hi, lo;
3960
3961         HEX_TO_INT(hi, c1);
3962         HEX_TO_INT(lo, c2);
3963
3964         if (hi == -1 || lo == -1)
3965                 return FALSE;
3966
3967         *out = (hi << 4) + lo;
3968         return TRUE;
3969 }
3970
3971 #define INT_TO_HEX(hex, val)            \
3972 {                                       \
3973         if ((val) < 10)                 \
3974                 hex = '0' + (val);      \
3975         else                            \
3976                 hex = 'A' + (val) - 10; \
3977 }
3978
3979 void get_hex_str(gchar *out, guchar ch)
3980 {
3981         gchar hex;
3982
3983         INT_TO_HEX(hex, ch >> 4);
3984         *out++ = hex;
3985         INT_TO_HEX(hex, ch & 0x0f);
3986         *out++ = hex;
3987 }
3988
3989 #undef REF_DEBUG
3990 #ifndef REF_DEBUG
3991 #define G_PRINT_REF 1 == 1 ? (void) 0 : (void)
3992 #else
3993 #define G_PRINT_REF g_print
3994 #endif
3995
3996 /*!
3997  *\brief        Register ref counted pointer. It is based on GBoxed, so should
3998  *              work with anything that uses the GType system. The semantics
3999  *              are similar to a C++ auto pointer, with the exception that
4000  *              C doesn't have automatic closure (calling destructors) when
4001  *              exiting a block scope.
4002  *              Use the \ref G_TYPE_AUTO_POINTER macro instead of calling this
4003  *              function directly.
4004  *
4005  *\return       GType A GType type.
4006  */
4007 GType g_auto_pointer_register(void)
4008 {
4009         static GType auto_pointer_type;
4010         if (!auto_pointer_type)
4011                 auto_pointer_type =
4012                         g_boxed_type_register_static
4013                                 ("G_TYPE_AUTO_POINTER",
4014                                  (GBoxedCopyFunc) g_auto_pointer_copy,
4015                                  (GBoxedFreeFunc) g_auto_pointer_free);
4016         return auto_pointer_type;
4017 }
4018
4019 /*!
4020  *\brief        Structure with g_new() allocated pointer guarded by the
4021  *              auto pointer
4022  */
4023 typedef struct AutoPointerRef {
4024         void          (*free) (gpointer);
4025         gpointer        pointer;
4026         glong           cnt;
4027 } AutoPointerRef;
4028
4029 /*!
4030  *\brief        The auto pointer opaque structure that references the
4031  *              pointer guard block.
4032  */
4033 typedef struct AutoPointer {
4034         AutoPointerRef *ref;
4035         gpointer        ptr; /*!< access to protected pointer */
4036 } AutoPointer;
4037
4038 /*!
4039  *\brief        Creates an auto pointer for a g_new()ed pointer. Example:
4040  *
4041  *\code
4042  *
4043  *              ... tell gtk_list_store it should use a G_TYPE_AUTO_POINTER
4044  *              ... when assigning, copying and freeing storage elements
4045  *
4046  *              gtk_list_store_new(N_S_COLUMNS,
4047  *                                 G_TYPE_AUTO_POINTER,
4048  *                                 -1);
4049  *
4050  *
4051  *              Template *precious_data = g_new0(Template, 1);
4052  *              g_pointer protect = g_auto_pointer_new(precious_data);
4053  *
4054  *              gtk_list_store_set(container, &iter,
4055  *                                 S_DATA, protect,
4056  *                                 -1);
4057  *
4058  *              ... the gtk_list_store has copied the pointer and
4059  *              ... incremented its reference count, we should free
4060  *              ... the auto pointer (in C++ a destructor would do
4061  *              ... this for us when leaving block scope)
4062  *
4063  *              g_auto_pointer_free(protect);
4064  *
4065  *              ... gtk_list_store_set() now manages the data. When
4066  *              ... *explicitly* requesting a pointer from the list
4067  *              ... store, don't forget you get a copy that should be
4068  *              ... freed with g_auto_pointer_free() eventually.
4069  *
4070  *\endcode
4071  *
4072  *\param        pointer Pointer to be guarded.
4073  *
4074  *\return       GAuto * Pointer that should be used in containers with
4075  *              GType support.
4076  */
4077 GAuto *g_auto_pointer_new(gpointer p)
4078 {
4079         AutoPointerRef *ref;
4080         AutoPointer    *ptr;
4081
4082         if (p == NULL)
4083                 return NULL;
4084
4085         ref = g_new0(AutoPointerRef, 1);
4086         ptr = g_new0(AutoPointer, 1);
4087
4088         ref->pointer = p;
4089         ref->free = g_free;
4090         ref->cnt = 1;
4091
4092         ptr->ref = ref;
4093         ptr->ptr = p;
4094
4095 #ifdef REF_DEBUG
4096         G_PRINT_REF ("XXXX ALLOC(%lx)\n", p);
4097 #endif
4098         return ptr;
4099 }
4100
4101 /*!
4102  *\brief        Allocate an autopointer using the passed \a free function to
4103  *              free the guarded pointer
4104  */
4105 GAuto *g_auto_pointer_new_with_free(gpointer p, GFreeFunc free_)
4106 {
4107         AutoPointer *aptr;
4108
4109         if (p == NULL)
4110                 return NULL;
4111
4112         aptr = g_auto_pointer_new(p);
4113         aptr->ref->free = free_;
4114         return aptr;
4115 }
4116
4117 gpointer g_auto_pointer_get_ptr(GAuto *auto_ptr)
4118 {
4119         if (auto_ptr == NULL)
4120                 return NULL;
4121         return ((AutoPointer *) auto_ptr)->ptr;
4122 }
4123
4124 /*!
4125  *\brief        Copies an auto pointer by. It's mostly not necessary
4126  *              to call this function directly, unless you copy/assign
4127  *              the guarded pointer.
4128  *
4129  *\param        auto_ptr Auto pointer returned by previous call to
4130  *              g_auto_pointer_new_XXX()
4131  *
4132  *\return       gpointer An auto pointer
4133  */
4134 GAuto *g_auto_pointer_copy(GAuto *auto_ptr)
4135 {
4136         AutoPointer     *ptr;
4137         AutoPointerRef  *ref;
4138         AutoPointer     *newp;
4139
4140         if (auto_ptr == NULL)
4141                 return NULL;
4142
4143         ptr = auto_ptr;
4144         ref = ptr->ref;
4145         newp = g_new0(AutoPointer, 1);
4146
4147         newp->ref = ref;
4148         newp->ptr = ref->pointer;
4149         ++(ref->cnt);
4150
4151 #ifdef REF_DEBUG
4152         G_PRINT_REF ("XXXX COPY(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4153 #endif
4154         return newp;
4155 }
4156
4157 /*!
4158  *\brief        Free an auto pointer
4159  */
4160 void g_auto_pointer_free(GAuto *auto_ptr)
4161 {
4162         AutoPointer     *ptr;
4163         AutoPointerRef  *ref;
4164
4165         if (auto_ptr == NULL)
4166                 return;
4167
4168         ptr = auto_ptr;
4169         ref = ptr->ref;
4170
4171         if (--(ref->cnt) == 0) {
4172 #ifdef REF_DEBUG
4173                 G_PRINT_REF ("XXXX FREE(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4174 #endif
4175                 ref->free(ref->pointer);
4176                 g_free(ref);
4177         }
4178 #ifdef REF_DEBUG
4179         else
4180                 G_PRINT_REF ("XXXX DEREF(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4181 #endif
4182         g_free(ptr);
4183 }
4184
4185 void replace_returns(gchar *str)
4186 {
4187         if (!str)
4188                 return;
4189
4190         while (strstr(str, "\n")) {
4191                 *strstr(str, "\n") = ' ';
4192         }
4193         while (strstr(str, "\r")) {
4194                 *strstr(str, "\r") = ' ';
4195         }
4196 }
4197
4198 /* get_uri_part() - retrieves a URI starting from scanpos.
4199                     Returns TRUE if succesful */
4200 gboolean get_uri_part(const gchar *start, const gchar *scanpos,
4201                              const gchar **bp, const gchar **ep, gboolean hdr)
4202 {
4203         const gchar *ep_;
4204         gint parenthese_cnt = 0;
4205
4206         cm_return_val_if_fail(start != NULL, FALSE);
4207         cm_return_val_if_fail(scanpos != NULL, FALSE);
4208         cm_return_val_if_fail(bp != NULL, FALSE);
4209         cm_return_val_if_fail(ep != NULL, FALSE);
4210
4211         *bp = scanpos;
4212
4213         /* find end point of URI */
4214         for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
4215                 if (!g_ascii_isgraph(*(const guchar *)ep_) ||
4216                     !IS_ASCII(*(const guchar *)ep_) ||
4217                     strchr("[]{}<>\"", *ep_)) {
4218                         break;
4219                 } else if (strchr("(", *ep_)) {
4220                         parenthese_cnt++;
4221                 } else if (strchr(")", *ep_)) {
4222                         if (parenthese_cnt > 0)
4223                                 parenthese_cnt--;
4224                         else
4225                                 break;
4226                 }
4227         }
4228
4229         /* no punctuation at end of string */
4230
4231         /* FIXME: this stripping of trailing punctuations may bite with other URIs.
4232          * should pass some URI type to this function and decide on that whether
4233          * to perform punctuation stripping */
4234
4235 #define IS_REAL_PUNCT(ch)       (g_ascii_ispunct(ch) && !strchr("/?=-)", ch))
4236
4237         for (; ep_ - 1 > scanpos + 1 &&
4238                IS_REAL_PUNCT(*(ep_ - 1));
4239              ep_--)
4240                 ;
4241
4242 #undef IS_REAL_PUNCT
4243
4244         *ep = ep_;
4245
4246         return TRUE;
4247 }
4248
4249 gchar *make_uri_string(const gchar *bp, const gchar *ep)
4250 {
4251         while (bp && *bp && g_ascii_isspace(*bp))
4252                 bp++;
4253         return g_strndup(bp, ep - bp);
4254 }
4255
4256 /* valid mail address characters */
4257 #define IS_RFC822_CHAR(ch) \
4258         (IS_ASCII(ch) && \
4259          (ch) > 32   && \
4260          (ch) != 127 && \
4261          !g_ascii_isspace(ch) && \
4262          !strchr("(),;<>\"", (ch)))
4263
4264 /* alphabet and number within 7bit ASCII */
4265 #define IS_ASCII_ALNUM(ch)      (IS_ASCII(ch) && g_ascii_isalnum(ch))
4266 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
4267
4268 static GHashTable *create_domain_tab(void)
4269 {
4270         static const gchar *toplvl_domains [] = {
4271             "museum", "aero",
4272             "arpa", "coop", "info", "name", "biz", "com", "edu", "gov",
4273             "int", "mil", "net", "org", "ac", "ad", "ae", "af", "ag",
4274             "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au",
4275             "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi",
4276             "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
4277             "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl",
4278             "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz", "de",
4279             "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er",
4280             "es", "et", "eu", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd",
4281             "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
4282             "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
4283             "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir",
4284             "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
4285             "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc",
4286             "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
4287             "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq",
4288             "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na",
4289             "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu",
4290             "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm",
4291             "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru",
4292             "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj",
4293             "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
4294             "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to",
4295             "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um",
4296             "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu",
4297             "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw"
4298         };
4299         gint n;
4300         GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
4301
4302         cm_return_val_if_fail(htab, NULL);
4303         for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++)
4304                 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
4305         return htab;
4306 }
4307
4308 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
4309 {
4310         const gint MAX_LVL_DOM_NAME_LEN = 6;
4311         gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
4312         const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
4313         register gchar *p;
4314
4315         if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
4316                 return FALSE;
4317
4318         for (p = buf; p < m &&  first < last; *p++ = *first++)
4319                 ;
4320         *p = 0;
4321
4322         return g_hash_table_lookup(tab, buf) != NULL;
4323 }
4324
4325 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
4326 gboolean get_email_part(const gchar *start, const gchar *scanpos,
4327                                const gchar **bp, const gchar **ep, gboolean hdr)
4328 {
4329         /* more complex than the uri part because we need to scan back and forward starting from
4330          * the scan position. */
4331         gboolean result = FALSE;
4332         const gchar *bp_ = NULL;
4333         const gchar *ep_ = NULL;
4334         static GHashTable *dom_tab;
4335         const gchar *last_dot = NULL;
4336         const gchar *prelast_dot = NULL;
4337         const gchar *last_tld_char = NULL;
4338
4339         /* the informative part of the email address (describing the name
4340          * of the email address owner) may contain quoted parts. the
4341          * closure stack stores the last encountered quotes. */
4342         gchar closure_stack[128];
4343         gchar *ptr = closure_stack;
4344
4345         cm_return_val_if_fail(start != NULL, FALSE);
4346         cm_return_val_if_fail(scanpos != NULL, FALSE);
4347         cm_return_val_if_fail(bp != NULL, FALSE);
4348         cm_return_val_if_fail(ep != NULL, FALSE);
4349
4350         if (hdr) {
4351                 const gchar *start_quote = NULL;
4352                 const gchar *end_quote = NULL;
4353 search_again:
4354                 /* go to the real start */
4355                 if (start[0] == ',')
4356                         start++;
4357                 if (start[0] == ';')
4358                         start++;
4359                 while (start[0] == '\n' || start[0] == '\r')
4360                         start++;
4361                 while (start[0] == ' ' || start[0] == '\t')
4362                         start++;
4363
4364                 *bp = start;
4365                 
4366                 /* check if there are quotes (to skip , in them) */
4367                 if (*start == '"') {
4368                         start_quote = start;
4369                         start++;
4370                         end_quote = strstr(start, "\"");
4371                 } else {
4372                         start_quote = NULL;
4373                         end_quote = NULL;
4374                 }
4375                 
4376                 /* skip anything between quotes */
4377                 if (start_quote && end_quote) {
4378                         start = end_quote;
4379                         
4380                 } 
4381
4382                 /* find end (either , or ; or end of line) */
4383                 if (strstr(start, ",") && strstr(start, ";"))
4384                         *ep = strstr(start,",") < strstr(start, ";")
4385                                 ? strstr(start, ",") : strstr(start, ";");
4386                 else if (strstr(start, ","))
4387                         *ep = strstr(start, ",");
4388                 else if (strstr(start, ";"))
4389                         *ep = strstr(start, ";");
4390                 else
4391                         *ep = start+strlen(start);
4392
4393                 /* go back to real start */
4394                 if (start_quote && end_quote) {
4395                         start = start_quote;
4396                 }
4397
4398                 /* check there's still an @ in that, or search
4399                  * further if possible */
4400                 if (strstr(start, "@") && strstr(start, "@") < *ep)
4401                         return TRUE;
4402                 else if (*ep < start+strlen(start)) {
4403                         start = *ep;
4404                         goto search_again;
4405                 } else if (start_quote && strstr(start, "\"") && strstr(start, "\"") < *ep) {
4406                         *bp = start_quote;
4407                         return TRUE;
4408                 } else
4409                         return FALSE;
4410         }
4411
4412         if (!dom_tab)
4413                 dom_tab = create_domain_tab();
4414         cm_return_val_if_fail(dom_tab, FALSE);
4415
4416         /* scan start of address */
4417         for (bp_ = scanpos - 1;
4418              bp_ >= start && IS_RFC822_CHAR(*(const guchar *)bp_); bp_--)
4419                 ;
4420
4421         /* TODO: should start with an alnum? */
4422         bp_++;
4423         for (; bp_ < scanpos && !IS_ASCII_ALNUM(*(const guchar *)bp_); bp_++)
4424                 ;
4425
4426         if (bp_ != scanpos) {
4427                 /* scan end of address */
4428                 for (ep_ = scanpos + 1;
4429                      *ep_ && IS_RFC822_CHAR(*(const guchar *)ep_); ep_++)
4430                         if (*ep_ == '.') {
4431                                 prelast_dot = last_dot;
4432                                 last_dot = ep_;
4433                                 if (*(last_dot + 1) == '.') {
4434                                         if (prelast_dot == NULL)
4435                                                 return FALSE;
4436                                         last_dot = prelast_dot;
4437                                         break;
4438                                 }
4439                         }
4440
4441                 /* TODO: really should terminate with an alnum? */
4442                 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*(const guchar *)ep_);
4443                      --ep_)
4444                         ;
4445                 ep_++;
4446
4447                 if (last_dot == NULL)
4448                         return FALSE;
4449                 if (last_dot >= ep_)
4450                         last_dot = prelast_dot;
4451                 if (last_dot == NULL || (scanpos + 1 >= last_dot))
4452                         return FALSE;
4453                 last_dot++;
4454
4455                 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
4456                         if (*last_tld_char == '?')
4457                                 break;
4458
4459                 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
4460                         result = TRUE;
4461
4462                 *ep = ep_;
4463                 *bp = bp_;
4464         }
4465
4466         if (!result) return FALSE;
4467
4468         if (*ep_ && bp_ != start && *(bp_ - 1) == '"' && *(ep_) == '"'
4469         && *(ep_ + 1) == ' ' && *(ep_ + 2) == '<'
4470         && IS_RFC822_CHAR(*(ep_ + 3))) {
4471                 /* this informative part with an @ in it is
4472                  * followed by the email address */
4473                 ep_ += 3;
4474
4475                 /* go to matching '>' (or next non-rfc822 char, like \n) */
4476                 for (; *ep_ != '>' && *ep != '\0' && IS_RFC822_CHAR(*ep_); ep_++)
4477                         ;
4478
4479                 /* include the bracket */
4480                 if (*ep_ == '>') ep_++;
4481
4482                 /* include the leading quote */
4483                 bp_--;
4484
4485                 *ep = ep_;
4486                 *bp = bp_;
4487                 return TRUE;
4488         }
4489
4490         /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
4491         if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
4492                 return FALSE;
4493
4494         /* see if this is <bracketed>; in this case we also scan for the informative part. */
4495         if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
4496                 return TRUE;
4497
4498 #define FULL_STACK()    ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
4499 #define IN_STACK()      (ptr > closure_stack)
4500 /* has underrun check */
4501 #define POP_STACK()     if(IN_STACK()) --ptr
4502 /* has overrun check */
4503 #define PUSH_STACK(c)   if(!FULL_STACK()) *ptr++ = (c); else return TRUE
4504 /* has underrun check */
4505 #define PEEK_STACK()    (IN_STACK() ? *(ptr - 1) : 0)
4506
4507         ep_++;
4508
4509         /* scan for the informative part. */
4510         for (bp_ -= 2; bp_ >= start; bp_--) {
4511                 /* if closure on the stack keep scanning */
4512                 if (PEEK_STACK() == *bp_) {
4513                         POP_STACK();
4514                         continue;
4515                 }
4516                 if (!IN_STACK() && (*bp_ == '\'' || *bp_ == '"')) {
4517                         PUSH_STACK(*bp_);
4518                         continue;
4519                 }
4520
4521                 /* if nothing in the closure stack, do the special conditions
4522                  * the following if..else expression simply checks whether
4523                  * a token is acceptable. if not acceptable, the clause
4524                  * should terminate the loop with a 'break' */
4525                 if (!PEEK_STACK()) {
4526                         if (*bp_ == '-'
4527                         && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
4528                         && (((bp_ + 1) < ep_)    && isalnum(*(bp_ + 1)))) {
4529                                 /* hyphens are allowed, but only in
4530                                    between alnums */
4531                         } else if (strchr(" \"'", *bp_)) {
4532                                 /* but anything not being a punctiation
4533                                    is ok */
4534                         } else {
4535                                 break; /* anything else is rejected */
4536                         }
4537                 }
4538         }
4539
4540         bp_++;
4541
4542         /* scan forward (should start with an alnum) */
4543         for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
4544                 ;
4545 #undef PEEK_STACK
4546 #undef PUSH_STACK
4547 #undef POP_STACK
4548 #undef IN_STACK
4549 #undef FULL_STACK
4550
4551
4552         *bp = bp_;
4553         *ep = ep_;
4554
4555         return result;
4556 }
4557
4558 #undef IS_QUOTE
4559 #undef IS_ASCII_ALNUM
4560 #undef IS_RFC822_CHAR
4561
4562 gchar *make_email_string(const gchar *bp, const gchar *ep)
4563 {
4564         /* returns a mailto: URI; mailto: is also used to detect the
4565          * uri type later on in the button_pressed signal handler */
4566         gchar *tmp;
4567         gchar *result;
4568
4569         tmp = g_strndup(bp, ep - bp);
4570         result = g_strconcat("mailto:", tmp, NULL);
4571         g_free(tmp);
4572
4573         return result;
4574 }
4575
4576 gchar *make_http_string(const gchar *bp, const gchar *ep)
4577 {
4578         /* returns an http: URI; */
4579         gchar *tmp;
4580         gchar *result;
4581
4582         while (bp && *bp && g_ascii_isspace(*bp))
4583                 bp++;
4584         tmp = g_strndup(bp, ep - bp);
4585         result = g_strconcat("http://", tmp, NULL);
4586         g_free(tmp);
4587
4588         return result;
4589 }
4590
4591 static gchar *mailcap_get_command_in_file(const gchar *path, const gchar *type, const gchar *file_to_open)
4592 {
4593         FILE *fp = g_fopen(path, "rb");
4594         gchar buf[BUFFSIZE];
4595         gchar *result = NULL;
4596         if (!fp)
4597                 return NULL;
4598         while (fgets(buf, sizeof (buf), fp) != NULL) {
4599                 gchar **parts = g_strsplit(buf, ";", 3);
4600                 gchar *trimmed = parts[0];
4601                 while (trimmed[0] == ' ' || trimmed[0] == '\t')
4602                         trimmed++;
4603                 while (trimmed[strlen(trimmed)-1] == ' ' || trimmed[strlen(trimmed)-1] == '\t')
4604                         trimmed[strlen(trimmed)-1] = '\0';
4605
4606                 if (!strcmp(trimmed, type)) {
4607                         gboolean needsterminal = FALSE;
4608                         if (parts[2] && strstr(parts[2], "needsterminal")) {
4609                                 needsterminal = TRUE;
4610                         }
4611                         if (parts[2] && strstr(parts[2], "test=")) {
4612                                 gchar *orig_testcmd = g_strdup(strstr(parts[2], "test=")+5);
4613                                 gchar *testcmd = orig_testcmd;
4614                                 if (strstr(testcmd,";"))
4615                                         *(strstr(testcmd,";")) = '\0';
4616                                 while (testcmd[0] == ' ' || testcmd[0] == '\t')
4617                                         testcmd++;
4618                                 while (testcmd[strlen(testcmd)-1] == '\n')
4619                                         testcmd[strlen(testcmd)-1] = '\0';
4620                                 while (testcmd[strlen(testcmd)-1] == '\r')
4621                                         testcmd[strlen(testcmd)-1] = '\0';
4622                                 while (testcmd[strlen(testcmd)-1] == ' ' || testcmd[strlen(testcmd)-1] == '\t')
4623                                         testcmd[strlen(testcmd)-1] = '\0';
4624                                         
4625                                 if (strstr(testcmd, "%s")) {
4626                                         gchar *tmp = g_strdup_printf(testcmd, file_to_open);
4627                                         gint res = system(tmp);
4628                                         g_free(tmp);
4629                                         g_free(orig_testcmd);
4630                                         
4631                                         if (res != 0) {
4632                                                 g_strfreev(parts);
4633                                                 continue;
4634                                         }
4635                                 } else {
4636                                         gint res = system(testcmd);
4637                                         g_free(orig_testcmd);
4638                                         
4639                                         if (res != 0) {
4640                                                 g_strfreev(parts);
4641                                                 continue;
4642                                         }
4643                                 }
4644                         }
4645                         
4646                         trimmed = parts[1];
4647                         while (trimmed[0] == ' ' || trimmed[0] == '\t')
4648                                 trimmed++;
4649                         while (trimmed[strlen(trimmed)-1] == '\n')
4650                                 trimmed[strlen(trimmed)-1] = '\0';
4651                         while (trimmed[strlen(trimmed)-1] == '\r')
4652                                 trimmed[strlen(trimmed)-1] = '\0';
4653                         while (trimmed[strlen(trimmed)-1] == ' ' || trimmed[strlen(trimmed)-1] == '\t')
4654                                 trimmed[strlen(trimmed)-1] = '\0';
4655                         result = g_strdup(trimmed);
4656                         g_strfreev(parts);
4657                         fclose(fp);
4658                         /* if there are no single quotes around %s, add them.
4659                          * '.*%s.*' is ok, as in display 'png:%s'
4660                          */
4661                         if (strstr(result, "%s") 
4662                         && !(strstr(result, "'") < strstr(result,"%s") &&
4663                              strstr(strstr(result,"%s"), "'"))) {
4664                                 gchar *start = g_strdup(result);
4665                                 gchar *end = g_strdup(strstr(result, "%s")+2);
4666                                 gchar *tmp;
4667                                 *strstr(start, "%s") = '\0';
4668                                 tmp = g_strconcat(start,"'%s'",end, NULL);
4669                                 g_free(start);
4670                                 g_free(end);
4671                                 g_free(result);
4672                                 result = tmp;
4673                         }
4674                         if (needsterminal) {
4675                                 gchar *tmp = g_strdup_printf("xterm -e %s", result);
4676                                 g_free(result);
4677                                 result = tmp;
4678                         }
4679                         return result;
4680                 }
4681                 g_strfreev(parts);
4682         }
4683         fclose(fp);
4684         return NULL;
4685 }
4686 gchar *mailcap_get_command_for_type(const gchar *type, const gchar *file_to_open)
4687 {
4688         gchar *result = NULL;
4689         gchar *path = NULL;
4690         if (type == NULL)
4691                 return NULL;
4692         path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap", NULL);
4693         result = mailcap_get_command_in_file(path, type, file_to_open);
4694         g_free(path);
4695         if (result)
4696                 return result;
4697         result = mailcap_get_command_in_file("/etc/mailcap", type, file_to_open);
4698         return result;
4699 }
4700
4701 void mailcap_update_default(const gchar *type, const gchar *command)
4702 {
4703         gchar *path = NULL, *outpath = NULL;
4704         path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap", NULL);
4705         outpath = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap.new", NULL);
4706         FILE *fp = g_fopen(path, "rb");
4707         FILE *outfp = g_fopen(outpath, "wb");
4708         gchar buf[BUFFSIZE];
4709         gboolean err = FALSE;
4710
4711         if (!outfp) {
4712                 g_free(path);
4713                 g_free(outpath);
4714                 fclose(fp);
4715                 return;
4716         }
4717         while (fp && fgets(buf, sizeof (buf), fp) != NULL) {
4718                 gchar **parts = g_strsplit(buf, ";", 3);
4719                 gchar *trimmed = parts[0];
4720                 while (trimmed[0] == ' ')
4721                         trimmed++;
4722                 while (trimmed[strlen(trimmed)-1] == ' ')
4723                         trimmed[strlen(trimmed)-1] = '\0';
4724
4725                 if (!strcmp(trimmed, type)) {
4726                         g_strfreev(parts);
4727                         continue;
4728                 }
4729                 else {
4730                         if(fputs(buf, outfp) == EOF) {
4731                                 err = TRUE;
4732                                 break;
4733                         }
4734                 }
4735                 g_strfreev(parts);
4736         }
4737         if (fprintf(outfp, "%s; %s\n", type, command) < 0)
4738                 err = TRUE;
4739
4740         if (fp)
4741                 fclose(fp);
4742
4743         if (fclose(outfp) == EOF)
4744                 err = TRUE;
4745                 
4746         if (!err)
4747                 g_rename(outpath, path);
4748
4749         g_free(path);
4750         g_free(outpath);
4751 }
4752
4753 gint copy_dir(const gchar *src, const gchar *dst)
4754 {
4755         GDir *dir;
4756         const gchar *name;
4757
4758         if ((dir = g_dir_open(src, 0, NULL)) == NULL) {
4759                 g_warning("failed to open directory: %s\n", src);
4760                 return -1;
4761         }
4762
4763         if (make_dir(dst) < 0)
4764                 return -1;
4765
4766         while ((name = g_dir_read_name(dir)) != NULL) {
4767                 gchar *old_file, *new_file;
4768                 old_file = g_strconcat(src, G_DIR_SEPARATOR_S, name, NULL);
4769                 new_file = g_strconcat(dst, G_DIR_SEPARATOR_S, name, NULL);
4770                 debug_print("copying: %s -> %s\n", old_file, new_file);
4771                 if (g_file_test(old_file, G_FILE_TEST_IS_REGULAR)) {
4772                         gint r = copy_file(old_file, new_file, TRUE);
4773                         if (r < 0) {
4774                                 g_dir_close(dir);
4775                                 return r;
4776                         }
4777                 }
4778 #ifndef G_OS_WIN32
4779                 /* Windows has no symlinks.  Or well, Vista seems to
4780                    have something like this but the semantics might be
4781                    different.  Thus we don't use it under Windows. */
4782                  else if (g_file_test(old_file, G_FILE_TEST_IS_SYMLINK)) {
4783                         GError *error;
4784                         gint r = 0;
4785                         gchar *target = g_file_read_link(old_file, &error);
4786                         if (target)
4787                                 r = symlink(target, new_file);
4788                         g_free(target);
4789                         if (r < 0) {
4790                                 g_dir_close(dir);
4791                                 return r;
4792                         }
4793                  }
4794 #endif /*G_OS_WIN32*/
4795                 else if (g_file_test(old_file, G_FILE_TEST_IS_DIR)) {
4796                         gint r = copy_dir(old_file, new_file);
4797                         if (r < 0) {
4798                                 g_dir_close(dir);
4799                                 return r;
4800                         }
4801                 }
4802         }
4803         g_dir_close(dir);
4804         return 0;
4805 }
4806
4807 /* crude test to see if a file is an email. */
4808 gboolean file_is_email (const gchar *filename)
4809 {
4810         FILE *fp = NULL;
4811         gchar buffer[2048];
4812         gint i = 0;
4813         gint score = 0;
4814         if (filename == NULL)
4815                 return FALSE;
4816         if ((fp = g_fopen(filename, "rb")) == NULL)
4817                 return FALSE;
4818         while (i < 60 && score < 3
4819                && fgets(buffer, sizeof (buffer), fp) > 0) {
4820                 if (!strncmp(buffer, "From:", strlen("From:")))
4821                         score++;
4822                 if (!strncmp(buffer, "To:", strlen("To:")))
4823                         score++;
4824                 if (!strncmp(buffer, "Subject:", strlen("Subject:")))
4825                         score++;
4826                 i++;
4827         }
4828         fclose(fp);
4829         return (score >= 3);
4830 }
4831
4832 gboolean sc_g_list_bigger(GList *list, gint max)
4833 {
4834         GList *cur = list;
4835         int i = 0;
4836         while (cur && i <= max+1) {
4837                 i++;
4838                 cur = cur->next;
4839         }
4840         return (i > max);
4841 }
4842
4843 gboolean sc_g_slist_bigger(GSList *list, gint max)
4844 {
4845         GSList *cur = list;
4846         int i = 0;
4847         while (cur && i <= max+1) {
4848                 i++;
4849                 cur = cur->next;
4850         }
4851         return (i > max);
4852 }
4853
4854 const gchar *daynames[] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL};
4855 const gchar *monthnames[] = {NULL, NULL, NULL, NULL, NULL, NULL, 
4856                              NULL, NULL, NULL, NULL, NULL, NULL};
4857 const gchar *s_daynames[] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL};
4858 const gchar *s_monthnames[] = {NULL, NULL, NULL, NULL, NULL, NULL, 
4859                              NULL, NULL, NULL, NULL, NULL, NULL};
4860
4861 gint daynames_len[] =     {0,0,0,0,0,0,0};
4862 gint monthnames_len[] =   {0,0,0,0,0,0,
4863                                  0,0,0,0,0,0};
4864 gint s_daynames_len[] =   {0,0,0,0,0,0,0};
4865 gint s_monthnames_len[] = {0,0,0,0,0,0,
4866                                  0,0,0,0,0,0};
4867 const gchar *s_am_up = NULL;
4868 const gchar *s_pm_up = NULL;
4869 const gchar *s_am_low = NULL;
4870 const gchar *s_pm_low = NULL;
4871
4872 gint s_am_up_len = 0;
4873 gint s_pm_up_len = 0;
4874 gint s_am_low_len = 0;
4875 gint s_pm_low_len = 0;
4876
4877 const gchar *def_loc_format = NULL;
4878 const gchar *date_loc_format = NULL;
4879 const gchar *time_loc_format = NULL;
4880 const gchar *time_am_pm = NULL;
4881
4882 static gboolean time_names_init_done = FALSE;
4883
4884 static void init_time_names(void)
4885 {
4886         int i = 0;
4887
4888         daynames[0] = Q_("Complete day name for use by strftime|Sunday");
4889         daynames[1] = Q_("Complete day name for use by strftime|Monday");
4890         daynames[2] = Q_("Complete day name for use by strftime|Tuesday");
4891         daynames[3] = Q_("Complete day name for use by strftime|Wednesday");
4892         daynames[4] = Q_("Complete day name for use by strftime|Thursday");
4893         daynames[5] = Q_("Complete day name for use by strftime|Friday");
4894         daynames[6] = Q_("Complete day name for use by strftime|Saturday");
4895
4896         monthnames[0] = Q_("Complete month name for use by strftime|January");
4897         monthnames[1] = Q_("Complete month name for use by strftime|February");
4898         monthnames[2] = Q_("Complete month name for use by strftime|March");
4899         monthnames[3] = Q_("Complete month name for use by strftime|April");
4900         monthnames[4] = Q_("Complete month name for use by strftime|May");
4901         monthnames[5] = Q_("Complete month name for use by strftime|June");
4902         monthnames[6] = Q_("Complete month name for use by strftime|July");
4903         monthnames[7] = Q_("Complete month name for use by strftime|August");
4904         monthnames[8] = Q_("Complete month name for use by strftime|September");
4905         monthnames[9] = Q_("Complete month name for use by strftime|October");
4906         monthnames[10] = Q_("Complete month name for use by strftime|November");
4907         monthnames[11] = Q_("Complete month name for use by strftime|December");
4908
4909         s_daynames[0] = Q_("Abbr. day name for use by strftime|Sun");
4910         s_daynames[1] = Q_("Abbr. day name for use by strftime|Mon");
4911         s_daynames[2] = Q_("Abbr. day name for use by strftime|Tue");
4912         s_daynames[3] = Q_("Abbr. day name for use by strftime|Wed");
4913         s_daynames[4] = Q_("Abbr. day name for use by strftime|Thu");
4914         s_daynames[5] = Q_("Abbr. day name for use by strftime|Fri");
4915         s_daynames[6] = Q_("Abbr. day name for use by strftime|Sat");
4916         
4917         s_monthnames[0] = Q_("Abbr. month name for use by strftime|Jan");
4918         s_monthnames[1] = Q_("Abbr. month name for use by strftime|Feb");
4919         s_monthnames[2] = Q_("Abbr. month name for use by strftime|Mar");
4920         s_monthnames[3] = Q_("Abbr. month name for use by strftime|Apr");
4921         s_monthnames[4] = Q_("Abbr. month name for use by strftime|May");
4922         s_monthnames[5] = Q_("Abbr. month name for use by strftime|Jun");
4923         s_monthnames[6] = Q_("Abbr. month name for use by strftime|Jul");
4924         s_monthnames[7] = Q_("Abbr. month name for use by strftime|Aug");
4925         s_monthnames[8] = Q_("Abbr. month name for use by strftime|Sep");
4926         s_monthnames[9] = Q_("Abbr. month name for use by strftime|Oct");
4927         s_monthnames[10] = Q_("Abbr. month name for use by strftime|Nov");
4928         s_monthnames[11] = Q_("Abbr. month name for use by strftime|Dec");
4929
4930         for (i = 0; i < 7; i++) {
4931                 daynames_len[i] = strlen(daynames[i]);
4932                 s_daynames_len[i] = strlen(s_daynames[i]);
4933         }
4934         for (i = 0; i < 12; i++) {
4935                 monthnames_len[i] = strlen(monthnames[i]);
4936                 s_monthnames_len[i] = strlen(s_monthnames[i]);
4937         }
4938
4939         s_am_up = Q_("For use by strftime (morning)|AM");
4940         s_pm_up = Q_("For use by strftime (afternoon)|PM");
4941         s_am_low = Q_("For use by strftime (morning, lowercase)|am");
4942         s_pm_low = Q_("For use by strftime (afternoon, lowercase)|pm");
4943         
4944         s_am_up_len = strlen(s_am_up);
4945         s_pm_up_len = strlen(s_pm_up);
4946         s_am_low_len = strlen(s_am_low);
4947         s_pm_low_len = strlen(s_pm_low);
4948         
4949         def_loc_format = Q_("For use by strftime (default date+time format)|%a %b %e %H:%M:%S %Y");
4950         date_loc_format = Q_("For use by strftime (default date format)|%m/%d/%y");
4951         time_loc_format = Q_("For use by strftime (default time format)|%H:%M:%S");
4952
4953         time_am_pm = Q_("For use by strftime (default 12-hour time format)|%I:%M:%S %p");
4954
4955         time_names_init_done = TRUE;
4956 }
4957
4958 #define CHECK_SIZE() {                  \
4959         total_done += len;              \
4960         if (total_done >= buflen) {     \
4961                 buf[buflen-1] = '\0';   \
4962                 return 0;               \
4963         }                               \
4964 }
4965
4966 size_t fast_strftime(gchar *buf, gint buflen, const gchar *format, struct tm *lt)
4967 {
4968         gchar *curpos = buf;
4969         gint total_done = 0;
4970         gchar subbuf[64], subfmt[64];
4971         static time_t last_tzset = (time_t)0;
4972         
4973         if (!time_names_init_done)
4974                 init_time_names();
4975         
4976         if (format == NULL || lt == NULL)
4977                 return 0;
4978                 
4979         if (last_tzset != time(NULL)) {
4980                 tzset();
4981                 last_tzset = time(NULL);
4982         }
4983         while(*format) {
4984                 if (*format == '%') {
4985                         gint len = 0, tmp = 0;
4986                         format++;
4987                         switch(*format) {
4988                         case '%':
4989                                 len = 1; CHECK_SIZE();
4990                                 *curpos = '%';
4991                                 break;
4992                         case 'a':
4993                                 len = s_daynames_len[lt->tm_wday]; CHECK_SIZE();
4994                                 strncpy2(curpos, s_daynames[lt->tm_wday], buflen - total_done);
4995                                 break;
4996                         case 'A':
4997                                 len = daynames_len[lt->tm_wday]; CHECK_SIZE();
4998                                 strncpy2(curpos, daynames[lt->tm_wday], buflen - total_done);
4999                                 break;
5000                         case 'b':
5001                         case 'h':
5002                                 len = s_monthnames_len[lt->tm_mon]; CHECK_SIZE();
5003                                 strncpy2(curpos, s_monthnames[lt->tm_mon], buflen - total_done);
5004                                 break;
5005                         case 'B':
5006                                 len = monthnames_len[lt->tm_mon]; CHECK_SIZE();
5007                                 strncpy2(curpos, monthnames[lt->tm_mon], buflen - total_done);
5008                                 break;
5009                         case 'c':
5010                                 fast_strftime(subbuf, 64, def_loc_format, lt);
5011                                 len = strlen(subbuf); CHECK_SIZE();
5012                                 strncpy2(curpos, subbuf, buflen - total_done);
5013                                 break;
5014                         case 'C':
5015                                 total_done += 2; CHECK_SIZE();
5016                                 tmp = (lt->tm_year + 1900)/100;
5017                                 *curpos++ = '0'+(tmp / 10);
5018                                 *curpos++ = '0'+(tmp % 10);
5019                                 break;
5020                         case 'd':
5021                                 total_done += 2; CHECK_SIZE();
5022                                 *curpos++ = '0'+(lt->tm_mday / 10);
5023                                 *curpos++ = '0'+(lt->tm_mday % 10);
5024                                 break;
5025                         case 'D':
5026                                 total_done += 8; CHECK_SIZE();
5027                                 *curpos++ = '0'+((lt->tm_mon+1) / 10);
5028                                 *curpos++ = '0'+((lt->tm_mon+1) % 10);
5029                                 *curpos++ = '/';
5030                                 *curpos++ = '0'+(lt->tm_mday / 10);
5031                                 *curpos++ = '0'+(lt->tm_mday % 10);
5032                                 *curpos++ = '/';
5033                                 tmp = lt->tm_year%100;
5034                                 *curpos++ = '0'+(tmp / 10);
5035                                 *curpos++ = '0'+(tmp % 10);
5036                                 break;
5037                         case 'e':
5038                                 len = 2; CHECK_SIZE();
5039                                 snprintf(curpos, buflen - total_done, "%2d", lt->tm_mday);
5040                                 break;
5041                         case 'F':
5042                                 len = 10; CHECK_SIZE();
5043                                 snprintf(curpos, buflen - total_done, "%4d-%02d-%02d", 
5044                                         lt->tm_year + 1900, lt->tm_mon +1, lt->tm_mday);
5045                                 break;
5046                         case 'H':
5047                                 total_done += 2; CHECK_SIZE();
5048                                 *curpos++ = '0'+(lt->tm_hour / 10);
5049                                 *curpos++ = '0'+(lt->tm_hour % 10);
5050                                 break;
5051                         case 'I':
5052                                 total_done += 2; CHECK_SIZE();
5053                                 tmp = lt->tm_hour;
5054                                 if (tmp > 12)
5055                                         tmp -= 12;
5056                                 else if (tmp == 0)
5057                                         tmp = 12;
5058                                 *curpos++ = '0'+(tmp / 10);
5059                                 *curpos++ = '0'+(tmp % 10);
5060                                 break;
5061                         case 'j':
5062                                 len = 3; CHECK_SIZE();
5063                                 snprintf(curpos, buflen - total_done, "%03d", lt->tm_yday+1);
5064                                 break;
5065                         case 'k':
5066                                 len = 2; CHECK_SIZE();
5067                                 snprintf(curpos, buflen - total_done, "%2d", lt->tm_hour);
5068                                 break;
5069                         case 'l':
5070                                 len = 2; CHECK_SIZE();
5071                                 tmp = lt->tm_hour;
5072                                 if (tmp > 12)
5073                                         tmp -= 12;
5074                                 else if (tmp == 0)
5075                                         tmp = 12;
5076                                 snprintf(curpos, buflen - total_done, "%2d", tmp);
5077                                 break;
5078                         case 'm':
5079                                 total_done += 2; CHECK_SIZE();
5080                                 tmp = lt->tm_mon + 1;
5081                                 *curpos++ = '0'+(tmp / 10);
5082                                 *curpos++ = '0'+(tmp % 10);
5083                                 break;
5084                         case 'M':
5085                                 total_done += 2; CHECK_SIZE();
5086                                 *curpos++ = '0'+(lt->tm_min / 10);
5087                                 *curpos++ = '0'+(lt->tm_min % 10);
5088                                 break;
5089                         case 'n':
5090                                 len = 1; CHECK_SIZE();
5091                                 *curpos = '\n';
5092                                 break;
5093                         case 'p':
5094                                 if (lt->tm_hour >= 12) {
5095                                         len = s_pm_up_len; CHECK_SIZE();
5096                                         snprintf(curpos, buflen-total_done, "%s", s_pm_up);
5097                                 } else {
5098                                         len = s_am_up_len; CHECK_SIZE();
5099                                         snprintf(curpos, buflen-total_done, "%s", s_am_up);
5100                                 }
5101                                 break;
5102                         case 'P':
5103                                 if (lt->tm_hour >= 12) {
5104                                         len = s_pm_low_len; CHECK_SIZE();
5105                                         snprintf(curpos, buflen-total_done, "%s", s_pm_low);
5106                                 } else {
5107                                         len = s_am_low_len; CHECK_SIZE();
5108                                         snprintf(curpos, buflen-total_done, "%s", s_am_low);
5109                                 }
5110                                 break;
5111                         case 'r':
5112                                 fast_strftime(subbuf, 64, time_am_pm, lt);
5113                                 len = strlen(subbuf); CHECK_SIZE();
5114                                 strncpy2(curpos, subbuf, buflen - total_done);
5115                                 break;
5116                         case 'R':
5117                                 total_done += 5; CHECK_SIZE();
5118                                 *curpos++ = '0'+(lt->tm_hour / 10);
5119                                 *curpos++ = '0'+(lt->tm_hour % 10);
5120                                 *curpos++ = ':';
5121                                 *curpos++ = '0'+(lt->tm_min / 10);
5122                                 *curpos++ = '0'+(lt->tm_min % 10);
5123                                 break;
5124                         case 's':
5125                                 snprintf(subbuf, 64, "%ld", mktime(lt));
5126                                 len = strlen(subbuf); CHECK_SIZE();
5127                                 strncpy2(curpos, subbuf, buflen - total_done);
5128                                 break;
5129                         case 'S':
5130                                 total_done += 2; CHECK_SIZE();
5131                                 *curpos++ = '0'+(lt->tm_sec / 10);
5132                                 *curpos++ = '0'+(lt->tm_sec % 10);
5133                                 break;
5134                         case 't':
5135                                 len = 1; CHECK_SIZE();
5136                                 *curpos = '\t';
5137                                 break;
5138                         case 'T':
5139                                 total_done += 8; CHECK_SIZE();
5140                                 *curpos++ = '0'+(lt->tm_hour / 10);
5141                                 *curpos++ = '0'+(lt->tm_hour % 10);
5142                                 *curpos++ = ':';
5143                                 *curpos++ = '0'+(lt->tm_min / 10);
5144                                 *curpos++ = '0'+(lt->tm_min % 10);
5145                                 *curpos++ = ':';
5146                                 *curpos++ = '0'+(lt->tm_sec / 10);
5147                                 *curpos++ = '0'+(lt->tm_sec % 10);
5148                                 break;
5149                         case 'u':
5150                                 len = 1; CHECK_SIZE();
5151                                 snprintf(curpos, buflen - total_done, "%d", lt->tm_wday == 0 ? 7: lt->tm_wday);
5152                                 break;
5153                         case 'w':
5154                                 len = 1; CHECK_SIZE();
5155                                 snprintf(curpos, buflen - total_done, "%d", lt->tm_wday);
5156                                 break;
5157                         case 'x':
5158                                 fast_strftime(subbuf, 64, date_loc_format, lt);
5159                                 len = strlen(subbuf); CHECK_SIZE();
5160                                 strncpy2(curpos, subbuf, buflen - total_done);
5161                                 break;
5162                         case 'X':
5163                                 fast_strftime(subbuf, 64, time_loc_format, lt);
5164                                 len = strlen(subbuf); CHECK_SIZE();
5165                                 strncpy2(curpos, subbuf, buflen - total_done);
5166                                 break;
5167                         case 'y':
5168                                 total_done += 2; CHECK_SIZE();
5169                                 tmp = lt->tm_year%100;
5170                                 *curpos++ = '0'+(tmp / 10);
5171                                 *curpos++ = '0'+(tmp % 10);
5172                                 break;
5173                         case 'Y':
5174                                 len = 4; CHECK_SIZE();
5175                                 snprintf(curpos, buflen - total_done, "%4d", lt->tm_year + 1900);
5176                                 break;
5177                         case 'G':
5178                         case 'g':
5179                         case 'U':
5180                         case 'V':
5181                         case 'W':
5182                         case 'z':
5183                         case 'Z':
5184                         case '+':
5185                                 /* let these complicated ones be done with the libc */
5186                                 snprintf(subfmt, 64, "%%%c", *format);
5187                                 strftime(subbuf, 64, subfmt, lt);
5188                                 len = strlen(subbuf); CHECK_SIZE();
5189                                 strncpy2(curpos, subbuf, buflen - total_done);
5190                                 break;
5191                         case 'E':
5192                         case 'O':
5193                                 /* let these complicated modifiers be done with the libc */
5194                                 snprintf(subfmt, 64, "%%%c%c", *format, *(format+1));
5195                                 strftime(subbuf, 64, subfmt, lt);
5196                                 len = strlen(subbuf); CHECK_SIZE();
5197                                 strncpy2(curpos, subbuf, buflen - total_done);
5198                                 format++;
5199                                 break;
5200                         default:
5201                                 if (format && *format)
5202                                         g_warning("format error (%c)", *format);
5203                                 *curpos = '\0';
5204                                 return total_done;
5205                         }
5206                         curpos += len;
5207                         format++;
5208                 } else {
5209                         int len = 1; CHECK_SIZE();
5210                         *curpos++ = *format++; 
5211                 }
5212         }
5213         *curpos++ = '\0';
5214         return total_done;
5215 }
5216
5217 gboolean prefs_common_get_use_shred(void);
5218
5219
5220 #ifdef G_OS_WIN32
5221 #define WEXITSTATUS(x) (x)
5222 #endif
5223
5224 int claws_unlink(const gchar *filename) 
5225 {
5226         struct stat s;
5227         static int found_shred = -1;
5228         static const gchar *args[4];
5229
5230         if (filename == NULL)
5231                 return 0;
5232
5233         if (prefs_common_get_use_shred()) {
5234                 if (found_shred == -1) {
5235                         /* init */
5236                         args[0] = g_find_program_in_path("shred");
5237                         debug_print("found shred: %s\n", args[0]);
5238                         found_shred = (args[0] != NULL) ? 1:0;
5239                         args[1] = "-f";
5240                         args[3] = NULL;
5241                 }
5242                 if (found_shred == 1) {
5243                         if (g_stat(filename, &s) == 0 && S_ISREG(s.st_mode)) {
5244                                 if (s.st_nlink == 1) {
5245                                         gint status=0;
5246                                         args[2] = filename;
5247                                         g_spawn_sync(NULL, (gchar **)args, NULL, 0,
5248                                          NULL, NULL, NULL, NULL, &status, NULL);
5249                                         debug_print("%s %s exited with status %d\n",
5250                                                 args[0], filename, WEXITSTATUS(status));
5251                                         if (truncate(filename, 0) < 0)
5252                                                 g_warning("couln't truncate");
5253                                 }
5254                         }
5255                 }
5256         }
5257         return g_unlink(filename);
5258 }