2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2006 Hiroyuki Yamamoto & The Sylpheed-Claws Team
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.
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.
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
28 #include <glib/gi18n.h>
38 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
46 #include <sys/types.h>
48 # include <sys/wait.h>
55 #include <sys/utsname.h>
66 #include "../codeconv.h"
70 static gboolean debug_mode = FALSE;
72 static GSList *tempfiles=NULL;
76 #if !GLIB_CHECK_VERSION(2, 7, 0) && !defined(G_OS_UNIX)
77 gint g_chdir(const gchar *path)
80 if (G_WIN32_HAVE_WIDECHAR_API()) {
85 wpath = g_utf8_to_utf16(path, -1, NULL, NULL, NULL);
91 retval = _wchdir(wpath);
103 cp_path = g_locale_from_utf8(path, -1, NULL, NULL, NULL);
104 if (cp_path == NULL) {
109 retval = chdir(cp_path);
122 gint g_chmod(const gchar *path, gint mode)
125 if (G_WIN32_HAVE_WIDECHAR_API()) {
130 wpath = g_utf8_to_utf16(path, -1, NULL, NULL, NULL);
136 retval = _wchmod(wpath, mode);
148 cp_path = g_locale_from_utf8(path, -1, NULL, NULL, NULL);
149 if (cp_path == NULL) {
154 retval = chmod(cp_path, mode);
163 return chmod(path, mode);
166 #endif /* GLIB_CHECK_VERSION && G_OS_UNIX */
170 gint mkstemp_name(const gchar *template, gchar **name_used)
172 static gulong count=0; /* W32-_mktemp only supports up to 27
176 *name_used = g_strdup_printf("%s.%ld",_mktemp(template),count++);
177 tmpfd = open (*name_used, (O_CREAT | O_RDWR | O_BINARY
178 | S_IREAD | S_IWRITE));
180 tempfiles=g_slist_append(tempfiles, g_strdup(*name_used));
182 perror(g_strdup_printf("cant create %s",*name_used));
188 #endif /* G_OS_WIN32 */
191 gint mkstemp(const gchar *template)
194 gint res = mkstemp_name(template, &dummyname);
198 #endif /* G_OS_WIN32 */
200 void list_free_strings(GList *list)
202 list = g_list_first(list);
204 while (list != NULL) {
210 void slist_free_strings(GSList *list)
212 while (list != NULL) {
218 GSList *slist_concat_unique (GSList *first, GSList *second)
226 } else if (second == NULL)
229 for (tmp = second; tmp != NULL; tmp = g_slist_next(tmp)) {
230 if (g_slist_find(ret, tmp->data) == NULL)
231 ret = g_slist_prepend(ret, tmp->data);
236 static void hash_free_strings_func(gpointer key, gpointer value, gpointer data)
241 void hash_free_strings(GHashTable *table)
243 g_hash_table_foreach(table, hash_free_strings_func, NULL);
246 static void hash_free_value_mem_func(gpointer key, gpointer value,
252 void hash_free_value_mem(GHashTable *table)
254 g_hash_table_foreach(table, hash_free_value_mem_func, NULL);
257 gint str_case_equal(gconstpointer v, gconstpointer v2)
259 return g_ascii_strcasecmp((const gchar *)v, (const gchar *)v2) == 0;
262 guint str_case_hash(gconstpointer key)
264 const gchar *p = key;
268 h = g_ascii_tolower(h);
269 for (p += 1; *p != '\0'; p++)
270 h = (h << 5) - h + g_ascii_tolower(*p);
276 void ptr_array_free_strings(GPtrArray *array)
281 g_return_if_fail(array != NULL);
283 for (i = 0; i < array->len; i++) {
284 str = g_ptr_array_index(array, i);
289 gboolean str_find(const gchar *haystack, const gchar *needle)
291 return strstr(haystack, needle) != NULL ? TRUE : FALSE;
294 gboolean str_case_find(const gchar *haystack, const gchar *needle)
296 return strcasestr(haystack, needle) != NULL ? TRUE : FALSE;
299 gboolean str_find_equal(const gchar *haystack, const gchar *needle)
301 return strcmp(haystack, needle) == 0;
304 gboolean str_case_find_equal(const gchar *haystack, const gchar *needle)
306 return g_ascii_strcasecmp(haystack, needle) == 0;
309 gint to_number(const gchar *nstr)
311 register const gchar *p;
313 if (*nstr == '\0') return -1;
315 for (p = nstr; *p != '\0'; p++)
316 if (!g_ascii_isdigit(*p)) return -1;
321 /* convert integer into string,
322 nstr must be not lower than 11 characters length */
323 gchar *itos_buf(gchar *nstr, gint n)
325 g_snprintf(nstr, 11, "%d", n);
329 /* convert integer into string */
332 static gchar nstr[11];
334 return itos_buf(nstr, n);
337 gchar *to_human_readable(off_t size)
339 static gchar str[10];
342 g_snprintf(str, sizeof(str), _("%dB"), (gint)size);
343 else if (size >> 10 < 1024)
344 g_snprintf(str, sizeof(str), _("%.1fKB"), (gfloat)size / (1 << 10));
345 else if (size >> 20 < 1024)
346 g_snprintf(str, sizeof(str), _("%.2fMB"), (gfloat)size / (1 << 20));
348 g_snprintf(str, sizeof(str), _("%.2fGB"), (gfloat)size / (1 << 30));
353 /* strcmp with NULL-checking */
354 gint strcmp2(const gchar *s1, const gchar *s2)
356 if (s1 == NULL || s2 == NULL)
359 return strcmp(s1, s2);
361 /* strstr with NULL-checking */
362 gchar *strstr2(const gchar *s1, const gchar *s2)
364 if (s1 == NULL || s2 == NULL)
367 return strstr(s1, s2);
370 gint path_cmp(const gchar *s1, const gchar *s2)
375 gchar *s1buf, *s2buf;
378 if (s1 == NULL || s2 == NULL) return -1;
379 if (*s1 == '\0' || *s2 == '\0') return -1;
382 s1buf = g_strdup (s1);
383 s2buf = g_strdup (s2);
384 subst_char (s1buf, '/', G_DIR_SEPARATOR);
385 subst_char (s2buf, '/', G_DIR_SEPARATOR);
388 #endif /* !G_OS_WIN32 */
393 if (s1[len1 - 1] == G_DIR_SEPARATOR) len1--;
394 if (s2[len2 - 1] == G_DIR_SEPARATOR) len2--;
396 rc = strncmp(s1, s2, MAX(len1, len2));
400 #endif /* !G_OS_WIN32 */
404 /* remove trailing return code */
405 gchar *strretchomp(gchar *str)
409 if (!*str) return str;
411 for (s = str + strlen(str) - 1;
412 s >= str && (*s == '\n' || *s == '\r');
419 /* remove trailing character */
420 gchar *strtailchomp(gchar *str, gchar tail_char)
424 if (!*str) return str;
425 if (tail_char == '\0') return str;
427 for (s = str + strlen(str) - 1; s >= str && *s == tail_char; s--)
433 /* remove CR (carriage return) */
434 gchar *strcrchomp(gchar *str)
438 if (!*str) return str;
440 s = str + strlen(str) - 1;
441 if (*s == '\n' && s > str && *(s - 1) == '\r') {
449 void file_strip_crs(const gchar *file)
451 FILE *fp = NULL, *outfp = NULL;
453 gchar *out = get_tmp_file();
457 fp = fopen(file, "rb");
461 outfp = fopen(out, "wb");
467 while (fgets(buf, sizeof (buf), fp) != NULL) {
474 rename_force(out, file);
479 /* Similar to `strstr' but this function ignores the case of both strings. */
480 gchar *strcasestr(const gchar *haystack, const gchar *needle)
482 register size_t haystack_len, needle_len;
484 haystack_len = strlen(haystack);
485 needle_len = strlen(needle);
487 if (haystack_len < needle_len || needle_len == 0)
490 while (haystack_len >= needle_len) {
491 if (!g_ascii_strncasecmp(haystack, needle, needle_len))
492 return (gchar *)haystack;
502 gpointer my_memmem(gconstpointer haystack, size_t haystacklen,
503 gconstpointer needle, size_t needlelen)
505 const gchar *haystack_ = (const gchar *)haystack;
506 const gchar *needle_ = (const gchar *)needle;
507 const gchar *haystack_cur = (const gchar *)haystack;
510 return memchr(haystack_, *needle_, haystacklen);
512 while ((haystack_cur = memchr(haystack_cur, *needle_, haystacklen))
514 if (haystacklen - (haystack_cur - haystack_) < needlelen)
516 if (memcmp(haystack_cur + 1, needle_ + 1, needlelen - 1) == 0)
517 return (gpointer)haystack_cur;
525 /* Copy no more than N characters of SRC to DEST, with NULL terminating. */
526 gchar *strncpy2(gchar *dest, const gchar *src, size_t n)
528 register const gchar *s = src;
529 register gchar *d = dest;
539 int iswalnum(wint_t wc)
541 return g_ascii_isalnum((int)wc);
546 int iswspace(wint_t wc)
548 return g_ascii_isspace((int)wc);
553 wint_t towlower(wint_t wc)
555 if (wc >= L'A' && wc <= L'Z')
556 return wc + L'a' - L'A';
563 size_t wcslen(const wchar_t *s)
575 /* Copy SRC to DEST. */
576 wchar_t *wcscpy(wchar_t *dest, const wchar_t *src)
584 } while (c != L'\0');
591 /* Copy no more than N wide-characters of SRC to DEST. */
592 wchar_t *wcsncpy (wchar_t *dest, const wchar_t *src, size_t n)
602 } while (c != L'\0');
613 /* Duplicate S, returning an identical malloc'd string. */
614 wchar_t *wcsdup(const wchar_t *s)
619 new_str = g_new(wchar_t, wcslen(s) + 1);
627 /* Duplicate no more than N wide-characters of S,
628 returning an identical malloc'd string. */
629 wchar_t *wcsndup(const wchar_t *s, size_t n)
634 new_str = g_new(wchar_t, n + 1);
635 wcsncpy(new_str, s, n);
636 new_str[n] = (wchar_t)0;
643 wchar_t *strdup_mbstowcs(const gchar *s)
648 new_str = g_new(wchar_t, strlen(s) + 1);
649 if (mbstowcs(new_str, s, strlen(s) + 1) < 0) {
653 new_str = g_realloc(new_str,
654 sizeof(wchar_t) * (wcslen(new_str) + 1));
661 gchar *strdup_wcstombs(const wchar_t *s)
667 len = wcslen(s) * MB_CUR_MAX + 1;
668 new_str = g_new(gchar, len);
669 if (wcstombs(new_str, s, len) < 0) {
673 new_str = g_realloc(new_str, strlen(new_str) + 1);
680 /* Compare S1 and S2, ignoring case. */
681 gint wcsncasecmp(const wchar_t *s1, const wchar_t *s2, size_t n)
687 c1 = towlower(*s1++);
688 c2 = towlower(*s2++);
691 else if (c1 == 0 && c2 == 0)
698 /* Find the first occurrence of NEEDLE in HAYSTACK, ignoring case. */
699 wchar_t *wcscasestr(const wchar_t *haystack, const wchar_t *needle)
701 register size_t haystack_len, needle_len;
703 haystack_len = wcslen(haystack);
704 needle_len = wcslen(needle);
706 if (haystack_len < needle_len || needle_len == 0)
709 while (haystack_len >= needle_len) {
710 if (!wcsncasecmp(haystack, needle, needle_len))
711 return (wchar_t *)haystack;
721 gint get_mbs_len(const gchar *s)
731 mb_len = g_utf8_skip[*(guchar *)p];
743 /* Examine if next block is non-ASCII string */
744 gboolean is_next_nonascii(const gchar *s)
748 /* skip head space */
749 for (p = s; *p != '\0' && g_ascii_isspace(*p); p++)
751 for (; *p != '\0' && !g_ascii_isspace(*p); p++) {
752 if (*(guchar *)p > 127 || *(guchar *)p < 32)
759 gint get_next_word_len(const gchar *s)
763 for (; *s != '\0' && !g_ascii_isspace(*s); s++, len++)
769 /* compare subjects */
770 gint subject_compare(const gchar *s1, const gchar *s2)
774 if (!s1 || !s2) return -1;
775 if (!*s1 || !*s2) return -1;
777 Xstrdup_a(str1, s1, return -1);
778 Xstrdup_a(str2, s2, return -1);
780 trim_subject_for_compare(str1);
781 trim_subject_for_compare(str2);
783 if (!*str1 || !*str2) return -1;
785 return strcmp(str1, str2);
788 gint subject_compare_for_sort(const gchar *s1, const gchar *s2)
792 if (!s1 || !s2) return -1;
794 Xstrdup_a(str1, s1, return -1);
795 Xstrdup_a(str2, s2, return -1);
797 trim_subject_for_sort(str1);
798 trim_subject_for_sort(str2);
800 return g_utf8_collate(str1, str2);
803 void trim_subject_for_compare(gchar *str)
807 eliminate_parenthesis(str, '[', ']');
808 eliminate_parenthesis(str, '(', ')');
811 srcp = str + subject_get_prefix_length(str);
813 memmove(str, srcp, strlen(srcp) + 1);
816 void trim_subject_for_sort(gchar *str)
822 srcp = str + subject_get_prefix_length(str);
824 memmove(str, srcp, strlen(srcp) + 1);
827 void trim_subject(gchar *str)
829 register gchar *srcp;
835 srcp = str + subject_get_prefix_length(str);
840 } else if (*srcp == '(') {
852 else if (*srcp == cl)
859 while (g_ascii_isspace(*srcp)) srcp++;
860 memmove(str, srcp, strlen(srcp) + 1);
863 void eliminate_parenthesis(gchar *str, gchar op, gchar cl)
865 register gchar *srcp, *destp;
870 while ((destp = strchr(destp, op))) {
876 else if (*srcp == cl)
882 while (g_ascii_isspace(*srcp)) srcp++;
883 memmove(destp, srcp, strlen(srcp) + 1);
887 void extract_parenthesis(gchar *str, gchar op, gchar cl)
889 register gchar *srcp, *destp;
894 while ((srcp = strchr(destp, op))) {
897 memmove(destp, srcp + 1, strlen(srcp));
902 else if (*destp == cl)
914 void extract_parenthesis_with_skip_quote(gchar *str, gchar quote_chr,
917 register gchar *srcp, *destp;
919 gboolean in_quote = FALSE;
923 while ((srcp = strchr_with_skip_quote(destp, quote_chr, op))) {
926 memmove(destp, srcp + 1, strlen(srcp));
929 if (*destp == op && !in_quote)
931 else if (*destp == cl && !in_quote)
933 else if (*destp == quote_chr)
945 void eliminate_quote(gchar *str, gchar quote_chr)
947 register gchar *srcp, *destp;
951 while ((destp = strchr(destp, quote_chr))) {
952 if ((srcp = strchr(destp + 1, quote_chr))) {
954 while (g_ascii_isspace(*srcp)) srcp++;
955 memmove(destp, srcp, strlen(srcp) + 1);
963 void extract_quote(gchar *str, gchar quote_chr)
967 if ((str = strchr(str, quote_chr))) {
969 while ((p = strchr(p + 1, quote_chr)) && (p[-1] == '\\')) {
970 memmove(p - 1, p, strlen(p) + 1);
975 memmove(str, str + 1, p - str);
980 void eliminate_address_comment(gchar *str)
982 register gchar *srcp, *destp;
987 while ((destp = strchr(destp, '"'))) {
988 if ((srcp = strchr(destp + 1, '"'))) {
993 while (g_ascii_isspace(*srcp)) srcp++;
994 memmove(destp, srcp, strlen(srcp) + 1);
1004 while ((destp = strchr_with_skip_quote(destp, '"', '('))) {
1010 else if (*srcp == ')')
1016 while (g_ascii_isspace(*srcp)) srcp++;
1017 memmove(destp, srcp, strlen(srcp) + 1);
1021 gchar *strchr_with_skip_quote(const gchar *str, gint quote_chr, gint c)
1023 gboolean in_quote = FALSE;
1026 if (*str == c && !in_quote)
1027 return (gchar *)str;
1028 if (*str == quote_chr)
1036 gchar *strrchr_with_skip_quote(const gchar *str, gint quote_chr, gint c)
1038 gboolean in_quote = FALSE;
1041 p = str + strlen(str) - 1;
1043 if (*p == c && !in_quote)
1045 if (*p == quote_chr)
1053 void extract_address(gchar *str)
1055 eliminate_address_comment(str);
1056 if (strchr_with_skip_quote(str, '"', '<'))
1057 extract_parenthesis_with_skip_quote(str, '"', '<', '>');
1061 void extract_list_id_str(gchar *str)
1063 if (strchr_with_skip_quote(str, '"', '<'))
1064 extract_parenthesis_with_skip_quote(str, '"', '<', '>');
1068 static GSList *address_list_append_real(GSList *addr_list, const gchar *str, gboolean removecomments)
1073 if (!str) return addr_list;
1075 Xstrdup_a(work, str, return addr_list);
1078 eliminate_address_comment(work);
1081 while (workp && *workp) {
1084 if ((p = strchr_with_skip_quote(workp, '"', ','))) {
1090 if (removecomments && strchr_with_skip_quote(workp, '"', '<'))
1091 extract_parenthesis_with_skip_quote
1092 (workp, '"', '<', '>');
1096 addr_list = g_slist_append(addr_list, g_strdup(workp));
1104 GSList *address_list_append(GSList *addr_list, const gchar *str)
1106 return address_list_append_real(addr_list, str, TRUE);
1109 GSList *address_list_append_with_comments(GSList *addr_list, const gchar *str)
1111 return address_list_append_real(addr_list, str, FALSE);
1114 GSList *references_list_prepend(GSList *msgid_list, const gchar *str)
1118 if (!str) return msgid_list;
1121 while (strp && *strp) {
1122 const gchar *start, *end;
1125 if ((start = strchr(strp, '<')) != NULL) {
1126 end = strchr(start + 1, '>');
1131 msgid = g_strndup(start + 1, end - start - 1);
1134 msgid_list = g_slist_prepend(msgid_list, msgid);
1144 GSList *references_list_append(GSList *msgid_list, const gchar *str)
1148 list = references_list_prepend(NULL, str);
1149 list = g_slist_reverse(list);
1150 msgid_list = g_slist_concat(msgid_list, list);
1155 GSList *newsgroup_list_append(GSList *group_list, const gchar *str)
1160 if (!str) return group_list;
1162 Xstrdup_a(work, str, return group_list);
1166 while (workp && *workp) {
1169 if ((p = strchr_with_skip_quote(workp, '"', ','))) {
1177 group_list = g_slist_append(group_list,
1186 GList *add_history(GList *list, const gchar *str)
1190 g_return_val_if_fail(str != NULL, list);
1192 old = g_list_find_custom(list, (gpointer)str, (GCompareFunc)strcmp2);
1195 list = g_list_remove(list, old->data);
1196 } else if (g_list_length(list) >= MAX_HISTORY_SIZE) {
1199 last = g_list_last(list);
1202 list = g_list_remove(list, last->data);
1206 list = g_list_prepend(list, g_strdup(str));
1211 void remove_return(gchar *str)
1213 register gchar *p = str;
1216 if (*p == '\n' || *p == '\r')
1217 memmove(p, p + 1, strlen(p));
1223 void remove_space(gchar *str)
1225 register gchar *p = str;
1230 while (g_ascii_isspace(*(p + spc)))
1233 memmove(p, p + spc, strlen(p + spc) + 1);
1239 void unfold_line(gchar *str)
1241 register gchar *p = str;
1245 if (*p == '\n' || *p == '\r') {
1248 while (g_ascii_isspace(*(p + spc)))
1251 memmove(p, p + spc, strlen(p + spc) + 1);
1257 void subst_char(gchar *str, gchar orig, gchar subst)
1259 register gchar *p = str;
1268 void subst_chars(gchar *str, gchar *orig, gchar subst)
1270 register gchar *p = str;
1273 if (strchr(orig, *p) != NULL)
1279 void subst_for_filename(gchar *str)
1284 subst_chars(str, "\t\r\n\\/*:", '_');
1286 subst_chars(str, "\t\r\n\\/*", '_');
1290 void subst_for_shellsafe_filename(gchar *str)
1294 subst_for_filename(str);
1295 subst_chars(str, " \"'|&;()<>'!{}[]",'_');
1298 gboolean is_header_line(const gchar *str)
1300 if (str[0] == ':') return FALSE;
1302 while (*str != '\0' && *str != ' ') {
1311 gboolean is_ascii_str(const gchar *str)
1313 const guchar *p = (const guchar *)str;
1315 while (*p != '\0') {
1316 if (*p != '\t' && *p != ' ' &&
1317 *p != '\r' && *p != '\n' &&
1318 (*p < 32 || *p >= 127))
1326 gint get_quote_level(const gchar *str, const gchar *quote_chars)
1328 const gchar *first_pos;
1329 const gchar *last_pos;
1330 const gchar *p = str;
1331 gint quote_level = -1;
1333 /* speed up line processing by only searching to the last '>' */
1334 if ((first_pos = line_has_quote_char(str, quote_chars)) != NULL) {
1335 /* skip a line if it contains a '<' before the initial '>' */
1336 if (memchr(str, '<', first_pos - str) != NULL)
1338 last_pos = line_has_quote_char_last(first_pos, quote_chars);
1342 while (p <= last_pos) {
1343 while (p < last_pos) {
1344 if (g_ascii_isspace(*p))
1350 if (strchr(quote_chars, *p))
1352 else if (*p != '-' && !g_ascii_isspace(*p) && p <= last_pos) {
1353 /* any characters are allowed except '-' and space */
1355 && !strchr(quote_chars, *p)
1356 && !g_ascii_isspace(*p)
1359 if (strchr(quote_chars, *p))
1371 gint check_line_length(const gchar *str, gint max_chars, gint *line)
1373 const gchar *p = str, *q;
1374 gint cur_line = 0, len;
1376 while ((q = strchr(p, '\n')) != NULL) {
1378 if (len > max_chars) {
1388 if (len > max_chars) {
1397 const gchar * line_has_quote_char(const gchar * str, const gchar *quote_chars)
1399 gchar * position = NULL;
1400 gchar * tmp_pos = NULL;
1403 if (quote_chars == NULL)
1406 for (i = 0; i < strlen(quote_chars); i++) {
1407 tmp_pos = strchr (str, quote_chars[i]);
1409 || (tmp_pos != NULL && position >= tmp_pos) )
1415 const gchar * line_has_quote_char_last(const gchar * str, const gchar *quote_chars)
1417 gchar * position = NULL;
1418 gchar * tmp_pos = NULL;
1421 if (quote_chars == NULL)
1424 for (i = 0; i < strlen(quote_chars); i++) {
1425 tmp_pos = strrchr (str, quote_chars[i]);
1427 || (tmp_pos != NULL && position <= tmp_pos) )
1433 gchar *strstr_with_skip_quote(const gchar *haystack, const gchar *needle)
1435 register guint haystack_len, needle_len;
1436 gboolean in_squote = FALSE, in_dquote = FALSE;
1438 haystack_len = strlen(haystack);
1439 needle_len = strlen(needle);
1441 if (haystack_len < needle_len || needle_len == 0)
1444 while (haystack_len >= needle_len) {
1445 if (!in_squote && !in_dquote &&
1446 !strncmp(haystack, needle, needle_len))
1447 return (gchar *)haystack;
1449 /* 'foo"bar"' -> foo"bar"
1450 "foo'bar'" -> foo'bar' */
1451 if (*haystack == '\'') {
1454 else if (!in_dquote)
1456 } else if (*haystack == '\"') {
1459 else if (!in_squote)
1470 gchar *strchr_parenthesis_close(const gchar *str, gchar op, gchar cl)
1473 gchar quote_chr = '"';
1475 gboolean in_quote = FALSE;
1479 if ((p = strchr_with_skip_quote(p, quote_chr, op))) {
1483 if (*p == op && !in_quote)
1485 else if (*p == cl && !in_quote)
1487 else if (*p == quote_chr)
1500 gchar **strsplit_parenthesis(const gchar *str, gchar op, gchar cl,
1503 GSList *string_list = NULL, *slist;
1505 const gchar *s_op, *s_cl;
1508 g_return_val_if_fail(str != NULL, NULL);
1511 max_tokens = G_MAXINT;
1513 s_op = strchr_with_skip_quote(str, '"', op);
1514 if (!s_op) return NULL;
1516 s_cl = strchr_parenthesis_close(str, op, cl);
1524 new_string = g_new(gchar, len + 1);
1525 strncpy(new_string, str, len);
1526 new_string[len] = 0;
1527 string_list = g_slist_prepend(string_list, new_string);
1531 while (*str && g_ascii_isspace(*str)) str++;
1533 string_list = g_slist_prepend(string_list,
1536 s_op = strchr_with_skip_quote(str, '"', op);
1537 if (!--max_tokens || !s_op) break;
1541 s_cl = strchr_parenthesis_close(str, op, cl);
1542 } while (--max_tokens && s_cl);
1545 str_array = g_new(gchar*, n);
1549 str_array[i--] = NULL;
1550 for (slist = string_list; slist; slist = slist->next)
1551 str_array[i--] = slist->data;
1553 g_slist_free(string_list);
1558 gchar **strsplit_with_quote(const gchar *str, const gchar *delim,
1561 GSList *string_list = NULL, *slist;
1562 gchar **str_array, *s, *new_str;
1563 guint i, n = 1, len;
1565 g_return_val_if_fail(str != NULL, NULL);
1566 g_return_val_if_fail(delim != NULL, NULL);
1569 max_tokens = G_MAXINT;
1571 s = strstr_with_skip_quote(str, delim);
1573 guint delimiter_len = strlen(delim);
1577 new_str = g_strndup(str, len);
1579 if (new_str[0] == '\'' || new_str[0] == '\"') {
1580 if (new_str[len - 1] == new_str[0]) {
1581 new_str[len - 1] = '\0';
1582 memmove(new_str, new_str + 1, len - 1);
1585 string_list = g_slist_prepend(string_list, new_str);
1587 str = s + delimiter_len;
1588 s = strstr_with_skip_quote(str, delim);
1589 } while (--max_tokens && s);
1593 new_str = g_strdup(str);
1594 if (new_str[0] == '\'' || new_str[0] == '\"') {
1596 if (new_str[len - 1] == new_str[0]) {
1597 new_str[len - 1] = '\0';
1598 memmove(new_str, new_str + 1, len - 1);
1601 string_list = g_slist_prepend(string_list, new_str);
1605 str_array = g_new(gchar*, n);
1609 str_array[i--] = NULL;
1610 for (slist = string_list; slist; slist = slist->next)
1611 str_array[i--] = slist->data;
1613 g_slist_free(string_list);
1618 gchar *get_abbrev_newsgroup_name(const gchar *group, gint len)
1620 gchar *abbrev_group;
1622 const gchar *p = group;
1625 g_return_val_if_fail(group != NULL, NULL);
1627 last = group + strlen(group);
1628 abbrev_group = ap = g_malloc(strlen(group) + 1);
1633 if ((ap - abbrev_group) + (last - p) > len && strchr(p, '.')) {
1635 while (*p != '.') p++;
1638 return abbrev_group;
1643 return abbrev_group;
1646 gchar *trim_string(const gchar *str, gint len)
1648 const gchar *p = str;
1653 if (!str) return NULL;
1654 if (strlen(str) <= len)
1655 return g_strdup(str);
1656 if (g_utf8_validate(str, -1, NULL) == FALSE)
1657 return g_strdup(str);
1659 while (*p != '\0') {
1660 mb_len = g_utf8_skip[*(guchar *)p];
1663 else if (new_len + mb_len > len)
1670 Xstrndup_a(new_str, str, new_len, return g_strdup(str));
1671 return g_strconcat(new_str, "...", NULL);
1674 GList *uri_list_extract_filenames(const gchar *uri_list)
1676 GList *result = NULL;
1678 gchar *escaped_utf8uri;
1684 while (g_ascii_isspace(*p)) p++;
1685 if (!strncmp(p, "file:", 5)) {
1688 while (*q && *q != '\n' && *q != '\r') q++;
1691 gchar *file, *locale_file = NULL;
1693 while (q > p && g_ascii_isspace(*q))
1695 Xalloca(escaped_utf8uri, q - p + 2,
1697 Xalloca(file, q - p + 2,
1700 strncpy(escaped_utf8uri, p, q - p + 1);
1701 escaped_utf8uri[q - p + 1] = '\0';
1702 decode_uri(file, escaped_utf8uri);
1704 * g_filename_from_uri() rejects escaped/locale encoded uri
1705 * string which come from Nautilus.
1707 if (g_utf8_validate(file, -1, NULL))
1709 = conv_codeset_strdup(
1712 conv_get_locale_charset_str());
1714 locale_file = g_strdup(file + 5);
1715 result = g_list_append(result, locale_file);
1719 p = strchr(p, '\n');
1726 /* Converts two-digit hexadecimal to decimal. Used for unescaping escaped
1729 static gint axtoi(const gchar *hexstr)
1731 gint hi, lo, result;
1734 if ('0' <= hi && hi <= '9') {
1737 if ('a' <= hi && hi <= 'f') {
1740 if ('A' <= hi && hi <= 'F') {
1745 if ('0' <= lo && lo <= '9') {
1748 if ('a' <= lo && lo <= 'f') {
1751 if ('A' <= lo && lo <= 'F') {
1754 result = lo + (16 * hi);
1758 gboolean is_uri_string(const gchar *str)
1760 return (g_ascii_strncasecmp(str, "http://", 7) == 0 ||
1761 g_ascii_strncasecmp(str, "https://", 8) == 0 ||
1762 g_ascii_strncasecmp(str, "ftp://", 6) == 0 ||
1763 g_ascii_strncasecmp(str, "www.", 4) == 0);
1766 gchar *get_uri_path(const gchar *uri)
1768 if (g_ascii_strncasecmp(uri, "http://", 7) == 0)
1769 return (gchar *)(uri + 7);
1770 else if (g_ascii_strncasecmp(uri, "https://", 8) == 0)
1771 return (gchar *)(uri + 8);
1772 else if (g_ascii_strncasecmp(uri, "ftp://", 6) == 0)
1773 return (gchar *)(uri + 6);
1775 return (gchar *)uri;
1778 gint get_uri_len(const gchar *str)
1782 if (is_uri_string(str)) {
1783 for (p = str; *p != '\0'; p++) {
1784 if (!g_ascii_isgraph(*p) || strchr("()<>\"", *p))
1793 /* Decodes URL-Encoded strings (i.e. strings in which spaces are replaced by
1794 * plusses, and escape characters are used)
1796 void decode_uri(gchar *decoded_uri, const gchar *encoded_uri)
1798 gchar *dec = decoded_uri;
1799 const gchar *enc = encoded_uri;
1804 if (isxdigit((guchar)enc[0]) &&
1805 isxdigit((guchar)enc[1])) {
1823 gint scan_mailto_url(const gchar *mailto, gchar **to, gchar **cc, gchar **bcc,
1824 gchar **subject, gchar **body)
1829 Xstrdup_a(tmp_mailto, mailto, return -1);
1831 if (!strncmp(tmp_mailto, "mailto:", 7))
1834 p = strchr(tmp_mailto, '?');
1841 *to = g_strdup(tmp_mailto);
1844 gchar *field, *value;
1861 if (*value == '\0') continue;
1863 if (cc && !*cc && !g_ascii_strcasecmp(field, "cc")) {
1864 *cc = g_strdup(value);
1865 } else if (bcc && !*bcc && !g_ascii_strcasecmp(field, "bcc")) {
1866 *bcc = g_strdup(value);
1867 } else if (subject && !*subject &&
1868 !g_ascii_strcasecmp(field, "subject")) {
1869 *subject = g_malloc(strlen(value) + 1);
1870 decode_uri(*subject, value);
1871 } else if (body && !*body && !g_ascii_strcasecmp(field, "body")) {
1872 *body = g_malloc(strlen(value) + 1);
1873 decode_uri(*body, value);
1882 #include <windows.h>
1883 #ifndef CSIDL_APPDATA
1884 #define CSIDL_APPDATA 0x001a
1886 #ifndef CSIDL_LOCAL_APPDATA
1887 #define CSIDL_LOCAL_APPDATA 0x001c
1889 #ifndef CSIDL_FLAG_CREATE
1890 #define CSIDL_FLAG_CREATE 0x8000
1892 #define DIM(v) (sizeof(v)/sizeof((v)[0]))
1896 w32_strerror (int w32_errno)
1898 static char strerr[256];
1899 int ec = (int)GetLastError ();
1903 FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM, NULL, w32_errno,
1904 MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
1905 strerr, DIM (strerr)-1, NULL);
1909 static __inline__ void *
1910 dlopen (const char * name, int flag)
1912 void * hd = LoadLibrary (name);
1916 static __inline__ void *
1917 dlsym (void * hd, const char * sym)
1921 void * fnc = GetProcAddress (hd, sym);
1930 static __inline__ const char *
1933 return w32_strerror (0);
1937 static __inline__ int
1949 w32_shgetfolderpath (HWND a, int b, HANDLE c, DWORD d, LPSTR e)
1951 static int initialized;
1952 static HRESULT (WINAPI * func)(HWND,int,HANDLE,DWORD,LPSTR);
1956 static char *dllnames[] = { "shell32.dll", "shfolder.dll", NULL };
1962 for (i=0, handle = NULL; !handle && dllnames[i]; i++)
1964 handle = dlopen (dllnames[i], RTLD_LAZY);
1967 func = dlsym (handle, "SHGetFolderPathA");
1978 return func (a,b,c,d,e);
1984 const gchar *get_home_dir(void)
1987 static char home_dir[MAX_PATH] = "";
1989 if (home_dir[0] == '\0')
1991 if (w32_shgetfolderpath
1992 (NULL, CSIDL_APPDATA|CSIDL_FLAG_CREATE,
1993 NULL, 0, home_dir) < 0)
1994 strcpy (home_dir, "C:\\Sylpheed");
1998 return g_get_home_dir();
2002 const gchar *get_rc_dir(void)
2004 static gchar *rc_dir = NULL;
2007 rc_dir = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
2013 const gchar *get_mail_base_dir(void)
2016 static gchar *mail_base_dir = NULL;
2019 mail_base_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
2022 return mail_base_dir;
2024 return get_home_dir();
2028 const gchar *get_news_cache_dir(void)
2030 static gchar *news_cache_dir = NULL;
2032 if (!news_cache_dir)
2033 news_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
2034 NEWS_CACHE_DIR, NULL);
2036 return news_cache_dir;
2039 const gchar *get_imap_cache_dir(void)
2041 static gchar *imap_cache_dir = NULL;
2043 if (!imap_cache_dir)
2044 imap_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
2045 IMAP_CACHE_DIR, NULL);
2047 return imap_cache_dir;
2050 const gchar *get_mbox_cache_dir(void)
2052 static gchar *mbox_cache_dir = NULL;
2054 if (!mbox_cache_dir)
2055 mbox_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
2056 MBOX_CACHE_DIR, NULL);
2058 return mbox_cache_dir;
2061 const gchar *get_mime_tmp_dir(void)
2063 static gchar *mime_tmp_dir = NULL;
2066 mime_tmp_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
2067 MIME_TMP_DIR, NULL);
2069 return mime_tmp_dir;
2072 const gchar *get_template_dir(void)
2074 static gchar *template_dir = NULL;
2077 template_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
2078 TEMPLATE_DIR, NULL);
2080 return template_dir;
2083 const gchar *get_header_cache_dir(void)
2085 static gchar *header_dir = NULL;
2088 header_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
2089 HEADER_CACHE_DIR, NULL);
2094 /* Return the default directory for Plugins. */
2095 const gchar *get_plugin_dir(void)
2098 static gchar *plugin_dir = NULL;
2101 plugin_dir = g_strconcat(sylpheed_get_startup_dir(),
2102 "\\lib\\sylpheed-claws\\plugins\\",
2110 const gchar *get_tmp_dir(void)
2112 static gchar *tmp_dir = NULL;
2115 tmp_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
2121 gchar *get_tmp_file(void)
2124 static guint32 id = 0;
2126 tmp_file = g_strdup_printf("%s%ctmpfile.%08x",
2127 get_tmp_dir(), G_DIR_SEPARATOR, id++);
2132 const gchar *get_domain_name(void)
2135 static gchar *domain_name = NULL;
2141 if (uname(&uts) < 0) {
2143 domain_name = "unknown";
2145 if ((hp = my_gethostbyname(uts.nodename)) == NULL) {
2146 perror("gethostbyname");
2147 domain_name = g_strdup(uts.nodename);
2149 domain_name = g_strdup(hp->h_name);
2153 debug_print("domain name = %s\n", domain_name);
2162 off_t get_file_size(const gchar *file)
2166 if (g_stat(file, &s) < 0) {
2167 FILE_OP_ERROR(file, "stat");
2174 off_t get_file_size_as_crlf(const gchar *file)
2178 gchar buf[BUFFSIZE];
2180 if ((fp = g_fopen(file, "rb")) == NULL) {
2181 FILE_OP_ERROR(file, "fopen");
2185 while (fgets(buf, sizeof(buf), fp) != NULL) {
2187 size += strlen(buf) + 2;
2191 FILE_OP_ERROR(file, "fgets");
2200 off_t get_left_file_size(FILE *fp)
2206 if ((pos = ftell(fp)) < 0) {
2210 if (fseek(fp, 0L, SEEK_END) < 0) {
2214 if ((end = ftell(fp)) < 0) {
2219 if (fseek(fp, pos, SEEK_SET) < 0) {
2227 gboolean file_exist(const gchar *file, gboolean allow_fifo)
2234 if (g_stat(file, &s) < 0) {
2235 if (ENOENT != errno) FILE_OP_ERROR(file, "stat");
2239 if (S_ISREG(s.st_mode) || (allow_fifo && S_ISFIFO(s.st_mode)))
2246 /* Test on whether FILE is a relative file name. This is
2247 * straightforward for Unix but more complex for Windows. */
2248 gboolean is_relative_filename(const gchar *file)
2253 if ( *file == '\\' && file[1] == '\\' && strchr (file+2, '\\') )
2254 return FALSE; /* Prefixed with a hostname - this can't
2255 * be a relative name. */
2257 if ( ((*file >= 'a' && *file <= 'z')
2258 || (*file >= 'A' && *file <= 'Z'))
2260 file += 2; /* Skip drive letter. */
2262 return !(*file == '\\' || *file == '/');
2264 return !(*file == G_DIR_SEPARATOR);
2269 gboolean is_dir_exist(const gchar *dir)
2274 return g_file_test(dir, G_FILE_TEST_IS_DIR);
2277 gboolean is_file_entry_exist(const gchar *file)
2282 return g_file_test(file, G_FILE_TEST_EXISTS);
2285 gboolean dirent_is_regular_file(struct dirent *d)
2287 #ifdef HAVE_DIRENT_D_TYPE
2288 if (d->d_type == DT_REG)
2290 else if (d->d_type != DT_UNKNOWN)
2294 return g_file_test(d->d_name, G_FILE_TEST_IS_REGULAR);
2297 gboolean dirent_is_directory(struct dirent *d)
2299 #ifdef HAVE_DIRENT_D_TYPE
2300 if (d->d_type == DT_DIR)
2302 else if (d->d_type != DT_UNKNOWN)
2306 return g_file_test(d->d_name, G_FILE_TEST_IS_DIR);
2309 gint change_dir(const gchar *dir)
2311 gchar *prevdir = NULL;
2314 prevdir = g_get_current_dir();
2316 if (g_chdir(dir) < 0) {
2317 FILE_OP_ERROR(dir, "chdir");
2318 if (debug_mode) g_free(prevdir);
2320 } else if (debug_mode) {
2323 cwd = g_get_current_dir();
2324 if (strcmp(prevdir, cwd) != 0)
2325 g_print("current dir: %s\n", cwd);
2333 gint make_dir(const gchar *dir)
2335 if (g_mkdir(dir, S_IRWXU) < 0) {
2336 FILE_OP_ERROR(dir, "mkdir");
2339 if (g_chmod(dir, S_IRWXU) < 0)
2340 FILE_OP_ERROR(dir, "chmod");
2345 gint make_dir_hier(const gchar *dir)
2350 for (p = dir; (p = strchr(p, G_DIR_SEPARATOR)) != NULL; p++) {
2351 parent_dir = g_strndup(dir, p - dir);
2352 if (*parent_dir != '\0') {
2353 if (!is_dir_exist(parent_dir)) {
2354 if (make_dir(parent_dir) < 0) {
2363 if (!is_dir_exist(dir)) {
2364 if (make_dir(dir) < 0)
2371 gint remove_all_files(const gchar *dir)
2374 const gchar *dir_name;
2377 prev_dir = g_get_current_dir();
2379 if (g_chdir(dir) < 0) {
2380 FILE_OP_ERROR(dir, "chdir");
2385 if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2386 g_warning("failed to open directory: %s\n", dir);
2391 while ((dir_name = g_dir_read_name(dp)) != NULL) {
2392 if (g_unlink(dir_name) < 0)
2393 FILE_OP_ERROR(dir_name, "unlink");
2398 if (g_chdir(prev_dir) < 0) {
2399 FILE_OP_ERROR(prev_dir, "chdir");
2409 gint remove_numbered_files(const gchar *dir, guint first, guint last)
2412 const gchar *dir_name;
2416 prev_dir = g_get_current_dir();
2418 if (g_chdir(dir) < 0) {
2419 FILE_OP_ERROR(dir, "chdir");
2424 if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2425 g_warning("failed to open directory: %s\n", dir);
2430 while ((dir_name = g_dir_read_name(dp)) != NULL) {
2431 file_no = to_number(dir_name);
2432 if (file_no > 0 && first <= file_no && file_no <= last) {
2433 if (is_dir_exist(dir_name))
2435 if (g_unlink(dir_name) < 0)
2436 FILE_OP_ERROR(dir_name, "unlink");
2442 if (g_chdir(prev_dir) < 0) {
2443 FILE_OP_ERROR(prev_dir, "chdir");
2453 gint remove_numbered_files_not_in_list(const gchar *dir, GSList *numberlist)
2456 const gchar *dir_name;
2460 prev_dir = g_get_current_dir();
2462 if (g_chdir(dir) < 0) {
2463 FILE_OP_ERROR(dir, "chdir");
2468 if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2469 FILE_OP_ERROR(dir, "opendir");
2474 while ((dir_name = g_dir_read_name(dp)) != NULL) {
2475 file_no = to_number(dir_name);
2476 if (file_no > 0 && (g_slist_find(numberlist, GINT_TO_POINTER(file_no)) == NULL)) {
2477 debug_print("removing unwanted file %d from %s\n", file_no, dir);
2478 if (is_dir_exist(dir_name))
2480 if (g_unlink(dir_name) < 0)
2481 FILE_OP_ERROR(dir_name, "unlink");
2487 if (g_chdir(prev_dir) < 0) {
2488 FILE_OP_ERROR(prev_dir, "chdir");
2498 gint remove_all_numbered_files(const gchar *dir)
2500 return remove_numbered_files(dir, 0, UINT_MAX);
2503 gint remove_expired_files(const gchar *dir, guint hours)
2506 const gchar *dir_name;
2510 time_t mtime, now, expire_time;
2512 prev_dir = g_get_current_dir();
2514 if (g_chdir(dir) < 0) {
2515 FILE_OP_ERROR(dir, "chdir");
2520 if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2521 g_warning("failed to open directory: %s\n", dir);
2527 expire_time = hours * 60 * 60;
2529 while ((dir_name = g_dir_read_name(dp)) != NULL) {
2530 file_no = to_number(dir_name);
2532 if (g_stat(dir_name, &s) < 0) {
2533 FILE_OP_ERROR(dir_name, "stat");
2536 if (S_ISDIR(s.st_mode))
2538 mtime = MAX(s.st_mtime, s.st_atime);
2539 if (now - mtime > expire_time) {
2540 if (g_unlink(dir_name) < 0)
2541 FILE_OP_ERROR(dir_name, "unlink");
2548 if (g_chdir(prev_dir) < 0) {
2549 FILE_OP_ERROR(prev_dir, "chdir");
2559 gint remove_dir_recursive(const gchar *dir)
2563 const gchar *dir_name;
2566 if (g_stat(dir, &s) < 0) {
2567 FILE_OP_ERROR(dir, "stat");
2568 if (ENOENT == errno) return 0;
2572 if (!S_ISDIR(s.st_mode)) {
2573 if (g_unlink(dir) < 0) {
2574 FILE_OP_ERROR(dir, "unlink");
2581 prev_dir = g_get_current_dir();
2582 /* g_print("prev_dir = %s\n", prev_dir); */
2584 if (!path_cmp(prev_dir, dir)) {
2586 if (g_chdir("..") < 0) {
2587 FILE_OP_ERROR(dir, "chdir");
2590 prev_dir = g_get_current_dir();
2593 if (g_chdir(dir) < 0) {
2594 FILE_OP_ERROR(dir, "chdir");
2599 if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2600 g_warning("failed to open directory: %s\n", dir);
2606 /* remove all files in the directory */
2607 while ((dir_name = g_dir_read_name(dp)) != NULL) {
2608 /* g_print("removing %s\n", dir_name); */
2610 if (is_dir_exist(dir_name)) {
2611 if (remove_dir_recursive(dir_name) < 0) {
2612 g_warning("can't remove directory\n");
2616 if (g_unlink(dir_name) < 0)
2617 FILE_OP_ERROR(dir_name, "unlink");
2623 if (g_chdir(prev_dir) < 0) {
2624 FILE_OP_ERROR(prev_dir, "chdir");
2631 if (g_rmdir(dir) < 0) {
2632 FILE_OP_ERROR(dir, "rmdir");
2639 gint rename_force(const gchar *oldpath, const gchar *newpath)
2642 if (!is_file_entry_exist(oldpath)) {
2646 if (is_file_exist(newpath)) {
2647 if (g_unlink(newpath) < 0)
2648 FILE_OP_ERROR(newpath, "unlink");
2651 return g_rename(oldpath, newpath);
2655 /* this seems to be slower than the stdio version... */
2656 gint copy_file(const gchar *src, const gchar *dest)
2658 gint src_fd, dest_fd;
2662 gchar *dest_bak = NULL;
2664 if ((src_fd = open(src, O_RDONLY)) < 0) {
2665 FILE_OP_ERROR(src, "open");
2669 if (is_file_exist(dest)) {
2670 dest_bak = g_strconcat(dest, ".bak", NULL);
2671 if (rename_force(dest, dest_bak) < 0) {
2672 FILE_OP_ERROR(dest, "rename");
2679 if ((dest_fd = open(dest, O_RDWR|O_CREAT, S_IRUSR|S_IWUSR)) < 0) {
2680 FILE_OP_ERROR(dest, "open");
2683 if (rename(dest_bak, dest) < 0)
2684 FILE_OP_ERROR(dest_bak, "rename");
2690 while ((n_read = read(src_fd, buf, sizeof(buf))) > 0) {
2695 n_write = write(dest_fd, bufp, len);
2697 g_warning("writing to %s failed.\n", dest);
2702 if (rename(dest_bak, dest) < 0)
2703 FILE_OP_ERROR(dest_bak, "rename");
2716 if (n_read < 0 || get_file_size(src) != get_file_size(dest)) {
2717 g_warning("File copy from %s to %s failed.\n", src, dest);
2720 if (rename(dest_bak, dest) < 0)
2721 FILE_OP_ERROR(dest_bak, "rename");
2734 * Append src file body to the tail of dest file.
2735 * Now keep_backup has no effects.
2737 gint append_file(const gchar *src, const gchar *dest, gboolean keep_backup)
2739 FILE *src_fp, *dest_fp;
2743 gboolean err = FALSE;
2745 if ((src_fp = g_fopen(src, "rb")) == NULL) {
2746 FILE_OP_ERROR(src, "fopen");
2750 if ((dest_fp = g_fopen(dest, "ab")) == NULL) {
2751 FILE_OP_ERROR(dest, "fopen");
2756 if (change_file_mode_rw(dest_fp, dest) < 0) {
2757 FILE_OP_ERROR(dest, "chmod");
2758 g_warning("can't change file mode\n");
2761 while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
2762 if (n_read < sizeof(buf) && ferror(src_fp))
2764 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2765 g_warning("writing to %s failed.\n", dest);
2773 if (ferror(src_fp)) {
2774 FILE_OP_ERROR(src, "fread");
2778 if (fclose(dest_fp) == EOF) {
2779 FILE_OP_ERROR(dest, "fclose");
2791 gint copy_file(const gchar *src, const gchar *dest, gboolean keep_backup)
2793 FILE *src_fp, *dest_fp;
2796 gchar *dest_bak = NULL;
2797 gboolean err = FALSE;
2799 if ((src_fp = g_fopen(src, "rb")) == NULL) {
2800 FILE_OP_ERROR(src, "fopen");
2803 if (is_file_exist(dest)) {
2804 dest_bak = g_strconcat(dest, ".bak", NULL);
2805 if (rename_force(dest, dest_bak) < 0) {
2806 FILE_OP_ERROR(dest, "rename");
2813 if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2814 FILE_OP_ERROR(dest, "fopen");
2817 if (rename_force(dest_bak, dest) < 0)
2818 FILE_OP_ERROR(dest_bak, "rename");
2824 if (change_file_mode_rw(dest_fp, dest) < 0) {
2825 FILE_OP_ERROR(dest, "chmod");
2826 g_warning("can't change file mode\n");
2829 while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
2830 if (n_read < sizeof(buf) && ferror(src_fp))
2832 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2833 g_warning("writing to %s failed.\n", dest);
2838 if (rename_force(dest_bak, dest) < 0)
2839 FILE_OP_ERROR(dest_bak, "rename");
2846 if (ferror(src_fp)) {
2847 FILE_OP_ERROR(src, "fread");
2851 if (fclose(dest_fp) == EOF) {
2852 FILE_OP_ERROR(dest, "fclose");
2859 if (rename_force(dest_bak, dest) < 0)
2860 FILE_OP_ERROR(dest_bak, "rename");
2866 if (keep_backup == FALSE && dest_bak)
2874 gint move_file(const gchar *src, const gchar *dest, gboolean overwrite)
2876 if (overwrite == FALSE && is_file_exist(dest)) {
2877 g_warning("move_file(): file %s already exists.", dest);
2881 if (rename_force(src, dest) == 0) return 0;
2883 if (EXDEV != errno) {
2884 FILE_OP_ERROR(src, "rename");
2888 if (copy_file(src, dest, FALSE) < 0) return -1;
2895 gint copy_file_part_to_fp(FILE *fp, off_t offset, size_t length, FILE *dest_fp)
2898 gint bytes_left, to_read;
2901 if (fseek(fp, offset, SEEK_SET) < 0) {
2906 bytes_left = length;
2907 to_read = MIN(bytes_left, sizeof(buf));
2909 while ((n_read = fread(buf, sizeof(gchar), to_read, fp)) > 0) {
2910 if (n_read < to_read && ferror(fp))
2912 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2915 bytes_left -= n_read;
2916 if (bytes_left == 0)
2918 to_read = MIN(bytes_left, sizeof(buf));
2929 gint copy_file_part(FILE *fp, off_t offset, size_t length, const gchar *dest)
2932 gboolean err = FALSE;
2934 if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2935 FILE_OP_ERROR(dest, "fopen");
2939 if (change_file_mode_rw(dest_fp, dest) < 0) {
2940 FILE_OP_ERROR(dest, "chmod");
2941 g_warning("can't change file mode\n");
2944 if (copy_file_part_to_fp(fp, offset, length, dest_fp) < 0)
2947 if (!err && fclose(dest_fp) == EOF) {
2948 FILE_OP_ERROR(dest, "fclose");
2953 g_warning("writing to %s failed.\n", dest);
2961 /* convert line endings into CRLF. If the last line doesn't end with
2962 * linebreak, add it.
2964 gchar *canonicalize_str(const gchar *str)
2970 for (p = str; *p != '\0'; ++p) {
2977 if (p == str || *(p - 1) != '\n')
2980 out = outp = g_malloc(new_len + 1);
2981 for (p = str; *p != '\0'; ++p) {
2988 if (p == str || *(p - 1) != '\n') {
2997 gint canonicalize_file(const gchar *src, const gchar *dest)
2999 FILE *src_fp, *dest_fp;
3000 gchar buf[BUFFSIZE];
3002 gboolean err = FALSE;
3003 gboolean last_linebreak = FALSE;
3005 if ((src_fp = g_fopen(src, "rb")) == NULL) {
3006 FILE_OP_ERROR(src, "fopen");
3010 if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
3011 FILE_OP_ERROR(dest, "fopen");
3016 if (change_file_mode_rw(dest_fp, dest) < 0) {
3017 FILE_OP_ERROR(dest, "chmod");
3018 g_warning("can't change file mode\n");
3021 while (fgets(buf, sizeof(buf), src_fp) != NULL) {
3025 if (len == 0) break;
3026 last_linebreak = FALSE;
3028 if (buf[len - 1] != '\n') {
3029 last_linebreak = TRUE;
3030 r = fputs(buf, dest_fp);
3031 } else if (len > 1 && buf[len - 1] == '\n' && buf[len - 2] == '\r') {
3032 r = fputs(buf, dest_fp);
3035 r = fwrite(buf, 1, len - 1, dest_fp);
3040 r = fputs("\r\n", dest_fp);
3044 g_warning("writing to %s failed.\n", dest);
3052 if (last_linebreak == TRUE) {
3053 if (fputs("\r\n", dest_fp) == EOF)
3057 if (ferror(src_fp)) {
3058 FILE_OP_ERROR(src, "fgets");
3062 if (fclose(dest_fp) == EOF) {
3063 FILE_OP_ERROR(dest, "fclose");
3075 gint canonicalize_file_replace(const gchar *file)
3079 tmp_file = get_tmp_file();
3081 if (canonicalize_file(file, tmp_file) < 0) {
3086 if (move_file(tmp_file, file, TRUE) < 0) {
3087 g_warning("can't replace %s .\n", file);
3097 gint uncanonicalize_file(const gchar *src, const gchar *dest)
3099 FILE *src_fp, *dest_fp;
3100 gchar buf[BUFFSIZE];
3101 gboolean err = FALSE;
3103 if ((src_fp = g_fopen(src, "rb")) == NULL) {
3104 FILE_OP_ERROR(src, "fopen");
3108 if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
3109 FILE_OP_ERROR(dest, "fopen");
3114 if (change_file_mode_rw(dest_fp, dest) < 0) {
3115 FILE_OP_ERROR(dest, "chmod");
3116 g_warning("can't change file mode\n");
3119 while (fgets(buf, sizeof(buf), src_fp) != NULL) {
3121 if (fputs(buf, dest_fp) == EOF) {
3122 g_warning("writing to %s failed.\n", dest);
3130 if (ferror(src_fp)) {
3131 FILE_OP_ERROR(src, "fgets");
3135 if (fclose(dest_fp) == EOF) {
3136 FILE_OP_ERROR(dest, "fclose");
3148 gint uncanonicalize_file_replace(const gchar *file)
3152 tmp_file = get_tmp_file();
3154 if (uncanonicalize_file(file, tmp_file) < 0) {
3159 if (move_file(tmp_file, file, TRUE) < 0) {
3160 g_warning("can't replace %s .\n", file);
3170 gchar *normalize_newlines(const gchar *str)
3172 const gchar *p = str;
3175 out = outp = g_malloc(strlen(str) + 1);
3176 for (p = str; *p != '\0'; ++p) {
3178 if (*(p + 1) != '\n')
3189 gchar *get_outgoing_rfc2822_str(FILE *fp)
3191 gchar buf[BUFFSIZE];
3195 str = g_string_new(NULL);
3197 /* output header part */
3198 while (fgets(buf, sizeof(buf), fp) != NULL) {
3200 if (!g_ascii_strncasecmp(buf, "Bcc:", 4)) {
3207 else if (next != ' ' && next != '\t') {
3211 if (fgets(buf, sizeof(buf), fp) == NULL)
3215 g_string_append(str, buf);
3216 g_string_append(str, "\r\n");
3222 /* output body part */
3223 while (fgets(buf, sizeof(buf), fp) != NULL) {
3226 g_string_append_c(str, '.');
3227 g_string_append(str, buf);
3228 g_string_append(str, "\r\n");
3232 g_string_free(str, FALSE);
3238 * Create a new boundary in a way that it is very unlikely that this
3239 * will occur in the following text. It would be easy to ensure
3240 * uniqueness if everything is either quoted-printable or base64
3241 * encoded (note that conversion is allowed), but because MIME bodies
3242 * may be nested, it may happen that the same boundary has already
3245 * boundary := 0*69<bchars> bcharsnospace
3246 * bchars := bcharsnospace / " "
3247 * bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" /
3248 * "+" / "_" / "," / "-" / "." /
3249 * "/" / ":" / "=" / "?"
3251 * some special characters removed because of buggy MTAs
3254 gchar *generate_mime_boundary(const gchar *prefix)
3256 static gchar tbl[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
3257 "abcdefghijklmnopqrstuvwxyz"
3262 for (i = 0; i < sizeof(buf_uniq) - 1; i++)
3263 buf_uniq[i] = tbl[g_random_int_range(0, sizeof(tbl) - 1)];
3266 return g_strdup_printf("%s_%s", prefix ? prefix : "MP",
3270 gint change_file_mode_rw(FILE *fp, const gchar *file)
3273 return fchmod(fileno(fp), S_IRUSR|S_IWUSR);
3275 return g_chmod(file, S_IRUSR|S_IWUSR);
3279 FILE *my_tmpfile(void)
3281 #if HAVE_MKSTEMP || defined(G_OS_WIN32)
3282 const gchar suffix[] = ".XXXXXX";
3283 const gchar *tmpdir;
3285 const gchar *progname;
3291 tmpdir = get_tmp_dir();
3292 tmplen = strlen(tmpdir);
3293 progname = g_get_prgname();
3294 if (progname == NULL)
3295 progname = "sylpheed-claws";
3296 proglen = strlen(progname);
3297 Xalloca(fname, tmplen + 1 + proglen + sizeof(suffix),
3300 memcpy(fname, tmpdir, tmplen);
3301 fname[tmplen] = G_DIR_SEPARATOR;
3302 memcpy(fname + tmplen + 1, progname, proglen);
3303 memcpy(fname + tmplen + 1 + proglen, suffix, sizeof(suffix));
3305 fd = mkstemp(fname);
3313 fp = fdopen(fd, "w+b");
3318 #endif /* HAVE_MKSTEMP || G_OS_WIN32 */
3323 FILE *get_tmpfile_in_dir(const gchar *dir, gchar **filename)
3327 char *template = g_strdup_printf ("%s%csylpheed.XXXXXX",
3328 dir, G_DIR_SEPARATOR);
3329 fd = mkstemp_name(template, filename);
3332 *filename = g_strdup_printf("%s%csylpheed.XXXXXX", dir, G_DIR_SEPARATOR);
3333 fd = mkstemp(*filename);
3335 return fdopen(fd, "w+");
3338 FILE *str_open_as_stream(const gchar *str)
3343 g_return_val_if_fail(str != NULL, NULL);
3347 FILE_OP_ERROR("str_open_as_stream", "my_tmpfile");
3352 if (len == 0) return fp;
3354 if (fwrite(str, 1, len, fp) != len) {
3355 FILE_OP_ERROR("str_open_as_stream", "fwrite");
3364 gint str_write_to_file(const gchar *str, const gchar *file)
3369 g_return_val_if_fail(str != NULL, -1);
3370 g_return_val_if_fail(file != NULL, -1);
3372 if ((fp = g_fopen(file, "wb")) == NULL) {
3373 FILE_OP_ERROR(file, "fopen");
3383 if (fwrite(str, 1, len, fp) != len) {
3384 FILE_OP_ERROR(file, "fwrite");
3390 if (fclose(fp) == EOF) {
3391 FILE_OP_ERROR(file, "fclose");
3399 gchar *file_read_to_str(const gchar *file)
3404 g_return_val_if_fail(file != NULL, NULL);
3406 if ((fp = g_fopen(file, "rb")) == NULL) {
3407 FILE_OP_ERROR(file, "fopen");
3411 str = file_read_stream_to_str(fp);
3418 gchar *file_read_stream_to_str(FILE *fp)
3425 g_return_val_if_fail(fp != NULL, NULL);
3427 array = g_byte_array_new();
3429 while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
3430 if (n_read < sizeof(buf) && ferror(fp))
3432 g_byte_array_append(array, buf, n_read);
3436 FILE_OP_ERROR("file stream", "fread");
3437 g_byte_array_free(array, TRUE);
3442 g_byte_array_append(array, buf, 1);
3443 str = (gchar *)array->data;
3444 g_byte_array_free(array, FALSE);
3446 if (!g_utf8_validate(str, -1, NULL)) {
3447 const gchar *src_codeset, *dest_codeset;
3449 src_codeset = conv_get_locale_charset_str();
3450 dest_codeset = CS_UTF_8;
3451 tmp = conv_codeset_strdup(str, src_codeset, dest_codeset);
3459 gint execute_async(gchar *const argv[])
3461 g_return_val_if_fail(argv != NULL && argv[0] != NULL, -1);
3463 if (g_spawn_async(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH,
3464 NULL, NULL, NULL, FALSE) == FALSE) {
3465 g_warning("Can't execute command: %s\n", argv[0]);
3472 gint execute_sync(gchar *const argv[])
3476 g_return_val_if_fail(argv != NULL && argv[0] != NULL, -1);
3478 if (g_spawn_sync(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH,
3479 NULL, NULL, NULL, NULL, &status, NULL) == FALSE) {
3480 g_warning("Can't execute command: %s\n", argv[0]);
3485 if (WIFEXITED(status))
3486 return WEXITSTATUS(status);
3494 gint execute_command_line(const gchar *cmdline, gboolean async)
3499 debug_print("execute_command_line(): executing: %s\n", cmdline);
3501 argv = strsplit_with_quote(cmdline, " ", 0);
3504 ret = execute_async(argv);
3506 ret = execute_sync(argv);
3513 gchar *get_command_output(const gchar *cmdline)
3515 gchar *child_stdout;
3518 g_return_val_if_fail(cmdline != NULL, NULL);
3520 debug_print("get_command_output(): executing: %s\n", cmdline);
3522 if (g_spawn_command_line_sync(cmdline, &child_stdout, NULL, &status,
3524 g_warning("Can't execute command: %s\n", cmdline);
3528 return child_stdout;
3531 static gint is_unchanged_uri_char(char c)
3543 void encode_uri(gchar *encoded_uri, gint bufsize, const gchar *uri)
3549 for(i = 0; i < strlen(uri) ; i++) {
3550 if (is_unchanged_uri_char(uri[i])) {
3551 if (k + 2 >= bufsize)
3553 encoded_uri[k++] = uri[i];
3556 char * hexa = "0123456789ABCDEF";
3558 if (k + 4 >= bufsize)
3560 encoded_uri[k++] = '%';
3561 encoded_uri[k++] = hexa[uri[i] / 16];
3562 encoded_uri[k++] = hexa[uri[i] % 16];
3568 gint open_uri(const gchar *uri, const gchar *cmdline)
3570 gchar buf[BUFFSIZE];
3572 gchar encoded_uri[BUFFSIZE];
3574 g_return_val_if_fail(uri != NULL, -1);
3576 /* an option to choose whether to use encode_uri or not ? */
3577 encode_uri(encoded_uri, BUFFSIZE, uri);
3580 (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
3581 !strchr(p + 2, '%'))
3582 g_snprintf(buf, sizeof(buf), cmdline, encoded_uri);
3585 g_warning("Open URI command line is invalid "
3586 "(there must be only one '%%s'): %s",
3588 g_snprintf(buf, sizeof(buf), DEFAULT_BROWSER_CMD, encoded_uri);
3591 execute_command_line(buf, TRUE);
3596 gint open_txt_editor(const gchar *filepath, const gchar *cmdline)
3598 gchar buf[BUFFSIZE];
3601 g_return_val_if_fail(filepath != NULL, -1);
3604 (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
3605 !strchr(p + 2, '%'))
3606 g_snprintf(buf, sizeof(buf), cmdline, filepath);
3609 g_warning("Open Text Editor command line is invalid "
3610 "(there must be only one '%%s'): %s",
3612 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, filepath);
3615 execute_command_line(buf, TRUE);
3620 time_t remote_tzoffset_sec(const gchar *zone)
3622 static gchar ustzstr[] = "PSTPDTMSTMDTCSTCDTESTEDT";
3628 time_t remoteoffset;
3630 strncpy(zone3, zone, 3);
3634 if (sscanf(zone, "%c%d", &c, &offset) == 2 &&
3635 (c == '+' || c == '-')) {
3636 remoteoffset = ((offset / 100) * 60 + (offset % 100)) * 60;
3638 remoteoffset = -remoteoffset;
3639 } else if (!strncmp(zone, "UT" , 2) ||
3640 !strncmp(zone, "GMT", 2)) {
3642 } else if (strlen(zone3) == 3) {
3643 for (p = ustzstr; *p != '\0'; p += 3) {
3644 if (!g_ascii_strncasecmp(p, zone3, 3)) {
3645 iustz = ((gint)(p - ustzstr) / 3 + 1) / 2 - 8;
3646 remoteoffset = iustz * 3600;
3652 } else if (strlen(zone3) == 1) {
3654 case 'Z': remoteoffset = 0; break;
3655 case 'A': remoteoffset = -1; break;
3656 case 'B': remoteoffset = -2; break;
3657 case 'C': remoteoffset = -3; break;
3658 case 'D': remoteoffset = -4; break;
3659 case 'E': remoteoffset = -5; break;
3660 case 'F': remoteoffset = -6; break;
3661 case 'G': remoteoffset = -7; break;
3662 case 'H': remoteoffset = -8; break;
3663 case 'I': remoteoffset = -9; break;
3664 case 'K': remoteoffset = -10; break; /* J is not used */
3665 case 'L': remoteoffset = -11; break;
3666 case 'M': remoteoffset = -12; break;
3667 case 'N': remoteoffset = 1; break;
3668 case 'O': remoteoffset = 2; break;
3669 case 'P': remoteoffset = 3; break;
3670 case 'Q': remoteoffset = 4; break;
3671 case 'R': remoteoffset = 5; break;
3672 case 'S': remoteoffset = 6; break;
3673 case 'T': remoteoffset = 7; break;
3674 case 'U': remoteoffset = 8; break;
3675 case 'V': remoteoffset = 9; break;
3676 case 'W': remoteoffset = 10; break;
3677 case 'X': remoteoffset = 11; break;
3678 case 'Y': remoteoffset = 12; break;
3679 default: remoteoffset = 0; break;
3681 remoteoffset = remoteoffset * 3600;
3685 return remoteoffset;
3688 time_t tzoffset_sec(time_t *now)
3694 lt = localtime(now);
3696 off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
3698 if (lt->tm_year < gmt.tm_year)
3700 else if (lt->tm_year > gmt.tm_year)
3702 else if (lt->tm_yday < gmt.tm_yday)
3704 else if (lt->tm_yday > gmt.tm_yday)
3707 if (off >= 24 * 60) /* should be impossible */
3708 off = 23 * 60 + 59; /* if not, insert silly value */
3709 if (off <= -24 * 60)
3710 off = -(23 * 60 + 59);
3715 /* calculate timezone offset */
3716 gchar *tzoffset(time_t *now)
3718 static gchar offset_string[6];
3724 lt = localtime(now);
3726 off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
3728 if (lt->tm_year < gmt.tm_year)
3730 else if (lt->tm_year > gmt.tm_year)
3732 else if (lt->tm_yday < gmt.tm_yday)
3734 else if (lt->tm_yday > gmt.tm_yday)
3742 if (off >= 24 * 60) /* should be impossible */
3743 off = 23 * 60 + 59; /* if not, insert silly value */
3745 sprintf(offset_string, "%c%02d%02d", sign, off / 60, off % 60);
3747 return offset_string;
3750 void get_rfc822_date(gchar *buf, gint len)
3754 gchar day[4], mon[4];
3755 gint dd, hh, mm, ss, yyyy;
3760 sscanf(asctime(lt), "%3s %3s %d %d:%d:%d %d\n",
3761 day, mon, &dd, &hh, &mm, &ss, &yyyy);
3762 g_snprintf(buf, len, "%s, %d %s %d %02d:%02d:%02d %s",
3763 day, dd, mon, yyyy, hh, mm, ss, tzoffset(&t));
3766 /* just a wrapper to suppress the warning of gcc about %c */
3767 size_t my_strftime(gchar *s, size_t max, const gchar *format,
3768 const struct tm *tm)
3770 return strftime(s, max, format, tm);
3773 void debug_set_mode(gboolean mode)
3778 gboolean debug_get_mode(void)
3783 void debug_print_real(const gchar *format, ...)
3786 gchar buf[BUFFSIZE];
3788 if (!debug_mode) return;
3790 va_start(args, format);
3791 g_vsnprintf(buf, sizeof(buf), format, args);
3798 const char * debug_srcname(const char *file)
3800 const char *s = strrchr (file, '/');
3805 void * subject_table_lookup(GHashTable *subject_table, gchar * subject)
3807 if (subject == NULL)
3810 subject += subject_get_prefix_length(subject);
3812 return g_hash_table_lookup(subject_table, subject);
3815 void subject_table_insert(GHashTable *subject_table, gchar * subject,
3818 if (subject == NULL || *subject == 0)
3820 subject += subject_get_prefix_length(subject);
3821 g_hash_table_insert(subject_table, subject, data);
3824 void subject_table_remove(GHashTable *subject_table, gchar * subject)
3826 if (subject == NULL)
3829 subject += subject_get_prefix_length(subject);
3830 g_hash_table_remove(subject_table, subject);
3834 *\brief Check if a string is prefixed with known (combinations)
3835 * of prefixes. The function assumes that each prefix
3836 * is terminated by zero or exactly _one_ space.
3838 *\param str String to check for a prefixes
3840 *\return int Number of chars in the prefix that should be skipped
3841 * for a "clean" subject line. If no prefix was found, 0
3844 int subject_get_prefix_length(const gchar *subject)
3846 /*!< Array with allowable reply prefixes regexps. */
3847 static const gchar * const prefixes[] = {
3848 "Re\\:", /* "Re:" */
3849 "Re\\[[1-9][0-9]*\\]\\:", /* "Re[XXX]:" (non-conforming news mail clients) */
3850 "Antw\\:", /* "Antw:" (Dutch / German Outlook) */
3851 "Aw\\:", /* "Aw:" (German) */
3852 "Antwort\\:", /* "Antwort:" (German Lotus Notes) */
3853 "Res\\:", /* "Res:" (Brazilian Outlook) */
3854 "Fw\\:", /* "Fw:" Forward */
3855 "Enc\\:", /* "Enc:" Forward (Brazilian Outlook) */
3856 "Odp\\:", /* "Odp:" Re (Polish Outlook) */
3857 "Rif\\:" /* "Rif:" (Italian Outlook) */
3860 const int PREFIXES = sizeof prefixes / sizeof prefixes[0];
3863 static regex_t regex;
3864 static gboolean init_;
3866 if (!subject) return 0;
3867 if (!*subject) return 0;
3870 GString *s = g_string_new("");
3872 for (n = 0; n < PREFIXES; n++)
3873 /* Terminate each prefix regexpression by a
3874 * "\ ?" (zero or ONE space), and OR them */
3875 g_string_append_printf(s, "(%s\\ ?)%s",
3880 g_string_prepend(s, "(");
3881 g_string_append(s, ")+"); /* match at least once */
3882 g_string_prepend(s, "^\\ *"); /* from beginning of line */
3885 /* We now have something like "^\ *((PREFIX1\ ?)|(PREFIX2\ ?))+"
3886 * TODO: Should this be "^\ *(((PREFIX1)|(PREFIX2))\ ?)+" ??? */
3887 if (regcomp(®ex, s->str, REG_EXTENDED | REG_ICASE)) {
3888 debug_print("Error compiling regexp %s\n", s->str);
3889 g_string_free(s, TRUE);
3893 g_string_free(s, TRUE);
3897 if (!regexec(®ex, subject, 1, &pos, 0) && pos.rm_so != -1)
3903 guint g_stricase_hash(gconstpointer gptr)
3905 guint hash_result = 0;
3908 for (str = gptr; str && *str; str++) {
3909 if (isupper((guchar)*str)) hash_result += (*str + ' ');
3910 else hash_result += *str;
3916 gint g_stricase_equal(gconstpointer gptr1, gconstpointer gptr2)
3918 const char *str1 = gptr1;
3919 const char *str2 = gptr2;
3921 return !g_utf8_collate(str1, str2);
3924 gint g_int_compare(gconstpointer a, gconstpointer b)
3926 return GPOINTER_TO_INT(a) - GPOINTER_TO_INT(b);
3929 gchar *generate_msgid(gchar *buf, gint len)
3938 addr = g_strconcat("@", get_domain_name(), NULL);
3940 g_snprintf(buf, len, "%04d%02d%02d%02d%02d%02d.%08x%s",
3941 lt->tm_year + 1900, lt->tm_mon + 1,
3942 lt->tm_mday, lt->tm_hour,
3943 lt->tm_min, lt->tm_sec,
3944 (guint) rand(), addr);
3951 quote_cmd_argument()
3953 return a quoted string safely usable in argument of a command.
3955 code is extracted and adapted from etPan! project -- DINH V. Hoà .
3958 gint quote_cmd_argument(gchar * result, guint size,
3968 for(p = path ; * p != '\0' ; p ++) {
3970 if (isalnum((guchar)*p) || (* p == '/')) {
3971 if (remaining > 0) {
3977 result[size - 1] = '\0';
3982 if (remaining >= 2) {
3990 result[size - 1] = '\0';
3995 if (remaining > 0) {
3999 result[size - 1] = '\0';
4013 static void g_node_map_recursive(GNode *node, gpointer data)
4015 GNodeMapData *mapdata = (GNodeMapData *) data;
4017 GNodeMapData newmapdata;
4020 newdata = mapdata->func(node->data, mapdata->data);
4021 if (newdata != NULL) {
4022 newnode = g_node_new(newdata);
4023 g_node_append(mapdata->parent, newnode);
4025 newmapdata.parent = newnode;
4026 newmapdata.func = mapdata->func;
4027 newmapdata.data = mapdata->data;
4029 g_node_children_foreach(node, G_TRAVERSE_ALL, g_node_map_recursive, &newmapdata);
4033 GNode *g_node_map(GNode *node, GNodeMapFunc func, gpointer data)
4036 GNodeMapData mapdata;
4038 g_return_val_if_fail(node != NULL, NULL);
4039 g_return_val_if_fail(func != NULL, NULL);
4041 root = g_node_new(func(node->data, data));
4043 mapdata.parent = root;
4044 mapdata.func = func;
4045 mapdata.data = data;
4047 g_node_children_foreach(node, G_TRAVERSE_ALL, g_node_map_recursive, &mapdata);
4052 #define HEX_TO_INT(val, hex) \
4056 if ('0' <= c && c <= '9') { \
4058 } else if ('a' <= c && c <= 'f') { \
4059 val = c - 'a' + 10; \
4060 } else if ('A' <= c && c <= 'F') { \
4061 val = c - 'A' + 10; \
4067 gboolean get_hex_value(guchar *out, gchar c1, gchar c2)
4074 if (hi == -1 || lo == -1)
4077 *out = (hi << 4) + lo;
4081 #define INT_TO_HEX(hex, val) \
4084 hex = '0' + (val); \
4086 hex = 'A' + (val) - 10; \
4089 void get_hex_str(gchar *out, guchar ch)
4093 INT_TO_HEX(hex, ch >> 4);
4095 INT_TO_HEX(hex, ch & 0x0f);
4101 #define G_PRINT_REF 1 == 1 ? (void) 0 : (void)
4103 #define G_PRINT_REF g_print
4107 *\brief Register ref counted pointer. It is based on GBoxed, so should
4108 * work with anything that uses the GType system. The semantics
4109 * are similar to a C++ auto pointer, with the exception that
4110 * C doesn't have automatic closure (calling destructors) when
4111 * exiting a block scope.
4112 * Use the \ref G_TYPE_AUTO_POINTER macro instead of calling this
4113 * function directly.
4115 *\return GType A GType type.
4117 GType g_auto_pointer_register(void)
4119 static GType auto_pointer_type;
4120 if (!auto_pointer_type)
4122 g_boxed_type_register_static
4123 ("G_TYPE_AUTO_POINTER",
4124 (GBoxedCopyFunc) g_auto_pointer_copy,
4125 (GBoxedFreeFunc) g_auto_pointer_free);
4126 return auto_pointer_type;
4130 *\brief Structure with g_new() allocated pointer guarded by the
4133 typedef struct AutoPointerRef {
4134 void (*free) (gpointer);
4140 *\brief The auto pointer opaque structure that references the
4141 * pointer guard block.
4143 typedef struct AutoPointer {
4144 AutoPointerRef *ref;
4145 gpointer ptr; /*!< access to protected pointer */
4149 *\brief Creates an auto pointer for a g_new()ed pointer. Example:
4153 * ... tell gtk_list_store it should use a G_TYPE_AUTO_POINTER
4154 * ... when assigning, copying and freeing storage elements
4156 * gtk_list_store_new(N_S_COLUMNS,
4157 * G_TYPE_AUTO_POINTER,
4161 * Template *precious_data = g_new0(Template, 1);
4162 * g_pointer protect = g_auto_pointer_new(precious_data);
4164 * gtk_list_store_set(container, &iter,
4168 * ... the gtk_list_store has copied the pointer and
4169 * ... incremented its reference count, we should free
4170 * ... the auto pointer (in C++ a destructor would do
4171 * ... this for us when leaving block scope)
4173 * g_auto_pointer_free(protect);
4175 * ... gtk_list_store_set() now manages the data. When
4176 * ... *explicitly* requesting a pointer from the list
4177 * ... store, don't forget you get a copy that should be
4178 * ... freed with g_auto_pointer_free() eventually.
4182 *\param pointer Pointer to be guarded.
4184 *\return GAuto * Pointer that should be used in containers with
4187 GAuto *g_auto_pointer_new(gpointer p)
4189 AutoPointerRef *ref;
4195 ref = g_new0(AutoPointerRef, 1);
4196 ptr = g_new0(AutoPointer, 1);
4206 G_PRINT_REF ("XXXX ALLOC(%lx)\n", p);
4212 *\brief Allocate an autopointer using the passed \a free function to
4213 * free the guarded pointer
4215 GAuto *g_auto_pointer_new_with_free(gpointer p, GFreeFunc free_)
4222 aptr = g_auto_pointer_new(p);
4223 aptr->ref->free = free_;
4227 gpointer g_auto_pointer_get_ptr(GAuto *auto_ptr)
4229 if (auto_ptr == NULL)
4231 return ((AutoPointer *) auto_ptr)->ptr;
4235 *\brief Copies an auto pointer by. It's mostly not necessary
4236 * to call this function directly, unless you copy/assign
4237 * the guarded pointer.
4239 *\param auto_ptr Auto pointer returned by previous call to
4240 * g_auto_pointer_new_XXX()
4242 *\return gpointer An auto pointer
4244 GAuto *g_auto_pointer_copy(GAuto *auto_ptr)
4247 AutoPointerRef *ref;
4250 if (auto_ptr == NULL)
4255 newp = g_new0(AutoPointer, 1);
4258 newp->ptr = ref->pointer;
4262 G_PRINT_REF ("XXXX COPY(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4268 *\brief Free an auto pointer
4270 void g_auto_pointer_free(GAuto *auto_ptr)
4273 AutoPointerRef *ref;
4275 if (auto_ptr == NULL)
4281 if (--(ref->cnt) == 0) {
4283 G_PRINT_REF ("XXXX FREE(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4285 ref->free(ref->pointer);
4290 G_PRINT_REF ("XXXX DEREF(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4295 void replace_returns(gchar *str)
4300 while (strstr(str, "\n")) {
4301 *strstr(str, "\n") = ' ';
4303 while (strstr(str, "\r")) {
4304 *strstr(str, "\r") = ' ';
4308 /* get_uri_part() - retrieves a URI starting from scanpos.
4309 Returns TRUE if succesful */
4310 gboolean get_uri_part(const gchar *start, const gchar *scanpos,
4311 const gchar **bp, const gchar **ep, gboolean hdr)
4315 g_return_val_if_fail(start != NULL, FALSE);
4316 g_return_val_if_fail(scanpos != NULL, FALSE);
4317 g_return_val_if_fail(bp != NULL, FALSE);
4318 g_return_val_if_fail(ep != NULL, FALSE);
4322 /* find end point of URI */
4323 for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
4324 if (!g_ascii_isgraph(*(const guchar *)ep_) ||
4325 !IS_ASCII(*(const guchar *)ep_) ||
4326 strchr("[]{}()<>\"", *ep_))
4330 /* no punctuation at end of string */
4332 /* FIXME: this stripping of trailing punctuations may bite with other URIs.
4333 * should pass some URI type to this function and decide on that whether
4334 * to perform punctuation stripping */
4336 #define IS_REAL_PUNCT(ch) (g_ascii_ispunct(ch) && !strchr("/?=", ch))
4338 for (; ep_ - 1 > scanpos + 1 &&
4339 IS_REAL_PUNCT(*(ep_ - 1));
4343 #undef IS_REAL_PUNCT
4350 gchar *make_uri_string(const gchar *bp, const gchar *ep)
4352 return g_strndup(bp, ep - bp);
4355 /* valid mail address characters */
4356 #define IS_RFC822_CHAR(ch) \
4360 !g_ascii_isspace(ch) && \
4361 !strchr("(),;<>\"", (ch)))
4363 /* alphabet and number within 7bit ASCII */
4364 #define IS_ASCII_ALNUM(ch) (IS_ASCII(ch) && g_ascii_isalnum(ch))
4365 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
4367 static GHashTable *create_domain_tab(void)
4369 static const gchar *toplvl_domains [] = {
4371 "arpa", "coop", "info", "name", "biz", "com", "edu", "gov",
4372 "int", "mil", "net", "org", "ac", "ad", "ae", "af", "ag",
4373 "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au",
4374 "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi",
4375 "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
4376 "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl",
4377 "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz", "de",
4378 "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er",
4379 "es", "et", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd",
4380 "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
4381 "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
4382 "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir",
4383 "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
4384 "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc",
4385 "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
4386 "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq",
4387 "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na",
4388 "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu",
4389 "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm",
4390 "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru",
4391 "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj",
4392 "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
4393 "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to",
4394 "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um",
4395 "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu",
4396 "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw"
4399 GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
4401 g_return_val_if_fail(htab, NULL);
4402 for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++)
4403 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
4407 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
4409 const gint MAX_LVL_DOM_NAME_LEN = 6;
4410 gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
4411 const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
4414 if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
4417 for (p = buf; p < m && first < last; *p++ = *first++)
4421 return g_hash_table_lookup(tab, buf) != NULL;
4424 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
4425 gboolean get_email_part(const gchar *start, const gchar *scanpos,
4426 const gchar **bp, const gchar **ep, gboolean hdr)
4428 /* more complex than the uri part because we need to scan back and forward starting from
4429 * the scan position. */
4430 gboolean result = FALSE;
4431 const gchar *bp_ = NULL;
4432 const gchar *ep_ = NULL;
4433 static GHashTable *dom_tab;
4434 const gchar *last_dot = NULL;
4435 const gchar *prelast_dot = NULL;
4436 const gchar *last_tld_char = NULL;
4438 /* the informative part of the email address (describing the name
4439 * of the email address owner) may contain quoted parts. the
4440 * closure stack stores the last encountered quotes. */
4441 gchar closure_stack[128];
4442 gchar *ptr = closure_stack;
4444 g_return_val_if_fail(start != NULL, FALSE);
4445 g_return_val_if_fail(scanpos != NULL, FALSE);
4446 g_return_val_if_fail(bp != NULL, FALSE);
4447 g_return_val_if_fail(ep != NULL, FALSE);
4451 /* go to the real start */
4452 if (start[0] == ',')
4454 if (start[0] == ';')
4456 while (start[0] == ' ' || start[0] == '\t')
4461 /* find end (either , or ; or end of line) */
4462 if (strstr(start, ",") && strstr(start, ";"))
4463 *ep = strstr(start,",") < strstr(start, ";")
4464 ? strstr(start, ",") : strstr(start, ";");
4465 else if (strstr(start, ","))
4466 *ep = strstr(start, ",");
4467 else if (strstr(start, ";"))
4468 *ep = strstr(start, ";");
4470 *ep = start+strlen(start);
4472 /* check there's still an @ in that, or search
4473 * further if possible */
4474 if (strstr(start, "@") && strstr(start, "@") < *ep)
4476 else if (*ep < start+strlen(start)) {
4484 dom_tab = create_domain_tab();
4485 g_return_val_if_fail(dom_tab, FALSE);
4487 /* scan start of address */
4488 for (bp_ = scanpos - 1;
4489 bp_ >= start && IS_RFC822_CHAR(*(const guchar *)bp_); bp_--)
4492 /* TODO: should start with an alnum? */
4494 for (; bp_ < scanpos && !IS_ASCII_ALNUM(*(const guchar *)bp_); bp_++)
4497 if (bp_ != scanpos) {
4498 /* scan end of address */
4499 for (ep_ = scanpos + 1;
4500 *ep_ && IS_RFC822_CHAR(*(const guchar *)ep_); ep_++)
4502 prelast_dot = last_dot;
4504 if (*(last_dot + 1) == '.') {
4505 if (prelast_dot == NULL)
4507 last_dot = prelast_dot;
4512 /* TODO: really should terminate with an alnum? */
4513 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*(const guchar *)ep_);
4518 if (last_dot == NULL)
4520 if (last_dot >= ep_)
4521 last_dot = prelast_dot;
4522 if (last_dot == NULL || (scanpos + 1 >= last_dot))
4526 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
4527 if (*last_tld_char == '?')
4530 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
4537 if (!result) return FALSE;
4539 if (*ep_ && *(bp_ - 1) == '"' && *(ep_) == '"'
4540 && *(ep_ + 1) == ' ' && *(ep_ + 2) == '<'
4541 && IS_RFC822_CHAR(*(ep_ + 3))) {
4542 /* this informative part with an @ in it is
4543 * followed by the email address */
4546 /* go to matching '>' (or next non-rfc822 char, like \n) */
4547 for (; *ep_ != '>' && *ep != '\0' && IS_RFC822_CHAR(*ep_); ep_++)
4550 /* include the bracket */
4551 if (*ep_ == '>') ep_++;
4553 /* include the leading quote */
4561 /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
4562 if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
4565 /* see if this is <bracketed>; in this case we also scan for the informative part. */
4566 if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
4569 #define FULL_STACK() ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
4570 #define IN_STACK() (ptr > closure_stack)
4571 /* has underrun check */
4572 #define POP_STACK() if(IN_STACK()) --ptr
4573 /* has overrun check */
4574 #define PUSH_STACK(c) if(!FULL_STACK()) *ptr++ = (c); else return TRUE
4575 /* has underrun check */
4576 #define PEEK_STACK() (IN_STACK() ? *(ptr - 1) : 0)
4580 /* scan for the informative part. */
4581 for (bp_ -= 2; bp_ >= start; bp_--) {
4582 /* if closure on the stack keep scanning */
4583 if (PEEK_STACK() == *bp_) {
4587 if (*bp_ == '\'' || *bp_ == '"') {
4592 /* if nothing in the closure stack, do the special conditions
4593 * the following if..else expression simply checks whether
4594 * a token is acceptable. if not acceptable, the clause
4595 * should terminate the loop with a 'break' */
4596 if (!PEEK_STACK()) {
4598 && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
4599 && (((bp_ + 1) < ep_) && isalnum(*(bp_ + 1)))) {
4600 /* hyphens are allowed, but only in
4602 } else if (strchr(" \"'", *bp_)) {
4603 /* but anything not being a punctiation
4606 break; /* anything else is rejected */
4613 /* scan forward (should start with an alnum) */
4614 for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
4630 #undef IS_ASCII_ALNUM
4631 #undef IS_RFC822_CHAR
4633 gchar *make_email_string(const gchar *bp, const gchar *ep)
4635 /* returns a mailto: URI; mailto: is also used to detect the
4636 * uri type later on in the button_pressed signal handler */
4640 tmp = g_strndup(bp, ep - bp);
4641 result = g_strconcat("mailto:", tmp, NULL);
4647 gchar *make_http_string(const gchar *bp, const gchar *ep)
4649 /* returns an http: URI; */
4653 tmp = g_strndup(bp, ep - bp);
4654 result = g_strconcat("http://", tmp, NULL);
4660 static gchar *mailcap_get_command_in_file(const gchar *path, const gchar *type)
4662 FILE *fp = fopen(path, "rb");
4663 gchar buf[BUFFSIZE];
4664 gchar *result = NULL;
4667 while (fgets(buf, sizeof (buf), fp) != NULL) {
4668 gchar **parts = g_strsplit(buf, ";", -1);
4669 gchar *trimmed = parts[0];
4670 while (trimmed[0] == ' ')
4672 while (trimmed[strlen(trimmed)-1] == ' ')
4673 trimmed[strlen(trimmed)-1] = '\0';
4675 if (!strcmp(trimmed, type)) {
4677 while (trimmed[0] == ' ')
4679 while (trimmed[strlen(trimmed)-1] == ' ')
4680 trimmed[strlen(trimmed)-1] = '\0';
4681 while (trimmed[strlen(trimmed)-1] == '\n')
4682 trimmed[strlen(trimmed)-1] = '\0';
4683 while (trimmed[strlen(trimmed)-1] == '\r')
4684 trimmed[strlen(trimmed)-1] = '\0';
4685 result = g_strdup(trimmed);
4688 if (strstr(result, "%s") && !strstr(result, "'%s'")) {
4689 gchar *start = g_strdup(result);
4690 gchar *end = g_strdup(strstr(result, "%s")+2);
4692 *strstr(start, "%s") = '\0';
4693 tmp = g_strconcat(start,"'%s'",end, NULL);
4706 gchar *mailcap_get_command_for_type(const gchar *type)
4708 gchar *result = NULL;
4710 path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap", NULL);
4711 result = mailcap_get_command_in_file(path, type);
4715 result = mailcap_get_command_in_file("/etc/mailcap", type);
4719 gint copy_dir(const gchar *src, const gchar *dst)
4724 if ((dir = g_dir_open(src, 0, NULL)) == NULL) {
4725 g_warning("failed to open directory: %s\n", src);
4729 if (make_dir(dst) < 0)
4732 while ((name = g_dir_read_name(dir)) != NULL) {
4733 gchar *old_file, *new_file;
4734 old_file = g_strconcat(src, G_DIR_SEPARATOR_S, name, NULL);
4735 new_file = g_strconcat(dst, G_DIR_SEPARATOR_S, name, NULL);
4736 debug_print("copying: %s -> %s\n", old_file, new_file);
4737 if (g_file_test(old_file, G_FILE_TEST_IS_REGULAR)) {
4738 gint r = copy_file(old_file, new_file, TRUE);
4741 } else if (g_file_test(old_file, G_FILE_TEST_IS_DIR)) {
4742 gint r = copy_dir(old_file, new_file);
4750 /* crude test to see if a file is an email. */
4751 gboolean file_is_email (const gchar *filename)
4757 if (filename == NULL)
4759 if ((fp = g_fopen(filename, "rb")) == NULL)
4761 while (i < 60 && score < 3
4762 && fgets(buffer, sizeof (buffer), fp) > 0) {
4763 if (!strncmp(buffer, "From:", strlen("From:")))
4765 if (!strncmp(buffer, "To:", strlen("To:")))
4767 if (!strncmp(buffer, "Subject:", strlen("Subject:")))
4772 return (score >= 3);