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 !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 /* 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 cm_return_if_fail(str != NULL);
930 eliminate_address_comment(str);
931 if (strchr_with_skip_quote(str, '"', '<'))
932 extract_parenthesis_with_skip_quote(str, '"', '<', '>');
936 void extract_list_id_str(gchar *str)
938 if (strchr_with_skip_quote(str, '"', '<'))
939 extract_parenthesis_with_skip_quote(str, '"', '<', '>');
943 static GSList *address_list_append_real(GSList *addr_list, const gchar *str, gboolean removecomments)
948 if (!str) return addr_list;
950 Xstrdup_a(work, str, return addr_list);
953 eliminate_address_comment(work);
956 while (workp && *workp) {
959 if ((p = strchr_with_skip_quote(workp, '"', ','))) {
965 if (removecomments && strchr_with_skip_quote(workp, '"', '<'))
966 extract_parenthesis_with_skip_quote
967 (workp, '"', '<', '>');
971 addr_list = g_slist_append(addr_list, g_strdup(workp));
979 GSList *address_list_append(GSList *addr_list, const gchar *str)
981 return address_list_append_real(addr_list, str, TRUE);
984 GSList *address_list_append_with_comments(GSList *addr_list, const gchar *str)
986 return address_list_append_real(addr_list, str, FALSE);
989 GSList *references_list_prepend(GSList *msgid_list, const gchar *str)
993 if (!str) return msgid_list;
996 while (strp && *strp) {
997 const gchar *start, *end;
1000 if ((start = strchr(strp, '<')) != NULL) {
1001 end = strchr(start + 1, '>');
1006 msgid = g_strndup(start + 1, end - start - 1);
1009 msgid_list = g_slist_prepend(msgid_list, msgid);
1019 GSList *references_list_append(GSList *msgid_list, const gchar *str)
1023 list = references_list_prepend(NULL, str);
1024 list = g_slist_reverse(list);
1025 msgid_list = g_slist_concat(msgid_list, list);
1030 GSList *newsgroup_list_append(GSList *group_list, const gchar *str)
1035 if (!str) return group_list;
1037 Xstrdup_a(work, str, return group_list);
1041 while (workp && *workp) {
1044 if ((p = strchr_with_skip_quote(workp, '"', ','))) {
1052 group_list = g_slist_append(group_list,
1061 GList *add_history(GList *list, const gchar *str)
1066 cm_return_val_if_fail(str != NULL, list);
1068 old = g_list_find_custom(list, (gpointer)str, (GCompareFunc)strcmp2);
1071 list = g_list_remove(list, old->data);
1073 } else if (g_list_length(list) >= MAX_HISTORY_SIZE) {
1076 last = g_list_last(list);
1078 oldstr = last->data;
1079 list = g_list_remove(list, last->data);
1084 list = g_list_prepend(list, g_strdup(str));
1089 void remove_return(gchar *str)
1091 register gchar *p = str;
1094 if (*p == '\n' || *p == '\r')
1095 memmove(p, p + 1, strlen(p));
1101 void remove_space(gchar *str)
1103 register gchar *p = str;
1108 while (g_ascii_isspace(*(p + spc)))
1111 memmove(p, p + spc, strlen(p + spc) + 1);
1117 void unfold_line(gchar *str)
1119 register gchar *p = str;
1123 if (*p == '\n' || *p == '\r') {
1126 while (g_ascii_isspace(*(p + spc)))
1129 memmove(p, p + spc, strlen(p + spc) + 1);
1135 void subst_char(gchar *str, gchar orig, gchar subst)
1137 register gchar *p = str;
1146 void subst_chars(gchar *str, gchar *orig, gchar subst)
1148 register gchar *p = str;
1151 if (strchr(orig, *p) != NULL)
1157 void subst_for_filename(gchar *str)
1162 subst_chars(str, "\t\r\n\\/*:", '_');
1164 subst_chars(str, "\t\r\n\\/*", '_');
1168 void subst_for_shellsafe_filename(gchar *str)
1172 subst_for_filename(str);
1173 subst_chars(str, " \"'|&;()<>'!{}[]",'_');
1176 gboolean is_ascii_str(const gchar *str)
1178 const guchar *p = (const guchar *)str;
1180 while (*p != '\0') {
1181 if (*p != '\t' && *p != ' ' &&
1182 *p != '\r' && *p != '\n' &&
1183 (*p < 32 || *p >= 127))
1191 static const gchar * line_has_quote_char_last(const gchar * str, const gchar *quote_chars)
1193 gchar * position = NULL;
1194 gchar * tmp_pos = NULL;
1197 if (quote_chars == NULL)
1200 for (i = 0; i < strlen(quote_chars); i++) {
1201 tmp_pos = strrchr (str, quote_chars[i]);
1203 || (tmp_pos != NULL && position <= tmp_pos) )
1209 gint get_quote_level(const gchar *str, const gchar *quote_chars)
1211 const gchar *first_pos;
1212 const gchar *last_pos;
1213 const gchar *p = str;
1214 gint quote_level = -1;
1216 /* speed up line processing by only searching to the last '>' */
1217 if ((first_pos = line_has_quote_char(str, quote_chars)) != NULL) {
1218 /* skip a line if it contains a '<' before the initial '>' */
1219 if (memchr(str, '<', first_pos - str) != NULL)
1221 last_pos = line_has_quote_char_last(first_pos, quote_chars);
1225 while (p <= last_pos) {
1226 while (p < last_pos) {
1227 if (g_ascii_isspace(*p))
1233 if (strchr(quote_chars, *p))
1235 else if (*p != '-' && !g_ascii_isspace(*p) && p <= last_pos) {
1236 /* any characters are allowed except '-','<' and space */
1237 while (*p != '-' && *p != '<'
1238 && !strchr(quote_chars, *p)
1239 && !g_ascii_isspace(*p)
1242 if (strchr(quote_chars, *p))
1254 gint check_line_length(const gchar *str, gint max_chars, gint *line)
1256 const gchar *p = str, *q;
1257 gint cur_line = 0, len;
1259 while ((q = strchr(p, '\n')) != NULL) {
1261 if (len > max_chars) {
1271 if (len > max_chars) {
1280 const gchar * line_has_quote_char(const gchar * str, const gchar *quote_chars)
1282 gchar * position = NULL;
1283 gchar * tmp_pos = NULL;
1286 if (quote_chars == NULL)
1289 for (i = 0; i < strlen(quote_chars); i++) {
1290 tmp_pos = strchr (str, quote_chars[i]);
1292 || (tmp_pos != NULL && position >= tmp_pos) )
1298 static gchar *strstr_with_skip_quote(const gchar *haystack, const gchar *needle)
1300 register guint haystack_len, needle_len;
1301 gboolean in_squote = FALSE, in_dquote = FALSE;
1303 haystack_len = strlen(haystack);
1304 needle_len = strlen(needle);
1306 if (haystack_len < needle_len || needle_len == 0)
1309 while (haystack_len >= needle_len) {
1310 if (!in_squote && !in_dquote &&
1311 !strncmp(haystack, needle, needle_len))
1312 return (gchar *)haystack;
1314 /* 'foo"bar"' -> foo"bar"
1315 "foo'bar'" -> foo'bar' */
1316 if (*haystack == '\'') {
1319 else if (!in_dquote)
1321 } else if (*haystack == '\"') {
1324 else if (!in_squote)
1326 } else if (*haystack == '\\') {
1338 gchar **strsplit_with_quote(const gchar *str, const gchar *delim,
1341 GSList *string_list = NULL, *slist;
1342 gchar **str_array, *s, *new_str;
1343 guint i, n = 1, len;
1345 cm_return_val_if_fail(str != NULL, NULL);
1346 cm_return_val_if_fail(delim != NULL, NULL);
1349 max_tokens = G_MAXINT;
1351 s = strstr_with_skip_quote(str, delim);
1353 guint delimiter_len = strlen(delim);
1357 new_str = g_strndup(str, len);
1359 if (new_str[0] == '\'' || new_str[0] == '\"') {
1360 if (new_str[len - 1] == new_str[0]) {
1361 new_str[len - 1] = '\0';
1362 memmove(new_str, new_str + 1, len - 1);
1365 string_list = g_slist_prepend(string_list, new_str);
1367 str = s + delimiter_len;
1368 s = strstr_with_skip_quote(str, delim);
1369 } while (--max_tokens && s);
1373 new_str = g_strdup(str);
1374 if (new_str[0] == '\'' || new_str[0] == '\"') {
1376 if (new_str[len - 1] == new_str[0]) {
1377 new_str[len - 1] = '\0';
1378 memmove(new_str, new_str + 1, len - 1);
1381 string_list = g_slist_prepend(string_list, new_str);
1385 str_array = g_new(gchar*, n);
1389 str_array[i--] = NULL;
1390 for (slist = string_list; slist; slist = slist->next)
1391 str_array[i--] = slist->data;
1393 g_slist_free(string_list);
1398 gchar *get_abbrev_newsgroup_name(const gchar *group, gint len)
1400 gchar *abbrev_group;
1402 const gchar *p = group;
1405 cm_return_val_if_fail(group != NULL, NULL);
1407 last = group + strlen(group);
1408 abbrev_group = ap = g_malloc(strlen(group) + 1);
1413 if ((ap - abbrev_group) + (last - p) > len && strchr(p, '.')) {
1415 while (*p != '.') p++;
1418 return abbrev_group;
1423 return abbrev_group;
1426 gchar *trim_string(const gchar *str, gint len)
1428 const gchar *p = str;
1433 if (!str) return NULL;
1434 if (strlen(str) <= len)
1435 return g_strdup(str);
1436 if (g_utf8_validate(str, -1, NULL) == FALSE)
1437 return g_strdup(str);
1439 while (*p != '\0') {
1440 mb_len = g_utf8_skip[*(guchar *)p];
1443 else if (new_len + mb_len > len)
1450 Xstrndup_a(new_str, str, new_len, return g_strdup(str));
1451 return g_strconcat(new_str, "...", NULL);
1454 GList *uri_list_extract_filenames(const gchar *uri_list)
1456 GList *result = NULL;
1458 gchar *escaped_utf8uri;
1464 while (g_ascii_isspace(*p)) p++;
1465 if (!strncmp(p, "file:", 5)) {
1468 while (*q && *q != '\n' && *q != '\r') q++;
1471 gchar *file, *locale_file = NULL;
1473 while (q > p && g_ascii_isspace(*q))
1475 Xalloca(escaped_utf8uri, q - p + 2,
1477 Xalloca(file, q - p + 2,
1480 strncpy(escaped_utf8uri, p, q - p + 1);
1481 escaped_utf8uri[q - p + 1] = '\0';
1482 decode_uri(file, escaped_utf8uri);
1484 * g_filename_from_uri() rejects escaped/locale encoded uri
1485 * string which come from Nautilus.
1488 if (g_utf8_validate(file, -1, NULL))
1490 = conv_codeset_strdup(
1493 conv_get_locale_charset_str());
1495 locale_file = g_strdup(file + 5);
1497 locale_file = g_filename_from_uri(file, NULL, NULL);
1499 result = g_list_append(result, locale_file);
1503 p = strchr(p, '\n');
1510 /* Converts two-digit hexadecimal to decimal. Used for unescaping escaped
1513 static gint axtoi(const gchar *hexstr)
1515 gint hi, lo, result;
1518 if ('0' <= hi && hi <= '9') {
1521 if ('a' <= hi && hi <= 'f') {
1524 if ('A' <= hi && hi <= 'F') {
1529 if ('0' <= lo && lo <= '9') {
1532 if ('a' <= lo && lo <= 'f') {
1535 if ('A' <= lo && lo <= 'F') {
1538 result = lo + (16 * hi);
1542 gboolean is_uri_string(const gchar *str)
1544 while (str && *str && g_ascii_isspace(*str))
1546 return (g_ascii_strncasecmp(str, "http://", 7) == 0 ||
1547 g_ascii_strncasecmp(str, "https://", 8) == 0 ||
1548 g_ascii_strncasecmp(str, "ftp://", 6) == 0 ||
1549 g_ascii_strncasecmp(str, "www.", 4) == 0);
1552 gchar *get_uri_path(const gchar *uri)
1554 while (uri && *uri && g_ascii_isspace(*uri))
1556 if (g_ascii_strncasecmp(uri, "http://", 7) == 0)
1557 return (gchar *)(uri + 7);
1558 else if (g_ascii_strncasecmp(uri, "https://", 8) == 0)
1559 return (gchar *)(uri + 8);
1560 else if (g_ascii_strncasecmp(uri, "ftp://", 6) == 0)
1561 return (gchar *)(uri + 6);
1563 return (gchar *)uri;
1566 gint get_uri_len(const gchar *str)
1570 if (is_uri_string(str)) {
1571 for (p = str; *p != '\0'; p++) {
1572 if (!g_ascii_isgraph(*p) || strchr("()<>\"", *p))
1581 /* Decodes URL-Encoded strings (i.e. strings in which spaces are replaced by
1582 * plusses, and escape characters are used)
1584 void decode_uri_with_plus(gchar *decoded_uri, const gchar *encoded_uri, gboolean with_plus)
1586 gchar *dec = decoded_uri;
1587 const gchar *enc = encoded_uri;
1592 if (isxdigit((guchar)enc[0]) &&
1593 isxdigit((guchar)enc[1])) {
1599 if (with_plus && *enc == '+')
1611 void decode_uri(gchar *decoded_uri, const gchar *encoded_uri)
1613 decode_uri_with_plus(decoded_uri, encoded_uri, TRUE);
1616 static gchar *decode_uri_gdup(const gchar *encoded_uri)
1618 gchar *buffer = g_malloc(strlen(encoded_uri)+1);
1619 decode_uri_with_plus(buffer, encoded_uri, FALSE);
1623 gint scan_mailto_url(const gchar *mailto, gchar **from, gchar **to, gchar **cc, gchar **bcc,
1624 gchar **subject, gchar **body, gchar ***attach, gchar **inreplyto)
1628 const gchar *forbidden_uris[] = { ".gnupg/",
1634 gint num_attach = 0;
1635 gchar **my_att = NULL;
1637 Xstrdup_a(tmp_mailto, mailto, return -1);
1639 if (!strncmp(tmp_mailto, "mailto:", 7))
1642 p = strchr(tmp_mailto, '?');
1649 *to = decode_uri_gdup(tmp_mailto);
1651 my_att = g_malloc(sizeof(char *));
1655 gchar *field, *value;
1672 if (*value == '\0') continue;
1674 if (from && !g_ascii_strcasecmp(field, "from")) {
1676 *from = decode_uri_gdup(value);
1678 gchar *tmp = decode_uri_gdup(value);
1679 gchar *new_from = g_strdup_printf("%s, %s", *from, tmp);
1683 } else if (cc && !g_ascii_strcasecmp(field, "cc")) {
1685 *cc = decode_uri_gdup(value);
1687 gchar *tmp = decode_uri_gdup(value);
1688 gchar *new_cc = g_strdup_printf("%s, %s", *cc, tmp);
1692 } else if (bcc && !g_ascii_strcasecmp(field, "bcc")) {
1694 *bcc = decode_uri_gdup(value);
1696 gchar *tmp = decode_uri_gdup(value);
1697 gchar *new_bcc = g_strdup_printf("%s, %s", *bcc, tmp);
1701 } else if (subject && !*subject &&
1702 !g_ascii_strcasecmp(field, "subject")) {
1703 *subject = decode_uri_gdup(value);
1704 } else if (body && !*body && !g_ascii_strcasecmp(field, "body")) {
1705 *body = decode_uri_gdup(value);
1706 } else if (body && !*body && !g_ascii_strcasecmp(field, "insert")) {
1707 gchar *tmp = decode_uri_gdup(value);
1708 if (!g_file_get_contents(tmp, body, NULL, NULL)) {
1709 g_warning("Error: couldn't set insert file '%s' in body\n", value);
1713 } else if (attach && !g_ascii_strcasecmp(field, "attach")) {
1715 gchar *tmp = decode_uri_gdup(value);
1716 for (; forbidden_uris[i]; i++) {
1717 if (strstr(tmp, forbidden_uris[i])) {
1718 g_print("Refusing to attach '%s', potential private data leak\n",
1726 /* attach is correct */
1728 my_att = g_realloc(my_att, (sizeof(char *))*(num_attach+1));
1729 my_att[num_attach-1] = tmp;
1730 my_att[num_attach] = NULL;
1732 } else if (inreplyto && !*inreplyto &&
1733 !g_ascii_strcasecmp(field, "in-reply-to")) {
1734 *inreplyto = decode_uri_gdup(value);
1745 #include <windows.h>
1746 #ifndef CSIDL_APPDATA
1747 #define CSIDL_APPDATA 0x001a
1749 #ifndef CSIDL_LOCAL_APPDATA
1750 #define CSIDL_LOCAL_APPDATA 0x001c
1752 #ifndef CSIDL_FLAG_CREATE
1753 #define CSIDL_FLAG_CREATE 0x8000
1755 #define DIM(v) (sizeof(v)/sizeof((v)[0]))
1759 w32_strerror (int w32_errno)
1761 static char strerr[256];
1762 int ec = (int)GetLastError ();
1766 FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM, NULL, w32_errno,
1767 MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
1768 strerr, DIM (strerr)-1, NULL);
1772 static __inline__ void *
1773 dlopen (const char * name, int flag)
1775 void * hd = LoadLibrary (name);
1779 static __inline__ void *
1780 dlsym (void * hd, const char * sym)
1784 void * fnc = GetProcAddress (hd, sym);
1793 static __inline__ const char *
1796 return w32_strerror (0);
1800 static __inline__ int
1812 w32_shgetfolderpath (HWND a, int b, HANDLE c, DWORD d, LPSTR e)
1814 static int initialized;
1815 static HRESULT (WINAPI * func)(HWND,int,HANDLE,DWORD,LPSTR);
1819 static char *dllnames[] = { "shell32.dll", "shfolder.dll", NULL };
1825 for (i=0, handle = NULL; !handle && dllnames[i]; i++)
1827 handle = dlopen (dllnames[i], RTLD_LAZY);
1830 func = dlsym (handle, "SHGetFolderPathW");
1841 return func (a,b,c,d,e);
1846 /* Returns a static string with the directroy from which the module
1847 has been loaded. Returns an empty string on error. */
1848 static char *w32_get_module_dir(void)
1850 static char *moddir;
1853 char name[MAX_PATH+10];
1856 if ( !GetModuleFileNameA (0, name, sizeof (name)-10) )
1859 p = strrchr (name, '\\');
1865 moddir = g_strdup (name);
1869 #endif /* G_OS_WIN32 */
1871 /* Return a static string with the locale dir. */
1872 const gchar *get_locale_dir(void)
1874 static gchar *loc_dir;
1878 loc_dir = g_strconcat(w32_get_module_dir(), G_DIR_SEPARATOR_S,
1879 "\\share\\locale", NULL);
1882 loc_dir = LOCALEDIR;
1888 const gchar *get_home_dir(void)
1891 static char home_dir_utf16[MAX_PATH] = "";
1892 static gchar *home_dir_utf8 = NULL;
1893 if (home_dir_utf16[0] == '\0') {
1894 if (w32_shgetfolderpath
1895 (NULL, CSIDL_APPDATA|CSIDL_FLAG_CREATE,
1896 NULL, 0, home_dir_utf16) < 0)
1897 strcpy (home_dir_utf16, "C:\\Sylpheed");
1898 home_dir_utf8 = g_utf16_to_utf8 ((const gunichar *)home_dir_utf16, -1, NULL, NULL, NULL);
1900 return home_dir_utf8;
1902 static const gchar *homeenv = NULL;
1907 if (!homeenv && g_getenv("HOME") != NULL)
1908 homeenv = g_strdup(g_getenv("HOME"));
1910 homeenv = g_get_home_dir();
1916 static gchar *claws_rc_dir = NULL;
1917 static gboolean rc_dir_alt = FALSE;
1918 const gchar *get_rc_dir(void)
1921 if (!claws_rc_dir) {
1922 claws_rc_dir = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S,
1924 debug_print("using default rc_dir %s\n", claws_rc_dir);
1926 return claws_rc_dir;
1929 void set_rc_dir(const gchar *dir)
1931 gchar *canonical_dir;
1932 if (claws_rc_dir != NULL) {
1933 g_print("Error: rc_dir already set\n");
1935 int err = cm_canonicalize_filename(dir, &canonical_dir);
1939 g_print("Error looking for %s: %d(%s)\n",
1940 dir, -err, strerror(-err));
1945 claws_rc_dir = canonical_dir;
1947 len = strlen(claws_rc_dir);
1948 if (claws_rc_dir[len - 1] == G_DIR_SEPARATOR)
1949 claws_rc_dir[len - 1] = '\0';
1951 debug_print("set rc_dir to %s\n", claws_rc_dir);
1952 if (!is_dir_exist(claws_rc_dir)) {
1953 if (make_dir_hier(claws_rc_dir) != 0) {
1954 g_print("Error: can't create %s\n",
1962 gboolean rc_dir_is_alt(void) {
1966 const gchar *get_mail_base_dir(void)
1968 return get_home_dir();
1971 const gchar *get_news_cache_dir(void)
1973 static gchar *news_cache_dir = NULL;
1974 if (!news_cache_dir)
1975 news_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1976 NEWS_CACHE_DIR, NULL);
1978 return news_cache_dir;
1981 const gchar *get_imap_cache_dir(void)
1983 static gchar *imap_cache_dir = NULL;
1985 if (!imap_cache_dir)
1986 imap_cache_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1987 IMAP_CACHE_DIR, NULL);
1989 return imap_cache_dir;
1992 const gchar *get_mime_tmp_dir(void)
1994 static gchar *mime_tmp_dir = NULL;
1997 mime_tmp_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1998 MIME_TMP_DIR, NULL);
2000 return mime_tmp_dir;
2003 const gchar *get_template_dir(void)
2005 static gchar *template_dir = NULL;
2008 template_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
2009 TEMPLATE_DIR, NULL);
2011 return template_dir;
2015 const gchar *get_cert_file(void)
2017 const gchar *cert_file = NULL;
2019 cert_file = g_strconcat(w32_get_module_dir(),
2020 "\\share\\claws-mail\\",
2021 "ca-certificates.crt",
2027 /* Return the filepath of the claws-mail.desktop file */
2028 const gchar *get_desktop_file(void)
2030 #ifdef DESKTOPFILEPATH
2031 return DESKTOPFILEPATH;
2037 /* Return the default directory for Plugins. */
2038 const gchar *get_plugin_dir(void)
2041 static gchar *plugin_dir = NULL;
2044 plugin_dir = g_strconcat(w32_get_module_dir(),
2045 "\\lib\\claws-mail\\plugins\\",
2049 if (is_dir_exist(PLUGINDIR))
2052 static gchar *plugin_dir = NULL;
2054 plugin_dir = g_strconcat(get_rc_dir(),
2055 G_DIR_SEPARATOR_S, "plugins",
2056 G_DIR_SEPARATOR_S, NULL);
2064 /* Return the default directory for Themes. */
2065 const gchar *get_themes_dir(void)
2067 static gchar *themes_dir = NULL;
2070 themes_dir = g_strconcat(w32_get_module_dir(),
2071 "\\share\\claws-mail\\themes",
2077 const gchar *get_tmp_dir(void)
2079 static gchar *tmp_dir = NULL;
2082 tmp_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
2088 gchar *get_tmp_file(void)
2091 static guint32 id = 0;
2093 tmp_file = g_strdup_printf("%s%ctmpfile.%08x",
2094 get_tmp_dir(), G_DIR_SEPARATOR, id++);
2099 const gchar *get_domain_name(void)
2102 static gchar *domain_name = NULL;
2108 if (gethostname(hostname, sizeof(hostname)) != 0) {
2109 perror("gethostname");
2110 domain_name = "localhost";
2112 hostname[sizeof(hostname) - 1] = '\0';
2113 if ((hp = my_gethostbyname(hostname)) == NULL) {
2114 perror("gethostbyname");
2115 domain_name = g_strdup(hostname);
2117 domain_name = g_strdup(hp->h_name);
2120 debug_print("domain name = %s\n", domain_name);
2129 off_t get_file_size(const gchar *file)
2133 if (g_stat(file, &s) < 0) {
2134 FILE_OP_ERROR(file, "stat");
2141 time_t get_file_mtime(const gchar *file)
2145 if (g_stat(file, &s) < 0) {
2146 FILE_OP_ERROR(file, "stat");
2153 off_t get_file_size_as_crlf(const gchar *file)
2157 gchar buf[BUFFSIZE];
2159 if ((fp = g_fopen(file, "rb")) == NULL) {
2160 FILE_OP_ERROR(file, "g_fopen");
2164 while (fgets(buf, sizeof(buf), fp) != NULL) {
2166 size += strlen(buf) + 2;
2170 FILE_OP_ERROR(file, "fgets");
2179 gboolean file_exist(const gchar *file, gboolean allow_fifo)
2186 if (g_stat(file, &s) < 0) {
2187 if (ENOENT != errno) FILE_OP_ERROR(file, "stat");
2191 if (S_ISREG(s.st_mode) || (allow_fifo && S_ISFIFO(s.st_mode)))
2198 /* Test on whether FILE is a relative file name. This is
2199 * straightforward for Unix but more complex for Windows. */
2200 gboolean is_relative_filename(const gchar *file)
2205 if ( *file == '\\' && file[1] == '\\' && strchr (file+2, '\\') )
2206 return FALSE; /* Prefixed with a hostname - this can't
2207 * be a relative name. */
2209 if ( ((*file >= 'a' && *file <= 'z')
2210 || (*file >= 'A' && *file <= 'Z'))
2212 file += 2; /* Skip drive letter. */
2214 return !(*file == '\\' || *file == '/');
2216 return !(*file == G_DIR_SEPARATOR);
2221 gboolean is_dir_exist(const gchar *dir)
2226 return g_file_test(dir, G_FILE_TEST_IS_DIR);
2229 gboolean is_file_entry_exist(const gchar *file)
2234 return g_file_test(file, G_FILE_TEST_EXISTS);
2237 gboolean dirent_is_regular_file(struct dirent *d)
2239 #if !defined(G_OS_WIN32) && defined(HAVE_DIRENT_D_TYPE)
2240 if (d->d_type == DT_REG)
2242 else if (d->d_type != DT_UNKNOWN)
2246 return g_file_test(d->d_name, G_FILE_TEST_IS_REGULAR);
2249 gint change_dir(const gchar *dir)
2251 gchar *prevdir = NULL;
2254 prevdir = g_get_current_dir();
2256 if (g_chdir(dir) < 0) {
2257 FILE_OP_ERROR(dir, "chdir");
2258 if (debug_mode) g_free(prevdir);
2260 } else if (debug_mode) {
2263 cwd = g_get_current_dir();
2264 if (strcmp(prevdir, cwd) != 0)
2265 g_print("current dir: %s\n", cwd);
2273 gint make_dir(const gchar *dir)
2275 if (g_mkdir(dir, S_IRWXU) < 0) {
2276 FILE_OP_ERROR(dir, "mkdir");
2279 if (g_chmod(dir, S_IRWXU) < 0)
2280 FILE_OP_ERROR(dir, "chmod");
2285 gint make_dir_hier(const gchar *dir)
2290 for (p = dir; (p = strchr(p, G_DIR_SEPARATOR)) != NULL; p++) {
2291 parent_dir = g_strndup(dir, p - dir);
2292 if (*parent_dir != '\0') {
2293 if (!is_dir_exist(parent_dir)) {
2294 if (make_dir(parent_dir) < 0) {
2303 if (!is_dir_exist(dir)) {
2304 if (make_dir(dir) < 0)
2311 gint remove_all_files(const gchar *dir)
2314 const gchar *dir_name;
2317 prev_dir = g_get_current_dir();
2319 if (g_chdir(dir) < 0) {
2320 FILE_OP_ERROR(dir, "chdir");
2325 if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2326 g_warning("failed to open directory: %s\n", dir);
2331 while ((dir_name = g_dir_read_name(dp)) != NULL) {
2332 if (claws_unlink(dir_name) < 0)
2333 FILE_OP_ERROR(dir_name, "unlink");
2338 if (g_chdir(prev_dir) < 0) {
2339 FILE_OP_ERROR(prev_dir, "chdir");
2349 gint remove_numbered_files(const gchar *dir, guint first, guint last)
2352 const gchar *dir_name;
2356 if (first == last) {
2357 /* Skip all the dir reading part. */
2358 gchar *filename = g_strdup_printf("%s%s%u", dir, G_DIR_SEPARATOR_S, first);
2359 if (is_dir_exist(filename)) {
2360 /* a numbered directory with this name exists,
2361 * remove the dot-file instead */
2363 filename = g_strdup_printf("%s%s.%u", dir, G_DIR_SEPARATOR_S, first);
2365 if (claws_unlink(filename) < 0) {
2366 FILE_OP_ERROR(filename, "unlink");
2374 prev_dir = g_get_current_dir();
2376 if (g_chdir(dir) < 0) {
2377 FILE_OP_ERROR(dir, "chdir");
2382 if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2383 g_warning("failed to open directory: %s\n", dir);
2388 while ((dir_name = g_dir_read_name(dp)) != NULL) {
2389 file_no = to_number(dir_name);
2390 if (file_no > 0 && first <= file_no && file_no <= last) {
2391 if (is_dir_exist(dir_name)) {
2392 gchar *dot_file = g_strdup_printf(".%s", dir_name);
2393 if (is_file_exist(dot_file) && claws_unlink(dot_file) < 0) {
2394 FILE_OP_ERROR(dot_file, "unlink");
2399 if (claws_unlink(dir_name) < 0)
2400 FILE_OP_ERROR(dir_name, "unlink");
2406 if (g_chdir(prev_dir) < 0) {
2407 FILE_OP_ERROR(prev_dir, "chdir");
2417 gint remove_numbered_files_not_in_list(const gchar *dir, GSList *numberlist)
2420 const gchar *dir_name;
2423 GHashTable *wanted_files;
2426 if (numberlist == NULL)
2429 prev_dir = g_get_current_dir();
2431 if (g_chdir(dir) < 0) {
2432 FILE_OP_ERROR(dir, "chdir");
2437 if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2438 FILE_OP_ERROR(dir, "opendir");
2443 wanted_files = g_hash_table_new(g_direct_hash, g_direct_equal);
2444 for (cur = numberlist; cur != NULL; cur = cur->next) {
2445 /* numberlist->data is expected to be GINT_TO_POINTER */
2446 g_hash_table_insert(wanted_files, cur->data, GINT_TO_POINTER(1));
2449 while ((dir_name = g_dir_read_name(dp)) != NULL) {
2450 file_no = to_number(dir_name);
2451 if (is_dir_exist(dir_name))
2453 if (file_no > 0 && g_hash_table_lookup(wanted_files, GINT_TO_POINTER(file_no)) == NULL) {
2454 debug_print("removing unwanted file %d from %s\n", file_no, dir);
2455 if (is_dir_exist(dir_name)) {
2456 gchar *dot_file = g_strdup_printf(".%s", dir_name);
2457 if (is_file_exist(dot_file) && claws_unlink(dot_file) < 0) {
2458 FILE_OP_ERROR(dot_file, "unlink");
2463 if (claws_unlink(dir_name) < 0)
2464 FILE_OP_ERROR(dir_name, "unlink");
2469 g_hash_table_destroy(wanted_files);
2471 if (g_chdir(prev_dir) < 0) {
2472 FILE_OP_ERROR(prev_dir, "chdir");
2482 gint remove_all_numbered_files(const gchar *dir)
2484 return remove_numbered_files(dir, 0, UINT_MAX);
2487 gint remove_dir_recursive(const gchar *dir)
2491 const gchar *dir_name;
2494 if (g_stat(dir, &s) < 0) {
2495 FILE_OP_ERROR(dir, "stat");
2496 if (ENOENT == errno) return 0;
2500 if (!S_ISDIR(s.st_mode)) {
2501 if (claws_unlink(dir) < 0) {
2502 FILE_OP_ERROR(dir, "unlink");
2509 prev_dir = g_get_current_dir();
2510 /* g_print("prev_dir = %s\n", prev_dir); */
2512 if (!path_cmp(prev_dir, dir)) {
2514 if (g_chdir("..") < 0) {
2515 FILE_OP_ERROR(dir, "chdir");
2518 prev_dir = g_get_current_dir();
2521 if (g_chdir(dir) < 0) {
2522 FILE_OP_ERROR(dir, "chdir");
2527 if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2528 g_warning("failed to open directory: %s\n", dir);
2534 /* remove all files in the directory */
2535 while ((dir_name = g_dir_read_name(dp)) != NULL) {
2536 /* g_print("removing %s\n", dir_name); */
2538 if (is_dir_exist(dir_name)) {
2539 if (remove_dir_recursive(dir_name) < 0) {
2540 g_warning("can't remove directory\n");
2544 if (claws_unlink(dir_name) < 0)
2545 FILE_OP_ERROR(dir_name, "unlink");
2551 if (g_chdir(prev_dir) < 0) {
2552 FILE_OP_ERROR(prev_dir, "chdir");
2559 if (g_rmdir(dir) < 0) {
2560 FILE_OP_ERROR(dir, "rmdir");
2567 gint rename_force(const gchar *oldpath, const gchar *newpath)
2570 if (!is_file_entry_exist(oldpath)) {
2574 if (is_file_exist(newpath)) {
2575 if (claws_unlink(newpath) < 0)
2576 FILE_OP_ERROR(newpath, "unlink");
2579 return g_rename(oldpath, newpath);
2583 * Append src file body to the tail of dest file.
2584 * Now keep_backup has no effects.
2586 gint append_file(const gchar *src, const gchar *dest, gboolean keep_backup)
2588 FILE *src_fp, *dest_fp;
2592 gboolean err = FALSE;
2594 if ((src_fp = g_fopen(src, "rb")) == NULL) {
2595 FILE_OP_ERROR(src, "g_fopen");
2599 if ((dest_fp = g_fopen(dest, "ab")) == NULL) {
2600 FILE_OP_ERROR(dest, "g_fopen");
2605 if (change_file_mode_rw(dest_fp, dest) < 0) {
2606 FILE_OP_ERROR(dest, "chmod");
2607 g_warning("can't change file mode\n");
2610 while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
2611 if (n_read < sizeof(buf) && ferror(src_fp))
2613 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2614 g_warning("writing to %s failed.\n", dest);
2622 if (ferror(src_fp)) {
2623 FILE_OP_ERROR(src, "fread");
2627 if (fclose(dest_fp) == EOF) {
2628 FILE_OP_ERROR(dest, "fclose");
2640 gint copy_file(const gchar *src, const gchar *dest, gboolean keep_backup)
2642 FILE *src_fp, *dest_fp;
2645 gchar *dest_bak = NULL;
2646 gboolean err = FALSE;
2648 if ((src_fp = g_fopen(src, "rb")) == NULL) {
2649 FILE_OP_ERROR(src, "g_fopen");
2652 if (is_file_exist(dest)) {
2653 dest_bak = g_strconcat(dest, ".bak", NULL);
2654 if (rename_force(dest, dest_bak) < 0) {
2655 FILE_OP_ERROR(dest, "rename");
2662 if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2663 FILE_OP_ERROR(dest, "g_fopen");
2666 if (rename_force(dest_bak, dest) < 0)
2667 FILE_OP_ERROR(dest_bak, "rename");
2673 if (change_file_mode_rw(dest_fp, dest) < 0) {
2674 FILE_OP_ERROR(dest, "chmod");
2675 g_warning("can't change file mode\n");
2678 while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), src_fp)) > 0) {
2679 if (n_read < sizeof(buf) && ferror(src_fp))
2681 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2682 g_warning("writing to %s failed.\n", dest);
2687 if (rename_force(dest_bak, dest) < 0)
2688 FILE_OP_ERROR(dest_bak, "rename");
2695 if (ferror(src_fp)) {
2696 FILE_OP_ERROR(src, "fread");
2700 if (fclose(dest_fp) == EOF) {
2701 FILE_OP_ERROR(dest, "fclose");
2708 if (rename_force(dest_bak, dest) < 0)
2709 FILE_OP_ERROR(dest_bak, "rename");
2715 if (keep_backup == FALSE && dest_bak)
2716 claws_unlink(dest_bak);
2723 gint move_file(const gchar *src, const gchar *dest, gboolean overwrite)
2725 if (overwrite == FALSE && is_file_exist(dest)) {
2726 g_warning("move_file(): file %s already exists.", dest);
2730 if (rename_force(src, dest) == 0) return 0;
2732 if (EXDEV != errno) {
2733 FILE_OP_ERROR(src, "rename");
2737 if (copy_file(src, dest, FALSE) < 0) return -1;
2744 gint copy_file_part_to_fp(FILE *fp, off_t offset, size_t length, FILE *dest_fp)
2747 gint bytes_left, to_read;
2750 if (fseek(fp, offset, SEEK_SET) < 0) {
2755 bytes_left = length;
2756 to_read = MIN(bytes_left, sizeof(buf));
2758 while ((n_read = fread(buf, sizeof(gchar), to_read, fp)) > 0) {
2759 if (n_read < to_read && ferror(fp))
2761 if (fwrite(buf, 1, n_read, dest_fp) < n_read) {
2764 bytes_left -= n_read;
2765 if (bytes_left == 0)
2767 to_read = MIN(bytes_left, sizeof(buf));
2778 gint copy_file_part(FILE *fp, off_t offset, size_t length, const gchar *dest)
2781 gboolean err = FALSE;
2783 if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2784 FILE_OP_ERROR(dest, "g_fopen");
2788 if (change_file_mode_rw(dest_fp, dest) < 0) {
2789 FILE_OP_ERROR(dest, "chmod");
2790 g_warning("can't change file mode\n");
2793 if (copy_file_part_to_fp(fp, offset, length, dest_fp) < 0)
2796 if (!err && fclose(dest_fp) == EOF) {
2797 FILE_OP_ERROR(dest, "fclose");
2802 g_warning("writing to %s failed.\n", dest);
2810 /* convert line endings into CRLF. If the last line doesn't end with
2811 * linebreak, add it.
2813 gchar *canonicalize_str(const gchar *str)
2819 for (p = str; *p != '\0'; ++p) {
2826 if (p == str || *(p - 1) != '\n')
2829 out = outp = g_malloc(new_len + 1);
2830 for (p = str; *p != '\0'; ++p) {
2837 if (p == str || *(p - 1) != '\n') {
2846 gint canonicalize_file(const gchar *src, const gchar *dest)
2848 FILE *src_fp, *dest_fp;
2849 gchar buf[BUFFSIZE];
2851 gboolean err = FALSE;
2852 gboolean last_linebreak = FALSE;
2854 if (src == NULL || dest == NULL)
2857 if ((src_fp = g_fopen(src, "rb")) == NULL) {
2858 FILE_OP_ERROR(src, "g_fopen");
2862 if ((dest_fp = g_fopen(dest, "wb")) == NULL) {
2863 FILE_OP_ERROR(dest, "g_fopen");
2868 if (change_file_mode_rw(dest_fp, dest) < 0) {
2869 FILE_OP_ERROR(dest, "chmod");
2870 g_warning("can't change file mode\n");
2873 while (fgets(buf, sizeof(buf), src_fp) != NULL) {
2877 if (len == 0) break;
2878 last_linebreak = FALSE;
2880 if (buf[len - 1] != '\n') {
2881 last_linebreak = TRUE;
2882 r = fputs(buf, dest_fp);
2883 } else if (len > 1 && buf[len - 1] == '\n' && buf[len - 2] == '\r') {
2884 r = fputs(buf, dest_fp);
2887 r = fwrite(buf, 1, len - 1, dest_fp);
2892 r = fputs("\r\n", dest_fp);
2896 g_warning("writing to %s failed.\n", dest);
2904 if (last_linebreak == TRUE) {
2905 if (fputs("\r\n", dest_fp) == EOF)
2909 if (ferror(src_fp)) {
2910 FILE_OP_ERROR(src, "fgets");
2914 if (fclose(dest_fp) == EOF) {
2915 FILE_OP_ERROR(dest, "fclose");
2927 gint canonicalize_file_replace(const gchar *file)
2931 tmp_file = get_tmp_file();
2933 if (canonicalize_file(file, tmp_file) < 0) {
2938 if (move_file(tmp_file, file, TRUE) < 0) {
2939 g_warning("can't replace %s .\n", file);
2940 claws_unlink(tmp_file);
2949 gchar *normalize_newlines(const gchar *str)
2954 out = outp = g_malloc(strlen(str) + 1);
2955 for (p = str; *p != '\0'; ++p) {
2957 if (*(p + 1) != '\n')
2968 gchar *get_outgoing_rfc2822_str(FILE *fp)
2970 gchar buf[BUFFSIZE];
2974 str = g_string_new(NULL);
2976 /* output header part */
2977 while (fgets(buf, sizeof(buf), fp) != NULL) {
2979 if (!g_ascii_strncasecmp(buf, "Bcc:", 4)) {
2986 else if (next != ' ' && next != '\t') {
2990 if (fgets(buf, sizeof(buf), fp) == NULL)
2994 g_string_append(str, buf);
2995 g_string_append(str, "\r\n");
3001 /* output body part */
3002 while (fgets(buf, sizeof(buf), fp) != NULL) {
3005 g_string_append_c(str, '.');
3006 g_string_append(str, buf);
3007 g_string_append(str, "\r\n");
3011 g_string_free(str, FALSE);
3017 * Create a new boundary in a way that it is very unlikely that this
3018 * will occur in the following text. It would be easy to ensure
3019 * uniqueness if everything is either quoted-printable or base64
3020 * encoded (note that conversion is allowed), but because MIME bodies
3021 * may be nested, it may happen that the same boundary has already
3024 * boundary := 0*69<bchars> bcharsnospace
3025 * bchars := bcharsnospace / " "
3026 * bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" /
3027 * "+" / "_" / "," / "-" / "." /
3028 * "/" / ":" / "=" / "?"
3030 * some special characters removed because of buggy MTAs
3033 gchar *generate_mime_boundary(const gchar *prefix)
3035 static gchar tbl[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
3036 "abcdefghijklmnopqrstuvwxyz"
3041 for (i = 0; i < sizeof(buf_uniq) - 1; i++)
3042 buf_uniq[i] = tbl[g_random_int_range(0, sizeof(tbl) - 1)];
3045 return g_strdup_printf("%s_/%s", prefix ? prefix : "MP",
3049 gint change_file_mode_rw(FILE *fp, const gchar *file)
3052 return fchmod(fileno(fp), S_IRUSR|S_IWUSR);
3054 return g_chmod(file, S_IRUSR|S_IWUSR);
3058 FILE *my_tmpfile(void)
3060 #if HAVE_MKSTEMP || defined(G_OS_WIN32)
3061 const gchar suffix[] = ".XXXXXX";
3062 const gchar *tmpdir;
3064 const gchar *progname;
3073 tmpdir = get_tmp_dir();
3074 tmplen = strlen(tmpdir);
3075 progname = g_get_prgname();
3076 if (progname == NULL)
3077 progname = "claws-mail";
3078 proglen = strlen(progname);
3079 Xalloca(fname, tmplen + 1 + proglen + sizeof(suffix),
3082 memcpy(fname, tmpdir, tmplen);
3083 fname[tmplen] = G_DIR_SEPARATOR;
3084 memcpy(fname + tmplen + 1, progname, proglen);
3085 memcpy(fname + tmplen + 1 + proglen, suffix, sizeof(suffix));
3087 fd = mkstemp(fname);
3092 claws_unlink(fname);
3094 /* verify that we can write in the file after unlinking */
3095 if (write(fd, buf, 1) < 0) {
3102 fp = fdopen(fd, "w+b");
3110 #endif /* HAVE_MKSTEMP || G_OS_WIN32 */
3115 FILE *get_tmpfile_in_dir(const gchar *dir, gchar **filename)
3119 char *template = g_strdup_printf ("%s%cclaws.XXXXXX",
3120 dir, G_DIR_SEPARATOR);
3121 fd = mkstemp_name(template, filename);
3124 *filename = g_strdup_printf("%s%cclaws.XXXXXX", dir, G_DIR_SEPARATOR);
3125 fd = mkstemp(*filename);
3129 return fdopen(fd, "w+");
3132 FILE *str_open_as_stream(const gchar *str)
3137 cm_return_val_if_fail(str != NULL, NULL);
3141 FILE_OP_ERROR("str_open_as_stream", "my_tmpfile");
3146 if (len == 0) return fp;
3148 if (fwrite(str, 1, len, fp) != len) {
3149 FILE_OP_ERROR("str_open_as_stream", "fwrite");
3158 gint str_write_to_file(const gchar *str, const gchar *file)
3163 cm_return_val_if_fail(str != NULL, -1);
3164 cm_return_val_if_fail(file != NULL, -1);
3166 if ((fp = g_fopen(file, "wb")) == NULL) {
3167 FILE_OP_ERROR(file, "g_fopen");
3177 if (fwrite(str, 1, len, fp) != len) {
3178 FILE_OP_ERROR(file, "fwrite");
3184 if (fclose(fp) == EOF) {
3185 FILE_OP_ERROR(file, "fclose");
3193 static gchar *file_read_stream_to_str_full(FILE *fp, gboolean recode)
3200 cm_return_val_if_fail(fp != NULL, NULL);
3202 array = g_byte_array_new();
3204 while ((n_read = fread(buf, sizeof(gchar), sizeof(buf), fp)) > 0) {
3205 if (n_read < sizeof(buf) && ferror(fp))
3207 g_byte_array_append(array, buf, n_read);
3211 FILE_OP_ERROR("file stream", "fread");
3212 g_byte_array_free(array, TRUE);
3217 g_byte_array_append(array, buf, 1);
3218 str = (gchar *)array->data;
3219 g_byte_array_free(array, FALSE);
3221 if (recode && !g_utf8_validate(str, -1, NULL)) {
3222 const gchar *src_codeset, *dest_codeset;
3224 src_codeset = conv_get_locale_charset_str();
3225 dest_codeset = CS_UTF_8;
3226 tmp = conv_codeset_strdup(str, src_codeset, dest_codeset);
3234 static gchar *file_read_to_str_full(const gchar *file, gboolean recode)
3241 struct timeval timeout = {1, 0};
3246 cm_return_val_if_fail(file != NULL, NULL);
3248 if (g_stat(file, &s) != 0) {
3249 FILE_OP_ERROR(file, "stat");
3252 if (S_ISDIR(s.st_mode)) {
3253 g_warning("%s: is a directory\n", file);
3258 fp = g_fopen (file, "rb");
3260 FILE_OP_ERROR(file, "open");
3264 /* test whether the file is readable without blocking */
3265 fd = g_open(file, O_RDONLY | O_NONBLOCK, 0);
3267 FILE_OP_ERROR(file, "open");
3274 /* allow for one second */
3275 err = select(fd+1, &fds, NULL, NULL, &timeout);
3276 if (err <= 0 || !FD_ISSET(fd, &fds)) {
3278 FILE_OP_ERROR(file, "select");
3280 g_warning("%s: doesn't seem readable\n", file);
3286 /* Now clear O_NONBLOCK */
3287 if ((fflags = fcntl(fd, F_GETFL)) < 0) {
3288 FILE_OP_ERROR(file, "fcntl (F_GETFL)");
3292 if (fcntl(fd, F_SETFL, (fflags & ~O_NONBLOCK)) < 0) {
3293 FILE_OP_ERROR(file, "fcntl (F_SETFL)");
3298 /* get the FILE pointer */
3299 fp = fdopen(fd, "rb");
3302 FILE_OP_ERROR(file, "fdopen");
3303 close(fd); /* if fp isn't NULL, we'll use fclose instead! */
3308 str = file_read_stream_to_str_full(fp, recode);
3315 gchar *file_read_to_str(const gchar *file)
3317 return file_read_to_str_full(file, TRUE);
3319 gchar *file_read_stream_to_str(FILE *fp)
3321 return file_read_stream_to_str_full(fp, TRUE);
3324 gchar *file_read_to_str_no_recode(const gchar *file)
3326 return file_read_to_str_full(file, FALSE);
3328 gchar *file_read_stream_to_str_no_recode(FILE *fp)
3330 return file_read_stream_to_str_full(fp, FALSE);
3333 char *fgets_crlf(char *buf, int size, FILE *stream)
3335 gboolean is_cr = FALSE;
3336 gboolean last_was_cr = FALSE;
3341 while (--size > 0 && (c = getc(stream)) != EOF)
3344 is_cr = (c == '\r');
3354 last_was_cr = is_cr;
3356 if (c == EOF && cs == buf)
3364 static gint execute_async(gchar *const argv[])
3366 cm_return_val_if_fail(argv != NULL && argv[0] != NULL, -1);
3368 if (g_spawn_async(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH,
3369 NULL, NULL, NULL, FALSE) == FALSE) {
3370 g_warning("Couldn't execute command: %s\n", argv[0]);
3377 static gint execute_sync(gchar *const argv[])
3381 cm_return_val_if_fail(argv != NULL && argv[0] != NULL, -1);
3384 if (g_spawn_sync(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH,
3385 NULL, NULL, NULL, NULL, &status, NULL) == FALSE) {
3386 g_warning("Couldn't execute command: %s\n", argv[0]);
3390 if (WIFEXITED(status))
3391 return WEXITSTATUS(status);
3395 if (g_spawn_sync(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH|
3396 G_SPAWN_CHILD_INHERITS_STDIN|G_SPAWN_LEAVE_DESCRIPTORS_OPEN,
3397 NULL, NULL, NULL, NULL, &status, NULL) == FALSE) {
3398 g_warning("Couldn't execute command: %s\n", argv[0]);
3406 gint execute_command_line(const gchar *cmdline, gboolean async)
3411 debug_print("execute_command_line(): executing: %s\n", cmdline?cmdline:"(null)");
3413 argv = strsplit_with_quote(cmdline, " ", 0);
3416 ret = execute_async(argv);
3418 ret = execute_sync(argv);
3425 gchar *get_command_output(const gchar *cmdline)
3427 gchar *child_stdout;
3430 cm_return_val_if_fail(cmdline != NULL, NULL);
3432 debug_print("get_command_output(): executing: %s\n", cmdline);
3434 if (g_spawn_command_line_sync(cmdline, &child_stdout, NULL, &status,
3436 g_warning("Couldn't execute command: %s\n", cmdline);
3440 return child_stdout;
3443 static gint is_unchanged_uri_char(char c)
3454 static void encode_uri(gchar *encoded_uri, gint bufsize, const gchar *uri)
3460 for(i = 0; i < strlen(uri) ; i++) {
3461 if (is_unchanged_uri_char(uri[i])) {
3462 if (k + 2 >= bufsize)
3464 encoded_uri[k++] = uri[i];
3467 char * hexa = "0123456789ABCDEF";
3469 if (k + 4 >= bufsize)
3471 encoded_uri[k++] = '%';
3472 encoded_uri[k++] = hexa[uri[i] / 16];
3473 encoded_uri[k++] = hexa[uri[i] % 16];
3479 gint open_uri(const gchar *uri, const gchar *cmdline)
3483 gchar buf[BUFFSIZE];
3485 gchar encoded_uri[BUFFSIZE];
3486 cm_return_val_if_fail(uri != NULL, -1);
3488 /* an option to choose whether to use encode_uri or not ? */
3489 encode_uri(encoded_uri, BUFFSIZE, uri);
3492 (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
3493 !strchr(p + 2, '%'))
3494 g_snprintf(buf, sizeof(buf), cmdline, encoded_uri);
3497 g_warning("Open URI command-line is invalid "
3498 "(there must be only one '%%s'): %s",
3500 g_snprintf(buf, sizeof(buf), DEFAULT_BROWSER_CMD, encoded_uri);
3503 execute_command_line(buf, TRUE);
3505 ShellExecute(NULL, "open", uri, NULL, NULL, SW_SHOW);
3510 gint open_txt_editor(const gchar *filepath, const gchar *cmdline)
3512 gchar buf[BUFFSIZE];
3515 cm_return_val_if_fail(filepath != NULL, -1);
3518 (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
3519 !strchr(p + 2, '%'))
3520 g_snprintf(buf, sizeof(buf), cmdline, filepath);
3523 g_warning("Open Text Editor command-line is invalid "
3524 "(there must be only one '%%s'): %s",
3526 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, filepath);
3529 execute_command_line(buf, TRUE);
3534 time_t remote_tzoffset_sec(const gchar *zone)
3536 static gchar ustzstr[] = "PSTPDTMSTMDTCSTCDTESTEDT";
3542 time_t remoteoffset;
3544 strncpy(zone3, zone, 3);
3548 if (sscanf(zone, "%c%d", &c, &offset) == 2 &&
3549 (c == '+' || c == '-')) {
3550 remoteoffset = ((offset / 100) * 60 + (offset % 100)) * 60;
3552 remoteoffset = -remoteoffset;
3553 } else if (!strncmp(zone, "UT" , 2) ||
3554 !strncmp(zone, "GMT", 2)) {
3556 } else if (strlen(zone3) == 3) {
3557 for (p = ustzstr; *p != '\0'; p += 3) {
3558 if (!g_ascii_strncasecmp(p, zone3, 3)) {
3559 iustz = ((gint)(p - ustzstr) / 3 + 1) / 2 - 8;
3560 remoteoffset = iustz * 3600;
3566 } else if (strlen(zone3) == 1) {
3568 case 'Z': remoteoffset = 0; break;
3569 case 'A': remoteoffset = -1; break;
3570 case 'B': remoteoffset = -2; break;
3571 case 'C': remoteoffset = -3; break;
3572 case 'D': remoteoffset = -4; break;
3573 case 'E': remoteoffset = -5; break;
3574 case 'F': remoteoffset = -6; break;
3575 case 'G': remoteoffset = -7; break;
3576 case 'H': remoteoffset = -8; break;
3577 case 'I': remoteoffset = -9; break;
3578 case 'K': remoteoffset = -10; break; /* J is not used */
3579 case 'L': remoteoffset = -11; break;
3580 case 'M': remoteoffset = -12; break;
3581 case 'N': remoteoffset = 1; break;
3582 case 'O': remoteoffset = 2; break;
3583 case 'P': remoteoffset = 3; break;
3584 case 'Q': remoteoffset = 4; break;
3585 case 'R': remoteoffset = 5; break;
3586 case 'S': remoteoffset = 6; break;
3587 case 'T': remoteoffset = 7; break;
3588 case 'U': remoteoffset = 8; break;
3589 case 'V': remoteoffset = 9; break;
3590 case 'W': remoteoffset = 10; break;
3591 case 'X': remoteoffset = 11; break;
3592 case 'Y': remoteoffset = 12; break;
3593 default: remoteoffset = 0; break;
3595 remoteoffset = remoteoffset * 3600;
3599 return remoteoffset;
3602 time_t tzoffset_sec(time_t *now)
3606 struct tm buf1, buf2;
3608 if (now && *now < 0)
3611 gmt = *gmtime_r(now, &buf1);
3612 lt = localtime_r(now, &buf2);
3614 off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
3616 if (lt->tm_year < gmt.tm_year)
3618 else if (lt->tm_year > gmt.tm_year)
3620 else if (lt->tm_yday < gmt.tm_yday)
3622 else if (lt->tm_yday > gmt.tm_yday)
3625 if (off >= 24 * 60) /* should be impossible */
3626 off = 23 * 60 + 59; /* if not, insert silly value */
3627 if (off <= -24 * 60)
3628 off = -(23 * 60 + 59);
3633 /* calculate timezone offset */
3634 gchar *tzoffset(time_t *now)
3636 static gchar offset_string[6];
3640 struct tm buf1, buf2;
3642 if (now && *now < 0)
3645 gmt = *gmtime_r(now, &buf1);
3646 lt = localtime_r(now, &buf2);
3648 off = (lt->tm_hour - gmt.tm_hour) * 60 + lt->tm_min - gmt.tm_min;
3650 if (lt->tm_year < gmt.tm_year)
3652 else if (lt->tm_year > gmt.tm_year)
3654 else if (lt->tm_yday < gmt.tm_yday)
3656 else if (lt->tm_yday > gmt.tm_yday)
3664 if (off >= 24 * 60) /* should be impossible */
3665 off = 23 * 60 + 59; /* if not, insert silly value */
3667 sprintf(offset_string, "%c%02d%02d", sign, off / 60, off % 60);
3669 return offset_string;
3672 void get_rfc822_date(gchar *buf, gint len)
3676 gchar day[4], mon[4];
3677 gint dd, hh, mm, ss, yyyy;
3679 gchar buf2[BUFFSIZE];
3682 lt = localtime_r(&t, &buf1);
3684 sscanf(asctime_r(lt, buf2), "%3s %3s %d %d:%d:%d %d\n",
3685 day, mon, &dd, &hh, &mm, &ss, &yyyy);
3687 g_snprintf(buf, len, "%s, %d %s %d %02d:%02d:%02d %s",
3688 day, dd, mon, yyyy, hh, mm, ss, tzoffset(&t));
3691 void debug_set_mode(gboolean mode)
3696 gboolean debug_get_mode(void)
3701 void debug_print_real(const gchar *format, ...)
3704 gchar buf[BUFFSIZE];
3706 if (!debug_mode) return;
3708 va_start(args, format);
3709 g_vsnprintf(buf, sizeof(buf), format, args);
3716 const char * debug_srcname(const char *file)
3718 const char *s = strrchr (file, '/');
3723 void * subject_table_lookup(GHashTable *subject_table, gchar * subject)
3725 if (subject == NULL)
3728 subject += subject_get_prefix_length(subject);
3730 return g_hash_table_lookup(subject_table, subject);
3733 void subject_table_insert(GHashTable *subject_table, gchar * subject,
3736 if (subject == NULL || *subject == 0)
3738 subject += subject_get_prefix_length(subject);
3739 g_hash_table_insert(subject_table, subject, data);
3742 void subject_table_remove(GHashTable *subject_table, gchar * subject)
3744 if (subject == NULL)
3747 subject += subject_get_prefix_length(subject);
3748 g_hash_table_remove(subject_table, subject);
3752 static regex_t u_regex;
3753 static gboolean u_init_;
3756 void utils_free_regex(void)
3767 *\brief Check if a string is prefixed with known (combinations)
3768 * of prefixes. The function assumes that each prefix
3769 * is terminated by zero or exactly _one_ space.
3771 *\param str String to check for a prefixes
3773 *\return int Number of chars in the prefix that should be skipped
3774 * for a "clean" subject line. If no prefix was found, 0
3777 int subject_get_prefix_length(const gchar *subject)
3780 /*!< Array with allowable reply prefixes regexps. */
3781 static const gchar * const prefixes[] = {
3782 "Re\\:", /* "Re:" */
3783 "Re\\[[1-9][0-9]*\\]\\:", /* "Re[XXX]:" (non-conforming news mail clients) */
3784 "Antw\\:", /* "Antw:" (Dutch / German Outlook) */
3785 "Aw\\:", /* "Aw:" (German) */
3786 "Antwort\\:", /* "Antwort:" (German Lotus Notes) */
3787 "Res\\:", /* "Res:" (Spanish/Brazilian Outlook) */
3788 "Fw\\:", /* "Fw:" Forward */
3789 "Fwd\\:", /* "Fwd:" Forward */
3790 "Enc\\:", /* "Enc:" Forward (Brazilian Outlook) */
3791 "Odp\\:", /* "Odp:" Re (Polish Outlook) */
3792 "Rif\\:", /* "Rif:" (Italian Outlook) */
3793 "Sv\\:", /* "Sv" (Norwegian) */
3794 "Vs\\:", /* "Vs" (Norwegian) */
3795 "Ad\\:", /* "Ad" (Norwegian) */
3796 "\347\255\224\345\244\215\\:", /* "Re" (Chinese, UTF-8) */
3797 "R\303\251f\\. \\:", /* "R�f. :" (French Lotus Notes) */
3798 "Re \\:", /* "Re :" (French Yahoo Mail) */
3801 const int PREFIXES = sizeof prefixes / sizeof prefixes[0];
3805 if (!subject) return 0;
3806 if (!*subject) return 0;
3809 GString *s = g_string_new("");
3811 for (n = 0; n < PREFIXES; n++)
3812 /* Terminate each prefix regexpression by a
3813 * "\ ?" (zero or ONE space), and OR them */
3814 g_string_append_printf(s, "(%s\\ ?)%s",
3819 g_string_prepend(s, "(");
3820 g_string_append(s, ")+"); /* match at least once */
3821 g_string_prepend(s, "^\\ *"); /* from beginning of line */
3824 /* We now have something like "^\ *((PREFIX1\ ?)|(PREFIX2\ ?))+"
3825 * TODO: Should this be "^\ *(((PREFIX1)|(PREFIX2))\ ?)+" ??? */
3826 if (regcomp(&u_regex, s->str, REG_EXTENDED | REG_ICASE)) {
3827 debug_print("Error compiling regexp %s\n", s->str);
3828 g_string_free(s, TRUE);
3832 g_string_free(s, TRUE);
3836 if (!regexec(&u_regex, subject, 1, &pos, 0) && pos.rm_so != -1)
3841 /*!< Array with allowable reply prefixes regexps. */
3842 static const gchar * const prefixes[] = {
3844 "antw:", /* "Antw:" (Dutch / German Outlook) */
3845 "aw:", /* "Aw:" (German) */
3846 "antwort:", /* "Antwort:" (German Lotus Notes) */
3847 "res:", /* "Res:" (Spanish/Brazilian Outlook) */
3848 "fw:", /* "Fw:" Forward */
3849 "fwd:", /* "Fwd:" Forward */
3850 "enc:", /* "Enc:" Forward (Brazilian Outlook) */
3851 "odp:", /* "Odp:" Re (Polish Outlook) */
3852 "rif:", /* "Rif:" (Italian Outlook) */
3853 "sv:", /* "Sv" (Norwegian) */
3854 "vs:", /* "Vs" (Norwegian) */
3855 "ad:", /* "Ad" (Norwegian) */
3856 "R\303\251f. :", /* "R�f. :" (French Lotus Notes) */
3857 "Re :", /* "Re :" (French Yahoo Mail) */
3860 const int PREFIXES = sizeof prefixes / sizeof prefixes[0];
3863 if (!subject) return 0;
3864 if (!*subject) return 0;
3866 for (n = 0; n < PREFIXES; n++) {
3867 int len = strlen(prefixes[n]);
3868 if (!strncasecmp(subject, prefixes[n], len)) {
3869 if (subject[len] == ' ')
3878 static guint g_stricase_hash(gconstpointer gptr)
3880 guint hash_result = 0;
3883 for (str = gptr; str && *str; str++) {
3884 hash_result += toupper(*str);
3890 static gint g_stricase_equal(gconstpointer gptr1, gconstpointer gptr2)
3892 const char *str1 = gptr1;
3893 const char *str2 = gptr2;
3895 return !strcasecmp(str1, str2);
3898 gint g_int_compare(gconstpointer a, gconstpointer b)
3900 return GPOINTER_TO_INT(a) - GPOINTER_TO_INT(b);
3903 gchar *generate_msgid(gchar *buf, gint len, gchar *user_addr)
3911 lt = localtime_r(&t, &buft);
3913 if (user_addr != NULL)
3914 addr = g_strdup_printf(".%s", user_addr);
3915 else if (strlen(buf) != 0)
3916 addr = g_strdup_printf("@%s", buf);
3918 addr = g_strdup_printf("@%s", get_domain_name());
3920 /* Replace all @ but the last one in addr, with underscores.
3921 * RFC 2822 States that msg-id syntax only allows one @.
3923 while (strchr(addr, '@') != NULL && strchr(addr, '@') != strrchr(addr, '@'))
3924 *(strchr(addr, '@')) = '_';
3926 g_snprintf(buf, len, "%04d%02d%02d%02d%02d%02d.%08x%s",
3927 lt->tm_year + 1900, lt->tm_mon + 1,
3928 lt->tm_mday, lt->tm_hour,
3929 lt->tm_min, lt->tm_sec,
3930 (guint) rand(), addr);
3937 quote_cmd_argument()
3939 return a quoted string safely usable in argument of a command.
3941 code is extracted and adapted from etPan! project -- DINH V. Ho�.
3944 gint quote_cmd_argument(gchar * result, guint size,
3954 for(p = path ; * p != '\0' ; p ++) {
3956 if (isalnum((guchar)*p) || (* p == '/')) {
3957 if (remaining > 0) {
3963 result[size - 1] = '\0';
3968 if (remaining >= 2) {
3976 result[size - 1] = '\0';
3981 if (remaining > 0) {
3985 result[size - 1] = '\0';
3999 static void g_node_map_recursive(GNode *node, gpointer data)
4001 GNodeMapData *mapdata = (GNodeMapData *) data;
4003 GNodeMapData newmapdata;
4006 newdata = mapdata->func(node->data, mapdata->data);
4007 if (newdata != NULL) {
4008 newnode = g_node_new(newdata);
4009 g_node_append(mapdata->parent, newnode);
4011 newmapdata.parent = newnode;
4012 newmapdata.func = mapdata->func;
4013 newmapdata.data = mapdata->data;
4015 g_node_children_foreach(node, G_TRAVERSE_ALL, g_node_map_recursive, &newmapdata);
4019 GNode *g_node_map(GNode *node, GNodeMapFunc func, gpointer data)
4022 GNodeMapData mapdata;
4024 cm_return_val_if_fail(node != NULL, NULL);
4025 cm_return_val_if_fail(func != NULL, NULL);
4027 root = g_node_new(func(node->data, data));
4029 mapdata.parent = root;
4030 mapdata.func = func;
4031 mapdata.data = data;
4033 g_node_children_foreach(node, G_TRAVERSE_ALL, g_node_map_recursive, &mapdata);
4038 #define HEX_TO_INT(val, hex) \
4042 if ('0' <= c && c <= '9') { \
4044 } else if ('a' <= c && c <= 'f') { \
4045 val = c - 'a' + 10; \
4046 } else if ('A' <= c && c <= 'F') { \
4047 val = c - 'A' + 10; \
4053 gboolean get_hex_value(guchar *out, gchar c1, gchar c2)
4060 if (hi == -1 || lo == -1)
4063 *out = (hi << 4) + lo;
4067 #define INT_TO_HEX(hex, val) \
4070 hex = '0' + (val); \
4072 hex = 'A' + (val) - 10; \
4075 void get_hex_str(gchar *out, guchar ch)
4079 INT_TO_HEX(hex, ch >> 4);
4081 INT_TO_HEX(hex, ch & 0x0f);
4087 #define G_PRINT_REF 1 == 1 ? (void) 0 : (void)
4089 #define G_PRINT_REF g_print
4093 *\brief Register ref counted pointer. It is based on GBoxed, so should
4094 * work with anything that uses the GType system. The semantics
4095 * are similar to a C++ auto pointer, with the exception that
4096 * C doesn't have automatic closure (calling destructors) when
4097 * exiting a block scope.
4098 * Use the \ref G_TYPE_AUTO_POINTER macro instead of calling this
4099 * function directly.
4101 *\return GType A GType type.
4103 GType g_auto_pointer_register(void)
4105 static GType auto_pointer_type;
4106 if (!auto_pointer_type)
4108 g_boxed_type_register_static
4109 ("G_TYPE_AUTO_POINTER",
4110 (GBoxedCopyFunc) g_auto_pointer_copy,
4111 (GBoxedFreeFunc) g_auto_pointer_free);
4112 return auto_pointer_type;
4116 *\brief Structure with g_new() allocated pointer guarded by the
4119 typedef struct AutoPointerRef {
4120 void (*free) (gpointer);
4126 *\brief The auto pointer opaque structure that references the
4127 * pointer guard block.
4129 typedef struct AutoPointer {
4130 AutoPointerRef *ref;
4131 gpointer ptr; /*!< access to protected pointer */
4135 *\brief Creates an auto pointer for a g_new()ed pointer. Example:
4139 * ... tell gtk_list_store it should use a G_TYPE_AUTO_POINTER
4140 * ... when assigning, copying and freeing storage elements
4142 * gtk_list_store_new(N_S_COLUMNS,
4143 * G_TYPE_AUTO_POINTER,
4147 * Template *precious_data = g_new0(Template, 1);
4148 * g_pointer protect = g_auto_pointer_new(precious_data);
4150 * gtk_list_store_set(container, &iter,
4154 * ... the gtk_list_store has copied the pointer and
4155 * ... incremented its reference count, we should free
4156 * ... the auto pointer (in C++ a destructor would do
4157 * ... this for us when leaving block scope)
4159 * g_auto_pointer_free(protect);
4161 * ... gtk_list_store_set() now manages the data. When
4162 * ... *explicitly* requesting a pointer from the list
4163 * ... store, don't forget you get a copy that should be
4164 * ... freed with g_auto_pointer_free() eventually.
4168 *\param pointer Pointer to be guarded.
4170 *\return GAuto * Pointer that should be used in containers with
4173 GAuto *g_auto_pointer_new(gpointer p)
4175 AutoPointerRef *ref;
4181 ref = g_new0(AutoPointerRef, 1);
4182 ptr = g_new0(AutoPointer, 1);
4192 G_PRINT_REF ("XXXX ALLOC(%lx)\n", p);
4198 *\brief Allocate an autopointer using the passed \a free function to
4199 * free the guarded pointer
4201 GAuto *g_auto_pointer_new_with_free(gpointer p, GFreeFunc free_)
4208 aptr = g_auto_pointer_new(p);
4209 aptr->ref->free = free_;
4213 gpointer g_auto_pointer_get_ptr(GAuto *auto_ptr)
4215 if (auto_ptr == NULL)
4217 return ((AutoPointer *) auto_ptr)->ptr;
4221 *\brief Copies an auto pointer by. It's mostly not necessary
4222 * to call this function directly, unless you copy/assign
4223 * the guarded pointer.
4225 *\param auto_ptr Auto pointer returned by previous call to
4226 * g_auto_pointer_new_XXX()
4228 *\return gpointer An auto pointer
4230 GAuto *g_auto_pointer_copy(GAuto *auto_ptr)
4233 AutoPointerRef *ref;
4236 if (auto_ptr == NULL)
4241 newp = g_new0(AutoPointer, 1);
4244 newp->ptr = ref->pointer;
4248 G_PRINT_REF ("XXXX COPY(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4254 *\brief Free an auto pointer
4256 void g_auto_pointer_free(GAuto *auto_ptr)
4259 AutoPointerRef *ref;
4261 if (auto_ptr == NULL)
4267 if (--(ref->cnt) == 0) {
4269 G_PRINT_REF ("XXXX FREE(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4271 ref->free(ref->pointer);
4276 G_PRINT_REF ("XXXX DEREF(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
4281 void replace_returns(gchar *str)
4286 while (strstr(str, "\n")) {
4287 *strstr(str, "\n") = ' ';
4289 while (strstr(str, "\r")) {
4290 *strstr(str, "\r") = ' ';
4294 /* get_uri_part() - retrieves a URI starting from scanpos.
4295 Returns TRUE if succesful */
4296 gboolean get_uri_part(const gchar *start, const gchar *scanpos,
4297 const gchar **bp, const gchar **ep, gboolean hdr)
4300 gint parenthese_cnt = 0;
4302 cm_return_val_if_fail(start != NULL, FALSE);
4303 cm_return_val_if_fail(scanpos != NULL, FALSE);
4304 cm_return_val_if_fail(bp != NULL, FALSE);
4305 cm_return_val_if_fail(ep != NULL, FALSE);
4309 /* find end point of URI */
4310 for (ep_ = scanpos; *ep_ != '\0'; ep_++) {
4311 if (!g_ascii_isgraph(*(const guchar *)ep_) ||
4312 !IS_ASCII(*(const guchar *)ep_) ||
4313 strchr("[]{}<>\"", *ep_)) {
4315 } else if (strchr("(", *ep_)) {
4317 } else if (strchr(")", *ep_)) {
4318 if (parenthese_cnt > 0)
4325 /* no punctuation at end of string */
4327 /* FIXME: this stripping of trailing punctuations may bite with other URIs.
4328 * should pass some URI type to this function and decide on that whether
4329 * to perform punctuation stripping */
4331 #define IS_REAL_PUNCT(ch) (g_ascii_ispunct(ch) && !strchr("/?=-_)", ch))
4333 for (; ep_ - 1 > scanpos + 1 &&
4334 IS_REAL_PUNCT(*(ep_ - 1));
4338 #undef IS_REAL_PUNCT
4345 gchar *make_uri_string(const gchar *bp, const gchar *ep)
4347 while (bp && *bp && g_ascii_isspace(*bp))
4349 return g_strndup(bp, ep - bp);
4352 /* valid mail address characters */
4353 #define IS_RFC822_CHAR(ch) \
4357 !g_ascii_isspace(ch) && \
4358 !strchr("(),;<>\"", (ch)))
4360 /* alphabet and number within 7bit ASCII */
4361 #define IS_ASCII_ALNUM(ch) (IS_ASCII(ch) && g_ascii_isalnum(ch))
4362 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
4364 static GHashTable *create_domain_tab(void)
4366 static const gchar *toplvl_domains [] = {
4368 "arpa", "coop", "info", "name", "biz", "com", "edu", "gov",
4369 "int", "mil", "net", "org", "ac", "ad", "ae", "af", "ag",
4370 "ai", "al", "am", "an", "ao", "aq", "ar", "as", "at", "au",
4371 "aw", "az", "ba", "bb", "bd", "be", "bf", "bg", "bh", "bi",
4372 "bj", "bm", "bn", "bo", "br", "bs", "bt", "bv", "bw", "by",
4373 "bz", "ca", "cc", "cd", "cf", "cg", "ch", "ci", "ck", "cl",
4374 "cm", "cn", "co", "cr", "cu", "cv", "cx", "cy", "cz", "de",
4375 "dj", "dk", "dm", "do", "dz", "ec", "ee", "eg", "eh", "er",
4376 "es", "et", "eu", "fi", "fj", "fk", "fm", "fo", "fr", "ga", "gd",
4377 "ge", "gf", "gg", "gh", "gi", "gl", "gm", "gn", "gp", "gq",
4378 "gr", "gs", "gt", "gu", "gw", "gy", "hk", "hm", "hn", "hr",
4379 "ht", "hu", "id", "ie", "il", "im", "in", "io", "iq", "ir",
4380 "is", "it", "je", "jm", "jo", "jp", "ke", "kg", "kh", "ki",
4381 "km", "kn", "kp", "kr", "kw", "ky", "kz", "la", "lb", "lc",
4382 "li", "lk", "lr", "ls", "lt", "lu", "lv", "ly", "ma", "mc",
4383 "md", "mg", "mh", "mk", "ml", "mm", "mn", "mo", "mp", "mq",
4384 "mr", "ms", "mt", "mu", "mv", "mw", "mx", "my", "mz", "na",
4385 "nc", "ne", "nf", "ng", "ni", "nl", "no", "np", "nr", "nu",
4386 "nz", "om", "pa", "pe", "pf", "pg", "ph", "pk", "pl", "pm",
4387 "pn", "pr", "ps", "pt", "pw", "py", "qa", "re", "ro", "ru",
4388 "rw", "sa", "sb", "sc", "sd", "se", "sg", "sh", "si", "sj",
4389 "sk", "sl", "sm", "sn", "so", "sr", "st", "sv", "sy", "sz",
4390 "tc", "td", "tf", "tg", "th", "tj", "tk", "tm", "tn", "to",
4391 "tp", "tr", "tt", "tv", "tw", "tz", "ua", "ug", "uk", "um",
4392 "us", "uy", "uz", "va", "vc", "ve", "vg", "vi", "vn", "vu",
4393 "wf", "ws", "ye", "yt", "yu", "za", "zm", "zw"
4396 GHashTable *htab = g_hash_table_new(g_stricase_hash, g_stricase_equal);
4398 cm_return_val_if_fail(htab, NULL);
4399 for (n = 0; n < sizeof toplvl_domains / sizeof toplvl_domains[0]; n++)
4400 g_hash_table_insert(htab, (gpointer) toplvl_domains[n], (gpointer) toplvl_domains[n]);
4404 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
4406 const gint MAX_LVL_DOM_NAME_LEN = 6;
4407 gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
4408 const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
4411 if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
4414 for (p = buf; p < m && first < last; *p++ = *first++)
4418 return g_hash_table_lookup(tab, buf) != NULL;
4421 /* get_email_part() - retrieves an email address. Returns TRUE if succesful */
4422 gboolean get_email_part(const gchar *start, const gchar *scanpos,
4423 const gchar **bp, const gchar **ep, gboolean hdr)
4425 /* more complex than the uri part because we need to scan back and forward starting from
4426 * the scan position. */
4427 gboolean result = FALSE;
4428 const gchar *bp_ = NULL;
4429 const gchar *ep_ = NULL;
4430 static GHashTable *dom_tab;
4431 const gchar *last_dot = NULL;
4432 const gchar *prelast_dot = NULL;
4433 const gchar *last_tld_char = NULL;
4435 /* the informative part of the email address (describing the name
4436 * of the email address owner) may contain quoted parts. the
4437 * closure stack stores the last encountered quotes. */
4438 gchar closure_stack[128];
4439 gchar *ptr = closure_stack;
4441 cm_return_val_if_fail(start != NULL, FALSE);
4442 cm_return_val_if_fail(scanpos != NULL, FALSE);
4443 cm_return_val_if_fail(bp != NULL, FALSE);
4444 cm_return_val_if_fail(ep != NULL, FALSE);
4447 const gchar *start_quote = NULL;
4448 const gchar *end_quote = NULL;
4450 /* go to the real start */
4451 if (start[0] == ',')
4453 if (start[0] == ';')
4455 while (start[0] == '\n' || start[0] == '\r')
4457 while (start[0] == ' ' || start[0] == '\t')
4462 /* check if there are quotes (to skip , in them) */
4463 if (*start == '"') {
4464 start_quote = start;
4466 end_quote = strstr(start, "\"");
4472 /* skip anything between quotes */
4473 if (start_quote && end_quote) {
4478 /* find end (either , or ; or end of line) */
4479 if (strstr(start, ",") && strstr(start, ";"))
4480 *ep = strstr(start,",") < strstr(start, ";")
4481 ? strstr(start, ",") : strstr(start, ";");
4482 else if (strstr(start, ","))
4483 *ep = strstr(start, ",");
4484 else if (strstr(start, ";"))
4485 *ep = strstr(start, ";");
4487 *ep = start+strlen(start);
4489 /* go back to real start */
4490 if (start_quote && end_quote) {
4491 start = start_quote;
4494 /* check there's still an @ in that, or search
4495 * further if possible */
4496 if (strstr(start, "@") && strstr(start, "@") < *ep)
4498 else if (*ep < start+strlen(start)) {
4501 } else if (start_quote && strstr(start, "\"") && strstr(start, "\"") < *ep) {
4509 dom_tab = create_domain_tab();
4510 cm_return_val_if_fail(dom_tab, FALSE);
4512 /* scan start of address */
4513 for (bp_ = scanpos - 1;
4514 bp_ >= start && IS_RFC822_CHAR(*(const guchar *)bp_); bp_--)
4517 /* TODO: should start with an alnum? */
4519 for (; bp_ < scanpos && !IS_ASCII_ALNUM(*(const guchar *)bp_); bp_++)
4522 if (bp_ != scanpos) {
4523 /* scan end of address */
4524 for (ep_ = scanpos + 1;
4525 *ep_ && IS_RFC822_CHAR(*(const guchar *)ep_); ep_++)
4527 prelast_dot = last_dot;
4529 if (*(last_dot + 1) == '.') {
4530 if (prelast_dot == NULL)
4532 last_dot = prelast_dot;
4537 /* TODO: really should terminate with an alnum? */
4538 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*(const guchar *)ep_);
4543 if (last_dot == NULL)
4545 if (last_dot >= ep_)
4546 last_dot = prelast_dot;
4547 if (last_dot == NULL || (scanpos + 1 >= last_dot))
4551 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
4552 if (*last_tld_char == '?')
4555 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
4562 if (!result) return FALSE;
4564 if (*ep_ && bp_ != start && *(bp_ - 1) == '"' && *(ep_) == '"'
4565 && *(ep_ + 1) == ' ' && *(ep_ + 2) == '<'
4566 && IS_RFC822_CHAR(*(ep_ + 3))) {
4567 /* this informative part with an @ in it is
4568 * followed by the email address */
4571 /* go to matching '>' (or next non-rfc822 char, like \n) */
4572 for (; *ep_ != '>' && *ep != '\0' && IS_RFC822_CHAR(*ep_); ep_++)
4575 /* include the bracket */
4576 if (*ep_ == '>') ep_++;
4578 /* include the leading quote */
4586 /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
4587 if (bp_ - 1 > start && IS_QUOTE(*(bp_ - 1)) && IS_QUOTE(*ep_))
4590 /* see if this is <bracketed>; in this case we also scan for the informative part. */
4591 if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
4594 #define FULL_STACK() ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
4595 #define IN_STACK() (ptr > closure_stack)
4596 /* has underrun check */
4597 #define POP_STACK() if(IN_STACK()) --ptr
4598 /* has overrun check */
4599 #define PUSH_STACK(c) if(!FULL_STACK()) *ptr++ = (c); else return TRUE
4600 /* has underrun check */
4601 #define PEEK_STACK() (IN_STACK() ? *(ptr - 1) : 0)
4605 /* scan for the informative part. */
4606 for (bp_ -= 2; bp_ >= start; bp_--) {
4607 /* if closure on the stack keep scanning */
4608 if (PEEK_STACK() == *bp_) {
4612 if (!IN_STACK() && (*bp_ == '\'' || *bp_ == '"')) {
4617 /* if nothing in the closure stack, do the special conditions
4618 * the following if..else expression simply checks whether
4619 * a token is acceptable. if not acceptable, the clause
4620 * should terminate the loop with a 'break' */
4621 if (!PEEK_STACK()) {
4623 && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
4624 && (((bp_ + 1) < ep_) && isalnum(*(bp_ + 1)))) {
4625 /* hyphens are allowed, but only in
4627 } else if (strchr(" \"'", *bp_)) {
4628 /* but anything not being a punctiation
4631 break; /* anything else is rejected */
4638 /* scan forward (should start with an alnum) */
4639 for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
4655 #undef IS_ASCII_ALNUM
4656 #undef IS_RFC822_CHAR
4658 gchar *make_email_string(const gchar *bp, const gchar *ep)
4660 /* returns a mailto: URI; mailto: is also used to detect the
4661 * uri type later on in the button_pressed signal handler */
4665 tmp = g_strndup(bp, ep - bp);
4666 result = g_strconcat("mailto:", tmp, NULL);
4672 gchar *make_http_string(const gchar *bp, const gchar *ep)
4674 /* returns an http: URI; */
4678 while (bp && *bp && g_ascii_isspace(*bp))
4680 tmp = g_strndup(bp, ep - bp);
4681 result = g_strconcat("http://", tmp, NULL);
4687 static gchar *mailcap_get_command_in_file(const gchar *path, const gchar *type, const gchar *file_to_open)
4689 FILE *fp = g_fopen(path, "rb");
4690 gchar buf[BUFFSIZE];
4691 gchar *result = NULL;
4694 while (fgets(buf, sizeof (buf), fp) != NULL) {
4695 gchar **parts = g_strsplit(buf, ";", 3);
4696 gchar *trimmed = parts[0];
4697 while (trimmed[0] == ' ' || trimmed[0] == '\t')
4699 while (trimmed[strlen(trimmed)-1] == ' ' || trimmed[strlen(trimmed)-1] == '\t')
4700 trimmed[strlen(trimmed)-1] = '\0';
4702 if (!strcmp(trimmed, type)) {
4703 gboolean needsterminal = FALSE;
4704 if (parts[2] && strstr(parts[2], "needsterminal")) {
4705 needsterminal = TRUE;
4707 if (parts[2] && strstr(parts[2], "test=")) {
4708 gchar *orig_testcmd = g_strdup(strstr(parts[2], "test=")+5);
4709 gchar *testcmd = orig_testcmd;
4710 if (strstr(testcmd,";"))
4711 *(strstr(testcmd,";")) = '\0';
4712 while (testcmd[0] == ' ' || testcmd[0] == '\t')
4714 while (testcmd[strlen(testcmd)-1] == '\n')
4715 testcmd[strlen(testcmd)-1] = '\0';
4716 while (testcmd[strlen(testcmd)-1] == '\r')
4717 testcmd[strlen(testcmd)-1] = '\0';
4718 while (testcmd[strlen(testcmd)-1] == ' ' || testcmd[strlen(testcmd)-1] == '\t')
4719 testcmd[strlen(testcmd)-1] = '\0';
4721 if (strstr(testcmd, "%s")) {
4722 gchar *tmp = g_strdup_printf(testcmd, file_to_open);
4723 gint res = system(tmp);
4725 g_free(orig_testcmd);
4732 gint res = system(testcmd);
4733 g_free(orig_testcmd);
4743 while (trimmed[0] == ' ' || trimmed[0] == '\t')
4745 while (trimmed[strlen(trimmed)-1] == '\n')
4746 trimmed[strlen(trimmed)-1] = '\0';
4747 while (trimmed[strlen(trimmed)-1] == '\r')
4748 trimmed[strlen(trimmed)-1] = '\0';
4749 while (trimmed[strlen(trimmed)-1] == ' ' || trimmed[strlen(trimmed)-1] == '\t')
4750 trimmed[strlen(trimmed)-1] = '\0';
4751 result = g_strdup(trimmed);
4754 /* if there are no single quotes around %s, add them.
4755 * '.*%s.*' is ok, as in display 'png:%s'
4757 if (strstr(result, "%s")
4758 && !(strstr(result, "'") < strstr(result,"%s") &&
4759 strstr(strstr(result,"%s"), "'"))) {
4760 gchar *start = g_strdup(result);
4761 gchar *end = g_strdup(strstr(result, "%s")+2);
4763 *strstr(start, "%s") = '\0';
4764 tmp = g_strconcat(start,"'%s'",end, NULL);
4770 if (needsterminal) {
4771 gchar *tmp = g_strdup_printf("xterm -e %s", result);
4782 gchar *mailcap_get_command_for_type(const gchar *type, const gchar *file_to_open)
4784 gchar *result = NULL;
4788 path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap", NULL);
4789 result = mailcap_get_command_in_file(path, type, file_to_open);
4793 result = mailcap_get_command_in_file("/etc/mailcap", type, file_to_open);
4797 void mailcap_update_default(const gchar *type, const gchar *command)
4799 gchar *path = NULL, *outpath = NULL;
4800 path = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap", NULL);
4801 outpath = g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S, ".mailcap.new", NULL);
4802 FILE *fp = g_fopen(path, "rb");
4804 gchar buf[BUFFSIZE];
4805 gboolean err = FALSE;
4808 fp = g_fopen(path, "a");
4810 g_warning("failed to create file %s\n", path);
4815 fp = g_freopen(path, "rb", fp);
4817 g_warning("failed to reopen file %s\n", path);
4824 outfp = g_fopen(outpath, "wb");
4826 g_warning("failed to create file %s\n", outpath);
4832 while (fp && fgets(buf, sizeof (buf), fp) != NULL) {
4833 gchar **parts = g_strsplit(buf, ";", 3);
4834 gchar *trimmed = parts[0];
4835 while (trimmed[0] == ' ')
4837 while (trimmed[strlen(trimmed)-1] == ' ')
4838 trimmed[strlen(trimmed)-1] = '\0';
4840 if (!strcmp(trimmed, type)) {
4845 if(fputs(buf, outfp) == EOF) {
4852 if (fprintf(outfp, "%s; %s\n", type, command) < 0)
4858 if (fclose(outfp) == EOF)
4862 g_rename(outpath, path);
4868 gint copy_dir(const gchar *src, const gchar *dst)
4873 if ((dir = g_dir_open(src, 0, NULL)) == NULL) {
4874 g_warning("failed to open directory: %s\n", src);
4878 if (make_dir(dst) < 0)
4881 while ((name = g_dir_read_name(dir)) != NULL) {
4882 gchar *old_file, *new_file;
4883 old_file = g_strconcat(src, G_DIR_SEPARATOR_S, name, NULL);
4884 new_file = g_strconcat(dst, G_DIR_SEPARATOR_S, name, NULL);
4885 debug_print("copying: %s -> %s\n", old_file, new_file);
4886 if (g_file_test(old_file, G_FILE_TEST_IS_REGULAR)) {
4887 gint r = copy_file(old_file, new_file, TRUE);
4894 /* Windows has no symlinks. Or well, Vista seems to
4895 have something like this but the semantics might be
4896 different. Thus we don't use it under Windows. */
4897 else if (g_file_test(old_file, G_FILE_TEST_IS_SYMLINK)) {
4900 gchar *target = g_file_read_link(old_file, &error);
4902 r = symlink(target, new_file);
4909 #endif /*G_OS_WIN32*/
4910 else if (g_file_test(old_file, G_FILE_TEST_IS_DIR)) {
4911 gint r = copy_dir(old_file, new_file);
4922 /* crude test to see if a file is an email. */
4923 gboolean file_is_email (const gchar *filename)
4929 if (filename == NULL)
4931 if ((fp = g_fopen(filename, "rb")) == NULL)
4933 while (i < 60 && score < 3
4934 && fgets(buffer, sizeof (buffer), fp) > 0) {
4935 if (!strncmp(buffer, "From:", strlen("From:")))
4937 else if (!strncmp(buffer, "Date:", strlen("Date:")))
4939 else if (!strncmp(buffer, "Message-ID:", strlen("Message-ID:")))
4941 else if (!strncmp(buffer, "Subject:", strlen("Subject:")))
4946 return (score >= 3);
4949 gboolean sc_g_list_bigger(GList *list, gint max)
4953 while (cur && i <= max+1) {
4960 gboolean sc_g_slist_bigger(GSList *list, gint max)
4964 while (cur && i <= max+1) {
4971 const gchar *daynames[] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL};
4972 const gchar *monthnames[] = {NULL, NULL, NULL, NULL, NULL, NULL,
4973 NULL, NULL, NULL, NULL, NULL, NULL};
4974 const gchar *s_daynames[] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL};
4975 const gchar *s_monthnames[] = {NULL, NULL, NULL, NULL, NULL, NULL,
4976 NULL, NULL, NULL, NULL, NULL, NULL};
4978 gint daynames_len[] = {0,0,0,0,0,0,0};
4979 gint monthnames_len[] = {0,0,0,0,0,0,
4981 gint s_daynames_len[] = {0,0,0,0,0,0,0};
4982 gint s_monthnames_len[] = {0,0,0,0,0,0,
4984 const gchar *s_am_up = NULL;
4985 const gchar *s_pm_up = NULL;
4986 const gchar *s_am_low = NULL;
4987 const gchar *s_pm_low = NULL;
4989 gint s_am_up_len = 0;
4990 gint s_pm_up_len = 0;
4991 gint s_am_low_len = 0;
4992 gint s_pm_low_len = 0;
4994 static gboolean time_names_init_done = FALSE;
4996 static void init_time_names(void)
5000 daynames[0] = C_("Complete day name for use by strftime", "Sunday");
5001 daynames[1] = C_("Complete day name for use by strftime", "Monday");
5002 daynames[2] = C_("Complete day name for use by strftime", "Tuesday");
5003 daynames[3] = C_("Complete day name for use by strftime", "Wednesday");
5004 daynames[4] = C_("Complete day name for use by strftime", "Thursday");
5005 daynames[5] = C_("Complete day name for use by strftime", "Friday");
5006 daynames[6] = C_("Complete day name for use by strftime", "Saturday");
5008 monthnames[0] = C_("Complete month name for use by strftime", "January");
5009 monthnames[1] = C_("Complete month name for use by strftime", "February");
5010 monthnames[2] = C_("Complete month name for use by strftime", "March");
5011 monthnames[3] = C_("Complete month name for use by strftime", "April");
5012 monthnames[4] = C_("Complete month name for use by strftime", "May");
5013 monthnames[5] = C_("Complete month name for use by strftime", "June");
5014 monthnames[6] = C_("Complete month name for use by strftime", "July");
5015 monthnames[7] = C_("Complete month name for use by strftime", "August");
5016 monthnames[8] = C_("Complete month name for use by strftime", "September");
5017 monthnames[9] = C_("Complete month name for use by strftime", "October");
5018 monthnames[10] = C_("Complete month name for use by strftime", "November");
5019 monthnames[11] = C_("Complete month name for use by strftime", "December");
5021 s_daynames[0] = C_("Abbr. day name for use by strftime", "Sun");
5022 s_daynames[1] = C_("Abbr. day name for use by strftime", "Mon");
5023 s_daynames[2] = C_("Abbr. day name for use by strftime", "Tue");
5024 s_daynames[3] = C_("Abbr. day name for use by strftime", "Wed");
5025 s_daynames[4] = C_("Abbr. day name for use by strftime", "Thu");
5026 s_daynames[5] = C_("Abbr. day name for use by strftime", "Fri");
5027 s_daynames[6] = C_("Abbr. day name for use by strftime", "Sat");
5029 s_monthnames[0] = C_("Abbr. month name for use by strftime", "Jan");
5030 s_monthnames[1] = C_("Abbr. month name for use by strftime", "Feb");
5031 s_monthnames[2] = C_("Abbr. month name for use by strftime", "Mar");
5032 s_monthnames[3] = C_("Abbr. month name for use by strftime", "Apr");
5033 s_monthnames[4] = C_("Abbr. month name for use by strftime", "May");
5034 s_monthnames[5] = C_("Abbr. month name for use by strftime", "Jun");
5035 s_monthnames[6] = C_("Abbr. month name for use by strftime", "Jul");
5036 s_monthnames[7] = C_("Abbr. month name for use by strftime", "Aug");
5037 s_monthnames[8] = C_("Abbr. month name for use by strftime", "Sep");
5038 s_monthnames[9] = C_("Abbr. month name for use by strftime", "Oct");
5039 s_monthnames[10] = C_("Abbr. month name for use by strftime", "Nov");
5040 s_monthnames[11] = C_("Abbr. month name for use by strftime", "Dec");
5042 for (i = 0; i < 7; i++) {
5043 daynames_len[i] = strlen(daynames[i]);
5044 s_daynames_len[i] = strlen(s_daynames[i]);
5046 for (i = 0; i < 12; i++) {
5047 monthnames_len[i] = strlen(monthnames[i]);
5048 s_monthnames_len[i] = strlen(s_monthnames[i]);
5051 s_am_up = C_("For use by strftime (morning)", "AM");
5052 s_pm_up = C_("For use by strftime (afternoon)", "PM");
5053 s_am_low = C_("For use by strftime (morning, lowercase)", "am");
5054 s_pm_low = C_("For use by strftime (afternoon, lowercase)", "pm");
5056 s_am_up_len = strlen(s_am_up);
5057 s_pm_up_len = strlen(s_pm_up);
5058 s_am_low_len = strlen(s_am_low);
5059 s_pm_low_len = strlen(s_pm_low);
5061 time_names_init_done = TRUE;
5064 #define CHECK_SIZE() { \
5065 total_done += len; \
5066 if (total_done >= buflen) { \
5067 buf[buflen-1] = '\0'; \
5072 size_t fast_strftime(gchar *buf, gint buflen, const gchar *format, struct tm *lt)
5074 gchar *curpos = buf;
5075 gint total_done = 0;
5076 gchar subbuf[64], subfmt[64];
5077 static time_t last_tzset = (time_t)0;
5079 if (!time_names_init_done)
5082 if (format == NULL || lt == NULL)
5085 if (last_tzset != time(NULL)) {
5087 last_tzset = time(NULL);
5090 if (*format == '%') {
5091 gint len = 0, tmp = 0;
5095 len = 1; CHECK_SIZE();
5099 len = s_daynames_len[lt->tm_wday]; CHECK_SIZE();
5100 strncpy2(curpos, s_daynames[lt->tm_wday], buflen - total_done);
5103 len = daynames_len[lt->tm_wday]; CHECK_SIZE();
5104 strncpy2(curpos, daynames[lt->tm_wday], buflen - total_done);
5108 len = s_monthnames_len[lt->tm_mon]; CHECK_SIZE();
5109 strncpy2(curpos, s_monthnames[lt->tm_mon], buflen - total_done);
5112 len = monthnames_len[lt->tm_mon]; CHECK_SIZE();
5113 strncpy2(curpos, monthnames[lt->tm_mon], buflen - total_done);
5116 strftime(subbuf, 64, "%c", lt);
5117 len = strlen(subbuf); CHECK_SIZE();
5118 strncpy2(curpos, subbuf, buflen - total_done);
5121 total_done += 2; CHECK_SIZE();
5122 tmp = (lt->tm_year + 1900)/100;
5123 *curpos++ = '0'+(tmp / 10);
5124 *curpos++ = '0'+(tmp % 10);
5127 total_done += 2; CHECK_SIZE();
5128 *curpos++ = '0'+(lt->tm_mday / 10);
5129 *curpos++ = '0'+(lt->tm_mday % 10);
5132 total_done += 8; CHECK_SIZE();
5133 *curpos++ = '0'+((lt->tm_mon+1) / 10);
5134 *curpos++ = '0'+((lt->tm_mon+1) % 10);
5136 *curpos++ = '0'+(lt->tm_mday / 10);
5137 *curpos++ = '0'+(lt->tm_mday % 10);
5139 tmp = lt->tm_year%100;
5140 *curpos++ = '0'+(tmp / 10);
5141 *curpos++ = '0'+(tmp % 10);
5144 len = 2; CHECK_SIZE();
5145 snprintf(curpos, buflen - total_done, "%2d", lt->tm_mday);
5148 len = 10; CHECK_SIZE();
5149 snprintf(curpos, buflen - total_done, "%4d-%02d-%02d",
5150 lt->tm_year + 1900, lt->tm_mon +1, lt->tm_mday);
5153 total_done += 2; CHECK_SIZE();
5154 *curpos++ = '0'+(lt->tm_hour / 10);
5155 *curpos++ = '0'+(lt->tm_hour % 10);
5158 total_done += 2; CHECK_SIZE();
5164 *curpos++ = '0'+(tmp / 10);
5165 *curpos++ = '0'+(tmp % 10);
5168 len = 3; CHECK_SIZE();
5169 snprintf(curpos, buflen - total_done, "%03d", lt->tm_yday+1);
5172 len = 2; CHECK_SIZE();
5173 snprintf(curpos, buflen - total_done, "%2d", lt->tm_hour);
5176 len = 2; CHECK_SIZE();
5182 snprintf(curpos, buflen - total_done, "%2d", tmp);
5185 total_done += 2; CHECK_SIZE();
5186 tmp = lt->tm_mon + 1;
5187 *curpos++ = '0'+(tmp / 10);
5188 *curpos++ = '0'+(tmp % 10);
5191 total_done += 2; CHECK_SIZE();
5192 *curpos++ = '0'+(lt->tm_min / 10);
5193 *curpos++ = '0'+(lt->tm_min % 10);
5196 len = 1; CHECK_SIZE();
5200 if (lt->tm_hour >= 12) {
5201 len = s_pm_up_len; CHECK_SIZE();
5202 snprintf(curpos, buflen-total_done, "%s", s_pm_up);
5204 len = s_am_up_len; CHECK_SIZE();
5205 snprintf(curpos, buflen-total_done, "%s", s_am_up);
5209 if (lt->tm_hour >= 12) {
5210 len = s_pm_low_len; CHECK_SIZE();
5211 snprintf(curpos, buflen-total_done, "%s", s_pm_low);
5213 len = s_am_low_len; CHECK_SIZE();
5214 snprintf(curpos, buflen-total_done, "%s", s_am_low);
5218 strftime(subbuf, 64, "%r", lt);
5219 len = strlen(subbuf); CHECK_SIZE();
5220 strncpy2(curpos, subbuf, buflen - total_done);
5223 total_done += 5; CHECK_SIZE();
5224 *curpos++ = '0'+(lt->tm_hour / 10);
5225 *curpos++ = '0'+(lt->tm_hour % 10);
5227 *curpos++ = '0'+(lt->tm_min / 10);
5228 *curpos++ = '0'+(lt->tm_min % 10);
5231 snprintf(subbuf, 64, "%ld", mktime(lt));
5232 len = strlen(subbuf); CHECK_SIZE();
5233 strncpy2(curpos, subbuf, buflen - total_done);
5236 total_done += 2; CHECK_SIZE();
5237 *curpos++ = '0'+(lt->tm_sec / 10);
5238 *curpos++ = '0'+(lt->tm_sec % 10);
5241 len = 1; CHECK_SIZE();
5245 total_done += 8; CHECK_SIZE();
5246 *curpos++ = '0'+(lt->tm_hour / 10);
5247 *curpos++ = '0'+(lt->tm_hour % 10);
5249 *curpos++ = '0'+(lt->tm_min / 10);
5250 *curpos++ = '0'+(lt->tm_min % 10);
5252 *curpos++ = '0'+(lt->tm_sec / 10);
5253 *curpos++ = '0'+(lt->tm_sec % 10);
5256 len = 1; CHECK_SIZE();
5257 snprintf(curpos, buflen - total_done, "%d", lt->tm_wday == 0 ? 7: lt->tm_wday);
5260 len = 1; CHECK_SIZE();
5261 snprintf(curpos, buflen - total_done, "%d", lt->tm_wday);
5264 strftime(subbuf, 64, "%x", lt);
5265 len = strlen(subbuf); CHECK_SIZE();
5266 strncpy2(curpos, subbuf, buflen - total_done);
5269 strftime(subbuf, 64, "%X", lt);
5270 len = strlen(subbuf); CHECK_SIZE();
5271 strncpy2(curpos, subbuf, buflen - total_done);
5274 total_done += 2; CHECK_SIZE();
5275 tmp = lt->tm_year%100;
5276 *curpos++ = '0'+(tmp / 10);
5277 *curpos++ = '0'+(tmp % 10);
5280 len = 4; CHECK_SIZE();
5281 snprintf(curpos, buflen - total_done, "%4d", lt->tm_year + 1900);
5291 /* let these complicated ones be done with the libc */
5292 snprintf(subfmt, 64, "%%%c", *format);
5293 strftime(subbuf, 64, subfmt, lt);
5294 len = strlen(subbuf); CHECK_SIZE();
5295 strncpy2(curpos, subbuf, buflen - total_done);
5299 /* let these complicated modifiers be done with the libc */
5300 snprintf(subfmt, 64, "%%%c%c", *format, *(format+1));
5301 strftime(subbuf, 64, subfmt, lt);
5302 len = strlen(subbuf); CHECK_SIZE();
5303 strncpy2(curpos, subbuf, buflen - total_done);
5307 g_warning("format error (%c)", *format);
5314 int len = 1; CHECK_SIZE();
5315 *curpos++ = *format++;
5322 gboolean prefs_common_get_use_shred(void);
5326 #define WEXITSTATUS(x) (x)
5329 int claws_unlink(const gchar *filename)
5332 static int found_shred = -1;
5333 static const gchar *args[4];
5335 if (filename == NULL)
5338 if (prefs_common_get_use_shred()) {
5339 if (found_shred == -1) {
5341 args[0] = g_find_program_in_path("shred");
5342 debug_print("found shred: %s\n", args[0]);
5343 found_shred = (args[0] != NULL) ? 1:0;
5347 if (found_shred == 1) {
5348 if (g_stat(filename, &s) == 0 && S_ISREG(s.st_mode)) {
5349 if (s.st_nlink == 1) {
5352 g_spawn_sync(NULL, (gchar **)args, NULL, 0,
5353 NULL, NULL, NULL, NULL, &status, NULL);
5354 debug_print("%s %s exited with status %d\n",
5355 args[0], filename, WEXITSTATUS(status));
5356 if (truncate(filename, 0) < 0)
5357 g_warning("couln't truncate");
5362 return g_unlink(filename);
5365 GMutex *cm_mutex_new(void) {
5366 #if GLIB_CHECK_VERSION(2,32,0)
5367 GMutex *m = g_new0(GMutex, 1);
5371 return g_mutex_new();
5375 void cm_mutex_free(GMutex *mutex) {
5376 #if GLIB_CHECK_VERSION(2,32,0)
5377 g_mutex_clear(mutex);
5380 g_mutex_free(mutex);
5384 static gchar *canonical_list_to_file(GSList *list)
5386 GString *result = g_string_new(NULL);
5387 GSList *pathlist = g_slist_reverse(g_slist_copy(list));
5392 result = g_string_append(result, G_DIR_SEPARATOR_S);
5394 if (pathlist->data) {
5395 const gchar *root = (gchar *)pathlist->data;
5396 if (root[0] != '\0' && g_ascii_isalpha(root[0]) &&
5398 /* drive - don't prepend dir separator */
5400 result = g_string_append(result, G_DIR_SEPARATOR_S);
5405 for (cur = pathlist; cur; cur = cur->next) {
5406 result = g_string_append(result, (gchar *)cur->data);
5408 result = g_string_append(result, G_DIR_SEPARATOR_S);
5410 g_slist_free(pathlist);
5413 g_string_free(result, FALSE);
5418 static GSList *cm_split_path(const gchar *filename, int depth)
5421 GSList *canonical_parts = NULL;
5424 gboolean follow_symlinks = TRUE;
5430 errno = EINVAL; /* can't happen, no symlink handling */
5435 if (!g_path_is_absolute(filename)) {
5440 path_parts = g_strsplit(filename, G_DIR_SEPARATOR_S, -1);
5442 for (i = 0; path_parts[i] != NULL; i++) {
5443 if (!strcmp(path_parts[i], ""))
5445 if (!strcmp(path_parts[i], "."))
5447 else if (!strcmp(path_parts[i], "..")) {
5452 else /* Remove the last inserted element */
5454 g_slist_delete_link(canonical_parts,
5459 canonical_parts = g_slist_prepend(canonical_parts,
5460 g_strdup(path_parts[i]));
5462 tmp_path = canonical_list_to_file(canonical_parts);
5464 if(g_stat(tmp_path, &st) < 0) {
5465 if (errno == ENOENT) {
5467 follow_symlinks = FALSE;
5471 slist_free_strings_full(canonical_parts);
5472 g_strfreev(path_parts);
5478 if (follow_symlinks && g_file_test(tmp_path, G_FILE_TEST_IS_SYMLINK)) {
5479 GError *error = NULL;
5480 gchar *target = g_file_read_link(tmp_path, &error);
5482 if (!g_path_is_absolute(target)) {
5483 /* remove the last inserted element */
5485 g_slist_delete_link(canonical_parts,
5487 /* add the target */
5488 canonical_parts = g_slist_prepend(canonical_parts,
5492 /* and get the new target */
5493 target = canonical_list_to_file(canonical_parts);
5496 /* restart from absolute target */
5497 slist_free_strings_full(canonical_parts);
5498 canonical_parts = NULL;
5500 canonical_parts = cm_split_path(target, depth + 1);
5502 g_error_free(error);
5503 if (canonical_parts == NULL) {
5505 g_strfreev(path_parts);
5514 g_strfreev(path_parts);
5515 return canonical_parts;
5519 * Canonicalize a filename, resolving symlinks along the way.
5520 * Returns a negative errno in case of error.
5522 int cm_canonicalize_filename(const gchar *filename, gchar **canonical_name) {
5523 GSList *canonical_parts;
5524 gboolean is_absolute;
5526 if (filename == NULL)
5528 if (canonical_name == NULL)
5530 *canonical_name = NULL;
5532 is_absolute = g_path_is_absolute(filename);
5534 /* Always work on absolute filenames. */
5535 gchar *cur = g_get_current_dir();
5536 gchar *absolute_filename = g_strconcat(cur, G_DIR_SEPARATOR_S,
5539 canonical_parts = cm_split_path(absolute_filename, 0);
5540 g_free(absolute_filename);
5543 canonical_parts = cm_split_path(filename, 0);
5545 if (canonical_parts == NULL)
5548 *canonical_name = canonical_list_to_file(canonical_parts);
5549 slist_free_strings_full(canonical_parts);
5553 #if !GLIB_CHECK_VERSION(2, 30, 0)
5556 * @str: a UTF-8 encoded string
5557 * @start_pos: a character offset within @str
5558 * @end_pos: another character offset within @str
5560 * Copies a substring out of a UTF-8 encoded string.
5561 * The substring will contain @end_pos - @start_pos
5564 * Returns: a newly allocated copy of the requested
5565 * substring. Free with g_free() when no longer needed.
5570 g_utf8_substring (const gchar *str,
5574 gchar *start, *end, *out;
5576 start = g_utf8_offset_to_pointer (str, start_pos);
5577 end = g_utf8_offset_to_pointer (start, end_pos - start_pos);
5579 out = g_malloc (end - start + 1);
5580 memcpy (out, start, end - start);
5581 out[end - start] = 0;