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 /* Return true if we are running as root. This function should beused
93 instead of getuid () == 0. */
94 gboolean superuser_p (void)
97 return w32_is_administrator ();
103 GSList *slist_copy_deep(GSList *list, GCopyFunc func)
105 #if GLIB_CHECK_VERSION(2, 34, 0)
106 return g_slist_copy_deep(list, func, NULL);
108 GSList *res = g_slist_copy(list);
111 walk->data = func(walk->data, NULL);
118 void list_free_strings(GList *list)
120 list = g_list_first(list);
122 while (list != NULL) {
128 void slist_free_strings(GSList *list)
130 while (list != NULL) {
136 void slist_free_strings_full(GSList *list)
138 #if GLIB_CHECK_VERSION(2,28,0)
139 g_slist_free_full(list, (GDestroyNotify)g_free);
141 g_slist_foreach(list, (GFunc)g_free, NULL);
146 static void hash_free_strings_func(gpointer key, gpointer value, gpointer data)
151 void hash_free_strings(GHashTable *table)
153 g_hash_table_foreach(table, hash_free_strings_func, NULL);
156 gint str_case_equal(gconstpointer v, gconstpointer v2)
158 return g_ascii_strcasecmp((const gchar *)v, (const gchar *)v2) == 0;
161 guint str_case_hash(gconstpointer key)
163 const gchar *p = key;
167 h = g_ascii_tolower(h);
168 for (p += 1; *p != '\0'; p++)
169 h = (h << 5) - h + g_ascii_tolower(*p);
175 void ptr_array_free_strings(GPtrArray *array)
180 cm_return_if_fail(array != NULL);
182 for (i = 0; i < array->len; i++) {
183 str = g_ptr_array_index(array, i);
188 gint to_number(const gchar *nstr)
190 register const gchar *p;
192 if (*nstr == '\0') return -1;
194 for (p = nstr; *p != '\0'; p++)
195 if (!g_ascii_isdigit(*p)) return -1;
200 /* convert integer into string,
201 nstr must be not lower than 11 characters length */
202 gchar *itos_buf(gchar *nstr, gint n)
204 g_snprintf(nstr, 11, "%d", n);
208 /* convert integer into string */
211 static gchar nstr[11];
213 return itos_buf(nstr, n);
216 #define divide(num,divisor,i,d) \
218 i = num >> divisor; \
219 d = num & ((1<<divisor)-1); \
220 d = (d*100) >> divisor; \
225 * \brief Convert a given size in bytes in a human-readable string
227 * \param size The size expressed in bytes to convert in string
228 * \return The string that respresents the size in an human-readable way
230 gchar *to_human_readable(goffset size)
232 static gchar str[14];
233 static gchar *b_format = NULL, *kb_format = NULL,
234 *mb_format = NULL, *gb_format = NULL;
235 register int t = 0, r = 0;
236 if (b_format == NULL) {
238 kb_format = _("%d.%02dKB");
239 mb_format = _("%d.%02dMB");
240 gb_format = _("%.2fGB");
243 if (size < (goffset)1024) {
244 g_snprintf(str, sizeof(str), b_format, (gint)size);
246 } else if (size >> 10 < (goffset)1024) {
247 divide(size, 10, t, r);
248 g_snprintf(str, sizeof(str), kb_format, t, r);
250 } else if (size >> 20 < (goffset)1024) {
251 divide(size, 20, t, r);
252 g_snprintf(str, sizeof(str), mb_format, t, r);
255 g_snprintf(str, sizeof(str), gb_format, (gfloat)(size >> 30));
260 /* strcmp with NULL-checking */
261 gint strcmp2(const gchar *s1, const gchar *s2)
263 if (s1 == NULL || s2 == NULL)
266 return strcmp(s1, s2);
268 /* strstr with NULL-checking */
269 gchar *strstr2(const gchar *s1, const gchar *s2)
271 if (s1 == NULL || s2 == NULL)
274 return strstr(s1, s2);
277 gint path_cmp(const gchar *s1, const gchar *s2)
282 gchar *s1buf, *s2buf;
285 if (s1 == NULL || s2 == NULL) return -1;
286 if (*s1 == '\0' || *s2 == '\0') return -1;
289 s1buf = g_strdup (s1);
290 s2buf = g_strdup (s2);
291 subst_char (s1buf, '/', G_DIR_SEPARATOR);
292 subst_char (s2buf, '/', G_DIR_SEPARATOR);
295 #endif /* !G_OS_WIN32 */
300 if (s1[len1 - 1] == G_DIR_SEPARATOR) len1--;
301 if (s2[len2 - 1] == G_DIR_SEPARATOR) len2--;
303 rc = strncmp(s1, s2, MAX(len1, len2));
307 #endif /* !G_OS_WIN32 */
311 /* remove trailing return code */
312 gchar *strretchomp(gchar *str)
316 if (!*str) return str;
318 for (s = str + strlen(str) - 1;
319 s >= str && (*s == '\n' || *s == '\r');
326 /* remove trailing character */
327 gchar *strtailchomp(gchar *str, gchar tail_char)
331 if (!*str) return str;
332 if (tail_char == '\0') return str;
334 for (s = str + strlen(str) - 1; s >= str && *s == tail_char; s--)
340 /* remove CR (carriage return) */
341 gchar *strcrchomp(gchar *str)
345 if (!*str) return str;
347 s = str + strlen(str) - 1;
348 if (*s == '\n' && s > str && *(s - 1) == '\r') {
356 gint file_strip_crs(const gchar *file)
358 FILE *fp = NULL, *outfp = NULL;
360 gchar *out = get_tmp_file();
364 fp = g_fopen(file, "rb");
368 outfp = g_fopen(out, "wb");
374 while (fgets(buf, sizeof (buf), fp) != NULL) {
376 if (fputs(buf, outfp) == EOF) {
384 if (fclose(outfp) == EOF) {
388 if (move_file(out, file, TRUE) < 0)
400 /* Similar to `strstr' but this function ignores the case of both strings. */
401 gchar *strcasestr(const gchar *haystack, const gchar *needle)
403 size_t haystack_len = strlen(haystack);
405 return strncasestr(haystack, haystack_len, needle);
408 gchar *strncasestr(const gchar *haystack, gint haystack_len, const gchar *needle)
410 register size_t needle_len;
412 needle_len = strlen(needle);
414 if (haystack_len < needle_len || needle_len == 0)
417 while (haystack_len >= needle_len) {
418 if (!g_ascii_strncasecmp(haystack, needle, needle_len))
419 return (gchar *)haystack;
429 gpointer my_memmem(gconstpointer haystack, size_t haystacklen,
430 gconstpointer needle, size_t needlelen)
432 const gchar *haystack_ = (const gchar *)haystack;
433 const gchar *needle_ = (const gchar *)needle;
434 const gchar *haystack_cur = (const gchar *)haystack;
435 size_t haystack_left = haystacklen;
438 return memchr(haystack_, *needle_, haystacklen);
440 while ((haystack_cur = memchr(haystack_cur, *needle_, haystack_left))
442 if (haystacklen - (haystack_cur - haystack_) < needlelen)
444 if (memcmp(haystack_cur + 1, needle_ + 1, needlelen - 1) == 0)
445 return (gpointer)haystack_cur;
448 haystack_left = haystacklen - (haystack_cur - haystack_);
455 /* Copy no more than N characters of SRC to DEST, with NULL terminating. */
456 gchar *strncpy2(gchar *dest, const gchar *src, size_t n)
458 register const gchar *s = src;
459 register gchar *d = dest;
469 /* Examine if next block is non-ASCII string */
470 gboolean is_next_nonascii(const gchar *s)
474 /* skip head space */
475 for (p = s; *p != '\0' && g_ascii_isspace(*p); p++)
477 for (; *p != '\0' && !g_ascii_isspace(*p); p++) {
478 if (*(guchar *)p > 127 || *(guchar *)p < 32)
485 gint get_next_word_len(const gchar *s)
489 for (; *s != '\0' && !g_ascii_isspace(*s); s++, len++)
495 static void trim_subject_for_compare(gchar *str)
499 eliminate_parenthesis(str, '[', ']');
500 eliminate_parenthesis(str, '(', ')');
503 srcp = str + subject_get_prefix_length(str);
505 memmove(str, srcp, strlen(srcp) + 1);
508 static void trim_subject_for_sort(gchar *str)
514 srcp = str + subject_get_prefix_length(str);
516 memmove(str, srcp, strlen(srcp) + 1);
519 /* compare subjects */
520 gint subject_compare(const gchar *s1, const gchar *s2)
524 if (!s1 || !s2) return -1;
525 if (!*s1 || !*s2) return -1;
527 Xstrdup_a(str1, s1, return -1);
528 Xstrdup_a(str2, s2, return -1);
530 trim_subject_for_compare(str1);
531 trim_subject_for_compare(str2);
533 if (!*str1 || !*str2) return -1;
535 return strcmp(str1, str2);
538 gint subject_compare_for_sort(const gchar *s1, const gchar *s2)
542 if (!s1 || !s2) return -1;
544 Xstrdup_a(str1, s1, return -1);
545 Xstrdup_a(str2, s2, return -1);
547 trim_subject_for_sort(str1);
548 trim_subject_for_sort(str2);
550 return g_utf8_collate(str1, str2);
553 void trim_subject(gchar *str)
555 register gchar *srcp;
561 srcp = str + subject_get_prefix_length(str);
566 } else if (*srcp == '(') {
578 else if (*srcp == cl)
585 while (g_ascii_isspace(*srcp)) srcp++;
586 memmove(str, srcp, strlen(srcp) + 1);
589 void eliminate_parenthesis(gchar *str, gchar op, gchar cl)
591 register gchar *srcp, *destp;
596 while ((destp = strchr(destp, op))) {
602 else if (*srcp == cl)
608 while (g_ascii_isspace(*srcp)) srcp++;
609 memmove(destp, srcp, strlen(srcp) + 1);
613 void extract_parenthesis(gchar *str, gchar op, gchar cl)
615 register gchar *srcp, *destp;
620 while ((srcp = strchr(destp, op))) {
623 memmove(destp, srcp + 1, strlen(srcp));
628 else if (*destp == cl)
640 static void extract_parenthesis_with_skip_quote(gchar *str, gchar quote_chr,
643 register gchar *srcp, *destp;
645 gboolean in_quote = FALSE;
649 while ((srcp = strchr_with_skip_quote(destp, quote_chr, op))) {
652 memmove(destp, srcp + 1, strlen(srcp));
655 if (*destp == op && !in_quote)
657 else if (*destp == cl && !in_quote)
659 else if (*destp == quote_chr)
671 void extract_quote(gchar *str, gchar quote_chr)
675 if ((str = strchr(str, quote_chr))) {
677 while ((p = strchr(p + 1, quote_chr)) && (p[-1] == '\\')) {
678 memmove(p - 1, p, strlen(p) + 1);
683 memmove(str, str + 1, p - str);
688 /* Returns a newly allocated string with all quote_chr not at the beginning
689 or the end of str escaped with '\' or the given str if not required. */
690 gchar *escape_internal_quotes(gchar *str, gchar quote_chr)
692 register gchar *p, *q;
696 if (str == NULL || *str == '\0')
699 /* search for unescaped quote_chr */
704 if (*p == quote_chr && *(p - 1) != '\\' && *(p + 1) != '\0')
708 if (!k) /* nothing to escape */
711 /* unescaped quote_chr found */
712 qstr = g_malloc(l + k + 1);
715 if (*p == quote_chr) {
720 if (*p == quote_chr && *(p - 1) != '\\' && *(p + 1) != '\0')
729 void eliminate_address_comment(gchar *str)
731 register gchar *srcp, *destp;
736 while ((destp = strchr(destp, '"'))) {
737 if ((srcp = strchr(destp + 1, '"'))) {
742 while (g_ascii_isspace(*srcp)) srcp++;
743 memmove(destp, srcp, strlen(srcp) + 1);
753 while ((destp = strchr_with_skip_quote(destp, '"', '('))) {
759 else if (*srcp == ')')
765 while (g_ascii_isspace(*srcp)) srcp++;
766 memmove(destp, srcp, strlen(srcp) + 1);
770 gchar *strchr_with_skip_quote(const gchar *str, gint quote_chr, gint c)
772 gboolean in_quote = FALSE;
775 if (*str == c && !in_quote)
777 if (*str == quote_chr)
785 void extract_address(gchar *str)
787 cm_return_if_fail(str != NULL);
788 eliminate_address_comment(str);
789 if (strchr_with_skip_quote(str, '"', '<'))
790 extract_parenthesis_with_skip_quote(str, '"', '<', '>');
794 void extract_list_id_str(gchar *str)
796 if (strchr_with_skip_quote(str, '"', '<'))
797 extract_parenthesis_with_skip_quote(str, '"', '<', '>');
801 static GSList *address_list_append_real(GSList *addr_list, const gchar *str, gboolean removecomments)
806 if (!str) return addr_list;
808 Xstrdup_a(work, str, return addr_list);
811 eliminate_address_comment(work);
814 while (workp && *workp) {
817 if ((p = strchr_with_skip_quote(workp, '"', ','))) {
823 if (removecomments && strchr_with_skip_quote(workp, '"', '<'))
824 extract_parenthesis_with_skip_quote
825 (workp, '"', '<', '>');
829 addr_list = g_slist_append(addr_list, g_strdup(workp));
837 GSList *address_list_append(GSList *addr_list, const gchar *str)
839 return address_list_append_real(addr_list, str, TRUE);
842 GSList *address_list_append_with_comments(GSList *addr_list, const gchar *str)
844 return address_list_append_real(addr_list, str, FALSE);
847 GSList *references_list_prepend(GSList *msgid_list, const gchar *str)
851 if (!str) return msgid_list;
854 while (strp && *strp) {
855 const gchar *start, *end;
858 if ((start = strchr(strp, '<')) != NULL) {
859 end = strchr(start + 1, '>');
864 msgid = g_strndup(start + 1, end - start - 1);
867 msgid_list = g_slist_prepend(msgid_list, msgid);
877 GSList *references_list_append(GSList *msgid_list, const gchar *str)
881 list = references_list_prepend(NULL, str);
882 list = g_slist_reverse(list);
883 msgid_list = g_slist_concat(msgid_list, list);
888 GSList *newsgroup_list_append(GSList *group_list, const gchar *str)
893 if (!str) return group_list;
895 Xstrdup_a(work, str, return group_list);
899 while (workp && *workp) {
902 if ((p = strchr_with_skip_quote(workp, '"', ','))) {
910 group_list = g_slist_append(group_list,
919 GList *add_history(GList *list, const gchar *str)
924 cm_return_val_if_fail(str != NULL, list);
926 old = g_list_find_custom(list, (gpointer)str, (GCompareFunc)strcmp2);
929 list = g_list_remove(list, old->data);
931 } else if (g_list_length(list) >= MAX_HISTORY_SIZE) {
934 last = g_list_last(list);
937 list = g_list_remove(list, last->data);
942 list = g_list_prepend(list, g_strdup(str));
947 void remove_return(gchar *str)
949 register gchar *p = str;
952 if (*p == '\n' || *p == '\r')
953 memmove(p, p + 1, strlen(p));
959 void remove_space(gchar *str)
961 register gchar *p = str;
966 while (g_ascii_isspace(*(p + spc)))
969 memmove(p, p + spc, strlen(p + spc) + 1);
975 void unfold_line(gchar *str)
977 register gchar *p = str;
981 if (*p == '\n' || *p == '\r') {
984 while (g_ascii_isspace(*(p + spc)))
987 memmove(p, p + spc, strlen(p + spc) + 1);
993 void subst_char(gchar *str, gchar orig, gchar subst)
995 register gchar *p = str;
1004 void subst_chars(gchar *str, gchar *orig, gchar subst)
1006 register gchar *p = str;
1009 if (strchr(orig, *p) != NULL)
1015 void subst_for_filename(gchar *str)
1020 subst_chars(str, "\t\r\n\\/*:", '_');
1022 subst_chars(str, "\t\r\n\\/*", '_');
1026 void subst_for_shellsafe_filename(gchar *str)
1030 subst_for_filename(str);
1031 subst_chars(str, " \"'|&;()<>'!{}[]",'_');
1034 gboolean is_ascii_str(const gchar *str)
1036 const guchar *p = (const guchar *)str;
1038 while (*p != '\0') {
1039 if (*p != '\t' && *p != ' ' &&
1040 *p != '\r' && *p != '\n' &&
1041 (*p < 32 || *p >= 127))
1049 static const gchar * line_has_quote_char_last(const gchar * str, const gchar *quote_chars)
1051 gchar * position = NULL;
1052 gchar * tmp_pos = NULL;
1055 if (quote_chars == NULL)
1058 for (i = 0; i < strlen(quote_chars); i++) {
1059 tmp_pos = strrchr (str, quote_chars[i]);
1061 || (tmp_pos != NULL && position <= tmp_pos) )
1067 gint get_quote_level(const gchar *str, const gchar *quote_chars)
1069 const gchar *first_pos;
1070 const gchar *last_pos;
1071 const gchar *p = str;
1072 gint quote_level = -1;
1074 /* speed up line processing by only searching to the last '>' */
1075 if ((first_pos = line_has_quote_char(str, quote_chars)) != NULL) {
1076 /* skip a line if it contains a '<' before the initial '>' */
1077 if (memchr(str, '<', first_pos - str) != NULL)
1079 last_pos = line_has_quote_char_last(first_pos, quote_chars);
1083 while (p <= last_pos) {
1084 while (p < last_pos) {
1085 if (g_ascii_isspace(*p))
1091 if (strchr(quote_chars, *p))
1093 else if (*p != '-' && !g_ascii_isspace(*p) && p <= last_pos) {
1094 /* any characters are allowed except '-','<' and space */
1095 while (*p != '-' && *p != '<'
1096 && !strchr(quote_chars, *p)
1097 && !g_ascii_isspace(*p)
1100 if (strchr(quote_chars, *p))
1112 gint check_line_length(const gchar *str, gint max_chars, gint *line)
1114 const gchar *p = str, *q;
1115 gint cur_line = 0, len;
1117 while ((q = strchr(p, '\n')) != NULL) {
1119 if (len > max_chars) {
1129 if (len > max_chars) {
1138 const gchar * line_has_quote_char(const gchar * str, const gchar *quote_chars)
1140 gchar * position = NULL;
1141 gchar * tmp_pos = NULL;
1144 if (quote_chars == NULL)
1147 for (i = 0; i < strlen(quote_chars); i++) {
1148 tmp_pos = strchr (str, quote_chars[i]);
1150 || (tmp_pos != NULL && position >= tmp_pos) )
1156 static gchar *strstr_with_skip_quote(const gchar *haystack, const gchar *needle)
1158 register guint haystack_len, needle_len;
1159 gboolean in_squote = FALSE, in_dquote = FALSE;
1161 haystack_len = strlen(haystack);
1162 needle_len = strlen(needle);
1164 if (haystack_len < needle_len || needle_len == 0)
1167 while (haystack_len >= needle_len) {
1168 if (!in_squote && !in_dquote &&
1169 !strncmp(haystack, needle, needle_len))
1170 return (gchar *)haystack;
1172 /* 'foo"bar"' -> foo"bar"
1173 "foo'bar'" -> foo'bar' */
1174 if (*haystack == '\'') {
1177 else if (!in_dquote)
1179 } else if (*haystack == '\"') {
1182 else if (!in_squote)
1184 } else if (*haystack == '\\') {
1196 gchar **strsplit_with_quote(const gchar *str, const gchar *delim,
1199 GSList *string_list = NULL, *slist;
1200 gchar **str_array, *s, *new_str;
1201 guint i, n = 1, len;
1203 cm_return_val_if_fail(str != NULL, NULL);
1204 cm_return_val_if_fail(delim != NULL, NULL);
1207 max_tokens = G_MAXINT;
1209 s = strstr_with_skip_quote(str, delim);
1211 guint delimiter_len = strlen(delim);
1215 new_str = g_strndup(str, len);
1217 if (new_str[0] == '\'' || new_str[0] == '\"') {
1218 if (new_str[len - 1] == new_str[0]) {
1219 new_str[len - 1] = '\0';
1220 memmove(new_str, new_str + 1, len - 1);
1223 string_list = g_slist_prepend(string_list, new_str);
1225 str = s + delimiter_len;
1226 s = strstr_with_skip_quote(str, delim);
1227 } while (--max_tokens && s);
1231 new_str = g_strdup(str);
1232 if (new_str[0] == '\'' || new_str[0] == '\"') {
1234 if (new_str[len - 1] == new_str[0]) {
1235 new_str[len - 1] = '\0';
1236 memmove(new_str, new_str + 1, len - 1);
1239 string_list = g_slist_prepend(string_list, new_str);
1243 str_array = g_new(gchar*, n);
1247 str_array[i--] = NULL;
1248 for (slist = string_list; slist; slist = slist->next)
1249 str_array[i--] = slist->data;
1251 g_slist_free(string_list);
1256 gchar *get_abbrev_newsgroup_name(const gchar *group, gint len)
1258 gchar *abbrev_group;
1260 const gchar *p = group;
1263 cm_return_val_if_fail(group != NULL, NULL);
1265 last = group + strlen(group);
1266 abbrev_group = ap = g_malloc(strlen(group) + 1);
1271 if ((ap - abbrev_group) + (last - p) > len && strchr(p, '.')) {
1273 while (*p != '.') p++;
1276 return abbrev_group;
1281 return abbrev_group;
1284 gchar *trim_string(const gchar *str, gint len)
1286 const gchar *p = str;
1291 if (!str) return NULL;
1292 if (strlen(str) <= len)
1293 return g_strdup(str);
1294 if (g_utf8_validate(str, -1, NULL) == FALSE)
1295 return g_strdup(str);
1297 while (*p != '\0') {
1298 mb_len = g_utf8_skip[*(guchar *)p];
1301 else if (new_len + mb_len > len)
1308 Xstrndup_a(new_str, str, new_len, return g_strdup(str));
1309 return g_strconcat(new_str, "...", NULL);
1312 GList *uri_list_extract_filenames(const gchar *uri_list)
1314 GList *result = NULL;
1316 gchar *escaped_utf8uri;
1322 while (g_ascii_isspace(*p)) p++;
1323 if (!strncmp(p, "file:", 5)) {
1326 while (*q && *q != '\n' && *q != '\r') q++;
1329 gchar *file, *locale_file = NULL;
1331 while (q > p && g_ascii_isspace(*q))
1333 Xalloca(escaped_utf8uri, q - p + 2,
1335 Xalloca(file, q - p + 2,
1338 strncpy(escaped_utf8uri, p, q - p + 1);
1339 escaped_utf8uri[q - p + 1] = '\0';
1340 decode_uri(file, escaped_utf8uri);
1342 * g_filename_from_uri() rejects escaped/locale encoded uri
1343 * string which come from Nautilus.
1346 if (g_utf8_validate(file, -1, NULL))
1348 = conv_codeset_strdup(
1351 conv_get_locale_charset_str());
1353 locale_file = g_strdup(file + 5);
1355 locale_file = g_filename_from_uri(escaped_utf8uri, NULL, NULL);
1357 result = g_list_append(result, locale_file);
1361 p = strchr(p, '\n');
1368 /* Converts two-digit hexadecimal to decimal. Used for unescaping escaped
1371 static gint axtoi(const gchar *hexstr)
1373 gint hi, lo, result;
1376 if ('0' <= hi && hi <= '9') {
1379 if ('a' <= hi && hi <= 'f') {
1382 if ('A' <= hi && hi <= 'F') {
1387 if ('0' <= lo && lo <= '9') {
1390 if ('a' <= lo && lo <= 'f') {
1393 if ('A' <= lo && lo <= 'F') {
1396 result = lo + (16 * hi);
1400 gboolean is_uri_string(const gchar *str)
1402 while (str && *str && g_ascii_isspace(*str))
1404 return (g_ascii_strncasecmp(str, "http://", 7) == 0 ||
1405 g_ascii_strncasecmp(str, "https://", 8) == 0 ||
1406 g_ascii_strncasecmp(str, "ftp://", 6) == 0 ||
1407 g_ascii_strncasecmp(str, "www.", 4) == 0);
1410 gchar *get_uri_path(const gchar *uri)
1412 while (uri && *uri && g_ascii_isspace(*uri))
1414 if (g_ascii_strncasecmp(uri, "http://", 7) == 0)
1415 return (gchar *)(uri + 7);
1416 else if (g_ascii_strncasecmp(uri, "https://", 8) == 0)
1417 return (gchar *)(uri + 8);
1418 else if (g_ascii_strncasecmp(uri, "ftp://", 6) == 0)
1419 return (gchar *)(uri + 6);
1421 return (gchar *)uri;
1424 gint get_uri_len(const gchar *str)
1428 if (is_uri_string(str)) {
1429 for (p = str; *p != '\0'; p++) {
1430 if (!g_ascii_isgraph(*p) || strchr("()<>\"", *p))
1439 /* Decodes URL-Encoded strings (i.e. strings in which spaces are replaced by
1440 * plusses, and escape characters are used)
1442 void decode_uri_with_plus(gchar *decoded_uri, const gchar *encoded_uri, gboolean with_plus)
1444 gchar *dec = decoded_uri;
1445 const gchar *enc = encoded_uri;
1450 if (isxdigit((guchar)enc[0]) &&
1451 isxdigit((guchar)enc[1])) {
1457 if (with_plus && *enc == '+')
1469 void decode_uri(gchar *decoded_uri, const gchar *encoded_uri)
1471 decode_uri_with_plus(decoded_uri, encoded_uri, TRUE);
1474 static gchar *decode_uri_gdup(const gchar *encoded_uri)
1476 gchar *buffer = g_malloc(strlen(encoded_uri)+1);
1477 decode_uri_with_plus(buffer, encoded_uri, FALSE);
1481 gint scan_mailto_url(const gchar *mailto, gchar **from, gchar **to, gchar **cc, gchar **bcc,
1482 gchar **subject, gchar **body, gchar ***attach, gchar **inreplyto)
1486 const gchar *forbidden_uris[] = { ".gnupg/",
1492 gint num_attach = 0;
1493 gchar **my_att = NULL;
1495 Xstrdup_a(tmp_mailto, mailto, return -1);
1497 if (!strncmp(tmp_mailto, "mailto:", 7))
1500 p = strchr(tmp_mailto, '?');
1507 *to = decode_uri_gdup(tmp_mailto);
1509 my_att = g_malloc(sizeof(char *));
1513 gchar *field, *value;
1530 if (*value == '\0') continue;
1532 if (from && !g_ascii_strcasecmp(field, "from")) {
1534 *from = decode_uri_gdup(value);
1536 gchar *tmp = decode_uri_gdup(value);
1537 gchar *new_from = g_strdup_printf("%s, %s", *from, tmp);
1541 } else if (cc && !g_ascii_strcasecmp(field, "cc")) {
1543 *cc = decode_uri_gdup(value);
1545 gchar *tmp = decode_uri_gdup(value);
1546 gchar *new_cc = g_strdup_printf("%s, %s", *cc, tmp);
1550 } else if (bcc && !g_ascii_strcasecmp(field, "bcc")) {
1552 *bcc = decode_uri_gdup(value);
1554 gchar *tmp = decode_uri_gdup(value);
1555 gchar *new_bcc = g_strdup_printf("%s, %s", *bcc, tmp);
1559 } else if (subject && !*subject &&
1560 !g_ascii_strcasecmp(field, "subject")) {
1561 *subject = decode_uri_gdup(value);
1562 } else if (body && !*body && !g_ascii_strcasecmp(field, "body")) {
1563 *body = decode_uri_gdup(value);
1564 } else if (body && !*body && !g_ascii_strcasecmp(field, "insert")) {
1565 gchar *tmp = decode_uri_gdup(value);
1566 if (!g_file_get_contents(tmp, body, NULL, NULL)) {
1567 g_warning("couldn't set insert file '%s' in body", value);
1571 } else if (attach && !g_ascii_strcasecmp(field, "attach")) {
1573 gchar *tmp = decode_uri_gdup(value);
1574 for (; forbidden_uris[i]; i++) {
1575 if (strstr(tmp, forbidden_uris[i])) {
1576 g_print("Refusing to attach '%s', potential private data leak\n",
1584 /* attach is correct */
1586 my_att = g_realloc(my_att, (sizeof(char *))*(num_attach+1));
1587 my_att[num_attach-1] = tmp;
1588 my_att[num_attach] = NULL;
1590 } else if (inreplyto && !*inreplyto &&
1591 !g_ascii_strcasecmp(field, "in-reply-to")) {
1592 *inreplyto = decode_uri_gdup(value);
1603 #include <windows.h>
1604 #ifndef CSIDL_APPDATA
1605 #define CSIDL_APPDATA 0x001a
1607 #ifndef CSIDL_LOCAL_APPDATA
1608 #define CSIDL_LOCAL_APPDATA 0x001c
1610 #ifndef CSIDL_FLAG_CREATE
1611 #define CSIDL_FLAG_CREATE 0x8000
1613 #define DIM(v) (sizeof(v)/sizeof((v)[0]))
1617 w32_strerror (int w32_errno)
1619 static char strerr[256];
1620 int ec = (int)GetLastError ();
1624 FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM, NULL, w32_errno,
1625 MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
1626 strerr, DIM (strerr)-1, NULL);
1630 static __inline__ void *
1631 dlopen (const char * name, int flag)
1633 void * hd = LoadLibrary (name);
1637 static __inline__ void *
1638 dlsym (void * hd, const char * sym)
1642 void * fnc = GetProcAddress (hd, sym);
1651 static __inline__ const char *
1654 return w32_strerror (0);
1658 static __inline__ int
1670 w32_shgetfolderpath (HWND a, int b, HANDLE c, DWORD d, LPSTR e)
1672 static int initialized;
1673 static HRESULT (WINAPI * func)(HWND,int,HANDLE,DWORD,LPSTR);
1677 static char *dllnames[] = { "shell32.dll", "shfolder.dll", NULL };
1683 for (i=0, handle = NULL; !handle && dllnames[i]; i++)
1685 handle = dlopen (dllnames[i], RTLD_LAZY);
1688 func = dlsym (handle, "SHGetFolderPathW");
1699 return func (a,b,c,d,e);
1704 /* Returns a static string with the directroy from which the module
1705 has been loaded. Returns an empty string on error. */
1706 static char *w32_get_module_dir(void)
1708 static char *moddir;
1711 char name[MAX_PATH+10];
1714 if ( !GetModuleFileNameA (0, name, sizeof (name)-10) )
1717 p = strrchr (name, '\\');
1723 moddir = g_strdup (name);
1727 #endif /* G_OS_WIN32 */
1729 /* Return a static string with the locale dir. */
1730 const gchar *get_locale_dir(void)
1732 static gchar *loc_dir;
1736 loc_dir = g_strconcat(w32_get_module_dir(), G_DIR_SEPARATOR_S,
1737 "\\share\\locale", NULL);
1740 loc_dir = LOCALEDIR;
1746 const gchar *get_home_dir(void)
1749 static char home_dir_utf16[MAX_PATH] = "";
1750 static gchar *home_dir_utf8 = NULL;
1751 if (home_dir_utf16[0] == '\0') {
1752 if (w32_shgetfolderpath
1753 (NULL, CSIDL_APPDATA|CSIDL_FLAG_CREATE,
1754 NULL, 0, home_dir_utf16) < 0)
1755 strcpy (home_dir_utf16, "C:\\Sylpheed");
1756 home_dir_utf8 = g_utf16_to_utf8 ((const gunichar *)home_dir_utf16, -1, NULL, NULL, NULL);
1758 return home_dir_utf8;
1760 static const gchar *homeenv = NULL;
1765 if (!homeenv && g_getenv("HOME") != NULL)
1766 homeenv = g_strdup(g_getenv("HOME"));
1768 homeenv = g_get_home_dir();
1774 static gchar *claws_rc_dir = NULL;
1775 static gboolean rc_dir_alt = FALSE;
1776 const gchar *get_rc_dir(void)
1779 if (!claws_rc_dir) {
1780 claws_rc_dir = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
1782 debug_print("using default rc_dir %s\n", claws_rc_dir);
1784 return claws_rc_dir;
1787 void set_rc_dir(const gchar *dir)
1789 gchar *canonical_dir;
1790 if (claws_rc_dir != NULL) {
1791 g_print("Error: rc_dir already set\n");
1793 int err = cm_canonicalize_filename(dir, &canonical_dir);
1797 g_print("Error looking for %s: %d(%s)\n",
1798 dir, -err, g_strerror(-err));
1803 claws_rc_dir = canonical_dir;
1805 len = strlen(claws_rc_dir);
1806 if (claws_rc_dir[len - 1] == G_DIR_SEPARATOR)
1807 claws_rc_dir[len - 1] = '\0';
1809 debug_print("set rc_dir to %s\n", claws_rc_dir);
1810 if (!is_dir_exist(claws_rc_dir)) {
1811 if (make_dir_hier(claws_rc_dir) != 0) {
1812 g_print("Error: can't create %s\n",
1820 gboolean rc_dir_is_alt(void) {
1824 const gchar *get_mail_base_dir(void)
1826 return get_home_dir();
1829 const gchar *get_news_cache_dir(void)
1831 static gchar *news_cache_dir = NULL;
1832 if (!news_cache_dir)
1833 news_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1834 NEWS_CACHE_DIR, NULL);
1836 return news_cache_dir;
1839 const gchar *get_imap_cache_dir(void)
1841 static gchar *imap_cache_dir = NULL;
1843 if (!imap_cache_dir)
1844 imap_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1845 IMAP_CACHE_DIR, NULL);
1847 return imap_cache_dir;
1850 const gchar *get_mime_tmp_dir(void)
1852 static gchar *mime_tmp_dir = NULL;
1855 mime_tmp_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1856 MIME_TMP_DIR, NULL);
1858 return mime_tmp_dir;
1861 const gchar *get_template_dir(void)
1863 static gchar *template_dir = NULL;
1866 template_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1867 TEMPLATE_DIR, NULL);
1869 return template_dir;
1873 const gchar *get_cert_file(void)
1875 const gchar *cert_file = NULL;
1877 cert_file = g_strconcat(w32_get_module_dir(),
1878 "\\share\\claws-mail\\",
1879 "ca-certificates.crt",
1885 /* Return the filepath of the claws-mail.desktop file */
1886 const gchar *get_desktop_file(void)
1888 #ifdef DESKTOPFILEPATH
1889 return DESKTOPFILEPATH;
1895 /* Return the default directory for Plugins. */
1896 const gchar *get_plugin_dir(void)
1899 static gchar *plugin_dir = NULL;
1902 plugin_dir = g_strconcat(w32_get_module_dir(),
1903 "\\lib\\claws-mail\\plugins\\",
1907 if (is_dir_exist(PLUGINDIR))
1910 static gchar *plugin_dir = NULL;
1912 plugin_dir = g_strconcat(get_rc_dir(),
1913 G_DIR_SEPARATOR_S, "plugins",
1914 G_DIR_SEPARATOR_S, NULL);
1922 /* Return the default directory for Themes. */
1923 const gchar *get_themes_dir(void)
1925 static gchar *themes_dir = NULL;
1928 themes_dir = g_strconcat(w32_get_module_dir(),
1929 "\\share\\claws-mail\\themes",
1935 const gchar *get_tmp_dir(void)
1937 static gchar *tmp_dir = NULL;
1940 tmp_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1946 gchar *get_tmp_file(void)
1949 static guint32 id = 0;
1951 tmp_file = g_strdup_printf("%s%ctmpfile.%08x",
1952 get_tmp_dir(), G_DIR_SEPARATOR, id++);
1957 const gchar *get_domain_name(void)
1960 static gchar *domain_name = NULL;
1966 if (gethostname(hostname, sizeof(hostname)) != 0) {
1967 perror("gethostname");
1968 domain_name = "localhost";
1970 hostname[sizeof(hostname) - 1] = '\0';
1971 if ((hp = my_gethostbyname(hostname)) == NULL) {
1972 perror("gethostbyname");
1973 domain_name = g_strdup(hostname);
1975 domain_name = g_strdup(hp->h_name);
1978 debug_print("domain name = %s\n", domain_name);
1987 off_t get_file_size(const gchar *file)
1991 if (g_stat(file, &s) < 0) {
1992 FILE_OP_ERROR(file, "stat");
1999 time_t get_file_mtime(const gchar *file)
2003 if (g_stat(file, &s) < 0) {
2004 FILE_OP_ERROR(file, "stat");
2011 off_t get_file_size_as_crlf(const gchar *file)
2015 gchar buf[BUFFSIZE];
2017 if ((fp = g_fopen(file, "rb")) == NULL) {
2018 FILE_OP_ERROR(file, "g_fopen");
2022 while (fgets(buf, sizeof(buf), fp) != NULL) {
2024 size += strlen(buf) + 2;
2028 FILE_OP_ERROR(file, "fgets");
2037 gboolean file_exist(const gchar *file, gboolean allow_fifo)
2044 if (g_stat(file, &s) < 0) {
2045 if (ENOENT != errno) FILE_OP_ERROR(file, "stat");
2049 if (S_ISREG(s.st_mode) || (allow_fifo && S_ISFIFO(s.st_mode)))
2056 /* Test on whether FILE is a relative file name. This is
2057 * straightforward for Unix but more complex for Windows. */
2058 gboolean is_relative_filename(const gchar *file)
2063 if ( *file == '\\' && file[1] == '\\' && strchr (file+2, '\\') )
2064 return FALSE; /* Prefixed with a hostname - this can't
2065 * be a relative name. */
2067 if ( ((*file >= 'a' && *file <= 'z')
2068 || (*file >= 'A' && *file <= 'Z'))
2070 file += 2; /* Skip drive letter. */
2072 return !(*file == '\\' || *file == '/');
2074 return !(*file == G_DIR_SEPARATOR);
2079 gboolean is_dir_exist(const gchar *dir)
2084 return g_file_test(dir, G_FILE_TEST_IS_DIR);
2087 gboolean is_file_entry_exist(const gchar *file)
2092 return g_file_test(file, G_FILE_TEST_EXISTS);
2095 gboolean dirent_is_regular_file(struct dirent *d)
2097 #if !defined(G_OS_WIN32) && defined(HAVE_DIRENT_D_TYPE)
2098 if (d->d_type == DT_REG)
2100 else if (d->d_type != DT_UNKNOWN)
2104 return g_file_test(d->d_name, G_FILE_TEST_IS_REGULAR);
2107 gint change_dir(const gchar *dir)
2109 gchar *prevdir = NULL;
2112 prevdir = g_get_current_dir();
2114 if (g_chdir(dir) < 0) {
2115 FILE_OP_ERROR(dir, "chdir");
2116 if (debug_mode) g_free(prevdir);
2118 } else if (debug_mode) {
2121 cwd = g_get_current_dir();
2122 if (strcmp(prevdir, cwd) != 0)
2123 g_print("current dir: %s\n", cwd);
2131 gint make_dir(const gchar *dir)
2133 if (g_mkdir(dir, S_IRWXU) < 0) {
2134 FILE_OP_ERROR(dir, "mkdir");
2137 if (g_chmod(dir, S_IRWXU) < 0)
2138 FILE_OP_ERROR(dir, "chmod");
2143 gint make_dir_hier(const gchar *dir)
2148 for (p = dir; (p = strchr(p, G_DIR_SEPARATOR)) != NULL; p++) {
2149 parent_dir = g_strndup(dir, p - dir);
2150 if (*parent_dir != '\0') {
2151 if (!is_dir_exist(parent_dir)) {
2152 if (make_dir(parent_dir) < 0) {
2161 if (!is_dir_exist(dir)) {
2162 if (make_dir(dir) < 0)
2169 gint remove_all_files(const gchar *dir)
2172 const gchar *dir_name;
2175 prev_dir = g_get_current_dir();
2177 if (g_chdir(dir) < 0) {
2178 FILE_OP_ERROR(dir, "chdir");
2183 if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2184 g_warning("failed to open directory: %s", dir);
2189 while ((dir_name = g_dir_read_name(dp)) != NULL) {
2190 if (claws_unlink(dir_name) < 0)
2191 FILE_OP_ERROR(dir_name, "unlink");
2196 if (g_chdir(prev_dir) < 0) {
2197 FILE_OP_ERROR(prev_dir, "chdir");
2207 gint remove_numbered_files(const gchar *dir, guint first, guint last)
2210 const gchar *dir_name;
2214 if (first == last) {
2215 /* Skip all the dir reading part. */
2216 gchar *filename = g_strdup_printf("%s%s%u", dir, G_DIR_SEPARATOR_S, first);
2217 if (is_dir_exist(filename)) {
2218 /* a numbered directory with this name exists,
2219 * remove the dot-file instead */
2221 filename = g_strdup_printf("%s%s.%u", dir, G_DIR_SEPARATOR_S, first);
2223 if (claws_unlink(filename) < 0) {
2224 FILE_OP_ERROR(filename, "unlink");
2232 prev_dir = g_get_current_dir();
2234 if (g_chdir(dir) < 0) {
2235 FILE_OP_ERROR(dir, "chdir");
2240 if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2241 g_warning("failed to open directory: %s", dir);
2246 while ((dir_name = g_dir_read_name(dp)) != NULL) {
2247 file_no = to_number(dir_name);
2248 if (file_no > 0 && first <= file_no && file_no <= last) {
2249 if (is_dir_exist(dir_name)) {
2250 gchar *dot_file = g_strdup_printf(".%s", dir_name);
2251 if (is_file_exist(dot_file) && claws_unlink(dot_file) < 0) {
2252 FILE_OP_ERROR(dot_file, "unlink");
2257 if (claws_unlink(dir_name) < 0)
2258 FILE_OP_ERROR(dir_name, "unlink");
2264 if (g_chdir(prev_dir) < 0) {
2265 FILE_OP_ERROR(prev_dir, "chdir");
2275 gint remove_numbered_files_not_in_list(const gchar *dir, GSList *numberlist)
2278 const gchar *dir_name;
2281 GHashTable *wanted_files;
2283 GError *error = NULL;
2285 if (numberlist == NULL)
2288 prev_dir = g_get_current_dir();
2290 if (g_chdir(dir) < 0) {
2291 FILE_OP_ERROR(dir, "chdir");
2296 if ((dp = g_dir_open(".", 0, &error)) == NULL) {
2297 g_message("Couldn't open current directory: %s (%d).\n",
2298 error->message, error->code);
2299 g_error_free(error);
2304 wanted_files = g_hash_table_new(g_direct_hash, g_direct_equal);
2305 for (cur = numberlist; cur != NULL; cur = cur->next) {
2306 /* numberlist->data is expected to be GINT_TO_POINTER */
2307 g_hash_table_insert(wanted_files, cur->data, GINT_TO_POINTER(1));
2310 while ((dir_name = g_dir_read_name(dp)) != NULL) {
2311 file_no = to_number(dir_name);
2312 if (is_dir_exist(dir_name))
2314 if (file_no > 0 && g_hash_table_lookup(wanted_files, GINT_TO_POINTER(file_no)) == NULL) {
2315 debug_print("removing unwanted file %d from %s\n", file_no, dir);
2316 if (is_dir_exist(dir_name)) {
2317 gchar *dot_file = g_strdup_printf(".%s", dir_name);
2318 if (is_file_exist(dot_file) && claws_unlink(dot_file) < 0) {
2319 FILE_OP_ERROR(dot_file, "unlink");
2324 if (claws_unlink(dir_name) < 0)
2325 FILE_OP_ERROR(dir_name, "unlink");
2330 g_hash_table_destroy(wanted_files);
2332 if (g_chdir(prev_dir) < 0) {
2333 FILE_OP_ERROR(prev_dir, "chdir");
2343 gint remove_all_numbered_files(const gchar *dir)
2345 return remove_numbered_files(dir, 0, UINT_MAX);
2348 gint remove_dir_recursive(const gchar *dir)
2352 const gchar *dir_name;
2355 if (g_stat(dir, &s) < 0) {
2356 FILE_OP_ERROR(dir, "stat");
2357 if (ENOENT == errno) return 0;
2361 if (!S_ISDIR(s.st_mode)) {
2362 if (claws_unlink(dir) < 0) {
2363 FILE_OP_ERROR(dir, "unlink");
2370 prev_dir = g_get_current_dir();
2371 /* g_print("prev_dir = %s\n", prev_dir); */
2373 if (!path_cmp(prev_dir, dir)) {
2375 if (g_chdir("..") < 0) {
2376 FILE_OP_ERROR(dir, "chdir");
2379 prev_dir = g_get_current_dir();
2382 if (g_chdir(dir) < 0) {
2383 FILE_OP_ERROR(dir, "chdir");
2388 if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2389 g_warning("failed to open directory: %s", dir);
2395 /* remove all files in the directory */
2396 while ((dir_name = g_dir_read_name(dp)) != NULL) {
2397 /* g_print("removing %s\n", dir_name); */
2399 if (is_dir_exist(dir_name)) {
2402 if ((ret = remove_dir_recursive(dir_name)) < 0) {
2403 g_warning("can't remove directory: %s", dir_name);
2407 if (claws_unlink(dir_name) < 0)
2408 FILE_OP_ERROR(dir_name, "unlink");
2414 if (g_chdir(prev_dir) < 0) {
2415 FILE_OP_ERROR(prev_dir, "chdir");
2422 if (g_rmdir(dir) < 0) {
2423 FILE_OP_ERROR(dir, "rmdir");
2430 gint rename_force(const gchar *oldpath, const gchar *newpath)
2433 if (!is_file_entry_exist(oldpath)) {
2437 if (is_file_exist(newpath)) {
2438 if (claws_unlink(newpath) < 0)
2439 FILE_OP_ERROR(newpath, "unlink");
2442 return g_rename(oldpath, newpath);
2446 * Append src file body to the tail of dest file.
2447 * Now keep_backup has no effects.
2449 gint append_file(const gchar *src, const gchar *dest, gboolean keep_backup)
2451 FILE *src_fp, *dest_fp;
2455 gboolean err = FALSE;
2457 if ((src_fp = g_fopen(src, "rb")) == NULL) {
2458 FILE_OP_ERROR(src, "g_fopen");
2462 if ((dest_fp = g_fopen(dest, "ab")) == NULL) {
2463 FILE_OP_ERROR(dest, "g_fopen");
2468 if (change_file_mode_rw(dest_fp, dest) < 0) {
2469 FILE_OP_ERROR(dest, "chmod");
2470 g_warning("can't change file mode: %s", dest);
2473 while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
2474 if (n_read < sizeof(buf) && ferror(src_fp))
2476 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2477 g_warning("writing to %s failed.", dest);
2485 if (ferror(src_fp)) {
2486 FILE_OP_ERROR(src, "fread");
2490 if (fclose(dest_fp) == EOF) {
2491 FILE_OP_ERROR(dest, "fclose");
2503 gint copy_file(const gchar *src, const gchar *dest, gboolean keep_backup)
2505 FILE *src_fp, *dest_fp;
2508 gchar *dest_bak = NULL;
2509 gboolean err = FALSE;
2511 if ((src_fp = g_fopen(src, "rb")) == NULL) {
2512 FILE_OP_ERROR(src, "g_fopen");
2515 if (is_file_exist(dest)) {
2516 dest_bak = g_strconcat(dest, ".bak", NULL);
2517 if (rename_force(dest, dest_bak) < 0) {
2518 FILE_OP_ERROR(dest, "rename");
2525 if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2526 FILE_OP_ERROR(dest, "g_fopen");
2529 if (rename_force(dest_bak, dest) < 0)
2530 FILE_OP_ERROR(dest_bak, "rename");
2536 if (change_file_mode_rw(dest_fp, dest) < 0) {
2537 FILE_OP_ERROR(dest, "chmod");
2538 g_warning("can't change file mode: %s", dest);
2541 while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
2542 if (n_read < sizeof(buf) && ferror(src_fp))
2544 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2545 g_warning("writing to %s failed.", dest);
2550 if (rename_force(dest_bak, dest) < 0)
2551 FILE_OP_ERROR(dest_bak, "rename");
2558 if (ferror(src_fp)) {
2559 FILE_OP_ERROR(src, "fread");
2563 if (fclose(dest_fp) == EOF) {
2564 FILE_OP_ERROR(dest, "fclose");
2571 if (rename_force(dest_bak, dest) < 0)
2572 FILE_OP_ERROR(dest_bak, "rename");
2578 if (keep_backup == FALSE && dest_bak)
2579 claws_unlink(dest_bak);
2586 gint move_file(const gchar *src, const gchar *dest, gboolean overwrite)
2588 if (overwrite == FALSE && is_file_exist(dest)) {
2589 g_warning("move_file(): file %s already exists.", dest);
2593 if (rename_force(src, dest) == 0) return 0;
2595 if (EXDEV != errno) {
2596 FILE_OP_ERROR(src, "rename");
2600 if (copy_file(src, dest, FALSE) < 0) return -1;
2607 gint copy_file_part_to_fp(FILE *fp, off_t offset, size_t length, FILE *dest_fp)
2610 gint bytes_left, to_read;
2613 if (fseek(fp, offset, SEEK_SET) < 0) {
2618 bytes_left = length;
2619 to_read = MIN(bytes_left, sizeof(buf));
2621 while ((n_read = fread(buf, sizeof(gchar), to_read, fp)) > 0) {
2622 if (n_read < to_read && ferror(fp))
2624 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2627 bytes_left -= n_read;
2628 if (bytes_left == 0)
2630 to_read = MIN(bytes_left, sizeof(buf));
2641 gint copy_file_part(FILE *fp, off_t offset, size_t length, const gchar *dest)
2644 gboolean err = FALSE;
2646 if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2647 FILE_OP_ERROR(dest, "g_fopen");
2651 if (change_file_mode_rw(dest_fp, dest) < 0) {
2652 FILE_OP_ERROR(dest, "chmod");
2653 g_warning("can't change file mode: %s", dest);
2656 if (copy_file_part_to_fp(fp, offset, length, dest_fp) < 0)
2659 if (!err && fclose(dest_fp) == EOF) {
2660 FILE_OP_ERROR(dest, "fclose");
2665 g_warning("writing to %s failed.", dest);
2673 /* convert line endings into CRLF. If the last line doesn't end with
2674 * linebreak, add it.
2676 gchar *canonicalize_str(const gchar *str)
2682 for (p = str; *p != '\0'; ++p) {
2689 if (p == str || *(p - 1) != '\n')
2692 out = outp = g_malloc(new_len + 1);
2693 for (p = str; *p != '\0'; ++p) {
2700 if (p == str || *(p - 1) != '\n') {
2709 gint canonicalize_file(const gchar *src, const gchar *dest)
2711 FILE *src_fp, *dest_fp;
2712 gchar buf[BUFFSIZE];
2714 gboolean err = FALSE;
2715 gboolean last_linebreak = FALSE;
2717 if (src == NULL || dest == NULL)
2720 if ((src_fp = g_fopen(src, "rb")) == NULL) {
2721 FILE_OP_ERROR(src, "g_fopen");
2725 if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2726 FILE_OP_ERROR(dest, "g_fopen");
2731 if (change_file_mode_rw(dest_fp, dest) < 0) {
2732 FILE_OP_ERROR(dest, "chmod");
2733 g_warning("can't change file mode: %s", dest);
2736 while (fgets(buf, sizeof(buf), src_fp) != NULL) {
2740 if (len == 0) break;
2741 last_linebreak = FALSE;
2743 if (buf[len - 1] != '\n') {
2744 last_linebreak = TRUE;
2745 r = fputs(buf, dest_fp);
2746 } else if (len > 1 && buf[len - 1] == '\n' && buf[len - 2] == '\r') {
2747 r = fputs(buf, dest_fp);
2750 r = fwrite(buf, 1, len - 1, dest_fp);
2755 r = fputs("\r\n", dest_fp);
2759 g_warning("writing to %s failed.", dest);
2767 if (last_linebreak == TRUE) {
2768 if (fputs("\r\n", dest_fp) == EOF)
2772 if (ferror(src_fp)) {
2773 FILE_OP_ERROR(src, "fgets");
2777 if (fclose(dest_fp) == EOF) {
2778 FILE_OP_ERROR(dest, "fclose");
2790 gint canonicalize_file_replace(const gchar *file)
2794 tmp_file = get_tmp_file();
2796 if (canonicalize_file(file, tmp_file) < 0) {
2801 if (move_file(tmp_file, file, TRUE) < 0) {
2802 g_warning("can't replace file: %s", file);
2803 claws_unlink(tmp_file);
2812 gchar *normalize_newlines(const gchar *str)
2817 out = outp = g_malloc(strlen(str) + 1);
2818 for (p = str; *p != '\0'; ++p) {
2820 if (*(p + 1) != '\n')
2831 gchar *get_outgoing_rfc2822_str(FILE *fp)
2833 gchar buf[BUFFSIZE];
2837 str = g_string_new(NULL);
2839 /* output header part */
2840 while (fgets(buf, sizeof(buf), fp) != NULL) {
2842 if (!g_ascii_strncasecmp(buf, "Bcc:", 4)) {
2849 else if (next != ' ' && next != '\t') {
2853 if (fgets(buf, sizeof(buf), fp) == NULL)
2857 g_string_append(str, buf);
2858 g_string_append(str, "\r\n");
2864 /* output body part */
2865 while (fgets(buf, sizeof(buf), fp) != NULL) {
2868 g_string_append_c(str, '.');
2869 g_string_append(str, buf);
2870 g_string_append(str, "\r\n");
2874 g_string_free(str, FALSE);
2880 * Create a new boundary in a way that it is very unlikely that this
2881 * will occur in the following text. It would be easy to ensure
2882 * uniqueness if everything is either quoted-printable or base64
2883 * encoded (note that conversion is allowed), but because MIME bodies
2884 * may be nested, it may happen that the same boundary has already
2887 * boundary := 0*69<bchars> bcharsnospace
2888 * bchars := bcharsnospace / " "
2889 * bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" /
2890 * "+" / "_" / "," / "-" / "." /
2891 * "/" / ":" / "=" / "?"
2893 * some special characters removed because of buggy MTAs
2896 gchar *generate_mime_boundary(const gchar *prefix)
2898 static gchar tbl[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
2899 "abcdefghijklmnopqrstuvwxyz"
2904 for (i = 0; i < sizeof(buf_uniq) - 1; i++)
2905 buf_uniq[i] = tbl[g_random_int_range(0, sizeof(tbl) - 1)];
2908 return g_strdup_printf("%s_/%s", prefix ? prefix : "MP",
2912 gint change_file_mode_rw(FILE *fp, const gchar *file)
2915 return fchmod(fileno(fp), S_IRUSR|S_IWUSR);
2917 return g_chmod(file, S_IRUSR|S_IWUSR);
2921 FILE *my_tmpfile(void)
2923 const gchar suffix[] = ".XXXXXX";
2924 const gchar *tmpdir;
2926 const gchar *progname;
2935 tmpdir = get_tmp_dir();
2936 tmplen = strlen(tmpdir);
2937 progname = g_get_prgname();
2938 if (progname == NULL)
2939 progname = "claws-mail";
2940 proglen = strlen(progname);
2941 Xalloca(fname, tmplen + 1 + proglen + sizeof(suffix),
2944 memcpy(fname, tmpdir, tmplen);
2945 fname[tmplen] = G_DIR_SEPARATOR;
2946 memcpy(fname + tmplen + 1, progname, proglen);
2947 memcpy(fname + tmplen + 1 + proglen, suffix, sizeof(suffix));
2949 fd = g_mkstemp(fname);
2954 claws_unlink(fname);
2956 /* verify that we can write in the file after unlinking */
2957 if (write(fd, buf, 1) < 0) {
2964 fp = fdopen(fd, "w+b");
2975 FILE *get_tmpfile_in_dir(const gchar *dir, gchar **filename)
2978 *filename = g_strdup_printf("%s%cclaws.XXXXXX", dir, G_DIR_SEPARATOR);
2979 fd = g_mkstemp(*filename);
2982 return fdopen(fd, "w+");
2985 FILE *str_open_as_stream(const gchar *str)
2990 cm_return_val_if_fail(str != NULL, NULL);
2994 FILE_OP_ERROR("str_open_as_stream", "my_tmpfile");
2999 if (len == 0) return fp;
3001 if (fwrite(str, 1, len, fp) != len) {
3002 FILE_OP_ERROR("str_open_as_stream", "fwrite");
3011 gint str_write_to_file(const gchar *str, const gchar *file)
3016 cm_return_val_if_fail(str != NULL, -1);
3017 cm_return_val_if_fail(file != NULL, -1);
3019 if ((fp = g_fopen(file, "wb")) == NULL) {
3020 FILE_OP_ERROR(file, "g_fopen");
3030 if (fwrite(str, 1, len, fp) != len) {
3031 FILE_OP_ERROR(file, "fwrite");
3037 if (fclose(fp) == EOF) {
3038 FILE_OP_ERROR(file, "fclose");
3046 static gchar *file_read_stream_to_str_full(FILE *fp, gboolean recode)
3053 cm_return_val_if_fail(fp != NULL, NULL);
3055 array = g_byte_array_new();
3057 while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
3058 if (n_read < sizeof(buf) && ferror(fp))
3060 g_byte_array_append(array, buf, n_read);
3064 FILE_OP_ERROR("file stream", "fread");
3065 g_byte_array_free(array, TRUE);
3070 g_byte_array_append(array, buf, 1);
3071 str = (gchar *)array->data;
3072 g_byte_array_free(array, FALSE);
3074 if (recode && !g_utf8_validate(str, -1, NULL)) {
3075 const gchar *src_codeset, *dest_codeset;
3077 src_codeset = conv_get_locale_charset_str();
3078 dest_codeset = CS_UTF_8;
3079 tmp = conv_codeset_strdup(str, src_codeset, dest_codeset);
3087 static gchar *file_read_to_str_full(const gchar *file, gboolean recode)
3094 struct timeval timeout = {1, 0};
3099 cm_return_val_if_fail(file != NULL, NULL);
3101 if (g_stat(file, &s) != 0) {
3102 FILE_OP_ERROR(file, "stat");
3105 if (S_ISDIR(s.st_mode)) {
3106 g_warning("%s: is a directory", file);
3111 fp = g_fopen (file, "rb");
3113 FILE_OP_ERROR(file, "open");
3117 /* test whether the file is readable without blocking */
3118 fd = g_open(file, O_RDONLY | O_NONBLOCK, 0);
3120 FILE_OP_ERROR(file, "open");
3127 /* allow for one second */
3128 err = select(fd+1, &fds, NULL, NULL, &timeout);
3129 if (err <= 0 || !FD_ISSET(fd, &fds)) {
3131 FILE_OP_ERROR(file, "select");
3133 g_warning("%s: doesn't seem readable", file);
3139 /* Now clear O_NONBLOCK */
3140 if ((fflags = fcntl(fd, F_GETFL)) < 0) {
3141 FILE_OP_ERROR(file, "fcntl (F_GETFL)");
3145 if (fcntl(fd, F_SETFL, (fflags & ~O_NONBLOCK)) < 0) {
3146 FILE_OP_ERROR(file, "fcntl (F_SETFL)");
3151 /* get the FILE pointer */
3152 fp = fdopen(fd, "rb");
3155 FILE_OP_ERROR(file, "fdopen");
3156 close(fd); /* if fp isn't NULL, we'll use fclose instead! */
3161 str = file_read_stream_to_str_full(fp, recode);
3168 gchar *file_read_to_str(const gchar *file)
3170 return file_read_to_str_full(file, TRUE);
3172 gchar *file_read_stream_to_str(FILE *fp)
3174 return file_read_stream_to_str_full(fp, TRUE);
3177 gchar *file_read_to_str_no_recode(const gchar *file)
3179 return file_read_to_str_full(file, FALSE);
3181 gchar *file_read_stream_to_str_no_recode(FILE *fp)
3183 return file_read_stream_to_str_full(fp, FALSE);
3186 char *fgets_crlf(char *buf, int size, FILE *stream)
3188 gboolean is_cr = FALSE;
3189 gboolean last_was_cr = FALSE;
3194 while (--size > 0 && (c = getc(stream)) != EOF)
3197 is_cr = (c == '\r');
3207 last_was_cr = is_cr;
3209 if (c == EOF && cs == buf)
3217 static gint execute_async(gchar *const argv[])
3219 cm_return_val_if_fail(argv != NULL && argv[0] != NULL, -1);
3221 if (g_spawn_async(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH,
3222 NULL, NULL, NULL, FALSE) == FALSE) {
3223 g_warning("couldn't execute command: %s", argv[0]);
3230 static gint execute_sync(gchar *const argv[])
3234 cm_return_val_if_fail(argv != NULL && argv[0] != NULL, -1);
3237 if (g_spawn_sync(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH,
3238 NULL, NULL, NULL, NULL, &status, NULL) == FALSE) {
3239 g_warning("couldn't execute command: %s", argv[0]);
3243 if (WIFEXITED(status))
3244 return WEXITSTATUS(status);
3248 if (g_spawn_sync(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH|
3249 G_SPAWN_CHILD_INHERITS_STDIN|G_SPAWN_LEAVE_DESCRIPTORS_OPEN,
3250 NULL, NULL, NULL, NULL, &status, NULL) == FALSE) {
3251 g_warning("couldn't execute command: %s", argv[0]);
3259 gint execute_command_line(const gchar *cmdline, gboolean async)
3264 debug_print("execute_command_line(): executing: %s\n", cmdline?cmdline:"(null)");
3266 argv = strsplit_with_quote(cmdline, " ", 0);
3269 ret = execute_async(argv);
3271 ret = execute_sync(argv);
3278 gchar *get_command_output(const gchar *cmdline)
3280 gchar *child_stdout;
3283 cm_return_val_if_fail(cmdline != NULL, NULL);
3285 debug_print("get_command_output(): executing: %s\n", cmdline);
3287 if (g_spawn_command_line_sync(cmdline, &child_stdout, NULL, &status,
3289 g_warning("couldn't execute command: %s", cmdline);
3293 return child_stdout;
3296 static gint is_unchanged_uri_char(char c)
3307 static void encode_uri(gchar *encoded_uri, gint bufsize, const gchar *uri)
3313 for(i = 0; i < strlen(uri) ; i++) {
3314 if (is_unchanged_uri_char(uri[i])) {
3315 if (k + 2 >= bufsize)
3317 encoded_uri[k++] = uri[i];
3320 char * hexa = "0123456789ABCDEF";
3322 if (k + 4 >= bufsize)
3324 encoded_uri[k++] = '%';
3325 encoded_uri[k++] = hexa[uri[i] / 16];
3326 encoded_uri[k++] = hexa[uri[i] % 16];
3332 gint open_uri(const gchar *uri, const gchar *cmdline)
3336 gchar buf[BUFFSIZE];
3338 gchar encoded_uri[BUFFSIZE];
3339 cm_return_val_if_fail(uri != NULL, -1);
3341 /* an option to choose whether to use encode_uri or not ? */
3342 encode_uri(encoded_uri, BUFFSIZE, uri);
3345 (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
3346 !strchr(p + 2, '%'))
3347 g_snprintf(buf, sizeof(buf), cmdline, encoded_uri);
3350 g_warning("Open URI command-line is invalid "
3351 "(there must be only one '%%s'): %s",
3353 g_snprintf(buf, sizeof(buf), DEFAULT_BROWSER_CMD, encoded_uri);
3356 execute_command_line(buf, TRUE);
3358 ShellExecute(NULL, "open", uri, NULL, NULL, SW_SHOW);
3363 gint open_txt_editor(const gchar *filepath, const gchar *cmdline)
3365 gchar buf[BUFFSIZE];
3368 cm_return_val_if_fail(filepath != NULL, -1);
3371 (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
3372 !strchr(p + 2, '%'))
3373 g_snprintf(buf, sizeof(buf), cmdline, filepath);
3376 g_warning("Open Text Editor command-line is invalid "
3377 "(there must be only one '%%s'): %s",
3379 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, filepath);
3382 execute_command_line(buf, TRUE);
3387 time_t remote_tzoffset_sec(const gchar *zone)
3389 static gchar ustzstr[] = "PSTPDTMSTMDTCSTCDTESTEDT";
3395 time_t remoteoffset;
3397 strncpy(zone3, zone, 3);
3401 if (sscanf(zone, "%c%d", &c, &offset) == 2 &&
3402 (c == '+' || c == '-')) {
3403 remoteoffset = ((offset / 100) * 60 + (offset % 100)) * 60;
3405 remoteoffset = -remoteoffset;
3406 } else if (!strncmp(zone, "UT" , 2) ||
3407 !strncmp(zone, "GMT", 3)) {
3409 } else if (strlen(zone3) == 3) {
3410 for (p = ustzstr; *p != '\0'; p += 3) {
3411 if (!g_ascii_strncasecmp(p, zone3, 3)) {
3412 iustz = ((gint)(p - ustzstr) / 3 + 1) / 2 - 8;
3413 remoteoffset = iustz * 3600;
3419 } else if (strlen(zone3) == 1) {
3421 case 'Z': remoteoffset = 0; break;
3422 case 'A': remoteoffset = -1; break;
3423 case 'B': remoteoffset = -2; break;
3424 case 'C': remoteoffset = -3; break;
3425 case 'D': remoteoffset = -4; break;
3426 case 'E': remoteoffset = -5; break;
3427 case 'F': remoteoffset = -6; break;
3428 case 'G': remoteoffset = -7; break;
3429 case 'H': remoteoffset = -8; break;
3430 case 'I': remoteoffset = -9; break;
3431 case 'K': remoteoffset = -10; break; /* J is not used */
3432 case 'L': remoteoffset = -11; break;
3433 case 'M': remoteoffset = -12; break;
3434 case 'N': remoteoffset = 1; break;
3435 case 'O': remoteoffset = 2; break;
3436 case 'P': remoteoffset = 3; break;
3437 case 'Q': remoteoffset = 4; break;
3438 case 'R': remoteoffset = 5; break;
3439 case 'S': remoteoffset = 6; break;
3440 case 'T': remoteoffset = 7; break;
3441 case 'U': remoteoffset = 8; break;
3442 case 'V': remoteoffset = 9; break;
3443 case 'W': remoteoffset = 10; break;
3444 case 'X': remoteoffset = 11; break;
3445 case 'Y': remoteoffset = 12; break;
3446 default: remoteoffset = 0; break;
3448 remoteoffset = remoteoffset * 3600;
3452 return remoteoffset;
3455 time_t tzoffset_sec(time_t *now)
3459 struct tm buf1, buf2;
3461 if (now && *now < 0)
3464 gmt = *gmtime_r(now, &buf1);
3465 lt = localtime_r(now, &buf2);
3467 off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
3469 if (lt->tm_year < gmt.tm_year)
3471 else if (lt->tm_year > gmt.tm_year)
3473 else if (lt->tm_yday < gmt.tm_yday)
3475 else if (lt->tm_yday > gmt.tm_yday)
3478 if (off >= 24 * 60) /* should be impossible */
3479 off = 23 * 60 + 59; /* if not, insert silly value */
3480 if (off <= -24 * 60)
3481 off = -(23 * 60 + 59);
3486 /* calculate timezone offset */
3487 gchar *tzoffset(time_t *now)
3489 static gchar offset_string[6];
3493 struct tm buf1, buf2;
3495 if (now && *now < 0)
3498 gmt = *gmtime_r(now, &buf1);
3499 lt = localtime_r(now, &buf2);
3501 off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
3503 if (lt->tm_year < gmt.tm_year)
3505 else if (lt->tm_year > gmt.tm_year)
3507 else if (lt->tm_yday < gmt.tm_yday)
3509 else if (lt->tm_yday > gmt.tm_yday)
3517 if (off >= 24 * 60) /* should be impossible */
3518 off = 23 * 60 + 59; /* if not, insert silly value */
3520 sprintf(offset_string, "%c%02d%02d", sign, off / 60, off % 60);
3522 return offset_string;
3525 void get_rfc822_date(gchar *buf, gint len)
3529 gchar day[4], mon[4];
3530 gint dd, hh, mm, ss, yyyy;
3532 gchar buf2[BUFFSIZE];
3535 lt = localtime_r(&t, &buf1);
3537 sscanf(asctime_r(lt, buf2), "%3s %3s %d %d:%d:%d %d\n",
3538 day, mon, &dd, &hh, &mm, &ss, &yyyy);
3540 g_snprintf(buf, len, "%s, %d %s %d %02d:%02d:%02d %s",
3541 day, dd, mon, yyyy, hh, mm, ss, tzoffset(&t));
3544 void debug_set_mode(gboolean mode)
3549 gboolean debug_get_mode(void)
3554 void debug_print_real(const gchar *format, ...)
3557 gchar buf[BUFFSIZE];
3559 if (!debug_mode) return;
3561 va_start(args, format);
3562 g_vsnprintf(buf, sizeof(buf), format, args);
3569 const char * debug_srcname(const char *file)
3571 const char *s = strrchr (file, '/');
3576 void * subject_table_lookup(GHashTable *subject_table, gchar * subject)
3578 if (subject == NULL)
3581 subject += subject_get_prefix_length(subject);
3583 return g_hash_table_lookup(subject_table, subject);
3586 void subject_table_insert(GHashTable *subject_table, gchar * subject,
3589 if (subject == NULL || *subject == 0)
3591 subject += subject_get_prefix_length(subject);
3592 g_hash_table_insert(subject_table, subject, data);
3595 void subject_table_remove(GHashTable *subject_table, gchar * subject)
3597 if (subject == NULL)
3600 subject += subject_get_prefix_length(subject);
3601 g_hash_table_remove(subject_table, subject);
3605 static regex_t u_regex;
3606 static gboolean u_init_;
3609 void utils_free_regex(void)
3620 *\brief Check if a string is prefixed with known (combinations)
3621 * of prefixes. The function assumes that each prefix
3622 * is terminated by zero or exactly _one_ space.
3624 *\param str String to check for a prefixes
3626 *\return int Number of chars in the prefix that should be skipped
3627 * for a "clean" subject line. If no prefix was found, 0
3630 int subject_get_prefix_length(const gchar *subject)
3633 /*!< Array with allowable reply prefixes regexps. */
3634 static const gchar * const prefixes[] = {
3635 "Re\\:", /* "Re:" */
3636 "Re\\[[1-9][0-9]*\\]\\:", /* "Re[XXX]:" (non-conforming news mail clients) */
3637 "Antw\\:", /* "Antw:" (Dutch / German Outlook) */
3638 "Aw\\:", /* "Aw:" (German) */
3639 "Antwort\\:", /* "Antwort:" (German Lotus Notes) */
3640 "Res\\:", /* "Res:" (Spanish/Brazilian Outlook) */
3641 "Fw\\:", /* "Fw:" Forward */
3642 "Fwd\\:", /* "Fwd:" Forward */
3643 "Enc\\:", /* "Enc:" Forward (Brazilian Outlook) */
3644 "Odp\\:", /* "Odp:" Re (Polish Outlook) */
3645 "Rif\\:", /* "Rif:" (Italian Outlook) */
3646 "Sv\\:", /* "Sv" (Norwegian) */
3647 "Vs\\:", /* "Vs" (Norwegian) */
3648 "Ad\\:", /* "Ad" (Norwegian) */
3649 "\347\255\224\345\244\215\\:", /* "Re" (Chinese, UTF-8) */
3650 "R\303\251f\\. \\:", /* "R�f. :" (French Lotus Notes) */
3651 "Re \\:", /* "Re :" (French Yahoo Mail) */
3654 const int PREFIXES = sizeof prefixes / sizeof prefixes[0];
3658 if (!subject) return 0;
3659 if (!*subject) return 0;
3662 GString *s = g_string_new("");
3664 for (n = 0; n < PREFIXES; n++)
3665 /* Terminate each prefix regexpression by a
3666 * "\ ?" (zero or ONE space), and OR them */
3667 g_string_append_printf(s, "(%s\\ ?)%s",
3672 g_string_prepend(s, "(");
3673 g_string_append(s, ")+"); /* match at least once */
3674 g_string_prepend(s, "^\\ *"); /* from beginning of line */
3677 /* We now have something like "^\ *((PREFIX1\ ?)|(PREFIX2\ ?))+"
3678 * TODO: Should this be "^\ *(((PREFIX1)|(PREFIX2))\ ?)+" ??? */
3679 if (regcomp(&u_regex, s->str, REG_EXTENDED | REG_ICASE)) {
3680 debug_print("Error compiling regexp %s\n", s->str);
3681 g_string_free(s, TRUE);
3685 g_string_free(s, TRUE);
3689 if (!regexec(&u_regex, subject, 1, &pos, 0) && pos.rm_so != -1)
3694 /*!< Array with allowable reply prefixes regexps. */
3695 static const gchar * const prefixes[] = {
3697 "antw:", /* "Antw:" (Dutch / German Outlook) */
3698 "aw:", /* "Aw:" (German) */
3699 "antwort:", /* "Antwort:" (German Lotus Notes) */
3700 "res:", /* "Res:" (Spanish/Brazilian Outlook) */
3701 "fw:", /* "Fw:" Forward */
3702 "fwd:", /* "Fwd:" Forward */
3703 "enc:", /* "Enc:" Forward (Brazilian Outlook) */
3704 "odp:", /* "Odp:" Re (Polish Outlook) */
3705 "rif:", /* "Rif:" (Italian Outlook) */
3706 "sv:", /* "Sv" (Norwegian) */
3707 "vs:", /* "Vs" (Norwegian) */
3708 "ad:", /* "Ad" (Norwegian) */
3709 "R\303\251f. :", /* "R�f. :" (French Lotus Notes) */
3710 "Re :", /* "Re :" (French Yahoo Mail) */
3713 const int PREFIXES = sizeof prefixes / sizeof prefixes[0];
3716 if (!subject) return 0;
3717 if (!*subject) return 0;
3719 for (n = 0; n < PREFIXES; n++) {
3720 int len = strlen(prefixes[n]);
3721 if (!strncasecmp(subject, prefixes[n], len)) {
3722 if (subject[len] == ' ')
3731 static guint g_stricase_hash(gconstpointer gptr)
3733 guint hash_result = 0;
3736 for (str = gptr; str && *str; str++) {
3737 hash_result += toupper(*str);
3743 static gint g_stricase_equal(gconstpointer gptr1, gconstpointer gptr2)
3745 const char *str1 = gptr1;
3746 const char *str2 = gptr2;
3748 return !strcasecmp(str1, str2);
3751 gint g_int_compare(gconstpointer a, gconstpointer b)
3753 return GPOINTER_TO_INT(a) - GPOINTER_TO_INT(b);
3756 gchar *generate_msgid(gchar *buf, gint len, gchar *user_addr)
3764 lt = localtime_r(&t, &buft);
3766 if (user_addr != NULL)
3767 addr = g_strdup_printf(".%s", user_addr);
3768 else if (strlen(buf) != 0)
3769 addr = g_strdup_printf("@%s", buf);
3771 addr = g_strdup_printf("@%s", get_domain_name());
3773 /* Replace all @ but the last one in addr, with underscores.
3774 * RFC 2822 States that msg-id syntax only allows one @.
3776 while (strchr(addr, '@') != NULL && strchr(addr, '@') != strrchr(addr, '@'))
3777 *(strchr(addr, '@')) = '_';
3779 g_snprintf(buf, len, "%04d%02d%02d%02d%02d%02d.%08x%s",
3780 lt->tm_year + 1900, lt->tm_mon + 1,
3781 lt->tm_mday, lt->tm_hour,
3782 lt->tm_min, lt->tm_sec,
3783 (guint) rand(), addr);
3790 quote_cmd_argument()
3792 return a quoted string safely usable in argument of a command.
3794 code is extracted and adapted from etPan! project -- DINH V. Ho�.
3797 gint quote_cmd_argument(gchar * result, guint size,
3807 for(p = path ; * p != '\0' ; p ++) {
3809 if (isalnum((guchar)*p) || (* p == '/')) {
3810 if (remaining > 0) {
3816 result[size - 1] = '\0';
3821 if (remaining >= 2) {
3829 result[size - 1] = '\0';
3834 if (remaining > 0) {
3838 result[size - 1] = '\0';
3852 static void g_node_map_recursive(GNode *node, gpointer data)
3854 GNodeMapData *mapdata = (GNodeMapData *) data;
3856 GNodeMapData newmapdata;
3859 newdata = mapdata->func(node->data, mapdata->data);
3860 if (newdata != NULL) {
3861 newnode = g_node_new(newdata);
3862 g_node_append(mapdata->parent, newnode);
3864 newmapdata.parent = newnode;
3865 newmapdata.func = mapdata->func;
3866 newmapdata.data = mapdata->data;
3868 g_node_children_foreach(node, G_TRAVERSE_ALL, g_node_map_recursive, &newmapdata);
3872 GNode *g_node_map(GNode *node, GNodeMapFunc func, gpointer data)
3875 GNodeMapData mapdata;
3877 cm_return_val_if_fail(node != NULL, NULL);
3878 cm_return_val_if_fail(func != NULL, NULL);
3880 root = g_node_new(func(node->data, data));
3882 mapdata.parent = root;
3883 mapdata.func = func;
3884 mapdata.data = data;
3886 g_node_children_foreach(node, G_TRAVERSE_ALL, g_node_map_recursive, &mapdata);
3891 #define HEX_TO_INT(val, hex) \
3895 if ('0' <= c && c <= '9') { \
3897 } else if ('a' <= c && c <= 'f') { \
3898 val = c - 'a' + 10; \
3899 } else if ('A' <= c && c <= 'F') { \
3900 val = c - 'A' + 10; \
3906 gboolean get_hex_value(guchar *out, gchar c1, gchar c2)
3913 if (hi == -1 || lo == -1)
3916 *out = (hi << 4) + lo;
3920 #define INT_TO_HEX(hex, val) \
3923 hex = '0' + (val); \
3925 hex = 'A' + (val) - 10; \
3928 void get_hex_str(gchar *out, guchar ch)
3932 INT_TO_HEX(hex, ch >> 4);
3934 INT_TO_HEX(hex, ch & 0x0f);
3940 #define G_PRINT_REF 1 == 1 ? (void) 0 : (void)
3942 #define G_PRINT_REF g_print
3946 *\brief Register ref counted pointer. It is based on GBoxed, so should
3947 * work with anything that uses the GType system. The semantics
3948 * are similar to a C++ auto pointer, with the exception that
3949 * C doesn't have automatic closure (calling destructors) when
3950 * exiting a block scope.
3951 * Use the \ref G_TYPE_AUTO_POINTER macro instead of calling this
3952 * function directly.
3954 *\return GType A GType type.
3956 GType g_auto_pointer_register(void)
3958 static GType auto_pointer_type;
3959 if (!auto_pointer_type)
3961 g_boxed_type_register_static
3962 ("G_TYPE_AUTO_POINTER",
3963 (GBoxedCopyFunc) g_auto_pointer_copy,
3964 (GBoxedFreeFunc) g_auto_pointer_free);
3965 return auto_pointer_type;
3969 *\brief Structure with g_new() allocated pointer guarded by the
3972 typedef struct AutoPointerRef {
3973 void (*free) (gpointer);
3979 *\brief The auto pointer opaque structure that references the
3980 * pointer guard block.
3982 typedef struct AutoPointer {
3983 AutoPointerRef *ref;
3984 gpointer ptr; /*!< access to protected pointer */
3988 *\brief Creates an auto pointer for a g_new()ed pointer. Example:
3992 * ... tell gtk_list_store it should use a G_TYPE_AUTO_POINTER
3993 * ... when assigning, copying and freeing storage elements
3995 * gtk_list_store_new(N_S_COLUMNS,
3996 * G_TYPE_AUTO_POINTER,
4000 * Template *precious_data = g_new0(Template, 1);
4001 * g_pointer protect = g_auto_pointer_new(precious_data);
4003 * gtk_list_store_set(container, &iter,
4007 * ... the gtk_list_store has copied the pointer and
4008 * ... incremented its reference count, we should free
4009 * ... the auto pointer (in C++ a destructor would do
4010 * ... this for us when leaving block scope)
4012 * g_auto_pointer_free(protect);
4014 * ... gtk_list_store_set() now manages the data. When
4015 * ... *explicitly* requesting a pointer from the list
4016 * ... store, don't forget you get a copy that should be
4017 * ... freed with g_auto_pointer_free() eventually.
4021 *\param pointer Pointer to be guarded.
4023 *\return GAuto * Pointer that should be used in containers with
4026 GAuto *g_auto_pointer_new(gpointer p)
4028 AutoPointerRef *ref;
4034 ref = g_new0(AutoPointerRef, 1);
4035 ptr = g_new0(AutoPointer, 1);
4045 G_PRINT_REF ("XXXX ALLOC(%lx)\n", p);
4051 *\brief Allocate an autopointer using the passed \a free function to
4052 * free the guarded pointer
4054 GAuto *g_auto_pointer_new_with_free(gpointer p, GFreeFunc free_)
4061 aptr = g_auto_pointer_new(p);
4062 aptr->ref->free = free_;
4066 gpointer g_auto_pointer_get_ptr(GAuto *auto_ptr)
4068 if (auto_ptr == NULL)
4070 return ((AutoPointer *) auto_ptr)->ptr;
4074 *\brief Copies an auto pointer by. It's mostly not necessary
4075 * to call this function directly, unless you copy/assign
4076 * the guarded pointer.
4078 *\param auto_ptr Auto pointer returned by previous call to
4079 * g_auto_pointer_new_XXX()
4081 *\return gpointer An auto pointer
4083 GAuto *g_auto_pointer_copy(GAuto *auto_ptr)
4086 AutoPointerRef *ref;
4089 if (auto_ptr == NULL)
4094 newp = g_new0(AutoPointer, 1);
4097 newp->ptr = ref->pointer;
4101 G_PRINT_REF ("XXXX COPY(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4107 *\brief Free an auto pointer
4109 void g_auto_pointer_free(GAuto *auto_ptr)
4112 AutoPointerRef *ref;
4114 if (auto_ptr == NULL)
4120 if (--(ref->cnt) == 0) {
4122 G_PRINT_REF ("XXXX FREE(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4124 ref->free(ref->pointer);
4129 G_PRINT_REF ("XXXX DEREF(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4134 void replace_returns(gchar *str)
4139 while (strstr(str, "\n")) {
4140 *strstr(str, "\n") = ' ';
4142 while (strstr(str, "\r")) {
4143 *strstr(str, "\r") = ' ';
4147 /* get_uri_part() - retrieves a URI starting from scanpos.
4148 Returns TRUE if succesful */
4149 gboolean get_uri_part(const gchar *start, const gchar *scanpos,
4150 const gchar **bp, const gchar **ep, gboolean hdr)
4153 gint parenthese_cnt = 0;
4155 cm_return_val_if_fail(start != NULL, FALSE);
4156 cm_return_val_if_fail(scanpos != NULL, FALSE);
4157 cm_return_val_if_fail(bp != NULL, FALSE);
4158 cm_return_val_if_fail(ep != NULL, FALSE);
4162 /* find end point of URI */
4163 for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
4164 if (!g_ascii_isgraph(*(const guchar *)ep_) ||
4165 !IS_ASCII(*(const guchar *)ep_) ||
4166 strchr("[]{}<>\"", *ep_)) {
4168 } else if (strchr("(", *ep_)) {
4170 } else if (strchr(")", *ep_)) {
4171 if (parenthese_cnt > 0)
4178 /* no punctuation at end of string */
4180 /* FIXME: this stripping of trailing punctuations may bite with other URIs.
4181 * should pass some URI type to this function and decide on that whether
4182 * to perform punctuation stripping */
4184 #define IS_REAL_PUNCT(ch) (g_ascii_ispunct(ch) && !strchr("/?=-_)", ch))
4186 for (; ep_ - 1 > scanpos + 1 &&
4187 IS_REAL_PUNCT(*(ep_ - 1));
4191 #undef IS_REAL_PUNCT
4198 gchar *make_uri_string(const gchar *bp, const gchar *ep)
4200 while (bp && *bp && g_ascii_isspace(*bp))
4202 return g_strndup(bp, ep - bp);
4205 /* valid mail address characters */
4206 #define IS_RFC822_CHAR(ch) \
4210 !g_ascii_isspace(ch) && \
4211 !strchr("(),;<>\"", (ch)))
4213 /* alphabet and number within 7bit ASCII */
4214 #define IS_ASCII_ALNUM(ch) (IS_ASCII(ch) && g_ascii_isalnum(ch))
4215 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
4217 static GHashTable *create_domain_tab(void)
4219 static const gchar *toplvl_domains [] = {
4221 "arpa", "coop", "info", "name", "biz", "com", "edu", "gov",
4222 "int", "mil", "net", "org", "ac", "ad", "ae", "af", "ag",
4223 "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au",
4224 "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi",
4225 "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
4226 "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl",
4227 "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz", "de",
4228 "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er",
4229 "es", "et", "eu", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd",
4230 "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
4231 "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
4232 "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir",
4233 "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
4234 "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc",
4235 "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
4236 "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq",
4237 "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na",
4238 "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu",
4239 "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm",
4240 "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru",
4241 "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj",
4242 "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
4243 "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to",
4244 "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um",
4245 "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu",
4246 "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw"
4249 GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
4251 cm_return_val_if_fail(htab, NULL);
4252 for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++)
4253 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
4257 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
4259 const gint MAX_LVL_DOM_NAME_LEN = 6;
4260 gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
4261 const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
4264 if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
4267 for (p = buf; p < m && first < last; *p++ = *first++)
4271 return g_hash_table_lookup(tab, buf) != NULL;
4274 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
4275 gboolean get_email_part(const gchar *start, const gchar *scanpos,
4276 const gchar **bp, const gchar **ep, gboolean hdr)
4278 /* more complex than the uri part because we need to scan back and forward starting from
4279 * the scan position. */
4280 gboolean result = FALSE;
4281 const gchar *bp_ = NULL;
4282 const gchar *ep_ = NULL;
4283 static GHashTable *dom_tab;
4284 const gchar *last_dot = NULL;
4285 const gchar *prelast_dot = NULL;
4286 const gchar *last_tld_char = NULL;
4288 /* the informative part of the email address (describing the name
4289 * of the email address owner) may contain quoted parts. the
4290 * closure stack stores the last encountered quotes. */
4291 gchar closure_stack[128];
4292 gchar *ptr = closure_stack;
4294 cm_return_val_if_fail(start != NULL, FALSE);
4295 cm_return_val_if_fail(scanpos != NULL, FALSE);
4296 cm_return_val_if_fail(bp != NULL, FALSE);
4297 cm_return_val_if_fail(ep != NULL, FALSE);
4300 const gchar *start_quote = NULL;
4301 const gchar *end_quote = NULL;
4303 /* go to the real start */
4304 if (start[0] == ',')
4306 if (start[0] == ';')
4308 while (start[0] == '\n' || start[0] == '\r')
4310 while (start[0] == ' ' || start[0] == '\t')
4315 /* check if there are quotes (to skip , in them) */
4316 if (*start == '"') {
4317 start_quote = start;
4319 end_quote = strstr(start, "\"");
4325 /* skip anything between quotes */
4326 if (start_quote && end_quote) {
4331 /* find end (either , or ; or end of line) */
4332 if (strstr(start, ",") && strstr(start, ";"))
4333 *ep = strstr(start,",") < strstr(start, ";")
4334 ? strstr(start, ",") : strstr(start, ";");
4335 else if (strstr(start, ","))
4336 *ep = strstr(start, ",");
4337 else if (strstr(start, ";"))
4338 *ep = strstr(start, ";");
4340 *ep = start+strlen(start);
4342 /* go back to real start */
4343 if (start_quote && end_quote) {
4344 start = start_quote;
4347 /* check there's still an @ in that, or search
4348 * further if possible */
4349 if (strstr(start, "@") && strstr(start, "@") < *ep)
4351 else if (*ep < start+strlen(start)) {
4354 } else if (start_quote && strstr(start, "\"") && strstr(start, "\"") < *ep) {
4362 dom_tab = create_domain_tab();
4363 cm_return_val_if_fail(dom_tab, FALSE);
4365 /* scan start of address */
4366 for (bp_ = scanpos - 1;
4367 bp_ >= start && IS_RFC822_CHAR(*(const guchar *)bp_); bp_--)
4370 /* TODO: should start with an alnum? */
4372 for (; bp_ < scanpos && !IS_ASCII_ALNUM(*(const guchar *)bp_); bp_++)
4375 if (bp_ != scanpos) {
4376 /* scan end of address */
4377 for (ep_ = scanpos + 1;
4378 *ep_ && IS_RFC822_CHAR(*(const guchar *)ep_); ep_++)
4380 prelast_dot = last_dot;
4382 if (*(last_dot + 1) == '.') {
4383 if (prelast_dot == NULL)
4385 last_dot = prelast_dot;
4390 /* TODO: really should terminate with an alnum? */
4391 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*(const guchar *)ep_);
4396 if (last_dot == NULL)
4398 if (last_dot >= ep_)
4399 last_dot = prelast_dot;
4400 if (last_dot == NULL || (scanpos + 1 >= last_dot))
4404 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
4405 if (*last_tld_char == '?')
4408 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
4415 if (!result) return FALSE;
4417 if (*ep_ && bp_ != start && *(bp_ - 1) == '"' && *(ep_) == '"'
4418 && *(ep_ + 1) == ' ' && *(ep_ + 2) == '<'
4419 && IS_RFC822_CHAR(*(ep_ + 3))) {
4420 /* this informative part with an @ in it is
4421 * followed by the email address */
4424 /* go to matching '>' (or next non-rfc822 char, like \n) */
4425 for (; *ep_ != '>' && *ep != '\0' && IS_RFC822_CHAR(*ep_); ep_++)
4428 /* include the bracket */
4429 if (*ep_ == '>') ep_++;
4431 /* include the leading quote */
4439 /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
4440 if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
4443 /* see if this is <bracketed>; in this case we also scan for the informative part. */
4444 if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
4447 #define FULL_STACK() ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
4448 #define IN_STACK() (ptr > closure_stack)
4449 /* has underrun check */
4450 #define POP_STACK() if(IN_STACK()) --ptr
4451 /* has overrun check */
4452 #define PUSH_STACK(c) if(!FULL_STACK()) *ptr++ = (c); else return TRUE
4453 /* has underrun check */
4454 #define PEEK_STACK() (IN_STACK() ? *(ptr - 1) : 0)
4458 /* scan for the informative part. */
4459 for (bp_ -= 2; bp_ >= start; bp_--) {
4460 /* if closure on the stack keep scanning */
4461 if (PEEK_STACK() == *bp_) {
4465 if (!IN_STACK() && (*bp_ == '\'' || *bp_ == '"')) {
4470 /* if nothing in the closure stack, do the special conditions
4471 * the following if..else expression simply checks whether
4472 * a token is acceptable. if not acceptable, the clause
4473 * should terminate the loop with a 'break' */
4474 if (!PEEK_STACK()) {
4476 && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
4477 && (((bp_ + 1) < ep_) && isalnum(*(bp_ + 1)))) {
4478 /* hyphens are allowed, but only in
4480 } else if (strchr(" \"'", *bp_)) {
4481 /* but anything not being a punctiation
4484 break; /* anything else is rejected */
4491 /* scan forward (should start with an alnum) */
4492 for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
4508 #undef IS_ASCII_ALNUM
4509 #undef IS_RFC822_CHAR
4511 gchar *make_email_string(const gchar *bp, const gchar *ep)
4513 /* returns a mailto: URI; mailto: is also used to detect the
4514 * uri type later on in the button_pressed signal handler */
4518 tmp = g_strndup(bp, ep - bp);
4519 result = g_strconcat("mailto:", tmp, NULL);
4525 gchar *make_http_string(const gchar *bp, const gchar *ep)
4527 /* returns an http: URI; */
4531 while (bp && *bp && g_ascii_isspace(*bp))
4533 tmp = g_strndup(bp, ep - bp);
4534 result = g_strconcat("http://", tmp, NULL);
4540 static gchar *mailcap_get_command_in_file(const gchar *path, const gchar *type, const gchar *file_to_open)
4542 FILE *fp = g_fopen(path, "rb");
4543 gchar buf[BUFFSIZE];
4544 gchar *result = NULL;
4547 while (fgets(buf, sizeof (buf), fp) != NULL) {
4548 gchar **parts = g_strsplit(buf, ";", 3);
4549 gchar *trimmed = parts[0];
4550 while (trimmed[0] == ' ' || trimmed[0] == '\t')
4552 while (trimmed[strlen(trimmed)-1] == ' ' || trimmed[strlen(trimmed)-1] == '\t')
4553 trimmed[strlen(trimmed)-1] = '\0';
4555 if (!strcmp(trimmed, type)) {
4556 gboolean needsterminal = FALSE;
4557 if (parts[2] && strstr(parts[2], "needsterminal")) {
4558 needsterminal = TRUE;
4560 if (parts[2] && strstr(parts[2], "test=")) {
4561 gchar *orig_testcmd = g_strdup(strstr(parts[2], "test=")+5);
4562 gchar *testcmd = orig_testcmd;
4563 if (strstr(testcmd,";"))
4564 *(strstr(testcmd,";")) = '\0';
4565 while (testcmd[0] == ' ' || testcmd[0] == '\t')
4567 while (testcmd[strlen(testcmd)-1] == '\n')
4568 testcmd[strlen(testcmd)-1] = '\0';
4569 while (testcmd[strlen(testcmd)-1] == '\r')
4570 testcmd[strlen(testcmd)-1] = '\0';
4571 while (testcmd[strlen(testcmd)-1] == ' ' || testcmd[strlen(testcmd)-1] == '\t')
4572 testcmd[strlen(testcmd)-1] = '\0';
4574 if (strstr(testcmd, "%s")) {
4575 gchar *tmp = g_strdup_printf(testcmd, file_to_open);
4576 gint res = system(tmp);
4578 g_free(orig_testcmd);
4585 gint res = system(testcmd);
4586 g_free(orig_testcmd);
4596 while (trimmed[0] == ' ' || trimmed[0] == '\t')
4598 while (trimmed[strlen(trimmed)-1] == '\n')
4599 trimmed[strlen(trimmed)-1] = '\0';
4600 while (trimmed[strlen(trimmed)-1] == '\r')
4601 trimmed[strlen(trimmed)-1] = '\0';
4602 while (trimmed[strlen(trimmed)-1] == ' ' || trimmed[strlen(trimmed)-1] == '\t')
4603 trimmed[strlen(trimmed)-1] = '\0';
4604 result = g_strdup(trimmed);
4607 /* if there are no single quotes around %s, add them.
4608 * '.*%s.*' is ok, as in display 'png:%s'
4610 if (strstr(result, "%s")
4611 && !(strstr(result, "'") < strstr(result,"%s") &&
4612 strstr(strstr(result,"%s"), "'"))) {
4613 gchar *start = g_strdup(result);
4614 gchar *end = g_strdup(strstr(result, "%s")+2);
4616 *strstr(start, "%s") = '\0';
4617 tmp = g_strconcat(start,"'%s'",end, NULL);
4623 if (needsterminal) {
4624 gchar *tmp = g_strdup_printf("xterm -e %s", result);
4635 gchar *mailcap_get_command_for_type(const gchar *type, const gchar *file_to_open)
4637 gchar *result = NULL;
4641 path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap", NULL);
4642 result = mailcap_get_command_in_file(path, type, file_to_open);
4646 result = mailcap_get_command_in_file("/etc/mailcap", type, file_to_open);
4650 void mailcap_update_default(const gchar *type, const gchar *command)
4652 gchar *path = NULL, *outpath = NULL;
4653 path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap", NULL);
4654 outpath = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap.new", NULL);
4655 FILE *fp = g_fopen(path, "rb");
4657 gchar buf[BUFFSIZE];
4658 gboolean err = FALSE;
4661 fp = g_fopen(path, "a");
4663 g_warning("failed to create file %s", path);
4668 fp = g_freopen(path, "rb", fp);
4670 g_warning("failed to reopen file %s", path);
4677 outfp = g_fopen(outpath, "wb");
4679 g_warning("failed to create file %s", outpath);
4685 while (fp && fgets(buf, sizeof (buf), fp) != NULL) {
4686 gchar **parts = g_strsplit(buf, ";", 3);
4687 gchar *trimmed = parts[0];
4688 while (trimmed[0] == ' ')
4690 while (trimmed[strlen(trimmed)-1] == ' ')
4691 trimmed[strlen(trimmed)-1] = '\0';
4693 if (!strcmp(trimmed, type)) {
4698 if(fputs(buf, outfp) == EOF) {
4705 if (fprintf(outfp, "%s; %s\n", type, command) < 0)
4711 if (fclose(outfp) == EOF)
4715 g_rename(outpath, path);
4721 gint copy_dir(const gchar *src, const gchar *dst)
4726 if ((dir = g_dir_open(src, 0, NULL)) == NULL) {
4727 g_warning("failed to open directory: %s", src);
4731 if (make_dir(dst) < 0)
4734 while ((name = g_dir_read_name(dir)) != NULL) {
4735 gchar *old_file, *new_file;
4736 old_file = g_strconcat(src, G_DIR_SEPARATOR_S, name, NULL);
4737 new_file = g_strconcat(dst, G_DIR_SEPARATOR_S, name, NULL);
4738 debug_print("copying: %s -> %s\n", old_file, new_file);
4739 if (g_file_test(old_file, G_FILE_TEST_IS_REGULAR)) {
4740 gint r = copy_file(old_file, new_file, TRUE);
4747 /* Windows has no symlinks. Or well, Vista seems to
4748 have something like this but the semantics might be
4749 different. Thus we don't use it under Windows. */
4750 else if (g_file_test(old_file, G_FILE_TEST_IS_SYMLINK)) {
4751 GError *error = NULL;
4753 gchar *target = g_file_read_link(old_file, &error);
4755 r = symlink(target, new_file);
4762 #endif /*G_OS_WIN32*/
4763 else if (g_file_test(old_file, G_FILE_TEST_IS_DIR)) {
4764 gint r = copy_dir(old_file, new_file);
4775 /* crude test to see if a file is an email. */
4776 gboolean file_is_email (const gchar *filename)
4782 if (filename == NULL)
4784 if ((fp = g_fopen(filename, "rb")) == NULL)
4786 while (i < 60 && score < 3
4787 && fgets(buffer, sizeof (buffer), fp) > 0) {
4788 if (!strncmp(buffer, "From:", strlen("From:")))
4790 else if (!strncmp(buffer, "Date:", strlen("Date:")))
4792 else if (!strncmp(buffer, "Message-ID:", strlen("Message-ID:")))
4794 else if (!strncmp(buffer, "Subject:", strlen("Subject:")))
4799 return (score >= 3);
4802 gboolean sc_g_list_bigger(GList *list, gint max)
4806 while (cur && i <= max+1) {
4813 gboolean sc_g_slist_bigger(GSList *list, gint max)
4817 while (cur && i <= max+1) {
4824 const gchar *daynames[] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL};
4825 const gchar *monthnames[] = {NULL, NULL, NULL, NULL, NULL, NULL,
4826 NULL, NULL, NULL, NULL, NULL, NULL};
4827 const gchar *s_daynames[] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL};
4828 const gchar *s_monthnames[] = {NULL, NULL, NULL, NULL, NULL, NULL,
4829 NULL, NULL, NULL, NULL, NULL, NULL};
4831 gint daynames_len[] = {0,0,0,0,0,0,0};
4832 gint monthnames_len[] = {0,0,0,0,0,0,
4834 gint s_daynames_len[] = {0,0,0,0,0,0,0};
4835 gint s_monthnames_len[] = {0,0,0,0,0,0,
4837 const gchar *s_am_up = NULL;
4838 const gchar *s_pm_up = NULL;
4839 const gchar *s_am_low = NULL;
4840 const gchar *s_pm_low = NULL;
4842 gint s_am_up_len = 0;
4843 gint s_pm_up_len = 0;
4844 gint s_am_low_len = 0;
4845 gint s_pm_low_len = 0;
4847 static gboolean time_names_init_done = FALSE;
4849 static void init_time_names(void)
4853 daynames[0] = C_("Complete day name for use by strftime", "Sunday");
4854 daynames[1] = C_("Complete day name for use by strftime", "Monday");
4855 daynames[2] = C_("Complete day name for use by strftime", "Tuesday");
4856 daynames[3] = C_("Complete day name for use by strftime", "Wednesday");
4857 daynames[4] = C_("Complete day name for use by strftime", "Thursday");
4858 daynames[5] = C_("Complete day name for use by strftime", "Friday");
4859 daynames[6] = C_("Complete day name for use by strftime", "Saturday");
4861 monthnames[0] = C_("Complete month name for use by strftime", "January");
4862 monthnames[1] = C_("Complete month name for use by strftime", "February");
4863 monthnames[2] = C_("Complete month name for use by strftime", "March");
4864 monthnames[3] = C_("Complete month name for use by strftime", "April");
4865 monthnames[4] = C_("Complete month name for use by strftime", "May");
4866 monthnames[5] = C_("Complete month name for use by strftime", "June");
4867 monthnames[6] = C_("Complete month name for use by strftime", "July");
4868 monthnames[7] = C_("Complete month name for use by strftime", "August");
4869 monthnames[8] = C_("Complete month name for use by strftime", "September");
4870 monthnames[9] = C_("Complete month name for use by strftime", "October");
4871 monthnames[10] = C_("Complete month name for use by strftime", "November");
4872 monthnames[11] = C_("Complete month name for use by strftime", "December");
4874 s_daynames[0] = C_("Abbr. day name for use by strftime", "Sun");
4875 s_daynames[1] = C_("Abbr. day name for use by strftime", "Mon");
4876 s_daynames[2] = C_("Abbr. day name for use by strftime", "Tue");
4877 s_daynames[3] = C_("Abbr. day name for use by strftime", "Wed");
4878 s_daynames[4] = C_("Abbr. day name for use by strftime", "Thu");
4879 s_daynames[5] = C_("Abbr. day name for use by strftime", "Fri");
4880 s_daynames[6] = C_("Abbr. day name for use by strftime", "Sat");
4882 s_monthnames[0] = C_("Abbr. month name for use by strftime", "Jan");
4883 s_monthnames[1] = C_("Abbr. month name for use by strftime", "Feb");
4884 s_monthnames[2] = C_("Abbr. month name for use by strftime", "Mar");
4885 s_monthnames[3] = C_("Abbr. month name for use by strftime", "Apr");
4886 s_monthnames[4] = C_("Abbr. month name for use by strftime", "May");
4887 s_monthnames[5] = C_("Abbr. month name for use by strftime", "Jun");
4888 s_monthnames[6] = C_("Abbr. month name for use by strftime", "Jul");
4889 s_monthnames[7] = C_("Abbr. month name for use by strftime", "Aug");
4890 s_monthnames[8] = C_("Abbr. month name for use by strftime", "Sep");
4891 s_monthnames[9] = C_("Abbr. month name for use by strftime", "Oct");
4892 s_monthnames[10] = C_("Abbr. month name for use by strftime", "Nov");
4893 s_monthnames[11] = C_("Abbr. month name for use by strftime", "Dec");
4895 for (i = 0; i < 7; i++) {
4896 daynames_len[i] = strlen(daynames[i]);
4897 s_daynames_len[i] = strlen(s_daynames[i]);
4899 for (i = 0; i < 12; i++) {
4900 monthnames_len[i] = strlen(monthnames[i]);
4901 s_monthnames_len[i] = strlen(s_monthnames[i]);
4904 s_am_up = C_("For use by strftime (morning)", "AM");
4905 s_pm_up = C_("For use by strftime (afternoon)", "PM");
4906 s_am_low = C_("For use by strftime (morning, lowercase)", "am");
4907 s_pm_low = C_("For use by strftime (afternoon, lowercase)", "pm");
4909 s_am_up_len = strlen(s_am_up);
4910 s_pm_up_len = strlen(s_pm_up);
4911 s_am_low_len = strlen(s_am_low);
4912 s_pm_low_len = strlen(s_pm_low);
4914 time_names_init_done = TRUE;
4917 #define CHECK_SIZE() { \
4918 total_done += len; \
4919 if (total_done >= buflen) { \
4920 buf[buflen-1] = '\0'; \
4925 size_t fast_strftime(gchar *buf, gint buflen, const gchar *format, struct tm *lt)
4927 gchar *curpos = buf;
4928 gint total_done = 0;
4929 gchar subbuf[64], subfmt[64];
4930 static time_t last_tzset = (time_t)0;
4932 if (!time_names_init_done)
4935 if (format == NULL || lt == NULL)
4938 if (last_tzset != time(NULL)) {
4940 last_tzset = time(NULL);
4943 if (*format == '%') {
4944 gint len = 0, tmp = 0;
4948 len = 1; CHECK_SIZE();
4952 len = s_daynames_len[lt->tm_wday]; CHECK_SIZE();
4953 strncpy2(curpos, s_daynames[lt->tm_wday], buflen - total_done);
4956 len = daynames_len[lt->tm_wday]; CHECK_SIZE();
4957 strncpy2(curpos, daynames[lt->tm_wday], buflen - total_done);
4961 len = s_monthnames_len[lt->tm_mon]; CHECK_SIZE();
4962 strncpy2(curpos, s_monthnames[lt->tm_mon], buflen - total_done);
4965 len = monthnames_len[lt->tm_mon]; CHECK_SIZE();
4966 strncpy2(curpos, monthnames[lt->tm_mon], buflen - total_done);
4969 strftime(subbuf, 64, "%c", lt);
4970 len = strlen(subbuf); CHECK_SIZE();
4971 strncpy2(curpos, subbuf, buflen - total_done);
4974 total_done += 2; CHECK_SIZE();
4975 tmp = (lt->tm_year + 1900)/100;
4976 *curpos++ = '0'+(tmp / 10);
4977 *curpos++ = '0'+(tmp % 10);
4980 total_done += 2; CHECK_SIZE();
4981 *curpos++ = '0'+(lt->tm_mday / 10);
4982 *curpos++ = '0'+(lt->tm_mday % 10);
4985 total_done += 8; CHECK_SIZE();
4986 *curpos++ = '0'+((lt->tm_mon+1) / 10);
4987 *curpos++ = '0'+((lt->tm_mon+1) % 10);
4989 *curpos++ = '0'+(lt->tm_mday / 10);
4990 *curpos++ = '0'+(lt->tm_mday % 10);
4992 tmp = lt->tm_year%100;
4993 *curpos++ = '0'+(tmp / 10);
4994 *curpos++ = '0'+(tmp % 10);
4997 len = 2; CHECK_SIZE();
4998 snprintf(curpos, buflen - total_done, "%2d", lt->tm_mday);
5001 len = 10; CHECK_SIZE();
5002 snprintf(curpos, buflen - total_done, "%4d-%02d-%02d",
5003 lt->tm_year + 1900, lt->tm_mon +1, lt->tm_mday);
5006 total_done += 2; CHECK_SIZE();
5007 *curpos++ = '0'+(lt->tm_hour / 10);
5008 *curpos++ = '0'+(lt->tm_hour % 10);
5011 total_done += 2; CHECK_SIZE();
5017 *curpos++ = '0'+(tmp / 10);
5018 *curpos++ = '0'+(tmp % 10);
5021 len = 3; CHECK_SIZE();
5022 snprintf(curpos, buflen - total_done, "%03d", lt->tm_yday+1);
5025 len = 2; CHECK_SIZE();
5026 snprintf(curpos, buflen - total_done, "%2d", lt->tm_hour);
5029 len = 2; CHECK_SIZE();
5035 snprintf(curpos, buflen - total_done, "%2d", tmp);
5038 total_done += 2; CHECK_SIZE();
5039 tmp = lt->tm_mon + 1;
5040 *curpos++ = '0'+(tmp / 10);
5041 *curpos++ = '0'+(tmp % 10);
5044 total_done += 2; CHECK_SIZE();
5045 *curpos++ = '0'+(lt->tm_min / 10);
5046 *curpos++ = '0'+(lt->tm_min % 10);
5049 len = 1; CHECK_SIZE();
5053 if (lt->tm_hour >= 12) {
5054 len = s_pm_up_len; CHECK_SIZE();
5055 snprintf(curpos, buflen-total_done, "%s", s_pm_up);
5057 len = s_am_up_len; CHECK_SIZE();
5058 snprintf(curpos, buflen-total_done, "%s", s_am_up);
5062 if (lt->tm_hour >= 12) {
5063 len = s_pm_low_len; CHECK_SIZE();
5064 snprintf(curpos, buflen-total_done, "%s", s_pm_low);
5066 len = s_am_low_len; CHECK_SIZE();
5067 snprintf(curpos, buflen-total_done, "%s", s_am_low);
5071 strftime(subbuf, 64, "%r", lt);
5072 len = strlen(subbuf); CHECK_SIZE();
5073 strncpy2(curpos, subbuf, buflen - total_done);
5076 total_done += 5; CHECK_SIZE();
5077 *curpos++ = '0'+(lt->tm_hour / 10);
5078 *curpos++ = '0'+(lt->tm_hour % 10);
5080 *curpos++ = '0'+(lt->tm_min / 10);
5081 *curpos++ = '0'+(lt->tm_min % 10);
5084 snprintf(subbuf, 64, "%ld", mktime(lt));
5085 len = strlen(subbuf); CHECK_SIZE();
5086 strncpy2(curpos, subbuf, buflen - total_done);
5089 total_done += 2; CHECK_SIZE();
5090 *curpos++ = '0'+(lt->tm_sec / 10);
5091 *curpos++ = '0'+(lt->tm_sec % 10);
5094 len = 1; CHECK_SIZE();
5098 total_done += 8; CHECK_SIZE();
5099 *curpos++ = '0'+(lt->tm_hour / 10);
5100 *curpos++ = '0'+(lt->tm_hour % 10);
5102 *curpos++ = '0'+(lt->tm_min / 10);
5103 *curpos++ = '0'+(lt->tm_min % 10);
5105 *curpos++ = '0'+(lt->tm_sec / 10);
5106 *curpos++ = '0'+(lt->tm_sec % 10);
5109 len = 1; CHECK_SIZE();
5110 snprintf(curpos, buflen - total_done, "%d", lt->tm_wday == 0 ? 7: lt->tm_wday);
5113 len = 1; CHECK_SIZE();
5114 snprintf(curpos, buflen - total_done, "%d", lt->tm_wday);
5117 strftime(subbuf, 64, "%x", lt);
5118 len = strlen(subbuf); CHECK_SIZE();
5119 strncpy2(curpos, subbuf, buflen - total_done);
5122 strftime(subbuf, 64, "%X", lt);
5123 len = strlen(subbuf); CHECK_SIZE();
5124 strncpy2(curpos, subbuf, buflen - total_done);
5127 total_done += 2; CHECK_SIZE();
5128 tmp = lt->tm_year%100;
5129 *curpos++ = '0'+(tmp / 10);
5130 *curpos++ = '0'+(tmp % 10);
5133 len = 4; CHECK_SIZE();
5134 snprintf(curpos, buflen - total_done, "%4d", lt->tm_year + 1900);
5144 /* let these complicated ones be done with the libc */
5145 snprintf(subfmt, 64, "%%%c", *format);
5146 strftime(subbuf, 64, subfmt, lt);
5147 len = strlen(subbuf); CHECK_SIZE();
5148 strncpy2(curpos, subbuf, buflen - total_done);
5152 /* let these complicated modifiers be done with the libc */
5153 snprintf(subfmt, 64, "%%%c%c", *format, *(format+1));
5154 strftime(subbuf, 64, subfmt, lt);
5155 len = strlen(subbuf); CHECK_SIZE();
5156 strncpy2(curpos, subbuf, buflen - total_done);
5160 g_warning("format error (%c)", *format);
5167 int len = 1; CHECK_SIZE();
5168 *curpos++ = *format++;
5175 gboolean prefs_common_get_use_shred(void);
5179 #define WEXITSTATUS(x) (x)
5182 int claws_unlink(const gchar *filename)
5185 static int found_shred = -1;
5186 static const gchar *args[4];
5188 if (filename == NULL)
5191 if (prefs_common_get_use_shred()) {
5192 if (found_shred == -1) {
5194 args[0] = g_find_program_in_path("shred");
5195 debug_print("found shred: %s\n", args[0]);
5196 found_shred = (args[0] != NULL) ? 1:0;
5200 if (found_shred == 1) {
5201 if (g_stat(filename, &s) == 0 && S_ISREG(s.st_mode)) {
5202 if (s.st_nlink == 1) {
5205 g_spawn_sync(NULL, (gchar **)args, NULL, 0,
5206 NULL, NULL, NULL, NULL, &status, NULL);
5207 debug_print("%s %s exited with status %d\n",
5208 args[0], filename, WEXITSTATUS(status));
5209 if (truncate(filename, 0) < 0)
5210 g_warning("couln't truncate: %s", filename);
5215 return g_unlink(filename);
5218 GMutex *cm_mutex_new(void) {
5219 #if GLIB_CHECK_VERSION(2,32,0)
5220 GMutex *m = g_new0(GMutex, 1);
5224 return g_mutex_new();
5228 void cm_mutex_free(GMutex *mutex) {
5229 #if GLIB_CHECK_VERSION(2,32,0)
5230 g_mutex_clear(mutex);
5233 g_mutex_free(mutex);
5237 static gchar *canonical_list_to_file(GSList *list)
5239 GString *result = g_string_new(NULL);
5240 GSList *pathlist = g_slist_reverse(g_slist_copy(list));
5245 result = g_string_append(result, G_DIR_SEPARATOR_S);
5247 if (pathlist->data) {
5248 const gchar *root = (gchar *)pathlist->data;
5249 if (root[0] != '\0' && g_ascii_isalpha(root[0]) &&
5251 /* drive - don't prepend dir separator */
5253 result = g_string_append(result, G_DIR_SEPARATOR_S);
5258 for (cur = pathlist; cur; cur = cur->next) {
5259 result = g_string_append(result, (gchar *)cur->data);
5261 result = g_string_append(result, G_DIR_SEPARATOR_S);
5263 g_slist_free(pathlist);
5266 g_string_free(result, FALSE);
5271 static GSList *cm_split_path(const gchar *filename, int depth)
5274 GSList *canonical_parts = NULL;
5277 gboolean follow_symlinks = TRUE;
5283 errno = EINVAL; /* can't happen, no symlink handling */
5288 if (!g_path_is_absolute(filename)) {
5293 path_parts = g_strsplit(filename, G_DIR_SEPARATOR_S, -1);
5295 for (i = 0; path_parts[i] != NULL; i++) {
5296 if (!strcmp(path_parts[i], ""))
5298 if (!strcmp(path_parts[i], "."))
5300 else if (!strcmp(path_parts[i], "..")) {
5305 else /* Remove the last inserted element */
5307 g_slist_delete_link(canonical_parts,
5312 canonical_parts = g_slist_prepend(canonical_parts,
5313 g_strdup(path_parts[i]));
5315 tmp_path = canonical_list_to_file(canonical_parts);
5317 if(g_stat(tmp_path, &st) < 0) {
5318 if (errno == ENOENT) {
5320 follow_symlinks = FALSE;
5324 slist_free_strings_full(canonical_parts);
5325 g_strfreev(path_parts);
5331 if (follow_symlinks && g_file_test(tmp_path, G_FILE_TEST_IS_SYMLINK)) {
5332 GError *error = NULL;
5333 gchar *target = g_file_read_link(tmp_path, &error);
5335 if (!g_path_is_absolute(target)) {
5336 /* remove the last inserted element */
5338 g_slist_delete_link(canonical_parts,
5340 /* add the target */
5341 canonical_parts = g_slist_prepend(canonical_parts,
5345 /* and get the new target */
5346 target = canonical_list_to_file(canonical_parts);
5349 /* restart from absolute target */
5350 slist_free_strings_full(canonical_parts);
5351 canonical_parts = NULL;
5353 canonical_parts = cm_split_path(target, depth + 1);
5355 g_error_free(error);
5356 if (canonical_parts == NULL) {
5358 g_strfreev(path_parts);
5367 g_strfreev(path_parts);
5368 return canonical_parts;
5372 * Canonicalize a filename, resolving symlinks along the way.
5373 * Returns a negative errno in case of error.
5375 int cm_canonicalize_filename(const gchar *filename, gchar **canonical_name) {
5376 GSList *canonical_parts;
5377 gboolean is_absolute;
5379 if (filename == NULL)
5381 if (canonical_name == NULL)
5383 *canonical_name = NULL;
5385 is_absolute = g_path_is_absolute(filename);
5387 /* Always work on absolute filenames. */
5388 gchar *cur = g_get_current_dir();
5389 gchar *absolute_filename = g_strconcat(cur, G_DIR_SEPARATOR_S,
5392 canonical_parts = cm_split_path(absolute_filename, 0);
5393 g_free(absolute_filename);
5396 canonical_parts = cm_split_path(filename, 0);
5398 if (canonical_parts == NULL)
5401 *canonical_name = canonical_list_to_file(canonical_parts);
5402 slist_free_strings_full(canonical_parts);
5406 /* Returns a decoded base64 string, guaranteed to be null-terminated. */
5407 guchar *g_base64_decode_zero(const gchar *text, gsize *out_len)
5409 gchar *tmp = g_base64_decode(text, out_len);
5410 gchar *out = g_strndup(tmp, *out_len);
5414 if (strlen(out) != *out_len) {
5415 g_warning ("strlen(out) %zd != *out_len %" G_GSIZE_FORMAT, strlen(out), *out_len);
5421 #if !GLIB_CHECK_VERSION(2, 30, 0)
5424 * @str: a UTF-8 encoded string
5425 * @start_pos: a character offset within @str
5426 * @end_pos: another character offset within @str
5428 * Copies a substring out of a UTF-8 encoded string.
5429 * The substring will contain @end_pos - @start_pos
5432 * Returns: a newly allocated copy of the requested
5433 * substring. Free with g_free() when no longer needed.
5438 g_utf8_substring (const gchar *str,
5442 gchar *start, *end, *out;
5444 start = g_utf8_offset_to_pointer (str, start_pos);
5445 end = g_utf8_offset_to_pointer (start, end_pos - start_pos);
5447 out = g_malloc (end - start + 1);
5448 memcpy (out, start, end - start);
5449 out[end - start] = 0;