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