2 * Claws Mail -- a GTK based, lightweight, and fast e-mail client
3 * Copyright (C) 1999-2024 The Claws Mail Team and Hiroyuki Yamamoto
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 * The code of the g_utf8_substring function below is owned by
19 * Matthias Clasen <matthiasc@src.gnome.org>/<mclasen@redhat.com>
20 * and is got from GLIB 2.30: https://git.gnome.org/browse/glib/commit/
21 * ?h=glib-2-30&id=9eb65dd3ed5e1a9638595cbe10699c7606376511
23 * GLib 2.30 is licensed under GPL v2 or later and:
24 * Copyright (C) 1999 Tom Tromey
25 * Copyright (C) 2000 Red Hat, Inc.
27 * https://git.gnome.org/browse/glib/tree/glib/gutf8.c
28 * ?h=glib-2-30&id=9eb65dd3ed5e1a9638595cbe10699c7606376511
33 #include "claws-features.h"
41 #include <glib/gi18n.h>
51 #include <sys/param.h>
53 # include <ws2tcpip.h>
55 # include <sys/socket.h>
58 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
66 #include <sys/types.h>
68 # include <sys/wait.h>
74 #include <sys/utsname.h>
89 #include "file-utils.h"
93 static gboolean debug_mode
= FALSE
;
95 void list_free_strings_full(GList
*list
)
97 g_list_free_full(list
, (GDestroyNotify
)g_free
);
100 void slist_free_strings_full(GSList
*list
)
102 g_slist_free_full(list
, (GDestroyNotify
)g_free
);
105 static void hash_free_strings_func(gpointer key
, gpointer value
, gpointer data
)
110 void hash_free_strings(GHashTable
*table
)
112 g_hash_table_foreach(table
, hash_free_strings_func
, NULL
);
115 gint
str_case_equal(gconstpointer v
, gconstpointer v2
)
117 return g_ascii_strcasecmp((const gchar
*)v
, (const gchar
*)v2
) == 0;
120 guint
str_case_hash(gconstpointer key
)
122 const gchar
*p
= key
;
126 h
= g_ascii_tolower(h
);
127 for (p
+= 1; *p
!= '\0'; p
++)
128 h
= (h
<< 5) - h
+ g_ascii_tolower(*p
);
134 gint
to_number(const gchar
*nstr
)
136 register const gchar
*p
;
138 if (*nstr
== '\0') return -1;
140 for (p
= nstr
; *p
!= '\0'; p
++)
141 if (!g_ascii_isdigit(*p
)) return -1;
146 /* convert integer into string,
147 nstr must be not lower than 11 characters length */
148 gchar
*itos_buf(gchar
*nstr
, gint n
)
150 g_snprintf(nstr
, 11, "%d", n
);
154 /* convert integer into string */
157 static gchar nstr
[11];
159 return itos_buf(nstr
, n
);
162 #define divide(num,divisor,i,d) \
164 i = num >> divisor; \
165 d = num & ((1<<divisor)-1); \
166 d = (d*100) >> divisor; \
171 * \brief Convert a given size in bytes in a human-readable string
173 * \param size The size expressed in bytes to convert in string
174 * \return The string that respresents the size in an human-readable way
176 gchar
*to_human_readable(goffset size
)
178 static gchar str
[14];
179 static gchar
*b_format
= NULL
, *kb_format
= NULL
,
180 *mb_format
= NULL
, *gb_format
= NULL
;
181 register int t
= 0, r
= 0;
182 if (b_format
== NULL
) {
184 kb_format
= _("%d.%02dKiB");
185 mb_format
= _("%d.%02dMiB");
186 gb_format
= _("%.2fGiB");
189 if (size
< (goffset
)1024) {
190 g_snprintf(str
, sizeof(str
), b_format
, (gint
)size
);
192 } else if (size
>> 10 < (goffset
)1024) {
193 divide(size
, 10, t
, r
);
194 g_snprintf(str
, sizeof(str
), kb_format
, t
, r
);
196 } else if (size
>> 20 < (goffset
)1024) {
197 divide(size
, 20, t
, r
);
198 g_snprintf(str
, sizeof(str
), mb_format
, t
, r
);
201 g_snprintf(str
, sizeof(str
), gb_format
, (gfloat
)(size
>> 30));
207 gint
path_cmp(const gchar
*s1
, const gchar
*s2
)
212 gchar
*s1buf
, *s2buf
;
215 if (s1
== NULL
|| s2
== NULL
) return -1;
216 if (*s1
== '\0' || *s2
== '\0') return -1;
219 s1buf
= g_strdup (s1
);
220 s2buf
= g_strdup (s2
);
221 subst_char (s1buf
, '/', G_DIR_SEPARATOR
);
222 subst_char (s2buf
, '/', G_DIR_SEPARATOR
);
225 #endif /* !G_OS_WIN32 */
230 if (s1
[len1
- 1] == G_DIR_SEPARATOR
) len1
--;
231 if (s2
[len2
- 1] == G_DIR_SEPARATOR
) len2
--;
233 rc
= strncmp(s1
, s2
, MAX(len1
, len2
));
237 #endif /* !G_OS_WIN32 */
241 /* remove trailing return code */
242 gchar
*strretchomp(gchar
*str
)
246 if (!*str
) return str
;
248 for (s
= str
+ strlen(str
) - 1;
249 s
>= str
&& (*s
== '\n' || *s
== '\r');
256 /* remove trailing character */
257 gchar
*strtailchomp(gchar
*str
, gchar tail_char
)
261 if (!*str
) return str
;
262 if (tail_char
== '\0') return str
;
264 for (s
= str
+ strlen(str
) - 1; s
>= str
&& *s
== tail_char
; s
--)
270 /* remove CR (carriage return) */
271 gchar
*strcrchomp(gchar
*str
)
275 if (!*str
) return str
;
277 s
= str
+ strlen(str
) - 1;
278 if (*s
== '\n' && s
> str
&& *(s
- 1) == '\r') {
286 /* truncates string at first CR (carriage return) or LF (line feed) */
287 gchar
*strcrlftrunc(gchar
*str
)
291 if ((str
== NULL
) || (!*str
)) return str
;
293 if ((p
= strstr(str
, "\r")) != NULL
)
295 if ((p
= strstr(str
, "\n")) != NULL
)
301 #ifndef HAVE_STRCASESTR
302 /* Similar to `strstr' but this function ignores the case of both strings. */
303 gchar
*strcasestr(const gchar
*haystack
, const gchar
*needle
)
305 size_t haystack_len
= strlen(haystack
);
307 return strncasestr(haystack
, haystack_len
, needle
);
309 #endif /* HAVE_STRCASESTR */
311 gchar
*strncasestr(const gchar
*haystack
, gint haystack_len
, const gchar
*needle
)
313 register size_t needle_len
;
315 needle_len
= strlen(needle
);
317 if (haystack_len
< needle_len
|| needle_len
== 0)
320 while (haystack_len
>= needle_len
) {
321 if (!g_ascii_strncasecmp(haystack
, needle
, needle_len
))
322 return (gchar
*)haystack
;
332 gpointer
my_memmem(gconstpointer haystack
, size_t haystacklen
,
333 gconstpointer needle
, size_t needlelen
)
335 const gchar
*haystack_
= (const gchar
*)haystack
;
336 const gchar
*needle_
= (const gchar
*)needle
;
337 const gchar
*haystack_cur
= (const gchar
*)haystack
;
338 size_t haystack_left
= haystacklen
;
341 return memchr(haystack_
, *needle_
, haystacklen
);
343 while ((haystack_cur
= memchr(haystack_cur
, *needle_
, haystack_left
))
345 if (haystacklen
- (haystack_cur
- haystack_
) < needlelen
)
347 if (memcmp(haystack_cur
+ 1, needle_
+ 1, needlelen
- 1) == 0)
348 return (gpointer
)haystack_cur
;
351 haystack_left
= haystacklen
- (haystack_cur
- haystack_
);
358 /* Copy no more than N characters of SRC to DEST, with NULL terminating. */
359 gchar
*strncpy2(gchar
*dest
, const gchar
*src
, size_t n
)
361 register const gchar
*s
= src
;
362 register gchar
*d
= dest
;
372 /* Examine if next block is non-ASCII string */
373 gboolean
is_next_nonascii(const gchar
*s
)
377 /* skip head space */
378 for (p
= s
; *p
!= '\0' && g_ascii_isspace(*p
); p
++)
380 for (; *p
!= '\0' && !g_ascii_isspace(*p
); p
++) {
381 if (*(guchar
*)p
> 127 || *(guchar
*)p
< 32)
388 gint
get_next_word_len(const gchar
*s
)
392 for (; *s
!= '\0' && !g_ascii_isspace(*s
); s
++, len
++)
398 static void trim_subject_for_compare(gchar
*str
)
402 eliminate_parenthesis(str
, '[', ']');
403 eliminate_parenthesis(str
, '(', ')');
406 srcp
= str
+ subject_get_prefix_length(str
);
408 memmove(str
, srcp
, strlen(srcp
) + 1);
411 static void trim_subject_for_sort(gchar
*str
)
417 srcp
= str
+ subject_get_prefix_length(str
);
419 memmove(str
, srcp
, strlen(srcp
) + 1);
422 /* compare subjects */
423 gint
subject_compare(const gchar
*s1
, const gchar
*s2
)
427 if (!s1
|| !s2
) return -1;
428 if (!*s1
|| !*s2
) return -1;
430 Xstrdup_a(str1
, s1
, return -1);
431 Xstrdup_a(str2
, s2
, return -1);
433 trim_subject_for_compare(str1
);
434 trim_subject_for_compare(str2
);
436 if (!*str1
|| !*str2
) return -1;
438 return strcmp(str1
, str2
);
441 gint
subject_compare_for_sort(const gchar
*s1
, const gchar
*s2
)
445 if (!s1
|| !s2
) return -1;
447 Xstrdup_a(str1
, s1
, return -1);
448 Xstrdup_a(str2
, s2
, return -1);
450 trim_subject_for_sort(str1
);
451 trim_subject_for_sort(str2
);
453 if (!g_utf8_validate(str1
, -1, NULL
)) {
454 g_warning("message subject \"%s\" failed UTF-8 validation", str1
);
456 } else if (!g_utf8_validate(str2
, -1, NULL
)) {
457 g_warning("message subject \"%s\" failed UTF-8 validation", str2
);
461 return g_utf8_collate(str1
, str2
);
464 void trim_subject(gchar
*str
)
466 register gchar
*srcp
;
472 srcp
= str
+ subject_get_prefix_length(str
);
477 } else if (*srcp
== '(') {
489 else if (*srcp
== cl
)
496 while (g_ascii_isspace(*srcp
)) srcp
++;
497 memmove(str
, srcp
, strlen(srcp
) + 1);
500 void eliminate_parenthesis(gchar
*str
, gchar op
, gchar cl
)
502 register gchar
*srcp
, *destp
;
507 while ((destp
= strchr(destp
, op
))) {
513 else if (*srcp
== cl
)
519 while (g_ascii_isspace(*srcp
)) srcp
++;
520 memmove(destp
, srcp
, strlen(srcp
) + 1);
524 void extract_parenthesis(gchar
*str
, gchar op
, gchar cl
)
526 register gchar
*srcp
, *destp
;
531 while ((srcp
= strchr(destp
, op
))) {
534 memmove(destp
, srcp
+ 1, strlen(srcp
));
539 else if (*destp
== cl
)
551 static void extract_parenthesis_with_skip_quote(gchar
*str
, gchar quote_chr
,
554 register gchar
*srcp
, *destp
;
556 gboolean in_quote
= FALSE
;
560 while ((srcp
= strchr_with_skip_quote(destp
, quote_chr
, op
))) {
563 memmove(destp
, srcp
+ 1, strlen(srcp
));
566 if (*destp
== op
&& !in_quote
)
568 else if (*destp
== cl
&& !in_quote
)
570 else if (*destp
== quote_chr
)
582 void extract_quote(gchar
*str
, gchar quote_chr
)
586 if ((str
= strchr(str
, quote_chr
))) {
588 while ((p
= strchr(p
+ 1, quote_chr
)) && (p
[-1] == '\\')) {
589 memmove(p
- 1, p
, strlen(p
) + 1);
594 memmove(str
, str
+ 1, p
- str
);
599 /* Returns a newly allocated string with all quote_chr not at the beginning
600 or the end of str escaped with '\' or the given str if not required. */
601 gchar
*escape_internal_quotes(gchar
*str
, gchar quote_chr
)
603 register gchar
*p
, *q
;
607 if (str
== NULL
|| *str
== '\0')
613 /* search for unescaped quote_chr */
618 if (*p
== quote_chr
&& *(p
- 1) != '\\' && *(p
+ 1) != '\0')
622 if (!k
) /* nothing to escape */
625 /* unescaped quote_chr found */
626 qstr
= g_malloc(l
+ k
+ 1);
629 if (*p
== quote_chr
) {
634 if (*p
== quote_chr
&& *(p
- 1) != '\\' && *(p
+ 1) != '\0')
643 void eliminate_address_comment(gchar
*str
)
645 register gchar
*srcp
, *destp
;
650 while ((destp
= strchr(destp
, '"'))) {
651 if ((srcp
= strchr(destp
+ 1, '"'))) {
656 while (g_ascii_isspace(*srcp
)) srcp
++;
657 memmove(destp
, srcp
, strlen(srcp
) + 1);
667 while ((destp
= strchr_with_skip_quote(destp
, '"', '('))) {
673 else if (*srcp
== ')')
679 while (g_ascii_isspace(*srcp
)) srcp
++;
680 memmove(destp
, srcp
, strlen(srcp
) + 1);
684 gchar
*strchr_with_skip_quote(const gchar
*str
, gint quote_chr
, gint c
)
686 gboolean in_quote
= FALSE
;
689 if (*str
== c
&& !in_quote
)
691 if (*str
== quote_chr
)
699 void extract_address(gchar
*str
)
701 cm_return_if_fail(str
!= NULL
);
702 eliminate_address_comment(str
);
703 if (strchr_with_skip_quote(str
, '"', '<'))
704 extract_parenthesis_with_skip_quote(str
, '"', '<', '>');
708 void extract_list_id_str(gchar
*str
)
710 if (strchr_with_skip_quote(str
, '"', '<'))
711 extract_parenthesis_with_skip_quote(str
, '"', '<', '>');
715 static GSList
*address_list_append_real(GSList
*addr_list
, const gchar
*str
, gboolean removecomments
)
720 if (!str
) return addr_list
;
722 Xstrdup_a(work
, str
, return addr_list
);
725 eliminate_address_comment(work
);
728 while (workp
&& *workp
) {
731 if ((p
= strchr_with_skip_quote(workp
, '"', ','))) {
737 if (removecomments
&& strchr_with_skip_quote(workp
, '"', '<'))
738 extract_parenthesis_with_skip_quote
739 (workp
, '"', '<', '>');
743 addr_list
= g_slist_append(addr_list
, g_strdup(workp
));
751 GSList
*address_list_append(GSList
*addr_list
, const gchar
*str
)
753 return address_list_append_real(addr_list
, str
, TRUE
);
756 GSList
*address_list_append_with_comments(GSList
*addr_list
, const gchar
*str
)
758 return address_list_append_real(addr_list
, str
, FALSE
);
761 GSList
*references_list_prepend(GSList
*msgid_list
, const gchar
*str
)
765 if (!str
) return msgid_list
;
768 while (strp
&& *strp
) {
769 const gchar
*start
, *end
;
772 if ((start
= strchr(strp
, '<')) != NULL
) {
773 end
= strchr(start
+ 1, '>');
778 msgid
= g_strndup(start
+ 1, end
- start
- 1);
781 msgid_list
= g_slist_prepend(msgid_list
, msgid
);
791 GSList
*references_list_append(GSList
*msgid_list
, const gchar
*str
)
795 list
= references_list_prepend(NULL
, str
);
796 list
= g_slist_reverse(list
);
797 msgid_list
= g_slist_concat(msgid_list
, list
);
802 GSList
*newsgroup_list_append(GSList
*group_list
, const gchar
*str
)
807 if (!str
) return group_list
;
809 Xstrdup_a(work
, str
, return group_list
);
813 while (workp
&& *workp
) {
816 if ((p
= strchr_with_skip_quote(workp
, '"', ','))) {
824 group_list
= g_slist_append(group_list
,
833 GList
*add_history(GList
*list
, const gchar
*str
)
838 cm_return_val_if_fail(str
!= NULL
, list
);
840 old
= g_list_find_custom(list
, (gpointer
)str
, (GCompareFunc
)g_strcmp0
);
843 list
= g_list_remove(list
, old
->data
);
845 } else if (g_list_length(list
) >= MAX_HISTORY_SIZE
) {
848 last
= g_list_last(list
);
851 list
= g_list_remove(list
, last
->data
);
856 list
= g_list_prepend(list
, g_strdup(str
));
861 void remove_return(gchar
*str
)
863 register gchar
*p
= str
;
866 if (*p
== '\n' || *p
== '\r')
867 memmove(p
, p
+ 1, strlen(p
));
873 void remove_space(gchar
*str
)
875 register gchar
*p
= str
;
880 while (g_ascii_isspace(*(p
+ spc
)))
883 memmove(p
, p
+ spc
, strlen(p
+ spc
) + 1);
889 void unfold_line(gchar
*str
)
895 ch
= str
; /* iterator for source string */
898 c
= g_utf8_get_char_validated(ch
, -1);
900 if (c
== (gunichar
)-1 || c
== (gunichar
)-2) {
901 /* non-unicode byte, move past it */
906 len
= g_unichar_to_utf8(c
, NULL
);
908 if ((!g_unichar_isdefined(c
) || !g_unichar_isprint(c
) ||
909 g_unichar_isspace(c
)) && c
!= 173) {
910 /* replace anything bad or whitespacey with a single space */
914 /* move rest of the string forwards, since we just replaced
915 * a multi-byte sequence with one byte */
916 memmove(ch
, ch
+ len
-1, strlen(ch
+ len
-1) + 1);
919 /* A valid unicode character, copy it. */
925 void subst_char(gchar
*str
, gchar orig
, gchar subst
)
927 register gchar
*p
= str
;
936 void subst_chars(gchar
*str
, gchar
*orig
, gchar subst
)
938 register gchar
*p
= str
;
941 if (strchr(orig
, *p
) != NULL
)
947 void subst_for_filename(gchar
*str
)
952 subst_chars(str
, "\t\r\n\\/*?:", '_');
954 subst_chars(str
, "\t\r\n\\/*", '_');
958 void subst_for_shellsafe_filename(gchar
*str
)
962 subst_for_filename(str
);
963 subst_chars(str
, " \"'|&;()<>'!{}[]",'_');
966 gboolean
is_ascii_str(const gchar
*str
)
968 const guchar
*p
= (const guchar
*)str
;
971 if (*p
!= '\t' && *p
!= ' ' &&
972 *p
!= '\r' && *p
!= '\n' &&
973 (*p
< 32 || *p
>= 127))
981 static const gchar
* line_has_quote_char_last(const gchar
* str
, const gchar
*quote_chars
)
983 gchar
* position
= NULL
;
984 gchar
* tmp_pos
= NULL
;
987 if (str
== NULL
|| quote_chars
== NULL
)
990 for (i
= 0; i
< strlen(quote_chars
); i
++) {
991 tmp_pos
= strrchr (str
, quote_chars
[i
]);
993 || (tmp_pos
!= NULL
&& position
<= tmp_pos
) )
999 gint
get_quote_level(const gchar
*str
, const gchar
*quote_chars
)
1001 const gchar
*first_pos
;
1002 const gchar
*last_pos
;
1003 const gchar
*p
= str
;
1004 gint quote_level
= -1;
1006 /* speed up line processing by only searching to the last '>' */
1007 if ((first_pos
= line_has_quote_char(str
, quote_chars
)) != NULL
) {
1008 /* skip a line if it contains a '<' before the initial '>' */
1009 if (memchr(str
, '<', first_pos
- str
) != NULL
)
1011 last_pos
= line_has_quote_char_last(first_pos
, quote_chars
);
1015 while (p
<= last_pos
) {
1016 while (p
< last_pos
) {
1017 if (g_ascii_isspace(*p
))
1023 if (strchr(quote_chars
, *p
))
1025 else if (*p
!= '-' && !g_ascii_isspace(*p
) && p
<= last_pos
) {
1026 /* any characters are allowed except '-','<' and space */
1027 while (*p
!= '-' && *p
!= '<'
1028 && !strchr(quote_chars
, *p
)
1029 && !g_ascii_isspace(*p
)
1032 if (strchr(quote_chars
, *p
))
1044 gint
check_line_length(const gchar
*str
, gint max_chars
, gint
*line
)
1046 const gchar
*p
= str
, *q
;
1047 gint cur_line
= 0, len
;
1049 while ((q
= strchr(p
, '\n')) != NULL
) {
1051 if (len
> max_chars
) {
1061 if (len
> max_chars
) {
1070 const gchar
* line_has_quote_char(const gchar
* str
, const gchar
*quote_chars
)
1072 gchar
* position
= NULL
;
1073 gchar
* tmp_pos
= NULL
;
1076 if (str
== NULL
|| quote_chars
== NULL
)
1079 for (i
= 0; i
< strlen(quote_chars
); i
++) {
1080 tmp_pos
= strchr (str
, quote_chars
[i
]);
1082 || (tmp_pos
!= NULL
&& position
>= tmp_pos
) )
1088 static gchar
*strstr_with_skip_quote(const gchar
*haystack
, const gchar
*needle
)
1090 register guint haystack_len
, needle_len
;
1091 gboolean in_squote
= FALSE
, in_dquote
= FALSE
;
1093 haystack_len
= strlen(haystack
);
1094 needle_len
= strlen(needle
);
1096 if (haystack_len
< needle_len
|| needle_len
== 0)
1099 while (haystack_len
>= needle_len
) {
1100 if (!in_squote
&& !in_dquote
&&
1101 !strncmp(haystack
, needle
, needle_len
))
1102 return (gchar
*)haystack
;
1104 /* 'foo"bar"' -> foo"bar"
1105 "foo'bar'" -> foo'bar' */
1106 if (*haystack
== '\'') {
1109 else if (!in_dquote
)
1111 } else if (*haystack
== '\"') {
1114 else if (!in_squote
)
1116 } else if (*haystack
== '\\') {
1128 gchar
**strsplit_with_quote(const gchar
*str
, const gchar
*delim
,
1131 GSList
*string_list
= NULL
, *slist
;
1132 gchar
**str_array
, *s
, *new_str
;
1133 guint i
, n
= 1, len
;
1135 cm_return_val_if_fail(str
!= NULL
, NULL
);
1136 cm_return_val_if_fail(delim
!= NULL
, NULL
);
1139 max_tokens
= G_MAXINT
;
1141 s
= strstr_with_skip_quote(str
, delim
);
1143 guint delimiter_len
= strlen(delim
);
1147 new_str
= g_strndup(str
, len
);
1149 if (new_str
[0] == '\'' || new_str
[0] == '\"') {
1150 if (new_str
[len
- 1] == new_str
[0]) {
1151 new_str
[len
- 1] = '\0';
1152 memmove(new_str
, new_str
+ 1, len
- 1);
1155 string_list
= g_slist_prepend(string_list
, new_str
);
1157 str
= s
+ delimiter_len
;
1158 s
= strstr_with_skip_quote(str
, delim
);
1159 } while (--max_tokens
&& s
);
1163 new_str
= g_strdup(str
);
1164 if (new_str
[0] == '\'' || new_str
[0] == '\"') {
1166 if (new_str
[len
- 1] == new_str
[0]) {
1167 new_str
[len
- 1] = '\0';
1168 memmove(new_str
, new_str
+ 1, len
- 1);
1171 string_list
= g_slist_prepend(string_list
, new_str
);
1175 str_array
= g_new(gchar
*, n
);
1179 str_array
[i
--] = NULL
;
1180 for (slist
= string_list
; slist
; slist
= slist
->next
)
1181 str_array
[i
--] = slist
->data
;
1183 g_slist_free(string_list
);
1188 gchar
*get_abbrev_newsgroup_name(const gchar
*group
, gint len
)
1190 gchar
*abbrev_group
;
1192 const gchar
*p
= group
;
1195 cm_return_val_if_fail(group
!= NULL
, NULL
);
1197 last
= group
+ strlen(group
);
1198 abbrev_group
= ap
= g_malloc(strlen(group
) + 1);
1203 if ((ap
- abbrev_group
) + (last
- p
) > len
&& strchr(p
, '.')) {
1205 while (*p
!= '.') p
++;
1208 return abbrev_group
;
1213 return abbrev_group
;
1216 gchar
*trim_string(const gchar
*str
, gint len
)
1218 const gchar
*p
= str
;
1223 if (!str
) return NULL
;
1224 if (strlen(str
) <= len
)
1225 return g_strdup(str
);
1226 if (g_utf8_validate(str
, -1, NULL
) == FALSE
)
1227 return g_strdup(str
);
1229 while (*p
!= '\0') {
1230 mb_len
= g_utf8_skip
[*(guchar
*)p
];
1233 else if (new_len
+ mb_len
> len
)
1240 Xstrndup_a(new_str
, str
, new_len
, return g_strdup(str
));
1241 return g_strconcat(new_str
, "...", NULL
);
1244 GList
*uri_list_extract_filenames(const gchar
*uri_list
)
1246 GList
*result
= NULL
;
1248 gchar
*escaped_utf8uri
;
1254 while (g_ascii_isspace(*p
)) p
++;
1255 if (!strncmp(p
, "file:", 5)) {
1258 while (*q
&& *q
!= '\n' && *q
!= '\r') q
++;
1261 gchar
*file
, *locale_file
= NULL
;
1263 while (q
> p
&& g_ascii_isspace(*q
))
1265 Xalloca(escaped_utf8uri
, q
- p
+ 2,
1267 Xalloca(file
, q
- p
+ 2,
1270 strncpy(escaped_utf8uri
, p
, q
- p
+ 1);
1271 escaped_utf8uri
[q
- p
+ 1] = '\0';
1272 decode_uri_with_plus(file
, escaped_utf8uri
, FALSE
);
1274 * g_filename_from_uri() rejects escaped/locale encoded uri
1275 * string which come from Nautilus.
1278 if (g_utf8_validate(file
, -1, NULL
))
1280 = conv_codeset_strdup(
1283 conv_get_locale_charset_str());
1285 locale_file
= g_strdup(file
+ 5);
1287 locale_file
= g_filename_from_uri(escaped_utf8uri
, NULL
, NULL
);
1289 result
= g_list_append(result
, locale_file
);
1293 p
= strchr(p
, '\n');
1300 /* Converts two-digit hexadecimal to decimal. Used for unescaping escaped
1303 static gint
axtoi(const gchar
*hexstr
)
1305 gint hi
, lo
, result
;
1308 if ('0' <= hi
&& hi
<= '9') {
1311 if ('a' <= hi
&& hi
<= 'f') {
1314 if ('A' <= hi
&& hi
<= 'F') {
1319 if ('0' <= lo
&& lo
<= '9') {
1322 if ('a' <= lo
&& lo
<= 'f') {
1325 if ('A' <= lo
&& lo
<= 'F') {
1328 result
= lo
+ (16 * hi
);
1332 gboolean
is_uri_string(const gchar
*str
)
1334 while (str
&& *str
&& g_ascii_isspace(*str
))
1336 return (g_ascii_strncasecmp(str
, "http://", 7) == 0 ||
1337 g_ascii_strncasecmp(str
, "https://", 8) == 0 ||
1338 g_ascii_strncasecmp(str
, "ftp://", 6) == 0 ||
1339 g_ascii_strncasecmp(str
, "ftps://", 7) == 0 ||
1340 g_ascii_strncasecmp(str
, "sftp://", 7) == 0 ||
1341 g_ascii_strncasecmp(str
, "ftp.", 4) == 0 ||
1342 g_ascii_strncasecmp(str
, "webcal://", 9) == 0 ||
1343 g_ascii_strncasecmp(str
, "webcals://", 10) == 0 ||
1344 g_ascii_strncasecmp(str
, "www.", 4) == 0);
1347 gchar
*get_uri_path(const gchar
*uri
)
1349 while (uri
&& *uri
&& g_ascii_isspace(*uri
))
1351 if (g_ascii_strncasecmp(uri
, "http://", 7) == 0)
1352 return (gchar
*)(uri
+ 7);
1353 else if (g_ascii_strncasecmp(uri
, "https://", 8) == 0)
1354 return (gchar
*)(uri
+ 8);
1355 else if (g_ascii_strncasecmp(uri
, "ftp://", 6) == 0)
1356 return (gchar
*)(uri
+ 6);
1357 else if (g_ascii_strncasecmp(uri
, "ftps://", 7) == 0)
1358 return (gchar
*)(uri
+ 7);
1359 else if (g_ascii_strncasecmp(uri
, "sftp://", 7) == 0)
1360 return (gchar
*)(uri
+ 7);
1361 else if (g_ascii_strncasecmp(uri
, "webcal://", 9) == 0)
1362 return (gchar
*)(uri
+ 7);
1363 else if (g_ascii_strncasecmp(uri
, "webcals://", 10) == 0)
1364 return (gchar
*)(uri
+ 7);
1366 return (gchar
*)uri
;
1369 gint
get_uri_len(const gchar
*str
)
1373 if (is_uri_string(str
)) {
1374 for (p
= str
; *p
!= '\0'; p
++) {
1375 if (strchr("<>\"", *p
))
1384 /* Decodes URL-Encoded strings (i.e. strings in which spaces are replaced by
1385 * plusses, and escape characters are used)
1387 void decode_uri_with_plus(gchar
*decoded_uri
, const gchar
*encoded_uri
, gboolean with_plus
)
1389 gchar
*dec
= decoded_uri
;
1390 const gchar
*enc
= encoded_uri
;
1395 if (isxdigit((guchar
)enc
[0]) &&
1396 isxdigit((guchar
)enc
[1])) {
1402 if (with_plus
&& *enc
== '+')
1414 void decode_uri(gchar
*decoded_uri
, const gchar
*encoded_uri
)
1416 decode_uri_with_plus(decoded_uri
, encoded_uri
, TRUE
);
1419 static gchar
*decode_uri_gdup(const gchar
*encoded_uri
)
1421 gchar
*buffer
= g_malloc(strlen(encoded_uri
)+1);
1422 decode_uri_with_plus(buffer
, encoded_uri
, FALSE
);
1426 gint
scan_mailto_url(const gchar
*mailto
, gchar
**from
, gchar
**to
, gchar
**cc
, gchar
**bcc
,
1427 gchar
**subject
, gchar
**body
, gchar
***attach
, gchar
**inreplyto
)
1431 const gchar
*forbidden_uris
[] = { ".gnupg/",
1437 gint num_attach
= 0;
1439 cm_return_val_if_fail(mailto
!= NULL
, -1);
1441 Xstrdup_a(tmp_mailto
, mailto
, return -1);
1443 if (!strncmp(tmp_mailto
, "mailto:", 7))
1446 p
= strchr(tmp_mailto
, '?');
1453 *to
= decode_uri_gdup(tmp_mailto
);
1456 gchar
*field
, *value
;
1473 if (*value
== '\0') continue;
1475 if (from
&& !g_ascii_strcasecmp(field
, "from")) {
1477 *from
= decode_uri_gdup(value
);
1479 gchar
*tmp
= decode_uri_gdup(value
);
1480 gchar
*new_from
= g_strdup_printf("%s, %s", *from
, tmp
);
1485 } else if (cc
&& !g_ascii_strcasecmp(field
, "cc")) {
1487 *cc
= decode_uri_gdup(value
);
1489 gchar
*tmp
= decode_uri_gdup(value
);
1490 gchar
*new_cc
= g_strdup_printf("%s, %s", *cc
, tmp
);
1495 } else if (bcc
&& !g_ascii_strcasecmp(field
, "bcc")) {
1497 *bcc
= decode_uri_gdup(value
);
1499 gchar
*tmp
= decode_uri_gdup(value
);
1500 gchar
*new_bcc
= g_strdup_printf("%s, %s", *bcc
, tmp
);
1505 } else if (subject
&& !*subject
&&
1506 !g_ascii_strcasecmp(field
, "subject")) {
1507 *subject
= decode_uri_gdup(value
);
1508 } else if (body
&& !*body
&& !g_ascii_strcasecmp(field
, "body")) {
1509 *body
= decode_uri_gdup(value
);
1510 } else if (body
&& !*body
&& !g_ascii_strcasecmp(field
, "insert")) {
1512 gchar
*tmp
= decode_uri_gdup(value
);
1514 for (; forbidden_uris
[i
]; i
++) {
1515 if (strstr(tmp
, forbidden_uris
[i
])) {
1516 g_print("Refusing to insert '%s', potential private data leak\n",
1525 if (!is_file_entry_regular(tmp
)) {
1526 g_warning("refusing to insert '%s', not a regular file", tmp
);
1527 } else if (!g_file_get_contents(tmp
, body
, NULL
, NULL
)) {
1528 g_warning("couldn't set insert file '%s' in body", value
);
1533 } else if (attach
&& !g_ascii_strcasecmp(field
, "attach")) {
1535 gchar
*tmp
= decode_uri_gdup(value
);
1536 gchar
**my_att
= g_malloc(sizeof(char *));
1540 for (; forbidden_uris
[i
]; i
++) {
1541 if (strstr(tmp
, forbidden_uris
[i
])) {
1542 g_print("Refusing to attach '%s', potential private data leak\n",
1550 /* attach is correct */
1552 my_att
= g_realloc(my_att
, (sizeof(char *))*(num_attach
+1));
1553 my_att
[num_attach
-1] = tmp
;
1554 my_att
[num_attach
] = NULL
;
1559 } else if (inreplyto
&& !*inreplyto
&&
1560 !g_ascii_strcasecmp(field
, "in-reply-to")) {
1561 *inreplyto
= decode_uri_gdup(value
);
1570 #include <windows.h>
1571 #ifndef CSIDL_APPDATA
1572 #define CSIDL_APPDATA 0x001a
1574 #ifndef CSIDL_LOCAL_APPDATA
1575 #define CSIDL_LOCAL_APPDATA 0x001c
1577 #ifndef CSIDL_FLAG_CREATE
1578 #define CSIDL_FLAG_CREATE 0x8000
1580 #define DIM(v) (sizeof(v)/sizeof((v)[0]))
1584 w32_strerror (int w32_errno
)
1586 static char strerr
[256];
1587 int ec
= (int)GetLastError ();
1591 FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM
, NULL
, w32_errno
,
1592 MAKELANGID (LANG_NEUTRAL
, SUBLANG_DEFAULT
),
1593 strerr
, DIM (strerr
)-1, NULL
);
1597 static __inline__
void *
1598 dlopen (const char * name
, int flag
)
1600 void * hd
= LoadLibrary (name
);
1604 static __inline__
void *
1605 dlsym (void * hd
, const char * sym
)
1609 void * fnc
= GetProcAddress (hd
, sym
);
1618 static __inline__
const char *
1621 return w32_strerror (0);
1625 static __inline__
int
1637 w32_shgetfolderpath (HWND a
, int b
, HANDLE c
, DWORD d
, LPSTR e
)
1639 static int initialized
;
1640 static HRESULT (WINAPI
* func
)(HWND
,int,HANDLE
,DWORD
,LPSTR
);
1644 static char *dllnames
[] = { "shell32.dll", "shfolder.dll", NULL
};
1650 for (i
=0, handle
= NULL
; !handle
&& dllnames
[i
]; i
++)
1652 handle
= dlopen (dllnames
[i
], RTLD_LAZY
);
1655 func
= dlsym (handle
, "SHGetFolderPathW");
1666 return func (a
,b
,c
,d
,e
);
1671 /* Returns a static string with the directroy from which the module
1672 has been loaded. Returns an empty string on error. */
1673 static char *w32_get_module_dir(void)
1675 static char *moddir
;
1678 char name
[MAX_PATH
+10];
1681 if ( !GetModuleFileNameA (0, name
, sizeof (name
)-10) )
1684 p
= strrchr (name
, '\\');
1690 moddir
= g_strdup (name
);
1694 #endif /* G_OS_WIN32 */
1696 /* Return a static string with the locale dir. */
1697 const gchar
*get_locale_dir(void)
1699 static gchar
*loc_dir
;
1703 loc_dir
= g_strconcat(w32_get_module_dir(), G_DIR_SEPARATOR_S
,
1704 "\\share\\locale", NULL
);
1707 loc_dir
= LOCALEDIR
;
1713 const gchar
*get_home_dir(void)
1716 static char home_dir_utf16
[MAX_PATH
] = "";
1717 static gchar
*home_dir_utf8
= NULL
;
1718 if (home_dir_utf16
[0] == '\0') {
1719 if (w32_shgetfolderpath
1720 (NULL
, CSIDL_APPDATA
|CSIDL_FLAG_CREATE
,
1721 NULL
, 0, home_dir_utf16
) < 0)
1722 strcpy (home_dir_utf16
, "C:\\Claws Mail");
1723 home_dir_utf8
= g_utf16_to_utf8 ((const gunichar2
*)home_dir_utf16
, -1, NULL
, NULL
, NULL
);
1725 return home_dir_utf8
;
1727 static const gchar
*homeenv
= NULL
;
1732 if (!homeenv
&& g_getenv("HOME") != NULL
)
1733 homeenv
= g_strdup(g_getenv("HOME"));
1735 homeenv
= g_get_home_dir();
1741 static gchar
*claws_rc_dir
= NULL
;
1742 static gboolean rc_dir_alt
= FALSE
;
1743 const gchar
*get_rc_dir(void)
1746 if (!claws_rc_dir
) {
1747 claws_rc_dir
= g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S
,
1749 debug_print("using default rc_dir %s\n", claws_rc_dir
);
1751 return claws_rc_dir
;
1754 void set_rc_dir(const gchar
*dir
)
1756 gchar
*canonical_dir
;
1757 if (claws_rc_dir
!= NULL
) {
1758 g_print("Error: rc_dir already set\n");
1760 int err
= cm_canonicalize_filename(dir
, &canonical_dir
);
1764 g_print("Error looking for %s: %d(%s)\n",
1765 dir
, -err
, g_strerror(-err
));
1770 claws_rc_dir
= canonical_dir
;
1772 len
= strlen(claws_rc_dir
);
1773 if (claws_rc_dir
[len
- 1] == G_DIR_SEPARATOR
)
1774 claws_rc_dir
[len
- 1] = '\0';
1776 debug_print("set rc_dir to %s\n", claws_rc_dir
);
1777 if (!is_dir_exist(claws_rc_dir
)) {
1778 if (make_dir_hier(claws_rc_dir
) != 0) {
1779 g_print("Error: can't create %s\n",
1787 gboolean
rc_dir_is_alt(void) {
1791 const gchar
*get_mail_base_dir(void)
1793 return get_home_dir();
1796 const gchar
*get_news_cache_dir(void)
1798 static gchar
*news_cache_dir
= NULL
;
1799 if (!news_cache_dir
)
1800 news_cache_dir
= g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S
,
1801 NEWS_CACHE_DIR
, NULL
);
1803 return news_cache_dir
;
1806 const gchar
*get_imap_cache_dir(void)
1808 static gchar
*imap_cache_dir
= NULL
;
1810 if (!imap_cache_dir
)
1811 imap_cache_dir
= g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S
,
1812 IMAP_CACHE_DIR
, NULL
);
1814 return imap_cache_dir
;
1817 const gchar
*get_mime_tmp_dir(void)
1819 static gchar
*mime_tmp_dir
= NULL
;
1822 mime_tmp_dir
= g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S
,
1823 MIME_TMP_DIR
, NULL
);
1825 return mime_tmp_dir
;
1828 const gchar
*get_template_dir(void)
1830 static gchar
*template_dir
= NULL
;
1833 template_dir
= g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S
,
1834 TEMPLATE_DIR
, NULL
);
1836 return template_dir
;
1840 const gchar
*w32_get_cert_file(void)
1842 const gchar
*cert_file
= NULL
;
1844 cert_file
= g_strconcat(w32_get_module_dir(),
1845 "\\share\\claws-mail\\",
1846 "ca-certificates.crt",
1852 /* Return the filepath of the claws-mail.desktop file */
1853 const gchar
*get_desktop_file(void)
1855 #ifdef DESKTOPFILEPATH
1856 return DESKTOPFILEPATH
;
1862 /* Return the default directory for Plugins. */
1863 const gchar
*get_plugin_dir(void)
1866 static gchar
*plugin_dir
= NULL
;
1869 plugin_dir
= g_strconcat(w32_get_module_dir(),
1870 "\\lib\\claws-mail\\plugins\\",
1874 if (is_dir_exist(PLUGINDIR
))
1877 static gchar
*plugin_dir
= NULL
;
1879 plugin_dir
= g_strconcat(get_rc_dir(),
1880 G_DIR_SEPARATOR_S
, "plugins",
1881 G_DIR_SEPARATOR_S
, NULL
);
1889 /* Return the default directory for Themes. */
1890 const gchar
*w32_get_themes_dir(void)
1892 static gchar
*themes_dir
= NULL
;
1895 themes_dir
= g_strconcat(w32_get_module_dir(),
1896 "\\share\\claws-mail\\themes",
1902 const gchar
*get_tmp_dir(void)
1904 static gchar
*tmp_dir
= NULL
;
1907 tmp_dir
= g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S
,
1913 gchar
*get_tmp_file(void)
1916 static guint32 id
= 0;
1918 tmp_file
= g_strdup_printf("%s%ctmpfile.%08x",
1919 get_tmp_dir(), G_DIR_SEPARATOR
, id
++);
1924 const gchar
*get_domain_name(void)
1927 static gchar
*domain_name
= NULL
;
1928 struct addrinfo hints
, *res
;
1933 if (gethostname(hostname
, sizeof(hostname
)) != 0) {
1934 perror("gethostname");
1935 domain_name
= "localhost";
1937 memset(&hints
, 0, sizeof(struct addrinfo
));
1938 hints
.ai_family
= AF_UNSPEC
;
1939 hints
.ai_socktype
= 0;
1940 hints
.ai_flags
= AI_CANONNAME
;
1941 hints
.ai_protocol
= 0;
1943 s
= getaddrinfo(hostname
, NULL
, &hints
, &res
);
1945 fprintf(stderr
, "getaddrinfo: %s\n", gai_strerror(s
));
1946 domain_name
= g_strdup(hostname
);
1948 domain_name
= g_strdup(res
->ai_canonname
);
1952 debug_print("domain name = %s\n", domain_name
);
1961 /* Tells whether the given host address string is a valid representation of a
1962 * numerical IP (v4 or, if supported, v6) address.
1964 gboolean
is_numeric_host_address(const gchar
*hostaddress
)
1966 struct addrinfo hints
, *res
;
1969 /* See what getaddrinfo makes of the string when told that it is a
1970 * numeric IP address representation. */
1971 memset(&hints
, 0, sizeof(struct addrinfo
));
1972 hints
.ai_family
= AF_UNSPEC
;
1973 hints
.ai_socktype
= 0;
1974 hints
.ai_flags
= AI_NUMERICHOST
;
1975 hints
.ai_protocol
= 0;
1977 err
= getaddrinfo(hostaddress
, NULL
, &hints
, &res
);
1984 off_t
get_file_size(const gchar
*file
)
1989 GError
*error
= NULL
;
1992 f
= g_file_new_for_path(file
);
1993 fi
= g_file_query_info(f
, "standard::size",
1994 G_FILE_QUERY_INFO_NONE
, NULL
, &error
);
1995 if (error
!= NULL
) {
1996 debug_print("get_file_size error: %s\n", error
->message
);
1997 g_error_free(error
);
2001 size
= g_file_info_get_size(fi
);
2009 if (g_stat(file
, &s
) < 0) {
2010 FILE_OP_ERROR(file
, "stat");
2018 time_t get_file_mtime(const gchar
*file
)
2022 if (g_stat(file
, &s
) < 0) {
2023 FILE_OP_ERROR(file
, "stat");
2030 gboolean
file_exist(const gchar
*file
, gboolean allow_fifo
)
2037 if (g_stat(file
, &s
) < 0) {
2038 if (ENOENT
!= errno
) FILE_OP_ERROR(file
, "stat");
2042 if (S_ISREG(s
.st_mode
) || (allow_fifo
&& S_ISFIFO(s
.st_mode
)))
2049 /* Test on whether FILE is a relative file name. This is
2050 * straightforward for Unix but more complex for Windows. */
2051 gboolean
is_relative_filename(const gchar
*file
)
2056 if ( *file
== '\\' && file
[1] == '\\' && strchr (file
+2, '\\') )
2057 return FALSE
; /* Prefixed with a hostname - this can't
2058 * be a relative name. */
2060 if ( ((*file
>= 'a' && *file
<= 'z')
2061 || (*file
>= 'A' && *file
<= 'Z'))
2063 file
+= 2; /* Skip drive letter. */
2065 return !(*file
== '\\' || *file
== '/');
2067 return !(*file
== G_DIR_SEPARATOR
);
2072 gboolean
is_dir_exist(const gchar
*dir
)
2077 return g_file_test(dir
, G_FILE_TEST_IS_DIR
);
2080 gboolean
is_file_entry_exist(const gchar
*file
)
2085 return g_file_test(file
, G_FILE_TEST_EXISTS
);
2088 gboolean
is_file_entry_regular(const gchar
*file
)
2093 return g_file_test(file
, G_FILE_TEST_IS_REGULAR
);
2096 gint
change_dir(const gchar
*dir
)
2098 gchar
*prevdir
= NULL
;
2101 prevdir
= g_get_current_dir();
2103 if (g_chdir(dir
) < 0) {
2104 FILE_OP_ERROR(dir
, "chdir");
2105 if (debug_mode
) g_free(prevdir
);
2107 } else if (debug_mode
) {
2110 cwd
= g_get_current_dir();
2111 if (strcmp(prevdir
, cwd
) != 0)
2112 g_print("current dir: %s\n", cwd
);
2120 gint
make_dir(const gchar
*dir
)
2122 if (g_mkdir(dir
, S_IRWXU
) < 0) {
2123 FILE_OP_ERROR(dir
, "mkdir");
2126 if (g_chmod(dir
, S_IRWXU
) < 0)
2127 FILE_OP_ERROR(dir
, "chmod");
2132 gint
make_dir_hier(const gchar
*dir
)
2137 for (p
= dir
; (p
= strchr(p
, G_DIR_SEPARATOR
)) != NULL
; p
++) {
2138 parent_dir
= g_strndup(dir
, p
- dir
);
2139 if (*parent_dir
!= '\0') {
2140 if (!is_dir_exist(parent_dir
)) {
2141 if (make_dir(parent_dir
) < 0) {
2150 if (!is_dir_exist(dir
)) {
2151 if (make_dir(dir
) < 0)
2158 gint
remove_all_files(const gchar
*dir
)
2161 const gchar
*file_name
;
2164 if ((dp
= g_dir_open(dir
, 0, NULL
)) == NULL
) {
2165 g_warning("failed to open directory: %s", dir
);
2169 while ((file_name
= g_dir_read_name(dp
)) != NULL
) {
2170 tmp
= g_strconcat(dir
, G_DIR_SEPARATOR_S
, file_name
, NULL
);
2171 if (claws_unlink(tmp
) < 0)
2172 FILE_OP_ERROR(tmp
, "unlink");
2181 gint
remove_numbered_files(const gchar
*dir
, guint first
, guint last
)
2184 const gchar
*dir_name
;
2188 if (first
== last
) {
2189 /* Skip all the dir reading part. */
2190 gchar
*filename
= g_strdup_printf("%s%s%u", dir
, G_DIR_SEPARATOR_S
, first
);
2191 if (is_dir_exist(filename
)) {
2192 /* a numbered directory with this name exists,
2193 * remove the dot-file instead */
2195 filename
= g_strdup_printf("%s%s.%u", dir
, G_DIR_SEPARATOR_S
, first
);
2197 if (claws_unlink(filename
) < 0) {
2198 FILE_OP_ERROR(filename
, "unlink");
2206 prev_dir
= g_get_current_dir();
2208 if (g_chdir(dir
) < 0) {
2209 FILE_OP_ERROR(dir
, "chdir");
2214 if ((dp
= g_dir_open(".", 0, NULL
)) == NULL
) {
2215 g_warning("failed to open directory: %s", dir
);
2220 while ((dir_name
= g_dir_read_name(dp
)) != NULL
) {
2221 file_no
= to_number(dir_name
);
2222 if (file_no
> 0 && first
<= file_no
&& file_no
<= last
) {
2223 if (is_dir_exist(dir_name
)) {
2224 gchar
*dot_file
= g_strdup_printf(".%s", dir_name
);
2225 if (is_file_exist(dot_file
) && claws_unlink(dot_file
) < 0) {
2226 FILE_OP_ERROR(dot_file
, "unlink");
2231 if (claws_unlink(dir_name
) < 0)
2232 FILE_OP_ERROR(dir_name
, "unlink");
2238 if (g_chdir(prev_dir
) < 0) {
2239 FILE_OP_ERROR(prev_dir
, "chdir");
2249 gint
remove_numbered_files_not_in_list(const gchar
*dir
, GSList
*numberlist
)
2252 const gchar
*dir_name
;
2255 GHashTable
*wanted_files
;
2257 GError
*error
= NULL
;
2259 if (numberlist
== NULL
)
2262 prev_dir
= g_get_current_dir();
2264 if (g_chdir(dir
) < 0) {
2265 FILE_OP_ERROR(dir
, "chdir");
2270 if ((dp
= g_dir_open(".", 0, &error
)) == NULL
) {
2271 g_message("Couldn't open current directory: %s (%d).\n",
2272 error
->message
, error
->code
);
2273 g_error_free(error
);
2278 wanted_files
= g_hash_table_new(g_direct_hash
, g_direct_equal
);
2279 for (cur
= numberlist
; cur
!= NULL
; cur
= cur
->next
) {
2280 /* numberlist->data is expected to be GINT_TO_POINTER */
2281 g_hash_table_insert(wanted_files
, cur
->data
, GINT_TO_POINTER(1));
2284 while ((dir_name
= g_dir_read_name(dp
)) != NULL
) {
2285 file_no
= to_number(dir_name
);
2286 if (is_dir_exist(dir_name
))
2288 if (file_no
> 0 && g_hash_table_lookup(wanted_files
, GINT_TO_POINTER(file_no
)) == NULL
) {
2289 debug_print("removing unwanted file %d from %s\n", file_no
, dir
);
2290 if (is_dir_exist(dir_name
)) {
2291 gchar
*dot_file
= g_strdup_printf(".%s", dir_name
);
2292 if (is_file_exist(dot_file
) && claws_unlink(dot_file
) < 0) {
2293 FILE_OP_ERROR(dot_file
, "unlink");
2298 if (claws_unlink(dir_name
) < 0)
2299 FILE_OP_ERROR(dir_name
, "unlink");
2304 g_hash_table_destroy(wanted_files
);
2306 if (g_chdir(prev_dir
) < 0) {
2307 FILE_OP_ERROR(prev_dir
, "chdir");
2317 gint
remove_all_numbered_files(const gchar
*dir
)
2319 return remove_numbered_files(dir
, 0, UINT_MAX
);
2322 gint
remove_dir_recursive(const gchar
*dir
)
2326 const gchar
*dir_name
;
2329 if (g_stat(dir
, &s
) < 0) {
2330 FILE_OP_ERROR(dir
, "stat");
2331 if (ENOENT
== errno
) return 0;
2335 if (!S_ISDIR(s
.st_mode
)) {
2336 if (claws_unlink(dir
) < 0) {
2337 FILE_OP_ERROR(dir
, "unlink");
2344 prev_dir
= g_get_current_dir();
2345 /* g_print("prev_dir = %s\n", prev_dir); */
2347 if (!path_cmp(prev_dir
, dir
)) {
2349 if (g_chdir("..") < 0) {
2350 FILE_OP_ERROR(dir
, "chdir");
2353 prev_dir
= g_get_current_dir();
2356 if (g_chdir(dir
) < 0) {
2357 FILE_OP_ERROR(dir
, "chdir");
2362 if ((dp
= g_dir_open(".", 0, NULL
)) == NULL
) {
2363 g_warning("failed to open directory: %s", dir
);
2369 /* remove all files in the directory */
2370 while ((dir_name
= g_dir_read_name(dp
)) != NULL
) {
2371 /* g_print("removing %s\n", dir_name); */
2373 if (is_dir_exist(dir_name
)) {
2376 if ((ret
= remove_dir_recursive(dir_name
)) < 0) {
2377 g_warning("can't remove directory: %s", dir_name
);
2382 if (claws_unlink(dir_name
) < 0)
2383 FILE_OP_ERROR(dir_name
, "unlink");
2389 if (g_chdir(prev_dir
) < 0) {
2390 FILE_OP_ERROR(prev_dir
, "chdir");
2397 if (g_rmdir(dir
) < 0) {
2398 FILE_OP_ERROR(dir
, "rmdir");
2405 /* convert line endings into CRLF. If the last line doesn't end with
2406 * linebreak, add it.
2408 gchar
*canonicalize_str(const gchar
*str
)
2414 for (p
= str
; *p
!= '\0'; ++p
) {
2421 if (p
== str
|| *(p
- 1) != '\n')
2424 out
= outp
= g_malloc(new_len
+ 1);
2425 for (p
= str
; *p
!= '\0'; ++p
) {
2432 if (p
== str
|| *(p
- 1) != '\n') {
2441 gchar
*normalize_newlines(const gchar
*str
)
2446 out
= outp
= g_malloc(strlen(str
) + 1);
2447 for (p
= str
; *p
!= '\0'; ++p
) {
2449 if (*(p
+ 1) != '\n')
2460 gchar
*get_outgoing_rfc2822_str(FILE *fp
)
2462 gchar buf
[BUFFSIZE
];
2465 str
= g_string_new(NULL
);
2467 /* output header part */
2468 while (claws_fgets(buf
, sizeof(buf
), fp
) != NULL
) {
2470 if (!g_ascii_strncasecmp(buf
, "Bcc:", 4)) {
2477 else if (next
!= ' ' && next
!= '\t') {
2481 if (claws_fgets(buf
, sizeof(buf
), fp
) == NULL
)
2485 g_string_append(str
, buf
);
2486 g_string_append(str
, "\r\n");
2492 /* output body part */
2493 while (claws_fgets(buf
, sizeof(buf
), fp
) != NULL
) {
2496 g_string_append_c(str
, '.');
2497 g_string_append(str
, buf
);
2498 g_string_append(str
, "\r\n");
2501 return g_string_free(str
, FALSE
);
2505 * Create a new boundary in a way that it is very unlikely that this
2506 * will occur in the following text. It would be easy to ensure
2507 * uniqueness if everything is either quoted-printable or base64
2508 * encoded (note that conversion is allowed), but because MIME bodies
2509 * may be nested, it may happen that the same boundary has already
2512 * boundary := 0*69<bchars> bcharsnospace
2513 * bchars := bcharsnospace / " "
2514 * bcharsnospace := DIGIT / ALPHA / "'" / "(" / ")" /
2515 * "+" / "_" / "," / "-" / "." /
2516 * "/" / ":" / "=" / "?"
2518 * some special characters removed because of buggy MTAs
2521 gchar
*generate_mime_boundary(const gchar
*prefix
)
2523 static gchar tbl
[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
2524 "abcdefghijklmnopqrstuvwxyz"
2529 for (i
= 0; i
< sizeof(buf_uniq
) - 1; i
++)
2530 buf_uniq
[i
] = tbl
[g_random_int_range(0, sizeof(tbl
) - 1)];
2533 return g_strdup_printf("%s_/%s", prefix
? prefix
: "MP",
2537 char *fgets_crlf(char *buf
, int size
, FILE *stream
)
2539 gboolean is_cr
= FALSE
;
2540 gboolean last_was_cr
= FALSE
;
2545 while (--size
> 0 && (c
= getc(stream
)) != EOF
)
2548 is_cr
= (c
== '\r');
2558 last_was_cr
= is_cr
;
2560 if (c
== EOF
&& cs
== buf
)
2568 static gint
execute_async(gchar
*const argv
[], const gchar
*working_directory
)
2570 cm_return_val_if_fail(argv
!= NULL
&& argv
[0] != NULL
, -1);
2572 if (g_spawn_async(working_directory
, (gchar
**)argv
, NULL
, G_SPAWN_SEARCH_PATH
,
2573 NULL
, NULL
, NULL
, FALSE
) == FALSE
) {
2574 g_warning("couldn't execute command: %s", argv
[0]);
2581 static gint
execute_sync(gchar
*const argv
[], const gchar
*working_directory
)
2585 cm_return_val_if_fail(argv
!= NULL
&& argv
[0] != NULL
, -1);
2588 if (g_spawn_sync(working_directory
, (gchar
**)argv
, NULL
, G_SPAWN_SEARCH_PATH
,
2589 NULL
, NULL
, NULL
, NULL
, &status
, NULL
) == FALSE
) {
2590 g_warning("couldn't execute command: %s", argv
[0]);
2594 if (WIFEXITED(status
))
2595 return WEXITSTATUS(status
);
2599 if (g_spawn_sync(working_directory
, (gchar
**)argv
, NULL
,
2600 G_SPAWN_SEARCH_PATH
|
2601 G_SPAWN_CHILD_INHERITS_STDIN
|
2602 G_SPAWN_LEAVE_DESCRIPTORS_OPEN
,
2603 NULL
, NULL
, NULL
, NULL
, &status
, NULL
) == FALSE
) {
2604 g_warning("couldn't execute command: %s", argv
[0]);
2612 gint
execute_command_line(const gchar
*cmdline
, gboolean async
,
2613 const gchar
*working_directory
)
2618 cm_return_val_if_fail(cmdline
!= NULL
, -1);
2620 debug_print("execute_command_line(): executing: %s\n", cmdline
);
2622 argv
= strsplit_with_quote(cmdline
, " ", 0);
2625 ret
= execute_async(argv
, working_directory
);
2627 ret
= execute_sync(argv
, working_directory
);
2634 gchar
*get_command_output(const gchar
*cmdline
)
2636 gchar
*child_stdout
;
2639 cm_return_val_if_fail(cmdline
!= NULL
, NULL
);
2641 debug_print("get_command_output(): executing: %s\n", cmdline
);
2643 if (g_spawn_command_line_sync(cmdline
, &child_stdout
, NULL
, &status
,
2645 g_warning("couldn't execute command: %s", cmdline
);
2649 return child_stdout
;
2652 FILE *get_command_output_stream(const char* cmdline
)
2656 gchar
**argv
= NULL
;
2659 cm_return_val_if_fail(cmdline
!= NULL
, NULL
);
2661 debug_print("get_command_output_stream(): executing: %s\n", cmdline
);
2663 /* turn the command-line string into an array */
2664 if (!g_shell_parse_argv(cmdline
, NULL
, &argv
, &err
)) {
2665 g_warning("could not parse command line from '%s': %s", cmdline
, err
->message
);
2670 if (!g_spawn_async_with_pipes(NULL
, argv
, NULL
, G_SPAWN_SEARCH_PATH
,
2671 NULL
, NULL
, &pid
, NULL
, &fd
, NULL
, &err
)
2674 g_warning("could not spawn '%s': %s", cmdline
, err
->message
);
2681 return fdopen(fd
, "r");
2685 static gint
is_unchanged_uri_char(char c
)
2696 static void encode_uri(gchar
*encoded_uri
, gint bufsize
, const gchar
*uri
)
2702 for(i
= 0; i
< strlen(uri
) ; i
++) {
2703 if (is_unchanged_uri_char(uri
[i
])) {
2704 if (k
+ 2 >= bufsize
)
2706 encoded_uri
[k
++] = uri
[i
];
2709 char * hexa
= "0123456789ABCDEF";
2711 if (k
+ 4 >= bufsize
)
2713 encoded_uri
[k
++] = '%';
2714 encoded_uri
[k
++] = hexa
[uri
[i
] / 16];
2715 encoded_uri
[k
++] = hexa
[uri
[i
] % 16];
2722 gint
open_uri(const gchar
*uri
, const gchar
*cmdline
)
2726 gchar buf
[BUFFSIZE
];
2728 gchar encoded_uri
[BUFFSIZE
];
2729 cm_return_val_if_fail(uri
!= NULL
, -1);
2731 /* an option to choose whether to use encode_uri or not ? */
2732 encode_uri(encoded_uri
, BUFFSIZE
, uri
);
2735 (p
= strchr(cmdline
, '%')) && *(p
+ 1) == 's' &&
2736 !strchr(p
+ 2, '%'))
2737 g_snprintf(buf
, sizeof(buf
), cmdline
, encoded_uri
);
2740 g_warning("Open URI command-line is invalid "
2741 "(there must be only one '%%s'): %s",
2743 g_snprintf(buf
, sizeof(buf
), DEFAULT_BROWSER_CMD
, encoded_uri
);
2746 execute_command_line(buf
, TRUE
, NULL
);
2748 ShellExecute(NULL
, "open", uri
, NULL
, NULL
, SW_SHOW
);
2753 gint
open_txt_editor(const gchar
*filepath
, const gchar
*cmdline
)
2755 gchar buf
[BUFFSIZE
];
2758 cm_return_val_if_fail(filepath
!= NULL
, -1);
2761 (p
= strchr(cmdline
, '%')) && *(p
+ 1) == 's' &&
2762 !strchr(p
+ 2, '%'))
2763 g_snprintf(buf
, sizeof(buf
), cmdline
, filepath
);
2766 g_warning("Open Text Editor command-line is invalid "
2767 "(there must be only one '%%s'): %s",
2769 g_snprintf(buf
, sizeof(buf
), DEFAULT_EDITOR_CMD
, filepath
);
2772 execute_command_line(buf
, TRUE
, NULL
);
2777 time_t remote_tzoffset_sec(const gchar
*zone
)
2779 static gchar ustzstr
[] = "PSTPDTMSTMDTCSTCDTESTEDT";
2785 time_t remoteoffset
;
2787 strncpy(zone3
, zone
, 3);
2791 if (sscanf(zone
, "%c%d", &c
, &offset
) == 2 &&
2792 (c
== '+' || c
== '-')) {
2793 remoteoffset
= ((offset
/ 100) * 60 + (offset
% 100)) * 60;
2795 remoteoffset
= -remoteoffset
;
2796 } else if (!strncmp(zone
, "UT" , 2) ||
2797 !strncmp(zone
, "GMT", 3)) {
2799 } else if (strlen(zone3
) == 3) {
2800 for (p
= ustzstr
; *p
!= '\0'; p
+= 3) {
2801 if (!g_ascii_strncasecmp(p
, zone3
, 3)) {
2802 iustz
= ((gint
)(p
- ustzstr
) / 3 + 1) / 2 - 8;
2803 remoteoffset
= iustz
* 3600;
2809 } else if (strlen(zone3
) == 1) {
2811 case 'Z': remoteoffset
= 0; break;
2812 case 'A': remoteoffset
= -1; break;
2813 case 'B': remoteoffset
= -2; break;
2814 case 'C': remoteoffset
= -3; break;
2815 case 'D': remoteoffset
= -4; break;
2816 case 'E': remoteoffset
= -5; break;
2817 case 'F': remoteoffset
= -6; break;
2818 case 'G': remoteoffset
= -7; break;
2819 case 'H': remoteoffset
= -8; break;
2820 case 'I': remoteoffset
= -9; break;
2821 case 'K': remoteoffset
= -10; break; /* J is not used */
2822 case 'L': remoteoffset
= -11; break;
2823 case 'M': remoteoffset
= -12; break;
2824 case 'N': remoteoffset
= 1; break;
2825 case 'O': remoteoffset
= 2; break;
2826 case 'P': remoteoffset
= 3; break;
2827 case 'Q': remoteoffset
= 4; break;
2828 case 'R': remoteoffset
= 5; break;
2829 case 'S': remoteoffset
= 6; break;
2830 case 'T': remoteoffset
= 7; break;
2831 case 'U': remoteoffset
= 8; break;
2832 case 'V': remoteoffset
= 9; break;
2833 case 'W': remoteoffset
= 10; break;
2834 case 'X': remoteoffset
= 11; break;
2835 case 'Y': remoteoffset
= 12; break;
2836 default: remoteoffset
= 0; break;
2838 remoteoffset
= remoteoffset
* 3600;
2842 return remoteoffset
;
2845 time_t tzoffset_sec(time_t *now
)
2849 struct tm buf1
, buf2
;
2851 if (now
&& *now
< 0)
2854 gmt
= *gmtime_r(now
, &buf1
);
2855 lt
= localtime_r(now
, &buf2
);
2857 off
= (lt
->tm_hour
- gmt
.tm_hour
) * 60 + lt
->tm_min
- gmt
.tm_min
;
2859 if (lt
->tm_year
< gmt
.tm_year
)
2861 else if (lt
->tm_year
> gmt
.tm_year
)
2863 else if (lt
->tm_yday
< gmt
.tm_yday
)
2865 else if (lt
->tm_yday
> gmt
.tm_yday
)
2868 if (off
>= 24 * 60) /* should be impossible */
2869 off
= 23 * 60 + 59; /* if not, insert silly value */
2870 if (off
<= -24 * 60)
2871 off
= -(23 * 60 + 59);
2876 /* calculate timezone offset */
2877 gchar
*tzoffset(time_t *now
)
2879 static gchar offset_string
[6];
2883 struct tm buf1
, buf2
;
2885 if (now
&& *now
< 0)
2888 gmt
= *gmtime_r(now
, &buf1
);
2889 lt
= localtime_r(now
, &buf2
);
2891 off
= (lt
->tm_hour
- gmt
.tm_hour
) * 60 + lt
->tm_min
- gmt
.tm_min
;
2893 if (lt
->tm_year
< gmt
.tm_year
)
2895 else if (lt
->tm_year
> gmt
.tm_year
)
2897 else if (lt
->tm_yday
< gmt
.tm_yday
)
2899 else if (lt
->tm_yday
> gmt
.tm_yday
)
2907 if (off
>= 24 * 60) /* should be impossible */
2908 off
= 23 * 60 + 59; /* if not, insert silly value */
2910 sprintf(offset_string
, "%c%02d%02d", sign
, off
/ 60, off
% 60);
2912 return offset_string
;
2915 static void _get_rfc822_date(gchar
*buf
, gint len
, gboolean hidetz
)
2919 gchar day
[4], mon
[4];
2920 gint dd
, hh
, mm
, ss
, yyyy
;
2922 gchar buf2
[RFC822_DATE_BUFFSIZE
];
2926 lt
= gmtime_r(&t
, &buf1
);
2928 lt
= localtime_r(&t
, &buf1
);
2930 if (sscanf(asctime_r(lt
, buf2
), "%3s %3s %d %d:%d:%d %d\n",
2931 day
, mon
, &dd
, &hh
, &mm
, &ss
, &yyyy
) != 7)
2932 g_warning("failed reading date/time");
2934 g_snprintf(buf
, len
, "%s, %d %s %d %02d:%02d:%02d %s",
2935 day
, dd
, mon
, yyyy
, hh
, mm
, ss
, (hidetz
? "-0000": tzoffset(&t
)));
2938 void get_rfc822_date(gchar
*buf
, gint len
)
2940 _get_rfc822_date(buf
, len
, FALSE
);
2943 void get_rfc822_date_hide_tz(gchar
*buf
, gint len
)
2945 _get_rfc822_date(buf
, len
, TRUE
);
2948 void debug_set_mode(gboolean mode
)
2953 gboolean
debug_get_mode(void)
2959 void debug_print_real(const char *file
, int line
, const gchar
*format
, ...)
2962 gchar buf
[BUFFSIZE
];
2965 if (!debug_mode
) return;
2967 prefix_len
= g_snprintf(buf
, sizeof(buf
), "%s:%d:", debug_srcname(file
), line
);
2969 va_start(args
, format
);
2970 g_vsnprintf(buf
+ prefix_len
, sizeof(buf
) - prefix_len
, format
, args
);
2976 void debug_print_real(const gchar
*format
, ...)
2979 gchar buf
[BUFFSIZE
];
2981 if (!debug_mode
) return;
2983 va_start(args
, format
);
2984 g_vsnprintf(buf
, sizeof(buf
), format
, args
);
2992 const char * debug_srcname(const char *file
)
2994 const char *s
= strrchr (file
, '/');
2999 void * subject_table_lookup(GHashTable
*subject_table
, gchar
* subject
)
3001 if (subject
== NULL
)
3004 subject
+= subject_get_prefix_length(subject
);
3006 return g_hash_table_lookup(subject_table
, subject
);
3009 void subject_table_insert(GHashTable
*subject_table
, gchar
* subject
,
3012 if (subject
== NULL
|| *subject
== 0)
3014 subject
+= subject_get_prefix_length(subject
);
3015 g_hash_table_insert(subject_table
, subject
, data
);
3018 void subject_table_remove(GHashTable
*subject_table
, gchar
* subject
)
3020 if (subject
== NULL
)
3023 subject
+= subject_get_prefix_length(subject
);
3024 g_hash_table_remove(subject_table
, subject
);
3027 static regex_t u_regex
;
3028 static gboolean u_init_
;
3030 void utils_free_regex(void)
3039 *\brief Check if a string is prefixed with known (combinations)
3040 * of prefixes. The function assumes that each prefix
3041 * is terminated by zero or exactly _one_ space.
3043 *\param str String to check for a prefixes
3045 *\return int Number of chars in the prefix that should be skipped
3046 * for a "clean" subject line. If no prefix was found, 0
3049 int subject_get_prefix_length(const gchar
*subject
)
3051 /*!< Array with allowable reply prefixes regexps. */
3052 static const gchar
* const prefixes
[] = {
3053 "Re\\:", /* "Re:" */
3054 "Re\\[[1-9][0-9]*\\]\\:", /* "Re[XXX]:" (non-conforming news mail clients) */
3055 "Antw\\:", /* "Antw:" (Dutch / German Outlook) */
3056 "Aw\\:", /* "Aw:" (German) */
3057 "Antwort\\:", /* "Antwort:" (German Lotus Notes) */
3058 "Res\\:", /* "Res:" (Spanish/Brazilian Outlook) */
3059 "Fw\\:", /* "Fw:" Forward */
3060 "Fwd\\:", /* "Fwd:" Forward */
3061 "Enc\\:", /* "Enc:" Forward (Brazilian Outlook) */
3062 "Odp\\:", /* "Odp:" Re (Polish Outlook) */
3063 "Rif\\:", /* "Rif:" (Italian Outlook) */
3064 "Sv\\:", /* "Sv" (Norwegian) */
3065 "Vs\\:", /* "Vs" (Norwegian) */
3066 "Ad\\:", /* "Ad" (Norwegian) */
3067 "\347\255\224\345\244\215\\:", /* "Re" (Chinese, UTF-8) */
3068 "R\303\251f\\. \\:", /* "R�f. :" (French Lotus Notes) */
3069 "Re \\:", /* "Re :" (French Yahoo Mail) */
3072 const int PREFIXES
= sizeof prefixes
/ sizeof prefixes
[0];
3076 if (!subject
) return 0;
3077 if (!*subject
) return 0;
3080 GString
*s
= g_string_new("");
3082 for (n
= 0; n
< PREFIXES
; n
++)
3083 /* Terminate each prefix regexpression by a
3084 * "\ ?" (zero or ONE space), and OR them */
3085 g_string_append_printf(s
, "(%s\\ ?)%s",
3090 g_string_prepend(s
, "(");
3091 g_string_append(s
, ")+"); /* match at least once */
3092 g_string_prepend(s
, "^\\ *"); /* from beginning of line */
3095 /* We now have something like "^\ *((PREFIX1\ ?)|(PREFIX2\ ?))+"
3096 * TODO: Should this be "^\ *(((PREFIX1)|(PREFIX2))\ ?)+" ??? */
3097 if (regcomp(&u_regex
, s
->str
, REG_EXTENDED
| REG_ICASE
)) {
3098 debug_print("Error compiling regexp %s\n", s
->str
);
3099 g_string_free(s
, TRUE
);
3103 g_string_free(s
, TRUE
);
3107 if (!regexec(&u_regex
, subject
, 1, &pos
, 0) && pos
.rm_so
!= -1)
3113 static guint
g_stricase_hash(gconstpointer gptr
)
3115 guint hash_result
= 0;
3118 for (str
= gptr
; str
&& *str
; str
++) {
3119 hash_result
+= toupper(*str
);
3125 static gint
g_stricase_equal(gconstpointer gptr1
, gconstpointer gptr2
)
3127 const char *str1
= gptr1
;
3128 const char *str2
= gptr2
;
3130 return !strcasecmp(str1
, str2
);
3133 gint
g_int_compare(gconstpointer a
, gconstpointer b
)
3135 return GPOINTER_TO_INT(a
) - GPOINTER_TO_INT(b
);
3139 quote_cmd_argument()
3141 return a quoted string safely usable in argument of a command.
3143 code is extracted and adapted from etPan! project -- DINH V. Ho�.
3146 gint
quote_cmd_argument(gchar
* result
, guint size
,
3156 for(p
= path
; * p
!= '\0' ; p
++) {
3158 if (isalnum((guchar
)*p
) || (* p
== '/')) {
3159 if (remaining
> 0) {
3165 result
[size
- 1] = '\0';
3170 if (remaining
>= 2) {
3178 result
[size
- 1] = '\0';
3183 if (remaining
> 0) {
3187 result
[size
- 1] = '\0';
3201 static void g_node_map_recursive(GNode
*node
, gpointer data
)
3203 GNodeMapData
*mapdata
= (GNodeMapData
*) data
;
3205 GNodeMapData newmapdata
;
3208 newdata
= mapdata
->func(node
->data
, mapdata
->data
);
3209 if (newdata
!= NULL
) {
3210 newnode
= g_node_new(newdata
);
3211 g_node_append(mapdata
->parent
, newnode
);
3213 newmapdata
.parent
= newnode
;
3214 newmapdata
.func
= mapdata
->func
;
3215 newmapdata
.data
= mapdata
->data
;
3217 g_node_children_foreach(node
, G_TRAVERSE_ALL
, g_node_map_recursive
, &newmapdata
);
3221 GNode
*g_node_map(GNode
*node
, GNodeMapFunc func
, gpointer data
)
3224 GNodeMapData mapdata
;
3226 cm_return_val_if_fail(node
!= NULL
, NULL
);
3227 cm_return_val_if_fail(func
!= NULL
, NULL
);
3229 root
= g_node_new(func(node
->data
, data
));
3231 mapdata
.parent
= root
;
3232 mapdata
.func
= func
;
3233 mapdata
.data
= data
;
3235 g_node_children_foreach(node
, G_TRAVERSE_ALL
, g_node_map_recursive
, &mapdata
);
3240 #define HEX_TO_INT(val, hex) \
3244 if ('0' <= c && c <= '9') { \
3246 } else if ('a' <= c && c <= 'f') { \
3247 val = c - 'a' + 10; \
3248 } else if ('A' <= c && c <= 'F') { \
3249 val = c - 'A' + 10; \
3255 gboolean
get_hex_value(guchar
*out
, gchar c1
, gchar c2
)
3262 if (hi
== -1 || lo
== -1)
3265 *out
= (hi
<< 4) + lo
;
3269 #define INT_TO_HEX(hex, val) \
3272 hex = '0' + (val); \
3274 hex = 'A' + (val) - 10; \
3277 void get_hex_str(gchar
*out
, guchar ch
)
3281 INT_TO_HEX(hex
, ch
>> 4);
3283 INT_TO_HEX(hex
, ch
& 0x0f);
3289 #define G_PRINT_REF 1 == 1 ? (void) 0 : (void)
3291 #define G_PRINT_REF g_print
3295 *\brief Register ref counted pointer. It is based on GBoxed, so should
3296 * work with anything that uses the GType system. The semantics
3297 * are similar to a C++ auto pointer, with the exception that
3298 * C doesn't have automatic closure (calling destructors) when
3299 * exiting a block scope.
3300 * Use the \ref G_TYPE_AUTO_POINTER macro instead of calling this
3301 * function directly.
3303 *\return GType A GType type.
3305 GType
g_auto_pointer_register(void)
3307 static GType auto_pointer_type
;
3308 if (!auto_pointer_type
)
3310 g_boxed_type_register_static
3311 ("G_TYPE_AUTO_POINTER",
3312 (GBoxedCopyFunc
) g_auto_pointer_copy
,
3313 (GBoxedFreeFunc
) g_auto_pointer_free
);
3314 return auto_pointer_type
;
3318 *\brief Structure with g_new() allocated pointer guarded by the
3321 typedef struct AutoPointerRef
{
3322 void (*free
) (gpointer
);
3328 *\brief The auto pointer opaque structure that references the
3329 * pointer guard block.
3331 typedef struct AutoPointer
{
3332 AutoPointerRef
*ref
;
3333 gpointer ptr
; /*!< access to protected pointer */
3337 *\brief Creates an auto pointer for a g_new()ed pointer. Example:
3341 * ... tell gtk_list_store it should use a G_TYPE_AUTO_POINTER
3342 * ... when assigning, copying and freeing storage elements
3344 * gtk_list_store_new(N_S_COLUMNS,
3345 * G_TYPE_AUTO_POINTER,
3349 * Template *precious_data = g_new0(Template, 1);
3350 * g_pointer protect = g_auto_pointer_new(precious_data);
3352 * gtk_list_store_set(container, &iter,
3356 * ... the gtk_list_store has copied the pointer and
3357 * ... incremented its reference count, we should free
3358 * ... the auto pointer (in C++ a destructor would do
3359 * ... this for us when leaving block scope)
3361 * g_auto_pointer_free(protect);
3363 * ... gtk_list_store_set() now manages the data. When
3364 * ... *explicitly* requesting a pointer from the list
3365 * ... store, don't forget you get a copy that should be
3366 * ... freed with g_auto_pointer_free() eventually.
3370 *\param pointer Pointer to be guarded.
3372 *\return GAuto * Pointer that should be used in containers with
3375 GAuto
*g_auto_pointer_new(gpointer p
)
3377 AutoPointerRef
*ref
;
3383 ref
= g_new0(AutoPointerRef
, 1);
3384 ptr
= g_new0(AutoPointer
, 1);
3394 G_PRINT_REF ("XXXX ALLOC(%lx)\n", p
);
3400 *\brief Allocate an autopointer using the passed \a free function to
3401 * free the guarded pointer
3403 GAuto
*g_auto_pointer_new_with_free(gpointer p
, GFreeFunc free_
)
3410 aptr
= g_auto_pointer_new(p
);
3411 aptr
->ref
->free
= free_
;
3415 gpointer
g_auto_pointer_get_ptr(GAuto
*auto_ptr
)
3417 if (auto_ptr
== NULL
)
3419 return ((AutoPointer
*) auto_ptr
)->ptr
;
3423 *\brief Copies an auto pointer by. It's mostly not necessary
3424 * to call this function directly, unless you copy/assign
3425 * the guarded pointer.
3427 *\param auto_ptr Auto pointer returned by previous call to
3428 * g_auto_pointer_new_XXX()
3430 *\return gpointer An auto pointer
3432 GAuto
*g_auto_pointer_copy(GAuto
*auto_ptr
)
3435 AutoPointerRef
*ref
;
3438 if (auto_ptr
== NULL
)
3443 newp
= g_new0(AutoPointer
, 1);
3446 newp
->ptr
= ref
->pointer
;
3450 G_PRINT_REF ("XXXX COPY(%lx) -- REF (%d)\n", ref
->pointer
, ref
->cnt
);
3456 *\brief Free an auto pointer
3458 void g_auto_pointer_free(GAuto
*auto_ptr
)
3461 AutoPointerRef
*ref
;
3463 if (auto_ptr
== NULL
)
3469 if (--(ref
->cnt
) == 0) {
3471 G_PRINT_REF ("XXXX FREE(%lx) -- REF (%d)\n", ref
->pointer
, ref
->cnt
);
3473 ref
->free(ref
->pointer
);
3478 G_PRINT_REF ("XXXX DEREF(%lx) -- REF (%d)\n", ref
->pointer
, ref
->cnt
);
3483 /* get_uri_part() - retrieves a URI starting from scanpos.
3484 Returns TRUE if successful */
3485 gboolean
get_uri_part(const gchar
*start
, const gchar
*scanpos
,
3486 const gchar
**bp
, const gchar
**ep
, gboolean hdr
)
3489 gint parenthese_cnt
= 0;
3491 cm_return_val_if_fail(start
!= NULL
, FALSE
);
3492 cm_return_val_if_fail(scanpos
!= NULL
, FALSE
);
3493 cm_return_val_if_fail(bp
!= NULL
, FALSE
);
3494 cm_return_val_if_fail(ep
!= NULL
, FALSE
);
3498 /* find end point of URI */
3499 for (ep_
= scanpos
; *ep_
!= '\0'; ep_
= g_utf8_next_char(ep_
)) {
3500 gunichar u
= g_utf8_get_char_validated(ep_
, -1);
3501 if (!g_unichar_isgraph(u
) ||
3502 u
== (gunichar
)-1 ||
3503 strchr("[]{}<>\"", *ep_
)) {
3505 } else if (strchr("(", *ep_
)) {
3507 } else if (strchr(")", *ep_
)) {
3508 if (parenthese_cnt
> 0)
3515 /* no punctuation at end of string */
3517 /* FIXME: this stripping of trailing punctuations may bite with other URIs.
3518 * should pass some URI type to this function and decide on that whether
3519 * to perform punctuation stripping */
3521 #define IS_REAL_PUNCT(ch) (g_ascii_ispunct(ch) && !strchr("$/?=-_~)", ch))
3523 for (; ep_
- 1 > scanpos
+ 1 &&
3524 IS_REAL_PUNCT(*(ep_
- 1));
3528 #undef IS_REAL_PUNCT
3535 gchar
*make_uri_string(const gchar
*bp
, const gchar
*ep
)
3537 while (bp
&& *bp
&& g_ascii_isspace(*bp
))
3539 return g_strndup(bp
, ep
- bp
);
3542 /* valid mail address characters */
3543 #define IS_RFC822_CHAR(ch) \
3547 !g_ascii_isspace(ch) && \
3548 !strchr("(),;<>\"", (ch)))
3550 /* alphabet and number within 7bit ASCII */
3551 #define IS_ASCII_ALNUM(ch) (IS_ASCII(ch) && g_ascii_isalnum(ch))
3552 #define IS_QUOTE(ch) ((ch) == '\'' || (ch) == '"')
3554 static GHashTable
*create_domain_tab(void)
3557 GHashTable
*htab
= g_hash_table_new(g_stricase_hash
, g_stricase_equal
);
3559 cm_return_val_if_fail(htab
, NULL
);
3560 for (n
= 0; n
< sizeof toplvl_domains
/ sizeof toplvl_domains
[0]; n
++)
3561 g_hash_table_insert(htab
, (gpointer
) toplvl_domains
[n
], (gpointer
) toplvl_domains
[n
]);
3565 static gboolean
is_toplvl_domain(GHashTable
*tab
, const gchar
*first
, const gchar
*last
)
3567 gchar buf
[BUFFSIZE
+ 1];
3568 const gchar
*m
= buf
+ BUFFSIZE
+ 1;
3571 if (last
- first
> BUFFSIZE
|| first
> last
)
3574 for (p
= buf
; p
< m
&& first
< last
; *p
++ = *first
++)
3578 return g_hash_table_lookup(tab
, buf
) != NULL
;
3581 /* get_email_part() - retrieves an email address. Returns TRUE if successful */
3582 gboolean
get_email_part(const gchar
*start
, const gchar
*scanpos
,
3583 const gchar
**bp
, const gchar
**ep
, gboolean hdr
)
3585 /* more complex than the uri part because we need to scan back and forward starting from
3586 * the scan position. */
3587 gboolean result
= FALSE
;
3588 const gchar
*bp_
= NULL
;
3589 const gchar
*ep_
= NULL
;
3590 static GHashTable
*dom_tab
;
3591 const gchar
*last_dot
= NULL
;
3592 const gchar
*prelast_dot
= NULL
;
3593 const gchar
*last_tld_char
= NULL
;
3595 /* the informative part of the email address (describing the name
3596 * of the email address owner) may contain quoted parts. the
3597 * closure stack stores the last encountered quotes. */
3598 gchar closure_stack
[128];
3599 gchar
*ptr
= closure_stack
;
3601 cm_return_val_if_fail(start
!= NULL
, FALSE
);
3602 cm_return_val_if_fail(scanpos
!= NULL
, FALSE
);
3603 cm_return_val_if_fail(bp
!= NULL
, FALSE
);
3604 cm_return_val_if_fail(ep
!= NULL
, FALSE
);
3607 const gchar
*start_quote
= NULL
;
3608 const gchar
*end_quote
= NULL
;
3610 /* go to the real start */
3611 if (start
[0] == ',')
3613 if (start
[0] == ';')
3615 while (start
[0] == '\n' || start
[0] == '\r')
3617 while (start
[0] == ' ' || start
[0] == '\t')
3622 /* check if there are quotes (to skip , in them) */
3623 if (*start
== '"') {
3624 start_quote
= start
;
3626 end_quote
= strstr(start
, "\"");
3632 /* skip anything between quotes */
3633 if (start_quote
&& end_quote
) {
3638 /* find end (either , or ; or end of line) */
3639 if (strstr(start
, ",") && strstr(start
, ";"))
3640 *ep
= strstr(start
,",") < strstr(start
, ";")
3641 ? strstr(start
, ",") : strstr(start
, ";");
3642 else if (strstr(start
, ","))
3643 *ep
= strstr(start
, ",");
3644 else if (strstr(start
, ";"))
3645 *ep
= strstr(start
, ";");
3647 *ep
= start
+strlen(start
);
3649 /* go back to real start */
3650 if (start_quote
&& end_quote
) {
3651 start
= start_quote
;
3654 /* check there's still an @ in that, or search
3655 * further if possible */
3656 if (strstr(start
, "@") && strstr(start
, "@") < *ep
)
3658 else if (*ep
< start
+strlen(start
)) {
3661 } else if (start_quote
&& strstr(start
, "\"") && strstr(start
, "\"") < *ep
) {
3669 dom_tab
= create_domain_tab();
3670 cm_return_val_if_fail(dom_tab
, FALSE
);
3672 /* scan start of address */
3673 for (bp_
= scanpos
- 1;
3674 bp_
>= start
&& IS_RFC822_CHAR(*(const guchar
*)bp_
); bp_
--)
3677 /* TODO: should start with an alnum? */
3679 for (; bp_
< scanpos
&& !IS_ASCII_ALNUM(*(const guchar
*)bp_
); bp_
++)
3682 if (bp_
!= scanpos
) {
3683 /* scan end of address */
3684 for (ep_
= scanpos
+ 1;
3685 *ep_
&& IS_RFC822_CHAR(*(const guchar
*)ep_
); ep_
++)
3687 prelast_dot
= last_dot
;
3689 if (*(last_dot
+ 1) == '.') {
3690 if (prelast_dot
== NULL
)
3692 last_dot
= prelast_dot
;
3697 /* TODO: really should terminate with an alnum? */
3698 for (; ep_
> scanpos
&& !IS_ASCII_ALNUM(*(const guchar
*)ep_
);
3703 if (last_dot
== NULL
)
3705 if (last_dot
>= ep_
)
3706 last_dot
= prelast_dot
;
3707 if (last_dot
== NULL
|| (scanpos
+ 1 >= last_dot
))
3711 for (last_tld_char
= last_dot
; last_tld_char
< ep_
; last_tld_char
++)
3712 if (*last_tld_char
== '?')
3715 if (is_toplvl_domain(dom_tab
, last_dot
, last_tld_char
))
3722 if (!result
) return FALSE
;
3724 if (*ep_
&& bp_
!= start
&& *(bp_
- 1) == '"' && *(ep_
) == '"'
3725 && *(ep_
+ 1) == ' ' && *(ep_
+ 2) == '<'
3726 && IS_RFC822_CHAR(*(ep_
+ 3))) {
3727 /* this informative part with an @ in it is
3728 * followed by the email address */
3731 /* go to matching '>' (or next non-rfc822 char, like \n) */
3732 for (; *ep_
!= '>' && *ep_
!= '\0' && IS_RFC822_CHAR(*ep_
); ep_
++)
3735 /* include the bracket */
3736 if (*ep_
== '>') ep_
++;
3738 /* include the leading quote */
3746 /* skip if it's between quotes "'alfons@proteus.demon.nl'" <alfons@proteus.demon.nl> */
3747 if (bp_
- 1 > start
&& IS_QUOTE(*(bp_
- 1)) && IS_QUOTE(*ep_
))
3750 /* see if this is <bracketed>; in this case we also scan for the informative part. */
3751 if (bp_
- 1 <= start
|| *(bp_
- 1) != '<' || *ep_
!= '>')
3754 #define FULL_STACK() ((size_t) (ptr - closure_stack) >= sizeof closure_stack)
3755 #define IN_STACK() (ptr > closure_stack)
3756 /* has underrun check */
3757 #define POP_STACK() if(IN_STACK()) --ptr
3758 /* has overrun check */
3759 #define PUSH_STACK(c) if(!FULL_STACK()) *ptr++ = (c); else return TRUE
3760 /* has underrun check */
3761 #define PEEK_STACK() (IN_STACK() ? *(ptr - 1) : 0)
3765 /* scan for the informative part. */
3766 for (bp_
-= 2; bp_
>= start
; bp_
--) {
3767 /* if closure on the stack keep scanning */
3768 if (PEEK_STACK() == *bp_
) {
3772 if (!IN_STACK() && (*bp_
== '\'' || *bp_
== '"')) {
3777 /* if nothing in the closure stack, do the special conditions
3778 * the following if..else expression simply checks whether
3779 * a token is acceptable. if not acceptable, the clause
3780 * should terminate the loop with a 'break' */
3781 if (!PEEK_STACK()) {
3783 && (((bp_
- 1) >= start
) && isalnum(*(bp_
- 1)))
3784 && (((bp_
+ 1) < ep_
) && isalnum(*(bp_
+ 1)))) {
3785 /* hyphens are allowed, but only in
3787 } else if (strchr(" \"'", *bp_
)) {
3788 /* but anything not being a punctiation
3791 break; /* anything else is rejected */
3798 /* scan forward (should start with an alnum) */
3799 for (; *bp_
!= '<' && isspace(*bp_
) && *bp_
!= '"'; bp_
++)
3815 #undef IS_ASCII_ALNUM
3816 #undef IS_RFC822_CHAR
3818 gchar
*make_email_string(const gchar
*bp
, const gchar
*ep
)
3820 /* returns a mailto: URI; mailto: is also used to detect the
3821 * uri type later on in the button_pressed signal handler */
3826 tmp
= g_strndup(bp
, ep
- bp
);
3828 /* If there is a colon in the username part of the address,
3829 * we're dealing with an URI for some other protocol - do
3830 * not prefix with mailto: in such case. */
3831 colon
= strchr(tmp
, ':');
3832 at
= strchr(tmp
, '@');
3833 if (colon
!= NULL
&& at
!= NULL
&& colon
< at
) {
3836 result
= g_strconcat("mailto:", tmp
, NULL
);
3843 gchar
*make_http_string(const gchar
*bp
, const gchar
*ep
)
3845 /* returns an http: URI; */
3849 while (bp
&& *bp
&& g_ascii_isspace(*bp
))
3851 tmp
= g_strndup(bp
, ep
- bp
);
3852 result
= g_strconcat("http://", tmp
, NULL
);
3858 static gchar
*mailcap_get_command_in_file(const gchar
*path
, const gchar
*type
, const gchar
*file_to_open
)
3860 FILE *fp
= claws_fopen(path
, "rb");
3861 gchar buf
[BUFFSIZE
];
3862 gchar
*result
= NULL
;
3865 while (claws_fgets(buf
, sizeof (buf
), fp
) != NULL
) {
3866 gchar
**parts
= g_strsplit(buf
, ";", 3);
3867 gchar
*trimmed
= parts
[0];
3868 while (trimmed
[0] == ' ' || trimmed
[0] == '\t')
3870 while (trimmed
[strlen(trimmed
)-1] == ' ' || trimmed
[strlen(trimmed
)-1] == '\t')
3871 trimmed
[strlen(trimmed
)-1] = '\0';
3873 if (!strcmp(trimmed
, type
)) {
3874 gboolean needsterminal
= FALSE
;
3875 if (parts
[2] && strstr(parts
[2], "needsterminal")) {
3876 needsterminal
= TRUE
;
3878 if (parts
[2] && strstr(parts
[2], "test=")) {
3879 gchar
*orig_testcmd
= g_strdup(strstr(parts
[2], "test=")+5);
3880 gchar
*testcmd
= orig_testcmd
;
3881 if (strstr(testcmd
,";"))
3882 *(strstr(testcmd
,";")) = '\0';
3883 while (testcmd
[0] == ' ' || testcmd
[0] == '\t')
3885 while (testcmd
[strlen(testcmd
)-1] == '\n')
3886 testcmd
[strlen(testcmd
)-1] = '\0';
3887 while (testcmd
[strlen(testcmd
)-1] == '\r')
3888 testcmd
[strlen(testcmd
)-1] = '\0';
3889 while (testcmd
[strlen(testcmd
)-1] == ' ' || testcmd
[strlen(testcmd
)-1] == '\t')
3890 testcmd
[strlen(testcmd
)-1] = '\0';
3892 if (strstr(testcmd
, "%s")) {
3893 gchar
*tmp
= g_strdup_printf(testcmd
, file_to_open
);
3894 gint res
= system(tmp
);
3896 g_free(orig_testcmd
);
3903 gint res
= system(testcmd
);
3904 g_free(orig_testcmd
);
3914 while (trimmed
[0] == ' ' || trimmed
[0] == '\t')
3916 while (trimmed
[strlen(trimmed
)-1] == '\n')
3917 trimmed
[strlen(trimmed
)-1] = '\0';
3918 while (trimmed
[strlen(trimmed
)-1] == '\r')
3919 trimmed
[strlen(trimmed
)-1] = '\0';
3920 while (trimmed
[strlen(trimmed
)-1] == ' ' || trimmed
[strlen(trimmed
)-1] == '\t')
3921 trimmed
[strlen(trimmed
)-1] = '\0';
3922 result
= g_strdup(trimmed
);
3925 if (needsterminal
) {
3926 gchar
*tmp
= g_strdup_printf("xterm -e %s", result
);
3937 gchar
*mailcap_get_command_for_type(const gchar
*type
, const gchar
*file_to_open
)
3939 gchar
*result
= NULL
;
3943 path
= g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S
, ".mailcap", NULL
);
3944 result
= mailcap_get_command_in_file(path
, type
, file_to_open
);
3948 result
= mailcap_get_command_in_file("/etc/mailcap", type
, file_to_open
);
3952 void mailcap_update_default(const gchar
*type
, const gchar
*command
)
3954 gchar
*path
= NULL
, *outpath
= NULL
;
3955 path
= g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S
, ".mailcap", NULL
);
3956 outpath
= g_strconcat(get_home_dir(), G_DIR_SEPARATOR_S
, ".mailcap.new", NULL
);
3957 FILE *fp
= claws_fopen(path
, "rb");
3959 gchar buf
[BUFFSIZE
];
3960 gboolean err
= FALSE
;
3963 fp
= claws_fopen(path
, "a");
3965 g_warning("failed to create file %s", path
);
3970 fp
= g_freopen(path
, "rb", fp
);
3972 g_warning("failed to reopen file %s", path
);
3979 outfp
= claws_fopen(outpath
, "wb");
3981 g_warning("failed to create file %s", outpath
);
3987 while (fp
&& claws_fgets(buf
, sizeof (buf
), fp
) != NULL
) {
3988 gchar
**parts
= g_strsplit(buf
, ";", 3);
3989 gchar
*trimmed
= parts
[0];
3990 while (trimmed
[0] == ' ')
3992 while (trimmed
[strlen(trimmed
)-1] == ' ')
3993 trimmed
[strlen(trimmed
)-1] = '\0';
3995 if (!strcmp(trimmed
, type
)) {
4000 if(claws_fputs(buf
, outfp
) == EOF
) {
4008 if (fprintf(outfp
, "%s; %s\n", type
, command
) < 0)
4014 if (claws_safe_fclose(outfp
) == EOF
)
4018 g_rename(outpath
, path
);
4024 /* crude test to see if a file is an email. */
4025 gboolean
file_is_email (const gchar
*filename
)
4030 if (filename
== NULL
)
4032 if ((fp
= claws_fopen(filename
, "rb")) == NULL
)
4035 && claws_fgets(buffer
, sizeof (buffer
), fp
) != NULL
) {
4036 if (!strncmp(buffer
, "From:", strlen("From:")))
4038 else if (!strncmp(buffer
, "Date:", strlen("Date:")))
4040 else if (!strncmp(buffer
, "Message-ID:", strlen("Message-ID:")))
4042 else if (!strncmp(buffer
, "Subject:", strlen("Subject:")))
4044 else if (!strcmp(buffer
, "\r\n")) {
4045 debug_print("End of headers\n");
4050 return (score
>= 3);
4053 gboolean
sc_g_list_bigger(GList
*list
, gint max
)
4057 while (cur
&& i
<= max
+1) {
4064 gboolean
sc_g_slist_bigger(GSList
*list
, gint max
)
4068 while (cur
&& i
<= max
+1) {
4075 const gchar
*daynames
[] = {NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
};
4076 const gchar
*monthnames
[] = {NULL
, NULL
, NULL
, NULL
, NULL
, NULL
,
4077 NULL
, NULL
, NULL
, NULL
, NULL
, NULL
};
4078 const gchar
*s_daynames
[] = {NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
};
4079 const gchar
*s_monthnames
[] = {NULL
, NULL
, NULL
, NULL
, NULL
, NULL
,
4080 NULL
, NULL
, NULL
, NULL
, NULL
, NULL
};
4082 gint daynames_len
[] = {0,0,0,0,0,0,0};
4083 gint monthnames_len
[] = {0,0,0,0,0,0,
4085 gint s_daynames_len
[] = {0,0,0,0,0,0,0};
4086 gint s_monthnames_len
[] = {0,0,0,0,0,0,
4088 const gchar
*s_am_up
= NULL
;
4089 const gchar
*s_pm_up
= NULL
;
4090 const gchar
*s_am_low
= NULL
;
4091 const gchar
*s_pm_low
= NULL
;
4093 gint s_am_up_len
= 0;
4094 gint s_pm_up_len
= 0;
4095 gint s_am_low_len
= 0;
4096 gint s_pm_low_len
= 0;
4098 static gboolean time_names_init_done
= FALSE
;
4100 static void init_time_names(void)
4104 daynames
[0] = C_("Complete day name for use by strftime", "Sunday");
4105 daynames
[1] = C_("Complete day name for use by strftime", "Monday");
4106 daynames
[2] = C_("Complete day name for use by strftime", "Tuesday");
4107 daynames
[3] = C_("Complete day name for use by strftime", "Wednesday");
4108 daynames
[4] = C_("Complete day name for use by strftime", "Thursday");
4109 daynames
[5] = C_("Complete day name for use by strftime", "Friday");
4110 daynames
[6] = C_("Complete day name for use by strftime", "Saturday");
4112 monthnames
[0] = C_("Complete month name for use by strftime", "January");
4113 monthnames
[1] = C_("Complete month name for use by strftime", "February");
4114 monthnames
[2] = C_("Complete month name for use by strftime", "March");
4115 monthnames
[3] = C_("Complete month name for use by strftime", "April");
4116 monthnames
[4] = C_("Complete month name for use by strftime", "May");
4117 monthnames
[5] = C_("Complete month name for use by strftime", "June");
4118 monthnames
[6] = C_("Complete month name for use by strftime", "July");
4119 monthnames
[7] = C_("Complete month name for use by strftime", "August");
4120 monthnames
[8] = C_("Complete month name for use by strftime", "September");
4121 monthnames
[9] = C_("Complete month name for use by strftime", "October");
4122 monthnames
[10] = C_("Complete month name for use by strftime", "November");
4123 monthnames
[11] = C_("Complete month name for use by strftime", "December");
4125 s_daynames
[0] = C_("Abbr. day name for use by strftime", "Sun");
4126 s_daynames
[1] = C_("Abbr. day name for use by strftime", "Mon");
4127 s_daynames
[2] = C_("Abbr. day name for use by strftime", "Tue");
4128 s_daynames
[3] = C_("Abbr. day name for use by strftime", "Wed");
4129 s_daynames
[4] = C_("Abbr. day name for use by strftime", "Thu");
4130 s_daynames
[5] = C_("Abbr. day name for use by strftime", "Fri");
4131 s_daynames
[6] = C_("Abbr. day name for use by strftime", "Sat");
4133 s_monthnames
[0] = C_("Abbr. month name for use by strftime", "Jan");
4134 s_monthnames
[1] = C_("Abbr. month name for use by strftime", "Feb");
4135 s_monthnames
[2] = C_("Abbr. month name for use by strftime", "Mar");
4136 s_monthnames
[3] = C_("Abbr. month name for use by strftime", "Apr");
4137 s_monthnames
[4] = C_("Abbr. month name for use by strftime", "May");
4138 s_monthnames
[5] = C_("Abbr. month name for use by strftime", "Jun");
4139 s_monthnames
[6] = C_("Abbr. month name for use by strftime", "Jul");
4140 s_monthnames
[7] = C_("Abbr. month name for use by strftime", "Aug");
4141 s_monthnames
[8] = C_("Abbr. month name for use by strftime", "Sep");
4142 s_monthnames
[9] = C_("Abbr. month name for use by strftime", "Oct");
4143 s_monthnames
[10] = C_("Abbr. month name for use by strftime", "Nov");
4144 s_monthnames
[11] = C_("Abbr. month name for use by strftime", "Dec");
4146 for (i
= 0; i
< 7; i
++) {
4147 daynames_len
[i
] = strlen(daynames
[i
]);
4148 s_daynames_len
[i
] = strlen(s_daynames
[i
]);
4150 for (i
= 0; i
< 12; i
++) {
4151 monthnames_len
[i
] = strlen(monthnames
[i
]);
4152 s_monthnames_len
[i
] = strlen(s_monthnames
[i
]);
4155 s_am_up
= C_("For use by strftime (morning)", "AM");
4156 s_pm_up
= C_("For use by strftime (afternoon)", "PM");
4157 s_am_low
= C_("For use by strftime (morning, lowercase)", "am");
4158 s_pm_low
= C_("For use by strftime (afternoon, lowercase)", "pm");
4160 s_am_up_len
= strlen(s_am_up
);
4161 s_pm_up_len
= strlen(s_pm_up
);
4162 s_am_low_len
= strlen(s_am_low
);
4163 s_pm_low_len
= strlen(s_pm_low
);
4165 time_names_init_done
= TRUE
;
4168 #define CHECK_SIZE() { \
4169 total_done += len; \
4170 if (total_done >= buflen) { \
4171 buf[buflen-1] = '\0'; \
4176 size_t fast_strftime(gchar
*buf
, gint buflen
, const gchar
*format
, struct tm
*lt
)
4178 gchar
*curpos
= buf
;
4179 gint total_done
= 0;
4180 gchar subbuf
[64], subfmt
[64];
4181 static time_t last_tzset
= (time_t)0;
4183 if (!time_names_init_done
)
4186 if (format
== NULL
|| lt
== NULL
)
4189 if (last_tzset
!= time(NULL
)) {
4191 last_tzset
= time(NULL
);
4194 if (*format
== '%') {
4195 gint len
= 0, tmp
= 0;
4199 len
= 1; CHECK_SIZE();
4203 len
= s_daynames_len
[lt
->tm_wday
]; CHECK_SIZE();
4204 strncpy2(curpos
, s_daynames
[lt
->tm_wday
], buflen
- total_done
);
4207 len
= daynames_len
[lt
->tm_wday
]; CHECK_SIZE();
4208 strncpy2(curpos
, daynames
[lt
->tm_wday
], buflen
- total_done
);
4212 len
= s_monthnames_len
[lt
->tm_mon
]; CHECK_SIZE();
4213 strncpy2(curpos
, s_monthnames
[lt
->tm_mon
], buflen
- total_done
);
4216 len
= monthnames_len
[lt
->tm_mon
]; CHECK_SIZE();
4217 strncpy2(curpos
, monthnames
[lt
->tm_mon
], buflen
- total_done
);
4220 strftime(subbuf
, 64, "%c", lt
);
4221 len
= strlen(subbuf
); CHECK_SIZE();
4222 strncpy2(curpos
, subbuf
, buflen
- total_done
);
4225 total_done
+= 2; CHECK_SIZE();
4226 tmp
= (lt
->tm_year
+ 1900)/100;
4227 *curpos
++ = '0'+(tmp
/ 10);
4228 *curpos
++ = '0'+(tmp
% 10);
4231 total_done
+= 2; CHECK_SIZE();
4232 *curpos
++ = '0'+(lt
->tm_mday
/ 10);
4233 *curpos
++ = '0'+(lt
->tm_mday
% 10);
4236 total_done
+= 8; CHECK_SIZE();
4237 *curpos
++ = '0'+((lt
->tm_mon
+1) / 10);
4238 *curpos
++ = '0'+((lt
->tm_mon
+1) % 10);
4240 *curpos
++ = '0'+(lt
->tm_mday
/ 10);
4241 *curpos
++ = '0'+(lt
->tm_mday
% 10);
4243 tmp
= lt
->tm_year
%100;
4244 *curpos
++ = '0'+(tmp
/ 10);
4245 *curpos
++ = '0'+(tmp
% 10);
4248 len
= 2; CHECK_SIZE();
4249 snprintf(curpos
, buflen
- total_done
, "%2d", lt
->tm_mday
);
4252 len
= 10; CHECK_SIZE();
4253 snprintf(curpos
, buflen
- total_done
, "%4d-%02d-%02d",
4254 lt
->tm_year
+ 1900, lt
->tm_mon
+1, lt
->tm_mday
);
4257 total_done
+= 2; CHECK_SIZE();
4258 *curpos
++ = '0'+(lt
->tm_hour
/ 10);
4259 *curpos
++ = '0'+(lt
->tm_hour
% 10);
4262 total_done
+= 2; CHECK_SIZE();
4268 *curpos
++ = '0'+(tmp
/ 10);
4269 *curpos
++ = '0'+(tmp
% 10);
4272 len
= 3; CHECK_SIZE();
4273 snprintf(curpos
, buflen
- total_done
, "%03d", lt
->tm_yday
+1);
4276 len
= 2; CHECK_SIZE();
4277 snprintf(curpos
, buflen
- total_done
, "%2d", lt
->tm_hour
);
4280 len
= 2; CHECK_SIZE();
4286 snprintf(curpos
, buflen
- total_done
, "%2d", tmp
);
4289 total_done
+= 2; CHECK_SIZE();
4290 tmp
= lt
->tm_mon
+ 1;
4291 *curpos
++ = '0'+(tmp
/ 10);
4292 *curpos
++ = '0'+(tmp
% 10);
4295 total_done
+= 2; CHECK_SIZE();
4296 *curpos
++ = '0'+(lt
->tm_min
/ 10);
4297 *curpos
++ = '0'+(lt
->tm_min
% 10);
4300 len
= 1; CHECK_SIZE();
4304 if (lt
->tm_hour
>= 12) {
4305 len
= s_pm_up_len
; CHECK_SIZE();
4306 snprintf(curpos
, buflen
-total_done
, "%s", s_pm_up
);
4308 len
= s_am_up_len
; CHECK_SIZE();
4309 snprintf(curpos
, buflen
-total_done
, "%s", s_am_up
);
4313 if (lt
->tm_hour
>= 12) {
4314 len
= s_pm_low_len
; CHECK_SIZE();
4315 snprintf(curpos
, buflen
-total_done
, "%s", s_pm_low
);
4317 len
= s_am_low_len
; CHECK_SIZE();
4318 snprintf(curpos
, buflen
-total_done
, "%s", s_am_low
);
4323 strftime(subbuf
, 64, "%I:%M:%S %p", lt
);
4325 strftime(subbuf
, 64, "%r", lt
);
4327 len
= strlen(subbuf
); CHECK_SIZE();
4328 strncpy2(curpos
, subbuf
, buflen
- total_done
);
4331 total_done
+= 5; CHECK_SIZE();
4332 *curpos
++ = '0'+(lt
->tm_hour
/ 10);
4333 *curpos
++ = '0'+(lt
->tm_hour
% 10);
4335 *curpos
++ = '0'+(lt
->tm_min
/ 10);
4336 *curpos
++ = '0'+(lt
->tm_min
% 10);
4339 snprintf(subbuf
, 64, "%" CM_TIME_FORMAT
, mktime(lt
));
4340 len
= strlen(subbuf
); CHECK_SIZE();
4341 strncpy2(curpos
, subbuf
, buflen
- total_done
);
4344 total_done
+= 2; CHECK_SIZE();
4345 *curpos
++ = '0'+(lt
->tm_sec
/ 10);
4346 *curpos
++ = '0'+(lt
->tm_sec
% 10);
4349 len
= 1; CHECK_SIZE();
4353 total_done
+= 8; CHECK_SIZE();
4354 *curpos
++ = '0'+(lt
->tm_hour
/ 10);
4355 *curpos
++ = '0'+(lt
->tm_hour
% 10);
4357 *curpos
++ = '0'+(lt
->tm_min
/ 10);
4358 *curpos
++ = '0'+(lt
->tm_min
% 10);
4360 *curpos
++ = '0'+(lt
->tm_sec
/ 10);
4361 *curpos
++ = '0'+(lt
->tm_sec
% 10);
4364 len
= 1; CHECK_SIZE();
4365 snprintf(curpos
, buflen
- total_done
, "%d", lt
->tm_wday
== 0 ? 7: lt
->tm_wday
);
4368 len
= 1; CHECK_SIZE();
4369 snprintf(curpos
, buflen
- total_done
, "%d", lt
->tm_wday
);
4372 strftime(subbuf
, 64, "%x", lt
);
4373 len
= strlen(subbuf
); CHECK_SIZE();
4374 strncpy2(curpos
, subbuf
, buflen
- total_done
);
4377 strftime(subbuf
, 64, "%X", lt
);
4378 len
= strlen(subbuf
); CHECK_SIZE();
4379 strncpy2(curpos
, subbuf
, buflen
- total_done
);
4382 total_done
+= 2; CHECK_SIZE();
4383 tmp
= lt
->tm_year
%100;
4384 *curpos
++ = '0'+(tmp
/ 10);
4385 *curpos
++ = '0'+(tmp
% 10);
4388 len
= 4; CHECK_SIZE();
4389 snprintf(curpos
, buflen
- total_done
, "%4d", lt
->tm_year
+ 1900);
4399 /* let these complicated ones be done with the libc */
4400 snprintf(subfmt
, 64, "%%%c", *format
);
4401 strftime(subbuf
, 64, subfmt
, lt
);
4402 len
= strlen(subbuf
); CHECK_SIZE();
4403 strncpy2(curpos
, subbuf
, buflen
- total_done
);
4407 /* let these complicated modifiers be done with the libc */
4408 snprintf(subfmt
, 64, "%%%c%c", *format
, *(format
+1));
4409 strftime(subbuf
, 64, subfmt
, lt
);
4410 len
= strlen(subbuf
); CHECK_SIZE();
4411 strncpy2(curpos
, subbuf
, buflen
- total_done
);
4415 g_warning("format error (%c)", *format
);
4422 int len
= 1; CHECK_SIZE();
4423 *curpos
++ = *format
++;
4431 #define WEXITSTATUS(x) (x)
4434 static gchar
*canonical_list_to_file(GSList
*list
)
4436 GString
*result
= g_string_new(NULL
);
4437 GSList
*pathlist
= g_slist_reverse(g_slist_copy(list
));
4441 result
= g_string_append(result
, G_DIR_SEPARATOR_S
);
4443 if (pathlist
->data
) {
4444 const gchar
*root
= (gchar
*)pathlist
->data
;
4445 if (root
[0] != '\0' && g_ascii_isalpha(root
[0]) &&
4447 /* drive - don't prepend dir separator */
4449 result
= g_string_append(result
, G_DIR_SEPARATOR_S
);
4454 for (cur
= pathlist
; cur
; cur
= cur
->next
) {
4455 result
= g_string_append(result
, (gchar
*)cur
->data
);
4457 result
= g_string_append(result
, G_DIR_SEPARATOR_S
);
4459 g_slist_free(pathlist
);
4461 return g_string_free(result
, FALSE
);
4464 static GSList
*cm_split_path(const gchar
*filename
, int depth
)
4467 GSList
*canonical_parts
= NULL
;
4471 gboolean follow_symlinks
= TRUE
;
4478 errno
= EINVAL
; /* can't happen, no symlink handling */
4483 if (!g_path_is_absolute(filename
)) {
4488 path_parts
= g_strsplit(filename
, G_DIR_SEPARATOR_S
, -1);
4490 for (i
= 0; path_parts
[i
] != NULL
; i
++) {
4491 if (!strcmp(path_parts
[i
], ""))
4493 if (!strcmp(path_parts
[i
], "."))
4495 else if (!strcmp(path_parts
[i
], "..")) {
4498 g_strfreev(path_parts
);
4501 else /* Remove the last inserted element */
4503 g_slist_delete_link(canonical_parts
,
4508 canonical_parts
= g_slist_prepend(canonical_parts
,
4509 g_strdup(path_parts
[i
]));
4511 tmp_path
= canonical_list_to_file(canonical_parts
);
4513 if(g_stat(tmp_path
, &st
) < 0) {
4514 if (errno
== ENOENT
) {
4517 follow_symlinks
= FALSE
;
4522 slist_free_strings_full(canonical_parts
);
4523 g_strfreev(path_parts
);
4529 if (follow_symlinks
&& g_file_test(tmp_path
, G_FILE_TEST_IS_SYMLINK
)) {
4530 GError
*error
= NULL
;
4531 gchar
*target
= g_file_read_link(tmp_path
, &error
);
4533 if (!g_path_is_absolute(target
)) {
4534 /* remove the last inserted element */
4536 g_slist_delete_link(canonical_parts
,
4538 /* add the target */
4539 canonical_parts
= g_slist_prepend(canonical_parts
,
4543 /* and get the new target */
4544 target
= canonical_list_to_file(canonical_parts
);
4547 /* restart from absolute target */
4548 slist_free_strings_full(canonical_parts
);
4549 canonical_parts
= NULL
;
4551 canonical_parts
= cm_split_path(target
, depth
+ 1);
4553 g_error_free(error
);
4554 if (canonical_parts
== NULL
) {
4556 g_strfreev(path_parts
);
4565 g_strfreev(path_parts
);
4566 return canonical_parts
;
4570 * Canonicalize a filename, resolving symlinks along the way.
4571 * Returns a negative errno in case of error.
4573 int cm_canonicalize_filename(const gchar
*filename
, gchar
**canonical_name
) {
4574 GSList
*canonical_parts
;
4575 gboolean is_absolute
;
4577 if (filename
== NULL
)
4579 if (canonical_name
== NULL
)
4581 *canonical_name
= NULL
;
4583 is_absolute
= g_path_is_absolute(filename
);
4585 /* Always work on absolute filenames. */
4586 gchar
*cur
= g_get_current_dir();
4587 gchar
*absolute_filename
= g_strconcat(cur
, G_DIR_SEPARATOR_S
,
4590 canonical_parts
= cm_split_path(absolute_filename
, 0);
4591 g_free(absolute_filename
);
4594 canonical_parts
= cm_split_path(filename
, 0);
4596 if (canonical_parts
== NULL
)
4599 *canonical_name
= canonical_list_to_file(canonical_parts
);
4600 slist_free_strings_full(canonical_parts
);
4604 /* Returns a decoded base64 string, guaranteed to be null-terminated. */
4605 guchar
*g_base64_decode_zero(const gchar
*text
, gsize
*out_len
)
4607 gchar
*tmp
= g_base64_decode(text
, out_len
);
4608 gchar
*out
= g_strndup(tmp
, *out_len
);
4612 if (strlen(out
) != *out_len
) {
4613 g_warning("strlen(out) %"G_GSIZE_FORMAT
" != *out_len %"G_GSIZE_FORMAT
, strlen(out
), *out_len
);
4619 /* Attempts to read count bytes from a PRNG into memory area starting at buf.
4620 * It is up to the caller to make sure there is at least count bytes
4621 * available at buf. */
4623 get_random_bytes(void *buf
, size_t count
)
4625 /* Open our prng source. */
4626 #if defined G_OS_WIN32
4629 if (!CryptAcquireContext(&rnd
, NULL
, NULL
, PROV_RSA_FULL
, 0) &&
4630 !CryptAcquireContext(&rnd
, NULL
, NULL
, PROV_RSA_FULL
, CRYPT_NEWKEYSET
)) {
4631 debug_print("Could not acquire a CSP handle.\n");
4638 rnd
= open("/dev/urandom", O_RDONLY
);
4640 FILE_OP_ERROR("/dev/urandom", "open");
4641 debug_print("Could not open /dev/urandom.\n");
4646 /* Read data from the source into buf. */
4647 #if defined G_OS_WIN32
4648 if (!CryptGenRandom(rnd
, count
, buf
)) {
4649 debug_print("Could not read %"G_GSIZE_FORMAT
" random bytes.\n", count
);
4650 CryptReleaseContext(rnd
, 0);
4654 ret
= read(rnd
, buf
, count
);
4656 FILE_OP_ERROR("/dev/urandom", "read");
4657 debug_print("Could not read enough data from /dev/urandom, read only %ld of %lu bytes.\n", ret
, count
);
4663 /* Close the prng source. */
4664 #if defined G_OS_WIN32
4665 CryptReleaseContext(rnd
, 0);
4673 /* returns FALSE if parsing failed, otherwise returns TRUE and sets *server, *port
4674 and eventually *fp from filename (if not NULL, they must be free'd by caller after
4676 filenames we expect: 'host.name.port.cert' or 'host.name.port.f:i:n:g:e:r:p:r:i:n:t.cert' */
4677 gboolean
get_serverportfp_from_filename(const gchar
*str
, gchar
**server
, gchar
**port
, gchar
**fp
)
4679 const gchar
*pos
, *dotport_pos
= NULL
, *dotcert_pos
= NULL
, *dotfp_pos
= NULL
;
4681 g_return_val_if_fail(str
!= NULL
, FALSE
);
4683 pos
= str
+ strlen(str
) - 1;
4684 while ((pos
> str
) && !dotport_pos
) {
4687 /* match the .cert suffix */
4688 if (strcmp(pos
, ".cert") == 0) {
4693 /* match an eventual fingerprint */
4694 /* or the port number */
4695 if (strncmp(pos
+ 3, ":", 1) == 0) {
4701 /* match the port number */
4708 if (!dotport_pos
|| !dotcert_pos
) {
4709 g_warning("could not parse filename %s", str
);
4714 *server
= g_strndup(str
, dotport_pos
- str
);
4717 *port
= g_strndup(dotport_pos
+ 1, dotfp_pos
- dotport_pos
- 1);
4719 *fp
= g_strndup(dotfp_pos
+ 1, dotcert_pos
- dotfp_pos
- 1);
4722 *port
= g_strndup(dotport_pos
+ 1, dotcert_pos
- dotport_pos
- 1);
4727 debug_print("filename='%s' => server='%s' port='%s' fp='%s'\n",
4729 (server
? *server
: "(n/a)"),
4730 (port
? *port
: "(n/a)"),
4731 (fp
? *fp
: "(n/a)"));
4733 if (!(server
&& *server
) || !(port
&& *port
))
4740 gchar
*win32_debug_log_path(void)
4742 return g_strconcat(g_get_tmp_dir(), G_DIR_SEPARATOR_S
,
4743 "claws-win32.log", NULL
);