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