2 * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2015 Hiroyuki Yamamoto & The Claws Mail 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 3 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, see <http://www.gnu.org/licenses/>.
18 * The code of the g_utf8_substring function below is owned by
19 * Matthias Clasen <matthiasc@src.gnome.org>/<mclasen@redhat.com>
20 * and is got from GLIB 2.30: https://git.gnome.org/browse/glib/commit/
21 * ?h=glib-2-30&id=9eb65dd3ed5e1a9638595cbe10699c7606376511
23 * GLib 2.30 is licensed under GPL v2 or later and:
24 * Copyright (C) 1999 Tom Tromey
25 * Copyright (C) 2000 Red Hat, Inc.
27 * https://git.gnome.org/browse/glib/tree/glib/gutf8.c
28 * ?h=glib-2-30&id=9eb65dd3ed5e1a9638595cbe10699c7606376511
33 #include "claws-features.h"
41 #include <glib/gi18n.h>
51 #include <sys/param.h>
53 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
61 #include <sys/types.h>
63 # include <sys/wait.h>
70 #include <sys/utsname.h>
83 #include "../codeconv.h"
87 static gboolean debug_mode = FALSE;
89 static GSList *tempfiles=NULL;
92 #if !GLIB_CHECK_VERSION(2, 26, 0)
93 guchar *g_base64_decode_wa(const gchar *text, gsize *out_len)
100 input_length = strlen(text);
102 ret = g_malloc0((input_length / 4) * 3 + 1);
104 *out_len = g_base64_decode_step(text, input_length, ret, &state, &save);
110 /* Return true if we are running as root. This function should beused
111 instead of getuid () == 0. */
112 gboolean superuser_p (void)
115 return w32_is_administrator ();
121 GSList *slist_copy_deep(GSList *list, GCopyFunc func)
123 #if GLIB_CHECK_VERSION(2, 34, 0)
124 return g_slist_copy_deep(list, func, NULL);
126 GSList *res = g_slist_copy(list);
129 walk->data = func(walk->data, NULL);
136 void list_free_strings(GList *list)
138 list = g_list_first(list);
140 while (list != NULL) {
146 void slist_free_strings(GSList *list)
148 while (list != NULL) {
154 void slist_free_strings_full(GSList *list)
156 #if GLIB_CHECK_VERSION(2,28,0)
157 g_slist_free_full(list, (GDestroyNotify)g_free);
159 g_slist_foreach(list, (GFunc)g_free, NULL);
164 static void hash_free_strings_func(gpointer key, gpointer value, gpointer data)
169 void hash_free_strings(GHashTable *table)
171 g_hash_table_foreach(table, hash_free_strings_func, NULL);
174 gint str_case_equal(gconstpointer v, gconstpointer v2)
176 return g_ascii_strcasecmp((const gchar *)v, (const gchar *)v2) == 0;
179 guint str_case_hash(gconstpointer key)
181 const gchar *p = key;
185 h = g_ascii_tolower(h);
186 for (p += 1; *p != '\0'; p++)
187 h = (h << 5) - h + g_ascii_tolower(*p);
193 void ptr_array_free_strings(GPtrArray *array)
198 cm_return_if_fail(array != NULL);
200 for (i = 0; i < array->len; i++) {
201 str = g_ptr_array_index(array, i);
206 gint to_number(const gchar *nstr)
208 register const gchar *p;
210 if (*nstr == '\0') return -1;
212 for (p = nstr; *p != '\0'; p++)
213 if (!g_ascii_isdigit(*p)) return -1;
218 /* convert integer into string,
219 nstr must be not lower than 11 characters length */
220 gchar *itos_buf(gchar *nstr, gint n)
222 g_snprintf(nstr, 11, "%d", n);
226 /* convert integer into string */
229 static gchar nstr[11];
231 return itos_buf(nstr, n);
234 #define divide(num,divisor,i,d) \
236 i = num >> divisor; \
237 d = num & ((1<<divisor)-1); \
238 d = (d*100) >> divisor; \
243 * \brief Convert a given size in bytes in a human-readable string
245 * \param size The size expressed in bytes to convert in string
246 * \return The string that respresents the size in an human-readable way
248 gchar *to_human_readable(goffset size)
250 static gchar str[14];
251 static gchar *b_format = NULL, *kb_format = NULL,
252 *mb_format = NULL, *gb_format = NULL;
253 register int t = 0, r = 0;
254 if (b_format == NULL) {
256 kb_format = _("%d.%02dKB");
257 mb_format = _("%d.%02dMB");
258 gb_format = _("%.2fGB");
261 if (size < (goffset)1024) {
262 g_snprintf(str, sizeof(str), b_format, (gint)size);
264 } else if (size >> 10 < (goffset)1024) {
265 divide(size, 10, t, r);
266 g_snprintf(str, sizeof(str), kb_format, t, r);
268 } else if (size >> 20 < (goffset)1024) {
269 divide(size, 20, t, r);
270 g_snprintf(str, sizeof(str), mb_format, t, r);
273 g_snprintf(str, sizeof(str), gb_format, (gfloat)(size >> 30));
278 /* strcmp with NULL-checking */
279 gint strcmp2(const gchar *s1, const gchar *s2)
281 if (s1 == NULL || s2 == NULL)
284 return strcmp(s1, s2);
286 /* strstr with NULL-checking */
287 gchar *strstr2(const gchar *s1, const gchar *s2)
289 if (s1 == NULL || s2 == NULL)
292 return strstr(s1, s2);
295 gint path_cmp(const gchar *s1, const gchar *s2)
300 gchar *s1buf, *s2buf;
303 if (s1 == NULL || s2 == NULL) return -1;
304 if (*s1 == '\0' || *s2 == '\0') return -1;
307 s1buf = g_strdup (s1);
308 s2buf = g_strdup (s2);
309 subst_char (s1buf, '/', G_DIR_SEPARATOR);
310 subst_char (s2buf, '/', G_DIR_SEPARATOR);
313 #endif /* !G_OS_WIN32 */
318 if (s1[len1 - 1] == G_DIR_SEPARATOR) len1--;
319 if (s2[len2 - 1] == G_DIR_SEPARATOR) len2--;
321 rc = strncmp(s1, s2, MAX(len1, len2));
325 #endif /* !G_OS_WIN32 */
329 /* remove trailing return code */
330 gchar *strretchomp(gchar *str)
334 if (!*str) return str;
336 for (s = str + strlen(str) - 1;
337 s >= str && (*s == '\n' || *s == '\r');
344 /* remove trailing character */
345 gchar *strtailchomp(gchar *str, gchar tail_char)
349 if (!*str) return str;
350 if (tail_char == '\0') return str;
352 for (s = str + strlen(str) - 1; s >= str && *s == tail_char; s--)
358 /* remove CR (carriage return) */
359 gchar *strcrchomp(gchar *str)
363 if (!*str) return str;
365 s = str + strlen(str) - 1;
366 if (*s == '\n' && s > str && *(s - 1) == '\r') {
374 gint file_strip_crs(const gchar *file)
376 FILE *fp = NULL, *outfp = NULL;
378 gchar *out = get_tmp_file();
382 fp = g_fopen(file, "rb");
386 outfp = g_fopen(out, "wb");
392 while (fgets(buf, sizeof (buf), fp) != NULL) {
394 if (fputs(buf, outfp) == EOF) {
402 if (fclose(outfp) == EOF) {
406 if (move_file(out, file, TRUE) < 0)
418 /* Similar to `strstr' but this function ignores the case of both strings. */
419 gchar *strcasestr(const gchar *haystack, const gchar *needle)
421 size_t haystack_len = strlen(haystack);
423 return strncasestr(haystack, haystack_len, needle);
426 gchar *strncasestr(const gchar *haystack, gint haystack_len, const gchar *needle)
428 register size_t needle_len;
430 needle_len = strlen(needle);
432 if (haystack_len < needle_len || needle_len == 0)
435 while (haystack_len >= needle_len) {
436 if (!g_ascii_strncasecmp(haystack, needle, needle_len))
437 return (gchar *)haystack;
447 gpointer my_memmem(gconstpointer haystack, size_t haystacklen,
448 gconstpointer needle, size_t needlelen)
450 const gchar *haystack_ = (const gchar *)haystack;
451 const gchar *needle_ = (const gchar *)needle;
452 const gchar *haystack_cur = (const gchar *)haystack;
453 size_t haystack_left = haystacklen;
456 return memchr(haystack_, *needle_, haystacklen);
458 while ((haystack_cur = memchr(haystack_cur, *needle_, haystack_left))
460 if (haystacklen - (haystack_cur - haystack_) < needlelen)
462 if (memcmp(haystack_cur + 1, needle_ + 1, needlelen - 1) == 0)
463 return (gpointer)haystack_cur;
466 haystack_left = haystacklen - (haystack_cur - haystack_);
473 /* Copy no more than N characters of SRC to DEST, with NULL terminating. */
474 gchar *strncpy2(gchar *dest, const gchar *src, size_t n)
476 register const gchar *s = src;
477 register gchar *d = dest;
487 /* Examine if next block is non-ASCII string */
488 gboolean is_next_nonascii(const gchar *s)
492 /* skip head space */
493 for (p = s; *p != '\0' && g_ascii_isspace(*p); p++)
495 for (; *p != '\0' && !g_ascii_isspace(*p); p++) {
496 if (*(guchar *)p > 127 || *(guchar *)p < 32)
503 gint get_next_word_len(const gchar *s)
507 for (; *s != '\0' && !g_ascii_isspace(*s); s++, len++)
513 static void trim_subject_for_compare(gchar *str)
517 eliminate_parenthesis(str, '[', ']');
518 eliminate_parenthesis(str, '(', ')');
521 srcp = str + subject_get_prefix_length(str);
523 memmove(str, srcp, strlen(srcp) + 1);
526 static void trim_subject_for_sort(gchar *str)
532 srcp = str + subject_get_prefix_length(str);
534 memmove(str, srcp, strlen(srcp) + 1);
537 /* compare subjects */
538 gint subject_compare(const gchar *s1, const gchar *s2)
542 if (!s1 || !s2) return -1;
543 if (!*s1 || !*s2) return -1;
545 Xstrdup_a(str1, s1, return -1);
546 Xstrdup_a(str2, s2, return -1);
548 trim_subject_for_compare(str1);
549 trim_subject_for_compare(str2);
551 if (!*str1 || !*str2) return -1;
553 return strcmp(str1, str2);
556 gint subject_compare_for_sort(const gchar *s1, const gchar *s2)
560 if (!s1 || !s2) return -1;
562 Xstrdup_a(str1, s1, return -1);
563 Xstrdup_a(str2, s2, return -1);
565 trim_subject_for_sort(str1);
566 trim_subject_for_sort(str2);
568 return g_utf8_collate(str1, str2);
571 void trim_subject(gchar *str)
573 register gchar *srcp;
579 srcp = str + subject_get_prefix_length(str);
584 } else if (*srcp == '(') {
596 else if (*srcp == cl)
603 while (g_ascii_isspace(*srcp)) srcp++;
604 memmove(str, srcp, strlen(srcp) + 1);
607 void eliminate_parenthesis(gchar *str, gchar op, gchar cl)
609 register gchar *srcp, *destp;
614 while ((destp = strchr(destp, op))) {
620 else if (*srcp == cl)
626 while (g_ascii_isspace(*srcp)) srcp++;
627 memmove(destp, srcp, strlen(srcp) + 1);
631 void extract_parenthesis(gchar *str, gchar op, gchar cl)
633 register gchar *srcp, *destp;
638 while ((srcp = strchr(destp, op))) {
641 memmove(destp, srcp + 1, strlen(srcp));
646 else if (*destp == cl)
658 static void extract_parenthesis_with_skip_quote(gchar *str, gchar quote_chr,
661 register gchar *srcp, *destp;
663 gboolean in_quote = FALSE;
667 while ((srcp = strchr_with_skip_quote(destp, quote_chr, op))) {
670 memmove(destp, srcp + 1, strlen(srcp));
673 if (*destp == op && !in_quote)
675 else if (*destp == cl && !in_quote)
677 else if (*destp == quote_chr)
689 void extract_quote(gchar *str, gchar quote_chr)
693 if ((str = strchr(str, quote_chr))) {
695 while ((p = strchr(p + 1, quote_chr)) && (p[-1] == '\\')) {
696 memmove(p - 1, p, strlen(p) + 1);
701 memmove(str, str + 1, p - str);
706 /* Returns a newly allocated string with all quote_chr not at the beginning
707 or the end of str escaped with '\' or the given str if not required. */
708 gchar *escape_internal_quotes(gchar *str, gchar quote_chr)
710 register gchar *p, *q;
714 if (str == NULL || *str == '\0')
717 /* search for unescaped quote_chr */
722 if (*p == quote_chr && *(p - 1) != '\\' && *(p + 1) != '\0')
726 if (!k) /* nothing to escape */
729 /* unescaped quote_chr found */
730 qstr = g_malloc(l + k + 1);
733 if (*p == quote_chr) {
738 if (*p == quote_chr && *(p - 1) != '\\' && *(p + 1) != '\0')
747 void eliminate_address_comment(gchar *str)
749 register gchar *srcp, *destp;
754 while ((destp = strchr(destp, '"'))) {
755 if ((srcp = strchr(destp + 1, '"'))) {
760 while (g_ascii_isspace(*srcp)) srcp++;
761 memmove(destp, srcp, strlen(srcp) + 1);
771 while ((destp = strchr_with_skip_quote(destp, '"', '('))) {
777 else if (*srcp == ')')
783 while (g_ascii_isspace(*srcp)) srcp++;
784 memmove(destp, srcp, strlen(srcp) + 1);
788 gchar *strchr_with_skip_quote(const gchar *str, gint quote_chr, gint c)
790 gboolean in_quote = FALSE;
793 if (*str == c && !in_quote)
795 if (*str == quote_chr)
803 void extract_address(gchar *str)
805 cm_return_if_fail(str != NULL);
806 eliminate_address_comment(str);
807 if (strchr_with_skip_quote(str, '"', '<'))
808 extract_parenthesis_with_skip_quote(str, '"', '<', '>');
812 void extract_list_id_str(gchar *str)
814 if (strchr_with_skip_quote(str, '"', '<'))
815 extract_parenthesis_with_skip_quote(str, '"', '<', '>');
819 static GSList *address_list_append_real(GSList *addr_list, const gchar *str, gboolean removecomments)
824 if (!str) return addr_list;
826 Xstrdup_a(work, str, return addr_list);
829 eliminate_address_comment(work);
832 while (workp && *workp) {
835 if ((p = strchr_with_skip_quote(workp, '"', ','))) {
841 if (removecomments && strchr_with_skip_quote(workp, '"', '<'))
842 extract_parenthesis_with_skip_quote
843 (workp, '"', '<', '>');
847 addr_list = g_slist_append(addr_list, g_strdup(workp));
855 GSList *address_list_append(GSList *addr_list, const gchar *str)
857 return address_list_append_real(addr_list, str, TRUE);
860 GSList *address_list_append_with_comments(GSList *addr_list, const gchar *str)
862 return address_list_append_real(addr_list, str, FALSE);
865 GSList *references_list_prepend(GSList *msgid_list, const gchar *str)
869 if (!str) return msgid_list;
872 while (strp && *strp) {
873 const gchar *start, *end;
876 if ((start = strchr(strp, '<')) != NULL) {
877 end = strchr(start + 1, '>');
882 msgid = g_strndup(start + 1, end - start - 1);
885 msgid_list = g_slist_prepend(msgid_list, msgid);
895 GSList *references_list_append(GSList *msgid_list, const gchar *str)
899 list = references_list_prepend(NULL, str);
900 list = g_slist_reverse(list);
901 msgid_list = g_slist_concat(msgid_list, list);
906 GSList *newsgroup_list_append(GSList *group_list, const gchar *str)
911 if (!str) return group_list;
913 Xstrdup_a(work, str, return group_list);
917 while (workp && *workp) {
920 if ((p = strchr_with_skip_quote(workp, '"', ','))) {
928 group_list = g_slist_append(group_list,
937 GList *add_history(GList *list, const gchar *str)
942 cm_return_val_if_fail(str != NULL, list);
944 old = g_list_find_custom(list, (gpointer)str, (GCompareFunc)strcmp2);
947 list = g_list_remove(list, old->data);
949 } else if (g_list_length(list) >= MAX_HISTORY_SIZE) {
952 last = g_list_last(list);
955 list = g_list_remove(list, last->data);
960 list = g_list_prepend(list, g_strdup(str));
965 void remove_return(gchar *str)
967 register gchar *p = str;
970 if (*p == '\n' || *p == '\r')
971 memmove(p, p + 1, strlen(p));
977 void remove_space(gchar *str)
979 register gchar *p = str;
984 while (g_ascii_isspace(*(p + spc)))
987 memmove(p, p + spc, strlen(p + spc) + 1);
993 void unfold_line(gchar *str)
995 register gchar *p = str;
999 if (*p == '\n' || *p == '\r') {
1002 while (g_ascii_isspace(*(p + spc)))
1005 memmove(p, p + spc, strlen(p + spc) + 1);
1011 void subst_char(gchar *str, gchar orig, gchar subst)
1013 register gchar *p = str;
1022 void subst_chars(gchar *str, gchar *orig, gchar subst)
1024 register gchar *p = str;
1027 if (strchr(orig, *p) != NULL)
1033 void subst_for_filename(gchar *str)
1038 subst_chars(str, "\t\r\n\\/*:", '_');
1040 subst_chars(str, "\t\r\n\\/*", '_');
1044 void subst_for_shellsafe_filename(gchar *str)
1048 subst_for_filename(str);
1049 subst_chars(str, " \"'|&;()<>'!{}[]",'_');
1052 gboolean is_ascii_str(const gchar *str)
1054 const guchar *p = (const guchar *)str;
1056 while (*p != '\0') {
1057 if (*p != '\t' && *p != ' ' &&
1058 *p != '\r' && *p != '\n' &&
1059 (*p < 32 || *p >= 127))
1067 static const gchar * line_has_quote_char_last(const gchar * str, const gchar *quote_chars)
1069 gchar * position = NULL;
1070 gchar * tmp_pos = NULL;
1073 if (quote_chars == NULL)
1076 for (i = 0; i < strlen(quote_chars); i++) {
1077 tmp_pos = strrchr (str, quote_chars[i]);
1079 || (tmp_pos != NULL && position <= tmp_pos) )
1085 gint get_quote_level(const gchar *str, const gchar *quote_chars)
1087 const gchar *first_pos;
1088 const gchar *last_pos;
1089 const gchar *p = str;
1090 gint quote_level = -1;
1092 /* speed up line processing by only searching to the last '>' */
1093 if ((first_pos = line_has_quote_char(str, quote_chars)) != NULL) {
1094 /* skip a line if it contains a '<' before the initial '>' */
1095 if (memchr(str, '<', first_pos - str) != NULL)
1097 last_pos = line_has_quote_char_last(first_pos, quote_chars);
1101 while (p <= last_pos) {
1102 while (p < last_pos) {
1103 if (g_ascii_isspace(*p))
1109 if (strchr(quote_chars, *p))
1111 else if (*p != '-' && !g_ascii_isspace(*p) && p <= last_pos) {
1112 /* any characters are allowed except '-','<' and space */
1113 while (*p != '-' && *p != '<'
1114 && !strchr(quote_chars, *p)
1115 && !g_ascii_isspace(*p)
1118 if (strchr(quote_chars, *p))
1130 gint check_line_length(const gchar *str, gint max_chars, gint *line)
1132 const gchar *p = str, *q;
1133 gint cur_line = 0, len;
1135 while ((q = strchr(p, '\n')) != NULL) {
1137 if (len > max_chars) {
1147 if (len > max_chars) {
1156 const gchar * line_has_quote_char(const gchar * str, const gchar *quote_chars)
1158 gchar * position = NULL;
1159 gchar * tmp_pos = NULL;
1162 if (quote_chars == NULL)
1165 for (i = 0; i < strlen(quote_chars); i++) {
1166 tmp_pos = strchr (str, quote_chars[i]);
1168 || (tmp_pos != NULL && position >= tmp_pos) )
1174 static gchar *strstr_with_skip_quote(const gchar *haystack, const gchar *needle)
1176 register guint haystack_len, needle_len;
1177 gboolean in_squote = FALSE, in_dquote = FALSE;
1179 haystack_len = strlen(haystack);
1180 needle_len = strlen(needle);
1182 if (haystack_len < needle_len || needle_len == 0)
1185 while (haystack_len >= needle_len) {
1186 if (!in_squote && !in_dquote &&
1187 !strncmp(haystack, needle, needle_len))
1188 return (gchar *)haystack;
1190 /* 'foo"bar"' -> foo"bar"
1191 "foo'bar'" -> foo'bar' */
1192 if (*haystack == '\'') {
1195 else if (!in_dquote)
1197 } else if (*haystack == '\"') {
1200 else if (!in_squote)
1202 } else if (*haystack == '\\') {
1214 gchar **strsplit_with_quote(const gchar *str, const gchar *delim,
1217 GSList *string_list = NULL, *slist;
1218 gchar **str_array, *s, *new_str;
1219 guint i, n = 1, len;
1221 cm_return_val_if_fail(str != NULL, NULL);
1222 cm_return_val_if_fail(delim != NULL, NULL);
1225 max_tokens = G_MAXINT;
1227 s = strstr_with_skip_quote(str, delim);
1229 guint delimiter_len = strlen(delim);
1233 new_str = g_strndup(str, len);
1235 if (new_str[0] == '\'' || new_str[0] == '\"') {
1236 if (new_str[len - 1] == new_str[0]) {
1237 new_str[len - 1] = '\0';
1238 memmove(new_str, new_str + 1, len - 1);
1241 string_list = g_slist_prepend(string_list, new_str);
1243 str = s + delimiter_len;
1244 s = strstr_with_skip_quote(str, delim);
1245 } while (--max_tokens && s);
1249 new_str = g_strdup(str);
1250 if (new_str[0] == '\'' || new_str[0] == '\"') {
1252 if (new_str[len - 1] == new_str[0]) {
1253 new_str[len - 1] = '\0';
1254 memmove(new_str, new_str + 1, len - 1);
1257 string_list = g_slist_prepend(string_list, new_str);
1261 str_array = g_new(gchar*, n);
1265 str_array[i--] = NULL;
1266 for (slist = string_list; slist; slist = slist->next)
1267 str_array[i--] = slist->data;
1269 g_slist_free(string_list);
1274 gchar *get_abbrev_newsgroup_name(const gchar *group, gint len)
1276 gchar *abbrev_group;
1278 const gchar *p = group;
1281 cm_return_val_if_fail(group != NULL, NULL);
1283 last = group + strlen(group);
1284 abbrev_group = ap = g_malloc(strlen(group) + 1);
1289 if ((ap - abbrev_group) + (last - p) > len && strchr(p, '.')) {
1291 while (*p != '.') p++;
1294 return abbrev_group;
1299 return abbrev_group;
1302 gchar *trim_string(const gchar *str, gint len)
1304 const gchar *p = str;
1309 if (!str) return NULL;
1310 if (strlen(str) <= len)
1311 return g_strdup(str);
1312 if (g_utf8_validate(str, -1, NULL) == FALSE)
1313 return g_strdup(str);
1315 while (*p != '\0') {
1316 mb_len = g_utf8_skip[*(guchar *)p];
1319 else if (new_len + mb_len > len)
1326 Xstrndup_a(new_str, str, new_len, return g_strdup(str));
1327 return g_strconcat(new_str, "...", NULL);
1330 GList *uri_list_extract_filenames(const gchar *uri_list)
1332 GList *result = NULL;
1334 gchar *escaped_utf8uri;
1340 while (g_ascii_isspace(*p)) p++;
1341 if (!strncmp(p, "file:", 5)) {
1344 while (*q && *q != '\n' && *q != '\r') q++;
1347 gchar *file, *locale_file = NULL;
1349 while (q > p && g_ascii_isspace(*q))
1351 Xalloca(escaped_utf8uri, q - p + 2,
1353 Xalloca(file, q - p + 2,
1356 strncpy(escaped_utf8uri, p, q - p + 1);
1357 escaped_utf8uri[q - p + 1] = '\0';
1358 decode_uri(file, escaped_utf8uri);
1360 * g_filename_from_uri() rejects escaped/locale encoded uri
1361 * string which come from Nautilus.
1364 if (g_utf8_validate(file, -1, NULL))
1366 = conv_codeset_strdup(
1369 conv_get_locale_charset_str());
1371 locale_file = g_strdup(file + 5);
1373 locale_file = g_filename_from_uri(escaped_utf8uri, NULL, NULL);
1375 result = g_list_append(result, locale_file);
1379 p = strchr(p, '\n');
1386 /* Converts two-digit hexadecimal to decimal. Used for unescaping escaped
1389 static gint axtoi(const gchar *hexstr)
1391 gint hi, lo, result;
1394 if ('0' <= hi && hi <= '9') {
1397 if ('a' <= hi && hi <= 'f') {
1400 if ('A' <= hi && hi <= 'F') {
1405 if ('0' <= lo && lo <= '9') {
1408 if ('a' <= lo && lo <= 'f') {
1411 if ('A' <= lo && lo <= 'F') {
1414 result = lo + (16 * hi);
1418 gboolean is_uri_string(const gchar *str)
1420 while (str && *str && g_ascii_isspace(*str))
1422 return (g_ascii_strncasecmp(str, "http://", 7) == 0 ||
1423 g_ascii_strncasecmp(str, "https://", 8) == 0 ||
1424 g_ascii_strncasecmp(str, "ftp://", 6) == 0 ||
1425 g_ascii_strncasecmp(str, "www.", 4) == 0);
1428 gchar *get_uri_path(const gchar *uri)
1430 while (uri && *uri && g_ascii_isspace(*uri))
1432 if (g_ascii_strncasecmp(uri, "http://", 7) == 0)
1433 return (gchar *)(uri + 7);
1434 else if (g_ascii_strncasecmp(uri, "https://", 8) == 0)
1435 return (gchar *)(uri + 8);
1436 else if (g_ascii_strncasecmp(uri, "ftp://", 6) == 0)
1437 return (gchar *)(uri + 6);
1439 return (gchar *)uri;
1442 gint get_uri_len(const gchar *str)
1446 if (is_uri_string(str)) {
1447 for (p = str; *p != '\0'; p++) {
1448 if (!g_ascii_isgraph(*p) || strchr("()<>\"", *p))
1457 /* Decodes URL-Encoded strings (i.e. strings in which spaces are replaced by
1458 * plusses, and escape characters are used)
1460 void decode_uri_with_plus(gchar *decoded_uri, const gchar *encoded_uri, gboolean with_plus)
1462 gchar *dec = decoded_uri;
1463 const gchar *enc = encoded_uri;
1468 if (isxdigit((guchar)enc[0]) &&
1469 isxdigit((guchar)enc[1])) {
1475 if (with_plus && *enc == '+')
1487 void decode_uri(gchar *decoded_uri, const gchar *encoded_uri)
1489 decode_uri_with_plus(decoded_uri, encoded_uri, TRUE);
1492 static gchar *decode_uri_gdup(const gchar *encoded_uri)
1494 gchar *buffer = g_malloc(strlen(encoded_uri)+1);
1495 decode_uri_with_plus(buffer, encoded_uri, FALSE);
1499 gint scan_mailto_url(const gchar *mailto, gchar **from, gchar **to, gchar **cc, gchar **bcc,
1500 gchar **subject, gchar **body, gchar ***attach, gchar **inreplyto)
1504 const gchar *forbidden_uris[] = { ".gnupg/",
1510 gint num_attach = 0;
1511 gchar **my_att = NULL;
1513 Xstrdup_a(tmp_mailto, mailto, return -1);
1515 if (!strncmp(tmp_mailto, "mailto:", 7))
1518 p = strchr(tmp_mailto, '?');
1525 *to = decode_uri_gdup(tmp_mailto);
1527 my_att = g_malloc(sizeof(char *));
1531 gchar *field, *value;
1548 if (*value == '\0') continue;
1550 if (from && !g_ascii_strcasecmp(field, "from")) {
1552 *from = decode_uri_gdup(value);
1554 gchar *tmp = decode_uri_gdup(value);
1555 gchar *new_from = g_strdup_printf("%s, %s", *from, tmp);
1559 } else if (cc && !g_ascii_strcasecmp(field, "cc")) {
1561 *cc = decode_uri_gdup(value);
1563 gchar *tmp = decode_uri_gdup(value);
1564 gchar *new_cc = g_strdup_printf("%s, %s", *cc, tmp);
1568 } else if (bcc && !g_ascii_strcasecmp(field, "bcc")) {
1570 *bcc = decode_uri_gdup(value);
1572 gchar *tmp = decode_uri_gdup(value);
1573 gchar *new_bcc = g_strdup_printf("%s, %s", *bcc, tmp);
1577 } else if (subject && !*subject &&
1578 !g_ascii_strcasecmp(field, "subject")) {
1579 *subject = decode_uri_gdup(value);
1580 } else if (body && !*body && !g_ascii_strcasecmp(field, "body")) {
1581 *body = decode_uri_gdup(value);
1582 } else if (body && !*body && !g_ascii_strcasecmp(field, "insert")) {
1583 gchar *tmp = decode_uri_gdup(value);
1584 if (!g_file_get_contents(tmp, body, NULL, NULL)) {
1585 g_warning("couldn't set insert file '%s' in body", value);
1589 } else if (attach && !g_ascii_strcasecmp(field, "attach")) {
1591 gchar *tmp = decode_uri_gdup(value);
1592 for (; forbidden_uris[i]; i++) {
1593 if (strstr(tmp, forbidden_uris[i])) {
1594 g_print("Refusing to attach '%s', potential private data leak\n",
1602 /* attach is correct */
1604 my_att = g_realloc(my_att, (sizeof(char *))*(num_attach+1));
1605 my_att[num_attach-1] = tmp;
1606 my_att[num_attach] = NULL;
1608 } else if (inreplyto && !*inreplyto &&
1609 !g_ascii_strcasecmp(field, "in-reply-to")) {
1610 *inreplyto = decode_uri_gdup(value);
1621 #include <windows.h>
1622 #ifndef CSIDL_APPDATA
1623 #define CSIDL_APPDATA 0x001a
1625 #ifndef CSIDL_LOCAL_APPDATA
1626 #define CSIDL_LOCAL_APPDATA 0x001c
1628 #ifndef CSIDL_FLAG_CREATE
1629 #define CSIDL_FLAG_CREATE 0x8000
1631 #define DIM(v) (sizeof(v)/sizeof((v)[0]))
1635 w32_strerror (int w32_errno)
1637 static char strerr[256];
1638 int ec = (int)GetLastError ();
1642 FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM, NULL, w32_errno,
1643 MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
1644 strerr, DIM (strerr)-1, NULL);
1648 static __inline__ void *
1649 dlopen (const char * name, int flag)
1651 void * hd = LoadLibrary (name);
1655 static __inline__ void *
1656 dlsym (void * hd, const char * sym)
1660 void * fnc = GetProcAddress (hd, sym);
1669 static __inline__ const char *
1672 return w32_strerror (0);
1676 static __inline__ int
1688 w32_shgetfolderpath (HWND a, int b, HANDLE c, DWORD d, LPSTR e)
1690 static int initialized;
1691 static HRESULT (WINAPI * func)(HWND,int,HANDLE,DWORD,LPSTR);
1695 static char *dllnames[] = { "shell32.dll", "shfolder.dll", NULL };
1701 for (i=0, handle = NULL; !handle && dllnames[i]; i++)
1703 handle = dlopen (dllnames[i], RTLD_LAZY);
1706 func = dlsym (handle, "SHGetFolderPathW");
1717 return func (a,b,c,d,e);
1722 /* Returns a static string with the directroy from which the module
1723 has been loaded. Returns an empty string on error. */
1724 static char *w32_get_module_dir(void)
1726 static char *moddir;
1729 char name[MAX_PATH+10];
1732 if ( !GetModuleFileNameA (0, name, sizeof (name)-10) )
1735 p = strrchr (name, '\\');
1741 moddir = g_strdup (name);
1745 #endif /* G_OS_WIN32 */
1747 /* Return a static string with the locale dir. */
1748 const gchar *get_locale_dir(void)
1750 static gchar *loc_dir;
1754 loc_dir = g_strconcat(w32_get_module_dir(), G_DIR_SEPARATOR_S,
1755 "\\share\\locale", NULL);
1758 loc_dir = LOCALEDIR;
1764 const gchar *get_home_dir(void)
1767 static char home_dir_utf16[MAX_PATH] = "";
1768 static gchar *home_dir_utf8 = NULL;
1769 if (home_dir_utf16[0] == '\0') {
1770 if (w32_shgetfolderpath
1771 (NULL, CSIDL_APPDATA|CSIDL_FLAG_CREATE,
1772 NULL, 0, home_dir_utf16) < 0)
1773 strcpy (home_dir_utf16, "C:\\Sylpheed");
1774 home_dir_utf8 = g_utf16_to_utf8 ((const gunichar *)home_dir_utf16, -1, NULL, NULL, NULL);
1776 return home_dir_utf8;
1778 static const gchar *homeenv = NULL;
1783 if (!homeenv && g_getenv("HOME") != NULL)
1784 homeenv = g_strdup(g_getenv("HOME"));
1786 homeenv = g_get_home_dir();
1792 static gchar *claws_rc_dir = NULL;
1793 static gboolean rc_dir_alt = FALSE;
1794 const gchar *get_rc_dir(void)
1797 if (!claws_rc_dir) {
1798 claws_rc_dir = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
1800 debug_print("using default rc_dir %s\n", claws_rc_dir);
1802 return claws_rc_dir;
1805 void set_rc_dir(const gchar *dir)
1807 gchar *canonical_dir;
1808 if (claws_rc_dir != NULL) {
1809 g_print("Error: rc_dir already set\n");
1811 int err = cm_canonicalize_filename(dir, &canonical_dir);
1815 g_print("Error looking for %s: %d(%s)\n",
1816 dir, -err, g_strerror(-err));
1821 claws_rc_dir = canonical_dir;
1823 len = strlen(claws_rc_dir);
1824 if (claws_rc_dir[len - 1] == G_DIR_SEPARATOR)
1825 claws_rc_dir[len - 1] = '\0';
1827 debug_print("set rc_dir to %s\n", claws_rc_dir);
1828 if (!is_dir_exist(claws_rc_dir)) {
1829 if (make_dir_hier(claws_rc_dir) != 0) {
1830 g_print("Error: can't create %s\n",
1838 gboolean rc_dir_is_alt(void) {
1842 const gchar *get_mail_base_dir(void)
1844 return get_home_dir();
1847 const gchar *get_news_cache_dir(void)
1849 static gchar *news_cache_dir = NULL;
1850 if (!news_cache_dir)
1851 news_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1852 NEWS_CACHE_DIR, NULL);
1854 return news_cache_dir;
1857 const gchar *get_imap_cache_dir(void)
1859 static gchar *imap_cache_dir = NULL;
1861 if (!imap_cache_dir)
1862 imap_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1863 IMAP_CACHE_DIR, NULL);
1865 return imap_cache_dir;
1868 const gchar *get_mime_tmp_dir(void)
1870 static gchar *mime_tmp_dir = NULL;
1873 mime_tmp_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1874 MIME_TMP_DIR, NULL);
1876 return mime_tmp_dir;
1879 const gchar *get_template_dir(void)
1881 static gchar *template_dir = NULL;
1884 template_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1885 TEMPLATE_DIR, NULL);
1887 return template_dir;
1891 const gchar *get_cert_file(void)
1893 const gchar *cert_file = NULL;
1895 cert_file = g_strconcat(w32_get_module_dir(),
1896 "\\share\\claws-mail\\",
1897 "ca-certificates.crt",
1903 /* Return the filepath of the claws-mail.desktop file */
1904 const gchar *get_desktop_file(void)
1906 #ifdef DESKTOPFILEPATH
1907 return DESKTOPFILEPATH;
1913 /* Return the default directory for Plugins. */
1914 const gchar *get_plugin_dir(void)
1917 static gchar *plugin_dir = NULL;
1920 plugin_dir = g_strconcat(w32_get_module_dir(),
1921 "\\lib\\claws-mail\\plugins\\",
1925 if (is_dir_exist(PLUGINDIR))
1928 static gchar *plugin_dir = NULL;
1930 plugin_dir = g_strconcat(get_rc_dir(),
1931 G_DIR_SEPARATOR_S, "plugins",
1932 G_DIR_SEPARATOR_S, NULL);
1940 /* Return the default directory for Themes. */
1941 const gchar *get_themes_dir(void)
1943 static gchar *themes_dir = NULL;
1946 themes_dir = g_strconcat(w32_get_module_dir(),
1947 "\\share\\claws-mail\\themes",
1953 const gchar *get_tmp_dir(void)
1955 static gchar *tmp_dir = NULL;
1958 tmp_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1964 gchar *get_tmp_file(void)
1967 static guint32 id = 0;
1969 tmp_file = g_strdup_printf("%s%ctmpfile.%08x",
1970 get_tmp_dir(), G_DIR_SEPARATOR, id++);
1975 const gchar *get_domain_name(void)
1978 static gchar *domain_name = NULL;
1979 struct addrinfo hints, *res;
1984 if (gethostname(hostname, sizeof(hostname)) != 0) {
1985 perror("gethostname");
1986 domain_name = "localhost";
1988 memset(&hints, 0, sizeof(struct addrinfo));
1989 hints.ai_family = AF_UNSPEC;
1990 hints.ai_socktype = 0;
1991 hints.ai_flags = AI_CANONNAME;
1992 hints.ai_protocol = 0;
1994 s = getaddrinfo(hostname, NULL, &hints, &res);
1996 fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
1997 domain_name = g_strdup(hostname);
1999 domain_name = g_strdup(res->ai_canonname);
2003 debug_print("domain name = %s\n", domain_name);
2012 off_t get_file_size(const gchar *file)
2016 if (g_stat(file, &s) < 0) {
2017 FILE_OP_ERROR(file, "stat");
2024 time_t get_file_mtime(const gchar *file)
2028 if (g_stat(file, &s) < 0) {
2029 FILE_OP_ERROR(file, "stat");
2036 off_t get_file_size_as_crlf(const gchar *file)
2040 gchar buf[BUFFSIZE];
2042 if ((fp = g_fopen(file, "rb")) == NULL) {
2043 FILE_OP_ERROR(file, "g_fopen");
2047 while (fgets(buf, sizeof(buf), fp) != NULL) {
2049 size += strlen(buf) + 2;
2053 FILE_OP_ERROR(file, "fgets");
2062 gboolean file_exist(const gchar *file, gboolean allow_fifo)
2069 if (g_stat(file, &s) < 0) {
2070 if (ENOENT != errno) FILE_OP_ERROR(file, "stat");
2074 if (S_ISREG(s.st_mode) || (allow_fifo && S_ISFIFO(s.st_mode)))
2081 /* Test on whether FILE is a relative file name. This is
2082 * straightforward for Unix but more complex for Windows. */
2083 gboolean is_relative_filename(const gchar *file)
2088 if ( *file == '\\' && file[1] == '\\' && strchr (file+2, '\\') )
2089 return FALSE; /* Prefixed with a hostname - this can't
2090 * be a relative name. */
2092 if ( ((*file >= 'a' && *file <= 'z')
2093 || (*file >= 'A' && *file <= 'Z'))
2095 file += 2; /* Skip drive letter. */
2097 return !(*file == '\\' || *file == '/');
2099 return !(*file == G_DIR_SEPARATOR);
2104 gboolean is_dir_exist(const gchar *dir)
2109 return g_file_test(dir, G_FILE_TEST_IS_DIR);
2112 gboolean is_file_entry_exist(const gchar *file)
2117 return g_file_test(file, G_FILE_TEST_EXISTS);
2120 gboolean dirent_is_regular_file(struct dirent *d)
2122 #if !defined(G_OS_WIN32) && defined(HAVE_DIRENT_D_TYPE)
2123 if (d->d_type == DT_REG)
2125 else if (d->d_type != DT_UNKNOWN)
2129 return g_file_test(d->d_name, G_FILE_TEST_IS_REGULAR);
2132 gint change_dir(const gchar *dir)
2134 gchar *prevdir = NULL;
2137 prevdir = g_get_current_dir();
2139 if (g_chdir(dir) < 0) {
2140 FILE_OP_ERROR(dir, "chdir");
2141 if (debug_mode) g_free(prevdir);
2143 } else if (debug_mode) {
2146 cwd = g_get_current_dir();
2147 if (strcmp(prevdir, cwd) != 0)
2148 g_print("current dir: %s\n", cwd);
2156 gint make_dir(const gchar *dir)
2158 if (g_mkdir(dir, S_IRWXU) < 0) {
2159 FILE_OP_ERROR(dir, "mkdir");
2162 if (g_chmod(dir, S_IRWXU) < 0)
2163 FILE_OP_ERROR(dir, "chmod");
2168 gint make_dir_hier(const gchar *dir)
2173 for (p = dir; (p = strchr(p, G_DIR_SEPARATOR)) != NULL; p++) {
2174 parent_dir = g_strndup(dir, p - dir);
2175 if (*parent_dir != '\0') {
2176 if (!is_dir_exist(parent_dir)) {
2177 if (make_dir(parent_dir) < 0) {
2186 if (!is_dir_exist(dir)) {
2187 if (make_dir(dir) < 0)
2194 gint remove_all_files(const gchar *dir)
2197 const gchar *dir_name;
2200 prev_dir = g_get_current_dir();
2202 if (g_chdir(dir) < 0) {
2203 FILE_OP_ERROR(dir, "chdir");
2208 if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2209 g_warning("failed to open directory: %s", dir);
2214 while ((dir_name = g_dir_read_name(dp)) != NULL) {
2215 if (claws_unlink(dir_name) < 0)
2216 FILE_OP_ERROR(dir_name, "unlink");
2221 if (g_chdir(prev_dir) < 0) {
2222 FILE_OP_ERROR(prev_dir, "chdir");
2232 gint remove_numbered_files(const gchar *dir, guint first, guint last)
2235 const gchar *dir_name;
2239 if (first == last) {
2240 /* Skip all the dir reading part. */
2241 gchar *filename = g_strdup_printf("%s%s%u", dir, G_DIR_SEPARATOR_S, first);
2242 if (is_dir_exist(filename)) {
2243 /* a numbered directory with this name exists,
2244 * remove the dot-file instead */
2246 filename = g_strdup_printf("%s%s.%u", dir, G_DIR_SEPARATOR_S, first);
2248 if (claws_unlink(filename) < 0) {
2249 FILE_OP_ERROR(filename, "unlink");
2257 prev_dir = g_get_current_dir();
2259 if (g_chdir(dir) < 0) {
2260 FILE_OP_ERROR(dir, "chdir");
2265 if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2266 g_warning("failed to open directory: %s", dir);
2271 while ((dir_name = g_dir_read_name(dp)) != NULL) {
2272 file_no = to_number(dir_name);
2273 if (file_no > 0 && first <= file_no && file_no <= last) {
2274 if (is_dir_exist(dir_name)) {
2275 gchar *dot_file = g_strdup_printf(".%s", dir_name);
2276 if (is_file_exist(dot_file) && claws_unlink(dot_file) < 0) {
2277 FILE_OP_ERROR(dot_file, "unlink");
2282 if (claws_unlink(dir_name) < 0)
2283 FILE_OP_ERROR(dir_name, "unlink");
2289 if (g_chdir(prev_dir) < 0) {
2290 FILE_OP_ERROR(prev_dir, "chdir");
2300 gint remove_numbered_files_not_in_list(const gchar *dir, GSList *numberlist)
2303 const gchar *dir_name;
2306 GHashTable *wanted_files;
2308 GError *error = NULL;
2310 if (numberlist == NULL)
2313 prev_dir = g_get_current_dir();
2315 if (g_chdir(dir) < 0) {
2316 FILE_OP_ERROR(dir, "chdir");
2321 if ((dp = g_dir_open(".", 0, &error)) == NULL) {
2322 g_message("Couldn't open current directory: %s (%d).\n",
2323 error->message, error->code);
2324 g_error_free(error);
2329 wanted_files = g_hash_table_new(g_direct_hash, g_direct_equal);
2330 for (cur = numberlist; cur != NULL; cur = cur->next) {
2331 /* numberlist->data is expected to be GINT_TO_POINTER */
2332 g_hash_table_insert(wanted_files, cur->data, GINT_TO_POINTER(1));
2335 while ((dir_name = g_dir_read_name(dp)) != NULL) {
2336 file_no = to_number(dir_name);
2337 if (is_dir_exist(dir_name))
2339 if (file_no > 0 && g_hash_table_lookup(wanted_files, GINT_TO_POINTER(file_no)) == NULL) {
2340 debug_print("removing unwanted file %d from %s\n", file_no, dir);
2341 if (is_dir_exist(dir_name)) {
2342 gchar *dot_file = g_strdup_printf(".%s", dir_name);
2343 if (is_file_exist(dot_file) && claws_unlink(dot_file) < 0) {
2344 FILE_OP_ERROR(dot_file, "unlink");
2349 if (claws_unlink(dir_name) < 0)
2350 FILE_OP_ERROR(dir_name, "unlink");
2355 g_hash_table_destroy(wanted_files);
2357 if (g_chdir(prev_dir) < 0) {
2358 FILE_OP_ERROR(prev_dir, "chdir");
2368 gint remove_all_numbered_files(const gchar *dir)
2370 return remove_numbered_files(dir, 0, UINT_MAX);
2373 gint remove_dir_recursive(const gchar *dir)
2377 const gchar *dir_name;
2380 if (g_stat(dir, &s) < 0) {
2381 FILE_OP_ERROR(dir, "stat");
2382 if (ENOENT == errno) return 0;
2386 if (!S_ISDIR(s.st_mode)) {
2387 if (claws_unlink(dir) < 0) {
2388 FILE_OP_ERROR(dir, "unlink");
2395 prev_dir = g_get_current_dir();
2396 /* g_print("prev_dir = %s\n", prev_dir); */
2398 if (!path_cmp(prev_dir, dir)) {
2400 if (g_chdir("..") < 0) {
2401 FILE_OP_ERROR(dir, "chdir");
2404 prev_dir = g_get_current_dir();
2407 if (g_chdir(dir) < 0) {
2408 FILE_OP_ERROR(dir, "chdir");
2413 if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2414 g_warning("failed to open directory: %s", dir);
2420 /* remove all files in the directory */
2421 while ((dir_name = g_dir_read_name(dp)) != NULL) {
2422 /* g_print("removing %s\n", dir_name); */
2424 if (is_dir_exist(dir_name)) {
2427 if ((ret = remove_dir_recursive(dir_name)) < 0) {
2428 g_warning("can't remove directory: %s", dir_name);
2432 if (claws_unlink(dir_name) < 0)
2433 FILE_OP_ERROR(dir_name, "unlink");
2439 if (g_chdir(prev_dir) < 0) {
2440 FILE_OP_ERROR(prev_dir, "chdir");
2447 if (g_rmdir(dir) < 0) {
2448 FILE_OP_ERROR(dir, "rmdir");
2455 gint rename_force(const gchar *oldpath, const gchar *newpath)
2458 if (!is_file_entry_exist(oldpath)) {
2462 if (is_file_exist(newpath)) {
2463 if (claws_unlink(newpath) < 0)
2464 FILE_OP_ERROR(newpath, "unlink");
2467 return g_rename(oldpath, newpath);
2471 * Append src file body to the tail of dest file.
2472 * Now keep_backup has no effects.
2474 gint append_file(const gchar *src, const gchar *dest, gboolean keep_backup)
2476 FILE *src_fp, *dest_fp;
2480 gboolean err = FALSE;
2482 if ((src_fp = g_fopen(src, "rb")) == NULL) {
2483 FILE_OP_ERROR(src, "g_fopen");
2487 if ((dest_fp = g_fopen(dest, "ab")) == NULL) {
2488 FILE_OP_ERROR(dest, "g_fopen");
2493 if (change_file_mode_rw(dest_fp, dest) < 0) {
2494 FILE_OP_ERROR(dest, "chmod");
2495 g_warning("can't change file mode: %s", dest);
2498 while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
2499 if (n_read < sizeof(buf) && ferror(src_fp))
2501 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2502 g_warning("writing to %s failed.", dest);
2510 if (ferror(src_fp)) {
2511 FILE_OP_ERROR(src, "fread");
2515 if (fclose(dest_fp) == EOF) {
2516 FILE_OP_ERROR(dest, "fclose");
2528 gint copy_file(const gchar *src, const gchar *dest, gboolean keep_backup)
2530 FILE *src_fp, *dest_fp;
2533 gchar *dest_bak = NULL;
2534 gboolean err = FALSE;
2536 if ((src_fp = g_fopen(src, "rb")) == NULL) {
2537 FILE_OP_ERROR(src, "g_fopen");
2540 if (is_file_exist(dest)) {
2541 dest_bak = g_strconcat(dest, ".bak", NULL);
2542 if (rename_force(dest, dest_bak) < 0) {
2543 FILE_OP_ERROR(dest, "rename");
2550 if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2551 FILE_OP_ERROR(dest, "g_fopen");
2554 if (rename_force(dest_bak, dest) < 0)
2555 FILE_OP_ERROR(dest_bak, "rename");
2561 if (change_file_mode_rw(dest_fp, dest) < 0) {
2562 FILE_OP_ERROR(dest, "chmod");
2563 g_warning("can't change file mode: %s", dest);
2566 while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
2567 if (n_read < sizeof(buf) && ferror(src_fp))
2569 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2570 g_warning("writing to %s failed.", dest);
2575 if (rename_force(dest_bak, dest) < 0)
2576 FILE_OP_ERROR(dest_bak, "rename");
2583 if (ferror(src_fp)) {
2584 FILE_OP_ERROR(src, "fread");
2588 if (fclose(dest_fp) == EOF) {
2589 FILE_OP_ERROR(dest, "fclose");
2596 if (rename_force(dest_bak, dest) < 0)
2597 FILE_OP_ERROR(dest_bak, "rename");
2603 if (keep_backup == FALSE && dest_bak)
2604 claws_unlink(dest_bak);
2611 gint move_file(const gchar *src, const gchar *dest, gboolean overwrite)
2613 if (overwrite == FALSE && is_file_exist(dest)) {
2614 g_warning("move_file(): file %s already exists.", dest);
2618 if (rename_force(src, dest) == 0) return 0;
2620 if (EXDEV != errno) {
2621 FILE_OP_ERROR(src, "rename");
2625 if (copy_file(src, dest, FALSE) < 0) return -1;
2632 gint copy_file_part_to_fp(FILE *fp, off_t offset, size_t length, FILE *dest_fp)
2635 gint bytes_left, to_read;
2638 if (fseek(fp, offset, SEEK_SET) < 0) {
2643 bytes_left = length;
2644 to_read = MIN(bytes_left, sizeof(buf));
2646 while ((n_read = fread(buf, sizeof(gchar), to_read, fp)) > 0) {
2647 if (n_read < to_read && ferror(fp))
2649 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2652 bytes_left -= n_read;
2653 if (bytes_left == 0)
2655 to_read = MIN(bytes_left, sizeof(buf));
2666 gint copy_file_part(FILE *fp, off_t offset, size_t length, const gchar *dest)
2669 gboolean err = FALSE;
2671 if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2672 FILE_OP_ERROR(dest, "g_fopen");
2676 if (change_file_mode_rw(dest_fp, dest) < 0) {
2677 FILE_OP_ERROR(dest, "chmod");
2678 g_warning("can't change file mode: %s", dest);
2681 if (copy_file_part_to_fp(fp, offset, length, dest_fp) < 0)
2684 if (!err && fclose(dest_fp) == EOF) {
2685 FILE_OP_ERROR(dest, "fclose");
2690 g_warning("writing to %s failed.", dest);
2698 /* convert line endings into CRLF. If the last line doesn't end with
2699 * linebreak, add it.
2701 gchar *canonicalize_str(const gchar *str)
2707 for (p = str; *p != '\0'; ++p) {
2714 if (p == str || *(p - 1) != '\n')
2717 out = outp = g_malloc(new_len + 1);
2718 for (p = str; *p != '\0'; ++p) {
2725 if (p == str || *(p - 1) != '\n') {
2734 gint canonicalize_file(const gchar *src, const gchar *dest)
2736 FILE *src_fp, *dest_fp;
2737 gchar buf[BUFFSIZE];
2739 gboolean err = FALSE;
2740 gboolean last_linebreak = FALSE;
2742 if (src == NULL || dest == NULL)
2745 if ((src_fp = g_fopen(src, "rb")) == NULL) {
2746 FILE_OP_ERROR(src, "g_fopen");
2750 if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2751 FILE_OP_ERROR(dest, "g_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: %s", dest);
2761 while (fgets(buf, sizeof(buf), src_fp) != NULL) {
2765 if (len == 0) break;
2766 last_linebreak = FALSE;
2768 if (buf[len - 1] != '\n') {
2769 last_linebreak = TRUE;
2770 r = fputs(buf, dest_fp);
2771 } else if (len > 1 && buf[len - 1] == '\n' && buf[len - 2] == '\r') {
2772 r = fputs(buf, dest_fp);
2775 r = fwrite(buf, 1, len - 1, dest_fp);
2780 r = fputs("\r\n", dest_fp);
2784 g_warning("writing to %s failed.", dest);
2792 if (last_linebreak == TRUE) {
2793 if (fputs("\r\n", dest_fp) == EOF)
2797 if (ferror(src_fp)) {
2798 FILE_OP_ERROR(src, "fgets");
2802 if (fclose(dest_fp) == EOF) {
2803 FILE_OP_ERROR(dest, "fclose");
2815 gint canonicalize_file_replace(const gchar *file)
2819 tmp_file = get_tmp_file();
2821 if (canonicalize_file(file, tmp_file) < 0) {
2826 if (move_file(tmp_file, file, TRUE) < 0) {
2827 g_warning("can't replace file: %s", file);
2828 claws_unlink(tmp_file);
2837 gchar *normalize_newlines(const gchar *str)
2842 out = outp = g_malloc(strlen(str) + 1);
2843 for (p = str; *p != '\0'; ++p) {
2845 if (*(p + 1) != '\n')
2856 gchar *get_outgoing_rfc2822_str(FILE *fp)
2858 gchar buf[BUFFSIZE];
2862 str = g_string_new(NULL);
2864 /* output header part */
2865 while (fgets(buf, sizeof(buf), fp) != NULL) {
2867 if (!g_ascii_strncasecmp(buf, "Bcc:", 4)) {
2874 else if (next != ' ' && next != '\t') {
2878 if (fgets(buf, sizeof(buf), fp) == NULL)
2882 g_string_append(str, buf);
2883 g_string_append(str, "\r\n");
2889 /* output body part */
2890 while (fgets(buf, sizeof(buf), fp) != NULL) {
2893 g_string_append_c(str, '.');
2894 g_string_append(str, buf);
2895 g_string_append(str, "\r\n");
2899 g_string_free(str, FALSE);
2905 * Create a new boundary in a way that it is very unlikely that this
2906 * will occur in the following text. It would be easy to ensure
2907 * uniqueness if everything is either quoted-printable or base64
2908 * encoded (note that conversion is allowed), but because MIME bodies
2909 * may be nested, it may happen that the same boundary has already
2912 * boundary := 0*69<bchars> bcharsnospace
2913 * bchars := bcharsnospace / " "
2914 * bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" /
2915 * "+" / "_" / "," / "-" / "." /
2916 * "/" / ":" / "=" / "?"
2918 * some special characters removed because of buggy MTAs
2921 gchar *generate_mime_boundary(const gchar *prefix)
2923 static gchar tbl[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
2924 "abcdefghijklmnopqrstuvwxyz"
2929 for (i = 0; i < sizeof(buf_uniq) - 1; i++)
2930 buf_uniq[i] = tbl[g_random_int_range(0, sizeof(tbl) - 1)];
2933 return g_strdup_printf("%s_/%s", prefix ? prefix : "MP",
2937 gint change_file_mode_rw(FILE *fp, const gchar *file)
2940 return fchmod(fileno(fp), S_IRUSR|S_IWUSR);
2942 return g_chmod(file, S_IRUSR|S_IWUSR);
2946 FILE *my_tmpfile(void)
2948 const gchar suffix[] = ".XXXXXX";
2949 const gchar *tmpdir;
2951 const gchar *progname;
2960 tmpdir = get_tmp_dir();
2961 tmplen = strlen(tmpdir);
2962 progname = g_get_prgname();
2963 if (progname == NULL)
2964 progname = "claws-mail";
2965 proglen = strlen(progname);
2966 Xalloca(fname, tmplen + 1 + proglen + sizeof(suffix),
2969 memcpy(fname, tmpdir, tmplen);
2970 fname[tmplen] = G_DIR_SEPARATOR;
2971 memcpy(fname + tmplen + 1, progname, proglen);
2972 memcpy(fname + tmplen + 1 + proglen, suffix, sizeof(suffix));
2974 fd = g_mkstemp(fname);
2979 claws_unlink(fname);
2981 /* verify that we can write in the file after unlinking */
2982 if (write(fd, buf, 1) < 0) {
2989 fp = fdopen(fd, "w+b");
3000 FILE *get_tmpfile_in_dir(const gchar *dir, gchar **filename)
3003 *filename = g_strdup_printf("%s%cclaws.XXXXXX", dir, G_DIR_SEPARATOR);
3004 fd = g_mkstemp(*filename);
3007 return fdopen(fd, "w+");
3010 FILE *str_open_as_stream(const gchar *str)
3015 cm_return_val_if_fail(str != NULL, NULL);
3019 FILE_OP_ERROR("str_open_as_stream", "my_tmpfile");
3024 if (len == 0) return fp;
3026 if (fwrite(str, 1, len, fp) != len) {
3027 FILE_OP_ERROR("str_open_as_stream", "fwrite");
3036 gint str_write_to_file(const gchar *str, const gchar *file)
3041 cm_return_val_if_fail(str != NULL, -1);
3042 cm_return_val_if_fail(file != NULL, -1);
3044 if ((fp = g_fopen(file, "wb")) == NULL) {
3045 FILE_OP_ERROR(file, "g_fopen");
3055 if (fwrite(str, 1, len, fp) != len) {
3056 FILE_OP_ERROR(file, "fwrite");
3062 if (fclose(fp) == EOF) {
3063 FILE_OP_ERROR(file, "fclose");
3071 static gchar *file_read_stream_to_str_full(FILE *fp, gboolean recode)
3078 cm_return_val_if_fail(fp != NULL, NULL);
3080 array = g_byte_array_new();
3082 while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
3083 if (n_read < sizeof(buf) && ferror(fp))
3085 g_byte_array_append(array, buf, n_read);
3089 FILE_OP_ERROR("file stream", "fread");
3090 g_byte_array_free(array, TRUE);
3095 g_byte_array_append(array, buf, 1);
3096 str = (gchar *)array->data;
3097 g_byte_array_free(array, FALSE);
3099 if (recode && !g_utf8_validate(str, -1, NULL)) {
3100 const gchar *src_codeset, *dest_codeset;
3102 src_codeset = conv_get_locale_charset_str();
3103 dest_codeset = CS_UTF_8;
3104 tmp = conv_codeset_strdup(str, src_codeset, dest_codeset);
3112 static gchar *file_read_to_str_full(const gchar *file, gboolean recode)
3119 struct timeval timeout = {1, 0};
3124 cm_return_val_if_fail(file != NULL, NULL);
3126 if (g_stat(file, &s) != 0) {
3127 FILE_OP_ERROR(file, "stat");
3130 if (S_ISDIR(s.st_mode)) {
3131 g_warning("%s: is a directory", file);
3136 fp = g_fopen (file, "rb");
3138 FILE_OP_ERROR(file, "open");
3142 /* test whether the file is readable without blocking */
3143 fd = g_open(file, O_RDONLY | O_NONBLOCK, 0);
3145 FILE_OP_ERROR(file, "open");
3152 /* allow for one second */
3153 err = select(fd+1, &fds, NULL, NULL, &timeout);
3154 if (err <= 0 || !FD_ISSET(fd, &fds)) {
3156 FILE_OP_ERROR(file, "select");
3158 g_warning("%s: doesn't seem readable", file);
3164 /* Now clear O_NONBLOCK */
3165 if ((fflags = fcntl(fd, F_GETFL)) < 0) {
3166 FILE_OP_ERROR(file, "fcntl (F_GETFL)");
3170 if (fcntl(fd, F_SETFL, (fflags & ~O_NONBLOCK)) < 0) {
3171 FILE_OP_ERROR(file, "fcntl (F_SETFL)");
3176 /* get the FILE pointer */
3177 fp = fdopen(fd, "rb");
3180 FILE_OP_ERROR(file, "fdopen");
3181 close(fd); /* if fp isn't NULL, we'll use fclose instead! */
3186 str = file_read_stream_to_str_full(fp, recode);
3193 gchar *file_read_to_str(const gchar *file)
3195 return file_read_to_str_full(file, TRUE);
3197 gchar *file_read_stream_to_str(FILE *fp)
3199 return file_read_stream_to_str_full(fp, TRUE);
3202 gchar *file_read_to_str_no_recode(const gchar *file)
3204 return file_read_to_str_full(file, FALSE);
3206 gchar *file_read_stream_to_str_no_recode(FILE *fp)
3208 return file_read_stream_to_str_full(fp, FALSE);
3211 char *fgets_crlf(char *buf, int size, FILE *stream)
3213 gboolean is_cr = FALSE;
3214 gboolean last_was_cr = FALSE;
3219 while (--size > 0 && (c = getc(stream)) != EOF)
3222 is_cr = (c == '\r');
3232 last_was_cr = is_cr;
3234 if (c == EOF && cs == buf)
3242 static gint execute_async(gchar *const argv[])
3244 cm_return_val_if_fail(argv != NULL && argv[0] != NULL, -1);
3246 if (g_spawn_async(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH,
3247 NULL, NULL, NULL, FALSE) == FALSE) {
3248 g_warning("couldn't execute command: %s", argv[0]);
3255 static gint execute_sync(gchar *const argv[])
3259 cm_return_val_if_fail(argv != NULL && argv[0] != NULL, -1);
3262 if (g_spawn_sync(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH,
3263 NULL, NULL, NULL, NULL, &status, NULL) == FALSE) {
3264 g_warning("couldn't execute command: %s", argv[0]);
3268 if (WIFEXITED(status))
3269 return WEXITSTATUS(status);
3273 if (g_spawn_sync(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH|
3274 G_SPAWN_CHILD_INHERITS_STDIN|G_SPAWN_LEAVE_DESCRIPTORS_OPEN,
3275 NULL, NULL, NULL, NULL, &status, NULL) == FALSE) {
3276 g_warning("couldn't execute command: %s", argv[0]);
3284 gint execute_command_line(const gchar *cmdline, gboolean async)
3289 debug_print("execute_command_line(): executing: %s\n", cmdline?cmdline:"(null)");
3291 argv = strsplit_with_quote(cmdline, " ", 0);
3294 ret = execute_async(argv);
3296 ret = execute_sync(argv);
3303 gchar *get_command_output(const gchar *cmdline)
3305 gchar *child_stdout;
3308 cm_return_val_if_fail(cmdline != NULL, NULL);
3310 debug_print("get_command_output(): executing: %s\n", cmdline);
3312 if (g_spawn_command_line_sync(cmdline, &child_stdout, NULL, &status,
3314 g_warning("couldn't execute command: %s", cmdline);
3318 return child_stdout;
3321 static gint is_unchanged_uri_char(char c)
3332 static void encode_uri(gchar *encoded_uri, gint bufsize, const gchar *uri)
3338 for(i = 0; i < strlen(uri) ; i++) {
3339 if (is_unchanged_uri_char(uri[i])) {
3340 if (k + 2 >= bufsize)
3342 encoded_uri[k++] = uri[i];
3345 char * hexa = "0123456789ABCDEF";
3347 if (k + 4 >= bufsize)
3349 encoded_uri[k++] = '%';
3350 encoded_uri[k++] = hexa[uri[i] / 16];
3351 encoded_uri[k++] = hexa[uri[i] % 16];
3357 gint open_uri(const gchar *uri, const gchar *cmdline)
3361 gchar buf[BUFFSIZE];
3363 gchar encoded_uri[BUFFSIZE];
3364 cm_return_val_if_fail(uri != NULL, -1);
3366 /* an option to choose whether to use encode_uri or not ? */
3367 encode_uri(encoded_uri, BUFFSIZE, uri);
3370 (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
3371 !strchr(p + 2, '%'))
3372 g_snprintf(buf, sizeof(buf), cmdline, encoded_uri);
3375 g_warning("Open URI command-line is invalid "
3376 "(there must be only one '%%s'): %s",
3378 g_snprintf(buf, sizeof(buf), DEFAULT_BROWSER_CMD, encoded_uri);
3381 execute_command_line(buf, TRUE);
3383 ShellExecute(NULL, "open", uri, NULL, NULL, SW_SHOW);
3388 gint open_txt_editor(const gchar *filepath, const gchar *cmdline)
3390 gchar buf[BUFFSIZE];
3393 cm_return_val_if_fail(filepath != NULL, -1);
3396 (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
3397 !strchr(p + 2, '%'))
3398 g_snprintf(buf, sizeof(buf), cmdline, filepath);
3401 g_warning("Open Text Editor command-line is invalid "
3402 "(there must be only one '%%s'): %s",
3404 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, filepath);
3407 execute_command_line(buf, TRUE);
3412 time_t remote_tzoffset_sec(const gchar *zone)
3414 static gchar ustzstr[] = "PSTPDTMSTMDTCSTCDTESTEDT";
3420 time_t remoteoffset;
3422 strncpy(zone3, zone, 3);
3426 if (sscanf(zone, "%c%d", &c, &offset) == 2 &&
3427 (c == '+' || c == '-')) {
3428 remoteoffset = ((offset / 100) * 60 + (offset % 100)) * 60;
3430 remoteoffset = -remoteoffset;
3431 } else if (!strncmp(zone, "UT" , 2) ||
3432 !strncmp(zone, "GMT", 3)) {
3434 } else if (strlen(zone3) == 3) {
3435 for (p = ustzstr; *p != '\0'; p += 3) {
3436 if (!g_ascii_strncasecmp(p, zone3, 3)) {
3437 iustz = ((gint)(p - ustzstr) / 3 + 1) / 2 - 8;
3438 remoteoffset = iustz * 3600;
3444 } else if (strlen(zone3) == 1) {
3446 case 'Z': remoteoffset = 0; break;
3447 case 'A': remoteoffset = -1; break;
3448 case 'B': remoteoffset = -2; break;
3449 case 'C': remoteoffset = -3; break;
3450 case 'D': remoteoffset = -4; break;
3451 case 'E': remoteoffset = -5; break;
3452 case 'F': remoteoffset = -6; break;
3453 case 'G': remoteoffset = -7; break;
3454 case 'H': remoteoffset = -8; break;
3455 case 'I': remoteoffset = -9; break;
3456 case 'K': remoteoffset = -10; break; /* J is not used */
3457 case 'L': remoteoffset = -11; break;
3458 case 'M': remoteoffset = -12; break;
3459 case 'N': remoteoffset = 1; break;
3460 case 'O': remoteoffset = 2; break;
3461 case 'P': remoteoffset = 3; break;
3462 case 'Q': remoteoffset = 4; break;
3463 case 'R': remoteoffset = 5; break;
3464 case 'S': remoteoffset = 6; break;
3465 case 'T': remoteoffset = 7; break;
3466 case 'U': remoteoffset = 8; break;
3467 case 'V': remoteoffset = 9; break;
3468 case 'W': remoteoffset = 10; break;
3469 case 'X': remoteoffset = 11; break;
3470 case 'Y': remoteoffset = 12; break;
3471 default: remoteoffset = 0; break;
3473 remoteoffset = remoteoffset * 3600;
3477 return remoteoffset;
3480 time_t tzoffset_sec(time_t *now)
3484 struct tm buf1, buf2;
3486 if (now && *now < 0)
3489 gmt = *gmtime_r(now, &buf1);
3490 lt = localtime_r(now, &buf2);
3492 off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
3494 if (lt->tm_year < gmt.tm_year)
3496 else if (lt->tm_year > gmt.tm_year)
3498 else if (lt->tm_yday < gmt.tm_yday)
3500 else if (lt->tm_yday > gmt.tm_yday)
3503 if (off >= 24 * 60) /* should be impossible */
3504 off = 23 * 60 + 59; /* if not, insert silly value */
3505 if (off <= -24 * 60)
3506 off = -(23 * 60 + 59);
3511 /* calculate timezone offset */
3512 gchar *tzoffset(time_t *now)
3514 static gchar offset_string[6];
3518 struct tm buf1, buf2;
3520 if (now && *now < 0)
3523 gmt = *gmtime_r(now, &buf1);
3524 lt = localtime_r(now, &buf2);
3526 off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
3528 if (lt->tm_year < gmt.tm_year)
3530 else if (lt->tm_year > gmt.tm_year)
3532 else if (lt->tm_yday < gmt.tm_yday)
3534 else if (lt->tm_yday > gmt.tm_yday)
3542 if (off >= 24 * 60) /* should be impossible */
3543 off = 23 * 60 + 59; /* if not, insert silly value */
3545 sprintf(offset_string, "%c%02d%02d", sign, off / 60, off % 60);
3547 return offset_string;
3550 void get_rfc822_date(gchar *buf, gint len)
3554 gchar day[4], mon[4];
3555 gint dd, hh, mm, ss, yyyy;
3557 gchar buf2[BUFFSIZE];
3560 lt = localtime_r(&t, &buf1);
3562 sscanf(asctime_r(lt, buf2), "%3s %3s %d %d:%d:%d %d\n",
3563 day, mon, &dd, &hh, &mm, &ss, &yyyy);
3565 g_snprintf(buf, len, "%s, %d %s %d %02d:%02d:%02d %s",
3566 day, dd, mon, yyyy, hh, mm, ss, tzoffset(&t));
3569 void debug_set_mode(gboolean mode)
3574 gboolean debug_get_mode(void)
3579 void debug_print_real(const gchar *format, ...)
3582 gchar buf[BUFFSIZE];
3584 if (!debug_mode) return;
3586 va_start(args, format);
3587 g_vsnprintf(buf, sizeof(buf), format, args);
3594 const char * debug_srcname(const char *file)
3596 const char *s = strrchr (file, '/');
3601 void * subject_table_lookup(GHashTable *subject_table, gchar * subject)
3603 if (subject == NULL)
3606 subject += subject_get_prefix_length(subject);
3608 return g_hash_table_lookup(subject_table, subject);
3611 void subject_table_insert(GHashTable *subject_table, gchar * subject,
3614 if (subject == NULL || *subject == 0)
3616 subject += subject_get_prefix_length(subject);
3617 g_hash_table_insert(subject_table, subject, data);
3620 void subject_table_remove(GHashTable *subject_table, gchar * subject)
3622 if (subject == NULL)
3625 subject += subject_get_prefix_length(subject);
3626 g_hash_table_remove(subject_table, subject);
3630 static regex_t u_regex;
3631 static gboolean u_init_;
3634 void utils_free_regex(void)
3645 *\brief Check if a string is prefixed with known (combinations)
3646 * of prefixes. The function assumes that each prefix
3647 * is terminated by zero or exactly _one_ space.
3649 *\param str String to check for a prefixes
3651 *\return int Number of chars in the prefix that should be skipped
3652 * for a "clean" subject line. If no prefix was found, 0
3655 int subject_get_prefix_length(const gchar *subject)
3658 /*!< Array with allowable reply prefixes regexps. */
3659 static const gchar * const prefixes[] = {
3660 "Re\\:", /* "Re:" */
3661 "Re\\[[1-9][0-9]*\\]\\:", /* "Re[XXX]:" (non-conforming news mail clients) */
3662 "Antw\\:", /* "Antw:" (Dutch / German Outlook) */
3663 "Aw\\:", /* "Aw:" (German) */
3664 "Antwort\\:", /* "Antwort:" (German Lotus Notes) */
3665 "Res\\:", /* "Res:" (Spanish/Brazilian Outlook) */
3666 "Fw\\:", /* "Fw:" Forward */
3667 "Fwd\\:", /* "Fwd:" Forward */
3668 "Enc\\:", /* "Enc:" Forward (Brazilian Outlook) */
3669 "Odp\\:", /* "Odp:" Re (Polish Outlook) */
3670 "Rif\\:", /* "Rif:" (Italian Outlook) */
3671 "Sv\\:", /* "Sv" (Norwegian) */
3672 "Vs\\:", /* "Vs" (Norwegian) */
3673 "Ad\\:", /* "Ad" (Norwegian) */
3674 "\347\255\224\345\244\215\\:", /* "Re" (Chinese, UTF-8) */
3675 "R\303\251f\\. \\:", /* "R�f. :" (French Lotus Notes) */
3676 "Re \\:", /* "Re :" (French Yahoo Mail) */
3679 const int PREFIXES = sizeof prefixes / sizeof prefixes[0];
3683 if (!subject) return 0;
3684 if (!*subject) return 0;
3687 GString *s = g_string_new("");
3689 for (n = 0; n < PREFIXES; n++)
3690 /* Terminate each prefix regexpression by a
3691 * "\ ?" (zero or ONE space), and OR them */
3692 g_string_append_printf(s, "(%s\\ ?)%s",
3697 g_string_prepend(s, "(");
3698 g_string_append(s, ")+"); /* match at least once */
3699 g_string_prepend(s, "^\\ *"); /* from beginning of line */
3702 /* We now have something like "^\ *((PREFIX1\ ?)|(PREFIX2\ ?))+"
3703 * TODO: Should this be "^\ *(((PREFIX1)|(PREFIX2))\ ?)+" ??? */
3704 if (regcomp(&u_regex, s->str, REG_EXTENDED | REG_ICASE)) {
3705 debug_print("Error compiling regexp %s\n", s->str);
3706 g_string_free(s, TRUE);
3710 g_string_free(s, TRUE);
3714 if (!regexec(&u_regex, subject, 1, &pos, 0) && pos.rm_so != -1)
3719 /*!< Array with allowable reply prefixes regexps. */
3720 static const gchar * const prefixes[] = {
3722 "antw:", /* "Antw:" (Dutch / German Outlook) */
3723 "aw:", /* "Aw:" (German) */
3724 "antwort:", /* "Antwort:" (German Lotus Notes) */
3725 "res:", /* "Res:" (Spanish/Brazilian Outlook) */
3726 "fw:", /* "Fw:" Forward */
3727 "fwd:", /* "Fwd:" Forward */
3728 "enc:", /* "Enc:" Forward (Brazilian Outlook) */
3729 "odp:", /* "Odp:" Re (Polish Outlook) */
3730 "rif:", /* "Rif:" (Italian Outlook) */
3731 "sv:", /* "Sv" (Norwegian) */
3732 "vs:", /* "Vs" (Norwegian) */
3733 "ad:", /* "Ad" (Norwegian) */
3734 "R\303\251f. :", /* "R�f. :" (French Lotus Notes) */
3735 "Re :", /* "Re :" (French Yahoo Mail) */
3738 const int PREFIXES = sizeof prefixes / sizeof prefixes[0];
3741 if (!subject) return 0;
3742 if (!*subject) return 0;
3744 for (n = 0; n < PREFIXES; n++) {
3745 int len = strlen(prefixes[n]);
3746 if (!strncasecmp(subject, prefixes[n], len)) {
3747 if (subject[len] == ' ')
3756 static guint g_stricase_hash(gconstpointer gptr)
3758 guint hash_result = 0;
3761 for (str = gptr; str && *str; str++) {
3762 hash_result += toupper(*str);
3768 static gint g_stricase_equal(gconstpointer gptr1, gconstpointer gptr2)
3770 const char *str1 = gptr1;
3771 const char *str2 = gptr2;
3773 return !strcasecmp(str1, str2);
3776 gint g_int_compare(gconstpointer a, gconstpointer b)
3778 return GPOINTER_TO_INT(a) - GPOINTER_TO_INT(b);
3781 gchar *generate_msgid(gchar *buf, gint len, gchar *user_addr)
3789 lt = localtime_r(&t, &buft);
3791 if (user_addr != NULL)
3792 addr = g_strdup_printf(".%s", user_addr);
3793 else if (strlen(buf) != 0)
3794 addr = g_strdup_printf("@%s", buf);
3796 addr = g_strdup_printf("@%s", get_domain_name());
3798 /* Replace all @ but the last one in addr, with underscores.
3799 * RFC 2822 States that msg-id syntax only allows one @.
3801 while (strchr(addr, '@') != NULL && strchr(addr, '@') != strrchr(addr, '@'))
3802 *(strchr(addr, '@')) = '_';
3804 g_snprintf(buf, len, "%04d%02d%02d%02d%02d%02d.%08x%s",
3805 lt->tm_year + 1900, lt->tm_mon + 1,
3806 lt->tm_mday, lt->tm_hour,
3807 lt->tm_min, lt->tm_sec,
3808 (guint) rand(), addr);
3815 quote_cmd_argument()
3817 return a quoted string safely usable in argument of a command.
3819 code is extracted and adapted from etPan! project -- DINH V. Ho�.
3822 gint quote_cmd_argument(gchar * result, guint size,
3832 for(p = path ; * p != '\0' ; p ++) {
3834 if (isalnum((guchar)*p) || (* p == '/')) {
3835 if (remaining > 0) {
3841 result[size - 1] = '\0';
3846 if (remaining >= 2) {
3854 result[size - 1] = '\0';
3859 if (remaining > 0) {
3863 result[size - 1] = '\0';
3877 static void g_node_map_recursive(GNode *node, gpointer data)
3879 GNodeMapData *mapdata = (GNodeMapData *) data;
3881 GNodeMapData newmapdata;
3884 newdata = mapdata->func(node->data, mapdata->data);
3885 if (newdata != NULL) {
3886 newnode = g_node_new(newdata);
3887 g_node_append(mapdata->parent, newnode);
3889 newmapdata.parent = newnode;
3890 newmapdata.func = mapdata->func;
3891 newmapdata.data = mapdata->data;
3893 g_node_children_foreach(node, G_TRAVERSE_ALL, g_node_map_recursive, &newmapdata);
3897 GNode *g_node_map(GNode *node, GNodeMapFunc func, gpointer data)
3900 GNodeMapData mapdata;
3902 cm_return_val_if_fail(node != NULL, NULL);
3903 cm_return_val_if_fail(func != NULL, NULL);
3905 root = g_node_new(func(node->data, data));
3907 mapdata.parent = root;
3908 mapdata.func = func;
3909 mapdata.data = data;
3911 g_node_children_foreach(node, G_TRAVERSE_ALL, g_node_map_recursive, &mapdata);
3916 #define HEX_TO_INT(val, hex) \
3920 if ('0' <= c && c <= '9') { \
3922 } else if ('a' <= c && c <= 'f') { \
3923 val = c - 'a' + 10; \
3924 } else if ('A' <= c && c <= 'F') { \
3925 val = c - 'A' + 10; \
3931 gboolean get_hex_value(guchar *out, gchar c1, gchar c2)
3938 if (hi == -1 || lo == -1)
3941 *out = (hi << 4) + lo;
3945 #define INT_TO_HEX(hex, val) \
3948 hex = '0' + (val); \
3950 hex = 'A' + (val) - 10; \
3953 void get_hex_str(gchar *out, guchar ch)
3957 INT_TO_HEX(hex, ch >> 4);
3959 INT_TO_HEX(hex, ch & 0x0f);
3965 #define G_PRINT_REF 1 == 1 ? (void) 0 : (void)
3967 #define G_PRINT_REF g_print
3971 *\brief Register ref counted pointer. It is based on GBoxed, so should
3972 * work with anything that uses the GType system. The semantics
3973 * are similar to a C++ auto pointer, with the exception that
3974 * C doesn't have automatic closure (calling destructors) when
3975 * exiting a block scope.
3976 * Use the \ref G_TYPE_AUTO_POINTER macro instead of calling this
3977 * function directly.
3979 *\return GType A GType type.
3981 GType g_auto_pointer_register(void)
3983 static GType auto_pointer_type;
3984 if (!auto_pointer_type)
3986 g_boxed_type_register_static
3987 ("G_TYPE_AUTO_POINTER",
3988 (GBoxedCopyFunc) g_auto_pointer_copy,
3989 (GBoxedFreeFunc) g_auto_pointer_free);
3990 return auto_pointer_type;
3994 *\brief Structure with g_new() allocated pointer guarded by the
3997 typedef struct AutoPointerRef {
3998 void (*free) (gpointer);
4004 *\brief The auto pointer opaque structure that references the
4005 * pointer guard block.
4007 typedef struct AutoPointer {
4008 AutoPointerRef *ref;
4009 gpointer ptr; /*!< access to protected pointer */
4013 *\brief Creates an auto pointer for a g_new()ed pointer. Example:
4017 * ... tell gtk_list_store it should use a G_TYPE_AUTO_POINTER
4018 * ... when assigning, copying and freeing storage elements
4020 * gtk_list_store_new(N_S_COLUMNS,
4021 * G_TYPE_AUTO_POINTER,
4025 * Template *precious_data = g_new0(Template, 1);
4026 * g_pointer protect = g_auto_pointer_new(precious_data);
4028 * gtk_list_store_set(container, &iter,
4032 * ... the gtk_list_store has copied the pointer and
4033 * ... incremented its reference count, we should free
4034 * ... the auto pointer (in C++ a destructor would do
4035 * ... this for us when leaving block scope)
4037 * g_auto_pointer_free(protect);
4039 * ... gtk_list_store_set() now manages the data. When
4040 * ... *explicitly* requesting a pointer from the list
4041 * ... store, don't forget you get a copy that should be
4042 * ... freed with g_auto_pointer_free() eventually.
4046 *\param pointer Pointer to be guarded.
4048 *\return GAuto * Pointer that should be used in containers with
4051 GAuto *g_auto_pointer_new(gpointer p)
4053 AutoPointerRef *ref;
4059 ref = g_new0(AutoPointerRef, 1);
4060 ptr = g_new0(AutoPointer, 1);
4070 G_PRINT_REF ("XXXX ALLOC(%lx)\n", p);
4076 *\brief Allocate an autopointer using the passed \a free function to
4077 * free the guarded pointer
4079 GAuto *g_auto_pointer_new_with_free(gpointer p, GFreeFunc free_)
4086 aptr = g_auto_pointer_new(p);
4087 aptr->ref->free = free_;
4091 gpointer g_auto_pointer_get_ptr(GAuto *auto_ptr)
4093 if (auto_ptr == NULL)
4095 return ((AutoPointer *) auto_ptr)->ptr;
4099 *\brief Copies an auto pointer by. It's mostly not necessary
4100 * to call this function directly, unless you copy/assign
4101 * the guarded pointer.
4103 *\param auto_ptr Auto pointer returned by previous call to
4104 * g_auto_pointer_new_XXX()
4106 *\return gpointer An auto pointer
4108 GAuto *g_auto_pointer_copy(GAuto *auto_ptr)
4111 AutoPointerRef *ref;
4114 if (auto_ptr == NULL)
4119 newp = g_new0(AutoPointer, 1);
4122 newp->ptr = ref->pointer;
4126 G_PRINT_REF ("XXXX COPY(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4132 *\brief Free an auto pointer
4134 void g_auto_pointer_free(GAuto *auto_ptr)
4137 AutoPointerRef *ref;
4139 if (auto_ptr == NULL)
4145 if (--(ref->cnt) == 0) {
4147 G_PRINT_REF ("XXXX FREE(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4149 ref->free(ref->pointer);
4154 G_PRINT_REF ("XXXX DEREF(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4159 void replace_returns(gchar *str)
4164 while (strstr(str, "\n")) {
4165 *strstr(str, "\n") = ' ';
4167 while (strstr(str, "\r")) {
4168 *strstr(str, "\r") = ' ';
4172 /* get_uri_part() - retrieves a URI starting from scanpos.
4173 Returns TRUE if succesful */
4174 gboolean get_uri_part(const gchar *start, const gchar *scanpos,
4175 const gchar **bp, const gchar **ep, gboolean hdr)
4178 gint parenthese_cnt = 0;
4180 cm_return_val_if_fail(start != NULL, FALSE);
4181 cm_return_val_if_fail(scanpos != NULL, FALSE);
4182 cm_return_val_if_fail(bp != NULL, FALSE);
4183 cm_return_val_if_fail(ep != NULL, FALSE);
4187 /* find end point of URI */
4188 for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
4189 if (!g_ascii_isgraph(*(const guchar *)ep_) ||
4190 !IS_ASCII(*(const guchar *)ep_) ||
4191 strchr("[]{}<>\"", *ep_)) {
4193 } else if (strchr("(", *ep_)) {
4195 } else if (strchr(")", *ep_)) {
4196 if (parenthese_cnt > 0)
4203 /* no punctuation at end of string */
4205 /* FIXME: this stripping of trailing punctuations may bite with other URIs.
4206 * should pass some URI type to this function and decide on that whether
4207 * to perform punctuation stripping */
4209 #define IS_REAL_PUNCT(ch) (g_ascii_ispunct(ch) && !strchr("/?=-_)", ch))
4211 for (; ep_ - 1 > scanpos + 1 &&
4212 IS_REAL_PUNCT(*(ep_ - 1));
4216 #undef IS_REAL_PUNCT
4223 gchar *make_uri_string(const gchar *bp, const gchar *ep)
4225 while (bp && *bp && g_ascii_isspace(*bp))
4227 return g_strndup(bp, ep - bp);
4230 /* valid mail address characters */
4231 #define IS_RFC822_CHAR(ch) \
4235 !g_ascii_isspace(ch) && \
4236 !strchr("(),;<>\"", (ch)))
4238 /* alphabet and number within 7bit ASCII */
4239 #define IS_ASCII_ALNUM(ch) (IS_ASCII(ch) && g_ascii_isalnum(ch))
4240 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
4242 static GHashTable *create_domain_tab(void)
4244 static const gchar *toplvl_domains [] = {
4246 "arpa", "coop", "info", "name", "biz", "com", "edu", "gov",
4247 "int", "mil", "net", "org", "ac", "ad", "ae", "af", "ag",
4248 "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au",
4249 "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi",
4250 "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
4251 "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl",
4252 "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz", "de",
4253 "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er",
4254 "es", "et", "eu", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd",
4255 "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
4256 "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
4257 "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir",
4258 "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
4259 "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc",
4260 "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
4261 "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq",
4262 "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na",
4263 "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu",
4264 "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm",
4265 "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru",
4266 "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj",
4267 "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
4268 "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to",
4269 "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um",
4270 "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu",
4271 "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw"
4274 GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
4276 cm_return_val_if_fail(htab, NULL);
4277 for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++)
4278 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
4282 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
4284 const gint MAX_LVL_DOM_NAME_LEN = 6;
4285 gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
4286 const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
4289 if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
4292 for (p = buf; p < m && first < last; *p++ = *first++)
4296 return g_hash_table_lookup(tab, buf) != NULL;
4299 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
4300 gboolean get_email_part(const gchar *start, const gchar *scanpos,
4301 const gchar **bp, const gchar **ep, gboolean hdr)
4303 /* more complex than the uri part because we need to scan back and forward starting from
4304 * the scan position. */
4305 gboolean result = FALSE;
4306 const gchar *bp_ = NULL;
4307 const gchar *ep_ = NULL;
4308 static GHashTable *dom_tab;
4309 const gchar *last_dot = NULL;
4310 const gchar *prelast_dot = NULL;
4311 const gchar *last_tld_char = NULL;
4313 /* the informative part of the email address (describing the name
4314 * of the email address owner) may contain quoted parts. the
4315 * closure stack stores the last encountered quotes. */
4316 gchar closure_stack[128];
4317 gchar *ptr = closure_stack;
4319 cm_return_val_if_fail(start != NULL, FALSE);
4320 cm_return_val_if_fail(scanpos != NULL, FALSE);
4321 cm_return_val_if_fail(bp != NULL, FALSE);
4322 cm_return_val_if_fail(ep != NULL, FALSE);
4325 const gchar *start_quote = NULL;
4326 const gchar *end_quote = NULL;
4328 /* go to the real start */
4329 if (start[0] == ',')
4331 if (start[0] == ';')
4333 while (start[0] == '\n' || start[0] == '\r')
4335 while (start[0] == ' ' || start[0] == '\t')
4340 /* check if there are quotes (to skip , in them) */
4341 if (*start == '"') {
4342 start_quote = start;
4344 end_quote = strstr(start, "\"");
4350 /* skip anything between quotes */
4351 if (start_quote && end_quote) {
4356 /* find end (either , or ; or end of line) */
4357 if (strstr(start, ",") && strstr(start, ";"))
4358 *ep = strstr(start,",") < strstr(start, ";")
4359 ? strstr(start, ",") : strstr(start, ";");
4360 else if (strstr(start, ","))
4361 *ep = strstr(start, ",");
4362 else if (strstr(start, ";"))
4363 *ep = strstr(start, ";");
4365 *ep = start+strlen(start);
4367 /* go back to real start */
4368 if (start_quote && end_quote) {
4369 start = start_quote;
4372 /* check there's still an @ in that, or search
4373 * further if possible */
4374 if (strstr(start, "@") && strstr(start, "@") < *ep)
4376 else if (*ep < start+strlen(start)) {
4379 } else if (start_quote && strstr(start, "\"") && strstr(start, "\"") < *ep) {
4387 dom_tab = create_domain_tab();
4388 cm_return_val_if_fail(dom_tab, FALSE);
4390 /* scan start of address */
4391 for (bp_ = scanpos - 1;
4392 bp_ >= start && IS_RFC822_CHAR(*(const guchar *)bp_); bp_--)
4395 /* TODO: should start with an alnum? */
4397 for (; bp_ < scanpos && !IS_ASCII_ALNUM(*(const guchar *)bp_); bp_++)
4400 if (bp_ != scanpos) {
4401 /* scan end of address */
4402 for (ep_ = scanpos + 1;
4403 *ep_ && IS_RFC822_CHAR(*(const guchar *)ep_); ep_++)
4405 prelast_dot = last_dot;
4407 if (*(last_dot + 1) == '.') {
4408 if (prelast_dot == NULL)
4410 last_dot = prelast_dot;
4415 /* TODO: really should terminate with an alnum? */
4416 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*(const guchar *)ep_);
4421 if (last_dot == NULL)
4423 if (last_dot >= ep_)
4424 last_dot = prelast_dot;
4425 if (last_dot == NULL || (scanpos + 1 >= last_dot))
4429 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
4430 if (*last_tld_char == '?')
4433 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
4440 if (!result) return FALSE;
4442 if (*ep_ && bp_ != start && *(bp_ - 1) == '"' && *(ep_) == '"'
4443 && *(ep_ + 1) == ' ' && *(ep_ + 2) == '<'
4444 && IS_RFC822_CHAR(*(ep_ + 3))) {
4445 /* this informative part with an @ in it is
4446 * followed by the email address */
4449 /* go to matching '>' (or next non-rfc822 char, like \n) */
4450 for (; *ep_ != '>' && *ep != '\0' && IS_RFC822_CHAR(*ep_); ep_++)
4453 /* include the bracket */
4454 if (*ep_ == '>') ep_++;
4456 /* include the leading quote */
4464 /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
4465 if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
4468 /* see if this is <bracketed>; in this case we also scan for the informative part. */
4469 if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
4472 #define FULL_STACK() ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
4473 #define IN_STACK() (ptr > closure_stack)
4474 /* has underrun check */
4475 #define POP_STACK() if(IN_STACK()) --ptr
4476 /* has overrun check */
4477 #define PUSH_STACK(c) if(!FULL_STACK()) *ptr++ = (c); else return TRUE
4478 /* has underrun check */
4479 #define PEEK_STACK() (IN_STACK() ? *(ptr - 1) : 0)
4483 /* scan for the informative part. */
4484 for (bp_ -= 2; bp_ >= start; bp_--) {
4485 /* if closure on the stack keep scanning */
4486 if (PEEK_STACK() == *bp_) {
4490 if (!IN_STACK() && (*bp_ == '\'' || *bp_ == '"')) {
4495 /* if nothing in the closure stack, do the special conditions
4496 * the following if..else expression simply checks whether
4497 * a token is acceptable. if not acceptable, the clause
4498 * should terminate the loop with a 'break' */
4499 if (!PEEK_STACK()) {
4501 && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
4502 && (((bp_ + 1) < ep_) && isalnum(*(bp_ + 1)))) {
4503 /* hyphens are allowed, but only in
4505 } else if (strchr(" \"'", *bp_)) {
4506 /* but anything not being a punctiation
4509 break; /* anything else is rejected */
4516 /* scan forward (should start with an alnum) */
4517 for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
4533 #undef IS_ASCII_ALNUM
4534 #undef IS_RFC822_CHAR
4536 gchar *make_email_string(const gchar *bp, const gchar *ep)
4538 /* returns a mailto: URI; mailto: is also used to detect the
4539 * uri type later on in the button_pressed signal handler */
4543 tmp = g_strndup(bp, ep - bp);
4544 result = g_strconcat("mailto:", tmp, NULL);
4550 gchar *make_http_string(const gchar *bp, const gchar *ep)
4552 /* returns an http: URI; */
4556 while (bp && *bp && g_ascii_isspace(*bp))
4558 tmp = g_strndup(bp, ep - bp);
4559 result = g_strconcat("http://", tmp, NULL);
4565 static gchar *mailcap_get_command_in_file(const gchar *path, const gchar *type, const gchar *file_to_open)
4567 FILE *fp = g_fopen(path, "rb");
4568 gchar buf[BUFFSIZE];
4569 gchar *result = NULL;
4572 while (fgets(buf, sizeof (buf), fp) != NULL) {
4573 gchar **parts = g_strsplit(buf, ";", 3);
4574 gchar *trimmed = parts[0];
4575 while (trimmed[0] == ' ' || trimmed[0] == '\t')
4577 while (trimmed[strlen(trimmed)-1] == ' ' || trimmed[strlen(trimmed)-1] == '\t')
4578 trimmed[strlen(trimmed)-1] = '\0';
4580 if (!strcmp(trimmed, type)) {
4581 gboolean needsterminal = FALSE;
4582 if (parts[2] && strstr(parts[2], "needsterminal")) {
4583 needsterminal = TRUE;
4585 if (parts[2] && strstr(parts[2], "test=")) {
4586 gchar *orig_testcmd = g_strdup(strstr(parts[2], "test=")+5);
4587 gchar *testcmd = orig_testcmd;
4588 if (strstr(testcmd,";"))
4589 *(strstr(testcmd,";")) = '\0';
4590 while (testcmd[0] == ' ' || testcmd[0] == '\t')
4592 while (testcmd[strlen(testcmd)-1] == '\n')
4593 testcmd[strlen(testcmd)-1] = '\0';
4594 while (testcmd[strlen(testcmd)-1] == '\r')
4595 testcmd[strlen(testcmd)-1] = '\0';
4596 while (testcmd[strlen(testcmd)-1] == ' ' || testcmd[strlen(testcmd)-1] == '\t')
4597 testcmd[strlen(testcmd)-1] = '\0';
4599 if (strstr(testcmd, "%s")) {
4600 gchar *tmp = g_strdup_printf(testcmd, file_to_open);
4601 gint res = system(tmp);
4603 g_free(orig_testcmd);
4610 gint res = system(testcmd);
4611 g_free(orig_testcmd);
4621 while (trimmed[0] == ' ' || trimmed[0] == '\t')
4623 while (trimmed[strlen(trimmed)-1] == '\n')
4624 trimmed[strlen(trimmed)-1] = '\0';
4625 while (trimmed[strlen(trimmed)-1] == '\r')
4626 trimmed[strlen(trimmed)-1] = '\0';
4627 while (trimmed[strlen(trimmed)-1] == ' ' || trimmed[strlen(trimmed)-1] == '\t')
4628 trimmed[strlen(trimmed)-1] = '\0';
4629 result = g_strdup(trimmed);
4632 /* if there are no single quotes around %s, add them.
4633 * '.*%s.*' is ok, as in display 'png:%s'
4635 if (strstr(result, "%s")
4636 && !(strstr(result, "'") < strstr(result,"%s") &&
4637 strstr(strstr(result,"%s"), "'"))) {
4638 gchar *start = g_strdup(result);
4639 gchar *end = g_strdup(strstr(result, "%s")+2);
4641 *strstr(start, "%s") = '\0';
4642 tmp = g_strconcat(start,"'%s'",end, NULL);
4648 if (needsterminal) {
4649 gchar *tmp = g_strdup_printf("xterm -e %s", result);
4660 gchar *mailcap_get_command_for_type(const gchar *type, const gchar *file_to_open)
4662 gchar *result = NULL;
4666 path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap", NULL);
4667 result = mailcap_get_command_in_file(path, type, file_to_open);
4671 result = mailcap_get_command_in_file("/etc/mailcap", type, file_to_open);
4675 void mailcap_update_default(const gchar *type, const gchar *command)
4677 gchar *path = NULL, *outpath = NULL;
4678 path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap", NULL);
4679 outpath = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap.new", NULL);
4680 FILE *fp = g_fopen(path, "rb");
4682 gchar buf[BUFFSIZE];
4683 gboolean err = FALSE;
4686 fp = g_fopen(path, "a");
4688 g_warning("failed to create file %s", path);
4693 fp = g_freopen(path, "rb", fp);
4695 g_warning("failed to reopen file %s", path);
4702 outfp = g_fopen(outpath, "wb");
4704 g_warning("failed to create file %s", outpath);
4710 while (fp && fgets(buf, sizeof (buf), fp) != NULL) {
4711 gchar **parts = g_strsplit(buf, ";", 3);
4712 gchar *trimmed = parts[0];
4713 while (trimmed[0] == ' ')
4715 while (trimmed[strlen(trimmed)-1] == ' ')
4716 trimmed[strlen(trimmed)-1] = '\0';
4718 if (!strcmp(trimmed, type)) {
4723 if(fputs(buf, outfp) == EOF) {
4730 if (fprintf(outfp, "%s; %s\n", type, command) < 0)
4736 if (fclose(outfp) == EOF)
4740 g_rename(outpath, path);
4746 gint copy_dir(const gchar *src, const gchar *dst)
4751 if ((dir = g_dir_open(src, 0, NULL)) == NULL) {
4752 g_warning("failed to open directory: %s", src);
4756 if (make_dir(dst) < 0)
4759 while ((name = g_dir_read_name(dir)) != NULL) {
4760 gchar *old_file, *new_file;
4761 old_file = g_strconcat(src, G_DIR_SEPARATOR_S, name, NULL);
4762 new_file = g_strconcat(dst, G_DIR_SEPARATOR_S, name, NULL);
4763 debug_print("copying: %s -> %s\n", old_file, new_file);
4764 if (g_file_test(old_file, G_FILE_TEST_IS_REGULAR)) {
4765 gint r = copy_file(old_file, new_file, TRUE);
4772 /* Windows has no symlinks. Or well, Vista seems to
4773 have something like this but the semantics might be
4774 different. Thus we don't use it under Windows. */
4775 else if (g_file_test(old_file, G_FILE_TEST_IS_SYMLINK)) {
4776 GError *error = NULL;
4778 gchar *target = g_file_read_link(old_file, &error);
4780 r = symlink(target, new_file);
4787 #endif /*G_OS_WIN32*/
4788 else if (g_file_test(old_file, G_FILE_TEST_IS_DIR)) {
4789 gint r = copy_dir(old_file, new_file);
4800 /* crude test to see if a file is an email. */
4801 gboolean file_is_email (const gchar *filename)
4807 if (filename == NULL)
4809 if ((fp = g_fopen(filename, "rb")) == NULL)
4811 while (i < 60 && score < 3
4812 && fgets(buffer, sizeof (buffer), fp) > 0) {
4813 if (!strncmp(buffer, "From:", strlen("From:")))
4815 else if (!strncmp(buffer, "Date:", strlen("Date:")))
4817 else if (!strncmp(buffer, "Message-ID:", strlen("Message-ID:")))
4819 else if (!strncmp(buffer, "Subject:", strlen("Subject:")))
4824 return (score >= 3);
4827 gboolean sc_g_list_bigger(GList *list, gint max)
4831 while (cur && i <= max+1) {
4838 gboolean sc_g_slist_bigger(GSList *list, gint max)
4842 while (cur && i <= max+1) {
4849 const gchar *daynames[] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL};
4850 const gchar *monthnames[] = {NULL, NULL, NULL, NULL, NULL, NULL,
4851 NULL, NULL, NULL, NULL, NULL, NULL};
4852 const gchar *s_daynames[] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL};
4853 const gchar *s_monthnames[] = {NULL, NULL, NULL, NULL, NULL, NULL,
4854 NULL, NULL, NULL, NULL, NULL, NULL};
4856 gint daynames_len[] = {0,0,0,0,0,0,0};
4857 gint monthnames_len[] = {0,0,0,0,0,0,
4859 gint s_daynames_len[] = {0,0,0,0,0,0,0};
4860 gint s_monthnames_len[] = {0,0,0,0,0,0,
4862 const gchar *s_am_up = NULL;
4863 const gchar *s_pm_up = NULL;
4864 const gchar *s_am_low = NULL;
4865 const gchar *s_pm_low = NULL;
4867 gint s_am_up_len = 0;
4868 gint s_pm_up_len = 0;
4869 gint s_am_low_len = 0;
4870 gint s_pm_low_len = 0;
4872 static gboolean time_names_init_done = FALSE;
4874 static void init_time_names(void)
4878 daynames[0] = C_("Complete day name for use by strftime", "Sunday");
4879 daynames[1] = C_("Complete day name for use by strftime", "Monday");
4880 daynames[2] = C_("Complete day name for use by strftime", "Tuesday");
4881 daynames[3] = C_("Complete day name for use by strftime", "Wednesday");
4882 daynames[4] = C_("Complete day name for use by strftime", "Thursday");
4883 daynames[5] = C_("Complete day name for use by strftime", "Friday");
4884 daynames[6] = C_("Complete day name for use by strftime", "Saturday");
4886 monthnames[0] = C_("Complete month name for use by strftime", "January");
4887 monthnames[1] = C_("Complete month name for use by strftime", "February");
4888 monthnames[2] = C_("Complete month name for use by strftime", "March");
4889 monthnames[3] = C_("Complete month name for use by strftime", "April");
4890 monthnames[4] = C_("Complete month name for use by strftime", "May");
4891 monthnames[5] = C_("Complete month name for use by strftime", "June");
4892 monthnames[6] = C_("Complete month name for use by strftime", "July");
4893 monthnames[7] = C_("Complete month name for use by strftime", "August");
4894 monthnames[8] = C_("Complete month name for use by strftime", "September");
4895 monthnames[9] = C_("Complete month name for use by strftime", "October");
4896 monthnames[10] = C_("Complete month name for use by strftime", "November");
4897 monthnames[11] = C_("Complete month name for use by strftime", "December");
4899 s_daynames[0] = C_("Abbr. day name for use by strftime", "Sun");
4900 s_daynames[1] = C_("Abbr. day name for use by strftime", "Mon");
4901 s_daynames[2] = C_("Abbr. day name for use by strftime", "Tue");
4902 s_daynames[3] = C_("Abbr. day name for use by strftime", "Wed");
4903 s_daynames[4] = C_("Abbr. day name for use by strftime", "Thu");
4904 s_daynames[5] = C_("Abbr. day name for use by strftime", "Fri");
4905 s_daynames[6] = C_("Abbr. day name for use by strftime", "Sat");
4907 s_monthnames[0] = C_("Abbr. month name for use by strftime", "Jan");
4908 s_monthnames[1] = C_("Abbr. month name for use by strftime", "Feb");
4909 s_monthnames[2] = C_("Abbr. month name for use by strftime", "Mar");
4910 s_monthnames[3] = C_("Abbr. month name for use by strftime", "Apr");
4911 s_monthnames[4] = C_("Abbr. month name for use by strftime", "May");
4912 s_monthnames[5] = C_("Abbr. month name for use by strftime", "Jun");
4913 s_monthnames[6] = C_("Abbr. month name for use by strftime", "Jul");
4914 s_monthnames[7] = C_("Abbr. month name for use by strftime", "Aug");
4915 s_monthnames[8] = C_("Abbr. month name for use by strftime", "Sep");
4916 s_monthnames[9] = C_("Abbr. month name for use by strftime", "Oct");
4917 s_monthnames[10] = C_("Abbr. month name for use by strftime", "Nov");
4918 s_monthnames[11] = C_("Abbr. month name for use by strftime", "Dec");
4920 for (i = 0; i < 7; i++) {
4921 daynames_len[i] = strlen(daynames[i]);
4922 s_daynames_len[i] = strlen(s_daynames[i]);
4924 for (i = 0; i < 12; i++) {
4925 monthnames_len[i] = strlen(monthnames[i]);
4926 s_monthnames_len[i] = strlen(s_monthnames[i]);
4929 s_am_up = C_("For use by strftime (morning)", "AM");
4930 s_pm_up = C_("For use by strftime (afternoon)", "PM");
4931 s_am_low = C_("For use by strftime (morning, lowercase)", "am");
4932 s_pm_low = C_("For use by strftime (afternoon, lowercase)", "pm");
4934 s_am_up_len = strlen(s_am_up);
4935 s_pm_up_len = strlen(s_pm_up);
4936 s_am_low_len = strlen(s_am_low);
4937 s_pm_low_len = strlen(s_pm_low);
4939 time_names_init_done = TRUE;
4942 #define CHECK_SIZE() { \
4943 total_done += len; \
4944 if (total_done >= buflen) { \
4945 buf[buflen-1] = '\0'; \
4950 size_t fast_strftime(gchar *buf, gint buflen, const gchar *format, struct tm *lt)
4952 gchar *curpos = buf;
4953 gint total_done = 0;
4954 gchar subbuf[64], subfmt[64];
4955 static time_t last_tzset = (time_t)0;
4957 if (!time_names_init_done)
4960 if (format == NULL || lt == NULL)
4963 if (last_tzset != time(NULL)) {
4965 last_tzset = time(NULL);
4968 if (*format == '%') {
4969 gint len = 0, tmp = 0;
4973 len = 1; CHECK_SIZE();
4977 len = s_daynames_len[lt->tm_wday]; CHECK_SIZE();
4978 strncpy2(curpos, s_daynames[lt->tm_wday], buflen - total_done);
4981 len = daynames_len[lt->tm_wday]; CHECK_SIZE();
4982 strncpy2(curpos, daynames[lt->tm_wday], buflen - total_done);
4986 len = s_monthnames_len[lt->tm_mon]; CHECK_SIZE();
4987 strncpy2(curpos, s_monthnames[lt->tm_mon], buflen - total_done);
4990 len = monthnames_len[lt->tm_mon]; CHECK_SIZE();
4991 strncpy2(curpos, monthnames[lt->tm_mon], buflen - total_done);
4994 strftime(subbuf, 64, "%c", lt);
4995 len = strlen(subbuf); CHECK_SIZE();
4996 strncpy2(curpos, subbuf, buflen - total_done);
4999 total_done += 2; CHECK_SIZE();
5000 tmp = (lt->tm_year + 1900)/100;
5001 *curpos++ = '0'+(tmp / 10);
5002 *curpos++ = '0'+(tmp % 10);
5005 total_done += 2; CHECK_SIZE();
5006 *curpos++ = '0'+(lt->tm_mday / 10);
5007 *curpos++ = '0'+(lt->tm_mday % 10);
5010 total_done += 8; CHECK_SIZE();
5011 *curpos++ = '0'+((lt->tm_mon+1) / 10);
5012 *curpos++ = '0'+((lt->tm_mon+1) % 10);
5014 *curpos++ = '0'+(lt->tm_mday / 10);
5015 *curpos++ = '0'+(lt->tm_mday % 10);
5017 tmp = lt->tm_year%100;
5018 *curpos++ = '0'+(tmp / 10);
5019 *curpos++ = '0'+(tmp % 10);
5022 len = 2; CHECK_SIZE();
5023 snprintf(curpos, buflen - total_done, "%2d", lt->tm_mday);
5026 len = 10; CHECK_SIZE();
5027 snprintf(curpos, buflen - total_done, "%4d-%02d-%02d",
5028 lt->tm_year + 1900, lt->tm_mon +1, lt->tm_mday);
5031 total_done += 2; CHECK_SIZE();
5032 *curpos++ = '0'+(lt->tm_hour / 10);
5033 *curpos++ = '0'+(lt->tm_hour % 10);
5036 total_done += 2; CHECK_SIZE();
5042 *curpos++ = '0'+(tmp / 10);
5043 *curpos++ = '0'+(tmp % 10);
5046 len = 3; CHECK_SIZE();
5047 snprintf(curpos, buflen - total_done, "%03d", lt->tm_yday+1);
5050 len = 2; CHECK_SIZE();
5051 snprintf(curpos, buflen - total_done, "%2d", lt->tm_hour);
5054 len = 2; CHECK_SIZE();
5060 snprintf(curpos, buflen - total_done, "%2d", tmp);
5063 total_done += 2; CHECK_SIZE();
5064 tmp = lt->tm_mon + 1;
5065 *curpos++ = '0'+(tmp / 10);
5066 *curpos++ = '0'+(tmp % 10);
5069 total_done += 2; CHECK_SIZE();
5070 *curpos++ = '0'+(lt->tm_min / 10);
5071 *curpos++ = '0'+(lt->tm_min % 10);
5074 len = 1; CHECK_SIZE();
5078 if (lt->tm_hour >= 12) {
5079 len = s_pm_up_len; CHECK_SIZE();
5080 snprintf(curpos, buflen-total_done, "%s", s_pm_up);
5082 len = s_am_up_len; CHECK_SIZE();
5083 snprintf(curpos, buflen-total_done, "%s", s_am_up);
5087 if (lt->tm_hour >= 12) {
5088 len = s_pm_low_len; CHECK_SIZE();
5089 snprintf(curpos, buflen-total_done, "%s", s_pm_low);
5091 len = s_am_low_len; CHECK_SIZE();
5092 snprintf(curpos, buflen-total_done, "%s", s_am_low);
5096 strftime(subbuf, 64, "%r", lt);
5097 len = strlen(subbuf); CHECK_SIZE();
5098 strncpy2(curpos, subbuf, buflen - total_done);
5101 total_done += 5; CHECK_SIZE();
5102 *curpos++ = '0'+(lt->tm_hour / 10);
5103 *curpos++ = '0'+(lt->tm_hour % 10);
5105 *curpos++ = '0'+(lt->tm_min / 10);
5106 *curpos++ = '0'+(lt->tm_min % 10);
5109 snprintf(subbuf, 64, "%ld", mktime(lt));
5110 len = strlen(subbuf); CHECK_SIZE();
5111 strncpy2(curpos, subbuf, buflen - total_done);
5114 total_done += 2; CHECK_SIZE();
5115 *curpos++ = '0'+(lt->tm_sec / 10);
5116 *curpos++ = '0'+(lt->tm_sec % 10);
5119 len = 1; CHECK_SIZE();
5123 total_done += 8; CHECK_SIZE();
5124 *curpos++ = '0'+(lt->tm_hour / 10);
5125 *curpos++ = '0'+(lt->tm_hour % 10);
5127 *curpos++ = '0'+(lt->tm_min / 10);
5128 *curpos++ = '0'+(lt->tm_min % 10);
5130 *curpos++ = '0'+(lt->tm_sec / 10);
5131 *curpos++ = '0'+(lt->tm_sec % 10);
5134 len = 1; CHECK_SIZE();
5135 snprintf(curpos, buflen - total_done, "%d", lt->tm_wday == 0 ? 7: lt->tm_wday);
5138 len = 1; CHECK_SIZE();
5139 snprintf(curpos, buflen - total_done, "%d", lt->tm_wday);
5142 strftime(subbuf, 64, "%x", lt);
5143 len = strlen(subbuf); CHECK_SIZE();
5144 strncpy2(curpos, subbuf, buflen - total_done);
5147 strftime(subbuf, 64, "%X", lt);
5148 len = strlen(subbuf); CHECK_SIZE();
5149 strncpy2(curpos, subbuf, buflen - total_done);
5152 total_done += 2; CHECK_SIZE();
5153 tmp = lt->tm_year%100;
5154 *curpos++ = '0'+(tmp / 10);
5155 *curpos++ = '0'+(tmp % 10);
5158 len = 4; CHECK_SIZE();
5159 snprintf(curpos, buflen - total_done, "%4d", lt->tm_year + 1900);
5169 /* let these complicated ones be done with the libc */
5170 snprintf(subfmt, 64, "%%%c", *format);
5171 strftime(subbuf, 64, subfmt, lt);
5172 len = strlen(subbuf); CHECK_SIZE();
5173 strncpy2(curpos, subbuf, buflen - total_done);
5177 /* let these complicated modifiers be done with the libc */
5178 snprintf(subfmt, 64, "%%%c%c", *format, *(format+1));
5179 strftime(subbuf, 64, subfmt, lt);
5180 len = strlen(subbuf); CHECK_SIZE();
5181 strncpy2(curpos, subbuf, buflen - total_done);
5185 g_warning("format error (%c)", *format);
5192 int len = 1; CHECK_SIZE();
5193 *curpos++ = *format++;
5200 gboolean prefs_common_get_use_shred(void);
5204 #define WEXITSTATUS(x) (x)
5207 int claws_unlink(const gchar *filename)
5210 static int found_shred = -1;
5211 static const gchar *args[4];
5213 if (filename == NULL)
5216 if (prefs_common_get_use_shred()) {
5217 if (found_shred == -1) {
5219 args[0] = g_find_program_in_path("shred");
5220 debug_print("found shred: %s\n", args[0]);
5221 found_shred = (args[0] != NULL) ? 1:0;
5225 if (found_shred == 1) {
5226 if (g_stat(filename, &s) == 0 && S_ISREG(s.st_mode)) {
5227 if (s.st_nlink == 1) {
5230 g_spawn_sync(NULL, (gchar **)args, NULL, 0,
5231 NULL, NULL, NULL, NULL, &status, NULL);
5232 debug_print("%s %s exited with status %d\n",
5233 args[0], filename, WEXITSTATUS(status));
5234 if (truncate(filename, 0) < 0)
5235 g_warning("couln't truncate: %s", filename);
5240 return g_unlink(filename);
5243 GMutex *cm_mutex_new(void) {
5244 #if GLIB_CHECK_VERSION(2,32,0)
5245 GMutex *m = g_new0(GMutex, 1);
5249 return g_mutex_new();
5253 void cm_mutex_free(GMutex *mutex) {
5254 #if GLIB_CHECK_VERSION(2,32,0)
5255 g_mutex_clear(mutex);
5258 g_mutex_free(mutex);
5262 static gchar *canonical_list_to_file(GSList *list)
5264 GString *result = g_string_new(NULL);
5265 GSList *pathlist = g_slist_reverse(g_slist_copy(list));
5270 result = g_string_append(result, G_DIR_SEPARATOR_S);
5272 if (pathlist->data) {
5273 const gchar *root = (gchar *)pathlist->data;
5274 if (root[0] != '\0' && g_ascii_isalpha(root[0]) &&
5276 /* drive - don't prepend dir separator */
5278 result = g_string_append(result, G_DIR_SEPARATOR_S);
5283 for (cur = pathlist; cur; cur = cur->next) {
5284 result = g_string_append(result, (gchar *)cur->data);
5286 result = g_string_append(result, G_DIR_SEPARATOR_S);
5288 g_slist_free(pathlist);
5291 g_string_free(result, FALSE);
5296 static GSList *cm_split_path(const gchar *filename, int depth)
5299 GSList *canonical_parts = NULL;
5302 gboolean follow_symlinks = TRUE;
5308 errno = EINVAL; /* can't happen, no symlink handling */
5313 if (!g_path_is_absolute(filename)) {
5318 path_parts = g_strsplit(filename, G_DIR_SEPARATOR_S, -1);
5320 for (i = 0; path_parts[i] != NULL; i++) {
5321 if (!strcmp(path_parts[i], ""))
5323 if (!strcmp(path_parts[i], "."))
5325 else if (!strcmp(path_parts[i], "..")) {
5330 else /* Remove the last inserted element */
5332 g_slist_delete_link(canonical_parts,
5337 canonical_parts = g_slist_prepend(canonical_parts,
5338 g_strdup(path_parts[i]));
5340 tmp_path = canonical_list_to_file(canonical_parts);
5342 if(g_stat(tmp_path, &st) < 0) {
5343 if (errno == ENOENT) {
5345 follow_symlinks = FALSE;
5349 slist_free_strings_full(canonical_parts);
5350 g_strfreev(path_parts);
5356 if (follow_symlinks && g_file_test(tmp_path, G_FILE_TEST_IS_SYMLINK)) {
5357 GError *error = NULL;
5358 gchar *target = g_file_read_link(tmp_path, &error);
5360 if (!g_path_is_absolute(target)) {
5361 /* remove the last inserted element */
5363 g_slist_delete_link(canonical_parts,
5365 /* add the target */
5366 canonical_parts = g_slist_prepend(canonical_parts,
5370 /* and get the new target */
5371 target = canonical_list_to_file(canonical_parts);
5374 /* restart from absolute target */
5375 slist_free_strings_full(canonical_parts);
5376 canonical_parts = NULL;
5378 canonical_parts = cm_split_path(target, depth + 1);
5380 g_error_free(error);
5381 if (canonical_parts == NULL) {
5383 g_strfreev(path_parts);
5392 g_strfreev(path_parts);
5393 return canonical_parts;
5397 * Canonicalize a filename, resolving symlinks along the way.
5398 * Returns a negative errno in case of error.
5400 int cm_canonicalize_filename(const gchar *filename, gchar **canonical_name) {
5401 GSList *canonical_parts;
5402 gboolean is_absolute;
5404 if (filename == NULL)
5406 if (canonical_name == NULL)
5408 *canonical_name = NULL;
5410 is_absolute = g_path_is_absolute(filename);
5412 /* Always work on absolute filenames. */
5413 gchar *cur = g_get_current_dir();
5414 gchar *absolute_filename = g_strconcat(cur, G_DIR_SEPARATOR_S,
5417 canonical_parts = cm_split_path(absolute_filename, 0);
5418 g_free(absolute_filename);
5421 canonical_parts = cm_split_path(filename, 0);
5423 if (canonical_parts == NULL)
5426 *canonical_name = canonical_list_to_file(canonical_parts);
5427 slist_free_strings_full(canonical_parts);
5431 /* Returns a decoded base64 string, guaranteed to be null-terminated. */
5432 guchar *g_base64_decode_zero(const gchar *text, gsize *out_len)
5434 gchar *tmp = g_base64_decode(text, out_len);
5435 gchar *out = g_strndup(tmp, *out_len);
5439 if (strlen(out) != *out_len) {
5440 g_warning ("strlen(out) %zd != *out_len %" G_GSIZE_FORMAT, strlen(out), *out_len);
5446 #if !GLIB_CHECK_VERSION(2, 30, 0)
5449 * @str: a UTF-8 encoded string
5450 * @start_pos: a character offset within @str
5451 * @end_pos: another character offset within @str
5453 * Copies a substring out of a UTF-8 encoded string.
5454 * The substring will contain @end_pos - @start_pos
5457 * Returns: a newly allocated copy of the requested
5458 * substring. Free with g_free() when no longer needed.
5463 g_utf8_substring (const gchar *str,
5467 gchar *start, *end, *out;
5469 start = g_utf8_offset_to_pointer (str, start_pos);
5470 end = g_utf8_offset_to_pointer (start, end_pos - start_pos);
5472 out = g_malloc (end - start + 1);
5473 memcpy (out, start, end - start);
5474 out[end - start] = 0;