2 * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2013 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/>.
22 #include "claws-features.h"
30 #include <glib/gi18n.h>
40 #include <sys/param.h>
42 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
50 #include <sys/types.h>
52 # include <sys/wait.h>
59 #include <sys/utsname.h>
72 #include "../codeconv.h"
76 static gboolean debug_mode = FALSE;
78 static GSList *tempfiles=NULL;
81 /* Return true if we are running as root. This function should beused
82 instead of getuid () == 0. */
83 gboolean superuser_p (void)
86 return w32_is_administrator ();
94 #if !GLIB_CHECK_VERSION(2, 7, 0) && !defined(G_OS_UNIX)
95 gint g_chdir(const gchar *path)
98 if (G_WIN32_HAVE_WIDECHAR_API()) {
103 wpath = g_utf8_to_utf16(path, -1, NULL, NULL, NULL);
109 retval = _wchdir(wpath);
121 cp_path = g_locale_from_utf8(path, -1, NULL, NULL, NULL);
122 if (cp_path == NULL) {
127 retval = chdir(cp_path);
140 gint g_chmod(const gchar *path, gint mode)
143 if (G_WIN32_HAVE_WIDECHAR_API()) {
148 wpath = g_utf8_to_utf16(path, -1, NULL, NULL, NULL);
154 retval = _wchmod(wpath, mode);
166 cp_path = g_locale_from_utf8(path, -1, NULL, NULL, NULL);
167 if (cp_path == NULL) {
172 retval = chmod(cp_path, mode);
181 return chmod(path, mode);
185 FILE* g_fopen(const gchar *filename, const gchar *mode)
188 char *name = g_win32_locale_filename_from_utf8(filename);
189 FILE* fp = fopen(name, mode);
193 return fopen(filename, mode);
196 int g_open(const gchar *filename, int flags, int mode)
199 char *name = g_win32_locale_filename_from_utf8(filename);
200 int fd = open(name, flags, mode);
204 return open(filename, flags, mode);
207 #endif /* GLIB_CHECK_VERSION && G_OS_UNIX */
211 gint mkstemp_name(gchar *template, gchar **name_used)
213 static gulong count=0; /* W32-_mktemp only supports up to 27
217 *name_used = g_strdup_printf("%s.%ld",_mktemp(template),count++);
218 tmpfd = g_open (*name_used, (O_CREAT | O_RDWR | O_BINARY),
219 (S_IRUSR | S_IWUSR));
221 tempfiles=g_slist_append(tempfiles, g_strdup(*name_used));
223 perror(g_strdup_printf("cant create %s",*name_used));
229 #endif /* G_OS_WIN32 */
232 gint mkstemp(gchar *template)
235 gint res = mkstemp_name(template, &dummyname);
239 #endif /* G_OS_WIN32 */
241 GSList *slist_copy_deep(GSList *list, GCopyFunc func)
243 #if GLIB_CHECK_VERSION(2, 34, 0)
244 return g_slist_copy_deep(list, func, NULL);
246 GSList *res = g_slist_copy(list);
249 walk->data = func(walk->data, NULL);
256 void list_free_strings(GList *list)
258 list = g_list_first(list);
260 while (list != NULL) {
266 void slist_free_strings(GSList *list)
268 while (list != NULL) {
274 void slist_free_strings_full(GSList *list)
276 #if GLIB_CHECK_VERSION(2,28,0)
277 g_slist_free_full(list, (GDestroyNotify)g_free);
279 g_slist_foreach(list, (GFunc)g_free, NULL);
284 static void hash_free_strings_func(gpointer key, gpointer value, gpointer data)
289 void hash_free_strings(GHashTable *table)
291 g_hash_table_foreach(table, hash_free_strings_func, NULL);
294 gint str_case_equal(gconstpointer v, gconstpointer v2)
296 return g_ascii_strcasecmp((const gchar *)v, (const gchar *)v2) == 0;
299 guint str_case_hash(gconstpointer key)
301 const gchar *p = key;
305 h = g_ascii_tolower(h);
306 for (p += 1; *p != '\0'; p++)
307 h = (h << 5) - h + g_ascii_tolower(*p);
313 void ptr_array_free_strings(GPtrArray *array)
318 cm_return_if_fail(array != NULL);
320 for (i = 0; i < array->len; i++) {
321 str = g_ptr_array_index(array, i);
326 gint to_number(const gchar *nstr)
328 register const gchar *p;
330 if (*nstr == '\0') return -1;
332 for (p = nstr; *p != '\0'; p++)
333 if (!g_ascii_isdigit(*p)) return -1;
338 /* convert integer into string,
339 nstr must be not lower than 11 characters length */
340 gchar *itos_buf(gchar *nstr, gint n)
342 g_snprintf(nstr, 11, "%d", n);
346 /* convert integer into string */
349 static gchar nstr[11];
351 return itos_buf(nstr, n);
354 #define divide(num,divisor,i,d) \
356 i = num >> divisor; \
357 d = num & ((1<<divisor)-1); \
358 d = (d*100) >> divisor; \
363 * \brief Convert a given size in bytes in a human-readable string
365 * \param size The size expressed in bytes to convert in string
366 * \return The string that respresents the size in an human-readable way
368 gchar *to_human_readable(goffset size)
370 static gchar str[14];
371 static gchar *b_format = NULL, *kb_format = NULL,
372 *mb_format = NULL, *gb_format = NULL;
373 register int t = 0, r = 0;
374 if (b_format == NULL) {
376 kb_format = _("%d.%02dKB");
377 mb_format = _("%d.%02dMB");
378 gb_format = _("%.2fGB");
381 if (size < (goffset)1024) {
382 g_snprintf(str, sizeof(str), b_format, (gint)size);
384 } else if (size >> 10 < (goffset)1024) {
385 divide(size, 10, t, r);
386 g_snprintf(str, sizeof(str), kb_format, t, r);
388 } else if (size >> 20 < (goffset)1024) {
389 divide(size, 20, t, r);
390 g_snprintf(str, sizeof(str), mb_format, t, r);
393 g_snprintf(str, sizeof(str), gb_format, (gfloat)(size >> 30));
398 /* strcmp with NULL-checking */
399 gint strcmp2(const gchar *s1, const gchar *s2)
401 if (s1 == NULL || s2 == NULL)
404 return strcmp(s1, s2);
406 /* strstr with NULL-checking */
407 gchar *strstr2(const gchar *s1, const gchar *s2)
409 if (s1 == NULL || s2 == NULL)
412 return strstr(s1, s2);
415 gint path_cmp(const gchar *s1, const gchar *s2)
420 gchar *s1buf, *s2buf;
423 if (s1 == NULL || s2 == NULL) return -1;
424 if (*s1 == '\0' || *s2 == '\0') return -1;
427 s1buf = g_strdup (s1);
428 s2buf = g_strdup (s2);
429 subst_char (s1buf, '/', G_DIR_SEPARATOR);
430 subst_char (s2buf, '/', G_DIR_SEPARATOR);
433 #endif /* !G_OS_WIN32 */
438 if (s1[len1 - 1] == G_DIR_SEPARATOR) len1--;
439 if (s2[len2 - 1] == G_DIR_SEPARATOR) len2--;
441 rc = strncmp(s1, s2, MAX(len1, len2));
445 #endif /* !G_OS_WIN32 */
449 /* remove trailing return code */
450 gchar *strretchomp(gchar *str)
454 if (!*str) return str;
456 for (s = str + strlen(str) - 1;
457 s >= str && (*s == '\n' || *s == '\r');
464 /* remove trailing character */
465 gchar *strtailchomp(gchar *str, gchar tail_char)
469 if (!*str) return str;
470 if (tail_char == '\0') return str;
472 for (s = str + strlen(str) - 1; s >= str && *s == tail_char; s--)
478 /* remove CR (carriage return) */
479 gchar *strcrchomp(gchar *str)
483 if (!*str) return str;
485 s = str + strlen(str) - 1;
486 if (*s == '\n' && s > str && *(s - 1) == '\r') {
494 gint file_strip_crs(const gchar *file)
496 FILE *fp = NULL, *outfp = NULL;
498 gchar *out = get_tmp_file();
502 fp = g_fopen(file, "rb");
506 outfp = g_fopen(out, "wb");
512 while (fgets(buf, sizeof (buf), fp) != NULL) {
514 if (fputs(buf, outfp) == EOF) {
522 if (fclose(outfp) == EOF) {
526 if (move_file(out, file, TRUE) < 0)
538 /* Similar to `strstr' but this function ignores the case of both strings. */
539 gchar *strcasestr(const gchar *haystack, const gchar *needle)
541 size_t haystack_len = strlen(haystack);
543 return strncasestr(haystack, haystack_len, needle);
546 gchar *strncasestr(const gchar *haystack, gint haystack_len, const gchar *needle)
548 register size_t needle_len;
550 needle_len = strlen(needle);
552 if (haystack_len < needle_len || needle_len == 0)
555 while (haystack_len >= needle_len) {
556 if (!g_ascii_strncasecmp(haystack, needle, needle_len))
557 return (gchar *)haystack;
567 gpointer my_memmem(gconstpointer haystack, size_t haystacklen,
568 gconstpointer needle, size_t needlelen)
570 const gchar *haystack_ = (const gchar *)haystack;
571 const gchar *needle_ = (const gchar *)needle;
572 const gchar *haystack_cur = (const gchar *)haystack;
573 size_t haystack_left = haystacklen;
576 return memchr(haystack_, *needle_, haystacklen);
578 while ((haystack_cur = memchr(haystack_cur, *needle_, haystack_left))
580 if (haystacklen - (haystack_cur - haystack_) < needlelen)
582 if (memcmp(haystack_cur + 1, needle_ + 1, needlelen - 1) == 0)
583 return (gpointer)haystack_cur;
586 haystack_left = haystacklen - (haystack_cur - haystack_);
593 /* Copy no more than N characters of SRC to DEST, with NULL terminating. */
594 gchar *strncpy2(gchar *dest, const gchar *src, size_t n)
596 register const gchar *s = src;
597 register gchar *d = dest;
607 /* Examine if next block is non-ASCII string */
608 gboolean is_next_nonascii(const gchar *s)
612 /* skip head space */
613 for (p = s; *p != '\0' && g_ascii_isspace(*p); p++)
615 for (; *p != '\0' && !g_ascii_isspace(*p); p++) {
616 if (*(guchar *)p > 127 || *(guchar *)p < 32)
623 gint get_next_word_len(const gchar *s)
627 for (; *s != '\0' && !g_ascii_isspace(*s); s++, len++)
633 static void trim_subject_for_compare(gchar *str)
637 eliminate_parenthesis(str, '[', ']');
638 eliminate_parenthesis(str, '(', ')');
641 srcp = str + subject_get_prefix_length(str);
643 memmove(str, srcp, strlen(srcp) + 1);
646 static void trim_subject_for_sort(gchar *str)
652 srcp = str + subject_get_prefix_length(str);
654 memmove(str, srcp, strlen(srcp) + 1);
657 /* compare subjects */
658 gint subject_compare(const gchar *s1, const gchar *s2)
662 if (!s1 || !s2) return -1;
663 if (!*s1 || !*s2) return -1;
665 Xstrdup_a(str1, s1, return -1);
666 Xstrdup_a(str2, s2, return -1);
668 trim_subject_for_compare(str1);
669 trim_subject_for_compare(str2);
671 if (!*str1 || !*str2) return -1;
673 return strcmp(str1, str2);
676 gint subject_compare_for_sort(const gchar *s1, const gchar *s2)
680 if (!s1 || !s2) return -1;
682 Xstrdup_a(str1, s1, return -1);
683 Xstrdup_a(str2, s2, return -1);
685 trim_subject_for_sort(str1);
686 trim_subject_for_sort(str2);
688 return g_utf8_collate(str1, str2);
691 void trim_subject(gchar *str)
693 register gchar *srcp;
699 srcp = str + subject_get_prefix_length(str);
704 } else if (*srcp == '(') {
716 else if (*srcp == cl)
723 while (g_ascii_isspace(*srcp)) srcp++;
724 memmove(str, srcp, strlen(srcp) + 1);
727 void eliminate_parenthesis(gchar *str, gchar op, gchar cl)
729 register gchar *srcp, *destp;
734 while ((destp = strchr(destp, op))) {
740 else if (*srcp == cl)
746 while (g_ascii_isspace(*srcp)) srcp++;
747 memmove(destp, srcp, strlen(srcp) + 1);
751 void extract_parenthesis(gchar *str, gchar op, gchar cl)
753 register gchar *srcp, *destp;
758 while ((srcp = strchr(destp, op))) {
761 memmove(destp, srcp + 1, strlen(srcp));
766 else if (*destp == cl)
778 static void extract_parenthesis_with_skip_quote(gchar *str, gchar quote_chr,
781 register gchar *srcp, *destp;
783 gboolean in_quote = FALSE;
787 while ((srcp = strchr_with_skip_quote(destp, quote_chr, op))) {
790 memmove(destp, srcp + 1, strlen(srcp));
793 if (*destp == op && !in_quote)
795 else if (*destp == cl && !in_quote)
797 else if (*destp == quote_chr)
809 void extract_quote(gchar *str, gchar quote_chr)
813 if ((str = strchr(str, quote_chr))) {
815 while ((p = strchr(p + 1, quote_chr)) && (p[-1] == '\\')) {
816 memmove(p - 1, p, strlen(p) + 1);
821 memmove(str, str + 1, p - str);
826 /* Returns a newly allocated string with all quote_chr not at the beginning
827 or the end of str escaped with '\' or the given str if not required. */
828 gchar *escape_internal_quotes(gchar *str, gchar quote_chr)
830 register gchar *p, *q;
834 if (str == NULL || *str == '\0')
837 /* search for unescaped quote_chr */
842 if (*p == quote_chr && *(p - 1) != '\\' && *(p + 1) != '\0')
846 if (!k) /* nothing to escape */
849 /* unescaped quote_chr found */
850 qstr = g_malloc(l + k + 1);
853 if (*p == quote_chr) {
858 if (*p == quote_chr && *(p - 1) != '\\' && *(p + 1) != '\0')
867 void eliminate_address_comment(gchar *str)
869 register gchar *srcp, *destp;
874 while ((destp = strchr(destp, '"'))) {
875 if ((srcp = strchr(destp + 1, '"'))) {
880 while (g_ascii_isspace(*srcp)) srcp++;
881 memmove(destp, srcp, strlen(srcp) + 1);
891 while ((destp = strchr_with_skip_quote(destp, '"', '('))) {
897 else if (*srcp == ')')
903 while (g_ascii_isspace(*srcp)) srcp++;
904 memmove(destp, srcp, strlen(srcp) + 1);
908 gchar *strchr_with_skip_quote(const gchar *str, gint quote_chr, gint c)
910 gboolean in_quote = FALSE;
913 if (*str == c && !in_quote)
915 if (*str == quote_chr)
923 void extract_address(gchar *str)
925 eliminate_address_comment(str);
926 if (strchr_with_skip_quote(str, '"', '<'))
927 extract_parenthesis_with_skip_quote(str, '"', '<', '>');
931 void extract_list_id_str(gchar *str)
933 if (strchr_with_skip_quote(str, '"', '<'))
934 extract_parenthesis_with_skip_quote(str, '"', '<', '>');
938 static GSList *address_list_append_real(GSList *addr_list, const gchar *str, gboolean removecomments)
943 if (!str) return addr_list;
945 Xstrdup_a(work, str, return addr_list);
948 eliminate_address_comment(work);
951 while (workp && *workp) {
954 if ((p = strchr_with_skip_quote(workp, '"', ','))) {
960 if (removecomments && strchr_with_skip_quote(workp, '"', '<'))
961 extract_parenthesis_with_skip_quote
962 (workp, '"', '<', '>');
966 addr_list = g_slist_append(addr_list, g_strdup(workp));
974 GSList *address_list_append(GSList *addr_list, const gchar *str)
976 return address_list_append_real(addr_list, str, TRUE);
979 GSList *address_list_append_with_comments(GSList *addr_list, const gchar *str)
981 return address_list_append_real(addr_list, str, FALSE);
984 GSList *references_list_prepend(GSList *msgid_list, const gchar *str)
988 if (!str) return msgid_list;
991 while (strp && *strp) {
992 const gchar *start, *end;
995 if ((start = strchr(strp, '<')) != NULL) {
996 end = strchr(start + 1, '>');
1001 msgid = g_strndup(start + 1, end - start - 1);
1004 msgid_list = g_slist_prepend(msgid_list, msgid);
1014 GSList *references_list_append(GSList *msgid_list, const gchar *str)
1018 list = references_list_prepend(NULL, str);
1019 list = g_slist_reverse(list);
1020 msgid_list = g_slist_concat(msgid_list, list);
1025 GSList *newsgroup_list_append(GSList *group_list, const gchar *str)
1030 if (!str) return group_list;
1032 Xstrdup_a(work, str, return group_list);
1036 while (workp && *workp) {
1039 if ((p = strchr_with_skip_quote(workp, '"', ','))) {
1047 group_list = g_slist_append(group_list,
1056 GList *add_history(GList *list, const gchar *str)
1061 cm_return_val_if_fail(str != NULL, list);
1063 old = g_list_find_custom(list, (gpointer)str, (GCompareFunc)strcmp2);
1066 list = g_list_remove(list, old->data);
1068 } else if (g_list_length(list) >= MAX_HISTORY_SIZE) {
1071 last = g_list_last(list);
1073 oldstr = last->data;
1074 list = g_list_remove(list, last->data);
1079 list = g_list_prepend(list, g_strdup(str));
1084 void remove_return(gchar *str)
1086 register gchar *p = str;
1089 if (*p == '\n' || *p == '\r')
1090 memmove(p, p + 1, strlen(p));
1096 void remove_space(gchar *str)
1098 register gchar *p = str;
1103 while (g_ascii_isspace(*(p + spc)))
1106 memmove(p, p + spc, strlen(p + spc) + 1);
1112 void unfold_line(gchar *str)
1114 register gchar *p = str;
1118 if (*p == '\n' || *p == '\r') {
1121 while (g_ascii_isspace(*(p + spc)))
1124 memmove(p, p + spc, strlen(p + spc) + 1);
1130 void subst_char(gchar *str, gchar orig, gchar subst)
1132 register gchar *p = str;
1141 void subst_chars(gchar *str, gchar *orig, gchar subst)
1143 register gchar *p = str;
1146 if (strchr(orig, *p) != NULL)
1152 void subst_for_filename(gchar *str)
1157 subst_chars(str, "\t\r\n\\/*:", '_');
1159 subst_chars(str, "\t\r\n\\/*", '_');
1163 void subst_for_shellsafe_filename(gchar *str)
1167 subst_for_filename(str);
1168 subst_chars(str, " \"'|&;()<>'!{}[]",'_');
1171 gboolean is_ascii_str(const gchar *str)
1173 const guchar *p = (const guchar *)str;
1175 while (*p != '\0') {
1176 if (*p != '\t' && *p != ' ' &&
1177 *p != '\r' && *p != '\n' &&
1178 (*p < 32 || *p >= 127))
1186 static const gchar * line_has_quote_char_last(const gchar * str, const gchar *quote_chars)
1188 gchar * position = NULL;
1189 gchar * tmp_pos = NULL;
1192 if (quote_chars == NULL)
1195 for (i = 0; i < strlen(quote_chars); i++) {
1196 tmp_pos = strrchr (str, quote_chars[i]);
1198 || (tmp_pos != NULL && position <= tmp_pos) )
1204 gint get_quote_level(const gchar *str, const gchar *quote_chars)
1206 const gchar *first_pos;
1207 const gchar *last_pos;
1208 const gchar *p = str;
1209 gint quote_level = -1;
1211 /* speed up line processing by only searching to the last '>' */
1212 if ((first_pos = line_has_quote_char(str, quote_chars)) != NULL) {
1213 /* skip a line if it contains a '<' before the initial '>' */
1214 if (memchr(str, '<', first_pos - str) != NULL)
1216 last_pos = line_has_quote_char_last(first_pos, quote_chars);
1220 while (p <= last_pos) {
1221 while (p < last_pos) {
1222 if (g_ascii_isspace(*p))
1228 if (strchr(quote_chars, *p))
1230 else if (*p != '-' && !g_ascii_isspace(*p) && p <= last_pos) {
1231 /* any characters are allowed except '-','<' and space */
1232 while (*p != '-' && *p != '<'
1233 && !strchr(quote_chars, *p)
1234 && !g_ascii_isspace(*p)
1237 if (strchr(quote_chars, *p))
1249 gint check_line_length(const gchar *str, gint max_chars, gint *line)
1251 const gchar *p = str, *q;
1252 gint cur_line = 0, len;
1254 while ((q = strchr(p, '\n')) != NULL) {
1256 if (len > max_chars) {
1266 if (len > max_chars) {
1275 const gchar * line_has_quote_char(const gchar * str, const gchar *quote_chars)
1277 gchar * position = NULL;
1278 gchar * tmp_pos = NULL;
1281 if (quote_chars == NULL)
1284 for (i = 0; i < strlen(quote_chars); i++) {
1285 tmp_pos = strchr (str, quote_chars[i]);
1287 || (tmp_pos != NULL && position >= tmp_pos) )
1293 static gchar *strstr_with_skip_quote(const gchar *haystack, const gchar *needle)
1295 register guint haystack_len, needle_len;
1296 gboolean in_squote = FALSE, in_dquote = FALSE;
1298 haystack_len = strlen(haystack);
1299 needle_len = strlen(needle);
1301 if (haystack_len < needle_len || needle_len == 0)
1304 while (haystack_len >= needle_len) {
1305 if (!in_squote && !in_dquote &&
1306 !strncmp(haystack, needle, needle_len))
1307 return (gchar *)haystack;
1309 /* 'foo"bar"' -> foo"bar"
1310 "foo'bar'" -> foo'bar' */
1311 if (*haystack == '\'') {
1314 else if (!in_dquote)
1316 } else if (*haystack == '\"') {
1319 else if (!in_squote)
1321 } else if (*haystack == '\\') {
1333 gchar **strsplit_with_quote(const gchar *str, const gchar *delim,
1336 GSList *string_list = NULL, *slist;
1337 gchar **str_array, *s, *new_str;
1338 guint i, n = 1, len;
1340 cm_return_val_if_fail(str != NULL, NULL);
1341 cm_return_val_if_fail(delim != NULL, NULL);
1344 max_tokens = G_MAXINT;
1346 s = strstr_with_skip_quote(str, delim);
1348 guint delimiter_len = strlen(delim);
1352 new_str = g_strndup(str, len);
1354 if (new_str[0] == '\'' || new_str[0] == '\"') {
1355 if (new_str[len - 1] == new_str[0]) {
1356 new_str[len - 1] = '\0';
1357 memmove(new_str, new_str + 1, len - 1);
1360 string_list = g_slist_prepend(string_list, new_str);
1362 str = s + delimiter_len;
1363 s = strstr_with_skip_quote(str, delim);
1364 } while (--max_tokens && s);
1368 new_str = g_strdup(str);
1369 if (new_str[0] == '\'' || new_str[0] == '\"') {
1371 if (new_str[len - 1] == new_str[0]) {
1372 new_str[len - 1] = '\0';
1373 memmove(new_str, new_str + 1, len - 1);
1376 string_list = g_slist_prepend(string_list, new_str);
1380 str_array = g_new(gchar*, n);
1384 str_array[i--] = NULL;
1385 for (slist = string_list; slist; slist = slist->next)
1386 str_array[i--] = slist->data;
1388 g_slist_free(string_list);
1393 gchar *get_abbrev_newsgroup_name(const gchar *group, gint len)
1395 gchar *abbrev_group;
1397 const gchar *p = group;
1400 cm_return_val_if_fail(group != NULL, NULL);
1402 last = group + strlen(group);
1403 abbrev_group = ap = g_malloc(strlen(group) + 1);
1408 if ((ap - abbrev_group) + (last - p) > len && strchr(p, '.')) {
1410 while (*p != '.') p++;
1413 return abbrev_group;
1418 return abbrev_group;
1421 gchar *trim_string(const gchar *str, gint len)
1423 const gchar *p = str;
1428 if (!str) return NULL;
1429 if (strlen(str) <= len)
1430 return g_strdup(str);
1431 if (g_utf8_validate(str, -1, NULL) == FALSE)
1432 return g_strdup(str);
1434 while (*p != '\0') {
1435 mb_len = g_utf8_skip[*(guchar *)p];
1438 else if (new_len + mb_len > len)
1445 Xstrndup_a(new_str, str, new_len, return g_strdup(str));
1446 return g_strconcat(new_str, "...", NULL);
1449 GList *uri_list_extract_filenames(const gchar *uri_list)
1451 GList *result = NULL;
1453 gchar *escaped_utf8uri;
1459 while (g_ascii_isspace(*p)) p++;
1460 if (!strncmp(p, "file:", 5)) {
1463 while (*q && *q != '\n' && *q != '\r') q++;
1466 gchar *file, *locale_file = NULL;
1468 while (q > p && g_ascii_isspace(*q))
1470 Xalloca(escaped_utf8uri, q - p + 2,
1472 Xalloca(file, q - p + 2,
1475 strncpy(escaped_utf8uri, p, q - p + 1);
1476 escaped_utf8uri[q - p + 1] = '\0';
1477 decode_uri(file, escaped_utf8uri);
1479 * g_filename_from_uri() rejects escaped/locale encoded uri
1480 * string which come from Nautilus.
1483 if (g_utf8_validate(file, -1, NULL))
1485 = conv_codeset_strdup(
1488 conv_get_locale_charset_str());
1490 locale_file = g_strdup(file + 5);
1492 locale_file = g_filename_from_uri(file, NULL, NULL);
1494 result = g_list_append(result, locale_file);
1498 p = strchr(p, '\n');
1505 /* Converts two-digit hexadecimal to decimal. Used for unescaping escaped
1508 static gint axtoi(const gchar *hexstr)
1510 gint hi, lo, result;
1513 if ('0' <= hi && hi <= '9') {
1516 if ('a' <= hi && hi <= 'f') {
1519 if ('A' <= hi && hi <= 'F') {
1524 if ('0' <= lo && lo <= '9') {
1527 if ('a' <= lo && lo <= 'f') {
1530 if ('A' <= lo && lo <= 'F') {
1533 result = lo + (16 * hi);
1537 gboolean is_uri_string(const gchar *str)
1539 while (str && *str && g_ascii_isspace(*str))
1541 return (g_ascii_strncasecmp(str, "http://", 7) == 0 ||
1542 g_ascii_strncasecmp(str, "https://", 8) == 0 ||
1543 g_ascii_strncasecmp(str, "ftp://", 6) == 0 ||
1544 g_ascii_strncasecmp(str, "www.", 4) == 0);
1547 gchar *get_uri_path(const gchar *uri)
1549 while (uri && *uri && g_ascii_isspace(*uri))
1551 if (g_ascii_strncasecmp(uri, "http://", 7) == 0)
1552 return (gchar *)(uri + 7);
1553 else if (g_ascii_strncasecmp(uri, "https://", 8) == 0)
1554 return (gchar *)(uri + 8);
1555 else if (g_ascii_strncasecmp(uri, "ftp://", 6) == 0)
1556 return (gchar *)(uri + 6);
1558 return (gchar *)uri;
1561 gint get_uri_len(const gchar *str)
1565 if (is_uri_string(str)) {
1566 for (p = str; *p != '\0'; p++) {
1567 if (!g_ascii_isgraph(*p) || strchr("()<>\"", *p))
1576 /* Decodes URL-Encoded strings (i.e. strings in which spaces are replaced by
1577 * plusses, and escape characters are used)
1579 void decode_uri_with_plus(gchar *decoded_uri, const gchar *encoded_uri, gboolean with_plus)
1581 gchar *dec = decoded_uri;
1582 const gchar *enc = encoded_uri;
1587 if (isxdigit((guchar)enc[0]) &&
1588 isxdigit((guchar)enc[1])) {
1594 if (with_plus && *enc == '+')
1606 void decode_uri(gchar *decoded_uri, const gchar *encoded_uri)
1608 decode_uri_with_plus(decoded_uri, encoded_uri, TRUE);
1611 static gchar *decode_uri_gdup(const gchar *encoded_uri)
1613 gchar *buffer = g_malloc(strlen(encoded_uri)+1);
1614 decode_uri_with_plus(buffer, encoded_uri, FALSE);
1618 gint scan_mailto_url(const gchar *mailto, gchar **from, gchar **to, gchar **cc, gchar **bcc,
1619 gchar **subject, gchar **body, gchar ***attach, gchar **inreplyto)
1623 const gchar *forbidden_uris[] = { ".gnupg/",
1629 gint num_attach = 0;
1630 gchar **my_att = NULL;
1632 Xstrdup_a(tmp_mailto, mailto, return -1);
1634 if (!strncmp(tmp_mailto, "mailto:", 7))
1637 p = strchr(tmp_mailto, '?');
1644 *to = decode_uri_gdup(tmp_mailto);
1646 my_att = g_malloc(sizeof(char *));
1650 gchar *field, *value;
1667 if (*value == '\0') continue;
1669 if (from && !g_ascii_strcasecmp(field, "from")) {
1671 *from = decode_uri_gdup(value);
1673 gchar *tmp = decode_uri_gdup(value);
1674 gchar *new_from = g_strdup_printf("%s, %s", *from, tmp);
1678 } else if (cc && !g_ascii_strcasecmp(field, "cc")) {
1680 *cc = decode_uri_gdup(value);
1682 gchar *tmp = decode_uri_gdup(value);
1683 gchar *new_cc = g_strdup_printf("%s, %s", *cc, tmp);
1687 } else if (bcc && !g_ascii_strcasecmp(field, "bcc")) {
1689 *bcc = decode_uri_gdup(value);
1691 gchar *tmp = decode_uri_gdup(value);
1692 gchar *new_bcc = g_strdup_printf("%s, %s", *bcc, tmp);
1696 } else if (subject && !*subject &&
1697 !g_ascii_strcasecmp(field, "subject")) {
1698 *subject = decode_uri_gdup(value);
1699 } else if (body && !*body && !g_ascii_strcasecmp(field, "body")) {
1700 *body = decode_uri_gdup(value);
1701 } else if (body && !*body && !g_ascii_strcasecmp(field, "insert")) {
1702 gchar *tmp = decode_uri_gdup(value);
1703 if (!g_file_get_contents(tmp, body, NULL, NULL)) {
1704 g_warning("Error: couldn't set insert file '%s' in body\n", value);
1708 } else if (attach && !g_ascii_strcasecmp(field, "attach")) {
1710 gchar *tmp = decode_uri_gdup(value);
1711 for (; forbidden_uris[i]; i++) {
1712 if (strstr(tmp, forbidden_uris[i])) {
1713 g_print("Refusing to attach '%s', potential private data leak\n",
1721 /* attach is correct */
1723 my_att = g_realloc(my_att, (sizeof(char *))*(num_attach+1));
1724 my_att[num_attach-1] = tmp;
1725 my_att[num_attach] = NULL;
1727 } else if (inreplyto && !*inreplyto &&
1728 !g_ascii_strcasecmp(field, "in-reply-to")) {
1729 *inreplyto = decode_uri_gdup(value);
1740 #include <windows.h>
1741 #ifndef CSIDL_APPDATA
1742 #define CSIDL_APPDATA 0x001a
1744 #ifndef CSIDL_LOCAL_APPDATA
1745 #define CSIDL_LOCAL_APPDATA 0x001c
1747 #ifndef CSIDL_FLAG_CREATE
1748 #define CSIDL_FLAG_CREATE 0x8000
1750 #define DIM(v) (sizeof(v)/sizeof((v)[0]))
1754 w32_strerror (int w32_errno)
1756 static char strerr[256];
1757 int ec = (int)GetLastError ();
1761 FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM, NULL, w32_errno,
1762 MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
1763 strerr, DIM (strerr)-1, NULL);
1767 static __inline__ void *
1768 dlopen (const char * name, int flag)
1770 void * hd = LoadLibrary (name);
1774 static __inline__ void *
1775 dlsym (void * hd, const char * sym)
1779 void * fnc = GetProcAddress (hd, sym);
1788 static __inline__ const char *
1791 return w32_strerror (0);
1795 static __inline__ int
1807 w32_shgetfolderpath (HWND a, int b, HANDLE c, DWORD d, LPSTR e)
1809 static int initialized;
1810 static HRESULT (WINAPI * func)(HWND,int,HANDLE,DWORD,LPSTR);
1814 static char *dllnames[] = { "shell32.dll", "shfolder.dll", NULL };
1820 for (i=0, handle = NULL; !handle && dllnames[i]; i++)
1822 handle = dlopen (dllnames[i], RTLD_LAZY);
1825 func = dlsym (handle, "SHGetFolderPathW");
1836 return func (a,b,c,d,e);
1841 /* Returns a static string with the directroy from which the module
1842 has been loaded. Returns an empty string on error. */
1843 static char *w32_get_module_dir(void)
1845 static char *moddir;
1848 char name[MAX_PATH+10];
1851 if ( !GetModuleFileNameA (0, name, sizeof (name)-10) )
1854 p = strrchr (name, '\\');
1860 moddir = g_strdup (name);
1864 #endif /* G_OS_WIN32 */
1866 /* Return a static string with the locale dir. */
1867 const gchar *get_locale_dir(void)
1869 static gchar *loc_dir;
1873 loc_dir = g_strconcat(w32_get_module_dir(), G_DIR_SEPARATOR_S,
1874 "\\share\\locale", NULL);
1877 loc_dir = LOCALEDIR;
1883 const gchar *get_home_dir(void)
1886 static char home_dir_utf16[MAX_PATH] = "";
1887 static gchar *home_dir_utf8 = NULL;
1888 if (home_dir_utf16[0] == '\0') {
1889 if (w32_shgetfolderpath
1890 (NULL, CSIDL_APPDATA|CSIDL_FLAG_CREATE,
1891 NULL, 0, home_dir_utf16) < 0)
1892 strcpy (home_dir_utf16, "C:\\Sylpheed");
1893 home_dir_utf8 = g_utf16_to_utf8 ((const gunichar *)home_dir_utf16, -1, NULL, NULL, NULL);
1895 return home_dir_utf8;
1897 static const gchar *homeenv = NULL;
1902 if (!homeenv && g_getenv("HOME") != NULL)
1903 homeenv = g_strdup(g_getenv("HOME"));
1905 homeenv = g_get_home_dir();
1911 static gchar *claws_rc_dir = NULL;
1912 static gboolean rc_dir_alt = FALSE;
1913 const gchar *get_rc_dir(void)
1916 if (!claws_rc_dir) {
1917 claws_rc_dir = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
1919 debug_print("using default rc_dir %s\n", claws_rc_dir);
1921 return claws_rc_dir;
1924 void set_rc_dir(const gchar *dir)
1926 gchar *canonical_dir;
1927 if (claws_rc_dir != NULL) {
1928 g_print("Error: rc_dir already set\n");
1930 int err = cm_canonicalize_filename(dir, &canonical_dir);
1934 g_print("Error looking for %s: %d(%s)\n",
1935 dir, -err, strerror(-err));
1940 claws_rc_dir = canonical_dir;
1942 len = strlen(claws_rc_dir);
1943 if (claws_rc_dir[len - 1] == G_DIR_SEPARATOR)
1944 claws_rc_dir[len - 1] = '\0';
1946 debug_print("set rc_dir to %s\n", claws_rc_dir);
1947 if (!is_dir_exist(claws_rc_dir)) {
1948 if (make_dir_hier(claws_rc_dir) != 0) {
1949 g_print("Error: can't create %s\n",
1957 gboolean rc_dir_is_alt(void) {
1961 const gchar *get_mail_base_dir(void)
1963 return get_home_dir();
1966 const gchar *get_news_cache_dir(void)
1968 static gchar *news_cache_dir = NULL;
1969 if (!news_cache_dir)
1970 news_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1971 NEWS_CACHE_DIR, NULL);
1973 return news_cache_dir;
1976 const gchar *get_imap_cache_dir(void)
1978 static gchar *imap_cache_dir = NULL;
1980 if (!imap_cache_dir)
1981 imap_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1982 IMAP_CACHE_DIR, NULL);
1984 return imap_cache_dir;
1987 const gchar *get_mime_tmp_dir(void)
1989 static gchar *mime_tmp_dir = NULL;
1992 mime_tmp_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1993 MIME_TMP_DIR, NULL);
1995 return mime_tmp_dir;
1998 const gchar *get_template_dir(void)
2000 static gchar *template_dir = NULL;
2003 template_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
2004 TEMPLATE_DIR, NULL);
2006 return template_dir;
2010 const gchar *get_cert_file(void)
2012 const gchar *cert_file = NULL;
2014 cert_file = g_strconcat(w32_get_module_dir(),
2015 "\\share\\claws-mail\\",
2016 "ca-certificates.crt",
2022 /* Return the filepath of the claws-mail.desktop file */
2023 const gchar *get_desktop_file(void)
2025 #ifdef DESKTOPFILEPATH
2026 return DESKTOPFILEPATH;
2032 /* Return the default directory for Plugins. */
2033 const gchar *get_plugin_dir(void)
2036 static gchar *plugin_dir = NULL;
2039 plugin_dir = g_strconcat(w32_get_module_dir(),
2040 "\\lib\\claws-mail\\plugins\\",
2044 if (is_dir_exist(PLUGINDIR))
2047 static gchar *plugin_dir = NULL;
2049 plugin_dir = g_strconcat(get_rc_dir(),
2050 G_DIR_SEPARATOR_S, "plugins",
2051 G_DIR_SEPARATOR_S, NULL);
2059 /* Return the default directory for Themes. */
2060 const gchar *get_themes_dir(void)
2062 static gchar *themes_dir = NULL;
2065 themes_dir = g_strconcat(w32_get_module_dir(),
2066 "\\share\\claws-mail\\themes",
2072 const gchar *get_tmp_dir(void)
2074 static gchar *tmp_dir = NULL;
2077 tmp_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
2083 gchar *get_tmp_file(void)
2086 static guint32 id = 0;
2088 tmp_file = g_strdup_printf("%s%ctmpfile.%08x",
2089 get_tmp_dir(), G_DIR_SEPARATOR, id++);
2094 const gchar *get_domain_name(void)
2097 static gchar *domain_name = NULL;
2103 if (gethostname(hostname, sizeof(hostname)) != 0) {
2104 perror("gethostname");
2105 domain_name = "localhost";
2107 hostname[sizeof(hostname) - 1] = '\0';
2108 if ((hp = my_gethostbyname(hostname)) == NULL) {
2109 perror("gethostbyname");
2110 domain_name = g_strdup(hostname);
2112 domain_name = g_strdup(hp->h_name);
2115 debug_print("domain name = %s\n", domain_name);
2124 off_t get_file_size(const gchar *file)
2128 if (g_stat(file, &s) < 0) {
2129 FILE_OP_ERROR(file, "stat");
2136 time_t get_file_mtime(const gchar *file)
2140 if (g_stat(file, &s) < 0) {
2141 FILE_OP_ERROR(file, "stat");
2148 off_t get_file_size_as_crlf(const gchar *file)
2152 gchar buf[BUFFSIZE];
2154 if ((fp = g_fopen(file, "rb")) == NULL) {
2155 FILE_OP_ERROR(file, "g_fopen");
2159 while (fgets(buf, sizeof(buf), fp) != NULL) {
2161 size += strlen(buf) + 2;
2165 FILE_OP_ERROR(file, "fgets");
2174 gboolean file_exist(const gchar *file, gboolean allow_fifo)
2181 if (g_stat(file, &s) < 0) {
2182 if (ENOENT != errno) FILE_OP_ERROR(file, "stat");
2186 if (S_ISREG(s.st_mode) || (allow_fifo && S_ISFIFO(s.st_mode)))
2193 /* Test on whether FILE is a relative file name. This is
2194 * straightforward for Unix but more complex for Windows. */
2195 gboolean is_relative_filename(const gchar *file)
2200 if ( *file == '\\' && file[1] == '\\' && strchr (file+2, '\\') )
2201 return FALSE; /* Prefixed with a hostname - this can't
2202 * be a relative name. */
2204 if ( ((*file >= 'a' && *file <= 'z')
2205 || (*file >= 'A' && *file <= 'Z'))
2207 file += 2; /* Skip drive letter. */
2209 return !(*file == '\\' || *file == '/');
2211 return !(*file == G_DIR_SEPARATOR);
2216 gboolean is_dir_exist(const gchar *dir)
2221 return g_file_test(dir, G_FILE_TEST_IS_DIR);
2224 gboolean is_file_entry_exist(const gchar *file)
2229 return g_file_test(file, G_FILE_TEST_EXISTS);
2232 gboolean dirent_is_regular_file(struct dirent *d)
2234 #if !defined(G_OS_WIN32) && defined(HAVE_DIRENT_D_TYPE)
2235 if (d->d_type == DT_REG)
2237 else if (d->d_type != DT_UNKNOWN)
2241 return g_file_test(d->d_name, G_FILE_TEST_IS_REGULAR);
2244 gint change_dir(const gchar *dir)
2246 gchar *prevdir = NULL;
2249 prevdir = g_get_current_dir();
2251 if (g_chdir(dir) < 0) {
2252 FILE_OP_ERROR(dir, "chdir");
2253 if (debug_mode) g_free(prevdir);
2255 } else if (debug_mode) {
2258 cwd = g_get_current_dir();
2259 if (strcmp(prevdir, cwd) != 0)
2260 g_print("current dir: %s\n", cwd);
2268 gint make_dir(const gchar *dir)
2270 if (g_mkdir(dir, S_IRWXU) < 0) {
2271 FILE_OP_ERROR(dir, "mkdir");
2274 if (g_chmod(dir, S_IRWXU) < 0)
2275 FILE_OP_ERROR(dir, "chmod");
2280 gint make_dir_hier(const gchar *dir)
2285 for (p = dir; (p = strchr(p, G_DIR_SEPARATOR)) != NULL; p++) {
2286 parent_dir = g_strndup(dir, p - dir);
2287 if (*parent_dir != '\0') {
2288 if (!is_dir_exist(parent_dir)) {
2289 if (make_dir(parent_dir) < 0) {
2298 if (!is_dir_exist(dir)) {
2299 if (make_dir(dir) < 0)
2306 gint remove_all_files(const gchar *dir)
2309 const gchar *dir_name;
2312 prev_dir = g_get_current_dir();
2314 if (g_chdir(dir) < 0) {
2315 FILE_OP_ERROR(dir, "chdir");
2320 if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2321 g_warning("failed to open directory: %s\n", dir);
2326 while ((dir_name = g_dir_read_name(dp)) != NULL) {
2327 if (claws_unlink(dir_name) < 0)
2328 FILE_OP_ERROR(dir_name, "unlink");
2333 if (g_chdir(prev_dir) < 0) {
2334 FILE_OP_ERROR(prev_dir, "chdir");
2344 gint remove_numbered_files(const gchar *dir, guint first, guint last)
2347 const gchar *dir_name;
2351 if (first == last) {
2352 /* Skip all the dir reading part. */
2353 gchar *filename = g_strdup_printf("%s%s%u", dir, G_DIR_SEPARATOR_S, first);
2354 if (claws_unlink(filename) < 0) {
2355 FILE_OP_ERROR(filename, "unlink");
2363 prev_dir = g_get_current_dir();
2365 if (g_chdir(dir) < 0) {
2366 FILE_OP_ERROR(dir, "chdir");
2371 if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2372 g_warning("failed to open directory: %s\n", dir);
2377 while ((dir_name = g_dir_read_name(dp)) != NULL) {
2378 file_no = to_number(dir_name);
2379 if (file_no > 0 && first <= file_no && file_no <= last) {
2380 if (is_dir_exist(dir_name))
2382 if (claws_unlink(dir_name) < 0)
2383 FILE_OP_ERROR(dir_name, "unlink");
2389 if (g_chdir(prev_dir) < 0) {
2390 FILE_OP_ERROR(prev_dir, "chdir");
2400 gint remove_numbered_files_not_in_list(const gchar *dir, GSList *numberlist)
2403 const gchar *dir_name;
2406 GHashTable *wanted_files;
2409 if (numberlist == NULL)
2412 prev_dir = g_get_current_dir();
2414 if (g_chdir(dir) < 0) {
2415 FILE_OP_ERROR(dir, "chdir");
2420 if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2421 FILE_OP_ERROR(dir, "opendir");
2426 wanted_files = g_hash_table_new(g_direct_hash, g_direct_equal);
2427 for (cur = numberlist; cur != NULL; cur = cur->next) {
2428 /* numberlist->data is expected to be GINT_TO_POINTER */
2429 g_hash_table_insert(wanted_files, numberlist->data, GINT_TO_POINTER(1));
2432 while ((dir_name = g_dir_read_name(dp)) != NULL) {
2433 file_no = to_number(dir_name);
2434 if (is_dir_exist(dir_name))
2436 if (file_no > 0 && g_hash_table_lookup(wanted_files, GINT_TO_POINTER(file_no)) == NULL) {
2437 debug_print("removing unwanted file %d from %s\n", file_no, dir);
2438 if (claws_unlink(dir_name) < 0)
2439 FILE_OP_ERROR(dir_name, "unlink");
2444 g_hash_table_destroy(wanted_files);
2446 if (g_chdir(prev_dir) < 0) {
2447 FILE_OP_ERROR(prev_dir, "chdir");
2457 gint remove_all_numbered_files(const gchar *dir)
2459 return remove_numbered_files(dir, 0, UINT_MAX);
2462 gint remove_dir_recursive(const gchar *dir)
2466 const gchar *dir_name;
2469 if (g_stat(dir, &s) < 0) {
2470 FILE_OP_ERROR(dir, "stat");
2471 if (ENOENT == errno) return 0;
2475 if (!S_ISDIR(s.st_mode)) {
2476 if (claws_unlink(dir) < 0) {
2477 FILE_OP_ERROR(dir, "unlink");
2484 prev_dir = g_get_current_dir();
2485 /* g_print("prev_dir = %s\n", prev_dir); */
2487 if (!path_cmp(prev_dir, dir)) {
2489 if (g_chdir("..") < 0) {
2490 FILE_OP_ERROR(dir, "chdir");
2493 prev_dir = g_get_current_dir();
2496 if (g_chdir(dir) < 0) {
2497 FILE_OP_ERROR(dir, "chdir");
2502 if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2503 g_warning("failed to open directory: %s\n", dir);
2509 /* remove all files in the directory */
2510 while ((dir_name = g_dir_read_name(dp)) != NULL) {
2511 /* g_print("removing %s\n", dir_name); */
2513 if (is_dir_exist(dir_name)) {
2514 if (remove_dir_recursive(dir_name) < 0) {
2515 g_warning("can't remove directory\n");
2519 if (claws_unlink(dir_name) < 0)
2520 FILE_OP_ERROR(dir_name, "unlink");
2526 if (g_chdir(prev_dir) < 0) {
2527 FILE_OP_ERROR(prev_dir, "chdir");
2534 if (g_rmdir(dir) < 0) {
2535 FILE_OP_ERROR(dir, "rmdir");
2542 gint rename_force(const gchar *oldpath, const gchar *newpath)
2545 if (!is_file_entry_exist(oldpath)) {
2549 if (is_file_exist(newpath)) {
2550 if (claws_unlink(newpath) < 0)
2551 FILE_OP_ERROR(newpath, "unlink");
2554 return g_rename(oldpath, newpath);
2558 * Append src file body to the tail of dest file.
2559 * Now keep_backup has no effects.
2561 gint append_file(const gchar *src, const gchar *dest, gboolean keep_backup)
2563 FILE *src_fp, *dest_fp;
2567 gboolean err = FALSE;
2569 if ((src_fp = g_fopen(src, "rb")) == NULL) {
2570 FILE_OP_ERROR(src, "g_fopen");
2574 if ((dest_fp = g_fopen(dest, "ab")) == NULL) {
2575 FILE_OP_ERROR(dest, "g_fopen");
2580 if (change_file_mode_rw(dest_fp, dest) < 0) {
2581 FILE_OP_ERROR(dest, "chmod");
2582 g_warning("can't change file mode\n");
2585 while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
2586 if (n_read < sizeof(buf) && ferror(src_fp))
2588 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2589 g_warning("writing to %s failed.\n", dest);
2597 if (ferror(src_fp)) {
2598 FILE_OP_ERROR(src, "fread");
2602 if (fclose(dest_fp) == EOF) {
2603 FILE_OP_ERROR(dest, "fclose");
2615 gint copy_file(const gchar *src, const gchar *dest, gboolean keep_backup)
2617 FILE *src_fp, *dest_fp;
2620 gchar *dest_bak = NULL;
2621 gboolean err = FALSE;
2623 if ((src_fp = g_fopen(src, "rb")) == NULL) {
2624 FILE_OP_ERROR(src, "g_fopen");
2627 if (is_file_exist(dest)) {
2628 dest_bak = g_strconcat(dest, ".bak", NULL);
2629 if (rename_force(dest, dest_bak) < 0) {
2630 FILE_OP_ERROR(dest, "rename");
2637 if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2638 FILE_OP_ERROR(dest, "g_fopen");
2641 if (rename_force(dest_bak, dest) < 0)
2642 FILE_OP_ERROR(dest_bak, "rename");
2648 if (change_file_mode_rw(dest_fp, dest) < 0) {
2649 FILE_OP_ERROR(dest, "chmod");
2650 g_warning("can't change file mode\n");
2653 while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
2654 if (n_read < sizeof(buf) && ferror(src_fp))
2656 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2657 g_warning("writing to %s failed.\n", dest);
2662 if (rename_force(dest_bak, dest) < 0)
2663 FILE_OP_ERROR(dest_bak, "rename");
2670 if (ferror(src_fp)) {
2671 FILE_OP_ERROR(src, "fread");
2675 if (fclose(dest_fp) == EOF) {
2676 FILE_OP_ERROR(dest, "fclose");
2683 if (rename_force(dest_bak, dest) < 0)
2684 FILE_OP_ERROR(dest_bak, "rename");
2690 if (keep_backup == FALSE && dest_bak)
2691 claws_unlink(dest_bak);
2698 gint move_file(const gchar *src, const gchar *dest, gboolean overwrite)
2700 if (overwrite == FALSE && is_file_exist(dest)) {
2701 g_warning("move_file(): file %s already exists.", dest);
2705 if (rename_force(src, dest) == 0) return 0;
2707 if (EXDEV != errno) {
2708 FILE_OP_ERROR(src, "rename");
2712 if (copy_file(src, dest, FALSE) < 0) return -1;
2719 gint copy_file_part_to_fp(FILE *fp, off_t offset, size_t length, FILE *dest_fp)
2722 gint bytes_left, to_read;
2725 if (fseek(fp, offset, SEEK_SET) < 0) {
2730 bytes_left = length;
2731 to_read = MIN(bytes_left, sizeof(buf));
2733 while ((n_read = fread(buf, sizeof(gchar), to_read, fp)) > 0) {
2734 if (n_read < to_read && ferror(fp))
2736 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2739 bytes_left -= n_read;
2740 if (bytes_left == 0)
2742 to_read = MIN(bytes_left, sizeof(buf));
2753 gint copy_file_part(FILE *fp, off_t offset, size_t length, const gchar *dest)
2756 gboolean err = FALSE;
2758 if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2759 FILE_OP_ERROR(dest, "g_fopen");
2763 if (change_file_mode_rw(dest_fp, dest) < 0) {
2764 FILE_OP_ERROR(dest, "chmod");
2765 g_warning("can't change file mode\n");
2768 if (copy_file_part_to_fp(fp, offset, length, dest_fp) < 0)
2771 if (!err && fclose(dest_fp) == EOF) {
2772 FILE_OP_ERROR(dest, "fclose");
2777 g_warning("writing to %s failed.\n", dest);
2785 /* convert line endings into CRLF. If the last line doesn't end with
2786 * linebreak, add it.
2788 gchar *canonicalize_str(const gchar *str)
2794 for (p = str; *p != '\0'; ++p) {
2801 if (p == str || *(p - 1) != '\n')
2804 out = outp = g_malloc(new_len + 1);
2805 for (p = str; *p != '\0'; ++p) {
2812 if (p == str || *(p - 1) != '\n') {
2821 gint canonicalize_file(const gchar *src, const gchar *dest)
2823 FILE *src_fp, *dest_fp;
2824 gchar buf[BUFFSIZE];
2826 gboolean err = FALSE;
2827 gboolean last_linebreak = FALSE;
2829 if (src == NULL || dest == NULL)
2832 if ((src_fp = g_fopen(src, "rb")) == NULL) {
2833 FILE_OP_ERROR(src, "g_fopen");
2837 if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2838 FILE_OP_ERROR(dest, "g_fopen");
2843 if (change_file_mode_rw(dest_fp, dest) < 0) {
2844 FILE_OP_ERROR(dest, "chmod");
2845 g_warning("can't change file mode\n");
2848 while (fgets(buf, sizeof(buf), src_fp) != NULL) {
2852 if (len == 0) break;
2853 last_linebreak = FALSE;
2855 if (buf[len - 1] != '\n') {
2856 last_linebreak = TRUE;
2857 r = fputs(buf, dest_fp);
2858 } else if (len > 1 && buf[len - 1] == '\n' && buf[len - 2] == '\r') {
2859 r = fputs(buf, dest_fp);
2862 r = fwrite(buf, 1, len - 1, dest_fp);
2867 r = fputs("\r\n", dest_fp);
2871 g_warning("writing to %s failed.\n", dest);
2879 if (last_linebreak == TRUE) {
2880 if (fputs("\r\n", dest_fp) == EOF)
2884 if (ferror(src_fp)) {
2885 FILE_OP_ERROR(src, "fgets");
2889 if (fclose(dest_fp) == EOF) {
2890 FILE_OP_ERROR(dest, "fclose");
2902 gint canonicalize_file_replace(const gchar *file)
2906 tmp_file = get_tmp_file();
2908 if (canonicalize_file(file, tmp_file) < 0) {
2913 if (move_file(tmp_file, file, TRUE) < 0) {
2914 g_warning("can't replace %s .\n", file);
2915 claws_unlink(tmp_file);
2924 gchar *normalize_newlines(const gchar *str)
2929 out = outp = g_malloc(strlen(str) + 1);
2930 for (p = str; *p != '\0'; ++p) {
2932 if (*(p + 1) != '\n')
2943 gchar *get_outgoing_rfc2822_str(FILE *fp)
2945 gchar buf[BUFFSIZE];
2949 str = g_string_new(NULL);
2951 /* output header part */
2952 while (fgets(buf, sizeof(buf), fp) != NULL) {
2954 if (!g_ascii_strncasecmp(buf, "Bcc:", 4)) {
2961 else if (next != ' ' && next != '\t') {
2965 if (fgets(buf, sizeof(buf), fp) == NULL)
2969 g_string_append(str, buf);
2970 g_string_append(str, "\r\n");
2976 /* output body part */
2977 while (fgets(buf, sizeof(buf), fp) != NULL) {
2980 g_string_append_c(str, '.');
2981 g_string_append(str, buf);
2982 g_string_append(str, "\r\n");
2986 g_string_free(str, FALSE);
2992 * Create a new boundary in a way that it is very unlikely that this
2993 * will occur in the following text. It would be easy to ensure
2994 * uniqueness if everything is either quoted-printable or base64
2995 * encoded (note that conversion is allowed), but because MIME bodies
2996 * may be nested, it may happen that the same boundary has already
2999 * boundary := 0*69<bchars> bcharsnospace
3000 * bchars := bcharsnospace / " "
3001 * bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" /
3002 * "+" / "_" / "," / "-" / "." /
3003 * "/" / ":" / "=" / "?"
3005 * some special characters removed because of buggy MTAs
3008 gchar *generate_mime_boundary(const gchar *prefix)
3010 static gchar tbl[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
3011 "abcdefghijklmnopqrstuvwxyz"
3016 for (i = 0; i < sizeof(buf_uniq) - 1; i++)
3017 buf_uniq[i] = tbl[g_random_int_range(0, sizeof(tbl) - 1)];
3020 return g_strdup_printf("%s_/%s", prefix ? prefix : "MP",
3024 gint change_file_mode_rw(FILE *fp, const gchar *file)
3027 return fchmod(fileno(fp), S_IRUSR|S_IWUSR);
3029 return g_chmod(file, S_IRUSR|S_IWUSR);
3033 FILE *my_tmpfile(void)
3035 #if HAVE_MKSTEMP || defined(G_OS_WIN32)
3036 const gchar suffix[] = ".XXXXXX";
3037 const gchar *tmpdir;
3039 const gchar *progname;
3048 tmpdir = get_tmp_dir();
3049 tmplen = strlen(tmpdir);
3050 progname = g_get_prgname();
3051 if (progname == NULL)
3052 progname = "claws-mail";
3053 proglen = strlen(progname);
3054 Xalloca(fname, tmplen + 1 + proglen + sizeof(suffix),
3057 memcpy(fname, tmpdir, tmplen);
3058 fname[tmplen] = G_DIR_SEPARATOR;
3059 memcpy(fname + tmplen + 1, progname, proglen);
3060 memcpy(fname + tmplen + 1 + proglen, suffix, sizeof(suffix));
3062 fd = mkstemp(fname);
3067 claws_unlink(fname);
3069 /* verify that we can write in the file after unlinking */
3070 if (write(fd, buf, 1) < 0) {
3077 fp = fdopen(fd, "w+b");
3085 #endif /* HAVE_MKSTEMP || G_OS_WIN32 */
3090 FILE *get_tmpfile_in_dir(const gchar *dir, gchar **filename)
3094 char *template = g_strdup_printf ("%s%cclaws.XXXXXX",
3095 dir, G_DIR_SEPARATOR);
3096 fd = mkstemp_name(template, filename);
3099 *filename = g_strdup_printf("%s%cclaws.XXXXXX", dir, G_DIR_SEPARATOR);
3100 fd = mkstemp(*filename);
3102 return fdopen(fd, "w+");
3105 FILE *str_open_as_stream(const gchar *str)
3110 cm_return_val_if_fail(str != NULL, NULL);
3114 FILE_OP_ERROR("str_open_as_stream", "my_tmpfile");
3119 if (len == 0) return fp;
3121 if (fwrite(str, 1, len, fp) != len) {
3122 FILE_OP_ERROR("str_open_as_stream", "fwrite");
3131 gint str_write_to_file(const gchar *str, const gchar *file)
3136 cm_return_val_if_fail(str != NULL, -1);
3137 cm_return_val_if_fail(file != NULL, -1);
3139 if ((fp = g_fopen(file, "wb")) == NULL) {
3140 FILE_OP_ERROR(file, "g_fopen");
3150 if (fwrite(str, 1, len, fp) != len) {
3151 FILE_OP_ERROR(file, "fwrite");
3157 if (fclose(fp) == EOF) {
3158 FILE_OP_ERROR(file, "fclose");
3166 static gchar *file_read_stream_to_str_full(FILE *fp, gboolean recode)
3173 cm_return_val_if_fail(fp != NULL, NULL);
3175 array = g_byte_array_new();
3177 while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
3178 if (n_read < sizeof(buf) && ferror(fp))
3180 g_byte_array_append(array, buf, n_read);
3184 FILE_OP_ERROR("file stream", "fread");
3185 g_byte_array_free(array, TRUE);
3190 g_byte_array_append(array, buf, 1);
3191 str = (gchar *)array->data;
3192 g_byte_array_free(array, FALSE);
3194 if (recode && !g_utf8_validate(str, -1, NULL)) {
3195 const gchar *src_codeset, *dest_codeset;
3197 src_codeset = conv_get_locale_charset_str();
3198 dest_codeset = CS_UTF_8;
3199 tmp = conv_codeset_strdup(str, src_codeset, dest_codeset);
3207 static gchar *file_read_to_str_full(const gchar *file, gboolean recode)
3214 struct timeval timeout = {1, 0};
3219 cm_return_val_if_fail(file != NULL, NULL);
3221 if (g_stat(file, &s) != 0) {
3222 FILE_OP_ERROR(file, "stat");
3225 if (S_ISDIR(s.st_mode)) {
3226 g_warning("%s: is a directory\n", file);
3231 fp = g_fopen (file, "rb");
3233 FILE_OP_ERROR(file, "open");
3237 /* test whether the file is readable without blocking */
3238 fd = g_open(file, O_RDONLY | O_NONBLOCK, 0);
3240 FILE_OP_ERROR(file, "open");
3247 /* allow for one second */
3248 err = select(fd+1, &fds, NULL, NULL, &timeout);
3249 if (err <= 0 || !FD_ISSET(fd, &fds)) {
3251 FILE_OP_ERROR(file, "select");
3253 g_warning("%s: doesn't seem readable\n", file);
3259 /* Now clear O_NONBLOCK */
3260 if ((fflags = fcntl(fd, F_GETFL)) < 0) {
3261 FILE_OP_ERROR(file, "fcntl (F_GETFL)");
3265 if (fcntl(fd, F_SETFL, (fflags & ~O_NONBLOCK)) < 0) {
3266 FILE_OP_ERROR(file, "fcntl (F_SETFL)");
3271 /* get the FILE pointer */
3272 fp = fdopen(fd, "rb");
3275 FILE_OP_ERROR(file, "fdopen");
3276 close(fd); /* if fp isn't NULL, we'll use fclose instead! */
3281 str = file_read_stream_to_str_full(fp, recode);
3288 gchar *file_read_to_str(const gchar *file)
3290 return file_read_to_str_full(file, TRUE);
3292 gchar *file_read_stream_to_str(FILE *fp)
3294 return file_read_stream_to_str_full(fp, TRUE);
3297 gchar *file_read_to_str_no_recode(const gchar *file)
3299 return file_read_to_str_full(file, FALSE);
3301 gchar *file_read_stream_to_str_no_recode(FILE *fp)
3303 return file_read_stream_to_str_full(fp, FALSE);
3306 char *fgets_crlf(char *buf, int size, FILE *stream)
3308 gboolean is_cr = FALSE;
3309 gboolean last_was_cr = FALSE;
3314 while (--size > 0 && (c = getc(stream)) != EOF)
3317 is_cr = (c == '\r');
3327 last_was_cr = is_cr;
3329 if (c == EOF && cs == buf)
3337 static gint execute_async(gchar *const argv[])
3339 cm_return_val_if_fail(argv != NULL && argv[0] != NULL, -1);
3341 if (g_spawn_async(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH,
3342 NULL, NULL, NULL, FALSE) == FALSE) {
3343 g_warning("Couldn't execute command: %s\n", argv[0]);
3350 static gint execute_sync(gchar *const argv[])
3354 cm_return_val_if_fail(argv != NULL && argv[0] != NULL, -1);
3357 if (g_spawn_sync(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH,
3358 NULL, NULL, NULL, NULL, &status, NULL) == FALSE) {
3359 g_warning("Couldn't execute command: %s\n", argv[0]);
3363 if (WIFEXITED(status))
3364 return WEXITSTATUS(status);
3368 if (g_spawn_sync(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH|
3369 G_SPAWN_CHILD_INHERITS_STDIN|G_SPAWN_LEAVE_DESCRIPTORS_OPEN,
3370 NULL, NULL, NULL, NULL, &status, NULL) == FALSE) {
3371 g_warning("Couldn't execute command: %s\n", argv[0]);
3379 gint execute_command_line(const gchar *cmdline, gboolean async)
3384 debug_print("execute_command_line(): executing: %s\n", cmdline?cmdline:"(null)");
3386 argv = strsplit_with_quote(cmdline, " ", 0);
3389 ret = execute_async(argv);
3391 ret = execute_sync(argv);
3398 gchar *get_command_output(const gchar *cmdline)
3400 gchar *child_stdout;
3403 cm_return_val_if_fail(cmdline != NULL, NULL);
3405 debug_print("get_command_output(): executing: %s\n", cmdline);
3407 if (g_spawn_command_line_sync(cmdline, &child_stdout, NULL, &status,
3409 g_warning("Couldn't execute command: %s\n", cmdline);
3413 return child_stdout;
3416 static gint is_unchanged_uri_char(char c)
3427 static void encode_uri(gchar *encoded_uri, gint bufsize, const gchar *uri)
3433 for(i = 0; i < strlen(uri) ; i++) {
3434 if (is_unchanged_uri_char(uri[i])) {
3435 if (k + 2 >= bufsize)
3437 encoded_uri[k++] = uri[i];
3440 char * hexa = "0123456789ABCDEF";
3442 if (k + 4 >= bufsize)
3444 encoded_uri[k++] = '%';
3445 encoded_uri[k++] = hexa[uri[i] / 16];
3446 encoded_uri[k++] = hexa[uri[i] % 16];
3452 gint open_uri(const gchar *uri, const gchar *cmdline)
3456 gchar buf[BUFFSIZE];
3458 gchar encoded_uri[BUFFSIZE];
3459 cm_return_val_if_fail(uri != NULL, -1);
3461 /* an option to choose whether to use encode_uri or not ? */
3462 encode_uri(encoded_uri, BUFFSIZE, uri);
3465 (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
3466 !strchr(p + 2, '%'))
3467 g_snprintf(buf, sizeof(buf), cmdline, encoded_uri);
3470 g_warning("Open URI command-line is invalid "
3471 "(there must be only one '%%s'): %s",
3473 g_snprintf(buf, sizeof(buf), DEFAULT_BROWSER_CMD, encoded_uri);
3476 execute_command_line(buf, TRUE);
3478 ShellExecute(NULL, "open", uri, NULL, NULL, SW_SHOW);
3483 gint open_txt_editor(const gchar *filepath, const gchar *cmdline)
3485 gchar buf[BUFFSIZE];
3488 cm_return_val_if_fail(filepath != NULL, -1);
3491 (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
3492 !strchr(p + 2, '%'))
3493 g_snprintf(buf, sizeof(buf), cmdline, filepath);
3496 g_warning("Open Text Editor command-line is invalid "
3497 "(there must be only one '%%s'): %s",
3499 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, filepath);
3502 execute_command_line(buf, TRUE);
3507 time_t remote_tzoffset_sec(const gchar *zone)
3509 static gchar ustzstr[] = "PSTPDTMSTMDTCSTCDTESTEDT";
3515 time_t remoteoffset;
3517 strncpy(zone3, zone, 3);
3521 if (sscanf(zone, "%c%d", &c, &offset) == 2 &&
3522 (c == '+' || c == '-')) {
3523 remoteoffset = ((offset / 100) * 60 + (offset % 100)) * 60;
3525 remoteoffset = -remoteoffset;
3526 } else if (!strncmp(zone, "UT" , 2) ||
3527 !strncmp(zone, "GMT", 2)) {
3529 } else if (strlen(zone3) == 3) {
3530 for (p = ustzstr; *p != '\0'; p += 3) {
3531 if (!g_ascii_strncasecmp(p, zone3, 3)) {
3532 iustz = ((gint)(p - ustzstr) / 3 + 1) / 2 - 8;
3533 remoteoffset = iustz * 3600;
3539 } else if (strlen(zone3) == 1) {
3541 case 'Z': remoteoffset = 0; break;
3542 case 'A': remoteoffset = -1; break;
3543 case 'B': remoteoffset = -2; break;
3544 case 'C': remoteoffset = -3; break;
3545 case 'D': remoteoffset = -4; break;
3546 case 'E': remoteoffset = -5; break;
3547 case 'F': remoteoffset = -6; break;
3548 case 'G': remoteoffset = -7; break;
3549 case 'H': remoteoffset = -8; break;
3550 case 'I': remoteoffset = -9; break;
3551 case 'K': remoteoffset = -10; break; /* J is not used */
3552 case 'L': remoteoffset = -11; break;
3553 case 'M': remoteoffset = -12; break;
3554 case 'N': remoteoffset = 1; break;
3555 case 'O': remoteoffset = 2; break;
3556 case 'P': remoteoffset = 3; break;
3557 case 'Q': remoteoffset = 4; break;
3558 case 'R': remoteoffset = 5; break;
3559 case 'S': remoteoffset = 6; break;
3560 case 'T': remoteoffset = 7; break;
3561 case 'U': remoteoffset = 8; break;
3562 case 'V': remoteoffset = 9; break;
3563 case 'W': remoteoffset = 10; break;
3564 case 'X': remoteoffset = 11; break;
3565 case 'Y': remoteoffset = 12; break;
3566 default: remoteoffset = 0; break;
3568 remoteoffset = remoteoffset * 3600;
3572 return remoteoffset;
3575 time_t tzoffset_sec(time_t *now)
3579 struct tm buf1, buf2;
3581 if (now && *now < 0)
3584 gmt = *gmtime_r(now, &buf1);
3585 lt = localtime_r(now, &buf2);
3587 off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
3589 if (lt->tm_year < gmt.tm_year)
3591 else if (lt->tm_year > gmt.tm_year)
3593 else if (lt->tm_yday < gmt.tm_yday)
3595 else if (lt->tm_yday > gmt.tm_yday)
3598 if (off >= 24 * 60) /* should be impossible */
3599 off = 23 * 60 + 59; /* if not, insert silly value */
3600 if (off <= -24 * 60)
3601 off = -(23 * 60 + 59);
3606 /* calculate timezone offset */
3607 gchar *tzoffset(time_t *now)
3609 static gchar offset_string[6];
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)
3637 if (off >= 24 * 60) /* should be impossible */
3638 off = 23 * 60 + 59; /* if not, insert silly value */
3640 sprintf(offset_string, "%c%02d%02d", sign, off / 60, off % 60);
3642 return offset_string;
3645 void get_rfc822_date(gchar *buf, gint len)
3649 gchar day[4], mon[4];
3650 gint dd, hh, mm, ss, yyyy;
3652 gchar buf2[BUFFSIZE];
3655 lt = localtime_r(&t, &buf1);
3657 sscanf(asctime_r(lt, buf2), "%3s %3s %d %d:%d:%d %d\n",
3658 day, mon, &dd, &hh, &mm, &ss, &yyyy);
3660 g_snprintf(buf, len, "%s, %d %s %d %02d:%02d:%02d %s",
3661 day, dd, mon, yyyy, hh, mm, ss, tzoffset(&t));
3664 void debug_set_mode(gboolean mode)
3669 gboolean debug_get_mode(void)
3674 void debug_print_real(const gchar *format, ...)
3677 gchar buf[BUFFSIZE];
3679 if (!debug_mode) return;
3681 va_start(args, format);
3682 g_vsnprintf(buf, sizeof(buf), format, args);
3689 const char * debug_srcname(const char *file)
3691 const char *s = strrchr (file, '/');
3696 void * subject_table_lookup(GHashTable *subject_table, gchar * subject)
3698 if (subject == NULL)
3701 subject += subject_get_prefix_length(subject);
3703 return g_hash_table_lookup(subject_table, subject);
3706 void subject_table_insert(GHashTable *subject_table, gchar * subject,
3709 if (subject == NULL || *subject == 0)
3711 subject += subject_get_prefix_length(subject);
3712 g_hash_table_insert(subject_table, subject, data);
3715 void subject_table_remove(GHashTable *subject_table, gchar * subject)
3717 if (subject == NULL)
3720 subject += subject_get_prefix_length(subject);
3721 g_hash_table_remove(subject_table, subject);
3725 static regex_t u_regex;
3726 static gboolean u_init_;
3729 void utils_free_regex(void)
3740 *\brief Check if a string is prefixed with known (combinations)
3741 * of prefixes. The function assumes that each prefix
3742 * is terminated by zero or exactly _one_ space.
3744 *\param str String to check for a prefixes
3746 *\return int Number of chars in the prefix that should be skipped
3747 * for a "clean" subject line. If no prefix was found, 0
3750 int subject_get_prefix_length(const gchar *subject)
3753 /*!< Array with allowable reply prefixes regexps. */
3754 static const gchar * const prefixes[] = {
3755 "Re\\:", /* "Re:" */
3756 "Re\\[[1-9][0-9]*\\]\\:", /* "Re[XXX]:" (non-conforming news mail clients) */
3757 "Antw\\:", /* "Antw:" (Dutch / German Outlook) */
3758 "Aw\\:", /* "Aw:" (German) */
3759 "Antwort\\:", /* "Antwort:" (German Lotus Notes) */
3760 "Res\\:", /* "Res:" (Spanish/Brazilian Outlook) */
3761 "Fw\\:", /* "Fw:" Forward */
3762 "Fwd\\:", /* "Fwd:" Forward */
3763 "Enc\\:", /* "Enc:" Forward (Brazilian Outlook) */
3764 "Odp\\:", /* "Odp:" Re (Polish Outlook) */
3765 "Rif\\:", /* "Rif:" (Italian Outlook) */
3766 "Sv\\:", /* "Sv" (Norwegian) */
3767 "Vs\\:", /* "Vs" (Norwegian) */
3768 "Ad\\:", /* "Ad" (Norwegian) */
3769 "\347\255\224\345\244\215\\:", /* "Re" (Chinese, UTF-8) */
3770 "R\303\251f\\. \\:", /* "R�f. :" (French Lotus Notes) */
3771 "Re \\:", /* "Re :" (French Yahoo Mail) */
3774 const int PREFIXES = sizeof prefixes / sizeof prefixes[0];
3778 if (!subject) return 0;
3779 if (!*subject) return 0;
3782 GString *s = g_string_new("");
3784 for (n = 0; n < PREFIXES; n++)
3785 /* Terminate each prefix regexpression by a
3786 * "\ ?" (zero or ONE space), and OR them */
3787 g_string_append_printf(s, "(%s\\ ?)%s",
3792 g_string_prepend(s, "(");
3793 g_string_append(s, ")+"); /* match at least once */
3794 g_string_prepend(s, "^\\ *"); /* from beginning of line */
3797 /* We now have something like "^\ *((PREFIX1\ ?)|(PREFIX2\ ?))+"
3798 * TODO: Should this be "^\ *(((PREFIX1)|(PREFIX2))\ ?)+" ??? */
3799 if (regcomp(&u_regex, s->str, REG_EXTENDED | REG_ICASE)) {
3800 debug_print("Error compiling regexp %s\n", s->str);
3801 g_string_free(s, TRUE);
3805 g_string_free(s, TRUE);
3809 if (!regexec(&u_regex, subject, 1, &pos, 0) && pos.rm_so != -1)
3814 /*!< Array with allowable reply prefixes regexps. */
3815 static const gchar * const prefixes[] = {
3817 "antw:", /* "Antw:" (Dutch / German Outlook) */
3818 "aw:", /* "Aw:" (German) */
3819 "antwort:", /* "Antwort:" (German Lotus Notes) */
3820 "res:", /* "Res:" (Spanish/Brazilian Outlook) */
3821 "fw:", /* "Fw:" Forward */
3822 "fwd:", /* "Fwd:" Forward */
3823 "enc:", /* "Enc:" Forward (Brazilian Outlook) */
3824 "odp:", /* "Odp:" Re (Polish Outlook) */
3825 "rif:", /* "Rif:" (Italian Outlook) */
3826 "sv:", /* "Sv" (Norwegian) */
3827 "vs:", /* "Vs" (Norwegian) */
3828 "ad:", /* "Ad" (Norwegian) */
3829 "R\303\251f. :", /* "R�f. :" (French Lotus Notes) */
3830 "Re :", /* "Re :" (French Yahoo Mail) */
3833 const int PREFIXES = sizeof prefixes / sizeof prefixes[0];
3836 if (!subject) return 0;
3837 if (!*subject) return 0;
3839 for (n = 0; n < PREFIXES; n++) {
3840 int len = strlen(prefixes[n]);
3841 if (!strncasecmp(subject, prefixes[n], len)) {
3842 if (subject[len] == ' ')
3851 static guint g_stricase_hash(gconstpointer gptr)
3853 guint hash_result = 0;
3856 for (str = gptr; str && *str; str++) {
3857 hash_result += toupper(*str);
3863 static gint g_stricase_equal(gconstpointer gptr1, gconstpointer gptr2)
3865 const char *str1 = gptr1;
3866 const char *str2 = gptr2;
3868 return !strcasecmp(str1, str2);
3871 gint g_int_compare(gconstpointer a, gconstpointer b)
3873 return GPOINTER_TO_INT(a) - GPOINTER_TO_INT(b);
3876 gchar *generate_msgid(gchar *buf, gint len, gchar *user_addr)
3884 lt = localtime_r(&t, &buft);
3886 if (user_addr != NULL)
3887 addr = g_strdup_printf(".%s", user_addr);
3888 else if (strlen(buf) != 0)
3889 addr = g_strdup_printf("@%s", buf);
3891 addr = g_strdup_printf("@%s", get_domain_name());
3893 /* Replace all @ but the last one in addr, with underscores.
3894 * RFC 2822 States that msg-id syntax only allows one @.
3896 while (strchr(addr, '@') != NULL && strchr(addr, '@') != strrchr(addr, '@'))
3897 *(strchr(addr, '@')) = '_';
3899 g_snprintf(buf, len, "%04d%02d%02d%02d%02d%02d.%08x%s",
3900 lt->tm_year + 1900, lt->tm_mon + 1,
3901 lt->tm_mday, lt->tm_hour,
3902 lt->tm_min, lt->tm_sec,
3903 (guint) rand(), addr);
3910 quote_cmd_argument()
3912 return a quoted string safely usable in argument of a command.
3914 code is extracted and adapted from etPan! project -- DINH V. Ho�.
3917 gint quote_cmd_argument(gchar * result, guint size,
3927 for(p = path ; * p != '\0' ; p ++) {
3929 if (isalnum((guchar)*p) || (* p == '/')) {
3930 if (remaining > 0) {
3936 result[size - 1] = '\0';
3941 if (remaining >= 2) {
3949 result[size - 1] = '\0';
3954 if (remaining > 0) {
3958 result[size - 1] = '\0';
3972 static void g_node_map_recursive(GNode *node, gpointer data)
3974 GNodeMapData *mapdata = (GNodeMapData *) data;
3976 GNodeMapData newmapdata;
3979 newdata = mapdata->func(node->data, mapdata->data);
3980 if (newdata != NULL) {
3981 newnode = g_node_new(newdata);
3982 g_node_append(mapdata->parent, newnode);
3984 newmapdata.parent = newnode;
3985 newmapdata.func = mapdata->func;
3986 newmapdata.data = mapdata->data;
3988 g_node_children_foreach(node, G_TRAVERSE_ALL, g_node_map_recursive, &newmapdata);
3992 GNode *g_node_map(GNode *node, GNodeMapFunc func, gpointer data)
3995 GNodeMapData mapdata;
3997 cm_return_val_if_fail(node != NULL, NULL);
3998 cm_return_val_if_fail(func != NULL, NULL);
4000 root = g_node_new(func(node->data, data));
4002 mapdata.parent = root;
4003 mapdata.func = func;
4004 mapdata.data = data;
4006 g_node_children_foreach(node, G_TRAVERSE_ALL, g_node_map_recursive, &mapdata);
4011 #define HEX_TO_INT(val, hex) \
4015 if ('0' <= c && c <= '9') { \
4017 } else if ('a' <= c && c <= 'f') { \
4018 val = c - 'a' + 10; \
4019 } else if ('A' <= c && c <= 'F') { \
4020 val = c - 'A' + 10; \
4026 gboolean get_hex_value(guchar *out, gchar c1, gchar c2)
4033 if (hi == -1 || lo == -1)
4036 *out = (hi << 4) + lo;
4040 #define INT_TO_HEX(hex, val) \
4043 hex = '0' + (val); \
4045 hex = 'A' + (val) - 10; \
4048 void get_hex_str(gchar *out, guchar ch)
4052 INT_TO_HEX(hex, ch >> 4);
4054 INT_TO_HEX(hex, ch & 0x0f);
4060 #define G_PRINT_REF 1 == 1 ? (void) 0 : (void)
4062 #define G_PRINT_REF g_print
4066 *\brief Register ref counted pointer. It is based on GBoxed, so should
4067 * work with anything that uses the GType system. The semantics
4068 * are similar to a C++ auto pointer, with the exception that
4069 * C doesn't have automatic closure (calling destructors) when
4070 * exiting a block scope.
4071 * Use the \ref G_TYPE_AUTO_POINTER macro instead of calling this
4072 * function directly.
4074 *\return GType A GType type.
4076 GType g_auto_pointer_register(void)
4078 static GType auto_pointer_type;
4079 if (!auto_pointer_type)
4081 g_boxed_type_register_static
4082 ("G_TYPE_AUTO_POINTER",
4083 (GBoxedCopyFunc) g_auto_pointer_copy,
4084 (GBoxedFreeFunc) g_auto_pointer_free);
4085 return auto_pointer_type;
4089 *\brief Structure with g_new() allocated pointer guarded by the
4092 typedef struct AutoPointerRef {
4093 void (*free) (gpointer);
4099 *\brief The auto pointer opaque structure that references the
4100 * pointer guard block.
4102 typedef struct AutoPointer {
4103 AutoPointerRef *ref;
4104 gpointer ptr; /*!< access to protected pointer */
4108 *\brief Creates an auto pointer for a g_new()ed pointer. Example:
4112 * ... tell gtk_list_store it should use a G_TYPE_AUTO_POINTER
4113 * ... when assigning, copying and freeing storage elements
4115 * gtk_list_store_new(N_S_COLUMNS,
4116 * G_TYPE_AUTO_POINTER,
4120 * Template *precious_data = g_new0(Template, 1);
4121 * g_pointer protect = g_auto_pointer_new(precious_data);
4123 * gtk_list_store_set(container, &iter,
4127 * ... the gtk_list_store has copied the pointer and
4128 * ... incremented its reference count, we should free
4129 * ... the auto pointer (in C++ a destructor would do
4130 * ... this for us when leaving block scope)
4132 * g_auto_pointer_free(protect);
4134 * ... gtk_list_store_set() now manages the data. When
4135 * ... *explicitly* requesting a pointer from the list
4136 * ... store, don't forget you get a copy that should be
4137 * ... freed with g_auto_pointer_free() eventually.
4141 *\param pointer Pointer to be guarded.
4143 *\return GAuto * Pointer that should be used in containers with
4146 GAuto *g_auto_pointer_new(gpointer p)
4148 AutoPointerRef *ref;
4154 ref = g_new0(AutoPointerRef, 1);
4155 ptr = g_new0(AutoPointer, 1);
4165 G_PRINT_REF ("XXXX ALLOC(%lx)\n", p);
4171 *\brief Allocate an autopointer using the passed \a free function to
4172 * free the guarded pointer
4174 GAuto *g_auto_pointer_new_with_free(gpointer p, GFreeFunc free_)
4181 aptr = g_auto_pointer_new(p);
4182 aptr->ref->free = free_;
4186 gpointer g_auto_pointer_get_ptr(GAuto *auto_ptr)
4188 if (auto_ptr == NULL)
4190 return ((AutoPointer *) auto_ptr)->ptr;
4194 *\brief Copies an auto pointer by. It's mostly not necessary
4195 * to call this function directly, unless you copy/assign
4196 * the guarded pointer.
4198 *\param auto_ptr Auto pointer returned by previous call to
4199 * g_auto_pointer_new_XXX()
4201 *\return gpointer An auto pointer
4203 GAuto *g_auto_pointer_copy(GAuto *auto_ptr)
4206 AutoPointerRef *ref;
4209 if (auto_ptr == NULL)
4214 newp = g_new0(AutoPointer, 1);
4217 newp->ptr = ref->pointer;
4221 G_PRINT_REF ("XXXX COPY(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4227 *\brief Free an auto pointer
4229 void g_auto_pointer_free(GAuto *auto_ptr)
4232 AutoPointerRef *ref;
4234 if (auto_ptr == NULL)
4240 if (--(ref->cnt) == 0) {
4242 G_PRINT_REF ("XXXX FREE(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4244 ref->free(ref->pointer);
4249 G_PRINT_REF ("XXXX DEREF(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4254 void replace_returns(gchar *str)
4259 while (strstr(str, "\n")) {
4260 *strstr(str, "\n") = ' ';
4262 while (strstr(str, "\r")) {
4263 *strstr(str, "\r") = ' ';
4267 /* get_uri_part() - retrieves a URI starting from scanpos.
4268 Returns TRUE if succesful */
4269 gboolean get_uri_part(const gchar *start, const gchar *scanpos,
4270 const gchar **bp, const gchar **ep, gboolean hdr)
4273 gint parenthese_cnt = 0;
4275 cm_return_val_if_fail(start != NULL, FALSE);
4276 cm_return_val_if_fail(scanpos != NULL, FALSE);
4277 cm_return_val_if_fail(bp != NULL, FALSE);
4278 cm_return_val_if_fail(ep != NULL, FALSE);
4282 /* find end point of URI */
4283 for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
4284 if (!g_ascii_isgraph(*(const guchar *)ep_) ||
4285 !IS_ASCII(*(const guchar *)ep_) ||
4286 strchr("[]{}<>\"", *ep_)) {
4288 } else if (strchr("(", *ep_)) {
4290 } else if (strchr(")", *ep_)) {
4291 if (parenthese_cnt > 0)
4298 /* no punctuation at end of string */
4300 /* FIXME: this stripping of trailing punctuations may bite with other URIs.
4301 * should pass some URI type to this function and decide on that whether
4302 * to perform punctuation stripping */
4304 #define IS_REAL_PUNCT(ch) (g_ascii_ispunct(ch) && !strchr("/?=-_)", ch))
4306 for (; ep_ - 1 > scanpos + 1 &&
4307 IS_REAL_PUNCT(*(ep_ - 1));
4311 #undef IS_REAL_PUNCT
4318 gchar *make_uri_string(const gchar *bp, const gchar *ep)
4320 while (bp && *bp && g_ascii_isspace(*bp))
4322 return g_strndup(bp, ep - bp);
4325 /* valid mail address characters */
4326 #define IS_RFC822_CHAR(ch) \
4330 !g_ascii_isspace(ch) && \
4331 !strchr("(),;<>\"", (ch)))
4333 /* alphabet and number within 7bit ASCII */
4334 #define IS_ASCII_ALNUM(ch) (IS_ASCII(ch) && g_ascii_isalnum(ch))
4335 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
4337 static GHashTable *create_domain_tab(void)
4339 static const gchar *toplvl_domains [] = {
4341 "arpa", "coop", "info", "name", "biz", "com", "edu", "gov",
4342 "int", "mil", "net", "org", "ac", "ad", "ae", "af", "ag",
4343 "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au",
4344 "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi",
4345 "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
4346 "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl",
4347 "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz", "de",
4348 "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er",
4349 "es", "et", "eu", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd",
4350 "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
4351 "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
4352 "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir",
4353 "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
4354 "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc",
4355 "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
4356 "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq",
4357 "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na",
4358 "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu",
4359 "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm",
4360 "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru",
4361 "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj",
4362 "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
4363 "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to",
4364 "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um",
4365 "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu",
4366 "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw"
4369 GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
4371 cm_return_val_if_fail(htab, NULL);
4372 for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++)
4373 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
4377 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
4379 const gint MAX_LVL_DOM_NAME_LEN = 6;
4380 gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
4381 const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
4384 if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
4387 for (p = buf; p < m && first < last; *p++ = *first++)
4391 return g_hash_table_lookup(tab, buf) != NULL;
4394 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
4395 gboolean get_email_part(const gchar *start, const gchar *scanpos,
4396 const gchar **bp, const gchar **ep, gboolean hdr)
4398 /* more complex than the uri part because we need to scan back and forward starting from
4399 * the scan position. */
4400 gboolean result = FALSE;
4401 const gchar *bp_ = NULL;
4402 const gchar *ep_ = NULL;
4403 static GHashTable *dom_tab;
4404 const gchar *last_dot = NULL;
4405 const gchar *prelast_dot = NULL;
4406 const gchar *last_tld_char = NULL;
4408 /* the informative part of the email address (describing the name
4409 * of the email address owner) may contain quoted parts. the
4410 * closure stack stores the last encountered quotes. */
4411 gchar closure_stack[128];
4412 gchar *ptr = closure_stack;
4414 cm_return_val_if_fail(start != NULL, FALSE);
4415 cm_return_val_if_fail(scanpos != NULL, FALSE);
4416 cm_return_val_if_fail(bp != NULL, FALSE);
4417 cm_return_val_if_fail(ep != NULL, FALSE);
4420 const gchar *start_quote = NULL;
4421 const gchar *end_quote = NULL;
4423 /* go to the real start */
4424 if (start[0] == ',')
4426 if (start[0] == ';')
4428 while (start[0] == '\n' || start[0] == '\r')
4430 while (start[0] == ' ' || start[0] == '\t')
4435 /* check if there are quotes (to skip , in them) */
4436 if (*start == '"') {
4437 start_quote = start;
4439 end_quote = strstr(start, "\"");
4445 /* skip anything between quotes */
4446 if (start_quote && end_quote) {
4451 /* find end (either , or ; or end of line) */
4452 if (strstr(start, ",") && strstr(start, ";"))
4453 *ep = strstr(start,",") < strstr(start, ";")
4454 ? strstr(start, ",") : strstr(start, ";");
4455 else if (strstr(start, ","))
4456 *ep = strstr(start, ",");
4457 else if (strstr(start, ";"))
4458 *ep = strstr(start, ";");
4460 *ep = start+strlen(start);
4462 /* go back to real start */
4463 if (start_quote && end_quote) {
4464 start = start_quote;
4467 /* check there's still an @ in that, or search
4468 * further if possible */
4469 if (strstr(start, "@") && strstr(start, "@") < *ep)
4471 else if (*ep < start+strlen(start)) {
4474 } else if (start_quote && strstr(start, "\"") && strstr(start, "\"") < *ep) {
4482 dom_tab = create_domain_tab();
4483 cm_return_val_if_fail(dom_tab, FALSE);
4485 /* scan start of address */
4486 for (bp_ = scanpos - 1;
4487 bp_ >= start && IS_RFC822_CHAR(*(const guchar *)bp_); bp_--)
4490 /* TODO: should start with an alnum? */
4492 for (; bp_ < scanpos && !IS_ASCII_ALNUM(*(const guchar *)bp_); bp_++)
4495 if (bp_ != scanpos) {
4496 /* scan end of address */
4497 for (ep_ = scanpos + 1;
4498 *ep_ && IS_RFC822_CHAR(*(const guchar *)ep_); ep_++)
4500 prelast_dot = last_dot;
4502 if (*(last_dot + 1) == '.') {
4503 if (prelast_dot == NULL)
4505 last_dot = prelast_dot;
4510 /* TODO: really should terminate with an alnum? */
4511 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*(const guchar *)ep_);
4516 if (last_dot == NULL)
4518 if (last_dot >= ep_)
4519 last_dot = prelast_dot;
4520 if (last_dot == NULL || (scanpos + 1 >= last_dot))
4524 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
4525 if (*last_tld_char == '?')
4528 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
4535 if (!result) return FALSE;
4537 if (*ep_ && bp_ != start && *(bp_ - 1) == '"' && *(ep_) == '"'
4538 && *(ep_ + 1) == ' ' && *(ep_ + 2) == '<'
4539 && IS_RFC822_CHAR(*(ep_ + 3))) {
4540 /* this informative part with an @ in it is
4541 * followed by the email address */
4544 /* go to matching '>' (or next non-rfc822 char, like \n) */
4545 for (; *ep_ != '>' && *ep != '\0' && IS_RFC822_CHAR(*ep_); ep_++)
4548 /* include the bracket */
4549 if (*ep_ == '>') ep_++;
4551 /* include the leading quote */
4559 /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
4560 if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
4563 /* see if this is <bracketed>; in this case we also scan for the informative part. */
4564 if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
4567 #define FULL_STACK() ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
4568 #define IN_STACK() (ptr > closure_stack)
4569 /* has underrun check */
4570 #define POP_STACK() if(IN_STACK()) --ptr
4571 /* has overrun check */
4572 #define PUSH_STACK(c) if(!FULL_STACK()) *ptr++ = (c); else return TRUE
4573 /* has underrun check */
4574 #define PEEK_STACK() (IN_STACK() ? *(ptr - 1) : 0)
4578 /* scan for the informative part. */
4579 for (bp_ -= 2; bp_ >= start; bp_--) {
4580 /* if closure on the stack keep scanning */
4581 if (PEEK_STACK() == *bp_) {
4585 if (!IN_STACK() && (*bp_ == '\'' || *bp_ == '"')) {
4590 /* if nothing in the closure stack, do the special conditions
4591 * the following if..else expression simply checks whether
4592 * a token is acceptable. if not acceptable, the clause
4593 * should terminate the loop with a 'break' */
4594 if (!PEEK_STACK()) {
4596 && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
4597 && (((bp_ + 1) < ep_) && isalnum(*(bp_ + 1)))) {
4598 /* hyphens are allowed, but only in
4600 } else if (strchr(" \"'", *bp_)) {
4601 /* but anything not being a punctiation
4604 break; /* anything else is rejected */
4611 /* scan forward (should start with an alnum) */
4612 for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
4628 #undef IS_ASCII_ALNUM
4629 #undef IS_RFC822_CHAR
4631 gchar *make_email_string(const gchar *bp, const gchar *ep)
4633 /* returns a mailto: URI; mailto: is also used to detect the
4634 * uri type later on in the button_pressed signal handler */
4638 tmp = g_strndup(bp, ep - bp);
4639 result = g_strconcat("mailto:", tmp, NULL);
4645 gchar *make_http_string(const gchar *bp, const gchar *ep)
4647 /* returns an http: URI; */
4651 while (bp && *bp && g_ascii_isspace(*bp))
4653 tmp = g_strndup(bp, ep - bp);
4654 result = g_strconcat("http://", tmp, NULL);
4660 static gchar *mailcap_get_command_in_file(const gchar *path, const gchar *type, const gchar *file_to_open)
4662 FILE *fp = g_fopen(path, "rb");
4663 gchar buf[BUFFSIZE];
4664 gchar *result = NULL;
4667 while (fgets(buf, sizeof (buf), fp) != NULL) {
4668 gchar **parts = g_strsplit(buf, ";", 3);
4669 gchar *trimmed = parts[0];
4670 while (trimmed[0] == ' ' || trimmed[0] == '\t')
4672 while (trimmed[strlen(trimmed)-1] == ' ' || trimmed[strlen(trimmed)-1] == '\t')
4673 trimmed[strlen(trimmed)-1] = '\0';
4675 if (!strcmp(trimmed, type)) {
4676 gboolean needsterminal = FALSE;
4677 if (parts[2] && strstr(parts[2], "needsterminal")) {
4678 needsterminal = TRUE;
4680 if (parts[2] && strstr(parts[2], "test=")) {
4681 gchar *orig_testcmd = g_strdup(strstr(parts[2], "test=")+5);
4682 gchar *testcmd = orig_testcmd;
4683 if (strstr(testcmd,";"))
4684 *(strstr(testcmd,";")) = '\0';
4685 while (testcmd[0] == ' ' || testcmd[0] == '\t')
4687 while (testcmd[strlen(testcmd)-1] == '\n')
4688 testcmd[strlen(testcmd)-1] = '\0';
4689 while (testcmd[strlen(testcmd)-1] == '\r')
4690 testcmd[strlen(testcmd)-1] = '\0';
4691 while (testcmd[strlen(testcmd)-1] == ' ' || testcmd[strlen(testcmd)-1] == '\t')
4692 testcmd[strlen(testcmd)-1] = '\0';
4694 if (strstr(testcmd, "%s")) {
4695 gchar *tmp = g_strdup_printf(testcmd, file_to_open);
4696 gint res = system(tmp);
4698 g_free(orig_testcmd);
4705 gint res = system(testcmd);
4706 g_free(orig_testcmd);
4716 while (trimmed[0] == ' ' || trimmed[0] == '\t')
4718 while (trimmed[strlen(trimmed)-1] == '\n')
4719 trimmed[strlen(trimmed)-1] = '\0';
4720 while (trimmed[strlen(trimmed)-1] == '\r')
4721 trimmed[strlen(trimmed)-1] = '\0';
4722 while (trimmed[strlen(trimmed)-1] == ' ' || trimmed[strlen(trimmed)-1] == '\t')
4723 trimmed[strlen(trimmed)-1] = '\0';
4724 result = g_strdup(trimmed);
4727 /* if there are no single quotes around %s, add them.
4728 * '.*%s.*' is ok, as in display 'png:%s'
4730 if (strstr(result, "%s")
4731 && !(strstr(result, "'") < strstr(result,"%s") &&
4732 strstr(strstr(result,"%s"), "'"))) {
4733 gchar *start = g_strdup(result);
4734 gchar *end = g_strdup(strstr(result, "%s")+2);
4736 *strstr(start, "%s") = '\0';
4737 tmp = g_strconcat(start,"'%s'",end, NULL);
4743 if (needsterminal) {
4744 gchar *tmp = g_strdup_printf("xterm -e %s", result);
4755 gchar *mailcap_get_command_for_type(const gchar *type, const gchar *file_to_open)
4757 gchar *result = NULL;
4761 path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap", NULL);
4762 result = mailcap_get_command_in_file(path, type, file_to_open);
4766 result = mailcap_get_command_in_file("/etc/mailcap", type, file_to_open);
4770 void mailcap_update_default(const gchar *type, const gchar *command)
4772 gchar *path = NULL, *outpath = NULL;
4773 path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap", NULL);
4774 outpath = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap.new", NULL);
4775 FILE *fp = g_fopen(path, "rb");
4776 FILE *outfp = g_fopen(outpath, "wb");
4777 gchar buf[BUFFSIZE];
4778 gboolean err = FALSE;
4786 while (fp && fgets(buf, sizeof (buf), fp) != NULL) {
4787 gchar **parts = g_strsplit(buf, ";", 3);
4788 gchar *trimmed = parts[0];
4789 while (trimmed[0] == ' ')
4791 while (trimmed[strlen(trimmed)-1] == ' ')
4792 trimmed[strlen(trimmed)-1] = '\0';
4794 if (!strcmp(trimmed, type)) {
4799 if(fputs(buf, outfp) == EOF) {
4806 if (fprintf(outfp, "%s; %s\n", type, command) < 0)
4812 if (fclose(outfp) == EOF)
4816 g_rename(outpath, path);
4822 gint copy_dir(const gchar *src, const gchar *dst)
4827 if ((dir = g_dir_open(src, 0, NULL)) == NULL) {
4828 g_warning("failed to open directory: %s\n", src);
4832 if (make_dir(dst) < 0)
4835 while ((name = g_dir_read_name(dir)) != NULL) {
4836 gchar *old_file, *new_file;
4837 old_file = g_strconcat(src, G_DIR_SEPARATOR_S, name, NULL);
4838 new_file = g_strconcat(dst, G_DIR_SEPARATOR_S, name, NULL);
4839 debug_print("copying: %s -> %s\n", old_file, new_file);
4840 if (g_file_test(old_file, G_FILE_TEST_IS_REGULAR)) {
4841 gint r = copy_file(old_file, new_file, TRUE);
4848 /* Windows has no symlinks. Or well, Vista seems to
4849 have something like this but the semantics might be
4850 different. Thus we don't use it under Windows. */
4851 else if (g_file_test(old_file, G_FILE_TEST_IS_SYMLINK)) {
4854 gchar *target = g_file_read_link(old_file, &error);
4856 r = symlink(target, new_file);
4863 #endif /*G_OS_WIN32*/
4864 else if (g_file_test(old_file, G_FILE_TEST_IS_DIR)) {
4865 gint r = copy_dir(old_file, new_file);
4876 /* crude test to see if a file is an email. */
4877 gboolean file_is_email (const gchar *filename)
4883 if (filename == NULL)
4885 if ((fp = g_fopen(filename, "rb")) == NULL)
4887 while (i < 60 && score < 3
4888 && fgets(buffer, sizeof (buffer), fp) > 0) {
4889 if (!strncmp(buffer, "From:", strlen("From:")))
4891 else if (!strncmp(buffer, "Date:", strlen("Date:")))
4893 else if (!strncmp(buffer, "Message-ID:", strlen("Message-ID:")))
4895 else if (!strncmp(buffer, "Subject:", strlen("Subject:")))
4900 return (score >= 3);
4903 gboolean sc_g_list_bigger(GList *list, gint max)
4907 while (cur && i <= max+1) {
4914 gboolean sc_g_slist_bigger(GSList *list, gint max)
4918 while (cur && i <= max+1) {
4925 const gchar *daynames[] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL};
4926 const gchar *monthnames[] = {NULL, NULL, NULL, NULL, NULL, NULL,
4927 NULL, NULL, NULL, NULL, NULL, NULL};
4928 const gchar *s_daynames[] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL};
4929 const gchar *s_monthnames[] = {NULL, NULL, NULL, NULL, NULL, NULL,
4930 NULL, NULL, NULL, NULL, NULL, NULL};
4932 gint daynames_len[] = {0,0,0,0,0,0,0};
4933 gint monthnames_len[] = {0,0,0,0,0,0,
4935 gint s_daynames_len[] = {0,0,0,0,0,0,0};
4936 gint s_monthnames_len[] = {0,0,0,0,0,0,
4938 const gchar *s_am_up = NULL;
4939 const gchar *s_pm_up = NULL;
4940 const gchar *s_am_low = NULL;
4941 const gchar *s_pm_low = NULL;
4943 gint s_am_up_len = 0;
4944 gint s_pm_up_len = 0;
4945 gint s_am_low_len = 0;
4946 gint s_pm_low_len = 0;
4948 const gchar *def_loc_format = NULL;
4949 const gchar *date_loc_format = NULL;
4950 const gchar *time_loc_format = NULL;
4951 const gchar *time_am_pm = NULL;
4953 static gboolean time_names_init_done = FALSE;
4955 static void init_time_names(void)
4959 daynames[0] = C_("Complete day name for use by strftime", "Sunday");
4960 daynames[1] = C_("Complete day name for use by strftime", "Monday");
4961 daynames[2] = C_("Complete day name for use by strftime", "Tuesday");
4962 daynames[3] = C_("Complete day name for use by strftime", "Wednesday");
4963 daynames[4] = C_("Complete day name for use by strftime", "Thursday");
4964 daynames[5] = C_("Complete day name for use by strftime", "Friday");
4965 daynames[6] = C_("Complete day name for use by strftime", "Saturday");
4967 monthnames[0] = C_("Complete month name for use by strftime", "January");
4968 monthnames[1] = C_("Complete month name for use by strftime", "February");
4969 monthnames[2] = C_("Complete month name for use by strftime", "March");
4970 monthnames[3] = C_("Complete month name for use by strftime", "April");
4971 monthnames[4] = C_("Complete month name for use by strftime", "May");
4972 monthnames[5] = C_("Complete month name for use by strftime", "June");
4973 monthnames[6] = C_("Complete month name for use by strftime", "July");
4974 monthnames[7] = C_("Complete month name for use by strftime", "August");
4975 monthnames[8] = C_("Complete month name for use by strftime", "September");
4976 monthnames[9] = C_("Complete month name for use by strftime", "October");
4977 monthnames[10] = C_("Complete month name for use by strftime", "November");
4978 monthnames[11] = C_("Complete month name for use by strftime", "December");
4980 s_daynames[0] = C_("Abbr. day name for use by strftime", "Sun");
4981 s_daynames[1] = C_("Abbr. day name for use by strftime", "Mon");
4982 s_daynames[2] = C_("Abbr. day name for use by strftime", "Tue");
4983 s_daynames[3] = C_("Abbr. day name for use by strftime", "Wed");
4984 s_daynames[4] = C_("Abbr. day name for use by strftime", "Thu");
4985 s_daynames[5] = C_("Abbr. day name for use by strftime", "Fri");
4986 s_daynames[6] = C_("Abbr. day name for use by strftime", "Sat");
4988 s_monthnames[0] = C_("Abbr. month name for use by strftime", "Jan");
4989 s_monthnames[1] = C_("Abbr. month name for use by strftime", "Feb");
4990 s_monthnames[2] = C_("Abbr. month name for use by strftime", "Mar");
4991 s_monthnames[3] = C_("Abbr. month name for use by strftime", "Apr");
4992 s_monthnames[4] = C_("Abbr. month name for use by strftime", "May");
4993 s_monthnames[5] = C_("Abbr. month name for use by strftime", "Jun");
4994 s_monthnames[6] = C_("Abbr. month name for use by strftime", "Jul");
4995 s_monthnames[7] = C_("Abbr. month name for use by strftime", "Aug");
4996 s_monthnames[8] = C_("Abbr. month name for use by strftime", "Sep");
4997 s_monthnames[9] = C_("Abbr. month name for use by strftime", "Oct");
4998 s_monthnames[10] = C_("Abbr. month name for use by strftime", "Nov");
4999 s_monthnames[11] = C_("Abbr. month name for use by strftime", "Dec");
5001 for (i = 0; i < 7; i++) {
5002 daynames_len[i] = strlen(daynames[i]);
5003 s_daynames_len[i] = strlen(s_daynames[i]);
5005 for (i = 0; i < 12; i++) {
5006 monthnames_len[i] = strlen(monthnames[i]);
5007 s_monthnames_len[i] = strlen(s_monthnames[i]);
5010 s_am_up = C_("For use by strftime (morning)", "AM");
5011 s_pm_up = C_("For use by strftime (afternoon)", "PM");
5012 s_am_low = C_("For use by strftime (morning, lowercase)", "am");
5013 s_pm_low = C_("For use by strftime (afternoon, lowercase)", "pm");
5015 s_am_up_len = strlen(s_am_up);
5016 s_pm_up_len = strlen(s_pm_up);
5017 s_am_low_len = strlen(s_am_low);
5018 s_pm_low_len = strlen(s_pm_low);
5020 def_loc_format = C_("For use by strftime (default date+time format)", "%a %b %e %H:%M:%S %Y");
5021 date_loc_format = C_("For use by strftime (default date format)", "%m/%d/%y");
5022 time_loc_format = C_("For use by strftime (default time format)", "%H:%M:%S");
5024 time_am_pm = C_("For use by strftime (default 12-hour time format)", "%I:%M:%S %p");
5026 time_names_init_done = TRUE;
5029 #define CHECK_SIZE() { \
5030 total_done += len; \
5031 if (total_done >= buflen) { \
5032 buf[buflen-1] = '\0'; \
5037 size_t fast_strftime(gchar *buf, gint buflen, const gchar *format, struct tm *lt)
5039 gchar *curpos = buf;
5040 gint total_done = 0;
5041 gchar subbuf[64], subfmt[64];
5042 static time_t last_tzset = (time_t)0;
5044 if (!time_names_init_done)
5047 if (format == NULL || lt == NULL)
5050 if (last_tzset != time(NULL)) {
5052 last_tzset = time(NULL);
5055 if (*format == '%') {
5056 gint len = 0, tmp = 0;
5060 len = 1; CHECK_SIZE();
5064 len = s_daynames_len[lt->tm_wday]; CHECK_SIZE();
5065 strncpy2(curpos, s_daynames[lt->tm_wday], buflen - total_done);
5068 len = daynames_len[lt->tm_wday]; CHECK_SIZE();
5069 strncpy2(curpos, daynames[lt->tm_wday], buflen - total_done);
5073 len = s_monthnames_len[lt->tm_mon]; CHECK_SIZE();
5074 strncpy2(curpos, s_monthnames[lt->tm_mon], buflen - total_done);
5077 len = monthnames_len[lt->tm_mon]; CHECK_SIZE();
5078 strncpy2(curpos, monthnames[lt->tm_mon], buflen - total_done);
5081 fast_strftime(subbuf, 64, def_loc_format, lt);
5082 len = strlen(subbuf); CHECK_SIZE();
5083 strncpy2(curpos, subbuf, buflen - total_done);
5086 total_done += 2; CHECK_SIZE();
5087 tmp = (lt->tm_year + 1900)/100;
5088 *curpos++ = '0'+(tmp / 10);
5089 *curpos++ = '0'+(tmp % 10);
5092 total_done += 2; CHECK_SIZE();
5093 *curpos++ = '0'+(lt->tm_mday / 10);
5094 *curpos++ = '0'+(lt->tm_mday % 10);
5097 total_done += 8; CHECK_SIZE();
5098 *curpos++ = '0'+((lt->tm_mon+1) / 10);
5099 *curpos++ = '0'+((lt->tm_mon+1) % 10);
5101 *curpos++ = '0'+(lt->tm_mday / 10);
5102 *curpos++ = '0'+(lt->tm_mday % 10);
5104 tmp = lt->tm_year%100;
5105 *curpos++ = '0'+(tmp / 10);
5106 *curpos++ = '0'+(tmp % 10);
5109 len = 2; CHECK_SIZE();
5110 snprintf(curpos, buflen - total_done, "%2d", lt->tm_mday);
5113 len = 10; CHECK_SIZE();
5114 snprintf(curpos, buflen - total_done, "%4d-%02d-%02d",
5115 lt->tm_year + 1900, lt->tm_mon +1, lt->tm_mday);
5118 total_done += 2; CHECK_SIZE();
5119 *curpos++ = '0'+(lt->tm_hour / 10);
5120 *curpos++ = '0'+(lt->tm_hour % 10);
5123 total_done += 2; CHECK_SIZE();
5129 *curpos++ = '0'+(tmp / 10);
5130 *curpos++ = '0'+(tmp % 10);
5133 len = 3; CHECK_SIZE();
5134 snprintf(curpos, buflen - total_done, "%03d", lt->tm_yday+1);
5137 len = 2; CHECK_SIZE();
5138 snprintf(curpos, buflen - total_done, "%2d", lt->tm_hour);
5141 len = 2; CHECK_SIZE();
5147 snprintf(curpos, buflen - total_done, "%2d", tmp);
5150 total_done += 2; CHECK_SIZE();
5151 tmp = lt->tm_mon + 1;
5152 *curpos++ = '0'+(tmp / 10);
5153 *curpos++ = '0'+(tmp % 10);
5156 total_done += 2; CHECK_SIZE();
5157 *curpos++ = '0'+(lt->tm_min / 10);
5158 *curpos++ = '0'+(lt->tm_min % 10);
5161 len = 1; CHECK_SIZE();
5165 if (lt->tm_hour >= 12) {
5166 len = s_pm_up_len; CHECK_SIZE();
5167 snprintf(curpos, buflen-total_done, "%s", s_pm_up);
5169 len = s_am_up_len; CHECK_SIZE();
5170 snprintf(curpos, buflen-total_done, "%s", s_am_up);
5174 if (lt->tm_hour >= 12) {
5175 len = s_pm_low_len; CHECK_SIZE();
5176 snprintf(curpos, buflen-total_done, "%s", s_pm_low);
5178 len = s_am_low_len; CHECK_SIZE();
5179 snprintf(curpos, buflen-total_done, "%s", s_am_low);
5183 fast_strftime(subbuf, 64, time_am_pm, lt);
5184 len = strlen(subbuf); CHECK_SIZE();
5185 strncpy2(curpos, subbuf, buflen - total_done);
5188 total_done += 5; CHECK_SIZE();
5189 *curpos++ = '0'+(lt->tm_hour / 10);
5190 *curpos++ = '0'+(lt->tm_hour % 10);
5192 *curpos++ = '0'+(lt->tm_min / 10);
5193 *curpos++ = '0'+(lt->tm_min % 10);
5196 snprintf(subbuf, 64, "%ld", mktime(lt));
5197 len = strlen(subbuf); CHECK_SIZE();
5198 strncpy2(curpos, subbuf, buflen - total_done);
5201 total_done += 2; CHECK_SIZE();
5202 *curpos++ = '0'+(lt->tm_sec / 10);
5203 *curpos++ = '0'+(lt->tm_sec % 10);
5206 len = 1; CHECK_SIZE();
5210 total_done += 8; CHECK_SIZE();
5211 *curpos++ = '0'+(lt->tm_hour / 10);
5212 *curpos++ = '0'+(lt->tm_hour % 10);
5214 *curpos++ = '0'+(lt->tm_min / 10);
5215 *curpos++ = '0'+(lt->tm_min % 10);
5217 *curpos++ = '0'+(lt->tm_sec / 10);
5218 *curpos++ = '0'+(lt->tm_sec % 10);
5221 len = 1; CHECK_SIZE();
5222 snprintf(curpos, buflen - total_done, "%d", lt->tm_wday == 0 ? 7: lt->tm_wday);
5225 len = 1; CHECK_SIZE();
5226 snprintf(curpos, buflen - total_done, "%d", lt->tm_wday);
5229 fast_strftime(subbuf, 64, date_loc_format, lt);
5230 len = strlen(subbuf); CHECK_SIZE();
5231 strncpy2(curpos, subbuf, buflen - total_done);
5234 fast_strftime(subbuf, 64, time_loc_format, lt);
5235 len = strlen(subbuf); CHECK_SIZE();
5236 strncpy2(curpos, subbuf, buflen - total_done);
5239 total_done += 2; CHECK_SIZE();
5240 tmp = lt->tm_year%100;
5241 *curpos++ = '0'+(tmp / 10);
5242 *curpos++ = '0'+(tmp % 10);
5245 len = 4; CHECK_SIZE();
5246 snprintf(curpos, buflen - total_done, "%4d", lt->tm_year + 1900);
5256 /* let these complicated ones be done with the libc */
5257 snprintf(subfmt, 64, "%%%c", *format);
5258 strftime(subbuf, 64, subfmt, lt);
5259 len = strlen(subbuf); CHECK_SIZE();
5260 strncpy2(curpos, subbuf, buflen - total_done);
5264 /* let these complicated modifiers be done with the libc */
5265 snprintf(subfmt, 64, "%%%c%c", *format, *(format+1));
5266 strftime(subbuf, 64, subfmt, lt);
5267 len = strlen(subbuf); CHECK_SIZE();
5268 strncpy2(curpos, subbuf, buflen - total_done);
5272 if (format && *format)
5273 g_warning("format error (%c)", *format);
5280 int len = 1; CHECK_SIZE();
5281 *curpos++ = *format++;
5288 gboolean prefs_common_get_use_shred(void);
5292 #define WEXITSTATUS(x) (x)
5295 int claws_unlink(const gchar *filename)
5298 static int found_shred = -1;
5299 static const gchar *args[4];
5301 if (filename == NULL)
5304 if (prefs_common_get_use_shred()) {
5305 if (found_shred == -1) {
5307 args[0] = g_find_program_in_path("shred");
5308 debug_print("found shred: %s\n", args[0]);
5309 found_shred = (args[0] != NULL) ? 1:0;
5313 if (found_shred == 1) {
5314 if (g_stat(filename, &s) == 0 && S_ISREG(s.st_mode)) {
5315 if (s.st_nlink == 1) {
5318 g_spawn_sync(NULL, (gchar **)args, NULL, 0,
5319 NULL, NULL, NULL, NULL, &status, NULL);
5320 debug_print("%s %s exited with status %d\n",
5321 args[0], filename, WEXITSTATUS(status));
5322 if (truncate(filename, 0) < 0)
5323 g_warning("couln't truncate");
5328 return g_unlink(filename);
5331 GMutex *cm_mutex_new(void) {
5332 #if GLIB_CHECK_VERSION(2,32,0)
5333 GMutex *m = g_new0(GMutex, 1);
5337 return g_mutex_new();
5341 void cm_mutex_free(GMutex *mutex) {
5342 #if GLIB_CHECK_VERSION(2,32,0)
5343 g_mutex_clear(mutex);
5346 g_mutex_free(mutex);
5350 static gchar *canonical_list_to_file(GSList *list)
5352 GString *result = g_string_new(NULL);
5353 GSList *pathlist = g_slist_reverse(g_slist_copy(list));
5358 result = g_string_append(result, G_DIR_SEPARATOR_S);
5360 if (pathlist->data) {
5361 const gchar *root = (gchar *)pathlist->data;
5362 if (root[0] != '\0' && g_ascii_isalpha(root[0]) &&
5364 /* drive - don't prepend dir separator */
5366 result = g_string_append(result, G_DIR_SEPARATOR_S);
5371 for (cur = pathlist; cur; cur = cur->next) {
5372 result = g_string_append(result, (gchar *)cur->data);
5374 result = g_string_append(result, G_DIR_SEPARATOR_S);
5376 g_slist_free(pathlist);
5379 g_string_free(result, FALSE);
5384 static GSList *cm_split_path(const gchar *filename, int depth)
5387 GSList *canonical_parts = NULL;
5390 gboolean follow_symlinks = TRUE;
5396 errno = EINVAL; /* can't happen, no symlink handling */
5401 if (!g_path_is_absolute(filename)) {
5406 path_parts = g_strsplit(filename, G_DIR_SEPARATOR_S, -1);
5408 for (i = 0; path_parts[i] != NULL; i++) {
5409 if (!strcmp(path_parts[i], ""))
5411 if (!strcmp(path_parts[i], "."))
5413 else if (!strcmp(path_parts[i], "..")) {
5418 else /* Remove the last inserted element */
5420 g_slist_delete_link(canonical_parts,
5425 canonical_parts = g_slist_prepend(canonical_parts,
5426 g_strdup(path_parts[i]));
5428 tmp_path = canonical_list_to_file(canonical_parts);
5430 if(g_stat(tmp_path, &st) < 0) {
5431 if (errno == ENOENT) {
5433 follow_symlinks = FALSE;
5437 slist_free_strings_full(canonical_parts);
5438 g_strfreev(path_parts);
5444 if (follow_symlinks && g_file_test(tmp_path, G_FILE_TEST_IS_SYMLINK)) {
5445 GError *error = NULL;
5446 gchar *target = g_file_read_link(tmp_path, &error);
5448 if (!g_path_is_absolute(target)) {
5449 /* remove the last inserted element */
5451 g_slist_delete_link(canonical_parts,
5453 /* add the target */
5454 canonical_parts = g_slist_prepend(canonical_parts,
5458 /* and get the new target */
5459 target = canonical_list_to_file(canonical_parts);
5462 /* restart from absolute target */
5463 slist_free_strings_full(canonical_parts);
5464 canonical_parts = NULL;
5466 canonical_parts = cm_split_path(target, depth + 1);
5468 g_error_free(error);
5469 if (canonical_parts == NULL) {
5471 g_strfreev(path_parts);
5480 g_strfreev(path_parts);
5481 return canonical_parts;
5485 * Canonicalize a filename, resolving symlinks along the way.
5486 * Returns a negative errno in case of error.
5488 int cm_canonicalize_filename(const gchar *filename, gchar **canonical_name) {
5489 GSList *canonical_parts;
5490 gboolean is_absolute;
5492 if (filename == NULL)
5494 if (canonical_name == NULL)
5496 *canonical_name = NULL;
5498 is_absolute = g_path_is_absolute(filename);
5500 /* Always work on absolute filenames. */
5501 gchar *cur = g_get_current_dir();
5502 gchar *absolute_filename = g_strconcat(cur, G_DIR_SEPARATOR_S,
5505 canonical_parts = cm_split_path(absolute_filename, 0);
5506 g_free(absolute_filename);
5509 canonical_parts = cm_split_path(filename, 0);
5511 if (canonical_parts == NULL)
5514 *canonical_name = canonical_list_to_file(canonical_parts);
5515 slist_free_strings_full(canonical_parts);