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;
2402 if (numberlist == NULL)
2405 prev_dir = g_get_current_dir();
2407 if (g_chdir(dir) < 0) {
2408 FILE_OP_ERROR(dir, "chdir");
2413 if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2414 FILE_OP_ERROR(dir, "opendir");
2419 wanted_files = g_hash_table_new(g_direct_hash, g_direct_equal);
2420 for (cur = numberlist; cur != NULL; cur = cur->next) {
2421 /* numberlist->data is expected to be GINT_TO_POINTER */
2422 g_hash_table_insert(wanted_files, cur->data, GINT_TO_POINTER(1));
2425 while ((dir_name = g_dir_read_name(dp)) != NULL) {
2426 file_no = to_number(dir_name);
2427 if (is_dir_exist(dir_name))
2429 if (file_no > 0 && g_hash_table_lookup(wanted_files, GINT_TO_POINTER(file_no)) == NULL) {
2430 debug_print("removing unwanted file %d from %s\n", file_no, dir);
2431 if (is_dir_exist(dir_name)) {
2432 gchar *dot_file = g_strdup_printf(".%s", dir_name);
2433 if (is_file_exist(dot_file) && claws_unlink(dot_file) < 0) {
2434 FILE_OP_ERROR(dot_file, "unlink");
2439 if (claws_unlink(dir_name) < 0)
2440 FILE_OP_ERROR(dir_name, "unlink");
2445 g_hash_table_destroy(wanted_files);
2447 if (g_chdir(prev_dir) < 0) {
2448 FILE_OP_ERROR(prev_dir, "chdir");
2458 gint remove_all_numbered_files(const gchar *dir)
2460 return remove_numbered_files(dir, 0, UINT_MAX);
2463 gint remove_dir_recursive(const gchar *dir)
2467 const gchar *dir_name;
2470 if (g_stat(dir, &s) < 0) {
2471 FILE_OP_ERROR(dir, "stat");
2472 if (ENOENT == errno) return 0;
2476 if (!S_ISDIR(s.st_mode)) {
2477 if (claws_unlink(dir) < 0) {
2478 FILE_OP_ERROR(dir, "unlink");
2485 prev_dir = g_get_current_dir();
2486 /* g_print("prev_dir = %s\n", prev_dir); */
2488 if (!path_cmp(prev_dir, dir)) {
2490 if (g_chdir("..") < 0) {
2491 FILE_OP_ERROR(dir, "chdir");
2494 prev_dir = g_get_current_dir();
2497 if (g_chdir(dir) < 0) {
2498 FILE_OP_ERROR(dir, "chdir");
2503 if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2504 g_warning("failed to open directory: %s", dir);
2510 /* remove all files in the directory */
2511 while ((dir_name = g_dir_read_name(dp)) != NULL) {
2512 /* g_print("removing %s\n", dir_name); */
2514 if (is_dir_exist(dir_name)) {
2517 if ((ret = remove_dir_recursive(dir_name)) < 0) {
2518 g_warning("can't remove directory: %s", dir_name);
2522 if (claws_unlink(dir_name) < 0)
2523 FILE_OP_ERROR(dir_name, "unlink");
2529 if (g_chdir(prev_dir) < 0) {
2530 FILE_OP_ERROR(prev_dir, "chdir");
2537 if (g_rmdir(dir) < 0) {
2538 FILE_OP_ERROR(dir, "rmdir");
2545 gint rename_force(const gchar *oldpath, const gchar *newpath)
2548 if (!is_file_entry_exist(oldpath)) {
2552 if (is_file_exist(newpath)) {
2553 if (claws_unlink(newpath) < 0)
2554 FILE_OP_ERROR(newpath, "unlink");
2557 return g_rename(oldpath, newpath);
2561 * Append src file body to the tail of dest file.
2562 * Now keep_backup has no effects.
2564 gint append_file(const gchar *src, const gchar *dest, gboolean keep_backup)
2566 FILE *src_fp, *dest_fp;
2570 gboolean err = FALSE;
2572 if ((src_fp = g_fopen(src, "rb")) == NULL) {
2573 FILE_OP_ERROR(src, "g_fopen");
2577 if ((dest_fp = g_fopen(dest, "ab")) == NULL) {
2578 FILE_OP_ERROR(dest, "g_fopen");
2583 if (change_file_mode_rw(dest_fp, dest) < 0) {
2584 FILE_OP_ERROR(dest, "chmod");
2585 g_warning("can't change file mode: %s", dest);
2588 while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
2589 if (n_read < sizeof(buf) && ferror(src_fp))
2591 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2592 g_warning("writing to %s failed.", dest);
2600 if (ferror(src_fp)) {
2601 FILE_OP_ERROR(src, "fread");
2605 if (fclose(dest_fp) == EOF) {
2606 FILE_OP_ERROR(dest, "fclose");
2618 gint copy_file(const gchar *src, const gchar *dest, gboolean keep_backup)
2620 FILE *src_fp, *dest_fp;
2623 gchar *dest_bak = NULL;
2624 gboolean err = FALSE;
2626 if ((src_fp = g_fopen(src, "rb")) == NULL) {
2627 FILE_OP_ERROR(src, "g_fopen");
2630 if (is_file_exist(dest)) {
2631 dest_bak = g_strconcat(dest, ".bak", NULL);
2632 if (rename_force(dest, dest_bak) < 0) {
2633 FILE_OP_ERROR(dest, "rename");
2640 if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2641 FILE_OP_ERROR(dest, "g_fopen");
2644 if (rename_force(dest_bak, dest) < 0)
2645 FILE_OP_ERROR(dest_bak, "rename");
2651 if (change_file_mode_rw(dest_fp, dest) < 0) {
2652 FILE_OP_ERROR(dest, "chmod");
2653 g_warning("can't change file mode: %s", dest);
2656 while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
2657 if (n_read < sizeof(buf) && ferror(src_fp))
2659 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2660 g_warning("writing to %s failed.", dest);
2665 if (rename_force(dest_bak, dest) < 0)
2666 FILE_OP_ERROR(dest_bak, "rename");
2673 if (ferror(src_fp)) {
2674 FILE_OP_ERROR(src, "fread");
2678 if (fclose(dest_fp) == EOF) {
2679 FILE_OP_ERROR(dest, "fclose");
2686 if (rename_force(dest_bak, dest) < 0)
2687 FILE_OP_ERROR(dest_bak, "rename");
2693 if (keep_backup == FALSE && dest_bak)
2694 claws_unlink(dest_bak);
2701 gint move_file(const gchar *src, const gchar *dest, gboolean overwrite)
2703 if (overwrite == FALSE && is_file_exist(dest)) {
2704 g_warning("move_file(): file %s already exists.", dest);
2708 if (rename_force(src, dest) == 0) return 0;
2710 if (EXDEV != errno) {
2711 FILE_OP_ERROR(src, "rename");
2715 if (copy_file(src, dest, FALSE) < 0) return -1;
2722 gint copy_file_part_to_fp(FILE *fp, off_t offset, size_t length, FILE *dest_fp)
2725 gint bytes_left, to_read;
2728 if (fseek(fp, offset, SEEK_SET) < 0) {
2733 bytes_left = length;
2734 to_read = MIN(bytes_left, sizeof(buf));
2736 while ((n_read = fread(buf, sizeof(gchar), to_read, fp)) > 0) {
2737 if (n_read < to_read && ferror(fp))
2739 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2742 bytes_left -= n_read;
2743 if (bytes_left == 0)
2745 to_read = MIN(bytes_left, sizeof(buf));
2756 gint copy_file_part(FILE *fp, off_t offset, size_t length, const gchar *dest)
2759 gboolean err = FALSE;
2761 if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2762 FILE_OP_ERROR(dest, "g_fopen");
2766 if (change_file_mode_rw(dest_fp, dest) < 0) {
2767 FILE_OP_ERROR(dest, "chmod");
2768 g_warning("can't change file mode: %s", dest);
2771 if (copy_file_part_to_fp(fp, offset, length, dest_fp) < 0)
2774 if (!err && fclose(dest_fp) == EOF) {
2775 FILE_OP_ERROR(dest, "fclose");
2780 g_warning("writing to %s failed.", dest);
2788 /* convert line endings into CRLF. If the last line doesn't end with
2789 * linebreak, add it.
2791 gchar *canonicalize_str(const gchar *str)
2797 for (p = str; *p != '\0'; ++p) {
2804 if (p == str || *(p - 1) != '\n')
2807 out = outp = g_malloc(new_len + 1);
2808 for (p = str; *p != '\0'; ++p) {
2815 if (p == str || *(p - 1) != '\n') {
2824 gint canonicalize_file(const gchar *src, const gchar *dest)
2826 FILE *src_fp, *dest_fp;
2827 gchar buf[BUFFSIZE];
2829 gboolean err = FALSE;
2830 gboolean last_linebreak = FALSE;
2832 if (src == NULL || dest == NULL)
2835 if ((src_fp = g_fopen(src, "rb")) == NULL) {
2836 FILE_OP_ERROR(src, "g_fopen");
2840 if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2841 FILE_OP_ERROR(dest, "g_fopen");
2846 if (change_file_mode_rw(dest_fp, dest) < 0) {
2847 FILE_OP_ERROR(dest, "chmod");
2848 g_warning("can't change file mode: %s", dest);
2851 while (fgets(buf, sizeof(buf), src_fp) != NULL) {
2855 if (len == 0) break;
2856 last_linebreak = FALSE;
2858 if (buf[len - 1] != '\n') {
2859 last_linebreak = TRUE;
2860 r = fputs(buf, dest_fp);
2861 } else if (len > 1 && buf[len - 1] == '\n' && buf[len - 2] == '\r') {
2862 r = fputs(buf, dest_fp);
2865 r = fwrite(buf, 1, len - 1, dest_fp);
2870 r = fputs("\r\n", dest_fp);
2874 g_warning("writing to %s failed.", dest);
2882 if (last_linebreak == TRUE) {
2883 if (fputs("\r\n", dest_fp) == EOF)
2887 if (ferror(src_fp)) {
2888 FILE_OP_ERROR(src, "fgets");
2892 if (fclose(dest_fp) == EOF) {
2893 FILE_OP_ERROR(dest, "fclose");
2905 gint canonicalize_file_replace(const gchar *file)
2909 tmp_file = get_tmp_file();
2911 if (canonicalize_file(file, tmp_file) < 0) {
2916 if (move_file(tmp_file, file, TRUE) < 0) {
2917 g_warning("can't replace file: %s", file);
2918 claws_unlink(tmp_file);
2927 gchar *normalize_newlines(const gchar *str)
2932 out = outp = g_malloc(strlen(str) + 1);
2933 for (p = str; *p != '\0'; ++p) {
2935 if (*(p + 1) != '\n')
2946 gchar *get_outgoing_rfc2822_str(FILE *fp)
2948 gchar buf[BUFFSIZE];
2952 str = g_string_new(NULL);
2954 /* output header part */
2955 while (fgets(buf, sizeof(buf), fp) != NULL) {
2957 if (!g_ascii_strncasecmp(buf, "Bcc:", 4)) {
2964 else if (next != ' ' && next != '\t') {
2968 if (fgets(buf, sizeof(buf), fp) == NULL)
2972 g_string_append(str, buf);
2973 g_string_append(str, "\r\n");
2979 /* output body part */
2980 while (fgets(buf, sizeof(buf), fp) != NULL) {
2983 g_string_append_c(str, '.');
2984 g_string_append(str, buf);
2985 g_string_append(str, "\r\n");
2989 g_string_free(str, FALSE);
2995 * Create a new boundary in a way that it is very unlikely that this
2996 * will occur in the following text. It would be easy to ensure
2997 * uniqueness if everything is either quoted-printable or base64
2998 * encoded (note that conversion is allowed), but because MIME bodies
2999 * may be nested, it may happen that the same boundary has already
3002 * boundary := 0*69<bchars> bcharsnospace
3003 * bchars := bcharsnospace / " "
3004 * bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" /
3005 * "+" / "_" / "," / "-" / "." /
3006 * "/" / ":" / "=" / "?"
3008 * some special characters removed because of buggy MTAs
3011 gchar *generate_mime_boundary(const gchar *prefix)
3013 static gchar tbl[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
3014 "abcdefghijklmnopqrstuvwxyz"
3019 for (i = 0; i < sizeof(buf_uniq) - 1; i++)
3020 buf_uniq[i] = tbl[g_random_int_range(0, sizeof(tbl) - 1)];
3023 return g_strdup_printf("%s_/%s", prefix ? prefix : "MP",
3027 gint change_file_mode_rw(FILE *fp, const gchar *file)
3030 return fchmod(fileno(fp), S_IRUSR|S_IWUSR);
3032 return g_chmod(file, S_IRUSR|S_IWUSR);
3036 FILE *my_tmpfile(void)
3038 const gchar suffix[] = ".XXXXXX";
3039 const gchar *tmpdir;
3041 const gchar *progname;
3050 tmpdir = get_tmp_dir();
3051 tmplen = strlen(tmpdir);
3052 progname = g_get_prgname();
3053 if (progname == NULL)
3054 progname = "claws-mail";
3055 proglen = strlen(progname);
3056 Xalloca(fname, tmplen + 1 + proglen + sizeof(suffix),
3059 memcpy(fname, tmpdir, tmplen);
3060 fname[tmplen] = G_DIR_SEPARATOR;
3061 memcpy(fname + tmplen + 1, progname, proglen);
3062 memcpy(fname + tmplen + 1 + proglen, suffix, sizeof(suffix));
3064 fd = g_mkstemp(fname);
3069 claws_unlink(fname);
3071 /* verify that we can write in the file after unlinking */
3072 if (write(fd, buf, 1) < 0) {
3079 fp = fdopen(fd, "w+b");
3090 FILE *get_tmpfile_in_dir(const gchar *dir, gchar **filename)
3093 *filename = g_strdup_printf("%s%cclaws.XXXXXX", dir, G_DIR_SEPARATOR);
3094 fd = g_mkstemp(*filename);
3097 return fdopen(fd, "w+");
3100 FILE *str_open_as_stream(const gchar *str)
3105 cm_return_val_if_fail(str != NULL, NULL);
3109 FILE_OP_ERROR("str_open_as_stream", "my_tmpfile");
3114 if (len == 0) return fp;
3116 if (fwrite(str, 1, len, fp) != len) {
3117 FILE_OP_ERROR("str_open_as_stream", "fwrite");
3126 gint str_write_to_file(const gchar *str, const gchar *file)
3131 cm_return_val_if_fail(str != NULL, -1);
3132 cm_return_val_if_fail(file != NULL, -1);
3134 if ((fp = g_fopen(file, "wb")) == NULL) {
3135 FILE_OP_ERROR(file, "g_fopen");
3145 if (fwrite(str, 1, len, fp) != len) {
3146 FILE_OP_ERROR(file, "fwrite");
3152 if (fclose(fp) == EOF) {
3153 FILE_OP_ERROR(file, "fclose");
3161 static gchar *file_read_stream_to_str_full(FILE *fp, gboolean recode)
3168 cm_return_val_if_fail(fp != NULL, NULL);
3170 array = g_byte_array_new();
3172 while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
3173 if (n_read < sizeof(buf) && ferror(fp))
3175 g_byte_array_append(array, buf, n_read);
3179 FILE_OP_ERROR("file stream", "fread");
3180 g_byte_array_free(array, TRUE);
3185 g_byte_array_append(array, buf, 1);
3186 str = (gchar *)array->data;
3187 g_byte_array_free(array, FALSE);
3189 if (recode && !g_utf8_validate(str, -1, NULL)) {
3190 const gchar *src_codeset, *dest_codeset;
3192 src_codeset = conv_get_locale_charset_str();
3193 dest_codeset = CS_UTF_8;
3194 tmp = conv_codeset_strdup(str, src_codeset, dest_codeset);
3202 static gchar *file_read_to_str_full(const gchar *file, gboolean recode)
3209 struct timeval timeout = {1, 0};
3214 cm_return_val_if_fail(file != NULL, NULL);
3216 if (g_stat(file, &s) != 0) {
3217 FILE_OP_ERROR(file, "stat");
3220 if (S_ISDIR(s.st_mode)) {
3221 g_warning("%s: is a directory", file);
3226 fp = g_fopen (file, "rb");
3228 FILE_OP_ERROR(file, "open");
3232 /* test whether the file is readable without blocking */
3233 fd = g_open(file, O_RDONLY | O_NONBLOCK, 0);
3235 FILE_OP_ERROR(file, "open");
3242 /* allow for one second */
3243 err = select(fd+1, &fds, NULL, NULL, &timeout);
3244 if (err <= 0 || !FD_ISSET(fd, &fds)) {
3246 FILE_OP_ERROR(file, "select");
3248 g_warning("%s: doesn't seem readable", file);
3254 /* Now clear O_NONBLOCK */
3255 if ((fflags = fcntl(fd, F_GETFL)) < 0) {
3256 FILE_OP_ERROR(file, "fcntl (F_GETFL)");
3260 if (fcntl(fd, F_SETFL, (fflags & ~O_NONBLOCK)) < 0) {
3261 FILE_OP_ERROR(file, "fcntl (F_SETFL)");
3266 /* get the FILE pointer */
3267 fp = fdopen(fd, "rb");
3270 FILE_OP_ERROR(file, "fdopen");
3271 close(fd); /* if fp isn't NULL, we'll use fclose instead! */
3276 str = file_read_stream_to_str_full(fp, recode);
3283 gchar *file_read_to_str(const gchar *file)
3285 return file_read_to_str_full(file, TRUE);
3287 gchar *file_read_stream_to_str(FILE *fp)
3289 return file_read_stream_to_str_full(fp, TRUE);
3292 gchar *file_read_to_str_no_recode(const gchar *file)
3294 return file_read_to_str_full(file, FALSE);
3296 gchar *file_read_stream_to_str_no_recode(FILE *fp)
3298 return file_read_stream_to_str_full(fp, FALSE);
3301 char *fgets_crlf(char *buf, int size, FILE *stream)
3303 gboolean is_cr = FALSE;
3304 gboolean last_was_cr = FALSE;
3309 while (--size > 0 && (c = getc(stream)) != EOF)
3312 is_cr = (c == '\r');
3322 last_was_cr = is_cr;
3324 if (c == EOF && cs == buf)
3332 static gint execute_async(gchar *const argv[])
3334 cm_return_val_if_fail(argv != NULL && argv[0] != NULL, -1);
3336 if (g_spawn_async(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH,
3337 NULL, NULL, NULL, FALSE) == FALSE) {
3338 g_warning("couldn't execute command: %s", argv[0]);
3345 static gint execute_sync(gchar *const argv[])
3349 cm_return_val_if_fail(argv != NULL && argv[0] != NULL, -1);
3352 if (g_spawn_sync(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH,
3353 NULL, NULL, NULL, NULL, &status, NULL) == FALSE) {
3354 g_warning("couldn't execute command: %s", argv[0]);
3358 if (WIFEXITED(status))
3359 return WEXITSTATUS(status);
3363 if (g_spawn_sync(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH|
3364 G_SPAWN_CHILD_INHERITS_STDIN|G_SPAWN_LEAVE_DESCRIPTORS_OPEN,
3365 NULL, NULL, NULL, NULL, &status, NULL) == FALSE) {
3366 g_warning("couldn't execute command: %s", argv[0]);
3374 gint execute_command_line(const gchar *cmdline, gboolean async)
3379 debug_print("execute_command_line(): executing: %s\n", cmdline?cmdline:"(null)");
3381 argv = strsplit_with_quote(cmdline, " ", 0);
3384 ret = execute_async(argv);
3386 ret = execute_sync(argv);
3393 gchar *get_command_output(const gchar *cmdline)
3395 gchar *child_stdout;
3398 cm_return_val_if_fail(cmdline != NULL, NULL);
3400 debug_print("get_command_output(): executing: %s\n", cmdline);
3402 if (g_spawn_command_line_sync(cmdline, &child_stdout, NULL, &status,
3404 g_warning("couldn't execute command: %s", cmdline);
3408 return child_stdout;
3411 static gint is_unchanged_uri_char(char c)
3422 static void encode_uri(gchar *encoded_uri, gint bufsize, const gchar *uri)
3428 for(i = 0; i < strlen(uri) ; i++) {
3429 if (is_unchanged_uri_char(uri[i])) {
3430 if (k + 2 >= bufsize)
3432 encoded_uri[k++] = uri[i];
3435 char * hexa = "0123456789ABCDEF";
3437 if (k + 4 >= bufsize)
3439 encoded_uri[k++] = '%';
3440 encoded_uri[k++] = hexa[uri[i] / 16];
3441 encoded_uri[k++] = hexa[uri[i] % 16];
3447 gint open_uri(const gchar *uri, const gchar *cmdline)
3451 gchar buf[BUFFSIZE];
3453 gchar encoded_uri[BUFFSIZE];
3454 cm_return_val_if_fail(uri != NULL, -1);
3456 /* an option to choose whether to use encode_uri or not ? */
3457 encode_uri(encoded_uri, BUFFSIZE, uri);
3460 (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
3461 !strchr(p + 2, '%'))
3462 g_snprintf(buf, sizeof(buf), cmdline, encoded_uri);
3465 g_warning("Open URI command-line is invalid "
3466 "(there must be only one '%%s'): %s",
3468 g_snprintf(buf, sizeof(buf), DEFAULT_BROWSER_CMD, encoded_uri);
3471 execute_command_line(buf, TRUE);
3473 ShellExecute(NULL, "open", uri, NULL, NULL, SW_SHOW);
3478 gint open_txt_editor(const gchar *filepath, const gchar *cmdline)
3480 gchar buf[BUFFSIZE];
3483 cm_return_val_if_fail(filepath != NULL, -1);
3486 (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
3487 !strchr(p + 2, '%'))
3488 g_snprintf(buf, sizeof(buf), cmdline, filepath);
3491 g_warning("Open Text Editor command-line is invalid "
3492 "(there must be only one '%%s'): %s",
3494 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, filepath);
3497 execute_command_line(buf, TRUE);
3502 time_t remote_tzoffset_sec(const gchar *zone)
3504 static gchar ustzstr[] = "PSTPDTMSTMDTCSTCDTESTEDT";
3510 time_t remoteoffset;
3512 strncpy(zone3, zone, 3);
3516 if (sscanf(zone, "%c%d", &c, &offset) == 2 &&
3517 (c == '+' || c == '-')) {
3518 remoteoffset = ((offset / 100) * 60 + (offset % 100)) * 60;
3520 remoteoffset = -remoteoffset;
3521 } else if (!strncmp(zone, "UT" , 2) ||
3522 !strncmp(zone, "GMT", 2)) {
3524 } else if (strlen(zone3) == 3) {
3525 for (p = ustzstr; *p != '\0'; p += 3) {
3526 if (!g_ascii_strncasecmp(p, zone3, 3)) {
3527 iustz = ((gint)(p - ustzstr) / 3 + 1) / 2 - 8;
3528 remoteoffset = iustz * 3600;
3534 } else if (strlen(zone3) == 1) {
3536 case 'Z': remoteoffset = 0; break;
3537 case 'A': remoteoffset = -1; break;
3538 case 'B': remoteoffset = -2; break;
3539 case 'C': remoteoffset = -3; break;
3540 case 'D': remoteoffset = -4; break;
3541 case 'E': remoteoffset = -5; break;
3542 case 'F': remoteoffset = -6; break;
3543 case 'G': remoteoffset = -7; break;
3544 case 'H': remoteoffset = -8; break;
3545 case 'I': remoteoffset = -9; break;
3546 case 'K': remoteoffset = -10; break; /* J is not used */
3547 case 'L': remoteoffset = -11; break;
3548 case 'M': remoteoffset = -12; break;
3549 case 'N': remoteoffset = 1; break;
3550 case 'O': remoteoffset = 2; break;
3551 case 'P': remoteoffset = 3; break;
3552 case 'Q': remoteoffset = 4; break;
3553 case 'R': remoteoffset = 5; break;
3554 case 'S': remoteoffset = 6; break;
3555 case 'T': remoteoffset = 7; break;
3556 case 'U': remoteoffset = 8; break;
3557 case 'V': remoteoffset = 9; break;
3558 case 'W': remoteoffset = 10; break;
3559 case 'X': remoteoffset = 11; break;
3560 case 'Y': remoteoffset = 12; break;
3561 default: remoteoffset = 0; break;
3563 remoteoffset = remoteoffset * 3600;
3567 return remoteoffset;
3570 time_t tzoffset_sec(time_t *now)
3574 struct tm buf1, buf2;
3576 if (now && *now < 0)
3579 gmt = *gmtime_r(now, &buf1);
3580 lt = localtime_r(now, &buf2);
3582 off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
3584 if (lt->tm_year < gmt.tm_year)
3586 else if (lt->tm_year > gmt.tm_year)
3588 else if (lt->tm_yday < gmt.tm_yday)
3590 else if (lt->tm_yday > gmt.tm_yday)
3593 if (off >= 24 * 60) /* should be impossible */
3594 off = 23 * 60 + 59; /* if not, insert silly value */
3595 if (off <= -24 * 60)
3596 off = -(23 * 60 + 59);
3601 /* calculate timezone offset */
3602 gchar *tzoffset(time_t *now)
3604 static gchar offset_string[6];
3608 struct tm buf1, buf2;
3610 if (now && *now < 0)
3613 gmt = *gmtime_r(now, &buf1);
3614 lt = localtime_r(now, &buf2);
3616 off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
3618 if (lt->tm_year < gmt.tm_year)
3620 else if (lt->tm_year > gmt.tm_year)
3622 else if (lt->tm_yday < gmt.tm_yday)
3624 else if (lt->tm_yday > gmt.tm_yday)
3632 if (off >= 24 * 60) /* should be impossible */
3633 off = 23 * 60 + 59; /* if not, insert silly value */
3635 sprintf(offset_string, "%c%02d%02d", sign, off / 60, off % 60);
3637 return offset_string;
3640 void get_rfc822_date(gchar *buf, gint len)
3644 gchar day[4], mon[4];
3645 gint dd, hh, mm, ss, yyyy;
3647 gchar buf2[BUFFSIZE];
3650 lt = localtime_r(&t, &buf1);
3652 sscanf(asctime_r(lt, buf2), "%3s %3s %d %d:%d:%d %d\n",
3653 day, mon, &dd, &hh, &mm, &ss, &yyyy);
3655 g_snprintf(buf, len, "%s, %d %s %d %02d:%02d:%02d %s",
3656 day, dd, mon, yyyy, hh, mm, ss, tzoffset(&t));
3659 void debug_set_mode(gboolean mode)
3664 gboolean debug_get_mode(void)
3669 void debug_print_real(const gchar *format, ...)
3672 gchar buf[BUFFSIZE];
3674 if (!debug_mode) return;
3676 va_start(args, format);
3677 g_vsnprintf(buf, sizeof(buf), format, args);
3684 const char * debug_srcname(const char *file)
3686 const char *s = strrchr (file, '/');
3691 void * subject_table_lookup(GHashTable *subject_table, gchar * subject)
3693 if (subject == NULL)
3696 subject += subject_get_prefix_length(subject);
3698 return g_hash_table_lookup(subject_table, subject);
3701 void subject_table_insert(GHashTable *subject_table, gchar * subject,
3704 if (subject == NULL || *subject == 0)
3706 subject += subject_get_prefix_length(subject);
3707 g_hash_table_insert(subject_table, subject, data);
3710 void subject_table_remove(GHashTable *subject_table, gchar * subject)
3712 if (subject == NULL)
3715 subject += subject_get_prefix_length(subject);
3716 g_hash_table_remove(subject_table, subject);
3720 static regex_t u_regex;
3721 static gboolean u_init_;
3724 void utils_free_regex(void)
3735 *\brief Check if a string is prefixed with known (combinations)
3736 * of prefixes. The function assumes that each prefix
3737 * is terminated by zero or exactly _one_ space.
3739 *\param str String to check for a prefixes
3741 *\return int Number of chars in the prefix that should be skipped
3742 * for a "clean" subject line. If no prefix was found, 0
3745 int subject_get_prefix_length(const gchar *subject)
3748 /*!< Array with allowable reply prefixes regexps. */
3749 static const gchar * const prefixes[] = {
3750 "Re\\:", /* "Re:" */
3751 "Re\\[[1-9][0-9]*\\]\\:", /* "Re[XXX]:" (non-conforming news mail clients) */
3752 "Antw\\:", /* "Antw:" (Dutch / German Outlook) */
3753 "Aw\\:", /* "Aw:" (German) */
3754 "Antwort\\:", /* "Antwort:" (German Lotus Notes) */
3755 "Res\\:", /* "Res:" (Spanish/Brazilian Outlook) */
3756 "Fw\\:", /* "Fw:" Forward */
3757 "Fwd\\:", /* "Fwd:" Forward */
3758 "Enc\\:", /* "Enc:" Forward (Brazilian Outlook) */
3759 "Odp\\:", /* "Odp:" Re (Polish Outlook) */
3760 "Rif\\:", /* "Rif:" (Italian Outlook) */
3761 "Sv\\:", /* "Sv" (Norwegian) */
3762 "Vs\\:", /* "Vs" (Norwegian) */
3763 "Ad\\:", /* "Ad" (Norwegian) */
3764 "\347\255\224\345\244\215\\:", /* "Re" (Chinese, UTF-8) */
3765 "R\303\251f\\. \\:", /* "R�f. :" (French Lotus Notes) */
3766 "Re \\:", /* "Re :" (French Yahoo Mail) */
3769 const int PREFIXES = sizeof prefixes / sizeof prefixes[0];
3773 if (!subject) return 0;
3774 if (!*subject) return 0;
3777 GString *s = g_string_new("");
3779 for (n = 0; n < PREFIXES; n++)
3780 /* Terminate each prefix regexpression by a
3781 * "\ ?" (zero or ONE space), and OR them */
3782 g_string_append_printf(s, "(%s\\ ?)%s",
3787 g_string_prepend(s, "(");
3788 g_string_append(s, ")+"); /* match at least once */
3789 g_string_prepend(s, "^\\ *"); /* from beginning of line */
3792 /* We now have something like "^\ *((PREFIX1\ ?)|(PREFIX2\ ?))+"
3793 * TODO: Should this be "^\ *(((PREFIX1)|(PREFIX2))\ ?)+" ??? */
3794 if (regcomp(&u_regex, s->str, REG_EXTENDED | REG_ICASE)) {
3795 debug_print("Error compiling regexp %s\n", s->str);
3796 g_string_free(s, TRUE);
3800 g_string_free(s, TRUE);
3804 if (!regexec(&u_regex, subject, 1, &pos, 0) && pos.rm_so != -1)
3809 /*!< Array with allowable reply prefixes regexps. */
3810 static const gchar * const prefixes[] = {
3812 "antw:", /* "Antw:" (Dutch / German Outlook) */
3813 "aw:", /* "Aw:" (German) */
3814 "antwort:", /* "Antwort:" (German Lotus Notes) */
3815 "res:", /* "Res:" (Spanish/Brazilian Outlook) */
3816 "fw:", /* "Fw:" Forward */
3817 "fwd:", /* "Fwd:" Forward */
3818 "enc:", /* "Enc:" Forward (Brazilian Outlook) */
3819 "odp:", /* "Odp:" Re (Polish Outlook) */
3820 "rif:", /* "Rif:" (Italian Outlook) */
3821 "sv:", /* "Sv" (Norwegian) */
3822 "vs:", /* "Vs" (Norwegian) */
3823 "ad:", /* "Ad" (Norwegian) */
3824 "R\303\251f. :", /* "R�f. :" (French Lotus Notes) */
3825 "Re :", /* "Re :" (French Yahoo Mail) */
3828 const int PREFIXES = sizeof prefixes / sizeof prefixes[0];
3831 if (!subject) return 0;
3832 if (!*subject) return 0;
3834 for (n = 0; n < PREFIXES; n++) {
3835 int len = strlen(prefixes[n]);
3836 if (!strncasecmp(subject, prefixes[n], len)) {
3837 if (subject[len] == ' ')
3846 static guint g_stricase_hash(gconstpointer gptr)
3848 guint hash_result = 0;
3851 for (str = gptr; str && *str; str++) {
3852 hash_result += toupper(*str);
3858 static gint g_stricase_equal(gconstpointer gptr1, gconstpointer gptr2)
3860 const char *str1 = gptr1;
3861 const char *str2 = gptr2;
3863 return !strcasecmp(str1, str2);
3866 gint g_int_compare(gconstpointer a, gconstpointer b)
3868 return GPOINTER_TO_INT(a) - GPOINTER_TO_INT(b);
3871 gchar *generate_msgid(gchar *buf, gint len, gchar *user_addr)
3879 lt = localtime_r(&t, &buft);
3881 if (user_addr != NULL)
3882 addr = g_strdup_printf(".%s", user_addr);
3883 else if (strlen(buf) != 0)
3884 addr = g_strdup_printf("@%s", buf);
3886 addr = g_strdup_printf("@%s", get_domain_name());
3888 /* Replace all @ but the last one in addr, with underscores.
3889 * RFC 2822 States that msg-id syntax only allows one @.
3891 while (strchr(addr, '@') != NULL && strchr(addr, '@') != strrchr(addr, '@'))
3892 *(strchr(addr, '@')) = '_';
3894 g_snprintf(buf, len, "%04d%02d%02d%02d%02d%02d.%08x%s",
3895 lt->tm_year + 1900, lt->tm_mon + 1,
3896 lt->tm_mday, lt->tm_hour,
3897 lt->tm_min, lt->tm_sec,
3898 (guint) rand(), addr);
3905 quote_cmd_argument()
3907 return a quoted string safely usable in argument of a command.
3909 code is extracted and adapted from etPan! project -- DINH V. Ho�.
3912 gint quote_cmd_argument(gchar * result, guint size,
3922 for(p = path ; * p != '\0' ; p ++) {
3924 if (isalnum((guchar)*p) || (* p == '/')) {
3925 if (remaining > 0) {
3931 result[size - 1] = '\0';
3936 if (remaining >= 2) {
3944 result[size - 1] = '\0';
3949 if (remaining > 0) {
3953 result[size - 1] = '\0';
3967 static void g_node_map_recursive(GNode *node, gpointer data)
3969 GNodeMapData *mapdata = (GNodeMapData *) data;
3971 GNodeMapData newmapdata;
3974 newdata = mapdata->func(node->data, mapdata->data);
3975 if (newdata != NULL) {
3976 newnode = g_node_new(newdata);
3977 g_node_append(mapdata->parent, newnode);
3979 newmapdata.parent = newnode;
3980 newmapdata.func = mapdata->func;
3981 newmapdata.data = mapdata->data;
3983 g_node_children_foreach(node, G_TRAVERSE_ALL, g_node_map_recursive, &newmapdata);
3987 GNode *g_node_map(GNode *node, GNodeMapFunc func, gpointer data)
3990 GNodeMapData mapdata;
3992 cm_return_val_if_fail(node != NULL, NULL);
3993 cm_return_val_if_fail(func != NULL, NULL);
3995 root = g_node_new(func(node->data, data));
3997 mapdata.parent = root;
3998 mapdata.func = func;
3999 mapdata.data = data;
4001 g_node_children_foreach(node, G_TRAVERSE_ALL, g_node_map_recursive, &mapdata);
4006 #define HEX_TO_INT(val, hex) \
4010 if ('0' <= c && c <= '9') { \
4012 } else if ('a' <= c && c <= 'f') { \
4013 val = c - 'a' + 10; \
4014 } else if ('A' <= c && c <= 'F') { \
4015 val = c - 'A' + 10; \
4021 gboolean get_hex_value(guchar *out, gchar c1, gchar c2)
4028 if (hi == -1 || lo == -1)
4031 *out = (hi << 4) + lo;
4035 #define INT_TO_HEX(hex, val) \
4038 hex = '0' + (val); \
4040 hex = 'A' + (val) - 10; \
4043 void get_hex_str(gchar *out, guchar ch)
4047 INT_TO_HEX(hex, ch >> 4);
4049 INT_TO_HEX(hex, ch & 0x0f);
4055 #define G_PRINT_REF 1 == 1 ? (void) 0 : (void)
4057 #define G_PRINT_REF g_print
4061 *\brief Register ref counted pointer. It is based on GBoxed, so should
4062 * work with anything that uses the GType system. The semantics
4063 * are similar to a C++ auto pointer, with the exception that
4064 * C doesn't have automatic closure (calling destructors) when
4065 * exiting a block scope.
4066 * Use the \ref G_TYPE_AUTO_POINTER macro instead of calling this
4067 * function directly.
4069 *\return GType A GType type.
4071 GType g_auto_pointer_register(void)
4073 static GType auto_pointer_type;
4074 if (!auto_pointer_type)
4076 g_boxed_type_register_static
4077 ("G_TYPE_AUTO_POINTER",
4078 (GBoxedCopyFunc) g_auto_pointer_copy,
4079 (GBoxedFreeFunc) g_auto_pointer_free);
4080 return auto_pointer_type;
4084 *\brief Structure with g_new() allocated pointer guarded by the
4087 typedef struct AutoPointerRef {
4088 void (*free) (gpointer);
4094 *\brief The auto pointer opaque structure that references the
4095 * pointer guard block.
4097 typedef struct AutoPointer {
4098 AutoPointerRef *ref;
4099 gpointer ptr; /*!< access to protected pointer */
4103 *\brief Creates an auto pointer for a g_new()ed pointer. Example:
4107 * ... tell gtk_list_store it should use a G_TYPE_AUTO_POINTER
4108 * ... when assigning, copying and freeing storage elements
4110 * gtk_list_store_new(N_S_COLUMNS,
4111 * G_TYPE_AUTO_POINTER,
4115 * Template *precious_data = g_new0(Template, 1);
4116 * g_pointer protect = g_auto_pointer_new(precious_data);
4118 * gtk_list_store_set(container, &iter,
4122 * ... the gtk_list_store has copied the pointer and
4123 * ... incremented its reference count, we should free
4124 * ... the auto pointer (in C++ a destructor would do
4125 * ... this for us when leaving block scope)
4127 * g_auto_pointer_free(protect);
4129 * ... gtk_list_store_set() now manages the data. When
4130 * ... *explicitly* requesting a pointer from the list
4131 * ... store, don't forget you get a copy that should be
4132 * ... freed with g_auto_pointer_free() eventually.
4136 *\param pointer Pointer to be guarded.
4138 *\return GAuto * Pointer that should be used in containers with
4141 GAuto *g_auto_pointer_new(gpointer p)
4143 AutoPointerRef *ref;
4149 ref = g_new0(AutoPointerRef, 1);
4150 ptr = g_new0(AutoPointer, 1);
4160 G_PRINT_REF ("XXXX ALLOC(%lx)\n", p);
4166 *\brief Allocate an autopointer using the passed \a free function to
4167 * free the guarded pointer
4169 GAuto *g_auto_pointer_new_with_free(gpointer p, GFreeFunc free_)
4176 aptr = g_auto_pointer_new(p);
4177 aptr->ref->free = free_;
4181 gpointer g_auto_pointer_get_ptr(GAuto *auto_ptr)
4183 if (auto_ptr == NULL)
4185 return ((AutoPointer *) auto_ptr)->ptr;
4189 *\brief Copies an auto pointer by. It's mostly not necessary
4190 * to call this function directly, unless you copy/assign
4191 * the guarded pointer.
4193 *\param auto_ptr Auto pointer returned by previous call to
4194 * g_auto_pointer_new_XXX()
4196 *\return gpointer An auto pointer
4198 GAuto *g_auto_pointer_copy(GAuto *auto_ptr)
4201 AutoPointerRef *ref;
4204 if (auto_ptr == NULL)
4209 newp = g_new0(AutoPointer, 1);
4212 newp->ptr = ref->pointer;
4216 G_PRINT_REF ("XXXX COPY(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4222 *\brief Free an auto pointer
4224 void g_auto_pointer_free(GAuto *auto_ptr)
4227 AutoPointerRef *ref;
4229 if (auto_ptr == NULL)
4235 if (--(ref->cnt) == 0) {
4237 G_PRINT_REF ("XXXX FREE(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4239 ref->free(ref->pointer);
4244 G_PRINT_REF ("XXXX DEREF(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4249 void replace_returns(gchar *str)
4254 while (strstr(str, "\n")) {
4255 *strstr(str, "\n") = ' ';
4257 while (strstr(str, "\r")) {
4258 *strstr(str, "\r") = ' ';
4262 /* get_uri_part() - retrieves a URI starting from scanpos.
4263 Returns TRUE if succesful */
4264 gboolean get_uri_part(const gchar *start, const gchar *scanpos,
4265 const gchar **bp, const gchar **ep, gboolean hdr)
4268 gint parenthese_cnt = 0;
4270 cm_return_val_if_fail(start != NULL, FALSE);
4271 cm_return_val_if_fail(scanpos != NULL, FALSE);
4272 cm_return_val_if_fail(bp != NULL, FALSE);
4273 cm_return_val_if_fail(ep != NULL, FALSE);
4277 /* find end point of URI */
4278 for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
4279 if (!g_ascii_isgraph(*(const guchar *)ep_) ||
4280 !IS_ASCII(*(const guchar *)ep_) ||
4281 strchr("[]{}<>\"", *ep_)) {
4283 } else if (strchr("(", *ep_)) {
4285 } else if (strchr(")", *ep_)) {
4286 if (parenthese_cnt > 0)
4293 /* no punctuation at end of string */
4295 /* FIXME: this stripping of trailing punctuations may bite with other URIs.
4296 * should pass some URI type to this function and decide on that whether
4297 * to perform punctuation stripping */
4299 #define IS_REAL_PUNCT(ch) (g_ascii_ispunct(ch) && !strchr("/?=-_)", ch))
4301 for (; ep_ - 1 > scanpos + 1 &&
4302 IS_REAL_PUNCT(*(ep_ - 1));
4306 #undef IS_REAL_PUNCT
4313 gchar *make_uri_string(const gchar *bp, const gchar *ep)
4315 while (bp && *bp && g_ascii_isspace(*bp))
4317 return g_strndup(bp, ep - bp);
4320 /* valid mail address characters */
4321 #define IS_RFC822_CHAR(ch) \
4325 !g_ascii_isspace(ch) && \
4326 !strchr("(),;<>\"", (ch)))
4328 /* alphabet and number within 7bit ASCII */
4329 #define IS_ASCII_ALNUM(ch) (IS_ASCII(ch) && g_ascii_isalnum(ch))
4330 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
4332 static GHashTable *create_domain_tab(void)
4334 static const gchar *toplvl_domains [] = {
4336 "arpa", "coop", "info", "name", "biz", "com", "edu", "gov",
4337 "int", "mil", "net", "org", "ac", "ad", "ae", "af", "ag",
4338 "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au",
4339 "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi",
4340 "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
4341 "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl",
4342 "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz", "de",
4343 "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er",
4344 "es", "et", "eu", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd",
4345 "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
4346 "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
4347 "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir",
4348 "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
4349 "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc",
4350 "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
4351 "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq",
4352 "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na",
4353 "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu",
4354 "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm",
4355 "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru",
4356 "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj",
4357 "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
4358 "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to",
4359 "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um",
4360 "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu",
4361 "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw"
4364 GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
4366 cm_return_val_if_fail(htab, NULL);
4367 for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++)
4368 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
4372 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
4374 const gint MAX_LVL_DOM_NAME_LEN = 6;
4375 gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
4376 const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
4379 if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
4382 for (p = buf; p < m && first < last; *p++ = *first++)
4386 return g_hash_table_lookup(tab, buf) != NULL;
4389 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
4390 gboolean get_email_part(const gchar *start, const gchar *scanpos,
4391 const gchar **bp, const gchar **ep, gboolean hdr)
4393 /* more complex than the uri part because we need to scan back and forward starting from
4394 * the scan position. */
4395 gboolean result = FALSE;
4396 const gchar *bp_ = NULL;
4397 const gchar *ep_ = NULL;
4398 static GHashTable *dom_tab;
4399 const gchar *last_dot = NULL;
4400 const gchar *prelast_dot = NULL;
4401 const gchar *last_tld_char = NULL;
4403 /* the informative part of the email address (describing the name
4404 * of the email address owner) may contain quoted parts. the
4405 * closure stack stores the last encountered quotes. */
4406 gchar closure_stack[128];
4407 gchar *ptr = closure_stack;
4409 cm_return_val_if_fail(start != NULL, FALSE);
4410 cm_return_val_if_fail(scanpos != NULL, FALSE);
4411 cm_return_val_if_fail(bp != NULL, FALSE);
4412 cm_return_val_if_fail(ep != NULL, FALSE);
4415 const gchar *start_quote = NULL;
4416 const gchar *end_quote = NULL;
4418 /* go to the real start */
4419 if (start[0] == ',')
4421 if (start[0] == ';')
4423 while (start[0] == '\n' || start[0] == '\r')
4425 while (start[0] == ' ' || start[0] == '\t')
4430 /* check if there are quotes (to skip , in them) */
4431 if (*start == '"') {
4432 start_quote = start;
4434 end_quote = strstr(start, "\"");
4440 /* skip anything between quotes */
4441 if (start_quote && end_quote) {
4446 /* find end (either , or ; or end of line) */
4447 if (strstr(start, ",") && strstr(start, ";"))
4448 *ep = strstr(start,",") < strstr(start, ";")
4449 ? strstr(start, ",") : strstr(start, ";");
4450 else if (strstr(start, ","))
4451 *ep = strstr(start, ",");
4452 else if (strstr(start, ";"))
4453 *ep = strstr(start, ";");
4455 *ep = start+strlen(start);
4457 /* go back to real start */
4458 if (start_quote && end_quote) {
4459 start = start_quote;
4462 /* check there's still an @ in that, or search
4463 * further if possible */
4464 if (strstr(start, "@") && strstr(start, "@") < *ep)
4466 else if (*ep < start+strlen(start)) {
4469 } else if (start_quote && strstr(start, "\"") && strstr(start, "\"") < *ep) {
4477 dom_tab = create_domain_tab();
4478 cm_return_val_if_fail(dom_tab, FALSE);
4480 /* scan start of address */
4481 for (bp_ = scanpos - 1;
4482 bp_ >= start && IS_RFC822_CHAR(*(const guchar *)bp_); bp_--)
4485 /* TODO: should start with an alnum? */
4487 for (; bp_ < scanpos && !IS_ASCII_ALNUM(*(const guchar *)bp_); bp_++)
4490 if (bp_ != scanpos) {
4491 /* scan end of address */
4492 for (ep_ = scanpos + 1;
4493 *ep_ && IS_RFC822_CHAR(*(const guchar *)ep_); ep_++)
4495 prelast_dot = last_dot;
4497 if (*(last_dot + 1) == '.') {
4498 if (prelast_dot == NULL)
4500 last_dot = prelast_dot;
4505 /* TODO: really should terminate with an alnum? */
4506 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*(const guchar *)ep_);
4511 if (last_dot == NULL)
4513 if (last_dot >= ep_)
4514 last_dot = prelast_dot;
4515 if (last_dot == NULL || (scanpos + 1 >= last_dot))
4519 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
4520 if (*last_tld_char == '?')
4523 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
4530 if (!result) return FALSE;
4532 if (*ep_ && bp_ != start && *(bp_ - 1) == '"' && *(ep_) == '"'
4533 && *(ep_ + 1) == ' ' && *(ep_ + 2) == '<'
4534 && IS_RFC822_CHAR(*(ep_ + 3))) {
4535 /* this informative part with an @ in it is
4536 * followed by the email address */
4539 /* go to matching '>' (or next non-rfc822 char, like \n) */
4540 for (; *ep_ != '>' && *ep != '\0' && IS_RFC822_CHAR(*ep_); ep_++)
4543 /* include the bracket */
4544 if (*ep_ == '>') ep_++;
4546 /* include the leading quote */
4554 /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
4555 if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
4558 /* see if this is <bracketed>; in this case we also scan for the informative part. */
4559 if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
4562 #define FULL_STACK() ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
4563 #define IN_STACK() (ptr > closure_stack)
4564 /* has underrun check */
4565 #define POP_STACK() if(IN_STACK()) --ptr
4566 /* has overrun check */
4567 #define PUSH_STACK(c) if(!FULL_STACK()) *ptr++ = (c); else return TRUE
4568 /* has underrun check */
4569 #define PEEK_STACK() (IN_STACK() ? *(ptr - 1) : 0)
4573 /* scan for the informative part. */
4574 for (bp_ -= 2; bp_ >= start; bp_--) {
4575 /* if closure on the stack keep scanning */
4576 if (PEEK_STACK() == *bp_) {
4580 if (!IN_STACK() && (*bp_ == '\'' || *bp_ == '"')) {
4585 /* if nothing in the closure stack, do the special conditions
4586 * the following if..else expression simply checks whether
4587 * a token is acceptable. if not acceptable, the clause
4588 * should terminate the loop with a 'break' */
4589 if (!PEEK_STACK()) {
4591 && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
4592 && (((bp_ + 1) < ep_) && isalnum(*(bp_ + 1)))) {
4593 /* hyphens are allowed, but only in
4595 } else if (strchr(" \"'", *bp_)) {
4596 /* but anything not being a punctiation
4599 break; /* anything else is rejected */
4606 /* scan forward (should start with an alnum) */
4607 for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
4623 #undef IS_ASCII_ALNUM
4624 #undef IS_RFC822_CHAR
4626 gchar *make_email_string(const gchar *bp, const gchar *ep)
4628 /* returns a mailto: URI; mailto: is also used to detect the
4629 * uri type later on in the button_pressed signal handler */
4633 tmp = g_strndup(bp, ep - bp);
4634 result = g_strconcat("mailto:", tmp, NULL);
4640 gchar *make_http_string(const gchar *bp, const gchar *ep)
4642 /* returns an http: URI; */
4646 while (bp && *bp && g_ascii_isspace(*bp))
4648 tmp = g_strndup(bp, ep - bp);
4649 result = g_strconcat("http://", tmp, NULL);
4655 static gchar *mailcap_get_command_in_file(const gchar *path, const gchar *type, const gchar *file_to_open)
4657 FILE *fp = g_fopen(path, "rb");
4658 gchar buf[BUFFSIZE];
4659 gchar *result = NULL;
4662 while (fgets(buf, sizeof (buf), fp) != NULL) {
4663 gchar **parts = g_strsplit(buf, ";", 3);
4664 gchar *trimmed = parts[0];
4665 while (trimmed[0] == ' ' || trimmed[0] == '\t')
4667 while (trimmed[strlen(trimmed)-1] == ' ' || trimmed[strlen(trimmed)-1] == '\t')
4668 trimmed[strlen(trimmed)-1] = '\0';
4670 if (!strcmp(trimmed, type)) {
4671 gboolean needsterminal = FALSE;
4672 if (parts[2] && strstr(parts[2], "needsterminal")) {
4673 needsterminal = TRUE;
4675 if (parts[2] && strstr(parts[2], "test=")) {
4676 gchar *orig_testcmd = g_strdup(strstr(parts[2], "test=")+5);
4677 gchar *testcmd = orig_testcmd;
4678 if (strstr(testcmd,";"))
4679 *(strstr(testcmd,";")) = '\0';
4680 while (testcmd[0] == ' ' || testcmd[0] == '\t')
4682 while (testcmd[strlen(testcmd)-1] == '\n')
4683 testcmd[strlen(testcmd)-1] = '\0';
4684 while (testcmd[strlen(testcmd)-1] == '\r')
4685 testcmd[strlen(testcmd)-1] = '\0';
4686 while (testcmd[strlen(testcmd)-1] == ' ' || testcmd[strlen(testcmd)-1] == '\t')
4687 testcmd[strlen(testcmd)-1] = '\0';
4689 if (strstr(testcmd, "%s")) {
4690 gchar *tmp = g_strdup_printf(testcmd, file_to_open);
4691 gint res = system(tmp);
4693 g_free(orig_testcmd);
4700 gint res = system(testcmd);
4701 g_free(orig_testcmd);
4711 while (trimmed[0] == ' ' || trimmed[0] == '\t')
4713 while (trimmed[strlen(trimmed)-1] == '\n')
4714 trimmed[strlen(trimmed)-1] = '\0';
4715 while (trimmed[strlen(trimmed)-1] == '\r')
4716 trimmed[strlen(trimmed)-1] = '\0';
4717 while (trimmed[strlen(trimmed)-1] == ' ' || trimmed[strlen(trimmed)-1] == '\t')
4718 trimmed[strlen(trimmed)-1] = '\0';
4719 result = g_strdup(trimmed);
4722 /* if there are no single quotes around %s, add them.
4723 * '.*%s.*' is ok, as in display 'png:%s'
4725 if (strstr(result, "%s")
4726 && !(strstr(result, "'") < strstr(result,"%s") &&
4727 strstr(strstr(result,"%s"), "'"))) {
4728 gchar *start = g_strdup(result);
4729 gchar *end = g_strdup(strstr(result, "%s")+2);
4731 *strstr(start, "%s") = '\0';
4732 tmp = g_strconcat(start,"'%s'",end, NULL);
4738 if (needsterminal) {
4739 gchar *tmp = g_strdup_printf("xterm -e %s", result);
4750 gchar *mailcap_get_command_for_type(const gchar *type, const gchar *file_to_open)
4752 gchar *result = NULL;
4756 path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap", NULL);
4757 result = mailcap_get_command_in_file(path, type, file_to_open);
4761 result = mailcap_get_command_in_file("/etc/mailcap", type, file_to_open);
4765 void mailcap_update_default(const gchar *type, const gchar *command)
4767 gchar *path = NULL, *outpath = NULL;
4768 path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap", NULL);
4769 outpath = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap.new", NULL);
4770 FILE *fp = g_fopen(path, "rb");
4772 gchar buf[BUFFSIZE];
4773 gboolean err = FALSE;
4776 fp = g_fopen(path, "a");
4778 g_warning("failed to create file %s", path);
4783 fp = g_freopen(path, "rb", fp);
4785 g_warning("failed to reopen file %s", path);
4792 outfp = g_fopen(outpath, "wb");
4794 g_warning("failed to create file %s", outpath);
4800 while (fp && fgets(buf, sizeof (buf), fp) != NULL) {
4801 gchar **parts = g_strsplit(buf, ";", 3);
4802 gchar *trimmed = parts[0];
4803 while (trimmed[0] == ' ')
4805 while (trimmed[strlen(trimmed)-1] == ' ')
4806 trimmed[strlen(trimmed)-1] = '\0';
4808 if (!strcmp(trimmed, type)) {
4813 if(fputs(buf, outfp) == EOF) {
4820 if (fprintf(outfp, "%s; %s\n", type, command) < 0)
4826 if (fclose(outfp) == EOF)
4830 g_rename(outpath, path);
4836 gint copy_dir(const gchar *src, const gchar *dst)
4841 if ((dir = g_dir_open(src, 0, NULL)) == NULL) {
4842 g_warning("failed to open directory: %s", src);
4846 if (make_dir(dst) < 0)
4849 while ((name = g_dir_read_name(dir)) != NULL) {
4850 gchar *old_file, *new_file;
4851 old_file = g_strconcat(src, G_DIR_SEPARATOR_S, name, NULL);
4852 new_file = g_strconcat(dst, G_DIR_SEPARATOR_S, name, NULL);
4853 debug_print("copying: %s -> %s\n", old_file, new_file);
4854 if (g_file_test(old_file, G_FILE_TEST_IS_REGULAR)) {
4855 gint r = copy_file(old_file, new_file, TRUE);
4862 /* Windows has no symlinks. Or well, Vista seems to
4863 have something like this but the semantics might be
4864 different. Thus we don't use it under Windows. */
4865 else if (g_file_test(old_file, G_FILE_TEST_IS_SYMLINK)) {
4868 gchar *target = g_file_read_link(old_file, &error);
4870 r = symlink(target, new_file);
4877 #endif /*G_OS_WIN32*/
4878 else if (g_file_test(old_file, G_FILE_TEST_IS_DIR)) {
4879 gint r = copy_dir(old_file, new_file);
4890 /* crude test to see if a file is an email. */
4891 gboolean file_is_email (const gchar *filename)
4897 if (filename == NULL)
4899 if ((fp = g_fopen(filename, "rb")) == NULL)
4901 while (i < 60 && score < 3
4902 && fgets(buffer, sizeof (buffer), fp) > 0) {
4903 if (!strncmp(buffer, "From:", strlen("From:")))
4905 else if (!strncmp(buffer, "Date:", strlen("Date:")))
4907 else if (!strncmp(buffer, "Message-ID:", strlen("Message-ID:")))
4909 else if (!strncmp(buffer, "Subject:", strlen("Subject:")))
4914 return (score >= 3);
4917 gboolean sc_g_list_bigger(GList *list, gint max)
4921 while (cur && i <= max+1) {
4928 gboolean sc_g_slist_bigger(GSList *list, gint max)
4932 while (cur && i <= max+1) {
4939 const gchar *daynames[] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL};
4940 const gchar *monthnames[] = {NULL, NULL, NULL, NULL, NULL, NULL,
4941 NULL, NULL, NULL, NULL, NULL, NULL};
4942 const gchar *s_daynames[] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL};
4943 const gchar *s_monthnames[] = {NULL, NULL, NULL, NULL, NULL, NULL,
4944 NULL, NULL, NULL, NULL, NULL, NULL};
4946 gint daynames_len[] = {0,0,0,0,0,0,0};
4947 gint monthnames_len[] = {0,0,0,0,0,0,
4949 gint s_daynames_len[] = {0,0,0,0,0,0,0};
4950 gint s_monthnames_len[] = {0,0,0,0,0,0,
4952 const gchar *s_am_up = NULL;
4953 const gchar *s_pm_up = NULL;
4954 const gchar *s_am_low = NULL;
4955 const gchar *s_pm_low = NULL;
4957 gint s_am_up_len = 0;
4958 gint s_pm_up_len = 0;
4959 gint s_am_low_len = 0;
4960 gint s_pm_low_len = 0;
4962 static gboolean time_names_init_done = FALSE;
4964 static void init_time_names(void)
4968 daynames[0] = C_("Complete day name for use by strftime", "Sunday");
4969 daynames[1] = C_("Complete day name for use by strftime", "Monday");
4970 daynames[2] = C_("Complete day name for use by strftime", "Tuesday");
4971 daynames[3] = C_("Complete day name for use by strftime", "Wednesday");
4972 daynames[4] = C_("Complete day name for use by strftime", "Thursday");
4973 daynames[5] = C_("Complete day name for use by strftime", "Friday");
4974 daynames[6] = C_("Complete day name for use by strftime", "Saturday");
4976 monthnames[0] = C_("Complete month name for use by strftime", "January");
4977 monthnames[1] = C_("Complete month name for use by strftime", "February");
4978 monthnames[2] = C_("Complete month name for use by strftime", "March");
4979 monthnames[3] = C_("Complete month name for use by strftime", "April");
4980 monthnames[4] = C_("Complete month name for use by strftime", "May");
4981 monthnames[5] = C_("Complete month name for use by strftime", "June");
4982 monthnames[6] = C_("Complete month name for use by strftime", "July");
4983 monthnames[7] = C_("Complete month name for use by strftime", "August");
4984 monthnames[8] = C_("Complete month name for use by strftime", "September");
4985 monthnames[9] = C_("Complete month name for use by strftime", "October");
4986 monthnames[10] = C_("Complete month name for use by strftime", "November");
4987 monthnames[11] = C_("Complete month name for use by strftime", "December");
4989 s_daynames[0] = C_("Abbr. day name for use by strftime", "Sun");
4990 s_daynames[1] = C_("Abbr. day name for use by strftime", "Mon");
4991 s_daynames[2] = C_("Abbr. day name for use by strftime", "Tue");
4992 s_daynames[3] = C_("Abbr. day name for use by strftime", "Wed");
4993 s_daynames[4] = C_("Abbr. day name for use by strftime", "Thu");
4994 s_daynames[5] = C_("Abbr. day name for use by strftime", "Fri");
4995 s_daynames[6] = C_("Abbr. day name for use by strftime", "Sat");
4997 s_monthnames[0] = C_("Abbr. month name for use by strftime", "Jan");
4998 s_monthnames[1] = C_("Abbr. month name for use by strftime", "Feb");
4999 s_monthnames[2] = C_("Abbr. month name for use by strftime", "Mar");
5000 s_monthnames[3] = C_("Abbr. month name for use by strftime", "Apr");
5001 s_monthnames[4] = C_("Abbr. month name for use by strftime", "May");
5002 s_monthnames[5] = C_("Abbr. month name for use by strftime", "Jun");
5003 s_monthnames[6] = C_("Abbr. month name for use by strftime", "Jul");
5004 s_monthnames[7] = C_("Abbr. month name for use by strftime", "Aug");
5005 s_monthnames[8] = C_("Abbr. month name for use by strftime", "Sep");
5006 s_monthnames[9] = C_("Abbr. month name for use by strftime", "Oct");
5007 s_monthnames[10] = C_("Abbr. month name for use by strftime", "Nov");
5008 s_monthnames[11] = C_("Abbr. month name for use by strftime", "Dec");
5010 for (i = 0; i < 7; i++) {
5011 daynames_len[i] = strlen(daynames[i]);
5012 s_daynames_len[i] = strlen(s_daynames[i]);
5014 for (i = 0; i < 12; i++) {
5015 monthnames_len[i] = strlen(monthnames[i]);
5016 s_monthnames_len[i] = strlen(s_monthnames[i]);
5019 s_am_up = C_("For use by strftime (morning)", "AM");
5020 s_pm_up = C_("For use by strftime (afternoon)", "PM");
5021 s_am_low = C_("For use by strftime (morning, lowercase)", "am");
5022 s_pm_low = C_("For use by strftime (afternoon, lowercase)", "pm");
5024 s_am_up_len = strlen(s_am_up);
5025 s_pm_up_len = strlen(s_pm_up);
5026 s_am_low_len = strlen(s_am_low);
5027 s_pm_low_len = strlen(s_pm_low);
5029 time_names_init_done = TRUE;
5032 #define CHECK_SIZE() { \
5033 total_done += len; \
5034 if (total_done >= buflen) { \
5035 buf[buflen-1] = '\0'; \
5040 size_t fast_strftime(gchar *buf, gint buflen, const gchar *format, struct tm *lt)
5042 gchar *curpos = buf;
5043 gint total_done = 0;
5044 gchar subbuf[64], subfmt[64];
5045 static time_t last_tzset = (time_t)0;
5047 if (!time_names_init_done)
5050 if (format == NULL || lt == NULL)
5053 if (last_tzset != time(NULL)) {
5055 last_tzset = time(NULL);
5058 if (*format == '%') {
5059 gint len = 0, tmp = 0;
5063 len = 1; CHECK_SIZE();
5067 len = s_daynames_len[lt->tm_wday]; CHECK_SIZE();
5068 strncpy2(curpos, s_daynames[lt->tm_wday], buflen - total_done);
5071 len = daynames_len[lt->tm_wday]; CHECK_SIZE();
5072 strncpy2(curpos, daynames[lt->tm_wday], buflen - total_done);
5076 len = s_monthnames_len[lt->tm_mon]; CHECK_SIZE();
5077 strncpy2(curpos, s_monthnames[lt->tm_mon], buflen - total_done);
5080 len = monthnames_len[lt->tm_mon]; CHECK_SIZE();
5081 strncpy2(curpos, monthnames[lt->tm_mon], buflen - total_done);
5084 strftime(subbuf, 64, "%c", lt);
5085 len = strlen(subbuf); CHECK_SIZE();
5086 strncpy2(curpos, subbuf, buflen - total_done);
5089 total_done += 2; CHECK_SIZE();
5090 tmp = (lt->tm_year + 1900)/100;
5091 *curpos++ = '0'+(tmp / 10);
5092 *curpos++ = '0'+(tmp % 10);
5095 total_done += 2; CHECK_SIZE();
5096 *curpos++ = '0'+(lt->tm_mday / 10);
5097 *curpos++ = '0'+(lt->tm_mday % 10);
5100 total_done += 8; CHECK_SIZE();
5101 *curpos++ = '0'+((lt->tm_mon+1) / 10);
5102 *curpos++ = '0'+((lt->tm_mon+1) % 10);
5104 *curpos++ = '0'+(lt->tm_mday / 10);
5105 *curpos++ = '0'+(lt->tm_mday % 10);
5107 tmp = lt->tm_year%100;
5108 *curpos++ = '0'+(tmp / 10);
5109 *curpos++ = '0'+(tmp % 10);
5112 len = 2; CHECK_SIZE();
5113 snprintf(curpos, buflen - total_done, "%2d", lt->tm_mday);
5116 len = 10; CHECK_SIZE();
5117 snprintf(curpos, buflen - total_done, "%4d-%02d-%02d",
5118 lt->tm_year + 1900, lt->tm_mon +1, lt->tm_mday);
5121 total_done += 2; CHECK_SIZE();
5122 *curpos++ = '0'+(lt->tm_hour / 10);
5123 *curpos++ = '0'+(lt->tm_hour % 10);
5126 total_done += 2; CHECK_SIZE();
5132 *curpos++ = '0'+(tmp / 10);
5133 *curpos++ = '0'+(tmp % 10);
5136 len = 3; CHECK_SIZE();
5137 snprintf(curpos, buflen - total_done, "%03d", lt->tm_yday+1);
5140 len = 2; CHECK_SIZE();
5141 snprintf(curpos, buflen - total_done, "%2d", lt->tm_hour);
5144 len = 2; CHECK_SIZE();
5150 snprintf(curpos, buflen - total_done, "%2d", tmp);
5153 total_done += 2; CHECK_SIZE();
5154 tmp = lt->tm_mon + 1;
5155 *curpos++ = '0'+(tmp / 10);
5156 *curpos++ = '0'+(tmp % 10);
5159 total_done += 2; CHECK_SIZE();
5160 *curpos++ = '0'+(lt->tm_min / 10);
5161 *curpos++ = '0'+(lt->tm_min % 10);
5164 len = 1; CHECK_SIZE();
5168 if (lt->tm_hour >= 12) {
5169 len = s_pm_up_len; CHECK_SIZE();
5170 snprintf(curpos, buflen-total_done, "%s", s_pm_up);
5172 len = s_am_up_len; CHECK_SIZE();
5173 snprintf(curpos, buflen-total_done, "%s", s_am_up);
5177 if (lt->tm_hour >= 12) {
5178 len = s_pm_low_len; CHECK_SIZE();
5179 snprintf(curpos, buflen-total_done, "%s", s_pm_low);
5181 len = s_am_low_len; CHECK_SIZE();
5182 snprintf(curpos, buflen-total_done, "%s", s_am_low);
5186 strftime(subbuf, 64, "%r", lt);
5187 len = strlen(subbuf); CHECK_SIZE();
5188 strncpy2(curpos, subbuf, buflen - total_done);
5191 total_done += 5; CHECK_SIZE();
5192 *curpos++ = '0'+(lt->tm_hour / 10);
5193 *curpos++ = '0'+(lt->tm_hour % 10);
5195 *curpos++ = '0'+(lt->tm_min / 10);
5196 *curpos++ = '0'+(lt->tm_min % 10);
5199 snprintf(subbuf, 64, "%ld", mktime(lt));
5200 len = strlen(subbuf); CHECK_SIZE();
5201 strncpy2(curpos, subbuf, buflen - total_done);
5204 total_done += 2; CHECK_SIZE();
5205 *curpos++ = '0'+(lt->tm_sec / 10);
5206 *curpos++ = '0'+(lt->tm_sec % 10);
5209 len = 1; CHECK_SIZE();
5213 total_done += 8; CHECK_SIZE();
5214 *curpos++ = '0'+(lt->tm_hour / 10);
5215 *curpos++ = '0'+(lt->tm_hour % 10);
5217 *curpos++ = '0'+(lt->tm_min / 10);
5218 *curpos++ = '0'+(lt->tm_min % 10);
5220 *curpos++ = '0'+(lt->tm_sec / 10);
5221 *curpos++ = '0'+(lt->tm_sec % 10);
5224 len = 1; CHECK_SIZE();
5225 snprintf(curpos, buflen - total_done, "%d", lt->tm_wday == 0 ? 7: lt->tm_wday);
5228 len = 1; CHECK_SIZE();
5229 snprintf(curpos, buflen - total_done, "%d", lt->tm_wday);
5232 strftime(subbuf, 64, "%x", lt);
5233 len = strlen(subbuf); CHECK_SIZE();
5234 strncpy2(curpos, subbuf, buflen - total_done);
5237 strftime(subbuf, 64, "%X", lt);
5238 len = strlen(subbuf); CHECK_SIZE();
5239 strncpy2(curpos, subbuf, buflen - total_done);
5242 total_done += 2; CHECK_SIZE();
5243 tmp = lt->tm_year%100;
5244 *curpos++ = '0'+(tmp / 10);
5245 *curpos++ = '0'+(tmp % 10);
5248 len = 4; CHECK_SIZE();
5249 snprintf(curpos, buflen - total_done, "%4d", lt->tm_year + 1900);
5259 /* let these complicated ones be done with the libc */
5260 snprintf(subfmt, 64, "%%%c", *format);
5261 strftime(subbuf, 64, subfmt, lt);
5262 len = strlen(subbuf); CHECK_SIZE();
5263 strncpy2(curpos, subbuf, buflen - total_done);
5267 /* let these complicated modifiers be done with the libc */
5268 snprintf(subfmt, 64, "%%%c%c", *format, *(format+1));
5269 strftime(subbuf, 64, subfmt, lt);
5270 len = strlen(subbuf); CHECK_SIZE();
5271 strncpy2(curpos, subbuf, buflen - total_done);
5275 g_warning("format error (%c)", *format);
5282 int len = 1; CHECK_SIZE();
5283 *curpos++ = *format++;
5290 gboolean prefs_common_get_use_shred(void);
5294 #define WEXITSTATUS(x) (x)
5297 int claws_unlink(const gchar *filename)
5300 static int found_shred = -1;
5301 static const gchar *args[4];
5303 if (filename == NULL)
5306 if (prefs_common_get_use_shred()) {
5307 if (found_shred == -1) {
5309 args[0] = g_find_program_in_path("shred");
5310 debug_print("found shred: %s\n", args[0]);
5311 found_shred = (args[0] != NULL) ? 1:0;
5315 if (found_shred == 1) {
5316 if (g_stat(filename, &s) == 0 && S_ISREG(s.st_mode)) {
5317 if (s.st_nlink == 1) {
5320 g_spawn_sync(NULL, (gchar **)args, NULL, 0,
5321 NULL, NULL, NULL, NULL, &status, NULL);
5322 debug_print("%s %s exited with status %d\n",
5323 args[0], filename, WEXITSTATUS(status));
5324 if (truncate(filename, 0) < 0)
5325 g_warning("couln't truncate: %s", filename);
5330 return g_unlink(filename);
5333 GMutex *cm_mutex_new(void) {
5334 #if GLIB_CHECK_VERSION(2,32,0)
5335 GMutex *m = g_new0(GMutex, 1);
5339 return g_mutex_new();
5343 void cm_mutex_free(GMutex *mutex) {
5344 #if GLIB_CHECK_VERSION(2,32,0)
5345 g_mutex_clear(mutex);
5348 g_mutex_free(mutex);
5352 static gchar *canonical_list_to_file(GSList *list)
5354 GString *result = g_string_new(NULL);
5355 GSList *pathlist = g_slist_reverse(g_slist_copy(list));
5360 result = g_string_append(result, G_DIR_SEPARATOR_S);
5362 if (pathlist->data) {
5363 const gchar *root = (gchar *)pathlist->data;
5364 if (root[0] != '\0' && g_ascii_isalpha(root[0]) &&
5366 /* drive - don't prepend dir separator */
5368 result = g_string_append(result, G_DIR_SEPARATOR_S);
5373 for (cur = pathlist; cur; cur = cur->next) {
5374 result = g_string_append(result, (gchar *)cur->data);
5376 result = g_string_append(result, G_DIR_SEPARATOR_S);
5378 g_slist_free(pathlist);
5381 g_string_free(result, FALSE);
5386 static GSList *cm_split_path(const gchar *filename, int depth)
5389 GSList *canonical_parts = NULL;
5392 gboolean follow_symlinks = TRUE;
5398 errno = EINVAL; /* can't happen, no symlink handling */
5403 if (!g_path_is_absolute(filename)) {
5408 path_parts = g_strsplit(filename, G_DIR_SEPARATOR_S, -1);
5410 for (i = 0; path_parts[i] != NULL; i++) {
5411 if (!strcmp(path_parts[i], ""))
5413 if (!strcmp(path_parts[i], "."))
5415 else if (!strcmp(path_parts[i], "..")) {
5420 else /* Remove the last inserted element */
5422 g_slist_delete_link(canonical_parts,
5427 canonical_parts = g_slist_prepend(canonical_parts,
5428 g_strdup(path_parts[i]));
5430 tmp_path = canonical_list_to_file(canonical_parts);
5432 if(g_stat(tmp_path, &st) < 0) {
5433 if (errno == ENOENT) {
5435 follow_symlinks = FALSE;
5439 slist_free_strings_full(canonical_parts);
5440 g_strfreev(path_parts);
5446 if (follow_symlinks && g_file_test(tmp_path, G_FILE_TEST_IS_SYMLINK)) {
5447 GError *error = NULL;
5448 gchar *target = g_file_read_link(tmp_path, &error);
5450 if (!g_path_is_absolute(target)) {
5451 /* remove the last inserted element */
5453 g_slist_delete_link(canonical_parts,
5455 /* add the target */
5456 canonical_parts = g_slist_prepend(canonical_parts,
5460 /* and get the new target */
5461 target = canonical_list_to_file(canonical_parts);
5464 /* restart from absolute target */
5465 slist_free_strings_full(canonical_parts);
5466 canonical_parts = NULL;
5468 canonical_parts = cm_split_path(target, depth + 1);
5470 g_error_free(error);
5471 if (canonical_parts == NULL) {
5473 g_strfreev(path_parts);
5482 g_strfreev(path_parts);
5483 return canonical_parts;
5487 * Canonicalize a filename, resolving symlinks along the way.
5488 * Returns a negative errno in case of error.
5490 int cm_canonicalize_filename(const gchar *filename, gchar **canonical_name) {
5491 GSList *canonical_parts;
5492 gboolean is_absolute;
5494 if (filename == NULL)
5496 if (canonical_name == NULL)
5498 *canonical_name = NULL;
5500 is_absolute = g_path_is_absolute(filename);
5502 /* Always work on absolute filenames. */
5503 gchar *cur = g_get_current_dir();
5504 gchar *absolute_filename = g_strconcat(cur, G_DIR_SEPARATOR_S,
5507 canonical_parts = cm_split_path(absolute_filename, 0);
5508 g_free(absolute_filename);
5511 canonical_parts = cm_split_path(filename, 0);
5513 if (canonical_parts == NULL)
5516 *canonical_name = canonical_list_to_file(canonical_parts);
5517 slist_free_strings_full(canonical_parts);
5521 /* Returns a decoded base64 string, guaranteed to be null-terminated. */
5522 guchar *g_base64_decode_zero(const gchar *text, gsize *out_len)
5524 gchar *tmp = g_base64_decode(text, out_len);
5525 gchar *out = g_strndup(tmp, *out_len);
5529 if (strlen(out) != *out_len) {
5530 g_warning ("strlen(out) %zd != *out_len %" G_GSIZE_FORMAT, strlen(out), *out_len);
5536 #if !GLIB_CHECK_VERSION(2, 30, 0)
5539 * @str: a UTF-8 encoded string
5540 * @start_pos: a character offset within @str
5541 * @end_pos: another character offset within @str
5543 * Copies a substring out of a UTF-8 encoded string.
5544 * The substring will contain @end_pos - @start_pos
5547 * Returns: a newly allocated copy of the requested
5548 * substring. Free with g_free() when no longer needed.
5553 g_utf8_substring (const gchar *str,
5557 gchar *start, *end, *out;
5559 start = g_utf8_offset_to_pointer (str, start_pos);
5560 end = g_utf8_offset_to_pointer (start, end_pos - start_pos);
5562 out = g_malloc (end - start + 1);
5563 memcpy (out, start, end - start);
5564 out[end - start] = 0;