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;
1984 if (gethostname(hostname, sizeof(hostname)) != 0) {
1985 perror("gethostname");
1986 domain_name = "localhost";
1988 hostname[sizeof(hostname) - 1] = '\0';
1989 if ((hp = my_gethostbyname(hostname)) == NULL) {
1990 perror("gethostbyname");
1991 domain_name = g_strdup(hostname);
1993 domain_name = g_strdup(hp->h_name);
1996 debug_print("domain name = %s\n", domain_name);
2005 off_t get_file_size(const gchar *file)
2009 if (g_stat(file, &s) < 0) {
2010 FILE_OP_ERROR(file, "stat");
2017 time_t get_file_mtime(const gchar *file)
2021 if (g_stat(file, &s) < 0) {
2022 FILE_OP_ERROR(file, "stat");
2029 off_t get_file_size_as_crlf(const gchar *file)
2033 gchar buf[BUFFSIZE];
2035 if ((fp = g_fopen(file, "rb")) == NULL) {
2036 FILE_OP_ERROR(file, "g_fopen");
2040 while (fgets(buf, sizeof(buf), fp) != NULL) {
2042 size += strlen(buf) + 2;
2046 FILE_OP_ERROR(file, "fgets");
2055 gboolean file_exist(const gchar *file, gboolean allow_fifo)
2062 if (g_stat(file, &s) < 0) {
2063 if (ENOENT != errno) FILE_OP_ERROR(file, "stat");
2067 if (S_ISREG(s.st_mode) || (allow_fifo && S_ISFIFO(s.st_mode)))
2074 /* Test on whether FILE is a relative file name. This is
2075 * straightforward for Unix but more complex for Windows. */
2076 gboolean is_relative_filename(const gchar *file)
2081 if ( *file == '\\' && file[1] == '\\' && strchr (file+2, '\\') )
2082 return FALSE; /* Prefixed with a hostname - this can't
2083 * be a relative name. */
2085 if ( ((*file >= 'a' && *file <= 'z')
2086 || (*file >= 'A' && *file <= 'Z'))
2088 file += 2; /* Skip drive letter. */
2090 return !(*file == '\\' || *file == '/');
2092 return !(*file == G_DIR_SEPARATOR);
2097 gboolean is_dir_exist(const gchar *dir)
2102 return g_file_test(dir, G_FILE_TEST_IS_DIR);
2105 gboolean is_file_entry_exist(const gchar *file)
2110 return g_file_test(file, G_FILE_TEST_EXISTS);
2113 gboolean dirent_is_regular_file(struct dirent *d)
2115 #if !defined(G_OS_WIN32) && defined(HAVE_DIRENT_D_TYPE)
2116 if (d->d_type == DT_REG)
2118 else if (d->d_type != DT_UNKNOWN)
2122 return g_file_test(d->d_name, G_FILE_TEST_IS_REGULAR);
2125 gint change_dir(const gchar *dir)
2127 gchar *prevdir = NULL;
2130 prevdir = g_get_current_dir();
2132 if (g_chdir(dir) < 0) {
2133 FILE_OP_ERROR(dir, "chdir");
2134 if (debug_mode) g_free(prevdir);
2136 } else if (debug_mode) {
2139 cwd = g_get_current_dir();
2140 if (strcmp(prevdir, cwd) != 0)
2141 g_print("current dir: %s\n", cwd);
2149 gint make_dir(const gchar *dir)
2151 if (g_mkdir(dir, S_IRWXU) < 0) {
2152 FILE_OP_ERROR(dir, "mkdir");
2155 if (g_chmod(dir, S_IRWXU) < 0)
2156 FILE_OP_ERROR(dir, "chmod");
2161 gint make_dir_hier(const gchar *dir)
2166 for (p = dir; (p = strchr(p, G_DIR_SEPARATOR)) != NULL; p++) {
2167 parent_dir = g_strndup(dir, p - dir);
2168 if (*parent_dir != '\0') {
2169 if (!is_dir_exist(parent_dir)) {
2170 if (make_dir(parent_dir) < 0) {
2179 if (!is_dir_exist(dir)) {
2180 if (make_dir(dir) < 0)
2187 gint remove_all_files(const gchar *dir)
2190 const gchar *dir_name;
2193 prev_dir = g_get_current_dir();
2195 if (g_chdir(dir) < 0) {
2196 FILE_OP_ERROR(dir, "chdir");
2201 if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2202 g_warning("failed to open directory: %s", dir);
2207 while ((dir_name = g_dir_read_name(dp)) != NULL) {
2208 if (claws_unlink(dir_name) < 0)
2209 FILE_OP_ERROR(dir_name, "unlink");
2214 if (g_chdir(prev_dir) < 0) {
2215 FILE_OP_ERROR(prev_dir, "chdir");
2225 gint remove_numbered_files(const gchar *dir, guint first, guint last)
2228 const gchar *dir_name;
2232 if (first == last) {
2233 /* Skip all the dir reading part. */
2234 gchar *filename = g_strdup_printf("%s%s%u", dir, G_DIR_SEPARATOR_S, first);
2235 if (is_dir_exist(filename)) {
2236 /* a numbered directory with this name exists,
2237 * remove the dot-file instead */
2239 filename = g_strdup_printf("%s%s.%u", dir, G_DIR_SEPARATOR_S, first);
2241 if (claws_unlink(filename) < 0) {
2242 FILE_OP_ERROR(filename, "unlink");
2250 prev_dir = g_get_current_dir();
2252 if (g_chdir(dir) < 0) {
2253 FILE_OP_ERROR(dir, "chdir");
2258 if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2259 g_warning("failed to open directory: %s", dir);
2264 while ((dir_name = g_dir_read_name(dp)) != NULL) {
2265 file_no = to_number(dir_name);
2266 if (file_no > 0 && first <= file_no && file_no <= last) {
2267 if (is_dir_exist(dir_name)) {
2268 gchar *dot_file = g_strdup_printf(".%s", dir_name);
2269 if (is_file_exist(dot_file) && claws_unlink(dot_file) < 0) {
2270 FILE_OP_ERROR(dot_file, "unlink");
2275 if (claws_unlink(dir_name) < 0)
2276 FILE_OP_ERROR(dir_name, "unlink");
2282 if (g_chdir(prev_dir) < 0) {
2283 FILE_OP_ERROR(prev_dir, "chdir");
2293 gint remove_numbered_files_not_in_list(const gchar *dir, GSList *numberlist)
2296 const gchar *dir_name;
2299 GHashTable *wanted_files;
2301 GError *error = NULL;
2303 if (numberlist == NULL)
2306 prev_dir = g_get_current_dir();
2308 if (g_chdir(dir) < 0) {
2309 FILE_OP_ERROR(dir, "chdir");
2314 if ((dp = g_dir_open(".", 0, &error)) == NULL) {
2315 g_message("Couldn't open current directory: %s (%d).\n",
2316 error->message, error->code);
2317 g_error_free(error);
2322 wanted_files = g_hash_table_new(g_direct_hash, g_direct_equal);
2323 for (cur = numberlist; cur != NULL; cur = cur->next) {
2324 /* numberlist->data is expected to be GINT_TO_POINTER */
2325 g_hash_table_insert(wanted_files, cur->data, GINT_TO_POINTER(1));
2328 while ((dir_name = g_dir_read_name(dp)) != NULL) {
2329 file_no = to_number(dir_name);
2330 if (is_dir_exist(dir_name))
2332 if (file_no > 0 && g_hash_table_lookup(wanted_files, GINT_TO_POINTER(file_no)) == NULL) {
2333 debug_print("removing unwanted file %d from %s\n", file_no, dir);
2334 if (is_dir_exist(dir_name)) {
2335 gchar *dot_file = g_strdup_printf(".%s", dir_name);
2336 if (is_file_exist(dot_file) && claws_unlink(dot_file) < 0) {
2337 FILE_OP_ERROR(dot_file, "unlink");
2342 if (claws_unlink(dir_name) < 0)
2343 FILE_OP_ERROR(dir_name, "unlink");
2348 g_hash_table_destroy(wanted_files);
2350 if (g_chdir(prev_dir) < 0) {
2351 FILE_OP_ERROR(prev_dir, "chdir");
2361 gint remove_all_numbered_files(const gchar *dir)
2363 return remove_numbered_files(dir, 0, UINT_MAX);
2366 gint remove_dir_recursive(const gchar *dir)
2370 const gchar *dir_name;
2373 if (g_stat(dir, &s) < 0) {
2374 FILE_OP_ERROR(dir, "stat");
2375 if (ENOENT == errno) return 0;
2379 if (!S_ISDIR(s.st_mode)) {
2380 if (claws_unlink(dir) < 0) {
2381 FILE_OP_ERROR(dir, "unlink");
2388 prev_dir = g_get_current_dir();
2389 /* g_print("prev_dir = %s\n", prev_dir); */
2391 if (!path_cmp(prev_dir, dir)) {
2393 if (g_chdir("..") < 0) {
2394 FILE_OP_ERROR(dir, "chdir");
2397 prev_dir = g_get_current_dir();
2400 if (g_chdir(dir) < 0) {
2401 FILE_OP_ERROR(dir, "chdir");
2406 if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2407 g_warning("failed to open directory: %s", dir);
2413 /* remove all files in the directory */
2414 while ((dir_name = g_dir_read_name(dp)) != NULL) {
2415 /* g_print("removing %s\n", dir_name); */
2417 if (is_dir_exist(dir_name)) {
2420 if ((ret = remove_dir_recursive(dir_name)) < 0) {
2421 g_warning("can't remove directory: %s", dir_name);
2425 if (claws_unlink(dir_name) < 0)
2426 FILE_OP_ERROR(dir_name, "unlink");
2432 if (g_chdir(prev_dir) < 0) {
2433 FILE_OP_ERROR(prev_dir, "chdir");
2440 if (g_rmdir(dir) < 0) {
2441 FILE_OP_ERROR(dir, "rmdir");
2448 gint rename_force(const gchar *oldpath, const gchar *newpath)
2451 if (!is_file_entry_exist(oldpath)) {
2455 if (is_file_exist(newpath)) {
2456 if (claws_unlink(newpath) < 0)
2457 FILE_OP_ERROR(newpath, "unlink");
2460 return g_rename(oldpath, newpath);
2464 * Append src file body to the tail of dest file.
2465 * Now keep_backup has no effects.
2467 gint append_file(const gchar *src, const gchar *dest, gboolean keep_backup)
2469 FILE *src_fp, *dest_fp;
2473 gboolean err = FALSE;
2475 if ((src_fp = g_fopen(src, "rb")) == NULL) {
2476 FILE_OP_ERROR(src, "g_fopen");
2480 if ((dest_fp = g_fopen(dest, "ab")) == NULL) {
2481 FILE_OP_ERROR(dest, "g_fopen");
2486 if (change_file_mode_rw(dest_fp, dest) < 0) {
2487 FILE_OP_ERROR(dest, "chmod");
2488 g_warning("can't change file mode: %s", dest);
2491 while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
2492 if (n_read < sizeof(buf) && ferror(src_fp))
2494 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2495 g_warning("writing to %s failed.", dest);
2503 if (ferror(src_fp)) {
2504 FILE_OP_ERROR(src, "fread");
2508 if (fclose(dest_fp) == EOF) {
2509 FILE_OP_ERROR(dest, "fclose");
2521 gint copy_file(const gchar *src, const gchar *dest, gboolean keep_backup)
2523 FILE *src_fp, *dest_fp;
2526 gchar *dest_bak = NULL;
2527 gboolean err = FALSE;
2529 if ((src_fp = g_fopen(src, "rb")) == NULL) {
2530 FILE_OP_ERROR(src, "g_fopen");
2533 if (is_file_exist(dest)) {
2534 dest_bak = g_strconcat(dest, ".bak", NULL);
2535 if (rename_force(dest, dest_bak) < 0) {
2536 FILE_OP_ERROR(dest, "rename");
2543 if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2544 FILE_OP_ERROR(dest, "g_fopen");
2547 if (rename_force(dest_bak, dest) < 0)
2548 FILE_OP_ERROR(dest_bak, "rename");
2554 if (change_file_mode_rw(dest_fp, dest) < 0) {
2555 FILE_OP_ERROR(dest, "chmod");
2556 g_warning("can't change file mode: %s", dest);
2559 while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
2560 if (n_read < sizeof(buf) && ferror(src_fp))
2562 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2563 g_warning("writing to %s failed.", dest);
2568 if (rename_force(dest_bak, dest) < 0)
2569 FILE_OP_ERROR(dest_bak, "rename");
2576 if (ferror(src_fp)) {
2577 FILE_OP_ERROR(src, "fread");
2581 if (fclose(dest_fp) == EOF) {
2582 FILE_OP_ERROR(dest, "fclose");
2589 if (rename_force(dest_bak, dest) < 0)
2590 FILE_OP_ERROR(dest_bak, "rename");
2596 if (keep_backup == FALSE && dest_bak)
2597 claws_unlink(dest_bak);
2604 gint move_file(const gchar *src, const gchar *dest, gboolean overwrite)
2606 if (overwrite == FALSE && is_file_exist(dest)) {
2607 g_warning("move_file(): file %s already exists.", dest);
2611 if (rename_force(src, dest) == 0) return 0;
2613 if (EXDEV != errno) {
2614 FILE_OP_ERROR(src, "rename");
2618 if (copy_file(src, dest, FALSE) < 0) return -1;
2625 gint copy_file_part_to_fp(FILE *fp, off_t offset, size_t length, FILE *dest_fp)
2628 gint bytes_left, to_read;
2631 if (fseek(fp, offset, SEEK_SET) < 0) {
2636 bytes_left = length;
2637 to_read = MIN(bytes_left, sizeof(buf));
2639 while ((n_read = fread(buf, sizeof(gchar), to_read, fp)) > 0) {
2640 if (n_read < to_read && ferror(fp))
2642 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2645 bytes_left -= n_read;
2646 if (bytes_left == 0)
2648 to_read = MIN(bytes_left, sizeof(buf));
2659 gint copy_file_part(FILE *fp, off_t offset, size_t length, const gchar *dest)
2662 gboolean err = FALSE;
2664 if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2665 FILE_OP_ERROR(dest, "g_fopen");
2669 if (change_file_mode_rw(dest_fp, dest) < 0) {
2670 FILE_OP_ERROR(dest, "chmod");
2671 g_warning("can't change file mode: %s", dest);
2674 if (copy_file_part_to_fp(fp, offset, length, dest_fp) < 0)
2677 if (!err && fclose(dest_fp) == EOF) {
2678 FILE_OP_ERROR(dest, "fclose");
2683 g_warning("writing to %s failed.", dest);
2691 /* convert line endings into CRLF. If the last line doesn't end with
2692 * linebreak, add it.
2694 gchar *canonicalize_str(const gchar *str)
2700 for (p = str; *p != '\0'; ++p) {
2707 if (p == str || *(p - 1) != '\n')
2710 out = outp = g_malloc(new_len + 1);
2711 for (p = str; *p != '\0'; ++p) {
2718 if (p == str || *(p - 1) != '\n') {
2727 gint canonicalize_file(const gchar *src, const gchar *dest)
2729 FILE *src_fp, *dest_fp;
2730 gchar buf[BUFFSIZE];
2732 gboolean err = FALSE;
2733 gboolean last_linebreak = FALSE;
2735 if (src == NULL || dest == NULL)
2738 if ((src_fp = g_fopen(src, "rb")) == NULL) {
2739 FILE_OP_ERROR(src, "g_fopen");
2743 if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2744 FILE_OP_ERROR(dest, "g_fopen");
2749 if (change_file_mode_rw(dest_fp, dest) < 0) {
2750 FILE_OP_ERROR(dest, "chmod");
2751 g_warning("can't change file mode: %s", dest);
2754 while (fgets(buf, sizeof(buf), src_fp) != NULL) {
2758 if (len == 0) break;
2759 last_linebreak = FALSE;
2761 if (buf[len - 1] != '\n') {
2762 last_linebreak = TRUE;
2763 r = fputs(buf, dest_fp);
2764 } else if (len > 1 && buf[len - 1] == '\n' && buf[len - 2] == '\r') {
2765 r = fputs(buf, dest_fp);
2768 r = fwrite(buf, 1, len - 1, dest_fp);
2773 r = fputs("\r\n", dest_fp);
2777 g_warning("writing to %s failed.", dest);
2785 if (last_linebreak == TRUE) {
2786 if (fputs("\r\n", dest_fp) == EOF)
2790 if (ferror(src_fp)) {
2791 FILE_OP_ERROR(src, "fgets");
2795 if (fclose(dest_fp) == EOF) {
2796 FILE_OP_ERROR(dest, "fclose");
2808 gint canonicalize_file_replace(const gchar *file)
2812 tmp_file = get_tmp_file();
2814 if (canonicalize_file(file, tmp_file) < 0) {
2819 if (move_file(tmp_file, file, TRUE) < 0) {
2820 g_warning("can't replace file: %s", file);
2821 claws_unlink(tmp_file);
2830 gchar *normalize_newlines(const gchar *str)
2835 out = outp = g_malloc(strlen(str) + 1);
2836 for (p = str; *p != '\0'; ++p) {
2838 if (*(p + 1) != '\n')
2849 gchar *get_outgoing_rfc2822_str(FILE *fp)
2851 gchar buf[BUFFSIZE];
2855 str = g_string_new(NULL);
2857 /* output header part */
2858 while (fgets(buf, sizeof(buf), fp) != NULL) {
2860 if (!g_ascii_strncasecmp(buf, "Bcc:", 4)) {
2867 else if (next != ' ' && next != '\t') {
2871 if (fgets(buf, sizeof(buf), fp) == NULL)
2875 g_string_append(str, buf);
2876 g_string_append(str, "\r\n");
2882 /* output body part */
2883 while (fgets(buf, sizeof(buf), fp) != NULL) {
2886 g_string_append_c(str, '.');
2887 g_string_append(str, buf);
2888 g_string_append(str, "\r\n");
2892 g_string_free(str, FALSE);
2898 * Create a new boundary in a way that it is very unlikely that this
2899 * will occur in the following text. It would be easy to ensure
2900 * uniqueness if everything is either quoted-printable or base64
2901 * encoded (note that conversion is allowed), but because MIME bodies
2902 * may be nested, it may happen that the same boundary has already
2905 * boundary := 0*69<bchars> bcharsnospace
2906 * bchars := bcharsnospace / " "
2907 * bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" /
2908 * "+" / "_" / "," / "-" / "." /
2909 * "/" / ":" / "=" / "?"
2911 * some special characters removed because of buggy MTAs
2914 gchar *generate_mime_boundary(const gchar *prefix)
2916 static gchar tbl[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
2917 "abcdefghijklmnopqrstuvwxyz"
2922 for (i = 0; i < sizeof(buf_uniq) - 1; i++)
2923 buf_uniq[i] = tbl[g_random_int_range(0, sizeof(tbl) - 1)];
2926 return g_strdup_printf("%s_/%s", prefix ? prefix : "MP",
2930 gint change_file_mode_rw(FILE *fp, const gchar *file)
2933 return fchmod(fileno(fp), S_IRUSR|S_IWUSR);
2935 return g_chmod(file, S_IRUSR|S_IWUSR);
2939 FILE *my_tmpfile(void)
2941 const gchar suffix[] = ".XXXXXX";
2942 const gchar *tmpdir;
2944 const gchar *progname;
2953 tmpdir = get_tmp_dir();
2954 tmplen = strlen(tmpdir);
2955 progname = g_get_prgname();
2956 if (progname == NULL)
2957 progname = "claws-mail";
2958 proglen = strlen(progname);
2959 Xalloca(fname, tmplen + 1 + proglen + sizeof(suffix),
2962 memcpy(fname, tmpdir, tmplen);
2963 fname[tmplen] = G_DIR_SEPARATOR;
2964 memcpy(fname + tmplen + 1, progname, proglen);
2965 memcpy(fname + tmplen + 1 + proglen, suffix, sizeof(suffix));
2967 fd = g_mkstemp(fname);
2972 claws_unlink(fname);
2974 /* verify that we can write in the file after unlinking */
2975 if (write(fd, buf, 1) < 0) {
2982 fp = fdopen(fd, "w+b");
2993 FILE *get_tmpfile_in_dir(const gchar *dir, gchar **filename)
2996 *filename = g_strdup_printf("%s%cclaws.XXXXXX", dir, G_DIR_SEPARATOR);
2997 fd = g_mkstemp(*filename);
3000 return fdopen(fd, "w+");
3003 FILE *str_open_as_stream(const gchar *str)
3008 cm_return_val_if_fail(str != NULL, NULL);
3012 FILE_OP_ERROR("str_open_as_stream", "my_tmpfile");
3017 if (len == 0) return fp;
3019 if (fwrite(str, 1, len, fp) != len) {
3020 FILE_OP_ERROR("str_open_as_stream", "fwrite");
3029 gint str_write_to_file(const gchar *str, const gchar *file)
3034 cm_return_val_if_fail(str != NULL, -1);
3035 cm_return_val_if_fail(file != NULL, -1);
3037 if ((fp = g_fopen(file, "wb")) == NULL) {
3038 FILE_OP_ERROR(file, "g_fopen");
3048 if (fwrite(str, 1, len, fp) != len) {
3049 FILE_OP_ERROR(file, "fwrite");
3055 if (fclose(fp) == EOF) {
3056 FILE_OP_ERROR(file, "fclose");
3064 static gchar *file_read_stream_to_str_full(FILE *fp, gboolean recode)
3071 cm_return_val_if_fail(fp != NULL, NULL);
3073 array = g_byte_array_new();
3075 while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
3076 if (n_read < sizeof(buf) && ferror(fp))
3078 g_byte_array_append(array, buf, n_read);
3082 FILE_OP_ERROR("file stream", "fread");
3083 g_byte_array_free(array, TRUE);
3088 g_byte_array_append(array, buf, 1);
3089 str = (gchar *)array->data;
3090 g_byte_array_free(array, FALSE);
3092 if (recode && !g_utf8_validate(str, -1, NULL)) {
3093 const gchar *src_codeset, *dest_codeset;
3095 src_codeset = conv_get_locale_charset_str();
3096 dest_codeset = CS_UTF_8;
3097 tmp = conv_codeset_strdup(str, src_codeset, dest_codeset);
3105 static gchar *file_read_to_str_full(const gchar *file, gboolean recode)
3112 struct timeval timeout = {1, 0};
3117 cm_return_val_if_fail(file != NULL, NULL);
3119 if (g_stat(file, &s) != 0) {
3120 FILE_OP_ERROR(file, "stat");
3123 if (S_ISDIR(s.st_mode)) {
3124 g_warning("%s: is a directory", file);
3129 fp = g_fopen (file, "rb");
3131 FILE_OP_ERROR(file, "open");
3135 /* test whether the file is readable without blocking */
3136 fd = g_open(file, O_RDONLY | O_NONBLOCK, 0);
3138 FILE_OP_ERROR(file, "open");
3145 /* allow for one second */
3146 err = select(fd+1, &fds, NULL, NULL, &timeout);
3147 if (err <= 0 || !FD_ISSET(fd, &fds)) {
3149 FILE_OP_ERROR(file, "select");
3151 g_warning("%s: doesn't seem readable", file);
3157 /* Now clear O_NONBLOCK */
3158 if ((fflags = fcntl(fd, F_GETFL)) < 0) {
3159 FILE_OP_ERROR(file, "fcntl (F_GETFL)");
3163 if (fcntl(fd, F_SETFL, (fflags & ~O_NONBLOCK)) < 0) {
3164 FILE_OP_ERROR(file, "fcntl (F_SETFL)");
3169 /* get the FILE pointer */
3170 fp = fdopen(fd, "rb");
3173 FILE_OP_ERROR(file, "fdopen");
3174 close(fd); /* if fp isn't NULL, we'll use fclose instead! */
3179 str = file_read_stream_to_str_full(fp, recode);
3186 gchar *file_read_to_str(const gchar *file)
3188 return file_read_to_str_full(file, TRUE);
3190 gchar *file_read_stream_to_str(FILE *fp)
3192 return file_read_stream_to_str_full(fp, TRUE);
3195 gchar *file_read_to_str_no_recode(const gchar *file)
3197 return file_read_to_str_full(file, FALSE);
3199 gchar *file_read_stream_to_str_no_recode(FILE *fp)
3201 return file_read_stream_to_str_full(fp, FALSE);
3204 char *fgets_crlf(char *buf, int size, FILE *stream)
3206 gboolean is_cr = FALSE;
3207 gboolean last_was_cr = FALSE;
3212 while (--size > 0 && (c = getc(stream)) != EOF)
3215 is_cr = (c == '\r');
3225 last_was_cr = is_cr;
3227 if (c == EOF && cs == buf)
3235 static gint execute_async(gchar *const argv[])
3237 cm_return_val_if_fail(argv != NULL && argv[0] != NULL, -1);
3239 if (g_spawn_async(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH,
3240 NULL, NULL, NULL, FALSE) == FALSE) {
3241 g_warning("couldn't execute command: %s", argv[0]);
3248 static gint execute_sync(gchar *const argv[])
3252 cm_return_val_if_fail(argv != NULL && argv[0] != NULL, -1);
3255 if (g_spawn_sync(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH,
3256 NULL, NULL, NULL, NULL, &status, NULL) == FALSE) {
3257 g_warning("couldn't execute command: %s", argv[0]);
3261 if (WIFEXITED(status))
3262 return WEXITSTATUS(status);
3266 if (g_spawn_sync(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH|
3267 G_SPAWN_CHILD_INHERITS_STDIN|G_SPAWN_LEAVE_DESCRIPTORS_OPEN,
3268 NULL, NULL, NULL, NULL, &status, NULL) == FALSE) {
3269 g_warning("couldn't execute command: %s", argv[0]);
3277 gint execute_command_line(const gchar *cmdline, gboolean async)
3282 debug_print("execute_command_line(): executing: %s\n", cmdline?cmdline:"(null)");
3284 argv = strsplit_with_quote(cmdline, " ", 0);
3287 ret = execute_async(argv);
3289 ret = execute_sync(argv);
3296 gchar *get_command_output(const gchar *cmdline)
3298 gchar *child_stdout;
3301 cm_return_val_if_fail(cmdline != NULL, NULL);
3303 debug_print("get_command_output(): executing: %s\n", cmdline);
3305 if (g_spawn_command_line_sync(cmdline, &child_stdout, NULL, &status,
3307 g_warning("couldn't execute command: %s", cmdline);
3311 return child_stdout;
3314 static gint is_unchanged_uri_char(char c)
3325 static void encode_uri(gchar *encoded_uri, gint bufsize, const gchar *uri)
3331 for(i = 0; i < strlen(uri) ; i++) {
3332 if (is_unchanged_uri_char(uri[i])) {
3333 if (k + 2 >= bufsize)
3335 encoded_uri[k++] = uri[i];
3338 char * hexa = "0123456789ABCDEF";
3340 if (k + 4 >= bufsize)
3342 encoded_uri[k++] = '%';
3343 encoded_uri[k++] = hexa[uri[i] / 16];
3344 encoded_uri[k++] = hexa[uri[i] % 16];
3350 gint open_uri(const gchar *uri, const gchar *cmdline)
3354 gchar buf[BUFFSIZE];
3356 gchar encoded_uri[BUFFSIZE];
3357 cm_return_val_if_fail(uri != NULL, -1);
3359 /* an option to choose whether to use encode_uri or not ? */
3360 encode_uri(encoded_uri, BUFFSIZE, uri);
3363 (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
3364 !strchr(p + 2, '%'))
3365 g_snprintf(buf, sizeof(buf), cmdline, encoded_uri);
3368 g_warning("Open URI command-line is invalid "
3369 "(there must be only one '%%s'): %s",
3371 g_snprintf(buf, sizeof(buf), DEFAULT_BROWSER_CMD, encoded_uri);
3374 execute_command_line(buf, TRUE);
3376 ShellExecute(NULL, "open", uri, NULL, NULL, SW_SHOW);
3381 gint open_txt_editor(const gchar *filepath, const gchar *cmdline)
3383 gchar buf[BUFFSIZE];
3386 cm_return_val_if_fail(filepath != NULL, -1);
3389 (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
3390 !strchr(p + 2, '%'))
3391 g_snprintf(buf, sizeof(buf), cmdline, filepath);
3394 g_warning("Open Text Editor command-line is invalid "
3395 "(there must be only one '%%s'): %s",
3397 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, filepath);
3400 execute_command_line(buf, TRUE);
3405 time_t remote_tzoffset_sec(const gchar *zone)
3407 static gchar ustzstr[] = "PSTPDTMSTMDTCSTCDTESTEDT";
3413 time_t remoteoffset;
3415 strncpy(zone3, zone, 3);
3419 if (sscanf(zone, "%c%d", &c, &offset) == 2 &&
3420 (c == '+' || c == '-')) {
3421 remoteoffset = ((offset / 100) * 60 + (offset % 100)) * 60;
3423 remoteoffset = -remoteoffset;
3424 } else if (!strncmp(zone, "UT" , 2) ||
3425 !strncmp(zone, "GMT", 3)) {
3427 } else if (strlen(zone3) == 3) {
3428 for (p = ustzstr; *p != '\0'; p += 3) {
3429 if (!g_ascii_strncasecmp(p, zone3, 3)) {
3430 iustz = ((gint)(p - ustzstr) / 3 + 1) / 2 - 8;
3431 remoteoffset = iustz * 3600;
3437 } else if (strlen(zone3) == 1) {
3439 case 'Z': remoteoffset = 0; break;
3440 case 'A': remoteoffset = -1; break;
3441 case 'B': remoteoffset = -2; break;
3442 case 'C': remoteoffset = -3; break;
3443 case 'D': remoteoffset = -4; break;
3444 case 'E': remoteoffset = -5; break;
3445 case 'F': remoteoffset = -6; break;
3446 case 'G': remoteoffset = -7; break;
3447 case 'H': remoteoffset = -8; break;
3448 case 'I': remoteoffset = -9; break;
3449 case 'K': remoteoffset = -10; break; /* J is not used */
3450 case 'L': remoteoffset = -11; break;
3451 case 'M': remoteoffset = -12; break;
3452 case 'N': remoteoffset = 1; break;
3453 case 'O': remoteoffset = 2; break;
3454 case 'P': remoteoffset = 3; break;
3455 case 'Q': remoteoffset = 4; break;
3456 case 'R': remoteoffset = 5; break;
3457 case 'S': remoteoffset = 6; break;
3458 case 'T': remoteoffset = 7; break;
3459 case 'U': remoteoffset = 8; break;
3460 case 'V': remoteoffset = 9; break;
3461 case 'W': remoteoffset = 10; break;
3462 case 'X': remoteoffset = 11; break;
3463 case 'Y': remoteoffset = 12; break;
3464 default: remoteoffset = 0; break;
3466 remoteoffset = remoteoffset * 3600;
3470 return remoteoffset;
3473 time_t tzoffset_sec(time_t *now)
3477 struct tm buf1, buf2;
3479 if (now && *now < 0)
3482 gmt = *gmtime_r(now, &buf1);
3483 lt = localtime_r(now, &buf2);
3485 off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
3487 if (lt->tm_year < gmt.tm_year)
3489 else if (lt->tm_year > gmt.tm_year)
3491 else if (lt->tm_yday < gmt.tm_yday)
3493 else if (lt->tm_yday > gmt.tm_yday)
3496 if (off >= 24 * 60) /* should be impossible */
3497 off = 23 * 60 + 59; /* if not, insert silly value */
3498 if (off <= -24 * 60)
3499 off = -(23 * 60 + 59);
3504 /* calculate timezone offset */
3505 gchar *tzoffset(time_t *now)
3507 static gchar offset_string[6];
3511 struct tm buf1, buf2;
3513 if (now && *now < 0)
3516 gmt = *gmtime_r(now, &buf1);
3517 lt = localtime_r(now, &buf2);
3519 off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
3521 if (lt->tm_year < gmt.tm_year)
3523 else if (lt->tm_year > gmt.tm_year)
3525 else if (lt->tm_yday < gmt.tm_yday)
3527 else if (lt->tm_yday > gmt.tm_yday)
3535 if (off >= 24 * 60) /* should be impossible */
3536 off = 23 * 60 + 59; /* if not, insert silly value */
3538 sprintf(offset_string, "%c%02d%02d", sign, off / 60, off % 60);
3540 return offset_string;
3543 void get_rfc822_date(gchar *buf, gint len)
3547 gchar day[4], mon[4];
3548 gint dd, hh, mm, ss, yyyy;
3550 gchar buf2[BUFFSIZE];
3553 lt = localtime_r(&t, &buf1);
3555 sscanf(asctime_r(lt, buf2), "%3s %3s %d %d:%d:%d %d\n",
3556 day, mon, &dd, &hh, &mm, &ss, &yyyy);
3558 g_snprintf(buf, len, "%s, %d %s %d %02d:%02d:%02d %s",
3559 day, dd, mon, yyyy, hh, mm, ss, tzoffset(&t));
3562 void debug_set_mode(gboolean mode)
3567 gboolean debug_get_mode(void)
3572 void debug_print_real(const gchar *format, ...)
3575 gchar buf[BUFFSIZE];
3577 if (!debug_mode) return;
3579 va_start(args, format);
3580 g_vsnprintf(buf, sizeof(buf), format, args);
3587 const char * debug_srcname(const char *file)
3589 const char *s = strrchr (file, '/');
3594 void * subject_table_lookup(GHashTable *subject_table, gchar * subject)
3596 if (subject == NULL)
3599 subject += subject_get_prefix_length(subject);
3601 return g_hash_table_lookup(subject_table, subject);
3604 void subject_table_insert(GHashTable *subject_table, gchar * subject,
3607 if (subject == NULL || *subject == 0)
3609 subject += subject_get_prefix_length(subject);
3610 g_hash_table_insert(subject_table, subject, data);
3613 void subject_table_remove(GHashTable *subject_table, gchar * subject)
3615 if (subject == NULL)
3618 subject += subject_get_prefix_length(subject);
3619 g_hash_table_remove(subject_table, subject);
3623 static regex_t u_regex;
3624 static gboolean u_init_;
3627 void utils_free_regex(void)
3638 *\brief Check if a string is prefixed with known (combinations)
3639 * of prefixes. The function assumes that each prefix
3640 * is terminated by zero or exactly _one_ space.
3642 *\param str String to check for a prefixes
3644 *\return int Number of chars in the prefix that should be skipped
3645 * for a "clean" subject line. If no prefix was found, 0
3648 int subject_get_prefix_length(const gchar *subject)
3651 /*!< Array with allowable reply prefixes regexps. */
3652 static const gchar * const prefixes[] = {
3653 "Re\\:", /* "Re:" */
3654 "Re\\[[1-9][0-9]*\\]\\:", /* "Re[XXX]:" (non-conforming news mail clients) */
3655 "Antw\\:", /* "Antw:" (Dutch / German Outlook) */
3656 "Aw\\:", /* "Aw:" (German) */
3657 "Antwort\\:", /* "Antwort:" (German Lotus Notes) */
3658 "Res\\:", /* "Res:" (Spanish/Brazilian Outlook) */
3659 "Fw\\:", /* "Fw:" Forward */
3660 "Fwd\\:", /* "Fwd:" Forward */
3661 "Enc\\:", /* "Enc:" Forward (Brazilian Outlook) */
3662 "Odp\\:", /* "Odp:" Re (Polish Outlook) */
3663 "Rif\\:", /* "Rif:" (Italian Outlook) */
3664 "Sv\\:", /* "Sv" (Norwegian) */
3665 "Vs\\:", /* "Vs" (Norwegian) */
3666 "Ad\\:", /* "Ad" (Norwegian) */
3667 "\347\255\224\345\244\215\\:", /* "Re" (Chinese, UTF-8) */
3668 "R\303\251f\\. \\:", /* "R�f. :" (French Lotus Notes) */
3669 "Re \\:", /* "Re :" (French Yahoo Mail) */
3672 const int PREFIXES = sizeof prefixes / sizeof prefixes[0];
3676 if (!subject) return 0;
3677 if (!*subject) return 0;
3680 GString *s = g_string_new("");
3682 for (n = 0; n < PREFIXES; n++)
3683 /* Terminate each prefix regexpression by a
3684 * "\ ?" (zero or ONE space), and OR them */
3685 g_string_append_printf(s, "(%s\\ ?)%s",
3690 g_string_prepend(s, "(");
3691 g_string_append(s, ")+"); /* match at least once */
3692 g_string_prepend(s, "^\\ *"); /* from beginning of line */
3695 /* We now have something like "^\ *((PREFIX1\ ?)|(PREFIX2\ ?))+"
3696 * TODO: Should this be "^\ *(((PREFIX1)|(PREFIX2))\ ?)+" ??? */
3697 if (regcomp(&u_regex, s->str, REG_EXTENDED | REG_ICASE)) {
3698 debug_print("Error compiling regexp %s\n", s->str);
3699 g_string_free(s, TRUE);
3703 g_string_free(s, TRUE);
3707 if (!regexec(&u_regex, subject, 1, &pos, 0) && pos.rm_so != -1)
3712 /*!< Array with allowable reply prefixes regexps. */
3713 static const gchar * const prefixes[] = {
3715 "antw:", /* "Antw:" (Dutch / German Outlook) */
3716 "aw:", /* "Aw:" (German) */
3717 "antwort:", /* "Antwort:" (German Lotus Notes) */
3718 "res:", /* "Res:" (Spanish/Brazilian Outlook) */
3719 "fw:", /* "Fw:" Forward */
3720 "fwd:", /* "Fwd:" Forward */
3721 "enc:", /* "Enc:" Forward (Brazilian Outlook) */
3722 "odp:", /* "Odp:" Re (Polish Outlook) */
3723 "rif:", /* "Rif:" (Italian Outlook) */
3724 "sv:", /* "Sv" (Norwegian) */
3725 "vs:", /* "Vs" (Norwegian) */
3726 "ad:", /* "Ad" (Norwegian) */
3727 "R\303\251f. :", /* "R�f. :" (French Lotus Notes) */
3728 "Re :", /* "Re :" (French Yahoo Mail) */
3731 const int PREFIXES = sizeof prefixes / sizeof prefixes[0];
3734 if (!subject) return 0;
3735 if (!*subject) return 0;
3737 for (n = 0; n < PREFIXES; n++) {
3738 int len = strlen(prefixes[n]);
3739 if (!strncasecmp(subject, prefixes[n], len)) {
3740 if (subject[len] == ' ')
3749 static guint g_stricase_hash(gconstpointer gptr)
3751 guint hash_result = 0;
3754 for (str = gptr; str && *str; str++) {
3755 hash_result += toupper(*str);
3761 static gint g_stricase_equal(gconstpointer gptr1, gconstpointer gptr2)
3763 const char *str1 = gptr1;
3764 const char *str2 = gptr2;
3766 return !strcasecmp(str1, str2);
3769 gint g_int_compare(gconstpointer a, gconstpointer b)
3771 return GPOINTER_TO_INT(a) - GPOINTER_TO_INT(b);
3774 gchar *generate_msgid(gchar *buf, gint len, gchar *user_addr)
3782 lt = localtime_r(&t, &buft);
3784 if (user_addr != NULL)
3785 addr = g_strdup_printf(".%s", user_addr);
3786 else if (strlen(buf) != 0)
3787 addr = g_strdup_printf("@%s", buf);
3789 addr = g_strdup_printf("@%s", get_domain_name());
3791 /* Replace all @ but the last one in addr, with underscores.
3792 * RFC 2822 States that msg-id syntax only allows one @.
3794 while (strchr(addr, '@') != NULL && strchr(addr, '@') != strrchr(addr, '@'))
3795 *(strchr(addr, '@')) = '_';
3797 g_snprintf(buf, len, "%04d%02d%02d%02d%02d%02d.%08x%s",
3798 lt->tm_year + 1900, lt->tm_mon + 1,
3799 lt->tm_mday, lt->tm_hour,
3800 lt->tm_min, lt->tm_sec,
3801 (guint) rand(), addr);
3808 quote_cmd_argument()
3810 return a quoted string safely usable in argument of a command.
3812 code is extracted and adapted from etPan! project -- DINH V. Ho�.
3815 gint quote_cmd_argument(gchar * result, guint size,
3825 for(p = path ; * p != '\0' ; p ++) {
3827 if (isalnum((guchar)*p) || (* p == '/')) {
3828 if (remaining > 0) {
3834 result[size - 1] = '\0';
3839 if (remaining >= 2) {
3847 result[size - 1] = '\0';
3852 if (remaining > 0) {
3856 result[size - 1] = '\0';
3870 static void g_node_map_recursive(GNode *node, gpointer data)
3872 GNodeMapData *mapdata = (GNodeMapData *) data;
3874 GNodeMapData newmapdata;
3877 newdata = mapdata->func(node->data, mapdata->data);
3878 if (newdata != NULL) {
3879 newnode = g_node_new(newdata);
3880 g_node_append(mapdata->parent, newnode);
3882 newmapdata.parent = newnode;
3883 newmapdata.func = mapdata->func;
3884 newmapdata.data = mapdata->data;
3886 g_node_children_foreach(node, G_TRAVERSE_ALL, g_node_map_recursive, &newmapdata);
3890 GNode *g_node_map(GNode *node, GNodeMapFunc func, gpointer data)
3893 GNodeMapData mapdata;
3895 cm_return_val_if_fail(node != NULL, NULL);
3896 cm_return_val_if_fail(func != NULL, NULL);
3898 root = g_node_new(func(node->data, data));
3900 mapdata.parent = root;
3901 mapdata.func = func;
3902 mapdata.data = data;
3904 g_node_children_foreach(node, G_TRAVERSE_ALL, g_node_map_recursive, &mapdata);
3909 #define HEX_TO_INT(val, hex) \
3913 if ('0' <= c && c <= '9') { \
3915 } else if ('a' <= c && c <= 'f') { \
3916 val = c - 'a' + 10; \
3917 } else if ('A' <= c && c <= 'F') { \
3918 val = c - 'A' + 10; \
3924 gboolean get_hex_value(guchar *out, gchar c1, gchar c2)
3931 if (hi == -1 || lo == -1)
3934 *out = (hi << 4) + lo;
3938 #define INT_TO_HEX(hex, val) \
3941 hex = '0' + (val); \
3943 hex = 'A' + (val) - 10; \
3946 void get_hex_str(gchar *out, guchar ch)
3950 INT_TO_HEX(hex, ch >> 4);
3952 INT_TO_HEX(hex, ch & 0x0f);
3958 #define G_PRINT_REF 1 == 1 ? (void) 0 : (void)
3960 #define G_PRINT_REF g_print
3964 *\brief Register ref counted pointer. It is based on GBoxed, so should
3965 * work with anything that uses the GType system. The semantics
3966 * are similar to a C++ auto pointer, with the exception that
3967 * C doesn't have automatic closure (calling destructors) when
3968 * exiting a block scope.
3969 * Use the \ref G_TYPE_AUTO_POINTER macro instead of calling this
3970 * function directly.
3972 *\return GType A GType type.
3974 GType g_auto_pointer_register(void)
3976 static GType auto_pointer_type;
3977 if (!auto_pointer_type)
3979 g_boxed_type_register_static
3980 ("G_TYPE_AUTO_POINTER",
3981 (GBoxedCopyFunc) g_auto_pointer_copy,
3982 (GBoxedFreeFunc) g_auto_pointer_free);
3983 return auto_pointer_type;
3987 *\brief Structure with g_new() allocated pointer guarded by the
3990 typedef struct AutoPointerRef {
3991 void (*free) (gpointer);
3997 *\brief The auto pointer opaque structure that references the
3998 * pointer guard block.
4000 typedef struct AutoPointer {
4001 AutoPointerRef *ref;
4002 gpointer ptr; /*!< access to protected pointer */
4006 *\brief Creates an auto pointer for a g_new()ed pointer. Example:
4010 * ... tell gtk_list_store it should use a G_TYPE_AUTO_POINTER
4011 * ... when assigning, copying and freeing storage elements
4013 * gtk_list_store_new(N_S_COLUMNS,
4014 * G_TYPE_AUTO_POINTER,
4018 * Template *precious_data = g_new0(Template, 1);
4019 * g_pointer protect = g_auto_pointer_new(precious_data);
4021 * gtk_list_store_set(container, &iter,
4025 * ... the gtk_list_store has copied the pointer and
4026 * ... incremented its reference count, we should free
4027 * ... the auto pointer (in C++ a destructor would do
4028 * ... this for us when leaving block scope)
4030 * g_auto_pointer_free(protect);
4032 * ... gtk_list_store_set() now manages the data. When
4033 * ... *explicitly* requesting a pointer from the list
4034 * ... store, don't forget you get a copy that should be
4035 * ... freed with g_auto_pointer_free() eventually.
4039 *\param pointer Pointer to be guarded.
4041 *\return GAuto * Pointer that should be used in containers with
4044 GAuto *g_auto_pointer_new(gpointer p)
4046 AutoPointerRef *ref;
4052 ref = g_new0(AutoPointerRef, 1);
4053 ptr = g_new0(AutoPointer, 1);
4063 G_PRINT_REF ("XXXX ALLOC(%lx)\n", p);
4069 *\brief Allocate an autopointer using the passed \a free function to
4070 * free the guarded pointer
4072 GAuto *g_auto_pointer_new_with_free(gpointer p, GFreeFunc free_)
4079 aptr = g_auto_pointer_new(p);
4080 aptr->ref->free = free_;
4084 gpointer g_auto_pointer_get_ptr(GAuto *auto_ptr)
4086 if (auto_ptr == NULL)
4088 return ((AutoPointer *) auto_ptr)->ptr;
4092 *\brief Copies an auto pointer by. It's mostly not necessary
4093 * to call this function directly, unless you copy/assign
4094 * the guarded pointer.
4096 *\param auto_ptr Auto pointer returned by previous call to
4097 * g_auto_pointer_new_XXX()
4099 *\return gpointer An auto pointer
4101 GAuto *g_auto_pointer_copy(GAuto *auto_ptr)
4104 AutoPointerRef *ref;
4107 if (auto_ptr == NULL)
4112 newp = g_new0(AutoPointer, 1);
4115 newp->ptr = ref->pointer;
4119 G_PRINT_REF ("XXXX COPY(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4125 *\brief Free an auto pointer
4127 void g_auto_pointer_free(GAuto *auto_ptr)
4130 AutoPointerRef *ref;
4132 if (auto_ptr == NULL)
4138 if (--(ref->cnt) == 0) {
4140 G_PRINT_REF ("XXXX FREE(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4142 ref->free(ref->pointer);
4147 G_PRINT_REF ("XXXX DEREF(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4152 void replace_returns(gchar *str)
4157 while (strstr(str, "\n")) {
4158 *strstr(str, "\n") = ' ';
4160 while (strstr(str, "\r")) {
4161 *strstr(str, "\r") = ' ';
4165 /* get_uri_part() - retrieves a URI starting from scanpos.
4166 Returns TRUE if succesful */
4167 gboolean get_uri_part(const gchar *start, const gchar *scanpos,
4168 const gchar **bp, const gchar **ep, gboolean hdr)
4171 gint parenthese_cnt = 0;
4173 cm_return_val_if_fail(start != NULL, FALSE);
4174 cm_return_val_if_fail(scanpos != NULL, FALSE);
4175 cm_return_val_if_fail(bp != NULL, FALSE);
4176 cm_return_val_if_fail(ep != NULL, FALSE);
4180 /* find end point of URI */
4181 for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
4182 if (!g_ascii_isgraph(*(const guchar *)ep_) ||
4183 !IS_ASCII(*(const guchar *)ep_) ||
4184 strchr("[]{}<>\"", *ep_)) {
4186 } else if (strchr("(", *ep_)) {
4188 } else if (strchr(")", *ep_)) {
4189 if (parenthese_cnt > 0)
4196 /* no punctuation at end of string */
4198 /* FIXME: this stripping of trailing punctuations may bite with other URIs.
4199 * should pass some URI type to this function and decide on that whether
4200 * to perform punctuation stripping */
4202 #define IS_REAL_PUNCT(ch) (g_ascii_ispunct(ch) && !strchr("/?=-_)", ch))
4204 for (; ep_ - 1 > scanpos + 1 &&
4205 IS_REAL_PUNCT(*(ep_ - 1));
4209 #undef IS_REAL_PUNCT
4216 gchar *make_uri_string(const gchar *bp, const gchar *ep)
4218 while (bp && *bp && g_ascii_isspace(*bp))
4220 return g_strndup(bp, ep - bp);
4223 /* valid mail address characters */
4224 #define IS_RFC822_CHAR(ch) \
4228 !g_ascii_isspace(ch) && \
4229 !strchr("(),;<>\"", (ch)))
4231 /* alphabet and number within 7bit ASCII */
4232 #define IS_ASCII_ALNUM(ch) (IS_ASCII(ch) && g_ascii_isalnum(ch))
4233 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
4235 static GHashTable *create_domain_tab(void)
4237 static const gchar *toplvl_domains [] = {
4239 "arpa", "coop", "info", "name", "biz", "com", "edu", "gov",
4240 "int", "mil", "net", "org", "ac", "ad", "ae", "af", "ag",
4241 "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au",
4242 "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi",
4243 "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
4244 "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl",
4245 "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz", "de",
4246 "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er",
4247 "es", "et", "eu", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd",
4248 "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
4249 "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
4250 "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir",
4251 "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
4252 "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc",
4253 "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
4254 "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq",
4255 "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na",
4256 "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu",
4257 "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm",
4258 "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru",
4259 "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj",
4260 "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
4261 "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to",
4262 "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um",
4263 "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu",
4264 "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw"
4267 GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
4269 cm_return_val_if_fail(htab, NULL);
4270 for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++)
4271 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
4275 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
4277 const gint MAX_LVL_DOM_NAME_LEN = 6;
4278 gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
4279 const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
4282 if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
4285 for (p = buf; p < m && first < last; *p++ = *first++)
4289 return g_hash_table_lookup(tab, buf) != NULL;
4292 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
4293 gboolean get_email_part(const gchar *start, const gchar *scanpos,
4294 const gchar **bp, const gchar **ep, gboolean hdr)
4296 /* more complex than the uri part because we need to scan back and forward starting from
4297 * the scan position. */
4298 gboolean result = FALSE;
4299 const gchar *bp_ = NULL;
4300 const gchar *ep_ = NULL;
4301 static GHashTable *dom_tab;
4302 const gchar *last_dot = NULL;
4303 const gchar *prelast_dot = NULL;
4304 const gchar *last_tld_char = NULL;
4306 /* the informative part of the email address (describing the name
4307 * of the email address owner) may contain quoted parts. the
4308 * closure stack stores the last encountered quotes. */
4309 gchar closure_stack[128];
4310 gchar *ptr = closure_stack;
4312 cm_return_val_if_fail(start != NULL, FALSE);
4313 cm_return_val_if_fail(scanpos != NULL, FALSE);
4314 cm_return_val_if_fail(bp != NULL, FALSE);
4315 cm_return_val_if_fail(ep != NULL, FALSE);
4318 const gchar *start_quote = NULL;
4319 const gchar *end_quote = NULL;
4321 /* go to the real start */
4322 if (start[0] == ',')
4324 if (start[0] == ';')
4326 while (start[0] == '\n' || start[0] == '\r')
4328 while (start[0] == ' ' || start[0] == '\t')
4333 /* check if there are quotes (to skip , in them) */
4334 if (*start == '"') {
4335 start_quote = start;
4337 end_quote = strstr(start, "\"");
4343 /* skip anything between quotes */
4344 if (start_quote && end_quote) {
4349 /* find end (either , or ; or end of line) */
4350 if (strstr(start, ",") && strstr(start, ";"))
4351 *ep = strstr(start,",") < strstr(start, ";")
4352 ? strstr(start, ",") : strstr(start, ";");
4353 else if (strstr(start, ","))
4354 *ep = strstr(start, ",");
4355 else if (strstr(start, ";"))
4356 *ep = strstr(start, ";");
4358 *ep = start+strlen(start);
4360 /* go back to real start */
4361 if (start_quote && end_quote) {
4362 start = start_quote;
4365 /* check there's still an @ in that, or search
4366 * further if possible */
4367 if (strstr(start, "@") && strstr(start, "@") < *ep)
4369 else if (*ep < start+strlen(start)) {
4372 } else if (start_quote && strstr(start, "\"") && strstr(start, "\"") < *ep) {
4380 dom_tab = create_domain_tab();
4381 cm_return_val_if_fail(dom_tab, FALSE);
4383 /* scan start of address */
4384 for (bp_ = scanpos - 1;
4385 bp_ >= start && IS_RFC822_CHAR(*(const guchar *)bp_); bp_--)
4388 /* TODO: should start with an alnum? */
4390 for (; bp_ < scanpos && !IS_ASCII_ALNUM(*(const guchar *)bp_); bp_++)
4393 if (bp_ != scanpos) {
4394 /* scan end of address */
4395 for (ep_ = scanpos + 1;
4396 *ep_ && IS_RFC822_CHAR(*(const guchar *)ep_); ep_++)
4398 prelast_dot = last_dot;
4400 if (*(last_dot + 1) == '.') {
4401 if (prelast_dot == NULL)
4403 last_dot = prelast_dot;
4408 /* TODO: really should terminate with an alnum? */
4409 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*(const guchar *)ep_);
4414 if (last_dot == NULL)
4416 if (last_dot >= ep_)
4417 last_dot = prelast_dot;
4418 if (last_dot == NULL || (scanpos + 1 >= last_dot))
4422 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
4423 if (*last_tld_char == '?')
4426 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
4433 if (!result) return FALSE;
4435 if (*ep_ && bp_ != start && *(bp_ - 1) == '"' && *(ep_) == '"'
4436 && *(ep_ + 1) == ' ' && *(ep_ + 2) == '<'
4437 && IS_RFC822_CHAR(*(ep_ + 3))) {
4438 /* this informative part with an @ in it is
4439 * followed by the email address */
4442 /* go to matching '>' (or next non-rfc822 char, like \n) */
4443 for (; *ep_ != '>' && *ep != '\0' && IS_RFC822_CHAR(*ep_); ep_++)
4446 /* include the bracket */
4447 if (*ep_ == '>') ep_++;
4449 /* include the leading quote */
4457 /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
4458 if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
4461 /* see if this is <bracketed>; in this case we also scan for the informative part. */
4462 if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
4465 #define FULL_STACK() ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
4466 #define IN_STACK() (ptr > closure_stack)
4467 /* has underrun check */
4468 #define POP_STACK() if(IN_STACK()) --ptr
4469 /* has overrun check */
4470 #define PUSH_STACK(c) if(!FULL_STACK()) *ptr++ = (c); else return TRUE
4471 /* has underrun check */
4472 #define PEEK_STACK() (IN_STACK() ? *(ptr - 1) : 0)
4476 /* scan for the informative part. */
4477 for (bp_ -= 2; bp_ >= start; bp_--) {
4478 /* if closure on the stack keep scanning */
4479 if (PEEK_STACK() == *bp_) {
4483 if (!IN_STACK() && (*bp_ == '\'' || *bp_ == '"')) {
4488 /* if nothing in the closure stack, do the special conditions
4489 * the following if..else expression simply checks whether
4490 * a token is acceptable. if not acceptable, the clause
4491 * should terminate the loop with a 'break' */
4492 if (!PEEK_STACK()) {
4494 && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
4495 && (((bp_ + 1) < ep_) && isalnum(*(bp_ + 1)))) {
4496 /* hyphens are allowed, but only in
4498 } else if (strchr(" \"'", *bp_)) {
4499 /* but anything not being a punctiation
4502 break; /* anything else is rejected */
4509 /* scan forward (should start with an alnum) */
4510 for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
4526 #undef IS_ASCII_ALNUM
4527 #undef IS_RFC822_CHAR
4529 gchar *make_email_string(const gchar *bp, const gchar *ep)
4531 /* returns a mailto: URI; mailto: is also used to detect the
4532 * uri type later on in the button_pressed signal handler */
4536 tmp = g_strndup(bp, ep - bp);
4537 result = g_strconcat("mailto:", tmp, NULL);
4543 gchar *make_http_string(const gchar *bp, const gchar *ep)
4545 /* returns an http: URI; */
4549 while (bp && *bp && g_ascii_isspace(*bp))
4551 tmp = g_strndup(bp, ep - bp);
4552 result = g_strconcat("http://", tmp, NULL);
4558 static gchar *mailcap_get_command_in_file(const gchar *path, const gchar *type, const gchar *file_to_open)
4560 FILE *fp = g_fopen(path, "rb");
4561 gchar buf[BUFFSIZE];
4562 gchar *result = NULL;
4565 while (fgets(buf, sizeof (buf), fp) != NULL) {
4566 gchar **parts = g_strsplit(buf, ";", 3);
4567 gchar *trimmed = parts[0];
4568 while (trimmed[0] == ' ' || trimmed[0] == '\t')
4570 while (trimmed[strlen(trimmed)-1] == ' ' || trimmed[strlen(trimmed)-1] == '\t')
4571 trimmed[strlen(trimmed)-1] = '\0';
4573 if (!strcmp(trimmed, type)) {
4574 gboolean needsterminal = FALSE;
4575 if (parts[2] && strstr(parts[2], "needsterminal")) {
4576 needsterminal = TRUE;
4578 if (parts[2] && strstr(parts[2], "test=")) {
4579 gchar *orig_testcmd = g_strdup(strstr(parts[2], "test=")+5);
4580 gchar *testcmd = orig_testcmd;
4581 if (strstr(testcmd,";"))
4582 *(strstr(testcmd,";")) = '\0';
4583 while (testcmd[0] == ' ' || testcmd[0] == '\t')
4585 while (testcmd[strlen(testcmd)-1] == '\n')
4586 testcmd[strlen(testcmd)-1] = '\0';
4587 while (testcmd[strlen(testcmd)-1] == '\r')
4588 testcmd[strlen(testcmd)-1] = '\0';
4589 while (testcmd[strlen(testcmd)-1] == ' ' || testcmd[strlen(testcmd)-1] == '\t')
4590 testcmd[strlen(testcmd)-1] = '\0';
4592 if (strstr(testcmd, "%s")) {
4593 gchar *tmp = g_strdup_printf(testcmd, file_to_open);
4594 gint res = system(tmp);
4596 g_free(orig_testcmd);
4603 gint res = system(testcmd);
4604 g_free(orig_testcmd);
4614 while (trimmed[0] == ' ' || trimmed[0] == '\t')
4616 while (trimmed[strlen(trimmed)-1] == '\n')
4617 trimmed[strlen(trimmed)-1] = '\0';
4618 while (trimmed[strlen(trimmed)-1] == '\r')
4619 trimmed[strlen(trimmed)-1] = '\0';
4620 while (trimmed[strlen(trimmed)-1] == ' ' || trimmed[strlen(trimmed)-1] == '\t')
4621 trimmed[strlen(trimmed)-1] = '\0';
4622 result = g_strdup(trimmed);
4625 /* if there are no single quotes around %s, add them.
4626 * '.*%s.*' is ok, as in display 'png:%s'
4628 if (strstr(result, "%s")
4629 && !(strstr(result, "'") < strstr(result,"%s") &&
4630 strstr(strstr(result,"%s"), "'"))) {
4631 gchar *start = g_strdup(result);
4632 gchar *end = g_strdup(strstr(result, "%s")+2);
4634 *strstr(start, "%s") = '\0';
4635 tmp = g_strconcat(start,"'%s'",end, NULL);
4641 if (needsterminal) {
4642 gchar *tmp = g_strdup_printf("xterm -e %s", result);
4653 gchar *mailcap_get_command_for_type(const gchar *type, const gchar *file_to_open)
4655 gchar *result = NULL;
4659 path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap", NULL);
4660 result = mailcap_get_command_in_file(path, type, file_to_open);
4664 result = mailcap_get_command_in_file("/etc/mailcap", type, file_to_open);
4668 void mailcap_update_default(const gchar *type, const gchar *command)
4670 gchar *path = NULL, *outpath = NULL;
4671 path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap", NULL);
4672 outpath = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap.new", NULL);
4673 FILE *fp = g_fopen(path, "rb");
4675 gchar buf[BUFFSIZE];
4676 gboolean err = FALSE;
4679 fp = g_fopen(path, "a");
4681 g_warning("failed to create file %s", path);
4686 fp = g_freopen(path, "rb", fp);
4688 g_warning("failed to reopen file %s", path);
4695 outfp = g_fopen(outpath, "wb");
4697 g_warning("failed to create file %s", outpath);
4703 while (fp && fgets(buf, sizeof (buf), fp) != NULL) {
4704 gchar **parts = g_strsplit(buf, ";", 3);
4705 gchar *trimmed = parts[0];
4706 while (trimmed[0] == ' ')
4708 while (trimmed[strlen(trimmed)-1] == ' ')
4709 trimmed[strlen(trimmed)-1] = '\0';
4711 if (!strcmp(trimmed, type)) {
4716 if(fputs(buf, outfp) == EOF) {
4723 if (fprintf(outfp, "%s; %s\n", type, command) < 0)
4729 if (fclose(outfp) == EOF)
4733 g_rename(outpath, path);
4739 gint copy_dir(const gchar *src, const gchar *dst)
4744 if ((dir = g_dir_open(src, 0, NULL)) == NULL) {
4745 g_warning("failed to open directory: %s", src);
4749 if (make_dir(dst) < 0)
4752 while ((name = g_dir_read_name(dir)) != NULL) {
4753 gchar *old_file, *new_file;
4754 old_file = g_strconcat(src, G_DIR_SEPARATOR_S, name, NULL);
4755 new_file = g_strconcat(dst, G_DIR_SEPARATOR_S, name, NULL);
4756 debug_print("copying: %s -> %s\n", old_file, new_file);
4757 if (g_file_test(old_file, G_FILE_TEST_IS_REGULAR)) {
4758 gint r = copy_file(old_file, new_file, TRUE);
4765 /* Windows has no symlinks. Or well, Vista seems to
4766 have something like this but the semantics might be
4767 different. Thus we don't use it under Windows. */
4768 else if (g_file_test(old_file, G_FILE_TEST_IS_SYMLINK)) {
4769 GError *error = NULL;
4771 gchar *target = g_file_read_link(old_file, &error);
4773 r = symlink(target, new_file);
4780 #endif /*G_OS_WIN32*/
4781 else if (g_file_test(old_file, G_FILE_TEST_IS_DIR)) {
4782 gint r = copy_dir(old_file, new_file);
4793 /* crude test to see if a file is an email. */
4794 gboolean file_is_email (const gchar *filename)
4800 if (filename == NULL)
4802 if ((fp = g_fopen(filename, "rb")) == NULL)
4804 while (i < 60 && score < 3
4805 && fgets(buffer, sizeof (buffer), fp) > 0) {
4806 if (!strncmp(buffer, "From:", strlen("From:")))
4808 else if (!strncmp(buffer, "Date:", strlen("Date:")))
4810 else if (!strncmp(buffer, "Message-ID:", strlen("Message-ID:")))
4812 else if (!strncmp(buffer, "Subject:", strlen("Subject:")))
4817 return (score >= 3);
4820 gboolean sc_g_list_bigger(GList *list, gint max)
4824 while (cur && i <= max+1) {
4831 gboolean sc_g_slist_bigger(GSList *list, gint max)
4835 while (cur && i <= max+1) {
4842 const gchar *daynames[] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL};
4843 const gchar *monthnames[] = {NULL, NULL, NULL, NULL, NULL, NULL,
4844 NULL, NULL, NULL, NULL, NULL, NULL};
4845 const gchar *s_daynames[] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL};
4846 const gchar *s_monthnames[] = {NULL, NULL, NULL, NULL, NULL, NULL,
4847 NULL, NULL, NULL, NULL, NULL, NULL};
4849 gint daynames_len[] = {0,0,0,0,0,0,0};
4850 gint monthnames_len[] = {0,0,0,0,0,0,
4852 gint s_daynames_len[] = {0,0,0,0,0,0,0};
4853 gint s_monthnames_len[] = {0,0,0,0,0,0,
4855 const gchar *s_am_up = NULL;
4856 const gchar *s_pm_up = NULL;
4857 const gchar *s_am_low = NULL;
4858 const gchar *s_pm_low = NULL;
4860 gint s_am_up_len = 0;
4861 gint s_pm_up_len = 0;
4862 gint s_am_low_len = 0;
4863 gint s_pm_low_len = 0;
4865 static gboolean time_names_init_done = FALSE;
4867 static void init_time_names(void)
4871 daynames[0] = C_("Complete day name for use by strftime", "Sunday");
4872 daynames[1] = C_("Complete day name for use by strftime", "Monday");
4873 daynames[2] = C_("Complete day name for use by strftime", "Tuesday");
4874 daynames[3] = C_("Complete day name for use by strftime", "Wednesday");
4875 daynames[4] = C_("Complete day name for use by strftime", "Thursday");
4876 daynames[5] = C_("Complete day name for use by strftime", "Friday");
4877 daynames[6] = C_("Complete day name for use by strftime", "Saturday");
4879 monthnames[0] = C_("Complete month name for use by strftime", "January");
4880 monthnames[1] = C_("Complete month name for use by strftime", "February");
4881 monthnames[2] = C_("Complete month name for use by strftime", "March");
4882 monthnames[3] = C_("Complete month name for use by strftime", "April");
4883 monthnames[4] = C_("Complete month name for use by strftime", "May");
4884 monthnames[5] = C_("Complete month name for use by strftime", "June");
4885 monthnames[6] = C_("Complete month name for use by strftime", "July");
4886 monthnames[7] = C_("Complete month name for use by strftime", "August");
4887 monthnames[8] = C_("Complete month name for use by strftime", "September");
4888 monthnames[9] = C_("Complete month name for use by strftime", "October");
4889 monthnames[10] = C_("Complete month name for use by strftime", "November");
4890 monthnames[11] = C_("Complete month name for use by strftime", "December");
4892 s_daynames[0] = C_("Abbr. day name for use by strftime", "Sun");
4893 s_daynames[1] = C_("Abbr. day name for use by strftime", "Mon");
4894 s_daynames[2] = C_("Abbr. day name for use by strftime", "Tue");
4895 s_daynames[3] = C_("Abbr. day name for use by strftime", "Wed");
4896 s_daynames[4] = C_("Abbr. day name for use by strftime", "Thu");
4897 s_daynames[5] = C_("Abbr. day name for use by strftime", "Fri");
4898 s_daynames[6] = C_("Abbr. day name for use by strftime", "Sat");
4900 s_monthnames[0] = C_("Abbr. month name for use by strftime", "Jan");
4901 s_monthnames[1] = C_("Abbr. month name for use by strftime", "Feb");
4902 s_monthnames[2] = C_("Abbr. month name for use by strftime", "Mar");
4903 s_monthnames[3] = C_("Abbr. month name for use by strftime", "Apr");
4904 s_monthnames[4] = C_("Abbr. month name for use by strftime", "May");
4905 s_monthnames[5] = C_("Abbr. month name for use by strftime", "Jun");
4906 s_monthnames[6] = C_("Abbr. month name for use by strftime", "Jul");
4907 s_monthnames[7] = C_("Abbr. month name for use by strftime", "Aug");
4908 s_monthnames[8] = C_("Abbr. month name for use by strftime", "Sep");
4909 s_monthnames[9] = C_("Abbr. month name for use by strftime", "Oct");
4910 s_monthnames[10] = C_("Abbr. month name for use by strftime", "Nov");
4911 s_monthnames[11] = C_("Abbr. month name for use by strftime", "Dec");
4913 for (i = 0; i < 7; i++) {
4914 daynames_len[i] = strlen(daynames[i]);
4915 s_daynames_len[i] = strlen(s_daynames[i]);
4917 for (i = 0; i < 12; i++) {
4918 monthnames_len[i] = strlen(monthnames[i]);
4919 s_monthnames_len[i] = strlen(s_monthnames[i]);
4922 s_am_up = C_("For use by strftime (morning)", "AM");
4923 s_pm_up = C_("For use by strftime (afternoon)", "PM");
4924 s_am_low = C_("For use by strftime (morning, lowercase)", "am");
4925 s_pm_low = C_("For use by strftime (afternoon, lowercase)", "pm");
4927 s_am_up_len = strlen(s_am_up);
4928 s_pm_up_len = strlen(s_pm_up);
4929 s_am_low_len = strlen(s_am_low);
4930 s_pm_low_len = strlen(s_pm_low);
4932 time_names_init_done = TRUE;
4935 #define CHECK_SIZE() { \
4936 total_done += len; \
4937 if (total_done >= buflen) { \
4938 buf[buflen-1] = '\0'; \
4943 size_t fast_strftime(gchar *buf, gint buflen, const gchar *format, struct tm *lt)
4945 gchar *curpos = buf;
4946 gint total_done = 0;
4947 gchar subbuf[64], subfmt[64];
4948 static time_t last_tzset = (time_t)0;
4950 if (!time_names_init_done)
4953 if (format == NULL || lt == NULL)
4956 if (last_tzset != time(NULL)) {
4958 last_tzset = time(NULL);
4961 if (*format == '%') {
4962 gint len = 0, tmp = 0;
4966 len = 1; CHECK_SIZE();
4970 len = s_daynames_len[lt->tm_wday]; CHECK_SIZE();
4971 strncpy2(curpos, s_daynames[lt->tm_wday], buflen - total_done);
4974 len = daynames_len[lt->tm_wday]; CHECK_SIZE();
4975 strncpy2(curpos, daynames[lt->tm_wday], buflen - total_done);
4979 len = s_monthnames_len[lt->tm_mon]; CHECK_SIZE();
4980 strncpy2(curpos, s_monthnames[lt->tm_mon], buflen - total_done);
4983 len = monthnames_len[lt->tm_mon]; CHECK_SIZE();
4984 strncpy2(curpos, monthnames[lt->tm_mon], buflen - total_done);
4987 strftime(subbuf, 64, "%c", lt);
4988 len = strlen(subbuf); CHECK_SIZE();
4989 strncpy2(curpos, subbuf, buflen - total_done);
4992 total_done += 2; CHECK_SIZE();
4993 tmp = (lt->tm_year + 1900)/100;
4994 *curpos++ = '0'+(tmp / 10);
4995 *curpos++ = '0'+(tmp % 10);
4998 total_done += 2; CHECK_SIZE();
4999 *curpos++ = '0'+(lt->tm_mday / 10);
5000 *curpos++ = '0'+(lt->tm_mday % 10);
5003 total_done += 8; CHECK_SIZE();
5004 *curpos++ = '0'+((lt->tm_mon+1) / 10);
5005 *curpos++ = '0'+((lt->tm_mon+1) % 10);
5007 *curpos++ = '0'+(lt->tm_mday / 10);
5008 *curpos++ = '0'+(lt->tm_mday % 10);
5010 tmp = lt->tm_year%100;
5011 *curpos++ = '0'+(tmp / 10);
5012 *curpos++ = '0'+(tmp % 10);
5015 len = 2; CHECK_SIZE();
5016 snprintf(curpos, buflen - total_done, "%2d", lt->tm_mday);
5019 len = 10; CHECK_SIZE();
5020 snprintf(curpos, buflen - total_done, "%4d-%02d-%02d",
5021 lt->tm_year + 1900, lt->tm_mon +1, lt->tm_mday);
5024 total_done += 2; CHECK_SIZE();
5025 *curpos++ = '0'+(lt->tm_hour / 10);
5026 *curpos++ = '0'+(lt->tm_hour % 10);
5029 total_done += 2; CHECK_SIZE();
5035 *curpos++ = '0'+(tmp / 10);
5036 *curpos++ = '0'+(tmp % 10);
5039 len = 3; CHECK_SIZE();
5040 snprintf(curpos, buflen - total_done, "%03d", lt->tm_yday+1);
5043 len = 2; CHECK_SIZE();
5044 snprintf(curpos, buflen - total_done, "%2d", lt->tm_hour);
5047 len = 2; CHECK_SIZE();
5053 snprintf(curpos, buflen - total_done, "%2d", tmp);
5056 total_done += 2; CHECK_SIZE();
5057 tmp = lt->tm_mon + 1;
5058 *curpos++ = '0'+(tmp / 10);
5059 *curpos++ = '0'+(tmp % 10);
5062 total_done += 2; CHECK_SIZE();
5063 *curpos++ = '0'+(lt->tm_min / 10);
5064 *curpos++ = '0'+(lt->tm_min % 10);
5067 len = 1; CHECK_SIZE();
5071 if (lt->tm_hour >= 12) {
5072 len = s_pm_up_len; CHECK_SIZE();
5073 snprintf(curpos, buflen-total_done, "%s", s_pm_up);
5075 len = s_am_up_len; CHECK_SIZE();
5076 snprintf(curpos, buflen-total_done, "%s", s_am_up);
5080 if (lt->tm_hour >= 12) {
5081 len = s_pm_low_len; CHECK_SIZE();
5082 snprintf(curpos, buflen-total_done, "%s", s_pm_low);
5084 len = s_am_low_len; CHECK_SIZE();
5085 snprintf(curpos, buflen-total_done, "%s", s_am_low);
5089 strftime(subbuf, 64, "%r", lt);
5090 len = strlen(subbuf); CHECK_SIZE();
5091 strncpy2(curpos, subbuf, buflen - total_done);
5094 total_done += 5; CHECK_SIZE();
5095 *curpos++ = '0'+(lt->tm_hour / 10);
5096 *curpos++ = '0'+(lt->tm_hour % 10);
5098 *curpos++ = '0'+(lt->tm_min / 10);
5099 *curpos++ = '0'+(lt->tm_min % 10);
5102 snprintf(subbuf, 64, "%ld", mktime(lt));
5103 len = strlen(subbuf); CHECK_SIZE();
5104 strncpy2(curpos, subbuf, buflen - total_done);
5107 total_done += 2; CHECK_SIZE();
5108 *curpos++ = '0'+(lt->tm_sec / 10);
5109 *curpos++ = '0'+(lt->tm_sec % 10);
5112 len = 1; CHECK_SIZE();
5116 total_done += 8; CHECK_SIZE();
5117 *curpos++ = '0'+(lt->tm_hour / 10);
5118 *curpos++ = '0'+(lt->tm_hour % 10);
5120 *curpos++ = '0'+(lt->tm_min / 10);
5121 *curpos++ = '0'+(lt->tm_min % 10);
5123 *curpos++ = '0'+(lt->tm_sec / 10);
5124 *curpos++ = '0'+(lt->tm_sec % 10);
5127 len = 1; CHECK_SIZE();
5128 snprintf(curpos, buflen - total_done, "%d", lt->tm_wday == 0 ? 7: lt->tm_wday);
5131 len = 1; CHECK_SIZE();
5132 snprintf(curpos, buflen - total_done, "%d", lt->tm_wday);
5135 strftime(subbuf, 64, "%x", lt);
5136 len = strlen(subbuf); CHECK_SIZE();
5137 strncpy2(curpos, subbuf, buflen - total_done);
5140 strftime(subbuf, 64, "%X", lt);
5141 len = strlen(subbuf); CHECK_SIZE();
5142 strncpy2(curpos, subbuf, buflen - total_done);
5145 total_done += 2; CHECK_SIZE();
5146 tmp = lt->tm_year%100;
5147 *curpos++ = '0'+(tmp / 10);
5148 *curpos++ = '0'+(tmp % 10);
5151 len = 4; CHECK_SIZE();
5152 snprintf(curpos, buflen - total_done, "%4d", lt->tm_year + 1900);
5162 /* let these complicated ones be done with the libc */
5163 snprintf(subfmt, 64, "%%%c", *format);
5164 strftime(subbuf, 64, subfmt, lt);
5165 len = strlen(subbuf); CHECK_SIZE();
5166 strncpy2(curpos, subbuf, buflen - total_done);
5170 /* let these complicated modifiers be done with the libc */
5171 snprintf(subfmt, 64, "%%%c%c", *format, *(format+1));
5172 strftime(subbuf, 64, subfmt, lt);
5173 len = strlen(subbuf); CHECK_SIZE();
5174 strncpy2(curpos, subbuf, buflen - total_done);
5178 g_warning("format error (%c)", *format);
5185 int len = 1; CHECK_SIZE();
5186 *curpos++ = *format++;
5193 gboolean prefs_common_get_use_shred(void);
5197 #define WEXITSTATUS(x) (x)
5200 int claws_unlink(const gchar *filename)
5203 static int found_shred = -1;
5204 static const gchar *args[4];
5206 if (filename == NULL)
5209 if (prefs_common_get_use_shred()) {
5210 if (found_shred == -1) {
5212 args[0] = g_find_program_in_path("shred");
5213 debug_print("found shred: %s\n", args[0]);
5214 found_shred = (args[0] != NULL) ? 1:0;
5218 if (found_shred == 1) {
5219 if (g_stat(filename, &s) == 0 && S_ISREG(s.st_mode)) {
5220 if (s.st_nlink == 1) {
5223 g_spawn_sync(NULL, (gchar **)args, NULL, 0,
5224 NULL, NULL, NULL, NULL, &status, NULL);
5225 debug_print("%s %s exited with status %d\n",
5226 args[0], filename, WEXITSTATUS(status));
5227 if (truncate(filename, 0) < 0)
5228 g_warning("couln't truncate: %s", filename);
5233 return g_unlink(filename);
5236 GMutex *cm_mutex_new(void) {
5237 #if GLIB_CHECK_VERSION(2,32,0)
5238 GMutex *m = g_new0(GMutex, 1);
5242 return g_mutex_new();
5246 void cm_mutex_free(GMutex *mutex) {
5247 #if GLIB_CHECK_VERSION(2,32,0)
5248 g_mutex_clear(mutex);
5251 g_mutex_free(mutex);
5255 static gchar *canonical_list_to_file(GSList *list)
5257 GString *result = g_string_new(NULL);
5258 GSList *pathlist = g_slist_reverse(g_slist_copy(list));
5263 result = g_string_append(result, G_DIR_SEPARATOR_S);
5265 if (pathlist->data) {
5266 const gchar *root = (gchar *)pathlist->data;
5267 if (root[0] != '\0' && g_ascii_isalpha(root[0]) &&
5269 /* drive - don't prepend dir separator */
5271 result = g_string_append(result, G_DIR_SEPARATOR_S);
5276 for (cur = pathlist; cur; cur = cur->next) {
5277 result = g_string_append(result, (gchar *)cur->data);
5279 result = g_string_append(result, G_DIR_SEPARATOR_S);
5281 g_slist_free(pathlist);
5284 g_string_free(result, FALSE);
5289 static GSList *cm_split_path(const gchar *filename, int depth)
5292 GSList *canonical_parts = NULL;
5295 gboolean follow_symlinks = TRUE;
5301 errno = EINVAL; /* can't happen, no symlink handling */
5306 if (!g_path_is_absolute(filename)) {
5311 path_parts = g_strsplit(filename, G_DIR_SEPARATOR_S, -1);
5313 for (i = 0; path_parts[i] != NULL; i++) {
5314 if (!strcmp(path_parts[i], ""))
5316 if (!strcmp(path_parts[i], "."))
5318 else if (!strcmp(path_parts[i], "..")) {
5323 else /* Remove the last inserted element */
5325 g_slist_delete_link(canonical_parts,
5330 canonical_parts = g_slist_prepend(canonical_parts,
5331 g_strdup(path_parts[i]));
5333 tmp_path = canonical_list_to_file(canonical_parts);
5335 if(g_stat(tmp_path, &st) < 0) {
5336 if (errno == ENOENT) {
5338 follow_symlinks = FALSE;
5342 slist_free_strings_full(canonical_parts);
5343 g_strfreev(path_parts);
5349 if (follow_symlinks && g_file_test(tmp_path, G_FILE_TEST_IS_SYMLINK)) {
5350 GError *error = NULL;
5351 gchar *target = g_file_read_link(tmp_path, &error);
5353 if (!g_path_is_absolute(target)) {
5354 /* remove the last inserted element */
5356 g_slist_delete_link(canonical_parts,
5358 /* add the target */
5359 canonical_parts = g_slist_prepend(canonical_parts,
5363 /* and get the new target */
5364 target = canonical_list_to_file(canonical_parts);
5367 /* restart from absolute target */
5368 slist_free_strings_full(canonical_parts);
5369 canonical_parts = NULL;
5371 canonical_parts = cm_split_path(target, depth + 1);
5373 g_error_free(error);
5374 if (canonical_parts == NULL) {
5376 g_strfreev(path_parts);
5385 g_strfreev(path_parts);
5386 return canonical_parts;
5390 * Canonicalize a filename, resolving symlinks along the way.
5391 * Returns a negative errno in case of error.
5393 int cm_canonicalize_filename(const gchar *filename, gchar **canonical_name) {
5394 GSList *canonical_parts;
5395 gboolean is_absolute;
5397 if (filename == NULL)
5399 if (canonical_name == NULL)
5401 *canonical_name = NULL;
5403 is_absolute = g_path_is_absolute(filename);
5405 /* Always work on absolute filenames. */
5406 gchar *cur = g_get_current_dir();
5407 gchar *absolute_filename = g_strconcat(cur, G_DIR_SEPARATOR_S,
5410 canonical_parts = cm_split_path(absolute_filename, 0);
5411 g_free(absolute_filename);
5414 canonical_parts = cm_split_path(filename, 0);
5416 if (canonical_parts == NULL)
5419 *canonical_name = canonical_list_to_file(canonical_parts);
5420 slist_free_strings_full(canonical_parts);
5424 /* Returns a decoded base64 string, guaranteed to be null-terminated. */
5425 guchar *g_base64_decode_zero(const gchar *text, gsize *out_len)
5427 gchar *tmp = g_base64_decode(text, out_len);
5428 gchar *out = g_strndup(tmp, *out_len);
5432 if (strlen(out) != *out_len) {
5433 g_warning ("strlen(out) %zd != *out_len %" G_GSIZE_FORMAT, strlen(out), *out_len);
5439 #if !GLIB_CHECK_VERSION(2, 30, 0)
5442 * @str: a UTF-8 encoded string
5443 * @start_pos: a character offset within @str
5444 * @end_pos: another character offset within @str
5446 * Copies a substring out of a UTF-8 encoded string.
5447 * The substring will contain @end_pos - @start_pos
5450 * Returns: a newly allocated copy of the requested
5451 * substring. Free with g_free() when no longer needed.
5456 g_utf8_substring (const gchar *str,
5460 gchar *start, *end, *out;
5462 start = g_utf8_offset_to_pointer (str, start_pos);
5463 end = g_utf8_offset_to_pointer (start, end_pos - start_pos);
5465 out = g_malloc (end - start + 1);
5466 memcpy (out, start, end - start);
5467 out[end - start] = 0;