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