* src/procmsg.c
[claws.git] / src / common / utils.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2003 Hiroyuki Yamamoto
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 2 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, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19
20 #ifdef HAVE_CONFIG_H
21 #  include "config.h"
22 #endif
23
24 #include "defs.h"
25
26 #include <glib.h>
27 #include <stdio.h>
28 #include <string.h>
29 #include <ctype.h>
30 #include <errno.h>
31
32 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
33 #  include <wchar.h>
34 #  include <wctype.h>
35 #endif
36 #include <stdlib.h>
37 #include <sys/stat.h>
38 #include <unistd.h>
39 #include <stdarg.h>
40 #include <sys/types.h>
41 #include <sys/wait.h>
42 #include <dirent.h>
43 #include <time.h>
44 #include <regex.h>
45
46 #include "intl.h"
47 #include "utils.h"
48 #include "socket.h"
49
50 #define BUFFSIZE        8192
51
52 static gboolean debug_mode = FALSE;
53
54 static void hash_free_strings_func(gpointer key, gpointer value, gpointer data);
55
56 void list_free_strings(GList *list)
57 {
58         list = g_list_first(list);
59
60         while (list != NULL) {
61                 g_free(list->data);
62                 list = list->next;
63         }
64 }
65
66 void slist_free_strings(GSList *list)
67 {
68         while (list != NULL) {
69                 g_free(list->data);
70                 list = list->next;
71         }
72 }
73
74 GSList *slist_concat_unique (GSList *first, GSList *second)
75 {
76         GSList *tmp, *ret;
77         if (first == NULL) {
78                 if (second == NULL)
79                         return NULL;
80                 else 
81                         return second;
82         } else if (second == NULL)
83                 return first;
84         ret = first;
85         for (tmp = second; tmp != NULL; tmp = g_slist_next(tmp)) {
86                 if (g_slist_find(ret, tmp->data) == NULL)
87                         ret = g_slist_prepend(ret, tmp->data);
88         }
89         return ret;
90 }
91  
92 static void hash_free_strings_func(gpointer key, gpointer value, gpointer data)
93 {
94         g_free(key);
95 }
96
97 void hash_free_strings(GHashTable *table)
98 {
99         g_hash_table_foreach(table, hash_free_strings_func, NULL);
100 }
101
102 static void hash_free_value_mem_func(gpointer key, gpointer value,
103                                      gpointer data)
104 {
105         g_free(value);
106 }
107
108 void hash_free_value_mem(GHashTable *table)
109 {
110         g_hash_table_foreach(table, hash_free_value_mem_func, NULL);
111 }
112
113 gint str_case_equal(gconstpointer v, gconstpointer v2)
114 {
115         return strcasecmp((const gchar *)v, (const gchar *)v2) == 0;
116 }
117
118 guint str_case_hash(gconstpointer key)
119 {
120         const gchar *p = key;
121         guint h = *p;
122
123         if (h) {
124                 h = tolower(h);
125                 for (p += 1; *p != '\0'; p++)
126                         h = (h << 5) - h + tolower(*p);
127         }
128
129         return h;
130 }
131
132 void ptr_array_free_strings(GPtrArray *array)
133 {
134         gint i;
135         gchar *str;
136
137         g_return_if_fail(array != NULL);
138
139         for (i = 0; i < array->len; i++) {
140                 str = g_ptr_array_index(array, i);
141                 g_free(str);
142         }
143 }
144
145 gint to_number(const gchar *nstr)
146 {
147         register const gchar *p;
148
149         if (*nstr == '\0') return -1;
150
151         for (p = nstr; *p != '\0'; p++)
152                 if (!isdigit(*p)) return -1;
153
154         return atoi(nstr);
155 }
156
157 /* convert integer into string,
158    nstr must be not lower than 11 characters length */
159 gchar *itos_buf(gchar *nstr, gint n)
160 {
161         g_snprintf(nstr, 11, "%d", n);
162         return nstr;
163 }
164
165 /* convert integer into string */
166 gchar *itos(gint n)
167 {
168         static gchar nstr[11];
169
170         return itos_buf(nstr, n);
171 }
172
173 gchar *to_human_readable(off_t size)
174 {
175         static gchar str[10];
176
177         if (size < 1024)
178                 g_snprintf(str, sizeof(str), _("%dB"), (gint)size);
179         else if (size >> 10 < 1024)
180                 g_snprintf(str, sizeof(str), _("%.1fKB"), (gfloat)size / (1 << 10));
181         else if (size >> 20 < 1024)
182                 g_snprintf(str, sizeof(str), _("%.2fMB"), (gfloat)size / (1 << 20));
183         else
184                 g_snprintf(str, sizeof(str), _("%.2fGB"), (gfloat)size / (1 << 30));
185
186         return str;
187 }
188
189 /* strcmp with NULL-checking */
190 gint strcmp2(const gchar *s1, const gchar *s2)
191 {
192         if (s1 == NULL || s2 == NULL)
193                 return -1;
194         else
195                 return strcmp(s1, s2);
196 }
197 /* strstr with NULL-checking */
198 gchar *strstr2(const gchar *s1, const gchar *s2)
199 {
200         if (s1 == NULL || s2 == NULL)
201                 return NULL;
202         else
203                 return strstr(s1, s2);
204 }
205 /* compare paths */
206 gint path_cmp(const gchar *s1, const gchar *s2)
207 {
208         gint len1, len2;
209
210         if (s1 == NULL || s2 == NULL) return -1;
211         if (*s1 == '\0' || *s2 == '\0') return -1;
212
213         len1 = strlen(s1);
214         len2 = strlen(s2);
215
216         if (s1[len1 - 1] == G_DIR_SEPARATOR) len1--;
217         if (s2[len2 - 1] == G_DIR_SEPARATOR) len2--;
218
219         return strncmp(s1, s2, MAX(len1, len2));
220 }
221
222 /* remove trailing return code */
223 gchar *strretchomp(gchar *str)
224 {
225         register gchar *s;
226
227         if (!*str) return str;
228
229         for (s = str + strlen(str) - 1;
230              s >= str && (*s == '\n' || *s == '\r');
231              s--)
232                 *s = '\0';
233
234         return str;
235 }
236
237 /* remove trailing character */
238 gchar *strtailchomp(gchar *str, gchar tail_char)
239 {
240         register gchar *s;
241
242         if (!*str) return str;
243         if (tail_char == '\0') return str;
244
245         for (s = str + strlen(str) - 1; s >= str && *s == tail_char; s--)
246                 *s = '\0';
247
248         return str;
249 }
250
251 /* remove CR (carriage return) */
252 gchar *strcrchomp(gchar *str)
253 {
254         register gchar *s;
255
256         if (!*str) return str;
257
258         s = str + strlen(str) - 1;
259         if (*s == '\n' && s > str && *(s - 1) == '\r') {
260                 *(s - 1) = '\n';
261                 *s = '\0';
262         }
263
264         return str;
265 }
266
267 /* Similar to `strstr' but this function ignores the case of both strings.  */
268 gchar *strcasestr(const gchar *haystack, const gchar *needle)
269 {
270         register size_t haystack_len, needle_len;
271
272         haystack_len = strlen(haystack);
273         needle_len   = strlen(needle);
274
275         if (haystack_len < needle_len || needle_len == 0)
276                 return NULL;
277
278         while (haystack_len >= needle_len) {
279                 if (!strncasecmp(haystack, needle, needle_len))
280                         return (gchar *)haystack;
281                 else {
282                         haystack++;
283                         haystack_len--;
284                 }
285         }
286
287         return NULL;
288 }
289
290 /* Copy no more than N characters of SRC to DEST, with NULL terminating.  */
291 gchar *strncpy2(gchar *dest, const gchar *src, size_t n)
292 {
293         register gchar c;
294         gchar *s = dest;
295
296         do {
297                 if (--n == 0) {
298                         *dest = '\0';
299                         return s;
300                 }
301                 c = *src++;
302                 *dest++ = c;
303         } while (c != '\0');
304
305         /* don't do zero fill */
306         return s;
307 }
308
309 #if !HAVE_ISWALNUM
310 int iswalnum(wint_t wc)
311 {
312         return isalnum((int)wc);
313 }
314 #endif
315
316 #if !HAVE_ISWSPACE
317 int iswspace(wint_t wc)
318 {
319         return isspace((int)wc);
320 }
321 #endif
322
323 #if !HAVE_TOWLOWER
324 wint_t towlower(wint_t wc)
325 {
326         if (wc >= L'A' && wc <= L'Z')
327                 return wc + L'a' - L'A';
328
329         return wc;
330 }
331 #endif
332
333 #if !HAVE_WCSLEN
334 size_t wcslen(const wchar_t *s)
335 {
336         size_t len = 0;
337
338         while (*s != L'\0')
339                 ++len, ++s;
340
341         return len;
342 }
343 #endif
344
345 #if !HAVE_WCSCPY
346 /* Copy SRC to DEST.  */
347 wchar_t *wcscpy(wchar_t *dest, const wchar_t *src)
348 {
349         wint_t c;
350         wchar_t *s = dest;
351
352         do {
353                 c = *src++;
354                 *dest++ = c;
355         } while (c != L'\0');
356
357         return s;
358 }
359 #endif
360
361 #if !HAVE_WCSNCPY
362 /* Copy no more than N wide-characters of SRC to DEST.  */
363 wchar_t *wcsncpy (wchar_t *dest, const wchar_t *src, size_t n)
364 {
365         wint_t c;
366         wchar_t *s = dest;
367
368         do {
369                 c = *src++;
370                 *dest++ = c;
371                 if (--n == 0)
372                         return s;
373         } while (c != L'\0');
374
375         /* zero fill */
376         do
377                 *dest++ = L'\0';
378         while (--n > 0);
379
380         return s;
381 }
382 #endif
383
384 /* Duplicate S, returning an identical malloc'd string. */
385 wchar_t *wcsdup(const wchar_t *s)
386 {
387         wchar_t *new_str;
388
389         if (s) {
390                 new_str = g_new(wchar_t, wcslen(s) + 1);
391                 wcscpy(new_str, s);
392         } else
393                 new_str = NULL;
394
395         return new_str;
396 }
397
398 /* Duplicate no more than N wide-characters of S,
399    returning an identical malloc'd string. */
400 wchar_t *wcsndup(const wchar_t *s, size_t n)
401 {
402         wchar_t *new_str;
403
404         if (s) {
405                 new_str = g_new(wchar_t, n + 1);
406                 wcsncpy(new_str, s, n);
407                 new_str[n] = (wchar_t)0;
408         } else
409                 new_str = NULL;
410
411         return new_str;
412 }
413
414 wchar_t *strdup_mbstowcs(const gchar *s)
415 {
416         wchar_t *new_str;
417
418         if (s) {
419                 new_str = g_new(wchar_t, strlen(s) + 1);
420                 if (mbstowcs(new_str, s, strlen(s) + 1) < 0) {
421                         g_free(new_str);
422                         new_str = NULL;
423                 } else
424                         new_str = g_realloc(new_str,
425                                             sizeof(wchar_t) * (wcslen(new_str) + 1));
426         } else
427                 new_str = NULL;
428
429         return new_str;
430 }
431
432 gchar *strdup_wcstombs(const wchar_t *s)
433 {
434         gchar *new_str;
435         size_t len;
436
437         if (s) {
438                 len = wcslen(s) * MB_CUR_MAX + 1;
439                 new_str = g_new(gchar, len);
440                 if (wcstombs(new_str, s, len) < 0) {
441                         g_free(new_str);
442                         new_str = NULL;
443                 } else
444                         new_str = g_realloc(new_str, strlen(new_str) + 1);
445         } else
446                 new_str = NULL;
447
448         return new_str;
449 }
450
451 /* Compare S1 and S2, ignoring case.  */
452 gint wcsncasecmp(const wchar_t *s1, const wchar_t *s2, size_t n)
453 {
454         wint_t c1;
455         wint_t c2;
456
457         while (n--) {
458                 c1 = towlower(*s1++);
459                 c2 = towlower(*s2++);
460                 if (c1 != c2)
461                         return c1 - c2;
462                 else if (c1 == 0 && c2 == 0)
463                         break;
464         }
465
466         return 0;
467 }
468
469 /* Find the first occurrence of NEEDLE in HAYSTACK, ignoring case.  */
470 wchar_t *wcscasestr(const wchar_t *haystack, const wchar_t *needle)
471 {
472         register size_t haystack_len, needle_len;
473
474         haystack_len = wcslen(haystack);
475         needle_len   = wcslen(needle);
476
477         if (haystack_len < needle_len || needle_len == 0)
478                 return NULL;
479
480         while (haystack_len >= needle_len) {
481                 if (!wcsncasecmp(haystack, needle, needle_len))
482                         return (wchar_t *)haystack;
483                 else {
484                         haystack++;
485                         haystack_len--;
486                 }
487         }
488
489         return NULL;
490 }
491
492 gint get_mbs_len(const gchar *s)
493 {
494         const gchar *p = s;
495         gint mb_len;
496         gint len = 0;
497
498         if (!p)
499                 return -1;
500
501         while (*p != '\0') {
502                 mb_len = mblen(p, MB_LEN_MAX);
503                 if (mb_len == 0)
504                         break;
505                 else if (mb_len < 0)
506                         return -1;
507                 else
508                         len++;
509
510                 p += mb_len;
511         }
512
513         return len;
514 }
515
516 /* Examine if next block is non-ASCII string */
517 gboolean is_next_nonascii(const guchar *s)
518 {
519         const guchar *p;
520
521         /* skip head space */
522         for (p = s; *p != '\0' && isspace(*p); p++)
523                 ;
524         for (; *p != '\0' && !isspace(*p); p++) {
525                 if (*p > 127 || *p < 32)
526                         return TRUE;
527         }
528
529         return FALSE;
530 }
531
532 gint get_next_word_len(const gchar *s)
533 {
534         gint len = 0;
535
536         for (; *s != '\0' && !isspace(*s); s++, len++)
537                 ;
538
539         return len;
540 }
541
542 /* compare subjects */
543 gint subject_compare(const gchar *s1, const gchar *s2)
544 {
545         gchar *str1, *str2;
546
547         if (!s1 || !s2) return -1;
548         if (!*s1 || !*s2) return -1;
549
550         Xstrdup_a(str1, s1, return -1);
551         Xstrdup_a(str2, s2, return -1);
552
553         trim_subject_for_compare(str1);
554         trim_subject_for_compare(str2);
555
556         if (!*str1 || !*str2) return -1;
557
558         return strcmp(str1, str2);
559 }
560
561 gint subject_compare_for_sort(const gchar *s1, const gchar *s2)
562 {
563         gchar *str1, *str2;
564
565         if (!s1 || !s2) return -1;
566
567         Xstrdup_a(str1, s1, return -1);
568         Xstrdup_a(str2, s2, return -1);
569
570         trim_subject_for_sort(str1);
571         trim_subject_for_sort(str2);
572
573         return strcasecmp(str1, str2);
574 }
575
576 void trim_subject_for_compare(gchar *str)
577 {
578         gchar *srcp;
579         int skip;
580
581         eliminate_parenthesis(str, '[', ']');
582         eliminate_parenthesis(str, '(', ')');
583         g_strstrip(str);
584
585         srcp = str + subject_get_prefix_length(str);
586         if (srcp != str)
587                 memmove(str, srcp, strlen(srcp) + 1);
588 }
589
590 void trim_subject_for_sort(gchar *str)
591 {
592         gchar *srcp;
593
594         g_strstrip(str);
595
596         srcp = str + subject_get_prefix_length(str);
597         if (srcp != str)        
598                 memmove(str, srcp, strlen(srcp) + 1);
599 }
600
601 void trim_subject(gchar *str)
602 {
603         register gchar *srcp, *destp;
604         gchar op, cl;
605         gint in_brace;
606
607         destp = str + subject_get_prefix_length(str);
608
609         if (*destp == '[') {
610                 op = '[';
611                 cl = ']';
612         } else if (*destp == '(') {
613                 op = '(';
614                 cl = ')';
615         } else
616                 return;
617
618         srcp = destp + 1;
619         in_brace = 1;
620         while (*srcp) {
621                 if (*srcp == op)
622                         in_brace++;
623                 else if (*srcp == cl)
624                         in_brace--;
625                 srcp++;
626                 if (in_brace == 0)
627                         break;
628         }
629         while (isspace(*srcp)) srcp++;
630         memmove(destp, srcp, strlen(srcp) + 1);
631 }
632
633 void eliminate_parenthesis(gchar *str, gchar op, gchar cl)
634 {
635         register gchar *srcp, *destp;
636         gint in_brace;
637
638         srcp = destp = str;
639
640         while ((destp = strchr(destp, op))) {
641                 in_brace = 1;
642                 srcp = destp + 1;
643                 while (*srcp) {
644                         if (*srcp == op)
645                                 in_brace++;
646                         else if (*srcp == cl)
647                                 in_brace--;
648                         srcp++;
649                         if (in_brace == 0)
650                                 break;
651                 }
652                 while (isspace(*srcp)) srcp++;
653                 memmove(destp, srcp, strlen(srcp) + 1);
654         }
655 }
656
657 void extract_parenthesis(gchar *str, gchar op, gchar cl)
658 {
659         register gchar *srcp, *destp;
660         gint in_brace;
661
662         srcp = destp = str;
663
664         while ((srcp = strchr(destp, op))) {
665                 if (destp > str)
666                         *destp++ = ' ';
667                 memmove(destp, srcp + 1, strlen(srcp));
668                 in_brace = 1;
669                 while(*destp) {
670                         if (*destp == op)
671                                 in_brace++;
672                         else if (*destp == cl)
673                                 in_brace--;
674
675                         if (in_brace == 0)
676                                 break;
677
678                         destp++;
679                 }
680         }
681         *destp = '\0';
682 }
683
684 void extract_parenthesis_with_skip_quote(gchar *str, gchar quote_chr,
685                                          gchar op, gchar cl)
686 {
687         register gchar *srcp, *destp;
688         gint in_brace;
689         gboolean in_quote = FALSE;
690
691         srcp = destp = str;
692
693         while ((srcp = strchr_with_skip_quote(destp, quote_chr, op))) {
694                 if (destp > str)
695                         *destp++ = ' ';
696                 memmove(destp, srcp + 1, strlen(srcp));
697                 in_brace = 1;
698                 while(*destp) {
699                         if (*destp == op && !in_quote)
700                                 in_brace++;
701                         else if (*destp == cl && !in_quote)
702                                 in_brace--;
703                         else if (*destp == quote_chr)
704                                 in_quote ^= TRUE;
705
706                         if (in_brace == 0)
707                                 break;
708
709                         destp++;
710                 }
711         }
712         *destp = '\0';
713 }
714
715 void eliminate_quote(gchar *str, gchar quote_chr)
716 {
717         register gchar *srcp, *destp;
718
719         srcp = destp = str;
720
721         while ((destp = strchr(destp, quote_chr))) {
722                 if ((srcp = strchr(destp + 1, quote_chr))) {
723                         srcp++;
724                         while (isspace(*srcp)) srcp++;
725                         memmove(destp, srcp, strlen(srcp) + 1);
726                 } else {
727                         *destp = '\0';
728                         break;
729                 }
730         }
731 }
732
733 void extract_quote(gchar *str, gchar quote_chr)
734 {
735         register gchar *p;
736
737         if ((str = strchr(str, quote_chr))) {
738                 p = str;
739                 while ((p = strchr(p + 1, quote_chr)) && (p[-1] == '\\')) {
740                         memmove(p - 1, p, strlen(p) + 1);
741                         p--;
742                 }
743                 if(p) {
744                         *p = '\0';
745                         memmove(str, str + 1, p - str);
746                 }
747         }
748 }
749
750 void eliminate_address_comment(gchar *str)
751 {
752         register gchar *srcp, *destp;
753         gint in_brace;
754
755         srcp = destp = str;
756
757         while ((destp = strchr(destp, '"'))) {
758                 if ((srcp = strchr(destp + 1, '"'))) {
759                         srcp++;
760                         if (*srcp == '@') {
761                                 destp = srcp + 1;
762                         } else {
763                                 while (isspace(*srcp)) srcp++;
764                                 memmove(destp, srcp, strlen(srcp) + 1);
765                         }
766                 } else {
767                         *destp = '\0';
768                         break;
769                 }
770         }
771
772         srcp = destp = str;
773
774         while ((destp = strchr_with_skip_quote(destp, '"', '('))) {
775                 in_brace = 1;
776                 srcp = destp + 1;
777                 while (*srcp) {
778                         if (*srcp == '(')
779                                 in_brace++;
780                         else if (*srcp == ')')
781                                 in_brace--;
782                         srcp++;
783                         if (in_brace == 0)
784                                 break;
785                 }
786                 while (isspace(*srcp)) srcp++;
787                 memmove(destp, srcp, strlen(srcp) + 1);
788         }
789 }
790
791 gchar *strchr_with_skip_quote(const gchar *str, gint quote_chr, gint c)
792 {
793         gboolean in_quote = FALSE;
794
795         while (*str) {
796                 if (*str == c && !in_quote)
797                         return (gchar *)str;
798                 if (*str == quote_chr)
799                         in_quote ^= TRUE;
800                 str++;
801         }
802
803         return NULL;
804 }
805
806 gchar *strrchr_with_skip_quote(const gchar *str, gint quote_chr, gint c)
807 {
808         gboolean in_quote = FALSE;
809         const gchar *p;
810
811         p = str + strlen(str) - 1;
812         while (p >= str) {
813                 if (*p == c && !in_quote)
814                         return (gchar *)p;
815                 if (*p == quote_chr)
816                         in_quote ^= TRUE;
817                 p--;
818         }
819
820         return NULL;
821 }
822
823 void extract_address(gchar *str)
824 {
825         eliminate_address_comment(str);
826         if (strchr_with_skip_quote(str, '"', '<'))
827                 extract_parenthesis_with_skip_quote(str, '"', '<', '>');
828         g_strstrip(str);
829 }
830
831 void extract_list_id_str(gchar *str)
832 {
833         if (strchr_with_skip_quote(str, '"', '<'))
834                 extract_parenthesis_with_skip_quote(str, '"', '<', '>');
835         g_strstrip(str);
836 }
837
838 static GSList *address_list_append_real(GSList *addr_list, const gchar *str, gboolean removecomments)
839 {
840         gchar *work;
841         gchar *workp;
842
843         if (!str) return addr_list;
844
845         Xstrdup_a(work, str, return addr_list);
846
847         if (removecomments)
848                 eliminate_address_comment(work);
849         workp = work;
850
851         while (workp && *workp) {
852                 gchar *p, *next;
853
854                 if ((p = strchr_with_skip_quote(workp, '"', ','))) {
855                         *p = '\0';
856                         next = p + 1;
857                 } else
858                         next = NULL;
859
860                 if (removecomments && strchr_with_skip_quote(workp, '"', '<'))
861                         extract_parenthesis_with_skip_quote
862                                 (workp, '"', '<', '>');
863
864                 g_strstrip(workp);
865                 if (*workp)
866                         addr_list = g_slist_append(addr_list, g_strdup(workp));
867
868                 workp = next;
869         }
870
871         return addr_list;
872 }
873
874 GSList *address_list_append(GSList *addr_list, const gchar *str)
875 {
876         return address_list_append_real(addr_list, str, TRUE);
877 }
878
879 GSList *address_list_append_with_comments(GSList *addr_list, const gchar *str)
880 {
881         return address_list_append_real(addr_list, str, FALSE);
882 }
883
884 GSList *references_list_append(GSList *msgid_list, const gchar *str)
885 {
886         const gchar *strp;
887
888         if (!str) return msgid_list;
889         strp = str;
890
891         while (strp && *strp) {
892                 const gchar *start, *end;
893                 gchar *msgid;
894
895                 if ((start = strchr(strp, '<')) != NULL) {
896                         end = strchr(start + 1, '>');
897                         if (!end) break;
898                 } else
899                         break;
900
901                 msgid = g_strndup(start + 1, end - start - 1);
902                 g_strstrip(msgid);
903                 if (*msgid)
904                         msgid_list = g_slist_append(msgid_list, msgid);
905                 else
906                         g_free(msgid);
907
908                 strp = end + 1;
909         }
910
911         return msgid_list;
912 }
913
914 GSList *newsgroup_list_append(GSList *group_list, const gchar *str)
915 {
916         gchar *work;
917         gchar *workp;
918
919         if (!str) return group_list;
920
921         Xstrdup_a(work, str, return group_list);
922
923         workp = work;
924
925         while (workp && *workp) {
926                 gchar *p, *next;
927
928                 if ((p = strchr_with_skip_quote(workp, '"', ','))) {
929                         *p = '\0';
930                         next = p + 1;
931                 } else
932                         next = NULL;
933
934                 g_strstrip(workp);
935                 if (*workp)
936                         group_list = g_slist_append(group_list,
937                                                     g_strdup(workp));
938
939                 workp = next;
940         }
941
942         return group_list;
943 }
944
945 GList *add_history(GList *list, const gchar *str)
946 {
947         GList *old;
948
949         g_return_val_if_fail(str != NULL, list);
950
951         old = g_list_find_custom(list, (gpointer)str, (GCompareFunc)strcmp2);
952         if (old) {
953                 g_free(old->data);
954                 list = g_list_remove(list, old->data);
955         } else if (g_list_length(list) >= MAX_HISTORY_SIZE) {
956                 GList *last;
957
958                 last = g_list_last(list);
959                 if (last) {
960                         g_free(last->data);
961                         g_list_remove(list, last->data);
962                 }
963         }
964
965         list = g_list_prepend(list, g_strdup(str));
966
967         return list;
968 }
969
970 void remove_return(gchar *str)
971 {
972         register gchar *p = str;
973
974         while (*p) {
975                 if (*p == '\n' || *p == '\r')
976                         memmove(p, p + 1, strlen(p));
977                 else
978                         p++;
979         }
980 }
981
982 void remove_space(gchar *str)
983 {
984         register gchar *p = str;
985         register gint spc;
986
987         while (*p) {
988                 spc = 0;
989                 while (isspace(*(p + spc)))
990                         spc++;
991                 if (spc)
992                         memmove(p, p + spc, strlen(p + spc) + 1);
993                 else
994                         p++;
995         }
996 }
997
998 void unfold_line(gchar *str)
999 {
1000         register gchar *p = str;
1001         register gint spc;
1002
1003         while (*p) {
1004                 if (*p == '\n' || *p == '\r') {
1005                         *p++ = ' ';
1006                         spc = 0;
1007                         while (isspace(*(p + spc)))
1008                                 spc++;
1009                         if (spc)
1010                                 memmove(p, p + spc, strlen(p + spc) + 1);
1011                 } else
1012                         p++;
1013         }
1014 }
1015
1016 void subst_char(gchar *str, gchar orig, gchar subst)
1017 {
1018         register gchar *p = str;
1019
1020         while (*p) {
1021                 if (*p == orig)
1022                         *p = subst;
1023                 p++;
1024         }
1025 }
1026
1027 void subst_chars(gchar *str, gchar *orig, gchar subst)
1028 {
1029         register gchar *p = str;
1030
1031         while (*p) {
1032                 if (strchr(orig, *p) != NULL)
1033                         *p = subst;
1034                 p++;
1035         }
1036 }
1037
1038 void subst_for_filename(gchar *str)
1039 {
1040         subst_chars(str, " \t\r\n\"/\\", '_');
1041 }
1042
1043 gboolean is_header_line(const gchar *str)
1044 {
1045         if (str[0] == ':') return FALSE;
1046
1047         while (*str != '\0' && *str != ' ') {
1048                 if (*str == ':')
1049                         return TRUE;
1050                 str++;
1051         }
1052
1053         return FALSE;
1054 }
1055
1056 gboolean is_ascii_str(const guchar *str)
1057 {
1058         g_return_val_if_fail(str, FALSE);
1059
1060         while (*str != '\0') {
1061                 if (*str != '\t' && *str != ' ' &&
1062                     *str != '\r' && *str != '\n' &&
1063                     (*str < 32 || *str >= 127))
1064                         return FALSE;
1065                 str++;
1066         }
1067
1068         return TRUE;
1069 }
1070
1071 gint get_quote_level(const gchar *str, const gchar *quote_chars)
1072 {
1073         const gchar *first_pos;
1074         const gchar *last_pos;
1075         const gchar *p = str;
1076         gint quote_level = -1;
1077
1078         /* speed up line processing by only searching to the last '>' */
1079         if ((first_pos = line_has_quote_char(str, quote_chars)) != NULL) {
1080                 /* skip a line if it contains a '<' before the initial '>' */
1081                 if (memchr(str, '<', first_pos - str) != NULL)
1082                         return -1;
1083                 last_pos = line_has_quote_char_last(first_pos, quote_chars);
1084         } else
1085                 return -1;
1086
1087         while (p <= last_pos) {
1088                 while (p < last_pos) {
1089                         if (isspace(*p))
1090                                 p++;
1091                         else
1092                                 break;
1093                 }
1094
1095                 if (strchr(quote_chars, *p))
1096                         quote_level++;
1097                 else if (*p != '-' && !isspace(*p) && p <= last_pos) {
1098                         /* any characters are allowed except '-' and space */
1099                         while (*p != '-' 
1100                                && !strchr(quote_chars, *p) 
1101                                && !isspace(*p) 
1102                                && p < last_pos)
1103                                 p++;
1104                         if (strchr(quote_chars, *p))
1105                                 quote_level++;
1106                         else
1107                                 break;
1108                 }
1109
1110                 p++;
1111         }
1112
1113         return quote_level;
1114 }
1115
1116 const gchar * line_has_quote_char(const gchar * str, const gchar *quote_chars) 
1117 {
1118         gchar * position = NULL;
1119         gchar * tmp_pos = NULL;
1120         int i;
1121
1122         if (quote_chars == NULL)
1123                 return FALSE;
1124         
1125         for (i = 0; i < strlen(quote_chars); i++) {
1126                 tmp_pos = strchr (str,  quote_chars[i]);
1127                 if(position == NULL 
1128                    || (tmp_pos != NULL && position >= tmp_pos) )
1129                         position = tmp_pos;
1130         }
1131         return position; 
1132 }
1133
1134 const gchar * line_has_quote_char_last(const gchar * str, const gchar *quote_chars) 
1135 {
1136         gchar * position = NULL;
1137         gchar * tmp_pos = NULL;
1138         int i;
1139
1140         if (quote_chars == NULL)
1141                 return FALSE;
1142         
1143         for (i = 0; i < strlen(quote_chars); i++) {
1144                 tmp_pos = strrchr (str, quote_chars[i]);
1145                 if(position == NULL 
1146                    || (tmp_pos != NULL && position <= tmp_pos) )
1147                         position = tmp_pos;
1148         }
1149         return position; 
1150 }
1151
1152 gchar *strstr_with_skip_quote(const gchar *haystack, const gchar *needle)
1153 {
1154         register guint haystack_len, needle_len;
1155         gboolean in_squote = FALSE, in_dquote = FALSE;
1156
1157         haystack_len = strlen(haystack);
1158         needle_len   = strlen(needle);
1159
1160         if (haystack_len < needle_len || needle_len == 0)
1161                 return NULL;
1162
1163         while (haystack_len >= needle_len) {
1164                 if (!in_squote && !in_dquote &&
1165                     !strncmp(haystack, needle, needle_len))
1166                         return (gchar *)haystack;
1167
1168                 /* 'foo"bar"' -> foo"bar"
1169                    "foo'bar'" -> foo'bar' */
1170                 if (*haystack == '\'') {
1171                         if (in_squote)
1172                                 in_squote = FALSE;
1173                         else if (!in_dquote)
1174                                 in_squote = TRUE;
1175                 } else if (*haystack == '\"') {
1176                         if (in_dquote)
1177                                 in_dquote = FALSE;
1178                         else if (!in_squote)
1179                                 in_dquote = TRUE;
1180                 }
1181
1182                 haystack++;
1183                 haystack_len--;
1184         }
1185
1186         return NULL;
1187 }
1188
1189 gchar *strchr_parenthesis_close(const gchar *str, gchar op, gchar cl)
1190 {
1191         const gchar *p;
1192         gchar quote_chr = '"';
1193         gint in_brace;
1194         gboolean in_quote = FALSE;
1195
1196         p = str;
1197
1198         if ((p = strchr_with_skip_quote(p, quote_chr, op))) {
1199                 p++;
1200                 in_brace = 1;
1201                 while (*p) {
1202                         if (*p == op && !in_quote)
1203                                 in_brace++;
1204                         else if (*p == cl && !in_quote)
1205                                 in_brace--;
1206                         else if (*p == quote_chr)
1207                                 in_quote ^= TRUE;
1208
1209                         if (in_brace == 0)
1210                                 return (gchar *)p;
1211
1212                         p++;
1213                 }
1214         }
1215
1216         return NULL;
1217 }
1218
1219 gchar **strsplit_parenthesis(const gchar *str, gchar op, gchar cl,
1220                              gint max_tokens)
1221 {
1222         GSList *string_list = NULL, *slist;
1223         gchar **str_array;
1224         const gchar *s_op, *s_cl;
1225         guint i, n = 1;
1226
1227         g_return_val_if_fail(str != NULL, NULL);
1228
1229         if (max_tokens < 1)
1230                 max_tokens = G_MAXINT;
1231
1232         s_op = strchr_with_skip_quote(str, '"', op);
1233         if (!s_op) return NULL;
1234         str = s_op;
1235         s_cl = strchr_parenthesis_close(str, op, cl);
1236         if (s_cl) {
1237                 do {
1238                         guint len;
1239                         gchar *new_string;
1240
1241                         str++;
1242                         len = s_cl - str;
1243                         new_string = g_new(gchar, len + 1);
1244                         strncpy(new_string, str, len);
1245                         new_string[len] = 0;
1246                         string_list = g_slist_prepend(string_list, new_string);
1247                         n++;
1248                         str = s_cl + 1;
1249
1250                         while (*str && isspace(*str)) str++;
1251                         if (*str != op) {
1252                                 string_list = g_slist_prepend(string_list,
1253                                                               g_strdup(""));
1254                                 n++;
1255                                 s_op = strchr_with_skip_quote(str, '"', op);
1256                                 if (!--max_tokens || !s_op) break;
1257                                 str = s_op;
1258                         } else
1259                                 s_op = str;
1260                         s_cl = strchr_parenthesis_close(str, op, cl);
1261                 } while (--max_tokens && s_cl);
1262         }
1263
1264         str_array = g_new(gchar*, n);
1265
1266         i = n - 1;
1267
1268         str_array[i--] = NULL;
1269         for (slist = string_list; slist; slist = slist->next)
1270                 str_array[i--] = slist->data;
1271
1272         g_slist_free(string_list);
1273
1274         return str_array;
1275 }
1276
1277 gchar **strsplit_with_quote(const gchar *str, const gchar *delim,
1278                             gint max_tokens)
1279 {
1280         GSList *string_list = NULL, *slist;
1281         gchar **str_array, *s, *new_str;
1282         guint i, n = 1, len;
1283
1284         g_return_val_if_fail(str != NULL, NULL);
1285         g_return_val_if_fail(delim != NULL, NULL);
1286
1287         if (max_tokens < 1)
1288                 max_tokens = G_MAXINT;
1289
1290         s = strstr_with_skip_quote(str, delim);
1291         if (s) {
1292                 guint delimiter_len = strlen(delim);
1293
1294                 do {
1295                         len = s - str;
1296                         new_str = g_strndup(str, len);
1297
1298                         if (new_str[0] == '\'' || new_str[0] == '\"') {
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                         str = s + delimiter_len;
1307                         s = strstr_with_skip_quote(str, delim);
1308                 } while (--max_tokens && s);
1309         }
1310
1311         if (*str) {
1312                 new_str = g_strdup(str);
1313                 if (new_str[0] == '\'' || new_str[0] == '\"') {
1314                         len = strlen(str);
1315                         if (new_str[len - 1] == new_str[0]) {
1316                                 new_str[len - 1] = '\0';
1317                                 memmove(new_str, new_str + 1, len - 1);
1318                         }
1319                 }
1320                 string_list = g_slist_prepend(string_list, new_str);
1321                 n++;
1322         }
1323
1324         str_array = g_new(gchar*, n);
1325
1326         i = n - 1;
1327
1328         str_array[i--] = NULL;
1329         for (slist = string_list; slist; slist = slist->next)
1330                 str_array[i--] = slist->data;
1331
1332         g_slist_free(string_list);
1333
1334         return str_array;
1335 }
1336
1337 gchar *get_abbrev_newsgroup_name(const gchar *group, gint len)
1338 {
1339         gchar *abbrev_group;
1340         gchar *ap;
1341         const gchar *p = group;
1342         const gchar *last;
1343
1344         g_return_val_if_fail(group != NULL, NULL);
1345
1346         last = group + strlen(group);
1347         abbrev_group = ap = g_malloc(strlen(group) + 1);
1348
1349         while (*p) {
1350                 while (*p == '.')
1351                         *ap++ = *p++;
1352                 if ((ap - abbrev_group) + (last - p) > len && strchr(p, '.')) {
1353                         *ap++ = *p++;
1354                         while (*p != '.') p++;
1355                 } else {
1356                         strcpy(ap, p);
1357                         return abbrev_group;
1358                 }
1359         }
1360
1361         *ap = '\0';
1362         return abbrev_group;
1363 }
1364
1365 gchar *trim_string(const gchar *str, gint len)
1366 {
1367         const gchar *p = str;
1368         gint mb_len;
1369         gchar *new_str;
1370         gint new_len = 0;
1371
1372         if (!str) return NULL;
1373         if (strlen(str) <= len)
1374                 return g_strdup(str);
1375
1376         while (*p != '\0') {
1377                 mb_len = mblen(p, MB_LEN_MAX);
1378                 if (mb_len == 0)
1379                         break;
1380                 else if (mb_len < 0)
1381                         return g_strdup(str);
1382                 else if (new_len + mb_len > len)
1383                         break;
1384                 else
1385                         new_len += mb_len;
1386                 p += mb_len;
1387         }
1388
1389         Xstrndup_a(new_str, str, new_len, return g_strdup(str));
1390         return g_strconcat(new_str, "...", NULL);
1391 }
1392
1393 GList *uri_list_extract_filenames(const gchar *uri_list)
1394 {
1395         GList *result = NULL;
1396         const gchar *p, *q;
1397         gchar *file;
1398
1399         p = uri_list;
1400
1401         while (p) {
1402                 if (*p != '#') {
1403                         while (isspace(*p)) p++;
1404                         if (!strncmp(p, "file:", 5)) {
1405                                 p += 5;
1406                                 q = p;
1407                                 while (*q && *q != '\n' && *q != '\r') q++;
1408
1409                                 if (q > p) {
1410                                         q--;
1411                                         while (q > p && isspace(*q)) q--;
1412                                         file = g_malloc(q - p + 2);
1413                                         strncpy(file, p, q - p + 1);
1414                                         file[q - p + 1] = '\0';
1415                                         result = g_list_append(result,file);
1416                                 }
1417                         }
1418                 }
1419                 p = strchr(p, '\n');
1420                 if (p) p++;
1421         }
1422
1423         return result;
1424 }
1425
1426 #define HEX_TO_INT(val, hex) \
1427 { \
1428         gchar c = hex; \
1429  \
1430         if ('0' <= c && c <= '9') { \
1431                 val = c - '0'; \
1432         } else if ('a' <= c && c <= 'f') { \
1433                 val = c - 'a' + 10; \
1434         } else if ('A' <= c && c <= 'F') { \
1435                 val = c - 'A' + 10; \
1436         } else { \
1437                 val = 0; \
1438         } \
1439 }
1440
1441 gint scan_mailto_url(const gchar *mailto, gchar **to, gchar **cc, gchar **bcc,
1442                      gchar **subject, gchar **body)
1443 {
1444         gchar *tmp_mailto;
1445         gchar *p;
1446
1447         Xstrdup_a(tmp_mailto, mailto, return -1);
1448
1449         if (!strncmp(tmp_mailto, "mailto:", 7))
1450                 tmp_mailto += 7;
1451
1452         p = strchr(tmp_mailto, '?');
1453         if (p) {
1454                 *p = '\0';
1455                 p++;
1456         }
1457
1458         if (to && !*to)
1459                 *to = g_strdup(tmp_mailto);
1460
1461         while (p) {
1462                 gchar *field, *value;
1463
1464                 field = p;
1465
1466                 p = strchr(p, '=');
1467                 if (!p) break;
1468                 *p = '\0';
1469                 p++;
1470
1471                 value = p;
1472
1473                 p = strchr(p, '&');
1474                 if (p) {
1475                         *p = '\0';
1476                         p++;
1477                 }
1478
1479                 if (*value == '\0') continue;
1480
1481                 if (cc && !*cc && !g_strcasecmp(field, "cc")) {
1482                         *cc = g_strdup(value);
1483                 } else if (bcc && !*bcc && !g_strcasecmp(field, "bcc")) {
1484                         *bcc = g_strdup(value);
1485                 } else if (subject && !*subject &&
1486                            !g_strcasecmp(field, "subject")) {
1487                         *subject = g_malloc(strlen(value) + 1);
1488                         decode_uri(*subject, value);
1489                 } else if (body && !*body && !g_strcasecmp(field, "body")) {
1490                         *body = g_malloc(strlen(value) + 1);
1491                         decode_uri(*body, value);
1492                 }
1493         }
1494
1495         return 0;
1496 }
1497
1498 /*
1499  * We need this wrapper around g_get_home_dir(), so that
1500  * we can fix some Windoze things here.  Should be done in glibc of course
1501  * but as long as we are not able to do our own extensions to glibc, we do
1502  * it here.
1503  */
1504 gchar *get_home_dir(void)
1505 {
1506 #if HAVE_DOSISH_SYSTEM
1507     static gchar *home_dir;
1508
1509     if (!home_dir) {
1510         home_dir = read_w32_registry_string(NULL,
1511                                             "Software\\Sylpheed", "HomeDir" );
1512         if (!home_dir || !*home_dir) {
1513             if (getenv ("HOMEDRIVE") && getenv("HOMEPATH")) {
1514                 const char *s = g_get_home_dir();
1515                 if (s && *s)
1516                     home_dir = g_strdup (s);
1517             }
1518             if (!home_dir || !*home_dir) 
1519                 home_dir = g_strdup ("c:\\sylpheed");
1520         }
1521         debug_print("initialized home_dir to `%s'\n", home_dir);
1522     }
1523     return home_dir;
1524 #else /* standard glib */
1525     return g_get_home_dir();
1526 #endif
1527 }
1528
1529 gchar *get_rc_dir(void)
1530 {
1531         static gchar *rc_dir = NULL;
1532
1533         if (!rc_dir)
1534                 rc_dir = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
1535                                      RC_DIR, NULL);
1536
1537         return rc_dir;
1538 }
1539
1540 gchar *get_news_cache_dir(void)
1541 {
1542         static gchar *news_cache_dir = NULL;
1543
1544         if (!news_cache_dir)
1545                 news_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1546                                              NEWS_CACHE_DIR, NULL);
1547
1548         return news_cache_dir;
1549 }
1550
1551 gchar *get_imap_cache_dir(void)
1552 {
1553         static gchar *imap_cache_dir = NULL;
1554
1555         if (!imap_cache_dir)
1556                 imap_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1557                                              IMAP_CACHE_DIR, NULL);
1558
1559         return imap_cache_dir;
1560 }
1561
1562 gchar *get_mbox_cache_dir(void)
1563 {
1564         static gchar *mbox_cache_dir = NULL;
1565
1566         if (!mbox_cache_dir)
1567                 mbox_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1568                                              MBOX_CACHE_DIR, NULL);
1569
1570         return mbox_cache_dir;
1571 }
1572
1573 gchar *get_mime_tmp_dir(void)
1574 {
1575         static gchar *mime_tmp_dir = NULL;
1576
1577         if (!mime_tmp_dir)
1578                 mime_tmp_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1579                                            MIME_TMP_DIR, NULL);
1580
1581         return mime_tmp_dir;
1582 }
1583
1584 gchar *get_template_dir(void)
1585 {
1586         static gchar *template_dir = NULL;
1587
1588         if (!template_dir)
1589                 template_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1590                                            TEMPLATE_DIR, NULL);
1591
1592         return template_dir;
1593 }
1594
1595 gchar *get_header_cache_dir(void)
1596 {
1597         static gchar *header_dir = NULL;
1598
1599         if (!header_dir)
1600                 header_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1601                                          HEADER_CACHE_DIR, NULL);
1602
1603         return header_dir;
1604 }
1605
1606 gchar *get_tmp_dir(void)
1607 {
1608         static gchar *tmp_dir = NULL;
1609
1610         if (!tmp_dir)
1611                 tmp_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1612                                       TMP_DIR, NULL);
1613
1614         return tmp_dir;
1615 }
1616
1617 gchar *get_tmp_file(void)
1618 {
1619         gchar *tmp_file;
1620         static guint32 id = 0;
1621
1622         tmp_file = g_strdup_printf("%s%ctmpfile.%08x",
1623                                    get_tmp_dir(), G_DIR_SEPARATOR, id++);
1624
1625         return tmp_file;
1626 }
1627
1628 gchar *get_domain_name(void)
1629 {
1630         static gchar *domain_name = NULL;
1631
1632         if (!domain_name) {
1633                 gchar buf[128] = "";
1634                 struct hostent *hp;
1635
1636                 if (gethostname(buf, sizeof(buf)) < 0) {
1637                         perror("gethostname");
1638                         domain_name = "unknown";
1639                 } else {
1640                         buf[sizeof(buf) - 1] = '\0';
1641                         if ((hp = my_gethostbyname(buf)) == NULL) {
1642                                 perror("gethostbyname");
1643                                 domain_name = g_strdup(buf);
1644                         } else {
1645                                 domain_name = g_strdup(hp->h_name);
1646                         }
1647                 }
1648
1649                 debug_print("domain name = %s\n", domain_name);
1650         }
1651
1652         return domain_name;
1653 }
1654
1655 off_t get_file_size(const gchar *file)
1656 {
1657         struct stat s;
1658
1659         if (stat(file, &s) < 0) {
1660                 FILE_OP_ERROR(file, "stat");
1661                 return -1;
1662         }
1663
1664         return s.st_size;
1665 }
1666
1667 off_t get_file_size_as_crlf(const gchar *file)
1668 {
1669         FILE *fp;
1670         off_t size = 0;
1671         gchar buf[BUFFSIZE];
1672
1673         if ((fp = fopen(file, "rb")) == NULL) {
1674                 FILE_OP_ERROR(file, "fopen");
1675                 return -1;
1676         }
1677
1678         while (fgets(buf, sizeof(buf), fp) != NULL) {
1679                 strretchomp(buf);
1680                 size += strlen(buf) + 2;
1681         }
1682
1683         if (ferror(fp)) {
1684                 FILE_OP_ERROR(file, "fgets");
1685                 size = -1;
1686         }
1687
1688         fclose(fp);
1689
1690         return size;
1691 }
1692
1693 off_t get_left_file_size(FILE *fp)
1694 {
1695         glong pos;
1696         glong end;
1697         off_t size;
1698
1699         if ((pos = ftell(fp)) < 0) {
1700                 perror("ftell");
1701                 return -1;
1702         }
1703         if (fseek(fp, 0L, SEEK_END) < 0) {
1704                 perror("fseek");
1705                 return -1;
1706         }
1707         if ((end = ftell(fp)) < 0) {
1708                 perror("fseek");
1709                 return -1;
1710         }
1711         size = end - pos;
1712         if (fseek(fp, pos, SEEK_SET) < 0) {
1713                 perror("fseek");
1714                 return -1;
1715         }
1716
1717         return size;
1718 }
1719
1720 gboolean file_exist(const gchar *file, gboolean allow_fifo)
1721 {
1722         struct stat s;
1723
1724         if (file == NULL)
1725                 return FALSE;
1726
1727         if (stat(file, &s) < 0) {
1728                 if (ENOENT != errno) FILE_OP_ERROR(file, "stat");
1729                 return FALSE;
1730         }
1731
1732         if (S_ISREG(s.st_mode) || (allow_fifo && S_ISFIFO(s.st_mode)))
1733                 return TRUE;
1734
1735         return FALSE;
1736 }
1737
1738 gboolean is_dir_exist(const gchar *dir)
1739 {
1740         struct stat s;
1741
1742         if (dir == NULL)
1743                 return FALSE;
1744
1745         if (stat(dir, &s) < 0) {
1746                 if (ENOENT != errno) FILE_OP_ERROR(dir, "stat");
1747                 return FALSE;
1748         }
1749
1750         if (S_ISDIR(s.st_mode))
1751                 return TRUE;
1752
1753         return FALSE;
1754 }
1755
1756 gboolean is_file_entry_exist(const gchar *file)
1757 {
1758         struct stat s;
1759
1760         if (file == NULL)
1761                 return FALSE;
1762
1763         if (stat(file, &s) < 0) {
1764                 if (ENOENT != errno) FILE_OP_ERROR(file, "stat");
1765                 return FALSE;
1766         }
1767
1768         return TRUE;
1769 }
1770
1771 gint change_dir(const gchar *dir)
1772 {
1773         gchar *prevdir = NULL;
1774
1775         if (debug_mode)
1776                 prevdir = g_get_current_dir();
1777
1778         if (chdir(dir) < 0) {
1779                 FILE_OP_ERROR(dir, "chdir");
1780                 if (debug_mode) g_free(prevdir);
1781                 return -1;
1782         } else if (debug_mode) {
1783                 gchar *cwd;
1784
1785                 cwd = g_get_current_dir();
1786                 if (strcmp(prevdir, cwd) != 0)
1787                         g_print("current dir: %s\n", cwd);
1788                 g_free(cwd);
1789                 g_free(prevdir);
1790         }
1791
1792         return 0;
1793 }
1794
1795 gint make_dir(const gchar *dir)
1796 {
1797         if (mkdir(dir, S_IRWXU) < 0) {
1798                 FILE_OP_ERROR(dir, "mkdir");
1799                 return -1;
1800         }
1801         if (chmod(dir, S_IRWXU) < 0)
1802                 FILE_OP_ERROR(dir, "chmod");
1803
1804         return 0;
1805 }
1806
1807 gint make_dir_hier(const gchar *dir)
1808 {
1809         gchar *parent_dir;
1810         const gchar *p;
1811
1812         for (p = dir; (p = strchr(p, G_DIR_SEPARATOR)) != NULL; p++) {
1813                 parent_dir = g_strndup(dir, p - dir);
1814                 if (*parent_dir != '\0') {
1815                         if (!is_dir_exist(parent_dir)) {
1816                                 if (make_dir(parent_dir) < 0) {
1817                                         g_free(parent_dir);
1818                                         return -1;
1819                                 }
1820                         }
1821                 }
1822                 g_free(parent_dir);
1823         }
1824
1825         if (!is_dir_exist(dir)) {
1826                 if (make_dir(dir) < 0)
1827                         return -1;
1828         }
1829
1830         return 0;
1831 }
1832
1833 gint remove_all_files(const gchar *dir)
1834 {
1835         DIR *dp;
1836         struct dirent *d;
1837         gchar *prev_dir;
1838
1839         prev_dir = g_get_current_dir();
1840
1841         if (chdir(dir) < 0) {
1842                 FILE_OP_ERROR(dir, "chdir");
1843                 g_free(prev_dir);
1844                 return -1;
1845         }
1846
1847         if ((dp = opendir(".")) == NULL) {
1848                 FILE_OP_ERROR(dir, "opendir");
1849                 g_free(prev_dir);
1850                 return -1;
1851         }
1852
1853         while ((d = readdir(dp)) != NULL) {
1854                 if (!strcmp(d->d_name, ".") ||
1855                     !strcmp(d->d_name, ".."))
1856                         continue;
1857
1858                 if (unlink(d->d_name) < 0)
1859                         FILE_OP_ERROR(d->d_name, "unlink");
1860         }
1861
1862         closedir(dp);
1863
1864         if (chdir(prev_dir) < 0) {
1865                 FILE_OP_ERROR(prev_dir, "chdir");
1866                 g_free(prev_dir);
1867                 return -1;
1868         }
1869
1870         g_free(prev_dir);
1871
1872         return 0;
1873 }
1874
1875 gint remove_numbered_files(const gchar *dir, guint first, guint last)
1876 {
1877         DIR *dp;
1878         struct dirent *d;
1879         gchar *prev_dir;
1880         gint fileno;
1881
1882         prev_dir = g_get_current_dir();
1883
1884         if (chdir(dir) < 0) {
1885                 FILE_OP_ERROR(dir, "chdir");
1886                 g_free(prev_dir);
1887                 return -1;
1888         }
1889
1890         if ((dp = opendir(".")) == NULL) {
1891                 FILE_OP_ERROR(dir, "opendir");
1892                 g_free(prev_dir);
1893                 return -1;
1894         }
1895
1896         while ((d = readdir(dp)) != NULL) {
1897                 fileno = to_number(d->d_name);
1898                 if (fileno >= 0 && first <= fileno && fileno <= last) {
1899                         if (is_dir_exist(d->d_name))
1900                                 continue;
1901                         if (unlink(d->d_name) < 0)
1902                                 FILE_OP_ERROR(d->d_name, "unlink");
1903                 }
1904         }
1905
1906         closedir(dp);
1907
1908         if (chdir(prev_dir) < 0) {
1909                 FILE_OP_ERROR(prev_dir, "chdir");
1910                 g_free(prev_dir);
1911                 return -1;
1912         }
1913
1914         g_free(prev_dir);
1915
1916         return 0;
1917 }
1918
1919 gint remove_numbered_files_not_in_list(const gchar *dir, GSList *numberlist)
1920 {
1921         DIR *dp;
1922         struct dirent *d;
1923         gchar *prev_dir;
1924         gint fileno;
1925
1926         prev_dir = g_get_current_dir();
1927
1928         if (chdir(dir) < 0) {
1929                 FILE_OP_ERROR(dir, "chdir");
1930                 g_free(prev_dir);
1931                 return -1;
1932         }
1933
1934         if ((dp = opendir(".")) == NULL) {
1935                 FILE_OP_ERROR(dir, "opendir");
1936                 g_free(prev_dir);
1937                 return -1;
1938         }
1939
1940         while ((d = readdir(dp)) != NULL) {
1941                 fileno = to_number(d->d_name);
1942                 if (fileno >= 0 && (g_slist_find(numberlist, GINT_TO_POINTER(fileno)) == NULL)) {
1943                         debug_print("removing unwanted file %d from %s\n", fileno, dir);
1944                         if (is_dir_exist(d->d_name))
1945                                 continue;
1946                         if (unlink(d->d_name) < 0)
1947                                 FILE_OP_ERROR(d->d_name, "unlink");
1948                 }
1949         }
1950
1951         closedir(dp);
1952
1953         if (chdir(prev_dir) < 0) {
1954                 FILE_OP_ERROR(prev_dir, "chdir");
1955                 g_free(prev_dir);
1956                 return -1;
1957         }
1958
1959         g_free(prev_dir);
1960
1961         return 0;
1962 }
1963
1964 gint remove_all_numbered_files(const gchar *dir)
1965 {
1966         return remove_numbered_files(dir, 0, UINT_MAX);
1967 }
1968
1969 gint remove_expired_files(const gchar *dir, guint hours)
1970 {
1971         DIR *dp;
1972         struct dirent *d;
1973         struct stat s;
1974         gchar *prev_dir;
1975         gint fileno;
1976         time_t mtime, now, expire_time;
1977
1978         prev_dir = g_get_current_dir();
1979
1980         if (chdir(dir) < 0) {
1981                 FILE_OP_ERROR(dir, "chdir");
1982                 g_free(prev_dir);
1983                 return -1;
1984         }
1985
1986         if ((dp = opendir(".")) == NULL) {
1987                 FILE_OP_ERROR(dir, "opendir");
1988                 g_free(prev_dir);
1989                 return -1;
1990         }
1991
1992         now = time(NULL);
1993         expire_time = hours * 60 * 60;
1994
1995         while ((d = readdir(dp)) != NULL) {
1996                 fileno = to_number(d->d_name);
1997                 if (fileno >= 0) {
1998                         if (stat(d->d_name, &s) < 0) {
1999                                 FILE_OP_ERROR(d->d_name, "stat");
2000                                 continue;
2001                         }
2002                         if (S_ISDIR(s.st_mode))
2003                                 continue;
2004                         mtime = MAX(s.st_mtime, s.st_atime);
2005                         if (now - mtime > expire_time) {
2006                                 if (unlink(d->d_name) < 0)
2007                                         FILE_OP_ERROR(d->d_name, "unlink");
2008                         }
2009                 }
2010         }
2011
2012         closedir(dp);
2013
2014         if (chdir(prev_dir) < 0) {
2015                 FILE_OP_ERROR(prev_dir, "chdir");
2016                 g_free(prev_dir);
2017                 return -1;
2018         }
2019
2020         g_free(prev_dir);
2021
2022         return 0;
2023 }
2024
2025 gint remove_dir_recursive(const gchar *dir)
2026 {
2027         struct stat s;
2028         DIR *dp;
2029         struct dirent *d;
2030         gchar *prev_dir;
2031
2032         /* g_print("dir = %s\n", dir); */
2033
2034         if (stat(dir, &s) < 0) {
2035                 FILE_OP_ERROR(dir, "stat");
2036                 if (ENOENT == errno) return 0;
2037                 return -1;
2038         }
2039
2040         if (!S_ISDIR(s.st_mode)) {
2041                 if (unlink(dir) < 0) {
2042                         FILE_OP_ERROR(dir, "unlink");
2043                         return -1;
2044                 }
2045
2046                 return 0;
2047         }
2048
2049         prev_dir = g_get_current_dir();
2050         /* g_print("prev_dir = %s\n", prev_dir); */
2051
2052         if (!path_cmp(prev_dir, dir)) {
2053                 g_free(prev_dir);
2054                 if (chdir("..") < 0) {
2055                         FILE_OP_ERROR(dir, "chdir");
2056                         return -1;
2057                 }
2058                 prev_dir = g_get_current_dir();
2059         }
2060
2061         if (chdir(dir) < 0) {
2062                 FILE_OP_ERROR(dir, "chdir");
2063                 g_free(prev_dir);
2064                 return -1;
2065         }
2066
2067         if ((dp = opendir(".")) == NULL) {
2068                 FILE_OP_ERROR(dir, "opendir");
2069                 chdir(prev_dir);
2070                 g_free(prev_dir);
2071                 return -1;
2072         }
2073
2074         /* remove all files in the directory */
2075         while ((d = readdir(dp)) != NULL) {
2076                 if (!strcmp(d->d_name, ".") ||
2077                     !strcmp(d->d_name, ".."))
2078                         continue;
2079
2080                 if (stat(d->d_name, &s) < 0) {
2081                         FILE_OP_ERROR(d->d_name, "stat");
2082                         continue;
2083                 }
2084
2085                 /* g_print("removing %s\n", d->d_name); */
2086
2087                 if (S_ISDIR(s.st_mode)) {
2088                         if (remove_dir_recursive(d->d_name) < 0) {
2089                                 g_warning("can't remove directory\n");
2090                                 return -1;
2091                         }
2092                 } else {
2093                         if (unlink(d->d_name) < 0)
2094                                 FILE_OP_ERROR(d->d_name, "unlink");
2095                 }
2096         }
2097
2098         closedir(dp);
2099
2100         if (chdir(prev_dir) < 0) {
2101                 FILE_OP_ERROR(prev_dir, "chdir");
2102                 g_free(prev_dir);
2103                 return -1;
2104         }
2105
2106         g_free(prev_dir);
2107
2108         if (rmdir(dir) < 0) {
2109                 FILE_OP_ERROR(dir, "rmdir");
2110                 return -1;
2111         }
2112
2113         return 0;
2114 }
2115
2116 #if 0
2117 /* this seems to be slower than the stdio version... */
2118 gint copy_file(const gchar *src, const gchar *dest)
2119 {
2120         gint src_fd, dest_fd;
2121         gint n_read;
2122         gint n_write;
2123         gchar buf[BUFSIZ];
2124         gchar *dest_bak = NULL;
2125
2126         if ((src_fd = open(src, O_RDONLY)) < 0) {
2127                 FILE_OP_ERROR(src, "open");
2128                 return -1;
2129         }
2130
2131         if (is_file_exist(dest)) {
2132                 dest_bak = g_strconcat(dest, ".bak", NULL);
2133                 if (rename(dest, dest_bak) < 0) {
2134                         FILE_OP_ERROR(dest, "rename");
2135                         close(src_fd);
2136                         g_free(dest_bak);
2137                         return -1;
2138                 }
2139         }
2140
2141         if ((dest_fd = open(dest, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR)) < 0) {
2142                 FILE_OP_ERROR(dest, "open");
2143                 close(src_fd);
2144                 if (dest_bak) {
2145                         if (rename(dest_bak, dest) < 0)
2146                                 FILE_OP_ERROR(dest_bak, "rename");
2147                         g_free(dest_bak);
2148                 }
2149                 return -1;
2150         }
2151
2152         while ((n_read = read(src_fd, buf, sizeof(buf))) > 0) {
2153                 gint len = n_read;
2154                 gchar *bufp = buf;
2155
2156                 while (len > 0) {
2157                         n_write = write(dest_fd, bufp, len);
2158                         if (n_write <= 0) {
2159                                 g_warning("writing to %s failed.\n", dest);
2160                                 close(dest_fd);
2161                                 close(src_fd);
2162                                 unlink(dest);
2163                                 if (dest_bak) {
2164                                         if (rename(dest_bak, dest) < 0)
2165                                                 FILE_OP_ERROR(dest_bak, "rename");
2166                                         g_free(dest_bak);
2167                                 }
2168                                 return -1;
2169                         }
2170                         len -= n_write;
2171                         bufp += n_write;
2172                 }
2173         }
2174
2175         close(src_fd);
2176         close(dest_fd);
2177
2178         if (n_read < 0 || get_file_size(src) != get_file_size(dest)) {
2179                 g_warning("File copy from %s to %s failed.\n", src, dest);
2180                 unlink(dest);
2181                 if (dest_bak) {
2182                         if (rename(dest_bak, dest) < 0)
2183                                 FILE_OP_ERROR(dest_bak, "rename");
2184                         g_free(dest_bak);
2185                 }
2186                 return -1;
2187         }
2188         g_free(dest_bak);
2189
2190         return 0;
2191 }
2192 #endif
2193
2194
2195 /*
2196  * Append src file body to the tail of dest file.
2197  * Now keep_backup has no effects.
2198  */
2199 gint append_file(const gchar *src, const gchar *dest, gboolean keep_backup)
2200 {
2201         FILE *src_fp, *dest_fp;
2202         gint n_read;
2203         gchar buf[BUFSIZ];
2204
2205         gboolean err = FALSE;
2206
2207         if ((src_fp = fopen(src, "rb")) == NULL) {
2208                 FILE_OP_ERROR(src, "fopen");
2209                 return -1;
2210         }
2211         
2212         if ((dest_fp = fopen(dest, "ab")) == NULL) {
2213                 FILE_OP_ERROR(dest, "fopen");
2214                 fclose(src_fp);
2215                 return -1;
2216         }
2217
2218         if (change_file_mode_rw(dest_fp, dest) < 0) {
2219                 FILE_OP_ERROR(dest, "chmod");
2220                 g_warning("can't change file mode\n");
2221         }
2222
2223         while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
2224                 if (n_read < sizeof(buf) && ferror(src_fp))
2225                         break;
2226                 if (fwrite(buf, n_read, 1, dest_fp) < 1) {
2227                         g_warning("writing to %s failed.\n", dest);
2228                         fclose(dest_fp);
2229                         fclose(src_fp);
2230                         unlink(dest);
2231                         return -1;
2232                 }
2233         }
2234
2235         if (ferror(src_fp)) {
2236                 FILE_OP_ERROR(src, "fread");
2237                 err = TRUE;
2238         }
2239         fclose(src_fp);
2240         if (fclose(dest_fp) == EOF) {
2241                 FILE_OP_ERROR(dest, "fclose");
2242                 err = TRUE;
2243         }
2244
2245         if (err) {
2246                 unlink(dest);
2247                 return -1;
2248         }
2249
2250         return 0;
2251 }
2252
2253 gint copy_file(const gchar *src, const gchar *dest, gboolean keep_backup)
2254 {
2255         FILE *src_fp, *dest_fp;
2256         gint n_read;
2257         gchar buf[BUFSIZ];
2258         gchar *dest_bak = NULL;
2259         gboolean err = FALSE;
2260
2261         if ((src_fp = fopen(src, "rb")) == NULL) {
2262                 FILE_OP_ERROR(src, "fopen");
2263                 return -1;
2264         }
2265         if (is_file_exist(dest)) {
2266                 dest_bak = g_strconcat(dest, ".bak", NULL);
2267                 if (rename(dest, dest_bak) < 0) {
2268                         FILE_OP_ERROR(dest, "rename");
2269                         fclose(src_fp);
2270                         g_free(dest_bak);
2271                         return -1;
2272                 }
2273         }
2274
2275         if ((dest_fp = fopen(dest, "wb")) == NULL) {
2276                 FILE_OP_ERROR(dest, "fopen");
2277                 fclose(src_fp);
2278                 if (dest_bak) {
2279                         if (rename(dest_bak, dest) < 0)
2280                                 FILE_OP_ERROR(dest_bak, "rename");
2281                         g_free(dest_bak);
2282                 }
2283                 return -1;
2284         }
2285
2286         if (change_file_mode_rw(dest_fp, dest) < 0) {
2287                 FILE_OP_ERROR(dest, "chmod");
2288                 g_warning("can't change file mode\n");
2289         }
2290
2291         while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
2292                 if (n_read < sizeof(buf) && ferror(src_fp))
2293                         break;
2294                 if (fwrite(buf, n_read, 1, dest_fp) < 1) {
2295                         g_warning("writing to %s failed.\n", dest);
2296                         fclose(dest_fp);
2297                         fclose(src_fp);
2298                         unlink(dest);
2299                         if (dest_bak) {
2300                                 if (rename(dest_bak, dest) < 0)
2301                                         FILE_OP_ERROR(dest_bak, "rename");
2302                                 g_free(dest_bak);
2303                         }
2304                         return -1;
2305                 }
2306         }
2307
2308         if (ferror(src_fp)) {
2309                 FILE_OP_ERROR(src, "fread");
2310                 err = TRUE;
2311         }
2312         fclose(src_fp);
2313         if (fclose(dest_fp) == EOF) {
2314                 FILE_OP_ERROR(dest, "fclose");
2315                 err = TRUE;
2316         }
2317
2318         if (err) {
2319                 unlink(dest);
2320                 if (dest_bak) {
2321                         if (rename(dest_bak, dest) < 0)
2322                                 FILE_OP_ERROR(dest_bak, "rename");
2323                         g_free(dest_bak);
2324                 }
2325                 return -1;
2326         }
2327
2328         if (keep_backup == FALSE && dest_bak)
2329                 unlink(dest_bak);
2330
2331         g_free(dest_bak);
2332
2333         return 0;
2334 }
2335
2336 gint move_file(const gchar *src, const gchar *dest, gboolean overwrite)
2337 {
2338         if (overwrite == FALSE && is_file_exist(dest)) {
2339                 g_warning("move_file(): file %s already exists.", dest);
2340                 return -1;
2341         }
2342
2343         if (rename(src, dest) == 0) return 0;
2344
2345         if (EXDEV != errno) {
2346                 FILE_OP_ERROR(src, "rename");
2347                 return -1;
2348         }
2349
2350         if (copy_file(src, dest, FALSE) < 0) return -1;
2351
2352         unlink(src);
2353
2354         return 0;
2355 }
2356
2357 gint copy_file_part(FILE *fp, off_t offset, size_t length, const gchar *dest)
2358 {
2359         FILE *dest_fp;
2360         gint n_read;
2361         gint bytes_left, to_read;
2362         gchar buf[BUFSIZ];
2363         gboolean err = FALSE;
2364
2365         if (fseek(fp, offset, SEEK_SET) < 0) {
2366                 perror("fseek");
2367                 return -1;
2368         }
2369
2370         if ((dest_fp = fopen(dest, "wb")) == NULL) {
2371                 FILE_OP_ERROR(dest, "fopen");
2372                 return -1;
2373         }
2374
2375         if (change_file_mode_rw(dest_fp, dest) < 0) {
2376                 FILE_OP_ERROR(dest, "chmod");
2377                 g_warning("can't change file mode\n");
2378         }
2379
2380         bytes_left = length;
2381         to_read = MIN(bytes_left, sizeof(buf));
2382
2383         while ((n_read = fread(buf, sizeof(gchar), to_read, fp)) > 0) {
2384                 if (n_read < to_read && ferror(fp))
2385                         break;
2386                 if (fwrite(buf, n_read, 1, dest_fp) < 1) {
2387                         g_warning("writing to %s failed.\n", dest);
2388                         fclose(dest_fp);
2389                         unlink(dest);
2390                         return -1;
2391                 }
2392                 bytes_left -= n_read;
2393                 if (bytes_left == 0)
2394                         break;
2395                 to_read = MIN(bytes_left, sizeof(buf));
2396         }
2397
2398         if (ferror(fp)) {
2399                 perror("fread");
2400                 err = TRUE;
2401         }
2402         if (fclose(dest_fp) == EOF) {
2403                 FILE_OP_ERROR(dest, "fclose");
2404                 err = TRUE;
2405         }
2406
2407         if (err) {
2408                 unlink(dest);
2409                 return -1;
2410         }
2411
2412         return 0;
2413 }
2414
2415 /* convert line endings into CRLF. If the last line doesn't end with
2416  * linebreak, add it.
2417  */
2418 gchar *canonicalize_str(const gchar *str)
2419 {
2420         const gchar *p;
2421         guint new_len = 0;
2422         gchar *out, *outp;
2423
2424         for (p = str; *p != '\0'; ++p) {
2425                 if (*p != '\r') {
2426                         ++new_len;
2427                         if (*p == '\n')
2428                                 ++new_len;
2429                 }
2430         }
2431         if (p == str || *(p - 1) != '\n')
2432                 new_len += 2;
2433
2434         out = outp = g_malloc(new_len + 1);
2435         for (p = str; *p != '\0'; ++p) {
2436                 if (*p != '\r') {
2437                         if (*p == '\n')
2438                                 *outp++ = '\r';
2439                         *outp++ = *p;
2440                 }
2441         }
2442         if (p == str || *(p - 1) != '\n') {
2443                 *outp++ = '\r';
2444                 *outp++ = '\n';
2445         }
2446         *outp = '\0';
2447
2448         return out;
2449 }
2450
2451 gint canonicalize_file(const gchar *src, const gchar *dest)
2452 {
2453         FILE *src_fp, *dest_fp;
2454         gchar buf[BUFFSIZE];
2455         gint len;
2456         gboolean err = FALSE;
2457         gboolean last_linebreak = FALSE;
2458
2459         if ((src_fp = fopen(src, "rb")) == NULL) {
2460                 FILE_OP_ERROR(src, "fopen");
2461                 return -1;
2462         }
2463
2464         if ((dest_fp = fopen(dest, "wb")) == NULL) {
2465                 FILE_OP_ERROR(dest, "fopen");
2466                 fclose(src_fp);
2467                 return -1;
2468         }
2469
2470         if (change_file_mode_rw(dest_fp, dest) < 0) {
2471                 FILE_OP_ERROR(dest, "chmod");
2472                 g_warning("can't change file mode\n");
2473         }
2474
2475         while (fgets(buf, sizeof(buf), src_fp) != NULL) {
2476                 gint r = 0;
2477
2478                 len = strlen(buf);
2479                 if (len == 0) break;
2480                 last_linebreak = FALSE;
2481
2482                 if (buf[len - 1] != '\n') {
2483                         last_linebreak = TRUE;
2484                         r = fputs(buf, dest_fp);
2485                 } else if (len > 1 && buf[len - 1] == '\n' && buf[len - 2] == '\r') {
2486                         r = fputs(buf, dest_fp);
2487                 } else {
2488                         if (len > 1) {
2489                                 r = fwrite(buf, len - 1, 1, dest_fp);
2490                                 if (r != 1)
2491                                         r = EOF;
2492                         }
2493                         if (r != EOF)
2494                                 r = fputs("\r\n", dest_fp);
2495                 }
2496
2497                 if (r == EOF) {
2498                         g_warning("writing to %s failed.\n", dest);
2499                         fclose(dest_fp);
2500                         fclose(src_fp);
2501                         unlink(dest);
2502                         return -1;
2503                 }
2504         }
2505
2506         if (last_linebreak == TRUE) {
2507                 if (fputs("\r\n", dest_fp) == EOF)
2508                         err = TRUE;
2509         }
2510
2511         if (ferror(src_fp)) {
2512                 FILE_OP_ERROR(src, "fgets");
2513                 err = TRUE;
2514         }
2515         fclose(src_fp);
2516         if (fclose(dest_fp) == EOF) {
2517                 FILE_OP_ERROR(dest, "fclose");
2518                 err = TRUE;
2519         }
2520
2521         if (err) {
2522                 unlink(dest);
2523                 return -1;
2524         }
2525
2526         return 0;
2527 }
2528
2529 gint canonicalize_file_replace(const gchar *file)
2530 {
2531         gchar *tmp_file;
2532
2533         tmp_file = get_tmp_file();
2534
2535         if (canonicalize_file(file, tmp_file) < 0) {
2536                 g_free(tmp_file);
2537                 return -1;
2538         }
2539
2540         if (move_file(tmp_file, file, TRUE) < 0) {
2541                 g_warning("can't replace %s .\n", file);
2542                 unlink(tmp_file);
2543                 g_free(tmp_file);
2544                 return -1;
2545         }
2546
2547         g_free(tmp_file);
2548         return 0;
2549 }
2550
2551 gint uncanonicalize_file(const gchar *src, const gchar *dest)
2552 {
2553         FILE *src_fp, *dest_fp;
2554         gchar buf[BUFFSIZE];
2555         gboolean err = FALSE;
2556
2557         if ((src_fp = fopen(src, "rb")) == NULL) {
2558                 FILE_OP_ERROR(src, "fopen");
2559                 return -1;
2560         }
2561
2562         if ((dest_fp = fopen(dest, "wb")) == NULL) {
2563                 FILE_OP_ERROR(dest, "fopen");
2564                 fclose(src_fp);
2565                 return -1;
2566         }
2567
2568         if (change_file_mode_rw(dest_fp, dest) < 0) {
2569                 FILE_OP_ERROR(dest, "chmod");
2570                 g_warning("can't change file mode\n");
2571         }
2572
2573         while (fgets(buf, sizeof(buf), src_fp) != NULL) {
2574                 strcrchomp(buf);
2575                 if (fputs(buf, dest_fp) == EOF) {
2576                         g_warning("writing to %s failed.\n", dest);
2577                         fclose(dest_fp);
2578                         fclose(src_fp);
2579                         unlink(dest);
2580                         return -1;
2581                 }
2582         }
2583
2584         if (ferror(src_fp)) {
2585                 FILE_OP_ERROR(src, "fgets");
2586                 err = TRUE;
2587         }
2588         fclose(src_fp);
2589         if (fclose(dest_fp) == EOF) {
2590                 FILE_OP_ERROR(dest, "fclose");
2591                 err = TRUE;
2592         }
2593
2594         if (err) {
2595                 unlink(dest);
2596                 return -1;
2597         }
2598
2599         return 0;
2600 }
2601
2602 gint uncanonicalize_file_replace(const gchar *file)
2603 {
2604         gchar *tmp_file;
2605
2606         tmp_file = get_tmp_file();
2607
2608         if (uncanonicalize_file(file, tmp_file) < 0) {
2609                 g_free(tmp_file);
2610                 return -1;
2611         }
2612
2613         if (move_file(tmp_file, file, TRUE) < 0) {
2614                 g_warning("can't replace %s .\n", file);
2615                 unlink(tmp_file);
2616                 g_free(tmp_file);
2617                 return -1;
2618         }
2619
2620         g_free(tmp_file);
2621         return 0;
2622 }
2623
2624 gchar *normalize_newlines(const gchar *str)
2625 {
2626         const gchar *p = str;
2627         gchar *out, *outp;
2628
2629         out = outp = g_malloc(strlen(str) + 1);
2630         for (p = str; *p != '\0'; ++p) {
2631                 if (*p == '\r') {
2632                         if (*(p + 1) != '\n')
2633                                 *outp++ = '\n';
2634                 } else
2635                         *outp++ = *p;
2636         }
2637
2638         *outp = '\0';
2639
2640         return out;
2641 }
2642
2643 gchar *get_outgoing_rfc2822_str(FILE *fp)
2644 {
2645         gchar buf[BUFFSIZE];
2646         GString *str;
2647         gchar *ret;
2648
2649         str = g_string_new(NULL);
2650
2651         /* output header part */
2652         while (fgets(buf, sizeof(buf), fp) != NULL) {
2653                 strretchomp(buf);
2654                 if (!g_strncasecmp(buf, "Bcc:", 4)) {
2655                         gint next;
2656
2657                         for (;;) {
2658                                 next = fgetc(fp);
2659                                 if (next == EOF)
2660                                         break;
2661                                 else if (next != ' ' && next != '\t') {
2662                                         ungetc(next, fp);
2663                                         break;
2664                                 }
2665                                 if (fgets(buf, sizeof(buf), fp) == NULL)
2666                                         break;
2667                         }
2668                 } else {
2669                         g_string_append(str, buf);
2670                         g_string_append(str, "\r\n");
2671                         if (buf[0] == '\0')
2672                                 break;
2673                 }
2674         }
2675
2676         /* output body part */
2677         while (fgets(buf, sizeof(buf), fp) != NULL) {
2678                 strretchomp(buf);
2679                 if (buf[0] == '.')
2680                         g_string_append_c(str, '.');
2681                 g_string_append(str, buf);
2682                 g_string_append(str, "\r\n");
2683         }
2684
2685         ret = str->str;
2686         g_string_free(str, FALSE);
2687
2688         return ret;
2689 }
2690
2691 gint change_file_mode_rw(FILE *fp, const gchar *file)
2692 {
2693 #if HAVE_FCHMOD
2694         return fchmod(fileno(fp), S_IRUSR|S_IWUSR);
2695 #else
2696         return chmod(file, S_IRUSR|S_IWUSR);
2697 #endif
2698 }
2699
2700 FILE *my_tmpfile(void)
2701 {
2702 #if HAVE_MKSTEMP
2703         const gchar suffix[] = ".XXXXXX";
2704         const gchar *tmpdir;
2705         guint tmplen;
2706         const gchar *progname;
2707         guint proglen;
2708         gchar *fname;
2709         gint fd;
2710         FILE *fp;
2711
2712         tmpdir = get_tmp_dir();
2713         tmplen = strlen(tmpdir);
2714         progname = g_get_prgname();
2715         proglen = strlen(progname);
2716         Xalloca(fname, tmplen + 1 + proglen + sizeof(suffix),
2717                 return tmpfile());
2718
2719         memcpy(fname, tmpdir, tmplen);
2720         fname[tmplen] = G_DIR_SEPARATOR;
2721         memcpy(fname + tmplen + 1, progname, proglen);
2722         memcpy(fname + tmplen + 1 + proglen, suffix, sizeof(suffix));
2723
2724         fd = mkstemp(fname);
2725         if (fd < 0)
2726                 return tmpfile();
2727
2728         unlink(fname);
2729
2730         fp = fdopen(fd, "w+b");
2731         if (!fp)
2732                 close(fd);
2733         else
2734                 return fp;
2735 #endif /* HAVE_MKSTEMP */
2736
2737         return tmpfile();
2738 }
2739
2740 FILE *str_open_as_stream(const gchar *str)
2741 {
2742         FILE *fp;
2743         size_t len;
2744
2745         g_return_val_if_fail(str != NULL, NULL);
2746
2747         fp = my_tmpfile();
2748         if (!fp) {
2749                 FILE_OP_ERROR("str_open_as_stream", "my_tmpfile");
2750                 return NULL;
2751         }
2752
2753         len = strlen(str);
2754         if (len == 0) return fp;
2755
2756         if (fwrite(str, len, 1, fp) != 1) {
2757                 FILE_OP_ERROR("str_open_as_stream", "fwrite");
2758                 fclose(fp);
2759                 return NULL;
2760         }
2761
2762         rewind(fp);
2763         return fp;
2764 }
2765
2766 gint str_write_to_file(const gchar *str, const gchar *file)
2767 {
2768         FILE *fp;
2769         size_t len;
2770
2771         g_return_val_if_fail(str != NULL, -1);
2772         g_return_val_if_fail(file != NULL, -1);
2773
2774         if ((fp = fopen(file, "wb")) == NULL) {
2775                 FILE_OP_ERROR(file, "fopen");
2776                 return -1;
2777         }
2778
2779         len = strlen(str);
2780         if (len == 0) {
2781                 fclose(fp);
2782                 return 0;
2783         }
2784
2785         if (fwrite(str, len, 1, fp) != 1) {
2786                 FILE_OP_ERROR(file, "fwrite");
2787                 fclose(fp);
2788                 unlink(file);
2789                 return -1;
2790         }
2791
2792         if (fclose(fp) == EOF) {
2793                 FILE_OP_ERROR(file, "fclose");
2794                 unlink(file);
2795                 return -1;
2796         }
2797
2798         return 0;
2799 }
2800
2801 gchar *file_read_to_str(const gchar *file)
2802 {
2803         FILE *fp;
2804         gchar *str;
2805
2806         g_return_val_if_fail(file != NULL, NULL);
2807
2808         if ((fp = fopen(file, "rb")) == NULL) {
2809                 FILE_OP_ERROR(file, "fopen");
2810                 return NULL;
2811         }
2812
2813         str = file_read_stream_to_str(fp);
2814
2815         fclose(fp);
2816
2817         return str;
2818 }
2819
2820 gchar *file_read_stream_to_str(FILE *fp)
2821 {
2822         GByteArray *array;
2823         gchar buf[BUFSIZ];
2824         gint n_read;
2825         gchar *str;
2826
2827         g_return_val_if_fail(fp != NULL, NULL);
2828
2829         array = g_byte_array_new();
2830
2831         while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
2832                 if (n_read < sizeof(buf) && ferror(fp))
2833                         break;
2834                 g_byte_array_append(array, buf, n_read);
2835         }
2836
2837         if (ferror(fp)) {
2838                 FILE_OP_ERROR("file stream", "fread");
2839                 g_byte_array_free(array, TRUE);
2840                 return NULL;
2841         }
2842
2843         buf[0] = '\0';
2844         g_byte_array_append(array, buf, 1);
2845         str = (gchar *)array->data;
2846         g_byte_array_free(array, FALSE);
2847
2848         return str;
2849 }
2850
2851 gint execute_async(gchar *const argv[])
2852 {
2853         pid_t pid;
2854
2855         if ((pid = fork()) < 0) {
2856                 perror("fork");
2857                 return -1;
2858         }
2859
2860         if (pid == 0) {                 /* child process */
2861                 pid_t gch_pid;
2862
2863                 if ((gch_pid = fork()) < 0) {
2864                         perror("fork");
2865                         _exit(1);
2866                 }
2867
2868                 if (gch_pid == 0) {     /* grandchild process */
2869                         execvp(argv[0], argv);
2870
2871                         perror("execvp");
2872                         _exit(1);
2873                 }
2874
2875                 _exit(0);
2876         }
2877
2878         waitpid(pid, NULL, 0);
2879
2880         return 0;
2881 }
2882
2883 gint execute_sync(gchar *const argv[])
2884 {
2885         pid_t pid;
2886
2887         if ((pid = fork()) < 0) {
2888                 perror("fork");
2889                 return -1;
2890         }
2891
2892         if (pid == 0) {         /* child process */
2893                 execvp(argv[0], argv);
2894
2895                 perror("execvp");
2896                 _exit(1);
2897         }
2898
2899         waitpid(pid, NULL, 0);
2900
2901         return 0;
2902 }
2903
2904 gint execute_command_line(const gchar *cmdline, gboolean async)
2905 {
2906         gchar **argv;
2907         gint ret;
2908
2909         argv = strsplit_with_quote(cmdline, " ", 0);
2910
2911         if (async)
2912                 ret = execute_async(argv);
2913         else
2914                 ret = execute_sync(argv);
2915         g_strfreev(argv);
2916
2917         return ret;
2918 }
2919
2920 gchar *get_command_output(const gchar *cmdline)
2921 {
2922         gchar buf[BUFFSIZE];
2923         FILE *fp;
2924         GString *str;
2925         gchar *ret;
2926
2927         g_return_val_if_fail(cmdline != NULL, NULL);
2928
2929         if ((fp = popen(cmdline, "r")) == NULL) {
2930                 FILE_OP_ERROR(cmdline, "popen");
2931                 return NULL;
2932         }
2933
2934         str = g_string_new("");
2935
2936         while (fgets(buf, sizeof(buf), fp) != NULL)
2937                 g_string_append(str, buf);
2938
2939         pclose(fp);
2940
2941         ret = str->str;
2942         g_string_free(str, FALSE);
2943
2944         return ret;
2945 }
2946
2947 static gint is_unchanged_uri_char(char c)
2948 {
2949         switch (c) {
2950                 case '(':
2951                 case ')':
2952                 case ',':
2953                         return 0;
2954                 default:
2955                         return 1;
2956         }
2957 }
2958
2959 void encode_uri(gchar *encoded_uri, gint bufsize, const gchar *uri)
2960 {
2961         int i;
2962         int k;
2963
2964         k = 0;
2965         for(i = 0; i < strlen(uri) ; i++) {
2966                 if (is_unchanged_uri_char(uri[i])) {
2967                         if (k + 2 >= bufsize)
2968                                 break;
2969                         encoded_uri[k++] = uri[i];
2970                 }
2971                 else {
2972                         char * hexa = "0123456789ABCDEF";
2973                         
2974                         if (k + 4 >= bufsize)
2975                                 break;
2976                         encoded_uri[k++] = '%';
2977                         encoded_uri[k++] = hexa[uri[i] / 16];
2978                         encoded_uri[k++] = hexa[uri[i] % 16];
2979                 }
2980         }
2981         encoded_uri[k] = 0;
2982 }
2983
2984 /* Converts two-digit hexadecimal to decimal.  Used for unescaping escaped 
2985  * characters
2986  */
2987 static gint axtoi(const gchar *hexstr)
2988 {
2989         gint hi, lo, result;
2990        
2991         hi = hexstr[0];
2992         if ('0' <= hi && hi <= '9') {
2993                 hi -= '0';
2994         } else
2995                 if ('a' <= hi && hi <= 'f') {
2996                         hi -= ('a' - 10);
2997                 } else
2998                         if ('A' <= hi && hi <= 'F') {
2999                                 hi -= ('A' - 10);
3000                         }
3001
3002         lo = hexstr[1];
3003         if ('0' <= lo && lo <= '9') {
3004                 lo -= '0';
3005         } else
3006                 if ('a' <= lo && lo <= 'f') {
3007                         lo -= ('a'-10);
3008                 } else
3009                         if ('A' <= lo && lo <= 'F') {
3010                                 lo -= ('A' - 10);
3011                         }
3012         result = lo + (16 * hi);
3013         return result;
3014 }
3015
3016
3017 /* Decodes URL-Encoded strings (i.e. strings in which spaces are replaced by
3018  * plusses, and escape characters are used)
3019  */
3020
3021 void decode_uri(gchar *decoded_uri, const gchar *encoded_uri)
3022 {
3023         const gchar *encoded;
3024         gchar *decoded;
3025
3026         encoded = encoded_uri;
3027         decoded = decoded_uri;
3028
3029         while (*encoded) {
3030                 if (*encoded == '%') {
3031                         encoded++;
3032                         if (isxdigit(encoded[0])
3033                             && isxdigit(encoded[1])) {
3034                                 *decoded = (gchar) axtoi(encoded);
3035                                 decoded++;
3036                                 encoded += 2;
3037                         }
3038                 }
3039                 else if (*encoded == '+') {
3040                         *decoded = ' ';
3041                         decoded++;
3042                         encoded++;
3043                 }
3044                 else {
3045                         *decoded = *encoded;
3046                         decoded++;
3047                         encoded++;
3048                 }
3049         }
3050
3051         *decoded = '\0';
3052 }
3053
3054
3055 gint open_uri(const gchar *uri, const gchar *cmdline)
3056 {
3057         gchar buf[BUFFSIZE];
3058         gchar *p;
3059         gchar encoded_uri[BUFFSIZE];
3060         
3061         g_return_val_if_fail(uri != NULL, -1);
3062
3063         /* an option to choose whether to use encode_uri or not ? */
3064         encode_uri(encoded_uri, BUFFSIZE, uri);
3065         
3066         if (cmdline &&
3067             (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
3068             !strchr(p + 2, '%'))
3069                 g_snprintf(buf, sizeof(buf), cmdline, encoded_uri);
3070         else {
3071                 if (cmdline)
3072                         g_warning("Open URI command line is invalid: `%s'",
3073                                   cmdline);
3074                 g_snprintf(buf, sizeof(buf), DEFAULT_BROWSER_CMD, encoded_uri);
3075         }
3076         
3077         execute_command_line(buf, TRUE);
3078
3079         return 0;
3080 }
3081
3082 time_t remote_tzoffset_sec(const gchar *zone)
3083 {
3084         static gchar ustzstr[] = "PSTPDTMSTMDTCSTCDTESTEDT";
3085         gchar zone3[4];
3086         gchar *p;
3087         gchar c;
3088         gint iustz;
3089         gint offset;
3090         time_t remoteoffset;
3091
3092         strncpy(zone3, zone, 3);
3093         zone3[3] = '\0';
3094         remoteoffset = 0;
3095
3096         if (sscanf(zone, "%c%d", &c, &offset) == 2 &&
3097             (c == '+' || c == '-')) {
3098                 remoteoffset = ((offset / 100) * 60 + (offset % 100)) * 60;
3099                 if (c == '-')
3100                         remoteoffset = -remoteoffset;
3101         } else if (!strncmp(zone, "UT" , 2) ||
3102                    !strncmp(zone, "GMT", 2)) {
3103                 remoteoffset = 0;
3104         } else if (strlen(zone3) == 3) {
3105                 for (p = ustzstr; *p != '\0'; p += 3) {
3106                         if (!strncasecmp(p, zone3, 3)) {
3107                                 iustz = ((gint)(p - ustzstr) / 3 + 1) / 2 - 8;
3108                                 remoteoffset = iustz * 3600;
3109                                 break;
3110                         }
3111                 }
3112                 if (*p == '\0')
3113                         return -1;
3114         } else if (strlen(zone3) == 1) {
3115                 switch (zone[0]) {
3116                 case 'Z': remoteoffset =   0; break;
3117                 case 'A': remoteoffset =  -1; break;
3118                 case 'B': remoteoffset =  -2; break;
3119                 case 'C': remoteoffset =  -3; break;
3120                 case 'D': remoteoffset =  -4; break;
3121                 case 'E': remoteoffset =  -5; break;
3122                 case 'F': remoteoffset =  -6; break;
3123                 case 'G': remoteoffset =  -7; break;
3124                 case 'H': remoteoffset =  -8; break;
3125                 case 'I': remoteoffset =  -9; break;
3126                 case 'K': remoteoffset = -10; break; /* J is not used */
3127                 case 'L': remoteoffset = -11; break;
3128                 case 'M': remoteoffset = -12; break;
3129                 case 'N': remoteoffset =   1; break;
3130                 case 'O': remoteoffset =   2; break;
3131                 case 'P': remoteoffset =   3; break;
3132                 case 'Q': remoteoffset =   4; break;
3133                 case 'R': remoteoffset =   5; break;
3134                 case 'S': remoteoffset =   6; break;
3135                 case 'T': remoteoffset =   7; break;
3136                 case 'U': remoteoffset =   8; break;
3137                 case 'V': remoteoffset =   9; break;
3138                 case 'W': remoteoffset =  10; break;
3139                 case 'X': remoteoffset =  11; break;
3140                 case 'Y': remoteoffset =  12; break;
3141                 default:  remoteoffset =   0; break;
3142                 }
3143                 remoteoffset = remoteoffset * 3600;
3144         } else
3145                 return -1;
3146
3147         return remoteoffset;
3148 }
3149
3150 time_t tzoffset_sec(time_t *now)
3151 {
3152         struct tm gmt, *lt;
3153         gint off;
3154
3155         gmt = *gmtime(now);
3156         lt = localtime(now);
3157
3158         off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
3159
3160         if (lt->tm_year < gmt.tm_year)
3161                 off -= 24 * 60;
3162         else if (lt->tm_year > gmt.tm_year)
3163                 off += 24 * 60;
3164         else if (lt->tm_yday < gmt.tm_yday)
3165                 off -= 24 * 60;
3166         else if (lt->tm_yday > gmt.tm_yday)
3167                 off += 24 * 60;
3168
3169         if (off >= 24 * 60)             /* should be impossible */
3170                 off = 23 * 60 + 59;     /* if not, insert silly value */
3171         if (off <= -24 * 60)
3172                 off = -(23 * 60 + 59);
3173
3174         return off * 60;
3175 }
3176
3177 /* calculate timezone offset */
3178 gchar *tzoffset(time_t *now)
3179 {
3180         static gchar offset_string[6];
3181         struct tm gmt, *lt;
3182         gint off;
3183         gchar sign = '+';
3184
3185         gmt = *gmtime(now);
3186         lt = localtime(now);
3187
3188         off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
3189
3190         if (lt->tm_year < gmt.tm_year)
3191                 off -= 24 * 60;
3192         else if (lt->tm_year > gmt.tm_year)
3193                 off += 24 * 60;
3194         else if (lt->tm_yday < gmt.tm_yday)
3195                 off -= 24 * 60;
3196         else if (lt->tm_yday > gmt.tm_yday)
3197                 off += 24 * 60;
3198
3199         if (off < 0) {
3200                 sign = '-';
3201                 off = -off;
3202         }
3203
3204         if (off >= 24 * 60)             /* should be impossible */
3205                 off = 23 * 60 + 59;     /* if not, insert silly value */
3206
3207         sprintf(offset_string, "%c%02d%02d", sign, off / 60, off % 60);
3208
3209         return offset_string;
3210 }
3211
3212 void get_rfc822_date(gchar *buf, gint len)
3213 {
3214         struct tm *lt;
3215         time_t t;
3216         gchar day[4], mon[4];
3217         gint dd, hh, mm, ss, yyyy;
3218
3219         t = time(NULL);
3220         lt = localtime(&t);
3221
3222         sscanf(asctime(lt), "%3s %3s %d %d:%d:%d %d\n",
3223                day, mon, &dd, &hh, &mm, &ss, &yyyy);
3224         g_snprintf(buf, len, "%s, %d %s %d %02d:%02d:%02d %s",
3225                    day, dd, mon, yyyy, hh, mm, ss, tzoffset(&t));
3226 }
3227
3228 void debug_set_mode(gboolean mode)
3229 {
3230         debug_mode = mode;
3231 }
3232
3233 gboolean debug_get_mode(void)
3234 {
3235         return debug_mode;
3236 }
3237
3238 void debug_print_real(const gchar *format, ...)
3239 {
3240         va_list args;
3241         gchar buf[BUFFSIZE];
3242
3243         if (!debug_mode) return;
3244
3245         va_start(args, format);
3246         g_vsnprintf(buf, sizeof(buf), format, args);
3247         va_end(args);
3248
3249         fputs(buf, stdout);
3250 }
3251
3252 void * subject_table_lookup(GHashTable *subject_table, gchar * subject)
3253 {
3254         if (subject == NULL)
3255                 subject = "";
3256         else
3257                 subject += subject_get_prefix_length(subject);
3258
3259         return g_hash_table_lookup(subject_table, subject);
3260 }
3261
3262 void subject_table_insert(GHashTable *subject_table, gchar * subject,
3263                           void * data)
3264 {
3265         if (subject == NULL || *subject == 0)
3266                 return;
3267         subject += subject_get_prefix_length(subject);
3268         g_hash_table_insert(subject_table, subject, data);
3269 }
3270
3271 void subject_table_remove(GHashTable *subject_table, gchar * subject)
3272 {
3273         if (subject == NULL)
3274                 return;
3275
3276         subject += subject_get_prefix_length(subject);  
3277         g_hash_table_remove(subject_table, subject);
3278 }
3279
3280 /*!
3281  *\brief        Check if a string is prefixed with known (combinations) 
3282  *              of prefixes. The function assumes that each prefix 
3283  *              is terminated by zero or exactly _one_ space.
3284  *
3285  *\param        str String to check for a prefixes
3286  *
3287  *\return       int Number of chars in the prefix that should be skipped 
3288  *              for a "clean" subject line. If no prefix was found, 0
3289  *              is returned.
3290  */             
3291 int subject_get_prefix_length(const gchar *subject)
3292 {
3293         /*!< Array with allowable reply prefixes regexps. */
3294         static const gchar * const prefixes[] = {
3295                 "Re\\:",                        /* "Re:" */
3296                 "Re\\[[1-9][0-9]*\\]\\:",       /* "Re[XXX]:" (non-conforming news mail clients) */
3297                 "Antw\\:",                      /* "Antw:" (Dutch / German Outlook) */
3298                 "Aw\\:",                        /* "Aw:"   (German) */
3299                 "Antwort\\:",                   /* "Antwort:" (German Lotus Notes) */
3300                 "Res\\:",                       /* "Res:" (Brazilian Outlook) */
3301                 "Fw\\:",                        /* "Fw:" Forward */
3302                 "Enc\\:"                        /* "Enc:" Forward (Brazilian Outlook) */
3303                 /* add more */
3304         };
3305         const int PREFIXES = sizeof prefixes / sizeof prefixes[0];
3306         int n;
3307         regmatch_t pos;
3308         static regex_t regex;
3309         static gboolean init_;
3310
3311         if (!subject) return 0;
3312         if (!*subject) return 0;
3313
3314         if (!init_) {
3315                 GString *s = g_string_new("");
3316                 
3317                 for (n = 0; n < PREFIXES; n++)
3318                         /* Terminate each prefix regexpression by a
3319                          * "\ ?" (zero or ONE space), and OR them */
3320                         g_string_sprintfa(s, "(%s\\ ?)%s",
3321                                           prefixes[n],
3322                                           n < PREFIXES - 1 ? 
3323                                           "|" : "");
3324                 
3325                 g_string_prepend(s, "(");
3326                 g_string_append(s, ")+");       /* match at least once */
3327                 g_string_prepend(s, "^\\ *");   /* from beginning of line */
3328                 
3329
3330                 /* We now have something like "^\ *((PREFIX1\ ?)|(PREFIX2\ ?))+" 
3331                  * TODO: Should this be       "^\ *(((PREFIX1)|(PREFIX2))\ ?)+" ??? */
3332                 if (regcomp(&regex, s->str, REG_EXTENDED | REG_ICASE)) { 
3333                         debug_print("Error compiling regexp %s\n", s->str);
3334                         g_string_free(s, TRUE);
3335                         return 0;
3336                 } else {
3337                         init_ = TRUE;
3338                         g_string_free(s, TRUE);
3339                 }
3340         }
3341         
3342         if (!regexec(&regex, subject, 1, &pos, 0) && pos.rm_so != -1)
3343                 return pos.rm_eo;
3344         else
3345                 return 0;
3346 }
3347
3348 FILE *get_tmpfile_in_dir(const gchar *dir, gchar **filename)
3349 {
3350         int fd;
3351         
3352         *filename = g_strdup_printf("%s%csylpheed.XXXXXX", dir, G_DIR_SEPARATOR);
3353         fd = mkstemp(*filename);
3354
3355         return fdopen(fd, "w+");
3356 }
3357
3358 /* allow Mutt-like patterns in quick search */
3359 gchar *expand_search_string(const gchar *search_string)
3360 {
3361         int i = 0;
3362         gchar term_char, save_char;
3363         gchar *cmd_start, *cmd_end;
3364         GString *matcherstr;
3365         gchar *returnstr = NULL;
3366         gchar *copy_str;
3367         gboolean casesens, dontmatch;
3368         /* list of allowed pattern abbreviations */
3369         struct {
3370                 gchar           *abbreviated;   /* abbreviation */
3371                 gchar           *command;       /* actual matcher command */ 
3372                 gint            numparams;      /* number of params for cmd */
3373                 gboolean        qualifier;      /* do we append regexpcase */
3374                 gboolean        quotes;         /* do we need quotes */
3375         }
3376         cmds[] = {
3377                 { "a",  "all",                          0,      FALSE,  FALSE },
3378                 { "ag", "age_greater",                  1,      FALSE,  FALSE },
3379                 { "al", "age_lower",                    1,      FALSE,  FALSE },
3380                 { "b",  "body_part",                    1,      TRUE,   TRUE  },
3381                 { "B",  "message",                      1,      TRUE,   TRUE  },
3382                 { "c",  "cc",                           1,      TRUE,   TRUE  },
3383                 { "C",  "to_or_cc",                     1,      TRUE,   TRUE  },
3384                 { "D",  "deleted",                      0,      FALSE,  FALSE },
3385                 { "e",  "header \"Sender\"",            1,      TRUE,   TRUE  },
3386                 { "E",  "execute",                      1,      FALSE,  TRUE  },
3387                 { "f",  "from",                         1,      TRUE,   TRUE  },
3388                 { "F",  "forwarded",                    0,      FALSE,  FALSE },
3389                 { "h",  "headers_part",                 1,      TRUE,   TRUE  },
3390                 { "i",  "header \"Message-Id\"",        1,      TRUE,   TRUE  },
3391                 { "I",  "inreplyto",                    1,      TRUE,   TRUE  },
3392                 { "L",  "locked",                       0,      FALSE,  FALSE },
3393                 { "n",  "newsgroups",                   1,      TRUE,   TRUE  },
3394                 { "N",  "new",                          0,      FALSE,  FALSE },
3395                 { "O",  "~new",                         0,      FALSE,  FALSE },
3396                 { "r",  "replied",                      0,      FALSE,  FALSE },
3397                 { "R",  "~unread",                      0,      FALSE,  FALSE },
3398                 { "s",  "subject",                      1,      TRUE,   TRUE  },
3399                 { "se", "score_equal",                  1,      FALSE,  FALSE },
3400                 { "sg", "score_greater",                1,      FALSE,  FALSE },
3401                 { "sl", "score_lower",                  1,      FALSE,  FALSE },
3402                 { "Se", "size_equal",                   1,      FALSE,  FALSE },
3403                 { "Sg", "size_greater",                 1,      FALSE,  FALSE },
3404                 { "Ss", "size_smaller",                 1,      FALSE,  FALSE },
3405                 { "t",  "to",                           1,      TRUE,   TRUE  },
3406                 { "T",  "marked",                       0,      FALSE,  FALSE },
3407                 { "U",  "unread",                       0,      FALSE,  FALSE },
3408                 { "x",  "header \"References\"",        1,      TRUE,   TRUE  },
3409                 { "y",  "header \"X-Label\"",           1,      TRUE,   TRUE  },
3410                 { "&",  "&",                            0,      FALSE,  FALSE },
3411                 { "|",  "|",                            0,      FALSE,  FALSE },
3412                 { NULL, NULL,                           0,      FALSE,  FALSE }
3413         };
3414
3415         if (search_string == NULL)
3416                 return NULL;
3417
3418         copy_str = g_strdup(search_string);
3419
3420         /* if it's a full command don't process it so users
3421            can still do something like from regexpcase "foo" */
3422         for (i = 0; cmds[i].command; i++) {
3423                 const gchar *tmp_search_string = search_string;
3424                 cmd_start = cmds[i].command;
3425                 /* allow logical NOT */
3426                 if (*tmp_search_string == '~')
3427                         tmp_search_string++;
3428                 if (!strncmp(tmp_search_string, cmd_start, strlen(cmd_start)))
3429                         break;
3430         }
3431         if (cmds[i].command)
3432                 return copy_str;
3433
3434         matcherstr = g_string_sized_new(16);
3435         cmd_start = cmd_end = copy_str;
3436         while (cmd_end && *cmd_end) {
3437                 /* skip all white spaces */
3438                 while (*cmd_end && isspace(*cmd_end))
3439                         cmd_end++;
3440
3441                 /* extract a command */
3442                 while (*cmd_end && !isspace(*cmd_end))
3443                         cmd_end++;
3444
3445                 /* save character */
3446                 save_char = *cmd_end;
3447                 *cmd_end = '\0';
3448
3449                 dontmatch = FALSE;
3450                 casesens = FALSE;
3451
3452                 /* ~ and ! mean logical NOT */
3453                 if (*cmd_start == '~' || *cmd_start == '!')
3454                 {
3455                         dontmatch = TRUE;
3456                         cmd_start++;
3457                 }
3458                 /* % means case sensitive match */
3459                 if (*cmd_start == '%')
3460                 {
3461                         casesens = TRUE;
3462                         cmd_start++;
3463                 }
3464
3465                 /* find matching abbreviation */
3466                 for (i = 0; cmds[i].command; i++) {
3467                         if (!strcmp(cmd_start, cmds[i].abbreviated)) {
3468                                 /* restore character */
3469                                 *cmd_end = save_char;
3470
3471                                 /* copy command */
3472                                 if (matcherstr->len > 0) {
3473                                         g_string_append(matcherstr, " ");
3474                                 }
3475                                 if (dontmatch)
3476                                         g_string_append(matcherstr, "~");
3477                                 g_string_append(matcherstr, cmds[i].command);
3478                                 g_string_append(matcherstr, " ");
3479
3480                                 /* stop if no params required */
3481                                 if (cmds[i].numparams == 0)
3482                                         break;
3483
3484                                 /* extract a parameter, allow quotes */
3485                                 cmd_end++;
3486                                 cmd_start = cmd_end;
3487                                 if (*cmd_start == '"') {
3488                                         term_char = '"';
3489                                         cmd_end++;
3490                                 }
3491                                 else
3492                                         term_char = ' ';
3493
3494                                 /* extract actual parameter */
3495                                 while ((*cmd_end) && (*cmd_end != term_char))
3496                                         cmd_end++;
3497
3498                                 if (*cmd_end && (*cmd_end != term_char))
3499                                         break;
3500
3501                                 if (*cmd_end == '"')
3502                                         cmd_end++;
3503
3504                                 save_char = *cmd_end;
3505                                 *cmd_end = '\0';
3506
3507                                 if (cmds[i].qualifier) {
3508                                         if (casesens)
3509                                                 g_string_append(matcherstr, "regexp ");
3510                                         else
3511                                                 g_string_append(matcherstr, "regexpcase ");
3512                                 }
3513
3514                                 /* do we need to add quotes ? */
3515                                 if (cmds[i].quotes && term_char != '"')
3516                                         g_string_append(matcherstr, "\"");
3517
3518                                 /* copy actual parameter */
3519                                 g_string_append(matcherstr, cmd_start);
3520
3521                                 /* do we need to add quotes ? */
3522                                 if (cmds[i].quotes && term_char != '"')
3523                                         g_string_append(matcherstr, "\"");
3524
3525                                 /* restore original character */
3526                                 *cmd_end = save_char;
3527
3528                                 break;
3529                         }
3530                 }
3531
3532                 if (*cmd_end) {
3533                         cmd_end++;
3534                         cmd_start = cmd_end;
3535                 }
3536         }
3537
3538         g_free(copy_str);
3539         returnstr = matcherstr->str;
3540         g_string_free(matcherstr, FALSE);
3541         return returnstr;
3542 }
3543
3544 guint g_stricase_hash(gconstpointer gptr)
3545 {
3546         guint hash_result = 0;
3547         const char *str;
3548
3549         for (str = gptr; str && *str; str++) {
3550                 if (isupper(*str)) hash_result += (*str + ' ');
3551                 else hash_result += *str;
3552         }
3553
3554         return hash_result;
3555 }
3556
3557 gint g_stricase_equal(gconstpointer gptr1, gconstpointer gptr2)
3558 {
3559         const char *str1 = gptr1;
3560         const char *str2 = gptr2;
3561
3562         return !strcasecmp(str1, str2);
3563 }
3564
3565 gint g_int_compare(gconstpointer a, gconstpointer b)
3566 {
3567         return GPOINTER_TO_INT(a) - GPOINTER_TO_INT(b);
3568 }