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>
52 #include <sys/socket.h>
54 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
62 #include <sys/types.h>
64 # include <sys/wait.h>
71 #include <sys/utsname.h>
84 #include "../codeconv.h"
88 static gboolean debug_mode = FALSE;
90 static GSList *tempfiles=NULL;
93 #if !GLIB_CHECK_VERSION(2, 26, 0)
94 guchar *g_base64_decode_wa(const gchar *text, gsize *out_len)
101 input_length = strlen(text);
103 ret = g_malloc0((input_length / 4) * 3 + 1);
105 *out_len = g_base64_decode_step(text, input_length, ret, &state, &save);
111 /* Return true if we are running as root. This function should beused
112 instead of getuid () == 0. */
113 gboolean superuser_p (void)
116 return w32_is_administrator ();
122 GSList *slist_copy_deep(GSList *list, GCopyFunc func)
124 #if GLIB_CHECK_VERSION(2, 34, 0)
125 return g_slist_copy_deep(list, func, NULL);
127 GSList *res = g_slist_copy(list);
130 walk->data = func(walk->data, NULL);
137 void list_free_strings(GList *list)
139 list = g_list_first(list);
141 while (list != NULL) {
147 void slist_free_strings(GSList *list)
149 while (list != NULL) {
155 void slist_free_strings_full(GSList *list)
157 #if GLIB_CHECK_VERSION(2,28,0)
158 g_slist_free_full(list, (GDestroyNotify)g_free);
160 g_slist_foreach(list, (GFunc)g_free, NULL);
165 static void hash_free_strings_func(gpointer key, gpointer value, gpointer data)
170 void hash_free_strings(GHashTable *table)
172 g_hash_table_foreach(table, hash_free_strings_func, NULL);
175 gint str_case_equal(gconstpointer v, gconstpointer v2)
177 return g_ascii_strcasecmp((const gchar *)v, (const gchar *)v2) == 0;
180 guint str_case_hash(gconstpointer key)
182 const gchar *p = key;
186 h = g_ascii_tolower(h);
187 for (p += 1; *p != '\0'; p++)
188 h = (h << 5) - h + g_ascii_tolower(*p);
194 void ptr_array_free_strings(GPtrArray *array)
199 cm_return_if_fail(array != NULL);
201 for (i = 0; i < array->len; i++) {
202 str = g_ptr_array_index(array, i);
207 gint to_number(const gchar *nstr)
209 register const gchar *p;
211 if (*nstr == '\0') return -1;
213 for (p = nstr; *p != '\0'; p++)
214 if (!g_ascii_isdigit(*p)) return -1;
219 /* convert integer into string,
220 nstr must be not lower than 11 characters length */
221 gchar *itos_buf(gchar *nstr, gint n)
223 g_snprintf(nstr, 11, "%d", n);
227 /* convert integer into string */
230 static gchar nstr[11];
232 return itos_buf(nstr, n);
235 #define divide(num,divisor,i,d) \
237 i = num >> divisor; \
238 d = num & ((1<<divisor)-1); \
239 d = (d*100) >> divisor; \
244 * \brief Convert a given size in bytes in a human-readable string
246 * \param size The size expressed in bytes to convert in string
247 * \return The string that respresents the size in an human-readable way
249 gchar *to_human_readable(goffset size)
251 static gchar str[14];
252 static gchar *b_format = NULL, *kb_format = NULL,
253 *mb_format = NULL, *gb_format = NULL;
254 register int t = 0, r = 0;
255 if (b_format == NULL) {
257 kb_format = _("%d.%02dKB");
258 mb_format = _("%d.%02dMB");
259 gb_format = _("%.2fGB");
262 if (size < (goffset)1024) {
263 g_snprintf(str, sizeof(str), b_format, (gint)size);
265 } else if (size >> 10 < (goffset)1024) {
266 divide(size, 10, t, r);
267 g_snprintf(str, sizeof(str), kb_format, t, r);
269 } else if (size >> 20 < (goffset)1024) {
270 divide(size, 20, t, r);
271 g_snprintf(str, sizeof(str), mb_format, t, r);
274 g_snprintf(str, sizeof(str), gb_format, (gfloat)(size >> 30));
279 /* strcmp with NULL-checking */
280 gint strcmp2(const gchar *s1, const gchar *s2)
282 if (s1 == NULL || s2 == NULL)
285 return strcmp(s1, s2);
287 /* strstr with NULL-checking */
288 gchar *strstr2(const gchar *s1, const gchar *s2)
290 if (s1 == NULL || s2 == NULL)
293 return strstr(s1, s2);
296 gint path_cmp(const gchar *s1, const gchar *s2)
301 gchar *s1buf, *s2buf;
304 if (s1 == NULL || s2 == NULL) return -1;
305 if (*s1 == '\0' || *s2 == '\0') return -1;
308 s1buf = g_strdup (s1);
309 s2buf = g_strdup (s2);
310 subst_char (s1buf, '/', G_DIR_SEPARATOR);
311 subst_char (s2buf, '/', G_DIR_SEPARATOR);
314 #endif /* !G_OS_WIN32 */
319 if (s1[len1 - 1] == G_DIR_SEPARATOR) len1--;
320 if (s2[len2 - 1] == G_DIR_SEPARATOR) len2--;
322 rc = strncmp(s1, s2, MAX(len1, len2));
326 #endif /* !G_OS_WIN32 */
330 /* remove trailing return code */
331 gchar *strretchomp(gchar *str)
335 if (!*str) return str;
337 for (s = str + strlen(str) - 1;
338 s >= str && (*s == '\n' || *s == '\r');
345 /* remove trailing character */
346 gchar *strtailchomp(gchar *str, gchar tail_char)
350 if (!*str) return str;
351 if (tail_char == '\0') return str;
353 for (s = str + strlen(str) - 1; s >= str && *s == tail_char; s--)
359 /* remove CR (carriage return) */
360 gchar *strcrchomp(gchar *str)
364 if (!*str) return str;
366 s = str + strlen(str) - 1;
367 if (*s == '\n' && s > str && *(s - 1) == '\r') {
375 gint file_strip_crs(const gchar *file)
377 FILE *fp = NULL, *outfp = NULL;
379 gchar *out = get_tmp_file();
383 fp = g_fopen(file, "rb");
387 outfp = g_fopen(out, "wb");
393 while (fgets(buf, sizeof (buf), fp) != NULL) {
395 if (fputs(buf, outfp) == EOF) {
403 if (fclose(outfp) == EOF) {
407 if (move_file(out, file, TRUE) < 0)
419 /* Similar to `strstr' but this function ignores the case of both strings. */
420 gchar *strcasestr(const gchar *haystack, const gchar *needle)
422 size_t haystack_len = strlen(haystack);
424 return strncasestr(haystack, haystack_len, needle);
427 gchar *strncasestr(const gchar *haystack, gint haystack_len, const gchar *needle)
429 register size_t needle_len;
431 needle_len = strlen(needle);
433 if (haystack_len < needle_len || needle_len == 0)
436 while (haystack_len >= needle_len) {
437 if (!g_ascii_strncasecmp(haystack, needle, needle_len))
438 return (gchar *)haystack;
448 gpointer my_memmem(gconstpointer haystack, size_t haystacklen,
449 gconstpointer needle, size_t needlelen)
451 const gchar *haystack_ = (const gchar *)haystack;
452 const gchar *needle_ = (const gchar *)needle;
453 const gchar *haystack_cur = (const gchar *)haystack;
454 size_t haystack_left = haystacklen;
457 return memchr(haystack_, *needle_, haystacklen);
459 while ((haystack_cur = memchr(haystack_cur, *needle_, haystack_left))
461 if (haystacklen - (haystack_cur - haystack_) < needlelen)
463 if (memcmp(haystack_cur + 1, needle_ + 1, needlelen - 1) == 0)
464 return (gpointer)haystack_cur;
467 haystack_left = haystacklen - (haystack_cur - haystack_);
474 /* Copy no more than N characters of SRC to DEST, with NULL terminating. */
475 gchar *strncpy2(gchar *dest, const gchar *src, size_t n)
477 register const gchar *s = src;
478 register gchar *d = dest;
488 /* Examine if next block is non-ASCII string */
489 gboolean is_next_nonascii(const gchar *s)
493 /* skip head space */
494 for (p = s; *p != '\0' && g_ascii_isspace(*p); p++)
496 for (; *p != '\0' && !g_ascii_isspace(*p); p++) {
497 if (*(guchar *)p > 127 || *(guchar *)p < 32)
504 gint get_next_word_len(const gchar *s)
508 for (; *s != '\0' && !g_ascii_isspace(*s); s++, len++)
514 static void trim_subject_for_compare(gchar *str)
518 eliminate_parenthesis(str, '[', ']');
519 eliminate_parenthesis(str, '(', ')');
522 srcp = str + subject_get_prefix_length(str);
524 memmove(str, srcp, strlen(srcp) + 1);
527 static void trim_subject_for_sort(gchar *str)
533 srcp = str + subject_get_prefix_length(str);
535 memmove(str, srcp, strlen(srcp) + 1);
538 /* compare subjects */
539 gint subject_compare(const gchar *s1, const gchar *s2)
543 if (!s1 || !s2) return -1;
544 if (!*s1 || !*s2) return -1;
546 Xstrdup_a(str1, s1, return -1);
547 Xstrdup_a(str2, s2, return -1);
549 trim_subject_for_compare(str1);
550 trim_subject_for_compare(str2);
552 if (!*str1 || !*str2) return -1;
554 return strcmp(str1, str2);
557 gint subject_compare_for_sort(const gchar *s1, const gchar *s2)
561 if (!s1 || !s2) return -1;
563 Xstrdup_a(str1, s1, return -1);
564 Xstrdup_a(str2, s2, return -1);
566 trim_subject_for_sort(str1);
567 trim_subject_for_sort(str2);
569 return g_utf8_collate(str1, str2);
572 void trim_subject(gchar *str)
574 register gchar *srcp;
580 srcp = str + subject_get_prefix_length(str);
585 } else if (*srcp == '(') {
597 else if (*srcp == cl)
604 while (g_ascii_isspace(*srcp)) srcp++;
605 memmove(str, srcp, strlen(srcp) + 1);
608 void eliminate_parenthesis(gchar *str, gchar op, gchar cl)
610 register gchar *srcp, *destp;
615 while ((destp = strchr(destp, op))) {
621 else if (*srcp == cl)
627 while (g_ascii_isspace(*srcp)) srcp++;
628 memmove(destp, srcp, strlen(srcp) + 1);
632 void extract_parenthesis(gchar *str, gchar op, gchar cl)
634 register gchar *srcp, *destp;
639 while ((srcp = strchr(destp, op))) {
642 memmove(destp, srcp + 1, strlen(srcp));
647 else if (*destp == cl)
659 static void extract_parenthesis_with_skip_quote(gchar *str, gchar quote_chr,
662 register gchar *srcp, *destp;
664 gboolean in_quote = FALSE;
668 while ((srcp = strchr_with_skip_quote(destp, quote_chr, op))) {
671 memmove(destp, srcp + 1, strlen(srcp));
674 if (*destp == op && !in_quote)
676 else if (*destp == cl && !in_quote)
678 else if (*destp == quote_chr)
690 void extract_quote(gchar *str, gchar quote_chr)
694 if ((str = strchr(str, quote_chr))) {
696 while ((p = strchr(p + 1, quote_chr)) && (p[-1] == '\\')) {
697 memmove(p - 1, p, strlen(p) + 1);
702 memmove(str, str + 1, p - str);
707 /* Returns a newly allocated string with all quote_chr not at the beginning
708 or the end of str escaped with '\' or the given str if not required. */
709 gchar *escape_internal_quotes(gchar *str, gchar quote_chr)
711 register gchar *p, *q;
715 if (str == NULL || *str == '\0')
718 /* search for unescaped quote_chr */
723 if (*p == quote_chr && *(p - 1) != '\\' && *(p + 1) != '\0')
727 if (!k) /* nothing to escape */
730 /* unescaped quote_chr found */
731 qstr = g_malloc(l + k + 1);
734 if (*p == quote_chr) {
739 if (*p == quote_chr && *(p - 1) != '\\' && *(p + 1) != '\0')
748 void eliminate_address_comment(gchar *str)
750 register gchar *srcp, *destp;
755 while ((destp = strchr(destp, '"'))) {
756 if ((srcp = strchr(destp + 1, '"'))) {
761 while (g_ascii_isspace(*srcp)) srcp++;
762 memmove(destp, srcp, strlen(srcp) + 1);
772 while ((destp = strchr_with_skip_quote(destp, '"', '('))) {
778 else if (*srcp == ')')
784 while (g_ascii_isspace(*srcp)) srcp++;
785 memmove(destp, srcp, strlen(srcp) + 1);
789 gchar *strchr_with_skip_quote(const gchar *str, gint quote_chr, gint c)
791 gboolean in_quote = FALSE;
794 if (*str == c && !in_quote)
796 if (*str == quote_chr)
804 void extract_address(gchar *str)
806 cm_return_if_fail(str != NULL);
807 eliminate_address_comment(str);
808 if (strchr_with_skip_quote(str, '"', '<'))
809 extract_parenthesis_with_skip_quote(str, '"', '<', '>');
813 void extract_list_id_str(gchar *str)
815 if (strchr_with_skip_quote(str, '"', '<'))
816 extract_parenthesis_with_skip_quote(str, '"', '<', '>');
820 static GSList *address_list_append_real(GSList *addr_list, const gchar *str, gboolean removecomments)
825 if (!str) return addr_list;
827 Xstrdup_a(work, str, return addr_list);
830 eliminate_address_comment(work);
833 while (workp && *workp) {
836 if ((p = strchr_with_skip_quote(workp, '"', ','))) {
842 if (removecomments && strchr_with_skip_quote(workp, '"', '<'))
843 extract_parenthesis_with_skip_quote
844 (workp, '"', '<', '>');
848 addr_list = g_slist_append(addr_list, g_strdup(workp));
856 GSList *address_list_append(GSList *addr_list, const gchar *str)
858 return address_list_append_real(addr_list, str, TRUE);
861 GSList *address_list_append_with_comments(GSList *addr_list, const gchar *str)
863 return address_list_append_real(addr_list, str, FALSE);
866 GSList *references_list_prepend(GSList *msgid_list, const gchar *str)
870 if (!str) return msgid_list;
873 while (strp && *strp) {
874 const gchar *start, *end;
877 if ((start = strchr(strp, '<')) != NULL) {
878 end = strchr(start + 1, '>');
883 msgid = g_strndup(start + 1, end - start - 1);
886 msgid_list = g_slist_prepend(msgid_list, msgid);
896 GSList *references_list_append(GSList *msgid_list, const gchar *str)
900 list = references_list_prepend(NULL, str);
901 list = g_slist_reverse(list);
902 msgid_list = g_slist_concat(msgid_list, list);
907 GSList *newsgroup_list_append(GSList *group_list, const gchar *str)
912 if (!str) return group_list;
914 Xstrdup_a(work, str, return group_list);
918 while (workp && *workp) {
921 if ((p = strchr_with_skip_quote(workp, '"', ','))) {
929 group_list = g_slist_append(group_list,
938 GList *add_history(GList *list, const gchar *str)
943 cm_return_val_if_fail(str != NULL, list);
945 old = g_list_find_custom(list, (gpointer)str, (GCompareFunc)strcmp2);
948 list = g_list_remove(list, old->data);
950 } else if (g_list_length(list) >= MAX_HISTORY_SIZE) {
953 last = g_list_last(list);
956 list = g_list_remove(list, last->data);
961 list = g_list_prepend(list, g_strdup(str));
966 void remove_return(gchar *str)
968 register gchar *p = str;
971 if (*p == '\n' || *p == '\r')
972 memmove(p, p + 1, strlen(p));
978 void remove_space(gchar *str)
980 register gchar *p = str;
985 while (g_ascii_isspace(*(p + spc)))
988 memmove(p, p + spc, strlen(p + spc) + 1);
994 void unfold_line(gchar *str)
996 register gchar *p = str;
1000 if (*p == '\n' || *p == '\r') {
1003 while (g_ascii_isspace(*(p + spc)))
1006 memmove(p, p + spc, strlen(p + spc) + 1);
1012 void subst_char(gchar *str, gchar orig, gchar subst)
1014 register gchar *p = str;
1023 void subst_chars(gchar *str, gchar *orig, gchar subst)
1025 register gchar *p = str;
1028 if (strchr(orig, *p) != NULL)
1034 void subst_for_filename(gchar *str)
1039 subst_chars(str, "\t\r\n\\/*:", '_');
1041 subst_chars(str, "\t\r\n\\/*", '_');
1045 void subst_for_shellsafe_filename(gchar *str)
1049 subst_for_filename(str);
1050 subst_chars(str, " \"'|&;()<>'!{}[]",'_');
1053 gboolean is_ascii_str(const gchar *str)
1055 const guchar *p = (const guchar *)str;
1057 while (*p != '\0') {
1058 if (*p != '\t' && *p != ' ' &&
1059 *p != '\r' && *p != '\n' &&
1060 (*p < 32 || *p >= 127))
1068 static const gchar * line_has_quote_char_last(const gchar * str, const gchar *quote_chars)
1070 gchar * position = NULL;
1071 gchar * tmp_pos = NULL;
1074 if (quote_chars == NULL)
1077 for (i = 0; i < strlen(quote_chars); i++) {
1078 tmp_pos = strrchr (str, quote_chars[i]);
1080 || (tmp_pos != NULL && position <= tmp_pos) )
1086 gint get_quote_level(const gchar *str, const gchar *quote_chars)
1088 const gchar *first_pos;
1089 const gchar *last_pos;
1090 const gchar *p = str;
1091 gint quote_level = -1;
1093 /* speed up line processing by only searching to the last '>' */
1094 if ((first_pos = line_has_quote_char(str, quote_chars)) != NULL) {
1095 /* skip a line if it contains a '<' before the initial '>' */
1096 if (memchr(str, '<', first_pos - str) != NULL)
1098 last_pos = line_has_quote_char_last(first_pos, quote_chars);
1102 while (p <= last_pos) {
1103 while (p < last_pos) {
1104 if (g_ascii_isspace(*p))
1110 if (strchr(quote_chars, *p))
1112 else if (*p != '-' && !g_ascii_isspace(*p) && p <= last_pos) {
1113 /* any characters are allowed except '-','<' and space */
1114 while (*p != '-' && *p != '<'
1115 && !strchr(quote_chars, *p)
1116 && !g_ascii_isspace(*p)
1119 if (strchr(quote_chars, *p))
1131 gint check_line_length(const gchar *str, gint max_chars, gint *line)
1133 const gchar *p = str, *q;
1134 gint cur_line = 0, len;
1136 while ((q = strchr(p, '\n')) != NULL) {
1138 if (len > max_chars) {
1148 if (len > max_chars) {
1157 const gchar * line_has_quote_char(const gchar * str, const gchar *quote_chars)
1159 gchar * position = NULL;
1160 gchar * tmp_pos = NULL;
1163 if (quote_chars == NULL)
1166 for (i = 0; i < strlen(quote_chars); i++) {
1167 tmp_pos = strchr (str, quote_chars[i]);
1169 || (tmp_pos != NULL && position >= tmp_pos) )
1175 static gchar *strstr_with_skip_quote(const gchar *haystack, const gchar *needle)
1177 register guint haystack_len, needle_len;
1178 gboolean in_squote = FALSE, in_dquote = FALSE;
1180 haystack_len = strlen(haystack);
1181 needle_len = strlen(needle);
1183 if (haystack_len < needle_len || needle_len == 0)
1186 while (haystack_len >= needle_len) {
1187 if (!in_squote && !in_dquote &&
1188 !strncmp(haystack, needle, needle_len))
1189 return (gchar *)haystack;
1191 /* 'foo"bar"' -> foo"bar"
1192 "foo'bar'" -> foo'bar' */
1193 if (*haystack == '\'') {
1196 else if (!in_dquote)
1198 } else if (*haystack == '\"') {
1201 else if (!in_squote)
1203 } else if (*haystack == '\\') {
1215 gchar **strsplit_with_quote(const gchar *str, const gchar *delim,
1218 GSList *string_list = NULL, *slist;
1219 gchar **str_array, *s, *new_str;
1220 guint i, n = 1, len;
1222 cm_return_val_if_fail(str != NULL, NULL);
1223 cm_return_val_if_fail(delim != NULL, NULL);
1226 max_tokens = G_MAXINT;
1228 s = strstr_with_skip_quote(str, delim);
1230 guint delimiter_len = strlen(delim);
1234 new_str = g_strndup(str, len);
1236 if (new_str[0] == '\'' || new_str[0] == '\"') {
1237 if (new_str[len - 1] == new_str[0]) {
1238 new_str[len - 1] = '\0';
1239 memmove(new_str, new_str + 1, len - 1);
1242 string_list = g_slist_prepend(string_list, new_str);
1244 str = s + delimiter_len;
1245 s = strstr_with_skip_quote(str, delim);
1246 } while (--max_tokens && s);
1250 new_str = g_strdup(str);
1251 if (new_str[0] == '\'' || new_str[0] == '\"') {
1253 if (new_str[len - 1] == new_str[0]) {
1254 new_str[len - 1] = '\0';
1255 memmove(new_str, new_str + 1, len - 1);
1258 string_list = g_slist_prepend(string_list, new_str);
1262 str_array = g_new(gchar*, n);
1266 str_array[i--] = NULL;
1267 for (slist = string_list; slist; slist = slist->next)
1268 str_array[i--] = slist->data;
1270 g_slist_free(string_list);
1275 gchar *get_abbrev_newsgroup_name(const gchar *group, gint len)
1277 gchar *abbrev_group;
1279 const gchar *p = group;
1282 cm_return_val_if_fail(group != NULL, NULL);
1284 last = group + strlen(group);
1285 abbrev_group = ap = g_malloc(strlen(group) + 1);
1290 if ((ap - abbrev_group) + (last - p) > len && strchr(p, '.')) {
1292 while (*p != '.') p++;
1295 return abbrev_group;
1300 return abbrev_group;
1303 gchar *trim_string(const gchar *str, gint len)
1305 const gchar *p = str;
1310 if (!str) return NULL;
1311 if (strlen(str) <= len)
1312 return g_strdup(str);
1313 if (g_utf8_validate(str, -1, NULL) == FALSE)
1314 return g_strdup(str);
1316 while (*p != '\0') {
1317 mb_len = g_utf8_skip[*(guchar *)p];
1320 else if (new_len + mb_len > len)
1327 Xstrndup_a(new_str, str, new_len, return g_strdup(str));
1328 return g_strconcat(new_str, "...", NULL);
1331 GList *uri_list_extract_filenames(const gchar *uri_list)
1333 GList *result = NULL;
1335 gchar *escaped_utf8uri;
1341 while (g_ascii_isspace(*p)) p++;
1342 if (!strncmp(p, "file:", 5)) {
1345 while (*q && *q != '\n' && *q != '\r') q++;
1348 gchar *file, *locale_file = NULL;
1350 while (q > p && g_ascii_isspace(*q))
1352 Xalloca(escaped_utf8uri, q - p + 2,
1354 Xalloca(file, q - p + 2,
1357 strncpy(escaped_utf8uri, p, q - p + 1);
1358 escaped_utf8uri[q - p + 1] = '\0';
1359 decode_uri(file, escaped_utf8uri);
1361 * g_filename_from_uri() rejects escaped/locale encoded uri
1362 * string which come from Nautilus.
1365 if (g_utf8_validate(file, -1, NULL))
1367 = conv_codeset_strdup(
1370 conv_get_locale_charset_str());
1372 locale_file = g_strdup(file + 5);
1374 locale_file = g_filename_from_uri(escaped_utf8uri, NULL, NULL);
1376 result = g_list_append(result, locale_file);
1380 p = strchr(p, '\n');
1387 /* Converts two-digit hexadecimal to decimal. Used for unescaping escaped
1390 static gint axtoi(const gchar *hexstr)
1392 gint hi, lo, result;
1395 if ('0' <= hi && hi <= '9') {
1398 if ('a' <= hi && hi <= 'f') {
1401 if ('A' <= hi && hi <= 'F') {
1406 if ('0' <= lo && lo <= '9') {
1409 if ('a' <= lo && lo <= 'f') {
1412 if ('A' <= lo && lo <= 'F') {
1415 result = lo + (16 * hi);
1419 gboolean is_uri_string(const gchar *str)
1421 while (str && *str && g_ascii_isspace(*str))
1423 return (g_ascii_strncasecmp(str, "http://", 7) == 0 ||
1424 g_ascii_strncasecmp(str, "https://", 8) == 0 ||
1425 g_ascii_strncasecmp(str, "ftp://", 6) == 0 ||
1426 g_ascii_strncasecmp(str, "www.", 4) == 0);
1429 gchar *get_uri_path(const gchar *uri)
1431 while (uri && *uri && g_ascii_isspace(*uri))
1433 if (g_ascii_strncasecmp(uri, "http://", 7) == 0)
1434 return (gchar *)(uri + 7);
1435 else if (g_ascii_strncasecmp(uri, "https://", 8) == 0)
1436 return (gchar *)(uri + 8);
1437 else if (g_ascii_strncasecmp(uri, "ftp://", 6) == 0)
1438 return (gchar *)(uri + 6);
1440 return (gchar *)uri;
1443 gint get_uri_len(const gchar *str)
1447 if (is_uri_string(str)) {
1448 for (p = str; *p != '\0'; p++) {
1449 if (!g_ascii_isgraph(*p) || strchr("()<>\"", *p))
1458 /* Decodes URL-Encoded strings (i.e. strings in which spaces are replaced by
1459 * plusses, and escape characters are used)
1461 void decode_uri_with_plus(gchar *decoded_uri, const gchar *encoded_uri, gboolean with_plus)
1463 gchar *dec = decoded_uri;
1464 const gchar *enc = encoded_uri;
1469 if (isxdigit((guchar)enc[0]) &&
1470 isxdigit((guchar)enc[1])) {
1476 if (with_plus && *enc == '+')
1488 void decode_uri(gchar *decoded_uri, const gchar *encoded_uri)
1490 decode_uri_with_plus(decoded_uri, encoded_uri, TRUE);
1493 static gchar *decode_uri_gdup(const gchar *encoded_uri)
1495 gchar *buffer = g_malloc(strlen(encoded_uri)+1);
1496 decode_uri_with_plus(buffer, encoded_uri, FALSE);
1500 gint scan_mailto_url(const gchar *mailto, gchar **from, gchar **to, gchar **cc, gchar **bcc,
1501 gchar **subject, gchar **body, gchar ***attach, gchar **inreplyto)
1505 const gchar *forbidden_uris[] = { ".gnupg/",
1511 gint num_attach = 0;
1512 gchar **my_att = NULL;
1514 Xstrdup_a(tmp_mailto, mailto, return -1);
1516 if (!strncmp(tmp_mailto, "mailto:", 7))
1519 p = strchr(tmp_mailto, '?');
1526 *to = decode_uri_gdup(tmp_mailto);
1528 my_att = g_malloc(sizeof(char *));
1532 gchar *field, *value;
1549 if (*value == '\0') continue;
1551 if (from && !g_ascii_strcasecmp(field, "from")) {
1553 *from = decode_uri_gdup(value);
1555 gchar *tmp = decode_uri_gdup(value);
1556 gchar *new_from = g_strdup_printf("%s, %s", *from, tmp);
1560 } else if (cc && !g_ascii_strcasecmp(field, "cc")) {
1562 *cc = decode_uri_gdup(value);
1564 gchar *tmp = decode_uri_gdup(value);
1565 gchar *new_cc = g_strdup_printf("%s, %s", *cc, tmp);
1569 } else if (bcc && !g_ascii_strcasecmp(field, "bcc")) {
1571 *bcc = decode_uri_gdup(value);
1573 gchar *tmp = decode_uri_gdup(value);
1574 gchar *new_bcc = g_strdup_printf("%s, %s", *bcc, tmp);
1578 } else if (subject && !*subject &&
1579 !g_ascii_strcasecmp(field, "subject")) {
1580 *subject = decode_uri_gdup(value);
1581 } else if (body && !*body && !g_ascii_strcasecmp(field, "body")) {
1582 *body = decode_uri_gdup(value);
1583 } else if (body && !*body && !g_ascii_strcasecmp(field, "insert")) {
1584 gchar *tmp = decode_uri_gdup(value);
1585 if (!g_file_get_contents(tmp, body, NULL, NULL)) {
1586 g_warning("couldn't set insert file '%s' in body", value);
1590 } else if (attach && !g_ascii_strcasecmp(field, "attach")) {
1592 gchar *tmp = decode_uri_gdup(value);
1593 for (; forbidden_uris[i]; i++) {
1594 if (strstr(tmp, forbidden_uris[i])) {
1595 g_print("Refusing to attach '%s', potential private data leak\n",
1603 /* attach is correct */
1605 my_att = g_realloc(my_att, (sizeof(char *))*(num_attach+1));
1606 my_att[num_attach-1] = tmp;
1607 my_att[num_attach] = NULL;
1609 } else if (inreplyto && !*inreplyto &&
1610 !g_ascii_strcasecmp(field, "in-reply-to")) {
1611 *inreplyto = decode_uri_gdup(value);
1622 #include <windows.h>
1623 #ifndef CSIDL_APPDATA
1624 #define CSIDL_APPDATA 0x001a
1626 #ifndef CSIDL_LOCAL_APPDATA
1627 #define CSIDL_LOCAL_APPDATA 0x001c
1629 #ifndef CSIDL_FLAG_CREATE
1630 #define CSIDL_FLAG_CREATE 0x8000
1632 #define DIM(v) (sizeof(v)/sizeof((v)[0]))
1636 w32_strerror (int w32_errno)
1638 static char strerr[256];
1639 int ec = (int)GetLastError ();
1643 FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM, NULL, w32_errno,
1644 MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
1645 strerr, DIM (strerr)-1, NULL);
1649 static __inline__ void *
1650 dlopen (const char * name, int flag)
1652 void * hd = LoadLibrary (name);
1656 static __inline__ void *
1657 dlsym (void * hd, const char * sym)
1661 void * fnc = GetProcAddress (hd, sym);
1670 static __inline__ const char *
1673 return w32_strerror (0);
1677 static __inline__ int
1689 w32_shgetfolderpath (HWND a, int b, HANDLE c, DWORD d, LPSTR e)
1691 static int initialized;
1692 static HRESULT (WINAPI * func)(HWND,int,HANDLE,DWORD,LPSTR);
1696 static char *dllnames[] = { "shell32.dll", "shfolder.dll", NULL };
1702 for (i=0, handle = NULL; !handle && dllnames[i]; i++)
1704 handle = dlopen (dllnames[i], RTLD_LAZY);
1707 func = dlsym (handle, "SHGetFolderPathW");
1718 return func (a,b,c,d,e);
1723 /* Returns a static string with the directroy from which the module
1724 has been loaded. Returns an empty string on error. */
1725 static char *w32_get_module_dir(void)
1727 static char *moddir;
1730 char name[MAX_PATH+10];
1733 if ( !GetModuleFileNameA (0, name, sizeof (name)-10) )
1736 p = strrchr (name, '\\');
1742 moddir = g_strdup (name);
1746 #endif /* G_OS_WIN32 */
1748 /* Return a static string with the locale dir. */
1749 const gchar *get_locale_dir(void)
1751 static gchar *loc_dir;
1755 loc_dir = g_strconcat(w32_get_module_dir(), G_DIR_SEPARATOR_S,
1756 "\\share\\locale", NULL);
1759 loc_dir = LOCALEDIR;
1765 const gchar *get_home_dir(void)
1768 static char home_dir_utf16[MAX_PATH] = "";
1769 static gchar *home_dir_utf8 = NULL;
1770 if (home_dir_utf16[0] == '\0') {
1771 if (w32_shgetfolderpath
1772 (NULL, CSIDL_APPDATA|CSIDL_FLAG_CREATE,
1773 NULL, 0, home_dir_utf16) < 0)
1774 strcpy (home_dir_utf16, "C:\\Sylpheed");
1775 home_dir_utf8 = g_utf16_to_utf8 ((const gunichar *)home_dir_utf16, -1, NULL, NULL, NULL);
1777 return home_dir_utf8;
1779 static const gchar *homeenv = NULL;
1784 if (!homeenv && g_getenv("HOME") != NULL)
1785 homeenv = g_strdup(g_getenv("HOME"));
1787 homeenv = g_get_home_dir();
1793 static gchar *claws_rc_dir = NULL;
1794 static gboolean rc_dir_alt = FALSE;
1795 const gchar *get_rc_dir(void)
1798 if (!claws_rc_dir) {
1799 claws_rc_dir = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
1801 debug_print("using default rc_dir %s\n", claws_rc_dir);
1803 return claws_rc_dir;
1806 void set_rc_dir(const gchar *dir)
1808 gchar *canonical_dir;
1809 if (claws_rc_dir != NULL) {
1810 g_print("Error: rc_dir already set\n");
1812 int err = cm_canonicalize_filename(dir, &canonical_dir);
1816 g_print("Error looking for %s: %d(%s)\n",
1817 dir, -err, g_strerror(-err));
1822 claws_rc_dir = canonical_dir;
1824 len = strlen(claws_rc_dir);
1825 if (claws_rc_dir[len - 1] == G_DIR_SEPARATOR)
1826 claws_rc_dir[len - 1] = '\0';
1828 debug_print("set rc_dir to %s\n", claws_rc_dir);
1829 if (!is_dir_exist(claws_rc_dir)) {
1830 if (make_dir_hier(claws_rc_dir) != 0) {
1831 g_print("Error: can't create %s\n",
1839 gboolean rc_dir_is_alt(void) {
1843 const gchar *get_mail_base_dir(void)
1845 return get_home_dir();
1848 const gchar *get_news_cache_dir(void)
1850 static gchar *news_cache_dir = NULL;
1851 if (!news_cache_dir)
1852 news_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1853 NEWS_CACHE_DIR, NULL);
1855 return news_cache_dir;
1858 const gchar *get_imap_cache_dir(void)
1860 static gchar *imap_cache_dir = NULL;
1862 if (!imap_cache_dir)
1863 imap_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1864 IMAP_CACHE_DIR, NULL);
1866 return imap_cache_dir;
1869 const gchar *get_mime_tmp_dir(void)
1871 static gchar *mime_tmp_dir = NULL;
1874 mime_tmp_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1875 MIME_TMP_DIR, NULL);
1877 return mime_tmp_dir;
1880 const gchar *get_template_dir(void)
1882 static gchar *template_dir = NULL;
1885 template_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1886 TEMPLATE_DIR, NULL);
1888 return template_dir;
1892 const gchar *get_cert_file(void)
1894 const gchar *cert_file = NULL;
1896 cert_file = g_strconcat(w32_get_module_dir(),
1897 "\\share\\claws-mail\\",
1898 "ca-certificates.crt",
1904 /* Return the filepath of the claws-mail.desktop file */
1905 const gchar *get_desktop_file(void)
1907 #ifdef DESKTOPFILEPATH
1908 return DESKTOPFILEPATH;
1914 /* Return the default directory for Plugins. */
1915 const gchar *get_plugin_dir(void)
1918 static gchar *plugin_dir = NULL;
1921 plugin_dir = g_strconcat(w32_get_module_dir(),
1922 "\\lib\\claws-mail\\plugins\\",
1926 if (is_dir_exist(PLUGINDIR))
1929 static gchar *plugin_dir = NULL;
1931 plugin_dir = g_strconcat(get_rc_dir(),
1932 G_DIR_SEPARATOR_S, "plugins",
1933 G_DIR_SEPARATOR_S, NULL);
1941 /* Return the default directory for Themes. */
1942 const gchar *get_themes_dir(void)
1944 static gchar *themes_dir = NULL;
1947 themes_dir = g_strconcat(w32_get_module_dir(),
1948 "\\share\\claws-mail\\themes",
1954 const gchar *get_tmp_dir(void)
1956 static gchar *tmp_dir = NULL;
1959 tmp_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1965 gchar *get_tmp_file(void)
1968 static guint32 id = 0;
1970 tmp_file = g_strdup_printf("%s%ctmpfile.%08x",
1971 get_tmp_dir(), G_DIR_SEPARATOR, id++);
1976 const gchar *get_domain_name(void)
1979 static gchar *domain_name = NULL;
1980 struct addrinfo hints, *res;
1985 if (gethostname(hostname, sizeof(hostname)) != 0) {
1986 perror("gethostname");
1987 domain_name = "localhost";
1989 memset(&hints, 0, sizeof(struct addrinfo));
1990 hints.ai_family = AF_UNSPEC;
1991 hints.ai_socktype = 0;
1992 hints.ai_flags = AI_CANONNAME;
1993 hints.ai_protocol = 0;
1995 s = getaddrinfo(hostname, NULL, &hints, &res);
1997 fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
1998 domain_name = g_strdup(hostname);
2000 domain_name = g_strdup(res->ai_canonname);
2004 debug_print("domain name = %s\n", domain_name);
2013 off_t get_file_size(const gchar *file)
2017 if (g_stat(file, &s) < 0) {
2018 FILE_OP_ERROR(file, "stat");
2025 time_t get_file_mtime(const gchar *file)
2029 if (g_stat(file, &s) < 0) {
2030 FILE_OP_ERROR(file, "stat");
2037 off_t get_file_size_as_crlf(const gchar *file)
2041 gchar buf[BUFFSIZE];
2043 if ((fp = g_fopen(file, "rb")) == NULL) {
2044 FILE_OP_ERROR(file, "g_fopen");
2048 while (fgets(buf, sizeof(buf), fp) != NULL) {
2050 size += strlen(buf) + 2;
2054 FILE_OP_ERROR(file, "fgets");
2063 gboolean file_exist(const gchar *file, gboolean allow_fifo)
2070 if (g_stat(file, &s) < 0) {
2071 if (ENOENT != errno) FILE_OP_ERROR(file, "stat");
2075 if (S_ISREG(s.st_mode) || (allow_fifo && S_ISFIFO(s.st_mode)))
2082 /* Test on whether FILE is a relative file name. This is
2083 * straightforward for Unix but more complex for Windows. */
2084 gboolean is_relative_filename(const gchar *file)
2089 if ( *file == '\\' && file[1] == '\\' && strchr (file+2, '\\') )
2090 return FALSE; /* Prefixed with a hostname - this can't
2091 * be a relative name. */
2093 if ( ((*file >= 'a' && *file <= 'z')
2094 || (*file >= 'A' && *file <= 'Z'))
2096 file += 2; /* Skip drive letter. */
2098 return !(*file == '\\' || *file == '/');
2100 return !(*file == G_DIR_SEPARATOR);
2105 gboolean is_dir_exist(const gchar *dir)
2110 return g_file_test(dir, G_FILE_TEST_IS_DIR);
2113 gboolean is_file_entry_exist(const gchar *file)
2118 return g_file_test(file, G_FILE_TEST_EXISTS);
2121 gboolean dirent_is_regular_file(struct dirent *d)
2123 #if !defined(G_OS_WIN32) && defined(HAVE_DIRENT_D_TYPE)
2124 if (d->d_type == DT_REG)
2126 else if (d->d_type != DT_UNKNOWN)
2130 return g_file_test(d->d_name, G_FILE_TEST_IS_REGULAR);
2133 gint change_dir(const gchar *dir)
2135 gchar *prevdir = NULL;
2138 prevdir = g_get_current_dir();
2140 if (g_chdir(dir) < 0) {
2141 FILE_OP_ERROR(dir, "chdir");
2142 if (debug_mode) g_free(prevdir);
2144 } else if (debug_mode) {
2147 cwd = g_get_current_dir();
2148 if (strcmp(prevdir, cwd) != 0)
2149 g_print("current dir: %s\n", cwd);
2157 gint make_dir(const gchar *dir)
2159 if (g_mkdir(dir, S_IRWXU) < 0) {
2160 FILE_OP_ERROR(dir, "mkdir");
2163 if (g_chmod(dir, S_IRWXU) < 0)
2164 FILE_OP_ERROR(dir, "chmod");
2169 gint make_dir_hier(const gchar *dir)
2174 for (p = dir; (p = strchr(p, G_DIR_SEPARATOR)) != NULL; p++) {
2175 parent_dir = g_strndup(dir, p - dir);
2176 if (*parent_dir != '\0') {
2177 if (!is_dir_exist(parent_dir)) {
2178 if (make_dir(parent_dir) < 0) {
2187 if (!is_dir_exist(dir)) {
2188 if (make_dir(dir) < 0)
2195 gint remove_all_files(const gchar *dir)
2198 const gchar *dir_name;
2201 prev_dir = g_get_current_dir();
2203 if (g_chdir(dir) < 0) {
2204 FILE_OP_ERROR(dir, "chdir");
2209 if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2210 g_warning("failed to open directory: %s", dir);
2215 while ((dir_name = g_dir_read_name(dp)) != NULL) {
2216 if (claws_unlink(dir_name) < 0)
2217 FILE_OP_ERROR(dir_name, "unlink");
2222 if (g_chdir(prev_dir) < 0) {
2223 FILE_OP_ERROR(prev_dir, "chdir");
2233 gint remove_numbered_files(const gchar *dir, guint first, guint last)
2236 const gchar *dir_name;
2240 if (first == last) {
2241 /* Skip all the dir reading part. */
2242 gchar *filename = g_strdup_printf("%s%s%u", dir, G_DIR_SEPARATOR_S, first);
2243 if (is_dir_exist(filename)) {
2244 /* a numbered directory with this name exists,
2245 * remove the dot-file instead */
2247 filename = g_strdup_printf("%s%s.%u", dir, G_DIR_SEPARATOR_S, first);
2249 if (claws_unlink(filename) < 0) {
2250 FILE_OP_ERROR(filename, "unlink");
2258 prev_dir = g_get_current_dir();
2260 if (g_chdir(dir) < 0) {
2261 FILE_OP_ERROR(dir, "chdir");
2266 if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2267 g_warning("failed to open directory: %s", dir);
2272 while ((dir_name = g_dir_read_name(dp)) != NULL) {
2273 file_no = to_number(dir_name);
2274 if (file_no > 0 && first <= file_no && file_no <= last) {
2275 if (is_dir_exist(dir_name)) {
2276 gchar *dot_file = g_strdup_printf(".%s", dir_name);
2277 if (is_file_exist(dot_file) && claws_unlink(dot_file) < 0) {
2278 FILE_OP_ERROR(dot_file, "unlink");
2283 if (claws_unlink(dir_name) < 0)
2284 FILE_OP_ERROR(dir_name, "unlink");
2290 if (g_chdir(prev_dir) < 0) {
2291 FILE_OP_ERROR(prev_dir, "chdir");
2301 gint remove_numbered_files_not_in_list(const gchar *dir, GSList *numberlist)
2304 const gchar *dir_name;
2307 GHashTable *wanted_files;
2309 GError *error = NULL;
2311 if (numberlist == NULL)
2314 prev_dir = g_get_current_dir();
2316 if (g_chdir(dir) < 0) {
2317 FILE_OP_ERROR(dir, "chdir");
2322 if ((dp = g_dir_open(".", 0, &error)) == NULL) {
2323 g_message("Couldn't open current directory: %s (%d).\n",
2324 error->message, error->code);
2325 g_error_free(error);
2330 wanted_files = g_hash_table_new(g_direct_hash, g_direct_equal);
2331 for (cur = numberlist; cur != NULL; cur = cur->next) {
2332 /* numberlist->data is expected to be GINT_TO_POINTER */
2333 g_hash_table_insert(wanted_files, cur->data, GINT_TO_POINTER(1));
2336 while ((dir_name = g_dir_read_name(dp)) != NULL) {
2337 file_no = to_number(dir_name);
2338 if (is_dir_exist(dir_name))
2340 if (file_no > 0 && g_hash_table_lookup(wanted_files, GINT_TO_POINTER(file_no)) == NULL) {
2341 debug_print("removing unwanted file %d from %s\n", file_no, dir);
2342 if (is_dir_exist(dir_name)) {
2343 gchar *dot_file = g_strdup_printf(".%s", dir_name);
2344 if (is_file_exist(dot_file) && claws_unlink(dot_file) < 0) {
2345 FILE_OP_ERROR(dot_file, "unlink");
2350 if (claws_unlink(dir_name) < 0)
2351 FILE_OP_ERROR(dir_name, "unlink");
2356 g_hash_table_destroy(wanted_files);
2358 if (g_chdir(prev_dir) < 0) {
2359 FILE_OP_ERROR(prev_dir, "chdir");
2369 gint remove_all_numbered_files(const gchar *dir)
2371 return remove_numbered_files(dir, 0, UINT_MAX);
2374 gint remove_dir_recursive(const gchar *dir)
2378 const gchar *dir_name;
2381 if (g_stat(dir, &s) < 0) {
2382 FILE_OP_ERROR(dir, "stat");
2383 if (ENOENT == errno) return 0;
2387 if (!S_ISDIR(s.st_mode)) {
2388 if (claws_unlink(dir) < 0) {
2389 FILE_OP_ERROR(dir, "unlink");
2396 prev_dir = g_get_current_dir();
2397 /* g_print("prev_dir = %s\n", prev_dir); */
2399 if (!path_cmp(prev_dir, dir)) {
2401 if (g_chdir("..") < 0) {
2402 FILE_OP_ERROR(dir, "chdir");
2405 prev_dir = g_get_current_dir();
2408 if (g_chdir(dir) < 0) {
2409 FILE_OP_ERROR(dir, "chdir");
2414 if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2415 g_warning("failed to open directory: %s", dir);
2421 /* remove all files in the directory */
2422 while ((dir_name = g_dir_read_name(dp)) != NULL) {
2423 /* g_print("removing %s\n", dir_name); */
2425 if (is_dir_exist(dir_name)) {
2428 if ((ret = remove_dir_recursive(dir_name)) < 0) {
2429 g_warning("can't remove directory: %s", dir_name);
2433 if (claws_unlink(dir_name) < 0)
2434 FILE_OP_ERROR(dir_name, "unlink");
2440 if (g_chdir(prev_dir) < 0) {
2441 FILE_OP_ERROR(prev_dir, "chdir");
2448 if (g_rmdir(dir) < 0) {
2449 FILE_OP_ERROR(dir, "rmdir");
2456 gint rename_force(const gchar *oldpath, const gchar *newpath)
2459 if (!is_file_entry_exist(oldpath)) {
2463 if (is_file_exist(newpath)) {
2464 if (claws_unlink(newpath) < 0)
2465 FILE_OP_ERROR(newpath, "unlink");
2468 return g_rename(oldpath, newpath);
2472 * Append src file body to the tail of dest file.
2473 * Now keep_backup has no effects.
2475 gint append_file(const gchar *src, const gchar *dest, gboolean keep_backup)
2477 FILE *src_fp, *dest_fp;
2481 gboolean err = FALSE;
2483 if ((src_fp = g_fopen(src, "rb")) == NULL) {
2484 FILE_OP_ERROR(src, "g_fopen");
2488 if ((dest_fp = g_fopen(dest, "ab")) == NULL) {
2489 FILE_OP_ERROR(dest, "g_fopen");
2494 if (change_file_mode_rw(dest_fp, dest) < 0) {
2495 FILE_OP_ERROR(dest, "chmod");
2496 g_warning("can't change file mode: %s", dest);
2499 while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
2500 if (n_read < sizeof(buf) && ferror(src_fp))
2502 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2503 g_warning("writing to %s failed.", dest);
2511 if (ferror(src_fp)) {
2512 FILE_OP_ERROR(src, "fread");
2516 if (fclose(dest_fp) == EOF) {
2517 FILE_OP_ERROR(dest, "fclose");
2529 gint copy_file(const gchar *src, const gchar *dest, gboolean keep_backup)
2531 FILE *src_fp, *dest_fp;
2534 gchar *dest_bak = NULL;
2535 gboolean err = FALSE;
2537 if ((src_fp = g_fopen(src, "rb")) == NULL) {
2538 FILE_OP_ERROR(src, "g_fopen");
2541 if (is_file_exist(dest)) {
2542 dest_bak = g_strconcat(dest, ".bak", NULL);
2543 if (rename_force(dest, dest_bak) < 0) {
2544 FILE_OP_ERROR(dest, "rename");
2551 if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2552 FILE_OP_ERROR(dest, "g_fopen");
2555 if (rename_force(dest_bak, dest) < 0)
2556 FILE_OP_ERROR(dest_bak, "rename");
2562 if (change_file_mode_rw(dest_fp, dest) < 0) {
2563 FILE_OP_ERROR(dest, "chmod");
2564 g_warning("can't change file mode: %s", dest);
2567 while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
2568 if (n_read < sizeof(buf) && ferror(src_fp))
2570 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2571 g_warning("writing to %s failed.", dest);
2576 if (rename_force(dest_bak, dest) < 0)
2577 FILE_OP_ERROR(dest_bak, "rename");
2584 if (ferror(src_fp)) {
2585 FILE_OP_ERROR(src, "fread");
2589 if (fclose(dest_fp) == EOF) {
2590 FILE_OP_ERROR(dest, "fclose");
2597 if (rename_force(dest_bak, dest) < 0)
2598 FILE_OP_ERROR(dest_bak, "rename");
2604 if (keep_backup == FALSE && dest_bak)
2605 claws_unlink(dest_bak);
2612 gint move_file(const gchar *src, const gchar *dest, gboolean overwrite)
2614 if (overwrite == FALSE && is_file_exist(dest)) {
2615 g_warning("move_file(): file %s already exists.", dest);
2619 if (rename_force(src, dest) == 0) return 0;
2621 if (EXDEV != errno) {
2622 FILE_OP_ERROR(src, "rename");
2626 if (copy_file(src, dest, FALSE) < 0) return -1;
2633 gint copy_file_part_to_fp(FILE *fp, off_t offset, size_t length, FILE *dest_fp)
2636 gint bytes_left, to_read;
2639 if (fseek(fp, offset, SEEK_SET) < 0) {
2644 bytes_left = length;
2645 to_read = MIN(bytes_left, sizeof(buf));
2647 while ((n_read = fread(buf, sizeof(gchar), to_read, fp)) > 0) {
2648 if (n_read < to_read && ferror(fp))
2650 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2653 bytes_left -= n_read;
2654 if (bytes_left == 0)
2656 to_read = MIN(bytes_left, sizeof(buf));
2667 gint copy_file_part(FILE *fp, off_t offset, size_t length, const gchar *dest)
2670 gboolean err = FALSE;
2672 if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2673 FILE_OP_ERROR(dest, "g_fopen");
2677 if (change_file_mode_rw(dest_fp, dest) < 0) {
2678 FILE_OP_ERROR(dest, "chmod");
2679 g_warning("can't change file mode: %s", dest);
2682 if (copy_file_part_to_fp(fp, offset, length, dest_fp) < 0)
2685 if (!err && fclose(dest_fp) == EOF) {
2686 FILE_OP_ERROR(dest, "fclose");
2691 g_warning("writing to %s failed.", dest);
2699 /* convert line endings into CRLF. If the last line doesn't end with
2700 * linebreak, add it.
2702 gchar *canonicalize_str(const gchar *str)
2708 for (p = str; *p != '\0'; ++p) {
2715 if (p == str || *(p - 1) != '\n')
2718 out = outp = g_malloc(new_len + 1);
2719 for (p = str; *p != '\0'; ++p) {
2726 if (p == str || *(p - 1) != '\n') {
2735 gint canonicalize_file(const gchar *src, const gchar *dest)
2737 FILE *src_fp, *dest_fp;
2738 gchar buf[BUFFSIZE];
2740 gboolean err = FALSE;
2741 gboolean last_linebreak = FALSE;
2743 if (src == NULL || dest == NULL)
2746 if ((src_fp = g_fopen(src, "rb")) == NULL) {
2747 FILE_OP_ERROR(src, "g_fopen");
2751 if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2752 FILE_OP_ERROR(dest, "g_fopen");
2757 if (change_file_mode_rw(dest_fp, dest) < 0) {
2758 FILE_OP_ERROR(dest, "chmod");
2759 g_warning("can't change file mode: %s", dest);
2762 while (fgets(buf, sizeof(buf), src_fp) != NULL) {
2766 if (len == 0) break;
2767 last_linebreak = FALSE;
2769 if (buf[len - 1] != '\n') {
2770 last_linebreak = TRUE;
2771 r = fputs(buf, dest_fp);
2772 } else if (len > 1 && buf[len - 1] == '\n' && buf[len - 2] == '\r') {
2773 r = fputs(buf, dest_fp);
2776 r = fwrite(buf, 1, len - 1, dest_fp);
2781 r = fputs("\r\n", dest_fp);
2785 g_warning("writing to %s failed.", dest);
2793 if (last_linebreak == TRUE) {
2794 if (fputs("\r\n", dest_fp) == EOF)
2798 if (ferror(src_fp)) {
2799 FILE_OP_ERROR(src, "fgets");
2803 if (fclose(dest_fp) == EOF) {
2804 FILE_OP_ERROR(dest, "fclose");
2816 gint canonicalize_file_replace(const gchar *file)
2820 tmp_file = get_tmp_file();
2822 if (canonicalize_file(file, tmp_file) < 0) {
2827 if (move_file(tmp_file, file, TRUE) < 0) {
2828 g_warning("can't replace file: %s", file);
2829 claws_unlink(tmp_file);
2838 gchar *normalize_newlines(const gchar *str)
2843 out = outp = g_malloc(strlen(str) + 1);
2844 for (p = str; *p != '\0'; ++p) {
2846 if (*(p + 1) != '\n')
2857 gchar *get_outgoing_rfc2822_str(FILE *fp)
2859 gchar buf[BUFFSIZE];
2863 str = g_string_new(NULL);
2865 /* output header part */
2866 while (fgets(buf, sizeof(buf), fp) != NULL) {
2868 if (!g_ascii_strncasecmp(buf, "Bcc:", 4)) {
2875 else if (next != ' ' && next != '\t') {
2879 if (fgets(buf, sizeof(buf), fp) == NULL)
2883 g_string_append(str, buf);
2884 g_string_append(str, "\r\n");
2890 /* output body part */
2891 while (fgets(buf, sizeof(buf), fp) != NULL) {
2894 g_string_append_c(str, '.');
2895 g_string_append(str, buf);
2896 g_string_append(str, "\r\n");
2900 g_string_free(str, FALSE);
2906 * Create a new boundary in a way that it is very unlikely that this
2907 * will occur in the following text. It would be easy to ensure
2908 * uniqueness if everything is either quoted-printable or base64
2909 * encoded (note that conversion is allowed), but because MIME bodies
2910 * may be nested, it may happen that the same boundary has already
2913 * boundary := 0*69<bchars> bcharsnospace
2914 * bchars := bcharsnospace / " "
2915 * bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" /
2916 * "+" / "_" / "," / "-" / "." /
2917 * "/" / ":" / "=" / "?"
2919 * some special characters removed because of buggy MTAs
2922 gchar *generate_mime_boundary(const gchar *prefix)
2924 static gchar tbl[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
2925 "abcdefghijklmnopqrstuvwxyz"
2930 for (i = 0; i < sizeof(buf_uniq) - 1; i++)
2931 buf_uniq[i] = tbl[g_random_int_range(0, sizeof(tbl) - 1)];
2934 return g_strdup_printf("%s_/%s", prefix ? prefix : "MP",
2938 gint change_file_mode_rw(FILE *fp, const gchar *file)
2941 return fchmod(fileno(fp), S_IRUSR|S_IWUSR);
2943 return g_chmod(file, S_IRUSR|S_IWUSR);
2947 FILE *my_tmpfile(void)
2949 const gchar suffix[] = ".XXXXXX";
2950 const gchar *tmpdir;
2952 const gchar *progname;
2961 tmpdir = get_tmp_dir();
2962 tmplen = strlen(tmpdir);
2963 progname = g_get_prgname();
2964 if (progname == NULL)
2965 progname = "claws-mail";
2966 proglen = strlen(progname);
2967 Xalloca(fname, tmplen + 1 + proglen + sizeof(suffix),
2970 memcpy(fname, tmpdir, tmplen);
2971 fname[tmplen] = G_DIR_SEPARATOR;
2972 memcpy(fname + tmplen + 1, progname, proglen);
2973 memcpy(fname + tmplen + 1 + proglen, suffix, sizeof(suffix));
2975 fd = g_mkstemp(fname);
2980 claws_unlink(fname);
2982 /* verify that we can write in the file after unlinking */
2983 if (write(fd, buf, 1) < 0) {
2990 fp = fdopen(fd, "w+b");
3001 FILE *get_tmpfile_in_dir(const gchar *dir, gchar **filename)
3004 *filename = g_strdup_printf("%s%cclaws.XXXXXX", dir, G_DIR_SEPARATOR);
3005 fd = g_mkstemp(*filename);
3008 return fdopen(fd, "w+");
3011 FILE *str_open_as_stream(const gchar *str)
3016 cm_return_val_if_fail(str != NULL, NULL);
3020 FILE_OP_ERROR("str_open_as_stream", "my_tmpfile");
3025 if (len == 0) return fp;
3027 if (fwrite(str, 1, len, fp) != len) {
3028 FILE_OP_ERROR("str_open_as_stream", "fwrite");
3037 gint str_write_to_file(const gchar *str, const gchar *file)
3042 cm_return_val_if_fail(str != NULL, -1);
3043 cm_return_val_if_fail(file != NULL, -1);
3045 if ((fp = g_fopen(file, "wb")) == NULL) {
3046 FILE_OP_ERROR(file, "g_fopen");
3056 if (fwrite(str, 1, len, fp) != len) {
3057 FILE_OP_ERROR(file, "fwrite");
3063 if (fclose(fp) == EOF) {
3064 FILE_OP_ERROR(file, "fclose");
3072 static gchar *file_read_stream_to_str_full(FILE *fp, gboolean recode)
3079 cm_return_val_if_fail(fp != NULL, NULL);
3081 array = g_byte_array_new();
3083 while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
3084 if (n_read < sizeof(buf) && ferror(fp))
3086 g_byte_array_append(array, buf, n_read);
3090 FILE_OP_ERROR("file stream", "fread");
3091 g_byte_array_free(array, TRUE);
3096 g_byte_array_append(array, buf, 1);
3097 str = (gchar *)array->data;
3098 g_byte_array_free(array, FALSE);
3100 if (recode && !g_utf8_validate(str, -1, NULL)) {
3101 const gchar *src_codeset, *dest_codeset;
3103 src_codeset = conv_get_locale_charset_str();
3104 dest_codeset = CS_UTF_8;
3105 tmp = conv_codeset_strdup(str, src_codeset, dest_codeset);
3113 static gchar *file_read_to_str_full(const gchar *file, gboolean recode)
3120 struct timeval timeout = {1, 0};
3125 cm_return_val_if_fail(file != NULL, NULL);
3127 if (g_stat(file, &s) != 0) {
3128 FILE_OP_ERROR(file, "stat");
3131 if (S_ISDIR(s.st_mode)) {
3132 g_warning("%s: is a directory", file);
3137 fp = g_fopen (file, "rb");
3139 FILE_OP_ERROR(file, "open");
3143 /* test whether the file is readable without blocking */
3144 fd = g_open(file, O_RDONLY | O_NONBLOCK, 0);
3146 FILE_OP_ERROR(file, "open");
3153 /* allow for one second */
3154 err = select(fd+1, &fds, NULL, NULL, &timeout);
3155 if (err <= 0 || !FD_ISSET(fd, &fds)) {
3157 FILE_OP_ERROR(file, "select");
3159 g_warning("%s: doesn't seem readable", file);
3165 /* Now clear O_NONBLOCK */
3166 if ((fflags = fcntl(fd, F_GETFL)) < 0) {
3167 FILE_OP_ERROR(file, "fcntl (F_GETFL)");
3171 if (fcntl(fd, F_SETFL, (fflags & ~O_NONBLOCK)) < 0) {
3172 FILE_OP_ERROR(file, "fcntl (F_SETFL)");
3177 /* get the FILE pointer */
3178 fp = fdopen(fd, "rb");
3181 FILE_OP_ERROR(file, "fdopen");
3182 close(fd); /* if fp isn't NULL, we'll use fclose instead! */
3187 str = file_read_stream_to_str_full(fp, recode);
3194 gchar *file_read_to_str(const gchar *file)
3196 return file_read_to_str_full(file, TRUE);
3198 gchar *file_read_stream_to_str(FILE *fp)
3200 return file_read_stream_to_str_full(fp, TRUE);
3203 gchar *file_read_to_str_no_recode(const gchar *file)
3205 return file_read_to_str_full(file, FALSE);
3207 gchar *file_read_stream_to_str_no_recode(FILE *fp)
3209 return file_read_stream_to_str_full(fp, FALSE);
3212 char *fgets_crlf(char *buf, int size, FILE *stream)
3214 gboolean is_cr = FALSE;
3215 gboolean last_was_cr = FALSE;
3220 while (--size > 0 && (c = getc(stream)) != EOF)
3223 is_cr = (c == '\r');
3233 last_was_cr = is_cr;
3235 if (c == EOF && cs == buf)
3243 static gint execute_async(gchar *const argv[])
3245 cm_return_val_if_fail(argv != NULL && argv[0] != NULL, -1);
3247 if (g_spawn_async(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH,
3248 NULL, NULL, NULL, FALSE) == FALSE) {
3249 g_warning("couldn't execute command: %s", argv[0]);
3256 static gint execute_sync(gchar *const argv[])
3260 cm_return_val_if_fail(argv != NULL && argv[0] != NULL, -1);
3263 if (g_spawn_sync(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH,
3264 NULL, NULL, NULL, NULL, &status, NULL) == FALSE) {
3265 g_warning("couldn't execute command: %s", argv[0]);
3269 if (WIFEXITED(status))
3270 return WEXITSTATUS(status);
3274 if (g_spawn_sync(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH|
3275 G_SPAWN_CHILD_INHERITS_STDIN|G_SPAWN_LEAVE_DESCRIPTORS_OPEN,
3276 NULL, NULL, NULL, NULL, &status, NULL) == FALSE) {
3277 g_warning("couldn't execute command: %s", argv[0]);
3285 gint execute_command_line(const gchar *cmdline, gboolean async)
3290 debug_print("execute_command_line(): executing: %s\n", cmdline?cmdline:"(null)");
3292 argv = strsplit_with_quote(cmdline, " ", 0);
3295 ret = execute_async(argv);
3297 ret = execute_sync(argv);
3304 gchar *get_command_output(const gchar *cmdline)
3306 gchar *child_stdout;
3309 cm_return_val_if_fail(cmdline != NULL, NULL);
3311 debug_print("get_command_output(): executing: %s\n", cmdline);
3313 if (g_spawn_command_line_sync(cmdline, &child_stdout, NULL, &status,
3315 g_warning("couldn't execute command: %s", cmdline);
3319 return child_stdout;
3322 static gint is_unchanged_uri_char(char c)
3333 static void encode_uri(gchar *encoded_uri, gint bufsize, const gchar *uri)
3339 for(i = 0; i < strlen(uri) ; i++) {
3340 if (is_unchanged_uri_char(uri[i])) {
3341 if (k + 2 >= bufsize)
3343 encoded_uri[k++] = uri[i];
3346 char * hexa = "0123456789ABCDEF";
3348 if (k + 4 >= bufsize)
3350 encoded_uri[k++] = '%';
3351 encoded_uri[k++] = hexa[uri[i] / 16];
3352 encoded_uri[k++] = hexa[uri[i] % 16];
3358 gint open_uri(const gchar *uri, const gchar *cmdline)
3362 gchar buf[BUFFSIZE];
3364 gchar encoded_uri[BUFFSIZE];
3365 cm_return_val_if_fail(uri != NULL, -1);
3367 /* an option to choose whether to use encode_uri or not ? */
3368 encode_uri(encoded_uri, BUFFSIZE, uri);
3371 (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
3372 !strchr(p + 2, '%'))
3373 g_snprintf(buf, sizeof(buf), cmdline, encoded_uri);
3376 g_warning("Open URI command-line is invalid "
3377 "(there must be only one '%%s'): %s",
3379 g_snprintf(buf, sizeof(buf), DEFAULT_BROWSER_CMD, encoded_uri);
3382 execute_command_line(buf, TRUE);
3384 ShellExecute(NULL, "open", uri, NULL, NULL, SW_SHOW);
3389 gint open_txt_editor(const gchar *filepath, const gchar *cmdline)
3391 gchar buf[BUFFSIZE];
3394 cm_return_val_if_fail(filepath != NULL, -1);
3397 (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
3398 !strchr(p + 2, '%'))
3399 g_snprintf(buf, sizeof(buf), cmdline, filepath);
3402 g_warning("Open Text Editor command-line is invalid "
3403 "(there must be only one '%%s'): %s",
3405 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, filepath);
3408 execute_command_line(buf, TRUE);
3413 time_t remote_tzoffset_sec(const gchar *zone)
3415 static gchar ustzstr[] = "PSTPDTMSTMDTCSTCDTESTEDT";
3421 time_t remoteoffset;
3423 strncpy(zone3, zone, 3);
3427 if (sscanf(zone, "%c%d", &c, &offset) == 2 &&
3428 (c == '+' || c == '-')) {
3429 remoteoffset = ((offset / 100) * 60 + (offset % 100)) * 60;
3431 remoteoffset = -remoteoffset;
3432 } else if (!strncmp(zone, "UT" , 2) ||
3433 !strncmp(zone, "GMT", 3)) {
3435 } else if (strlen(zone3) == 3) {
3436 for (p = ustzstr; *p != '\0'; p += 3) {
3437 if (!g_ascii_strncasecmp(p, zone3, 3)) {
3438 iustz = ((gint)(p - ustzstr) / 3 + 1) / 2 - 8;
3439 remoteoffset = iustz * 3600;
3445 } else if (strlen(zone3) == 1) {
3447 case 'Z': remoteoffset = 0; break;
3448 case 'A': remoteoffset = -1; break;
3449 case 'B': remoteoffset = -2; break;
3450 case 'C': remoteoffset = -3; break;
3451 case 'D': remoteoffset = -4; break;
3452 case 'E': remoteoffset = -5; break;
3453 case 'F': remoteoffset = -6; break;
3454 case 'G': remoteoffset = -7; break;
3455 case 'H': remoteoffset = -8; break;
3456 case 'I': remoteoffset = -9; break;
3457 case 'K': remoteoffset = -10; break; /* J is not used */
3458 case 'L': remoteoffset = -11; break;
3459 case 'M': remoteoffset = -12; break;
3460 case 'N': remoteoffset = 1; break;
3461 case 'O': remoteoffset = 2; break;
3462 case 'P': remoteoffset = 3; break;
3463 case 'Q': remoteoffset = 4; break;
3464 case 'R': remoteoffset = 5; break;
3465 case 'S': remoteoffset = 6; break;
3466 case 'T': remoteoffset = 7; break;
3467 case 'U': remoteoffset = 8; break;
3468 case 'V': remoteoffset = 9; break;
3469 case 'W': remoteoffset = 10; break;
3470 case 'X': remoteoffset = 11; break;
3471 case 'Y': remoteoffset = 12; break;
3472 default: remoteoffset = 0; break;
3474 remoteoffset = remoteoffset * 3600;
3478 return remoteoffset;
3481 time_t tzoffset_sec(time_t *now)
3485 struct tm buf1, buf2;
3487 if (now && *now < 0)
3490 gmt = *gmtime_r(now, &buf1);
3491 lt = localtime_r(now, &buf2);
3493 off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
3495 if (lt->tm_year < gmt.tm_year)
3497 else if (lt->tm_year > gmt.tm_year)
3499 else if (lt->tm_yday < gmt.tm_yday)
3501 else if (lt->tm_yday > gmt.tm_yday)
3504 if (off >= 24 * 60) /* should be impossible */
3505 off = 23 * 60 + 59; /* if not, insert silly value */
3506 if (off <= -24 * 60)
3507 off = -(23 * 60 + 59);
3512 /* calculate timezone offset */
3513 gchar *tzoffset(time_t *now)
3515 static gchar offset_string[6];
3519 struct tm buf1, buf2;
3521 if (now && *now < 0)
3524 gmt = *gmtime_r(now, &buf1);
3525 lt = localtime_r(now, &buf2);
3527 off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
3529 if (lt->tm_year < gmt.tm_year)
3531 else if (lt->tm_year > gmt.tm_year)
3533 else if (lt->tm_yday < gmt.tm_yday)
3535 else if (lt->tm_yday > gmt.tm_yday)
3543 if (off >= 24 * 60) /* should be impossible */
3544 off = 23 * 60 + 59; /* if not, insert silly value */
3546 sprintf(offset_string, "%c%02d%02d", sign, off / 60, off % 60);
3548 return offset_string;
3551 void get_rfc822_date(gchar *buf, gint len)
3555 gchar day[4], mon[4];
3556 gint dd, hh, mm, ss, yyyy;
3558 gchar buf2[BUFFSIZE];
3561 lt = localtime_r(&t, &buf1);
3563 sscanf(asctime_r(lt, buf2), "%3s %3s %d %d:%d:%d %d\n",
3564 day, mon, &dd, &hh, &mm, &ss, &yyyy);
3566 g_snprintf(buf, len, "%s, %d %s %d %02d:%02d:%02d %s",
3567 day, dd, mon, yyyy, hh, mm, ss, tzoffset(&t));
3570 void debug_set_mode(gboolean mode)
3575 gboolean debug_get_mode(void)
3580 void debug_print_real(const gchar *format, ...)
3583 gchar buf[BUFFSIZE];
3585 if (!debug_mode) return;
3587 va_start(args, format);
3588 g_vsnprintf(buf, sizeof(buf), format, args);
3595 const char * debug_srcname(const char *file)
3597 const char *s = strrchr (file, '/');
3602 void * subject_table_lookup(GHashTable *subject_table, gchar * subject)
3604 if (subject == NULL)
3607 subject += subject_get_prefix_length(subject);
3609 return g_hash_table_lookup(subject_table, subject);
3612 void subject_table_insert(GHashTable *subject_table, gchar * subject,
3615 if (subject == NULL || *subject == 0)
3617 subject += subject_get_prefix_length(subject);
3618 g_hash_table_insert(subject_table, subject, data);
3621 void subject_table_remove(GHashTable *subject_table, gchar * subject)
3623 if (subject == NULL)
3626 subject += subject_get_prefix_length(subject);
3627 g_hash_table_remove(subject_table, subject);
3631 static regex_t u_regex;
3632 static gboolean u_init_;
3635 void utils_free_regex(void)
3646 *\brief Check if a string is prefixed with known (combinations)
3647 * of prefixes. The function assumes that each prefix
3648 * is terminated by zero or exactly _one_ space.
3650 *\param str String to check for a prefixes
3652 *\return int Number of chars in the prefix that should be skipped
3653 * for a "clean" subject line. If no prefix was found, 0
3656 int subject_get_prefix_length(const gchar *subject)
3659 /*!< Array with allowable reply prefixes regexps. */
3660 static const gchar * const prefixes[] = {
3661 "Re\\:", /* "Re:" */
3662 "Re\\[[1-9][0-9]*\\]\\:", /* "Re[XXX]:" (non-conforming news mail clients) */
3663 "Antw\\:", /* "Antw:" (Dutch / German Outlook) */
3664 "Aw\\:", /* "Aw:" (German) */
3665 "Antwort\\:", /* "Antwort:" (German Lotus Notes) */
3666 "Res\\:", /* "Res:" (Spanish/Brazilian Outlook) */
3667 "Fw\\:", /* "Fw:" Forward */
3668 "Fwd\\:", /* "Fwd:" Forward */
3669 "Enc\\:", /* "Enc:" Forward (Brazilian Outlook) */
3670 "Odp\\:", /* "Odp:" Re (Polish Outlook) */
3671 "Rif\\:", /* "Rif:" (Italian Outlook) */
3672 "Sv\\:", /* "Sv" (Norwegian) */
3673 "Vs\\:", /* "Vs" (Norwegian) */
3674 "Ad\\:", /* "Ad" (Norwegian) */
3675 "\347\255\224\345\244\215\\:", /* "Re" (Chinese, UTF-8) */
3676 "R\303\251f\\. \\:", /* "R�f. :" (French Lotus Notes) */
3677 "Re \\:", /* "Re :" (French Yahoo Mail) */
3680 const int PREFIXES = sizeof prefixes / sizeof prefixes[0];
3684 if (!subject) return 0;
3685 if (!*subject) return 0;
3688 GString *s = g_string_new("");
3690 for (n = 0; n < PREFIXES; n++)
3691 /* Terminate each prefix regexpression by a
3692 * "\ ?" (zero or ONE space), and OR them */
3693 g_string_append_printf(s, "(%s\\ ?)%s",
3698 g_string_prepend(s, "(");
3699 g_string_append(s, ")+"); /* match at least once */
3700 g_string_prepend(s, "^\\ *"); /* from beginning of line */
3703 /* We now have something like "^\ *((PREFIX1\ ?)|(PREFIX2\ ?))+"
3704 * TODO: Should this be "^\ *(((PREFIX1)|(PREFIX2))\ ?)+" ??? */
3705 if (regcomp(&u_regex, s->str, REG_EXTENDED | REG_ICASE)) {
3706 debug_print("Error compiling regexp %s\n", s->str);
3707 g_string_free(s, TRUE);
3711 g_string_free(s, TRUE);
3715 if (!regexec(&u_regex, subject, 1, &pos, 0) && pos.rm_so != -1)
3720 /*!< Array with allowable reply prefixes regexps. */
3721 static const gchar * const prefixes[] = {
3723 "antw:", /* "Antw:" (Dutch / German Outlook) */
3724 "aw:", /* "Aw:" (German) */
3725 "antwort:", /* "Antwort:" (German Lotus Notes) */
3726 "res:", /* "Res:" (Spanish/Brazilian Outlook) */
3727 "fw:", /* "Fw:" Forward */
3728 "fwd:", /* "Fwd:" Forward */
3729 "enc:", /* "Enc:" Forward (Brazilian Outlook) */
3730 "odp:", /* "Odp:" Re (Polish Outlook) */
3731 "rif:", /* "Rif:" (Italian Outlook) */
3732 "sv:", /* "Sv" (Norwegian) */
3733 "vs:", /* "Vs" (Norwegian) */
3734 "ad:", /* "Ad" (Norwegian) */
3735 "R\303\251f. :", /* "R�f. :" (French Lotus Notes) */
3736 "Re :", /* "Re :" (French Yahoo Mail) */
3739 const int PREFIXES = sizeof prefixes / sizeof prefixes[0];
3742 if (!subject) return 0;
3743 if (!*subject) return 0;
3745 for (n = 0; n < PREFIXES; n++) {
3746 int len = strlen(prefixes[n]);
3747 if (!strncasecmp(subject, prefixes[n], len)) {
3748 if (subject[len] == ' ')
3757 static guint g_stricase_hash(gconstpointer gptr)
3759 guint hash_result = 0;
3762 for (str = gptr; str && *str; str++) {
3763 hash_result += toupper(*str);
3769 static gint g_stricase_equal(gconstpointer gptr1, gconstpointer gptr2)
3771 const char *str1 = gptr1;
3772 const char *str2 = gptr2;
3774 return !strcasecmp(str1, str2);
3777 gint g_int_compare(gconstpointer a, gconstpointer b)
3779 return GPOINTER_TO_INT(a) - GPOINTER_TO_INT(b);
3782 gchar *generate_msgid(gchar *buf, gint len, gchar *user_addr)
3790 lt = localtime_r(&t, &buft);
3792 if (user_addr != NULL)
3793 addr = g_strdup_printf(".%s", user_addr);
3794 else if (strlen(buf) != 0)
3795 addr = g_strdup_printf("@%s", buf);
3797 addr = g_strdup_printf("@%s", get_domain_name());
3799 /* Replace all @ but the last one in addr, with underscores.
3800 * RFC 2822 States that msg-id syntax only allows one @.
3802 while (strchr(addr, '@') != NULL && strchr(addr, '@') != strrchr(addr, '@'))
3803 *(strchr(addr, '@')) = '_';
3805 g_snprintf(buf, len, "%04d%02d%02d%02d%02d%02d.%08x%s",
3806 lt->tm_year + 1900, lt->tm_mon + 1,
3807 lt->tm_mday, lt->tm_hour,
3808 lt->tm_min, lt->tm_sec,
3809 (guint) rand(), addr);
3816 quote_cmd_argument()
3818 return a quoted string safely usable in argument of a command.
3820 code is extracted and adapted from etPan! project -- DINH V. Ho�.
3823 gint quote_cmd_argument(gchar * result, guint size,
3833 for(p = path ; * p != '\0' ; p ++) {
3835 if (isalnum((guchar)*p) || (* p == '/')) {
3836 if (remaining > 0) {
3842 result[size - 1] = '\0';
3847 if (remaining >= 2) {
3855 result[size - 1] = '\0';
3860 if (remaining > 0) {
3864 result[size - 1] = '\0';
3878 static void g_node_map_recursive(GNode *node, gpointer data)
3880 GNodeMapData *mapdata = (GNodeMapData *) data;
3882 GNodeMapData newmapdata;
3885 newdata = mapdata->func(node->data, mapdata->data);
3886 if (newdata != NULL) {
3887 newnode = g_node_new(newdata);
3888 g_node_append(mapdata->parent, newnode);
3890 newmapdata.parent = newnode;
3891 newmapdata.func = mapdata->func;
3892 newmapdata.data = mapdata->data;
3894 g_node_children_foreach(node, G_TRAVERSE_ALL, g_node_map_recursive, &newmapdata);
3898 GNode *g_node_map(GNode *node, GNodeMapFunc func, gpointer data)
3901 GNodeMapData mapdata;
3903 cm_return_val_if_fail(node != NULL, NULL);
3904 cm_return_val_if_fail(func != NULL, NULL);
3906 root = g_node_new(func(node->data, data));
3908 mapdata.parent = root;
3909 mapdata.func = func;
3910 mapdata.data = data;
3912 g_node_children_foreach(node, G_TRAVERSE_ALL, g_node_map_recursive, &mapdata);
3917 #define HEX_TO_INT(val, hex) \
3921 if ('0' <= c && c <= '9') { \
3923 } else if ('a' <= c && c <= 'f') { \
3924 val = c - 'a' + 10; \
3925 } else if ('A' <= c && c <= 'F') { \
3926 val = c - 'A' + 10; \
3932 gboolean get_hex_value(guchar *out, gchar c1, gchar c2)
3939 if (hi == -1 || lo == -1)
3942 *out = (hi << 4) + lo;
3946 #define INT_TO_HEX(hex, val) \
3949 hex = '0' + (val); \
3951 hex = 'A' + (val) - 10; \
3954 void get_hex_str(gchar *out, guchar ch)
3958 INT_TO_HEX(hex, ch >> 4);
3960 INT_TO_HEX(hex, ch & 0x0f);
3966 #define G_PRINT_REF 1 == 1 ? (void) 0 : (void)
3968 #define G_PRINT_REF g_print
3972 *\brief Register ref counted pointer. It is based on GBoxed, so should
3973 * work with anything that uses the GType system. The semantics
3974 * are similar to a C++ auto pointer, with the exception that
3975 * C doesn't have automatic closure (calling destructors) when
3976 * exiting a block scope.
3977 * Use the \ref G_TYPE_AUTO_POINTER macro instead of calling this
3978 * function directly.
3980 *\return GType A GType type.
3982 GType g_auto_pointer_register(void)
3984 static GType auto_pointer_type;
3985 if (!auto_pointer_type)
3987 g_boxed_type_register_static
3988 ("G_TYPE_AUTO_POINTER",
3989 (GBoxedCopyFunc) g_auto_pointer_copy,
3990 (GBoxedFreeFunc) g_auto_pointer_free);
3991 return auto_pointer_type;
3995 *\brief Structure with g_new() allocated pointer guarded by the
3998 typedef struct AutoPointerRef {
3999 void (*free) (gpointer);
4005 *\brief The auto pointer opaque structure that references the
4006 * pointer guard block.
4008 typedef struct AutoPointer {
4009 AutoPointerRef *ref;
4010 gpointer ptr; /*!< access to protected pointer */
4014 *\brief Creates an auto pointer for a g_new()ed pointer. Example:
4018 * ... tell gtk_list_store it should use a G_TYPE_AUTO_POINTER
4019 * ... when assigning, copying and freeing storage elements
4021 * gtk_list_store_new(N_S_COLUMNS,
4022 * G_TYPE_AUTO_POINTER,
4026 * Template *precious_data = g_new0(Template, 1);
4027 * g_pointer protect = g_auto_pointer_new(precious_data);
4029 * gtk_list_store_set(container, &iter,
4033 * ... the gtk_list_store has copied the pointer and
4034 * ... incremented its reference count, we should free
4035 * ... the auto pointer (in C++ a destructor would do
4036 * ... this for us when leaving block scope)
4038 * g_auto_pointer_free(protect);
4040 * ... gtk_list_store_set() now manages the data. When
4041 * ... *explicitly* requesting a pointer from the list
4042 * ... store, don't forget you get a copy that should be
4043 * ... freed with g_auto_pointer_free() eventually.
4047 *\param pointer Pointer to be guarded.
4049 *\return GAuto * Pointer that should be used in containers with
4052 GAuto *g_auto_pointer_new(gpointer p)
4054 AutoPointerRef *ref;
4060 ref = g_new0(AutoPointerRef, 1);
4061 ptr = g_new0(AutoPointer, 1);
4071 G_PRINT_REF ("XXXX ALLOC(%lx)\n", p);
4077 *\brief Allocate an autopointer using the passed \a free function to
4078 * free the guarded pointer
4080 GAuto *g_auto_pointer_new_with_free(gpointer p, GFreeFunc free_)
4087 aptr = g_auto_pointer_new(p);
4088 aptr->ref->free = free_;
4092 gpointer g_auto_pointer_get_ptr(GAuto *auto_ptr)
4094 if (auto_ptr == NULL)
4096 return ((AutoPointer *) auto_ptr)->ptr;
4100 *\brief Copies an auto pointer by. It's mostly not necessary
4101 * to call this function directly, unless you copy/assign
4102 * the guarded pointer.
4104 *\param auto_ptr Auto pointer returned by previous call to
4105 * g_auto_pointer_new_XXX()
4107 *\return gpointer An auto pointer
4109 GAuto *g_auto_pointer_copy(GAuto *auto_ptr)
4112 AutoPointerRef *ref;
4115 if (auto_ptr == NULL)
4120 newp = g_new0(AutoPointer, 1);
4123 newp->ptr = ref->pointer;
4127 G_PRINT_REF ("XXXX COPY(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4133 *\brief Free an auto pointer
4135 void g_auto_pointer_free(GAuto *auto_ptr)
4138 AutoPointerRef *ref;
4140 if (auto_ptr == NULL)
4146 if (--(ref->cnt) == 0) {
4148 G_PRINT_REF ("XXXX FREE(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4150 ref->free(ref->pointer);
4155 G_PRINT_REF ("XXXX DEREF(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4160 void replace_returns(gchar *str)
4165 while (strstr(str, "\n")) {
4166 *strstr(str, "\n") = ' ';
4168 while (strstr(str, "\r")) {
4169 *strstr(str, "\r") = ' ';
4173 /* get_uri_part() - retrieves a URI starting from scanpos.
4174 Returns TRUE if succesful */
4175 gboolean get_uri_part(const gchar *start, const gchar *scanpos,
4176 const gchar **bp, const gchar **ep, gboolean hdr)
4179 gint parenthese_cnt = 0;
4181 cm_return_val_if_fail(start != NULL, FALSE);
4182 cm_return_val_if_fail(scanpos != NULL, FALSE);
4183 cm_return_val_if_fail(bp != NULL, FALSE);
4184 cm_return_val_if_fail(ep != NULL, FALSE);
4188 /* find end point of URI */
4189 for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
4190 if (!g_ascii_isgraph(*(const guchar *)ep_) ||
4191 !IS_ASCII(*(const guchar *)ep_) ||
4192 strchr("[]{}<>\"", *ep_)) {
4194 } else if (strchr("(", *ep_)) {
4196 } else if (strchr(")", *ep_)) {
4197 if (parenthese_cnt > 0)
4204 /* no punctuation at end of string */
4206 /* FIXME: this stripping of trailing punctuations may bite with other URIs.
4207 * should pass some URI type to this function and decide on that whether
4208 * to perform punctuation stripping */
4210 #define IS_REAL_PUNCT(ch) (g_ascii_ispunct(ch) && !strchr("/?=-_)", ch))
4212 for (; ep_ - 1 > scanpos + 1 &&
4213 IS_REAL_PUNCT(*(ep_ - 1));
4217 #undef IS_REAL_PUNCT
4224 gchar *make_uri_string(const gchar *bp, const gchar *ep)
4226 while (bp && *bp && g_ascii_isspace(*bp))
4228 return g_strndup(bp, ep - bp);
4231 /* valid mail address characters */
4232 #define IS_RFC822_CHAR(ch) \
4236 !g_ascii_isspace(ch) && \
4237 !strchr("(),;<>\"", (ch)))
4239 /* alphabet and number within 7bit ASCII */
4240 #define IS_ASCII_ALNUM(ch) (IS_ASCII(ch) && g_ascii_isalnum(ch))
4241 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
4243 static GHashTable *create_domain_tab(void)
4245 static const gchar *toplvl_domains [] = {
4247 "arpa", "coop", "info", "name", "biz", "com", "edu", "gov",
4248 "int", "mil", "net", "org", "ac", "ad", "ae", "af", "ag",
4249 "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au",
4250 "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi",
4251 "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
4252 "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl",
4253 "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz", "de",
4254 "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er",
4255 "es", "et", "eu", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd",
4256 "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
4257 "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
4258 "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir",
4259 "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
4260 "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc",
4261 "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
4262 "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq",
4263 "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na",
4264 "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu",
4265 "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm",
4266 "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru",
4267 "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj",
4268 "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
4269 "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to",
4270 "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um",
4271 "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu",
4272 "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw"
4275 GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
4277 cm_return_val_if_fail(htab, NULL);
4278 for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++)
4279 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
4283 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
4285 const gint MAX_LVL_DOM_NAME_LEN = 6;
4286 gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
4287 const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
4290 if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
4293 for (p = buf; p < m && first < last; *p++ = *first++)
4297 return g_hash_table_lookup(tab, buf) != NULL;
4300 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
4301 gboolean get_email_part(const gchar *start, const gchar *scanpos,
4302 const gchar **bp, const gchar **ep, gboolean hdr)
4304 /* more complex than the uri part because we need to scan back and forward starting from
4305 * the scan position. */
4306 gboolean result = FALSE;
4307 const gchar *bp_ = NULL;
4308 const gchar *ep_ = NULL;
4309 static GHashTable *dom_tab;
4310 const gchar *last_dot = NULL;
4311 const gchar *prelast_dot = NULL;
4312 const gchar *last_tld_char = NULL;
4314 /* the informative part of the email address (describing the name
4315 * of the email address owner) may contain quoted parts. the
4316 * closure stack stores the last encountered quotes. */
4317 gchar closure_stack[128];
4318 gchar *ptr = closure_stack;
4320 cm_return_val_if_fail(start != NULL, FALSE);
4321 cm_return_val_if_fail(scanpos != NULL, FALSE);
4322 cm_return_val_if_fail(bp != NULL, FALSE);
4323 cm_return_val_if_fail(ep != NULL, FALSE);
4326 const gchar *start_quote = NULL;
4327 const gchar *end_quote = NULL;
4329 /* go to the real start */
4330 if (start[0] == ',')
4332 if (start[0] == ';')
4334 while (start[0] == '\n' || start[0] == '\r')
4336 while (start[0] == ' ' || start[0] == '\t')
4341 /* check if there are quotes (to skip , in them) */
4342 if (*start == '"') {
4343 start_quote = start;
4345 end_quote = strstr(start, "\"");
4351 /* skip anything between quotes */
4352 if (start_quote && end_quote) {
4357 /* find end (either , or ; or end of line) */
4358 if (strstr(start, ",") && strstr(start, ";"))
4359 *ep = strstr(start,",") < strstr(start, ";")
4360 ? strstr(start, ",") : strstr(start, ";");
4361 else if (strstr(start, ","))
4362 *ep = strstr(start, ",");
4363 else if (strstr(start, ";"))
4364 *ep = strstr(start, ";");
4366 *ep = start+strlen(start);
4368 /* go back to real start */
4369 if (start_quote && end_quote) {
4370 start = start_quote;
4373 /* check there's still an @ in that, or search
4374 * further if possible */
4375 if (strstr(start, "@") && strstr(start, "@") < *ep)
4377 else if (*ep < start+strlen(start)) {
4380 } else if (start_quote && strstr(start, "\"") && strstr(start, "\"") < *ep) {
4388 dom_tab = create_domain_tab();
4389 cm_return_val_if_fail(dom_tab, FALSE);
4391 /* scan start of address */
4392 for (bp_ = scanpos - 1;
4393 bp_ >= start && IS_RFC822_CHAR(*(const guchar *)bp_); bp_--)
4396 /* TODO: should start with an alnum? */
4398 for (; bp_ < scanpos && !IS_ASCII_ALNUM(*(const guchar *)bp_); bp_++)
4401 if (bp_ != scanpos) {
4402 /* scan end of address */
4403 for (ep_ = scanpos + 1;
4404 *ep_ && IS_RFC822_CHAR(*(const guchar *)ep_); ep_++)
4406 prelast_dot = last_dot;
4408 if (*(last_dot + 1) == '.') {
4409 if (prelast_dot == NULL)
4411 last_dot = prelast_dot;
4416 /* TODO: really should terminate with an alnum? */
4417 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*(const guchar *)ep_);
4422 if (last_dot == NULL)
4424 if (last_dot >= ep_)
4425 last_dot = prelast_dot;
4426 if (last_dot == NULL || (scanpos + 1 >= last_dot))
4430 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
4431 if (*last_tld_char == '?')
4434 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
4441 if (!result) return FALSE;
4443 if (*ep_ && bp_ != start && *(bp_ - 1) == '"' && *(ep_) == '"'
4444 && *(ep_ + 1) == ' ' && *(ep_ + 2) == '<'
4445 && IS_RFC822_CHAR(*(ep_ + 3))) {
4446 /* this informative part with an @ in it is
4447 * followed by the email address */
4450 /* go to matching '>' (or next non-rfc822 char, like \n) */
4451 for (; *ep_ != '>' && *ep != '\0' && IS_RFC822_CHAR(*ep_); ep_++)
4454 /* include the bracket */
4455 if (*ep_ == '>') ep_++;
4457 /* include the leading quote */
4465 /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
4466 if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
4469 /* see if this is <bracketed>; in this case we also scan for the informative part. */
4470 if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
4473 #define FULL_STACK() ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
4474 #define IN_STACK() (ptr > closure_stack)
4475 /* has underrun check */
4476 #define POP_STACK() if(IN_STACK()) --ptr
4477 /* has overrun check */
4478 #define PUSH_STACK(c) if(!FULL_STACK()) *ptr++ = (c); else return TRUE
4479 /* has underrun check */
4480 #define PEEK_STACK() (IN_STACK() ? *(ptr - 1) : 0)
4484 /* scan for the informative part. */
4485 for (bp_ -= 2; bp_ >= start; bp_--) {
4486 /* if closure on the stack keep scanning */
4487 if (PEEK_STACK() == *bp_) {
4491 if (!IN_STACK() && (*bp_ == '\'' || *bp_ == '"')) {
4496 /* if nothing in the closure stack, do the special conditions
4497 * the following if..else expression simply checks whether
4498 * a token is acceptable. if not acceptable, the clause
4499 * should terminate the loop with a 'break' */
4500 if (!PEEK_STACK()) {
4502 && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
4503 && (((bp_ + 1) < ep_) && isalnum(*(bp_ + 1)))) {
4504 /* hyphens are allowed, but only in
4506 } else if (strchr(" \"'", *bp_)) {
4507 /* but anything not being a punctiation
4510 break; /* anything else is rejected */
4517 /* scan forward (should start with an alnum) */
4518 for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
4534 #undef IS_ASCII_ALNUM
4535 #undef IS_RFC822_CHAR
4537 gchar *make_email_string(const gchar *bp, const gchar *ep)
4539 /* returns a mailto: URI; mailto: is also used to detect the
4540 * uri type later on in the button_pressed signal handler */
4544 tmp = g_strndup(bp, ep - bp);
4545 result = g_strconcat("mailto:", tmp, NULL);
4551 gchar *make_http_string(const gchar *bp, const gchar *ep)
4553 /* returns an http: URI; */
4557 while (bp && *bp && g_ascii_isspace(*bp))
4559 tmp = g_strndup(bp, ep - bp);
4560 result = g_strconcat("http://", tmp, NULL);
4566 static gchar *mailcap_get_command_in_file(const gchar *path, const gchar *type, const gchar *file_to_open)
4568 FILE *fp = g_fopen(path, "rb");
4569 gchar buf[BUFFSIZE];
4570 gchar *result = NULL;
4573 while (fgets(buf, sizeof (buf), fp) != NULL) {
4574 gchar **parts = g_strsplit(buf, ";", 3);
4575 gchar *trimmed = parts[0];
4576 while (trimmed[0] == ' ' || trimmed[0] == '\t')
4578 while (trimmed[strlen(trimmed)-1] == ' ' || trimmed[strlen(trimmed)-1] == '\t')
4579 trimmed[strlen(trimmed)-1] = '\0';
4581 if (!strcmp(trimmed, type)) {
4582 gboolean needsterminal = FALSE;
4583 if (parts[2] && strstr(parts[2], "needsterminal")) {
4584 needsterminal = TRUE;
4586 if (parts[2] && strstr(parts[2], "test=")) {
4587 gchar *orig_testcmd = g_strdup(strstr(parts[2], "test=")+5);
4588 gchar *testcmd = orig_testcmd;
4589 if (strstr(testcmd,";"))
4590 *(strstr(testcmd,";")) = '\0';
4591 while (testcmd[0] == ' ' || testcmd[0] == '\t')
4593 while (testcmd[strlen(testcmd)-1] == '\n')
4594 testcmd[strlen(testcmd)-1] = '\0';
4595 while (testcmd[strlen(testcmd)-1] == '\r')
4596 testcmd[strlen(testcmd)-1] = '\0';
4597 while (testcmd[strlen(testcmd)-1] == ' ' || testcmd[strlen(testcmd)-1] == '\t')
4598 testcmd[strlen(testcmd)-1] = '\0';
4600 if (strstr(testcmd, "%s")) {
4601 gchar *tmp = g_strdup_printf(testcmd, file_to_open);
4602 gint res = system(tmp);
4604 g_free(orig_testcmd);
4611 gint res = system(testcmd);
4612 g_free(orig_testcmd);
4622 while (trimmed[0] == ' ' || trimmed[0] == '\t')
4624 while (trimmed[strlen(trimmed)-1] == '\n')
4625 trimmed[strlen(trimmed)-1] = '\0';
4626 while (trimmed[strlen(trimmed)-1] == '\r')
4627 trimmed[strlen(trimmed)-1] = '\0';
4628 while (trimmed[strlen(trimmed)-1] == ' ' || trimmed[strlen(trimmed)-1] == '\t')
4629 trimmed[strlen(trimmed)-1] = '\0';
4630 result = g_strdup(trimmed);
4633 /* if there are no single quotes around %s, add them.
4634 * '.*%s.*' is ok, as in display 'png:%s'
4636 if (strstr(result, "%s")
4637 && !(strstr(result, "'") < strstr(result,"%s") &&
4638 strstr(strstr(result,"%s"), "'"))) {
4639 gchar *start = g_strdup(result);
4640 gchar *end = g_strdup(strstr(result, "%s")+2);
4642 *strstr(start, "%s") = '\0';
4643 tmp = g_strconcat(start,"'%s'",end, NULL);
4649 if (needsterminal) {
4650 gchar *tmp = g_strdup_printf("xterm -e %s", result);
4661 gchar *mailcap_get_command_for_type(const gchar *type, const gchar *file_to_open)
4663 gchar *result = NULL;
4667 path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap", NULL);
4668 result = mailcap_get_command_in_file(path, type, file_to_open);
4672 result = mailcap_get_command_in_file("/etc/mailcap", type, file_to_open);
4676 void mailcap_update_default(const gchar *type, const gchar *command)
4678 gchar *path = NULL, *outpath = NULL;
4679 path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap", NULL);
4680 outpath = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap.new", NULL);
4681 FILE *fp = g_fopen(path, "rb");
4683 gchar buf[BUFFSIZE];
4684 gboolean err = FALSE;
4687 fp = g_fopen(path, "a");
4689 g_warning("failed to create file %s", path);
4694 fp = g_freopen(path, "rb", fp);
4696 g_warning("failed to reopen file %s", path);
4703 outfp = g_fopen(outpath, "wb");
4705 g_warning("failed to create file %s", outpath);
4711 while (fp && fgets(buf, sizeof (buf), fp) != NULL) {
4712 gchar **parts = g_strsplit(buf, ";", 3);
4713 gchar *trimmed = parts[0];
4714 while (trimmed[0] == ' ')
4716 while (trimmed[strlen(trimmed)-1] == ' ')
4717 trimmed[strlen(trimmed)-1] = '\0';
4719 if (!strcmp(trimmed, type)) {
4724 if(fputs(buf, outfp) == EOF) {
4731 if (fprintf(outfp, "%s; %s\n", type, command) < 0)
4737 if (fclose(outfp) == EOF)
4741 g_rename(outpath, path);
4747 gint copy_dir(const gchar *src, const gchar *dst)
4752 if ((dir = g_dir_open(src, 0, NULL)) == NULL) {
4753 g_warning("failed to open directory: %s", src);
4757 if (make_dir(dst) < 0)
4760 while ((name = g_dir_read_name(dir)) != NULL) {
4761 gchar *old_file, *new_file;
4762 old_file = g_strconcat(src, G_DIR_SEPARATOR_S, name, NULL);
4763 new_file = g_strconcat(dst, G_DIR_SEPARATOR_S, name, NULL);
4764 debug_print("copying: %s -> %s\n", old_file, new_file);
4765 if (g_file_test(old_file, G_FILE_TEST_IS_REGULAR)) {
4766 gint r = copy_file(old_file, new_file, TRUE);
4773 /* Windows has no symlinks. Or well, Vista seems to
4774 have something like this but the semantics might be
4775 different. Thus we don't use it under Windows. */
4776 else if (g_file_test(old_file, G_FILE_TEST_IS_SYMLINK)) {
4777 GError *error = NULL;
4779 gchar *target = g_file_read_link(old_file, &error);
4781 r = symlink(target, new_file);
4788 #endif /*G_OS_WIN32*/
4789 else if (g_file_test(old_file, G_FILE_TEST_IS_DIR)) {
4790 gint r = copy_dir(old_file, new_file);
4801 /* crude test to see if a file is an email. */
4802 gboolean file_is_email (const gchar *filename)
4808 if (filename == NULL)
4810 if ((fp = g_fopen(filename, "rb")) == NULL)
4812 while (i < 60 && score < 3
4813 && fgets(buffer, sizeof (buffer), fp) > 0) {
4814 if (!strncmp(buffer, "From:", strlen("From:")))
4816 else if (!strncmp(buffer, "Date:", strlen("Date:")))
4818 else if (!strncmp(buffer, "Message-ID:", strlen("Message-ID:")))
4820 else if (!strncmp(buffer, "Subject:", strlen("Subject:")))
4825 return (score >= 3);
4828 gboolean sc_g_list_bigger(GList *list, gint max)
4832 while (cur && i <= max+1) {
4839 gboolean sc_g_slist_bigger(GSList *list, gint max)
4843 while (cur && i <= max+1) {
4850 const gchar *daynames[] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL};
4851 const gchar *monthnames[] = {NULL, NULL, NULL, NULL, NULL, NULL,
4852 NULL, NULL, NULL, NULL, NULL, NULL};
4853 const gchar *s_daynames[] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL};
4854 const gchar *s_monthnames[] = {NULL, NULL, NULL, NULL, NULL, NULL,
4855 NULL, NULL, NULL, NULL, NULL, NULL};
4857 gint daynames_len[] = {0,0,0,0,0,0,0};
4858 gint monthnames_len[] = {0,0,0,0,0,0,
4860 gint s_daynames_len[] = {0,0,0,0,0,0,0};
4861 gint s_monthnames_len[] = {0,0,0,0,0,0,
4863 const gchar *s_am_up = NULL;
4864 const gchar *s_pm_up = NULL;
4865 const gchar *s_am_low = NULL;
4866 const gchar *s_pm_low = NULL;
4868 gint s_am_up_len = 0;
4869 gint s_pm_up_len = 0;
4870 gint s_am_low_len = 0;
4871 gint s_pm_low_len = 0;
4873 static gboolean time_names_init_done = FALSE;
4875 static void init_time_names(void)
4879 daynames[0] = C_("Complete day name for use by strftime", "Sunday");
4880 daynames[1] = C_("Complete day name for use by strftime", "Monday");
4881 daynames[2] = C_("Complete day name for use by strftime", "Tuesday");
4882 daynames[3] = C_("Complete day name for use by strftime", "Wednesday");
4883 daynames[4] = C_("Complete day name for use by strftime", "Thursday");
4884 daynames[5] = C_("Complete day name for use by strftime", "Friday");
4885 daynames[6] = C_("Complete day name for use by strftime", "Saturday");
4887 monthnames[0] = C_("Complete month name for use by strftime", "January");
4888 monthnames[1] = C_("Complete month name for use by strftime", "February");
4889 monthnames[2] = C_("Complete month name for use by strftime", "March");
4890 monthnames[3] = C_("Complete month name for use by strftime", "April");
4891 monthnames[4] = C_("Complete month name for use by strftime", "May");
4892 monthnames[5] = C_("Complete month name for use by strftime", "June");
4893 monthnames[6] = C_("Complete month name for use by strftime", "July");
4894 monthnames[7] = C_("Complete month name for use by strftime", "August");
4895 monthnames[8] = C_("Complete month name for use by strftime", "September");
4896 monthnames[9] = C_("Complete month name for use by strftime", "October");
4897 monthnames[10] = C_("Complete month name for use by strftime", "November");
4898 monthnames[11] = C_("Complete month name for use by strftime", "December");
4900 s_daynames[0] = C_("Abbr. day name for use by strftime", "Sun");
4901 s_daynames[1] = C_("Abbr. day name for use by strftime", "Mon");
4902 s_daynames[2] = C_("Abbr. day name for use by strftime", "Tue");
4903 s_daynames[3] = C_("Abbr. day name for use by strftime", "Wed");
4904 s_daynames[4] = C_("Abbr. day name for use by strftime", "Thu");
4905 s_daynames[5] = C_("Abbr. day name for use by strftime", "Fri");
4906 s_daynames[6] = C_("Abbr. day name for use by strftime", "Sat");
4908 s_monthnames[0] = C_("Abbr. month name for use by strftime", "Jan");
4909 s_monthnames[1] = C_("Abbr. month name for use by strftime", "Feb");
4910 s_monthnames[2] = C_("Abbr. month name for use by strftime", "Mar");
4911 s_monthnames[3] = C_("Abbr. month name for use by strftime", "Apr");
4912 s_monthnames[4] = C_("Abbr. month name for use by strftime", "May");
4913 s_monthnames[5] = C_("Abbr. month name for use by strftime", "Jun");
4914 s_monthnames[6] = C_("Abbr. month name for use by strftime", "Jul");
4915 s_monthnames[7] = C_("Abbr. month name for use by strftime", "Aug");
4916 s_monthnames[8] = C_("Abbr. month name for use by strftime", "Sep");
4917 s_monthnames[9] = C_("Abbr. month name for use by strftime", "Oct");
4918 s_monthnames[10] = C_("Abbr. month name for use by strftime", "Nov");
4919 s_monthnames[11] = C_("Abbr. month name for use by strftime", "Dec");
4921 for (i = 0; i < 7; i++) {
4922 daynames_len[i] = strlen(daynames[i]);
4923 s_daynames_len[i] = strlen(s_daynames[i]);
4925 for (i = 0; i < 12; i++) {
4926 monthnames_len[i] = strlen(monthnames[i]);
4927 s_monthnames_len[i] = strlen(s_monthnames[i]);
4930 s_am_up = C_("For use by strftime (morning)", "AM");
4931 s_pm_up = C_("For use by strftime (afternoon)", "PM");
4932 s_am_low = C_("For use by strftime (morning, lowercase)", "am");
4933 s_pm_low = C_("For use by strftime (afternoon, lowercase)", "pm");
4935 s_am_up_len = strlen(s_am_up);
4936 s_pm_up_len = strlen(s_pm_up);
4937 s_am_low_len = strlen(s_am_low);
4938 s_pm_low_len = strlen(s_pm_low);
4940 time_names_init_done = TRUE;
4943 #define CHECK_SIZE() { \
4944 total_done += len; \
4945 if (total_done >= buflen) { \
4946 buf[buflen-1] = '\0'; \
4951 size_t fast_strftime(gchar *buf, gint buflen, const gchar *format, struct tm *lt)
4953 gchar *curpos = buf;
4954 gint total_done = 0;
4955 gchar subbuf[64], subfmt[64];
4956 static time_t last_tzset = (time_t)0;
4958 if (!time_names_init_done)
4961 if (format == NULL || lt == NULL)
4964 if (last_tzset != time(NULL)) {
4966 last_tzset = time(NULL);
4969 if (*format == '%') {
4970 gint len = 0, tmp = 0;
4974 len = 1; CHECK_SIZE();
4978 len = s_daynames_len[lt->tm_wday]; CHECK_SIZE();
4979 strncpy2(curpos, s_daynames[lt->tm_wday], buflen - total_done);
4982 len = daynames_len[lt->tm_wday]; CHECK_SIZE();
4983 strncpy2(curpos, daynames[lt->tm_wday], buflen - total_done);
4987 len = s_monthnames_len[lt->tm_mon]; CHECK_SIZE();
4988 strncpy2(curpos, s_monthnames[lt->tm_mon], buflen - total_done);
4991 len = monthnames_len[lt->tm_mon]; CHECK_SIZE();
4992 strncpy2(curpos, monthnames[lt->tm_mon], buflen - total_done);
4995 strftime(subbuf, 64, "%c", lt);
4996 len = strlen(subbuf); CHECK_SIZE();
4997 strncpy2(curpos, subbuf, buflen - total_done);
5000 total_done += 2; CHECK_SIZE();
5001 tmp = (lt->tm_year + 1900)/100;
5002 *curpos++ = '0'+(tmp / 10);
5003 *curpos++ = '0'+(tmp % 10);
5006 total_done += 2; CHECK_SIZE();
5007 *curpos++ = '0'+(lt->tm_mday / 10);
5008 *curpos++ = '0'+(lt->tm_mday % 10);
5011 total_done += 8; CHECK_SIZE();
5012 *curpos++ = '0'+((lt->tm_mon+1) / 10);
5013 *curpos++ = '0'+((lt->tm_mon+1) % 10);
5015 *curpos++ = '0'+(lt->tm_mday / 10);
5016 *curpos++ = '0'+(lt->tm_mday % 10);
5018 tmp = lt->tm_year%100;
5019 *curpos++ = '0'+(tmp / 10);
5020 *curpos++ = '0'+(tmp % 10);
5023 len = 2; CHECK_SIZE();
5024 snprintf(curpos, buflen - total_done, "%2d", lt->tm_mday);
5027 len = 10; CHECK_SIZE();
5028 snprintf(curpos, buflen - total_done, "%4d-%02d-%02d",
5029 lt->tm_year + 1900, lt->tm_mon +1, lt->tm_mday);
5032 total_done += 2; CHECK_SIZE();
5033 *curpos++ = '0'+(lt->tm_hour / 10);
5034 *curpos++ = '0'+(lt->tm_hour % 10);
5037 total_done += 2; CHECK_SIZE();
5043 *curpos++ = '0'+(tmp / 10);
5044 *curpos++ = '0'+(tmp % 10);
5047 len = 3; CHECK_SIZE();
5048 snprintf(curpos, buflen - total_done, "%03d", lt->tm_yday+1);
5051 len = 2; CHECK_SIZE();
5052 snprintf(curpos, buflen - total_done, "%2d", lt->tm_hour);
5055 len = 2; CHECK_SIZE();
5061 snprintf(curpos, buflen - total_done, "%2d", tmp);
5064 total_done += 2; CHECK_SIZE();
5065 tmp = lt->tm_mon + 1;
5066 *curpos++ = '0'+(tmp / 10);
5067 *curpos++ = '0'+(tmp % 10);
5070 total_done += 2; CHECK_SIZE();
5071 *curpos++ = '0'+(lt->tm_min / 10);
5072 *curpos++ = '0'+(lt->tm_min % 10);
5075 len = 1; CHECK_SIZE();
5079 if (lt->tm_hour >= 12) {
5080 len = s_pm_up_len; CHECK_SIZE();
5081 snprintf(curpos, buflen-total_done, "%s", s_pm_up);
5083 len = s_am_up_len; CHECK_SIZE();
5084 snprintf(curpos, buflen-total_done, "%s", s_am_up);
5088 if (lt->tm_hour >= 12) {
5089 len = s_pm_low_len; CHECK_SIZE();
5090 snprintf(curpos, buflen-total_done, "%s", s_pm_low);
5092 len = s_am_low_len; CHECK_SIZE();
5093 snprintf(curpos, buflen-total_done, "%s", s_am_low);
5097 strftime(subbuf, 64, "%r", lt);
5098 len = strlen(subbuf); CHECK_SIZE();
5099 strncpy2(curpos, subbuf, buflen - total_done);
5102 total_done += 5; CHECK_SIZE();
5103 *curpos++ = '0'+(lt->tm_hour / 10);
5104 *curpos++ = '0'+(lt->tm_hour % 10);
5106 *curpos++ = '0'+(lt->tm_min / 10);
5107 *curpos++ = '0'+(lt->tm_min % 10);
5110 snprintf(subbuf, 64, "%ld", mktime(lt));
5111 len = strlen(subbuf); CHECK_SIZE();
5112 strncpy2(curpos, subbuf, buflen - total_done);
5115 total_done += 2; CHECK_SIZE();
5116 *curpos++ = '0'+(lt->tm_sec / 10);
5117 *curpos++ = '0'+(lt->tm_sec % 10);
5120 len = 1; CHECK_SIZE();
5124 total_done += 8; CHECK_SIZE();
5125 *curpos++ = '0'+(lt->tm_hour / 10);
5126 *curpos++ = '0'+(lt->tm_hour % 10);
5128 *curpos++ = '0'+(lt->tm_min / 10);
5129 *curpos++ = '0'+(lt->tm_min % 10);
5131 *curpos++ = '0'+(lt->tm_sec / 10);
5132 *curpos++ = '0'+(lt->tm_sec % 10);
5135 len = 1; CHECK_SIZE();
5136 snprintf(curpos, buflen - total_done, "%d", lt->tm_wday == 0 ? 7: lt->tm_wday);
5139 len = 1; CHECK_SIZE();
5140 snprintf(curpos, buflen - total_done, "%d", lt->tm_wday);
5143 strftime(subbuf, 64, "%x", lt);
5144 len = strlen(subbuf); CHECK_SIZE();
5145 strncpy2(curpos, subbuf, buflen - total_done);
5148 strftime(subbuf, 64, "%X", lt);
5149 len = strlen(subbuf); CHECK_SIZE();
5150 strncpy2(curpos, subbuf, buflen - total_done);
5153 total_done += 2; CHECK_SIZE();
5154 tmp = lt->tm_year%100;
5155 *curpos++ = '0'+(tmp / 10);
5156 *curpos++ = '0'+(tmp % 10);
5159 len = 4; CHECK_SIZE();
5160 snprintf(curpos, buflen - total_done, "%4d", lt->tm_year + 1900);
5170 /* let these complicated ones be done with the libc */
5171 snprintf(subfmt, 64, "%%%c", *format);
5172 strftime(subbuf, 64, subfmt, lt);
5173 len = strlen(subbuf); CHECK_SIZE();
5174 strncpy2(curpos, subbuf, buflen - total_done);
5178 /* let these complicated modifiers be done with the libc */
5179 snprintf(subfmt, 64, "%%%c%c", *format, *(format+1));
5180 strftime(subbuf, 64, subfmt, lt);
5181 len = strlen(subbuf); CHECK_SIZE();
5182 strncpy2(curpos, subbuf, buflen - total_done);
5186 g_warning("format error (%c)", *format);
5193 int len = 1; CHECK_SIZE();
5194 *curpos++ = *format++;
5201 gboolean prefs_common_get_use_shred(void);
5205 #define WEXITSTATUS(x) (x)
5208 int claws_unlink(const gchar *filename)
5211 static int found_shred = -1;
5212 static const gchar *args[4];
5214 if (filename == NULL)
5217 if (prefs_common_get_use_shred()) {
5218 if (found_shred == -1) {
5220 args[0] = g_find_program_in_path("shred");
5221 debug_print("found shred: %s\n", args[0]);
5222 found_shred = (args[0] != NULL) ? 1:0;
5226 if (found_shred == 1) {
5227 if (g_stat(filename, &s) == 0 && S_ISREG(s.st_mode)) {
5228 if (s.st_nlink == 1) {
5231 g_spawn_sync(NULL, (gchar **)args, NULL, 0,
5232 NULL, NULL, NULL, NULL, &status, NULL);
5233 debug_print("%s %s exited with status %d\n",
5234 args[0], filename, WEXITSTATUS(status));
5235 if (truncate(filename, 0) < 0)
5236 g_warning("couln't truncate: %s", filename);
5241 return g_unlink(filename);
5244 GMutex *cm_mutex_new(void) {
5245 #if GLIB_CHECK_VERSION(2,32,0)
5246 GMutex *m = g_new0(GMutex, 1);
5250 return g_mutex_new();
5254 void cm_mutex_free(GMutex *mutex) {
5255 #if GLIB_CHECK_VERSION(2,32,0)
5256 g_mutex_clear(mutex);
5259 g_mutex_free(mutex);
5263 static gchar *canonical_list_to_file(GSList *list)
5265 GString *result = g_string_new(NULL);
5266 GSList *pathlist = g_slist_reverse(g_slist_copy(list));
5271 result = g_string_append(result, G_DIR_SEPARATOR_S);
5273 if (pathlist->data) {
5274 const gchar *root = (gchar *)pathlist->data;
5275 if (root[0] != '\0' && g_ascii_isalpha(root[0]) &&
5277 /* drive - don't prepend dir separator */
5279 result = g_string_append(result, G_DIR_SEPARATOR_S);
5284 for (cur = pathlist; cur; cur = cur->next) {
5285 result = g_string_append(result, (gchar *)cur->data);
5287 result = g_string_append(result, G_DIR_SEPARATOR_S);
5289 g_slist_free(pathlist);
5292 g_string_free(result, FALSE);
5297 static GSList *cm_split_path(const gchar *filename, int depth)
5300 GSList *canonical_parts = NULL;
5303 gboolean follow_symlinks = TRUE;
5309 errno = EINVAL; /* can't happen, no symlink handling */
5314 if (!g_path_is_absolute(filename)) {
5319 path_parts = g_strsplit(filename, G_DIR_SEPARATOR_S, -1);
5321 for (i = 0; path_parts[i] != NULL; i++) {
5322 if (!strcmp(path_parts[i], ""))
5324 if (!strcmp(path_parts[i], "."))
5326 else if (!strcmp(path_parts[i], "..")) {
5331 else /* Remove the last inserted element */
5333 g_slist_delete_link(canonical_parts,
5338 canonical_parts = g_slist_prepend(canonical_parts,
5339 g_strdup(path_parts[i]));
5341 tmp_path = canonical_list_to_file(canonical_parts);
5343 if(g_stat(tmp_path, &st) < 0) {
5344 if (errno == ENOENT) {
5346 follow_symlinks = FALSE;
5350 slist_free_strings_full(canonical_parts);
5351 g_strfreev(path_parts);
5357 if (follow_symlinks && g_file_test(tmp_path, G_FILE_TEST_IS_SYMLINK)) {
5358 GError *error = NULL;
5359 gchar *target = g_file_read_link(tmp_path, &error);
5361 if (!g_path_is_absolute(target)) {
5362 /* remove the last inserted element */
5364 g_slist_delete_link(canonical_parts,
5366 /* add the target */
5367 canonical_parts = g_slist_prepend(canonical_parts,
5371 /* and get the new target */
5372 target = canonical_list_to_file(canonical_parts);
5375 /* restart from absolute target */
5376 slist_free_strings_full(canonical_parts);
5377 canonical_parts = NULL;
5379 canonical_parts = cm_split_path(target, depth + 1);
5381 g_error_free(error);
5382 if (canonical_parts == NULL) {
5384 g_strfreev(path_parts);
5393 g_strfreev(path_parts);
5394 return canonical_parts;
5398 * Canonicalize a filename, resolving symlinks along the way.
5399 * Returns a negative errno in case of error.
5401 int cm_canonicalize_filename(const gchar *filename, gchar **canonical_name) {
5402 GSList *canonical_parts;
5403 gboolean is_absolute;
5405 if (filename == NULL)
5407 if (canonical_name == NULL)
5409 *canonical_name = NULL;
5411 is_absolute = g_path_is_absolute(filename);
5413 /* Always work on absolute filenames. */
5414 gchar *cur = g_get_current_dir();
5415 gchar *absolute_filename = g_strconcat(cur, G_DIR_SEPARATOR_S,
5418 canonical_parts = cm_split_path(absolute_filename, 0);
5419 g_free(absolute_filename);
5422 canonical_parts = cm_split_path(filename, 0);
5424 if (canonical_parts == NULL)
5427 *canonical_name = canonical_list_to_file(canonical_parts);
5428 slist_free_strings_full(canonical_parts);
5432 /* Returns a decoded base64 string, guaranteed to be null-terminated. */
5433 guchar *g_base64_decode_zero(const gchar *text, gsize *out_len)
5435 gchar *tmp = g_base64_decode(text, out_len);
5436 gchar *out = g_strndup(tmp, *out_len);
5440 if (strlen(out) != *out_len) {
5441 g_warning ("strlen(out) %zd != *out_len %" G_GSIZE_FORMAT, strlen(out), *out_len);
5447 #if !GLIB_CHECK_VERSION(2, 30, 0)
5450 * @str: a UTF-8 encoded string
5451 * @start_pos: a character offset within @str
5452 * @end_pos: another character offset within @str
5454 * Copies a substring out of a UTF-8 encoded string.
5455 * The substring will contain @end_pos - @start_pos
5458 * Returns: a newly allocated copy of the requested
5459 * substring. Free with g_free() when no longer needed.
5464 g_utf8_substring (const gchar *str,
5468 gchar *start, *end, *out;
5470 start = g_utf8_offset_to_pointer (str, start_pos);
5471 end = g_utf8_offset_to_pointer (start, end_pos - start_pos);
5473 out = g_malloc (end - start + 1);
5474 memcpy (out, start, end - start);
5475 out[end - start] = 0;