2 * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2015 Hiroyuki Yamamoto & The Claws Mail Team
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 * The code of the g_utf8_substring function below is owned by
19 * Matthias Clasen <matthiasc@src.gnome.org>/<mclasen@redhat.com>
20 * and is got from GLIB 2.30: https://git.gnome.org/browse/glib/commit/
21 * ?h=glib-2-30&id=9eb65dd3ed5e1a9638595cbe10699c7606376511
23 * GLib 2.30 is licensed under GPL v2 or later and:
24 * Copyright (C) 1999 Tom Tromey
25 * Copyright (C) 2000 Red Hat, Inc.
27 * https://git.gnome.org/browse/glib/tree/glib/gutf8.c
28 * ?h=glib-2-30&id=9eb65dd3ed5e1a9638595cbe10699c7606376511
33 #include "claws-features.h"
41 #include <glib/gi18n.h>
51 #include <sys/param.h>
53 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
61 #include <sys/types.h>
63 # include <sys/wait.h>
70 #include <sys/utsname.h>
83 #include "../codeconv.h"
87 static gboolean debug_mode = FALSE;
89 static GSList *tempfiles=NULL;
92 /* Return true if we are running as root. This function should beused
93 instead of getuid () == 0. */
94 gboolean superuser_p (void)
97 return w32_is_administrator ();
105 #if !defined(G_OS_UNIX)
106 gint g_chdir(const gchar *path)
109 if (G_WIN32_HAVE_WIDECHAR_API()) {
114 wpath = g_utf8_to_utf16(path, -1, NULL, NULL, NULL);
120 retval = _wchdir(wpath);
132 cp_path = g_locale_from_utf8(path, -1, NULL, NULL, NULL);
133 if (cp_path == NULL) {
138 retval = chdir(cp_path);
151 gint g_chmod(const gchar *path, gint mode)
154 if (G_WIN32_HAVE_WIDECHAR_API()) {
159 wpath = g_utf8_to_utf16(path, -1, NULL, NULL, NULL);
165 retval = _wchmod(wpath, mode);
177 cp_path = g_locale_from_utf8(path, -1, NULL, NULL, NULL);
178 if (cp_path == NULL) {
183 retval = chmod(cp_path, mode);
192 return chmod(path, mode);
196 FILE* g_fopen(const gchar *filename, const gchar *mode)
199 char *name = g_win32_locale_filename_from_utf8(filename);
200 FILE* fp = fopen(name, mode);
204 return fopen(filename, mode);
207 int g_open(const gchar *filename, int flags, int mode)
210 char *name = g_win32_locale_filename_from_utf8(filename);
211 int fd = open(name, flags, mode);
215 return open(filename, flags, mode);
218 #endif /* G_OS_UNIX */
221 GSList *slist_copy_deep(GSList *list, GCopyFunc func)
223 #if GLIB_CHECK_VERSION(2, 34, 0)
224 return g_slist_copy_deep(list, func, NULL);
226 GSList *res = g_slist_copy(list);
229 walk->data = func(walk->data, NULL);
236 void list_free_strings(GList *list)
238 list = g_list_first(list);
240 while (list != NULL) {
246 void slist_free_strings(GSList *list)
248 while (list != NULL) {
254 void slist_free_strings_full(GSList *list)
256 #if GLIB_CHECK_VERSION(2,28,0)
257 g_slist_free_full(list, (GDestroyNotify)g_free);
259 g_slist_foreach(list, (GFunc)g_free, NULL);
264 static void hash_free_strings_func(gpointer key, gpointer value, gpointer data)
269 void hash_free_strings(GHashTable *table)
271 g_hash_table_foreach(table, hash_free_strings_func, NULL);
274 gint str_case_equal(gconstpointer v, gconstpointer v2)
276 return g_ascii_strcasecmp((const gchar *)v, (const gchar *)v2) == 0;
279 guint str_case_hash(gconstpointer key)
281 const gchar *p = key;
285 h = g_ascii_tolower(h);
286 for (p += 1; *p != '\0'; p++)
287 h = (h << 5) - h + g_ascii_tolower(*p);
293 void ptr_array_free_strings(GPtrArray *array)
298 cm_return_if_fail(array != NULL);
300 for (i = 0; i < array->len; i++) {
301 str = g_ptr_array_index(array, i);
306 gint to_number(const gchar *nstr)
308 register const gchar *p;
310 if (*nstr == '\0') return -1;
312 for (p = nstr; *p != '\0'; p++)
313 if (!g_ascii_isdigit(*p)) return -1;
318 /* convert integer into string,
319 nstr must be not lower than 11 characters length */
320 gchar *itos_buf(gchar *nstr, gint n)
322 g_snprintf(nstr, 11, "%d", n);
326 /* convert integer into string */
329 static gchar nstr[11];
331 return itos_buf(nstr, n);
334 #define divide(num,divisor,i,d) \
336 i = num >> divisor; \
337 d = num & ((1<<divisor)-1); \
338 d = (d*100) >> divisor; \
343 * \brief Convert a given size in bytes in a human-readable string
345 * \param size The size expressed in bytes to convert in string
346 * \return The string that respresents the size in an human-readable way
348 gchar *to_human_readable(goffset size)
350 static gchar str[14];
351 static gchar *b_format = NULL, *kb_format = NULL,
352 *mb_format = NULL, *gb_format = NULL;
353 register int t = 0, r = 0;
354 if (b_format == NULL) {
356 kb_format = _("%d.%02dKB");
357 mb_format = _("%d.%02dMB");
358 gb_format = _("%.2fGB");
361 if (size < (goffset)1024) {
362 g_snprintf(str, sizeof(str), b_format, (gint)size);
364 } else if (size >> 10 < (goffset)1024) {
365 divide(size, 10, t, r);
366 g_snprintf(str, sizeof(str), kb_format, t, r);
368 } else if (size >> 20 < (goffset)1024) {
369 divide(size, 20, t, r);
370 g_snprintf(str, sizeof(str), mb_format, t, r);
373 g_snprintf(str, sizeof(str), gb_format, (gfloat)(size >> 30));
378 /* strcmp with NULL-checking */
379 gint strcmp2(const gchar *s1, const gchar *s2)
381 if (s1 == NULL || s2 == NULL)
384 return strcmp(s1, s2);
386 /* strstr with NULL-checking */
387 gchar *strstr2(const gchar *s1, const gchar *s2)
389 if (s1 == NULL || s2 == NULL)
392 return strstr(s1, s2);
395 gint path_cmp(const gchar *s1, const gchar *s2)
400 gchar *s1buf, *s2buf;
403 if (s1 == NULL || s2 == NULL) return -1;
404 if (*s1 == '\0' || *s2 == '\0') return -1;
407 s1buf = g_strdup (s1);
408 s2buf = g_strdup (s2);
409 subst_char (s1buf, '/', G_DIR_SEPARATOR);
410 subst_char (s2buf, '/', G_DIR_SEPARATOR);
413 #endif /* !G_OS_WIN32 */
418 if (s1[len1 - 1] == G_DIR_SEPARATOR) len1--;
419 if (s2[len2 - 1] == G_DIR_SEPARATOR) len2--;
421 rc = strncmp(s1, s2, MAX(len1, len2));
425 #endif /* !G_OS_WIN32 */
429 /* remove trailing return code */
430 gchar *strretchomp(gchar *str)
434 if (!*str) return str;
436 for (s = str + strlen(str) - 1;
437 s >= str && (*s == '\n' || *s == '\r');
444 /* remove trailing character */
445 gchar *strtailchomp(gchar *str, gchar tail_char)
449 if (!*str) return str;
450 if (tail_char == '\0') return str;
452 for (s = str + strlen(str) - 1; s >= str && *s == tail_char; s--)
458 /* remove CR (carriage return) */
459 gchar *strcrchomp(gchar *str)
463 if (!*str) return str;
465 s = str + strlen(str) - 1;
466 if (*s == '\n' && s > str && *(s - 1) == '\r') {
474 gint file_strip_crs(const gchar *file)
476 FILE *fp = NULL, *outfp = NULL;
478 gchar *out = get_tmp_file();
482 fp = g_fopen(file, "rb");
486 outfp = g_fopen(out, "wb");
492 while (fgets(buf, sizeof (buf), fp) != NULL) {
494 if (fputs(buf, outfp) == EOF) {
502 if (fclose(outfp) == EOF) {
506 if (move_file(out, file, TRUE) < 0)
518 /* Similar to `strstr' but this function ignores the case of both strings. */
519 gchar *strcasestr(const gchar *haystack, const gchar *needle)
521 size_t haystack_len = strlen(haystack);
523 return strncasestr(haystack, haystack_len, needle);
526 gchar *strncasestr(const gchar *haystack, gint haystack_len, const gchar *needle)
528 register size_t needle_len;
530 needle_len = strlen(needle);
532 if (haystack_len < needle_len || needle_len == 0)
535 while (haystack_len >= needle_len) {
536 if (!g_ascii_strncasecmp(haystack, needle, needle_len))
537 return (gchar *)haystack;
547 gpointer my_memmem(gconstpointer haystack, size_t haystacklen,
548 gconstpointer needle, size_t needlelen)
550 const gchar *haystack_ = (const gchar *)haystack;
551 const gchar *needle_ = (const gchar *)needle;
552 const gchar *haystack_cur = (const gchar *)haystack;
553 size_t haystack_left = haystacklen;
556 return memchr(haystack_, *needle_, haystacklen);
558 while ((haystack_cur = memchr(haystack_cur, *needle_, haystack_left))
560 if (haystacklen - (haystack_cur - haystack_) < needlelen)
562 if (memcmp(haystack_cur + 1, needle_ + 1, needlelen - 1) == 0)
563 return (gpointer)haystack_cur;
566 haystack_left = haystacklen - (haystack_cur - haystack_);
573 /* Copy no more than N characters of SRC to DEST, with NULL terminating. */
574 gchar *strncpy2(gchar *dest, const gchar *src, size_t n)
576 register const gchar *s = src;
577 register gchar *d = dest;
587 /* Examine if next block is non-ASCII string */
588 gboolean is_next_nonascii(const gchar *s)
592 /* skip head space */
593 for (p = s; *p != '\0' && g_ascii_isspace(*p); p++)
595 for (; *p != '\0' && !g_ascii_isspace(*p); p++) {
596 if (*(guchar *)p > 127 || *(guchar *)p < 32)
603 gint get_next_word_len(const gchar *s)
607 for (; *s != '\0' && !g_ascii_isspace(*s); s++, len++)
613 static void trim_subject_for_compare(gchar *str)
617 eliminate_parenthesis(str, '[', ']');
618 eliminate_parenthesis(str, '(', ')');
621 srcp = str + subject_get_prefix_length(str);
623 memmove(str, srcp, strlen(srcp) + 1);
626 static void trim_subject_for_sort(gchar *str)
632 srcp = str + subject_get_prefix_length(str);
634 memmove(str, srcp, strlen(srcp) + 1);
637 /* compare subjects */
638 gint subject_compare(const gchar *s1, const gchar *s2)
642 if (!s1 || !s2) return -1;
643 if (!*s1 || !*s2) return -1;
645 Xstrdup_a(str1, s1, return -1);
646 Xstrdup_a(str2, s2, return -1);
648 trim_subject_for_compare(str1);
649 trim_subject_for_compare(str2);
651 if (!*str1 || !*str2) return -1;
653 return strcmp(str1, str2);
656 gint subject_compare_for_sort(const gchar *s1, const gchar *s2)
660 if (!s1 || !s2) return -1;
662 Xstrdup_a(str1, s1, return -1);
663 Xstrdup_a(str2, s2, return -1);
665 trim_subject_for_sort(str1);
666 trim_subject_for_sort(str2);
668 return g_utf8_collate(str1, str2);
671 void trim_subject(gchar *str)
673 register gchar *srcp;
679 srcp = str + subject_get_prefix_length(str);
684 } else if (*srcp == '(') {
696 else if (*srcp == cl)
703 while (g_ascii_isspace(*srcp)) srcp++;
704 memmove(str, srcp, strlen(srcp) + 1);
707 void eliminate_parenthesis(gchar *str, gchar op, gchar cl)
709 register gchar *srcp, *destp;
714 while ((destp = strchr(destp, op))) {
720 else if (*srcp == cl)
726 while (g_ascii_isspace(*srcp)) srcp++;
727 memmove(destp, srcp, strlen(srcp) + 1);
731 void extract_parenthesis(gchar *str, gchar op, gchar cl)
733 register gchar *srcp, *destp;
738 while ((srcp = strchr(destp, op))) {
741 memmove(destp, srcp + 1, strlen(srcp));
746 else if (*destp == cl)
758 static void extract_parenthesis_with_skip_quote(gchar *str, gchar quote_chr,
761 register gchar *srcp, *destp;
763 gboolean in_quote = FALSE;
767 while ((srcp = strchr_with_skip_quote(destp, quote_chr, op))) {
770 memmove(destp, srcp + 1, strlen(srcp));
773 if (*destp == op && !in_quote)
775 else if (*destp == cl && !in_quote)
777 else if (*destp == quote_chr)
789 void extract_quote(gchar *str, gchar quote_chr)
793 if ((str = strchr(str, quote_chr))) {
795 while ((p = strchr(p + 1, quote_chr)) && (p[-1] == '\\')) {
796 memmove(p - 1, p, strlen(p) + 1);
801 memmove(str, str + 1, p - str);
806 /* Returns a newly allocated string with all quote_chr not at the beginning
807 or the end of str escaped with '\' or the given str if not required. */
808 gchar *escape_internal_quotes(gchar *str, gchar quote_chr)
810 register gchar *p, *q;
814 if (str == NULL || *str == '\0')
817 /* search for unescaped quote_chr */
822 if (*p == quote_chr && *(p - 1) != '\\' && *(p + 1) != '\0')
826 if (!k) /* nothing to escape */
829 /* unescaped quote_chr found */
830 qstr = g_malloc(l + k + 1);
833 if (*p == quote_chr) {
838 if (*p == quote_chr && *(p - 1) != '\\' && *(p + 1) != '\0')
847 void eliminate_address_comment(gchar *str)
849 register gchar *srcp, *destp;
854 while ((destp = strchr(destp, '"'))) {
855 if ((srcp = strchr(destp + 1, '"'))) {
860 while (g_ascii_isspace(*srcp)) srcp++;
861 memmove(destp, srcp, strlen(srcp) + 1);
871 while ((destp = strchr_with_skip_quote(destp, '"', '('))) {
877 else if (*srcp == ')')
883 while (g_ascii_isspace(*srcp)) srcp++;
884 memmove(destp, srcp, strlen(srcp) + 1);
888 gchar *strchr_with_skip_quote(const gchar *str, gint quote_chr, gint c)
890 gboolean in_quote = FALSE;
893 if (*str == c && !in_quote)
895 if (*str == quote_chr)
903 void extract_address(gchar *str)
905 cm_return_if_fail(str != NULL);
906 eliminate_address_comment(str);
907 if (strchr_with_skip_quote(str, '"', '<'))
908 extract_parenthesis_with_skip_quote(str, '"', '<', '>');
912 void extract_list_id_str(gchar *str)
914 if (strchr_with_skip_quote(str, '"', '<'))
915 extract_parenthesis_with_skip_quote(str, '"', '<', '>');
919 static GSList *address_list_append_real(GSList *addr_list, const gchar *str, gboolean removecomments)
924 if (!str) return addr_list;
926 Xstrdup_a(work, str, return addr_list);
929 eliminate_address_comment(work);
932 while (workp && *workp) {
935 if ((p = strchr_with_skip_quote(workp, '"', ','))) {
941 if (removecomments && strchr_with_skip_quote(workp, '"', '<'))
942 extract_parenthesis_with_skip_quote
943 (workp, '"', '<', '>');
947 addr_list = g_slist_append(addr_list, g_strdup(workp));
955 GSList *address_list_append(GSList *addr_list, const gchar *str)
957 return address_list_append_real(addr_list, str, TRUE);
960 GSList *address_list_append_with_comments(GSList *addr_list, const gchar *str)
962 return address_list_append_real(addr_list, str, FALSE);
965 GSList *references_list_prepend(GSList *msgid_list, const gchar *str)
969 if (!str) return msgid_list;
972 while (strp && *strp) {
973 const gchar *start, *end;
976 if ((start = strchr(strp, '<')) != NULL) {
977 end = strchr(start + 1, '>');
982 msgid = g_strndup(start + 1, end - start - 1);
985 msgid_list = g_slist_prepend(msgid_list, msgid);
995 GSList *references_list_append(GSList *msgid_list, const gchar *str)
999 list = references_list_prepend(NULL, str);
1000 list = g_slist_reverse(list);
1001 msgid_list = g_slist_concat(msgid_list, list);
1006 GSList *newsgroup_list_append(GSList *group_list, const gchar *str)
1011 if (!str) return group_list;
1013 Xstrdup_a(work, str, return group_list);
1017 while (workp && *workp) {
1020 if ((p = strchr_with_skip_quote(workp, '"', ','))) {
1028 group_list = g_slist_append(group_list,
1037 GList *add_history(GList *list, const gchar *str)
1042 cm_return_val_if_fail(str != NULL, list);
1044 old = g_list_find_custom(list, (gpointer)str, (GCompareFunc)strcmp2);
1047 list = g_list_remove(list, old->data);
1049 } else if (g_list_length(list) >= MAX_HISTORY_SIZE) {
1052 last = g_list_last(list);
1054 oldstr = last->data;
1055 list = g_list_remove(list, last->data);
1060 list = g_list_prepend(list, g_strdup(str));
1065 void remove_return(gchar *str)
1067 register gchar *p = str;
1070 if (*p == '\n' || *p == '\r')
1071 memmove(p, p + 1, strlen(p));
1077 void remove_space(gchar *str)
1079 register gchar *p = str;
1084 while (g_ascii_isspace(*(p + spc)))
1087 memmove(p, p + spc, strlen(p + spc) + 1);
1093 void unfold_line(gchar *str)
1095 register gchar *p = str;
1099 if (*p == '\n' || *p == '\r') {
1102 while (g_ascii_isspace(*(p + spc)))
1105 memmove(p, p + spc, strlen(p + spc) + 1);
1111 void subst_char(gchar *str, gchar orig, gchar subst)
1113 register gchar *p = str;
1122 void subst_chars(gchar *str, gchar *orig, gchar subst)
1124 register gchar *p = str;
1127 if (strchr(orig, *p) != NULL)
1133 void subst_for_filename(gchar *str)
1138 subst_chars(str, "\t\r\n\\/*:", '_');
1140 subst_chars(str, "\t\r\n\\/*", '_');
1144 void subst_for_shellsafe_filename(gchar *str)
1148 subst_for_filename(str);
1149 subst_chars(str, " \"'|&;()<>'!{}[]",'_');
1152 gboolean is_ascii_str(const gchar *str)
1154 const guchar *p = (const guchar *)str;
1156 while (*p != '\0') {
1157 if (*p != '\t' && *p != ' ' &&
1158 *p != '\r' && *p != '\n' &&
1159 (*p < 32 || *p >= 127))
1167 static const gchar * line_has_quote_char_last(const gchar * str, const gchar *quote_chars)
1169 gchar * position = NULL;
1170 gchar * tmp_pos = NULL;
1173 if (quote_chars == NULL)
1176 for (i = 0; i < strlen(quote_chars); i++) {
1177 tmp_pos = strrchr (str, quote_chars[i]);
1179 || (tmp_pos != NULL && position <= tmp_pos) )
1185 gint get_quote_level(const gchar *str, const gchar *quote_chars)
1187 const gchar *first_pos;
1188 const gchar *last_pos;
1189 const gchar *p = str;
1190 gint quote_level = -1;
1192 /* speed up line processing by only searching to the last '>' */
1193 if ((first_pos = line_has_quote_char(str, quote_chars)) != NULL) {
1194 /* skip a line if it contains a '<' before the initial '>' */
1195 if (memchr(str, '<', first_pos - str) != NULL)
1197 last_pos = line_has_quote_char_last(first_pos, quote_chars);
1201 while (p <= last_pos) {
1202 while (p < last_pos) {
1203 if (g_ascii_isspace(*p))
1209 if (strchr(quote_chars, *p))
1211 else if (*p != '-' && !g_ascii_isspace(*p) && p <= last_pos) {
1212 /* any characters are allowed except '-','<' and space */
1213 while (*p != '-' && *p != '<'
1214 && !strchr(quote_chars, *p)
1215 && !g_ascii_isspace(*p)
1218 if (strchr(quote_chars, *p))
1230 gint check_line_length(const gchar *str, gint max_chars, gint *line)
1232 const gchar *p = str, *q;
1233 gint cur_line = 0, len;
1235 while ((q = strchr(p, '\n')) != NULL) {
1237 if (len > max_chars) {
1247 if (len > max_chars) {
1256 const gchar * line_has_quote_char(const gchar * str, const gchar *quote_chars)
1258 gchar * position = NULL;
1259 gchar * tmp_pos = NULL;
1262 if (quote_chars == NULL)
1265 for (i = 0; i < strlen(quote_chars); i++) {
1266 tmp_pos = strchr (str, quote_chars[i]);
1268 || (tmp_pos != NULL && position >= tmp_pos) )
1274 static gchar *strstr_with_skip_quote(const gchar *haystack, const gchar *needle)
1276 register guint haystack_len, needle_len;
1277 gboolean in_squote = FALSE, in_dquote = FALSE;
1279 haystack_len = strlen(haystack);
1280 needle_len = strlen(needle);
1282 if (haystack_len < needle_len || needle_len == 0)
1285 while (haystack_len >= needle_len) {
1286 if (!in_squote && !in_dquote &&
1287 !strncmp(haystack, needle, needle_len))
1288 return (gchar *)haystack;
1290 /* 'foo"bar"' -> foo"bar"
1291 "foo'bar'" -> foo'bar' */
1292 if (*haystack == '\'') {
1295 else if (!in_dquote)
1297 } else if (*haystack == '\"') {
1300 else if (!in_squote)
1302 } else if (*haystack == '\\') {
1314 gchar **strsplit_with_quote(const gchar *str, const gchar *delim,
1317 GSList *string_list = NULL, *slist;
1318 gchar **str_array, *s, *new_str;
1319 guint i, n = 1, len;
1321 cm_return_val_if_fail(str != NULL, NULL);
1322 cm_return_val_if_fail(delim != NULL, NULL);
1325 max_tokens = G_MAXINT;
1327 s = strstr_with_skip_quote(str, delim);
1329 guint delimiter_len = strlen(delim);
1333 new_str = g_strndup(str, len);
1335 if (new_str[0] == '\'' || new_str[0] == '\"') {
1336 if (new_str[len - 1] == new_str[0]) {
1337 new_str[len - 1] = '\0';
1338 memmove(new_str, new_str + 1, len - 1);
1341 string_list = g_slist_prepend(string_list, new_str);
1343 str = s + delimiter_len;
1344 s = strstr_with_skip_quote(str, delim);
1345 } while (--max_tokens && s);
1349 new_str = g_strdup(str);
1350 if (new_str[0] == '\'' || new_str[0] == '\"') {
1352 if (new_str[len - 1] == new_str[0]) {
1353 new_str[len - 1] = '\0';
1354 memmove(new_str, new_str + 1, len - 1);
1357 string_list = g_slist_prepend(string_list, new_str);
1361 str_array = g_new(gchar*, n);
1365 str_array[i--] = NULL;
1366 for (slist = string_list; slist; slist = slist->next)
1367 str_array[i--] = slist->data;
1369 g_slist_free(string_list);
1374 gchar *get_abbrev_newsgroup_name(const gchar *group, gint len)
1376 gchar *abbrev_group;
1378 const gchar *p = group;
1381 cm_return_val_if_fail(group != NULL, NULL);
1383 last = group + strlen(group);
1384 abbrev_group = ap = g_malloc(strlen(group) + 1);
1389 if ((ap - abbrev_group) + (last - p) > len && strchr(p, '.')) {
1391 while (*p != '.') p++;
1394 return abbrev_group;
1399 return abbrev_group;
1402 gchar *trim_string(const gchar *str, gint len)
1404 const gchar *p = str;
1409 if (!str) return NULL;
1410 if (strlen(str) <= len)
1411 return g_strdup(str);
1412 if (g_utf8_validate(str, -1, NULL) == FALSE)
1413 return g_strdup(str);
1415 while (*p != '\0') {
1416 mb_len = g_utf8_skip[*(guchar *)p];
1419 else if (new_len + mb_len > len)
1426 Xstrndup_a(new_str, str, new_len, return g_strdup(str));
1427 return g_strconcat(new_str, "...", NULL);
1430 GList *uri_list_extract_filenames(const gchar *uri_list)
1432 GList *result = NULL;
1434 gchar *escaped_utf8uri;
1440 while (g_ascii_isspace(*p)) p++;
1441 if (!strncmp(p, "file:", 5)) {
1444 while (*q && *q != '\n' && *q != '\r') q++;
1447 gchar *file, *locale_file = NULL;
1449 while (q > p && g_ascii_isspace(*q))
1451 Xalloca(escaped_utf8uri, q - p + 2,
1453 Xalloca(file, q - p + 2,
1456 strncpy(escaped_utf8uri, p, q - p + 1);
1457 escaped_utf8uri[q - p + 1] = '\0';
1458 decode_uri(file, escaped_utf8uri);
1460 * g_filename_from_uri() rejects escaped/locale encoded uri
1461 * string which come from Nautilus.
1464 if (g_utf8_validate(file, -1, NULL))
1466 = conv_codeset_strdup(
1469 conv_get_locale_charset_str());
1471 locale_file = g_strdup(file + 5);
1473 locale_file = g_filename_from_uri(file, NULL, NULL);
1475 result = g_list_append(result, locale_file);
1479 p = strchr(p, '\n');
1486 /* Converts two-digit hexadecimal to decimal. Used for unescaping escaped
1489 static gint axtoi(const gchar *hexstr)
1491 gint hi, lo, result;
1494 if ('0' <= hi && hi <= '9') {
1497 if ('a' <= hi && hi <= 'f') {
1500 if ('A' <= hi && hi <= 'F') {
1505 if ('0' <= lo && lo <= '9') {
1508 if ('a' <= lo && lo <= 'f') {
1511 if ('A' <= lo && lo <= 'F') {
1514 result = lo + (16 * hi);
1518 gboolean is_uri_string(const gchar *str)
1520 while (str && *str && g_ascii_isspace(*str))
1522 return (g_ascii_strncasecmp(str, "http://", 7) == 0 ||
1523 g_ascii_strncasecmp(str, "https://", 8) == 0 ||
1524 g_ascii_strncasecmp(str, "ftp://", 6) == 0 ||
1525 g_ascii_strncasecmp(str, "www.", 4) == 0);
1528 gchar *get_uri_path(const gchar *uri)
1530 while (uri && *uri && g_ascii_isspace(*uri))
1532 if (g_ascii_strncasecmp(uri, "http://", 7) == 0)
1533 return (gchar *)(uri + 7);
1534 else if (g_ascii_strncasecmp(uri, "https://", 8) == 0)
1535 return (gchar *)(uri + 8);
1536 else if (g_ascii_strncasecmp(uri, "ftp://", 6) == 0)
1537 return (gchar *)(uri + 6);
1539 return (gchar *)uri;
1542 gint get_uri_len(const gchar *str)
1546 if (is_uri_string(str)) {
1547 for (p = str; *p != '\0'; p++) {
1548 if (!g_ascii_isgraph(*p) || strchr("()<>\"", *p))
1557 /* Decodes URL-Encoded strings (i.e. strings in which spaces are replaced by
1558 * plusses, and escape characters are used)
1560 void decode_uri_with_plus(gchar *decoded_uri, const gchar *encoded_uri, gboolean with_plus)
1562 gchar *dec = decoded_uri;
1563 const gchar *enc = encoded_uri;
1568 if (isxdigit((guchar)enc[0]) &&
1569 isxdigit((guchar)enc[1])) {
1575 if (with_plus && *enc == '+')
1587 void decode_uri(gchar *decoded_uri, const gchar *encoded_uri)
1589 decode_uri_with_plus(decoded_uri, encoded_uri, TRUE);
1592 static gchar *decode_uri_gdup(const gchar *encoded_uri)
1594 gchar *buffer = g_malloc(strlen(encoded_uri)+1);
1595 decode_uri_with_plus(buffer, encoded_uri, FALSE);
1599 gint scan_mailto_url(const gchar *mailto, gchar **from, gchar **to, gchar **cc, gchar **bcc,
1600 gchar **subject, gchar **body, gchar ***attach, gchar **inreplyto)
1604 const gchar *forbidden_uris[] = { ".gnupg/",
1610 gint num_attach = 0;
1611 gchar **my_att = NULL;
1613 Xstrdup_a(tmp_mailto, mailto, return -1);
1615 if (!strncmp(tmp_mailto, "mailto:", 7))
1618 p = strchr(tmp_mailto, '?');
1625 *to = decode_uri_gdup(tmp_mailto);
1627 my_att = g_malloc(sizeof(char *));
1631 gchar *field, *value;
1648 if (*value == '\0') continue;
1650 if (from && !g_ascii_strcasecmp(field, "from")) {
1652 *from = decode_uri_gdup(value);
1654 gchar *tmp = decode_uri_gdup(value);
1655 gchar *new_from = g_strdup_printf("%s, %s", *from, tmp);
1659 } else if (cc && !g_ascii_strcasecmp(field, "cc")) {
1661 *cc = decode_uri_gdup(value);
1663 gchar *tmp = decode_uri_gdup(value);
1664 gchar *new_cc = g_strdup_printf("%s, %s", *cc, tmp);
1668 } else if (bcc && !g_ascii_strcasecmp(field, "bcc")) {
1670 *bcc = decode_uri_gdup(value);
1672 gchar *tmp = decode_uri_gdup(value);
1673 gchar *new_bcc = g_strdup_printf("%s, %s", *bcc, tmp);
1677 } else if (subject && !*subject &&
1678 !g_ascii_strcasecmp(field, "subject")) {
1679 *subject = decode_uri_gdup(value);
1680 } else if (body && !*body && !g_ascii_strcasecmp(field, "body")) {
1681 *body = decode_uri_gdup(value);
1682 } else if (body && !*body && !g_ascii_strcasecmp(field, "insert")) {
1683 gchar *tmp = decode_uri_gdup(value);
1684 if (!g_file_get_contents(tmp, body, NULL, NULL)) {
1685 g_warning("couldn't set insert file '%s' in body", value);
1689 } else if (attach && !g_ascii_strcasecmp(field, "attach")) {
1691 gchar *tmp = decode_uri_gdup(value);
1692 for (; forbidden_uris[i]; i++) {
1693 if (strstr(tmp, forbidden_uris[i])) {
1694 g_print("Refusing to attach '%s', potential private data leak\n",
1702 /* attach is correct */
1704 my_att = g_realloc(my_att, (sizeof(char *))*(num_attach+1));
1705 my_att[num_attach-1] = tmp;
1706 my_att[num_attach] = NULL;
1708 } else if (inreplyto && !*inreplyto &&
1709 !g_ascii_strcasecmp(field, "in-reply-to")) {
1710 *inreplyto = decode_uri_gdup(value);
1721 #include <windows.h>
1722 #ifndef CSIDL_APPDATA
1723 #define CSIDL_APPDATA 0x001a
1725 #ifndef CSIDL_LOCAL_APPDATA
1726 #define CSIDL_LOCAL_APPDATA 0x001c
1728 #ifndef CSIDL_FLAG_CREATE
1729 #define CSIDL_FLAG_CREATE 0x8000
1731 #define DIM(v) (sizeof(v)/sizeof((v)[0]))
1735 w32_strerror (int w32_errno)
1737 static char strerr[256];
1738 int ec = (int)GetLastError ();
1742 FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM, NULL, w32_errno,
1743 MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
1744 strerr, DIM (strerr)-1, NULL);
1748 static __inline__ void *
1749 dlopen (const char * name, int flag)
1751 void * hd = LoadLibrary (name);
1755 static __inline__ void *
1756 dlsym (void * hd, const char * sym)
1760 void * fnc = GetProcAddress (hd, sym);
1769 static __inline__ const char *
1772 return w32_strerror (0);
1776 static __inline__ int
1788 w32_shgetfolderpath (HWND a, int b, HANDLE c, DWORD d, LPSTR e)
1790 static int initialized;
1791 static HRESULT (WINAPI * func)(HWND,int,HANDLE,DWORD,LPSTR);
1795 static char *dllnames[] = { "shell32.dll", "shfolder.dll", NULL };
1801 for (i=0, handle = NULL; !handle && dllnames[i]; i++)
1803 handle = dlopen (dllnames[i], RTLD_LAZY);
1806 func = dlsym (handle, "SHGetFolderPathW");
1817 return func (a,b,c,d,e);
1822 /* Returns a static string with the directroy from which the module
1823 has been loaded. Returns an empty string on error. */
1824 static char *w32_get_module_dir(void)
1826 static char *moddir;
1829 char name[MAX_PATH+10];
1832 if ( !GetModuleFileNameA (0, name, sizeof (name)-10) )
1835 p = strrchr (name, '\\');
1841 moddir = g_strdup (name);
1845 #endif /* G_OS_WIN32 */
1847 /* Return a static string with the locale dir. */
1848 const gchar *get_locale_dir(void)
1850 static gchar *loc_dir;
1854 loc_dir = g_strconcat(w32_get_module_dir(), G_DIR_SEPARATOR_S,
1855 "\\share\\locale", NULL);
1858 loc_dir = LOCALEDIR;
1864 const gchar *get_home_dir(void)
1867 static char home_dir_utf16[MAX_PATH] = "";
1868 static gchar *home_dir_utf8 = NULL;
1869 if (home_dir_utf16[0] == '\0') {
1870 if (w32_shgetfolderpath
1871 (NULL, CSIDL_APPDATA|CSIDL_FLAG_CREATE,
1872 NULL, 0, home_dir_utf16) < 0)
1873 strcpy (home_dir_utf16, "C:\\Sylpheed");
1874 home_dir_utf8 = g_utf16_to_utf8 ((const gunichar *)home_dir_utf16, -1, NULL, NULL, NULL);
1876 return home_dir_utf8;
1878 static const gchar *homeenv = NULL;
1883 if (!homeenv && g_getenv("HOME") != NULL)
1884 homeenv = g_strdup(g_getenv("HOME"));
1886 homeenv = g_get_home_dir();
1892 static gchar *claws_rc_dir = NULL;
1893 static gboolean rc_dir_alt = FALSE;
1894 const gchar *get_rc_dir(void)
1897 if (!claws_rc_dir) {
1898 claws_rc_dir = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
1900 debug_print("using default rc_dir %s\n", claws_rc_dir);
1902 return claws_rc_dir;
1905 void set_rc_dir(const gchar *dir)
1907 gchar *canonical_dir;
1908 if (claws_rc_dir != NULL) {
1909 g_print("Error: rc_dir already set\n");
1911 int err = cm_canonicalize_filename(dir, &canonical_dir);
1915 g_print("Error looking for %s: %d(%s)\n",
1916 dir, -err, g_strerror(-err));
1921 claws_rc_dir = canonical_dir;
1923 len = strlen(claws_rc_dir);
1924 if (claws_rc_dir[len - 1] == G_DIR_SEPARATOR)
1925 claws_rc_dir[len - 1] = '\0';
1927 debug_print("set rc_dir to %s\n", claws_rc_dir);
1928 if (!is_dir_exist(claws_rc_dir)) {
1929 if (make_dir_hier(claws_rc_dir) != 0) {
1930 g_print("Error: can't create %s\n",
1938 gboolean rc_dir_is_alt(void) {
1942 const gchar *get_mail_base_dir(void)
1944 return get_home_dir();
1947 const gchar *get_news_cache_dir(void)
1949 static gchar *news_cache_dir = NULL;
1950 if (!news_cache_dir)
1951 news_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1952 NEWS_CACHE_DIR, NULL);
1954 return news_cache_dir;
1957 const gchar *get_imap_cache_dir(void)
1959 static gchar *imap_cache_dir = NULL;
1961 if (!imap_cache_dir)
1962 imap_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1963 IMAP_CACHE_DIR, NULL);
1965 return imap_cache_dir;
1968 const gchar *get_mime_tmp_dir(void)
1970 static gchar *mime_tmp_dir = NULL;
1973 mime_tmp_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1974 MIME_TMP_DIR, NULL);
1976 return mime_tmp_dir;
1979 const gchar *get_template_dir(void)
1981 static gchar *template_dir = NULL;
1984 template_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1985 TEMPLATE_DIR, NULL);
1987 return template_dir;
1991 const gchar *get_cert_file(void)
1993 const gchar *cert_file = NULL;
1995 cert_file = g_strconcat(w32_get_module_dir(),
1996 "\\share\\claws-mail\\",
1997 "ca-certificates.crt",
2003 /* Return the filepath of the claws-mail.desktop file */
2004 const gchar *get_desktop_file(void)
2006 #ifdef DESKTOPFILEPATH
2007 return DESKTOPFILEPATH;
2013 /* Return the default directory for Plugins. */
2014 const gchar *get_plugin_dir(void)
2017 static gchar *plugin_dir = NULL;
2020 plugin_dir = g_strconcat(w32_get_module_dir(),
2021 "\\lib\\claws-mail\\plugins\\",
2025 if (is_dir_exist(PLUGINDIR))
2028 static gchar *plugin_dir = NULL;
2030 plugin_dir = g_strconcat(get_rc_dir(),
2031 G_DIR_SEPARATOR_S, "plugins",
2032 G_DIR_SEPARATOR_S, NULL);
2040 /* Return the default directory for Themes. */
2041 const gchar *get_themes_dir(void)
2043 static gchar *themes_dir = NULL;
2046 themes_dir = g_strconcat(w32_get_module_dir(),
2047 "\\share\\claws-mail\\themes",
2053 const gchar *get_tmp_dir(void)
2055 static gchar *tmp_dir = NULL;
2058 tmp_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
2064 gchar *get_tmp_file(void)
2067 static guint32 id = 0;
2069 tmp_file = g_strdup_printf("%s%ctmpfile.%08x",
2070 get_tmp_dir(), G_DIR_SEPARATOR, id++);
2075 const gchar *get_domain_name(void)
2078 static gchar *domain_name = NULL;
2084 if (gethostname(hostname, sizeof(hostname)) != 0) {
2085 perror("gethostname");
2086 domain_name = "localhost";
2088 hostname[sizeof(hostname) - 1] = '\0';
2089 if ((hp = my_gethostbyname(hostname)) == NULL) {
2090 perror("gethostbyname");
2091 domain_name = g_strdup(hostname);
2093 domain_name = g_strdup(hp->h_name);
2096 debug_print("domain name = %s\n", domain_name);
2105 off_t get_file_size(const gchar *file)
2109 if (g_stat(file, &s) < 0) {
2110 FILE_OP_ERROR(file, "stat");
2117 time_t get_file_mtime(const gchar *file)
2121 if (g_stat(file, &s) < 0) {
2122 FILE_OP_ERROR(file, "stat");
2129 off_t get_file_size_as_crlf(const gchar *file)
2133 gchar buf[BUFFSIZE];
2135 if ((fp = g_fopen(file, "rb")) == NULL) {
2136 FILE_OP_ERROR(file, "g_fopen");
2140 while (fgets(buf, sizeof(buf), fp) != NULL) {
2142 size += strlen(buf) + 2;
2146 FILE_OP_ERROR(file, "fgets");
2155 gboolean file_exist(const gchar *file, gboolean allow_fifo)
2162 if (g_stat(file, &s) < 0) {
2163 if (ENOENT != errno) FILE_OP_ERROR(file, "stat");
2167 if (S_ISREG(s.st_mode) || (allow_fifo && S_ISFIFO(s.st_mode)))
2174 /* Test on whether FILE is a relative file name. This is
2175 * straightforward for Unix but more complex for Windows. */
2176 gboolean is_relative_filename(const gchar *file)
2181 if ( *file == '\\' && file[1] == '\\' && strchr (file+2, '\\') )
2182 return FALSE; /* Prefixed with a hostname - this can't
2183 * be a relative name. */
2185 if ( ((*file >= 'a' && *file <= 'z')
2186 || (*file >= 'A' && *file <= 'Z'))
2188 file += 2; /* Skip drive letter. */
2190 return !(*file == '\\' || *file == '/');
2192 return !(*file == G_DIR_SEPARATOR);
2197 gboolean is_dir_exist(const gchar *dir)
2202 return g_file_test(dir, G_FILE_TEST_IS_DIR);
2205 gboolean is_file_entry_exist(const gchar *file)
2210 return g_file_test(file, G_FILE_TEST_EXISTS);
2213 gboolean dirent_is_regular_file(struct dirent *d)
2215 #if !defined(G_OS_WIN32) && defined(HAVE_DIRENT_D_TYPE)
2216 if (d->d_type == DT_REG)
2218 else if (d->d_type != DT_UNKNOWN)
2222 return g_file_test(d->d_name, G_FILE_TEST_IS_REGULAR);
2225 gint change_dir(const gchar *dir)
2227 gchar *prevdir = NULL;
2230 prevdir = g_get_current_dir();
2232 if (g_chdir(dir) < 0) {
2233 FILE_OP_ERROR(dir, "chdir");
2234 if (debug_mode) g_free(prevdir);
2236 } else if (debug_mode) {
2239 cwd = g_get_current_dir();
2240 if (strcmp(prevdir, cwd) != 0)
2241 g_print("current dir: %s\n", cwd);
2249 gint make_dir(const gchar *dir)
2251 if (g_mkdir(dir, S_IRWXU) < 0) {
2252 FILE_OP_ERROR(dir, "mkdir");
2255 if (g_chmod(dir, S_IRWXU) < 0)
2256 FILE_OP_ERROR(dir, "chmod");
2261 gint make_dir_hier(const gchar *dir)
2266 for (p = dir; (p = strchr(p, G_DIR_SEPARATOR)) != NULL; p++) {
2267 parent_dir = g_strndup(dir, p - dir);
2268 if (*parent_dir != '\0') {
2269 if (!is_dir_exist(parent_dir)) {
2270 if (make_dir(parent_dir) < 0) {
2279 if (!is_dir_exist(dir)) {
2280 if (make_dir(dir) < 0)
2287 gint remove_all_files(const gchar *dir)
2290 const gchar *dir_name;
2293 prev_dir = g_get_current_dir();
2295 if (g_chdir(dir) < 0) {
2296 FILE_OP_ERROR(dir, "chdir");
2301 if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2302 g_warning("failed to open directory: %s", dir);
2307 while ((dir_name = g_dir_read_name(dp)) != NULL) {
2308 if (claws_unlink(dir_name) < 0)
2309 FILE_OP_ERROR(dir_name, "unlink");
2314 if (g_chdir(prev_dir) < 0) {
2315 FILE_OP_ERROR(prev_dir, "chdir");
2325 gint remove_numbered_files(const gchar *dir, guint first, guint last)
2328 const gchar *dir_name;
2332 if (first == last) {
2333 /* Skip all the dir reading part. */
2334 gchar *filename = g_strdup_printf("%s%s%u", dir, G_DIR_SEPARATOR_S, first);
2335 if (is_dir_exist(filename)) {
2336 /* a numbered directory with this name exists,
2337 * remove the dot-file instead */
2339 filename = g_strdup_printf("%s%s.%u", dir, G_DIR_SEPARATOR_S, first);
2341 if (claws_unlink(filename) < 0) {
2342 FILE_OP_ERROR(filename, "unlink");
2350 prev_dir = g_get_current_dir();
2352 if (g_chdir(dir) < 0) {
2353 FILE_OP_ERROR(dir, "chdir");
2358 if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2359 g_warning("failed to open directory: %s", dir);
2364 while ((dir_name = g_dir_read_name(dp)) != NULL) {
2365 file_no = to_number(dir_name);
2366 if (file_no > 0 && first <= file_no && file_no <= last) {
2367 if (is_dir_exist(dir_name)) {
2368 gchar *dot_file = g_strdup_printf(".%s", dir_name);
2369 if (is_file_exist(dot_file) && claws_unlink(dot_file) < 0) {
2370 FILE_OP_ERROR(dot_file, "unlink");
2375 if (claws_unlink(dir_name) < 0)
2376 FILE_OP_ERROR(dir_name, "unlink");
2382 if (g_chdir(prev_dir) < 0) {
2383 FILE_OP_ERROR(prev_dir, "chdir");
2393 gint remove_numbered_files_not_in_list(const gchar *dir, GSList *numberlist)
2396 const gchar *dir_name;
2399 GHashTable *wanted_files;
2401 GError *error = NULL;
2403 if (numberlist == NULL)
2406 prev_dir = g_get_current_dir();
2408 if (g_chdir(dir) < 0) {
2409 FILE_OP_ERROR(dir, "chdir");
2414 if ((dp = g_dir_open(".", 0, &error)) == NULL) {
2415 g_message("Couldn't open current directory: %s (%d).\n",
2416 error->message, error->code);
2417 g_error_free(error);
2422 wanted_files = g_hash_table_new(g_direct_hash, g_direct_equal);
2423 for (cur = numberlist; cur != NULL; cur = cur->next) {
2424 /* numberlist->data is expected to be GINT_TO_POINTER */
2425 g_hash_table_insert(wanted_files, cur->data, GINT_TO_POINTER(1));
2428 while ((dir_name = g_dir_read_name(dp)) != NULL) {
2429 file_no = to_number(dir_name);
2430 if (is_dir_exist(dir_name))
2432 if (file_no > 0 && g_hash_table_lookup(wanted_files, GINT_TO_POINTER(file_no)) == NULL) {
2433 debug_print("removing unwanted file %d from %s\n", file_no, dir);
2434 if (is_dir_exist(dir_name)) {
2435 gchar *dot_file = g_strdup_printf(".%s", dir_name);
2436 if (is_file_exist(dot_file) && claws_unlink(dot_file) < 0) {
2437 FILE_OP_ERROR(dot_file, "unlink");
2442 if (claws_unlink(dir_name) < 0)
2443 FILE_OP_ERROR(dir_name, "unlink");
2448 g_hash_table_destroy(wanted_files);
2450 if (g_chdir(prev_dir) < 0) {
2451 FILE_OP_ERROR(prev_dir, "chdir");
2461 gint remove_all_numbered_files(const gchar *dir)
2463 return remove_numbered_files(dir, 0, UINT_MAX);
2466 gint remove_dir_recursive(const gchar *dir)
2470 const gchar *dir_name;
2473 if (g_stat(dir, &s) < 0) {
2474 FILE_OP_ERROR(dir, "stat");
2475 if (ENOENT == errno) return 0;
2479 if (!S_ISDIR(s.st_mode)) {
2480 if (claws_unlink(dir) < 0) {
2481 FILE_OP_ERROR(dir, "unlink");
2488 prev_dir = g_get_current_dir();
2489 /* g_print("prev_dir = %s\n", prev_dir); */
2491 if (!path_cmp(prev_dir, dir)) {
2493 if (g_chdir("..") < 0) {
2494 FILE_OP_ERROR(dir, "chdir");
2497 prev_dir = g_get_current_dir();
2500 if (g_chdir(dir) < 0) {
2501 FILE_OP_ERROR(dir, "chdir");
2506 if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2507 g_warning("failed to open directory: %s", dir);
2513 /* remove all files in the directory */
2514 while ((dir_name = g_dir_read_name(dp)) != NULL) {
2515 /* g_print("removing %s\n", dir_name); */
2517 if (is_dir_exist(dir_name)) {
2520 if ((ret = remove_dir_recursive(dir_name)) < 0) {
2521 g_warning("can't remove directory: %s", dir_name);
2525 if (claws_unlink(dir_name) < 0)
2526 FILE_OP_ERROR(dir_name, "unlink");
2532 if (g_chdir(prev_dir) < 0) {
2533 FILE_OP_ERROR(prev_dir, "chdir");
2540 if (g_rmdir(dir) < 0) {
2541 FILE_OP_ERROR(dir, "rmdir");
2548 gint rename_force(const gchar *oldpath, const gchar *newpath)
2551 if (!is_file_entry_exist(oldpath)) {
2555 if (is_file_exist(newpath)) {
2556 if (claws_unlink(newpath) < 0)
2557 FILE_OP_ERROR(newpath, "unlink");
2560 return g_rename(oldpath, newpath);
2564 * Append src file body to the tail of dest file.
2565 * Now keep_backup has no effects.
2567 gint append_file(const gchar *src, const gchar *dest, gboolean keep_backup)
2569 FILE *src_fp, *dest_fp;
2573 gboolean err = FALSE;
2575 if ((src_fp = g_fopen(src, "rb")) == NULL) {
2576 FILE_OP_ERROR(src, "g_fopen");
2580 if ((dest_fp = g_fopen(dest, "ab")) == NULL) {
2581 FILE_OP_ERROR(dest, "g_fopen");
2586 if (change_file_mode_rw(dest_fp, dest) < 0) {
2587 FILE_OP_ERROR(dest, "chmod");
2588 g_warning("can't change file mode: %s", dest);
2591 while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
2592 if (n_read < sizeof(buf) && ferror(src_fp))
2594 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2595 g_warning("writing to %s failed.", dest);
2603 if (ferror(src_fp)) {
2604 FILE_OP_ERROR(src, "fread");
2608 if (fclose(dest_fp) == EOF) {
2609 FILE_OP_ERROR(dest, "fclose");
2621 gint copy_file(const gchar *src, const gchar *dest, gboolean keep_backup)
2623 FILE *src_fp, *dest_fp;
2626 gchar *dest_bak = NULL;
2627 gboolean err = FALSE;
2629 if ((src_fp = g_fopen(src, "rb")) == NULL) {
2630 FILE_OP_ERROR(src, "g_fopen");
2633 if (is_file_exist(dest)) {
2634 dest_bak = g_strconcat(dest, ".bak", NULL);
2635 if (rename_force(dest, dest_bak) < 0) {
2636 FILE_OP_ERROR(dest, "rename");
2643 if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2644 FILE_OP_ERROR(dest, "g_fopen");
2647 if (rename_force(dest_bak, dest) < 0)
2648 FILE_OP_ERROR(dest_bak, "rename");
2654 if (change_file_mode_rw(dest_fp, dest) < 0) {
2655 FILE_OP_ERROR(dest, "chmod");
2656 g_warning("can't change file mode: %s", dest);
2659 while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
2660 if (n_read < sizeof(buf) && ferror(src_fp))
2662 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2663 g_warning("writing to %s failed.", dest);
2668 if (rename_force(dest_bak, dest) < 0)
2669 FILE_OP_ERROR(dest_bak, "rename");
2676 if (ferror(src_fp)) {
2677 FILE_OP_ERROR(src, "fread");
2681 if (fclose(dest_fp) == EOF) {
2682 FILE_OP_ERROR(dest, "fclose");
2689 if (rename_force(dest_bak, dest) < 0)
2690 FILE_OP_ERROR(dest_bak, "rename");
2696 if (keep_backup == FALSE && dest_bak)
2697 claws_unlink(dest_bak);
2704 gint move_file(const gchar *src, const gchar *dest, gboolean overwrite)
2706 if (overwrite == FALSE && is_file_exist(dest)) {
2707 g_warning("move_file(): file %s already exists.", dest);
2711 if (rename_force(src, dest) == 0) return 0;
2713 if (EXDEV != errno) {
2714 FILE_OP_ERROR(src, "rename");
2718 if (copy_file(src, dest, FALSE) < 0) return -1;
2725 gint copy_file_part_to_fp(FILE *fp, off_t offset, size_t length, FILE *dest_fp)
2728 gint bytes_left, to_read;
2731 if (fseek(fp, offset, SEEK_SET) < 0) {
2736 bytes_left = length;
2737 to_read = MIN(bytes_left, sizeof(buf));
2739 while ((n_read = fread(buf, sizeof(gchar), to_read, fp)) > 0) {
2740 if (n_read < to_read && ferror(fp))
2742 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2745 bytes_left -= n_read;
2746 if (bytes_left == 0)
2748 to_read = MIN(bytes_left, sizeof(buf));
2759 gint copy_file_part(FILE *fp, off_t offset, size_t length, const gchar *dest)
2762 gboolean err = FALSE;
2764 if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2765 FILE_OP_ERROR(dest, "g_fopen");
2769 if (change_file_mode_rw(dest_fp, dest) < 0) {
2770 FILE_OP_ERROR(dest, "chmod");
2771 g_warning("can't change file mode: %s", dest);
2774 if (copy_file_part_to_fp(fp, offset, length, dest_fp) < 0)
2777 if (!err && fclose(dest_fp) == EOF) {
2778 FILE_OP_ERROR(dest, "fclose");
2783 g_warning("writing to %s failed.", dest);
2791 /* convert line endings into CRLF. If the last line doesn't end with
2792 * linebreak, add it.
2794 gchar *canonicalize_str(const gchar *str)
2800 for (p = str; *p != '\0'; ++p) {
2807 if (p == str || *(p - 1) != '\n')
2810 out = outp = g_malloc(new_len + 1);
2811 for (p = str; *p != '\0'; ++p) {
2818 if (p == str || *(p - 1) != '\n') {
2827 gint canonicalize_file(const gchar *src, const gchar *dest)
2829 FILE *src_fp, *dest_fp;
2830 gchar buf[BUFFSIZE];
2832 gboolean err = FALSE;
2833 gboolean last_linebreak = FALSE;
2835 if (src == NULL || dest == NULL)
2838 if ((src_fp = g_fopen(src, "rb")) == NULL) {
2839 FILE_OP_ERROR(src, "g_fopen");
2843 if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2844 FILE_OP_ERROR(dest, "g_fopen");
2849 if (change_file_mode_rw(dest_fp, dest) < 0) {
2850 FILE_OP_ERROR(dest, "chmod");
2851 g_warning("can't change file mode: %s", dest);
2854 while (fgets(buf, sizeof(buf), src_fp) != NULL) {
2858 if (len == 0) break;
2859 last_linebreak = FALSE;
2861 if (buf[len - 1] != '\n') {
2862 last_linebreak = TRUE;
2863 r = fputs(buf, dest_fp);
2864 } else if (len > 1 && buf[len - 1] == '\n' && buf[len - 2] == '\r') {
2865 r = fputs(buf, dest_fp);
2868 r = fwrite(buf, 1, len - 1, dest_fp);
2873 r = fputs("\r\n", dest_fp);
2877 g_warning("writing to %s failed.", dest);
2885 if (last_linebreak == TRUE) {
2886 if (fputs("\r\n", dest_fp) == EOF)
2890 if (ferror(src_fp)) {
2891 FILE_OP_ERROR(src, "fgets");
2895 if (fclose(dest_fp) == EOF) {
2896 FILE_OP_ERROR(dest, "fclose");
2908 gint canonicalize_file_replace(const gchar *file)
2912 tmp_file = get_tmp_file();
2914 if (canonicalize_file(file, tmp_file) < 0) {
2919 if (move_file(tmp_file, file, TRUE) < 0) {
2920 g_warning("can't replace file: %s", file);
2921 claws_unlink(tmp_file);
2930 gchar *normalize_newlines(const gchar *str)
2935 out = outp = g_malloc(strlen(str) + 1);
2936 for (p = str; *p != '\0'; ++p) {
2938 if (*(p + 1) != '\n')
2949 gchar *get_outgoing_rfc2822_str(FILE *fp)
2951 gchar buf[BUFFSIZE];
2955 str = g_string_new(NULL);
2957 /* output header part */
2958 while (fgets(buf, sizeof(buf), fp) != NULL) {
2960 if (!g_ascii_strncasecmp(buf, "Bcc:", 4)) {
2967 else if (next != ' ' && next != '\t') {
2971 if (fgets(buf, sizeof(buf), fp) == NULL)
2975 g_string_append(str, buf);
2976 g_string_append(str, "\r\n");
2982 /* output body part */
2983 while (fgets(buf, sizeof(buf), fp) != NULL) {
2986 g_string_append_c(str, '.');
2987 g_string_append(str, buf);
2988 g_string_append(str, "\r\n");
2992 g_string_free(str, FALSE);
2998 * Create a new boundary in a way that it is very unlikely that this
2999 * will occur in the following text. It would be easy to ensure
3000 * uniqueness if everything is either quoted-printable or base64
3001 * encoded (note that conversion is allowed), but because MIME bodies
3002 * may be nested, it may happen that the same boundary has already
3005 * boundary := 0*69<bchars> bcharsnospace
3006 * bchars := bcharsnospace / " "
3007 * bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" /
3008 * "+" / "_" / "," / "-" / "." /
3009 * "/" / ":" / "=" / "?"
3011 * some special characters removed because of buggy MTAs
3014 gchar *generate_mime_boundary(const gchar *prefix)
3016 static gchar tbl[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
3017 "abcdefghijklmnopqrstuvwxyz"
3022 for (i = 0; i < sizeof(buf_uniq) - 1; i++)
3023 buf_uniq[i] = tbl[g_random_int_range(0, sizeof(tbl) - 1)];
3026 return g_strdup_printf("%s_/%s", prefix ? prefix : "MP",
3030 gint change_file_mode_rw(FILE *fp, const gchar *file)
3033 return fchmod(fileno(fp), S_IRUSR|S_IWUSR);
3035 return g_chmod(file, S_IRUSR|S_IWUSR);
3039 FILE *my_tmpfile(void)
3041 const gchar suffix[] = ".XXXXXX";
3042 const gchar *tmpdir;
3044 const gchar *progname;
3053 tmpdir = get_tmp_dir();
3054 tmplen = strlen(tmpdir);
3055 progname = g_get_prgname();
3056 if (progname == NULL)
3057 progname = "claws-mail";
3058 proglen = strlen(progname);
3059 Xalloca(fname, tmplen + 1 + proglen + sizeof(suffix),
3062 memcpy(fname, tmpdir, tmplen);
3063 fname[tmplen] = G_DIR_SEPARATOR;
3064 memcpy(fname + tmplen + 1, progname, proglen);
3065 memcpy(fname + tmplen + 1 + proglen, suffix, sizeof(suffix));
3067 fd = g_mkstemp(fname);
3072 claws_unlink(fname);
3074 /* verify that we can write in the file after unlinking */
3075 if (write(fd, buf, 1) < 0) {
3082 fp = fdopen(fd, "w+b");
3093 FILE *get_tmpfile_in_dir(const gchar *dir, gchar **filename)
3096 *filename = g_strdup_printf("%s%cclaws.XXXXXX", dir, G_DIR_SEPARATOR);
3097 fd = g_mkstemp(*filename);
3100 return fdopen(fd, "w+");
3103 FILE *str_open_as_stream(const gchar *str)
3108 cm_return_val_if_fail(str != NULL, NULL);
3112 FILE_OP_ERROR("str_open_as_stream", "my_tmpfile");
3117 if (len == 0) return fp;
3119 if (fwrite(str, 1, len, fp) != len) {
3120 FILE_OP_ERROR("str_open_as_stream", "fwrite");
3129 gint str_write_to_file(const gchar *str, const gchar *file)
3134 cm_return_val_if_fail(str != NULL, -1);
3135 cm_return_val_if_fail(file != NULL, -1);
3137 if ((fp = g_fopen(file, "wb")) == NULL) {
3138 FILE_OP_ERROR(file, "g_fopen");
3148 if (fwrite(str, 1, len, fp) != len) {
3149 FILE_OP_ERROR(file, "fwrite");
3155 if (fclose(fp) == EOF) {
3156 FILE_OP_ERROR(file, "fclose");
3164 static gchar *file_read_stream_to_str_full(FILE *fp, gboolean recode)
3171 cm_return_val_if_fail(fp != NULL, NULL);
3173 array = g_byte_array_new();
3175 while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
3176 if (n_read < sizeof(buf) && ferror(fp))
3178 g_byte_array_append(array, buf, n_read);
3182 FILE_OP_ERROR("file stream", "fread");
3183 g_byte_array_free(array, TRUE);
3188 g_byte_array_append(array, buf, 1);
3189 str = (gchar *)array->data;
3190 g_byte_array_free(array, FALSE);
3192 if (recode && !g_utf8_validate(str, -1, NULL)) {
3193 const gchar *src_codeset, *dest_codeset;
3195 src_codeset = conv_get_locale_charset_str();
3196 dest_codeset = CS_UTF_8;
3197 tmp = conv_codeset_strdup(str, src_codeset, dest_codeset);
3205 static gchar *file_read_to_str_full(const gchar *file, gboolean recode)
3212 struct timeval timeout = {1, 0};
3217 cm_return_val_if_fail(file != NULL, NULL);
3219 if (g_stat(file, &s) != 0) {
3220 FILE_OP_ERROR(file, "stat");
3223 if (S_ISDIR(s.st_mode)) {
3224 g_warning("%s: is a directory", file);
3229 fp = g_fopen (file, "rb");
3231 FILE_OP_ERROR(file, "open");
3235 /* test whether the file is readable without blocking */
3236 fd = g_open(file, O_RDONLY | O_NONBLOCK, 0);
3238 FILE_OP_ERROR(file, "open");
3245 /* allow for one second */
3246 err = select(fd+1, &fds, NULL, NULL, &timeout);
3247 if (err <= 0 || !FD_ISSET(fd, &fds)) {
3249 FILE_OP_ERROR(file, "select");
3251 g_warning("%s: doesn't seem readable", file);
3257 /* Now clear O_NONBLOCK */
3258 if ((fflags = fcntl(fd, F_GETFL)) < 0) {
3259 FILE_OP_ERROR(file, "fcntl (F_GETFL)");
3263 if (fcntl(fd, F_SETFL, (fflags & ~O_NONBLOCK)) < 0) {
3264 FILE_OP_ERROR(file, "fcntl (F_SETFL)");
3269 /* get the FILE pointer */
3270 fp = fdopen(fd, "rb");
3273 FILE_OP_ERROR(file, "fdopen");
3274 close(fd); /* if fp isn't NULL, we'll use fclose instead! */
3279 str = file_read_stream_to_str_full(fp, recode);
3286 gchar *file_read_to_str(const gchar *file)
3288 return file_read_to_str_full(file, TRUE);
3290 gchar *file_read_stream_to_str(FILE *fp)
3292 return file_read_stream_to_str_full(fp, TRUE);
3295 gchar *file_read_to_str_no_recode(const gchar *file)
3297 return file_read_to_str_full(file, FALSE);
3299 gchar *file_read_stream_to_str_no_recode(FILE *fp)
3301 return file_read_stream_to_str_full(fp, FALSE);
3304 char *fgets_crlf(char *buf, int size, FILE *stream)
3306 gboolean is_cr = FALSE;
3307 gboolean last_was_cr = FALSE;
3312 while (--size > 0 && (c = getc(stream)) != EOF)
3315 is_cr = (c == '\r');
3325 last_was_cr = is_cr;
3327 if (c == EOF && cs == buf)
3335 static gint execute_async(gchar *const argv[])
3337 cm_return_val_if_fail(argv != NULL && argv[0] != NULL, -1);
3339 if (g_spawn_async(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH,
3340 NULL, NULL, NULL, FALSE) == FALSE) {
3341 g_warning("couldn't execute command: %s", argv[0]);
3348 static gint execute_sync(gchar *const argv[])
3352 cm_return_val_if_fail(argv != NULL && argv[0] != NULL, -1);
3355 if (g_spawn_sync(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH,
3356 NULL, NULL, NULL, NULL, &status, NULL) == FALSE) {
3357 g_warning("couldn't execute command: %s", argv[0]);
3361 if (WIFEXITED(status))
3362 return WEXITSTATUS(status);
3366 if (g_spawn_sync(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH|
3367 G_SPAWN_CHILD_INHERITS_STDIN|G_SPAWN_LEAVE_DESCRIPTORS_OPEN,
3368 NULL, NULL, NULL, NULL, &status, NULL) == FALSE) {
3369 g_warning("couldn't execute command: %s", argv[0]);
3377 gint execute_command_line(const gchar *cmdline, gboolean async)
3382 debug_print("execute_command_line(): executing: %s\n", cmdline?cmdline:"(null)");
3384 argv = strsplit_with_quote(cmdline, " ", 0);
3387 ret = execute_async(argv);
3389 ret = execute_sync(argv);
3396 gchar *get_command_output(const gchar *cmdline)
3398 gchar *child_stdout;
3401 cm_return_val_if_fail(cmdline != NULL, NULL);
3403 debug_print("get_command_output(): executing: %s\n", cmdline);
3405 if (g_spawn_command_line_sync(cmdline, &child_stdout, NULL, &status,
3407 g_warning("couldn't execute command: %s", cmdline);
3411 return child_stdout;
3414 static gint is_unchanged_uri_char(char c)
3425 static void encode_uri(gchar *encoded_uri, gint bufsize, const gchar *uri)
3431 for(i = 0; i < strlen(uri) ; i++) {
3432 if (is_unchanged_uri_char(uri[i])) {
3433 if (k + 2 >= bufsize)
3435 encoded_uri[k++] = uri[i];
3438 char * hexa = "0123456789ABCDEF";
3440 if (k + 4 >= bufsize)
3442 encoded_uri[k++] = '%';
3443 encoded_uri[k++] = hexa[uri[i] / 16];
3444 encoded_uri[k++] = hexa[uri[i] % 16];
3450 gint open_uri(const gchar *uri, const gchar *cmdline)
3454 gchar buf[BUFFSIZE];
3456 gchar encoded_uri[BUFFSIZE];
3457 cm_return_val_if_fail(uri != NULL, -1);
3459 /* an option to choose whether to use encode_uri or not ? */
3460 encode_uri(encoded_uri, BUFFSIZE, uri);
3463 (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
3464 !strchr(p + 2, '%'))
3465 g_snprintf(buf, sizeof(buf), cmdline, encoded_uri);
3468 g_warning("Open URI command-line is invalid "
3469 "(there must be only one '%%s'): %s",
3471 g_snprintf(buf, sizeof(buf), DEFAULT_BROWSER_CMD, encoded_uri);
3474 execute_command_line(buf, TRUE);
3476 ShellExecute(NULL, "open", uri, NULL, NULL, SW_SHOW);
3481 gint open_txt_editor(const gchar *filepath, const gchar *cmdline)
3483 gchar buf[BUFFSIZE];
3486 cm_return_val_if_fail(filepath != NULL, -1);
3489 (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
3490 !strchr(p + 2, '%'))
3491 g_snprintf(buf, sizeof(buf), cmdline, filepath);
3494 g_warning("Open Text Editor command-line is invalid "
3495 "(there must be only one '%%s'): %s",
3497 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, filepath);
3500 execute_command_line(buf, TRUE);
3505 time_t remote_tzoffset_sec(const gchar *zone)
3507 static gchar ustzstr[] = "PSTPDTMSTMDTCSTCDTESTEDT";
3513 time_t remoteoffset;
3515 strncpy(zone3, zone, 3);
3519 if (sscanf(zone, "%c%d", &c, &offset) == 2 &&
3520 (c == '+' || c == '-')) {
3521 remoteoffset = ((offset / 100) * 60 + (offset % 100)) * 60;
3523 remoteoffset = -remoteoffset;
3524 } else if (!strncmp(zone, "UT" , 2) ||
3525 !strncmp(zone, "GMT", 3)) {
3527 } else if (strlen(zone3) == 3) {
3528 for (p = ustzstr; *p != '\0'; p += 3) {
3529 if (!g_ascii_strncasecmp(p, zone3, 3)) {
3530 iustz = ((gint)(p - ustzstr) / 3 + 1) / 2 - 8;
3531 remoteoffset = iustz * 3600;
3537 } else if (strlen(zone3) == 1) {
3539 case 'Z': remoteoffset = 0; break;
3540 case 'A': remoteoffset = -1; break;
3541 case 'B': remoteoffset = -2; break;
3542 case 'C': remoteoffset = -3; break;
3543 case 'D': remoteoffset = -4; break;
3544 case 'E': remoteoffset = -5; break;
3545 case 'F': remoteoffset = -6; break;
3546 case 'G': remoteoffset = -7; break;
3547 case 'H': remoteoffset = -8; break;
3548 case 'I': remoteoffset = -9; break;
3549 case 'K': remoteoffset = -10; break; /* J is not used */
3550 case 'L': remoteoffset = -11; break;
3551 case 'M': remoteoffset = -12; break;
3552 case 'N': remoteoffset = 1; break;
3553 case 'O': remoteoffset = 2; break;
3554 case 'P': remoteoffset = 3; break;
3555 case 'Q': remoteoffset = 4; break;
3556 case 'R': remoteoffset = 5; break;
3557 case 'S': remoteoffset = 6; break;
3558 case 'T': remoteoffset = 7; break;
3559 case 'U': remoteoffset = 8; break;
3560 case 'V': remoteoffset = 9; break;
3561 case 'W': remoteoffset = 10; break;
3562 case 'X': remoteoffset = 11; break;
3563 case 'Y': remoteoffset = 12; break;
3564 default: remoteoffset = 0; break;
3566 remoteoffset = remoteoffset * 3600;
3570 return remoteoffset;
3573 time_t tzoffset_sec(time_t *now)
3577 struct tm buf1, buf2;
3579 if (now && *now < 0)
3582 gmt = *gmtime_r(now, &buf1);
3583 lt = localtime_r(now, &buf2);
3585 off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
3587 if (lt->tm_year < gmt.tm_year)
3589 else if (lt->tm_year > gmt.tm_year)
3591 else if (lt->tm_yday < gmt.tm_yday)
3593 else if (lt->tm_yday > gmt.tm_yday)
3596 if (off >= 24 * 60) /* should be impossible */
3597 off = 23 * 60 + 59; /* if not, insert silly value */
3598 if (off <= -24 * 60)
3599 off = -(23 * 60 + 59);
3604 /* calculate timezone offset */
3605 gchar *tzoffset(time_t *now)
3607 static gchar offset_string[6];
3611 struct tm buf1, buf2;
3613 if (now && *now < 0)
3616 gmt = *gmtime_r(now, &buf1);
3617 lt = localtime_r(now, &buf2);
3619 off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
3621 if (lt->tm_year < gmt.tm_year)
3623 else if (lt->tm_year > gmt.tm_year)
3625 else if (lt->tm_yday < gmt.tm_yday)
3627 else if (lt->tm_yday > gmt.tm_yday)
3635 if (off >= 24 * 60) /* should be impossible */
3636 off = 23 * 60 + 59; /* if not, insert silly value */
3638 sprintf(offset_string, "%c%02d%02d", sign, off / 60, off % 60);
3640 return offset_string;
3643 void get_rfc822_date(gchar *buf, gint len)
3647 gchar day[4], mon[4];
3648 gint dd, hh, mm, ss, yyyy;
3650 gchar buf2[BUFFSIZE];
3653 lt = localtime_r(&t, &buf1);
3655 sscanf(asctime_r(lt, buf2), "%3s %3s %d %d:%d:%d %d\n",
3656 day, mon, &dd, &hh, &mm, &ss, &yyyy);
3658 g_snprintf(buf, len, "%s, %d %s %d %02d:%02d:%02d %s",
3659 day, dd, mon, yyyy, hh, mm, ss, tzoffset(&t));
3662 void debug_set_mode(gboolean mode)
3667 gboolean debug_get_mode(void)
3672 void debug_print_real(const gchar *format, ...)
3675 gchar buf[BUFFSIZE];
3677 if (!debug_mode) return;
3679 va_start(args, format);
3680 g_vsnprintf(buf, sizeof(buf), format, args);
3687 const char * debug_srcname(const char *file)
3689 const char *s = strrchr (file, '/');
3694 void * subject_table_lookup(GHashTable *subject_table, gchar * subject)
3696 if (subject == NULL)
3699 subject += subject_get_prefix_length(subject);
3701 return g_hash_table_lookup(subject_table, subject);
3704 void subject_table_insert(GHashTable *subject_table, gchar * subject,
3707 if (subject == NULL || *subject == 0)
3709 subject += subject_get_prefix_length(subject);
3710 g_hash_table_insert(subject_table, subject, data);
3713 void subject_table_remove(GHashTable *subject_table, gchar * subject)
3715 if (subject == NULL)
3718 subject += subject_get_prefix_length(subject);
3719 g_hash_table_remove(subject_table, subject);
3723 static regex_t u_regex;
3724 static gboolean u_init_;
3727 void utils_free_regex(void)
3738 *\brief Check if a string is prefixed with known (combinations)
3739 * of prefixes. The function assumes that each prefix
3740 * is terminated by zero or exactly _one_ space.
3742 *\param str String to check for a prefixes
3744 *\return int Number of chars in the prefix that should be skipped
3745 * for a "clean" subject line. If no prefix was found, 0
3748 int subject_get_prefix_length(const gchar *subject)
3751 /*!< Array with allowable reply prefixes regexps. */
3752 static const gchar * const prefixes[] = {
3753 "Re\\:", /* "Re:" */
3754 "Re\\[[1-9][0-9]*\\]\\:", /* "Re[XXX]:" (non-conforming news mail clients) */
3755 "Antw\\:", /* "Antw:" (Dutch / German Outlook) */
3756 "Aw\\:", /* "Aw:" (German) */
3757 "Antwort\\:", /* "Antwort:" (German Lotus Notes) */
3758 "Res\\:", /* "Res:" (Spanish/Brazilian Outlook) */
3759 "Fw\\:", /* "Fw:" Forward */
3760 "Fwd\\:", /* "Fwd:" Forward */
3761 "Enc\\:", /* "Enc:" Forward (Brazilian Outlook) */
3762 "Odp\\:", /* "Odp:" Re (Polish Outlook) */
3763 "Rif\\:", /* "Rif:" (Italian Outlook) */
3764 "Sv\\:", /* "Sv" (Norwegian) */
3765 "Vs\\:", /* "Vs" (Norwegian) */
3766 "Ad\\:", /* "Ad" (Norwegian) */
3767 "\347\255\224\345\244\215\\:", /* "Re" (Chinese, UTF-8) */
3768 "R\303\251f\\. \\:", /* "R�f. :" (French Lotus Notes) */
3769 "Re \\:", /* "Re :" (French Yahoo Mail) */
3772 const int PREFIXES = sizeof prefixes / sizeof prefixes[0];
3776 if (!subject) return 0;
3777 if (!*subject) return 0;
3780 GString *s = g_string_new("");
3782 for (n = 0; n < PREFIXES; n++)
3783 /* Terminate each prefix regexpression by a
3784 * "\ ?" (zero or ONE space), and OR them */
3785 g_string_append_printf(s, "(%s\\ ?)%s",
3790 g_string_prepend(s, "(");
3791 g_string_append(s, ")+"); /* match at least once */
3792 g_string_prepend(s, "^\\ *"); /* from beginning of line */
3795 /* We now have something like "^\ *((PREFIX1\ ?)|(PREFIX2\ ?))+"
3796 * TODO: Should this be "^\ *(((PREFIX1)|(PREFIX2))\ ?)+" ??? */
3797 if (regcomp(&u_regex, s->str, REG_EXTENDED | REG_ICASE)) {
3798 debug_print("Error compiling regexp %s\n", s->str);
3799 g_string_free(s, TRUE);
3803 g_string_free(s, TRUE);
3807 if (!regexec(&u_regex, subject, 1, &pos, 0) && pos.rm_so != -1)
3812 /*!< Array with allowable reply prefixes regexps. */
3813 static const gchar * const prefixes[] = {
3815 "antw:", /* "Antw:" (Dutch / German Outlook) */
3816 "aw:", /* "Aw:" (German) */
3817 "antwort:", /* "Antwort:" (German Lotus Notes) */
3818 "res:", /* "Res:" (Spanish/Brazilian Outlook) */
3819 "fw:", /* "Fw:" Forward */
3820 "fwd:", /* "Fwd:" Forward */
3821 "enc:", /* "Enc:" Forward (Brazilian Outlook) */
3822 "odp:", /* "Odp:" Re (Polish Outlook) */
3823 "rif:", /* "Rif:" (Italian Outlook) */
3824 "sv:", /* "Sv" (Norwegian) */
3825 "vs:", /* "Vs" (Norwegian) */
3826 "ad:", /* "Ad" (Norwegian) */
3827 "R\303\251f. :", /* "R�f. :" (French Lotus Notes) */
3828 "Re :", /* "Re :" (French Yahoo Mail) */
3831 const int PREFIXES = sizeof prefixes / sizeof prefixes[0];
3834 if (!subject) return 0;
3835 if (!*subject) return 0;
3837 for (n = 0; n < PREFIXES; n++) {
3838 int len = strlen(prefixes[n]);
3839 if (!strncasecmp(subject, prefixes[n], len)) {
3840 if (subject[len] == ' ')
3849 static guint g_stricase_hash(gconstpointer gptr)
3851 guint hash_result = 0;
3854 for (str = gptr; str && *str; str++) {
3855 hash_result += toupper(*str);
3861 static gint g_stricase_equal(gconstpointer gptr1, gconstpointer gptr2)
3863 const char *str1 = gptr1;
3864 const char *str2 = gptr2;
3866 return !strcasecmp(str1, str2);
3869 gint g_int_compare(gconstpointer a, gconstpointer b)
3871 return GPOINTER_TO_INT(a) - GPOINTER_TO_INT(b);
3874 gchar *generate_msgid(gchar *buf, gint len, gchar *user_addr)
3882 lt = localtime_r(&t, &buft);
3884 if (user_addr != NULL)
3885 addr = g_strdup_printf(".%s", user_addr);
3886 else if (strlen(buf) != 0)
3887 addr = g_strdup_printf("@%s", buf);
3889 addr = g_strdup_printf("@%s", get_domain_name());
3891 /* Replace all @ but the last one in addr, with underscores.
3892 * RFC 2822 States that msg-id syntax only allows one @.
3894 while (strchr(addr, '@') != NULL && strchr(addr, '@') != strrchr(addr, '@'))
3895 *(strchr(addr, '@')) = '_';
3897 g_snprintf(buf, len, "%04d%02d%02d%02d%02d%02d.%08x%s",
3898 lt->tm_year + 1900, lt->tm_mon + 1,
3899 lt->tm_mday, lt->tm_hour,
3900 lt->tm_min, lt->tm_sec,
3901 (guint) rand(), addr);
3908 quote_cmd_argument()
3910 return a quoted string safely usable in argument of a command.
3912 code is extracted and adapted from etPan! project -- DINH V. Ho�.
3915 gint quote_cmd_argument(gchar * result, guint size,
3925 for(p = path ; * p != '\0' ; p ++) {
3927 if (isalnum((guchar)*p) || (* p == '/')) {
3928 if (remaining > 0) {
3934 result[size - 1] = '\0';
3939 if (remaining >= 2) {
3947 result[size - 1] = '\0';
3952 if (remaining > 0) {
3956 result[size - 1] = '\0';
3970 static void g_node_map_recursive(GNode *node, gpointer data)
3972 GNodeMapData *mapdata = (GNodeMapData *) data;
3974 GNodeMapData newmapdata;
3977 newdata = mapdata->func(node->data, mapdata->data);
3978 if (newdata != NULL) {
3979 newnode = g_node_new(newdata);
3980 g_node_append(mapdata->parent, newnode);
3982 newmapdata.parent = newnode;
3983 newmapdata.func = mapdata->func;
3984 newmapdata.data = mapdata->data;
3986 g_node_children_foreach(node, G_TRAVERSE_ALL, g_node_map_recursive, &newmapdata);
3990 GNode *g_node_map(GNode *node, GNodeMapFunc func, gpointer data)
3993 GNodeMapData mapdata;
3995 cm_return_val_if_fail(node != NULL, NULL);
3996 cm_return_val_if_fail(func != NULL, NULL);
3998 root = g_node_new(func(node->data, data));
4000 mapdata.parent = root;
4001 mapdata.func = func;
4002 mapdata.data = data;
4004 g_node_children_foreach(node, G_TRAVERSE_ALL, g_node_map_recursive, &mapdata);
4009 #define HEX_TO_INT(val, hex) \
4013 if ('0' <= c && c <= '9') { \
4015 } else if ('a' <= c && c <= 'f') { \
4016 val = c - 'a' + 10; \
4017 } else if ('A' <= c && c <= 'F') { \
4018 val = c - 'A' + 10; \
4024 gboolean get_hex_value(guchar *out, gchar c1, gchar c2)
4031 if (hi == -1 || lo == -1)
4034 *out = (hi << 4) + lo;
4038 #define INT_TO_HEX(hex, val) \
4041 hex = '0' + (val); \
4043 hex = 'A' + (val) - 10; \
4046 void get_hex_str(gchar *out, guchar ch)
4050 INT_TO_HEX(hex, ch >> 4);
4052 INT_TO_HEX(hex, ch & 0x0f);
4058 #define G_PRINT_REF 1 == 1 ? (void) 0 : (void)
4060 #define G_PRINT_REF g_print
4064 *\brief Register ref counted pointer. It is based on GBoxed, so should
4065 * work with anything that uses the GType system. The semantics
4066 * are similar to a C++ auto pointer, with the exception that
4067 * C doesn't have automatic closure (calling destructors) when
4068 * exiting a block scope.
4069 * Use the \ref G_TYPE_AUTO_POINTER macro instead of calling this
4070 * function directly.
4072 *\return GType A GType type.
4074 GType g_auto_pointer_register(void)
4076 static GType auto_pointer_type;
4077 if (!auto_pointer_type)
4079 g_boxed_type_register_static
4080 ("G_TYPE_AUTO_POINTER",
4081 (GBoxedCopyFunc) g_auto_pointer_copy,
4082 (GBoxedFreeFunc) g_auto_pointer_free);
4083 return auto_pointer_type;
4087 *\brief Structure with g_new() allocated pointer guarded by the
4090 typedef struct AutoPointerRef {
4091 void (*free) (gpointer);
4097 *\brief The auto pointer opaque structure that references the
4098 * pointer guard block.
4100 typedef struct AutoPointer {
4101 AutoPointerRef *ref;
4102 gpointer ptr; /*!< access to protected pointer */
4106 *\brief Creates an auto pointer for a g_new()ed pointer. Example:
4110 * ... tell gtk_list_store it should use a G_TYPE_AUTO_POINTER
4111 * ... when assigning, copying and freeing storage elements
4113 * gtk_list_store_new(N_S_COLUMNS,
4114 * G_TYPE_AUTO_POINTER,
4118 * Template *precious_data = g_new0(Template, 1);
4119 * g_pointer protect = g_auto_pointer_new(precious_data);
4121 * gtk_list_store_set(container, &iter,
4125 * ... the gtk_list_store has copied the pointer and
4126 * ... incremented its reference count, we should free
4127 * ... the auto pointer (in C++ a destructor would do
4128 * ... this for us when leaving block scope)
4130 * g_auto_pointer_free(protect);
4132 * ... gtk_list_store_set() now manages the data. When
4133 * ... *explicitly* requesting a pointer from the list
4134 * ... store, don't forget you get a copy that should be
4135 * ... freed with g_auto_pointer_free() eventually.
4139 *\param pointer Pointer to be guarded.
4141 *\return GAuto * Pointer that should be used in containers with
4144 GAuto *g_auto_pointer_new(gpointer p)
4146 AutoPointerRef *ref;
4152 ref = g_new0(AutoPointerRef, 1);
4153 ptr = g_new0(AutoPointer, 1);
4163 G_PRINT_REF ("XXXX ALLOC(%lx)\n", p);
4169 *\brief Allocate an autopointer using the passed \a free function to
4170 * free the guarded pointer
4172 GAuto *g_auto_pointer_new_with_free(gpointer p, GFreeFunc free_)
4179 aptr = g_auto_pointer_new(p);
4180 aptr->ref->free = free_;
4184 gpointer g_auto_pointer_get_ptr(GAuto *auto_ptr)
4186 if (auto_ptr == NULL)
4188 return ((AutoPointer *) auto_ptr)->ptr;
4192 *\brief Copies an auto pointer by. It's mostly not necessary
4193 * to call this function directly, unless you copy/assign
4194 * the guarded pointer.
4196 *\param auto_ptr Auto pointer returned by previous call to
4197 * g_auto_pointer_new_XXX()
4199 *\return gpointer An auto pointer
4201 GAuto *g_auto_pointer_copy(GAuto *auto_ptr)
4204 AutoPointerRef *ref;
4207 if (auto_ptr == NULL)
4212 newp = g_new0(AutoPointer, 1);
4215 newp->ptr = ref->pointer;
4219 G_PRINT_REF ("XXXX COPY(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4225 *\brief Free an auto pointer
4227 void g_auto_pointer_free(GAuto *auto_ptr)
4230 AutoPointerRef *ref;
4232 if (auto_ptr == NULL)
4238 if (--(ref->cnt) == 0) {
4240 G_PRINT_REF ("XXXX FREE(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4242 ref->free(ref->pointer);
4247 G_PRINT_REF ("XXXX DEREF(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4252 void replace_returns(gchar *str)
4257 while (strstr(str, "\n")) {
4258 *strstr(str, "\n") = ' ';
4260 while (strstr(str, "\r")) {
4261 *strstr(str, "\r") = ' ';
4265 /* get_uri_part() - retrieves a URI starting from scanpos.
4266 Returns TRUE if succesful */
4267 gboolean get_uri_part(const gchar *start, const gchar *scanpos,
4268 const gchar **bp, const gchar **ep, gboolean hdr)
4271 gint parenthese_cnt = 0;
4273 cm_return_val_if_fail(start != NULL, FALSE);
4274 cm_return_val_if_fail(scanpos != NULL, FALSE);
4275 cm_return_val_if_fail(bp != NULL, FALSE);
4276 cm_return_val_if_fail(ep != NULL, FALSE);
4280 /* find end point of URI */
4281 for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
4282 if (!g_ascii_isgraph(*(const guchar *)ep_) ||
4283 !IS_ASCII(*(const guchar *)ep_) ||
4284 strchr("[]{}<>\"", *ep_)) {
4286 } else if (strchr("(", *ep_)) {
4288 } else if (strchr(")", *ep_)) {
4289 if (parenthese_cnt > 0)
4296 /* no punctuation at end of string */
4298 /* FIXME: this stripping of trailing punctuations may bite with other URIs.
4299 * should pass some URI type to this function and decide on that whether
4300 * to perform punctuation stripping */
4302 #define IS_REAL_PUNCT(ch) (g_ascii_ispunct(ch) && !strchr("/?=-_)", ch))
4304 for (; ep_ - 1 > scanpos + 1 &&
4305 IS_REAL_PUNCT(*(ep_ - 1));
4309 #undef IS_REAL_PUNCT
4316 gchar *make_uri_string(const gchar *bp, const gchar *ep)
4318 while (bp && *bp && g_ascii_isspace(*bp))
4320 return g_strndup(bp, ep - bp);
4323 /* valid mail address characters */
4324 #define IS_RFC822_CHAR(ch) \
4328 !g_ascii_isspace(ch) && \
4329 !strchr("(),;<>\"", (ch)))
4331 /* alphabet and number within 7bit ASCII */
4332 #define IS_ASCII_ALNUM(ch) (IS_ASCII(ch) && g_ascii_isalnum(ch))
4333 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
4335 static GHashTable *create_domain_tab(void)
4337 static const gchar *toplvl_domains [] = {
4339 "arpa", "coop", "info", "name", "biz", "com", "edu", "gov",
4340 "int", "mil", "net", "org", "ac", "ad", "ae", "af", "ag",
4341 "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au",
4342 "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi",
4343 "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
4344 "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl",
4345 "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz", "de",
4346 "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er",
4347 "es", "et", "eu", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd",
4348 "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
4349 "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
4350 "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir",
4351 "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
4352 "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc",
4353 "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
4354 "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq",
4355 "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na",
4356 "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu",
4357 "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm",
4358 "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru",
4359 "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj",
4360 "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
4361 "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to",
4362 "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um",
4363 "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu",
4364 "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw"
4367 GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
4369 cm_return_val_if_fail(htab, NULL);
4370 for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++)
4371 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
4375 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
4377 const gint MAX_LVL_DOM_NAME_LEN = 6;
4378 gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
4379 const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
4382 if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
4385 for (p = buf; p < m && first < last; *p++ = *first++)
4389 return g_hash_table_lookup(tab, buf) != NULL;
4392 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
4393 gboolean get_email_part(const gchar *start, const gchar *scanpos,
4394 const gchar **bp, const gchar **ep, gboolean hdr)
4396 /* more complex than the uri part because we need to scan back and forward starting from
4397 * the scan position. */
4398 gboolean result = FALSE;
4399 const gchar *bp_ = NULL;
4400 const gchar *ep_ = NULL;
4401 static GHashTable *dom_tab;
4402 const gchar *last_dot = NULL;
4403 const gchar *prelast_dot = NULL;
4404 const gchar *last_tld_char = NULL;
4406 /* the informative part of the email address (describing the name
4407 * of the email address owner) may contain quoted parts. the
4408 * closure stack stores the last encountered quotes. */
4409 gchar closure_stack[128];
4410 gchar *ptr = closure_stack;
4412 cm_return_val_if_fail(start != NULL, FALSE);
4413 cm_return_val_if_fail(scanpos != NULL, FALSE);
4414 cm_return_val_if_fail(bp != NULL, FALSE);
4415 cm_return_val_if_fail(ep != NULL, FALSE);
4418 const gchar *start_quote = NULL;
4419 const gchar *end_quote = NULL;
4421 /* go to the real start */
4422 if (start[0] == ',')
4424 if (start[0] == ';')
4426 while (start[0] == '\n' || start[0] == '\r')
4428 while (start[0] == ' ' || start[0] == '\t')
4433 /* check if there are quotes (to skip , in them) */
4434 if (*start == '"') {
4435 start_quote = start;
4437 end_quote = strstr(start, "\"");
4443 /* skip anything between quotes */
4444 if (start_quote && end_quote) {
4449 /* find end (either , or ; or end of line) */
4450 if (strstr(start, ",") && strstr(start, ";"))
4451 *ep = strstr(start,",") < strstr(start, ";")
4452 ? strstr(start, ",") : strstr(start, ";");
4453 else if (strstr(start, ","))
4454 *ep = strstr(start, ",");
4455 else if (strstr(start, ";"))
4456 *ep = strstr(start, ";");
4458 *ep = start+strlen(start);
4460 /* go back to real start */
4461 if (start_quote && end_quote) {
4462 start = start_quote;
4465 /* check there's still an @ in that, or search
4466 * further if possible */
4467 if (strstr(start, "@") && strstr(start, "@") < *ep)
4469 else if (*ep < start+strlen(start)) {
4472 } else if (start_quote && strstr(start, "\"") && strstr(start, "\"") < *ep) {
4480 dom_tab = create_domain_tab();
4481 cm_return_val_if_fail(dom_tab, FALSE);
4483 /* scan start of address */
4484 for (bp_ = scanpos - 1;
4485 bp_ >= start && IS_RFC822_CHAR(*(const guchar *)bp_); bp_--)
4488 /* TODO: should start with an alnum? */
4490 for (; bp_ < scanpos && !IS_ASCII_ALNUM(*(const guchar *)bp_); bp_++)
4493 if (bp_ != scanpos) {
4494 /* scan end of address */
4495 for (ep_ = scanpos + 1;
4496 *ep_ && IS_RFC822_CHAR(*(const guchar *)ep_); ep_++)
4498 prelast_dot = last_dot;
4500 if (*(last_dot + 1) == '.') {
4501 if (prelast_dot == NULL)
4503 last_dot = prelast_dot;
4508 /* TODO: really should terminate with an alnum? */
4509 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*(const guchar *)ep_);
4514 if (last_dot == NULL)
4516 if (last_dot >= ep_)
4517 last_dot = prelast_dot;
4518 if (last_dot == NULL || (scanpos + 1 >= last_dot))
4522 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
4523 if (*last_tld_char == '?')
4526 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
4533 if (!result) return FALSE;
4535 if (*ep_ && bp_ != start && *(bp_ - 1) == '"' && *(ep_) == '"'
4536 && *(ep_ + 1) == ' ' && *(ep_ + 2) == '<'
4537 && IS_RFC822_CHAR(*(ep_ + 3))) {
4538 /* this informative part with an @ in it is
4539 * followed by the email address */
4542 /* go to matching '>' (or next non-rfc822 char, like \n) */
4543 for (; *ep_ != '>' && *ep != '\0' && IS_RFC822_CHAR(*ep_); ep_++)
4546 /* include the bracket */
4547 if (*ep_ == '>') ep_++;
4549 /* include the leading quote */
4557 /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
4558 if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
4561 /* see if this is <bracketed>; in this case we also scan for the informative part. */
4562 if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
4565 #define FULL_STACK() ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
4566 #define IN_STACK() (ptr > closure_stack)
4567 /* has underrun check */
4568 #define POP_STACK() if(IN_STACK()) --ptr
4569 /* has overrun check */
4570 #define PUSH_STACK(c) if(!FULL_STACK()) *ptr++ = (c); else return TRUE
4571 /* has underrun check */
4572 #define PEEK_STACK() (IN_STACK() ? *(ptr - 1) : 0)
4576 /* scan for the informative part. */
4577 for (bp_ -= 2; bp_ >= start; bp_--) {
4578 /* if closure on the stack keep scanning */
4579 if (PEEK_STACK() == *bp_) {
4583 if (!IN_STACK() && (*bp_ == '\'' || *bp_ == '"')) {
4588 /* if nothing in the closure stack, do the special conditions
4589 * the following if..else expression simply checks whether
4590 * a token is acceptable. if not acceptable, the clause
4591 * should terminate the loop with a 'break' */
4592 if (!PEEK_STACK()) {
4594 && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
4595 && (((bp_ + 1) < ep_) && isalnum(*(bp_ + 1)))) {
4596 /* hyphens are allowed, but only in
4598 } else if (strchr(" \"'", *bp_)) {
4599 /* but anything not being a punctiation
4602 break; /* anything else is rejected */
4609 /* scan forward (should start with an alnum) */
4610 for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
4626 #undef IS_ASCII_ALNUM
4627 #undef IS_RFC822_CHAR
4629 gchar *make_email_string(const gchar *bp, const gchar *ep)
4631 /* returns a mailto: URI; mailto: is also used to detect the
4632 * uri type later on in the button_pressed signal handler */
4636 tmp = g_strndup(bp, ep - bp);
4637 result = g_strconcat("mailto:", tmp, NULL);
4643 gchar *make_http_string(const gchar *bp, const gchar *ep)
4645 /* returns an http: URI; */
4649 while (bp && *bp && g_ascii_isspace(*bp))
4651 tmp = g_strndup(bp, ep - bp);
4652 result = g_strconcat("http://", tmp, NULL);
4658 static gchar *mailcap_get_command_in_file(const gchar *path, const gchar *type, const gchar *file_to_open)
4660 FILE *fp = g_fopen(path, "rb");
4661 gchar buf[BUFFSIZE];
4662 gchar *result = NULL;
4665 while (fgets(buf, sizeof (buf), fp) != NULL) {
4666 gchar **parts = g_strsplit(buf, ";", 3);
4667 gchar *trimmed = parts[0];
4668 while (trimmed[0] == ' ' || trimmed[0] == '\t')
4670 while (trimmed[strlen(trimmed)-1] == ' ' || trimmed[strlen(trimmed)-1] == '\t')
4671 trimmed[strlen(trimmed)-1] = '\0';
4673 if (!strcmp(trimmed, type)) {
4674 gboolean needsterminal = FALSE;
4675 if (parts[2] && strstr(parts[2], "needsterminal")) {
4676 needsterminal = TRUE;
4678 if (parts[2] && strstr(parts[2], "test=")) {
4679 gchar *orig_testcmd = g_strdup(strstr(parts[2], "test=")+5);
4680 gchar *testcmd = orig_testcmd;
4681 if (strstr(testcmd,";"))
4682 *(strstr(testcmd,";")) = '\0';
4683 while (testcmd[0] == ' ' || testcmd[0] == '\t')
4685 while (testcmd[strlen(testcmd)-1] == '\n')
4686 testcmd[strlen(testcmd)-1] = '\0';
4687 while (testcmd[strlen(testcmd)-1] == '\r')
4688 testcmd[strlen(testcmd)-1] = '\0';
4689 while (testcmd[strlen(testcmd)-1] == ' ' || testcmd[strlen(testcmd)-1] == '\t')
4690 testcmd[strlen(testcmd)-1] = '\0';
4692 if (strstr(testcmd, "%s")) {
4693 gchar *tmp = g_strdup_printf(testcmd, file_to_open);
4694 gint res = system(tmp);
4696 g_free(orig_testcmd);
4703 gint res = system(testcmd);
4704 g_free(orig_testcmd);
4714 while (trimmed[0] == ' ' || trimmed[0] == '\t')
4716 while (trimmed[strlen(trimmed)-1] == '\n')
4717 trimmed[strlen(trimmed)-1] = '\0';
4718 while (trimmed[strlen(trimmed)-1] == '\r')
4719 trimmed[strlen(trimmed)-1] = '\0';
4720 while (trimmed[strlen(trimmed)-1] == ' ' || trimmed[strlen(trimmed)-1] == '\t')
4721 trimmed[strlen(trimmed)-1] = '\0';
4722 result = g_strdup(trimmed);
4725 /* if there are no single quotes around %s, add them.
4726 * '.*%s.*' is ok, as in display 'png:%s'
4728 if (strstr(result, "%s")
4729 && !(strstr(result, "'") < strstr(result,"%s") &&
4730 strstr(strstr(result,"%s"), "'"))) {
4731 gchar *start = g_strdup(result);
4732 gchar *end = g_strdup(strstr(result, "%s")+2);
4734 *strstr(start, "%s") = '\0';
4735 tmp = g_strconcat(start,"'%s'",end, NULL);
4741 if (needsterminal) {
4742 gchar *tmp = g_strdup_printf("xterm -e %s", result);
4753 gchar *mailcap_get_command_for_type(const gchar *type, const gchar *file_to_open)
4755 gchar *result = NULL;
4759 path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap", NULL);
4760 result = mailcap_get_command_in_file(path, type, file_to_open);
4764 result = mailcap_get_command_in_file("/etc/mailcap", type, file_to_open);
4768 void mailcap_update_default(const gchar *type, const gchar *command)
4770 gchar *path = NULL, *outpath = NULL;
4771 path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap", NULL);
4772 outpath = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap.new", NULL);
4773 FILE *fp = g_fopen(path, "rb");
4775 gchar buf[BUFFSIZE];
4776 gboolean err = FALSE;
4779 fp = g_fopen(path, "a");
4781 g_warning("failed to create file %s", path);
4786 fp = g_freopen(path, "rb", fp);
4788 g_warning("failed to reopen file %s", path);
4795 outfp = g_fopen(outpath, "wb");
4797 g_warning("failed to create file %s", outpath);
4803 while (fp && fgets(buf, sizeof (buf), fp) != NULL) {
4804 gchar **parts = g_strsplit(buf, ";", 3);
4805 gchar *trimmed = parts[0];
4806 while (trimmed[0] == ' ')
4808 while (trimmed[strlen(trimmed)-1] == ' ')
4809 trimmed[strlen(trimmed)-1] = '\0';
4811 if (!strcmp(trimmed, type)) {
4816 if(fputs(buf, outfp) == EOF) {
4823 if (fprintf(outfp, "%s; %s\n", type, command) < 0)
4829 if (fclose(outfp) == EOF)
4833 g_rename(outpath, path);
4839 gint copy_dir(const gchar *src, const gchar *dst)
4844 if ((dir = g_dir_open(src, 0, NULL)) == NULL) {
4845 g_warning("failed to open directory: %s", src);
4849 if (make_dir(dst) < 0)
4852 while ((name = g_dir_read_name(dir)) != NULL) {
4853 gchar *old_file, *new_file;
4854 old_file = g_strconcat(src, G_DIR_SEPARATOR_S, name, NULL);
4855 new_file = g_strconcat(dst, G_DIR_SEPARATOR_S, name, NULL);
4856 debug_print("copying: %s -> %s\n", old_file, new_file);
4857 if (g_file_test(old_file, G_FILE_TEST_IS_REGULAR)) {
4858 gint r = copy_file(old_file, new_file, TRUE);
4865 /* Windows has no symlinks. Or well, Vista seems to
4866 have something like this but the semantics might be
4867 different. Thus we don't use it under Windows. */
4868 else if (g_file_test(old_file, G_FILE_TEST_IS_SYMLINK)) {
4869 GError *error = NULL;
4871 gchar *target = g_file_read_link(old_file, &error);
4873 r = symlink(target, new_file);
4880 #endif /*G_OS_WIN32*/
4881 else if (g_file_test(old_file, G_FILE_TEST_IS_DIR)) {
4882 gint r = copy_dir(old_file, new_file);
4893 /* crude test to see if a file is an email. */
4894 gboolean file_is_email (const gchar *filename)
4900 if (filename == NULL)
4902 if ((fp = g_fopen(filename, "rb")) == NULL)
4904 while (i < 60 && score < 3
4905 && fgets(buffer, sizeof (buffer), fp) > 0) {
4906 if (!strncmp(buffer, "From:", strlen("From:")))
4908 else if (!strncmp(buffer, "Date:", strlen("Date:")))
4910 else if (!strncmp(buffer, "Message-ID:", strlen("Message-ID:")))
4912 else if (!strncmp(buffer, "Subject:", strlen("Subject:")))
4917 return (score >= 3);
4920 gboolean sc_g_list_bigger(GList *list, gint max)
4924 while (cur && i <= max+1) {
4931 gboolean sc_g_slist_bigger(GSList *list, gint max)
4935 while (cur && i <= max+1) {
4942 const gchar *daynames[] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL};
4943 const gchar *monthnames[] = {NULL, NULL, NULL, NULL, NULL, NULL,
4944 NULL, NULL, NULL, NULL, NULL, NULL};
4945 const gchar *s_daynames[] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL};
4946 const gchar *s_monthnames[] = {NULL, NULL, NULL, NULL, NULL, NULL,
4947 NULL, NULL, NULL, NULL, NULL, NULL};
4949 gint daynames_len[] = {0,0,0,0,0,0,0};
4950 gint monthnames_len[] = {0,0,0,0,0,0,
4952 gint s_daynames_len[] = {0,0,0,0,0,0,0};
4953 gint s_monthnames_len[] = {0,0,0,0,0,0,
4955 const gchar *s_am_up = NULL;
4956 const gchar *s_pm_up = NULL;
4957 const gchar *s_am_low = NULL;
4958 const gchar *s_pm_low = NULL;
4960 gint s_am_up_len = 0;
4961 gint s_pm_up_len = 0;
4962 gint s_am_low_len = 0;
4963 gint s_pm_low_len = 0;
4965 static gboolean time_names_init_done = FALSE;
4967 static void init_time_names(void)
4971 daynames[0] = C_("Complete day name for use by strftime", "Sunday");
4972 daynames[1] = C_("Complete day name for use by strftime", "Monday");
4973 daynames[2] = C_("Complete day name for use by strftime", "Tuesday");
4974 daynames[3] = C_("Complete day name for use by strftime", "Wednesday");
4975 daynames[4] = C_("Complete day name for use by strftime", "Thursday");
4976 daynames[5] = C_("Complete day name for use by strftime", "Friday");
4977 daynames[6] = C_("Complete day name for use by strftime", "Saturday");
4979 monthnames[0] = C_("Complete month name for use by strftime", "January");
4980 monthnames[1] = C_("Complete month name for use by strftime", "February");
4981 monthnames[2] = C_("Complete month name for use by strftime", "March");
4982 monthnames[3] = C_("Complete month name for use by strftime", "April");
4983 monthnames[4] = C_("Complete month name for use by strftime", "May");
4984 monthnames[5] = C_("Complete month name for use by strftime", "June");
4985 monthnames[6] = C_("Complete month name for use by strftime", "July");
4986 monthnames[7] = C_("Complete month name for use by strftime", "August");
4987 monthnames[8] = C_("Complete month name for use by strftime", "September");
4988 monthnames[9] = C_("Complete month name for use by strftime", "October");
4989 monthnames[10] = C_("Complete month name for use by strftime", "November");
4990 monthnames[11] = C_("Complete month name for use by strftime", "December");
4992 s_daynames[0] = C_("Abbr. day name for use by strftime", "Sun");
4993 s_daynames[1] = C_("Abbr. day name for use by strftime", "Mon");
4994 s_daynames[2] = C_("Abbr. day name for use by strftime", "Tue");
4995 s_daynames[3] = C_("Abbr. day name for use by strftime", "Wed");
4996 s_daynames[4] = C_("Abbr. day name for use by strftime", "Thu");
4997 s_daynames[5] = C_("Abbr. day name for use by strftime", "Fri");
4998 s_daynames[6] = C_("Abbr. day name for use by strftime", "Sat");
5000 s_monthnames[0] = C_("Abbr. month name for use by strftime", "Jan");
5001 s_monthnames[1] = C_("Abbr. month name for use by strftime", "Feb");
5002 s_monthnames[2] = C_("Abbr. month name for use by strftime", "Mar");
5003 s_monthnames[3] = C_("Abbr. month name for use by strftime", "Apr");
5004 s_monthnames[4] = C_("Abbr. month name for use by strftime", "May");
5005 s_monthnames[5] = C_("Abbr. month name for use by strftime", "Jun");
5006 s_monthnames[6] = C_("Abbr. month name for use by strftime", "Jul");
5007 s_monthnames[7] = C_("Abbr. month name for use by strftime", "Aug");
5008 s_monthnames[8] = C_("Abbr. month name for use by strftime", "Sep");
5009 s_monthnames[9] = C_("Abbr. month name for use by strftime", "Oct");
5010 s_monthnames[10] = C_("Abbr. month name for use by strftime", "Nov");
5011 s_monthnames[11] = C_("Abbr. month name for use by strftime", "Dec");
5013 for (i = 0; i < 7; i++) {
5014 daynames_len[i] = strlen(daynames[i]);
5015 s_daynames_len[i] = strlen(s_daynames[i]);
5017 for (i = 0; i < 12; i++) {
5018 monthnames_len[i] = strlen(monthnames[i]);
5019 s_monthnames_len[i] = strlen(s_monthnames[i]);
5022 s_am_up = C_("For use by strftime (morning)", "AM");
5023 s_pm_up = C_("For use by strftime (afternoon)", "PM");
5024 s_am_low = C_("For use by strftime (morning, lowercase)", "am");
5025 s_pm_low = C_("For use by strftime (afternoon, lowercase)", "pm");
5027 s_am_up_len = strlen(s_am_up);
5028 s_pm_up_len = strlen(s_pm_up);
5029 s_am_low_len = strlen(s_am_low);
5030 s_pm_low_len = strlen(s_pm_low);
5032 time_names_init_done = TRUE;
5035 #define CHECK_SIZE() { \
5036 total_done += len; \
5037 if (total_done >= buflen) { \
5038 buf[buflen-1] = '\0'; \
5043 size_t fast_strftime(gchar *buf, gint buflen, const gchar *format, struct tm *lt)
5045 gchar *curpos = buf;
5046 gint total_done = 0;
5047 gchar subbuf[64], subfmt[64];
5048 static time_t last_tzset = (time_t)0;
5050 if (!time_names_init_done)
5053 if (format == NULL || lt == NULL)
5056 if (last_tzset != time(NULL)) {
5058 last_tzset = time(NULL);
5061 if (*format == '%') {
5062 gint len = 0, tmp = 0;
5066 len = 1; CHECK_SIZE();
5070 len = s_daynames_len[lt->tm_wday]; CHECK_SIZE();
5071 strncpy2(curpos, s_daynames[lt->tm_wday], buflen - total_done);
5074 len = daynames_len[lt->tm_wday]; CHECK_SIZE();
5075 strncpy2(curpos, daynames[lt->tm_wday], buflen - total_done);
5079 len = s_monthnames_len[lt->tm_mon]; CHECK_SIZE();
5080 strncpy2(curpos, s_monthnames[lt->tm_mon], buflen - total_done);
5083 len = monthnames_len[lt->tm_mon]; CHECK_SIZE();
5084 strncpy2(curpos, monthnames[lt->tm_mon], buflen - total_done);
5087 strftime(subbuf, 64, "%c", lt);
5088 len = strlen(subbuf); CHECK_SIZE();
5089 strncpy2(curpos, subbuf, buflen - total_done);
5092 total_done += 2; CHECK_SIZE();
5093 tmp = (lt->tm_year + 1900)/100;
5094 *curpos++ = '0'+(tmp / 10);
5095 *curpos++ = '0'+(tmp % 10);
5098 total_done += 2; CHECK_SIZE();
5099 *curpos++ = '0'+(lt->tm_mday / 10);
5100 *curpos++ = '0'+(lt->tm_mday % 10);
5103 total_done += 8; CHECK_SIZE();
5104 *curpos++ = '0'+((lt->tm_mon+1) / 10);
5105 *curpos++ = '0'+((lt->tm_mon+1) % 10);
5107 *curpos++ = '0'+(lt->tm_mday / 10);
5108 *curpos++ = '0'+(lt->tm_mday % 10);
5110 tmp = lt->tm_year%100;
5111 *curpos++ = '0'+(tmp / 10);
5112 *curpos++ = '0'+(tmp % 10);
5115 len = 2; CHECK_SIZE();
5116 snprintf(curpos, buflen - total_done, "%2d", lt->tm_mday);
5119 len = 10; CHECK_SIZE();
5120 snprintf(curpos, buflen - total_done, "%4d-%02d-%02d",
5121 lt->tm_year + 1900, lt->tm_mon +1, lt->tm_mday);
5124 total_done += 2; CHECK_SIZE();
5125 *curpos++ = '0'+(lt->tm_hour / 10);
5126 *curpos++ = '0'+(lt->tm_hour % 10);
5129 total_done += 2; CHECK_SIZE();
5135 *curpos++ = '0'+(tmp / 10);
5136 *curpos++ = '0'+(tmp % 10);
5139 len = 3; CHECK_SIZE();
5140 snprintf(curpos, buflen - total_done, "%03d", lt->tm_yday+1);
5143 len = 2; CHECK_SIZE();
5144 snprintf(curpos, buflen - total_done, "%2d", lt->tm_hour);
5147 len = 2; CHECK_SIZE();
5153 snprintf(curpos, buflen - total_done, "%2d", tmp);
5156 total_done += 2; CHECK_SIZE();
5157 tmp = lt->tm_mon + 1;
5158 *curpos++ = '0'+(tmp / 10);
5159 *curpos++ = '0'+(tmp % 10);
5162 total_done += 2; CHECK_SIZE();
5163 *curpos++ = '0'+(lt->tm_min / 10);
5164 *curpos++ = '0'+(lt->tm_min % 10);
5167 len = 1; CHECK_SIZE();
5171 if (lt->tm_hour >= 12) {
5172 len = s_pm_up_len; CHECK_SIZE();
5173 snprintf(curpos, buflen-total_done, "%s", s_pm_up);
5175 len = s_am_up_len; CHECK_SIZE();
5176 snprintf(curpos, buflen-total_done, "%s", s_am_up);
5180 if (lt->tm_hour >= 12) {
5181 len = s_pm_low_len; CHECK_SIZE();
5182 snprintf(curpos, buflen-total_done, "%s", s_pm_low);
5184 len = s_am_low_len; CHECK_SIZE();
5185 snprintf(curpos, buflen-total_done, "%s", s_am_low);
5189 strftime(subbuf, 64, "%r", lt);
5190 len = strlen(subbuf); CHECK_SIZE();
5191 strncpy2(curpos, subbuf, buflen - total_done);
5194 total_done += 5; CHECK_SIZE();
5195 *curpos++ = '0'+(lt->tm_hour / 10);
5196 *curpos++ = '0'+(lt->tm_hour % 10);
5198 *curpos++ = '0'+(lt->tm_min / 10);
5199 *curpos++ = '0'+(lt->tm_min % 10);
5202 snprintf(subbuf, 64, "%ld", mktime(lt));
5203 len = strlen(subbuf); CHECK_SIZE();
5204 strncpy2(curpos, subbuf, buflen - total_done);
5207 total_done += 2; CHECK_SIZE();
5208 *curpos++ = '0'+(lt->tm_sec / 10);
5209 *curpos++ = '0'+(lt->tm_sec % 10);
5212 len = 1; CHECK_SIZE();
5216 total_done += 8; CHECK_SIZE();
5217 *curpos++ = '0'+(lt->tm_hour / 10);
5218 *curpos++ = '0'+(lt->tm_hour % 10);
5220 *curpos++ = '0'+(lt->tm_min / 10);
5221 *curpos++ = '0'+(lt->tm_min % 10);
5223 *curpos++ = '0'+(lt->tm_sec / 10);
5224 *curpos++ = '0'+(lt->tm_sec % 10);
5227 len = 1; CHECK_SIZE();
5228 snprintf(curpos, buflen - total_done, "%d", lt->tm_wday == 0 ? 7: lt->tm_wday);
5231 len = 1; CHECK_SIZE();
5232 snprintf(curpos, buflen - total_done, "%d", lt->tm_wday);
5235 strftime(subbuf, 64, "%x", lt);
5236 len = strlen(subbuf); CHECK_SIZE();
5237 strncpy2(curpos, subbuf, buflen - total_done);
5240 strftime(subbuf, 64, "%X", lt);
5241 len = strlen(subbuf); CHECK_SIZE();
5242 strncpy2(curpos, subbuf, buflen - total_done);
5245 total_done += 2; CHECK_SIZE();
5246 tmp = lt->tm_year%100;
5247 *curpos++ = '0'+(tmp / 10);
5248 *curpos++ = '0'+(tmp % 10);
5251 len = 4; CHECK_SIZE();
5252 snprintf(curpos, buflen - total_done, "%4d", lt->tm_year + 1900);
5262 /* let these complicated ones be done with the libc */
5263 snprintf(subfmt, 64, "%%%c", *format);
5264 strftime(subbuf, 64, subfmt, lt);
5265 len = strlen(subbuf); CHECK_SIZE();
5266 strncpy2(curpos, subbuf, buflen - total_done);
5270 /* let these complicated modifiers be done with the libc */
5271 snprintf(subfmt, 64, "%%%c%c", *format, *(format+1));
5272 strftime(subbuf, 64, subfmt, lt);
5273 len = strlen(subbuf); CHECK_SIZE();
5274 strncpy2(curpos, subbuf, buflen - total_done);
5278 g_warning("format error (%c)", *format);
5285 int len = 1; CHECK_SIZE();
5286 *curpos++ = *format++;
5293 gboolean prefs_common_get_use_shred(void);
5297 #define WEXITSTATUS(x) (x)
5300 int claws_unlink(const gchar *filename)
5303 static int found_shred = -1;
5304 static const gchar *args[4];
5306 if (filename == NULL)
5309 if (prefs_common_get_use_shred()) {
5310 if (found_shred == -1) {
5312 args[0] = g_find_program_in_path("shred");
5313 debug_print("found shred: %s\n", args[0]);
5314 found_shred = (args[0] != NULL) ? 1:0;
5318 if (found_shred == 1) {
5319 if (g_stat(filename, &s) == 0 && S_ISREG(s.st_mode)) {
5320 if (s.st_nlink == 1) {
5323 g_spawn_sync(NULL, (gchar **)args, NULL, 0,
5324 NULL, NULL, NULL, NULL, &status, NULL);
5325 debug_print("%s %s exited with status %d\n",
5326 args[0], filename, WEXITSTATUS(status));
5327 if (truncate(filename, 0) < 0)
5328 g_warning("couln't truncate: %s", filename);
5333 return g_unlink(filename);
5336 GMutex *cm_mutex_new(void) {
5337 #if GLIB_CHECK_VERSION(2,32,0)
5338 GMutex *m = g_new0(GMutex, 1);
5342 return g_mutex_new();
5346 void cm_mutex_free(GMutex *mutex) {
5347 #if GLIB_CHECK_VERSION(2,32,0)
5348 g_mutex_clear(mutex);
5351 g_mutex_free(mutex);
5355 static gchar *canonical_list_to_file(GSList *list)
5357 GString *result = g_string_new(NULL);
5358 GSList *pathlist = g_slist_reverse(g_slist_copy(list));
5363 result = g_string_append(result, G_DIR_SEPARATOR_S);
5365 if (pathlist->data) {
5366 const gchar *root = (gchar *)pathlist->data;
5367 if (root[0] != '\0' && g_ascii_isalpha(root[0]) &&
5369 /* drive - don't prepend dir separator */
5371 result = g_string_append(result, G_DIR_SEPARATOR_S);
5376 for (cur = pathlist; cur; cur = cur->next) {
5377 result = g_string_append(result, (gchar *)cur->data);
5379 result = g_string_append(result, G_DIR_SEPARATOR_S);
5381 g_slist_free(pathlist);
5384 g_string_free(result, FALSE);
5389 static GSList *cm_split_path(const gchar *filename, int depth)
5392 GSList *canonical_parts = NULL;
5395 gboolean follow_symlinks = TRUE;
5401 errno = EINVAL; /* can't happen, no symlink handling */
5406 if (!g_path_is_absolute(filename)) {
5411 path_parts = g_strsplit(filename, G_DIR_SEPARATOR_S, -1);
5413 for (i = 0; path_parts[i] != NULL; i++) {
5414 if (!strcmp(path_parts[i], ""))
5416 if (!strcmp(path_parts[i], "."))
5418 else if (!strcmp(path_parts[i], "..")) {
5423 else /* Remove the last inserted element */
5425 g_slist_delete_link(canonical_parts,
5430 canonical_parts = g_slist_prepend(canonical_parts,
5431 g_strdup(path_parts[i]));
5433 tmp_path = canonical_list_to_file(canonical_parts);
5435 if(g_stat(tmp_path, &st) < 0) {
5436 if (errno == ENOENT) {
5438 follow_symlinks = FALSE;
5442 slist_free_strings_full(canonical_parts);
5443 g_strfreev(path_parts);
5449 if (follow_symlinks && g_file_test(tmp_path, G_FILE_TEST_IS_SYMLINK)) {
5450 GError *error = NULL;
5451 gchar *target = g_file_read_link(tmp_path, &error);
5453 if (!g_path_is_absolute(target)) {
5454 /* remove the last inserted element */
5456 g_slist_delete_link(canonical_parts,
5458 /* add the target */
5459 canonical_parts = g_slist_prepend(canonical_parts,
5463 /* and get the new target */
5464 target = canonical_list_to_file(canonical_parts);
5467 /* restart from absolute target */
5468 slist_free_strings_full(canonical_parts);
5469 canonical_parts = NULL;
5471 canonical_parts = cm_split_path(target, depth + 1);
5473 g_error_free(error);
5474 if (canonical_parts == NULL) {
5476 g_strfreev(path_parts);
5485 g_strfreev(path_parts);
5486 return canonical_parts;
5490 * Canonicalize a filename, resolving symlinks along the way.
5491 * Returns a negative errno in case of error.
5493 int cm_canonicalize_filename(const gchar *filename, gchar **canonical_name) {
5494 GSList *canonical_parts;
5495 gboolean is_absolute;
5497 if (filename == NULL)
5499 if (canonical_name == NULL)
5501 *canonical_name = NULL;
5503 is_absolute = g_path_is_absolute(filename);
5505 /* Always work on absolute filenames. */
5506 gchar *cur = g_get_current_dir();
5507 gchar *absolute_filename = g_strconcat(cur, G_DIR_SEPARATOR_S,
5510 canonical_parts = cm_split_path(absolute_filename, 0);
5511 g_free(absolute_filename);
5514 canonical_parts = cm_split_path(filename, 0);
5516 if (canonical_parts == NULL)
5519 *canonical_name = canonical_list_to_file(canonical_parts);
5520 slist_free_strings_full(canonical_parts);
5524 /* Returns a decoded base64 string, guaranteed to be null-terminated. */
5525 guchar *g_base64_decode_zero(const gchar *text, gsize *out_len)
5527 gchar *tmp = g_base64_decode(text, out_len);
5528 gchar *out = g_strndup(tmp, *out_len);
5532 if (strlen(out) != *out_len) {
5533 g_warning ("strlen(out) %zd != *out_len %" G_GSIZE_FORMAT, strlen(out), *out_len);
5539 #if !GLIB_CHECK_VERSION(2, 30, 0)
5542 * @str: a UTF-8 encoded string
5543 * @start_pos: a character offset within @str
5544 * @end_pos: another character offset within @str
5546 * Copies a substring out of a UTF-8 encoded string.
5547 * The substring will contain @end_pos - @start_pos
5550 * Returns: a newly allocated copy of the requested
5551 * substring. Free with g_free() when no longer needed.
5556 g_utf8_substring (const gchar *str,
5560 gchar *start, *end, *out;
5562 start = g_utf8_offset_to_pointer (str, start_pos);
5563 end = g_utf8_offset_to_pointer (start, end_pos - start_pos);
5565 out = g_malloc (end - start + 1);
5566 memcpy (out, start, end - start);
5567 out[end - start] = 0;