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 */
222 gint mkstemp_name(gchar *template, gchar **name_used)
224 static gulong count=0; /* W32-_mktemp only supports up to 27
228 *name_used = g_strdup_printf("%s.%ld",_mktemp(template),count++);
229 tmpfd = g_open (*name_used, (O_CREAT | O_RDWR | O_BINARY),
230 (S_IRUSR | S_IWUSR));
232 tempfiles=g_slist_append(tempfiles, g_strdup(*name_used));
234 perror(g_strdup_printf("cant create %s",*name_used));
240 #endif /* G_OS_WIN32 */
243 gint mkstemp(gchar *template)
246 gint res = mkstemp_name(template, &dummyname);
250 #endif /* G_OS_WIN32 */
252 GSList *slist_copy_deep(GSList *list, GCopyFunc func)
254 #if GLIB_CHECK_VERSION(2, 34, 0)
255 return g_slist_copy_deep(list, func, NULL);
257 GSList *res = g_slist_copy(list);
260 walk->data = func(walk->data, NULL);
267 void list_free_strings(GList *list)
269 list = g_list_first(list);
271 while (list != NULL) {
277 void slist_free_strings(GSList *list)
279 while (list != NULL) {
285 void slist_free_strings_full(GSList *list)
287 #if GLIB_CHECK_VERSION(2,28,0)
288 g_slist_free_full(list, (GDestroyNotify)g_free);
290 g_slist_foreach(list, (GFunc)g_free, NULL);
295 static void hash_free_strings_func(gpointer key, gpointer value, gpointer data)
300 void hash_free_strings(GHashTable *table)
302 g_hash_table_foreach(table, hash_free_strings_func, NULL);
305 gint str_case_equal(gconstpointer v, gconstpointer v2)
307 return g_ascii_strcasecmp((const gchar *)v, (const gchar *)v2) == 0;
310 guint str_case_hash(gconstpointer key)
312 const gchar *p = key;
316 h = g_ascii_tolower(h);
317 for (p += 1; *p != '\0'; p++)
318 h = (h << 5) - h + g_ascii_tolower(*p);
324 void ptr_array_free_strings(GPtrArray *array)
329 cm_return_if_fail(array != NULL);
331 for (i = 0; i < array->len; i++) {
332 str = g_ptr_array_index(array, i);
337 gint to_number(const gchar *nstr)
339 register const gchar *p;
341 if (*nstr == '\0') return -1;
343 for (p = nstr; *p != '\0'; p++)
344 if (!g_ascii_isdigit(*p)) return -1;
349 /* convert integer into string,
350 nstr must be not lower than 11 characters length */
351 gchar *itos_buf(gchar *nstr, gint n)
353 g_snprintf(nstr, 11, "%d", n);
357 /* convert integer into string */
360 static gchar nstr[11];
362 return itos_buf(nstr, n);
365 #define divide(num,divisor,i,d) \
367 i = num >> divisor; \
368 d = num & ((1<<divisor)-1); \
369 d = (d*100) >> divisor; \
374 * \brief Convert a given size in bytes in a human-readable string
376 * \param size The size expressed in bytes to convert in string
377 * \return The string that respresents the size in an human-readable way
379 gchar *to_human_readable(goffset size)
381 static gchar str[14];
382 static gchar *b_format = NULL, *kb_format = NULL,
383 *mb_format = NULL, *gb_format = NULL;
384 register int t = 0, r = 0;
385 if (b_format == NULL) {
387 kb_format = _("%d.%02dKB");
388 mb_format = _("%d.%02dMB");
389 gb_format = _("%.2fGB");
392 if (size < (goffset)1024) {
393 g_snprintf(str, sizeof(str), b_format, (gint)size);
395 } else if (size >> 10 < (goffset)1024) {
396 divide(size, 10, t, r);
397 g_snprintf(str, sizeof(str), kb_format, t, r);
399 } else if (size >> 20 < (goffset)1024) {
400 divide(size, 20, t, r);
401 g_snprintf(str, sizeof(str), mb_format, t, r);
404 g_snprintf(str, sizeof(str), gb_format, (gfloat)(size >> 30));
409 /* strcmp with NULL-checking */
410 gint strcmp2(const gchar *s1, const gchar *s2)
412 if (s1 == NULL || s2 == NULL)
415 return strcmp(s1, s2);
417 /* strstr with NULL-checking */
418 gchar *strstr2(const gchar *s1, const gchar *s2)
420 if (s1 == NULL || s2 == NULL)
423 return strstr(s1, s2);
426 gint path_cmp(const gchar *s1, const gchar *s2)
431 gchar *s1buf, *s2buf;
434 if (s1 == NULL || s2 == NULL) return -1;
435 if (*s1 == '\0' || *s2 == '\0') return -1;
438 s1buf = g_strdup (s1);
439 s2buf = g_strdup (s2);
440 subst_char (s1buf, '/', G_DIR_SEPARATOR);
441 subst_char (s2buf, '/', G_DIR_SEPARATOR);
444 #endif /* !G_OS_WIN32 */
449 if (s1[len1 - 1] == G_DIR_SEPARATOR) len1--;
450 if (s2[len2 - 1] == G_DIR_SEPARATOR) len2--;
452 rc = strncmp(s1, s2, MAX(len1, len2));
456 #endif /* !G_OS_WIN32 */
460 /* remove trailing return code */
461 gchar *strretchomp(gchar *str)
465 if (!*str) return str;
467 for (s = str + strlen(str) - 1;
468 s >= str && (*s == '\n' || *s == '\r');
475 /* remove trailing character */
476 gchar *strtailchomp(gchar *str, gchar tail_char)
480 if (!*str) return str;
481 if (tail_char == '\0') return str;
483 for (s = str + strlen(str) - 1; s >= str && *s == tail_char; s--)
489 /* remove CR (carriage return) */
490 gchar *strcrchomp(gchar *str)
494 if (!*str) return str;
496 s = str + strlen(str) - 1;
497 if (*s == '\n' && s > str && *(s - 1) == '\r') {
505 gint file_strip_crs(const gchar *file)
507 FILE *fp = NULL, *outfp = NULL;
509 gchar *out = get_tmp_file();
513 fp = g_fopen(file, "rb");
517 outfp = g_fopen(out, "wb");
523 while (fgets(buf, sizeof (buf), fp) != NULL) {
525 if (fputs(buf, outfp) == EOF) {
533 if (fclose(outfp) == EOF) {
537 if (move_file(out, file, TRUE) < 0)
549 /* Similar to `strstr' but this function ignores the case of both strings. */
550 gchar *strcasestr(const gchar *haystack, const gchar *needle)
552 size_t haystack_len = strlen(haystack);
554 return strncasestr(haystack, haystack_len, needle);
557 gchar *strncasestr(const gchar *haystack, gint haystack_len, const gchar *needle)
559 register size_t needle_len;
561 needle_len = strlen(needle);
563 if (haystack_len < needle_len || needle_len == 0)
566 while (haystack_len >= needle_len) {
567 if (!g_ascii_strncasecmp(haystack, needle, needle_len))
568 return (gchar *)haystack;
578 gpointer my_memmem(gconstpointer haystack, size_t haystacklen,
579 gconstpointer needle, size_t needlelen)
581 const gchar *haystack_ = (const gchar *)haystack;
582 const gchar *needle_ = (const gchar *)needle;
583 const gchar *haystack_cur = (const gchar *)haystack;
584 size_t haystack_left = haystacklen;
587 return memchr(haystack_, *needle_, haystacklen);
589 while ((haystack_cur = memchr(haystack_cur, *needle_, haystack_left))
591 if (haystacklen - (haystack_cur - haystack_) < needlelen)
593 if (memcmp(haystack_cur + 1, needle_ + 1, needlelen - 1) == 0)
594 return (gpointer)haystack_cur;
597 haystack_left = haystacklen - (haystack_cur - haystack_);
604 /* Copy no more than N characters of SRC to DEST, with NULL terminating. */
605 gchar *strncpy2(gchar *dest, const gchar *src, size_t n)
607 register const gchar *s = src;
608 register gchar *d = dest;
618 /* Examine if next block is non-ASCII string */
619 gboolean is_next_nonascii(const gchar *s)
623 /* skip head space */
624 for (p = s; *p != '\0' && g_ascii_isspace(*p); p++)
626 for (; *p != '\0' && !g_ascii_isspace(*p); p++) {
627 if (*(guchar *)p > 127 || *(guchar *)p < 32)
634 gint get_next_word_len(const gchar *s)
638 for (; *s != '\0' && !g_ascii_isspace(*s); s++, len++)
644 static void trim_subject_for_compare(gchar *str)
648 eliminate_parenthesis(str, '[', ']');
649 eliminate_parenthesis(str, '(', ')');
652 srcp = str + subject_get_prefix_length(str);
654 memmove(str, srcp, strlen(srcp) + 1);
657 static void trim_subject_for_sort(gchar *str)
663 srcp = str + subject_get_prefix_length(str);
665 memmove(str, srcp, strlen(srcp) + 1);
668 /* compare subjects */
669 gint subject_compare(const gchar *s1, const gchar *s2)
673 if (!s1 || !s2) return -1;
674 if (!*s1 || !*s2) return -1;
676 Xstrdup_a(str1, s1, return -1);
677 Xstrdup_a(str2, s2, return -1);
679 trim_subject_for_compare(str1);
680 trim_subject_for_compare(str2);
682 if (!*str1 || !*str2) return -1;
684 return strcmp(str1, str2);
687 gint subject_compare_for_sort(const gchar *s1, const gchar *s2)
691 if (!s1 || !s2) return -1;
693 Xstrdup_a(str1, s1, return -1);
694 Xstrdup_a(str2, s2, return -1);
696 trim_subject_for_sort(str1);
697 trim_subject_for_sort(str2);
699 return g_utf8_collate(str1, str2);
702 void trim_subject(gchar *str)
704 register gchar *srcp;
710 srcp = str + subject_get_prefix_length(str);
715 } else if (*srcp == '(') {
727 else if (*srcp == cl)
734 while (g_ascii_isspace(*srcp)) srcp++;
735 memmove(str, srcp, strlen(srcp) + 1);
738 void eliminate_parenthesis(gchar *str, gchar op, gchar cl)
740 register gchar *srcp, *destp;
745 while ((destp = strchr(destp, op))) {
751 else if (*srcp == cl)
757 while (g_ascii_isspace(*srcp)) srcp++;
758 memmove(destp, srcp, strlen(srcp) + 1);
762 void extract_parenthesis(gchar *str, gchar op, gchar cl)
764 register gchar *srcp, *destp;
769 while ((srcp = strchr(destp, op))) {
772 memmove(destp, srcp + 1, strlen(srcp));
777 else if (*destp == cl)
789 static void extract_parenthesis_with_skip_quote(gchar *str, gchar quote_chr,
792 register gchar *srcp, *destp;
794 gboolean in_quote = FALSE;
798 while ((srcp = strchr_with_skip_quote(destp, quote_chr, op))) {
801 memmove(destp, srcp + 1, strlen(srcp));
804 if (*destp == op && !in_quote)
806 else if (*destp == cl && !in_quote)
808 else if (*destp == quote_chr)
820 void extract_quote(gchar *str, gchar quote_chr)
824 if ((str = strchr(str, quote_chr))) {
826 while ((p = strchr(p + 1, quote_chr)) && (p[-1] == '\\')) {
827 memmove(p - 1, p, strlen(p) + 1);
832 memmove(str, str + 1, p - str);
837 /* Returns a newly allocated string with all quote_chr not at the beginning
838 or the end of str escaped with '\' or the given str if not required. */
839 gchar *escape_internal_quotes(gchar *str, gchar quote_chr)
841 register gchar *p, *q;
845 if (str == NULL || *str == '\0')
848 /* search for unescaped quote_chr */
853 if (*p == quote_chr && *(p - 1) != '\\' && *(p + 1) != '\0')
857 if (!k) /* nothing to escape */
860 /* unescaped quote_chr found */
861 qstr = g_malloc(l + k + 1);
864 if (*p == quote_chr) {
869 if (*p == quote_chr && *(p - 1) != '\\' && *(p + 1) != '\0')
878 void eliminate_address_comment(gchar *str)
880 register gchar *srcp, *destp;
885 while ((destp = strchr(destp, '"'))) {
886 if ((srcp = strchr(destp + 1, '"'))) {
891 while (g_ascii_isspace(*srcp)) srcp++;
892 memmove(destp, srcp, strlen(srcp) + 1);
902 while ((destp = strchr_with_skip_quote(destp, '"', '('))) {
908 else if (*srcp == ')')
914 while (g_ascii_isspace(*srcp)) srcp++;
915 memmove(destp, srcp, strlen(srcp) + 1);
919 gchar *strchr_with_skip_quote(const gchar *str, gint quote_chr, gint c)
921 gboolean in_quote = FALSE;
924 if (*str == c && !in_quote)
926 if (*str == quote_chr)
934 void extract_address(gchar *str)
936 cm_return_if_fail(str != NULL);
937 eliminate_address_comment(str);
938 if (strchr_with_skip_quote(str, '"', '<'))
939 extract_parenthesis_with_skip_quote(str, '"', '<', '>');
943 void extract_list_id_str(gchar *str)
945 if (strchr_with_skip_quote(str, '"', '<'))
946 extract_parenthesis_with_skip_quote(str, '"', '<', '>');
950 static GSList *address_list_append_real(GSList *addr_list, const gchar *str, gboolean removecomments)
955 if (!str) return addr_list;
957 Xstrdup_a(work, str, return addr_list);
960 eliminate_address_comment(work);
963 while (workp && *workp) {
966 if ((p = strchr_with_skip_quote(workp, '"', ','))) {
972 if (removecomments && strchr_with_skip_quote(workp, '"', '<'))
973 extract_parenthesis_with_skip_quote
974 (workp, '"', '<', '>');
978 addr_list = g_slist_append(addr_list, g_strdup(workp));
986 GSList *address_list_append(GSList *addr_list, const gchar *str)
988 return address_list_append_real(addr_list, str, TRUE);
991 GSList *address_list_append_with_comments(GSList *addr_list, const gchar *str)
993 return address_list_append_real(addr_list, str, FALSE);
996 GSList *references_list_prepend(GSList *msgid_list, const gchar *str)
1000 if (!str) return msgid_list;
1003 while (strp && *strp) {
1004 const gchar *start, *end;
1007 if ((start = strchr(strp, '<')) != NULL) {
1008 end = strchr(start + 1, '>');
1013 msgid = g_strndup(start + 1, end - start - 1);
1016 msgid_list = g_slist_prepend(msgid_list, msgid);
1026 GSList *references_list_append(GSList *msgid_list, const gchar *str)
1030 list = references_list_prepend(NULL, str);
1031 list = g_slist_reverse(list);
1032 msgid_list = g_slist_concat(msgid_list, list);
1037 GSList *newsgroup_list_append(GSList *group_list, const gchar *str)
1042 if (!str) return group_list;
1044 Xstrdup_a(work, str, return group_list);
1048 while (workp && *workp) {
1051 if ((p = strchr_with_skip_quote(workp, '"', ','))) {
1059 group_list = g_slist_append(group_list,
1068 GList *add_history(GList *list, const gchar *str)
1073 cm_return_val_if_fail(str != NULL, list);
1075 old = g_list_find_custom(list, (gpointer)str, (GCompareFunc)strcmp2);
1078 list = g_list_remove(list, old->data);
1080 } else if (g_list_length(list) >= MAX_HISTORY_SIZE) {
1083 last = g_list_last(list);
1085 oldstr = last->data;
1086 list = g_list_remove(list, last->data);
1091 list = g_list_prepend(list, g_strdup(str));
1096 void remove_return(gchar *str)
1098 register gchar *p = str;
1101 if (*p == '\n' || *p == '\r')
1102 memmove(p, p + 1, strlen(p));
1108 void remove_space(gchar *str)
1110 register gchar *p = str;
1115 while (g_ascii_isspace(*(p + spc)))
1118 memmove(p, p + spc, strlen(p + spc) + 1);
1124 void unfold_line(gchar *str)
1126 register gchar *p = str;
1130 if (*p == '\n' || *p == '\r') {
1133 while (g_ascii_isspace(*(p + spc)))
1136 memmove(p, p + spc, strlen(p + spc) + 1);
1142 void subst_char(gchar *str, gchar orig, gchar subst)
1144 register gchar *p = str;
1153 void subst_chars(gchar *str, gchar *orig, gchar subst)
1155 register gchar *p = str;
1158 if (strchr(orig, *p) != NULL)
1164 void subst_for_filename(gchar *str)
1169 subst_chars(str, "\t\r\n\\/*:", '_');
1171 subst_chars(str, "\t\r\n\\/*", '_');
1175 void subst_for_shellsafe_filename(gchar *str)
1179 subst_for_filename(str);
1180 subst_chars(str, " \"'|&;()<>'!{}[]",'_');
1183 gboolean is_ascii_str(const gchar *str)
1185 const guchar *p = (const guchar *)str;
1187 while (*p != '\0') {
1188 if (*p != '\t' && *p != ' ' &&
1189 *p != '\r' && *p != '\n' &&
1190 (*p < 32 || *p >= 127))
1198 static const gchar * line_has_quote_char_last(const gchar * str, const gchar *quote_chars)
1200 gchar * position = NULL;
1201 gchar * tmp_pos = NULL;
1204 if (quote_chars == NULL)
1207 for (i = 0; i < strlen(quote_chars); i++) {
1208 tmp_pos = strrchr (str, quote_chars[i]);
1210 || (tmp_pos != NULL && position <= tmp_pos) )
1216 gint get_quote_level(const gchar *str, const gchar *quote_chars)
1218 const gchar *first_pos;
1219 const gchar *last_pos;
1220 const gchar *p = str;
1221 gint quote_level = -1;
1223 /* speed up line processing by only searching to the last '>' */
1224 if ((first_pos = line_has_quote_char(str, quote_chars)) != NULL) {
1225 /* skip a line if it contains a '<' before the initial '>' */
1226 if (memchr(str, '<', first_pos - str) != NULL)
1228 last_pos = line_has_quote_char_last(first_pos, quote_chars);
1232 while (p <= last_pos) {
1233 while (p < last_pos) {
1234 if (g_ascii_isspace(*p))
1240 if (strchr(quote_chars, *p))
1242 else if (*p != '-' && !g_ascii_isspace(*p) && p <= last_pos) {
1243 /* any characters are allowed except '-','<' and space */
1244 while (*p != '-' && *p != '<'
1245 && !strchr(quote_chars, *p)
1246 && !g_ascii_isspace(*p)
1249 if (strchr(quote_chars, *p))
1261 gint check_line_length(const gchar *str, gint max_chars, gint *line)
1263 const gchar *p = str, *q;
1264 gint cur_line = 0, len;
1266 while ((q = strchr(p, '\n')) != NULL) {
1268 if (len > max_chars) {
1278 if (len > max_chars) {
1287 const gchar * line_has_quote_char(const gchar * str, const gchar *quote_chars)
1289 gchar * position = NULL;
1290 gchar * tmp_pos = NULL;
1293 if (quote_chars == NULL)
1296 for (i = 0; i < strlen(quote_chars); i++) {
1297 tmp_pos = strchr (str, quote_chars[i]);
1299 || (tmp_pos != NULL && position >= tmp_pos) )
1305 static gchar *strstr_with_skip_quote(const gchar *haystack, const gchar *needle)
1307 register guint haystack_len, needle_len;
1308 gboolean in_squote = FALSE, in_dquote = FALSE;
1310 haystack_len = strlen(haystack);
1311 needle_len = strlen(needle);
1313 if (haystack_len < needle_len || needle_len == 0)
1316 while (haystack_len >= needle_len) {
1317 if (!in_squote && !in_dquote &&
1318 !strncmp(haystack, needle, needle_len))
1319 return (gchar *)haystack;
1321 /* 'foo"bar"' -> foo"bar"
1322 "foo'bar'" -> foo'bar' */
1323 if (*haystack == '\'') {
1326 else if (!in_dquote)
1328 } else if (*haystack == '\"') {
1331 else if (!in_squote)
1333 } else if (*haystack == '\\') {
1345 gchar **strsplit_with_quote(const gchar *str, const gchar *delim,
1348 GSList *string_list = NULL, *slist;
1349 gchar **str_array, *s, *new_str;
1350 guint i, n = 1, len;
1352 cm_return_val_if_fail(str != NULL, NULL);
1353 cm_return_val_if_fail(delim != NULL, NULL);
1356 max_tokens = G_MAXINT;
1358 s = strstr_with_skip_quote(str, delim);
1360 guint delimiter_len = strlen(delim);
1364 new_str = g_strndup(str, len);
1366 if (new_str[0] == '\'' || new_str[0] == '\"') {
1367 if (new_str[len - 1] == new_str[0]) {
1368 new_str[len - 1] = '\0';
1369 memmove(new_str, new_str + 1, len - 1);
1372 string_list = g_slist_prepend(string_list, new_str);
1374 str = s + delimiter_len;
1375 s = strstr_with_skip_quote(str, delim);
1376 } while (--max_tokens && s);
1380 new_str = g_strdup(str);
1381 if (new_str[0] == '\'' || new_str[0] == '\"') {
1383 if (new_str[len - 1] == new_str[0]) {
1384 new_str[len - 1] = '\0';
1385 memmove(new_str, new_str + 1, len - 1);
1388 string_list = g_slist_prepend(string_list, new_str);
1392 str_array = g_new(gchar*, n);
1396 str_array[i--] = NULL;
1397 for (slist = string_list; slist; slist = slist->next)
1398 str_array[i--] = slist->data;
1400 g_slist_free(string_list);
1405 gchar *get_abbrev_newsgroup_name(const gchar *group, gint len)
1407 gchar *abbrev_group;
1409 const gchar *p = group;
1412 cm_return_val_if_fail(group != NULL, NULL);
1414 last = group + strlen(group);
1415 abbrev_group = ap = g_malloc(strlen(group) + 1);
1420 if ((ap - abbrev_group) + (last - p) > len && strchr(p, '.')) {
1422 while (*p != '.') p++;
1425 return abbrev_group;
1430 return abbrev_group;
1433 gchar *trim_string(const gchar *str, gint len)
1435 const gchar *p = str;
1440 if (!str) return NULL;
1441 if (strlen(str) <= len)
1442 return g_strdup(str);
1443 if (g_utf8_validate(str, -1, NULL) == FALSE)
1444 return g_strdup(str);
1446 while (*p != '\0') {
1447 mb_len = g_utf8_skip[*(guchar *)p];
1450 else if (new_len + mb_len > len)
1457 Xstrndup_a(new_str, str, new_len, return g_strdup(str));
1458 return g_strconcat(new_str, "...", NULL);
1461 GList *uri_list_extract_filenames(const gchar *uri_list)
1463 GList *result = NULL;
1465 gchar *escaped_utf8uri;
1471 while (g_ascii_isspace(*p)) p++;
1472 if (!strncmp(p, "file:", 5)) {
1475 while (*q && *q != '\n' && *q != '\r') q++;
1478 gchar *file, *locale_file = NULL;
1480 while (q > p && g_ascii_isspace(*q))
1482 Xalloca(escaped_utf8uri, q - p + 2,
1484 Xalloca(file, q - p + 2,
1487 strncpy(escaped_utf8uri, p, q - p + 1);
1488 escaped_utf8uri[q - p + 1] = '\0';
1489 decode_uri(file, escaped_utf8uri);
1491 * g_filename_from_uri() rejects escaped/locale encoded uri
1492 * string which come from Nautilus.
1495 if (g_utf8_validate(file, -1, NULL))
1497 = conv_codeset_strdup(
1500 conv_get_locale_charset_str());
1502 locale_file = g_strdup(file + 5);
1504 locale_file = g_filename_from_uri(file, NULL, NULL);
1506 result = g_list_append(result, locale_file);
1510 p = strchr(p, '\n');
1517 /* Converts two-digit hexadecimal to decimal. Used for unescaping escaped
1520 static gint axtoi(const gchar *hexstr)
1522 gint hi, lo, result;
1525 if ('0' <= hi && hi <= '9') {
1528 if ('a' <= hi && hi <= 'f') {
1531 if ('A' <= hi && hi <= 'F') {
1536 if ('0' <= lo && lo <= '9') {
1539 if ('a' <= lo && lo <= 'f') {
1542 if ('A' <= lo && lo <= 'F') {
1545 result = lo + (16 * hi);
1549 gboolean is_uri_string(const gchar *str)
1551 while (str && *str && g_ascii_isspace(*str))
1553 return (g_ascii_strncasecmp(str, "http://", 7) == 0 ||
1554 g_ascii_strncasecmp(str, "https://", 8) == 0 ||
1555 g_ascii_strncasecmp(str, "ftp://", 6) == 0 ||
1556 g_ascii_strncasecmp(str, "www.", 4) == 0);
1559 gchar *get_uri_path(const gchar *uri)
1561 while (uri && *uri && g_ascii_isspace(*uri))
1563 if (g_ascii_strncasecmp(uri, "http://", 7) == 0)
1564 return (gchar *)(uri + 7);
1565 else if (g_ascii_strncasecmp(uri, "https://", 8) == 0)
1566 return (gchar *)(uri + 8);
1567 else if (g_ascii_strncasecmp(uri, "ftp://", 6) == 0)
1568 return (gchar *)(uri + 6);
1570 return (gchar *)uri;
1573 gint get_uri_len(const gchar *str)
1577 if (is_uri_string(str)) {
1578 for (p = str; *p != '\0'; p++) {
1579 if (!g_ascii_isgraph(*p) || strchr("()<>\"", *p))
1588 /* Decodes URL-Encoded strings (i.e. strings in which spaces are replaced by
1589 * plusses, and escape characters are used)
1591 void decode_uri_with_plus(gchar *decoded_uri, const gchar *encoded_uri, gboolean with_plus)
1593 gchar *dec = decoded_uri;
1594 const gchar *enc = encoded_uri;
1599 if (isxdigit((guchar)enc[0]) &&
1600 isxdigit((guchar)enc[1])) {
1606 if (with_plus && *enc == '+')
1618 void decode_uri(gchar *decoded_uri, const gchar *encoded_uri)
1620 decode_uri_with_plus(decoded_uri, encoded_uri, TRUE);
1623 static gchar *decode_uri_gdup(const gchar *encoded_uri)
1625 gchar *buffer = g_malloc(strlen(encoded_uri)+1);
1626 decode_uri_with_plus(buffer, encoded_uri, FALSE);
1630 gint scan_mailto_url(const gchar *mailto, gchar **from, gchar **to, gchar **cc, gchar **bcc,
1631 gchar **subject, gchar **body, gchar ***attach, gchar **inreplyto)
1635 const gchar *forbidden_uris[] = { ".gnupg/",
1641 gint num_attach = 0;
1642 gchar **my_att = NULL;
1644 Xstrdup_a(tmp_mailto, mailto, return -1);
1646 if (!strncmp(tmp_mailto, "mailto:", 7))
1649 p = strchr(tmp_mailto, '?');
1656 *to = decode_uri_gdup(tmp_mailto);
1658 my_att = g_malloc(sizeof(char *));
1662 gchar *field, *value;
1679 if (*value == '\0') continue;
1681 if (from && !g_ascii_strcasecmp(field, "from")) {
1683 *from = decode_uri_gdup(value);
1685 gchar *tmp = decode_uri_gdup(value);
1686 gchar *new_from = g_strdup_printf("%s, %s", *from, tmp);
1690 } else if (cc && !g_ascii_strcasecmp(field, "cc")) {
1692 *cc = decode_uri_gdup(value);
1694 gchar *tmp = decode_uri_gdup(value);
1695 gchar *new_cc = g_strdup_printf("%s, %s", *cc, tmp);
1699 } else if (bcc && !g_ascii_strcasecmp(field, "bcc")) {
1701 *bcc = decode_uri_gdup(value);
1703 gchar *tmp = decode_uri_gdup(value);
1704 gchar *new_bcc = g_strdup_printf("%s, %s", *bcc, tmp);
1708 } else if (subject && !*subject &&
1709 !g_ascii_strcasecmp(field, "subject")) {
1710 *subject = decode_uri_gdup(value);
1711 } else if (body && !*body && !g_ascii_strcasecmp(field, "body")) {
1712 *body = decode_uri_gdup(value);
1713 } else if (body && !*body && !g_ascii_strcasecmp(field, "insert")) {
1714 gchar *tmp = decode_uri_gdup(value);
1715 if (!g_file_get_contents(tmp, body, NULL, NULL)) {
1716 g_warning("couldn't set insert file '%s' in body", value);
1720 } else if (attach && !g_ascii_strcasecmp(field, "attach")) {
1722 gchar *tmp = decode_uri_gdup(value);
1723 for (; forbidden_uris[i]; i++) {
1724 if (strstr(tmp, forbidden_uris[i])) {
1725 g_print("Refusing to attach '%s', potential private data leak\n",
1733 /* attach is correct */
1735 my_att = g_realloc(my_att, (sizeof(char *))*(num_attach+1));
1736 my_att[num_attach-1] = tmp;
1737 my_att[num_attach] = NULL;
1739 } else if (inreplyto && !*inreplyto &&
1740 !g_ascii_strcasecmp(field, "in-reply-to")) {
1741 *inreplyto = decode_uri_gdup(value);
1752 #include <windows.h>
1753 #ifndef CSIDL_APPDATA
1754 #define CSIDL_APPDATA 0x001a
1756 #ifndef CSIDL_LOCAL_APPDATA
1757 #define CSIDL_LOCAL_APPDATA 0x001c
1759 #ifndef CSIDL_FLAG_CREATE
1760 #define CSIDL_FLAG_CREATE 0x8000
1762 #define DIM(v) (sizeof(v)/sizeof((v)[0]))
1766 w32_strerror (int w32_errno)
1768 static char strerr[256];
1769 int ec = (int)GetLastError ();
1773 FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM, NULL, w32_errno,
1774 MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
1775 strerr, DIM (strerr)-1, NULL);
1779 static __inline__ void *
1780 dlopen (const char * name, int flag)
1782 void * hd = LoadLibrary (name);
1786 static __inline__ void *
1787 dlsym (void * hd, const char * sym)
1791 void * fnc = GetProcAddress (hd, sym);
1800 static __inline__ const char *
1803 return w32_strerror (0);
1807 static __inline__ int
1819 w32_shgetfolderpath (HWND a, int b, HANDLE c, DWORD d, LPSTR e)
1821 static int initialized;
1822 static HRESULT (WINAPI * func)(HWND,int,HANDLE,DWORD,LPSTR);
1826 static char *dllnames[] = { "shell32.dll", "shfolder.dll", NULL };
1832 for (i=0, handle = NULL; !handle && dllnames[i]; i++)
1834 handle = dlopen (dllnames[i], RTLD_LAZY);
1837 func = dlsym (handle, "SHGetFolderPathW");
1848 return func (a,b,c,d,e);
1853 /* Returns a static string with the directroy from which the module
1854 has been loaded. Returns an empty string on error. */
1855 static char *w32_get_module_dir(void)
1857 static char *moddir;
1860 char name[MAX_PATH+10];
1863 if ( !GetModuleFileNameA (0, name, sizeof (name)-10) )
1866 p = strrchr (name, '\\');
1872 moddir = g_strdup (name);
1876 #endif /* G_OS_WIN32 */
1878 /* Return a static string with the locale dir. */
1879 const gchar *get_locale_dir(void)
1881 static gchar *loc_dir;
1885 loc_dir = g_strconcat(w32_get_module_dir(), G_DIR_SEPARATOR_S,
1886 "\\share\\locale", NULL);
1889 loc_dir = LOCALEDIR;
1895 const gchar *get_home_dir(void)
1898 static char home_dir_utf16[MAX_PATH] = "";
1899 static gchar *home_dir_utf8 = NULL;
1900 if (home_dir_utf16[0] == '\0') {
1901 if (w32_shgetfolderpath
1902 (NULL, CSIDL_APPDATA|CSIDL_FLAG_CREATE,
1903 NULL, 0, home_dir_utf16) < 0)
1904 strcpy (home_dir_utf16, "C:\\Sylpheed");
1905 home_dir_utf8 = g_utf16_to_utf8 ((const gunichar *)home_dir_utf16, -1, NULL, NULL, NULL);
1907 return home_dir_utf8;
1909 static const gchar *homeenv = NULL;
1914 if (!homeenv && g_getenv("HOME") != NULL)
1915 homeenv = g_strdup(g_getenv("HOME"));
1917 homeenv = g_get_home_dir();
1923 static gchar *claws_rc_dir = NULL;
1924 static gboolean rc_dir_alt = FALSE;
1925 const gchar *get_rc_dir(void)
1928 if (!claws_rc_dir) {
1929 claws_rc_dir = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
1931 debug_print("using default rc_dir %s\n", claws_rc_dir);
1933 return claws_rc_dir;
1936 void set_rc_dir(const gchar *dir)
1938 gchar *canonical_dir;
1939 if (claws_rc_dir != NULL) {
1940 g_print("Error: rc_dir already set\n");
1942 int err = cm_canonicalize_filename(dir, &canonical_dir);
1946 g_print("Error looking for %s: %d(%s)\n",
1947 dir, -err, g_strerror(-err));
1952 claws_rc_dir = canonical_dir;
1954 len = strlen(claws_rc_dir);
1955 if (claws_rc_dir[len - 1] == G_DIR_SEPARATOR)
1956 claws_rc_dir[len - 1] = '\0';
1958 debug_print("set rc_dir to %s\n", claws_rc_dir);
1959 if (!is_dir_exist(claws_rc_dir)) {
1960 if (make_dir_hier(claws_rc_dir) != 0) {
1961 g_print("Error: can't create %s\n",
1969 gboolean rc_dir_is_alt(void) {
1973 const gchar *get_mail_base_dir(void)
1975 return get_home_dir();
1978 const gchar *get_news_cache_dir(void)
1980 static gchar *news_cache_dir = NULL;
1981 if (!news_cache_dir)
1982 news_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1983 NEWS_CACHE_DIR, NULL);
1985 return news_cache_dir;
1988 const gchar *get_imap_cache_dir(void)
1990 static gchar *imap_cache_dir = NULL;
1992 if (!imap_cache_dir)
1993 imap_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1994 IMAP_CACHE_DIR, NULL);
1996 return imap_cache_dir;
1999 const gchar *get_mime_tmp_dir(void)
2001 static gchar *mime_tmp_dir = NULL;
2004 mime_tmp_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
2005 MIME_TMP_DIR, NULL);
2007 return mime_tmp_dir;
2010 const gchar *get_template_dir(void)
2012 static gchar *template_dir = NULL;
2015 template_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
2016 TEMPLATE_DIR, NULL);
2018 return template_dir;
2022 const gchar *get_cert_file(void)
2024 const gchar *cert_file = NULL;
2026 cert_file = g_strconcat(w32_get_module_dir(),
2027 "\\share\\claws-mail\\",
2028 "ca-certificates.crt",
2034 /* Return the filepath of the claws-mail.desktop file */
2035 const gchar *get_desktop_file(void)
2037 #ifdef DESKTOPFILEPATH
2038 return DESKTOPFILEPATH;
2044 /* Return the default directory for Plugins. */
2045 const gchar *get_plugin_dir(void)
2048 static gchar *plugin_dir = NULL;
2051 plugin_dir = g_strconcat(w32_get_module_dir(),
2052 "\\lib\\claws-mail\\plugins\\",
2056 if (is_dir_exist(PLUGINDIR))
2059 static gchar *plugin_dir = NULL;
2061 plugin_dir = g_strconcat(get_rc_dir(),
2062 G_DIR_SEPARATOR_S, "plugins",
2063 G_DIR_SEPARATOR_S, NULL);
2071 /* Return the default directory for Themes. */
2072 const gchar *get_themes_dir(void)
2074 static gchar *themes_dir = NULL;
2077 themes_dir = g_strconcat(w32_get_module_dir(),
2078 "\\share\\claws-mail\\themes",
2084 const gchar *get_tmp_dir(void)
2086 static gchar *tmp_dir = NULL;
2089 tmp_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
2095 gchar *get_tmp_file(void)
2098 static guint32 id = 0;
2100 tmp_file = g_strdup_printf("%s%ctmpfile.%08x",
2101 get_tmp_dir(), G_DIR_SEPARATOR, id++);
2106 const gchar *get_domain_name(void)
2109 static gchar *domain_name = NULL;
2115 if (gethostname(hostname, sizeof(hostname)) != 0) {
2116 perror("gethostname");
2117 domain_name = "localhost";
2119 hostname[sizeof(hostname) - 1] = '\0';
2120 if ((hp = my_gethostbyname(hostname)) == NULL) {
2121 perror("gethostbyname");
2122 domain_name = g_strdup(hostname);
2124 domain_name = g_strdup(hp->h_name);
2127 debug_print("domain name = %s\n", domain_name);
2136 off_t get_file_size(const gchar *file)
2140 if (g_stat(file, &s) < 0) {
2141 FILE_OP_ERROR(file, "stat");
2148 time_t get_file_mtime(const gchar *file)
2152 if (g_stat(file, &s) < 0) {
2153 FILE_OP_ERROR(file, "stat");
2160 off_t get_file_size_as_crlf(const gchar *file)
2164 gchar buf[BUFFSIZE];
2166 if ((fp = g_fopen(file, "rb")) == NULL) {
2167 FILE_OP_ERROR(file, "g_fopen");
2171 while (fgets(buf, sizeof(buf), fp) != NULL) {
2173 size += strlen(buf) + 2;
2177 FILE_OP_ERROR(file, "fgets");
2186 gboolean file_exist(const gchar *file, gboolean allow_fifo)
2193 if (g_stat(file, &s) < 0) {
2194 if (ENOENT != errno) FILE_OP_ERROR(file, "stat");
2198 if (S_ISREG(s.st_mode) || (allow_fifo && S_ISFIFO(s.st_mode)))
2205 /* Test on whether FILE is a relative file name. This is
2206 * straightforward for Unix but more complex for Windows. */
2207 gboolean is_relative_filename(const gchar *file)
2212 if ( *file == '\\' && file[1] == '\\' && strchr (file+2, '\\') )
2213 return FALSE; /* Prefixed with a hostname - this can't
2214 * be a relative name. */
2216 if ( ((*file >= 'a' && *file <= 'z')
2217 || (*file >= 'A' && *file <= 'Z'))
2219 file += 2; /* Skip drive letter. */
2221 return !(*file == '\\' || *file == '/');
2223 return !(*file == G_DIR_SEPARATOR);
2228 gboolean is_dir_exist(const gchar *dir)
2233 return g_file_test(dir, G_FILE_TEST_IS_DIR);
2236 gboolean is_file_entry_exist(const gchar *file)
2241 return g_file_test(file, G_FILE_TEST_EXISTS);
2244 gboolean dirent_is_regular_file(struct dirent *d)
2246 #if !defined(G_OS_WIN32) && defined(HAVE_DIRENT_D_TYPE)
2247 if (d->d_type == DT_REG)
2249 else if (d->d_type != DT_UNKNOWN)
2253 return g_file_test(d->d_name, G_FILE_TEST_IS_REGULAR);
2256 gint change_dir(const gchar *dir)
2258 gchar *prevdir = NULL;
2261 prevdir = g_get_current_dir();
2263 if (g_chdir(dir) < 0) {
2264 FILE_OP_ERROR(dir, "chdir");
2265 if (debug_mode) g_free(prevdir);
2267 } else if (debug_mode) {
2270 cwd = g_get_current_dir();
2271 if (strcmp(prevdir, cwd) != 0)
2272 g_print("current dir: %s\n", cwd);
2280 gint make_dir(const gchar *dir)
2282 if (g_mkdir(dir, S_IRWXU) < 0) {
2283 FILE_OP_ERROR(dir, "mkdir");
2286 if (g_chmod(dir, S_IRWXU) < 0)
2287 FILE_OP_ERROR(dir, "chmod");
2292 gint make_dir_hier(const gchar *dir)
2297 for (p = dir; (p = strchr(p, G_DIR_SEPARATOR)) != NULL; p++) {
2298 parent_dir = g_strndup(dir, p - dir);
2299 if (*parent_dir != '\0') {
2300 if (!is_dir_exist(parent_dir)) {
2301 if (make_dir(parent_dir) < 0) {
2310 if (!is_dir_exist(dir)) {
2311 if (make_dir(dir) < 0)
2318 gint remove_all_files(const gchar *dir)
2321 const gchar *dir_name;
2324 prev_dir = g_get_current_dir();
2326 if (g_chdir(dir) < 0) {
2327 FILE_OP_ERROR(dir, "chdir");
2332 if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2333 g_warning("failed to open directory: %s", dir);
2338 while ((dir_name = g_dir_read_name(dp)) != NULL) {
2339 if (claws_unlink(dir_name) < 0)
2340 FILE_OP_ERROR(dir_name, "unlink");
2345 if (g_chdir(prev_dir) < 0) {
2346 FILE_OP_ERROR(prev_dir, "chdir");
2356 gint remove_numbered_files(const gchar *dir, guint first, guint last)
2359 const gchar *dir_name;
2363 if (first == last) {
2364 /* Skip all the dir reading part. */
2365 gchar *filename = g_strdup_printf("%s%s%u", dir, G_DIR_SEPARATOR_S, first);
2366 if (is_dir_exist(filename)) {
2367 /* a numbered directory with this name exists,
2368 * remove the dot-file instead */
2370 filename = g_strdup_printf("%s%s.%u", dir, G_DIR_SEPARATOR_S, first);
2372 if (claws_unlink(filename) < 0) {
2373 FILE_OP_ERROR(filename, "unlink");
2381 prev_dir = g_get_current_dir();
2383 if (g_chdir(dir) < 0) {
2384 FILE_OP_ERROR(dir, "chdir");
2389 if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2390 g_warning("failed to open directory: %s", dir);
2395 while ((dir_name = g_dir_read_name(dp)) != NULL) {
2396 file_no = to_number(dir_name);
2397 if (file_no > 0 && first <= file_no && file_no <= last) {
2398 if (is_dir_exist(dir_name)) {
2399 gchar *dot_file = g_strdup_printf(".%s", dir_name);
2400 if (is_file_exist(dot_file) && claws_unlink(dot_file) < 0) {
2401 FILE_OP_ERROR(dot_file, "unlink");
2406 if (claws_unlink(dir_name) < 0)
2407 FILE_OP_ERROR(dir_name, "unlink");
2413 if (g_chdir(prev_dir) < 0) {
2414 FILE_OP_ERROR(prev_dir, "chdir");
2424 gint remove_numbered_files_not_in_list(const gchar *dir, GSList *numberlist)
2427 const gchar *dir_name;
2430 GHashTable *wanted_files;
2433 if (numberlist == NULL)
2436 prev_dir = g_get_current_dir();
2438 if (g_chdir(dir) < 0) {
2439 FILE_OP_ERROR(dir, "chdir");
2444 if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2445 FILE_OP_ERROR(dir, "opendir");
2450 wanted_files = g_hash_table_new(g_direct_hash, g_direct_equal);
2451 for (cur = numberlist; cur != NULL; cur = cur->next) {
2452 /* numberlist->data is expected to be GINT_TO_POINTER */
2453 g_hash_table_insert(wanted_files, cur->data, GINT_TO_POINTER(1));
2456 while ((dir_name = g_dir_read_name(dp)) != NULL) {
2457 file_no = to_number(dir_name);
2458 if (is_dir_exist(dir_name))
2460 if (file_no > 0 && g_hash_table_lookup(wanted_files, GINT_TO_POINTER(file_no)) == NULL) {
2461 debug_print("removing unwanted file %d from %s\n", file_no, dir);
2462 if (is_dir_exist(dir_name)) {
2463 gchar *dot_file = g_strdup_printf(".%s", dir_name);
2464 if (is_file_exist(dot_file) && claws_unlink(dot_file) < 0) {
2465 FILE_OP_ERROR(dot_file, "unlink");
2470 if (claws_unlink(dir_name) < 0)
2471 FILE_OP_ERROR(dir_name, "unlink");
2476 g_hash_table_destroy(wanted_files);
2478 if (g_chdir(prev_dir) < 0) {
2479 FILE_OP_ERROR(prev_dir, "chdir");
2489 gint remove_all_numbered_files(const gchar *dir)
2491 return remove_numbered_files(dir, 0, UINT_MAX);
2494 gint remove_dir_recursive(const gchar *dir)
2498 const gchar *dir_name;
2501 if (g_stat(dir, &s) < 0) {
2502 FILE_OP_ERROR(dir, "stat");
2503 if (ENOENT == errno) return 0;
2507 if (!S_ISDIR(s.st_mode)) {
2508 if (claws_unlink(dir) < 0) {
2509 FILE_OP_ERROR(dir, "unlink");
2516 prev_dir = g_get_current_dir();
2517 /* g_print("prev_dir = %s\n", prev_dir); */
2519 if (!path_cmp(prev_dir, dir)) {
2521 if (g_chdir("..") < 0) {
2522 FILE_OP_ERROR(dir, "chdir");
2525 prev_dir = g_get_current_dir();
2528 if (g_chdir(dir) < 0) {
2529 FILE_OP_ERROR(dir, "chdir");
2534 if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2535 g_warning("failed to open directory: %s", dir);
2541 /* remove all files in the directory */
2542 while ((dir_name = g_dir_read_name(dp)) != NULL) {
2543 /* g_print("removing %s\n", dir_name); */
2545 if (is_dir_exist(dir_name)) {
2548 if ((ret = remove_dir_recursive(dir_name)) < 0) {
2549 g_warning("can't remove directory: %s", dir_name);
2553 if (claws_unlink(dir_name) < 0)
2554 FILE_OP_ERROR(dir_name, "unlink");
2560 if (g_chdir(prev_dir) < 0) {
2561 FILE_OP_ERROR(prev_dir, "chdir");
2568 if (g_rmdir(dir) < 0) {
2569 FILE_OP_ERROR(dir, "rmdir");
2576 gint rename_force(const gchar *oldpath, const gchar *newpath)
2579 if (!is_file_entry_exist(oldpath)) {
2583 if (is_file_exist(newpath)) {
2584 if (claws_unlink(newpath) < 0)
2585 FILE_OP_ERROR(newpath, "unlink");
2588 return g_rename(oldpath, newpath);
2592 * Append src file body to the tail of dest file.
2593 * Now keep_backup has no effects.
2595 gint append_file(const gchar *src, const gchar *dest, gboolean keep_backup)
2597 FILE *src_fp, *dest_fp;
2601 gboolean err = FALSE;
2603 if ((src_fp = g_fopen(src, "rb")) == NULL) {
2604 FILE_OP_ERROR(src, "g_fopen");
2608 if ((dest_fp = g_fopen(dest, "ab")) == NULL) {
2609 FILE_OP_ERROR(dest, "g_fopen");
2614 if (change_file_mode_rw(dest_fp, dest) < 0) {
2615 FILE_OP_ERROR(dest, "chmod");
2616 g_warning("can't change file mode: %s", dest);
2619 while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
2620 if (n_read < sizeof(buf) && ferror(src_fp))
2622 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2623 g_warning("writing to %s failed.", dest);
2631 if (ferror(src_fp)) {
2632 FILE_OP_ERROR(src, "fread");
2636 if (fclose(dest_fp) == EOF) {
2637 FILE_OP_ERROR(dest, "fclose");
2649 gint copy_file(const gchar *src, const gchar *dest, gboolean keep_backup)
2651 FILE *src_fp, *dest_fp;
2654 gchar *dest_bak = NULL;
2655 gboolean err = FALSE;
2657 if ((src_fp = g_fopen(src, "rb")) == NULL) {
2658 FILE_OP_ERROR(src, "g_fopen");
2661 if (is_file_exist(dest)) {
2662 dest_bak = g_strconcat(dest, ".bak", NULL);
2663 if (rename_force(dest, dest_bak) < 0) {
2664 FILE_OP_ERROR(dest, "rename");
2671 if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2672 FILE_OP_ERROR(dest, "g_fopen");
2675 if (rename_force(dest_bak, dest) < 0)
2676 FILE_OP_ERROR(dest_bak, "rename");
2682 if (change_file_mode_rw(dest_fp, dest) < 0) {
2683 FILE_OP_ERROR(dest, "chmod");
2684 g_warning("can't change file mode: %s", dest);
2687 while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
2688 if (n_read < sizeof(buf) && ferror(src_fp))
2690 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2691 g_warning("writing to %s failed.", dest);
2696 if (rename_force(dest_bak, dest) < 0)
2697 FILE_OP_ERROR(dest_bak, "rename");
2704 if (ferror(src_fp)) {
2705 FILE_OP_ERROR(src, "fread");
2709 if (fclose(dest_fp) == EOF) {
2710 FILE_OP_ERROR(dest, "fclose");
2717 if (rename_force(dest_bak, dest) < 0)
2718 FILE_OP_ERROR(dest_bak, "rename");
2724 if (keep_backup == FALSE && dest_bak)
2725 claws_unlink(dest_bak);
2732 gint move_file(const gchar *src, const gchar *dest, gboolean overwrite)
2734 if (overwrite == FALSE && is_file_exist(dest)) {
2735 g_warning("move_file(): file %s already exists.", dest);
2739 if (rename_force(src, dest) == 0) return 0;
2741 if (EXDEV != errno) {
2742 FILE_OP_ERROR(src, "rename");
2746 if (copy_file(src, dest, FALSE) < 0) return -1;
2753 gint copy_file_part_to_fp(FILE *fp, off_t offset, size_t length, FILE *dest_fp)
2756 gint bytes_left, to_read;
2759 if (fseek(fp, offset, SEEK_SET) < 0) {
2764 bytes_left = length;
2765 to_read = MIN(bytes_left, sizeof(buf));
2767 while ((n_read = fread(buf, sizeof(gchar), to_read, fp)) > 0) {
2768 if (n_read < to_read && ferror(fp))
2770 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2773 bytes_left -= n_read;
2774 if (bytes_left == 0)
2776 to_read = MIN(bytes_left, sizeof(buf));
2787 gint copy_file_part(FILE *fp, off_t offset, size_t length, const gchar *dest)
2790 gboolean err = FALSE;
2792 if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2793 FILE_OP_ERROR(dest, "g_fopen");
2797 if (change_file_mode_rw(dest_fp, dest) < 0) {
2798 FILE_OP_ERROR(dest, "chmod");
2799 g_warning("can't change file mode: %s", dest);
2802 if (copy_file_part_to_fp(fp, offset, length, dest_fp) < 0)
2805 if (!err && fclose(dest_fp) == EOF) {
2806 FILE_OP_ERROR(dest, "fclose");
2811 g_warning("writing to %s failed.", dest);
2819 /* convert line endings into CRLF. If the last line doesn't end with
2820 * linebreak, add it.
2822 gchar *canonicalize_str(const gchar *str)
2828 for (p = str; *p != '\0'; ++p) {
2835 if (p == str || *(p - 1) != '\n')
2838 out = outp = g_malloc(new_len + 1);
2839 for (p = str; *p != '\0'; ++p) {
2846 if (p == str || *(p - 1) != '\n') {
2855 gint canonicalize_file(const gchar *src, const gchar *dest)
2857 FILE *src_fp, *dest_fp;
2858 gchar buf[BUFFSIZE];
2860 gboolean err = FALSE;
2861 gboolean last_linebreak = FALSE;
2863 if (src == NULL || dest == NULL)
2866 if ((src_fp = g_fopen(src, "rb")) == NULL) {
2867 FILE_OP_ERROR(src, "g_fopen");
2871 if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2872 FILE_OP_ERROR(dest, "g_fopen");
2877 if (change_file_mode_rw(dest_fp, dest) < 0) {
2878 FILE_OP_ERROR(dest, "chmod");
2879 g_warning("can't change file mode: %s", dest);
2882 while (fgets(buf, sizeof(buf), src_fp) != NULL) {
2886 if (len == 0) break;
2887 last_linebreak = FALSE;
2889 if (buf[len - 1] != '\n') {
2890 last_linebreak = TRUE;
2891 r = fputs(buf, dest_fp);
2892 } else if (len > 1 && buf[len - 1] == '\n' && buf[len - 2] == '\r') {
2893 r = fputs(buf, dest_fp);
2896 r = fwrite(buf, 1, len - 1, dest_fp);
2901 r = fputs("\r\n", dest_fp);
2905 g_warning("writing to %s failed.", dest);
2913 if (last_linebreak == TRUE) {
2914 if (fputs("\r\n", dest_fp) == EOF)
2918 if (ferror(src_fp)) {
2919 FILE_OP_ERROR(src, "fgets");
2923 if (fclose(dest_fp) == EOF) {
2924 FILE_OP_ERROR(dest, "fclose");
2936 gint canonicalize_file_replace(const gchar *file)
2940 tmp_file = get_tmp_file();
2942 if (canonicalize_file(file, tmp_file) < 0) {
2947 if (move_file(tmp_file, file, TRUE) < 0) {
2948 g_warning("can't replace file: %s", file);
2949 claws_unlink(tmp_file);
2958 gchar *normalize_newlines(const gchar *str)
2963 out = outp = g_malloc(strlen(str) + 1);
2964 for (p = str; *p != '\0'; ++p) {
2966 if (*(p + 1) != '\n')
2977 gchar *get_outgoing_rfc2822_str(FILE *fp)
2979 gchar buf[BUFFSIZE];
2983 str = g_string_new(NULL);
2985 /* output header part */
2986 while (fgets(buf, sizeof(buf), fp) != NULL) {
2988 if (!g_ascii_strncasecmp(buf, "Bcc:", 4)) {
2995 else if (next != ' ' && next != '\t') {
2999 if (fgets(buf, sizeof(buf), fp) == NULL)
3003 g_string_append(str, buf);
3004 g_string_append(str, "\r\n");
3010 /* output body part */
3011 while (fgets(buf, sizeof(buf), fp) != NULL) {
3014 g_string_append_c(str, '.');
3015 g_string_append(str, buf);
3016 g_string_append(str, "\r\n");
3020 g_string_free(str, FALSE);
3026 * Create a new boundary in a way that it is very unlikely that this
3027 * will occur in the following text. It would be easy to ensure
3028 * uniqueness if everything is either quoted-printable or base64
3029 * encoded (note that conversion is allowed), but because MIME bodies
3030 * may be nested, it may happen that the same boundary has already
3033 * boundary := 0*69<bchars> bcharsnospace
3034 * bchars := bcharsnospace / " "
3035 * bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" /
3036 * "+" / "_" / "," / "-" / "." /
3037 * "/" / ":" / "=" / "?"
3039 * some special characters removed because of buggy MTAs
3042 gchar *generate_mime_boundary(const gchar *prefix)
3044 static gchar tbl[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
3045 "abcdefghijklmnopqrstuvwxyz"
3050 for (i = 0; i < sizeof(buf_uniq) - 1; i++)
3051 buf_uniq[i] = tbl[g_random_int_range(0, sizeof(tbl) - 1)];
3054 return g_strdup_printf("%s_/%s", prefix ? prefix : "MP",
3058 gint change_file_mode_rw(FILE *fp, const gchar *file)
3061 return fchmod(fileno(fp), S_IRUSR|S_IWUSR);
3063 return g_chmod(file, S_IRUSR|S_IWUSR);
3067 FILE *my_tmpfile(void)
3069 #if HAVE_MKSTEMP || defined(G_OS_WIN32)
3070 const gchar suffix[] = ".XXXXXX";
3071 const gchar *tmpdir;
3073 const gchar *progname;
3082 tmpdir = get_tmp_dir();
3083 tmplen = strlen(tmpdir);
3084 progname = g_get_prgname();
3085 if (progname == NULL)
3086 progname = "claws-mail";
3087 proglen = strlen(progname);
3088 Xalloca(fname, tmplen + 1 + proglen + sizeof(suffix),
3091 memcpy(fname, tmpdir, tmplen);
3092 fname[tmplen] = G_DIR_SEPARATOR;
3093 memcpy(fname + tmplen + 1, progname, proglen);
3094 memcpy(fname + tmplen + 1 + proglen, suffix, sizeof(suffix));
3096 fd = mkstemp(fname);
3101 claws_unlink(fname);
3103 /* verify that we can write in the file after unlinking */
3104 if (write(fd, buf, 1) < 0) {
3111 fp = fdopen(fd, "w+b");
3119 #endif /* HAVE_MKSTEMP || G_OS_WIN32 */
3124 FILE *get_tmpfile_in_dir(const gchar *dir, gchar **filename)
3128 char *template = g_strdup_printf ("%s%cclaws.XXXXXX",
3129 dir, G_DIR_SEPARATOR);
3130 fd = mkstemp_name(template, filename);
3133 *filename = g_strdup_printf("%s%cclaws.XXXXXX", dir, G_DIR_SEPARATOR);
3134 fd = mkstemp(*filename);
3138 return fdopen(fd, "w+");
3141 FILE *str_open_as_stream(const gchar *str)
3146 cm_return_val_if_fail(str != NULL, NULL);
3150 FILE_OP_ERROR("str_open_as_stream", "my_tmpfile");
3155 if (len == 0) return fp;
3157 if (fwrite(str, 1, len, fp) != len) {
3158 FILE_OP_ERROR("str_open_as_stream", "fwrite");
3167 gint str_write_to_file(const gchar *str, const gchar *file)
3172 cm_return_val_if_fail(str != NULL, -1);
3173 cm_return_val_if_fail(file != NULL, -1);
3175 if ((fp = g_fopen(file, "wb")) == NULL) {
3176 FILE_OP_ERROR(file, "g_fopen");
3186 if (fwrite(str, 1, len, fp) != len) {
3187 FILE_OP_ERROR(file, "fwrite");
3193 if (fclose(fp) == EOF) {
3194 FILE_OP_ERROR(file, "fclose");
3202 static gchar *file_read_stream_to_str_full(FILE *fp, gboolean recode)
3209 cm_return_val_if_fail(fp != NULL, NULL);
3211 array = g_byte_array_new();
3213 while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
3214 if (n_read < sizeof(buf) && ferror(fp))
3216 g_byte_array_append(array, buf, n_read);
3220 FILE_OP_ERROR("file stream", "fread");
3221 g_byte_array_free(array, TRUE);
3226 g_byte_array_append(array, buf, 1);
3227 str = (gchar *)array->data;
3228 g_byte_array_free(array, FALSE);
3230 if (recode && !g_utf8_validate(str, -1, NULL)) {
3231 const gchar *src_codeset, *dest_codeset;
3233 src_codeset = conv_get_locale_charset_str();
3234 dest_codeset = CS_UTF_8;
3235 tmp = conv_codeset_strdup(str, src_codeset, dest_codeset);
3243 static gchar *file_read_to_str_full(const gchar *file, gboolean recode)
3250 struct timeval timeout = {1, 0};
3255 cm_return_val_if_fail(file != NULL, NULL);
3257 if (g_stat(file, &s) != 0) {
3258 FILE_OP_ERROR(file, "stat");
3261 if (S_ISDIR(s.st_mode)) {
3262 g_warning("%s: is a directory", file);
3267 fp = g_fopen (file, "rb");
3269 FILE_OP_ERROR(file, "open");
3273 /* test whether the file is readable without blocking */
3274 fd = g_open(file, O_RDONLY | O_NONBLOCK, 0);
3276 FILE_OP_ERROR(file, "open");
3283 /* allow for one second */
3284 err = select(fd+1, &fds, NULL, NULL, &timeout);
3285 if (err <= 0 || !FD_ISSET(fd, &fds)) {
3287 FILE_OP_ERROR(file, "select");
3289 g_warning("%s: doesn't seem readable", file);
3295 /* Now clear O_NONBLOCK */
3296 if ((fflags = fcntl(fd, F_GETFL)) < 0) {
3297 FILE_OP_ERROR(file, "fcntl (F_GETFL)");
3301 if (fcntl(fd, F_SETFL, (fflags & ~O_NONBLOCK)) < 0) {
3302 FILE_OP_ERROR(file, "fcntl (F_SETFL)");
3307 /* get the FILE pointer */
3308 fp = fdopen(fd, "rb");
3311 FILE_OP_ERROR(file, "fdopen");
3312 close(fd); /* if fp isn't NULL, we'll use fclose instead! */
3317 str = file_read_stream_to_str_full(fp, recode);
3324 gchar *file_read_to_str(const gchar *file)
3326 return file_read_to_str_full(file, TRUE);
3328 gchar *file_read_stream_to_str(FILE *fp)
3330 return file_read_stream_to_str_full(fp, TRUE);
3333 gchar *file_read_to_str_no_recode(const gchar *file)
3335 return file_read_to_str_full(file, FALSE);
3337 gchar *file_read_stream_to_str_no_recode(FILE *fp)
3339 return file_read_stream_to_str_full(fp, FALSE);
3342 char *fgets_crlf(char *buf, int size, FILE *stream)
3344 gboolean is_cr = FALSE;
3345 gboolean last_was_cr = FALSE;
3350 while (--size > 0 && (c = getc(stream)) != EOF)
3353 is_cr = (c == '\r');
3363 last_was_cr = is_cr;
3365 if (c == EOF && cs == buf)
3373 static gint execute_async(gchar *const argv[])
3375 cm_return_val_if_fail(argv != NULL && argv[0] != NULL, -1);
3377 if (g_spawn_async(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH,
3378 NULL, NULL, NULL, FALSE) == FALSE) {
3379 g_warning("couldn't execute command: %s", argv[0]);
3386 static gint execute_sync(gchar *const argv[])
3390 cm_return_val_if_fail(argv != NULL && argv[0] != NULL, -1);
3393 if (g_spawn_sync(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH,
3394 NULL, NULL, NULL, NULL, &status, NULL) == FALSE) {
3395 g_warning("couldn't execute command: %s", argv[0]);
3399 if (WIFEXITED(status))
3400 return WEXITSTATUS(status);
3404 if (g_spawn_sync(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH|
3405 G_SPAWN_CHILD_INHERITS_STDIN|G_SPAWN_LEAVE_DESCRIPTORS_OPEN,
3406 NULL, NULL, NULL, NULL, &status, NULL) == FALSE) {
3407 g_warning("couldn't execute command: %s", argv[0]);
3415 gint execute_command_line(const gchar *cmdline, gboolean async)
3420 debug_print("execute_command_line(): executing: %s\n", cmdline?cmdline:"(null)");
3422 argv = strsplit_with_quote(cmdline, " ", 0);
3425 ret = execute_async(argv);
3427 ret = execute_sync(argv);
3434 gchar *get_command_output(const gchar *cmdline)
3436 gchar *child_stdout;
3439 cm_return_val_if_fail(cmdline != NULL, NULL);
3441 debug_print("get_command_output(): executing: %s\n", cmdline);
3443 if (g_spawn_command_line_sync(cmdline, &child_stdout, NULL, &status,
3445 g_warning("couldn't execute command: %s", cmdline);
3449 return child_stdout;
3452 static gint is_unchanged_uri_char(char c)
3463 static void encode_uri(gchar *encoded_uri, gint bufsize, const gchar *uri)
3469 for(i = 0; i < strlen(uri) ; i++) {
3470 if (is_unchanged_uri_char(uri[i])) {
3471 if (k + 2 >= bufsize)
3473 encoded_uri[k++] = uri[i];
3476 char * hexa = "0123456789ABCDEF";
3478 if (k + 4 >= bufsize)
3480 encoded_uri[k++] = '%';
3481 encoded_uri[k++] = hexa[uri[i] / 16];
3482 encoded_uri[k++] = hexa[uri[i] % 16];
3488 gint open_uri(const gchar *uri, const gchar *cmdline)
3492 gchar buf[BUFFSIZE];
3494 gchar encoded_uri[BUFFSIZE];
3495 cm_return_val_if_fail(uri != NULL, -1);
3497 /* an option to choose whether to use encode_uri or not ? */
3498 encode_uri(encoded_uri, BUFFSIZE, uri);
3501 (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
3502 !strchr(p + 2, '%'))
3503 g_snprintf(buf, sizeof(buf), cmdline, encoded_uri);
3506 g_warning("Open URI command-line is invalid "
3507 "(there must be only one '%%s'): %s",
3509 g_snprintf(buf, sizeof(buf), DEFAULT_BROWSER_CMD, encoded_uri);
3512 execute_command_line(buf, TRUE);
3514 ShellExecute(NULL, "open", uri, NULL, NULL, SW_SHOW);
3519 gint open_txt_editor(const gchar *filepath, const gchar *cmdline)
3521 gchar buf[BUFFSIZE];
3524 cm_return_val_if_fail(filepath != NULL, -1);
3527 (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
3528 !strchr(p + 2, '%'))
3529 g_snprintf(buf, sizeof(buf), cmdline, filepath);
3532 g_warning("Open Text Editor command-line is invalid "
3533 "(there must be only one '%%s'): %s",
3535 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, filepath);
3538 execute_command_line(buf, TRUE);
3543 time_t remote_tzoffset_sec(const gchar *zone)
3545 static gchar ustzstr[] = "PSTPDTMSTMDTCSTCDTESTEDT";
3551 time_t remoteoffset;
3553 strncpy(zone3, zone, 3);
3557 if (sscanf(zone, "%c%d", &c, &offset) == 2 &&
3558 (c == '+' || c == '-')) {
3559 remoteoffset = ((offset / 100) * 60 + (offset % 100)) * 60;
3561 remoteoffset = -remoteoffset;
3562 } else if (!strncmp(zone, "UT" , 2) ||
3563 !strncmp(zone, "GMT", 2)) {
3565 } else if (strlen(zone3) == 3) {
3566 for (p = ustzstr; *p != '\0'; p += 3) {
3567 if (!g_ascii_strncasecmp(p, zone3, 3)) {
3568 iustz = ((gint)(p - ustzstr) / 3 + 1) / 2 - 8;
3569 remoteoffset = iustz * 3600;
3575 } else if (strlen(zone3) == 1) {
3577 case 'Z': remoteoffset = 0; break;
3578 case 'A': remoteoffset = -1; break;
3579 case 'B': remoteoffset = -2; break;
3580 case 'C': remoteoffset = -3; break;
3581 case 'D': remoteoffset = -4; break;
3582 case 'E': remoteoffset = -5; break;
3583 case 'F': remoteoffset = -6; break;
3584 case 'G': remoteoffset = -7; break;
3585 case 'H': remoteoffset = -8; break;
3586 case 'I': remoteoffset = -9; break;
3587 case 'K': remoteoffset = -10; break; /* J is not used */
3588 case 'L': remoteoffset = -11; break;
3589 case 'M': remoteoffset = -12; break;
3590 case 'N': remoteoffset = 1; break;
3591 case 'O': remoteoffset = 2; break;
3592 case 'P': remoteoffset = 3; break;
3593 case 'Q': remoteoffset = 4; break;
3594 case 'R': remoteoffset = 5; break;
3595 case 'S': remoteoffset = 6; break;
3596 case 'T': remoteoffset = 7; break;
3597 case 'U': remoteoffset = 8; break;
3598 case 'V': remoteoffset = 9; break;
3599 case 'W': remoteoffset = 10; break;
3600 case 'X': remoteoffset = 11; break;
3601 case 'Y': remoteoffset = 12; break;
3602 default: remoteoffset = 0; break;
3604 remoteoffset = remoteoffset * 3600;
3608 return remoteoffset;
3611 time_t tzoffset_sec(time_t *now)
3615 struct tm buf1, buf2;
3617 if (now && *now < 0)
3620 gmt = *gmtime_r(now, &buf1);
3621 lt = localtime_r(now, &buf2);
3623 off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
3625 if (lt->tm_year < gmt.tm_year)
3627 else if (lt->tm_year > gmt.tm_year)
3629 else if (lt->tm_yday < gmt.tm_yday)
3631 else if (lt->tm_yday > gmt.tm_yday)
3634 if (off >= 24 * 60) /* should be impossible */
3635 off = 23 * 60 + 59; /* if not, insert silly value */
3636 if (off <= -24 * 60)
3637 off = -(23 * 60 + 59);
3642 /* calculate timezone offset */
3643 gchar *tzoffset(time_t *now)
3645 static gchar offset_string[6];
3649 struct tm buf1, buf2;
3651 if (now && *now < 0)
3654 gmt = *gmtime_r(now, &buf1);
3655 lt = localtime_r(now, &buf2);
3657 off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
3659 if (lt->tm_year < gmt.tm_year)
3661 else if (lt->tm_year > gmt.tm_year)
3663 else if (lt->tm_yday < gmt.tm_yday)
3665 else if (lt->tm_yday > gmt.tm_yday)
3673 if (off >= 24 * 60) /* should be impossible */
3674 off = 23 * 60 + 59; /* if not, insert silly value */
3676 sprintf(offset_string, "%c%02d%02d", sign, off / 60, off % 60);
3678 return offset_string;
3681 void get_rfc822_date(gchar *buf, gint len)
3685 gchar day[4], mon[4];
3686 gint dd, hh, mm, ss, yyyy;
3688 gchar buf2[BUFFSIZE];
3691 lt = localtime_r(&t, &buf1);
3693 sscanf(asctime_r(lt, buf2), "%3s %3s %d %d:%d:%d %d\n",
3694 day, mon, &dd, &hh, &mm, &ss, &yyyy);
3696 g_snprintf(buf, len, "%s, %d %s %d %02d:%02d:%02d %s",
3697 day, dd, mon, yyyy, hh, mm, ss, tzoffset(&t));
3700 void debug_set_mode(gboolean mode)
3705 gboolean debug_get_mode(void)
3710 void debug_print_real(const gchar *format, ...)
3713 gchar buf[BUFFSIZE];
3715 if (!debug_mode) return;
3717 va_start(args, format);
3718 g_vsnprintf(buf, sizeof(buf), format, args);
3725 const char * debug_srcname(const char *file)
3727 const char *s = strrchr (file, '/');
3732 void * subject_table_lookup(GHashTable *subject_table, gchar * subject)
3734 if (subject == NULL)
3737 subject += subject_get_prefix_length(subject);
3739 return g_hash_table_lookup(subject_table, subject);
3742 void subject_table_insert(GHashTable *subject_table, gchar * subject,
3745 if (subject == NULL || *subject == 0)
3747 subject += subject_get_prefix_length(subject);
3748 g_hash_table_insert(subject_table, subject, data);
3751 void subject_table_remove(GHashTable *subject_table, gchar * subject)
3753 if (subject == NULL)
3756 subject += subject_get_prefix_length(subject);
3757 g_hash_table_remove(subject_table, subject);
3761 static regex_t u_regex;
3762 static gboolean u_init_;
3765 void utils_free_regex(void)
3776 *\brief Check if a string is prefixed with known (combinations)
3777 * of prefixes. The function assumes that each prefix
3778 * is terminated by zero or exactly _one_ space.
3780 *\param str String to check for a prefixes
3782 *\return int Number of chars in the prefix that should be skipped
3783 * for a "clean" subject line. If no prefix was found, 0
3786 int subject_get_prefix_length(const gchar *subject)
3789 /*!< Array with allowable reply prefixes regexps. */
3790 static const gchar * const prefixes[] = {
3791 "Re\\:", /* "Re:" */
3792 "Re\\[[1-9][0-9]*\\]\\:", /* "Re[XXX]:" (non-conforming news mail clients) */
3793 "Antw\\:", /* "Antw:" (Dutch / German Outlook) */
3794 "Aw\\:", /* "Aw:" (German) */
3795 "Antwort\\:", /* "Antwort:" (German Lotus Notes) */
3796 "Res\\:", /* "Res:" (Spanish/Brazilian Outlook) */
3797 "Fw\\:", /* "Fw:" Forward */
3798 "Fwd\\:", /* "Fwd:" Forward */
3799 "Enc\\:", /* "Enc:" Forward (Brazilian Outlook) */
3800 "Odp\\:", /* "Odp:" Re (Polish Outlook) */
3801 "Rif\\:", /* "Rif:" (Italian Outlook) */
3802 "Sv\\:", /* "Sv" (Norwegian) */
3803 "Vs\\:", /* "Vs" (Norwegian) */
3804 "Ad\\:", /* "Ad" (Norwegian) */
3805 "\347\255\224\345\244\215\\:", /* "Re" (Chinese, UTF-8) */
3806 "R\303\251f\\. \\:", /* "R�f. :" (French Lotus Notes) */
3807 "Re \\:", /* "Re :" (French Yahoo Mail) */
3810 const int PREFIXES = sizeof prefixes / sizeof prefixes[0];
3814 if (!subject) return 0;
3815 if (!*subject) return 0;
3818 GString *s = g_string_new("");
3820 for (n = 0; n < PREFIXES; n++)
3821 /* Terminate each prefix regexpression by a
3822 * "\ ?" (zero or ONE space), and OR them */
3823 g_string_append_printf(s, "(%s\\ ?)%s",
3828 g_string_prepend(s, "(");
3829 g_string_append(s, ")+"); /* match at least once */
3830 g_string_prepend(s, "^\\ *"); /* from beginning of line */
3833 /* We now have something like "^\ *((PREFIX1\ ?)|(PREFIX2\ ?))+"
3834 * TODO: Should this be "^\ *(((PREFIX1)|(PREFIX2))\ ?)+" ??? */
3835 if (regcomp(&u_regex, s->str, REG_EXTENDED | REG_ICASE)) {
3836 debug_print("Error compiling regexp %s\n", s->str);
3837 g_string_free(s, TRUE);
3841 g_string_free(s, TRUE);
3845 if (!regexec(&u_regex, subject, 1, &pos, 0) && pos.rm_so != -1)
3850 /*!< Array with allowable reply prefixes regexps. */
3851 static const gchar * const prefixes[] = {
3853 "antw:", /* "Antw:" (Dutch / German Outlook) */
3854 "aw:", /* "Aw:" (German) */
3855 "antwort:", /* "Antwort:" (German Lotus Notes) */
3856 "res:", /* "Res:" (Spanish/Brazilian Outlook) */
3857 "fw:", /* "Fw:" Forward */
3858 "fwd:", /* "Fwd:" Forward */
3859 "enc:", /* "Enc:" Forward (Brazilian Outlook) */
3860 "odp:", /* "Odp:" Re (Polish Outlook) */
3861 "rif:", /* "Rif:" (Italian Outlook) */
3862 "sv:", /* "Sv" (Norwegian) */
3863 "vs:", /* "Vs" (Norwegian) */
3864 "ad:", /* "Ad" (Norwegian) */
3865 "R\303\251f. :", /* "R�f. :" (French Lotus Notes) */
3866 "Re :", /* "Re :" (French Yahoo Mail) */
3869 const int PREFIXES = sizeof prefixes / sizeof prefixes[0];
3872 if (!subject) return 0;
3873 if (!*subject) return 0;
3875 for (n = 0; n < PREFIXES; n++) {
3876 int len = strlen(prefixes[n]);
3877 if (!strncasecmp(subject, prefixes[n], len)) {
3878 if (subject[len] == ' ')
3887 static guint g_stricase_hash(gconstpointer gptr)
3889 guint hash_result = 0;
3892 for (str = gptr; str && *str; str++) {
3893 hash_result += toupper(*str);
3899 static gint g_stricase_equal(gconstpointer gptr1, gconstpointer gptr2)
3901 const char *str1 = gptr1;
3902 const char *str2 = gptr2;
3904 return !strcasecmp(str1, str2);
3907 gint g_int_compare(gconstpointer a, gconstpointer b)
3909 return GPOINTER_TO_INT(a) - GPOINTER_TO_INT(b);
3912 gchar *generate_msgid(gchar *buf, gint len, gchar *user_addr)
3920 lt = localtime_r(&t, &buft);
3922 if (user_addr != NULL)
3923 addr = g_strdup_printf(".%s", user_addr);
3924 else if (strlen(buf) != 0)
3925 addr = g_strdup_printf("@%s", buf);
3927 addr = g_strdup_printf("@%s", get_domain_name());
3929 /* Replace all @ but the last one in addr, with underscores.
3930 * RFC 2822 States that msg-id syntax only allows one @.
3932 while (strchr(addr, '@') != NULL && strchr(addr, '@') != strrchr(addr, '@'))
3933 *(strchr(addr, '@')) = '_';
3935 g_snprintf(buf, len, "%04d%02d%02d%02d%02d%02d.%08x%s",
3936 lt->tm_year + 1900, lt->tm_mon + 1,
3937 lt->tm_mday, lt->tm_hour,
3938 lt->tm_min, lt->tm_sec,
3939 (guint) rand(), addr);
3946 quote_cmd_argument()
3948 return a quoted string safely usable in argument of a command.
3950 code is extracted and adapted from etPan! project -- DINH V. Ho�.
3953 gint quote_cmd_argument(gchar * result, guint size,
3963 for(p = path ; * p != '\0' ; p ++) {
3965 if (isalnum((guchar)*p) || (* p == '/')) {
3966 if (remaining > 0) {
3972 result[size - 1] = '\0';
3977 if (remaining >= 2) {
3985 result[size - 1] = '\0';
3990 if (remaining > 0) {
3994 result[size - 1] = '\0';
4008 static void g_node_map_recursive(GNode *node, gpointer data)
4010 GNodeMapData *mapdata = (GNodeMapData *) data;
4012 GNodeMapData newmapdata;
4015 newdata = mapdata->func(node->data, mapdata->data);
4016 if (newdata != NULL) {
4017 newnode = g_node_new(newdata);
4018 g_node_append(mapdata->parent, newnode);
4020 newmapdata.parent = newnode;
4021 newmapdata.func = mapdata->func;
4022 newmapdata.data = mapdata->data;
4024 g_node_children_foreach(node, G_TRAVERSE_ALL, g_node_map_recursive, &newmapdata);
4028 GNode *g_node_map(GNode *node, GNodeMapFunc func, gpointer data)
4031 GNodeMapData mapdata;
4033 cm_return_val_if_fail(node != NULL, NULL);
4034 cm_return_val_if_fail(func != NULL, NULL);
4036 root = g_node_new(func(node->data, data));
4038 mapdata.parent = root;
4039 mapdata.func = func;
4040 mapdata.data = data;
4042 g_node_children_foreach(node, G_TRAVERSE_ALL, g_node_map_recursive, &mapdata);
4047 #define HEX_TO_INT(val, hex) \
4051 if ('0' <= c && c <= '9') { \
4053 } else if ('a' <= c && c <= 'f') { \
4054 val = c - 'a' + 10; \
4055 } else if ('A' <= c && c <= 'F') { \
4056 val = c - 'A' + 10; \
4062 gboolean get_hex_value(guchar *out, gchar c1, gchar c2)
4069 if (hi == -1 || lo == -1)
4072 *out = (hi << 4) + lo;
4076 #define INT_TO_HEX(hex, val) \
4079 hex = '0' + (val); \
4081 hex = 'A' + (val) - 10; \
4084 void get_hex_str(gchar *out, guchar ch)
4088 INT_TO_HEX(hex, ch >> 4);
4090 INT_TO_HEX(hex, ch & 0x0f);
4096 #define G_PRINT_REF 1 == 1 ? (void) 0 : (void)
4098 #define G_PRINT_REF g_print
4102 *\brief Register ref counted pointer. It is based on GBoxed, so should
4103 * work with anything that uses the GType system. The semantics
4104 * are similar to a C++ auto pointer, with the exception that
4105 * C doesn't have automatic closure (calling destructors) when
4106 * exiting a block scope.
4107 * Use the \ref G_TYPE_AUTO_POINTER macro instead of calling this
4108 * function directly.
4110 *\return GType A GType type.
4112 GType g_auto_pointer_register(void)
4114 static GType auto_pointer_type;
4115 if (!auto_pointer_type)
4117 g_boxed_type_register_static
4118 ("G_TYPE_AUTO_POINTER",
4119 (GBoxedCopyFunc) g_auto_pointer_copy,
4120 (GBoxedFreeFunc) g_auto_pointer_free);
4121 return auto_pointer_type;
4125 *\brief Structure with g_new() allocated pointer guarded by the
4128 typedef struct AutoPointerRef {
4129 void (*free) (gpointer);
4135 *\brief The auto pointer opaque structure that references the
4136 * pointer guard block.
4138 typedef struct AutoPointer {
4139 AutoPointerRef *ref;
4140 gpointer ptr; /*!< access to protected pointer */
4144 *\brief Creates an auto pointer for a g_new()ed pointer. Example:
4148 * ... tell gtk_list_store it should use a G_TYPE_AUTO_POINTER
4149 * ... when assigning, copying and freeing storage elements
4151 * gtk_list_store_new(N_S_COLUMNS,
4152 * G_TYPE_AUTO_POINTER,
4156 * Template *precious_data = g_new0(Template, 1);
4157 * g_pointer protect = g_auto_pointer_new(precious_data);
4159 * gtk_list_store_set(container, &iter,
4163 * ... the gtk_list_store has copied the pointer and
4164 * ... incremented its reference count, we should free
4165 * ... the auto pointer (in C++ a destructor would do
4166 * ... this for us when leaving block scope)
4168 * g_auto_pointer_free(protect);
4170 * ... gtk_list_store_set() now manages the data. When
4171 * ... *explicitly* requesting a pointer from the list
4172 * ... store, don't forget you get a copy that should be
4173 * ... freed with g_auto_pointer_free() eventually.
4177 *\param pointer Pointer to be guarded.
4179 *\return GAuto * Pointer that should be used in containers with
4182 GAuto *g_auto_pointer_new(gpointer p)
4184 AutoPointerRef *ref;
4190 ref = g_new0(AutoPointerRef, 1);
4191 ptr = g_new0(AutoPointer, 1);
4201 G_PRINT_REF ("XXXX ALLOC(%lx)\n", p);
4207 *\brief Allocate an autopointer using the passed \a free function to
4208 * free the guarded pointer
4210 GAuto *g_auto_pointer_new_with_free(gpointer p, GFreeFunc free_)
4217 aptr = g_auto_pointer_new(p);
4218 aptr->ref->free = free_;
4222 gpointer g_auto_pointer_get_ptr(GAuto *auto_ptr)
4224 if (auto_ptr == NULL)
4226 return ((AutoPointer *) auto_ptr)->ptr;
4230 *\brief Copies an auto pointer by. It's mostly not necessary
4231 * to call this function directly, unless you copy/assign
4232 * the guarded pointer.
4234 *\param auto_ptr Auto pointer returned by previous call to
4235 * g_auto_pointer_new_XXX()
4237 *\return gpointer An auto pointer
4239 GAuto *g_auto_pointer_copy(GAuto *auto_ptr)
4242 AutoPointerRef *ref;
4245 if (auto_ptr == NULL)
4250 newp = g_new0(AutoPointer, 1);
4253 newp->ptr = ref->pointer;
4257 G_PRINT_REF ("XXXX COPY(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4263 *\brief Free an auto pointer
4265 void g_auto_pointer_free(GAuto *auto_ptr)
4268 AutoPointerRef *ref;
4270 if (auto_ptr == NULL)
4276 if (--(ref->cnt) == 0) {
4278 G_PRINT_REF ("XXXX FREE(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4280 ref->free(ref->pointer);
4285 G_PRINT_REF ("XXXX DEREF(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4290 void replace_returns(gchar *str)
4295 while (strstr(str, "\n")) {
4296 *strstr(str, "\n") = ' ';
4298 while (strstr(str, "\r")) {
4299 *strstr(str, "\r") = ' ';
4303 /* get_uri_part() - retrieves a URI starting from scanpos.
4304 Returns TRUE if succesful */
4305 gboolean get_uri_part(const gchar *start, const gchar *scanpos,
4306 const gchar **bp, const gchar **ep, gboolean hdr)
4309 gint parenthese_cnt = 0;
4311 cm_return_val_if_fail(start != NULL, FALSE);
4312 cm_return_val_if_fail(scanpos != NULL, FALSE);
4313 cm_return_val_if_fail(bp != NULL, FALSE);
4314 cm_return_val_if_fail(ep != NULL, FALSE);
4318 /* find end point of URI */
4319 for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
4320 if (!g_ascii_isgraph(*(const guchar *)ep_) ||
4321 !IS_ASCII(*(const guchar *)ep_) ||
4322 strchr("[]{}<>\"", *ep_)) {
4324 } else if (strchr("(", *ep_)) {
4326 } else if (strchr(")", *ep_)) {
4327 if (parenthese_cnt > 0)
4334 /* no punctuation at end of string */
4336 /* FIXME: this stripping of trailing punctuations may bite with other URIs.
4337 * should pass some URI type to this function and decide on that whether
4338 * to perform punctuation stripping */
4340 #define IS_REAL_PUNCT(ch) (g_ascii_ispunct(ch) && !strchr("/?=-_)", ch))
4342 for (; ep_ - 1 > scanpos + 1 &&
4343 IS_REAL_PUNCT(*(ep_ - 1));
4347 #undef IS_REAL_PUNCT
4354 gchar *make_uri_string(const gchar *bp, const gchar *ep)
4356 while (bp && *bp && g_ascii_isspace(*bp))
4358 return g_strndup(bp, ep - bp);
4361 /* valid mail address characters */
4362 #define IS_RFC822_CHAR(ch) \
4366 !g_ascii_isspace(ch) && \
4367 !strchr("(),;<>\"", (ch)))
4369 /* alphabet and number within 7bit ASCII */
4370 #define IS_ASCII_ALNUM(ch) (IS_ASCII(ch) && g_ascii_isalnum(ch))
4371 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
4373 static GHashTable *create_domain_tab(void)
4375 static const gchar *toplvl_domains [] = {
4377 "arpa", "coop", "info", "name", "biz", "com", "edu", "gov",
4378 "int", "mil", "net", "org", "ac", "ad", "ae", "af", "ag",
4379 "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au",
4380 "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi",
4381 "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
4382 "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl",
4383 "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz", "de",
4384 "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er",
4385 "es", "et", "eu", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd",
4386 "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
4387 "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
4388 "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir",
4389 "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
4390 "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc",
4391 "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
4392 "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq",
4393 "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na",
4394 "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu",
4395 "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm",
4396 "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru",
4397 "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj",
4398 "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
4399 "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to",
4400 "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um",
4401 "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu",
4402 "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw"
4405 GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
4407 cm_return_val_if_fail(htab, NULL);
4408 for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++)
4409 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
4413 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
4415 const gint MAX_LVL_DOM_NAME_LEN = 6;
4416 gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
4417 const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
4420 if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
4423 for (p = buf; p < m && first < last; *p++ = *first++)
4427 return g_hash_table_lookup(tab, buf) != NULL;
4430 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
4431 gboolean get_email_part(const gchar *start, const gchar *scanpos,
4432 const gchar **bp, const gchar **ep, gboolean hdr)
4434 /* more complex than the uri part because we need to scan back and forward starting from
4435 * the scan position. */
4436 gboolean result = FALSE;
4437 const gchar *bp_ = NULL;
4438 const gchar *ep_ = NULL;
4439 static GHashTable *dom_tab;
4440 const gchar *last_dot = NULL;
4441 const gchar *prelast_dot = NULL;
4442 const gchar *last_tld_char = NULL;
4444 /* the informative part of the email address (describing the name
4445 * of the email address owner) may contain quoted parts. the
4446 * closure stack stores the last encountered quotes. */
4447 gchar closure_stack[128];
4448 gchar *ptr = closure_stack;
4450 cm_return_val_if_fail(start != NULL, FALSE);
4451 cm_return_val_if_fail(scanpos != NULL, FALSE);
4452 cm_return_val_if_fail(bp != NULL, FALSE);
4453 cm_return_val_if_fail(ep != NULL, FALSE);
4456 const gchar *start_quote = NULL;
4457 const gchar *end_quote = NULL;
4459 /* go to the real start */
4460 if (start[0] == ',')
4462 if (start[0] == ';')
4464 while (start[0] == '\n' || start[0] == '\r')
4466 while (start[0] == ' ' || start[0] == '\t')
4471 /* check if there are quotes (to skip , in them) */
4472 if (*start == '"') {
4473 start_quote = start;
4475 end_quote = strstr(start, "\"");
4481 /* skip anything between quotes */
4482 if (start_quote && end_quote) {
4487 /* find end (either , or ; or end of line) */
4488 if (strstr(start, ",") && strstr(start, ";"))
4489 *ep = strstr(start,",") < strstr(start, ";")
4490 ? strstr(start, ",") : strstr(start, ";");
4491 else if (strstr(start, ","))
4492 *ep = strstr(start, ",");
4493 else if (strstr(start, ";"))
4494 *ep = strstr(start, ";");
4496 *ep = start+strlen(start);
4498 /* go back to real start */
4499 if (start_quote && end_quote) {
4500 start = start_quote;
4503 /* check there's still an @ in that, or search
4504 * further if possible */
4505 if (strstr(start, "@") && strstr(start, "@") < *ep)
4507 else if (*ep < start+strlen(start)) {
4510 } else if (start_quote && strstr(start, "\"") && strstr(start, "\"") < *ep) {
4518 dom_tab = create_domain_tab();
4519 cm_return_val_if_fail(dom_tab, FALSE);
4521 /* scan start of address */
4522 for (bp_ = scanpos - 1;
4523 bp_ >= start && IS_RFC822_CHAR(*(const guchar *)bp_); bp_--)
4526 /* TODO: should start with an alnum? */
4528 for (; bp_ < scanpos && !IS_ASCII_ALNUM(*(const guchar *)bp_); bp_++)
4531 if (bp_ != scanpos) {
4532 /* scan end of address */
4533 for (ep_ = scanpos + 1;
4534 *ep_ && IS_RFC822_CHAR(*(const guchar *)ep_); ep_++)
4536 prelast_dot = last_dot;
4538 if (*(last_dot + 1) == '.') {
4539 if (prelast_dot == NULL)
4541 last_dot = prelast_dot;
4546 /* TODO: really should terminate with an alnum? */
4547 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*(const guchar *)ep_);
4552 if (last_dot == NULL)
4554 if (last_dot >= ep_)
4555 last_dot = prelast_dot;
4556 if (last_dot == NULL || (scanpos + 1 >= last_dot))
4560 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
4561 if (*last_tld_char == '?')
4564 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
4571 if (!result) return FALSE;
4573 if (*ep_ && bp_ != start && *(bp_ - 1) == '"' && *(ep_) == '"'
4574 && *(ep_ + 1) == ' ' && *(ep_ + 2) == '<'
4575 && IS_RFC822_CHAR(*(ep_ + 3))) {
4576 /* this informative part with an @ in it is
4577 * followed by the email address */
4580 /* go to matching '>' (or next non-rfc822 char, like \n) */
4581 for (; *ep_ != '>' && *ep != '\0' && IS_RFC822_CHAR(*ep_); ep_++)
4584 /* include the bracket */
4585 if (*ep_ == '>') ep_++;
4587 /* include the leading quote */
4595 /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
4596 if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
4599 /* see if this is <bracketed>; in this case we also scan for the informative part. */
4600 if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
4603 #define FULL_STACK() ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
4604 #define IN_STACK() (ptr > closure_stack)
4605 /* has underrun check */
4606 #define POP_STACK() if(IN_STACK()) --ptr
4607 /* has overrun check */
4608 #define PUSH_STACK(c) if(!FULL_STACK()) *ptr++ = (c); else return TRUE
4609 /* has underrun check */
4610 #define PEEK_STACK() (IN_STACK() ? *(ptr - 1) : 0)
4614 /* scan for the informative part. */
4615 for (bp_ -= 2; bp_ >= start; bp_--) {
4616 /* if closure on the stack keep scanning */
4617 if (PEEK_STACK() == *bp_) {
4621 if (!IN_STACK() && (*bp_ == '\'' || *bp_ == '"')) {
4626 /* if nothing in the closure stack, do the special conditions
4627 * the following if..else expression simply checks whether
4628 * a token is acceptable. if not acceptable, the clause
4629 * should terminate the loop with a 'break' */
4630 if (!PEEK_STACK()) {
4632 && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
4633 && (((bp_ + 1) < ep_) && isalnum(*(bp_ + 1)))) {
4634 /* hyphens are allowed, but only in
4636 } else if (strchr(" \"'", *bp_)) {
4637 /* but anything not being a punctiation
4640 break; /* anything else is rejected */
4647 /* scan forward (should start with an alnum) */
4648 for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
4664 #undef IS_ASCII_ALNUM
4665 #undef IS_RFC822_CHAR
4667 gchar *make_email_string(const gchar *bp, const gchar *ep)
4669 /* returns a mailto: URI; mailto: is also used to detect the
4670 * uri type later on in the button_pressed signal handler */
4674 tmp = g_strndup(bp, ep - bp);
4675 result = g_strconcat("mailto:", tmp, NULL);
4681 gchar *make_http_string(const gchar *bp, const gchar *ep)
4683 /* returns an http: URI; */
4687 while (bp && *bp && g_ascii_isspace(*bp))
4689 tmp = g_strndup(bp, ep - bp);
4690 result = g_strconcat("http://", tmp, NULL);
4696 static gchar *mailcap_get_command_in_file(const gchar *path, const gchar *type, const gchar *file_to_open)
4698 FILE *fp = g_fopen(path, "rb");
4699 gchar buf[BUFFSIZE];
4700 gchar *result = NULL;
4703 while (fgets(buf, sizeof (buf), fp) != NULL) {
4704 gchar **parts = g_strsplit(buf, ";", 3);
4705 gchar *trimmed = parts[0];
4706 while (trimmed[0] == ' ' || trimmed[0] == '\t')
4708 while (trimmed[strlen(trimmed)-1] == ' ' || trimmed[strlen(trimmed)-1] == '\t')
4709 trimmed[strlen(trimmed)-1] = '\0';
4711 if (!strcmp(trimmed, type)) {
4712 gboolean needsterminal = FALSE;
4713 if (parts[2] && strstr(parts[2], "needsterminal")) {
4714 needsterminal = TRUE;
4716 if (parts[2] && strstr(parts[2], "test=")) {
4717 gchar *orig_testcmd = g_strdup(strstr(parts[2], "test=")+5);
4718 gchar *testcmd = orig_testcmd;
4719 if (strstr(testcmd,";"))
4720 *(strstr(testcmd,";")) = '\0';
4721 while (testcmd[0] == ' ' || testcmd[0] == '\t')
4723 while (testcmd[strlen(testcmd)-1] == '\n')
4724 testcmd[strlen(testcmd)-1] = '\0';
4725 while (testcmd[strlen(testcmd)-1] == '\r')
4726 testcmd[strlen(testcmd)-1] = '\0';
4727 while (testcmd[strlen(testcmd)-1] == ' ' || testcmd[strlen(testcmd)-1] == '\t')
4728 testcmd[strlen(testcmd)-1] = '\0';
4730 if (strstr(testcmd, "%s")) {
4731 gchar *tmp = g_strdup_printf(testcmd, file_to_open);
4732 gint res = system(tmp);
4734 g_free(orig_testcmd);
4741 gint res = system(testcmd);
4742 g_free(orig_testcmd);
4752 while (trimmed[0] == ' ' || trimmed[0] == '\t')
4754 while (trimmed[strlen(trimmed)-1] == '\n')
4755 trimmed[strlen(trimmed)-1] = '\0';
4756 while (trimmed[strlen(trimmed)-1] == '\r')
4757 trimmed[strlen(trimmed)-1] = '\0';
4758 while (trimmed[strlen(trimmed)-1] == ' ' || trimmed[strlen(trimmed)-1] == '\t')
4759 trimmed[strlen(trimmed)-1] = '\0';
4760 result = g_strdup(trimmed);
4763 /* if there are no single quotes around %s, add them.
4764 * '.*%s.*' is ok, as in display 'png:%s'
4766 if (strstr(result, "%s")
4767 && !(strstr(result, "'") < strstr(result,"%s") &&
4768 strstr(strstr(result,"%s"), "'"))) {
4769 gchar *start = g_strdup(result);
4770 gchar *end = g_strdup(strstr(result, "%s")+2);
4772 *strstr(start, "%s") = '\0';
4773 tmp = g_strconcat(start,"'%s'",end, NULL);
4779 if (needsterminal) {
4780 gchar *tmp = g_strdup_printf("xterm -e %s", result);
4791 gchar *mailcap_get_command_for_type(const gchar *type, const gchar *file_to_open)
4793 gchar *result = NULL;
4797 path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap", NULL);
4798 result = mailcap_get_command_in_file(path, type, file_to_open);
4802 result = mailcap_get_command_in_file("/etc/mailcap", type, file_to_open);
4806 void mailcap_update_default(const gchar *type, const gchar *command)
4808 gchar *path = NULL, *outpath = NULL;
4809 path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap", NULL);
4810 outpath = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap.new", NULL);
4811 FILE *fp = g_fopen(path, "rb");
4813 gchar buf[BUFFSIZE];
4814 gboolean err = FALSE;
4817 fp = g_fopen(path, "a");
4819 g_warning("failed to create file %s", path);
4824 fp = g_freopen(path, "rb", fp);
4826 g_warning("failed to reopen file %s", path);
4833 outfp = g_fopen(outpath, "wb");
4835 g_warning("failed to create file %s", outpath);
4841 while (fp && fgets(buf, sizeof (buf), fp) != NULL) {
4842 gchar **parts = g_strsplit(buf, ";", 3);
4843 gchar *trimmed = parts[0];
4844 while (trimmed[0] == ' ')
4846 while (trimmed[strlen(trimmed)-1] == ' ')
4847 trimmed[strlen(trimmed)-1] = '\0';
4849 if (!strcmp(trimmed, type)) {
4854 if(fputs(buf, outfp) == EOF) {
4861 if (fprintf(outfp, "%s; %s\n", type, command) < 0)
4867 if (fclose(outfp) == EOF)
4871 g_rename(outpath, path);
4877 gint copy_dir(const gchar *src, const gchar *dst)
4882 if ((dir = g_dir_open(src, 0, NULL)) == NULL) {
4883 g_warning("failed to open directory: %s", src);
4887 if (make_dir(dst) < 0)
4890 while ((name = g_dir_read_name(dir)) != NULL) {
4891 gchar *old_file, *new_file;
4892 old_file = g_strconcat(src, G_DIR_SEPARATOR_S, name, NULL);
4893 new_file = g_strconcat(dst, G_DIR_SEPARATOR_S, name, NULL);
4894 debug_print("copying: %s -> %s\n", old_file, new_file);
4895 if (g_file_test(old_file, G_FILE_TEST_IS_REGULAR)) {
4896 gint r = copy_file(old_file, new_file, TRUE);
4903 /* Windows has no symlinks. Or well, Vista seems to
4904 have something like this but the semantics might be
4905 different. Thus we don't use it under Windows. */
4906 else if (g_file_test(old_file, G_FILE_TEST_IS_SYMLINK)) {
4909 gchar *target = g_file_read_link(old_file, &error);
4911 r = symlink(target, new_file);
4918 #endif /*G_OS_WIN32*/
4919 else if (g_file_test(old_file, G_FILE_TEST_IS_DIR)) {
4920 gint r = copy_dir(old_file, new_file);
4931 /* crude test to see if a file is an email. */
4932 gboolean file_is_email (const gchar *filename)
4938 if (filename == NULL)
4940 if ((fp = g_fopen(filename, "rb")) == NULL)
4942 while (i < 60 && score < 3
4943 && fgets(buffer, sizeof (buffer), fp) > 0) {
4944 if (!strncmp(buffer, "From:", strlen("From:")))
4946 else if (!strncmp(buffer, "Date:", strlen("Date:")))
4948 else if (!strncmp(buffer, "Message-ID:", strlen("Message-ID:")))
4950 else if (!strncmp(buffer, "Subject:", strlen("Subject:")))
4955 return (score >= 3);
4958 gboolean sc_g_list_bigger(GList *list, gint max)
4962 while (cur && i <= max+1) {
4969 gboolean sc_g_slist_bigger(GSList *list, gint max)
4973 while (cur && i <= max+1) {
4980 const gchar *daynames[] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL};
4981 const gchar *monthnames[] = {NULL, NULL, NULL, NULL, NULL, NULL,
4982 NULL, NULL, NULL, NULL, NULL, NULL};
4983 const gchar *s_daynames[] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL};
4984 const gchar *s_monthnames[] = {NULL, NULL, NULL, NULL, NULL, NULL,
4985 NULL, NULL, NULL, NULL, NULL, NULL};
4987 gint daynames_len[] = {0,0,0,0,0,0,0};
4988 gint monthnames_len[] = {0,0,0,0,0,0,
4990 gint s_daynames_len[] = {0,0,0,0,0,0,0};
4991 gint s_monthnames_len[] = {0,0,0,0,0,0,
4993 const gchar *s_am_up = NULL;
4994 const gchar *s_pm_up = NULL;
4995 const gchar *s_am_low = NULL;
4996 const gchar *s_pm_low = NULL;
4998 gint s_am_up_len = 0;
4999 gint s_pm_up_len = 0;
5000 gint s_am_low_len = 0;
5001 gint s_pm_low_len = 0;
5003 static gboolean time_names_init_done = FALSE;
5005 static void init_time_names(void)
5009 daynames[0] = C_("Complete day name for use by strftime", "Sunday");
5010 daynames[1] = C_("Complete day name for use by strftime", "Monday");
5011 daynames[2] = C_("Complete day name for use by strftime", "Tuesday");
5012 daynames[3] = C_("Complete day name for use by strftime", "Wednesday");
5013 daynames[4] = C_("Complete day name for use by strftime", "Thursday");
5014 daynames[5] = C_("Complete day name for use by strftime", "Friday");
5015 daynames[6] = C_("Complete day name for use by strftime", "Saturday");
5017 monthnames[0] = C_("Complete month name for use by strftime", "January");
5018 monthnames[1] = C_("Complete month name for use by strftime", "February");
5019 monthnames[2] = C_("Complete month name for use by strftime", "March");
5020 monthnames[3] = C_("Complete month name for use by strftime", "April");
5021 monthnames[4] = C_("Complete month name for use by strftime", "May");
5022 monthnames[5] = C_("Complete month name for use by strftime", "June");
5023 monthnames[6] = C_("Complete month name for use by strftime", "July");
5024 monthnames[7] = C_("Complete month name for use by strftime", "August");
5025 monthnames[8] = C_("Complete month name for use by strftime", "September");
5026 monthnames[9] = C_("Complete month name for use by strftime", "October");
5027 monthnames[10] = C_("Complete month name for use by strftime", "November");
5028 monthnames[11] = C_("Complete month name for use by strftime", "December");
5030 s_daynames[0] = C_("Abbr. day name for use by strftime", "Sun");
5031 s_daynames[1] = C_("Abbr. day name for use by strftime", "Mon");
5032 s_daynames[2] = C_("Abbr. day name for use by strftime", "Tue");
5033 s_daynames[3] = C_("Abbr. day name for use by strftime", "Wed");
5034 s_daynames[4] = C_("Abbr. day name for use by strftime", "Thu");
5035 s_daynames[5] = C_("Abbr. day name for use by strftime", "Fri");
5036 s_daynames[6] = C_("Abbr. day name for use by strftime", "Sat");
5038 s_monthnames[0] = C_("Abbr. month name for use by strftime", "Jan");
5039 s_monthnames[1] = C_("Abbr. month name for use by strftime", "Feb");
5040 s_monthnames[2] = C_("Abbr. month name for use by strftime", "Mar");
5041 s_monthnames[3] = C_("Abbr. month name for use by strftime", "Apr");
5042 s_monthnames[4] = C_("Abbr. month name for use by strftime", "May");
5043 s_monthnames[5] = C_("Abbr. month name for use by strftime", "Jun");
5044 s_monthnames[6] = C_("Abbr. month name for use by strftime", "Jul");
5045 s_monthnames[7] = C_("Abbr. month name for use by strftime", "Aug");
5046 s_monthnames[8] = C_("Abbr. month name for use by strftime", "Sep");
5047 s_monthnames[9] = C_("Abbr. month name for use by strftime", "Oct");
5048 s_monthnames[10] = C_("Abbr. month name for use by strftime", "Nov");
5049 s_monthnames[11] = C_("Abbr. month name for use by strftime", "Dec");
5051 for (i = 0; i < 7; i++) {
5052 daynames_len[i] = strlen(daynames[i]);
5053 s_daynames_len[i] = strlen(s_daynames[i]);
5055 for (i = 0; i < 12; i++) {
5056 monthnames_len[i] = strlen(monthnames[i]);
5057 s_monthnames_len[i] = strlen(s_monthnames[i]);
5060 s_am_up = C_("For use by strftime (morning)", "AM");
5061 s_pm_up = C_("For use by strftime (afternoon)", "PM");
5062 s_am_low = C_("For use by strftime (morning, lowercase)", "am");
5063 s_pm_low = C_("For use by strftime (afternoon, lowercase)", "pm");
5065 s_am_up_len = strlen(s_am_up);
5066 s_pm_up_len = strlen(s_pm_up);
5067 s_am_low_len = strlen(s_am_low);
5068 s_pm_low_len = strlen(s_pm_low);
5070 time_names_init_done = TRUE;
5073 #define CHECK_SIZE() { \
5074 total_done += len; \
5075 if (total_done >= buflen) { \
5076 buf[buflen-1] = '\0'; \
5081 size_t fast_strftime(gchar *buf, gint buflen, const gchar *format, struct tm *lt)
5083 gchar *curpos = buf;
5084 gint total_done = 0;
5085 gchar subbuf[64], subfmt[64];
5086 static time_t last_tzset = (time_t)0;
5088 if (!time_names_init_done)
5091 if (format == NULL || lt == NULL)
5094 if (last_tzset != time(NULL)) {
5096 last_tzset = time(NULL);
5099 if (*format == '%') {
5100 gint len = 0, tmp = 0;
5104 len = 1; CHECK_SIZE();
5108 len = s_daynames_len[lt->tm_wday]; CHECK_SIZE();
5109 strncpy2(curpos, s_daynames[lt->tm_wday], buflen - total_done);
5112 len = daynames_len[lt->tm_wday]; CHECK_SIZE();
5113 strncpy2(curpos, daynames[lt->tm_wday], buflen - total_done);
5117 len = s_monthnames_len[lt->tm_mon]; CHECK_SIZE();
5118 strncpy2(curpos, s_monthnames[lt->tm_mon], buflen - total_done);
5121 len = monthnames_len[lt->tm_mon]; CHECK_SIZE();
5122 strncpy2(curpos, monthnames[lt->tm_mon], buflen - total_done);
5125 strftime(subbuf, 64, "%c", lt);
5126 len = strlen(subbuf); CHECK_SIZE();
5127 strncpy2(curpos, subbuf, buflen - total_done);
5130 total_done += 2; CHECK_SIZE();
5131 tmp = (lt->tm_year + 1900)/100;
5132 *curpos++ = '0'+(tmp / 10);
5133 *curpos++ = '0'+(tmp % 10);
5136 total_done += 2; CHECK_SIZE();
5137 *curpos++ = '0'+(lt->tm_mday / 10);
5138 *curpos++ = '0'+(lt->tm_mday % 10);
5141 total_done += 8; CHECK_SIZE();
5142 *curpos++ = '0'+((lt->tm_mon+1) / 10);
5143 *curpos++ = '0'+((lt->tm_mon+1) % 10);
5145 *curpos++ = '0'+(lt->tm_mday / 10);
5146 *curpos++ = '0'+(lt->tm_mday % 10);
5148 tmp = lt->tm_year%100;
5149 *curpos++ = '0'+(tmp / 10);
5150 *curpos++ = '0'+(tmp % 10);
5153 len = 2; CHECK_SIZE();
5154 snprintf(curpos, buflen - total_done, "%2d", lt->tm_mday);
5157 len = 10; CHECK_SIZE();
5158 snprintf(curpos, buflen - total_done, "%4d-%02d-%02d",
5159 lt->tm_year + 1900, lt->tm_mon +1, lt->tm_mday);
5162 total_done += 2; CHECK_SIZE();
5163 *curpos++ = '0'+(lt->tm_hour / 10);
5164 *curpos++ = '0'+(lt->tm_hour % 10);
5167 total_done += 2; CHECK_SIZE();
5173 *curpos++ = '0'+(tmp / 10);
5174 *curpos++ = '0'+(tmp % 10);
5177 len = 3; CHECK_SIZE();
5178 snprintf(curpos, buflen - total_done, "%03d", lt->tm_yday+1);
5181 len = 2; CHECK_SIZE();
5182 snprintf(curpos, buflen - total_done, "%2d", lt->tm_hour);
5185 len = 2; CHECK_SIZE();
5191 snprintf(curpos, buflen - total_done, "%2d", tmp);
5194 total_done += 2; CHECK_SIZE();
5195 tmp = lt->tm_mon + 1;
5196 *curpos++ = '0'+(tmp / 10);
5197 *curpos++ = '0'+(tmp % 10);
5200 total_done += 2; CHECK_SIZE();
5201 *curpos++ = '0'+(lt->tm_min / 10);
5202 *curpos++ = '0'+(lt->tm_min % 10);
5205 len = 1; CHECK_SIZE();
5209 if (lt->tm_hour >= 12) {
5210 len = s_pm_up_len; CHECK_SIZE();
5211 snprintf(curpos, buflen-total_done, "%s", s_pm_up);
5213 len = s_am_up_len; CHECK_SIZE();
5214 snprintf(curpos, buflen-total_done, "%s", s_am_up);
5218 if (lt->tm_hour >= 12) {
5219 len = s_pm_low_len; CHECK_SIZE();
5220 snprintf(curpos, buflen-total_done, "%s", s_pm_low);
5222 len = s_am_low_len; CHECK_SIZE();
5223 snprintf(curpos, buflen-total_done, "%s", s_am_low);
5227 strftime(subbuf, 64, "%r", lt);
5228 len = strlen(subbuf); CHECK_SIZE();
5229 strncpy2(curpos, subbuf, buflen - total_done);
5232 total_done += 5; CHECK_SIZE();
5233 *curpos++ = '0'+(lt->tm_hour / 10);
5234 *curpos++ = '0'+(lt->tm_hour % 10);
5236 *curpos++ = '0'+(lt->tm_min / 10);
5237 *curpos++ = '0'+(lt->tm_min % 10);
5240 snprintf(subbuf, 64, "%ld", mktime(lt));
5241 len = strlen(subbuf); CHECK_SIZE();
5242 strncpy2(curpos, subbuf, buflen - total_done);
5245 total_done += 2; CHECK_SIZE();
5246 *curpos++ = '0'+(lt->tm_sec / 10);
5247 *curpos++ = '0'+(lt->tm_sec % 10);
5250 len = 1; CHECK_SIZE();
5254 total_done += 8; CHECK_SIZE();
5255 *curpos++ = '0'+(lt->tm_hour / 10);
5256 *curpos++ = '0'+(lt->tm_hour % 10);
5258 *curpos++ = '0'+(lt->tm_min / 10);
5259 *curpos++ = '0'+(lt->tm_min % 10);
5261 *curpos++ = '0'+(lt->tm_sec / 10);
5262 *curpos++ = '0'+(lt->tm_sec % 10);
5265 len = 1; CHECK_SIZE();
5266 snprintf(curpos, buflen - total_done, "%d", lt->tm_wday == 0 ? 7: lt->tm_wday);
5269 len = 1; CHECK_SIZE();
5270 snprintf(curpos, buflen - total_done, "%d", lt->tm_wday);
5273 strftime(subbuf, 64, "%x", lt);
5274 len = strlen(subbuf); CHECK_SIZE();
5275 strncpy2(curpos, subbuf, buflen - total_done);
5278 strftime(subbuf, 64, "%X", lt);
5279 len = strlen(subbuf); CHECK_SIZE();
5280 strncpy2(curpos, subbuf, buflen - total_done);
5283 total_done += 2; CHECK_SIZE();
5284 tmp = lt->tm_year%100;
5285 *curpos++ = '0'+(tmp / 10);
5286 *curpos++ = '0'+(tmp % 10);
5289 len = 4; CHECK_SIZE();
5290 snprintf(curpos, buflen - total_done, "%4d", lt->tm_year + 1900);
5300 /* let these complicated ones be done with the libc */
5301 snprintf(subfmt, 64, "%%%c", *format);
5302 strftime(subbuf, 64, subfmt, lt);
5303 len = strlen(subbuf); CHECK_SIZE();
5304 strncpy2(curpos, subbuf, buflen - total_done);
5308 /* let these complicated modifiers be done with the libc */
5309 snprintf(subfmt, 64, "%%%c%c", *format, *(format+1));
5310 strftime(subbuf, 64, subfmt, lt);
5311 len = strlen(subbuf); CHECK_SIZE();
5312 strncpy2(curpos, subbuf, buflen - total_done);
5316 g_warning("format error (%c)", *format);
5323 int len = 1; CHECK_SIZE();
5324 *curpos++ = *format++;
5331 gboolean prefs_common_get_use_shred(void);
5335 #define WEXITSTATUS(x) (x)
5338 int claws_unlink(const gchar *filename)
5341 static int found_shred = -1;
5342 static const gchar *args[4];
5344 if (filename == NULL)
5347 if (prefs_common_get_use_shred()) {
5348 if (found_shred == -1) {
5350 args[0] = g_find_program_in_path("shred");
5351 debug_print("found shred: %s\n", args[0]);
5352 found_shred = (args[0] != NULL) ? 1:0;
5356 if (found_shred == 1) {
5357 if (g_stat(filename, &s) == 0 && S_ISREG(s.st_mode)) {
5358 if (s.st_nlink == 1) {
5361 g_spawn_sync(NULL, (gchar **)args, NULL, 0,
5362 NULL, NULL, NULL, NULL, &status, NULL);
5363 debug_print("%s %s exited with status %d\n",
5364 args[0], filename, WEXITSTATUS(status));
5365 if (truncate(filename, 0) < 0)
5366 g_warning("couln't truncate: %s", filename);
5371 return g_unlink(filename);
5374 GMutex *cm_mutex_new(void) {
5375 #if GLIB_CHECK_VERSION(2,32,0)
5376 GMutex *m = g_new0(GMutex, 1);
5380 return g_mutex_new();
5384 void cm_mutex_free(GMutex *mutex) {
5385 #if GLIB_CHECK_VERSION(2,32,0)
5386 g_mutex_clear(mutex);
5389 g_mutex_free(mutex);
5393 static gchar *canonical_list_to_file(GSList *list)
5395 GString *result = g_string_new(NULL);
5396 GSList *pathlist = g_slist_reverse(g_slist_copy(list));
5401 result = g_string_append(result, G_DIR_SEPARATOR_S);
5403 if (pathlist->data) {
5404 const gchar *root = (gchar *)pathlist->data;
5405 if (root[0] != '\0' && g_ascii_isalpha(root[0]) &&
5407 /* drive - don't prepend dir separator */
5409 result = g_string_append(result, G_DIR_SEPARATOR_S);
5414 for (cur = pathlist; cur; cur = cur->next) {
5415 result = g_string_append(result, (gchar *)cur->data);
5417 result = g_string_append(result, G_DIR_SEPARATOR_S);
5419 g_slist_free(pathlist);
5422 g_string_free(result, FALSE);
5427 static GSList *cm_split_path(const gchar *filename, int depth)
5430 GSList *canonical_parts = NULL;
5433 gboolean follow_symlinks = TRUE;
5439 errno = EINVAL; /* can't happen, no symlink handling */
5444 if (!g_path_is_absolute(filename)) {
5449 path_parts = g_strsplit(filename, G_DIR_SEPARATOR_S, -1);
5451 for (i = 0; path_parts[i] != NULL; i++) {
5452 if (!strcmp(path_parts[i], ""))
5454 if (!strcmp(path_parts[i], "."))
5456 else if (!strcmp(path_parts[i], "..")) {
5461 else /* Remove the last inserted element */
5463 g_slist_delete_link(canonical_parts,
5468 canonical_parts = g_slist_prepend(canonical_parts,
5469 g_strdup(path_parts[i]));
5471 tmp_path = canonical_list_to_file(canonical_parts);
5473 if(g_stat(tmp_path, &st) < 0) {
5474 if (errno == ENOENT) {
5476 follow_symlinks = FALSE;
5480 slist_free_strings_full(canonical_parts);
5481 g_strfreev(path_parts);
5487 if (follow_symlinks && g_file_test(tmp_path, G_FILE_TEST_IS_SYMLINK)) {
5488 GError *error = NULL;
5489 gchar *target = g_file_read_link(tmp_path, &error);
5491 if (!g_path_is_absolute(target)) {
5492 /* remove the last inserted element */
5494 g_slist_delete_link(canonical_parts,
5496 /* add the target */
5497 canonical_parts = g_slist_prepend(canonical_parts,
5501 /* and get the new target */
5502 target = canonical_list_to_file(canonical_parts);
5505 /* restart from absolute target */
5506 slist_free_strings_full(canonical_parts);
5507 canonical_parts = NULL;
5509 canonical_parts = cm_split_path(target, depth + 1);
5511 g_error_free(error);
5512 if (canonical_parts == NULL) {
5514 g_strfreev(path_parts);
5523 g_strfreev(path_parts);
5524 return canonical_parts;
5528 * Canonicalize a filename, resolving symlinks along the way.
5529 * Returns a negative errno in case of error.
5531 int cm_canonicalize_filename(const gchar *filename, gchar **canonical_name) {
5532 GSList *canonical_parts;
5533 gboolean is_absolute;
5535 if (filename == NULL)
5537 if (canonical_name == NULL)
5539 *canonical_name = NULL;
5541 is_absolute = g_path_is_absolute(filename);
5543 /* Always work on absolute filenames. */
5544 gchar *cur = g_get_current_dir();
5545 gchar *absolute_filename = g_strconcat(cur, G_DIR_SEPARATOR_S,
5548 canonical_parts = cm_split_path(absolute_filename, 0);
5549 g_free(absolute_filename);
5552 canonical_parts = cm_split_path(filename, 0);
5554 if (canonical_parts == NULL)
5557 *canonical_name = canonical_list_to_file(canonical_parts);
5558 slist_free_strings_full(canonical_parts);
5562 /* Returns a decoded base64 string, guaranteed to be null-terminated. */
5563 guchar *g_base64_decode_zero(const gchar *text, gsize *out_len)
5565 gchar *tmp = g_base64_decode(text, out_len);
5566 gchar *out = g_strndup(tmp, *out_len);
5570 if (strlen(out) != *out_len) {
5571 g_warning ("strlen(out) %zd != *out_len %" G_GSIZE_FORMAT, strlen(out), *out_len);
5577 #if !GLIB_CHECK_VERSION(2, 30, 0)
5580 * @str: a UTF-8 encoded string
5581 * @start_pos: a character offset within @str
5582 * @end_pos: another character offset within @str
5584 * Copies a substring out of a UTF-8 encoded string.
5585 * The substring will contain @end_pos - @start_pos
5588 * Returns: a newly allocated copy of the requested
5589 * substring. Free with g_free() when no longer needed.
5594 g_utf8_substring (const gchar *str,
5598 gchar *start, *end, *out;
5600 start = g_utf8_offset_to_pointer (str, start_pos);
5601 end = g_utf8_offset_to_pointer (start, end_pos - start_pos);
5603 out = g_malloc (end - start + 1);
5604 memcpy (out, start, end - start);
5605 out[end - start] = 0;