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("Error: couldn't set insert file '%s' in body\n", 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\n", 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\n", 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\n", 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)) {
2546 if (remove_dir_recursive(dir_name) < 0) {
2547 g_warning("can't remove directory\n");
2551 if (claws_unlink(dir_name) < 0)
2552 FILE_OP_ERROR(dir_name, "unlink");
2558 if (g_chdir(prev_dir) < 0) {
2559 FILE_OP_ERROR(prev_dir, "chdir");
2566 if (g_rmdir(dir) < 0) {
2567 FILE_OP_ERROR(dir, "rmdir");
2574 gint rename_force(const gchar *oldpath, const gchar *newpath)
2577 if (!is_file_entry_exist(oldpath)) {
2581 if (is_file_exist(newpath)) {
2582 if (claws_unlink(newpath) < 0)
2583 FILE_OP_ERROR(newpath, "unlink");
2586 return g_rename(oldpath, newpath);
2590 * Append src file body to the tail of dest file.
2591 * Now keep_backup has no effects.
2593 gint append_file(const gchar *src, const gchar *dest, gboolean keep_backup)
2595 FILE *src_fp, *dest_fp;
2599 gboolean err = FALSE;
2601 if ((src_fp = g_fopen(src, "rb")) == NULL) {
2602 FILE_OP_ERROR(src, "g_fopen");
2606 if ((dest_fp = g_fopen(dest, "ab")) == NULL) {
2607 FILE_OP_ERROR(dest, "g_fopen");
2612 if (change_file_mode_rw(dest_fp, dest) < 0) {
2613 FILE_OP_ERROR(dest, "chmod");
2614 g_warning("can't change file mode\n");
2617 while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
2618 if (n_read < sizeof(buf) && ferror(src_fp))
2620 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2621 g_warning("writing to %s failed.\n", dest);
2629 if (ferror(src_fp)) {
2630 FILE_OP_ERROR(src, "fread");
2634 if (fclose(dest_fp) == EOF) {
2635 FILE_OP_ERROR(dest, "fclose");
2647 gint copy_file(const gchar *src, const gchar *dest, gboolean keep_backup)
2649 FILE *src_fp, *dest_fp;
2652 gchar *dest_bak = NULL;
2653 gboolean err = FALSE;
2655 if ((src_fp = g_fopen(src, "rb")) == NULL) {
2656 FILE_OP_ERROR(src, "g_fopen");
2659 if (is_file_exist(dest)) {
2660 dest_bak = g_strconcat(dest, ".bak", NULL);
2661 if (rename_force(dest, dest_bak) < 0) {
2662 FILE_OP_ERROR(dest, "rename");
2669 if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2670 FILE_OP_ERROR(dest, "g_fopen");
2673 if (rename_force(dest_bak, dest) < 0)
2674 FILE_OP_ERROR(dest_bak, "rename");
2680 if (change_file_mode_rw(dest_fp, dest) < 0) {
2681 FILE_OP_ERROR(dest, "chmod");
2682 g_warning("can't change file mode\n");
2685 while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
2686 if (n_read < sizeof(buf) && ferror(src_fp))
2688 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2689 g_warning("writing to %s failed.\n", dest);
2694 if (rename_force(dest_bak, dest) < 0)
2695 FILE_OP_ERROR(dest_bak, "rename");
2702 if (ferror(src_fp)) {
2703 FILE_OP_ERROR(src, "fread");
2707 if (fclose(dest_fp) == EOF) {
2708 FILE_OP_ERROR(dest, "fclose");
2715 if (rename_force(dest_bak, dest) < 0)
2716 FILE_OP_ERROR(dest_bak, "rename");
2722 if (keep_backup == FALSE && dest_bak)
2723 claws_unlink(dest_bak);
2730 gint move_file(const gchar *src, const gchar *dest, gboolean overwrite)
2732 if (overwrite == FALSE && is_file_exist(dest)) {
2733 g_warning("move_file(): file %s already exists.", dest);
2737 if (rename_force(src, dest) == 0) return 0;
2739 if (EXDEV != errno) {
2740 FILE_OP_ERROR(src, "rename");
2744 if (copy_file(src, dest, FALSE) < 0) return -1;
2751 gint copy_file_part_to_fp(FILE *fp, off_t offset, size_t length, FILE *dest_fp)
2754 gint bytes_left, to_read;
2757 if (fseek(fp, offset, SEEK_SET) < 0) {
2762 bytes_left = length;
2763 to_read = MIN(bytes_left, sizeof(buf));
2765 while ((n_read = fread(buf, sizeof(gchar), to_read, fp)) > 0) {
2766 if (n_read < to_read && ferror(fp))
2768 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2771 bytes_left -= n_read;
2772 if (bytes_left == 0)
2774 to_read = MIN(bytes_left, sizeof(buf));
2785 gint copy_file_part(FILE *fp, off_t offset, size_t length, const gchar *dest)
2788 gboolean err = FALSE;
2790 if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2791 FILE_OP_ERROR(dest, "g_fopen");
2795 if (change_file_mode_rw(dest_fp, dest) < 0) {
2796 FILE_OP_ERROR(dest, "chmod");
2797 g_warning("can't change file mode\n");
2800 if (copy_file_part_to_fp(fp, offset, length, dest_fp) < 0)
2803 if (!err && fclose(dest_fp) == EOF) {
2804 FILE_OP_ERROR(dest, "fclose");
2809 g_warning("writing to %s failed.\n", dest);
2817 /* convert line endings into CRLF. If the last line doesn't end with
2818 * linebreak, add it.
2820 gchar *canonicalize_str(const gchar *str)
2826 for (p = str; *p != '\0'; ++p) {
2833 if (p == str || *(p - 1) != '\n')
2836 out = outp = g_malloc(new_len + 1);
2837 for (p = str; *p != '\0'; ++p) {
2844 if (p == str || *(p - 1) != '\n') {
2853 gint canonicalize_file(const gchar *src, const gchar *dest)
2855 FILE *src_fp, *dest_fp;
2856 gchar buf[BUFFSIZE];
2858 gboolean err = FALSE;
2859 gboolean last_linebreak = FALSE;
2861 if (src == NULL || dest == NULL)
2864 if ((src_fp = g_fopen(src, "rb")) == NULL) {
2865 FILE_OP_ERROR(src, "g_fopen");
2869 if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2870 FILE_OP_ERROR(dest, "g_fopen");
2875 if (change_file_mode_rw(dest_fp, dest) < 0) {
2876 FILE_OP_ERROR(dest, "chmod");
2877 g_warning("can't change file mode\n");
2880 while (fgets(buf, sizeof(buf), src_fp) != NULL) {
2884 if (len == 0) break;
2885 last_linebreak = FALSE;
2887 if (buf[len - 1] != '\n') {
2888 last_linebreak = TRUE;
2889 r = fputs(buf, dest_fp);
2890 } else if (len > 1 && buf[len - 1] == '\n' && buf[len - 2] == '\r') {
2891 r = fputs(buf, dest_fp);
2894 r = fwrite(buf, 1, len - 1, dest_fp);
2899 r = fputs("\r\n", dest_fp);
2903 g_warning("writing to %s failed.\n", dest);
2911 if (last_linebreak == TRUE) {
2912 if (fputs("\r\n", dest_fp) == EOF)
2916 if (ferror(src_fp)) {
2917 FILE_OP_ERROR(src, "fgets");
2921 if (fclose(dest_fp) == EOF) {
2922 FILE_OP_ERROR(dest, "fclose");
2934 gint canonicalize_file_replace(const gchar *file)
2938 tmp_file = get_tmp_file();
2940 if (canonicalize_file(file, tmp_file) < 0) {
2945 if (move_file(tmp_file, file, TRUE) < 0) {
2946 g_warning("can't replace %s .\n", file);
2947 claws_unlink(tmp_file);
2956 gchar *normalize_newlines(const gchar *str)
2961 out = outp = g_malloc(strlen(str) + 1);
2962 for (p = str; *p != '\0'; ++p) {
2964 if (*(p + 1) != '\n')
2975 gchar *get_outgoing_rfc2822_str(FILE *fp)
2977 gchar buf[BUFFSIZE];
2981 str = g_string_new(NULL);
2983 /* output header part */
2984 while (fgets(buf, sizeof(buf), fp) != NULL) {
2986 if (!g_ascii_strncasecmp(buf, "Bcc:", 4)) {
2993 else if (next != ' ' && next != '\t') {
2997 if (fgets(buf, sizeof(buf), fp) == NULL)
3001 g_string_append(str, buf);
3002 g_string_append(str, "\r\n");
3008 /* output body part */
3009 while (fgets(buf, sizeof(buf), fp) != NULL) {
3012 g_string_append_c(str, '.');
3013 g_string_append(str, buf);
3014 g_string_append(str, "\r\n");
3018 g_string_free(str, FALSE);
3024 * Create a new boundary in a way that it is very unlikely that this
3025 * will occur in the following text. It would be easy to ensure
3026 * uniqueness if everything is either quoted-printable or base64
3027 * encoded (note that conversion is allowed), but because MIME bodies
3028 * may be nested, it may happen that the same boundary has already
3031 * boundary := 0*69<bchars> bcharsnospace
3032 * bchars := bcharsnospace / " "
3033 * bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" /
3034 * "+" / "_" / "," / "-" / "." /
3035 * "/" / ":" / "=" / "?"
3037 * some special characters removed because of buggy MTAs
3040 gchar *generate_mime_boundary(const gchar *prefix)
3042 static gchar tbl[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
3043 "abcdefghijklmnopqrstuvwxyz"
3048 for (i = 0; i < sizeof(buf_uniq) - 1; i++)
3049 buf_uniq[i] = tbl[g_random_int_range(0, sizeof(tbl) - 1)];
3052 return g_strdup_printf("%s_/%s", prefix ? prefix : "MP",
3056 gint change_file_mode_rw(FILE *fp, const gchar *file)
3059 return fchmod(fileno(fp), S_IRUSR|S_IWUSR);
3061 return g_chmod(file, S_IRUSR|S_IWUSR);
3065 FILE *my_tmpfile(void)
3067 #if HAVE_MKSTEMP || defined(G_OS_WIN32)
3068 const gchar suffix[] = ".XXXXXX";
3069 const gchar *tmpdir;
3071 const gchar *progname;
3080 tmpdir = get_tmp_dir();
3081 tmplen = strlen(tmpdir);
3082 progname = g_get_prgname();
3083 if (progname == NULL)
3084 progname = "claws-mail";
3085 proglen = strlen(progname);
3086 Xalloca(fname, tmplen + 1 + proglen + sizeof(suffix),
3089 memcpy(fname, tmpdir, tmplen);
3090 fname[tmplen] = G_DIR_SEPARATOR;
3091 memcpy(fname + tmplen + 1, progname, proglen);
3092 memcpy(fname + tmplen + 1 + proglen, suffix, sizeof(suffix));
3094 fd = mkstemp(fname);
3099 claws_unlink(fname);
3101 /* verify that we can write in the file after unlinking */
3102 if (write(fd, buf, 1) < 0) {
3109 fp = fdopen(fd, "w+b");
3117 #endif /* HAVE_MKSTEMP || G_OS_WIN32 */
3122 FILE *get_tmpfile_in_dir(const gchar *dir, gchar **filename)
3126 char *template = g_strdup_printf ("%s%cclaws.XXXXXX",
3127 dir, G_DIR_SEPARATOR);
3128 fd = mkstemp_name(template, filename);
3131 *filename = g_strdup_printf("%s%cclaws.XXXXXX", dir, G_DIR_SEPARATOR);
3132 fd = mkstemp(*filename);
3136 return fdopen(fd, "w+");
3139 FILE *str_open_as_stream(const gchar *str)
3144 cm_return_val_if_fail(str != NULL, NULL);
3148 FILE_OP_ERROR("str_open_as_stream", "my_tmpfile");
3153 if (len == 0) return fp;
3155 if (fwrite(str, 1, len, fp) != len) {
3156 FILE_OP_ERROR("str_open_as_stream", "fwrite");
3165 gint str_write_to_file(const gchar *str, const gchar *file)
3170 cm_return_val_if_fail(str != NULL, -1);
3171 cm_return_val_if_fail(file != NULL, -1);
3173 if ((fp = g_fopen(file, "wb")) == NULL) {
3174 FILE_OP_ERROR(file, "g_fopen");
3184 if (fwrite(str, 1, len, fp) != len) {
3185 FILE_OP_ERROR(file, "fwrite");
3191 if (fclose(fp) == EOF) {
3192 FILE_OP_ERROR(file, "fclose");
3200 static gchar *file_read_stream_to_str_full(FILE *fp, gboolean recode)
3207 cm_return_val_if_fail(fp != NULL, NULL);
3209 array = g_byte_array_new();
3211 while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
3212 if (n_read < sizeof(buf) && ferror(fp))
3214 g_byte_array_append(array, buf, n_read);
3218 FILE_OP_ERROR("file stream", "fread");
3219 g_byte_array_free(array, TRUE);
3224 g_byte_array_append(array, buf, 1);
3225 str = (gchar *)array->data;
3226 g_byte_array_free(array, FALSE);
3228 if (recode && !g_utf8_validate(str, -1, NULL)) {
3229 const gchar *src_codeset, *dest_codeset;
3231 src_codeset = conv_get_locale_charset_str();
3232 dest_codeset = CS_UTF_8;
3233 tmp = conv_codeset_strdup(str, src_codeset, dest_codeset);
3241 static gchar *file_read_to_str_full(const gchar *file, gboolean recode)
3248 struct timeval timeout = {1, 0};
3253 cm_return_val_if_fail(file != NULL, NULL);
3255 if (g_stat(file, &s) != 0) {
3256 FILE_OP_ERROR(file, "stat");
3259 if (S_ISDIR(s.st_mode)) {
3260 g_warning("%s: is a directory\n", file);
3265 fp = g_fopen (file, "rb");
3267 FILE_OP_ERROR(file, "open");
3271 /* test whether the file is readable without blocking */
3272 fd = g_open(file, O_RDONLY | O_NONBLOCK, 0);
3274 FILE_OP_ERROR(file, "open");
3281 /* allow for one second */
3282 err = select(fd+1, &fds, NULL, NULL, &timeout);
3283 if (err <= 0 || !FD_ISSET(fd, &fds)) {
3285 FILE_OP_ERROR(file, "select");
3287 g_warning("%s: doesn't seem readable\n", file);
3293 /* Now clear O_NONBLOCK */
3294 if ((fflags = fcntl(fd, F_GETFL)) < 0) {
3295 FILE_OP_ERROR(file, "fcntl (F_GETFL)");
3299 if (fcntl(fd, F_SETFL, (fflags & ~O_NONBLOCK)) < 0) {
3300 FILE_OP_ERROR(file, "fcntl (F_SETFL)");
3305 /* get the FILE pointer */
3306 fp = fdopen(fd, "rb");
3309 FILE_OP_ERROR(file, "fdopen");
3310 close(fd); /* if fp isn't NULL, we'll use fclose instead! */
3315 str = file_read_stream_to_str_full(fp, recode);
3322 gchar *file_read_to_str(const gchar *file)
3324 return file_read_to_str_full(file, TRUE);
3326 gchar *file_read_stream_to_str(FILE *fp)
3328 return file_read_stream_to_str_full(fp, TRUE);
3331 gchar *file_read_to_str_no_recode(const gchar *file)
3333 return file_read_to_str_full(file, FALSE);
3335 gchar *file_read_stream_to_str_no_recode(FILE *fp)
3337 return file_read_stream_to_str_full(fp, FALSE);
3340 char *fgets_crlf(char *buf, int size, FILE *stream)
3342 gboolean is_cr = FALSE;
3343 gboolean last_was_cr = FALSE;
3348 while (--size > 0 && (c = getc(stream)) != EOF)
3351 is_cr = (c == '\r');
3361 last_was_cr = is_cr;
3363 if (c == EOF && cs == buf)
3371 static gint execute_async(gchar *const argv[])
3373 cm_return_val_if_fail(argv != NULL && argv[0] != NULL, -1);
3375 if (g_spawn_async(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH,
3376 NULL, NULL, NULL, FALSE) == FALSE) {
3377 g_warning("Couldn't execute command: %s\n", argv[0]);
3384 static gint execute_sync(gchar *const argv[])
3388 cm_return_val_if_fail(argv != NULL && argv[0] != NULL, -1);
3391 if (g_spawn_sync(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH,
3392 NULL, NULL, NULL, NULL, &status, NULL) == FALSE) {
3393 g_warning("Couldn't execute command: %s\n", argv[0]);
3397 if (WIFEXITED(status))
3398 return WEXITSTATUS(status);
3402 if (g_spawn_sync(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH|
3403 G_SPAWN_CHILD_INHERITS_STDIN|G_SPAWN_LEAVE_DESCRIPTORS_OPEN,
3404 NULL, NULL, NULL, NULL, &status, NULL) == FALSE) {
3405 g_warning("Couldn't execute command: %s\n", argv[0]);
3413 gint execute_command_line(const gchar *cmdline, gboolean async)
3418 debug_print("execute_command_line(): executing: %s\n", cmdline?cmdline:"(null)");
3420 argv = strsplit_with_quote(cmdline, " ", 0);
3423 ret = execute_async(argv);
3425 ret = execute_sync(argv);
3432 gchar *get_command_output(const gchar *cmdline)
3434 gchar *child_stdout;
3437 cm_return_val_if_fail(cmdline != NULL, NULL);
3439 debug_print("get_command_output(): executing: %s\n", cmdline);
3441 if (g_spawn_command_line_sync(cmdline, &child_stdout, NULL, &status,
3443 g_warning("Couldn't execute command: %s\n", cmdline);
3447 return child_stdout;
3450 static gint is_unchanged_uri_char(char c)
3461 static void encode_uri(gchar *encoded_uri, gint bufsize, const gchar *uri)
3467 for(i = 0; i < strlen(uri) ; i++) {
3468 if (is_unchanged_uri_char(uri[i])) {
3469 if (k + 2 >= bufsize)
3471 encoded_uri[k++] = uri[i];
3474 char * hexa = "0123456789ABCDEF";
3476 if (k + 4 >= bufsize)
3478 encoded_uri[k++] = '%';
3479 encoded_uri[k++] = hexa[uri[i] / 16];
3480 encoded_uri[k++] = hexa[uri[i] % 16];
3486 gint open_uri(const gchar *uri, const gchar *cmdline)
3490 gchar buf[BUFFSIZE];
3492 gchar encoded_uri[BUFFSIZE];
3493 cm_return_val_if_fail(uri != NULL, -1);
3495 /* an option to choose whether to use encode_uri or not ? */
3496 encode_uri(encoded_uri, BUFFSIZE, uri);
3499 (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
3500 !strchr(p + 2, '%'))
3501 g_snprintf(buf, sizeof(buf), cmdline, encoded_uri);
3504 g_warning("Open URI command-line is invalid "
3505 "(there must be only one '%%s'): %s",
3507 g_snprintf(buf, sizeof(buf), DEFAULT_BROWSER_CMD, encoded_uri);
3510 execute_command_line(buf, TRUE);
3512 ShellExecute(NULL, "open", uri, NULL, NULL, SW_SHOW);
3517 gint open_txt_editor(const gchar *filepath, const gchar *cmdline)
3519 gchar buf[BUFFSIZE];
3522 cm_return_val_if_fail(filepath != NULL, -1);
3525 (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
3526 !strchr(p + 2, '%'))
3527 g_snprintf(buf, sizeof(buf), cmdline, filepath);
3530 g_warning("Open Text Editor command-line is invalid "
3531 "(there must be only one '%%s'): %s",
3533 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, filepath);
3536 execute_command_line(buf, TRUE);
3541 time_t remote_tzoffset_sec(const gchar *zone)
3543 static gchar ustzstr[] = "PSTPDTMSTMDTCSTCDTESTEDT";
3549 time_t remoteoffset;
3551 strncpy(zone3, zone, 3);
3555 if (sscanf(zone, "%c%d", &c, &offset) == 2 &&
3556 (c == '+' || c == '-')) {
3557 remoteoffset = ((offset / 100) * 60 + (offset % 100)) * 60;
3559 remoteoffset = -remoteoffset;
3560 } else if (!strncmp(zone, "UT" , 2) ||
3561 !strncmp(zone, "GMT", 2)) {
3563 } else if (strlen(zone3) == 3) {
3564 for (p = ustzstr; *p != '\0'; p += 3) {
3565 if (!g_ascii_strncasecmp(p, zone3, 3)) {
3566 iustz = ((gint)(p - ustzstr) / 3 + 1) / 2 - 8;
3567 remoteoffset = iustz * 3600;
3573 } else if (strlen(zone3) == 1) {
3575 case 'Z': remoteoffset = 0; break;
3576 case 'A': remoteoffset = -1; break;
3577 case 'B': remoteoffset = -2; break;
3578 case 'C': remoteoffset = -3; break;
3579 case 'D': remoteoffset = -4; break;
3580 case 'E': remoteoffset = -5; break;
3581 case 'F': remoteoffset = -6; break;
3582 case 'G': remoteoffset = -7; break;
3583 case 'H': remoteoffset = -8; break;
3584 case 'I': remoteoffset = -9; break;
3585 case 'K': remoteoffset = -10; break; /* J is not used */
3586 case 'L': remoteoffset = -11; break;
3587 case 'M': remoteoffset = -12; break;
3588 case 'N': remoteoffset = 1; break;
3589 case 'O': remoteoffset = 2; break;
3590 case 'P': remoteoffset = 3; break;
3591 case 'Q': remoteoffset = 4; break;
3592 case 'R': remoteoffset = 5; break;
3593 case 'S': remoteoffset = 6; break;
3594 case 'T': remoteoffset = 7; break;
3595 case 'U': remoteoffset = 8; break;
3596 case 'V': remoteoffset = 9; break;
3597 case 'W': remoteoffset = 10; break;
3598 case 'X': remoteoffset = 11; break;
3599 case 'Y': remoteoffset = 12; break;
3600 default: remoteoffset = 0; break;
3602 remoteoffset = remoteoffset * 3600;
3606 return remoteoffset;
3609 time_t tzoffset_sec(time_t *now)
3613 struct tm buf1, buf2;
3615 if (now && *now < 0)
3618 gmt = *gmtime_r(now, &buf1);
3619 lt = localtime_r(now, &buf2);
3621 off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
3623 if (lt->tm_year < gmt.tm_year)
3625 else if (lt->tm_year > gmt.tm_year)
3627 else if (lt->tm_yday < gmt.tm_yday)
3629 else if (lt->tm_yday > gmt.tm_yday)
3632 if (off >= 24 * 60) /* should be impossible */
3633 off = 23 * 60 + 59; /* if not, insert silly value */
3634 if (off <= -24 * 60)
3635 off = -(23 * 60 + 59);
3640 /* calculate timezone offset */
3641 gchar *tzoffset(time_t *now)
3643 static gchar offset_string[6];
3647 struct tm buf1, buf2;
3649 if (now && *now < 0)
3652 gmt = *gmtime_r(now, &buf1);
3653 lt = localtime_r(now, &buf2);
3655 off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
3657 if (lt->tm_year < gmt.tm_year)
3659 else if (lt->tm_year > gmt.tm_year)
3661 else if (lt->tm_yday < gmt.tm_yday)
3663 else if (lt->tm_yday > gmt.tm_yday)
3671 if (off >= 24 * 60) /* should be impossible */
3672 off = 23 * 60 + 59; /* if not, insert silly value */
3674 sprintf(offset_string, "%c%02d%02d", sign, off / 60, off % 60);
3676 return offset_string;
3679 void get_rfc822_date(gchar *buf, gint len)
3683 gchar day[4], mon[4];
3684 gint dd, hh, mm, ss, yyyy;
3686 gchar buf2[BUFFSIZE];
3689 lt = localtime_r(&t, &buf1);
3691 sscanf(asctime_r(lt, buf2), "%3s %3s %d %d:%d:%d %d\n",
3692 day, mon, &dd, &hh, &mm, &ss, &yyyy);
3694 g_snprintf(buf, len, "%s, %d %s %d %02d:%02d:%02d %s",
3695 day, dd, mon, yyyy, hh, mm, ss, tzoffset(&t));
3698 void debug_set_mode(gboolean mode)
3703 gboolean debug_get_mode(void)
3708 void debug_print_real(const gchar *format, ...)
3711 gchar buf[BUFFSIZE];
3713 if (!debug_mode) return;
3715 va_start(args, format);
3716 g_vsnprintf(buf, sizeof(buf), format, args);
3723 const char * debug_srcname(const char *file)
3725 const char *s = strrchr (file, '/');
3730 void * subject_table_lookup(GHashTable *subject_table, gchar * subject)
3732 if (subject == NULL)
3735 subject += subject_get_prefix_length(subject);
3737 return g_hash_table_lookup(subject_table, subject);
3740 void subject_table_insert(GHashTable *subject_table, gchar * subject,
3743 if (subject == NULL || *subject == 0)
3745 subject += subject_get_prefix_length(subject);
3746 g_hash_table_insert(subject_table, subject, data);
3749 void subject_table_remove(GHashTable *subject_table, gchar * subject)
3751 if (subject == NULL)
3754 subject += subject_get_prefix_length(subject);
3755 g_hash_table_remove(subject_table, subject);
3759 static regex_t u_regex;
3760 static gboolean u_init_;
3763 void utils_free_regex(void)
3774 *\brief Check if a string is prefixed with known (combinations)
3775 * of prefixes. The function assumes that each prefix
3776 * is terminated by zero or exactly _one_ space.
3778 *\param str String to check for a prefixes
3780 *\return int Number of chars in the prefix that should be skipped
3781 * for a "clean" subject line. If no prefix was found, 0
3784 int subject_get_prefix_length(const gchar *subject)
3787 /*!< Array with allowable reply prefixes regexps. */
3788 static const gchar * const prefixes[] = {
3789 "Re\\:", /* "Re:" */
3790 "Re\\[[1-9][0-9]*\\]\\:", /* "Re[XXX]:" (non-conforming news mail clients) */
3791 "Antw\\:", /* "Antw:" (Dutch / German Outlook) */
3792 "Aw\\:", /* "Aw:" (German) */
3793 "Antwort\\:", /* "Antwort:" (German Lotus Notes) */
3794 "Res\\:", /* "Res:" (Spanish/Brazilian Outlook) */
3795 "Fw\\:", /* "Fw:" Forward */
3796 "Fwd\\:", /* "Fwd:" Forward */
3797 "Enc\\:", /* "Enc:" Forward (Brazilian Outlook) */
3798 "Odp\\:", /* "Odp:" Re (Polish Outlook) */
3799 "Rif\\:", /* "Rif:" (Italian Outlook) */
3800 "Sv\\:", /* "Sv" (Norwegian) */
3801 "Vs\\:", /* "Vs" (Norwegian) */
3802 "Ad\\:", /* "Ad" (Norwegian) */
3803 "\347\255\224\345\244\215\\:", /* "Re" (Chinese, UTF-8) */
3804 "R\303\251f\\. \\:", /* "R�f. :" (French Lotus Notes) */
3805 "Re \\:", /* "Re :" (French Yahoo Mail) */
3808 const int PREFIXES = sizeof prefixes / sizeof prefixes[0];
3812 if (!subject) return 0;
3813 if (!*subject) return 0;
3816 GString *s = g_string_new("");
3818 for (n = 0; n < PREFIXES; n++)
3819 /* Terminate each prefix regexpression by a
3820 * "\ ?" (zero or ONE space), and OR them */
3821 g_string_append_printf(s, "(%s\\ ?)%s",
3826 g_string_prepend(s, "(");
3827 g_string_append(s, ")+"); /* match at least once */
3828 g_string_prepend(s, "^\\ *"); /* from beginning of line */
3831 /* We now have something like "^\ *((PREFIX1\ ?)|(PREFIX2\ ?))+"
3832 * TODO: Should this be "^\ *(((PREFIX1)|(PREFIX2))\ ?)+" ??? */
3833 if (regcomp(&u_regex, s->str, REG_EXTENDED | REG_ICASE)) {
3834 debug_print("Error compiling regexp %s\n", s->str);
3835 g_string_free(s, TRUE);
3839 g_string_free(s, TRUE);
3843 if (!regexec(&u_regex, subject, 1, &pos, 0) && pos.rm_so != -1)
3848 /*!< Array with allowable reply prefixes regexps. */
3849 static const gchar * const prefixes[] = {
3851 "antw:", /* "Antw:" (Dutch / German Outlook) */
3852 "aw:", /* "Aw:" (German) */
3853 "antwort:", /* "Antwort:" (German Lotus Notes) */
3854 "res:", /* "Res:" (Spanish/Brazilian Outlook) */
3855 "fw:", /* "Fw:" Forward */
3856 "fwd:", /* "Fwd:" Forward */
3857 "enc:", /* "Enc:" Forward (Brazilian Outlook) */
3858 "odp:", /* "Odp:" Re (Polish Outlook) */
3859 "rif:", /* "Rif:" (Italian Outlook) */
3860 "sv:", /* "Sv" (Norwegian) */
3861 "vs:", /* "Vs" (Norwegian) */
3862 "ad:", /* "Ad" (Norwegian) */
3863 "R\303\251f. :", /* "R�f. :" (French Lotus Notes) */
3864 "Re :", /* "Re :" (French Yahoo Mail) */
3867 const int PREFIXES = sizeof prefixes / sizeof prefixes[0];
3870 if (!subject) return 0;
3871 if (!*subject) return 0;
3873 for (n = 0; n < PREFIXES; n++) {
3874 int len = strlen(prefixes[n]);
3875 if (!strncasecmp(subject, prefixes[n], len)) {
3876 if (subject[len] == ' ')
3885 static guint g_stricase_hash(gconstpointer gptr)
3887 guint hash_result = 0;
3890 for (str = gptr; str && *str; str++) {
3891 hash_result += toupper(*str);
3897 static gint g_stricase_equal(gconstpointer gptr1, gconstpointer gptr2)
3899 const char *str1 = gptr1;
3900 const char *str2 = gptr2;
3902 return !strcasecmp(str1, str2);
3905 gint g_int_compare(gconstpointer a, gconstpointer b)
3907 return GPOINTER_TO_INT(a) - GPOINTER_TO_INT(b);
3910 gchar *generate_msgid(gchar *buf, gint len, gchar *user_addr)
3918 lt = localtime_r(&t, &buft);
3920 if (user_addr != NULL)
3921 addr = g_strdup_printf(".%s", user_addr);
3922 else if (strlen(buf) != 0)
3923 addr = g_strdup_printf("@%s", buf);
3925 addr = g_strdup_printf("@%s", get_domain_name());
3927 /* Replace all @ but the last one in addr, with underscores.
3928 * RFC 2822 States that msg-id syntax only allows one @.
3930 while (strchr(addr, '@') != NULL && strchr(addr, '@') != strrchr(addr, '@'))
3931 *(strchr(addr, '@')) = '_';
3933 g_snprintf(buf, len, "%04d%02d%02d%02d%02d%02d.%08x%s",
3934 lt->tm_year + 1900, lt->tm_mon + 1,
3935 lt->tm_mday, lt->tm_hour,
3936 lt->tm_min, lt->tm_sec,
3937 (guint) rand(), addr);
3944 quote_cmd_argument()
3946 return a quoted string safely usable in argument of a command.
3948 code is extracted and adapted from etPan! project -- DINH V. Ho�.
3951 gint quote_cmd_argument(gchar * result, guint size,
3961 for(p = path ; * p != '\0' ; p ++) {
3963 if (isalnum((guchar)*p) || (* p == '/')) {
3964 if (remaining > 0) {
3970 result[size - 1] = '\0';
3975 if (remaining >= 2) {
3983 result[size - 1] = '\0';
3988 if (remaining > 0) {
3992 result[size - 1] = '\0';
4006 static void g_node_map_recursive(GNode *node, gpointer data)
4008 GNodeMapData *mapdata = (GNodeMapData *) data;
4010 GNodeMapData newmapdata;
4013 newdata = mapdata->func(node->data, mapdata->data);
4014 if (newdata != NULL) {
4015 newnode = g_node_new(newdata);
4016 g_node_append(mapdata->parent, newnode);
4018 newmapdata.parent = newnode;
4019 newmapdata.func = mapdata->func;
4020 newmapdata.data = mapdata->data;
4022 g_node_children_foreach(node, G_TRAVERSE_ALL, g_node_map_recursive, &newmapdata);
4026 GNode *g_node_map(GNode *node, GNodeMapFunc func, gpointer data)
4029 GNodeMapData mapdata;
4031 cm_return_val_if_fail(node != NULL, NULL);
4032 cm_return_val_if_fail(func != NULL, NULL);
4034 root = g_node_new(func(node->data, data));
4036 mapdata.parent = root;
4037 mapdata.func = func;
4038 mapdata.data = data;
4040 g_node_children_foreach(node, G_TRAVERSE_ALL, g_node_map_recursive, &mapdata);
4045 #define HEX_TO_INT(val, hex) \
4049 if ('0' <= c && c <= '9') { \
4051 } else if ('a' <= c && c <= 'f') { \
4052 val = c - 'a' + 10; \
4053 } else if ('A' <= c && c <= 'F') { \
4054 val = c - 'A' + 10; \
4060 gboolean get_hex_value(guchar *out, gchar c1, gchar c2)
4067 if (hi == -1 || lo == -1)
4070 *out = (hi << 4) + lo;
4074 #define INT_TO_HEX(hex, val) \
4077 hex = '0' + (val); \
4079 hex = 'A' + (val) - 10; \
4082 void get_hex_str(gchar *out, guchar ch)
4086 INT_TO_HEX(hex, ch >> 4);
4088 INT_TO_HEX(hex, ch & 0x0f);
4094 #define G_PRINT_REF 1 == 1 ? (void) 0 : (void)
4096 #define G_PRINT_REF g_print
4100 *\brief Register ref counted pointer. It is based on GBoxed, so should
4101 * work with anything that uses the GType system. The semantics
4102 * are similar to a C++ auto pointer, with the exception that
4103 * C doesn't have automatic closure (calling destructors) when
4104 * exiting a block scope.
4105 * Use the \ref G_TYPE_AUTO_POINTER macro instead of calling this
4106 * function directly.
4108 *\return GType A GType type.
4110 GType g_auto_pointer_register(void)
4112 static GType auto_pointer_type;
4113 if (!auto_pointer_type)
4115 g_boxed_type_register_static
4116 ("G_TYPE_AUTO_POINTER",
4117 (GBoxedCopyFunc) g_auto_pointer_copy,
4118 (GBoxedFreeFunc) g_auto_pointer_free);
4119 return auto_pointer_type;
4123 *\brief Structure with g_new() allocated pointer guarded by the
4126 typedef struct AutoPointerRef {
4127 void (*free) (gpointer);
4133 *\brief The auto pointer opaque structure that references the
4134 * pointer guard block.
4136 typedef struct AutoPointer {
4137 AutoPointerRef *ref;
4138 gpointer ptr; /*!< access to protected pointer */
4142 *\brief Creates an auto pointer for a g_new()ed pointer. Example:
4146 * ... tell gtk_list_store it should use a G_TYPE_AUTO_POINTER
4147 * ... when assigning, copying and freeing storage elements
4149 * gtk_list_store_new(N_S_COLUMNS,
4150 * G_TYPE_AUTO_POINTER,
4154 * Template *precious_data = g_new0(Template, 1);
4155 * g_pointer protect = g_auto_pointer_new(precious_data);
4157 * gtk_list_store_set(container, &iter,
4161 * ... the gtk_list_store has copied the pointer and
4162 * ... incremented its reference count, we should free
4163 * ... the auto pointer (in C++ a destructor would do
4164 * ... this for us when leaving block scope)
4166 * g_auto_pointer_free(protect);
4168 * ... gtk_list_store_set() now manages the data. When
4169 * ... *explicitly* requesting a pointer from the list
4170 * ... store, don't forget you get a copy that should be
4171 * ... freed with g_auto_pointer_free() eventually.
4175 *\param pointer Pointer to be guarded.
4177 *\return GAuto * Pointer that should be used in containers with
4180 GAuto *g_auto_pointer_new(gpointer p)
4182 AutoPointerRef *ref;
4188 ref = g_new0(AutoPointerRef, 1);
4189 ptr = g_new0(AutoPointer, 1);
4199 G_PRINT_REF ("XXXX ALLOC(%lx)\n", p);
4205 *\brief Allocate an autopointer using the passed \a free function to
4206 * free the guarded pointer
4208 GAuto *g_auto_pointer_new_with_free(gpointer p, GFreeFunc free_)
4215 aptr = g_auto_pointer_new(p);
4216 aptr->ref->free = free_;
4220 gpointer g_auto_pointer_get_ptr(GAuto *auto_ptr)
4222 if (auto_ptr == NULL)
4224 return ((AutoPointer *) auto_ptr)->ptr;
4228 *\brief Copies an auto pointer by. It's mostly not necessary
4229 * to call this function directly, unless you copy/assign
4230 * the guarded pointer.
4232 *\param auto_ptr Auto pointer returned by previous call to
4233 * g_auto_pointer_new_XXX()
4235 *\return gpointer An auto pointer
4237 GAuto *g_auto_pointer_copy(GAuto *auto_ptr)
4240 AutoPointerRef *ref;
4243 if (auto_ptr == NULL)
4248 newp = g_new0(AutoPointer, 1);
4251 newp->ptr = ref->pointer;
4255 G_PRINT_REF ("XXXX COPY(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4261 *\brief Free an auto pointer
4263 void g_auto_pointer_free(GAuto *auto_ptr)
4266 AutoPointerRef *ref;
4268 if (auto_ptr == NULL)
4274 if (--(ref->cnt) == 0) {
4276 G_PRINT_REF ("XXXX FREE(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4278 ref->free(ref->pointer);
4283 G_PRINT_REF ("XXXX DEREF(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4288 void replace_returns(gchar *str)
4293 while (strstr(str, "\n")) {
4294 *strstr(str, "\n") = ' ';
4296 while (strstr(str, "\r")) {
4297 *strstr(str, "\r") = ' ';
4301 /* get_uri_part() - retrieves a URI starting from scanpos.
4302 Returns TRUE if succesful */
4303 gboolean get_uri_part(const gchar *start, const gchar *scanpos,
4304 const gchar **bp, const gchar **ep, gboolean hdr)
4307 gint parenthese_cnt = 0;
4309 cm_return_val_if_fail(start != NULL, FALSE);
4310 cm_return_val_if_fail(scanpos != NULL, FALSE);
4311 cm_return_val_if_fail(bp != NULL, FALSE);
4312 cm_return_val_if_fail(ep != NULL, FALSE);
4316 /* find end point of URI */
4317 for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
4318 if (!g_ascii_isgraph(*(const guchar *)ep_) ||
4319 !IS_ASCII(*(const guchar *)ep_) ||
4320 strchr("[]{}<>\"", *ep_)) {
4322 } else if (strchr("(", *ep_)) {
4324 } else if (strchr(")", *ep_)) {
4325 if (parenthese_cnt > 0)
4332 /* no punctuation at end of string */
4334 /* FIXME: this stripping of trailing punctuations may bite with other URIs.
4335 * should pass some URI type to this function and decide on that whether
4336 * to perform punctuation stripping */
4338 #define IS_REAL_PUNCT(ch) (g_ascii_ispunct(ch) && !strchr("/?=-_)", ch))
4340 for (; ep_ - 1 > scanpos + 1 &&
4341 IS_REAL_PUNCT(*(ep_ - 1));
4345 #undef IS_REAL_PUNCT
4352 gchar *make_uri_string(const gchar *bp, const gchar *ep)
4354 while (bp && *bp && g_ascii_isspace(*bp))
4356 return g_strndup(bp, ep - bp);
4359 /* valid mail address characters */
4360 #define IS_RFC822_CHAR(ch) \
4364 !g_ascii_isspace(ch) && \
4365 !strchr("(),;<>\"", (ch)))
4367 /* alphabet and number within 7bit ASCII */
4368 #define IS_ASCII_ALNUM(ch) (IS_ASCII(ch) && g_ascii_isalnum(ch))
4369 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
4371 static GHashTable *create_domain_tab(void)
4373 static const gchar *toplvl_domains [] = {
4375 "arpa", "coop", "info", "name", "biz", "com", "edu", "gov",
4376 "int", "mil", "net", "org", "ac", "ad", "ae", "af", "ag",
4377 "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au",
4378 "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi",
4379 "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
4380 "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl",
4381 "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz", "de",
4382 "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er",
4383 "es", "et", "eu", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd",
4384 "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
4385 "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
4386 "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir",
4387 "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
4388 "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc",
4389 "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
4390 "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq",
4391 "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na",
4392 "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu",
4393 "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm",
4394 "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru",
4395 "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj",
4396 "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
4397 "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to",
4398 "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um",
4399 "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu",
4400 "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw"
4403 GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
4405 cm_return_val_if_fail(htab, NULL);
4406 for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++)
4407 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
4411 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
4413 const gint MAX_LVL_DOM_NAME_LEN = 6;
4414 gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
4415 const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
4418 if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
4421 for (p = buf; p < m && first < last; *p++ = *first++)
4425 return g_hash_table_lookup(tab, buf) != NULL;
4428 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
4429 gboolean get_email_part(const gchar *start, const gchar *scanpos,
4430 const gchar **bp, const gchar **ep, gboolean hdr)
4432 /* more complex than the uri part because we need to scan back and forward starting from
4433 * the scan position. */
4434 gboolean result = FALSE;
4435 const gchar *bp_ = NULL;
4436 const gchar *ep_ = NULL;
4437 static GHashTable *dom_tab;
4438 const gchar *last_dot = NULL;
4439 const gchar *prelast_dot = NULL;
4440 const gchar *last_tld_char = NULL;
4442 /* the informative part of the email address (describing the name
4443 * of the email address owner) may contain quoted parts. the
4444 * closure stack stores the last encountered quotes. */
4445 gchar closure_stack[128];
4446 gchar *ptr = closure_stack;
4448 cm_return_val_if_fail(start != NULL, FALSE);
4449 cm_return_val_if_fail(scanpos != NULL, FALSE);
4450 cm_return_val_if_fail(bp != NULL, FALSE);
4451 cm_return_val_if_fail(ep != NULL, FALSE);
4454 const gchar *start_quote = NULL;
4455 const gchar *end_quote = NULL;
4457 /* go to the real start */
4458 if (start[0] == ',')
4460 if (start[0] == ';')
4462 while (start[0] == '\n' || start[0] == '\r')
4464 while (start[0] == ' ' || start[0] == '\t')
4469 /* check if there are quotes (to skip , in them) */
4470 if (*start == '"') {
4471 start_quote = start;
4473 end_quote = strstr(start, "\"");
4479 /* skip anything between quotes */
4480 if (start_quote && end_quote) {
4485 /* find end (either , or ; or end of line) */
4486 if (strstr(start, ",") && strstr(start, ";"))
4487 *ep = strstr(start,",") < strstr(start, ";")
4488 ? strstr(start, ",") : strstr(start, ";");
4489 else if (strstr(start, ","))
4490 *ep = strstr(start, ",");
4491 else if (strstr(start, ";"))
4492 *ep = strstr(start, ";");
4494 *ep = start+strlen(start);
4496 /* go back to real start */
4497 if (start_quote && end_quote) {
4498 start = start_quote;
4501 /* check there's still an @ in that, or search
4502 * further if possible */
4503 if (strstr(start, "@") && strstr(start, "@") < *ep)
4505 else if (*ep < start+strlen(start)) {
4508 } else if (start_quote && strstr(start, "\"") && strstr(start, "\"") < *ep) {
4516 dom_tab = create_domain_tab();
4517 cm_return_val_if_fail(dom_tab, FALSE);
4519 /* scan start of address */
4520 for (bp_ = scanpos - 1;
4521 bp_ >= start && IS_RFC822_CHAR(*(const guchar *)bp_); bp_--)
4524 /* TODO: should start with an alnum? */
4526 for (; bp_ < scanpos && !IS_ASCII_ALNUM(*(const guchar *)bp_); bp_++)
4529 if (bp_ != scanpos) {
4530 /* scan end of address */
4531 for (ep_ = scanpos + 1;
4532 *ep_ && IS_RFC822_CHAR(*(const guchar *)ep_); ep_++)
4534 prelast_dot = last_dot;
4536 if (*(last_dot + 1) == '.') {
4537 if (prelast_dot == NULL)
4539 last_dot = prelast_dot;
4544 /* TODO: really should terminate with an alnum? */
4545 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*(const guchar *)ep_);
4550 if (last_dot == NULL)
4552 if (last_dot >= ep_)
4553 last_dot = prelast_dot;
4554 if (last_dot == NULL || (scanpos + 1 >= last_dot))
4558 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
4559 if (*last_tld_char == '?')
4562 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
4569 if (!result) return FALSE;
4571 if (*ep_ && bp_ != start && *(bp_ - 1) == '"' && *(ep_) == '"'
4572 && *(ep_ + 1) == ' ' && *(ep_ + 2) == '<'
4573 && IS_RFC822_CHAR(*(ep_ + 3))) {
4574 /* this informative part with an @ in it is
4575 * followed by the email address */
4578 /* go to matching '>' (or next non-rfc822 char, like \n) */
4579 for (; *ep_ != '>' && *ep != '\0' && IS_RFC822_CHAR(*ep_); ep_++)
4582 /* include the bracket */
4583 if (*ep_ == '>') ep_++;
4585 /* include the leading quote */
4593 /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
4594 if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
4597 /* see if this is <bracketed>; in this case we also scan for the informative part. */
4598 if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
4601 #define FULL_STACK() ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
4602 #define IN_STACK() (ptr > closure_stack)
4603 /* has underrun check */
4604 #define POP_STACK() if(IN_STACK()) --ptr
4605 /* has overrun check */
4606 #define PUSH_STACK(c) if(!FULL_STACK()) *ptr++ = (c); else return TRUE
4607 /* has underrun check */
4608 #define PEEK_STACK() (IN_STACK() ? *(ptr - 1) : 0)
4612 /* scan for the informative part. */
4613 for (bp_ -= 2; bp_ >= start; bp_--) {
4614 /* if closure on the stack keep scanning */
4615 if (PEEK_STACK() == *bp_) {
4619 if (!IN_STACK() && (*bp_ == '\'' || *bp_ == '"')) {
4624 /* if nothing in the closure stack, do the special conditions
4625 * the following if..else expression simply checks whether
4626 * a token is acceptable. if not acceptable, the clause
4627 * should terminate the loop with a 'break' */
4628 if (!PEEK_STACK()) {
4630 && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
4631 && (((bp_ + 1) < ep_) && isalnum(*(bp_ + 1)))) {
4632 /* hyphens are allowed, but only in
4634 } else if (strchr(" \"'", *bp_)) {
4635 /* but anything not being a punctiation
4638 break; /* anything else is rejected */
4645 /* scan forward (should start with an alnum) */
4646 for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
4662 #undef IS_ASCII_ALNUM
4663 #undef IS_RFC822_CHAR
4665 gchar *make_email_string(const gchar *bp, const gchar *ep)
4667 /* returns a mailto: URI; mailto: is also used to detect the
4668 * uri type later on in the button_pressed signal handler */
4672 tmp = g_strndup(bp, ep - bp);
4673 result = g_strconcat("mailto:", tmp, NULL);
4679 gchar *make_http_string(const gchar *bp, const gchar *ep)
4681 /* returns an http: URI; */
4685 while (bp && *bp && g_ascii_isspace(*bp))
4687 tmp = g_strndup(bp, ep - bp);
4688 result = g_strconcat("http://", tmp, NULL);
4694 static gchar *mailcap_get_command_in_file(const gchar *path, const gchar *type, const gchar *file_to_open)
4696 FILE *fp = g_fopen(path, "rb");
4697 gchar buf[BUFFSIZE];
4698 gchar *result = NULL;
4701 while (fgets(buf, sizeof (buf), fp) != NULL) {
4702 gchar **parts = g_strsplit(buf, ";", 3);
4703 gchar *trimmed = parts[0];
4704 while (trimmed[0] == ' ' || trimmed[0] == '\t')
4706 while (trimmed[strlen(trimmed)-1] == ' ' || trimmed[strlen(trimmed)-1] == '\t')
4707 trimmed[strlen(trimmed)-1] = '\0';
4709 if (!strcmp(trimmed, type)) {
4710 gboolean needsterminal = FALSE;
4711 if (parts[2] && strstr(parts[2], "needsterminal")) {
4712 needsterminal = TRUE;
4714 if (parts[2] && strstr(parts[2], "test=")) {
4715 gchar *orig_testcmd = g_strdup(strstr(parts[2], "test=")+5);
4716 gchar *testcmd = orig_testcmd;
4717 if (strstr(testcmd,";"))
4718 *(strstr(testcmd,";")) = '\0';
4719 while (testcmd[0] == ' ' || testcmd[0] == '\t')
4721 while (testcmd[strlen(testcmd)-1] == '\n')
4722 testcmd[strlen(testcmd)-1] = '\0';
4723 while (testcmd[strlen(testcmd)-1] == '\r')
4724 testcmd[strlen(testcmd)-1] = '\0';
4725 while (testcmd[strlen(testcmd)-1] == ' ' || testcmd[strlen(testcmd)-1] == '\t')
4726 testcmd[strlen(testcmd)-1] = '\0';
4728 if (strstr(testcmd, "%s")) {
4729 gchar *tmp = g_strdup_printf(testcmd, file_to_open);
4730 gint res = system(tmp);
4732 g_free(orig_testcmd);
4739 gint res = system(testcmd);
4740 g_free(orig_testcmd);
4750 while (trimmed[0] == ' ' || trimmed[0] == '\t')
4752 while (trimmed[strlen(trimmed)-1] == '\n')
4753 trimmed[strlen(trimmed)-1] = '\0';
4754 while (trimmed[strlen(trimmed)-1] == '\r')
4755 trimmed[strlen(trimmed)-1] = '\0';
4756 while (trimmed[strlen(trimmed)-1] == ' ' || trimmed[strlen(trimmed)-1] == '\t')
4757 trimmed[strlen(trimmed)-1] = '\0';
4758 result = g_strdup(trimmed);
4761 /* if there are no single quotes around %s, add them.
4762 * '.*%s.*' is ok, as in display 'png:%s'
4764 if (strstr(result, "%s")
4765 && !(strstr(result, "'") < strstr(result,"%s") &&
4766 strstr(strstr(result,"%s"), "'"))) {
4767 gchar *start = g_strdup(result);
4768 gchar *end = g_strdup(strstr(result, "%s")+2);
4770 *strstr(start, "%s") = '\0';
4771 tmp = g_strconcat(start,"'%s'",end, NULL);
4777 if (needsterminal) {
4778 gchar *tmp = g_strdup_printf("xterm -e %s", result);
4789 gchar *mailcap_get_command_for_type(const gchar *type, const gchar *file_to_open)
4791 gchar *result = NULL;
4795 path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap", NULL);
4796 result = mailcap_get_command_in_file(path, type, file_to_open);
4800 result = mailcap_get_command_in_file("/etc/mailcap", type, file_to_open);
4804 void mailcap_update_default(const gchar *type, const gchar *command)
4806 gchar *path = NULL, *outpath = NULL;
4807 path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap", NULL);
4808 outpath = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap.new", NULL);
4809 FILE *fp = g_fopen(path, "rb");
4811 gchar buf[BUFFSIZE];
4812 gboolean err = FALSE;
4815 fp = g_fopen(path, "a");
4817 g_warning("failed to create file %s\n", path);
4822 fp = g_freopen(path, "rb", fp);
4824 g_warning("failed to reopen file %s\n", path);
4831 outfp = g_fopen(outpath, "wb");
4833 g_warning("failed to create file %s\n", outpath);
4839 while (fp && fgets(buf, sizeof (buf), fp) != NULL) {
4840 gchar **parts = g_strsplit(buf, ";", 3);
4841 gchar *trimmed = parts[0];
4842 while (trimmed[0] == ' ')
4844 while (trimmed[strlen(trimmed)-1] == ' ')
4845 trimmed[strlen(trimmed)-1] = '\0';
4847 if (!strcmp(trimmed, type)) {
4852 if(fputs(buf, outfp) == EOF) {
4859 if (fprintf(outfp, "%s; %s\n", type, command) < 0)
4865 if (fclose(outfp) == EOF)
4869 g_rename(outpath, path);
4875 gint copy_dir(const gchar *src, const gchar *dst)
4880 if ((dir = g_dir_open(src, 0, NULL)) == NULL) {
4881 g_warning("failed to open directory: %s\n", src);
4885 if (make_dir(dst) < 0)
4888 while ((name = g_dir_read_name(dir)) != NULL) {
4889 gchar *old_file, *new_file;
4890 old_file = g_strconcat(src, G_DIR_SEPARATOR_S, name, NULL);
4891 new_file = g_strconcat(dst, G_DIR_SEPARATOR_S, name, NULL);
4892 debug_print("copying: %s -> %s\n", old_file, new_file);
4893 if (g_file_test(old_file, G_FILE_TEST_IS_REGULAR)) {
4894 gint r = copy_file(old_file, new_file, TRUE);
4901 /* Windows has no symlinks. Or well, Vista seems to
4902 have something like this but the semantics might be
4903 different. Thus we don't use it under Windows. */
4904 else if (g_file_test(old_file, G_FILE_TEST_IS_SYMLINK)) {
4907 gchar *target = g_file_read_link(old_file, &error);
4909 r = symlink(target, new_file);
4916 #endif /*G_OS_WIN32*/
4917 else if (g_file_test(old_file, G_FILE_TEST_IS_DIR)) {
4918 gint r = copy_dir(old_file, new_file);
4929 /* crude test to see if a file is an email. */
4930 gboolean file_is_email (const gchar *filename)
4936 if (filename == NULL)
4938 if ((fp = g_fopen(filename, "rb")) == NULL)
4940 while (i < 60 && score < 3
4941 && fgets(buffer, sizeof (buffer), fp) > 0) {
4942 if (!strncmp(buffer, "From:", strlen("From:")))
4944 else if (!strncmp(buffer, "Date:", strlen("Date:")))
4946 else if (!strncmp(buffer, "Message-ID:", strlen("Message-ID:")))
4948 else if (!strncmp(buffer, "Subject:", strlen("Subject:")))
4953 return (score >= 3);
4956 gboolean sc_g_list_bigger(GList *list, gint max)
4960 while (cur && i <= max+1) {
4967 gboolean sc_g_slist_bigger(GSList *list, gint max)
4971 while (cur && i <= max+1) {
4978 const gchar *daynames[] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL};
4979 const gchar *monthnames[] = {NULL, NULL, NULL, NULL, NULL, NULL,
4980 NULL, NULL, NULL, NULL, NULL, NULL};
4981 const gchar *s_daynames[] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL};
4982 const gchar *s_monthnames[] = {NULL, NULL, NULL, NULL, NULL, NULL,
4983 NULL, NULL, NULL, NULL, NULL, NULL};
4985 gint daynames_len[] = {0,0,0,0,0,0,0};
4986 gint monthnames_len[] = {0,0,0,0,0,0,
4988 gint s_daynames_len[] = {0,0,0,0,0,0,0};
4989 gint s_monthnames_len[] = {0,0,0,0,0,0,
4991 const gchar *s_am_up = NULL;
4992 const gchar *s_pm_up = NULL;
4993 const gchar *s_am_low = NULL;
4994 const gchar *s_pm_low = NULL;
4996 gint s_am_up_len = 0;
4997 gint s_pm_up_len = 0;
4998 gint s_am_low_len = 0;
4999 gint s_pm_low_len = 0;
5001 static gboolean time_names_init_done = FALSE;
5003 static void init_time_names(void)
5007 daynames[0] = C_("Complete day name for use by strftime", "Sunday");
5008 daynames[1] = C_("Complete day name for use by strftime", "Monday");
5009 daynames[2] = C_("Complete day name for use by strftime", "Tuesday");
5010 daynames[3] = C_("Complete day name for use by strftime", "Wednesday");
5011 daynames[4] = C_("Complete day name for use by strftime", "Thursday");
5012 daynames[5] = C_("Complete day name for use by strftime", "Friday");
5013 daynames[6] = C_("Complete day name for use by strftime", "Saturday");
5015 monthnames[0] = C_("Complete month name for use by strftime", "January");
5016 monthnames[1] = C_("Complete month name for use by strftime", "February");
5017 monthnames[2] = C_("Complete month name for use by strftime", "March");
5018 monthnames[3] = C_("Complete month name for use by strftime", "April");
5019 monthnames[4] = C_("Complete month name for use by strftime", "May");
5020 monthnames[5] = C_("Complete month name for use by strftime", "June");
5021 monthnames[6] = C_("Complete month name for use by strftime", "July");
5022 monthnames[7] = C_("Complete month name for use by strftime", "August");
5023 monthnames[8] = C_("Complete month name for use by strftime", "September");
5024 monthnames[9] = C_("Complete month name for use by strftime", "October");
5025 monthnames[10] = C_("Complete month name for use by strftime", "November");
5026 monthnames[11] = C_("Complete month name for use by strftime", "December");
5028 s_daynames[0] = C_("Abbr. day name for use by strftime", "Sun");
5029 s_daynames[1] = C_("Abbr. day name for use by strftime", "Mon");
5030 s_daynames[2] = C_("Abbr. day name for use by strftime", "Tue");
5031 s_daynames[3] = C_("Abbr. day name for use by strftime", "Wed");
5032 s_daynames[4] = C_("Abbr. day name for use by strftime", "Thu");
5033 s_daynames[5] = C_("Abbr. day name for use by strftime", "Fri");
5034 s_daynames[6] = C_("Abbr. day name for use by strftime", "Sat");
5036 s_monthnames[0] = C_("Abbr. month name for use by strftime", "Jan");
5037 s_monthnames[1] = C_("Abbr. month name for use by strftime", "Feb");
5038 s_monthnames[2] = C_("Abbr. month name for use by strftime", "Mar");
5039 s_monthnames[3] = C_("Abbr. month name for use by strftime", "Apr");
5040 s_monthnames[4] = C_("Abbr. month name for use by strftime", "May");
5041 s_monthnames[5] = C_("Abbr. month name for use by strftime", "Jun");
5042 s_monthnames[6] = C_("Abbr. month name for use by strftime", "Jul");
5043 s_monthnames[7] = C_("Abbr. month name for use by strftime", "Aug");
5044 s_monthnames[8] = C_("Abbr. month name for use by strftime", "Sep");
5045 s_monthnames[9] = C_("Abbr. month name for use by strftime", "Oct");
5046 s_monthnames[10] = C_("Abbr. month name for use by strftime", "Nov");
5047 s_monthnames[11] = C_("Abbr. month name for use by strftime", "Dec");
5049 for (i = 0; i < 7; i++) {
5050 daynames_len[i] = strlen(daynames[i]);
5051 s_daynames_len[i] = strlen(s_daynames[i]);
5053 for (i = 0; i < 12; i++) {
5054 monthnames_len[i] = strlen(monthnames[i]);
5055 s_monthnames_len[i] = strlen(s_monthnames[i]);
5058 s_am_up = C_("For use by strftime (morning)", "AM");
5059 s_pm_up = C_("For use by strftime (afternoon)", "PM");
5060 s_am_low = C_("For use by strftime (morning, lowercase)", "am");
5061 s_pm_low = C_("For use by strftime (afternoon, lowercase)", "pm");
5063 s_am_up_len = strlen(s_am_up);
5064 s_pm_up_len = strlen(s_pm_up);
5065 s_am_low_len = strlen(s_am_low);
5066 s_pm_low_len = strlen(s_pm_low);
5068 time_names_init_done = TRUE;
5071 #define CHECK_SIZE() { \
5072 total_done += len; \
5073 if (total_done >= buflen) { \
5074 buf[buflen-1] = '\0'; \
5079 size_t fast_strftime(gchar *buf, gint buflen, const gchar *format, struct tm *lt)
5081 gchar *curpos = buf;
5082 gint total_done = 0;
5083 gchar subbuf[64], subfmt[64];
5084 static time_t last_tzset = (time_t)0;
5086 if (!time_names_init_done)
5089 if (format == NULL || lt == NULL)
5092 if (last_tzset != time(NULL)) {
5094 last_tzset = time(NULL);
5097 if (*format == '%') {
5098 gint len = 0, tmp = 0;
5102 len = 1; CHECK_SIZE();
5106 len = s_daynames_len[lt->tm_wday]; CHECK_SIZE();
5107 strncpy2(curpos, s_daynames[lt->tm_wday], buflen - total_done);
5110 len = daynames_len[lt->tm_wday]; CHECK_SIZE();
5111 strncpy2(curpos, daynames[lt->tm_wday], buflen - total_done);
5115 len = s_monthnames_len[lt->tm_mon]; CHECK_SIZE();
5116 strncpy2(curpos, s_monthnames[lt->tm_mon], buflen - total_done);
5119 len = monthnames_len[lt->tm_mon]; CHECK_SIZE();
5120 strncpy2(curpos, monthnames[lt->tm_mon], buflen - total_done);
5123 strftime(subbuf, 64, "%c", lt);
5124 len = strlen(subbuf); CHECK_SIZE();
5125 strncpy2(curpos, subbuf, buflen - total_done);
5128 total_done += 2; CHECK_SIZE();
5129 tmp = (lt->tm_year + 1900)/100;
5130 *curpos++ = '0'+(tmp / 10);
5131 *curpos++ = '0'+(tmp % 10);
5134 total_done += 2; CHECK_SIZE();
5135 *curpos++ = '0'+(lt->tm_mday / 10);
5136 *curpos++ = '0'+(lt->tm_mday % 10);
5139 total_done += 8; CHECK_SIZE();
5140 *curpos++ = '0'+((lt->tm_mon+1) / 10);
5141 *curpos++ = '0'+((lt->tm_mon+1) % 10);
5143 *curpos++ = '0'+(lt->tm_mday / 10);
5144 *curpos++ = '0'+(lt->tm_mday % 10);
5146 tmp = lt->tm_year%100;
5147 *curpos++ = '0'+(tmp / 10);
5148 *curpos++ = '0'+(tmp % 10);
5151 len = 2; CHECK_SIZE();
5152 snprintf(curpos, buflen - total_done, "%2d", lt->tm_mday);
5155 len = 10; CHECK_SIZE();
5156 snprintf(curpos, buflen - total_done, "%4d-%02d-%02d",
5157 lt->tm_year + 1900, lt->tm_mon +1, lt->tm_mday);
5160 total_done += 2; CHECK_SIZE();
5161 *curpos++ = '0'+(lt->tm_hour / 10);
5162 *curpos++ = '0'+(lt->tm_hour % 10);
5165 total_done += 2; CHECK_SIZE();
5171 *curpos++ = '0'+(tmp / 10);
5172 *curpos++ = '0'+(tmp % 10);
5175 len = 3; CHECK_SIZE();
5176 snprintf(curpos, buflen - total_done, "%03d", lt->tm_yday+1);
5179 len = 2; CHECK_SIZE();
5180 snprintf(curpos, buflen - total_done, "%2d", lt->tm_hour);
5183 len = 2; CHECK_SIZE();
5189 snprintf(curpos, buflen - total_done, "%2d", tmp);
5192 total_done += 2; CHECK_SIZE();
5193 tmp = lt->tm_mon + 1;
5194 *curpos++ = '0'+(tmp / 10);
5195 *curpos++ = '0'+(tmp % 10);
5198 total_done += 2; CHECK_SIZE();
5199 *curpos++ = '0'+(lt->tm_min / 10);
5200 *curpos++ = '0'+(lt->tm_min % 10);
5203 len = 1; CHECK_SIZE();
5207 if (lt->tm_hour >= 12) {
5208 len = s_pm_up_len; CHECK_SIZE();
5209 snprintf(curpos, buflen-total_done, "%s", s_pm_up);
5211 len = s_am_up_len; CHECK_SIZE();
5212 snprintf(curpos, buflen-total_done, "%s", s_am_up);
5216 if (lt->tm_hour >= 12) {
5217 len = s_pm_low_len; CHECK_SIZE();
5218 snprintf(curpos, buflen-total_done, "%s", s_pm_low);
5220 len = s_am_low_len; CHECK_SIZE();
5221 snprintf(curpos, buflen-total_done, "%s", s_am_low);
5225 strftime(subbuf, 64, "%r", lt);
5226 len = strlen(subbuf); CHECK_SIZE();
5227 strncpy2(curpos, subbuf, buflen - total_done);
5230 total_done += 5; CHECK_SIZE();
5231 *curpos++ = '0'+(lt->tm_hour / 10);
5232 *curpos++ = '0'+(lt->tm_hour % 10);
5234 *curpos++ = '0'+(lt->tm_min / 10);
5235 *curpos++ = '0'+(lt->tm_min % 10);
5238 snprintf(subbuf, 64, "%ld", mktime(lt));
5239 len = strlen(subbuf); CHECK_SIZE();
5240 strncpy2(curpos, subbuf, buflen - total_done);
5243 total_done += 2; CHECK_SIZE();
5244 *curpos++ = '0'+(lt->tm_sec / 10);
5245 *curpos++ = '0'+(lt->tm_sec % 10);
5248 len = 1; CHECK_SIZE();
5252 total_done += 8; CHECK_SIZE();
5253 *curpos++ = '0'+(lt->tm_hour / 10);
5254 *curpos++ = '0'+(lt->tm_hour % 10);
5256 *curpos++ = '0'+(lt->tm_min / 10);
5257 *curpos++ = '0'+(lt->tm_min % 10);
5259 *curpos++ = '0'+(lt->tm_sec / 10);
5260 *curpos++ = '0'+(lt->tm_sec % 10);
5263 len = 1; CHECK_SIZE();
5264 snprintf(curpos, buflen - total_done, "%d", lt->tm_wday == 0 ? 7: lt->tm_wday);
5267 len = 1; CHECK_SIZE();
5268 snprintf(curpos, buflen - total_done, "%d", lt->tm_wday);
5271 strftime(subbuf, 64, "%x", lt);
5272 len = strlen(subbuf); CHECK_SIZE();
5273 strncpy2(curpos, subbuf, buflen - total_done);
5276 strftime(subbuf, 64, "%X", lt);
5277 len = strlen(subbuf); CHECK_SIZE();
5278 strncpy2(curpos, subbuf, buflen - total_done);
5281 total_done += 2; CHECK_SIZE();
5282 tmp = lt->tm_year%100;
5283 *curpos++ = '0'+(tmp / 10);
5284 *curpos++ = '0'+(tmp % 10);
5287 len = 4; CHECK_SIZE();
5288 snprintf(curpos, buflen - total_done, "%4d", lt->tm_year + 1900);
5298 /* let these complicated ones be done with the libc */
5299 snprintf(subfmt, 64, "%%%c", *format);
5300 strftime(subbuf, 64, subfmt, lt);
5301 len = strlen(subbuf); CHECK_SIZE();
5302 strncpy2(curpos, subbuf, buflen - total_done);
5306 /* let these complicated modifiers be done with the libc */
5307 snprintf(subfmt, 64, "%%%c%c", *format, *(format+1));
5308 strftime(subbuf, 64, subfmt, lt);
5309 len = strlen(subbuf); CHECK_SIZE();
5310 strncpy2(curpos, subbuf, buflen - total_done);
5314 g_warning("format error (%c)", *format);
5321 int len = 1; CHECK_SIZE();
5322 *curpos++ = *format++;
5329 gboolean prefs_common_get_use_shred(void);
5333 #define WEXITSTATUS(x) (x)
5336 int claws_unlink(const gchar *filename)
5339 static int found_shred = -1;
5340 static const gchar *args[4];
5342 if (filename == NULL)
5345 if (prefs_common_get_use_shred()) {
5346 if (found_shred == -1) {
5348 args[0] = g_find_program_in_path("shred");
5349 debug_print("found shred: %s\n", args[0]);
5350 found_shred = (args[0] != NULL) ? 1:0;
5354 if (found_shred == 1) {
5355 if (g_stat(filename, &s) == 0 && S_ISREG(s.st_mode)) {
5356 if (s.st_nlink == 1) {
5359 g_spawn_sync(NULL, (gchar **)args, NULL, 0,
5360 NULL, NULL, NULL, NULL, &status, NULL);
5361 debug_print("%s %s exited with status %d\n",
5362 args[0], filename, WEXITSTATUS(status));
5363 if (truncate(filename, 0) < 0)
5364 g_warning("couln't truncate");
5369 return g_unlink(filename);
5372 GMutex *cm_mutex_new(void) {
5373 #if GLIB_CHECK_VERSION(2,32,0)
5374 GMutex *m = g_new0(GMutex, 1);
5378 return g_mutex_new();
5382 void cm_mutex_free(GMutex *mutex) {
5383 #if GLIB_CHECK_VERSION(2,32,0)
5384 g_mutex_clear(mutex);
5387 g_mutex_free(mutex);
5391 static gchar *canonical_list_to_file(GSList *list)
5393 GString *result = g_string_new(NULL);
5394 GSList *pathlist = g_slist_reverse(g_slist_copy(list));
5399 result = g_string_append(result, G_DIR_SEPARATOR_S);
5401 if (pathlist->data) {
5402 const gchar *root = (gchar *)pathlist->data;
5403 if (root[0] != '\0' && g_ascii_isalpha(root[0]) &&
5405 /* drive - don't prepend dir separator */
5407 result = g_string_append(result, G_DIR_SEPARATOR_S);
5412 for (cur = pathlist; cur; cur = cur->next) {
5413 result = g_string_append(result, (gchar *)cur->data);
5415 result = g_string_append(result, G_DIR_SEPARATOR_S);
5417 g_slist_free(pathlist);
5420 g_string_free(result, FALSE);
5425 static GSList *cm_split_path(const gchar *filename, int depth)
5428 GSList *canonical_parts = NULL;
5431 gboolean follow_symlinks = TRUE;
5437 errno = EINVAL; /* can't happen, no symlink handling */
5442 if (!g_path_is_absolute(filename)) {
5447 path_parts = g_strsplit(filename, G_DIR_SEPARATOR_S, -1);
5449 for (i = 0; path_parts[i] != NULL; i++) {
5450 if (!strcmp(path_parts[i], ""))
5452 if (!strcmp(path_parts[i], "."))
5454 else if (!strcmp(path_parts[i], "..")) {
5459 else /* Remove the last inserted element */
5461 g_slist_delete_link(canonical_parts,
5466 canonical_parts = g_slist_prepend(canonical_parts,
5467 g_strdup(path_parts[i]));
5469 tmp_path = canonical_list_to_file(canonical_parts);
5471 if(g_stat(tmp_path, &st) < 0) {
5472 if (errno == ENOENT) {
5474 follow_symlinks = FALSE;
5478 slist_free_strings_full(canonical_parts);
5479 g_strfreev(path_parts);
5485 if (follow_symlinks && g_file_test(tmp_path, G_FILE_TEST_IS_SYMLINK)) {
5486 GError *error = NULL;
5487 gchar *target = g_file_read_link(tmp_path, &error);
5489 if (!g_path_is_absolute(target)) {
5490 /* remove the last inserted element */
5492 g_slist_delete_link(canonical_parts,
5494 /* add the target */
5495 canonical_parts = g_slist_prepend(canonical_parts,
5499 /* and get the new target */
5500 target = canonical_list_to_file(canonical_parts);
5503 /* restart from absolute target */
5504 slist_free_strings_full(canonical_parts);
5505 canonical_parts = NULL;
5507 canonical_parts = cm_split_path(target, depth + 1);
5509 g_error_free(error);
5510 if (canonical_parts == NULL) {
5512 g_strfreev(path_parts);
5521 g_strfreev(path_parts);
5522 return canonical_parts;
5526 * Canonicalize a filename, resolving symlinks along the way.
5527 * Returns a negative errno in case of error.
5529 int cm_canonicalize_filename(const gchar *filename, gchar **canonical_name) {
5530 GSList *canonical_parts;
5531 gboolean is_absolute;
5533 if (filename == NULL)
5535 if (canonical_name == NULL)
5537 *canonical_name = NULL;
5539 is_absolute = g_path_is_absolute(filename);
5541 /* Always work on absolute filenames. */
5542 gchar *cur = g_get_current_dir();
5543 gchar *absolute_filename = g_strconcat(cur, G_DIR_SEPARATOR_S,
5546 canonical_parts = cm_split_path(absolute_filename, 0);
5547 g_free(absolute_filename);
5550 canonical_parts = cm_split_path(filename, 0);
5552 if (canonical_parts == NULL)
5555 *canonical_name = canonical_list_to_file(canonical_parts);
5556 slist_free_strings_full(canonical_parts);
5560 /* Returns a decoded base64 string, guaranteed to be null-terminated. */
5561 guchar *g_base64_decode_zero(const gchar *text, gsize *out_len)
5563 gchar *tmp = g_base64_decode(text, out_len);
5564 gchar *out = g_strndup(tmp, *out_len);
5568 if (strlen(out) != *out_len) {
5569 g_warning ("strlen(out) %d != *out_len %d", strlen(out), *out_len);
5575 #if !GLIB_CHECK_VERSION(2, 30, 0)
5578 * @str: a UTF-8 encoded string
5579 * @start_pos: a character offset within @str
5580 * @end_pos: another character offset within @str
5582 * Copies a substring out of a UTF-8 encoded string.
5583 * The substring will contain @end_pos - @start_pos
5586 * Returns: a newly allocated copy of the requested
5587 * substring. Free with g_free() when no longer needed.
5592 g_utf8_substring (const gchar *str,
5596 gchar *start, *end, *out;
5598 start = g_utf8_offset_to_pointer (str, start_pos);
5599 end = g_utf8_offset_to_pointer (start, end_pos - start_pos);
5601 out = g_malloc (end - start + 1);
5602 memcpy (out, start, end - start);
5603 out[end - start] = 0;