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 *file_name;
2201 if ((dp = g_dir_open(dir, 0, NULL)) == NULL) {
2202 g_warning("failed to open directory: %s", dir);
2206 while ((file_name = g_dir_read_name(dp)) != NULL) {
2207 tmp = g_strconcat(dir, G_DIR_SEPARATOR_S, file_name, NULL);
2208 if (claws_unlink(tmp) < 0)
2209 FILE_OP_ERROR(tmp, "unlink");
2218 gint remove_numbered_files(const gchar *dir, guint first, guint last)
2221 const gchar *dir_name;
2225 if (first == last) {
2226 /* Skip all the dir reading part. */
2227 gchar *filename = g_strdup_printf("%s%s%u", dir, G_DIR_SEPARATOR_S, first);
2228 if (is_dir_exist(filename)) {
2229 /* a numbered directory with this name exists,
2230 * remove the dot-file instead */
2232 filename = g_strdup_printf("%s%s.%u", dir, G_DIR_SEPARATOR_S, first);
2234 if (claws_unlink(filename) < 0) {
2235 FILE_OP_ERROR(filename, "unlink");
2243 prev_dir = g_get_current_dir();
2245 if (g_chdir(dir) < 0) {
2246 FILE_OP_ERROR(dir, "chdir");
2251 if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2252 g_warning("failed to open directory: %s", dir);
2257 while ((dir_name = g_dir_read_name(dp)) != NULL) {
2258 file_no = to_number(dir_name);
2259 if (file_no > 0 && first <= file_no && file_no <= last) {
2260 if (is_dir_exist(dir_name)) {
2261 gchar *dot_file = g_strdup_printf(".%s", dir_name);
2262 if (is_file_exist(dot_file) && claws_unlink(dot_file) < 0) {
2263 FILE_OP_ERROR(dot_file, "unlink");
2268 if (claws_unlink(dir_name) < 0)
2269 FILE_OP_ERROR(dir_name, "unlink");
2275 if (g_chdir(prev_dir) < 0) {
2276 FILE_OP_ERROR(prev_dir, "chdir");
2286 gint remove_numbered_files_not_in_list(const gchar *dir, GSList *numberlist)
2289 const gchar *dir_name;
2292 GHashTable *wanted_files;
2294 GError *error = NULL;
2296 if (numberlist == NULL)
2299 prev_dir = g_get_current_dir();
2301 if (g_chdir(dir) < 0) {
2302 FILE_OP_ERROR(dir, "chdir");
2307 if ((dp = g_dir_open(".", 0, &error)) == NULL) {
2308 g_message("Couldn't open current directory: %s (%d).\n",
2309 error->message, error->code);
2310 g_error_free(error);
2315 wanted_files = g_hash_table_new(g_direct_hash, g_direct_equal);
2316 for (cur = numberlist; cur != NULL; cur = cur->next) {
2317 /* numberlist->data is expected to be GINT_TO_POINTER */
2318 g_hash_table_insert(wanted_files, cur->data, GINT_TO_POINTER(1));
2321 while ((dir_name = g_dir_read_name(dp)) != NULL) {
2322 file_no = to_number(dir_name);
2323 if (is_dir_exist(dir_name))
2325 if (file_no > 0 && g_hash_table_lookup(wanted_files, GINT_TO_POINTER(file_no)) == NULL) {
2326 debug_print("removing unwanted file %d from %s\n", file_no, dir);
2327 if (is_dir_exist(dir_name)) {
2328 gchar *dot_file = g_strdup_printf(".%s", dir_name);
2329 if (is_file_exist(dot_file) && claws_unlink(dot_file) < 0) {
2330 FILE_OP_ERROR(dot_file, "unlink");
2335 if (claws_unlink(dir_name) < 0)
2336 FILE_OP_ERROR(dir_name, "unlink");
2341 g_hash_table_destroy(wanted_files);
2343 if (g_chdir(prev_dir) < 0) {
2344 FILE_OP_ERROR(prev_dir, "chdir");
2354 gint remove_all_numbered_files(const gchar *dir)
2356 return remove_numbered_files(dir, 0, UINT_MAX);
2359 gint remove_dir_recursive(const gchar *dir)
2363 const gchar *dir_name;
2366 if (g_stat(dir, &s) < 0) {
2367 FILE_OP_ERROR(dir, "stat");
2368 if (ENOENT == errno) return 0;
2372 if (!S_ISDIR(s.st_mode)) {
2373 if (claws_unlink(dir) < 0) {
2374 FILE_OP_ERROR(dir, "unlink");
2381 prev_dir = g_get_current_dir();
2382 /* g_print("prev_dir = %s\n", prev_dir); */
2384 if (!path_cmp(prev_dir, dir)) {
2386 if (g_chdir("..") < 0) {
2387 FILE_OP_ERROR(dir, "chdir");
2390 prev_dir = g_get_current_dir();
2393 if (g_chdir(dir) < 0) {
2394 FILE_OP_ERROR(dir, "chdir");
2399 if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2400 g_warning("failed to open directory: %s", dir);
2406 /* remove all files in the directory */
2407 while ((dir_name = g_dir_read_name(dp)) != NULL) {
2408 /* g_print("removing %s\n", dir_name); */
2410 if (is_dir_exist(dir_name)) {
2413 if ((ret = remove_dir_recursive(dir_name)) < 0) {
2414 g_warning("can't remove directory: %s", dir_name);
2418 if (claws_unlink(dir_name) < 0)
2419 FILE_OP_ERROR(dir_name, "unlink");
2425 if (g_chdir(prev_dir) < 0) {
2426 FILE_OP_ERROR(prev_dir, "chdir");
2433 if (g_rmdir(dir) < 0) {
2434 FILE_OP_ERROR(dir, "rmdir");
2441 gint rename_force(const gchar *oldpath, const gchar *newpath)
2444 if (!is_file_entry_exist(oldpath)) {
2448 if (is_file_exist(newpath)) {
2449 if (claws_unlink(newpath) < 0)
2450 FILE_OP_ERROR(newpath, "unlink");
2453 return g_rename(oldpath, newpath);
2457 * Append src file body to the tail of dest file.
2458 * Now keep_backup has no effects.
2460 gint append_file(const gchar *src, const gchar *dest, gboolean keep_backup)
2462 FILE *src_fp, *dest_fp;
2466 gboolean err = FALSE;
2468 if ((src_fp = g_fopen(src, "rb")) == NULL) {
2469 FILE_OP_ERROR(src, "g_fopen");
2473 if ((dest_fp = g_fopen(dest, "ab")) == NULL) {
2474 FILE_OP_ERROR(dest, "g_fopen");
2479 if (change_file_mode_rw(dest_fp, dest) < 0) {
2480 FILE_OP_ERROR(dest, "chmod");
2481 g_warning("can't change file mode: %s", dest);
2484 while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
2485 if (n_read < sizeof(buf) && ferror(src_fp))
2487 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2488 g_warning("writing to %s failed.", dest);
2496 if (ferror(src_fp)) {
2497 FILE_OP_ERROR(src, "fread");
2501 if (fclose(dest_fp) == EOF) {
2502 FILE_OP_ERROR(dest, "fclose");
2514 gint copy_file(const gchar *src, const gchar *dest, gboolean keep_backup)
2516 FILE *src_fp, *dest_fp;
2519 gchar *dest_bak = NULL;
2520 gboolean err = FALSE;
2522 if ((src_fp = g_fopen(src, "rb")) == NULL) {
2523 FILE_OP_ERROR(src, "g_fopen");
2526 if (is_file_exist(dest)) {
2527 dest_bak = g_strconcat(dest, ".bak", NULL);
2528 if (rename_force(dest, dest_bak) < 0) {
2529 FILE_OP_ERROR(dest, "rename");
2536 if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2537 FILE_OP_ERROR(dest, "g_fopen");
2540 if (rename_force(dest_bak, dest) < 0)
2541 FILE_OP_ERROR(dest_bak, "rename");
2547 if (change_file_mode_rw(dest_fp, dest) < 0) {
2548 FILE_OP_ERROR(dest, "chmod");
2549 g_warning("can't change file mode: %s", dest);
2552 while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
2553 if (n_read < sizeof(buf) && ferror(src_fp))
2555 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2556 g_warning("writing to %s failed.", dest);
2561 if (rename_force(dest_bak, dest) < 0)
2562 FILE_OP_ERROR(dest_bak, "rename");
2569 if (ferror(src_fp)) {
2570 FILE_OP_ERROR(src, "fread");
2574 if (fclose(dest_fp) == EOF) {
2575 FILE_OP_ERROR(dest, "fclose");
2582 if (rename_force(dest_bak, dest) < 0)
2583 FILE_OP_ERROR(dest_bak, "rename");
2589 if (keep_backup == FALSE && dest_bak)
2590 claws_unlink(dest_bak);
2597 gint move_file(const gchar *src, const gchar *dest, gboolean overwrite)
2599 if (overwrite == FALSE && is_file_exist(dest)) {
2600 g_warning("move_file(): file %s already exists.", dest);
2604 if (rename_force(src, dest) == 0) return 0;
2606 if (EXDEV != errno) {
2607 FILE_OP_ERROR(src, "rename");
2611 if (copy_file(src, dest, FALSE) < 0) return -1;
2618 gint copy_file_part_to_fp(FILE *fp, off_t offset, size_t length, FILE *dest_fp)
2621 gint bytes_left, to_read;
2624 if (fseek(fp, offset, SEEK_SET) < 0) {
2629 bytes_left = length;
2630 to_read = MIN(bytes_left, sizeof(buf));
2632 while ((n_read = fread(buf, sizeof(gchar), to_read, fp)) > 0) {
2633 if (n_read < to_read && ferror(fp))
2635 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2638 bytes_left -= n_read;
2639 if (bytes_left == 0)
2641 to_read = MIN(bytes_left, sizeof(buf));
2652 gint copy_file_part(FILE *fp, off_t offset, size_t length, const gchar *dest)
2655 gboolean err = FALSE;
2657 if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2658 FILE_OP_ERROR(dest, "g_fopen");
2662 if (change_file_mode_rw(dest_fp, dest) < 0) {
2663 FILE_OP_ERROR(dest, "chmod");
2664 g_warning("can't change file mode: %s", dest);
2667 if (copy_file_part_to_fp(fp, offset, length, dest_fp) < 0)
2670 if (!err && fclose(dest_fp) == EOF) {
2671 FILE_OP_ERROR(dest, "fclose");
2676 g_warning("writing to %s failed.", dest);
2684 /* convert line endings into CRLF. If the last line doesn't end with
2685 * linebreak, add it.
2687 gchar *canonicalize_str(const gchar *str)
2693 for (p = str; *p != '\0'; ++p) {
2700 if (p == str || *(p - 1) != '\n')
2703 out = outp = g_malloc(new_len + 1);
2704 for (p = str; *p != '\0'; ++p) {
2711 if (p == str || *(p - 1) != '\n') {
2720 gint canonicalize_file(const gchar *src, const gchar *dest)
2722 FILE *src_fp, *dest_fp;
2723 gchar buf[BUFFSIZE];
2725 gboolean err = FALSE;
2726 gboolean last_linebreak = FALSE;
2728 if (src == NULL || dest == NULL)
2731 if ((src_fp = g_fopen(src, "rb")) == NULL) {
2732 FILE_OP_ERROR(src, "g_fopen");
2736 if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2737 FILE_OP_ERROR(dest, "g_fopen");
2742 if (change_file_mode_rw(dest_fp, dest) < 0) {
2743 FILE_OP_ERROR(dest, "chmod");
2744 g_warning("can't change file mode: %s", dest);
2747 while (fgets(buf, sizeof(buf), src_fp) != NULL) {
2751 if (len == 0) break;
2752 last_linebreak = FALSE;
2754 if (buf[len - 1] != '\n') {
2755 last_linebreak = TRUE;
2756 r = fputs(buf, dest_fp);
2757 } else if (len > 1 && buf[len - 1] == '\n' && buf[len - 2] == '\r') {
2758 r = fputs(buf, dest_fp);
2761 r = fwrite(buf, 1, len - 1, dest_fp);
2766 r = fputs("\r\n", dest_fp);
2770 g_warning("writing to %s failed.", dest);
2778 if (last_linebreak == TRUE) {
2779 if (fputs("\r\n", dest_fp) == EOF)
2783 if (ferror(src_fp)) {
2784 FILE_OP_ERROR(src, "fgets");
2788 if (fclose(dest_fp) == EOF) {
2789 FILE_OP_ERROR(dest, "fclose");
2801 gint canonicalize_file_replace(const gchar *file)
2805 tmp_file = get_tmp_file();
2807 if (canonicalize_file(file, tmp_file) < 0) {
2812 if (move_file(tmp_file, file, TRUE) < 0) {
2813 g_warning("can't replace file: %s", file);
2814 claws_unlink(tmp_file);
2823 gchar *normalize_newlines(const gchar *str)
2828 out = outp = g_malloc(strlen(str) + 1);
2829 for (p = str; *p != '\0'; ++p) {
2831 if (*(p + 1) != '\n')
2842 gchar *get_outgoing_rfc2822_str(FILE *fp)
2844 gchar buf[BUFFSIZE];
2848 str = g_string_new(NULL);
2850 /* output header part */
2851 while (fgets(buf, sizeof(buf), fp) != NULL) {
2853 if (!g_ascii_strncasecmp(buf, "Bcc:", 4)) {
2860 else if (next != ' ' && next != '\t') {
2864 if (fgets(buf, sizeof(buf), fp) == NULL)
2868 g_string_append(str, buf);
2869 g_string_append(str, "\r\n");
2875 /* output body part */
2876 while (fgets(buf, sizeof(buf), fp) != NULL) {
2879 g_string_append_c(str, '.');
2880 g_string_append(str, buf);
2881 g_string_append(str, "\r\n");
2885 g_string_free(str, FALSE);
2891 * Create a new boundary in a way that it is very unlikely that this
2892 * will occur in the following text. It would be easy to ensure
2893 * uniqueness if everything is either quoted-printable or base64
2894 * encoded (note that conversion is allowed), but because MIME bodies
2895 * may be nested, it may happen that the same boundary has already
2898 * boundary := 0*69<bchars> bcharsnospace
2899 * bchars := bcharsnospace / " "
2900 * bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" /
2901 * "+" / "_" / "," / "-" / "." /
2902 * "/" / ":" / "=" / "?"
2904 * some special characters removed because of buggy MTAs
2907 gchar *generate_mime_boundary(const gchar *prefix)
2909 static gchar tbl[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
2910 "abcdefghijklmnopqrstuvwxyz"
2915 for (i = 0; i < sizeof(buf_uniq) - 1; i++)
2916 buf_uniq[i] = tbl[g_random_int_range(0, sizeof(tbl) - 1)];
2919 return g_strdup_printf("%s_/%s", prefix ? prefix : "MP",
2923 gint change_file_mode_rw(FILE *fp, const gchar *file)
2926 return fchmod(fileno(fp), S_IRUSR|S_IWUSR);
2928 return g_chmod(file, S_IRUSR|S_IWUSR);
2932 FILE *my_tmpfile(void)
2934 const gchar suffix[] = ".XXXXXX";
2935 const gchar *tmpdir;
2937 const gchar *progname;
2946 tmpdir = get_tmp_dir();
2947 tmplen = strlen(tmpdir);
2948 progname = g_get_prgname();
2949 if (progname == NULL)
2950 progname = "claws-mail";
2951 proglen = strlen(progname);
2952 Xalloca(fname, tmplen + 1 + proglen + sizeof(suffix),
2955 memcpy(fname, tmpdir, tmplen);
2956 fname[tmplen] = G_DIR_SEPARATOR;
2957 memcpy(fname + tmplen + 1, progname, proglen);
2958 memcpy(fname + tmplen + 1 + proglen, suffix, sizeof(suffix));
2960 fd = g_mkstemp(fname);
2965 claws_unlink(fname);
2967 /* verify that we can write in the file after unlinking */
2968 if (write(fd, buf, 1) < 0) {
2975 fp = fdopen(fd, "w+b");
2986 FILE *get_tmpfile_in_dir(const gchar *dir, gchar **filename)
2989 *filename = g_strdup_printf("%s%cclaws.XXXXXX", dir, G_DIR_SEPARATOR);
2990 fd = g_mkstemp(*filename);
2993 return fdopen(fd, "w+");
2996 FILE *str_open_as_stream(const gchar *str)
3001 cm_return_val_if_fail(str != NULL, NULL);
3005 FILE_OP_ERROR("str_open_as_stream", "my_tmpfile");
3010 if (len == 0) return fp;
3012 if (fwrite(str, 1, len, fp) != len) {
3013 FILE_OP_ERROR("str_open_as_stream", "fwrite");
3022 gint str_write_to_file(const gchar *str, const gchar *file)
3027 cm_return_val_if_fail(str != NULL, -1);
3028 cm_return_val_if_fail(file != NULL, -1);
3030 if ((fp = g_fopen(file, "wb")) == NULL) {
3031 FILE_OP_ERROR(file, "g_fopen");
3041 if (fwrite(str, 1, len, fp) != len) {
3042 FILE_OP_ERROR(file, "fwrite");
3048 if (fclose(fp) == EOF) {
3049 FILE_OP_ERROR(file, "fclose");
3057 static gchar *file_read_stream_to_str_full(FILE *fp, gboolean recode)
3064 cm_return_val_if_fail(fp != NULL, NULL);
3066 array = g_byte_array_new();
3068 while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
3069 if (n_read < sizeof(buf) && ferror(fp))
3071 g_byte_array_append(array, buf, n_read);
3075 FILE_OP_ERROR("file stream", "fread");
3076 g_byte_array_free(array, TRUE);
3081 g_byte_array_append(array, buf, 1);
3082 str = (gchar *)array->data;
3083 g_byte_array_free(array, FALSE);
3085 if (recode && !g_utf8_validate(str, -1, NULL)) {
3086 const gchar *src_codeset, *dest_codeset;
3088 src_codeset = conv_get_locale_charset_str();
3089 dest_codeset = CS_UTF_8;
3090 tmp = conv_codeset_strdup(str, src_codeset, dest_codeset);
3098 static gchar *file_read_to_str_full(const gchar *file, gboolean recode)
3105 struct timeval timeout = {1, 0};
3110 cm_return_val_if_fail(file != NULL, NULL);
3112 if (g_stat(file, &s) != 0) {
3113 FILE_OP_ERROR(file, "stat");
3116 if (S_ISDIR(s.st_mode)) {
3117 g_warning("%s: is a directory", file);
3122 fp = g_fopen (file, "rb");
3124 FILE_OP_ERROR(file, "open");
3128 /* test whether the file is readable without blocking */
3129 fd = g_open(file, O_RDONLY | O_NONBLOCK, 0);
3131 FILE_OP_ERROR(file, "open");
3138 /* allow for one second */
3139 err = select(fd+1, &fds, NULL, NULL, &timeout);
3140 if (err <= 0 || !FD_ISSET(fd, &fds)) {
3142 FILE_OP_ERROR(file, "select");
3144 g_warning("%s: doesn't seem readable", file);
3150 /* Now clear O_NONBLOCK */
3151 if ((fflags = fcntl(fd, F_GETFL)) < 0) {
3152 FILE_OP_ERROR(file, "fcntl (F_GETFL)");
3156 if (fcntl(fd, F_SETFL, (fflags & ~O_NONBLOCK)) < 0) {
3157 FILE_OP_ERROR(file, "fcntl (F_SETFL)");
3162 /* get the FILE pointer */
3163 fp = fdopen(fd, "rb");
3166 FILE_OP_ERROR(file, "fdopen");
3167 close(fd); /* if fp isn't NULL, we'll use fclose instead! */
3172 str = file_read_stream_to_str_full(fp, recode);
3179 gchar *file_read_to_str(const gchar *file)
3181 return file_read_to_str_full(file, TRUE);
3183 gchar *file_read_stream_to_str(FILE *fp)
3185 return file_read_stream_to_str_full(fp, TRUE);
3188 gchar *file_read_to_str_no_recode(const gchar *file)
3190 return file_read_to_str_full(file, FALSE);
3192 gchar *file_read_stream_to_str_no_recode(FILE *fp)
3194 return file_read_stream_to_str_full(fp, FALSE);
3197 char *fgets_crlf(char *buf, int size, FILE *stream)
3199 gboolean is_cr = FALSE;
3200 gboolean last_was_cr = FALSE;
3205 while (--size > 0 && (c = getc(stream)) != EOF)
3208 is_cr = (c == '\r');
3218 last_was_cr = is_cr;
3220 if (c == EOF && cs == buf)
3228 static gint execute_async(gchar *const argv[])
3230 cm_return_val_if_fail(argv != NULL && argv[0] != NULL, -1);
3232 if (g_spawn_async(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH,
3233 NULL, NULL, NULL, FALSE) == FALSE) {
3234 g_warning("couldn't execute command: %s", argv[0]);
3241 static gint execute_sync(gchar *const argv[])
3245 cm_return_val_if_fail(argv != NULL && argv[0] != NULL, -1);
3248 if (g_spawn_sync(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH,
3249 NULL, NULL, NULL, NULL, &status, NULL) == FALSE) {
3250 g_warning("couldn't execute command: %s", argv[0]);
3254 if (WIFEXITED(status))
3255 return WEXITSTATUS(status);
3259 if (g_spawn_sync(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH|
3260 G_SPAWN_CHILD_INHERITS_STDIN|G_SPAWN_LEAVE_DESCRIPTORS_OPEN,
3261 NULL, NULL, NULL, NULL, &status, NULL) == FALSE) {
3262 g_warning("couldn't execute command: %s", argv[0]);
3270 gint execute_command_line(const gchar *cmdline, gboolean async)
3275 debug_print("execute_command_line(): executing: %s\n", cmdline?cmdline:"(null)");
3277 argv = strsplit_with_quote(cmdline, " ", 0);
3280 ret = execute_async(argv);
3282 ret = execute_sync(argv);
3289 gchar *get_command_output(const gchar *cmdline)
3291 gchar *child_stdout;
3294 cm_return_val_if_fail(cmdline != NULL, NULL);
3296 debug_print("get_command_output(): executing: %s\n", cmdline);
3298 if (g_spawn_command_line_sync(cmdline, &child_stdout, NULL, &status,
3300 g_warning("couldn't execute command: %s", cmdline);
3304 return child_stdout;
3307 static gint is_unchanged_uri_char(char c)
3318 static void encode_uri(gchar *encoded_uri, gint bufsize, const gchar *uri)
3324 for(i = 0; i < strlen(uri) ; i++) {
3325 if (is_unchanged_uri_char(uri[i])) {
3326 if (k + 2 >= bufsize)
3328 encoded_uri[k++] = uri[i];
3331 char * hexa = "0123456789ABCDEF";
3333 if (k + 4 >= bufsize)
3335 encoded_uri[k++] = '%';
3336 encoded_uri[k++] = hexa[uri[i] / 16];
3337 encoded_uri[k++] = hexa[uri[i] % 16];
3343 gint open_uri(const gchar *uri, const gchar *cmdline)
3347 gchar buf[BUFFSIZE];
3349 gchar encoded_uri[BUFFSIZE];
3350 cm_return_val_if_fail(uri != NULL, -1);
3352 /* an option to choose whether to use encode_uri or not ? */
3353 encode_uri(encoded_uri, BUFFSIZE, uri);
3356 (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
3357 !strchr(p + 2, '%'))
3358 g_snprintf(buf, sizeof(buf), cmdline, encoded_uri);
3361 g_warning("Open URI command-line is invalid "
3362 "(there must be only one '%%s'): %s",
3364 g_snprintf(buf, sizeof(buf), DEFAULT_BROWSER_CMD, encoded_uri);
3367 execute_command_line(buf, TRUE);
3369 ShellExecute(NULL, "open", uri, NULL, NULL, SW_SHOW);
3374 gint open_txt_editor(const gchar *filepath, const gchar *cmdline)
3376 gchar buf[BUFFSIZE];
3379 cm_return_val_if_fail(filepath != NULL, -1);
3382 (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
3383 !strchr(p + 2, '%'))
3384 g_snprintf(buf, sizeof(buf), cmdline, filepath);
3387 g_warning("Open Text Editor command-line is invalid "
3388 "(there must be only one '%%s'): %s",
3390 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, filepath);
3393 execute_command_line(buf, TRUE);
3398 time_t remote_tzoffset_sec(const gchar *zone)
3400 static gchar ustzstr[] = "PSTPDTMSTMDTCSTCDTESTEDT";
3406 time_t remoteoffset;
3408 strncpy(zone3, zone, 3);
3412 if (sscanf(zone, "%c%d", &c, &offset) == 2 &&
3413 (c == '+' || c == '-')) {
3414 remoteoffset = ((offset / 100) * 60 + (offset % 100)) * 60;
3416 remoteoffset = -remoteoffset;
3417 } else if (!strncmp(zone, "UT" , 2) ||
3418 !strncmp(zone, "GMT", 3)) {
3420 } else if (strlen(zone3) == 3) {
3421 for (p = ustzstr; *p != '\0'; p += 3) {
3422 if (!g_ascii_strncasecmp(p, zone3, 3)) {
3423 iustz = ((gint)(p - ustzstr) / 3 + 1) / 2 - 8;
3424 remoteoffset = iustz * 3600;
3430 } else if (strlen(zone3) == 1) {
3432 case 'Z': remoteoffset = 0; break;
3433 case 'A': remoteoffset = -1; break;
3434 case 'B': remoteoffset = -2; break;
3435 case 'C': remoteoffset = -3; break;
3436 case 'D': remoteoffset = -4; break;
3437 case 'E': remoteoffset = -5; break;
3438 case 'F': remoteoffset = -6; break;
3439 case 'G': remoteoffset = -7; break;
3440 case 'H': remoteoffset = -8; break;
3441 case 'I': remoteoffset = -9; break;
3442 case 'K': remoteoffset = -10; break; /* J is not used */
3443 case 'L': remoteoffset = -11; break;
3444 case 'M': remoteoffset = -12; break;
3445 case 'N': remoteoffset = 1; break;
3446 case 'O': remoteoffset = 2; break;
3447 case 'P': remoteoffset = 3; break;
3448 case 'Q': remoteoffset = 4; break;
3449 case 'R': remoteoffset = 5; break;
3450 case 'S': remoteoffset = 6; break;
3451 case 'T': remoteoffset = 7; break;
3452 case 'U': remoteoffset = 8; break;
3453 case 'V': remoteoffset = 9; break;
3454 case 'W': remoteoffset = 10; break;
3455 case 'X': remoteoffset = 11; break;
3456 case 'Y': remoteoffset = 12; break;
3457 default: remoteoffset = 0; break;
3459 remoteoffset = remoteoffset * 3600;
3463 return remoteoffset;
3466 time_t tzoffset_sec(time_t *now)
3470 struct tm buf1, buf2;
3472 if (now && *now < 0)
3475 gmt = *gmtime_r(now, &buf1);
3476 lt = localtime_r(now, &buf2);
3478 off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
3480 if (lt->tm_year < gmt.tm_year)
3482 else if (lt->tm_year > gmt.tm_year)
3484 else if (lt->tm_yday < gmt.tm_yday)
3486 else if (lt->tm_yday > gmt.tm_yday)
3489 if (off >= 24 * 60) /* should be impossible */
3490 off = 23 * 60 + 59; /* if not, insert silly value */
3491 if (off <= -24 * 60)
3492 off = -(23 * 60 + 59);
3497 /* calculate timezone offset */
3498 gchar *tzoffset(time_t *now)
3500 static gchar offset_string[6];
3504 struct tm buf1, buf2;
3506 if (now && *now < 0)
3509 gmt = *gmtime_r(now, &buf1);
3510 lt = localtime_r(now, &buf2);
3512 off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
3514 if (lt->tm_year < gmt.tm_year)
3516 else if (lt->tm_year > gmt.tm_year)
3518 else if (lt->tm_yday < gmt.tm_yday)
3520 else if (lt->tm_yday > gmt.tm_yday)
3528 if (off >= 24 * 60) /* should be impossible */
3529 off = 23 * 60 + 59; /* if not, insert silly value */
3531 sprintf(offset_string, "%c%02d%02d", sign, off / 60, off % 60);
3533 return offset_string;
3536 void get_rfc822_date(gchar *buf, gint len)
3540 gchar day[4], mon[4];
3541 gint dd, hh, mm, ss, yyyy;
3543 gchar buf2[BUFFSIZE];
3546 lt = localtime_r(&t, &buf1);
3548 sscanf(asctime_r(lt, buf2), "%3s %3s %d %d:%d:%d %d\n",
3549 day, mon, &dd, &hh, &mm, &ss, &yyyy);
3551 g_snprintf(buf, len, "%s, %d %s %d %02d:%02d:%02d %s",
3552 day, dd, mon, yyyy, hh, mm, ss, tzoffset(&t));
3555 void debug_set_mode(gboolean mode)
3560 gboolean debug_get_mode(void)
3565 void debug_print_real(const gchar *format, ...)
3568 gchar buf[BUFFSIZE];
3570 if (!debug_mode) return;
3572 va_start(args, format);
3573 g_vsnprintf(buf, sizeof(buf), format, args);
3580 const char * debug_srcname(const char *file)
3582 const char *s = strrchr (file, '/');
3587 void * subject_table_lookup(GHashTable *subject_table, gchar * subject)
3589 if (subject == NULL)
3592 subject += subject_get_prefix_length(subject);
3594 return g_hash_table_lookup(subject_table, subject);
3597 void subject_table_insert(GHashTable *subject_table, gchar * subject,
3600 if (subject == NULL || *subject == 0)
3602 subject += subject_get_prefix_length(subject);
3603 g_hash_table_insert(subject_table, subject, data);
3606 void subject_table_remove(GHashTable *subject_table, gchar * subject)
3608 if (subject == NULL)
3611 subject += subject_get_prefix_length(subject);
3612 g_hash_table_remove(subject_table, subject);
3616 static regex_t u_regex;
3617 static gboolean u_init_;
3620 void utils_free_regex(void)
3631 *\brief Check if a string is prefixed with known (combinations)
3632 * of prefixes. The function assumes that each prefix
3633 * is terminated by zero or exactly _one_ space.
3635 *\param str String to check for a prefixes
3637 *\return int Number of chars in the prefix that should be skipped
3638 * for a "clean" subject line. If no prefix was found, 0
3641 int subject_get_prefix_length(const gchar *subject)
3644 /*!< Array with allowable reply prefixes regexps. */
3645 static const gchar * const prefixes[] = {
3646 "Re\\:", /* "Re:" */
3647 "Re\\[[1-9][0-9]*\\]\\:", /* "Re[XXX]:" (non-conforming news mail clients) */
3648 "Antw\\:", /* "Antw:" (Dutch / German Outlook) */
3649 "Aw\\:", /* "Aw:" (German) */
3650 "Antwort\\:", /* "Antwort:" (German Lotus Notes) */
3651 "Res\\:", /* "Res:" (Spanish/Brazilian Outlook) */
3652 "Fw\\:", /* "Fw:" Forward */
3653 "Fwd\\:", /* "Fwd:" Forward */
3654 "Enc\\:", /* "Enc:" Forward (Brazilian Outlook) */
3655 "Odp\\:", /* "Odp:" Re (Polish Outlook) */
3656 "Rif\\:", /* "Rif:" (Italian Outlook) */
3657 "Sv\\:", /* "Sv" (Norwegian) */
3658 "Vs\\:", /* "Vs" (Norwegian) */
3659 "Ad\\:", /* "Ad" (Norwegian) */
3660 "\347\255\224\345\244\215\\:", /* "Re" (Chinese, UTF-8) */
3661 "R\303\251f\\. \\:", /* "R�f. :" (French Lotus Notes) */
3662 "Re \\:", /* "Re :" (French Yahoo Mail) */
3665 const int PREFIXES = sizeof prefixes / sizeof prefixes[0];
3669 if (!subject) return 0;
3670 if (!*subject) return 0;
3673 GString *s = g_string_new("");
3675 for (n = 0; n < PREFIXES; n++)
3676 /* Terminate each prefix regexpression by a
3677 * "\ ?" (zero or ONE space), and OR them */
3678 g_string_append_printf(s, "(%s\\ ?)%s",
3683 g_string_prepend(s, "(");
3684 g_string_append(s, ")+"); /* match at least once */
3685 g_string_prepend(s, "^\\ *"); /* from beginning of line */
3688 /* We now have something like "^\ *((PREFIX1\ ?)|(PREFIX2\ ?))+"
3689 * TODO: Should this be "^\ *(((PREFIX1)|(PREFIX2))\ ?)+" ??? */
3690 if (regcomp(&u_regex, s->str, REG_EXTENDED | REG_ICASE)) {
3691 debug_print("Error compiling regexp %s\n", s->str);
3692 g_string_free(s, TRUE);
3696 g_string_free(s, TRUE);
3700 if (!regexec(&u_regex, subject, 1, &pos, 0) && pos.rm_so != -1)
3705 /*!< Array with allowable reply prefixes regexps. */
3706 static const gchar * const prefixes[] = {
3708 "antw:", /* "Antw:" (Dutch / German Outlook) */
3709 "aw:", /* "Aw:" (German) */
3710 "antwort:", /* "Antwort:" (German Lotus Notes) */
3711 "res:", /* "Res:" (Spanish/Brazilian Outlook) */
3712 "fw:", /* "Fw:" Forward */
3713 "fwd:", /* "Fwd:" Forward */
3714 "enc:", /* "Enc:" Forward (Brazilian Outlook) */
3715 "odp:", /* "Odp:" Re (Polish Outlook) */
3716 "rif:", /* "Rif:" (Italian Outlook) */
3717 "sv:", /* "Sv" (Norwegian) */
3718 "vs:", /* "Vs" (Norwegian) */
3719 "ad:", /* "Ad" (Norwegian) */
3720 "R\303\251f. :", /* "R�f. :" (French Lotus Notes) */
3721 "Re :", /* "Re :" (French Yahoo Mail) */
3724 const int PREFIXES = sizeof prefixes / sizeof prefixes[0];
3727 if (!subject) return 0;
3728 if (!*subject) return 0;
3730 for (n = 0; n < PREFIXES; n++) {
3731 int len = strlen(prefixes[n]);
3732 if (!strncasecmp(subject, prefixes[n], len)) {
3733 if (subject[len] == ' ')
3742 static guint g_stricase_hash(gconstpointer gptr)
3744 guint hash_result = 0;
3747 for (str = gptr; str && *str; str++) {
3748 hash_result += toupper(*str);
3754 static gint g_stricase_equal(gconstpointer gptr1, gconstpointer gptr2)
3756 const char *str1 = gptr1;
3757 const char *str2 = gptr2;
3759 return !strcasecmp(str1, str2);
3762 gint g_int_compare(gconstpointer a, gconstpointer b)
3764 return GPOINTER_TO_INT(a) - GPOINTER_TO_INT(b);
3767 gchar *generate_msgid(gchar *buf, gint len, gchar *user_addr)
3775 lt = localtime_r(&t, &buft);
3777 if (user_addr != NULL)
3778 addr = g_strdup_printf(".%s", user_addr);
3779 else if (strlen(buf) != 0)
3780 addr = g_strdup_printf("@%s", buf);
3782 addr = g_strdup_printf("@%s", get_domain_name());
3784 /* Replace all @ but the last one in addr, with underscores.
3785 * RFC 2822 States that msg-id syntax only allows one @.
3787 while (strchr(addr, '@') != NULL && strchr(addr, '@') != strrchr(addr, '@'))
3788 *(strchr(addr, '@')) = '_';
3790 g_snprintf(buf, len, "%04d%02d%02d%02d%02d%02d.%08x%s",
3791 lt->tm_year + 1900, lt->tm_mon + 1,
3792 lt->tm_mday, lt->tm_hour,
3793 lt->tm_min, lt->tm_sec,
3794 (guint) rand(), addr);
3801 quote_cmd_argument()
3803 return a quoted string safely usable in argument of a command.
3805 code is extracted and adapted from etPan! project -- DINH V. Ho�.
3808 gint quote_cmd_argument(gchar * result, guint size,
3818 for(p = path ; * p != '\0' ; p ++) {
3820 if (isalnum((guchar)*p) || (* p == '/')) {
3821 if (remaining > 0) {
3827 result[size - 1] = '\0';
3832 if (remaining >= 2) {
3840 result[size - 1] = '\0';
3845 if (remaining > 0) {
3849 result[size - 1] = '\0';
3863 static void g_node_map_recursive(GNode *node, gpointer data)
3865 GNodeMapData *mapdata = (GNodeMapData *) data;
3867 GNodeMapData newmapdata;
3870 newdata = mapdata->func(node->data, mapdata->data);
3871 if (newdata != NULL) {
3872 newnode = g_node_new(newdata);
3873 g_node_append(mapdata->parent, newnode);
3875 newmapdata.parent = newnode;
3876 newmapdata.func = mapdata->func;
3877 newmapdata.data = mapdata->data;
3879 g_node_children_foreach(node, G_TRAVERSE_ALL, g_node_map_recursive, &newmapdata);
3883 GNode *g_node_map(GNode *node, GNodeMapFunc func, gpointer data)
3886 GNodeMapData mapdata;
3888 cm_return_val_if_fail(node != NULL, NULL);
3889 cm_return_val_if_fail(func != NULL, NULL);
3891 root = g_node_new(func(node->data, data));
3893 mapdata.parent = root;
3894 mapdata.func = func;
3895 mapdata.data = data;
3897 g_node_children_foreach(node, G_TRAVERSE_ALL, g_node_map_recursive, &mapdata);
3902 #define HEX_TO_INT(val, hex) \
3906 if ('0' <= c && c <= '9') { \
3908 } else if ('a' <= c && c <= 'f') { \
3909 val = c - 'a' + 10; \
3910 } else if ('A' <= c && c <= 'F') { \
3911 val = c - 'A' + 10; \
3917 gboolean get_hex_value(guchar *out, gchar c1, gchar c2)
3924 if (hi == -1 || lo == -1)
3927 *out = (hi << 4) + lo;
3931 #define INT_TO_HEX(hex, val) \
3934 hex = '0' + (val); \
3936 hex = 'A' + (val) - 10; \
3939 void get_hex_str(gchar *out, guchar ch)
3943 INT_TO_HEX(hex, ch >> 4);
3945 INT_TO_HEX(hex, ch & 0x0f);
3951 #define G_PRINT_REF 1 == 1 ? (void) 0 : (void)
3953 #define G_PRINT_REF g_print
3957 *\brief Register ref counted pointer. It is based on GBoxed, so should
3958 * work with anything that uses the GType system. The semantics
3959 * are similar to a C++ auto pointer, with the exception that
3960 * C doesn't have automatic closure (calling destructors) when
3961 * exiting a block scope.
3962 * Use the \ref G_TYPE_AUTO_POINTER macro instead of calling this
3963 * function directly.
3965 *\return GType A GType type.
3967 GType g_auto_pointer_register(void)
3969 static GType auto_pointer_type;
3970 if (!auto_pointer_type)
3972 g_boxed_type_register_static
3973 ("G_TYPE_AUTO_POINTER",
3974 (GBoxedCopyFunc) g_auto_pointer_copy,
3975 (GBoxedFreeFunc) g_auto_pointer_free);
3976 return auto_pointer_type;
3980 *\brief Structure with g_new() allocated pointer guarded by the
3983 typedef struct AutoPointerRef {
3984 void (*free) (gpointer);
3990 *\brief The auto pointer opaque structure that references the
3991 * pointer guard block.
3993 typedef struct AutoPointer {
3994 AutoPointerRef *ref;
3995 gpointer ptr; /*!< access to protected pointer */
3999 *\brief Creates an auto pointer for a g_new()ed pointer. Example:
4003 * ... tell gtk_list_store it should use a G_TYPE_AUTO_POINTER
4004 * ... when assigning, copying and freeing storage elements
4006 * gtk_list_store_new(N_S_COLUMNS,
4007 * G_TYPE_AUTO_POINTER,
4011 * Template *precious_data = g_new0(Template, 1);
4012 * g_pointer protect = g_auto_pointer_new(precious_data);
4014 * gtk_list_store_set(container, &iter,
4018 * ... the gtk_list_store has copied the pointer and
4019 * ... incremented its reference count, we should free
4020 * ... the auto pointer (in C++ a destructor would do
4021 * ... this for us when leaving block scope)
4023 * g_auto_pointer_free(protect);
4025 * ... gtk_list_store_set() now manages the data. When
4026 * ... *explicitly* requesting a pointer from the list
4027 * ... store, don't forget you get a copy that should be
4028 * ... freed with g_auto_pointer_free() eventually.
4032 *\param pointer Pointer to be guarded.
4034 *\return GAuto * Pointer that should be used in containers with
4037 GAuto *g_auto_pointer_new(gpointer p)
4039 AutoPointerRef *ref;
4045 ref = g_new0(AutoPointerRef, 1);
4046 ptr = g_new0(AutoPointer, 1);
4056 G_PRINT_REF ("XXXX ALLOC(%lx)\n", p);
4062 *\brief Allocate an autopointer using the passed \a free function to
4063 * free the guarded pointer
4065 GAuto *g_auto_pointer_new_with_free(gpointer p, GFreeFunc free_)
4072 aptr = g_auto_pointer_new(p);
4073 aptr->ref->free = free_;
4077 gpointer g_auto_pointer_get_ptr(GAuto *auto_ptr)
4079 if (auto_ptr == NULL)
4081 return ((AutoPointer *) auto_ptr)->ptr;
4085 *\brief Copies an auto pointer by. It's mostly not necessary
4086 * to call this function directly, unless you copy/assign
4087 * the guarded pointer.
4089 *\param auto_ptr Auto pointer returned by previous call to
4090 * g_auto_pointer_new_XXX()
4092 *\return gpointer An auto pointer
4094 GAuto *g_auto_pointer_copy(GAuto *auto_ptr)
4097 AutoPointerRef *ref;
4100 if (auto_ptr == NULL)
4105 newp = g_new0(AutoPointer, 1);
4108 newp->ptr = ref->pointer;
4112 G_PRINT_REF ("XXXX COPY(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4118 *\brief Free an auto pointer
4120 void g_auto_pointer_free(GAuto *auto_ptr)
4123 AutoPointerRef *ref;
4125 if (auto_ptr == NULL)
4131 if (--(ref->cnt) == 0) {
4133 G_PRINT_REF ("XXXX FREE(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4135 ref->free(ref->pointer);
4140 G_PRINT_REF ("XXXX DEREF(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4145 void replace_returns(gchar *str)
4150 while (strstr(str, "\n")) {
4151 *strstr(str, "\n") = ' ';
4153 while (strstr(str, "\r")) {
4154 *strstr(str, "\r") = ' ';
4158 /* get_uri_part() - retrieves a URI starting from scanpos.
4159 Returns TRUE if succesful */
4160 gboolean get_uri_part(const gchar *start, const gchar *scanpos,
4161 const gchar **bp, const gchar **ep, gboolean hdr)
4164 gint parenthese_cnt = 0;
4166 cm_return_val_if_fail(start != NULL, FALSE);
4167 cm_return_val_if_fail(scanpos != NULL, FALSE);
4168 cm_return_val_if_fail(bp != NULL, FALSE);
4169 cm_return_val_if_fail(ep != NULL, FALSE);
4173 /* find end point of URI */
4174 for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
4175 if (!g_ascii_isgraph(*(const guchar *)ep_) ||
4176 !IS_ASCII(*(const guchar *)ep_) ||
4177 strchr("[]{}<>\"", *ep_)) {
4179 } else if (strchr("(", *ep_)) {
4181 } else if (strchr(")", *ep_)) {
4182 if (parenthese_cnt > 0)
4189 /* no punctuation at end of string */
4191 /* FIXME: this stripping of trailing punctuations may bite with other URIs.
4192 * should pass some URI type to this function and decide on that whether
4193 * to perform punctuation stripping */
4195 #define IS_REAL_PUNCT(ch) (g_ascii_ispunct(ch) && !strchr("/?=-_)", ch))
4197 for (; ep_ - 1 > scanpos + 1 &&
4198 IS_REAL_PUNCT(*(ep_ - 1));
4202 #undef IS_REAL_PUNCT
4209 gchar *make_uri_string(const gchar *bp, const gchar *ep)
4211 while (bp && *bp && g_ascii_isspace(*bp))
4213 return g_strndup(bp, ep - bp);
4216 /* valid mail address characters */
4217 #define IS_RFC822_CHAR(ch) \
4221 !g_ascii_isspace(ch) && \
4222 !strchr("(),;<>\"", (ch)))
4224 /* alphabet and number within 7bit ASCII */
4225 #define IS_ASCII_ALNUM(ch) (IS_ASCII(ch) && g_ascii_isalnum(ch))
4226 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
4228 static GHashTable *create_domain_tab(void)
4230 static const gchar *toplvl_domains [] = {
4232 "arpa", "coop", "info", "name", "biz", "com", "edu", "gov",
4233 "int", "mil", "net", "org", "ac", "ad", "ae", "af", "ag",
4234 "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au",
4235 "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi",
4236 "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
4237 "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl",
4238 "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz", "de",
4239 "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er",
4240 "es", "et", "eu", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd",
4241 "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
4242 "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
4243 "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir",
4244 "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
4245 "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc",
4246 "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
4247 "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq",
4248 "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na",
4249 "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu",
4250 "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm",
4251 "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru",
4252 "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj",
4253 "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
4254 "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to",
4255 "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um",
4256 "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu",
4257 "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw"
4260 GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
4262 cm_return_val_if_fail(htab, NULL);
4263 for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++)
4264 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
4268 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
4270 const gint MAX_LVL_DOM_NAME_LEN = 6;
4271 gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
4272 const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
4275 if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
4278 for (p = buf; p < m && first < last; *p++ = *first++)
4282 return g_hash_table_lookup(tab, buf) != NULL;
4285 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
4286 gboolean get_email_part(const gchar *start, const gchar *scanpos,
4287 const gchar **bp, const gchar **ep, gboolean hdr)
4289 /* more complex than the uri part because we need to scan back and forward starting from
4290 * the scan position. */
4291 gboolean result = FALSE;
4292 const gchar *bp_ = NULL;
4293 const gchar *ep_ = NULL;
4294 static GHashTable *dom_tab;
4295 const gchar *last_dot = NULL;
4296 const gchar *prelast_dot = NULL;
4297 const gchar *last_tld_char = NULL;
4299 /* the informative part of the email address (describing the name
4300 * of the email address owner) may contain quoted parts. the
4301 * closure stack stores the last encountered quotes. */
4302 gchar closure_stack[128];
4303 gchar *ptr = closure_stack;
4305 cm_return_val_if_fail(start != NULL, FALSE);
4306 cm_return_val_if_fail(scanpos != NULL, FALSE);
4307 cm_return_val_if_fail(bp != NULL, FALSE);
4308 cm_return_val_if_fail(ep != NULL, FALSE);
4311 const gchar *start_quote = NULL;
4312 const gchar *end_quote = NULL;
4314 /* go to the real start */
4315 if (start[0] == ',')
4317 if (start[0] == ';')
4319 while (start[0] == '\n' || start[0] == '\r')
4321 while (start[0] == ' ' || start[0] == '\t')
4326 /* check if there are quotes (to skip , in them) */
4327 if (*start == '"') {
4328 start_quote = start;
4330 end_quote = strstr(start, "\"");
4336 /* skip anything between quotes */
4337 if (start_quote && end_quote) {
4342 /* find end (either , or ; or end of line) */
4343 if (strstr(start, ",") && strstr(start, ";"))
4344 *ep = strstr(start,",") < strstr(start, ";")
4345 ? strstr(start, ",") : strstr(start, ";");
4346 else if (strstr(start, ","))
4347 *ep = strstr(start, ",");
4348 else if (strstr(start, ";"))
4349 *ep = strstr(start, ";");
4351 *ep = start+strlen(start);
4353 /* go back to real start */
4354 if (start_quote && end_quote) {
4355 start = start_quote;
4358 /* check there's still an @ in that, or search
4359 * further if possible */
4360 if (strstr(start, "@") && strstr(start, "@") < *ep)
4362 else if (*ep < start+strlen(start)) {
4365 } else if (start_quote && strstr(start, "\"") && strstr(start, "\"") < *ep) {
4373 dom_tab = create_domain_tab();
4374 cm_return_val_if_fail(dom_tab, FALSE);
4376 /* scan start of address */
4377 for (bp_ = scanpos - 1;
4378 bp_ >= start && IS_RFC822_CHAR(*(const guchar *)bp_); bp_--)
4381 /* TODO: should start with an alnum? */
4383 for (; bp_ < scanpos && !IS_ASCII_ALNUM(*(const guchar *)bp_); bp_++)
4386 if (bp_ != scanpos) {
4387 /* scan end of address */
4388 for (ep_ = scanpos + 1;
4389 *ep_ && IS_RFC822_CHAR(*(const guchar *)ep_); ep_++)
4391 prelast_dot = last_dot;
4393 if (*(last_dot + 1) == '.') {
4394 if (prelast_dot == NULL)
4396 last_dot = prelast_dot;
4401 /* TODO: really should terminate with an alnum? */
4402 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*(const guchar *)ep_);
4407 if (last_dot == NULL)
4409 if (last_dot >= ep_)
4410 last_dot = prelast_dot;
4411 if (last_dot == NULL || (scanpos + 1 >= last_dot))
4415 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
4416 if (*last_tld_char == '?')
4419 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
4426 if (!result) return FALSE;
4428 if (*ep_ && bp_ != start && *(bp_ - 1) == '"' && *(ep_) == '"'
4429 && *(ep_ + 1) == ' ' && *(ep_ + 2) == '<'
4430 && IS_RFC822_CHAR(*(ep_ + 3))) {
4431 /* this informative part with an @ in it is
4432 * followed by the email address */
4435 /* go to matching '>' (or next non-rfc822 char, like \n) */
4436 for (; *ep_ != '>' && *ep != '\0' && IS_RFC822_CHAR(*ep_); ep_++)
4439 /* include the bracket */
4440 if (*ep_ == '>') ep_++;
4442 /* include the leading quote */
4450 /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
4451 if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
4454 /* see if this is <bracketed>; in this case we also scan for the informative part. */
4455 if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
4458 #define FULL_STACK() ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
4459 #define IN_STACK() (ptr > closure_stack)
4460 /* has underrun check */
4461 #define POP_STACK() if(IN_STACK()) --ptr
4462 /* has overrun check */
4463 #define PUSH_STACK(c) if(!FULL_STACK()) *ptr++ = (c); else return TRUE
4464 /* has underrun check */
4465 #define PEEK_STACK() (IN_STACK() ? *(ptr - 1) : 0)
4469 /* scan for the informative part. */
4470 for (bp_ -= 2; bp_ >= start; bp_--) {
4471 /* if closure on the stack keep scanning */
4472 if (PEEK_STACK() == *bp_) {
4476 if (!IN_STACK() && (*bp_ == '\'' || *bp_ == '"')) {
4481 /* if nothing in the closure stack, do the special conditions
4482 * the following if..else expression simply checks whether
4483 * a token is acceptable. if not acceptable, the clause
4484 * should terminate the loop with a 'break' */
4485 if (!PEEK_STACK()) {
4487 && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
4488 && (((bp_ + 1) < ep_) && isalnum(*(bp_ + 1)))) {
4489 /* hyphens are allowed, but only in
4491 } else if (strchr(" \"'", *bp_)) {
4492 /* but anything not being a punctiation
4495 break; /* anything else is rejected */
4502 /* scan forward (should start with an alnum) */
4503 for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
4519 #undef IS_ASCII_ALNUM
4520 #undef IS_RFC822_CHAR
4522 gchar *make_email_string(const gchar *bp, const gchar *ep)
4524 /* returns a mailto: URI; mailto: is also used to detect the
4525 * uri type later on in the button_pressed signal handler */
4530 tmp = g_strndup(bp, ep - bp);
4532 /* If there is a colon in the username part of the address,
4533 * we're dealing with an URI for some other protocol - do
4534 * not prefix with mailto: in such case. */
4535 colon = strchr(tmp, ':');
4536 at = strchr(tmp, '@');
4537 if (colon != NULL && at != NULL && colon < at) {
4540 result = g_strconcat("mailto:", tmp, NULL);
4547 gchar *make_http_string(const gchar *bp, const gchar *ep)
4549 /* returns an http: URI; */
4553 while (bp && *bp && g_ascii_isspace(*bp))
4555 tmp = g_strndup(bp, ep - bp);
4556 result = g_strconcat("http://", tmp, NULL);
4562 static gchar *mailcap_get_command_in_file(const gchar *path, const gchar *type, const gchar *file_to_open)
4564 FILE *fp = g_fopen(path, "rb");
4565 gchar buf[BUFFSIZE];
4566 gchar *result = NULL;
4569 while (fgets(buf, sizeof (buf), fp) != NULL) {
4570 gchar **parts = g_strsplit(buf, ";", 3);
4571 gchar *trimmed = parts[0];
4572 while (trimmed[0] == ' ' || trimmed[0] == '\t')
4574 while (trimmed[strlen(trimmed)-1] == ' ' || trimmed[strlen(trimmed)-1] == '\t')
4575 trimmed[strlen(trimmed)-1] = '\0';
4577 if (!strcmp(trimmed, type)) {
4578 gboolean needsterminal = FALSE;
4579 if (parts[2] && strstr(parts[2], "needsterminal")) {
4580 needsterminal = TRUE;
4582 if (parts[2] && strstr(parts[2], "test=")) {
4583 gchar *orig_testcmd = g_strdup(strstr(parts[2], "test=")+5);
4584 gchar *testcmd = orig_testcmd;
4585 if (strstr(testcmd,";"))
4586 *(strstr(testcmd,";")) = '\0';
4587 while (testcmd[0] == ' ' || testcmd[0] == '\t')
4589 while (testcmd[strlen(testcmd)-1] == '\n')
4590 testcmd[strlen(testcmd)-1] = '\0';
4591 while (testcmd[strlen(testcmd)-1] == '\r')
4592 testcmd[strlen(testcmd)-1] = '\0';
4593 while (testcmd[strlen(testcmd)-1] == ' ' || testcmd[strlen(testcmd)-1] == '\t')
4594 testcmd[strlen(testcmd)-1] = '\0';
4596 if (strstr(testcmd, "%s")) {
4597 gchar *tmp = g_strdup_printf(testcmd, file_to_open);
4598 gint res = system(tmp);
4600 g_free(orig_testcmd);
4607 gint res = system(testcmd);
4608 g_free(orig_testcmd);
4618 while (trimmed[0] == ' ' || trimmed[0] == '\t')
4620 while (trimmed[strlen(trimmed)-1] == '\n')
4621 trimmed[strlen(trimmed)-1] = '\0';
4622 while (trimmed[strlen(trimmed)-1] == '\r')
4623 trimmed[strlen(trimmed)-1] = '\0';
4624 while (trimmed[strlen(trimmed)-1] == ' ' || trimmed[strlen(trimmed)-1] == '\t')
4625 trimmed[strlen(trimmed)-1] = '\0';
4626 result = g_strdup(trimmed);
4629 /* if there are no single quotes around %s, add them.
4630 * '.*%s.*' is ok, as in display 'png:%s'
4632 if (strstr(result, "%s")
4633 && !(strstr(result, "'") < strstr(result,"%s") &&
4634 strstr(strstr(result,"%s"), "'"))) {
4635 gchar *start = g_strdup(result);
4636 gchar *end = g_strdup(strstr(result, "%s")+2);
4638 *strstr(start, "%s") = '\0';
4639 tmp = g_strconcat(start,"'%s'",end, NULL);
4645 if (needsterminal) {
4646 gchar *tmp = g_strdup_printf("xterm -e %s", result);
4657 gchar *mailcap_get_command_for_type(const gchar *type, const gchar *file_to_open)
4659 gchar *result = NULL;
4663 path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap", NULL);
4664 result = mailcap_get_command_in_file(path, type, file_to_open);
4668 result = mailcap_get_command_in_file("/etc/mailcap", type, file_to_open);
4672 void mailcap_update_default(const gchar *type, const gchar *command)
4674 gchar *path = NULL, *outpath = NULL;
4675 path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap", NULL);
4676 outpath = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap.new", NULL);
4677 FILE *fp = g_fopen(path, "rb");
4679 gchar buf[BUFFSIZE];
4680 gboolean err = FALSE;
4683 fp = g_fopen(path, "a");
4685 g_warning("failed to create file %s", path);
4690 fp = g_freopen(path, "rb", fp);
4692 g_warning("failed to reopen file %s", path);
4699 outfp = g_fopen(outpath, "wb");
4701 g_warning("failed to create file %s", outpath);
4707 while (fp && fgets(buf, sizeof (buf), fp) != NULL) {
4708 gchar **parts = g_strsplit(buf, ";", 3);
4709 gchar *trimmed = parts[0];
4710 while (trimmed[0] == ' ')
4712 while (trimmed[strlen(trimmed)-1] == ' ')
4713 trimmed[strlen(trimmed)-1] = '\0';
4715 if (!strcmp(trimmed, type)) {
4720 if(fputs(buf, outfp) == EOF) {
4727 if (fprintf(outfp, "%s; %s\n", type, command) < 0)
4733 if (fclose(outfp) == EOF)
4737 g_rename(outpath, path);
4743 gint copy_dir(const gchar *src, const gchar *dst)
4748 if ((dir = g_dir_open(src, 0, NULL)) == NULL) {
4749 g_warning("failed to open directory: %s", src);
4753 if (make_dir(dst) < 0)
4756 while ((name = g_dir_read_name(dir)) != NULL) {
4757 gchar *old_file, *new_file;
4758 old_file = g_strconcat(src, G_DIR_SEPARATOR_S, name, NULL);
4759 new_file = g_strconcat(dst, G_DIR_SEPARATOR_S, name, NULL);
4760 debug_print("copying: %s -> %s\n", old_file, new_file);
4761 if (g_file_test(old_file, G_FILE_TEST_IS_REGULAR)) {
4762 gint r = copy_file(old_file, new_file, TRUE);
4769 /* Windows has no symlinks. Or well, Vista seems to
4770 have something like this but the semantics might be
4771 different. Thus we don't use it under Windows. */
4772 else if (g_file_test(old_file, G_FILE_TEST_IS_SYMLINK)) {
4773 GError *error = NULL;
4775 gchar *target = g_file_read_link(old_file, &error);
4777 r = symlink(target, new_file);
4784 #endif /*G_OS_WIN32*/
4785 else if (g_file_test(old_file, G_FILE_TEST_IS_DIR)) {
4786 gint r = copy_dir(old_file, new_file);
4797 /* crude test to see if a file is an email. */
4798 gboolean file_is_email (const gchar *filename)
4804 if (filename == NULL)
4806 if ((fp = g_fopen(filename, "rb")) == NULL)
4808 while (i < 60 && score < 3
4809 && fgets(buffer, sizeof (buffer), fp) > 0) {
4810 if (!strncmp(buffer, "From:", strlen("From:")))
4812 else if (!strncmp(buffer, "Date:", strlen("Date:")))
4814 else if (!strncmp(buffer, "Message-ID:", strlen("Message-ID:")))
4816 else if (!strncmp(buffer, "Subject:", strlen("Subject:")))
4821 return (score >= 3);
4824 gboolean sc_g_list_bigger(GList *list, gint max)
4828 while (cur && i <= max+1) {
4835 gboolean sc_g_slist_bigger(GSList *list, gint max)
4839 while (cur && i <= max+1) {
4846 const gchar *daynames[] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL};
4847 const gchar *monthnames[] = {NULL, NULL, NULL, NULL, NULL, NULL,
4848 NULL, NULL, NULL, NULL, NULL, NULL};
4849 const gchar *s_daynames[] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL};
4850 const gchar *s_monthnames[] = {NULL, NULL, NULL, NULL, NULL, NULL,
4851 NULL, NULL, NULL, NULL, NULL, NULL};
4853 gint daynames_len[] = {0,0,0,0,0,0,0};
4854 gint monthnames_len[] = {0,0,0,0,0,0,
4856 gint s_daynames_len[] = {0,0,0,0,0,0,0};
4857 gint s_monthnames_len[] = {0,0,0,0,0,0,
4859 const gchar *s_am_up = NULL;
4860 const gchar *s_pm_up = NULL;
4861 const gchar *s_am_low = NULL;
4862 const gchar *s_pm_low = NULL;
4864 gint s_am_up_len = 0;
4865 gint s_pm_up_len = 0;
4866 gint s_am_low_len = 0;
4867 gint s_pm_low_len = 0;
4869 static gboolean time_names_init_done = FALSE;
4871 static void init_time_names(void)
4875 daynames[0] = C_("Complete day name for use by strftime", "Sunday");
4876 daynames[1] = C_("Complete day name for use by strftime", "Monday");
4877 daynames[2] = C_("Complete day name for use by strftime", "Tuesday");
4878 daynames[3] = C_("Complete day name for use by strftime", "Wednesday");
4879 daynames[4] = C_("Complete day name for use by strftime", "Thursday");
4880 daynames[5] = C_("Complete day name for use by strftime", "Friday");
4881 daynames[6] = C_("Complete day name for use by strftime", "Saturday");
4883 monthnames[0] = C_("Complete month name for use by strftime", "January");
4884 monthnames[1] = C_("Complete month name for use by strftime", "February");
4885 monthnames[2] = C_("Complete month name for use by strftime", "March");
4886 monthnames[3] = C_("Complete month name for use by strftime", "April");
4887 monthnames[4] = C_("Complete month name for use by strftime", "May");
4888 monthnames[5] = C_("Complete month name for use by strftime", "June");
4889 monthnames[6] = C_("Complete month name for use by strftime", "July");
4890 monthnames[7] = C_("Complete month name for use by strftime", "August");
4891 monthnames[8] = C_("Complete month name for use by strftime", "September");
4892 monthnames[9] = C_("Complete month name for use by strftime", "October");
4893 monthnames[10] = C_("Complete month name for use by strftime", "November");
4894 monthnames[11] = C_("Complete month name for use by strftime", "December");
4896 s_daynames[0] = C_("Abbr. day name for use by strftime", "Sun");
4897 s_daynames[1] = C_("Abbr. day name for use by strftime", "Mon");
4898 s_daynames[2] = C_("Abbr. day name for use by strftime", "Tue");
4899 s_daynames[3] = C_("Abbr. day name for use by strftime", "Wed");
4900 s_daynames[4] = C_("Abbr. day name for use by strftime", "Thu");
4901 s_daynames[5] = C_("Abbr. day name for use by strftime", "Fri");
4902 s_daynames[6] = C_("Abbr. day name for use by strftime", "Sat");
4904 s_monthnames[0] = C_("Abbr. month name for use by strftime", "Jan");
4905 s_monthnames[1] = C_("Abbr. month name for use by strftime", "Feb");
4906 s_monthnames[2] = C_("Abbr. month name for use by strftime", "Mar");
4907 s_monthnames[3] = C_("Abbr. month name for use by strftime", "Apr");
4908 s_monthnames[4] = C_("Abbr. month name for use by strftime", "May");
4909 s_monthnames[5] = C_("Abbr. month name for use by strftime", "Jun");
4910 s_monthnames[6] = C_("Abbr. month name for use by strftime", "Jul");
4911 s_monthnames[7] = C_("Abbr. month name for use by strftime", "Aug");
4912 s_monthnames[8] = C_("Abbr. month name for use by strftime", "Sep");
4913 s_monthnames[9] = C_("Abbr. month name for use by strftime", "Oct");
4914 s_monthnames[10] = C_("Abbr. month name for use by strftime", "Nov");
4915 s_monthnames[11] = C_("Abbr. month name for use by strftime", "Dec");
4917 for (i = 0; i < 7; i++) {
4918 daynames_len[i] = strlen(daynames[i]);
4919 s_daynames_len[i] = strlen(s_daynames[i]);
4921 for (i = 0; i < 12; i++) {
4922 monthnames_len[i] = strlen(monthnames[i]);
4923 s_monthnames_len[i] = strlen(s_monthnames[i]);
4926 s_am_up = C_("For use by strftime (morning)", "AM");
4927 s_pm_up = C_("For use by strftime (afternoon)", "PM");
4928 s_am_low = C_("For use by strftime (morning, lowercase)", "am");
4929 s_pm_low = C_("For use by strftime (afternoon, lowercase)", "pm");
4931 s_am_up_len = strlen(s_am_up);
4932 s_pm_up_len = strlen(s_pm_up);
4933 s_am_low_len = strlen(s_am_low);
4934 s_pm_low_len = strlen(s_pm_low);
4936 time_names_init_done = TRUE;
4939 #define CHECK_SIZE() { \
4940 total_done += len; \
4941 if (total_done >= buflen) { \
4942 buf[buflen-1] = '\0'; \
4947 size_t fast_strftime(gchar *buf, gint buflen, const gchar *format, struct tm *lt)
4949 gchar *curpos = buf;
4950 gint total_done = 0;
4951 gchar subbuf[64], subfmt[64];
4952 static time_t last_tzset = (time_t)0;
4954 if (!time_names_init_done)
4957 if (format == NULL || lt == NULL)
4960 if (last_tzset != time(NULL)) {
4962 last_tzset = time(NULL);
4965 if (*format == '%') {
4966 gint len = 0, tmp = 0;
4970 len = 1; CHECK_SIZE();
4974 len = s_daynames_len[lt->tm_wday]; CHECK_SIZE();
4975 strncpy2(curpos, s_daynames[lt->tm_wday], buflen - total_done);
4978 len = daynames_len[lt->tm_wday]; CHECK_SIZE();
4979 strncpy2(curpos, daynames[lt->tm_wday], buflen - total_done);
4983 len = s_monthnames_len[lt->tm_mon]; CHECK_SIZE();
4984 strncpy2(curpos, s_monthnames[lt->tm_mon], buflen - total_done);
4987 len = monthnames_len[lt->tm_mon]; CHECK_SIZE();
4988 strncpy2(curpos, monthnames[lt->tm_mon], buflen - total_done);
4991 strftime(subbuf, 64, "%c", lt);
4992 len = strlen(subbuf); CHECK_SIZE();
4993 strncpy2(curpos, subbuf, buflen - total_done);
4996 total_done += 2; CHECK_SIZE();
4997 tmp = (lt->tm_year + 1900)/100;
4998 *curpos++ = '0'+(tmp / 10);
4999 *curpos++ = '0'+(tmp % 10);
5002 total_done += 2; CHECK_SIZE();
5003 *curpos++ = '0'+(lt->tm_mday / 10);
5004 *curpos++ = '0'+(lt->tm_mday % 10);
5007 total_done += 8; CHECK_SIZE();
5008 *curpos++ = '0'+((lt->tm_mon+1) / 10);
5009 *curpos++ = '0'+((lt->tm_mon+1) % 10);
5011 *curpos++ = '0'+(lt->tm_mday / 10);
5012 *curpos++ = '0'+(lt->tm_mday % 10);
5014 tmp = lt->tm_year%100;
5015 *curpos++ = '0'+(tmp / 10);
5016 *curpos++ = '0'+(tmp % 10);
5019 len = 2; CHECK_SIZE();
5020 snprintf(curpos, buflen - total_done, "%2d", lt->tm_mday);
5023 len = 10; CHECK_SIZE();
5024 snprintf(curpos, buflen - total_done, "%4d-%02d-%02d",
5025 lt->tm_year + 1900, lt->tm_mon +1, lt->tm_mday);
5028 total_done += 2; CHECK_SIZE();
5029 *curpos++ = '0'+(lt->tm_hour / 10);
5030 *curpos++ = '0'+(lt->tm_hour % 10);
5033 total_done += 2; CHECK_SIZE();
5039 *curpos++ = '0'+(tmp / 10);
5040 *curpos++ = '0'+(tmp % 10);
5043 len = 3; CHECK_SIZE();
5044 snprintf(curpos, buflen - total_done, "%03d", lt->tm_yday+1);
5047 len = 2; CHECK_SIZE();
5048 snprintf(curpos, buflen - total_done, "%2d", lt->tm_hour);
5051 len = 2; CHECK_SIZE();
5057 snprintf(curpos, buflen - total_done, "%2d", tmp);
5060 total_done += 2; CHECK_SIZE();
5061 tmp = lt->tm_mon + 1;
5062 *curpos++ = '0'+(tmp / 10);
5063 *curpos++ = '0'+(tmp % 10);
5066 total_done += 2; CHECK_SIZE();
5067 *curpos++ = '0'+(lt->tm_min / 10);
5068 *curpos++ = '0'+(lt->tm_min % 10);
5071 len = 1; CHECK_SIZE();
5075 if (lt->tm_hour >= 12) {
5076 len = s_pm_up_len; CHECK_SIZE();
5077 snprintf(curpos, buflen-total_done, "%s", s_pm_up);
5079 len = s_am_up_len; CHECK_SIZE();
5080 snprintf(curpos, buflen-total_done, "%s", s_am_up);
5084 if (lt->tm_hour >= 12) {
5085 len = s_pm_low_len; CHECK_SIZE();
5086 snprintf(curpos, buflen-total_done, "%s", s_pm_low);
5088 len = s_am_low_len; CHECK_SIZE();
5089 snprintf(curpos, buflen-total_done, "%s", s_am_low);
5093 strftime(subbuf, 64, "%r", lt);
5094 len = strlen(subbuf); CHECK_SIZE();
5095 strncpy2(curpos, subbuf, buflen - total_done);
5098 total_done += 5; CHECK_SIZE();
5099 *curpos++ = '0'+(lt->tm_hour / 10);
5100 *curpos++ = '0'+(lt->tm_hour % 10);
5102 *curpos++ = '0'+(lt->tm_min / 10);
5103 *curpos++ = '0'+(lt->tm_min % 10);
5106 snprintf(subbuf, 64, "%lld", (long long)mktime(lt));
5107 len = strlen(subbuf); CHECK_SIZE();
5108 strncpy2(curpos, subbuf, buflen - total_done);
5111 total_done += 2; CHECK_SIZE();
5112 *curpos++ = '0'+(lt->tm_sec / 10);
5113 *curpos++ = '0'+(lt->tm_sec % 10);
5116 len = 1; CHECK_SIZE();
5120 total_done += 8; CHECK_SIZE();
5121 *curpos++ = '0'+(lt->tm_hour / 10);
5122 *curpos++ = '0'+(lt->tm_hour % 10);
5124 *curpos++ = '0'+(lt->tm_min / 10);
5125 *curpos++ = '0'+(lt->tm_min % 10);
5127 *curpos++ = '0'+(lt->tm_sec / 10);
5128 *curpos++ = '0'+(lt->tm_sec % 10);
5131 len = 1; CHECK_SIZE();
5132 snprintf(curpos, buflen - total_done, "%d", lt->tm_wday == 0 ? 7: lt->tm_wday);
5135 len = 1; CHECK_SIZE();
5136 snprintf(curpos, buflen - total_done, "%d", lt->tm_wday);
5139 strftime(subbuf, 64, "%x", lt);
5140 len = strlen(subbuf); CHECK_SIZE();
5141 strncpy2(curpos, subbuf, buflen - total_done);
5144 strftime(subbuf, 64, "%X", lt);
5145 len = strlen(subbuf); CHECK_SIZE();
5146 strncpy2(curpos, subbuf, buflen - total_done);
5149 total_done += 2; CHECK_SIZE();
5150 tmp = lt->tm_year%100;
5151 *curpos++ = '0'+(tmp / 10);
5152 *curpos++ = '0'+(tmp % 10);
5155 len = 4; CHECK_SIZE();
5156 snprintf(curpos, buflen - total_done, "%4d", lt->tm_year + 1900);
5166 /* let these complicated ones be done with the libc */
5167 snprintf(subfmt, 64, "%%%c", *format);
5168 strftime(subbuf, 64, subfmt, lt);
5169 len = strlen(subbuf); CHECK_SIZE();
5170 strncpy2(curpos, subbuf, buflen - total_done);
5174 /* let these complicated modifiers be done with the libc */
5175 snprintf(subfmt, 64, "%%%c%c", *format, *(format+1));
5176 strftime(subbuf, 64, subfmt, lt);
5177 len = strlen(subbuf); CHECK_SIZE();
5178 strncpy2(curpos, subbuf, buflen - total_done);
5182 g_warning("format error (%c)", *format);
5189 int len = 1; CHECK_SIZE();
5190 *curpos++ = *format++;
5197 gboolean prefs_common_get_use_shred(void);
5201 #define WEXITSTATUS(x) (x)
5204 int claws_unlink(const gchar *filename)
5207 static int found_shred = -1;
5208 static const gchar *args[4];
5210 if (filename == NULL)
5213 if (prefs_common_get_use_shred()) {
5214 if (found_shred == -1) {
5216 args[0] = g_find_program_in_path("shred");
5217 debug_print("found shred: %s\n", args[0]);
5218 found_shred = (args[0] != NULL) ? 1:0;
5222 if (found_shred == 1) {
5223 if (g_stat(filename, &s) == 0 && S_ISREG(s.st_mode)) {
5224 if (s.st_nlink == 1) {
5227 g_spawn_sync(NULL, (gchar **)args, NULL, 0,
5228 NULL, NULL, NULL, NULL, &status, NULL);
5229 debug_print("%s %s exited with status %d\n",
5230 args[0], filename, WEXITSTATUS(status));
5231 if (truncate(filename, 0) < 0)
5232 g_warning("couln't truncate: %s", filename);
5237 return g_unlink(filename);
5240 GMutex *cm_mutex_new(void) {
5241 #if GLIB_CHECK_VERSION(2,32,0)
5242 GMutex *m = g_new0(GMutex, 1);
5246 return g_mutex_new();
5250 void cm_mutex_free(GMutex *mutex) {
5251 #if GLIB_CHECK_VERSION(2,32,0)
5252 g_mutex_clear(mutex);
5255 g_mutex_free(mutex);
5259 static gchar *canonical_list_to_file(GSList *list)
5261 GString *result = g_string_new(NULL);
5262 GSList *pathlist = g_slist_reverse(g_slist_copy(list));
5267 result = g_string_append(result, G_DIR_SEPARATOR_S);
5269 if (pathlist->data) {
5270 const gchar *root = (gchar *)pathlist->data;
5271 if (root[0] != '\0' && g_ascii_isalpha(root[0]) &&
5273 /* drive - don't prepend dir separator */
5275 result = g_string_append(result, G_DIR_SEPARATOR_S);
5280 for (cur = pathlist; cur; cur = cur->next) {
5281 result = g_string_append(result, (gchar *)cur->data);
5283 result = g_string_append(result, G_DIR_SEPARATOR_S);
5285 g_slist_free(pathlist);
5288 g_string_free(result, FALSE);
5293 static GSList *cm_split_path(const gchar *filename, int depth)
5296 GSList *canonical_parts = NULL;
5299 gboolean follow_symlinks = TRUE;
5305 errno = EINVAL; /* can't happen, no symlink handling */
5310 if (!g_path_is_absolute(filename)) {
5315 path_parts = g_strsplit(filename, G_DIR_SEPARATOR_S, -1);
5317 for (i = 0; path_parts[i] != NULL; i++) {
5318 if (!strcmp(path_parts[i], ""))
5320 if (!strcmp(path_parts[i], "."))
5322 else if (!strcmp(path_parts[i], "..")) {
5327 else /* Remove the last inserted element */
5329 g_slist_delete_link(canonical_parts,
5334 canonical_parts = g_slist_prepend(canonical_parts,
5335 g_strdup(path_parts[i]));
5337 tmp_path = canonical_list_to_file(canonical_parts);
5339 if(g_stat(tmp_path, &st) < 0) {
5340 if (errno == ENOENT) {
5342 follow_symlinks = FALSE;
5346 slist_free_strings_full(canonical_parts);
5347 g_strfreev(path_parts);
5353 if (follow_symlinks && g_file_test(tmp_path, G_FILE_TEST_IS_SYMLINK)) {
5354 GError *error = NULL;
5355 gchar *target = g_file_read_link(tmp_path, &error);
5357 if (!g_path_is_absolute(target)) {
5358 /* remove the last inserted element */
5360 g_slist_delete_link(canonical_parts,
5362 /* add the target */
5363 canonical_parts = g_slist_prepend(canonical_parts,
5367 /* and get the new target */
5368 target = canonical_list_to_file(canonical_parts);
5371 /* restart from absolute target */
5372 slist_free_strings_full(canonical_parts);
5373 canonical_parts = NULL;
5375 canonical_parts = cm_split_path(target, depth + 1);
5377 g_error_free(error);
5378 if (canonical_parts == NULL) {
5380 g_strfreev(path_parts);
5389 g_strfreev(path_parts);
5390 return canonical_parts;
5394 * Canonicalize a filename, resolving symlinks along the way.
5395 * Returns a negative errno in case of error.
5397 int cm_canonicalize_filename(const gchar *filename, gchar **canonical_name) {
5398 GSList *canonical_parts;
5399 gboolean is_absolute;
5401 if (filename == NULL)
5403 if (canonical_name == NULL)
5405 *canonical_name = NULL;
5407 is_absolute = g_path_is_absolute(filename);
5409 /* Always work on absolute filenames. */
5410 gchar *cur = g_get_current_dir();
5411 gchar *absolute_filename = g_strconcat(cur, G_DIR_SEPARATOR_S,
5414 canonical_parts = cm_split_path(absolute_filename, 0);
5415 g_free(absolute_filename);
5418 canonical_parts = cm_split_path(filename, 0);
5420 if (canonical_parts == NULL)
5423 *canonical_name = canonical_list_to_file(canonical_parts);
5424 slist_free_strings_full(canonical_parts);
5428 /* Returns a decoded base64 string, guaranteed to be null-terminated. */
5429 guchar *g_base64_decode_zero(const gchar *text, gsize *out_len)
5431 gchar *tmp = g_base64_decode(text, out_len);
5432 gchar *out = g_strndup(tmp, *out_len);
5436 if (strlen(out) != *out_len) {
5437 g_warning ("strlen(out) %zd != *out_len %" G_GSIZE_FORMAT, strlen(out), *out_len);
5443 #if !GLIB_CHECK_VERSION(2, 30, 0)
5446 * @str: a UTF-8 encoded string
5447 * @start_pos: a character offset within @str
5448 * @end_pos: another character offset within @str
5450 * Copies a substring out of a UTF-8 encoded string.
5451 * The substring will contain @end_pos - @start_pos
5454 * Returns: a newly allocated copy of the requested
5455 * substring. Free with g_free() when no longer needed.
5460 g_utf8_substring (const gchar *str,
5464 gchar *start, *end, *out;
5466 start = g_utf8_offset_to_pointer (str, start_pos);
5467 end = g_utf8_offset_to_pointer (start, end_pos - start_pos);
5469 out = g_malloc (end - start + 1);
5470 memcpy (out, start, end - start);
5471 out[end - start] = 0;