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/>.
18 * The code of the g_utf8_substring function below is owned by
19 * Matthias Clasen <matthiasc@src.gnome.org>/<mclasen@redhat.com>
20 * and is got from GLIB 2.30
26 #include "claws-features.h"
34 #include <glib/gi18n.h>
44 #include <sys/param.h>
46 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
54 #include <sys/types.h>
56 # include <sys/wait.h>
63 #include <sys/utsname.h>
76 #include "../codeconv.h"
80 static gboolean debug_mode = FALSE;
82 static GSList *tempfiles=NULL;
85 /* Return true if we are running as root. This function should beused
86 instead of getuid () == 0. */
87 gboolean superuser_p (void)
90 return w32_is_administrator ();
98 #if !GLIB_CHECK_VERSION(2, 7, 0) && !defined(G_OS_UNIX)
99 gint g_chdir(const gchar *path)
102 if (G_WIN32_HAVE_WIDECHAR_API()) {
107 wpath = g_utf8_to_utf16(path, -1, NULL, NULL, NULL);
113 retval = _wchdir(wpath);
125 cp_path = g_locale_from_utf8(path, -1, NULL, NULL, NULL);
126 if (cp_path == NULL) {
131 retval = chdir(cp_path);
144 gint g_chmod(const gchar *path, gint mode)
147 if (G_WIN32_HAVE_WIDECHAR_API()) {
152 wpath = g_utf8_to_utf16(path, -1, NULL, NULL, NULL);
158 retval = _wchmod(wpath, mode);
170 cp_path = g_locale_from_utf8(path, -1, NULL, NULL, NULL);
171 if (cp_path == NULL) {
176 retval = chmod(cp_path, mode);
185 return chmod(path, mode);
189 FILE* g_fopen(const gchar *filename, const gchar *mode)
192 char *name = g_win32_locale_filename_from_utf8(filename);
193 FILE* fp = fopen(name, mode);
197 return fopen(filename, mode);
200 int g_open(const gchar *filename, int flags, int mode)
203 char *name = g_win32_locale_filename_from_utf8(filename);
204 int fd = open(name, flags, mode);
208 return open(filename, flags, mode);
211 #endif /* GLIB_CHECK_VERSION && G_OS_UNIX */
215 gint mkstemp_name(gchar *template, gchar **name_used)
217 static gulong count=0; /* W32-_mktemp only supports up to 27
221 *name_used = g_strdup_printf("%s.%ld",_mktemp(template),count++);
222 tmpfd = g_open (*name_used, (O_CREAT | O_RDWR | O_BINARY),
223 (S_IRUSR | S_IWUSR));
225 tempfiles=g_slist_append(tempfiles, g_strdup(*name_used));
227 perror(g_strdup_printf("cant create %s",*name_used));
233 #endif /* G_OS_WIN32 */
236 gint mkstemp(gchar *template)
239 gint res = mkstemp_name(template, &dummyname);
243 #endif /* G_OS_WIN32 */
245 GSList *slist_copy_deep(GSList *list, GCopyFunc func)
247 #if GLIB_CHECK_VERSION(2, 34, 0)
248 return g_slist_copy_deep(list, func, NULL);
250 GSList *res = g_slist_copy(list);
253 walk->data = func(walk->data, NULL);
260 void list_free_strings(GList *list)
262 list = g_list_first(list);
264 while (list != NULL) {
270 void slist_free_strings(GSList *list)
272 while (list != NULL) {
278 void slist_free_strings_full(GSList *list)
280 #if GLIB_CHECK_VERSION(2,28,0)
281 g_slist_free_full(list, (GDestroyNotify)g_free);
283 g_slist_foreach(list, (GFunc)g_free, NULL);
288 static void hash_free_strings_func(gpointer key, gpointer value, gpointer data)
293 void hash_free_strings(GHashTable *table)
295 g_hash_table_foreach(table, hash_free_strings_func, NULL);
298 gint str_case_equal(gconstpointer v, gconstpointer v2)
300 return g_ascii_strcasecmp((const gchar *)v, (const gchar *)v2) == 0;
303 guint str_case_hash(gconstpointer key)
305 const gchar *p = key;
309 h = g_ascii_tolower(h);
310 for (p += 1; *p != '\0'; p++)
311 h = (h << 5) - h + g_ascii_tolower(*p);
317 void ptr_array_free_strings(GPtrArray *array)
322 cm_return_if_fail(array != NULL);
324 for (i = 0; i < array->len; i++) {
325 str = g_ptr_array_index(array, i);
330 gint to_number(const gchar *nstr)
332 register const gchar *p;
334 if (*nstr == '\0') return -1;
336 for (p = nstr; *p != '\0'; p++)
337 if (!g_ascii_isdigit(*p)) return -1;
342 /* convert integer into string,
343 nstr must be not lower than 11 characters length */
344 gchar *itos_buf(gchar *nstr, gint n)
346 g_snprintf(nstr, 11, "%d", n);
350 /* convert integer into string */
353 static gchar nstr[11];
355 return itos_buf(nstr, n);
358 #define divide(num,divisor,i,d) \
360 i = num >> divisor; \
361 d = num & ((1<<divisor)-1); \
362 d = (d*100) >> divisor; \
367 * \brief Convert a given size in bytes in a human-readable string
369 * \param size The size expressed in bytes to convert in string
370 * \return The string that respresents the size in an human-readable way
372 gchar *to_human_readable(goffset size)
374 static gchar str[14];
375 static gchar *b_format = NULL, *kb_format = NULL,
376 *mb_format = NULL, *gb_format = NULL;
377 register int t = 0, r = 0;
378 if (b_format == NULL) {
380 kb_format = _("%d.%02dKB");
381 mb_format = _("%d.%02dMB");
382 gb_format = _("%.2fGB");
385 if (size < (goffset)1024) {
386 g_snprintf(str, sizeof(str), b_format, (gint)size);
388 } else if (size >> 10 < (goffset)1024) {
389 divide(size, 10, t, r);
390 g_snprintf(str, sizeof(str), kb_format, t, r);
392 } else if (size >> 20 < (goffset)1024) {
393 divide(size, 20, t, r);
394 g_snprintf(str, sizeof(str), mb_format, t, r);
397 g_snprintf(str, sizeof(str), gb_format, (gfloat)(size >> 30));
402 /* strcmp with NULL-checking */
403 gint strcmp2(const gchar *s1, const gchar *s2)
405 if (s1 == NULL || s2 == NULL)
408 return strcmp(s1, s2);
410 /* strstr with NULL-checking */
411 gchar *strstr2(const gchar *s1, const gchar *s2)
413 if (s1 == NULL || s2 == NULL)
416 return strstr(s1, s2);
419 gint path_cmp(const gchar *s1, const gchar *s2)
424 gchar *s1buf, *s2buf;
427 if (s1 == NULL || s2 == NULL) return -1;
428 if (*s1 == '\0' || *s2 == '\0') return -1;
431 s1buf = g_strdup (s1);
432 s2buf = g_strdup (s2);
433 subst_char (s1buf, '/', G_DIR_SEPARATOR);
434 subst_char (s2buf, '/', G_DIR_SEPARATOR);
437 #endif /* !G_OS_WIN32 */
442 if (s1[len1 - 1] == G_DIR_SEPARATOR) len1--;
443 if (s2[len2 - 1] == G_DIR_SEPARATOR) len2--;
445 rc = strncmp(s1, s2, MAX(len1, len2));
449 #endif /* !G_OS_WIN32 */
453 /* remove trailing return code */
454 gchar *strretchomp(gchar *str)
458 if (!*str) return str;
460 for (s = str + strlen(str) - 1;
461 s >= str && (*s == '\n' || *s == '\r');
468 /* remove trailing character */
469 gchar *strtailchomp(gchar *str, gchar tail_char)
473 if (!*str) return str;
474 if (tail_char == '\0') return str;
476 for (s = str + strlen(str) - 1; s >= str && *s == tail_char; s--)
482 /* remove CR (carriage return) */
483 gchar *strcrchomp(gchar *str)
487 if (!*str) return str;
489 s = str + strlen(str) - 1;
490 if (*s == '\n' && s > str && *(s - 1) == '\r') {
498 gint file_strip_crs(const gchar *file)
500 FILE *fp = NULL, *outfp = NULL;
502 gchar *out = get_tmp_file();
506 fp = g_fopen(file, "rb");
510 outfp = g_fopen(out, "wb");
516 while (fgets(buf, sizeof (buf), fp) != NULL) {
518 if (fputs(buf, outfp) == EOF) {
526 if (fclose(outfp) == EOF) {
530 if (move_file(out, file, TRUE) < 0)
542 /* Similar to `strstr' but this function ignores the case of both strings. */
543 gchar *strcasestr(const gchar *haystack, const gchar *needle)
545 size_t haystack_len = strlen(haystack);
547 return strncasestr(haystack, haystack_len, needle);
550 gchar *strncasestr(const gchar *haystack, gint haystack_len, const gchar *needle)
552 register size_t needle_len;
554 needle_len = strlen(needle);
556 if (haystack_len < needle_len || needle_len == 0)
559 while (haystack_len >= needle_len) {
560 if (!g_ascii_strncasecmp(haystack, needle, needle_len))
561 return (gchar *)haystack;
571 gpointer my_memmem(gconstpointer haystack, size_t haystacklen,
572 gconstpointer needle, size_t needlelen)
574 const gchar *haystack_ = (const gchar *)haystack;
575 const gchar *needle_ = (const gchar *)needle;
576 const gchar *haystack_cur = (const gchar *)haystack;
577 size_t haystack_left = haystacklen;
580 return memchr(haystack_, *needle_, haystacklen);
582 while ((haystack_cur = memchr(haystack_cur, *needle_, haystack_left))
584 if (haystacklen - (haystack_cur - haystack_) < needlelen)
586 if (memcmp(haystack_cur + 1, needle_ + 1, needlelen - 1) == 0)
587 return (gpointer)haystack_cur;
590 haystack_left = haystacklen - (haystack_cur - haystack_);
597 /* Copy no more than N characters of SRC to DEST, with NULL terminating. */
598 gchar *strncpy2(gchar *dest, const gchar *src, size_t n)
600 register const gchar *s = src;
601 register gchar *d = dest;
611 /* Examine if next block is non-ASCII string */
612 gboolean is_next_nonascii(const gchar *s)
616 /* skip head space */
617 for (p = s; *p != '\0' && g_ascii_isspace(*p); p++)
619 for (; *p != '\0' && !g_ascii_isspace(*p); p++) {
620 if (*(guchar *)p > 127 || *(guchar *)p < 32)
627 gint get_next_word_len(const gchar *s)
631 for (; *s != '\0' && !g_ascii_isspace(*s); s++, len++)
637 static void trim_subject_for_compare(gchar *str)
641 eliminate_parenthesis(str, '[', ']');
642 eliminate_parenthesis(str, '(', ')');
645 srcp = str + subject_get_prefix_length(str);
647 memmove(str, srcp, strlen(srcp) + 1);
650 static void trim_subject_for_sort(gchar *str)
656 srcp = str + subject_get_prefix_length(str);
658 memmove(str, srcp, strlen(srcp) + 1);
661 /* compare subjects */
662 gint subject_compare(const gchar *s1, const gchar *s2)
666 if (!s1 || !s2) return -1;
667 if (!*s1 || !*s2) return -1;
669 Xstrdup_a(str1, s1, return -1);
670 Xstrdup_a(str2, s2, return -1);
672 trim_subject_for_compare(str1);
673 trim_subject_for_compare(str2);
675 if (!*str1 || !*str2) return -1;
677 return strcmp(str1, str2);
680 gint subject_compare_for_sort(const gchar *s1, const gchar *s2)
684 if (!s1 || !s2) return -1;
686 Xstrdup_a(str1, s1, return -1);
687 Xstrdup_a(str2, s2, return -1);
689 trim_subject_for_sort(str1);
690 trim_subject_for_sort(str2);
692 return g_utf8_collate(str1, str2);
695 void trim_subject(gchar *str)
697 register gchar *srcp;
703 srcp = str + subject_get_prefix_length(str);
708 } else if (*srcp == '(') {
720 else if (*srcp == cl)
727 while (g_ascii_isspace(*srcp)) srcp++;
728 memmove(str, srcp, strlen(srcp) + 1);
731 void eliminate_parenthesis(gchar *str, gchar op, gchar cl)
733 register gchar *srcp, *destp;
738 while ((destp = strchr(destp, op))) {
744 else if (*srcp == cl)
750 while (g_ascii_isspace(*srcp)) srcp++;
751 memmove(destp, srcp, strlen(srcp) + 1);
755 void extract_parenthesis(gchar *str, gchar op, gchar cl)
757 register gchar *srcp, *destp;
762 while ((srcp = strchr(destp, op))) {
765 memmove(destp, srcp + 1, strlen(srcp));
770 else if (*destp == cl)
782 static void extract_parenthesis_with_skip_quote(gchar *str, gchar quote_chr,
785 register gchar *srcp, *destp;
787 gboolean in_quote = FALSE;
791 while ((srcp = strchr_with_skip_quote(destp, quote_chr, op))) {
794 memmove(destp, srcp + 1, strlen(srcp));
797 if (*destp == op && !in_quote)
799 else if (*destp == cl && !in_quote)
801 else if (*destp == quote_chr)
813 void extract_quote(gchar *str, gchar quote_chr)
817 if ((str = strchr(str, quote_chr))) {
819 while ((p = strchr(p + 1, quote_chr)) && (p[-1] == '\\')) {
820 memmove(p - 1, p, strlen(p) + 1);
825 memmove(str, str + 1, p - str);
830 /* Returns a newly allocated string with all quote_chr not at the beginning
831 or the end of str escaped with '\' or the given str if not required. */
832 gchar *escape_internal_quotes(gchar *str, gchar quote_chr)
834 register gchar *p, *q;
838 if (str == NULL || *str == '\0')
841 /* search for unescaped quote_chr */
846 if (*p == quote_chr && *(p - 1) != '\\' && *(p + 1) != '\0')
850 if (!k) /* nothing to escape */
853 /* unescaped quote_chr found */
854 qstr = g_malloc(l + k + 1);
857 if (*p == quote_chr) {
862 if (*p == quote_chr && *(p - 1) != '\\' && *(p + 1) != '\0')
871 void eliminate_address_comment(gchar *str)
873 register gchar *srcp, *destp;
878 while ((destp = strchr(destp, '"'))) {
879 if ((srcp = strchr(destp + 1, '"'))) {
884 while (g_ascii_isspace(*srcp)) srcp++;
885 memmove(destp, srcp, strlen(srcp) + 1);
895 while ((destp = strchr_with_skip_quote(destp, '"', '('))) {
901 else if (*srcp == ')')
907 while (g_ascii_isspace(*srcp)) srcp++;
908 memmove(destp, srcp, strlen(srcp) + 1);
912 gchar *strchr_with_skip_quote(const gchar *str, gint quote_chr, gint c)
914 gboolean in_quote = FALSE;
917 if (*str == c && !in_quote)
919 if (*str == quote_chr)
927 void extract_address(gchar *str)
929 eliminate_address_comment(str);
930 if (strchr_with_skip_quote(str, '"', '<'))
931 extract_parenthesis_with_skip_quote(str, '"', '<', '>');
935 void extract_list_id_str(gchar *str)
937 if (strchr_with_skip_quote(str, '"', '<'))
938 extract_parenthesis_with_skip_quote(str, '"', '<', '>');
942 static GSList *address_list_append_real(GSList *addr_list, const gchar *str, gboolean removecomments)
947 if (!str) return addr_list;
949 Xstrdup_a(work, str, return addr_list);
952 eliminate_address_comment(work);
955 while (workp && *workp) {
958 if ((p = strchr_with_skip_quote(workp, '"', ','))) {
964 if (removecomments && strchr_with_skip_quote(workp, '"', '<'))
965 extract_parenthesis_with_skip_quote
966 (workp, '"', '<', '>');
970 addr_list = g_slist_append(addr_list, g_strdup(workp));
978 GSList *address_list_append(GSList *addr_list, const gchar *str)
980 return address_list_append_real(addr_list, str, TRUE);
983 GSList *address_list_append_with_comments(GSList *addr_list, const gchar *str)
985 return address_list_append_real(addr_list, str, FALSE);
988 GSList *references_list_prepend(GSList *msgid_list, const gchar *str)
992 if (!str) return msgid_list;
995 while (strp && *strp) {
996 const gchar *start, *end;
999 if ((start = strchr(strp, '<')) != NULL) {
1000 end = strchr(start + 1, '>');
1005 msgid = g_strndup(start + 1, end - start - 1);
1008 msgid_list = g_slist_prepend(msgid_list, msgid);
1018 GSList *references_list_append(GSList *msgid_list, const gchar *str)
1022 list = references_list_prepend(NULL, str);
1023 list = g_slist_reverse(list);
1024 msgid_list = g_slist_concat(msgid_list, list);
1029 GSList *newsgroup_list_append(GSList *group_list, const gchar *str)
1034 if (!str) return group_list;
1036 Xstrdup_a(work, str, return group_list);
1040 while (workp && *workp) {
1043 if ((p = strchr_with_skip_quote(workp, '"', ','))) {
1051 group_list = g_slist_append(group_list,
1060 GList *add_history(GList *list, const gchar *str)
1065 cm_return_val_if_fail(str != NULL, list);
1067 old = g_list_find_custom(list, (gpointer)str, (GCompareFunc)strcmp2);
1070 list = g_list_remove(list, old->data);
1072 } else if (g_list_length(list) >= MAX_HISTORY_SIZE) {
1075 last = g_list_last(list);
1077 oldstr = last->data;
1078 list = g_list_remove(list, last->data);
1083 list = g_list_prepend(list, g_strdup(str));
1088 void remove_return(gchar *str)
1090 register gchar *p = str;
1093 if (*p == '\n' || *p == '\r')
1094 memmove(p, p + 1, strlen(p));
1100 void remove_space(gchar *str)
1102 register gchar *p = str;
1107 while (g_ascii_isspace(*(p + spc)))
1110 memmove(p, p + spc, strlen(p + spc) + 1);
1116 void unfold_line(gchar *str)
1118 register gchar *p = str;
1122 if (*p == '\n' || *p == '\r') {
1125 while (g_ascii_isspace(*(p + spc)))
1128 memmove(p, p + spc, strlen(p + spc) + 1);
1134 void subst_char(gchar *str, gchar orig, gchar subst)
1136 register gchar *p = str;
1145 void subst_chars(gchar *str, gchar *orig, gchar subst)
1147 register gchar *p = str;
1150 if (strchr(orig, *p) != NULL)
1156 void subst_for_filename(gchar *str)
1161 subst_chars(str, "\t\r\n\\/*:", '_');
1163 subst_chars(str, "\t\r\n\\/*", '_');
1167 void subst_for_shellsafe_filename(gchar *str)
1171 subst_for_filename(str);
1172 subst_chars(str, " \"'|&;()<>'!{}[]",'_');
1175 gboolean is_ascii_str(const gchar *str)
1177 const guchar *p = (const guchar *)str;
1179 while (*p != '\0') {
1180 if (*p != '\t' && *p != ' ' &&
1181 *p != '\r' && *p != '\n' &&
1182 (*p < 32 || *p >= 127))
1190 static const gchar * line_has_quote_char_last(const gchar * str, const gchar *quote_chars)
1192 gchar * position = NULL;
1193 gchar * tmp_pos = NULL;
1196 if (quote_chars == NULL)
1199 for (i = 0; i < strlen(quote_chars); i++) {
1200 tmp_pos = strrchr (str, quote_chars[i]);
1202 || (tmp_pos != NULL && position <= tmp_pos) )
1208 gint get_quote_level(const gchar *str, const gchar *quote_chars)
1210 const gchar *first_pos;
1211 const gchar *last_pos;
1212 const gchar *p = str;
1213 gint quote_level = -1;
1215 /* speed up line processing by only searching to the last '>' */
1216 if ((first_pos = line_has_quote_char(str, quote_chars)) != NULL) {
1217 /* skip a line if it contains a '<' before the initial '>' */
1218 if (memchr(str, '<', first_pos - str) != NULL)
1220 last_pos = line_has_quote_char_last(first_pos, quote_chars);
1224 while (p <= last_pos) {
1225 while (p < last_pos) {
1226 if (g_ascii_isspace(*p))
1232 if (strchr(quote_chars, *p))
1234 else if (*p != '-' && !g_ascii_isspace(*p) && p <= last_pos) {
1235 /* any characters are allowed except '-','<' and space */
1236 while (*p != '-' && *p != '<'
1237 && !strchr(quote_chars, *p)
1238 && !g_ascii_isspace(*p)
1241 if (strchr(quote_chars, *p))
1253 gint check_line_length(const gchar *str, gint max_chars, gint *line)
1255 const gchar *p = str, *q;
1256 gint cur_line = 0, len;
1258 while ((q = strchr(p, '\n')) != NULL) {
1260 if (len > max_chars) {
1270 if (len > max_chars) {
1279 const gchar * line_has_quote_char(const gchar * str, const gchar *quote_chars)
1281 gchar * position = NULL;
1282 gchar * tmp_pos = NULL;
1285 if (quote_chars == NULL)
1288 for (i = 0; i < strlen(quote_chars); i++) {
1289 tmp_pos = strchr (str, quote_chars[i]);
1291 || (tmp_pos != NULL && position >= tmp_pos) )
1297 static gchar *strstr_with_skip_quote(const gchar *haystack, const gchar *needle)
1299 register guint haystack_len, needle_len;
1300 gboolean in_squote = FALSE, in_dquote = FALSE;
1302 haystack_len = strlen(haystack);
1303 needle_len = strlen(needle);
1305 if (haystack_len < needle_len || needle_len == 0)
1308 while (haystack_len >= needle_len) {
1309 if (!in_squote && !in_dquote &&
1310 !strncmp(haystack, needle, needle_len))
1311 return (gchar *)haystack;
1313 /* 'foo"bar"' -> foo"bar"
1314 "foo'bar'" -> foo'bar' */
1315 if (*haystack == '\'') {
1318 else if (!in_dquote)
1320 } else if (*haystack == '\"') {
1323 else if (!in_squote)
1325 } else if (*haystack == '\\') {
1337 gchar **strsplit_with_quote(const gchar *str, const gchar *delim,
1340 GSList *string_list = NULL, *slist;
1341 gchar **str_array, *s, *new_str;
1342 guint i, n = 1, len;
1344 cm_return_val_if_fail(str != NULL, NULL);
1345 cm_return_val_if_fail(delim != NULL, NULL);
1348 max_tokens = G_MAXINT;
1350 s = strstr_with_skip_quote(str, delim);
1352 guint delimiter_len = strlen(delim);
1356 new_str = g_strndup(str, len);
1358 if (new_str[0] == '\'' || new_str[0] == '\"') {
1359 if (new_str[len - 1] == new_str[0]) {
1360 new_str[len - 1] = '\0';
1361 memmove(new_str, new_str + 1, len - 1);
1364 string_list = g_slist_prepend(string_list, new_str);
1366 str = s + delimiter_len;
1367 s = strstr_with_skip_quote(str, delim);
1368 } while (--max_tokens && s);
1372 new_str = g_strdup(str);
1373 if (new_str[0] == '\'' || new_str[0] == '\"') {
1375 if (new_str[len - 1] == new_str[0]) {
1376 new_str[len - 1] = '\0';
1377 memmove(new_str, new_str + 1, len - 1);
1380 string_list = g_slist_prepend(string_list, new_str);
1384 str_array = g_new(gchar*, n);
1388 str_array[i--] = NULL;
1389 for (slist = string_list; slist; slist = slist->next)
1390 str_array[i--] = slist->data;
1392 g_slist_free(string_list);
1397 gchar *get_abbrev_newsgroup_name(const gchar *group, gint len)
1399 gchar *abbrev_group;
1401 const gchar *p = group;
1404 cm_return_val_if_fail(group != NULL, NULL);
1406 last = group + strlen(group);
1407 abbrev_group = ap = g_malloc(strlen(group) + 1);
1412 if ((ap - abbrev_group) + (last - p) > len && strchr(p, '.')) {
1414 while (*p != '.') p++;
1417 return abbrev_group;
1422 return abbrev_group;
1425 gchar *trim_string(const gchar *str, gint len)
1427 const gchar *p = str;
1432 if (!str) return NULL;
1433 if (strlen(str) <= len)
1434 return g_strdup(str);
1435 if (g_utf8_validate(str, -1, NULL) == FALSE)
1436 return g_strdup(str);
1438 while (*p != '\0') {
1439 mb_len = g_utf8_skip[*(guchar *)p];
1442 else if (new_len + mb_len > len)
1449 Xstrndup_a(new_str, str, new_len, return g_strdup(str));
1450 return g_strconcat(new_str, "...", NULL);
1453 GList *uri_list_extract_filenames(const gchar *uri_list)
1455 GList *result = NULL;
1457 gchar *escaped_utf8uri;
1463 while (g_ascii_isspace(*p)) p++;
1464 if (!strncmp(p, "file:", 5)) {
1467 while (*q && *q != '\n' && *q != '\r') q++;
1470 gchar *file, *locale_file = NULL;
1472 while (q > p && g_ascii_isspace(*q))
1474 Xalloca(escaped_utf8uri, q - p + 2,
1476 Xalloca(file, q - p + 2,
1479 strncpy(escaped_utf8uri, p, q - p + 1);
1480 escaped_utf8uri[q - p + 1] = '\0';
1481 decode_uri(file, escaped_utf8uri);
1483 * g_filename_from_uri() rejects escaped/locale encoded uri
1484 * string which come from Nautilus.
1487 if (g_utf8_validate(file, -1, NULL))
1489 = conv_codeset_strdup(
1492 conv_get_locale_charset_str());
1494 locale_file = g_strdup(file + 5);
1496 locale_file = g_filename_from_uri(file, NULL, NULL);
1498 result = g_list_append(result, locale_file);
1502 p = strchr(p, '\n');
1509 /* Converts two-digit hexadecimal to decimal. Used for unescaping escaped
1512 static gint axtoi(const gchar *hexstr)
1514 gint hi, lo, result;
1517 if ('0' <= hi && hi <= '9') {
1520 if ('a' <= hi && hi <= 'f') {
1523 if ('A' <= hi && hi <= 'F') {
1528 if ('0' <= lo && lo <= '9') {
1531 if ('a' <= lo && lo <= 'f') {
1534 if ('A' <= lo && lo <= 'F') {
1537 result = lo + (16 * hi);
1541 gboolean is_uri_string(const gchar *str)
1543 while (str && *str && g_ascii_isspace(*str))
1545 return (g_ascii_strncasecmp(str, "http://", 7) == 0 ||
1546 g_ascii_strncasecmp(str, "https://", 8) == 0 ||
1547 g_ascii_strncasecmp(str, "ftp://", 6) == 0 ||
1548 g_ascii_strncasecmp(str, "www.", 4) == 0);
1551 gchar *get_uri_path(const gchar *uri)
1553 while (uri && *uri && g_ascii_isspace(*uri))
1555 if (g_ascii_strncasecmp(uri, "http://", 7) == 0)
1556 return (gchar *)(uri + 7);
1557 else if (g_ascii_strncasecmp(uri, "https://", 8) == 0)
1558 return (gchar *)(uri + 8);
1559 else if (g_ascii_strncasecmp(uri, "ftp://", 6) == 0)
1560 return (gchar *)(uri + 6);
1562 return (gchar *)uri;
1565 gint get_uri_len(const gchar *str)
1569 if (is_uri_string(str)) {
1570 for (p = str; *p != '\0'; p++) {
1571 if (!g_ascii_isgraph(*p) || strchr("()<>\"", *p))
1580 /* Decodes URL-Encoded strings (i.e. strings in which spaces are replaced by
1581 * plusses, and escape characters are used)
1583 void decode_uri_with_plus(gchar *decoded_uri, const gchar *encoded_uri, gboolean with_plus)
1585 gchar *dec = decoded_uri;
1586 const gchar *enc = encoded_uri;
1591 if (isxdigit((guchar)enc[0]) &&
1592 isxdigit((guchar)enc[1])) {
1598 if (with_plus && *enc == '+')
1610 void decode_uri(gchar *decoded_uri, const gchar *encoded_uri)
1612 decode_uri_with_plus(decoded_uri, encoded_uri, TRUE);
1615 static gchar *decode_uri_gdup(const gchar *encoded_uri)
1617 gchar *buffer = g_malloc(strlen(encoded_uri)+1);
1618 decode_uri_with_plus(buffer, encoded_uri, FALSE);
1622 gint scan_mailto_url(const gchar *mailto, gchar **from, gchar **to, gchar **cc, gchar **bcc,
1623 gchar **subject, gchar **body, gchar ***attach, gchar **inreplyto)
1627 const gchar *forbidden_uris[] = { ".gnupg/",
1633 gint num_attach = 0;
1634 gchar **my_att = NULL;
1636 Xstrdup_a(tmp_mailto, mailto, return -1);
1638 if (!strncmp(tmp_mailto, "mailto:", 7))
1641 p = strchr(tmp_mailto, '?');
1648 *to = decode_uri_gdup(tmp_mailto);
1650 my_att = g_malloc(sizeof(char *));
1654 gchar *field, *value;
1671 if (*value == '\0') continue;
1673 if (from && !g_ascii_strcasecmp(field, "from")) {
1675 *from = decode_uri_gdup(value);
1677 gchar *tmp = decode_uri_gdup(value);
1678 gchar *new_from = g_strdup_printf("%s, %s", *from, tmp);
1682 } else if (cc && !g_ascii_strcasecmp(field, "cc")) {
1684 *cc = decode_uri_gdup(value);
1686 gchar *tmp = decode_uri_gdup(value);
1687 gchar *new_cc = g_strdup_printf("%s, %s", *cc, tmp);
1691 } else if (bcc && !g_ascii_strcasecmp(field, "bcc")) {
1693 *bcc = decode_uri_gdup(value);
1695 gchar *tmp = decode_uri_gdup(value);
1696 gchar *new_bcc = g_strdup_printf("%s, %s", *bcc, tmp);
1700 } else if (subject && !*subject &&
1701 !g_ascii_strcasecmp(field, "subject")) {
1702 *subject = decode_uri_gdup(value);
1703 } else if (body && !*body && !g_ascii_strcasecmp(field, "body")) {
1704 *body = decode_uri_gdup(value);
1705 } else if (body && !*body && !g_ascii_strcasecmp(field, "insert")) {
1706 gchar *tmp = decode_uri_gdup(value);
1707 if (!g_file_get_contents(tmp, body, NULL, NULL)) {
1708 g_warning("Error: couldn't set insert file '%s' in body\n", value);
1712 } else if (attach && !g_ascii_strcasecmp(field, "attach")) {
1714 gchar *tmp = decode_uri_gdup(value);
1715 for (; forbidden_uris[i]; i++) {
1716 if (strstr(tmp, forbidden_uris[i])) {
1717 g_print("Refusing to attach '%s', potential private data leak\n",
1725 /* attach is correct */
1727 my_att = g_realloc(my_att, (sizeof(char *))*(num_attach+1));
1728 my_att[num_attach-1] = tmp;
1729 my_att[num_attach] = NULL;
1731 } else if (inreplyto && !*inreplyto &&
1732 !g_ascii_strcasecmp(field, "in-reply-to")) {
1733 *inreplyto = decode_uri_gdup(value);
1744 #include <windows.h>
1745 #ifndef CSIDL_APPDATA
1746 #define CSIDL_APPDATA 0x001a
1748 #ifndef CSIDL_LOCAL_APPDATA
1749 #define CSIDL_LOCAL_APPDATA 0x001c
1751 #ifndef CSIDL_FLAG_CREATE
1752 #define CSIDL_FLAG_CREATE 0x8000
1754 #define DIM(v) (sizeof(v)/sizeof((v)[0]))
1758 w32_strerror (int w32_errno)
1760 static char strerr[256];
1761 int ec = (int)GetLastError ();
1765 FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM, NULL, w32_errno,
1766 MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
1767 strerr, DIM (strerr)-1, NULL);
1771 static __inline__ void *
1772 dlopen (const char * name, int flag)
1774 void * hd = LoadLibrary (name);
1778 static __inline__ void *
1779 dlsym (void * hd, const char * sym)
1783 void * fnc = GetProcAddress (hd, sym);
1792 static __inline__ const char *
1795 return w32_strerror (0);
1799 static __inline__ int
1811 w32_shgetfolderpath (HWND a, int b, HANDLE c, DWORD d, LPSTR e)
1813 static int initialized;
1814 static HRESULT (WINAPI * func)(HWND,int,HANDLE,DWORD,LPSTR);
1818 static char *dllnames[] = { "shell32.dll", "shfolder.dll", NULL };
1824 for (i=0, handle = NULL; !handle && dllnames[i]; i++)
1826 handle = dlopen (dllnames[i], RTLD_LAZY);
1829 func = dlsym (handle, "SHGetFolderPathW");
1840 return func (a,b,c,d,e);
1845 /* Returns a static string with the directroy from which the module
1846 has been loaded. Returns an empty string on error. */
1847 static char *w32_get_module_dir(void)
1849 static char *moddir;
1852 char name[MAX_PATH+10];
1855 if ( !GetModuleFileNameA (0, name, sizeof (name)-10) )
1858 p = strrchr (name, '\\');
1864 moddir = g_strdup (name);
1868 #endif /* G_OS_WIN32 */
1870 /* Return a static string with the locale dir. */
1871 const gchar *get_locale_dir(void)
1873 static gchar *loc_dir;
1877 loc_dir = g_strconcat(w32_get_module_dir(), G_DIR_SEPARATOR_S,
1878 "\\share\\locale", NULL);
1881 loc_dir = LOCALEDIR;
1887 const gchar *get_home_dir(void)
1890 static char home_dir_utf16[MAX_PATH] = "";
1891 static gchar *home_dir_utf8 = NULL;
1892 if (home_dir_utf16[0] == '\0') {
1893 if (w32_shgetfolderpath
1894 (NULL, CSIDL_APPDATA|CSIDL_FLAG_CREATE,
1895 NULL, 0, home_dir_utf16) < 0)
1896 strcpy (home_dir_utf16, "C:\\Sylpheed");
1897 home_dir_utf8 = g_utf16_to_utf8 ((const gunichar *)home_dir_utf16, -1, NULL, NULL, NULL);
1899 return home_dir_utf8;
1901 static const gchar *homeenv = NULL;
1906 if (!homeenv && g_getenv("HOME") != NULL)
1907 homeenv = g_strdup(g_getenv("HOME"));
1909 homeenv = g_get_home_dir();
1915 static gchar *claws_rc_dir = NULL;
1916 static gboolean rc_dir_alt = FALSE;
1917 const gchar *get_rc_dir(void)
1920 if (!claws_rc_dir) {
1921 claws_rc_dir = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
1923 debug_print("using default rc_dir %s\n", claws_rc_dir);
1925 return claws_rc_dir;
1928 void set_rc_dir(const gchar *dir)
1930 gchar *canonical_dir;
1931 if (claws_rc_dir != NULL) {
1932 g_print("Error: rc_dir already set\n");
1934 int err = cm_canonicalize_filename(dir, &canonical_dir);
1938 g_print("Error looking for %s: %d(%s)\n",
1939 dir, -err, strerror(-err));
1944 claws_rc_dir = canonical_dir;
1946 len = strlen(claws_rc_dir);
1947 if (claws_rc_dir[len - 1] == G_DIR_SEPARATOR)
1948 claws_rc_dir[len - 1] = '\0';
1950 debug_print("set rc_dir to %s\n", claws_rc_dir);
1951 if (!is_dir_exist(claws_rc_dir)) {
1952 if (make_dir_hier(claws_rc_dir) != 0) {
1953 g_print("Error: can't create %s\n",
1961 gboolean rc_dir_is_alt(void) {
1965 const gchar *get_mail_base_dir(void)
1967 return get_home_dir();
1970 const gchar *get_news_cache_dir(void)
1972 static gchar *news_cache_dir = NULL;
1973 if (!news_cache_dir)
1974 news_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1975 NEWS_CACHE_DIR, NULL);
1977 return news_cache_dir;
1980 const gchar *get_imap_cache_dir(void)
1982 static gchar *imap_cache_dir = NULL;
1984 if (!imap_cache_dir)
1985 imap_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1986 IMAP_CACHE_DIR, NULL);
1988 return imap_cache_dir;
1991 const gchar *get_mime_tmp_dir(void)
1993 static gchar *mime_tmp_dir = NULL;
1996 mime_tmp_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1997 MIME_TMP_DIR, NULL);
1999 return mime_tmp_dir;
2002 const gchar *get_template_dir(void)
2004 static gchar *template_dir = NULL;
2007 template_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
2008 TEMPLATE_DIR, NULL);
2010 return template_dir;
2014 const gchar *get_cert_file(void)
2016 const gchar *cert_file = NULL;
2018 cert_file = g_strconcat(w32_get_module_dir(),
2019 "\\share\\claws-mail\\",
2020 "ca-certificates.crt",
2026 /* Return the filepath of the claws-mail.desktop file */
2027 const gchar *get_desktop_file(void)
2029 #ifdef DESKTOPFILEPATH
2030 return DESKTOPFILEPATH;
2036 /* Return the default directory for Plugins. */
2037 const gchar *get_plugin_dir(void)
2040 static gchar *plugin_dir = NULL;
2043 plugin_dir = g_strconcat(w32_get_module_dir(),
2044 "\\lib\\claws-mail\\plugins\\",
2048 if (is_dir_exist(PLUGINDIR))
2051 static gchar *plugin_dir = NULL;
2053 plugin_dir = g_strconcat(get_rc_dir(),
2054 G_DIR_SEPARATOR_S, "plugins",
2055 G_DIR_SEPARATOR_S, NULL);
2063 /* Return the default directory for Themes. */
2064 const gchar *get_themes_dir(void)
2066 static gchar *themes_dir = NULL;
2069 themes_dir = g_strconcat(w32_get_module_dir(),
2070 "\\share\\claws-mail\\themes",
2076 const gchar *get_tmp_dir(void)
2078 static gchar *tmp_dir = NULL;
2081 tmp_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
2087 gchar *get_tmp_file(void)
2090 static guint32 id = 0;
2092 tmp_file = g_strdup_printf("%s%ctmpfile.%08x",
2093 get_tmp_dir(), G_DIR_SEPARATOR, id++);
2098 const gchar *get_domain_name(void)
2101 static gchar *domain_name = NULL;
2107 if (gethostname(hostname, sizeof(hostname)) != 0) {
2108 perror("gethostname");
2109 domain_name = "localhost";
2111 hostname[sizeof(hostname) - 1] = '\0';
2112 if ((hp = my_gethostbyname(hostname)) == NULL) {
2113 perror("gethostbyname");
2114 domain_name = g_strdup(hostname);
2116 domain_name = g_strdup(hp->h_name);
2119 debug_print("domain name = %s\n", domain_name);
2128 off_t get_file_size(const gchar *file)
2132 if (g_stat(file, &s) < 0) {
2133 FILE_OP_ERROR(file, "stat");
2140 time_t get_file_mtime(const gchar *file)
2144 if (g_stat(file, &s) < 0) {
2145 FILE_OP_ERROR(file, "stat");
2152 off_t get_file_size_as_crlf(const gchar *file)
2156 gchar buf[BUFFSIZE];
2158 if ((fp = g_fopen(file, "rb")) == NULL) {
2159 FILE_OP_ERROR(file, "g_fopen");
2163 while (fgets(buf, sizeof(buf), fp) != NULL) {
2165 size += strlen(buf) + 2;
2169 FILE_OP_ERROR(file, "fgets");
2178 gboolean file_exist(const gchar *file, gboolean allow_fifo)
2185 if (g_stat(file, &s) < 0) {
2186 if (ENOENT != errno) FILE_OP_ERROR(file, "stat");
2190 if (S_ISREG(s.st_mode) || (allow_fifo && S_ISFIFO(s.st_mode)))
2197 /* Test on whether FILE is a relative file name. This is
2198 * straightforward for Unix but more complex for Windows. */
2199 gboolean is_relative_filename(const gchar *file)
2204 if ( *file == '\\' && file[1] == '\\' && strchr (file+2, '\\') )
2205 return FALSE; /* Prefixed with a hostname - this can't
2206 * be a relative name. */
2208 if ( ((*file >= 'a' && *file <= 'z')
2209 || (*file >= 'A' && *file <= 'Z'))
2211 file += 2; /* Skip drive letter. */
2213 return !(*file == '\\' || *file == '/');
2215 return !(*file == G_DIR_SEPARATOR);
2220 gboolean is_dir_exist(const gchar *dir)
2225 return g_file_test(dir, G_FILE_TEST_IS_DIR);
2228 gboolean is_file_entry_exist(const gchar *file)
2233 return g_file_test(file, G_FILE_TEST_EXISTS);
2236 gboolean dirent_is_regular_file(struct dirent *d)
2238 #if !defined(G_OS_WIN32) && defined(HAVE_DIRENT_D_TYPE)
2239 if (d->d_type == DT_REG)
2241 else if (d->d_type != DT_UNKNOWN)
2245 return g_file_test(d->d_name, G_FILE_TEST_IS_REGULAR);
2248 gint change_dir(const gchar *dir)
2250 gchar *prevdir = NULL;
2253 prevdir = g_get_current_dir();
2255 if (g_chdir(dir) < 0) {
2256 FILE_OP_ERROR(dir, "chdir");
2257 if (debug_mode) g_free(prevdir);
2259 } else if (debug_mode) {
2262 cwd = g_get_current_dir();
2263 if (strcmp(prevdir, cwd) != 0)
2264 g_print("current dir: %s\n", cwd);
2272 gint make_dir(const gchar *dir)
2274 if (g_mkdir(dir, S_IRWXU) < 0) {
2275 FILE_OP_ERROR(dir, "mkdir");
2278 if (g_chmod(dir, S_IRWXU) < 0)
2279 FILE_OP_ERROR(dir, "chmod");
2284 gint make_dir_hier(const gchar *dir)
2289 for (p = dir; (p = strchr(p, G_DIR_SEPARATOR)) != NULL; p++) {
2290 parent_dir = g_strndup(dir, p - dir);
2291 if (*parent_dir != '\0') {
2292 if (!is_dir_exist(parent_dir)) {
2293 if (make_dir(parent_dir) < 0) {
2302 if (!is_dir_exist(dir)) {
2303 if (make_dir(dir) < 0)
2310 gint remove_all_files(const gchar *dir)
2313 const gchar *dir_name;
2316 prev_dir = g_get_current_dir();
2318 if (g_chdir(dir) < 0) {
2319 FILE_OP_ERROR(dir, "chdir");
2324 if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2325 g_warning("failed to open directory: %s\n", dir);
2330 while ((dir_name = g_dir_read_name(dp)) != NULL) {
2331 if (claws_unlink(dir_name) < 0)
2332 FILE_OP_ERROR(dir_name, "unlink");
2337 if (g_chdir(prev_dir) < 0) {
2338 FILE_OP_ERROR(prev_dir, "chdir");
2348 gint remove_numbered_files(const gchar *dir, guint first, guint last)
2351 const gchar *dir_name;
2355 if (first == last) {
2356 /* Skip all the dir reading part. */
2357 gchar *filename = g_strdup_printf("%s%s%u", dir, G_DIR_SEPARATOR_S, first);
2358 if (claws_unlink(filename) < 0) {
2359 FILE_OP_ERROR(filename, "unlink");
2367 prev_dir = g_get_current_dir();
2369 if (g_chdir(dir) < 0) {
2370 FILE_OP_ERROR(dir, "chdir");
2375 if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2376 g_warning("failed to open directory: %s\n", dir);
2381 while ((dir_name = g_dir_read_name(dp)) != NULL) {
2382 file_no = to_number(dir_name);
2383 if (file_no > 0 && first <= file_no && file_no <= last) {
2384 if (is_dir_exist(dir_name))
2386 if (claws_unlink(dir_name) < 0)
2387 FILE_OP_ERROR(dir_name, "unlink");
2393 if (g_chdir(prev_dir) < 0) {
2394 FILE_OP_ERROR(prev_dir, "chdir");
2404 gint remove_numbered_files_not_in_list(const gchar *dir, GSList *numberlist)
2407 const gchar *dir_name;
2410 GHashTable *wanted_files;
2413 if (numberlist == NULL)
2416 prev_dir = g_get_current_dir();
2418 if (g_chdir(dir) < 0) {
2419 FILE_OP_ERROR(dir, "chdir");
2424 if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2425 FILE_OP_ERROR(dir, "opendir");
2430 wanted_files = g_hash_table_new(g_direct_hash, g_direct_equal);
2431 for (cur = numberlist; cur != NULL; cur = cur->next) {
2432 /* numberlist->data is expected to be GINT_TO_POINTER */
2433 g_hash_table_insert(wanted_files, cur->data, GINT_TO_POINTER(1));
2436 while ((dir_name = g_dir_read_name(dp)) != NULL) {
2437 file_no = to_number(dir_name);
2438 if (is_dir_exist(dir_name))
2440 if (file_no > 0 && g_hash_table_lookup(wanted_files, GINT_TO_POINTER(file_no)) == NULL) {
2441 debug_print("removing unwanted file %d from %s\n", file_no, dir);
2442 if (claws_unlink(dir_name) < 0)
2443 FILE_OP_ERROR(dir_name, "unlink");
2448 g_hash_table_destroy(wanted_files);
2450 if (g_chdir(prev_dir) < 0) {
2451 FILE_OP_ERROR(prev_dir, "chdir");
2461 gint remove_all_numbered_files(const gchar *dir)
2463 return remove_numbered_files(dir, 0, UINT_MAX);
2466 gint remove_dir_recursive(const gchar *dir)
2470 const gchar *dir_name;
2473 if (g_stat(dir, &s) < 0) {
2474 FILE_OP_ERROR(dir, "stat");
2475 if (ENOENT == errno) return 0;
2479 if (!S_ISDIR(s.st_mode)) {
2480 if (claws_unlink(dir) < 0) {
2481 FILE_OP_ERROR(dir, "unlink");
2488 prev_dir = g_get_current_dir();
2489 /* g_print("prev_dir = %s\n", prev_dir); */
2491 if (!path_cmp(prev_dir, dir)) {
2493 if (g_chdir("..") < 0) {
2494 FILE_OP_ERROR(dir, "chdir");
2497 prev_dir = g_get_current_dir();
2500 if (g_chdir(dir) < 0) {
2501 FILE_OP_ERROR(dir, "chdir");
2506 if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2507 g_warning("failed to open directory: %s\n", dir);
2513 /* remove all files in the directory */
2514 while ((dir_name = g_dir_read_name(dp)) != NULL) {
2515 /* g_print("removing %s\n", dir_name); */
2517 if (is_dir_exist(dir_name)) {
2518 if (remove_dir_recursive(dir_name) < 0) {
2519 g_warning("can't remove directory\n");
2523 if (claws_unlink(dir_name) < 0)
2524 FILE_OP_ERROR(dir_name, "unlink");
2530 if (g_chdir(prev_dir) < 0) {
2531 FILE_OP_ERROR(prev_dir, "chdir");
2538 if (g_rmdir(dir) < 0) {
2539 FILE_OP_ERROR(dir, "rmdir");
2546 gint rename_force(const gchar *oldpath, const gchar *newpath)
2549 if (!is_file_entry_exist(oldpath)) {
2553 if (is_file_exist(newpath)) {
2554 if (claws_unlink(newpath) < 0)
2555 FILE_OP_ERROR(newpath, "unlink");
2558 return g_rename(oldpath, newpath);
2562 * Append src file body to the tail of dest file.
2563 * Now keep_backup has no effects.
2565 gint append_file(const gchar *src, const gchar *dest, gboolean keep_backup)
2567 FILE *src_fp, *dest_fp;
2571 gboolean err = FALSE;
2573 if ((src_fp = g_fopen(src, "rb")) == NULL) {
2574 FILE_OP_ERROR(src, "g_fopen");
2578 if ((dest_fp = g_fopen(dest, "ab")) == NULL) {
2579 FILE_OP_ERROR(dest, "g_fopen");
2584 if (change_file_mode_rw(dest_fp, dest) < 0) {
2585 FILE_OP_ERROR(dest, "chmod");
2586 g_warning("can't change file mode\n");
2589 while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
2590 if (n_read < sizeof(buf) && ferror(src_fp))
2592 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2593 g_warning("writing to %s failed.\n", dest);
2601 if (ferror(src_fp)) {
2602 FILE_OP_ERROR(src, "fread");
2606 if (fclose(dest_fp) == EOF) {
2607 FILE_OP_ERROR(dest, "fclose");
2619 gint copy_file(const gchar *src, const gchar *dest, gboolean keep_backup)
2621 FILE *src_fp, *dest_fp;
2624 gchar *dest_bak = NULL;
2625 gboolean err = FALSE;
2627 if ((src_fp = g_fopen(src, "rb")) == NULL) {
2628 FILE_OP_ERROR(src, "g_fopen");
2631 if (is_file_exist(dest)) {
2632 dest_bak = g_strconcat(dest, ".bak", NULL);
2633 if (rename_force(dest, dest_bak) < 0) {
2634 FILE_OP_ERROR(dest, "rename");
2641 if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2642 FILE_OP_ERROR(dest, "g_fopen");
2645 if (rename_force(dest_bak, dest) < 0)
2646 FILE_OP_ERROR(dest_bak, "rename");
2652 if (change_file_mode_rw(dest_fp, dest) < 0) {
2653 FILE_OP_ERROR(dest, "chmod");
2654 g_warning("can't change file mode\n");
2657 while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
2658 if (n_read < sizeof(buf) && ferror(src_fp))
2660 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2661 g_warning("writing to %s failed.\n", dest);
2666 if (rename_force(dest_bak, dest) < 0)
2667 FILE_OP_ERROR(dest_bak, "rename");
2674 if (ferror(src_fp)) {
2675 FILE_OP_ERROR(src, "fread");
2679 if (fclose(dest_fp) == EOF) {
2680 FILE_OP_ERROR(dest, "fclose");
2687 if (rename_force(dest_bak, dest) < 0)
2688 FILE_OP_ERROR(dest_bak, "rename");
2694 if (keep_backup == FALSE && dest_bak)
2695 claws_unlink(dest_bak);
2702 gint move_file(const gchar *src, const gchar *dest, gboolean overwrite)
2704 if (overwrite == FALSE && is_file_exist(dest)) {
2705 g_warning("move_file(): file %s already exists.", dest);
2709 if (rename_force(src, dest) == 0) return 0;
2711 if (EXDEV != errno) {
2712 FILE_OP_ERROR(src, "rename");
2716 if (copy_file(src, dest, FALSE) < 0) return -1;
2723 gint copy_file_part_to_fp(FILE *fp, off_t offset, size_t length, FILE *dest_fp)
2726 gint bytes_left, to_read;
2729 if (fseek(fp, offset, SEEK_SET) < 0) {
2734 bytes_left = length;
2735 to_read = MIN(bytes_left, sizeof(buf));
2737 while ((n_read = fread(buf, sizeof(gchar), to_read, fp)) > 0) {
2738 if (n_read < to_read && ferror(fp))
2740 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2743 bytes_left -= n_read;
2744 if (bytes_left == 0)
2746 to_read = MIN(bytes_left, sizeof(buf));
2757 gint copy_file_part(FILE *fp, off_t offset, size_t length, const gchar *dest)
2760 gboolean err = FALSE;
2762 if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2763 FILE_OP_ERROR(dest, "g_fopen");
2767 if (change_file_mode_rw(dest_fp, dest) < 0) {
2768 FILE_OP_ERROR(dest, "chmod");
2769 g_warning("can't change file mode\n");
2772 if (copy_file_part_to_fp(fp, offset, length, dest_fp) < 0)
2775 if (!err && fclose(dest_fp) == EOF) {
2776 FILE_OP_ERROR(dest, "fclose");
2781 g_warning("writing to %s failed.\n", dest);
2789 /* convert line endings into CRLF. If the last line doesn't end with
2790 * linebreak, add it.
2792 gchar *canonicalize_str(const gchar *str)
2798 for (p = str; *p != '\0'; ++p) {
2805 if (p == str || *(p - 1) != '\n')
2808 out = outp = g_malloc(new_len + 1);
2809 for (p = str; *p != '\0'; ++p) {
2816 if (p == str || *(p - 1) != '\n') {
2825 gint canonicalize_file(const gchar *src, const gchar *dest)
2827 FILE *src_fp, *dest_fp;
2828 gchar buf[BUFFSIZE];
2830 gboolean err = FALSE;
2831 gboolean last_linebreak = FALSE;
2833 if (src == NULL || dest == NULL)
2836 if ((src_fp = g_fopen(src, "rb")) == NULL) {
2837 FILE_OP_ERROR(src, "g_fopen");
2841 if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2842 FILE_OP_ERROR(dest, "g_fopen");
2847 if (change_file_mode_rw(dest_fp, dest) < 0) {
2848 FILE_OP_ERROR(dest, "chmod");
2849 g_warning("can't change file mode\n");
2852 while (fgets(buf, sizeof(buf), src_fp) != NULL) {
2856 if (len == 0) break;
2857 last_linebreak = FALSE;
2859 if (buf[len - 1] != '\n') {
2860 last_linebreak = TRUE;
2861 r = fputs(buf, dest_fp);
2862 } else if (len > 1 && buf[len - 1] == '\n' && buf[len - 2] == '\r') {
2863 r = fputs(buf, dest_fp);
2866 r = fwrite(buf, 1, len - 1, dest_fp);
2871 r = fputs("\r\n", dest_fp);
2875 g_warning("writing to %s failed.\n", dest);
2883 if (last_linebreak == TRUE) {
2884 if (fputs("\r\n", dest_fp) == EOF)
2888 if (ferror(src_fp)) {
2889 FILE_OP_ERROR(src, "fgets");
2893 if (fclose(dest_fp) == EOF) {
2894 FILE_OP_ERROR(dest, "fclose");
2906 gint canonicalize_file_replace(const gchar *file)
2910 tmp_file = get_tmp_file();
2912 if (canonicalize_file(file, tmp_file) < 0) {
2917 if (move_file(tmp_file, file, TRUE) < 0) {
2918 g_warning("can't replace %s .\n", file);
2919 claws_unlink(tmp_file);
2928 gchar *normalize_newlines(const gchar *str)
2933 out = outp = g_malloc(strlen(str) + 1);
2934 for (p = str; *p != '\0'; ++p) {
2936 if (*(p + 1) != '\n')
2947 gchar *get_outgoing_rfc2822_str(FILE *fp)
2949 gchar buf[BUFFSIZE];
2953 str = g_string_new(NULL);
2955 /* output header part */
2956 while (fgets(buf, sizeof(buf), fp) != NULL) {
2958 if (!g_ascii_strncasecmp(buf, "Bcc:", 4)) {
2965 else if (next != ' ' && next != '\t') {
2969 if (fgets(buf, sizeof(buf), fp) == NULL)
2973 g_string_append(str, buf);
2974 g_string_append(str, "\r\n");
2980 /* output body part */
2981 while (fgets(buf, sizeof(buf), fp) != NULL) {
2984 g_string_append_c(str, '.');
2985 g_string_append(str, buf);
2986 g_string_append(str, "\r\n");
2990 g_string_free(str, FALSE);
2996 * Create a new boundary in a way that it is very unlikely that this
2997 * will occur in the following text. It would be easy to ensure
2998 * uniqueness if everything is either quoted-printable or base64
2999 * encoded (note that conversion is allowed), but because MIME bodies
3000 * may be nested, it may happen that the same boundary has already
3003 * boundary := 0*69<bchars> bcharsnospace
3004 * bchars := bcharsnospace / " "
3005 * bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" /
3006 * "+" / "_" / "," / "-" / "." /
3007 * "/" / ":" / "=" / "?"
3009 * some special characters removed because of buggy MTAs
3012 gchar *generate_mime_boundary(const gchar *prefix)
3014 static gchar tbl[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
3015 "abcdefghijklmnopqrstuvwxyz"
3020 for (i = 0; i < sizeof(buf_uniq) - 1; i++)
3021 buf_uniq[i] = tbl[g_random_int_range(0, sizeof(tbl) - 1)];
3024 return g_strdup_printf("%s_/%s", prefix ? prefix : "MP",
3028 gint change_file_mode_rw(FILE *fp, const gchar *file)
3031 return fchmod(fileno(fp), S_IRUSR|S_IWUSR);
3033 return g_chmod(file, S_IRUSR|S_IWUSR);
3037 FILE *my_tmpfile(void)
3039 #if HAVE_MKSTEMP || defined(G_OS_WIN32)
3040 const gchar suffix[] = ".XXXXXX";
3041 const gchar *tmpdir;
3043 const gchar *progname;
3052 tmpdir = get_tmp_dir();
3053 tmplen = strlen(tmpdir);
3054 progname = g_get_prgname();
3055 if (progname == NULL)
3056 progname = "claws-mail";
3057 proglen = strlen(progname);
3058 Xalloca(fname, tmplen + 1 + proglen + sizeof(suffix),
3061 memcpy(fname, tmpdir, tmplen);
3062 fname[tmplen] = G_DIR_SEPARATOR;
3063 memcpy(fname + tmplen + 1, progname, proglen);
3064 memcpy(fname + tmplen + 1 + proglen, suffix, sizeof(suffix));
3066 fd = mkstemp(fname);
3071 claws_unlink(fname);
3073 /* verify that we can write in the file after unlinking */
3074 if (write(fd, buf, 1) < 0) {
3081 fp = fdopen(fd, "w+b");
3089 #endif /* HAVE_MKSTEMP || G_OS_WIN32 */
3094 FILE *get_tmpfile_in_dir(const gchar *dir, gchar **filename)
3098 char *template = g_strdup_printf ("%s%cclaws.XXXXXX",
3099 dir, G_DIR_SEPARATOR);
3100 fd = mkstemp_name(template, filename);
3103 *filename = g_strdup_printf("%s%cclaws.XXXXXX", dir, G_DIR_SEPARATOR);
3104 fd = mkstemp(*filename);
3108 return fdopen(fd, "w+");
3111 FILE *str_open_as_stream(const gchar *str)
3116 cm_return_val_if_fail(str != NULL, NULL);
3120 FILE_OP_ERROR("str_open_as_stream", "my_tmpfile");
3125 if (len == 0) return fp;
3127 if (fwrite(str, 1, len, fp) != len) {
3128 FILE_OP_ERROR("str_open_as_stream", "fwrite");
3137 gint str_write_to_file(const gchar *str, const gchar *file)
3142 cm_return_val_if_fail(str != NULL, -1);
3143 cm_return_val_if_fail(file != NULL, -1);
3145 if ((fp = g_fopen(file, "wb")) == NULL) {
3146 FILE_OP_ERROR(file, "g_fopen");
3156 if (fwrite(str, 1, len, fp) != len) {
3157 FILE_OP_ERROR(file, "fwrite");
3163 if (fclose(fp) == EOF) {
3164 FILE_OP_ERROR(file, "fclose");
3172 static gchar *file_read_stream_to_str_full(FILE *fp, gboolean recode)
3179 cm_return_val_if_fail(fp != NULL, NULL);
3181 array = g_byte_array_new();
3183 while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
3184 if (n_read < sizeof(buf) && ferror(fp))
3186 g_byte_array_append(array, buf, n_read);
3190 FILE_OP_ERROR("file stream", "fread");
3191 g_byte_array_free(array, TRUE);
3196 g_byte_array_append(array, buf, 1);
3197 str = (gchar *)array->data;
3198 g_byte_array_free(array, FALSE);
3200 if (recode && !g_utf8_validate(str, -1, NULL)) {
3201 const gchar *src_codeset, *dest_codeset;
3203 src_codeset = conv_get_locale_charset_str();
3204 dest_codeset = CS_UTF_8;
3205 tmp = conv_codeset_strdup(str, src_codeset, dest_codeset);
3213 static gchar *file_read_to_str_full(const gchar *file, gboolean recode)
3220 struct timeval timeout = {1, 0};
3225 cm_return_val_if_fail(file != NULL, NULL);
3227 if (g_stat(file, &s) != 0) {
3228 FILE_OP_ERROR(file, "stat");
3231 if (S_ISDIR(s.st_mode)) {
3232 g_warning("%s: is a directory\n", file);
3237 fp = g_fopen (file, "rb");
3239 FILE_OP_ERROR(file, "open");
3243 /* test whether the file is readable without blocking */
3244 fd = g_open(file, O_RDONLY | O_NONBLOCK, 0);
3246 FILE_OP_ERROR(file, "open");
3253 /* allow for one second */
3254 err = select(fd+1, &fds, NULL, NULL, &timeout);
3255 if (err <= 0 || !FD_ISSET(fd, &fds)) {
3257 FILE_OP_ERROR(file, "select");
3259 g_warning("%s: doesn't seem readable\n", file);
3265 /* Now clear O_NONBLOCK */
3266 if ((fflags = fcntl(fd, F_GETFL)) < 0) {
3267 FILE_OP_ERROR(file, "fcntl (F_GETFL)");
3271 if (fcntl(fd, F_SETFL, (fflags & ~O_NONBLOCK)) < 0) {
3272 FILE_OP_ERROR(file, "fcntl (F_SETFL)");
3277 /* get the FILE pointer */
3278 fp = fdopen(fd, "rb");
3281 FILE_OP_ERROR(file, "fdopen");
3282 close(fd); /* if fp isn't NULL, we'll use fclose instead! */
3287 str = file_read_stream_to_str_full(fp, recode);
3294 gchar *file_read_to_str(const gchar *file)
3296 return file_read_to_str_full(file, TRUE);
3298 gchar *file_read_stream_to_str(FILE *fp)
3300 return file_read_stream_to_str_full(fp, TRUE);
3303 gchar *file_read_to_str_no_recode(const gchar *file)
3305 return file_read_to_str_full(file, FALSE);
3307 gchar *file_read_stream_to_str_no_recode(FILE *fp)
3309 return file_read_stream_to_str_full(fp, FALSE);
3312 char *fgets_crlf(char *buf, int size, FILE *stream)
3314 gboolean is_cr = FALSE;
3315 gboolean last_was_cr = FALSE;
3320 while (--size > 0 && (c = getc(stream)) != EOF)
3323 is_cr = (c == '\r');
3333 last_was_cr = is_cr;
3335 if (c == EOF && cs == buf)
3343 static gint execute_async(gchar *const argv[])
3345 cm_return_val_if_fail(argv != NULL && argv[0] != NULL, -1);
3347 if (g_spawn_async(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH,
3348 NULL, NULL, NULL, FALSE) == FALSE) {
3349 g_warning("Couldn't execute command: %s\n", argv[0]);
3356 static gint execute_sync(gchar *const argv[])
3360 cm_return_val_if_fail(argv != NULL && argv[0] != NULL, -1);
3363 if (g_spawn_sync(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH,
3364 NULL, NULL, NULL, NULL, &status, NULL) == FALSE) {
3365 g_warning("Couldn't execute command: %s\n", argv[0]);
3369 if (WIFEXITED(status))
3370 return WEXITSTATUS(status);
3374 if (g_spawn_sync(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH|
3375 G_SPAWN_CHILD_INHERITS_STDIN|G_SPAWN_LEAVE_DESCRIPTORS_OPEN,
3376 NULL, NULL, NULL, NULL, &status, NULL) == FALSE) {
3377 g_warning("Couldn't execute command: %s\n", argv[0]);
3385 gint execute_command_line(const gchar *cmdline, gboolean async)
3390 debug_print("execute_command_line(): executing: %s\n", cmdline?cmdline:"(null)");
3392 argv = strsplit_with_quote(cmdline, " ", 0);
3395 ret = execute_async(argv);
3397 ret = execute_sync(argv);
3404 gchar *get_command_output(const gchar *cmdline)
3406 gchar *child_stdout;
3409 cm_return_val_if_fail(cmdline != NULL, NULL);
3411 debug_print("get_command_output(): executing: %s\n", cmdline);
3413 if (g_spawn_command_line_sync(cmdline, &child_stdout, NULL, &status,
3415 g_warning("Couldn't execute command: %s\n", cmdline);
3419 return child_stdout;
3422 static gint is_unchanged_uri_char(char c)
3433 static void encode_uri(gchar *encoded_uri, gint bufsize, const gchar *uri)
3439 for(i = 0; i < strlen(uri) ; i++) {
3440 if (is_unchanged_uri_char(uri[i])) {
3441 if (k + 2 >= bufsize)
3443 encoded_uri[k++] = uri[i];
3446 char * hexa = "0123456789ABCDEF";
3448 if (k + 4 >= bufsize)
3450 encoded_uri[k++] = '%';
3451 encoded_uri[k++] = hexa[uri[i] / 16];
3452 encoded_uri[k++] = hexa[uri[i] % 16];
3458 gint open_uri(const gchar *uri, const gchar *cmdline)
3462 gchar buf[BUFFSIZE];
3464 gchar encoded_uri[BUFFSIZE];
3465 cm_return_val_if_fail(uri != NULL, -1);
3467 /* an option to choose whether to use encode_uri or not ? */
3468 encode_uri(encoded_uri, BUFFSIZE, uri);
3471 (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
3472 !strchr(p + 2, '%'))
3473 g_snprintf(buf, sizeof(buf), cmdline, encoded_uri);
3476 g_warning("Open URI command-line is invalid "
3477 "(there must be only one '%%s'): %s",
3479 g_snprintf(buf, sizeof(buf), DEFAULT_BROWSER_CMD, encoded_uri);
3482 execute_command_line(buf, TRUE);
3484 ShellExecute(NULL, "open", uri, NULL, NULL, SW_SHOW);
3489 gint open_txt_editor(const gchar *filepath, const gchar *cmdline)
3491 gchar buf[BUFFSIZE];
3494 cm_return_val_if_fail(filepath != NULL, -1);
3497 (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
3498 !strchr(p + 2, '%'))
3499 g_snprintf(buf, sizeof(buf), cmdline, filepath);
3502 g_warning("Open Text Editor command-line is invalid "
3503 "(there must be only one '%%s'): %s",
3505 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, filepath);
3508 execute_command_line(buf, TRUE);
3513 time_t remote_tzoffset_sec(const gchar *zone)
3515 static gchar ustzstr[] = "PSTPDTMSTMDTCSTCDTESTEDT";
3521 time_t remoteoffset;
3523 strncpy(zone3, zone, 3);
3527 if (sscanf(zone, "%c%d", &c, &offset) == 2 &&
3528 (c == '+' || c == '-')) {
3529 remoteoffset = ((offset / 100) * 60 + (offset % 100)) * 60;
3531 remoteoffset = -remoteoffset;
3532 } else if (!strncmp(zone, "UT" , 2) ||
3533 !strncmp(zone, "GMT", 2)) {
3535 } else if (strlen(zone3) == 3) {
3536 for (p = ustzstr; *p != '\0'; p += 3) {
3537 if (!g_ascii_strncasecmp(p, zone3, 3)) {
3538 iustz = ((gint)(p - ustzstr) / 3 + 1) / 2 - 8;
3539 remoteoffset = iustz * 3600;
3545 } else if (strlen(zone3) == 1) {
3547 case 'Z': remoteoffset = 0; break;
3548 case 'A': remoteoffset = -1; break;
3549 case 'B': remoteoffset = -2; break;
3550 case 'C': remoteoffset = -3; break;
3551 case 'D': remoteoffset = -4; break;
3552 case 'E': remoteoffset = -5; break;
3553 case 'F': remoteoffset = -6; break;
3554 case 'G': remoteoffset = -7; break;
3555 case 'H': remoteoffset = -8; break;
3556 case 'I': remoteoffset = -9; break;
3557 case 'K': remoteoffset = -10; break; /* J is not used */
3558 case 'L': remoteoffset = -11; break;
3559 case 'M': remoteoffset = -12; break;
3560 case 'N': remoteoffset = 1; break;
3561 case 'O': remoteoffset = 2; break;
3562 case 'P': remoteoffset = 3; break;
3563 case 'Q': remoteoffset = 4; break;
3564 case 'R': remoteoffset = 5; break;
3565 case 'S': remoteoffset = 6; break;
3566 case 'T': remoteoffset = 7; break;
3567 case 'U': remoteoffset = 8; break;
3568 case 'V': remoteoffset = 9; break;
3569 case 'W': remoteoffset = 10; break;
3570 case 'X': remoteoffset = 11; break;
3571 case 'Y': remoteoffset = 12; break;
3572 default: remoteoffset = 0; break;
3574 remoteoffset = remoteoffset * 3600;
3578 return remoteoffset;
3581 time_t tzoffset_sec(time_t *now)
3585 struct tm buf1, buf2;
3587 if (now && *now < 0)
3590 gmt = *gmtime_r(now, &buf1);
3591 lt = localtime_r(now, &buf2);
3593 off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
3595 if (lt->tm_year < gmt.tm_year)
3597 else if (lt->tm_year > gmt.tm_year)
3599 else if (lt->tm_yday < gmt.tm_yday)
3601 else if (lt->tm_yday > gmt.tm_yday)
3604 if (off >= 24 * 60) /* should be impossible */
3605 off = 23 * 60 + 59; /* if not, insert silly value */
3606 if (off <= -24 * 60)
3607 off = -(23 * 60 + 59);
3612 /* calculate timezone offset */
3613 gchar *tzoffset(time_t *now)
3615 static gchar offset_string[6];
3619 struct tm buf1, buf2;
3621 if (now && *now < 0)
3624 gmt = *gmtime_r(now, &buf1);
3625 lt = localtime_r(now, &buf2);
3627 off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
3629 if (lt->tm_year < gmt.tm_year)
3631 else if (lt->tm_year > gmt.tm_year)
3633 else if (lt->tm_yday < gmt.tm_yday)
3635 else if (lt->tm_yday > gmt.tm_yday)
3643 if (off >= 24 * 60) /* should be impossible */
3644 off = 23 * 60 + 59; /* if not, insert silly value */
3646 sprintf(offset_string, "%c%02d%02d", sign, off / 60, off % 60);
3648 return offset_string;
3651 void get_rfc822_date(gchar *buf, gint len)
3655 gchar day[4], mon[4];
3656 gint dd, hh, mm, ss, yyyy;
3658 gchar buf2[BUFFSIZE];
3661 lt = localtime_r(&t, &buf1);
3663 sscanf(asctime_r(lt, buf2), "%3s %3s %d %d:%d:%d %d\n",
3664 day, mon, &dd, &hh, &mm, &ss, &yyyy);
3666 g_snprintf(buf, len, "%s, %d %s %d %02d:%02d:%02d %s",
3667 day, dd, mon, yyyy, hh, mm, ss, tzoffset(&t));
3670 void debug_set_mode(gboolean mode)
3675 gboolean debug_get_mode(void)
3680 void debug_print_real(const gchar *format, ...)
3683 gchar buf[BUFFSIZE];
3685 if (!debug_mode) return;
3687 va_start(args, format);
3688 g_vsnprintf(buf, sizeof(buf), format, args);
3695 const char * debug_srcname(const char *file)
3697 const char *s = strrchr (file, '/');
3702 void * subject_table_lookup(GHashTable *subject_table, gchar * subject)
3704 if (subject == NULL)
3707 subject += subject_get_prefix_length(subject);
3709 return g_hash_table_lookup(subject_table, subject);
3712 void subject_table_insert(GHashTable *subject_table, gchar * subject,
3715 if (subject == NULL || *subject == 0)
3717 subject += subject_get_prefix_length(subject);
3718 g_hash_table_insert(subject_table, subject, data);
3721 void subject_table_remove(GHashTable *subject_table, gchar * subject)
3723 if (subject == NULL)
3726 subject += subject_get_prefix_length(subject);
3727 g_hash_table_remove(subject_table, subject);
3731 static regex_t u_regex;
3732 static gboolean u_init_;
3735 void utils_free_regex(void)
3746 *\brief Check if a string is prefixed with known (combinations)
3747 * of prefixes. The function assumes that each prefix
3748 * is terminated by zero or exactly _one_ space.
3750 *\param str String to check for a prefixes
3752 *\return int Number of chars in the prefix that should be skipped
3753 * for a "clean" subject line. If no prefix was found, 0
3756 int subject_get_prefix_length(const gchar *subject)
3759 /*!< Array with allowable reply prefixes regexps. */
3760 static const gchar * const prefixes[] = {
3761 "Re\\:", /* "Re:" */
3762 "Re\\[[1-9][0-9]*\\]\\:", /* "Re[XXX]:" (non-conforming news mail clients) */
3763 "Antw\\:", /* "Antw:" (Dutch / German Outlook) */
3764 "Aw\\:", /* "Aw:" (German) */
3765 "Antwort\\:", /* "Antwort:" (German Lotus Notes) */
3766 "Res\\:", /* "Res:" (Spanish/Brazilian Outlook) */
3767 "Fw\\:", /* "Fw:" Forward */
3768 "Fwd\\:", /* "Fwd:" Forward */
3769 "Enc\\:", /* "Enc:" Forward (Brazilian Outlook) */
3770 "Odp\\:", /* "Odp:" Re (Polish Outlook) */
3771 "Rif\\:", /* "Rif:" (Italian Outlook) */
3772 "Sv\\:", /* "Sv" (Norwegian) */
3773 "Vs\\:", /* "Vs" (Norwegian) */
3774 "Ad\\:", /* "Ad" (Norwegian) */
3775 "\347\255\224\345\244\215\\:", /* "Re" (Chinese, UTF-8) */
3776 "R\303\251f\\. \\:", /* "R�f. :" (French Lotus Notes) */
3777 "Re \\:", /* "Re :" (French Yahoo Mail) */
3780 const int PREFIXES = sizeof prefixes / sizeof prefixes[0];
3784 if (!subject) return 0;
3785 if (!*subject) return 0;
3788 GString *s = g_string_new("");
3790 for (n = 0; n < PREFIXES; n++)
3791 /* Terminate each prefix regexpression by a
3792 * "\ ?" (zero or ONE space), and OR them */
3793 g_string_append_printf(s, "(%s\\ ?)%s",
3798 g_string_prepend(s, "(");
3799 g_string_append(s, ")+"); /* match at least once */
3800 g_string_prepend(s, "^\\ *"); /* from beginning of line */
3803 /* We now have something like "^\ *((PREFIX1\ ?)|(PREFIX2\ ?))+"
3804 * TODO: Should this be "^\ *(((PREFIX1)|(PREFIX2))\ ?)+" ??? */
3805 if (regcomp(&u_regex, s->str, REG_EXTENDED | REG_ICASE)) {
3806 debug_print("Error compiling regexp %s\n", s->str);
3807 g_string_free(s, TRUE);
3811 g_string_free(s, TRUE);
3815 if (!regexec(&u_regex, subject, 1, &pos, 0) && pos.rm_so != -1)
3820 /*!< Array with allowable reply prefixes regexps. */
3821 static const gchar * const prefixes[] = {
3823 "antw:", /* "Antw:" (Dutch / German Outlook) */
3824 "aw:", /* "Aw:" (German) */
3825 "antwort:", /* "Antwort:" (German Lotus Notes) */
3826 "res:", /* "Res:" (Spanish/Brazilian Outlook) */
3827 "fw:", /* "Fw:" Forward */
3828 "fwd:", /* "Fwd:" Forward */
3829 "enc:", /* "Enc:" Forward (Brazilian Outlook) */
3830 "odp:", /* "Odp:" Re (Polish Outlook) */
3831 "rif:", /* "Rif:" (Italian Outlook) */
3832 "sv:", /* "Sv" (Norwegian) */
3833 "vs:", /* "Vs" (Norwegian) */
3834 "ad:", /* "Ad" (Norwegian) */
3835 "R\303\251f. :", /* "R�f. :" (French Lotus Notes) */
3836 "Re :", /* "Re :" (French Yahoo Mail) */
3839 const int PREFIXES = sizeof prefixes / sizeof prefixes[0];
3842 if (!subject) return 0;
3843 if (!*subject) return 0;
3845 for (n = 0; n < PREFIXES; n++) {
3846 int len = strlen(prefixes[n]);
3847 if (!strncasecmp(subject, prefixes[n], len)) {
3848 if (subject[len] == ' ')
3857 static guint g_stricase_hash(gconstpointer gptr)
3859 guint hash_result = 0;
3862 for (str = gptr; str && *str; str++) {
3863 hash_result += toupper(*str);
3869 static gint g_stricase_equal(gconstpointer gptr1, gconstpointer gptr2)
3871 const char *str1 = gptr1;
3872 const char *str2 = gptr2;
3874 return !strcasecmp(str1, str2);
3877 gint g_int_compare(gconstpointer a, gconstpointer b)
3879 return GPOINTER_TO_INT(a) - GPOINTER_TO_INT(b);
3882 gchar *generate_msgid(gchar *buf, gint len, gchar *user_addr)
3890 lt = localtime_r(&t, &buft);
3892 if (user_addr != NULL)
3893 addr = g_strdup_printf(".%s", user_addr);
3894 else if (strlen(buf) != 0)
3895 addr = g_strdup_printf("@%s", buf);
3897 addr = g_strdup_printf("@%s", get_domain_name());
3899 /* Replace all @ but the last one in addr, with underscores.
3900 * RFC 2822 States that msg-id syntax only allows one @.
3902 while (strchr(addr, '@') != NULL && strchr(addr, '@') != strrchr(addr, '@'))
3903 *(strchr(addr, '@')) = '_';
3905 g_snprintf(buf, len, "%04d%02d%02d%02d%02d%02d.%08x%s",
3906 lt->tm_year + 1900, lt->tm_mon + 1,
3907 lt->tm_mday, lt->tm_hour,
3908 lt->tm_min, lt->tm_sec,
3909 (guint) rand(), addr);
3916 quote_cmd_argument()
3918 return a quoted string safely usable in argument of a command.
3920 code is extracted and adapted from etPan! project -- DINH V. Ho�.
3923 gint quote_cmd_argument(gchar * result, guint size,
3933 for(p = path ; * p != '\0' ; p ++) {
3935 if (isalnum((guchar)*p) || (* p == '/')) {
3936 if (remaining > 0) {
3942 result[size - 1] = '\0';
3947 if (remaining >= 2) {
3955 result[size - 1] = '\0';
3960 if (remaining > 0) {
3964 result[size - 1] = '\0';
3978 static void g_node_map_recursive(GNode *node, gpointer data)
3980 GNodeMapData *mapdata = (GNodeMapData *) data;
3982 GNodeMapData newmapdata;
3985 newdata = mapdata->func(node->data, mapdata->data);
3986 if (newdata != NULL) {
3987 newnode = g_node_new(newdata);
3988 g_node_append(mapdata->parent, newnode);
3990 newmapdata.parent = newnode;
3991 newmapdata.func = mapdata->func;
3992 newmapdata.data = mapdata->data;
3994 g_node_children_foreach(node, G_TRAVERSE_ALL, g_node_map_recursive, &newmapdata);
3998 GNode *g_node_map(GNode *node, GNodeMapFunc func, gpointer data)
4001 GNodeMapData mapdata;
4003 cm_return_val_if_fail(node != NULL, NULL);
4004 cm_return_val_if_fail(func != NULL, NULL);
4006 root = g_node_new(func(node->data, data));
4008 mapdata.parent = root;
4009 mapdata.func = func;
4010 mapdata.data = data;
4012 g_node_children_foreach(node, G_TRAVERSE_ALL, g_node_map_recursive, &mapdata);
4017 #define HEX_TO_INT(val, hex) \
4021 if ('0' <= c && c <= '9') { \
4023 } else if ('a' <= c && c <= 'f') { \
4024 val = c - 'a' + 10; \
4025 } else if ('A' <= c && c <= 'F') { \
4026 val = c - 'A' + 10; \
4032 gboolean get_hex_value(guchar *out, gchar c1, gchar c2)
4039 if (hi == -1 || lo == -1)
4042 *out = (hi << 4) + lo;
4046 #define INT_TO_HEX(hex, val) \
4049 hex = '0' + (val); \
4051 hex = 'A' + (val) - 10; \
4054 void get_hex_str(gchar *out, guchar ch)
4058 INT_TO_HEX(hex, ch >> 4);
4060 INT_TO_HEX(hex, ch & 0x0f);
4066 #define G_PRINT_REF 1 == 1 ? (void) 0 : (void)
4068 #define G_PRINT_REF g_print
4072 *\brief Register ref counted pointer. It is based on GBoxed, so should
4073 * work with anything that uses the GType system. The semantics
4074 * are similar to a C++ auto pointer, with the exception that
4075 * C doesn't have automatic closure (calling destructors) when
4076 * exiting a block scope.
4077 * Use the \ref G_TYPE_AUTO_POINTER macro instead of calling this
4078 * function directly.
4080 *\return GType A GType type.
4082 GType g_auto_pointer_register(void)
4084 static GType auto_pointer_type;
4085 if (!auto_pointer_type)
4087 g_boxed_type_register_static
4088 ("G_TYPE_AUTO_POINTER",
4089 (GBoxedCopyFunc) g_auto_pointer_copy,
4090 (GBoxedFreeFunc) g_auto_pointer_free);
4091 return auto_pointer_type;
4095 *\brief Structure with g_new() allocated pointer guarded by the
4098 typedef struct AutoPointerRef {
4099 void (*free) (gpointer);
4105 *\brief The auto pointer opaque structure that references the
4106 * pointer guard block.
4108 typedef struct AutoPointer {
4109 AutoPointerRef *ref;
4110 gpointer ptr; /*!< access to protected pointer */
4114 *\brief Creates an auto pointer for a g_new()ed pointer. Example:
4118 * ... tell gtk_list_store it should use a G_TYPE_AUTO_POINTER
4119 * ... when assigning, copying and freeing storage elements
4121 * gtk_list_store_new(N_S_COLUMNS,
4122 * G_TYPE_AUTO_POINTER,
4126 * Template *precious_data = g_new0(Template, 1);
4127 * g_pointer protect = g_auto_pointer_new(precious_data);
4129 * gtk_list_store_set(container, &iter,
4133 * ... the gtk_list_store has copied the pointer and
4134 * ... incremented its reference count, we should free
4135 * ... the auto pointer (in C++ a destructor would do
4136 * ... this for us when leaving block scope)
4138 * g_auto_pointer_free(protect);
4140 * ... gtk_list_store_set() now manages the data. When
4141 * ... *explicitly* requesting a pointer from the list
4142 * ... store, don't forget you get a copy that should be
4143 * ... freed with g_auto_pointer_free() eventually.
4147 *\param pointer Pointer to be guarded.
4149 *\return GAuto * Pointer that should be used in containers with
4152 GAuto *g_auto_pointer_new(gpointer p)
4154 AutoPointerRef *ref;
4160 ref = g_new0(AutoPointerRef, 1);
4161 ptr = g_new0(AutoPointer, 1);
4171 G_PRINT_REF ("XXXX ALLOC(%lx)\n", p);
4177 *\brief Allocate an autopointer using the passed \a free function to
4178 * free the guarded pointer
4180 GAuto *g_auto_pointer_new_with_free(gpointer p, GFreeFunc free_)
4187 aptr = g_auto_pointer_new(p);
4188 aptr->ref->free = free_;
4192 gpointer g_auto_pointer_get_ptr(GAuto *auto_ptr)
4194 if (auto_ptr == NULL)
4196 return ((AutoPointer *) auto_ptr)->ptr;
4200 *\brief Copies an auto pointer by. It's mostly not necessary
4201 * to call this function directly, unless you copy/assign
4202 * the guarded pointer.
4204 *\param auto_ptr Auto pointer returned by previous call to
4205 * g_auto_pointer_new_XXX()
4207 *\return gpointer An auto pointer
4209 GAuto *g_auto_pointer_copy(GAuto *auto_ptr)
4212 AutoPointerRef *ref;
4215 if (auto_ptr == NULL)
4220 newp = g_new0(AutoPointer, 1);
4223 newp->ptr = ref->pointer;
4227 G_PRINT_REF ("XXXX COPY(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4233 *\brief Free an auto pointer
4235 void g_auto_pointer_free(GAuto *auto_ptr)
4238 AutoPointerRef *ref;
4240 if (auto_ptr == NULL)
4246 if (--(ref->cnt) == 0) {
4248 G_PRINT_REF ("XXXX FREE(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4250 ref->free(ref->pointer);
4255 G_PRINT_REF ("XXXX DEREF(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4260 void replace_returns(gchar *str)
4265 while (strstr(str, "\n")) {
4266 *strstr(str, "\n") = ' ';
4268 while (strstr(str, "\r")) {
4269 *strstr(str, "\r") = ' ';
4273 /* get_uri_part() - retrieves a URI starting from scanpos.
4274 Returns TRUE if succesful */
4275 gboolean get_uri_part(const gchar *start, const gchar *scanpos,
4276 const gchar **bp, const gchar **ep, gboolean hdr)
4279 gint parenthese_cnt = 0;
4281 cm_return_val_if_fail(start != NULL, FALSE);
4282 cm_return_val_if_fail(scanpos != NULL, FALSE);
4283 cm_return_val_if_fail(bp != NULL, FALSE);
4284 cm_return_val_if_fail(ep != NULL, FALSE);
4288 /* find end point of URI */
4289 for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
4290 if (!g_ascii_isgraph(*(const guchar *)ep_) ||
4291 !IS_ASCII(*(const guchar *)ep_) ||
4292 strchr("[]{}<>\"", *ep_)) {
4294 } else if (strchr("(", *ep_)) {
4296 } else if (strchr(")", *ep_)) {
4297 if (parenthese_cnt > 0)
4304 /* no punctuation at end of string */
4306 /* FIXME: this stripping of trailing punctuations may bite with other URIs.
4307 * should pass some URI type to this function and decide on that whether
4308 * to perform punctuation stripping */
4310 #define IS_REAL_PUNCT(ch) (g_ascii_ispunct(ch) && !strchr("/?=-_)", ch))
4312 for (; ep_ - 1 > scanpos + 1 &&
4313 IS_REAL_PUNCT(*(ep_ - 1));
4317 #undef IS_REAL_PUNCT
4324 gchar *make_uri_string(const gchar *bp, const gchar *ep)
4326 while (bp && *bp && g_ascii_isspace(*bp))
4328 return g_strndup(bp, ep - bp);
4331 /* valid mail address characters */
4332 #define IS_RFC822_CHAR(ch) \
4336 !g_ascii_isspace(ch) && \
4337 !strchr("(),;<>\"", (ch)))
4339 /* alphabet and number within 7bit ASCII */
4340 #define IS_ASCII_ALNUM(ch) (IS_ASCII(ch) && g_ascii_isalnum(ch))
4341 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
4343 static GHashTable *create_domain_tab(void)
4345 static const gchar *toplvl_domains [] = {
4347 "arpa", "coop", "info", "name", "biz", "com", "edu", "gov",
4348 "int", "mil", "net", "org", "ac", "ad", "ae", "af", "ag",
4349 "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au",
4350 "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi",
4351 "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
4352 "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl",
4353 "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz", "de",
4354 "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er",
4355 "es", "et", "eu", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd",
4356 "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
4357 "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
4358 "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir",
4359 "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
4360 "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc",
4361 "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
4362 "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq",
4363 "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na",
4364 "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu",
4365 "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm",
4366 "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru",
4367 "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj",
4368 "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
4369 "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to",
4370 "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um",
4371 "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu",
4372 "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw"
4375 GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
4377 cm_return_val_if_fail(htab, NULL);
4378 for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++)
4379 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
4383 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
4385 const gint MAX_LVL_DOM_NAME_LEN = 6;
4386 gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
4387 const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
4390 if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
4393 for (p = buf; p < m && first < last; *p++ = *first++)
4397 return g_hash_table_lookup(tab, buf) != NULL;
4400 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
4401 gboolean get_email_part(const gchar *start, const gchar *scanpos,
4402 const gchar **bp, const gchar **ep, gboolean hdr)
4404 /* more complex than the uri part because we need to scan back and forward starting from
4405 * the scan position. */
4406 gboolean result = FALSE;
4407 const gchar *bp_ = NULL;
4408 const gchar *ep_ = NULL;
4409 static GHashTable *dom_tab;
4410 const gchar *last_dot = NULL;
4411 const gchar *prelast_dot = NULL;
4412 const gchar *last_tld_char = NULL;
4414 /* the informative part of the email address (describing the name
4415 * of the email address owner) may contain quoted parts. the
4416 * closure stack stores the last encountered quotes. */
4417 gchar closure_stack[128];
4418 gchar *ptr = closure_stack;
4420 cm_return_val_if_fail(start != NULL, FALSE);
4421 cm_return_val_if_fail(scanpos != NULL, FALSE);
4422 cm_return_val_if_fail(bp != NULL, FALSE);
4423 cm_return_val_if_fail(ep != NULL, FALSE);
4426 const gchar *start_quote = NULL;
4427 const gchar *end_quote = NULL;
4429 /* go to the real start */
4430 if (start[0] == ',')
4432 if (start[0] == ';')
4434 while (start[0] == '\n' || start[0] == '\r')
4436 while (start[0] == ' ' || start[0] == '\t')
4441 /* check if there are quotes (to skip , in them) */
4442 if (*start == '"') {
4443 start_quote = start;
4445 end_quote = strstr(start, "\"");
4451 /* skip anything between quotes */
4452 if (start_quote && end_quote) {
4457 /* find end (either , or ; or end of line) */
4458 if (strstr(start, ",") && strstr(start, ";"))
4459 *ep = strstr(start,",") < strstr(start, ";")
4460 ? strstr(start, ",") : strstr(start, ";");
4461 else if (strstr(start, ","))
4462 *ep = strstr(start, ",");
4463 else if (strstr(start, ";"))
4464 *ep = strstr(start, ";");
4466 *ep = start+strlen(start);
4468 /* go back to real start */
4469 if (start_quote && end_quote) {
4470 start = start_quote;
4473 /* check there's still an @ in that, or search
4474 * further if possible */
4475 if (strstr(start, "@") && strstr(start, "@") < *ep)
4477 else if (*ep < start+strlen(start)) {
4480 } else if (start_quote && strstr(start, "\"") && strstr(start, "\"") < *ep) {
4488 dom_tab = create_domain_tab();
4489 cm_return_val_if_fail(dom_tab, FALSE);
4491 /* scan start of address */
4492 for (bp_ = scanpos - 1;
4493 bp_ >= start && IS_RFC822_CHAR(*(const guchar *)bp_); bp_--)
4496 /* TODO: should start with an alnum? */
4498 for (; bp_ < scanpos && !IS_ASCII_ALNUM(*(const guchar *)bp_); bp_++)
4501 if (bp_ != scanpos) {
4502 /* scan end of address */
4503 for (ep_ = scanpos + 1;
4504 *ep_ && IS_RFC822_CHAR(*(const guchar *)ep_); ep_++)
4506 prelast_dot = last_dot;
4508 if (*(last_dot + 1) == '.') {
4509 if (prelast_dot == NULL)
4511 last_dot = prelast_dot;
4516 /* TODO: really should terminate with an alnum? */
4517 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*(const guchar *)ep_);
4522 if (last_dot == NULL)
4524 if (last_dot >= ep_)
4525 last_dot = prelast_dot;
4526 if (last_dot == NULL || (scanpos + 1 >= last_dot))
4530 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
4531 if (*last_tld_char == '?')
4534 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
4541 if (!result) return FALSE;
4543 if (*ep_ && bp_ != start && *(bp_ - 1) == '"' && *(ep_) == '"'
4544 && *(ep_ + 1) == ' ' && *(ep_ + 2) == '<'
4545 && IS_RFC822_CHAR(*(ep_ + 3))) {
4546 /* this informative part with an @ in it is
4547 * followed by the email address */
4550 /* go to matching '>' (or next non-rfc822 char, like \n) */
4551 for (; *ep_ != '>' && *ep != '\0' && IS_RFC822_CHAR(*ep_); ep_++)
4554 /* include the bracket */
4555 if (*ep_ == '>') ep_++;
4557 /* include the leading quote */
4565 /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
4566 if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
4569 /* see if this is <bracketed>; in this case we also scan for the informative part. */
4570 if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
4573 #define FULL_STACK() ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
4574 #define IN_STACK() (ptr > closure_stack)
4575 /* has underrun check */
4576 #define POP_STACK() if(IN_STACK()) --ptr
4577 /* has overrun check */
4578 #define PUSH_STACK(c) if(!FULL_STACK()) *ptr++ = (c); else return TRUE
4579 /* has underrun check */
4580 #define PEEK_STACK() (IN_STACK() ? *(ptr - 1) : 0)
4584 /* scan for the informative part. */
4585 for (bp_ -= 2; bp_ >= start; bp_--) {
4586 /* if closure on the stack keep scanning */
4587 if (PEEK_STACK() == *bp_) {
4591 if (!IN_STACK() && (*bp_ == '\'' || *bp_ == '"')) {
4596 /* if nothing in the closure stack, do the special conditions
4597 * the following if..else expression simply checks whether
4598 * a token is acceptable. if not acceptable, the clause
4599 * should terminate the loop with a 'break' */
4600 if (!PEEK_STACK()) {
4602 && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
4603 && (((bp_ + 1) < ep_) && isalnum(*(bp_ + 1)))) {
4604 /* hyphens are allowed, but only in
4606 } else if (strchr(" \"'", *bp_)) {
4607 /* but anything not being a punctiation
4610 break; /* anything else is rejected */
4617 /* scan forward (should start with an alnum) */
4618 for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
4634 #undef IS_ASCII_ALNUM
4635 #undef IS_RFC822_CHAR
4637 gchar *make_email_string(const gchar *bp, const gchar *ep)
4639 /* returns a mailto: URI; mailto: is also used to detect the
4640 * uri type later on in the button_pressed signal handler */
4644 tmp = g_strndup(bp, ep - bp);
4645 result = g_strconcat("mailto:", tmp, NULL);
4651 gchar *make_http_string(const gchar *bp, const gchar *ep)
4653 /* returns an http: URI; */
4657 while (bp && *bp && g_ascii_isspace(*bp))
4659 tmp = g_strndup(bp, ep - bp);
4660 result = g_strconcat("http://", tmp, NULL);
4666 static gchar *mailcap_get_command_in_file(const gchar *path, const gchar *type, const gchar *file_to_open)
4668 FILE *fp = g_fopen(path, "rb");
4669 gchar buf[BUFFSIZE];
4670 gchar *result = NULL;
4673 while (fgets(buf, sizeof (buf), fp) != NULL) {
4674 gchar **parts = g_strsplit(buf, ";", 3);
4675 gchar *trimmed = parts[0];
4676 while (trimmed[0] == ' ' || trimmed[0] == '\t')
4678 while (trimmed[strlen(trimmed)-1] == ' ' || trimmed[strlen(trimmed)-1] == '\t')
4679 trimmed[strlen(trimmed)-1] = '\0';
4681 if (!strcmp(trimmed, type)) {
4682 gboolean needsterminal = FALSE;
4683 if (parts[2] && strstr(parts[2], "needsterminal")) {
4684 needsterminal = TRUE;
4686 if (parts[2] && strstr(parts[2], "test=")) {
4687 gchar *orig_testcmd = g_strdup(strstr(parts[2], "test=")+5);
4688 gchar *testcmd = orig_testcmd;
4689 if (strstr(testcmd,";"))
4690 *(strstr(testcmd,";")) = '\0';
4691 while (testcmd[0] == ' ' || testcmd[0] == '\t')
4693 while (testcmd[strlen(testcmd)-1] == '\n')
4694 testcmd[strlen(testcmd)-1] = '\0';
4695 while (testcmd[strlen(testcmd)-1] == '\r')
4696 testcmd[strlen(testcmd)-1] = '\0';
4697 while (testcmd[strlen(testcmd)-1] == ' ' || testcmd[strlen(testcmd)-1] == '\t')
4698 testcmd[strlen(testcmd)-1] = '\0';
4700 if (strstr(testcmd, "%s")) {
4701 gchar *tmp = g_strdup_printf(testcmd, file_to_open);
4702 gint res = system(tmp);
4704 g_free(orig_testcmd);
4711 gint res = system(testcmd);
4712 g_free(orig_testcmd);
4722 while (trimmed[0] == ' ' || trimmed[0] == '\t')
4724 while (trimmed[strlen(trimmed)-1] == '\n')
4725 trimmed[strlen(trimmed)-1] = '\0';
4726 while (trimmed[strlen(trimmed)-1] == '\r')
4727 trimmed[strlen(trimmed)-1] = '\0';
4728 while (trimmed[strlen(trimmed)-1] == ' ' || trimmed[strlen(trimmed)-1] == '\t')
4729 trimmed[strlen(trimmed)-1] = '\0';
4730 result = g_strdup(trimmed);
4733 /* if there are no single quotes around %s, add them.
4734 * '.*%s.*' is ok, as in display 'png:%s'
4736 if (strstr(result, "%s")
4737 && !(strstr(result, "'") < strstr(result,"%s") &&
4738 strstr(strstr(result,"%s"), "'"))) {
4739 gchar *start = g_strdup(result);
4740 gchar *end = g_strdup(strstr(result, "%s")+2);
4742 *strstr(start, "%s") = '\0';
4743 tmp = g_strconcat(start,"'%s'",end, NULL);
4749 if (needsterminal) {
4750 gchar *tmp = g_strdup_printf("xterm -e %s", result);
4761 gchar *mailcap_get_command_for_type(const gchar *type, const gchar *file_to_open)
4763 gchar *result = NULL;
4767 path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap", NULL);
4768 result = mailcap_get_command_in_file(path, type, file_to_open);
4772 result = mailcap_get_command_in_file("/etc/mailcap", type, file_to_open);
4776 void mailcap_update_default(const gchar *type, const gchar *command)
4778 gchar *path = NULL, *outpath = NULL;
4779 path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap", NULL);
4780 outpath = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap.new", NULL);
4781 FILE *fp = g_fopen(path, "rb");
4783 gchar buf[BUFFSIZE];
4784 gboolean err = FALSE;
4792 outfp = g_fopen(outpath, "wb");
4799 while (fp && fgets(buf, sizeof (buf), fp) != NULL) {
4800 gchar **parts = g_strsplit(buf, ";", 3);
4801 gchar *trimmed = parts[0];
4802 while (trimmed[0] == ' ')
4804 while (trimmed[strlen(trimmed)-1] == ' ')
4805 trimmed[strlen(trimmed)-1] = '\0';
4807 if (!strcmp(trimmed, type)) {
4812 if(fputs(buf, outfp) == EOF) {
4819 if (fprintf(outfp, "%s; %s\n", type, command) < 0)
4825 if (fclose(outfp) == EOF)
4829 g_rename(outpath, path);
4835 gint copy_dir(const gchar *src, const gchar *dst)
4840 if ((dir = g_dir_open(src, 0, NULL)) == NULL) {
4841 g_warning("failed to open directory: %s\n", src);
4845 if (make_dir(dst) < 0)
4848 while ((name = g_dir_read_name(dir)) != NULL) {
4849 gchar *old_file, *new_file;
4850 old_file = g_strconcat(src, G_DIR_SEPARATOR_S, name, NULL);
4851 new_file = g_strconcat(dst, G_DIR_SEPARATOR_S, name, NULL);
4852 debug_print("copying: %s -> %s\n", old_file, new_file);
4853 if (g_file_test(old_file, G_FILE_TEST_IS_REGULAR)) {
4854 gint r = copy_file(old_file, new_file, TRUE);
4861 /* Windows has no symlinks. Or well, Vista seems to
4862 have something like this but the semantics might be
4863 different. Thus we don't use it under Windows. */
4864 else if (g_file_test(old_file, G_FILE_TEST_IS_SYMLINK)) {
4867 gchar *target = g_file_read_link(old_file, &error);
4869 r = symlink(target, new_file);
4876 #endif /*G_OS_WIN32*/
4877 else if (g_file_test(old_file, G_FILE_TEST_IS_DIR)) {
4878 gint r = copy_dir(old_file, new_file);
4889 /* crude test to see if a file is an email. */
4890 gboolean file_is_email (const gchar *filename)
4896 if (filename == NULL)
4898 if ((fp = g_fopen(filename, "rb")) == NULL)
4900 while (i < 60 && score < 3
4901 && fgets(buffer, sizeof (buffer), fp) > 0) {
4902 if (!strncmp(buffer, "From:", strlen("From:")))
4904 else if (!strncmp(buffer, "Date:", strlen("Date:")))
4906 else if (!strncmp(buffer, "Message-ID:", strlen("Message-ID:")))
4908 else if (!strncmp(buffer, "Subject:", strlen("Subject:")))
4913 return (score >= 3);
4916 gboolean sc_g_list_bigger(GList *list, gint max)
4920 while (cur && i <= max+1) {
4927 gboolean sc_g_slist_bigger(GSList *list, gint max)
4931 while (cur && i <= max+1) {
4938 const gchar *daynames[] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL};
4939 const gchar *monthnames[] = {NULL, NULL, NULL, NULL, NULL, NULL,
4940 NULL, NULL, NULL, NULL, NULL, NULL};
4941 const gchar *s_daynames[] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL};
4942 const gchar *s_monthnames[] = {NULL, NULL, NULL, NULL, NULL, NULL,
4943 NULL, NULL, NULL, NULL, NULL, NULL};
4945 gint daynames_len[] = {0,0,0,0,0,0,0};
4946 gint monthnames_len[] = {0,0,0,0,0,0,
4948 gint s_daynames_len[] = {0,0,0,0,0,0,0};
4949 gint s_monthnames_len[] = {0,0,0,0,0,0,
4951 const gchar *s_am_up = NULL;
4952 const gchar *s_pm_up = NULL;
4953 const gchar *s_am_low = NULL;
4954 const gchar *s_pm_low = NULL;
4956 gint s_am_up_len = 0;
4957 gint s_pm_up_len = 0;
4958 gint s_am_low_len = 0;
4959 gint s_pm_low_len = 0;
4961 static gboolean time_names_init_done = FALSE;
4963 static void init_time_names(void)
4967 daynames[0] = C_("Complete day name for use by strftime", "Sunday");
4968 daynames[1] = C_("Complete day name for use by strftime", "Monday");
4969 daynames[2] = C_("Complete day name for use by strftime", "Tuesday");
4970 daynames[3] = C_("Complete day name for use by strftime", "Wednesday");
4971 daynames[4] = C_("Complete day name for use by strftime", "Thursday");
4972 daynames[5] = C_("Complete day name for use by strftime", "Friday");
4973 daynames[6] = C_("Complete day name for use by strftime", "Saturday");
4975 monthnames[0] = C_("Complete month name for use by strftime", "January");
4976 monthnames[1] = C_("Complete month name for use by strftime", "February");
4977 monthnames[2] = C_("Complete month name for use by strftime", "March");
4978 monthnames[3] = C_("Complete month name for use by strftime", "April");
4979 monthnames[4] = C_("Complete month name for use by strftime", "May");
4980 monthnames[5] = C_("Complete month name for use by strftime", "June");
4981 monthnames[6] = C_("Complete month name for use by strftime", "July");
4982 monthnames[7] = C_("Complete month name for use by strftime", "August");
4983 monthnames[8] = C_("Complete month name for use by strftime", "September");
4984 monthnames[9] = C_("Complete month name for use by strftime", "October");
4985 monthnames[10] = C_("Complete month name for use by strftime", "November");
4986 monthnames[11] = C_("Complete month name for use by strftime", "December");
4988 s_daynames[0] = C_("Abbr. day name for use by strftime", "Sun");
4989 s_daynames[1] = C_("Abbr. day name for use by strftime", "Mon");
4990 s_daynames[2] = C_("Abbr. day name for use by strftime", "Tue");
4991 s_daynames[3] = C_("Abbr. day name for use by strftime", "Wed");
4992 s_daynames[4] = C_("Abbr. day name for use by strftime", "Thu");
4993 s_daynames[5] = C_("Abbr. day name for use by strftime", "Fri");
4994 s_daynames[6] = C_("Abbr. day name for use by strftime", "Sat");
4996 s_monthnames[0] = C_("Abbr. month name for use by strftime", "Jan");
4997 s_monthnames[1] = C_("Abbr. month name for use by strftime", "Feb");
4998 s_monthnames[2] = C_("Abbr. month name for use by strftime", "Mar");
4999 s_monthnames[3] = C_("Abbr. month name for use by strftime", "Apr");
5000 s_monthnames[4] = C_("Abbr. month name for use by strftime", "May");
5001 s_monthnames[5] = C_("Abbr. month name for use by strftime", "Jun");
5002 s_monthnames[6] = C_("Abbr. month name for use by strftime", "Jul");
5003 s_monthnames[7] = C_("Abbr. month name for use by strftime", "Aug");
5004 s_monthnames[8] = C_("Abbr. month name for use by strftime", "Sep");
5005 s_monthnames[9] = C_("Abbr. month name for use by strftime", "Oct");
5006 s_monthnames[10] = C_("Abbr. month name for use by strftime", "Nov");
5007 s_monthnames[11] = C_("Abbr. month name for use by strftime", "Dec");
5009 for (i = 0; i < 7; i++) {
5010 daynames_len[i] = strlen(daynames[i]);
5011 s_daynames_len[i] = strlen(s_daynames[i]);
5013 for (i = 0; i < 12; i++) {
5014 monthnames_len[i] = strlen(monthnames[i]);
5015 s_monthnames_len[i] = strlen(s_monthnames[i]);
5018 s_am_up = C_("For use by strftime (morning)", "AM");
5019 s_pm_up = C_("For use by strftime (afternoon)", "PM");
5020 s_am_low = C_("For use by strftime (morning, lowercase)", "am");
5021 s_pm_low = C_("For use by strftime (afternoon, lowercase)", "pm");
5023 s_am_up_len = strlen(s_am_up);
5024 s_pm_up_len = strlen(s_pm_up);
5025 s_am_low_len = strlen(s_am_low);
5026 s_pm_low_len = strlen(s_pm_low);
5028 time_names_init_done = TRUE;
5031 #define CHECK_SIZE() { \
5032 total_done += len; \
5033 if (total_done >= buflen) { \
5034 buf[buflen-1] = '\0'; \
5039 size_t fast_strftime(gchar *buf, gint buflen, const gchar *format, struct tm *lt)
5041 gchar *curpos = buf;
5042 gint total_done = 0;
5043 gchar subbuf[64], subfmt[64];
5044 static time_t last_tzset = (time_t)0;
5046 if (!time_names_init_done)
5049 if (format == NULL || lt == NULL)
5052 if (last_tzset != time(NULL)) {
5054 last_tzset = time(NULL);
5057 if (*format == '%') {
5058 gint len = 0, tmp = 0;
5062 len = 1; CHECK_SIZE();
5066 len = s_daynames_len[lt->tm_wday]; CHECK_SIZE();
5067 strncpy2(curpos, s_daynames[lt->tm_wday], buflen - total_done);
5070 len = daynames_len[lt->tm_wday]; CHECK_SIZE();
5071 strncpy2(curpos, daynames[lt->tm_wday], buflen - total_done);
5075 len = s_monthnames_len[lt->tm_mon]; CHECK_SIZE();
5076 strncpy2(curpos, s_monthnames[lt->tm_mon], buflen - total_done);
5079 len = monthnames_len[lt->tm_mon]; CHECK_SIZE();
5080 strncpy2(curpos, monthnames[lt->tm_mon], buflen - total_done);
5083 strftime(subbuf, 64, "%c", lt);
5084 len = strlen(subbuf); CHECK_SIZE();
5085 strncpy2(curpos, subbuf, buflen - total_done);
5088 total_done += 2; CHECK_SIZE();
5089 tmp = (lt->tm_year + 1900)/100;
5090 *curpos++ = '0'+(tmp / 10);
5091 *curpos++ = '0'+(tmp % 10);
5094 total_done += 2; CHECK_SIZE();
5095 *curpos++ = '0'+(lt->tm_mday / 10);
5096 *curpos++ = '0'+(lt->tm_mday % 10);
5099 total_done += 8; CHECK_SIZE();
5100 *curpos++ = '0'+((lt->tm_mon+1) / 10);
5101 *curpos++ = '0'+((lt->tm_mon+1) % 10);
5103 *curpos++ = '0'+(lt->tm_mday / 10);
5104 *curpos++ = '0'+(lt->tm_mday % 10);
5106 tmp = lt->tm_year%100;
5107 *curpos++ = '0'+(tmp / 10);
5108 *curpos++ = '0'+(tmp % 10);
5111 len = 2; CHECK_SIZE();
5112 snprintf(curpos, buflen - total_done, "%2d", lt->tm_mday);
5115 len = 10; CHECK_SIZE();
5116 snprintf(curpos, buflen - total_done, "%4d-%02d-%02d",
5117 lt->tm_year + 1900, lt->tm_mon +1, lt->tm_mday);
5120 total_done += 2; CHECK_SIZE();
5121 *curpos++ = '0'+(lt->tm_hour / 10);
5122 *curpos++ = '0'+(lt->tm_hour % 10);
5125 total_done += 2; CHECK_SIZE();
5131 *curpos++ = '0'+(tmp / 10);
5132 *curpos++ = '0'+(tmp % 10);
5135 len = 3; CHECK_SIZE();
5136 snprintf(curpos, buflen - total_done, "%03d", lt->tm_yday+1);
5139 len = 2; CHECK_SIZE();
5140 snprintf(curpos, buflen - total_done, "%2d", lt->tm_hour);
5143 len = 2; CHECK_SIZE();
5149 snprintf(curpos, buflen - total_done, "%2d", tmp);
5152 total_done += 2; CHECK_SIZE();
5153 tmp = lt->tm_mon + 1;
5154 *curpos++ = '0'+(tmp / 10);
5155 *curpos++ = '0'+(tmp % 10);
5158 total_done += 2; CHECK_SIZE();
5159 *curpos++ = '0'+(lt->tm_min / 10);
5160 *curpos++ = '0'+(lt->tm_min % 10);
5163 len = 1; CHECK_SIZE();
5167 if (lt->tm_hour >= 12) {
5168 len = s_pm_up_len; CHECK_SIZE();
5169 snprintf(curpos, buflen-total_done, "%s", s_pm_up);
5171 len = s_am_up_len; CHECK_SIZE();
5172 snprintf(curpos, buflen-total_done, "%s", s_am_up);
5176 if (lt->tm_hour >= 12) {
5177 len = s_pm_low_len; CHECK_SIZE();
5178 snprintf(curpos, buflen-total_done, "%s", s_pm_low);
5180 len = s_am_low_len; CHECK_SIZE();
5181 snprintf(curpos, buflen-total_done, "%s", s_am_low);
5185 strftime(subbuf, 64, "%r", lt);
5186 len = strlen(subbuf); CHECK_SIZE();
5187 strncpy2(curpos, subbuf, buflen - total_done);
5190 total_done += 5; CHECK_SIZE();
5191 *curpos++ = '0'+(lt->tm_hour / 10);
5192 *curpos++ = '0'+(lt->tm_hour % 10);
5194 *curpos++ = '0'+(lt->tm_min / 10);
5195 *curpos++ = '0'+(lt->tm_min % 10);
5198 snprintf(subbuf, 64, "%ld", mktime(lt));
5199 len = strlen(subbuf); CHECK_SIZE();
5200 strncpy2(curpos, subbuf, buflen - total_done);
5203 total_done += 2; CHECK_SIZE();
5204 *curpos++ = '0'+(lt->tm_sec / 10);
5205 *curpos++ = '0'+(lt->tm_sec % 10);
5208 len = 1; CHECK_SIZE();
5212 total_done += 8; CHECK_SIZE();
5213 *curpos++ = '0'+(lt->tm_hour / 10);
5214 *curpos++ = '0'+(lt->tm_hour % 10);
5216 *curpos++ = '0'+(lt->tm_min / 10);
5217 *curpos++ = '0'+(lt->tm_min % 10);
5219 *curpos++ = '0'+(lt->tm_sec / 10);
5220 *curpos++ = '0'+(lt->tm_sec % 10);
5223 len = 1; CHECK_SIZE();
5224 snprintf(curpos, buflen - total_done, "%d", lt->tm_wday == 0 ? 7: lt->tm_wday);
5227 len = 1; CHECK_SIZE();
5228 snprintf(curpos, buflen - total_done, "%d", lt->tm_wday);
5231 strftime(subbuf, 64, "%x", lt);
5232 len = strlen(subbuf); CHECK_SIZE();
5233 strncpy2(curpos, subbuf, buflen - total_done);
5236 strftime(subbuf, 64, "%X", lt);
5237 len = strlen(subbuf); CHECK_SIZE();
5238 strncpy2(curpos, subbuf, buflen - total_done);
5241 total_done += 2; CHECK_SIZE();
5242 tmp = lt->tm_year%100;
5243 *curpos++ = '0'+(tmp / 10);
5244 *curpos++ = '0'+(tmp % 10);
5247 len = 4; CHECK_SIZE();
5248 snprintf(curpos, buflen - total_done, "%4d", lt->tm_year + 1900);
5258 /* let these complicated ones be done with the libc */
5259 snprintf(subfmt, 64, "%%%c", *format);
5260 strftime(subbuf, 64, subfmt, lt);
5261 len = strlen(subbuf); CHECK_SIZE();
5262 strncpy2(curpos, subbuf, buflen - total_done);
5266 /* let these complicated modifiers be done with the libc */
5267 snprintf(subfmt, 64, "%%%c%c", *format, *(format+1));
5268 strftime(subbuf, 64, subfmt, lt);
5269 len = strlen(subbuf); CHECK_SIZE();
5270 strncpy2(curpos, subbuf, buflen - total_done);
5274 g_warning("format error (%c)", *format);
5281 int len = 1; CHECK_SIZE();
5282 *curpos++ = *format++;
5289 gboolean prefs_common_get_use_shred(void);
5293 #define WEXITSTATUS(x) (x)
5296 int claws_unlink(const gchar *filename)
5299 static int found_shred = -1;
5300 static const gchar *args[4];
5302 if (filename == NULL)
5305 if (prefs_common_get_use_shred()) {
5306 if (found_shred == -1) {
5308 args[0] = g_find_program_in_path("shred");
5309 debug_print("found shred: %s\n", args[0]);
5310 found_shred = (args[0] != NULL) ? 1:0;
5314 if (found_shred == 1) {
5315 if (g_stat(filename, &s) == 0 && S_ISREG(s.st_mode)) {
5316 if (s.st_nlink == 1) {
5319 g_spawn_sync(NULL, (gchar **)args, NULL, 0,
5320 NULL, NULL, NULL, NULL, &status, NULL);
5321 debug_print("%s %s exited with status %d\n",
5322 args[0], filename, WEXITSTATUS(status));
5323 if (truncate(filename, 0) < 0)
5324 g_warning("couln't truncate");
5329 return g_unlink(filename);
5332 GMutex *cm_mutex_new(void) {
5333 #if GLIB_CHECK_VERSION(2,32,0)
5334 GMutex *m = g_new0(GMutex, 1);
5338 return g_mutex_new();
5342 void cm_mutex_free(GMutex *mutex) {
5343 #if GLIB_CHECK_VERSION(2,32,0)
5344 g_mutex_clear(mutex);
5347 g_mutex_free(mutex);
5351 static gchar *canonical_list_to_file(GSList *list)
5353 GString *result = g_string_new(NULL);
5354 GSList *pathlist = g_slist_reverse(g_slist_copy(list));
5359 result = g_string_append(result, G_DIR_SEPARATOR_S);
5361 if (pathlist->data) {
5362 const gchar *root = (gchar *)pathlist->data;
5363 if (root[0] != '\0' && g_ascii_isalpha(root[0]) &&
5365 /* drive - don't prepend dir separator */
5367 result = g_string_append(result, G_DIR_SEPARATOR_S);
5372 for (cur = pathlist; cur; cur = cur->next) {
5373 result = g_string_append(result, (gchar *)cur->data);
5375 result = g_string_append(result, G_DIR_SEPARATOR_S);
5377 g_slist_free(pathlist);
5380 g_string_free(result, FALSE);
5385 static GSList *cm_split_path(const gchar *filename, int depth)
5388 GSList *canonical_parts = NULL;
5391 gboolean follow_symlinks = TRUE;
5397 errno = EINVAL; /* can't happen, no symlink handling */
5402 if (!g_path_is_absolute(filename)) {
5407 path_parts = g_strsplit(filename, G_DIR_SEPARATOR_S, -1);
5409 for (i = 0; path_parts[i] != NULL; i++) {
5410 if (!strcmp(path_parts[i], ""))
5412 if (!strcmp(path_parts[i], "."))
5414 else if (!strcmp(path_parts[i], "..")) {
5419 else /* Remove the last inserted element */
5421 g_slist_delete_link(canonical_parts,
5426 canonical_parts = g_slist_prepend(canonical_parts,
5427 g_strdup(path_parts[i]));
5429 tmp_path = canonical_list_to_file(canonical_parts);
5431 if(g_stat(tmp_path, &st) < 0) {
5432 if (errno == ENOENT) {
5434 follow_symlinks = FALSE;
5438 slist_free_strings_full(canonical_parts);
5439 g_strfreev(path_parts);
5445 if (follow_symlinks && g_file_test(tmp_path, G_FILE_TEST_IS_SYMLINK)) {
5446 GError *error = NULL;
5447 gchar *target = g_file_read_link(tmp_path, &error);
5449 if (!g_path_is_absolute(target)) {
5450 /* remove the last inserted element */
5452 g_slist_delete_link(canonical_parts,
5454 /* add the target */
5455 canonical_parts = g_slist_prepend(canonical_parts,
5459 /* and get the new target */
5460 target = canonical_list_to_file(canonical_parts);
5463 /* restart from absolute target */
5464 slist_free_strings_full(canonical_parts);
5465 canonical_parts = NULL;
5467 canonical_parts = cm_split_path(target, depth + 1);
5469 g_error_free(error);
5470 if (canonical_parts == NULL) {
5472 g_strfreev(path_parts);
5481 g_strfreev(path_parts);
5482 return canonical_parts;
5486 * Canonicalize a filename, resolving symlinks along the way.
5487 * Returns a negative errno in case of error.
5489 int cm_canonicalize_filename(const gchar *filename, gchar **canonical_name) {
5490 GSList *canonical_parts;
5491 gboolean is_absolute;
5493 if (filename == NULL)
5495 if (canonical_name == NULL)
5497 *canonical_name = NULL;
5499 is_absolute = g_path_is_absolute(filename);
5501 /* Always work on absolute filenames. */
5502 gchar *cur = g_get_current_dir();
5503 gchar *absolute_filename = g_strconcat(cur, G_DIR_SEPARATOR_S,
5506 canonical_parts = cm_split_path(absolute_filename, 0);
5507 g_free(absolute_filename);
5510 canonical_parts = cm_split_path(filename, 0);
5512 if (canonical_parts == NULL)
5515 *canonical_name = canonical_list_to_file(canonical_parts);
5516 slist_free_strings_full(canonical_parts);
5520 #if !GLIB_CHECK_VERSION(2, 30, 0)
5523 * @str: a UTF-8 encoded string
5524 * @start_pos: a character offset within @str
5525 * @end_pos: another character offset within @str
5527 * Copies a substring out of a UTF-8 encoded string.
5528 * The substring will contain @end_pos - @start_pos
5531 * Returns: a newly allocated copy of the requested
5532 * substring. Free with g_free() when no longer needed.
5537 g_utf8_substring (const gchar *str,
5541 gchar *start, *end, *out;
5543 start = g_utf8_offset_to_pointer (str, start_pos);
5544 end = g_utf8_offset_to_pointer (start, end_pos - start_pos);
5546 out = g_malloc (end - start + 1);
5547 memcpy (out, start, end - start);
5548 out[end - start] = 0;