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