inital gtk2 patch
[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 #include "../codeconv.h"
50
51 #define BUFFSIZE        8192
52
53 static gboolean debug_mode = FALSE;
54
55 static void hash_free_strings_func(gpointer key, gpointer value, gpointer data);
56
57 void list_free_strings(GList *list)
58 {
59         list = g_list_first(list);
60
61         while (list != NULL) {
62                 g_free(list->data);
63                 list = list->next;
64         }
65 }
66
67 void slist_free_strings(GSList *list)
68 {
69         while (list != NULL) {
70                 g_free(list->data);
71                 list = list->next;
72         }
73 }
74
75 GSList *slist_concat_unique (GSList *first, GSList *second)
76 {
77         GSList *tmp, *ret;
78         if (first == NULL) {
79                 if (second == NULL)
80                         return NULL;
81                 else 
82                         return second;
83         } else if (second == NULL)
84                 return first;
85         ret = first;
86         for (tmp = second; tmp != NULL; tmp = g_slist_next(tmp)) {
87                 if (g_slist_find(ret, tmp->data) == NULL)
88                         ret = g_slist_prepend(ret, tmp->data);
89         }
90         return ret;
91 }
92  
93 static void hash_free_strings_func(gpointer key, gpointer value, gpointer data)
94 {
95         g_free(key);
96 }
97
98 void hash_free_strings(GHashTable *table)
99 {
100         g_hash_table_foreach(table, hash_free_strings_func, NULL);
101 }
102
103 static void hash_free_value_mem_func(gpointer key, gpointer value,
104                                      gpointer data)
105 {
106         g_free(value);
107 }
108
109 void hash_free_value_mem(GHashTable *table)
110 {
111         g_hash_table_foreach(table, hash_free_value_mem_func, NULL);
112 }
113
114 gint str_case_equal(gconstpointer v, gconstpointer v2)
115 {
116         return strcasecmp((const gchar *)v, (const gchar *)v2) == 0;
117 }
118
119 guint str_case_hash(gconstpointer key)
120 {
121         const gchar *p = key;
122         guint h = *p;
123
124         if (h) {
125                 h = tolower(h);
126                 for (p += 1; *p != '\0'; p++)
127                         h = (h << 5) - h + tolower(*p);
128         }
129
130         return h;
131 }
132
133 void ptr_array_free_strings(GPtrArray *array)
134 {
135         gint i;
136         gchar *str;
137
138         g_return_if_fail(array != NULL);
139
140         for (i = 0; i < array->len; i++) {
141                 str = g_ptr_array_index(array, i);
142                 g_free(str);
143         }
144 }
145
146 gint to_number(const gchar *nstr)
147 {
148         register const gchar *p;
149
150         if (*nstr == '\0') return -1;
151
152         for (p = nstr; *p != '\0'; p++)
153                 if (!isdigit(*p)) return -1;
154
155         return atoi(nstr);
156 }
157
158 /* convert integer into string,
159    nstr must be not lower than 11 characters length */
160 gchar *itos_buf(gchar *nstr, gint n)
161 {
162         g_snprintf(nstr, 11, "%d", n);
163         return nstr;
164 }
165
166 /* convert integer into string */
167 gchar *itos(gint n)
168 {
169         static gchar nstr[11];
170
171         return itos_buf(nstr, n);
172 }
173
174 gchar *to_human_readable(off_t size)
175 {
176         static gchar str[10];
177
178         if (size < 1024)
179                 g_snprintf(str, sizeof(str), _("%dB"), (gint)size);
180         else if (size >> 10 < 1024)
181                 g_snprintf(str, sizeof(str), _("%.1fKB"), (gfloat)size / (1 << 10));
182         else if (size >> 20 < 1024)
183                 g_snprintf(str, sizeof(str), _("%.2fMB"), (gfloat)size / (1 << 20));
184         else
185                 g_snprintf(str, sizeof(str), _("%.2fGB"), (gfloat)size / (1 << 30));
186
187         return str;
188 }
189
190 /* strcmp with NULL-checking */
191 gint strcmp2(const gchar *s1, const gchar *s2)
192 {
193         if (s1 == NULL || s2 == NULL)
194                 return -1;
195         else
196                 return strcmp(s1, s2);
197 }
198 /* strstr with NULL-checking */
199 gchar *strstr2(const gchar *s1, const gchar *s2)
200 {
201         if (s1 == NULL || s2 == NULL)
202                 return NULL;
203         else
204                 return strstr(s1, s2);
205 }
206 /* compare paths */
207 gint path_cmp(const gchar *s1, const gchar *s2)
208 {
209         gint len1, len2;
210
211         if (s1 == NULL || s2 == NULL) return -1;
212         if (*s1 == '\0' || *s2 == '\0') return -1;
213
214         len1 = strlen(s1);
215         len2 = strlen(s2);
216
217         if (s1[len1 - 1] == G_DIR_SEPARATOR) len1--;
218         if (s2[len2 - 1] == G_DIR_SEPARATOR) len2--;
219
220         return strncmp(s1, s2, MAX(len1, len2));
221 }
222
223 /* remove trailing return code */
224 gchar *strretchomp(gchar *str)
225 {
226         register gchar *s;
227
228         if (!*str) return str;
229
230         for (s = str + strlen(str) - 1;
231              s >= str && (*s == '\n' || *s == '\r');
232              s--)
233                 *s = '\0';
234
235         return str;
236 }
237
238 /* remove trailing character */
239 gchar *strtailchomp(gchar *str, gchar tail_char)
240 {
241         register gchar *s;
242
243         if (!*str) return str;
244         if (tail_char == '\0') return str;
245
246         for (s = str + strlen(str) - 1; s >= str && *s == tail_char; s--)
247                 *s = '\0';
248
249         return str;
250 }
251
252 /* remove CR (carriage return) */
253 gchar *strcrchomp(gchar *str)
254 {
255         register gchar *s;
256
257         if (!*str) return str;
258
259         s = str + strlen(str) - 1;
260         if (*s == '\n' && s > str && *(s - 1) == '\r') {
261                 *(s - 1) = '\n';
262                 *s = '\0';
263         }
264
265         return str;
266 }
267
268 /* Similar to `strstr' but this function ignores the case of both strings.  */
269 gchar *strcasestr(const gchar *haystack, const gchar *needle)
270 {
271         register size_t haystack_len, needle_len;
272
273         haystack_len = strlen(haystack);
274         needle_len   = strlen(needle);
275
276         if (haystack_len < needle_len || needle_len == 0)
277                 return NULL;
278
279         while (haystack_len >= needle_len) {
280                 if (!strncasecmp(haystack, needle, needle_len))
281                         return (gchar *)haystack;
282                 else {
283                         haystack++;
284                         haystack_len--;
285                 }
286         }
287
288         return NULL;
289 }
290
291 /* Copy no more than N characters of SRC to DEST, with NULL terminating.  */
292 gchar *strncpy2(gchar *dest, const gchar *src, size_t n)
293 {
294         register gchar c;
295         gchar *s = dest;
296
297         do {
298                 if (--n == 0) {
299                         *dest = '\0';
300                         return s;
301                 }
302                 c = *src++;
303                 *dest++ = c;
304         } while (c != '\0');
305
306         /* don't do zero fill */
307         return s;
308 }
309
310 #if !HAVE_ISWALNUM
311 int iswalnum(wint_t wc)
312 {
313         return isalnum((int)wc);
314 }
315 #endif
316
317 #if !HAVE_ISWSPACE
318 int iswspace(wint_t wc)
319 {
320         return isspace((int)wc);
321 }
322 #endif
323
324 #if !HAVE_TOWLOWER
325 wint_t towlower(wint_t wc)
326 {
327         if (wc >= L'A' && wc <= L'Z')
328                 return wc + L'a' - L'A';
329
330         return wc;
331 }
332 #endif
333
334 #if !HAVE_WCSLEN
335 size_t wcslen(const wchar_t *s)
336 {
337         size_t len = 0;
338
339         while (*s != L'\0')
340                 ++len, ++s;
341
342         return len;
343 }
344 #endif
345
346 #if !HAVE_WCSCPY
347 /* Copy SRC to DEST.  */
348 wchar_t *wcscpy(wchar_t *dest, const wchar_t *src)
349 {
350         wint_t c;
351         wchar_t *s = dest;
352
353         do {
354                 c = *src++;
355                 *dest++ = c;
356         } while (c != L'\0');
357
358         return s;
359 }
360 #endif
361
362 #if !HAVE_WCSNCPY
363 /* Copy no more than N wide-characters of SRC to DEST.  */
364 wchar_t *wcsncpy (wchar_t *dest, const wchar_t *src, size_t n)
365 {
366         wint_t c;
367         wchar_t *s = dest;
368
369         do {
370                 c = *src++;
371                 *dest++ = c;
372                 if (--n == 0)
373                         return s;
374         } while (c != L'\0');
375
376         /* zero fill */
377         do
378                 *dest++ = L'\0';
379         while (--n > 0);
380
381         return s;
382 }
383 #endif
384
385 /* Duplicate S, returning an identical malloc'd string. */
386 wchar_t *wcsdup(const wchar_t *s)
387 {
388         wchar_t *new_str;
389
390         if (s) {
391                 new_str = g_new(wchar_t, wcslen(s) + 1);
392                 wcscpy(new_str, s);
393         } else
394                 new_str = NULL;
395
396         return new_str;
397 }
398
399 /* Duplicate no more than N wide-characters of S,
400    returning an identical malloc'd string. */
401 wchar_t *wcsndup(const wchar_t *s, size_t n)
402 {
403         wchar_t *new_str;
404
405         if (s) {
406                 new_str = g_new(wchar_t, n + 1);
407                 wcsncpy(new_str, s, n);
408                 new_str[n] = (wchar_t)0;
409         } else
410                 new_str = NULL;
411
412         return new_str;
413 }
414
415 wchar_t *strdup_mbstowcs(const gchar *s)
416 {
417         wchar_t *new_str;
418
419         if (s) {
420                 new_str = g_new(wchar_t, strlen(s) + 1);
421                 if (mbstowcs(new_str, s, strlen(s) + 1) < 0) {
422                         g_free(new_str);
423                         new_str = NULL;
424                 } else
425                         new_str = g_realloc(new_str,
426                                             sizeof(wchar_t) * (wcslen(new_str) + 1));
427         } else
428                 new_str = NULL;
429
430         return new_str;
431 }
432
433 gchar *strdup_wcstombs(const wchar_t *s)
434 {
435         gchar *new_str;
436         size_t len;
437
438         if (s) {
439                 len = wcslen(s) * MB_CUR_MAX + 1;
440                 new_str = g_new(gchar, len);
441                 if (wcstombs(new_str, s, len) < 0) {
442                         g_free(new_str);
443                         new_str = NULL;
444                 } else
445                         new_str = g_realloc(new_str, strlen(new_str) + 1);
446         } else
447                 new_str = NULL;
448
449         return new_str;
450 }
451
452 /* Compare S1 and S2, ignoring case.  */
453 gint wcsncasecmp(const wchar_t *s1, const wchar_t *s2, size_t n)
454 {
455         wint_t c1;
456         wint_t c2;
457
458         while (n--) {
459                 c1 = towlower(*s1++);
460                 c2 = towlower(*s2++);
461                 if (c1 != c2)
462                         return c1 - c2;
463                 else if (c1 == 0 && c2 == 0)
464                         break;
465         }
466
467         return 0;
468 }
469
470 /* Find the first occurrence of NEEDLE in HAYSTACK, ignoring case.  */
471 wchar_t *wcscasestr(const wchar_t *haystack, const wchar_t *needle)
472 {
473         register size_t haystack_len, needle_len;
474
475         haystack_len = wcslen(haystack);
476         needle_len   = wcslen(needle);
477
478         if (haystack_len < needle_len || needle_len == 0)
479                 return NULL;
480
481         while (haystack_len >= needle_len) {
482                 if (!wcsncasecmp(haystack, needle, needle_len))
483                         return (wchar_t *)haystack;
484                 else {
485                         haystack++;
486                         haystack_len--;
487                 }
488         }
489
490         return NULL;
491 }
492
493 gint get_mbs_len(const gchar *s)
494 {
495         const gchar *p = s;
496         gint mb_len;
497         gint len = 0;
498
499         if (!p)
500                 return -1;
501
502         while (*p != '\0') {
503                 mb_len = mblen(p, MB_LEN_MAX);
504                 if (mb_len == 0)
505                         break;
506                 else if (mb_len < 0)
507                         return -1;
508                 else
509                         len++;
510
511                 p += mb_len;
512         }
513
514         return len;
515 }
516
517 /* Examine if next block is non-ASCII string */
518 gboolean is_next_nonascii(const guchar *s)
519 {
520         const guchar *p;
521
522         /* skip head space */
523         for (p = s; *p != '\0' && isspace(*p); p++)
524                 ;
525         for (; *p != '\0' && !isspace(*p); p++) {
526                 if (*p > 127 || *p < 32)
527                         return TRUE;
528         }
529
530         return FALSE;
531 }
532
533 gint get_next_word_len(const gchar *s)
534 {
535         gint len = 0;
536
537         for (; *s != '\0' && !isspace(*s); s++, len++)
538                 ;
539
540         return len;
541 }
542
543 /* compare subjects */
544 gint subject_compare(const gchar *s1, const gchar *s2)
545 {
546         gchar *str1, *str2;
547
548         if (!s1 || !s2) return -1;
549         if (!*s1 || !*s2) return -1;
550
551         Xstrdup_a(str1, s1, return -1);
552         Xstrdup_a(str2, s2, return -1);
553
554         trim_subject_for_compare(str1);
555         trim_subject_for_compare(str2);
556
557         if (!*str1 || !*str2) return -1;
558
559         return strcmp(str1, str2);
560 }
561
562 gint subject_compare_for_sort(const gchar *s1, const gchar *s2)
563 {
564         gchar *str1, *str2;
565
566         if (!s1 || !s2) return -1;
567
568         Xstrdup_a(str1, s1, return -1);
569         Xstrdup_a(str2, s2, return -1);
570
571         trim_subject_for_sort(str1);
572         trim_subject_for_sort(str2);
573
574         return strcasecmp(str1, str2);
575 }
576
577 void trim_subject_for_compare(gchar *str)
578 {
579         gchar *srcp;
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 *escaped_utf8uri;
1398
1399         p = uri_list;
1400
1401         while (p) {
1402                 if (*p != '#') {
1403                         while (isspace(*p)) p++;
1404                         if (!strncmp(p, "file:", 5)) {
1405                                 q = p;
1406                                 q += 5;
1407                                 while (*q && *q != '\n' && *q != '\r') q++;
1408
1409                                 if (q > p) {
1410                                         gchar *file, *locale_file = NULL;
1411                                         q--;
1412                                         while (q > p && isspace(*q)) q--;
1413                                         Xalloca(escaped_utf8uri, q - p + 2,
1414                                                 return result);
1415                                         Xalloca(file, q - p + 2,
1416                                                 return result);
1417                                         *file = '\0';
1418                                         strncpy(escaped_utf8uri, p, q - p + 1);
1419                                         escaped_utf8uri[q - p + 1] = '\0';
1420                                         decode_uri(file, escaped_utf8uri);
1421 #warning FIXME_GTK2 /* should we use g_filename_from_utf8()? */
1422                     /*
1423                      * g_filename_from_uri() rejects escaped/locale encoded uri
1424                      * string which come from Nautilus.
1425                      */
1426                                         if (g_utf8_validate(file, -1, NULL))
1427                                                 locale_file
1428                                                         = conv_codeset_strdup(
1429                                                                 file + 5,
1430                                                                 CS_UTF_8,
1431                                                                 conv_get_current_charset_str());
1432                                         if (!locale_file)
1433                                                 locale_file = g_strdup(file + 5);
1434                                         result = g_list_append(result, locale_file);
1435                                 }
1436                         }
1437                 }
1438                 p = strchr(p, '\n');
1439                 if (p) p++;
1440         }
1441
1442         return result;
1443 }
1444
1445 #define HEX_TO_INT(val, hex) \
1446 { \
1447         gchar c = hex; \
1448  \
1449         if ('0' <= c && c <= '9') { \
1450                 val = c - '0'; \
1451         } else if ('a' <= c && c <= 'f') { \
1452                 val = c - 'a' + 10; \
1453         } else if ('A' <= c && c <= 'F') { \
1454                 val = c - 'A' + 10; \
1455         } else { \
1456                 val = 0; \
1457         } \
1458 }
1459
1460 gint scan_mailto_url(const gchar *mailto, gchar **to, gchar **cc, gchar **bcc,
1461                      gchar **subject, gchar **body)
1462 {
1463         gchar *tmp_mailto;
1464         gchar *p;
1465
1466         Xstrdup_a(tmp_mailto, mailto, return -1);
1467
1468         if (!strncmp(tmp_mailto, "mailto:", 7))
1469                 tmp_mailto += 7;
1470
1471         p = strchr(tmp_mailto, '?');
1472         if (p) {
1473                 *p = '\0';
1474                 p++;
1475         }
1476
1477         if (to && !*to)
1478                 *to = g_strdup(tmp_mailto);
1479
1480         while (p) {
1481                 gchar *field, *value;
1482
1483                 field = p;
1484
1485                 p = strchr(p, '=');
1486                 if (!p) break;
1487                 *p = '\0';
1488                 p++;
1489
1490                 value = p;
1491
1492                 p = strchr(p, '&');
1493                 if (p) {
1494                         *p = '\0';
1495                         p++;
1496                 }
1497
1498                 if (*value == '\0') continue;
1499
1500                 if (cc && !*cc && !g_strcasecmp(field, "cc")) {
1501                         *cc = g_strdup(value);
1502                 } else if (bcc && !*bcc && !g_strcasecmp(field, "bcc")) {
1503                         *bcc = g_strdup(value);
1504                 } else if (subject && !*subject &&
1505                            !g_strcasecmp(field, "subject")) {
1506                         *subject = g_malloc(strlen(value) + 1);
1507                         decode_uri(*subject, value);
1508                 } else if (body && !*body && !g_strcasecmp(field, "body")) {
1509                         *body = g_malloc(strlen(value) + 1);
1510                         decode_uri(*body, value);
1511                 }
1512         }
1513
1514         return 0;
1515 }
1516
1517 /*
1518  * We need this wrapper around g_get_home_dir(), so that
1519  * we can fix some Windoze things here.  Should be done in glibc of course
1520  * but as long as we are not able to do our own extensions to glibc, we do
1521  * it here.
1522  */
1523 const gchar *get_home_dir(void)
1524 {
1525 #if HAVE_DOSISH_SYSTEM
1526     static gchar *home_dir;
1527
1528     if (!home_dir) {
1529         home_dir = read_w32_registry_string(NULL,
1530                                             "Software\\Sylpheed", "HomeDir" );
1531         if (!home_dir || !*home_dir) {
1532             if (getenv ("HOMEDRIVE") && getenv("HOMEPATH")) {
1533                 const char *s = g_get_home_dir();
1534                 if (s && *s)
1535                     home_dir = g_strdup (s);
1536             }
1537             if (!home_dir || !*home_dir) 
1538                 home_dir = g_strdup ("c:\\sylpheed");
1539         }
1540         debug_print("initialized home_dir to `%s'\n", home_dir);
1541     }
1542     return home_dir;
1543 #else /* standard glib */
1544     return g_get_home_dir();
1545 #endif
1546 }
1547
1548 const gchar *get_rc_dir(void)
1549 {
1550         static gchar *rc_dir = NULL;
1551
1552         if (!rc_dir)
1553                 rc_dir = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
1554                                      RC_DIR, NULL);
1555
1556         return rc_dir;
1557 }
1558
1559 const gchar *get_news_cache_dir(void)
1560 {
1561         static gchar *news_cache_dir = NULL;
1562
1563         if (!news_cache_dir)
1564                 news_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1565                                              NEWS_CACHE_DIR, NULL);
1566
1567         return news_cache_dir;
1568 }
1569
1570 const gchar *get_imap_cache_dir(void)
1571 {
1572         static gchar *imap_cache_dir = NULL;
1573
1574         if (!imap_cache_dir)
1575                 imap_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1576                                              IMAP_CACHE_DIR, NULL);
1577
1578         return imap_cache_dir;
1579 }
1580
1581 const gchar *get_mbox_cache_dir(void)
1582 {
1583         static gchar *mbox_cache_dir = NULL;
1584
1585         if (!mbox_cache_dir)
1586                 mbox_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1587                                              MBOX_CACHE_DIR, NULL);
1588
1589         return mbox_cache_dir;
1590 }
1591
1592 const gchar *get_mime_tmp_dir(void)
1593 {
1594         static gchar *mime_tmp_dir = NULL;
1595
1596         if (!mime_tmp_dir)
1597                 mime_tmp_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1598                                            MIME_TMP_DIR, NULL);
1599
1600         return mime_tmp_dir;
1601 }
1602
1603 const gchar *get_template_dir(void)
1604 {
1605         static gchar *template_dir = NULL;
1606
1607         if (!template_dir)
1608                 template_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1609                                            TEMPLATE_DIR, NULL);
1610
1611         return template_dir;
1612 }
1613
1614 const gchar *get_header_cache_dir(void)
1615 {
1616         static gchar *header_dir = NULL;
1617
1618         if (!header_dir)
1619                 header_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1620                                          HEADER_CACHE_DIR, NULL);
1621
1622         return header_dir;
1623 }
1624
1625 const gchar *get_tmp_dir(void)
1626 {
1627         static gchar *tmp_dir = NULL;
1628
1629         if (!tmp_dir)
1630                 tmp_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1631                                       TMP_DIR, NULL);
1632
1633         return tmp_dir;
1634 }
1635
1636 gchar *get_tmp_file(void)
1637 {
1638         gchar *tmp_file;
1639         static guint32 id = 0;
1640
1641         tmp_file = g_strdup_printf("%s%ctmpfile.%08x",
1642                                    get_tmp_dir(), G_DIR_SEPARATOR, id++);
1643
1644         return tmp_file;
1645 }
1646
1647 const gchar *get_domain_name(void)
1648 {
1649         static gchar *domain_name = NULL;
1650
1651         if (!domain_name) {
1652                 gchar buf[128] = "";
1653                 struct hostent *hp;
1654
1655                 if (gethostname(buf, sizeof(buf)) < 0) {
1656                         perror("gethostname");
1657                         domain_name = "unknown";
1658                 } else {
1659                         buf[sizeof(buf) - 1] = '\0';
1660                         if ((hp = my_gethostbyname(buf)) == NULL) {
1661                                 perror("gethostbyname");
1662                                 domain_name = g_strdup(buf);
1663                         } else {
1664                                 domain_name = g_strdup(hp->h_name);
1665                         }
1666                 }
1667
1668                 debug_print("domain name = %s\n", domain_name);
1669         }
1670
1671         return domain_name;
1672 }
1673
1674 off_t get_file_size(const gchar *file)
1675 {
1676         struct stat s;
1677
1678         if (stat(file, &s) < 0) {
1679                 FILE_OP_ERROR(file, "stat");
1680                 return -1;
1681         }
1682
1683         return s.st_size;
1684 }
1685
1686 off_t get_file_size_as_crlf(const gchar *file)
1687 {
1688         FILE *fp;
1689         off_t size = 0;
1690         gchar buf[BUFFSIZE];
1691
1692         if ((fp = fopen(file, "rb")) == NULL) {
1693                 FILE_OP_ERROR(file, "fopen");
1694                 return -1;
1695         }
1696
1697         while (fgets(buf, sizeof(buf), fp) != NULL) {
1698                 strretchomp(buf);
1699                 size += strlen(buf) + 2;
1700         }
1701
1702         if (ferror(fp)) {
1703                 FILE_OP_ERROR(file, "fgets");
1704                 size = -1;
1705         }
1706
1707         fclose(fp);
1708
1709         return size;
1710 }
1711
1712 off_t get_left_file_size(FILE *fp)
1713 {
1714         glong pos;
1715         glong end;
1716         off_t size;
1717
1718         if ((pos = ftell(fp)) < 0) {
1719                 perror("ftell");
1720                 return -1;
1721         }
1722         if (fseek(fp, 0L, SEEK_END) < 0) {
1723                 perror("fseek");
1724                 return -1;
1725         }
1726         if ((end = ftell(fp)) < 0) {
1727                 perror("fseek");
1728                 return -1;
1729         }
1730         size = end - pos;
1731         if (fseek(fp, pos, SEEK_SET) < 0) {
1732                 perror("fseek");
1733                 return -1;
1734         }
1735
1736         return size;
1737 }
1738
1739 gboolean file_exist(const gchar *file, gboolean allow_fifo)
1740 {
1741         struct stat s;
1742
1743         if (file == NULL)
1744                 return FALSE;
1745
1746         if (stat(file, &s) < 0) {
1747                 if (ENOENT != errno) FILE_OP_ERROR(file, "stat");
1748                 return FALSE;
1749         }
1750
1751         if (S_ISREG(s.st_mode) || (allow_fifo && S_ISFIFO(s.st_mode)))
1752                 return TRUE;
1753
1754         return FALSE;
1755 }
1756
1757 gboolean is_dir_exist(const gchar *dir)
1758 {
1759         struct stat s;
1760
1761         if (dir == NULL)
1762                 return FALSE;
1763
1764         if (stat(dir, &s) < 0) {
1765                 if (ENOENT != errno) FILE_OP_ERROR(dir, "stat");
1766                 return FALSE;
1767         }
1768
1769         if (S_ISDIR(s.st_mode))
1770                 return TRUE;
1771
1772         return FALSE;
1773 }
1774
1775 gboolean is_file_entry_exist(const gchar *file)
1776 {
1777         struct stat s;
1778
1779         if (file == NULL)
1780                 return FALSE;
1781
1782         if (stat(file, &s) < 0) {
1783                 if (ENOENT != errno) FILE_OP_ERROR(file, "stat");
1784                 return FALSE;
1785         }
1786
1787         return TRUE;
1788 }
1789
1790 gint change_dir(const gchar *dir)
1791 {
1792         gchar *prevdir = NULL;
1793
1794         if (debug_mode)
1795                 prevdir = g_get_current_dir();
1796
1797         if (chdir(dir) < 0) {
1798                 FILE_OP_ERROR(dir, "chdir");
1799                 if (debug_mode) g_free(prevdir);
1800                 return -1;
1801         } else if (debug_mode) {
1802                 gchar *cwd;
1803
1804                 cwd = g_get_current_dir();
1805                 if (strcmp(prevdir, cwd) != 0)
1806                         g_print("current dir: %s\n", cwd);
1807                 g_free(cwd);
1808                 g_free(prevdir);
1809         }
1810
1811         return 0;
1812 }
1813
1814 gint make_dir(const gchar *dir)
1815 {
1816         if (mkdir(dir, S_IRWXU) < 0) {
1817                 FILE_OP_ERROR(dir, "mkdir");
1818                 return -1;
1819         }
1820         if (chmod(dir, S_IRWXU) < 0)
1821                 FILE_OP_ERROR(dir, "chmod");
1822
1823         return 0;
1824 }
1825
1826 gint make_dir_hier(const gchar *dir)
1827 {
1828         gchar *parent_dir;
1829         const gchar *p;
1830
1831         for (p = dir; (p = strchr(p, G_DIR_SEPARATOR)) != NULL; p++) {
1832                 parent_dir = g_strndup(dir, p - dir);
1833                 if (*parent_dir != '\0') {
1834                         if (!is_dir_exist(parent_dir)) {
1835                                 if (make_dir(parent_dir) < 0) {
1836                                         g_free(parent_dir);
1837                                         return -1;
1838                                 }
1839                         }
1840                 }
1841                 g_free(parent_dir);
1842         }
1843
1844         if (!is_dir_exist(dir)) {
1845                 if (make_dir(dir) < 0)
1846                         return -1;
1847         }
1848
1849         return 0;
1850 }
1851
1852 gint remove_all_files(const gchar *dir)
1853 {
1854         DIR *dp;
1855         struct dirent *d;
1856         gchar *prev_dir;
1857
1858         prev_dir = g_get_current_dir();
1859
1860         if (chdir(dir) < 0) {
1861                 FILE_OP_ERROR(dir, "chdir");
1862                 g_free(prev_dir);
1863                 return -1;
1864         }
1865
1866         if ((dp = opendir(".")) == NULL) {
1867                 FILE_OP_ERROR(dir, "opendir");
1868                 g_free(prev_dir);
1869                 return -1;
1870         }
1871
1872         while ((d = readdir(dp)) != NULL) {
1873                 if (!strcmp(d->d_name, ".") ||
1874                     !strcmp(d->d_name, ".."))
1875                         continue;
1876
1877                 if (unlink(d->d_name) < 0)
1878                         FILE_OP_ERROR(d->d_name, "unlink");
1879         }
1880
1881         closedir(dp);
1882
1883         if (chdir(prev_dir) < 0) {
1884                 FILE_OP_ERROR(prev_dir, "chdir");
1885                 g_free(prev_dir);
1886                 return -1;
1887         }
1888
1889         g_free(prev_dir);
1890
1891         return 0;
1892 }
1893
1894 gint remove_numbered_files(const gchar *dir, guint first, guint last)
1895 {
1896         DIR *dp;
1897         struct dirent *d;
1898         gchar *prev_dir;
1899         gint fileno;
1900
1901         prev_dir = g_get_current_dir();
1902
1903         if (chdir(dir) < 0) {
1904                 FILE_OP_ERROR(dir, "chdir");
1905                 g_free(prev_dir);
1906                 return -1;
1907         }
1908
1909         if ((dp = opendir(".")) == NULL) {
1910                 FILE_OP_ERROR(dir, "opendir");
1911                 g_free(prev_dir);
1912                 return -1;
1913         }
1914
1915         while ((d = readdir(dp)) != NULL) {
1916                 fileno = to_number(d->d_name);
1917                 if (fileno >= 0 && first <= fileno && fileno <= last) {
1918                         if (is_dir_exist(d->d_name))
1919                                 continue;
1920                         if (unlink(d->d_name) < 0)
1921                                 FILE_OP_ERROR(d->d_name, "unlink");
1922                 }
1923         }
1924
1925         closedir(dp);
1926
1927         if (chdir(prev_dir) < 0) {
1928                 FILE_OP_ERROR(prev_dir, "chdir");
1929                 g_free(prev_dir);
1930                 return -1;
1931         }
1932
1933         g_free(prev_dir);
1934
1935         return 0;
1936 }
1937
1938 gint remove_numbered_files_not_in_list(const gchar *dir, GSList *numberlist)
1939 {
1940         DIR *dp;
1941         struct dirent *d;
1942         gchar *prev_dir;
1943         gint fileno;
1944
1945         prev_dir = g_get_current_dir();
1946
1947         if (chdir(dir) < 0) {
1948                 FILE_OP_ERROR(dir, "chdir");
1949                 g_free(prev_dir);
1950                 return -1;
1951         }
1952
1953         if ((dp = opendir(".")) == NULL) {
1954                 FILE_OP_ERROR(dir, "opendir");
1955                 g_free(prev_dir);
1956                 return -1;
1957         }
1958
1959         while ((d = readdir(dp)) != NULL) {
1960                 fileno = to_number(d->d_name);
1961                 if (fileno >= 0 && (g_slist_find(numberlist, GINT_TO_POINTER(fileno)) == NULL)) {
1962                         debug_print("removing unwanted file %d from %s\n", fileno, dir);
1963                         if (is_dir_exist(d->d_name))
1964                                 continue;
1965                         if (unlink(d->d_name) < 0)
1966                                 FILE_OP_ERROR(d->d_name, "unlink");
1967                 }
1968         }
1969
1970         closedir(dp);
1971
1972         if (chdir(prev_dir) < 0) {
1973                 FILE_OP_ERROR(prev_dir, "chdir");
1974                 g_free(prev_dir);
1975                 return -1;
1976         }
1977
1978         g_free(prev_dir);
1979
1980         return 0;
1981 }
1982
1983 gint remove_all_numbered_files(const gchar *dir)
1984 {
1985         return remove_numbered_files(dir, 0, UINT_MAX);
1986 }
1987
1988 gint remove_expired_files(const gchar *dir, guint hours)
1989 {
1990         DIR *dp;
1991         struct dirent *d;
1992         struct stat s;
1993         gchar *prev_dir;
1994         gint fileno;
1995         time_t mtime, now, expire_time;
1996
1997         prev_dir = g_get_current_dir();
1998
1999         if (chdir(dir) < 0) {
2000                 FILE_OP_ERROR(dir, "chdir");
2001                 g_free(prev_dir);
2002                 return -1;
2003         }
2004
2005         if ((dp = opendir(".")) == NULL) {
2006                 FILE_OP_ERROR(dir, "opendir");
2007                 g_free(prev_dir);
2008                 return -1;
2009         }
2010
2011         now = time(NULL);
2012         expire_time = hours * 60 * 60;
2013
2014         while ((d = readdir(dp)) != NULL) {
2015                 fileno = to_number(d->d_name);
2016                 if (fileno >= 0) {
2017                         if (stat(d->d_name, &s) < 0) {
2018                                 FILE_OP_ERROR(d->d_name, "stat");
2019                                 continue;
2020                         }
2021                         if (S_ISDIR(s.st_mode))
2022                                 continue;
2023                         mtime = MAX(s.st_mtime, s.st_atime);
2024                         if (now - mtime > expire_time) {
2025                                 if (unlink(d->d_name) < 0)
2026                                         FILE_OP_ERROR(d->d_name, "unlink");
2027                         }
2028                 }
2029         }
2030
2031         closedir(dp);
2032
2033         if (chdir(prev_dir) < 0) {
2034                 FILE_OP_ERROR(prev_dir, "chdir");
2035                 g_free(prev_dir);
2036                 return -1;
2037         }
2038
2039         g_free(prev_dir);
2040
2041         return 0;
2042 }
2043
2044 gint remove_dir_recursive(const gchar *dir)
2045 {
2046         struct stat s;
2047         DIR *dp;
2048         struct dirent *d;
2049         gchar *prev_dir;
2050
2051         /* g_print("dir = %s\n", dir); */
2052
2053         if (stat(dir, &s) < 0) {
2054                 FILE_OP_ERROR(dir, "stat");
2055                 if (ENOENT == errno) return 0;
2056                 return -1;
2057         }
2058
2059         if (!S_ISDIR(s.st_mode)) {
2060                 if (unlink(dir) < 0) {
2061                         FILE_OP_ERROR(dir, "unlink");
2062                         return -1;
2063                 }
2064
2065                 return 0;
2066         }
2067
2068         prev_dir = g_get_current_dir();
2069         /* g_print("prev_dir = %s\n", prev_dir); */
2070
2071         if (!path_cmp(prev_dir, dir)) {
2072                 g_free(prev_dir);
2073                 if (chdir("..") < 0) {
2074                         FILE_OP_ERROR(dir, "chdir");
2075                         return -1;
2076                 }
2077                 prev_dir = g_get_current_dir();
2078         }
2079
2080         if (chdir(dir) < 0) {
2081                 FILE_OP_ERROR(dir, "chdir");
2082                 g_free(prev_dir);
2083                 return -1;
2084         }
2085
2086         if ((dp = opendir(".")) == NULL) {
2087                 FILE_OP_ERROR(dir, "opendir");
2088                 chdir(prev_dir);
2089                 g_free(prev_dir);
2090                 return -1;
2091         }
2092
2093         /* remove all files in the directory */
2094         while ((d = readdir(dp)) != NULL) {
2095                 if (!strcmp(d->d_name, ".") ||
2096                     !strcmp(d->d_name, ".."))
2097                         continue;
2098
2099                 if (stat(d->d_name, &s) < 0) {
2100                         FILE_OP_ERROR(d->d_name, "stat");
2101                         continue;
2102                 }
2103
2104                 /* g_print("removing %s\n", d->d_name); */
2105
2106                 if (S_ISDIR(s.st_mode)) {
2107                         if (remove_dir_recursive(d->d_name) < 0) {
2108                                 g_warning("can't remove directory\n");
2109                                 return -1;
2110                         }
2111                 } else {
2112                         if (unlink(d->d_name) < 0)
2113                                 FILE_OP_ERROR(d->d_name, "unlink");
2114                 }
2115         }
2116
2117         closedir(dp);
2118
2119         if (chdir(prev_dir) < 0) {
2120                 FILE_OP_ERROR(prev_dir, "chdir");
2121                 g_free(prev_dir);
2122                 return -1;
2123         }
2124
2125         g_free(prev_dir);
2126
2127         if (rmdir(dir) < 0) {
2128                 FILE_OP_ERROR(dir, "rmdir");
2129                 return -1;
2130         }
2131
2132         return 0;
2133 }
2134
2135 #if 0
2136 /* this seems to be slower than the stdio version... */
2137 gint copy_file(const gchar *src, const gchar *dest)
2138 {
2139         gint src_fd, dest_fd;
2140         gint n_read;
2141         gint n_write;
2142         gchar buf[BUFSIZ];
2143         gchar *dest_bak = NULL;
2144
2145         if ((src_fd = open(src, O_RDONLY)) < 0) {
2146                 FILE_OP_ERROR(src, "open");
2147                 return -1;
2148         }
2149
2150         if (is_file_exist(dest)) {
2151                 dest_bak = g_strconcat(dest, ".bak", NULL);
2152                 if (rename(dest, dest_bak) < 0) {
2153                         FILE_OP_ERROR(dest, "rename");
2154                         close(src_fd);
2155                         g_free(dest_bak);
2156                         return -1;
2157                 }
2158         }
2159
2160         if ((dest_fd = open(dest, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR)) < 0) {
2161                 FILE_OP_ERROR(dest, "open");
2162                 close(src_fd);
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
2171         while ((n_read = read(src_fd, buf, sizeof(buf))) > 0) {
2172                 gint len = n_read;
2173                 gchar *bufp = buf;
2174
2175                 while (len > 0) {
2176                         n_write = write(dest_fd, bufp, len);
2177                         if (n_write <= 0) {
2178                                 g_warning("writing to %s failed.\n", dest);
2179                                 close(dest_fd);
2180                                 close(src_fd);
2181                                 unlink(dest);
2182                                 if (dest_bak) {
2183                                         if (rename(dest_bak, dest) < 0)
2184                                                 FILE_OP_ERROR(dest_bak, "rename");
2185                                         g_free(dest_bak);
2186                                 }
2187                                 return -1;
2188                         }
2189                         len -= n_write;
2190                         bufp += n_write;
2191                 }
2192         }
2193
2194         close(src_fd);
2195         close(dest_fd);
2196
2197         if (n_read < 0 || get_file_size(src) != get_file_size(dest)) {
2198                 g_warning("File copy from %s to %s failed.\n", src, dest);
2199                 unlink(dest);
2200                 if (dest_bak) {
2201                         if (rename(dest_bak, dest) < 0)
2202                                 FILE_OP_ERROR(dest_bak, "rename");
2203                         g_free(dest_bak);
2204                 }
2205                 return -1;
2206         }
2207         g_free(dest_bak);
2208
2209         return 0;
2210 }
2211 #endif
2212
2213
2214 /*
2215  * Append src file body to the tail of dest file.
2216  * Now keep_backup has no effects.
2217  */
2218 gint append_file(const gchar *src, const gchar *dest, gboolean keep_backup)
2219 {
2220         FILE *src_fp, *dest_fp;
2221         gint n_read;
2222         gchar buf[BUFSIZ];
2223
2224         gboolean err = FALSE;
2225
2226         if ((src_fp = fopen(src, "rb")) == NULL) {
2227                 FILE_OP_ERROR(src, "fopen");
2228                 return -1;
2229         }
2230         
2231         if ((dest_fp = fopen(dest, "ab")) == NULL) {
2232                 FILE_OP_ERROR(dest, "fopen");
2233                 fclose(src_fp);
2234                 return -1;
2235         }
2236
2237         if (change_file_mode_rw(dest_fp, dest) < 0) {
2238                 FILE_OP_ERROR(dest, "chmod");
2239                 g_warning("can't change file mode\n");
2240         }
2241
2242         while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
2243                 if (n_read < sizeof(buf) && ferror(src_fp))
2244                         break;
2245                 if (fwrite(buf, n_read, 1, dest_fp) < 1) {
2246                         g_warning("writing to %s failed.\n", dest);
2247                         fclose(dest_fp);
2248                         fclose(src_fp);
2249                         unlink(dest);
2250                         return -1;
2251                 }
2252         }
2253
2254         if (ferror(src_fp)) {
2255                 FILE_OP_ERROR(src, "fread");
2256                 err = TRUE;
2257         }
2258         fclose(src_fp);
2259         if (fclose(dest_fp) == EOF) {
2260                 FILE_OP_ERROR(dest, "fclose");
2261                 err = TRUE;
2262         }
2263
2264         if (err) {
2265                 unlink(dest);
2266                 return -1;
2267         }
2268
2269         return 0;
2270 }
2271
2272 gint copy_file(const gchar *src, const gchar *dest, gboolean keep_backup)
2273 {
2274         FILE *src_fp, *dest_fp;
2275         gint n_read;
2276         gchar buf[BUFSIZ];
2277         gchar *dest_bak = NULL;
2278         gboolean err = FALSE;
2279
2280         if ((src_fp = fopen(src, "rb")) == NULL) {
2281                 FILE_OP_ERROR(src, "fopen");
2282                 return -1;
2283         }
2284         if (is_file_exist(dest)) {
2285                 dest_bak = g_strconcat(dest, ".bak", NULL);
2286                 if (rename(dest, dest_bak) < 0) {
2287                         FILE_OP_ERROR(dest, "rename");
2288                         fclose(src_fp);
2289                         g_free(dest_bak);
2290                         return -1;
2291                 }
2292         }
2293
2294         if ((dest_fp = fopen(dest, "wb")) == NULL) {
2295                 FILE_OP_ERROR(dest, "fopen");
2296                 fclose(src_fp);
2297                 if (dest_bak) {
2298                         if (rename(dest_bak, dest) < 0)
2299                                 FILE_OP_ERROR(dest_bak, "rename");
2300                         g_free(dest_bak);
2301                 }
2302                 return -1;
2303         }
2304
2305         if (change_file_mode_rw(dest_fp, dest) < 0) {
2306                 FILE_OP_ERROR(dest, "chmod");
2307                 g_warning("can't change file mode\n");
2308         }
2309
2310         while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
2311                 if (n_read < sizeof(buf) && ferror(src_fp))
2312                         break;
2313                 if (fwrite(buf, n_read, 1, dest_fp) < 1) {
2314                         g_warning("writing to %s failed.\n", dest);
2315                         fclose(dest_fp);
2316                         fclose(src_fp);
2317                         unlink(dest);
2318                         if (dest_bak) {
2319                                 if (rename(dest_bak, dest) < 0)
2320                                         FILE_OP_ERROR(dest_bak, "rename");
2321                                 g_free(dest_bak);
2322                         }
2323                         return -1;
2324                 }
2325         }
2326
2327         if (ferror(src_fp)) {
2328                 FILE_OP_ERROR(src, "fread");
2329                 err = TRUE;
2330         }
2331         fclose(src_fp);
2332         if (fclose(dest_fp) == EOF) {
2333                 FILE_OP_ERROR(dest, "fclose");
2334                 err = TRUE;
2335         }
2336
2337         if (err) {
2338                 unlink(dest);
2339                 if (dest_bak) {
2340                         if (rename(dest_bak, dest) < 0)
2341                                 FILE_OP_ERROR(dest_bak, "rename");
2342                         g_free(dest_bak);
2343                 }
2344                 return -1;
2345         }
2346
2347         if (keep_backup == FALSE && dest_bak)
2348                 unlink(dest_bak);
2349
2350         g_free(dest_bak);
2351
2352         return 0;
2353 }
2354
2355 gint move_file(const gchar *src, const gchar *dest, gboolean overwrite)
2356 {
2357         if (overwrite == FALSE && is_file_exist(dest)) {
2358                 g_warning("move_file(): file %s already exists.", dest);
2359                 return -1;
2360         }
2361
2362         if (rename(src, dest) == 0) return 0;
2363
2364         if (EXDEV != errno) {
2365                 FILE_OP_ERROR(src, "rename");
2366                 return -1;
2367         }
2368
2369         if (copy_file(src, dest, FALSE) < 0) return -1;
2370
2371         unlink(src);
2372
2373         return 0;
2374 }
2375
2376 gint copy_file_part(FILE *fp, off_t offset, size_t length, const gchar *dest)
2377 {
2378         FILE *dest_fp;
2379         gint n_read;
2380         gint bytes_left, to_read;
2381         gchar buf[BUFSIZ];
2382         gboolean err = FALSE;
2383
2384         if (fseek(fp, offset, SEEK_SET) < 0) {
2385                 perror("fseek");
2386                 return -1;
2387         }
2388
2389         if ((dest_fp = fopen(dest, "wb")) == NULL) {
2390                 FILE_OP_ERROR(dest, "fopen");
2391                 return -1;
2392         }
2393
2394         if (change_file_mode_rw(dest_fp, dest) < 0) {
2395                 FILE_OP_ERROR(dest, "chmod");
2396                 g_warning("can't change file mode\n");
2397         }
2398
2399         bytes_left = length;
2400         to_read = MIN(bytes_left, sizeof(buf));
2401
2402         while ((n_read = fread(buf, sizeof(gchar), to_read, fp)) > 0) {
2403                 if (n_read < to_read && ferror(fp))
2404                         break;
2405                 if (fwrite(buf, n_read, 1, dest_fp) < 1) {
2406                         g_warning("writing to %s failed.\n", dest);
2407                         fclose(dest_fp);
2408                         unlink(dest);
2409                         return -1;
2410                 }
2411                 bytes_left -= n_read;
2412                 if (bytes_left == 0)
2413                         break;
2414                 to_read = MIN(bytes_left, sizeof(buf));
2415         }
2416
2417         if (ferror(fp)) {
2418                 perror("fread");
2419                 err = TRUE;
2420         }
2421         if (fclose(dest_fp) == EOF) {
2422                 FILE_OP_ERROR(dest, "fclose");
2423                 err = TRUE;
2424         }
2425
2426         if (err) {
2427                 unlink(dest);
2428                 return -1;
2429         }
2430
2431         return 0;
2432 }
2433
2434 /* convert line endings into CRLF. If the last line doesn't end with
2435  * linebreak, add it.
2436  */
2437 gchar *canonicalize_str(const gchar *str)
2438 {
2439         const gchar *p;
2440         guint new_len = 0;
2441         gchar *out, *outp;
2442
2443         for (p = str; *p != '\0'; ++p) {
2444                 if (*p != '\r') {
2445                         ++new_len;
2446                         if (*p == '\n')
2447                                 ++new_len;
2448                 }
2449         }
2450         if (p == str || *(p - 1) != '\n')
2451                 new_len += 2;
2452
2453         out = outp = g_malloc(new_len + 1);
2454         for (p = str; *p != '\0'; ++p) {
2455                 if (*p != '\r') {
2456                         if (*p == '\n')
2457                                 *outp++ = '\r';
2458                         *outp++ = *p;
2459                 }
2460         }
2461         if (p == str || *(p - 1) != '\n') {
2462                 *outp++ = '\r';
2463                 *outp++ = '\n';
2464         }
2465         *outp = '\0';
2466
2467         return out;
2468 }
2469
2470 gint canonicalize_file(const gchar *src, const gchar *dest)
2471 {
2472         FILE *src_fp, *dest_fp;
2473         gchar buf[BUFFSIZE];
2474         gint len;
2475         gboolean err = FALSE;
2476         gboolean last_linebreak = FALSE;
2477
2478         if ((src_fp = fopen(src, "rb")) == NULL) {
2479                 FILE_OP_ERROR(src, "fopen");
2480                 return -1;
2481         }
2482
2483         if ((dest_fp = fopen(dest, "wb")) == NULL) {
2484                 FILE_OP_ERROR(dest, "fopen");
2485                 fclose(src_fp);
2486                 return -1;
2487         }
2488
2489         if (change_file_mode_rw(dest_fp, dest) < 0) {
2490                 FILE_OP_ERROR(dest, "chmod");
2491                 g_warning("can't change file mode\n");
2492         }
2493
2494         while (fgets(buf, sizeof(buf), src_fp) != NULL) {
2495                 gint r = 0;
2496
2497                 len = strlen(buf);
2498                 if (len == 0) break;
2499                 last_linebreak = FALSE;
2500
2501                 if (buf[len - 1] != '\n') {
2502                         last_linebreak = TRUE;
2503                         r = fputs(buf, dest_fp);
2504                 } else if (len > 1 && buf[len - 1] == '\n' && buf[len - 2] == '\r') {
2505                         r = fputs(buf, dest_fp);
2506                 } else {
2507                         if (len > 1) {
2508                                 r = fwrite(buf, len - 1, 1, dest_fp);
2509                                 if (r != 1)
2510                                         r = EOF;
2511                         }
2512                         if (r != EOF)
2513                                 r = fputs("\r\n", dest_fp);
2514                 }
2515
2516                 if (r == EOF) {
2517                         g_warning("writing to %s failed.\n", dest);
2518                         fclose(dest_fp);
2519                         fclose(src_fp);
2520                         unlink(dest);
2521                         return -1;
2522                 }
2523         }
2524
2525         if (last_linebreak == TRUE) {
2526                 if (fputs("\r\n", dest_fp) == EOF)
2527                         err = TRUE;
2528         }
2529
2530         if (ferror(src_fp)) {
2531                 FILE_OP_ERROR(src, "fgets");
2532                 err = TRUE;
2533         }
2534         fclose(src_fp);
2535         if (fclose(dest_fp) == EOF) {
2536                 FILE_OP_ERROR(dest, "fclose");
2537                 err = TRUE;
2538         }
2539
2540         if (err) {
2541                 unlink(dest);
2542                 return -1;
2543         }
2544
2545         return 0;
2546 }
2547
2548 gint canonicalize_file_replace(const gchar *file)
2549 {
2550         gchar *tmp_file;
2551
2552         tmp_file = get_tmp_file();
2553
2554         if (canonicalize_file(file, tmp_file) < 0) {
2555                 g_free(tmp_file);
2556                 return -1;
2557         }
2558
2559         if (move_file(tmp_file, file, TRUE) < 0) {
2560                 g_warning("can't replace %s .\n", file);
2561                 unlink(tmp_file);
2562                 g_free(tmp_file);
2563                 return -1;
2564         }
2565
2566         g_free(tmp_file);
2567         return 0;
2568 }
2569
2570 gint uncanonicalize_file(const gchar *src, const gchar *dest)
2571 {
2572         FILE *src_fp, *dest_fp;
2573         gchar buf[BUFFSIZE];
2574         gboolean err = FALSE;
2575
2576         if ((src_fp = fopen(src, "rb")) == NULL) {
2577                 FILE_OP_ERROR(src, "fopen");
2578                 return -1;
2579         }
2580
2581         if ((dest_fp = fopen(dest, "wb")) == NULL) {
2582                 FILE_OP_ERROR(dest, "fopen");
2583                 fclose(src_fp);
2584                 return -1;
2585         }
2586
2587         if (change_file_mode_rw(dest_fp, dest) < 0) {
2588                 FILE_OP_ERROR(dest, "chmod");
2589                 g_warning("can't change file mode\n");
2590         }
2591
2592         while (fgets(buf, sizeof(buf), src_fp) != NULL) {
2593                 strcrchomp(buf);
2594                 if (fputs(buf, dest_fp) == EOF) {
2595                         g_warning("writing to %s failed.\n", dest);
2596                         fclose(dest_fp);
2597                         fclose(src_fp);
2598                         unlink(dest);
2599                         return -1;
2600                 }
2601         }
2602
2603         if (ferror(src_fp)) {
2604                 FILE_OP_ERROR(src, "fgets");
2605                 err = TRUE;
2606         }
2607         fclose(src_fp);
2608         if (fclose(dest_fp) == EOF) {
2609                 FILE_OP_ERROR(dest, "fclose");
2610                 err = TRUE;
2611         }
2612
2613         if (err) {
2614                 unlink(dest);
2615                 return -1;
2616         }
2617
2618         return 0;
2619 }
2620
2621 gint uncanonicalize_file_replace(const gchar *file)
2622 {
2623         gchar *tmp_file;
2624
2625         tmp_file = get_tmp_file();
2626
2627         if (uncanonicalize_file(file, tmp_file) < 0) {
2628                 g_free(tmp_file);
2629                 return -1;
2630         }
2631
2632         if (move_file(tmp_file, file, TRUE) < 0) {
2633                 g_warning("can't replace %s .\n", file);
2634                 unlink(tmp_file);
2635                 g_free(tmp_file);
2636                 return -1;
2637         }
2638
2639         g_free(tmp_file);
2640         return 0;
2641 }
2642
2643 gchar *normalize_newlines(const gchar *str)
2644 {
2645         const gchar *p = str;
2646         gchar *out, *outp;
2647
2648         out = outp = g_malloc(strlen(str) + 1);
2649         for (p = str; *p != '\0'; ++p) {
2650                 if (*p == '\r') {
2651                         if (*(p + 1) != '\n')
2652                                 *outp++ = '\n';
2653                 } else
2654                         *outp++ = *p;
2655         }
2656
2657         *outp = '\0';
2658
2659         return out;
2660 }
2661
2662 gchar *get_outgoing_rfc2822_str(FILE *fp)
2663 {
2664         gchar buf[BUFFSIZE];
2665         GString *str;
2666         gchar *ret;
2667
2668         str = g_string_new(NULL);
2669
2670         /* output header part */
2671         while (fgets(buf, sizeof(buf), fp) != NULL) {
2672                 strretchomp(buf);
2673                 if (!g_strncasecmp(buf, "Bcc:", 4)) {
2674                         gint next;
2675
2676                         for (;;) {
2677                                 next = fgetc(fp);
2678                                 if (next == EOF)
2679                                         break;
2680                                 else if (next != ' ' && next != '\t') {
2681                                         ungetc(next, fp);
2682                                         break;
2683                                 }
2684                                 if (fgets(buf, sizeof(buf), fp) == NULL)
2685                                         break;
2686                         }
2687                 } else {
2688                         g_string_append(str, buf);
2689                         g_string_append(str, "\r\n");
2690                         if (buf[0] == '\0')
2691                                 break;
2692                 }
2693         }
2694
2695         /* output body part */
2696         while (fgets(buf, sizeof(buf), fp) != NULL) {
2697                 strretchomp(buf);
2698                 if (buf[0] == '.')
2699                         g_string_append_c(str, '.');
2700                 g_string_append(str, buf);
2701                 g_string_append(str, "\r\n");
2702         }
2703
2704         ret = str->str;
2705         g_string_free(str, FALSE);
2706
2707         return ret;
2708 }
2709
2710 gint change_file_mode_rw(FILE *fp, const gchar *file)
2711 {
2712 #if HAVE_FCHMOD
2713         return fchmod(fileno(fp), S_IRUSR|S_IWUSR);
2714 #else
2715         return chmod(file, S_IRUSR|S_IWUSR);
2716 #endif
2717 }
2718
2719 FILE *my_tmpfile(void)
2720 {
2721 #if HAVE_MKSTEMP
2722         const gchar suffix[] = ".XXXXXX";
2723         const gchar *tmpdir;
2724         guint tmplen;
2725         const gchar *progname;
2726         guint proglen;
2727         gchar *fname;
2728         gint fd;
2729         FILE *fp;
2730
2731         tmpdir = get_tmp_dir();
2732         tmplen = strlen(tmpdir);
2733         progname = g_get_prgname();
2734         proglen = strlen(progname);
2735         Xalloca(fname, tmplen + 1 + proglen + sizeof(suffix),
2736                 return tmpfile());
2737
2738         memcpy(fname, tmpdir, tmplen);
2739         fname[tmplen] = G_DIR_SEPARATOR;
2740         memcpy(fname + tmplen + 1, progname, proglen);
2741         memcpy(fname + tmplen + 1 + proglen, suffix, sizeof(suffix));
2742
2743         fd = mkstemp(fname);
2744         if (fd < 0)
2745                 return tmpfile();
2746
2747         unlink(fname);
2748
2749         fp = fdopen(fd, "w+b");
2750         if (!fp)
2751                 close(fd);
2752         else
2753                 return fp;
2754 #endif /* HAVE_MKSTEMP */
2755
2756         return tmpfile();
2757 }
2758
2759 FILE *str_open_as_stream(const gchar *str)
2760 {
2761         FILE *fp;
2762         size_t len;
2763
2764         g_return_val_if_fail(str != NULL, NULL);
2765
2766         fp = my_tmpfile();
2767         if (!fp) {
2768                 FILE_OP_ERROR("str_open_as_stream", "my_tmpfile");
2769                 return NULL;
2770         }
2771
2772         len = strlen(str);
2773         if (len == 0) return fp;
2774
2775         if (fwrite(str, len, 1, fp) != 1) {
2776                 FILE_OP_ERROR("str_open_as_stream", "fwrite");
2777                 fclose(fp);
2778                 return NULL;
2779         }
2780
2781         rewind(fp);
2782         return fp;
2783 }
2784
2785 gint str_write_to_file(const gchar *str, const gchar *file)
2786 {
2787         FILE *fp;
2788         size_t len;
2789
2790         g_return_val_if_fail(str != NULL, -1);
2791         g_return_val_if_fail(file != NULL, -1);
2792
2793         if ((fp = fopen(file, "wb")) == NULL) {
2794                 FILE_OP_ERROR(file, "fopen");
2795                 return -1;
2796         }
2797
2798         len = strlen(str);
2799         if (len == 0) {
2800                 fclose(fp);
2801                 return 0;
2802         }
2803
2804         if (fwrite(str, len, 1, fp) != 1) {
2805                 FILE_OP_ERROR(file, "fwrite");
2806                 fclose(fp);
2807                 unlink(file);
2808                 return -1;
2809         }
2810
2811         if (fclose(fp) == EOF) {
2812                 FILE_OP_ERROR(file, "fclose");
2813                 unlink(file);
2814                 return -1;
2815         }
2816
2817         return 0;
2818 }
2819
2820 gchar *file_read_to_str(const gchar *file)
2821 {
2822         FILE *fp;
2823         gchar *str;
2824
2825         g_return_val_if_fail(file != NULL, NULL);
2826
2827         if ((fp = fopen(file, "rb")) == NULL) {
2828                 FILE_OP_ERROR(file, "fopen");
2829                 return NULL;
2830         }
2831
2832         str = file_read_stream_to_str(fp);
2833
2834         fclose(fp);
2835
2836         return str;
2837 }
2838
2839 gchar *file_read_stream_to_str(FILE *fp)
2840 {
2841         GByteArray *array;
2842         gchar buf[BUFSIZ];
2843         gint n_read;
2844         gchar *str;
2845
2846         g_return_val_if_fail(fp != NULL, NULL);
2847
2848         array = g_byte_array_new();
2849
2850         while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
2851                 if (n_read < sizeof(buf) && ferror(fp))
2852                         break;
2853                 g_byte_array_append(array, buf, n_read);
2854         }
2855
2856         if (ferror(fp)) {
2857                 FILE_OP_ERROR("file stream", "fread");
2858                 g_byte_array_free(array, TRUE);
2859                 return NULL;
2860         }
2861
2862         buf[0] = '\0';
2863         g_byte_array_append(array, buf, 1);
2864         str = (gchar *)array->data;
2865         g_byte_array_free(array, FALSE);
2866
2867         return str;
2868 }
2869
2870 gint execute_async(gchar *const argv[])
2871 {
2872         pid_t pid;
2873
2874         if ((pid = fork()) < 0) {
2875                 perror("fork");
2876                 return -1;
2877         }
2878
2879         if (pid == 0) {                 /* child process */
2880                 pid_t gch_pid;
2881
2882                 if ((gch_pid = fork()) < 0) {
2883                         perror("fork");
2884                         _exit(1);
2885                 }
2886
2887                 if (gch_pid == 0) {     /* grandchild process */
2888                         execvp(argv[0], argv);
2889
2890                         perror("execvp");
2891                         _exit(1);
2892                 }
2893
2894                 _exit(0);
2895         }
2896
2897         waitpid(pid, NULL, 0);
2898
2899         return 0;
2900 }
2901
2902 gint execute_sync(gchar *const argv[])
2903 {
2904         pid_t pid;
2905
2906         if ((pid = fork()) < 0) {
2907                 perror("fork");
2908                 return -1;
2909         }
2910
2911         if (pid == 0) {         /* child process */
2912                 execvp(argv[0], argv);
2913
2914                 perror("execvp");
2915                 _exit(1);
2916         }
2917
2918         waitpid(pid, NULL, 0);
2919
2920         return 0;
2921 }
2922
2923 gint execute_command_line(const gchar *cmdline, gboolean async)
2924 {
2925         gchar **argv;
2926         gint ret;
2927
2928         argv = strsplit_with_quote(cmdline, " ", 0);
2929
2930         if (async)
2931                 ret = execute_async(argv);
2932         else
2933                 ret = execute_sync(argv);
2934         g_strfreev(argv);
2935
2936         return ret;
2937 }
2938
2939 gchar *get_command_output(const gchar *cmdline)
2940 {
2941         gchar buf[BUFFSIZE];
2942         FILE *fp;
2943         GString *str;
2944         gchar *ret;
2945
2946         g_return_val_if_fail(cmdline != NULL, NULL);
2947
2948         if ((fp = popen(cmdline, "r")) == NULL) {
2949                 FILE_OP_ERROR(cmdline, "popen");
2950                 return NULL;
2951         }
2952
2953         str = g_string_new("");
2954
2955         while (fgets(buf, sizeof(buf), fp) != NULL)
2956                 g_string_append(str, buf);
2957
2958         pclose(fp);
2959
2960         ret = str->str;
2961         g_string_free(str, FALSE);
2962
2963         return ret;
2964 }
2965
2966 static gint is_unchanged_uri_char(char c)
2967 {
2968         switch (c) {
2969                 case '(':
2970                 case ')':
2971                 case ',':
2972                         return 0;
2973                 default:
2974                         return 1;
2975         }
2976 }
2977
2978 void encode_uri(gchar *encoded_uri, gint bufsize, const gchar *uri)
2979 {
2980         int i;
2981         int k;
2982
2983         k = 0;
2984         for(i = 0; i < strlen(uri) ; i++) {
2985                 if (is_unchanged_uri_char(uri[i])) {
2986                         if (k + 2 >= bufsize)
2987                                 break;
2988                         encoded_uri[k++] = uri[i];
2989                 }
2990                 else {
2991                         char * hexa = "0123456789ABCDEF";
2992                         
2993                         if (k + 4 >= bufsize)
2994                                 break;
2995                         encoded_uri[k++] = '%';
2996                         encoded_uri[k++] = hexa[uri[i] / 16];
2997                         encoded_uri[k++] = hexa[uri[i] % 16];
2998                 }
2999         }
3000         encoded_uri[k] = 0;
3001 }
3002
3003 /* Converts two-digit hexadecimal to decimal.  Used for unescaping escaped 
3004  * characters
3005  */
3006 static gint axtoi(const gchar *hexstr)
3007 {
3008         gint hi, lo, result;
3009        
3010         hi = hexstr[0];
3011         if ('0' <= hi && hi <= '9') {
3012                 hi -= '0';
3013         } else
3014                 if ('a' <= hi && hi <= 'f') {
3015                         hi -= ('a' - 10);
3016                 } else
3017                         if ('A' <= hi && hi <= 'F') {
3018                                 hi -= ('A' - 10);
3019                         }
3020
3021         lo = hexstr[1];
3022         if ('0' <= lo && lo <= '9') {
3023                 lo -= '0';
3024         } else
3025                 if ('a' <= lo && lo <= 'f') {
3026                         lo -= ('a'-10);
3027                 } else
3028                         if ('A' <= lo && lo <= 'F') {
3029                                 lo -= ('A' - 10);
3030                         }
3031         result = lo + (16 * hi);
3032         return result;
3033 }
3034
3035
3036 /* Decodes URL-Encoded strings (i.e. strings in which spaces are replaced by
3037  * plusses, and escape characters are used)
3038  */
3039
3040 void decode_uri(gchar *decoded_uri, const gchar *encoded_uri)
3041 {
3042         const gchar *encoded;
3043         gchar *decoded;
3044
3045         encoded = encoded_uri;
3046         decoded = decoded_uri;
3047
3048         while (*encoded) {
3049                 if (*encoded == '%') {
3050                         encoded++;
3051                         if (isxdigit(encoded[0])
3052                             && isxdigit(encoded[1])) {
3053                                 *decoded = (gchar) axtoi(encoded);
3054                                 decoded++;
3055                                 encoded += 2;
3056                         }
3057                 }
3058                 else if (*encoded == '+') {
3059                         *decoded = ' ';
3060                         decoded++;
3061                         encoded++;
3062                 }
3063                 else {
3064                         *decoded = *encoded;
3065                         decoded++;
3066                         encoded++;
3067                 }
3068         }
3069
3070         *decoded = '\0';
3071 }
3072
3073
3074 gint open_uri(const gchar *uri, const gchar *cmdline)
3075 {
3076         gchar buf[BUFFSIZE];
3077         gchar *p;
3078         gchar encoded_uri[BUFFSIZE];
3079         
3080         g_return_val_if_fail(uri != NULL, -1);
3081
3082         /* an option to choose whether to use encode_uri or not ? */
3083         encode_uri(encoded_uri, BUFFSIZE, uri);
3084         
3085         if (cmdline &&
3086             (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
3087             !strchr(p + 2, '%'))
3088                 g_snprintf(buf, sizeof(buf), cmdline, encoded_uri);
3089         else {
3090                 if (cmdline)
3091                         g_warning("Open URI command line is invalid: `%s'",
3092                                   cmdline);
3093                 g_snprintf(buf, sizeof(buf), DEFAULT_BROWSER_CMD, encoded_uri);
3094         }
3095         
3096         execute_command_line(buf, TRUE);
3097
3098         return 0;
3099 }
3100
3101 time_t remote_tzoffset_sec(const gchar *zone)
3102 {
3103         static gchar ustzstr[] = "PSTPDTMSTMDTCSTCDTESTEDT";
3104         gchar zone3[4];
3105         gchar *p;
3106         gchar c;
3107         gint iustz;
3108         gint offset;
3109         time_t remoteoffset;
3110
3111         strncpy(zone3, zone, 3);
3112         zone3[3] = '\0';
3113         remoteoffset = 0;
3114
3115         if (sscanf(zone, "%c%d", &c, &offset) == 2 &&
3116             (c == '+' || c == '-')) {
3117                 remoteoffset = ((offset / 100) * 60 + (offset % 100)) * 60;
3118                 if (c == '-')
3119                         remoteoffset = -remoteoffset;
3120         } else if (!strncmp(zone, "UT" , 2) ||
3121                    !strncmp(zone, "GMT", 2)) {
3122                 remoteoffset = 0;
3123         } else if (strlen(zone3) == 3) {
3124                 for (p = ustzstr; *p != '\0'; p += 3) {
3125                         if (!strncasecmp(p, zone3, 3)) {
3126                                 iustz = ((gint)(p - ustzstr) / 3 + 1) / 2 - 8;
3127                                 remoteoffset = iustz * 3600;
3128                                 break;
3129                         }
3130                 }
3131                 if (*p == '\0')
3132                         return -1;
3133         } else if (strlen(zone3) == 1) {
3134                 switch (zone[0]) {
3135                 case 'Z': remoteoffset =   0; break;
3136                 case 'A': remoteoffset =  -1; break;
3137                 case 'B': remoteoffset =  -2; break;
3138                 case 'C': remoteoffset =  -3; break;
3139                 case 'D': remoteoffset =  -4; break;
3140                 case 'E': remoteoffset =  -5; break;
3141                 case 'F': remoteoffset =  -6; break;
3142                 case 'G': remoteoffset =  -7; break;
3143                 case 'H': remoteoffset =  -8; break;
3144                 case 'I': remoteoffset =  -9; break;
3145                 case 'K': remoteoffset = -10; break; /* J is not used */
3146                 case 'L': remoteoffset = -11; break;
3147                 case 'M': remoteoffset = -12; break;
3148                 case 'N': remoteoffset =   1; break;
3149                 case 'O': remoteoffset =   2; break;
3150                 case 'P': remoteoffset =   3; break;
3151                 case 'Q': remoteoffset =   4; break;
3152                 case 'R': remoteoffset =   5; break;
3153                 case 'S': remoteoffset =   6; break;
3154                 case 'T': remoteoffset =   7; break;
3155                 case 'U': remoteoffset =   8; break;
3156                 case 'V': remoteoffset =   9; break;
3157                 case 'W': remoteoffset =  10; break;
3158                 case 'X': remoteoffset =  11; break;
3159                 case 'Y': remoteoffset =  12; break;
3160                 default:  remoteoffset =   0; break;
3161                 }
3162                 remoteoffset = remoteoffset * 3600;
3163         } else
3164                 return -1;
3165
3166         return remoteoffset;
3167 }
3168
3169 time_t tzoffset_sec(time_t *now)
3170 {
3171         struct tm gmt, *lt;
3172         gint off;
3173
3174         gmt = *gmtime(now);
3175         lt = localtime(now);
3176
3177         off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
3178
3179         if (lt->tm_year < gmt.tm_year)
3180                 off -= 24 * 60;
3181         else if (lt->tm_year > gmt.tm_year)
3182                 off += 24 * 60;
3183         else if (lt->tm_yday < gmt.tm_yday)
3184                 off -= 24 * 60;
3185         else if (lt->tm_yday > gmt.tm_yday)
3186                 off += 24 * 60;
3187
3188         if (off >= 24 * 60)             /* should be impossible */
3189                 off = 23 * 60 + 59;     /* if not, insert silly value */
3190         if (off <= -24 * 60)
3191                 off = -(23 * 60 + 59);
3192
3193         return off * 60;
3194 }
3195
3196 /* calculate timezone offset */
3197 gchar *tzoffset(time_t *now)
3198 {
3199         static gchar offset_string[6];
3200         struct tm gmt, *lt;
3201         gint off;
3202         gchar sign = '+';
3203
3204         gmt = *gmtime(now);
3205         lt = localtime(now);
3206
3207         off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
3208
3209         if (lt->tm_year < gmt.tm_year)
3210                 off -= 24 * 60;
3211         else if (lt->tm_year > gmt.tm_year)
3212                 off += 24 * 60;
3213         else if (lt->tm_yday < gmt.tm_yday)
3214                 off -= 24 * 60;
3215         else if (lt->tm_yday > gmt.tm_yday)
3216                 off += 24 * 60;
3217
3218         if (off < 0) {
3219                 sign = '-';
3220                 off = -off;
3221         }
3222
3223         if (off >= 24 * 60)             /* should be impossible */
3224                 off = 23 * 60 + 59;     /* if not, insert silly value */
3225
3226         sprintf(offset_string, "%c%02d%02d", sign, off / 60, off % 60);
3227
3228         return offset_string;
3229 }
3230
3231 void get_rfc822_date(gchar *buf, gint len)
3232 {
3233         struct tm *lt;
3234         time_t t;
3235         gchar day[4], mon[4];
3236         gint dd, hh, mm, ss, yyyy;
3237
3238         t = time(NULL);
3239         lt = localtime(&t);
3240
3241         sscanf(asctime(lt), "%3s %3s %d %d:%d:%d %d\n",
3242                day, mon, &dd, &hh, &mm, &ss, &yyyy);
3243         g_snprintf(buf, len, "%s, %d %s %d %02d:%02d:%02d %s",
3244                    day, dd, mon, yyyy, hh, mm, ss, tzoffset(&t));
3245 }
3246
3247 void debug_set_mode(gboolean mode)
3248 {
3249         debug_mode = mode;
3250 }
3251
3252 gboolean debug_get_mode(void)
3253 {
3254         return debug_mode;
3255 }
3256
3257 void debug_print_real(const gchar *format, ...)
3258 {
3259         va_list args;
3260         gchar buf[BUFFSIZE];
3261
3262         if (!debug_mode) return;
3263
3264         va_start(args, format);
3265         g_vsnprintf(buf, sizeof(buf), format, args);
3266         va_end(args);
3267
3268         fputs(buf, stdout);
3269 }
3270
3271 void * subject_table_lookup(GHashTable *subject_table, gchar * subject)
3272 {
3273         if (subject == NULL)
3274                 subject = "";
3275         else
3276                 subject += subject_get_prefix_length(subject);
3277
3278         return g_hash_table_lookup(subject_table, subject);
3279 }
3280
3281 void subject_table_insert(GHashTable *subject_table, gchar * subject,
3282                           void * data)
3283 {
3284         if (subject == NULL || *subject == 0)
3285                 return;
3286         subject += subject_get_prefix_length(subject);
3287         g_hash_table_insert(subject_table, subject, data);
3288 }
3289
3290 void subject_table_remove(GHashTable *subject_table, gchar * subject)
3291 {
3292         if (subject == NULL)
3293                 return;
3294
3295         subject += subject_get_prefix_length(subject);  
3296         g_hash_table_remove(subject_table, subject);
3297 }
3298
3299 /*!
3300  *\brief        Check if a string is prefixed with known (combinations) 
3301  *              of prefixes. The function assumes that each prefix 
3302  *              is terminated by zero or exactly _one_ space.
3303  *
3304  *\param        str String to check for a prefixes
3305  *
3306  *\return       int Number of chars in the prefix that should be skipped 
3307  *              for a "clean" subject line. If no prefix was found, 0
3308  *              is returned.
3309  */             
3310 int subject_get_prefix_length(const gchar *subject)
3311 {
3312         /*!< Array with allowable reply prefixes regexps. */
3313         static const gchar * const prefixes[] = {
3314                 "Re\\:",                        /* "Re:" */
3315                 "Re\\[[1-9][0-9]*\\]\\:",       /* "Re[XXX]:" (non-conforming news mail clients) */
3316                 "Antw\\:",                      /* "Antw:" (Dutch / German Outlook) */
3317                 "Aw\\:",                        /* "Aw:"   (German) */
3318                 "Antwort\\:",                   /* "Antwort:" (German Lotus Notes) */
3319                 "Res\\:",                       /* "Res:" (Brazilian Outlook) */
3320                 "Fw\\:",                        /* "Fw:" Forward */
3321                 "Enc\\:"                        /* "Enc:" Forward (Brazilian Outlook) */
3322                 /* add more */
3323         };
3324         const int PREFIXES = sizeof prefixes / sizeof prefixes[0];
3325         int n;
3326         regmatch_t pos;
3327         static regex_t regex;
3328         static gboolean init_;
3329
3330         if (!subject) return 0;
3331         if (!*subject) return 0;
3332
3333         if (!init_) {
3334                 GString *s = g_string_new("");
3335                 
3336                 for (n = 0; n < PREFIXES; n++)
3337                         /* Terminate each prefix regexpression by a
3338                          * "\ ?" (zero or ONE space), and OR them */
3339                         g_string_sprintfa(s, "(%s\\ ?)%s",
3340                                           prefixes[n],
3341                                           n < PREFIXES - 1 ? 
3342                                           "|" : "");
3343                 
3344                 g_string_prepend(s, "(");
3345                 g_string_append(s, ")+");       /* match at least once */
3346                 g_string_prepend(s, "^\\ *");   /* from beginning of line */
3347                 
3348
3349                 /* We now have something like "^\ *((PREFIX1\ ?)|(PREFIX2\ ?))+" 
3350                  * TODO: Should this be       "^\ *(((PREFIX1)|(PREFIX2))\ ?)+" ??? */
3351                 if (regcomp(&regex, s->str, REG_EXTENDED | REG_ICASE)) { 
3352                         debug_print("Error compiling regexp %s\n", s->str);
3353                         g_string_free(s, TRUE);
3354                         return 0;
3355                 } else {
3356                         init_ = TRUE;
3357                         g_string_free(s, TRUE);
3358                 }
3359         }
3360         
3361         if (!regexec(&regex, subject, 1, &pos, 0) && pos.rm_so != -1)
3362                 return pos.rm_eo;
3363         else
3364                 return 0;
3365 }
3366
3367 FILE *get_tmpfile_in_dir(const gchar *dir, gchar **filename)
3368 {
3369         int fd;
3370         
3371         *filename = g_strdup_printf("%s%csylpheed.XXXXXX", dir, G_DIR_SEPARATOR);
3372         fd = mkstemp(*filename);
3373
3374         return fdopen(fd, "w+");
3375 }
3376
3377 /* allow Mutt-like patterns in quick search */
3378 gchar *expand_search_string(const gchar *search_string)
3379 {
3380         int i = 0;
3381         gchar term_char, save_char;
3382         gchar *cmd_start, *cmd_end;
3383         GString *matcherstr;
3384         gchar *returnstr = NULL;
3385         gchar *copy_str;
3386         gboolean casesens, dontmatch;
3387         /* list of allowed pattern abbreviations */
3388         struct {
3389                 gchar           *abbreviated;   /* abbreviation */
3390                 gchar           *command;       /* actual matcher command */ 
3391                 gint            numparams;      /* number of params for cmd */
3392                 gboolean        qualifier;      /* do we append regexpcase */
3393                 gboolean        quotes;         /* do we need quotes */
3394         }
3395         cmds[] = {
3396                 { "a",  "all",                          0,      FALSE,  FALSE },
3397                 { "ag", "age_greater",                  1,      FALSE,  FALSE },
3398                 { "al", "age_lower",                    1,      FALSE,  FALSE },
3399                 { "b",  "body_part",                    1,      TRUE,   TRUE  },
3400                 { "B",  "message",                      1,      TRUE,   TRUE  },
3401                 { "c",  "cc",                           1,      TRUE,   TRUE  },
3402                 { "C",  "to_or_cc",                     1,      TRUE,   TRUE  },
3403                 { "D",  "deleted",                      0,      FALSE,  FALSE },
3404                 { "e",  "header \"Sender\"",            1,      TRUE,   TRUE  },
3405                 { "E",  "execute",                      1,      FALSE,  TRUE  },
3406                 { "f",  "from",                         1,      TRUE,   TRUE  },
3407                 { "F",  "forwarded",                    0,      FALSE,  FALSE },
3408                 { "h",  "headers_part",                 1,      TRUE,   TRUE  },
3409                 { "i",  "header \"Message-Id\"",        1,      TRUE,   TRUE  },
3410                 { "I",  "inreplyto",                    1,      TRUE,   TRUE  },
3411                 { "L",  "locked",                       0,      FALSE,  FALSE },
3412                 { "n",  "newsgroups",                   1,      TRUE,   TRUE  },
3413                 { "N",  "new",                          0,      FALSE,  FALSE },
3414                 { "O",  "~new",                         0,      FALSE,  FALSE },
3415                 { "r",  "replied",                      0,      FALSE,  FALSE },
3416                 { "R",  "~unread",                      0,      FALSE,  FALSE },
3417                 { "s",  "subject",                      1,      TRUE,   TRUE  },
3418                 { "se", "score_equal",                  1,      FALSE,  FALSE },
3419                 { "sg", "score_greater",                1,      FALSE,  FALSE },
3420                 { "sl", "score_lower",                  1,      FALSE,  FALSE },
3421                 { "Se", "size_equal",                   1,      FALSE,  FALSE },
3422                 { "Sg", "size_greater",                 1,      FALSE,  FALSE },
3423                 { "Ss", "size_smaller",                 1,      FALSE,  FALSE },
3424                 { "t",  "to",                           1,      TRUE,   TRUE  },
3425                 { "T",  "marked",                       0,      FALSE,  FALSE },
3426                 { "U",  "unread",                       0,      FALSE,  FALSE },
3427                 { "x",  "header \"References\"",        1,      TRUE,   TRUE  },
3428                 { "y",  "header \"X-Label\"",           1,      TRUE,   TRUE  },
3429                 { "&",  "&",                            0,      FALSE,  FALSE },
3430                 { "|",  "|",                            0,      FALSE,  FALSE },
3431                 { NULL, NULL,                           0,      FALSE,  FALSE }
3432         };
3433
3434         if (search_string == NULL)
3435                 return NULL;
3436
3437         copy_str = g_strdup(search_string);
3438
3439         /* if it's a full command don't process it so users
3440            can still do something like from regexpcase "foo" */
3441         for (i = 0; cmds[i].command; i++) {
3442                 const gchar *tmp_search_string = search_string;
3443                 cmd_start = cmds[i].command;
3444                 /* allow logical NOT */
3445                 if (*tmp_search_string == '~')
3446                         tmp_search_string++;
3447                 if (!strncmp(tmp_search_string, cmd_start, strlen(cmd_start)))
3448                         break;
3449         }
3450         if (cmds[i].command)
3451                 return copy_str;
3452
3453         matcherstr = g_string_sized_new(16);
3454         cmd_start = cmd_end = copy_str;
3455         while (cmd_end && *cmd_end) {
3456                 /* skip all white spaces */
3457                 while (*cmd_end && isspace(*cmd_end))
3458                         cmd_end++;
3459
3460                 /* extract a command */
3461                 while (*cmd_end && !isspace(*cmd_end))
3462                         cmd_end++;
3463
3464                 /* save character */
3465                 save_char = *cmd_end;
3466                 *cmd_end = '\0';
3467
3468                 dontmatch = FALSE;
3469                 casesens = FALSE;
3470
3471                 /* ~ and ! mean logical NOT */
3472                 if (*cmd_start == '~' || *cmd_start == '!')
3473                 {
3474                         dontmatch = TRUE;
3475                         cmd_start++;
3476                 }
3477                 /* % means case sensitive match */
3478                 if (*cmd_start == '%')
3479                 {
3480                         casesens = TRUE;
3481                         cmd_start++;
3482                 }
3483
3484                 /* find matching abbreviation */
3485                 for (i = 0; cmds[i].command; i++) {
3486                         if (!strcmp(cmd_start, cmds[i].abbreviated)) {
3487                                 /* restore character */
3488                                 *cmd_end = save_char;
3489
3490                                 /* copy command */
3491                                 if (matcherstr->len > 0) {
3492                                         g_string_append(matcherstr, " ");
3493                                 }
3494                                 if (dontmatch)
3495                                         g_string_append(matcherstr, "~");
3496                                 g_string_append(matcherstr, cmds[i].command);
3497                                 g_string_append(matcherstr, " ");
3498
3499                                 /* stop if no params required */
3500                                 if (cmds[i].numparams == 0)
3501                                         break;
3502
3503                                 /* extract a parameter, allow quotes */
3504                                 cmd_end++;
3505                                 cmd_start = cmd_end;
3506                                 if (*cmd_start == '"') {
3507                                         term_char = '"';
3508                                         cmd_end++;
3509                                 }
3510                                 else
3511                                         term_char = ' ';
3512
3513                                 /* extract actual parameter */
3514                                 while ((*cmd_end) && (*cmd_end != term_char))
3515                                         cmd_end++;
3516
3517                                 if (*cmd_end && (*cmd_end != term_char))
3518                                         break;
3519
3520                                 if (*cmd_end == '"')
3521                                         cmd_end++;
3522
3523                                 save_char = *cmd_end;
3524                                 *cmd_end = '\0';
3525
3526                                 if (cmds[i].qualifier) {
3527                                         if (casesens)
3528                                                 g_string_append(matcherstr, "regexp ");
3529                                         else
3530                                                 g_string_append(matcherstr, "regexpcase ");
3531                                 }
3532
3533                                 /* do we need to add quotes ? */
3534                                 if (cmds[i].quotes && term_char != '"')
3535                                         g_string_append(matcherstr, "\"");
3536
3537                                 /* copy actual parameter */
3538                                 g_string_append(matcherstr, cmd_start);
3539
3540                                 /* do we need to add quotes ? */
3541                                 if (cmds[i].quotes && term_char != '"')
3542                                         g_string_append(matcherstr, "\"");
3543
3544                                 /* restore original character */
3545                                 *cmd_end = save_char;
3546
3547                                 break;
3548                         }
3549                 }
3550
3551                 if (*cmd_end) {
3552                         cmd_end++;
3553                         cmd_start = cmd_end;
3554                 }
3555         }
3556
3557         g_free(copy_str);
3558         returnstr = matcherstr->str;
3559         g_string_free(matcherstr, FALSE);
3560         return returnstr;
3561 }
3562
3563 guint g_stricase_hash(gconstpointer gptr)
3564 {
3565         guint hash_result = 0;
3566         const char *str;
3567
3568         for (str = gptr; str && *str; str++) {
3569                 if (isupper(*str)) hash_result += (*str + ' ');
3570                 else hash_result += *str;
3571         }
3572
3573         return hash_result;
3574 }
3575
3576 gint g_stricase_equal(gconstpointer gptr1, gconstpointer gptr2)
3577 {
3578         const char *str1 = gptr1;
3579         const char *str2 = gptr2;
3580
3581         return !strcasecmp(str1, str2);
3582 }
3583
3584 gint g_int_compare(gconstpointer a, gconstpointer b)
3585 {
3586         return GPOINTER_TO_INT(a) - GPOINTER_TO_INT(b);
3587 }
3588
3589 gchar *generate_msgid(const gchar *address, gchar *buf, gint len)
3590 {
3591         /* steal from compose.c::compose_generate_msgid() */
3592         struct tm *lt;
3593         time_t t;
3594         gchar *addr;
3595
3596         t = time(NULL);
3597         lt = localtime(&t);
3598
3599         if (address && *address) {
3600                 if (strchr(address, '@'))
3601                         addr = g_strdup(address);
3602                 else
3603                         addr = g_strconcat(address, "@", get_domain_name(), NULL);
3604         } else
3605                 addr = g_strconcat(g_get_user_name(), "@", get_domain_name(),
3606                                    NULL);
3607
3608         g_snprintf(buf, len, "%04d%02d%02d%02d%02d%02d.%08x.%s",
3609                    lt->tm_year + 1900, lt->tm_mon + 1,
3610                    lt->tm_mday, lt->tm_hour,
3611                    lt->tm_min, lt->tm_sec,
3612                    (guint)random(), addr);
3613
3614         g_free(addr);
3615         return buf;
3616 }
3617
3618 /**
3619  * Create a new boundary in a way that it is very unlikely that this
3620  * will occur in the following text.  It would be easy to ensure
3621  * uniqueness if everything is either quoted-printable or base64
3622  * encoded (note that conversion is allowed), but because MIME bodies
3623  * may be nested, it may happen that the same boundary has already
3624  * been used. We avoid scanning the message for conflicts and hope the
3625  * best.
3626  *
3627  *   boundary := 0*69<bchars> bcharsnospace
3628  *   bchars := bcharsnospace / " "
3629  *   bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" /
3630  *                    "+" / "_" / "," / "-" / "." /
3631  *                    "/" / ":" / "=" / "?"  
3632  *
3633  * ":" and "," removed because of buggy MTAs
3634  */
3635
3636 gchar *generate_mime_boundary(void)
3637 {
3638         static gchar tbl[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
3639                              "abcdefghijklmnopqrstuvwxyz" 
3640                              "1234567890'()+_./=?";
3641         gchar bufuniq[17];
3642         gchar bufdate[BUFFSIZE];
3643         int i, equal;
3644         int pid;
3645
3646         pid = getpid();
3647
3648         /* We make the boundary depend on the pid, so that all running
3649          * processed generate different values even when they have been
3650          * started within the same second and srand48(time(NULL)) has been
3651          * used.  I can't see whether this is really an advantage but it
3652          * doesn't do any harm.
3653          */
3654         equal = -1;
3655         for (i = 0; i < sizeof(bufuniq) - 1; i++) {
3656                 bufuniq[i] = tbl[(lrand48() ^ pid) % (sizeof(tbl) - 1)];        /* fill with random */
3657                 if (bufuniq[i] == '=' && equal == -1)
3658                         equal = i;
3659         }
3660         bufuniq[i] = 0;
3661
3662         /* now make sure that we do have the sequence "=." in it which cannot
3663          * be matched by quoted-printable or base64 encoding */
3664         if (equal != -1 && (equal + 1) < i)
3665                 bufuniq[equal + 1] = '.';
3666         else {
3667                 bufuniq[0] = '=';
3668                 bufuniq[1] = '.';
3669         }
3670
3671         get_rfc822_date(bufdate, sizeof(bufdate));
3672         subst_char(bufdate, ' ', '_');
3673         subst_char(bufdate, ',', '_');
3674         subst_char(bufdate, ':', '_');
3675
3676         return g_strdup_printf("Multipart_%s_%s",
3677                                bufdate, bufuniq);
3678 }