add support for Ayatana indicator to Notification plugin
[claws.git] / src / common / utils.c
blob1313c0b8bbebb2c858e4a01a1c4156db8e75a956
1 /*
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
31 #ifdef HAVE_CONFIG_H
32 # include "config.h"
33 #include "claws-features.h"
34 #endif
36 #include "defs.h"
38 #include <glib.h>
39 #include <gio/gio.h>
41 #include <glib/gi18n.h>
43 #ifdef USE_PTHREAD
44 #include <pthread.h>
45 #endif
47 #include <stdio.h>
48 #include <string.h>
49 #include <ctype.h>
50 #include <errno.h>
51 #include <sys/param.h>
52 #ifdef G_OS_WIN32
53 # include <ws2tcpip.h>
54 #else
55 # include <sys/socket.h>
56 #endif
58 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
59 # include <wchar.h>
60 # include <wctype.h>
61 #endif
62 #include <stdlib.h>
63 #include <sys/stat.h>
64 #include <unistd.h>
65 #include <stdarg.h>
66 #include <sys/types.h>
67 #if HAVE_SYS_WAIT_H
68 # include <sys/wait.h>
69 #endif
70 #include <time.h>
71 #include <regex.h>
73 #ifdef G_OS_UNIX
74 #include <sys/utsname.h>
75 #endif
77 #include <fcntl.h>
79 #ifdef G_OS_WIN32
80 # include <direct.h>
81 # include <io.h>
82 #endif
84 #include "utils.h"
85 #include "socket.h"
86 #include "codeconv.h"
87 #include "tlds.h"
88 #include "timing.h"
89 #include "file-utils.h"
91 #define BUFFSIZE 8192
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)
107 g_free(key);
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;
123 guint h = *p;
125 if (h) {
126 h = g_ascii_tolower(h);
127 for (p += 1; *p != '\0'; p++)
128 h = (h << 5) - h + g_ascii_tolower(*p);
131 return h;
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;
143 return atoi(nstr);
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);
151 return nstr;
154 /* convert integer into string */
155 gchar *itos(gint n)
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) {
183 b_format = _("%dB");
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);
191 return str;
192 } else if (size >> 10 < (goffset)1024) {
193 divide(size, 10, t, r);
194 g_snprintf(str, sizeof(str), kb_format, t, r);
195 return str;
196 } else if (size >> 20 < (goffset)1024) {
197 divide(size, 20, t, r);
198 g_snprintf(str, sizeof(str), mb_format, t, r);
199 return str;
200 } else {
201 g_snprintf(str, sizeof(str), gb_format, (gfloat)(size >> 30));
202 return str;
206 /* compare paths */
207 gint path_cmp(const gchar *s1, const gchar *s2)
209 gint len1, len2;
210 int rc;
211 #ifdef G_OS_WIN32
212 gchar *s1buf, *s2buf;
213 #endif
215 if (s1 == NULL || s2 == NULL) return -1;
216 if (*s1 == '\0' || *s2 == '\0') return -1;
218 #ifdef G_OS_WIN32
219 s1buf = g_strdup (s1);
220 s2buf = g_strdup (s2);
221 subst_char (s1buf, '/', G_DIR_SEPARATOR);
222 subst_char (s2buf, '/', G_DIR_SEPARATOR);
223 s1 = s1buf;
224 s2 = s2buf;
225 #endif /* !G_OS_WIN32 */
227 len1 = strlen(s1);
228 len2 = strlen(s2);
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));
234 #ifdef G_OS_WIN32
235 g_free (s1buf);
236 g_free (s2buf);
237 #endif /* !G_OS_WIN32 */
238 return rc;
241 /* remove trailing return code */
242 gchar *strretchomp(gchar *str)
244 register gchar *s;
246 if (!*str) return str;
248 for (s = str + strlen(str) - 1;
249 s >= str && (*s == '\n' || *s == '\r');
250 s--)
251 *s = '\0';
253 return str;
256 /* remove trailing character */
257 gchar *strtailchomp(gchar *str, gchar tail_char)
259 register gchar *s;
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--)
265 *s = '\0';
267 return str;
270 /* remove CR (carriage return) */
271 gchar *strcrchomp(gchar *str)
273 register gchar *s;
275 if (!*str) return str;
277 s = str + strlen(str) - 1;
278 if (*s == '\n' && s > str && *(s - 1) == '\r') {
279 *(s - 1) = '\n';
280 *s = '\0';
283 return str;
286 /* truncates string at first CR (carriage return) or LF (line feed) */
287 gchar *strcrlftrunc(gchar *str)
289 gchar *p = NULL;
291 if ((str == NULL) || (!*str)) return str;
293 if ((p = strstr(str, "\r")) != NULL)
294 *p = '\0';
295 if ((p = strstr(str, "\n")) != NULL)
296 *p = '\0';
298 return str;
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)
318 return NULL;
320 while (haystack_len >= needle_len) {
321 if (!g_ascii_strncasecmp(haystack, needle, needle_len))
322 return (gchar *)haystack;
323 else {
324 haystack++;
325 haystack_len--;
329 return NULL;
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;
340 if (needlelen == 1)
341 return memchr(haystack_, *needle_, haystacklen);
343 while ((haystack_cur = memchr(haystack_cur, *needle_, haystack_left))
344 != NULL) {
345 if (haystacklen - (haystack_cur - haystack_) < needlelen)
346 break;
347 if (memcmp(haystack_cur + 1, needle_ + 1, needlelen - 1) == 0)
348 return (gpointer)haystack_cur;
349 else{
350 haystack_cur++;
351 haystack_left = haystacklen - (haystack_cur - haystack_);
355 return NULL;
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;
364 while (--n && *s)
365 *d++ = *s++;
366 *d = '\0';
368 return dest;
372 /* Examine if next block is non-ASCII string */
373 gboolean is_next_nonascii(const gchar *s)
375 const gchar *p;
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)
382 return TRUE;
385 return FALSE;
388 gint get_next_word_len(const gchar *s)
390 gint len = 0;
392 for (; *s != '\0' && !g_ascii_isspace(*s); s++, len++)
395 return len;
398 static void trim_subject_for_compare(gchar *str)
400 gchar *srcp;
402 eliminate_parenthesis(str, '[', ']');
403 eliminate_parenthesis(str, '(', ')');
404 g_strstrip(str);
406 srcp = str + subject_get_prefix_length(str);
407 if (srcp != str)
408 memmove(str, srcp, strlen(srcp) + 1);
411 static void trim_subject_for_sort(gchar *str)
413 gchar *srcp;
415 g_strstrip(str);
417 srcp = str + subject_get_prefix_length(str);
418 if (srcp != str)
419 memmove(str, srcp, strlen(srcp) + 1);
422 /* compare subjects */
423 gint subject_compare(const gchar *s1, const gchar *s2)
425 gchar *str1, *str2;
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)
443 gchar *str1, *str2;
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);
455 return 0;
456 } else if (!g_utf8_validate(str2, -1, NULL)) {
457 g_warning("message subject \"%s\" failed UTF-8 validation", str2);
458 return 0;
461 return g_utf8_collate(str1, str2);
464 void trim_subject(gchar *str)
466 register gchar *srcp;
467 gchar op, cl;
468 gint in_brace;
470 g_strstrip(str);
472 srcp = str + subject_get_prefix_length(str);
474 if (*srcp == '[') {
475 op = '[';
476 cl = ']';
477 } else if (*srcp == '(') {
478 op = '(';
479 cl = ')';
480 } else
481 op = 0;
483 if (op) {
484 ++srcp;
485 in_brace = 1;
486 while (*srcp) {
487 if (*srcp == op)
488 in_brace++;
489 else if (*srcp == cl)
490 in_brace--;
491 srcp++;
492 if (in_brace == 0)
493 break;
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;
503 gint in_brace;
505 destp = str;
507 while ((destp = strchr(destp, op))) {
508 in_brace = 1;
509 srcp = destp + 1;
510 while (*srcp) {
511 if (*srcp == op)
512 in_brace++;
513 else if (*srcp == cl)
514 in_brace--;
515 srcp++;
516 if (in_brace == 0)
517 break;
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;
527 gint in_brace;
529 destp = str;
531 while ((srcp = strchr(destp, op))) {
532 if (destp > str)
533 *destp++ = ' ';
534 memmove(destp, srcp + 1, strlen(srcp));
535 in_brace = 1;
536 while(*destp) {
537 if (*destp == op)
538 in_brace++;
539 else if (*destp == cl)
540 in_brace--;
542 if (in_brace == 0)
543 break;
545 destp++;
548 *destp = '\0';
551 static void extract_parenthesis_with_skip_quote(gchar *str, gchar quote_chr,
552 gchar op, gchar cl)
554 register gchar *srcp, *destp;
555 gint in_brace;
556 gboolean in_quote = FALSE;
558 destp = str;
560 while ((srcp = strchr_with_skip_quote(destp, quote_chr, op))) {
561 if (destp > str)
562 *destp++ = ' ';
563 memmove(destp, srcp + 1, strlen(srcp));
564 in_brace = 1;
565 while(*destp) {
566 if (*destp == op && !in_quote)
567 in_brace++;
568 else if (*destp == cl && !in_quote)
569 in_brace--;
570 else if (*destp == quote_chr)
571 in_quote ^= TRUE;
573 if (in_brace == 0)
574 break;
576 destp++;
579 *destp = '\0';
582 void extract_quote(gchar *str, gchar quote_chr)
584 register gchar *p;
586 if ((str = strchr(str, quote_chr))) {
587 p = str;
588 while ((p = strchr(p + 1, quote_chr)) && (p[-1] == '\\')) {
589 memmove(p - 1, p, strlen(p) + 1);
590 p--;
592 if(p) {
593 *p = '\0';
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;
604 gchar *qstr;
605 int k = 0, l = 0;
607 if (str == NULL || *str == '\0')
608 return str;
610 g_strstrip(str);
611 if (*str == '\0')
612 return str;
613 /* search for unescaped quote_chr */
614 p = str;
615 if (*p == quote_chr)
616 ++p, ++l;
617 while (*p) {
618 if (*p == quote_chr && *(p - 1) != '\\' && *(p + 1) != '\0')
619 ++k;
620 ++p, ++l;
622 if (!k) /* nothing to escape */
623 return str;
625 /* unescaped quote_chr found */
626 qstr = g_malloc(l + k + 1);
627 p = str;
628 q = qstr;
629 if (*p == quote_chr) {
630 *q = quote_chr;
631 ++p, ++q;
633 while (*p) {
634 if (*p == quote_chr && *(p - 1) != '\\' && *(p + 1) != '\0')
635 *q++ = '\\';
636 *q++ = *p++;
638 *q = '\0';
640 return qstr;
643 void eliminate_address_comment(gchar *str)
645 register gchar *srcp, *destp;
646 gint in_brace;
648 destp = str;
650 while ((destp = strchr(destp, '"'))) {
651 if ((srcp = strchr(destp + 1, '"'))) {
652 srcp++;
653 if (*srcp == '@') {
654 destp = srcp + 1;
655 } else {
656 while (g_ascii_isspace(*srcp)) srcp++;
657 memmove(destp, srcp, strlen(srcp) + 1);
659 } else {
660 *destp = '\0';
661 break;
665 destp = str;
667 while ((destp = strchr_with_skip_quote(destp, '"', '('))) {
668 in_brace = 1;
669 srcp = destp + 1;
670 while (*srcp) {
671 if (*srcp == '(')
672 in_brace++;
673 else if (*srcp == ')')
674 in_brace--;
675 srcp++;
676 if (in_brace == 0)
677 break;
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;
688 while (*str) {
689 if (*str == c && !in_quote)
690 return (gchar *)str;
691 if (*str == quote_chr)
692 in_quote ^= TRUE;
693 str++;
696 return NULL;
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, '"', '<', '>');
705 g_strstrip(str);
708 void extract_list_id_str(gchar *str)
710 if (strchr_with_skip_quote(str, '"', '<'))
711 extract_parenthesis_with_skip_quote(str, '"', '<', '>');
712 g_strstrip(str);
715 static GSList *address_list_append_real(GSList *addr_list, const gchar *str, gboolean removecomments)
717 gchar *work;
718 gchar *workp;
720 if (!str) return addr_list;
722 Xstrdup_a(work, str, return addr_list);
724 if (removecomments)
725 eliminate_address_comment(work);
726 workp = work;
728 while (workp && *workp) {
729 gchar *p, *next;
731 if ((p = strchr_with_skip_quote(workp, '"', ','))) {
732 *p = '\0';
733 next = p + 1;
734 } else
735 next = NULL;
737 if (removecomments && strchr_with_skip_quote(workp, '"', '<'))
738 extract_parenthesis_with_skip_quote
739 (workp, '"', '<', '>');
741 g_strstrip(workp);
742 if (*workp)
743 addr_list = g_slist_append(addr_list, g_strdup(workp));
745 workp = next;
748 return addr_list;
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)
763 const gchar *strp;
765 if (!str) return msgid_list;
766 strp = str;
768 while (strp && *strp) {
769 const gchar *start, *end;
770 gchar *msgid;
772 if ((start = strchr(strp, '<')) != NULL) {
773 end = strchr(start + 1, '>');
774 if (!end) break;
775 } else
776 break;
778 msgid = g_strndup(start + 1, end - start - 1);
779 g_strstrip(msgid);
780 if (*msgid)
781 msgid_list = g_slist_prepend(msgid_list, msgid);
782 else
783 g_free(msgid);
785 strp = end + 1;
788 return msgid_list;
791 GSList *references_list_append(GSList *msgid_list, const gchar *str)
793 GSList *list;
795 list = references_list_prepend(NULL, str);
796 list = g_slist_reverse(list);
797 msgid_list = g_slist_concat(msgid_list, list);
799 return msgid_list;
802 GSList *newsgroup_list_append(GSList *group_list, const gchar *str)
804 gchar *work;
805 gchar *workp;
807 if (!str) return group_list;
809 Xstrdup_a(work, str, return group_list);
811 workp = work;
813 while (workp && *workp) {
814 gchar *p, *next;
816 if ((p = strchr_with_skip_quote(workp, '"', ','))) {
817 *p = '\0';
818 next = p + 1;
819 } else
820 next = NULL;
822 g_strstrip(workp);
823 if (*workp)
824 group_list = g_slist_append(group_list,
825 g_strdup(workp));
827 workp = next;
830 return group_list;
833 GList *add_history(GList *list, const gchar *str)
835 GList *old;
836 gchar *oldstr;
838 cm_return_val_if_fail(str != NULL, list);
840 old = g_list_find_custom(list, (gpointer)str, (GCompareFunc)g_strcmp0);
841 if (old) {
842 oldstr = old->data;
843 list = g_list_remove(list, old->data);
844 g_free(oldstr);
845 } else if (g_list_length(list) >= MAX_HISTORY_SIZE) {
846 GList *last;
848 last = g_list_last(list);
849 if (last) {
850 oldstr = last->data;
851 list = g_list_remove(list, last->data);
852 g_free(oldstr);
856 list = g_list_prepend(list, g_strdup(str));
858 return list;
861 void remove_return(gchar *str)
863 register gchar *p = str;
865 while (*p) {
866 if (*p == '\n' || *p == '\r')
867 memmove(p, p + 1, strlen(p));
868 else
869 p++;
873 void remove_space(gchar *str)
875 register gchar *p = str;
876 register gint spc;
878 while (*p) {
879 spc = 0;
880 while (g_ascii_isspace(*(p + spc)))
881 spc++;
882 if (spc)
883 memmove(p, p + spc, strlen(p + spc) + 1);
884 else
885 p++;
889 void unfold_line(gchar *str)
891 register gchar *ch;
892 register gunichar c;
893 register gint len;
895 ch = str; /* iterator for source string */
897 while (*ch != 0) {
898 c = g_utf8_get_char_validated(ch, -1);
900 if (c == (gunichar)-1 || c == (gunichar)-2) {
901 /* non-unicode byte, move past it */
902 ch++;
903 continue;
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 */
911 *ch = ' ';
912 ch++;
913 if (len > 1) {
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);
918 } else {
919 /* A valid unicode character, copy it. */
920 ch += len;
925 void subst_char(gchar *str, gchar orig, gchar subst)
927 register gchar *p = str;
929 while (*p) {
930 if (*p == orig)
931 *p = subst;
932 p++;
936 void subst_chars(gchar *str, gchar *orig, gchar subst)
938 register gchar *p = str;
940 while (*p) {
941 if (strchr(orig, *p) != NULL)
942 *p = subst;
943 p++;
947 void subst_for_filename(gchar *str)
949 if (!str)
950 return;
951 #ifdef G_OS_WIN32
952 subst_chars(str, "\t\r\n\\/*?:", '_');
953 #else
954 subst_chars(str, "\t\r\n\\/*", '_');
955 #endif
958 void subst_for_shellsafe_filename(gchar *str)
960 if (!str)
961 return;
962 subst_for_filename(str);
963 subst_chars(str, " \"'|&;()<>'!{}[]",'_');
966 gboolean is_ascii_str(const gchar *str)
968 const guchar *p = (const guchar *)str;
970 while (*p != '\0') {
971 if (*p != '\t' && *p != ' ' &&
972 *p != '\r' && *p != '\n' &&
973 (*p < 32 || *p >= 127))
974 return FALSE;
975 p++;
978 return TRUE;
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;
985 int i;
987 if (str == NULL || quote_chars == NULL)
988 return NULL;
990 for (i = 0; i < strlen(quote_chars); i++) {
991 tmp_pos = strrchr (str, quote_chars[i]);
992 if(position == NULL
993 || (tmp_pos != NULL && position <= tmp_pos) )
994 position = tmp_pos;
996 return position;
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)
1010 return -1;
1011 last_pos = line_has_quote_char_last(first_pos, quote_chars);
1012 } else
1013 return -1;
1015 while (p <= last_pos) {
1016 while (p < last_pos) {
1017 if (g_ascii_isspace(*p))
1018 p++;
1019 else
1020 break;
1023 if (strchr(quote_chars, *p))
1024 quote_level++;
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)
1030 && p < last_pos)
1031 p++;
1032 if (strchr(quote_chars, *p))
1033 quote_level++;
1034 else
1035 break;
1038 p++;
1041 return quote_level;
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) {
1050 len = q - p + 1;
1051 if (len > max_chars) {
1052 if (line)
1053 *line = cur_line;
1054 return -1;
1056 p = q + 1;
1057 ++cur_line;
1060 len = strlen(p);
1061 if (len > max_chars) {
1062 if (line)
1063 *line = cur_line;
1064 return -1;
1067 return 0;
1070 const gchar * line_has_quote_char(const gchar * str, const gchar *quote_chars)
1072 gchar * position = NULL;
1073 gchar * tmp_pos = NULL;
1074 int i;
1076 if (str == NULL || quote_chars == NULL)
1077 return NULL;
1079 for (i = 0; i < strlen(quote_chars); i++) {
1080 tmp_pos = strchr (str, quote_chars[i]);
1081 if(position == NULL
1082 || (tmp_pos != NULL && position >= tmp_pos) )
1083 position = tmp_pos;
1085 return position;
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)
1097 return NULL;
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 == '\'') {
1107 if (in_squote)
1108 in_squote = FALSE;
1109 else if (!in_dquote)
1110 in_squote = TRUE;
1111 } else if (*haystack == '\"') {
1112 if (in_dquote)
1113 in_dquote = FALSE;
1114 else if (!in_squote)
1115 in_dquote = TRUE;
1116 } else if (*haystack == '\\') {
1117 haystack++;
1118 haystack_len--;
1121 haystack++;
1122 haystack_len--;
1125 return NULL;
1128 gchar **strsplit_with_quote(const gchar *str, const gchar *delim,
1129 gint max_tokens)
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);
1138 if (max_tokens < 1)
1139 max_tokens = G_MAXINT;
1141 s = strstr_with_skip_quote(str, delim);
1142 if (s) {
1143 guint delimiter_len = strlen(delim);
1145 do {
1146 len = s - str;
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);
1156 n++;
1157 str = s + delimiter_len;
1158 s = strstr_with_skip_quote(str, delim);
1159 } while (--max_tokens && s);
1162 if (*str) {
1163 new_str = g_strdup(str);
1164 if (new_str[0] == '\'' || new_str[0] == '\"') {
1165 len = strlen(str);
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);
1172 n++;
1175 str_array = g_new(gchar*, n);
1177 i = n - 1;
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);
1185 return str_array;
1188 gchar *get_abbrev_newsgroup_name(const gchar *group, gint len)
1190 gchar *abbrev_group;
1191 gchar *ap;
1192 const gchar *p = group;
1193 const gchar *last;
1195 cm_return_val_if_fail(group != NULL, NULL);
1197 last = group + strlen(group);
1198 abbrev_group = ap = g_malloc(strlen(group) + 1);
1200 while (*p) {
1201 while (*p == '.')
1202 *ap++ = *p++;
1203 if ((ap - abbrev_group) + (last - p) > len && strchr(p, '.')) {
1204 *ap++ = *p++;
1205 while (*p != '.') p++;
1206 } else {
1207 strcpy(ap, p);
1208 return abbrev_group;
1212 *ap = '\0';
1213 return abbrev_group;
1216 gchar *trim_string(const gchar *str, gint len)
1218 const gchar *p = str;
1219 gint mb_len;
1220 gchar *new_str;
1221 gint new_len = 0;
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];
1231 if (mb_len == 0)
1232 break;
1233 else if (new_len + mb_len > len)
1234 break;
1236 new_len += mb_len;
1237 p += mb_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;
1247 const gchar *p, *q;
1248 gchar *escaped_utf8uri;
1250 p = uri_list;
1252 while (p) {
1253 if (*p != '#') {
1254 while (g_ascii_isspace(*p)) p++;
1255 if (!strncmp(p, "file:", 5)) {
1256 q = p;
1257 q += 5;
1258 while (*q && *q != '\n' && *q != '\r') q++;
1260 if (q > p) {
1261 gchar *file, *locale_file = NULL;
1262 q--;
1263 while (q > p && g_ascii_isspace(*q))
1264 q--;
1265 Xalloca(escaped_utf8uri, q - p + 2,
1266 return result);
1267 Xalloca(file, q - p + 2,
1268 return result);
1269 *file = '\0';
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.
1277 #ifndef G_OS_WIN32
1278 if (g_utf8_validate(file, -1, NULL))
1279 locale_file
1280 = conv_codeset_strdup(
1281 file + 5,
1282 CS_UTF_8,
1283 conv_get_locale_charset_str());
1284 if (!locale_file)
1285 locale_file = g_strdup(file + 5);
1286 #else
1287 locale_file = g_filename_from_uri(escaped_utf8uri, NULL, NULL);
1288 #endif
1289 result = g_list_append(result, locale_file);
1293 p = strchr(p, '\n');
1294 if (p) p++;
1297 return result;
1300 /* Converts two-digit hexadecimal to decimal. Used for unescaping escaped
1301 * characters
1303 static gint axtoi(const gchar *hexstr)
1305 gint hi, lo, result;
1307 hi = hexstr[0];
1308 if ('0' <= hi && hi <= '9') {
1309 hi -= '0';
1310 } else
1311 if ('a' <= hi && hi <= 'f') {
1312 hi -= ('a' - 10);
1313 } else
1314 if ('A' <= hi && hi <= 'F') {
1315 hi -= ('A' - 10);
1318 lo = hexstr[1];
1319 if ('0' <= lo && lo <= '9') {
1320 lo -= '0';
1321 } else
1322 if ('a' <= lo && lo <= 'f') {
1323 lo -= ('a'-10);
1324 } else
1325 if ('A' <= lo && lo <= 'F') {
1326 lo -= ('A' - 10);
1328 result = lo + (16 * hi);
1329 return result;
1332 gboolean is_uri_string(const gchar *str)
1334 while (str && *str && g_ascii_isspace(*str))
1335 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))
1350 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);
1365 else
1366 return (gchar *)uri;
1369 gint get_uri_len(const gchar *str)
1371 const gchar *p;
1373 if (is_uri_string(str)) {
1374 for (p = str; *p != '\0'; p++) {
1375 if (g_ascii_isspace(*p) || strchr("<>\"", *p))
1376 break;
1378 return p - str;
1381 return 0;
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;
1392 while (*enc) {
1393 if (*enc == '%') {
1394 enc++;
1395 if (isxdigit((guchar)enc[0]) &&
1396 isxdigit((guchar)enc[1])) {
1397 *dec = axtoi(enc);
1398 dec++;
1399 enc += 2;
1401 } else {
1402 if (with_plus && *enc == '+')
1403 *dec = ' ';
1404 else
1405 *dec = *enc;
1406 dec++;
1407 enc++;
1411 *dec = '\0';
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);
1423 return buffer;
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)
1429 gchar *tmp_mailto;
1430 gchar *p;
1431 const gchar *forbidden_uris[] = { ".gnupg/",
1432 "/etc/passwd",
1433 "/etc/shadow",
1434 ".ssh/",
1435 "../",
1436 NULL };
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))
1444 tmp_mailto += 7;
1446 p = strchr(tmp_mailto, '?');
1447 if (p) {
1448 *p = '\0';
1449 p++;
1452 if (to && !*to)
1453 *to = decode_uri_gdup(tmp_mailto);
1455 while (p) {
1456 gchar *field, *value;
1458 field = p;
1460 p = strchr(p, '=');
1461 if (!p) break;
1462 *p = '\0';
1463 p++;
1465 value = p;
1467 p = strchr(p, '&');
1468 if (p) {
1469 *p = '\0';
1470 p++;
1473 if (*value == '\0') continue;
1475 if (from && !g_ascii_strcasecmp(field, "from")) {
1476 if (!*from) {
1477 *from = decode_uri_gdup(value);
1478 } else {
1479 gchar *tmp = decode_uri_gdup(value);
1480 gchar *new_from = g_strdup_printf("%s, %s", *from, tmp);
1481 g_free(tmp);
1482 g_free(*from);
1483 *from = new_from;
1485 } else if (cc && !g_ascii_strcasecmp(field, "cc")) {
1486 if (!*cc) {
1487 *cc = decode_uri_gdup(value);
1488 } else {
1489 gchar *tmp = decode_uri_gdup(value);
1490 gchar *new_cc = g_strdup_printf("%s, %s", *cc, tmp);
1491 g_free(tmp);
1492 g_free(*cc);
1493 *cc = new_cc;
1495 } else if (bcc && !g_ascii_strcasecmp(field, "bcc")) {
1496 if (!*bcc) {
1497 *bcc = decode_uri_gdup(value);
1498 } else {
1499 gchar *tmp = decode_uri_gdup(value);
1500 gchar *new_bcc = g_strdup_printf("%s, %s", *bcc, tmp);
1501 g_free(tmp);
1502 g_free(*bcc);
1503 *bcc = new_bcc;
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")) {
1511 int i = 0;
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",
1517 tmp);
1518 g_free(tmp);
1519 tmp = NULL;
1520 break;
1524 if (tmp) {
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);
1531 g_free(tmp);
1533 } else if (attach && !g_ascii_strcasecmp(field, "attach")) {
1534 int i = 0;
1535 gchar *tmp = decode_uri_gdup(value);
1536 gchar **my_att = g_malloc(sizeof(char *));
1538 my_att[0] = NULL;
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",
1543 tmp);
1544 g_free(tmp);
1545 tmp = NULL;
1546 break;
1549 if (tmp) {
1550 /* attach is correct */
1551 num_attach++;
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;
1555 *attach = my_att;
1557 else
1558 g_free(my_att);
1559 } else if (inreplyto && !*inreplyto &&
1560 !g_ascii_strcasecmp(field, "in-reply-to")) {
1561 *inreplyto = decode_uri_gdup(value);
1565 return 0;
1569 #ifdef G_OS_WIN32
1570 #include <windows.h>
1571 #ifndef CSIDL_APPDATA
1572 #define CSIDL_APPDATA 0x001a
1573 #endif
1574 #ifndef CSIDL_LOCAL_APPDATA
1575 #define CSIDL_LOCAL_APPDATA 0x001c
1576 #endif
1577 #ifndef CSIDL_FLAG_CREATE
1578 #define CSIDL_FLAG_CREATE 0x8000
1579 #endif
1580 #define DIM(v) (sizeof(v)/sizeof((v)[0]))
1582 #define RTLD_LAZY 0
1583 const char *
1584 w32_strerror (int w32_errno)
1586 static char strerr[256];
1587 int ec = (int)GetLastError ();
1589 if (w32_errno == 0)
1590 w32_errno = ec;
1591 FormatMessage (FORMAT_MESSAGE_FROM_SYSTEM, NULL, w32_errno,
1592 MAKELANGID (LANG_NEUTRAL, SUBLANG_DEFAULT),
1593 strerr, DIM (strerr)-1, NULL);
1594 return strerr;
1597 static __inline__ void *
1598 dlopen (const char * name, int flag)
1600 void * hd = LoadLibrary (name);
1601 return hd;
1604 static __inline__ void *
1605 dlsym (void * hd, const char * sym)
1607 if (hd && sym)
1609 void * fnc = GetProcAddress (hd, sym);
1610 if (!fnc)
1611 return NULL;
1612 return fnc;
1614 return NULL;
1618 static __inline__ const char *
1619 dlerror (void)
1621 return w32_strerror (0);
1625 static __inline__ int
1626 dlclose (void * hd)
1628 if (hd)
1630 FreeLibrary (hd);
1631 return 0;
1633 return -1;
1636 static HRESULT
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);
1642 if (!initialized)
1644 static char *dllnames[] = { "shell32.dll", "shfolder.dll", NULL };
1645 void *handle;
1646 int i;
1648 initialized = 1;
1650 for (i=0, handle = NULL; !handle && dllnames[i]; i++)
1652 handle = dlopen (dllnames[i], RTLD_LAZY);
1653 if (handle)
1655 func = dlsym (handle, "SHGetFolderPathW");
1656 if (!func)
1658 dlclose (handle);
1659 handle = NULL;
1665 if (func)
1666 return func (a,b,c,d,e);
1667 else
1668 return -1;
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;
1677 if (!moddir) {
1678 char name[MAX_PATH+10];
1679 char *p;
1681 if ( !GetModuleFileNameA (0, name, sizeof (name)-10) )
1682 *name = 0;
1683 else {
1684 p = strrchr (name, '\\');
1685 if (p)
1686 *p = 0;
1687 else
1688 *name = 0;
1690 moddir = g_strdup (name);
1692 return moddir;
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;
1701 #ifdef G_OS_WIN32
1702 if (!loc_dir)
1703 loc_dir = g_strconcat(w32_get_module_dir(), G_DIR_SEPARATOR_S,
1704 "\\share\\locale", NULL);
1705 #endif
1706 if (!loc_dir)
1707 loc_dir = LOCALEDIR;
1709 return loc_dir;
1713 const gchar *get_home_dir(void)
1715 #ifdef G_OS_WIN32
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;
1726 #else
1727 static const gchar *homeenv = NULL;
1729 if (homeenv)
1730 return homeenv;
1732 if (!homeenv && g_getenv("HOME") != NULL)
1733 homeenv = g_strdup(g_getenv("HOME"));
1734 if (!homeenv)
1735 homeenv = g_get_home_dir();
1737 return homeenv;
1738 #endif
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,
1748 RC_DIR, NULL);
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");
1759 } else {
1760 int err = cm_canonicalize_filename(dir, &canonical_dir);
1761 int len;
1763 if (err) {
1764 g_print("Error looking for %s: %d(%s)\n",
1765 dir, -err, g_strerror(-err));
1766 exit(0);
1768 rc_dir_alt = TRUE;
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",
1780 claws_rc_dir);
1781 exit(0);
1787 gboolean rc_dir_is_alt(void) {
1788 return rc_dir_alt;
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;
1821 if (!mime_tmp_dir)
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;
1832 if (!template_dir)
1833 template_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1834 TEMPLATE_DIR, NULL);
1836 return template_dir;
1839 #ifdef G_OS_WIN32
1840 const gchar *w32_get_cert_file(void)
1842 const gchar *cert_file = NULL;
1843 if (!cert_file)
1844 cert_file = g_strconcat(w32_get_module_dir(),
1845 "\\share\\claws-mail\\",
1846 "ca-certificates.crt",
1847 NULL);
1848 return cert_file;
1850 #endif
1852 /* Return the filepath of the claws-mail.desktop file */
1853 const gchar *get_desktop_file(void)
1855 #ifdef DESKTOPFILEPATH
1856 return DESKTOPFILEPATH;
1857 #else
1858 return NULL;
1859 #endif
1862 /* Return the default directory for Plugins. */
1863 const gchar *get_plugin_dir(void)
1865 #ifdef G_OS_WIN32
1866 static gchar *plugin_dir = NULL;
1868 if (!plugin_dir)
1869 plugin_dir = g_strconcat(w32_get_module_dir(),
1870 "\\lib\\claws-mail\\plugins\\",
1871 NULL);
1872 return plugin_dir;
1873 #else
1874 if (is_dir_exist(PLUGINDIR))
1875 return PLUGINDIR;
1876 else {
1877 static gchar *plugin_dir = NULL;
1878 if (!plugin_dir)
1879 plugin_dir = g_strconcat(get_rc_dir(),
1880 G_DIR_SEPARATOR_S, "plugins",
1881 G_DIR_SEPARATOR_S, NULL);
1882 return plugin_dir;
1884 #endif
1888 #ifdef G_OS_WIN32
1889 /* Return the default directory for Themes. */
1890 const gchar *w32_get_themes_dir(void)
1892 static gchar *themes_dir = NULL;
1894 if (!themes_dir)
1895 themes_dir = g_strconcat(w32_get_module_dir(),
1896 "\\share\\claws-mail\\themes",
1897 NULL);
1898 return themes_dir;
1900 #endif
1902 const gchar *get_tmp_dir(void)
1904 static gchar *tmp_dir = NULL;
1906 if (!tmp_dir)
1907 tmp_dir = g_strconcat(get_rc_dir(), G_DIR_SEPARATOR_S,
1908 TMP_DIR, NULL);
1910 return tmp_dir;
1913 gchar *get_tmp_file(void)
1915 gchar *tmp_file;
1916 static guint32 id = 0;
1918 tmp_file = g_strdup_printf("%s%ctmpfile.%08x",
1919 get_tmp_dir(), G_DIR_SEPARATOR, id++);
1921 return tmp_file;
1924 const gchar *get_domain_name(void)
1926 #ifdef G_OS_UNIX
1927 static gchar *domain_name = NULL;
1928 struct addrinfo hints, *res;
1929 char hostname[256];
1930 int s;
1932 if (!domain_name) {
1933 if (gethostname(hostname, sizeof(hostname)) != 0) {
1934 perror("gethostname");
1935 domain_name = "localhost";
1936 } else {
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);
1944 if (s != 0) {
1945 fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
1946 domain_name = g_strdup(hostname);
1947 } else {
1948 domain_name = g_strdup(res->ai_canonname);
1949 freeaddrinfo(res);
1952 debug_print("domain name = %s\n", domain_name);
1955 return domain_name;
1956 #else
1957 return "localhost";
1958 #endif
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;
1967 int err;
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);
1978 if (err == 0)
1979 freeaddrinfo(res);
1981 return (err == 0);
1984 off_t get_file_size(const gchar *file)
1986 #ifdef G_OS_WIN32
1987 GFile *f;
1988 GFileInfo *fi;
1989 GError *error = NULL;
1990 goffset size;
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);
1998 g_object_unref(f);
1999 return -1;
2001 size = g_file_info_get_size(fi);
2002 g_object_unref(fi);
2003 g_object_unref(f);
2004 return size;
2006 #else
2007 GStatBuf s;
2009 if (g_stat(file, &s) < 0) {
2010 FILE_OP_ERROR(file, "stat");
2011 return -1;
2014 return s.st_size;
2015 #endif
2018 time_t get_file_mtime(const gchar *file)
2020 GStatBuf s;
2022 if (g_stat(file, &s) < 0) {
2023 FILE_OP_ERROR(file, "stat");
2024 return -1;
2027 return s.st_mtime;
2030 gboolean file_exist(const gchar *file, gboolean allow_fifo)
2032 GStatBuf s;
2034 if (file == NULL)
2035 return FALSE;
2037 if (g_stat(file, &s) < 0) {
2038 if (ENOENT != errno) FILE_OP_ERROR(file, "stat");
2039 return FALSE;
2042 if (S_ISREG(s.st_mode) || (allow_fifo && S_ISFIFO(s.st_mode)))
2043 return TRUE;
2045 return FALSE;
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)
2053 if (!file)
2054 return TRUE;
2055 #ifdef G_OS_WIN32
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'))
2062 && file[1] == ':')
2063 file += 2; /* Skip drive letter. */
2065 return !(*file == '\\' || *file == '/');
2066 #else
2067 return !(*file == G_DIR_SEPARATOR);
2068 #endif
2072 gboolean is_dir_exist(const gchar *dir)
2074 if (dir == NULL)
2075 return FALSE;
2077 return g_file_test(dir, G_FILE_TEST_IS_DIR);
2080 gboolean is_file_entry_exist(const gchar *file)
2082 if (file == NULL)
2083 return FALSE;
2085 return g_file_test(file, G_FILE_TEST_EXISTS);
2088 gboolean is_file_entry_regular(const gchar *file)
2090 if (file == NULL)
2091 return FALSE;
2093 return g_file_test(file, G_FILE_TEST_IS_REGULAR);
2096 gint change_dir(const gchar *dir)
2098 gchar *prevdir = NULL;
2100 if (debug_mode)
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);
2106 return -1;
2107 } else if (debug_mode) {
2108 gchar *cwd;
2110 cwd = g_get_current_dir();
2111 if (strcmp(prevdir, cwd) != 0)
2112 g_print("current dir: %s\n", cwd);
2113 g_free(cwd);
2114 g_free(prevdir);
2117 return 0;
2120 gint make_dir(const gchar *dir)
2122 if (g_mkdir(dir, S_IRWXU) < 0) {
2123 FILE_OP_ERROR(dir, "mkdir");
2124 return -1;
2126 if (g_chmod(dir, S_IRWXU) < 0)
2127 FILE_OP_ERROR(dir, "chmod");
2129 return 0;
2132 gint make_dir_hier(const gchar *dir)
2134 gchar *parent_dir;
2135 const gchar *p;
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) {
2142 g_free(parent_dir);
2143 return -1;
2147 g_free(parent_dir);
2150 if (!is_dir_exist(dir)) {
2151 if (make_dir(dir) < 0)
2152 return -1;
2155 return 0;
2158 gint remove_all_files(const gchar *dir)
2160 GDir *dp;
2161 const gchar *file_name;
2162 gchar *tmp;
2164 if ((dp = g_dir_open(dir, 0, NULL)) == NULL) {
2165 g_warning("failed to open directory: %s", dir);
2166 return -1;
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");
2173 g_free(tmp);
2176 g_dir_close(dp);
2178 return 0;
2181 gint remove_numbered_files(const gchar *dir, guint first, guint last)
2183 GDir *dp;
2184 const gchar *dir_name;
2185 gchar *prev_dir;
2186 gint file_no;
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 */
2194 g_free(filename);
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");
2199 g_free(filename);
2200 return -1;
2202 g_free(filename);
2203 return 0;
2206 prev_dir = g_get_current_dir();
2208 if (g_chdir(dir) < 0) {
2209 FILE_OP_ERROR(dir, "chdir");
2210 g_free(prev_dir);
2211 return -1;
2214 if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2215 g_warning("failed to open directory: %s", dir);
2216 g_free(prev_dir);
2217 return -1;
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");
2228 g_free(dot_file);
2229 continue;
2231 if (claws_unlink(dir_name) < 0)
2232 FILE_OP_ERROR(dir_name, "unlink");
2236 g_dir_close(dp);
2238 if (g_chdir(prev_dir) < 0) {
2239 FILE_OP_ERROR(prev_dir, "chdir");
2240 g_free(prev_dir);
2241 return -1;
2244 g_free(prev_dir);
2246 return 0;
2249 gint remove_numbered_files_not_in_list(const gchar *dir, GSList *numberlist)
2251 GDir *dp;
2252 const gchar *dir_name;
2253 gchar *prev_dir;
2254 gint file_no;
2255 GHashTable *wanted_files;
2256 GSList *cur;
2257 GError *error = NULL;
2259 if (numberlist == NULL)
2260 return 0;
2262 prev_dir = g_get_current_dir();
2264 if (g_chdir(dir) < 0) {
2265 FILE_OP_ERROR(dir, "chdir");
2266 g_free(prev_dir);
2267 return -1;
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);
2274 g_free(prev_dir);
2275 return -1;
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))
2287 continue;
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");
2295 g_free(dot_file);
2296 continue;
2298 if (claws_unlink(dir_name) < 0)
2299 FILE_OP_ERROR(dir_name, "unlink");
2303 g_dir_close(dp);
2304 g_hash_table_destroy(wanted_files);
2306 if (g_chdir(prev_dir) < 0) {
2307 FILE_OP_ERROR(prev_dir, "chdir");
2308 g_free(prev_dir);
2309 return -1;
2312 g_free(prev_dir);
2314 return 0;
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)
2324 GStatBuf s;
2325 GDir *dp;
2326 const gchar *dir_name;
2327 gchar *prev_dir;
2329 if (g_stat(dir, &s) < 0) {
2330 FILE_OP_ERROR(dir, "stat");
2331 if (ENOENT == errno) return 0;
2332 return -(errno);
2335 if (!S_ISDIR(s.st_mode)) {
2336 if (claws_unlink(dir) < 0) {
2337 FILE_OP_ERROR(dir, "unlink");
2338 return -(errno);
2341 return 0;
2344 prev_dir = g_get_current_dir();
2345 /* g_print("prev_dir = %s\n", prev_dir); */
2347 if (!path_cmp(prev_dir, dir)) {
2348 g_free(prev_dir);
2349 if (g_chdir("..") < 0) {
2350 FILE_OP_ERROR(dir, "chdir");
2351 return -(errno);
2353 prev_dir = g_get_current_dir();
2356 if (g_chdir(dir) < 0) {
2357 FILE_OP_ERROR(dir, "chdir");
2358 g_free(prev_dir);
2359 return -(errno);
2362 if ((dp = g_dir_open(".", 0, NULL)) == NULL) {
2363 g_warning("failed to open directory: %s", dir);
2364 g_chdir(prev_dir);
2365 g_free(prev_dir);
2366 return -(errno);
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)) {
2374 gint ret;
2376 if ((ret = remove_dir_recursive(dir_name)) < 0) {
2377 g_warning("can't remove directory: %s", dir_name);
2378 g_dir_close(dp);
2379 return ret;
2381 } else {
2382 if (claws_unlink(dir_name) < 0)
2383 FILE_OP_ERROR(dir_name, "unlink");
2387 g_dir_close(dp);
2389 if (g_chdir(prev_dir) < 0) {
2390 FILE_OP_ERROR(prev_dir, "chdir");
2391 g_free(prev_dir);
2392 return -(errno);
2395 g_free(prev_dir);
2397 if (g_rmdir(dir) < 0) {
2398 FILE_OP_ERROR(dir, "rmdir");
2399 return -(errno);
2402 return 0;
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)
2410 const gchar *p;
2411 guint new_len = 0;
2412 gchar *out, *outp;
2414 for (p = str; *p != '\0'; ++p) {
2415 if (*p != '\r') {
2416 ++new_len;
2417 if (*p == '\n')
2418 ++new_len;
2421 if (p == str || *(p - 1) != '\n')
2422 new_len += 2;
2424 out = outp = g_malloc(new_len + 1);
2425 for (p = str; *p != '\0'; ++p) {
2426 if (*p != '\r') {
2427 if (*p == '\n')
2428 *outp++ = '\r';
2429 *outp++ = *p;
2432 if (p == str || *(p - 1) != '\n') {
2433 *outp++ = '\r';
2434 *outp++ = '\n';
2436 *outp = '\0';
2438 return out;
2441 gchar *normalize_newlines(const gchar *str)
2443 const gchar *p;
2444 gchar *out, *outp;
2446 out = outp = g_malloc(strlen(str) + 1);
2447 for (p = str; *p != '\0'; ++p) {
2448 if (*p == '\r') {
2449 if (*(p + 1) != '\n')
2450 *outp++ = '\n';
2451 } else
2452 *outp++ = *p;
2455 *outp = '\0';
2457 return out;
2460 gchar *get_outgoing_rfc2822_str(FILE *fp)
2462 gchar buf[BUFFSIZE];
2463 GString *str;
2465 str = g_string_new(NULL);
2467 /* output header part */
2468 while (claws_fgets(buf, sizeof(buf), fp) != NULL) {
2469 strretchomp(buf);
2470 if (!g_ascii_strncasecmp(buf, "Bcc:", 4)) {
2471 gint next;
2473 for (;;) {
2474 next = fgetc(fp);
2475 if (next == EOF)
2476 break;
2477 else if (next != ' ' && next != '\t') {
2478 ungetc(next, fp);
2479 break;
2481 if (claws_fgets(buf, sizeof(buf), fp) == NULL)
2482 break;
2484 } else {
2485 g_string_append(str, buf);
2486 g_string_append(str, "\r\n");
2487 if (buf[0] == '\0')
2488 break;
2492 /* output body part */
2493 while (claws_fgets(buf, sizeof(buf), fp) != NULL) {
2494 strretchomp(buf);
2495 if (buf[0] == '.')
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
2510 * been used.
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"
2525 "1234567890+_./=";
2526 gchar buf_uniq[24];
2527 gint i;
2529 for (i = 0; i < sizeof(buf_uniq) - 1; i++)
2530 buf_uniq[i] = tbl[g_random_int_range(0, sizeof(tbl) - 1)];
2531 buf_uniq[i] = '\0';
2533 return g_strdup_printf("%s_/%s", prefix ? prefix : "MP",
2534 buf_uniq);
2537 char *fgets_crlf(char *buf, int size, FILE *stream)
2539 gboolean is_cr = FALSE;
2540 gboolean last_was_cr = FALSE;
2541 int c = 0;
2542 char *cs;
2544 cs = buf;
2545 while (--size > 0 && (c = getc(stream)) != EOF)
2547 *cs++ = c;
2548 is_cr = (c == '\r');
2549 if (c == '\n') {
2550 break;
2552 if (last_was_cr) {
2553 *(--cs) = '\n';
2554 cs++;
2555 ungetc(c, stream);
2556 break;
2558 last_was_cr = is_cr;
2560 if (c == EOF && cs == buf)
2561 return NULL;
2563 *cs = '\0';
2565 return 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]);
2575 return -1;
2578 return 0;
2581 static gint execute_sync(gchar *const argv[], const gchar *working_directory)
2583 gint status;
2585 cm_return_val_if_fail(argv != NULL && argv[0] != NULL, -1);
2587 #ifdef G_OS_UNIX
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]);
2591 return -1;
2594 if (WIFEXITED(status))
2595 return WEXITSTATUS(status);
2596 else
2597 return -1;
2598 #else
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]);
2605 return -1;
2608 return status;
2609 #endif
2612 gint execute_command_line(const gchar *cmdline, gboolean async,
2613 const gchar *working_directory)
2615 gchar **argv;
2616 gint ret;
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);
2624 if (async)
2625 ret = execute_async(argv, working_directory);
2626 else
2627 ret = execute_sync(argv, working_directory);
2629 g_strfreev(argv);
2631 return ret;
2634 gchar *get_command_output(const gchar *cmdline)
2636 gchar *child_stdout;
2637 gint status;
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,
2644 NULL) == FALSE) {
2645 g_warning("couldn't execute command: %s", cmdline);
2646 return NULL;
2649 return child_stdout;
2652 FILE *get_command_output_stream(const char* cmdline)
2654 GPid pid;
2655 GError *err = NULL;
2656 gchar **argv = NULL;
2657 int fd;
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);
2666 g_error_free(err);
2667 return NULL;
2670 if (!g_spawn_async_with_pipes(NULL, argv, NULL, G_SPAWN_SEARCH_PATH,
2671 NULL, NULL, &pid, NULL, &fd, NULL, &err)
2672 && err)
2674 g_warning("could not spawn '%s': %s", cmdline, err->message);
2675 g_error_free(err);
2676 g_strfreev(argv);
2677 return NULL;
2680 g_strfreev(argv);
2681 return fdopen(fd, "r");
2684 #ifndef G_OS_WIN32
2685 static gint is_unchanged_uri_char(char c)
2687 switch (c) {
2688 case '(':
2689 case ')':
2690 return 0;
2691 default:
2692 return 1;
2696 static void encode_uri(gchar *encoded_uri, gint bufsize, const gchar *uri)
2698 int i;
2699 int k;
2701 k = 0;
2702 for(i = 0; i < strlen(uri) ; i++) {
2703 if (is_unchanged_uri_char(uri[i])) {
2704 if (k + 2 >= bufsize)
2705 break;
2706 encoded_uri[k++] = uri[i];
2708 else {
2709 char * hexa = "0123456789ABCDEF";
2711 if (k + 4 >= bufsize)
2712 break;
2713 encoded_uri[k++] = '%';
2714 encoded_uri[k++] = hexa[uri[i] / 16];
2715 encoded_uri[k++] = hexa[uri[i] % 16];
2718 encoded_uri[k] = 0;
2720 #endif
2722 gint open_uri(const gchar *uri, const gchar *cmdline)
2725 #ifndef G_OS_WIN32
2726 gchar buf[BUFFSIZE];
2727 gchar *p;
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);
2734 if (cmdline &&
2735 (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
2736 !strchr(p + 2, '%'))
2737 g_snprintf(buf, sizeof(buf), cmdline, encoded_uri);
2738 else {
2739 if (cmdline)
2740 g_warning("Open URI command-line is invalid "
2741 "(there must be only one '%%s'): %s",
2742 cmdline);
2743 g_snprintf(buf, sizeof(buf), DEFAULT_BROWSER_CMD, encoded_uri);
2746 execute_command_line(buf, TRUE, NULL);
2747 #else
2748 ShellExecute(NULL, "open", uri, NULL, NULL, SW_SHOW);
2749 #endif
2750 return 0;
2753 gint open_txt_editor(const gchar *filepath, const gchar *cmdline)
2755 gchar buf[BUFFSIZE];
2756 gchar *p;
2758 cm_return_val_if_fail(filepath != NULL, -1);
2760 if (cmdline &&
2761 (p = strchr(cmdline, '%')) && *(p + 1) == 's' &&
2762 !strchr(p + 2, '%'))
2763 g_snprintf(buf, sizeof(buf), cmdline, filepath);
2764 else {
2765 if (cmdline)
2766 g_warning("Open Text Editor command-line is invalid "
2767 "(there must be only one '%%s'): %s",
2768 cmdline);
2769 g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, filepath);
2772 execute_command_line(buf, TRUE, NULL);
2774 return 0;
2777 time_t remote_tzoffset_sec(const gchar *zone)
2779 static gchar ustzstr[] = "PSTPDTMSTMDTCSTCDTESTEDT";
2780 gchar zone3[4];
2781 gchar *p;
2782 gchar c;
2783 gint iustz;
2784 gint offset;
2785 time_t remoteoffset;
2787 strncpy(zone3, zone, 3);
2788 zone3[3] = '\0';
2789 remoteoffset = 0;
2791 if (sscanf(zone, "%c%d", &c, &offset) == 2 &&
2792 (c == '+' || c == '-')) {
2793 remoteoffset = ((offset / 100) * 60 + (offset % 100)) * 60;
2794 if (c == '-')
2795 remoteoffset = -remoteoffset;
2796 } else if (!strncmp(zone, "UT" , 2) ||
2797 !strncmp(zone, "GMT", 3)) {
2798 remoteoffset = 0;
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;
2804 break;
2807 if (*p == '\0')
2808 return -1;
2809 } else if (strlen(zone3) == 1) {
2810 switch (zone[0]) {
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;
2839 } else
2840 return -1;
2842 return remoteoffset;
2845 time_t tzoffset_sec(time_t *now)
2847 struct tm gmt, *lt;
2848 gint off;
2849 struct tm buf1, buf2;
2850 #ifdef G_OS_WIN32
2851 if (now && *now < 0)
2852 return 0;
2853 #endif
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)
2860 off -= 24 * 60;
2861 else if (lt->tm_year > gmt.tm_year)
2862 off += 24 * 60;
2863 else if (lt->tm_yday < gmt.tm_yday)
2864 off -= 24 * 60;
2865 else if (lt->tm_yday > gmt.tm_yday)
2866 off += 24 * 60;
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);
2873 return off * 60;
2876 /* calculate timezone offset */
2877 gchar *tzoffset(time_t *now)
2879 static gchar offset_string[6];
2880 struct tm gmt, *lt;
2881 gint off;
2882 gchar sign = '+';
2883 struct tm buf1, buf2;
2884 #ifdef G_OS_WIN32
2885 if (now && *now < 0)
2886 return 0;
2887 #endif
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)
2894 off -= 24 * 60;
2895 else if (lt->tm_year > gmt.tm_year)
2896 off += 24 * 60;
2897 else if (lt->tm_yday < gmt.tm_yday)
2898 off -= 24 * 60;
2899 else if (lt->tm_yday > gmt.tm_yday)
2900 off += 24 * 60;
2902 if (off < 0) {
2903 sign = '-';
2904 off = -off;
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)
2917 struct tm *lt;
2918 time_t t;
2919 gchar day[4], mon[4];
2920 gint dd, hh, mm, ss, yyyy;
2921 struct tm buf1;
2922 gchar buf2[RFC822_DATE_BUFFSIZE];
2924 t = time(NULL);
2925 if (hidetz)
2926 lt = gmtime_r(&t, &buf1);
2927 else
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)
2950 debug_mode = mode;
2953 gboolean debug_get_mode(void)
2955 return debug_mode;
2958 #ifdef HAVE_VA_OPT
2959 void debug_print_real(const char *file, int line, const gchar *format, ...)
2961 va_list args;
2962 gchar buf[BUFFSIZE];
2963 gint prefix_len;
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);
2971 va_end(args);
2973 g_print("%s", buf);
2975 #else
2976 void debug_print_real(const gchar *format, ...)
2978 va_list args;
2979 gchar buf[BUFFSIZE];
2981 if (!debug_mode) return;
2983 va_start(args, format);
2984 g_vsnprintf(buf, sizeof(buf), format, args);
2985 va_end(args);
2987 g_print("%s", buf);
2989 #endif
2992 const char * debug_srcname(const char *file)
2994 const char *s = strrchr (file, '/');
2995 return s? s+1:file;
2999 void * subject_table_lookup(GHashTable *subject_table, gchar * subject)
3001 if (subject == NULL)
3002 subject = "";
3003 else
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,
3010 void * data)
3012 if (subject == NULL || *subject == 0)
3013 return;
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)
3021 return;
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)
3032 if (u_init_) {
3033 regfree(&u_regex);
3034 u_init_ = FALSE;
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
3047 * is returned.
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) */
3070 /* add more */
3072 const int PREFIXES = sizeof prefixes / sizeof prefixes[0];
3073 int n;
3074 regmatch_t pos;
3076 if (!subject) return 0;
3077 if (!*subject) return 0;
3079 if (!u_init_) {
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",
3086 prefixes[n],
3087 n < PREFIXES - 1 ?
3088 "|" : "");
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);
3100 return 0;
3101 } else {
3102 u_init_ = TRUE;
3103 g_string_free(s, TRUE);
3107 if (!regexec(&u_regex, subject, 1, &pos, 0) && pos.rm_so != -1)
3108 return pos.rm_eo;
3109 else
3110 return 0;
3113 static guint g_stricase_hash(gconstpointer gptr)
3115 guint hash_result = 0;
3116 const char *str;
3118 for (str = gptr; str && *str; str++) {
3119 hash_result += toupper(*str);
3122 return hash_result;
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,
3147 const gchar * path)
3149 const gchar * p;
3150 gchar * result_p;
3151 guint remaining;
3153 result_p = result;
3154 remaining = size;
3156 for(p = path ; * p != '\0' ; p ++) {
3158 if (isalnum((guchar)*p) || (* p == '/')) {
3159 if (remaining > 0) {
3160 * result_p = * p;
3161 result_p ++;
3162 remaining --;
3164 else {
3165 result[size - 1] = '\0';
3166 return -1;
3169 else {
3170 if (remaining >= 2) {
3171 * result_p = '\\';
3172 result_p ++;
3173 * result_p = * p;
3174 result_p ++;
3175 remaining -= 2;
3177 else {
3178 result[size - 1] = '\0';
3179 return -1;
3183 if (remaining > 0) {
3184 * result_p = '\0';
3186 else {
3187 result[size - 1] = '\0';
3188 return -1;
3191 return 0;
3194 typedef struct
3196 GNode *parent;
3197 GNodeMapFunc func;
3198 gpointer data;
3199 } GNodeMapData;
3201 static void g_node_map_recursive(GNode *node, gpointer data)
3203 GNodeMapData *mapdata = (GNodeMapData *) data;
3204 GNode *newnode;
3205 GNodeMapData newmapdata;
3206 gpointer newdata;
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)
3223 GNode *root;
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);
3237 return root;
3240 #define HEX_TO_INT(val, hex) \
3242 gchar c = hex; \
3244 if ('0' <= c && c <= '9') { \
3245 val = c - '0'; \
3246 } else if ('a' <= c && c <= 'f') { \
3247 val = c - 'a' + 10; \
3248 } else if ('A' <= c && c <= 'F') { \
3249 val = c - 'A' + 10; \
3250 } else { \
3251 val = -1; \
3255 gboolean get_hex_value(guchar *out, gchar c1, gchar c2)
3257 gint hi, lo;
3259 HEX_TO_INT(hi, c1);
3260 HEX_TO_INT(lo, c2);
3262 if (hi == -1 || lo == -1)
3263 return FALSE;
3265 *out = (hi << 4) + lo;
3266 return TRUE;
3269 #define INT_TO_HEX(hex, val) \
3271 if ((val) < 10) \
3272 hex = '0' + (val); \
3273 else \
3274 hex = 'A' + (val) - 10; \
3277 void get_hex_str(gchar *out, guchar ch)
3279 gchar hex;
3281 INT_TO_HEX(hex, ch >> 4);
3282 *out++ = hex;
3283 INT_TO_HEX(hex, ch & 0x0f);
3284 *out = hex;
3287 #undef REF_DEBUG
3288 #ifndef REF_DEBUG
3289 #define G_PRINT_REF 1 == 1 ? (void) 0 : (void)
3290 #else
3291 #define G_PRINT_REF g_print
3292 #endif
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)
3309 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
3319 * auto pointer
3321 typedef struct AutoPointerRef {
3322 void (*free) (gpointer);
3323 gpointer pointer;
3324 glong cnt;
3325 } AutoPointerRef;
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 */
3334 } AutoPointer;
3337 *\brief Creates an auto pointer for a g_new()ed pointer. Example:
3339 *\code
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,
3346 * -1);
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,
3353 * S_DATA, protect,
3354 * -1);
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.
3368 *\endcode
3370 *\param pointer Pointer to be guarded.
3372 *\return GAuto * Pointer that should be used in containers with
3373 * GType support.
3375 GAuto *g_auto_pointer_new(gpointer p)
3377 AutoPointerRef *ref;
3378 AutoPointer *ptr;
3380 if (p == NULL)
3381 return NULL;
3383 ref = g_new0(AutoPointerRef, 1);
3384 ptr = g_new0(AutoPointer, 1);
3386 ref->pointer = p;
3387 ref->free = g_free;
3388 ref->cnt = 1;
3390 ptr->ref = ref;
3391 ptr->ptr = p;
3393 #ifdef REF_DEBUG
3394 G_PRINT_REF ("XXXX ALLOC(%lx)\n", p);
3395 #endif
3396 return ptr;
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_)
3405 AutoPointer *aptr;
3407 if (p == NULL)
3408 return NULL;
3410 aptr = g_auto_pointer_new(p);
3411 aptr->ref->free = free_;
3412 return aptr;
3415 gpointer g_auto_pointer_get_ptr(GAuto *auto_ptr)
3417 if (auto_ptr == NULL)
3418 return 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)
3434 AutoPointer *ptr;
3435 AutoPointerRef *ref;
3436 AutoPointer *newp;
3438 if (auto_ptr == NULL)
3439 return NULL;
3441 ptr = auto_ptr;
3442 ref = ptr->ref;
3443 newp = g_new0(AutoPointer, 1);
3445 newp->ref = ref;
3446 newp->ptr = ref->pointer;
3447 ++(ref->cnt);
3449 #ifdef REF_DEBUG
3450 G_PRINT_REF ("XXXX COPY(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
3451 #endif
3452 return newp;
3456 *\brief Free an auto pointer
3458 void g_auto_pointer_free(GAuto *auto_ptr)
3460 AutoPointer *ptr;
3461 AutoPointerRef *ref;
3463 if (auto_ptr == NULL)
3464 return;
3466 ptr = auto_ptr;
3467 ref = ptr->ref;
3469 if (--(ref->cnt) == 0) {
3470 #ifdef REF_DEBUG
3471 G_PRINT_REF ("XXXX FREE(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
3472 #endif
3473 ref->free(ref->pointer);
3474 g_free(ref);
3476 #ifdef REF_DEBUG
3477 else
3478 G_PRINT_REF ("XXXX DEREF(%lx) -- REF (%d)\n", ref->pointer, ref->cnt);
3479 #endif
3480 g_free(ptr);
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)
3488 const gchar *ep_;
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);
3496 *bp = scanpos;
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_)) {
3504 break;
3505 } else if (strchr("(", *ep_)) {
3506 parenthese_cnt++;
3507 } else if (strchr(")", *ep_)) {
3508 if (parenthese_cnt > 0)
3509 parenthese_cnt--;
3510 else
3511 break;
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));
3525 ep_--)
3528 #undef IS_REAL_PUNCT
3530 *ep = ep_;
3532 return TRUE;
3535 gchar *make_uri_string(const gchar *bp, const gchar *ep)
3537 while (bp && *bp && g_ascii_isspace(*bp))
3538 bp++;
3539 return g_strndup(bp, ep - bp);
3542 /* valid mail address characters */
3543 #define IS_RFC822_CHAR(ch) \
3544 (IS_ASCII(ch) && \
3545 (ch) > 32 && \
3546 (ch) != 127 && \
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)
3556 gint n;
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]);
3562 return htab;
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;
3569 register gchar *p;
3571 if (last - first > BUFFSIZE || first > last)
3572 return FALSE;
3574 for (p = buf; p < m && first < last; *p++ = *first++)
3576 *p = 0;
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);
3606 if (hdr) {
3607 const gchar *start_quote = NULL;
3608 const gchar *end_quote = NULL;
3609 search_again:
3610 /* go to the real start */
3611 if (start[0] == ',')
3612 start++;
3613 if (start[0] == ';')
3614 start++;
3615 while (start[0] == '\n' || start[0] == '\r')
3616 start++;
3617 while (start[0] == ' ' || start[0] == '\t')
3618 start++;
3620 *bp = start;
3622 /* check if there are quotes (to skip , in them) */
3623 if (*start == '"') {
3624 start_quote = start;
3625 start++;
3626 end_quote = strstr(start, "\"");
3627 } else {
3628 start_quote = NULL;
3629 end_quote = NULL;
3632 /* skip anything between quotes */
3633 if (start_quote && end_quote) {
3634 start = 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, ";");
3646 else
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)
3657 return TRUE;
3658 else if (*ep < start+strlen(start)) {
3659 start = *ep;
3660 goto search_again;
3661 } else if (start_quote && strstr(start, "\"") && strstr(start, "\"") < *ep) {
3662 *bp = start_quote;
3663 return TRUE;
3664 } else
3665 return FALSE;
3668 if (!dom_tab)
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? */
3678 bp_++;
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_++)
3686 if (*ep_ == '.') {
3687 prelast_dot = last_dot;
3688 last_dot = ep_;
3689 if (*(last_dot + 1) == '.') {
3690 if (prelast_dot == NULL)
3691 return FALSE;
3692 last_dot = prelast_dot;
3693 break;
3697 /* TODO: really should terminate with an alnum? */
3698 for (; ep_ > scanpos && !IS_ASCII_ALNUM(*(const guchar *)ep_);
3699 --ep_)
3701 ep_++;
3703 if (last_dot == NULL)
3704 return FALSE;
3705 if (last_dot >= ep_)
3706 last_dot = prelast_dot;
3707 if (last_dot == NULL || (scanpos + 1 >= last_dot))
3708 return FALSE;
3709 last_dot++;
3711 for (last_tld_char = last_dot; last_tld_char < ep_; last_tld_char++)
3712 if (*last_tld_char == '?')
3713 break;
3715 if (is_toplvl_domain(dom_tab, last_dot, last_tld_char))
3716 result = TRUE;
3718 *ep = ep_;
3719 *bp = bp_;
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 */
3729 ep_ += 3;
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 */
3739 bp_--;
3741 *ep = ep_;
3742 *bp = bp_;
3743 return TRUE;
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_))
3748 return FALSE;
3750 /* see if this is <bracketed>; in this case we also scan for the informative part. */
3751 if (bp_ - 1 <= start || *(bp_ - 1) != '<' || *ep_ != '>')
3752 return TRUE;
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)
3763 ep_++;
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_) {
3769 POP_STACK();
3770 continue;
3772 if (!IN_STACK() && (*bp_ == '\'' || *bp_ == '"')) {
3773 PUSH_STACK(*bp_);
3774 continue;
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()) {
3782 if (*bp_ == '-'
3783 && (((bp_ - 1) >= start) && isalnum(*(bp_ - 1)))
3784 && (((bp_ + 1) < ep_) && isalnum(*(bp_ + 1)))) {
3785 /* hyphens are allowed, but only in
3786 between alnums */
3787 } else if (strchr(" \"'", *bp_)) {
3788 /* but anything not being a punctiation
3789 is ok */
3790 } else {
3791 break; /* anything else is rejected */
3796 bp_++;
3798 /* scan forward (should start with an alnum) */
3799 for (; *bp_ != '<' && isspace(*bp_) && *bp_ != '"'; bp_++)
3801 #undef PEEK_STACK
3802 #undef PUSH_STACK
3803 #undef POP_STACK
3804 #undef IN_STACK
3805 #undef FULL_STACK
3808 *bp = bp_;
3809 *ep = ep_;
3811 return result;
3814 #undef IS_QUOTE
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 */
3822 gchar *tmp;
3823 gchar *result;
3824 gchar *colon, *at;
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) {
3834 result = tmp;
3835 } else {
3836 result = g_strconcat("mailto:", tmp, NULL);
3837 g_free(tmp);
3840 return result;
3843 gchar *make_http_string(const gchar *bp, const gchar *ep)
3845 /* returns an http: URI; */
3846 gchar *tmp;
3847 gchar *result;
3849 while (bp && *bp && g_ascii_isspace(*bp))
3850 bp++;
3851 tmp = g_strndup(bp, ep - bp);
3852 result = g_strconcat("http://", tmp, NULL);
3853 g_free(tmp);
3855 return result;
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;
3863 if (!fp)
3864 return 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')
3869 trimmed++;
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')
3884 testcmd++;
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);
3895 g_free(tmp);
3896 g_free(orig_testcmd);
3898 if (res != 0) {
3899 g_strfreev(parts);
3900 continue;
3902 } else {
3903 gint res = system(testcmd);
3904 g_free(orig_testcmd);
3906 if (res != 0) {
3907 g_strfreev(parts);
3908 continue;
3913 trimmed = parts[1];
3914 while (trimmed[0] == ' ' || trimmed[0] == '\t')
3915 trimmed++;
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);
3923 g_strfreev(parts);
3924 claws_fclose(fp);
3925 if (needsterminal) {
3926 gchar *tmp = g_strdup_printf("xterm -e %s", result);
3927 g_free(result);
3928 result = tmp;
3930 return result;
3932 g_strfreev(parts);
3934 claws_fclose(fp);
3935 return NULL;
3937 gchar *mailcap_get_command_for_type(const gchar *type, const gchar *file_to_open)
3939 gchar *result = NULL;
3940 gchar *path = NULL;
3941 if (type == NULL)
3942 return 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);
3945 g_free(path);
3946 if (result)
3947 return result;
3948 result = mailcap_get_command_in_file("/etc/mailcap", type, file_to_open);
3949 return result;
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");
3958 FILE *outfp = NULL;
3959 gchar buf[BUFFSIZE];
3960 gboolean err = FALSE;
3962 if (!fp) {
3963 fp = claws_fopen(path, "a");
3964 if (!fp) {
3965 g_warning("failed to create file %s", path);
3966 g_free(path);
3967 g_free(outpath);
3968 return;
3970 fp = g_freopen(path, "rb", fp);
3971 if (!fp) {
3972 g_warning("failed to reopen file %s", path);
3973 g_free(path);
3974 g_free(outpath);
3975 return;
3979 outfp = claws_fopen(outpath, "wb");
3980 if (!outfp) {
3981 g_warning("failed to create file %s", outpath);
3982 g_free(path);
3983 g_free(outpath);
3984 claws_fclose(fp);
3985 return;
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] == ' ')
3991 trimmed++;
3992 while (trimmed[strlen(trimmed)-1] == ' ')
3993 trimmed[strlen(trimmed)-1] = '\0';
3995 if (!strcmp(trimmed, type)) {
3996 g_strfreev(parts);
3997 continue;
3999 else {
4000 if(claws_fputs(buf, outfp) == EOF) {
4001 err = TRUE;
4002 g_strfreev(parts);
4003 break;
4006 g_strfreev(parts);
4008 if (fprintf(outfp, "%s; %s\n", type, command) < 0)
4009 err = TRUE;
4011 if (fp)
4012 claws_fclose(fp);
4014 if (claws_safe_fclose(outfp) == EOF)
4015 err = TRUE;
4017 if (!err)
4018 g_rename(outpath, path);
4020 g_free(path);
4021 g_free(outpath);
4024 /* crude test to see if a file is an email. */
4025 gboolean file_is_email (const gchar *filename)
4027 FILE *fp = NULL;
4028 gchar buffer[2048];
4029 gint score = 0;
4030 if (filename == NULL)
4031 return FALSE;
4032 if ((fp = claws_fopen(filename, "rb")) == NULL)
4033 return FALSE;
4034 while (score < 3
4035 && claws_fgets(buffer, sizeof (buffer), fp) != NULL) {
4036 if (!strncmp(buffer, "From:", strlen("From:")))
4037 score++;
4038 else if (!strncmp(buffer, "Date:", strlen("Date:")))
4039 score++;
4040 else if (!strncmp(buffer, "Message-ID:", strlen("Message-ID:")))
4041 score++;
4042 else if (!strncmp(buffer, "Subject:", strlen("Subject:")))
4043 score++;
4044 else if (!strcmp(buffer, "\r\n")) {
4045 debug_print("End of headers\n");
4046 break;
4049 claws_fclose(fp);
4050 return (score >= 3);
4053 gboolean sc_g_list_bigger(GList *list, gint max)
4055 GList *cur = list;
4056 int i = 0;
4057 while (cur && i <= max+1) {
4058 i++;
4059 cur = cur->next;
4061 return (i > max);
4064 gboolean sc_g_slist_bigger(GSList *list, gint max)
4066 GSList *cur = list;
4067 int i = 0;
4068 while (cur && i <= max+1) {
4069 i++;
4070 cur = cur->next;
4072 return (i > max);
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,
4084 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,
4087 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)
4102 int i = 0;
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'; \
4172 return 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)
4184 init_time_names();
4186 if (format == NULL || lt == NULL)
4187 return 0;
4189 if (last_tzset != time(NULL)) {
4190 tzset();
4191 last_tzset = time(NULL);
4193 while(*format) {
4194 if (*format == '%') {
4195 gint len = 0, tmp = 0;
4196 format++;
4197 switch(*format) {
4198 case '%':
4199 len = 1; CHECK_SIZE();
4200 *curpos = '%';
4201 break;
4202 case 'a':
4203 len = s_daynames_len[lt->tm_wday]; CHECK_SIZE();
4204 strncpy2(curpos, s_daynames[lt->tm_wday], buflen - total_done);
4205 break;
4206 case 'A':
4207 len = daynames_len[lt->tm_wday]; CHECK_SIZE();
4208 strncpy2(curpos, daynames[lt->tm_wday], buflen - total_done);
4209 break;
4210 case 'b':
4211 case 'h':
4212 len = s_monthnames_len[lt->tm_mon]; CHECK_SIZE();
4213 strncpy2(curpos, s_monthnames[lt->tm_mon], buflen - total_done);
4214 break;
4215 case 'B':
4216 len = monthnames_len[lt->tm_mon]; CHECK_SIZE();
4217 strncpy2(curpos, monthnames[lt->tm_mon], buflen - total_done);
4218 break;
4219 case 'c':
4220 strftime(subbuf, 64, "%c", lt);
4221 len = strlen(subbuf); CHECK_SIZE();
4222 strncpy2(curpos, subbuf, buflen - total_done);
4223 break;
4224 case 'C':
4225 total_done += 2; CHECK_SIZE();
4226 tmp = (lt->tm_year + 1900)/100;
4227 *curpos++ = '0'+(tmp / 10);
4228 *curpos++ = '0'+(tmp % 10);
4229 break;
4230 case 'd':
4231 total_done += 2; CHECK_SIZE();
4232 *curpos++ = '0'+(lt->tm_mday / 10);
4233 *curpos++ = '0'+(lt->tm_mday % 10);
4234 break;
4235 case 'D':
4236 total_done += 8; CHECK_SIZE();
4237 *curpos++ = '0'+((lt->tm_mon+1) / 10);
4238 *curpos++ = '0'+((lt->tm_mon+1) % 10);
4239 *curpos++ = '/';
4240 *curpos++ = '0'+(lt->tm_mday / 10);
4241 *curpos++ = '0'+(lt->tm_mday % 10);
4242 *curpos++ = '/';
4243 tmp = lt->tm_year%100;
4244 *curpos++ = '0'+(tmp / 10);
4245 *curpos++ = '0'+(tmp % 10);
4246 break;
4247 case 'e':
4248 len = 2; CHECK_SIZE();
4249 snprintf(curpos, buflen - total_done, "%2d", lt->tm_mday);
4250 break;
4251 case 'F':
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);
4255 break;
4256 case 'H':
4257 total_done += 2; CHECK_SIZE();
4258 *curpos++ = '0'+(lt->tm_hour / 10);
4259 *curpos++ = '0'+(lt->tm_hour % 10);
4260 break;
4261 case 'I':
4262 total_done += 2; CHECK_SIZE();
4263 tmp = lt->tm_hour;
4264 if (tmp > 12)
4265 tmp -= 12;
4266 else if (tmp == 0)
4267 tmp = 12;
4268 *curpos++ = '0'+(tmp / 10);
4269 *curpos++ = '0'+(tmp % 10);
4270 break;
4271 case 'j':
4272 len = 3; CHECK_SIZE();
4273 snprintf(curpos, buflen - total_done, "%03d", lt->tm_yday+1);
4274 break;
4275 case 'k':
4276 len = 2; CHECK_SIZE();
4277 snprintf(curpos, buflen - total_done, "%2d", lt->tm_hour);
4278 break;
4279 case 'l':
4280 len = 2; CHECK_SIZE();
4281 tmp = lt->tm_hour;
4282 if (tmp > 12)
4283 tmp -= 12;
4284 else if (tmp == 0)
4285 tmp = 12;
4286 snprintf(curpos, buflen - total_done, "%2d", tmp);
4287 break;
4288 case 'm':
4289 total_done += 2; CHECK_SIZE();
4290 tmp = lt->tm_mon + 1;
4291 *curpos++ = '0'+(tmp / 10);
4292 *curpos++ = '0'+(tmp % 10);
4293 break;
4294 case 'M':
4295 total_done += 2; CHECK_SIZE();
4296 *curpos++ = '0'+(lt->tm_min / 10);
4297 *curpos++ = '0'+(lt->tm_min % 10);
4298 break;
4299 case 'n':
4300 len = 1; CHECK_SIZE();
4301 *curpos = '\n';
4302 break;
4303 case 'p':
4304 if (lt->tm_hour >= 12) {
4305 len = s_pm_up_len; CHECK_SIZE();
4306 snprintf(curpos, buflen-total_done, "%s", s_pm_up);
4307 } else {
4308 len = s_am_up_len; CHECK_SIZE();
4309 snprintf(curpos, buflen-total_done, "%s", s_am_up);
4311 break;
4312 case 'P':
4313 if (lt->tm_hour >= 12) {
4314 len = s_pm_low_len; CHECK_SIZE();
4315 snprintf(curpos, buflen-total_done, "%s", s_pm_low);
4316 } else {
4317 len = s_am_low_len; CHECK_SIZE();
4318 snprintf(curpos, buflen-total_done, "%s", s_am_low);
4320 break;
4321 case 'r':
4322 #ifdef G_OS_WIN32
4323 strftime(subbuf, 64, "%I:%M:%S %p", lt);
4324 #else
4325 strftime(subbuf, 64, "%r", lt);
4326 #endif
4327 len = strlen(subbuf); CHECK_SIZE();
4328 strncpy2(curpos, subbuf, buflen - total_done);
4329 break;
4330 case 'R':
4331 total_done += 5; CHECK_SIZE();
4332 *curpos++ = '0'+(lt->tm_hour / 10);
4333 *curpos++ = '0'+(lt->tm_hour % 10);
4334 *curpos++ = ':';
4335 *curpos++ = '0'+(lt->tm_min / 10);
4336 *curpos++ = '0'+(lt->tm_min % 10);
4337 break;
4338 case 's':
4339 snprintf(subbuf, 64, "%" CM_TIME_FORMAT, mktime(lt));
4340 len = strlen(subbuf); CHECK_SIZE();
4341 strncpy2(curpos, subbuf, buflen - total_done);
4342 break;
4343 case 'S':
4344 total_done += 2; CHECK_SIZE();
4345 *curpos++ = '0'+(lt->tm_sec / 10);
4346 *curpos++ = '0'+(lt->tm_sec % 10);
4347 break;
4348 case 't':
4349 len = 1; CHECK_SIZE();
4350 *curpos = '\t';
4351 break;
4352 case 'T':
4353 total_done += 8; CHECK_SIZE();
4354 *curpos++ = '0'+(lt->tm_hour / 10);
4355 *curpos++ = '0'+(lt->tm_hour % 10);
4356 *curpos++ = ':';
4357 *curpos++ = '0'+(lt->tm_min / 10);
4358 *curpos++ = '0'+(lt->tm_min % 10);
4359 *curpos++ = ':';
4360 *curpos++ = '0'+(lt->tm_sec / 10);
4361 *curpos++ = '0'+(lt->tm_sec % 10);
4362 break;
4363 case 'u':
4364 len = 1; CHECK_SIZE();
4365 snprintf(curpos, buflen - total_done, "%d", lt->tm_wday == 0 ? 7: lt->tm_wday);
4366 break;
4367 case 'w':
4368 len = 1; CHECK_SIZE();
4369 snprintf(curpos, buflen - total_done, "%d", lt->tm_wday);
4370 break;
4371 case 'x':
4372 strftime(subbuf, 64, "%x", lt);
4373 len = strlen(subbuf); CHECK_SIZE();
4374 strncpy2(curpos, subbuf, buflen - total_done);
4375 break;
4376 case 'X':
4377 strftime(subbuf, 64, "%X", lt);
4378 len = strlen(subbuf); CHECK_SIZE();
4379 strncpy2(curpos, subbuf, buflen - total_done);
4380 break;
4381 case 'y':
4382 total_done += 2; CHECK_SIZE();
4383 tmp = lt->tm_year%100;
4384 *curpos++ = '0'+(tmp / 10);
4385 *curpos++ = '0'+(tmp % 10);
4386 break;
4387 case 'Y':
4388 len = 4; CHECK_SIZE();
4389 snprintf(curpos, buflen - total_done, "%4d", lt->tm_year + 1900);
4390 break;
4391 case 'G':
4392 case 'g':
4393 case 'U':
4394 case 'V':
4395 case 'W':
4396 case 'z':
4397 case 'Z':
4398 case '+':
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);
4404 break;
4405 case 'E':
4406 case 'O':
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);
4412 format++;
4413 break;
4414 default:
4415 g_warning("format error (%c)", *format);
4416 *curpos = '\0';
4417 return total_done;
4419 curpos += len;
4420 format++;
4421 } else {
4422 int len = 1; CHECK_SIZE();
4423 *curpos++ = *format++;
4426 *curpos = '\0';
4427 return total_done;
4430 #ifdef G_OS_WIN32
4431 #define WEXITSTATUS(x) (x)
4432 #endif
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));
4438 GSList *cur;
4440 #ifndef G_OS_WIN32
4441 result = g_string_append(result, G_DIR_SEPARATOR_S);
4442 #else
4443 if (pathlist->data) {
4444 const gchar *root = (gchar *)pathlist->data;
4445 if (root[0] != '\0' && g_ascii_isalpha(root[0]) &&
4446 root[1] == ':') {
4447 /* drive - don't prepend dir separator */
4448 } else {
4449 result = g_string_append(result, G_DIR_SEPARATOR_S);
4452 #endif
4454 for (cur = pathlist; cur; cur = cur->next) {
4455 result = g_string_append(result, (gchar *)cur->data);
4456 if (cur->next)
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)
4466 gchar **path_parts;
4467 GSList *canonical_parts = NULL;
4468 GStatBuf st;
4469 int i;
4470 #ifndef G_OS_WIN32
4471 gboolean follow_symlinks = TRUE;
4472 #endif
4474 if (depth > 32) {
4475 #ifndef G_OS_WIN32
4476 errno = ELOOP;
4477 #else
4478 errno = EINVAL; /* can't happen, no symlink handling */
4479 #endif
4480 return NULL;
4483 if (!g_path_is_absolute(filename)) {
4484 errno =EINVAL;
4485 return NULL;
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], ""))
4492 continue;
4493 if (!strcmp(path_parts[i], "."))
4494 continue;
4495 else if (!strcmp(path_parts[i], "..")) {
4496 if (i == 0) {
4497 errno =ENOTDIR;
4498 g_strfreev(path_parts);
4499 return NULL;
4501 else /* Remove the last inserted element */
4502 canonical_parts =
4503 g_slist_delete_link(canonical_parts,
4504 canonical_parts);
4505 } else {
4506 gchar *tmp_path;
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) {
4515 errno = 0;
4516 #ifndef G_OS_WIN32
4517 follow_symlinks = FALSE;
4518 #endif
4520 if (errno != 0) {
4521 g_free(tmp_path);
4522 slist_free_strings_full(canonical_parts);
4523 g_strfreev(path_parts);
4525 return NULL;
4528 #ifndef G_OS_WIN32
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 */
4535 canonical_parts =
4536 g_slist_delete_link(canonical_parts,
4537 canonical_parts);
4538 /* add the target */
4539 canonical_parts = g_slist_prepend(canonical_parts,
4540 g_strdup(target));
4541 g_free(target);
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;
4550 if (!error)
4551 canonical_parts = cm_split_path(target, depth + 1);
4552 else
4553 g_error_free(error);
4554 if (canonical_parts == NULL) {
4555 g_free(tmp_path);
4556 g_strfreev(path_parts);
4557 return NULL;
4559 g_free(target);
4561 #endif
4562 g_free(tmp_path);
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)
4578 return -EINVAL;
4579 if (canonical_name == NULL)
4580 return -EINVAL;
4581 *canonical_name = NULL;
4583 is_absolute = g_path_is_absolute(filename);
4584 if (!is_absolute) {
4585 /* Always work on absolute filenames. */
4586 gchar *cur = g_get_current_dir();
4587 gchar *absolute_filename = g_strconcat(cur, G_DIR_SEPARATOR_S,
4588 filename, NULL);
4590 canonical_parts = cm_split_path(absolute_filename, 0);
4591 g_free(absolute_filename);
4592 g_free(cur);
4593 } else
4594 canonical_parts = cm_split_path(filename, 0);
4596 if (canonical_parts == NULL)
4597 return -errno;
4599 *canonical_name = canonical_list_to_file(canonical_parts);
4600 slist_free_strings_full(canonical_parts);
4601 return 0;
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);
4610 g_free(tmp);
4612 if (strlen(out) != *out_len) {
4613 g_warning("strlen(out) %"G_GSIZE_FORMAT" != *out_len %"G_GSIZE_FORMAT, strlen(out), *out_len);
4616 return out;
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. */
4622 gboolean
4623 get_random_bytes(void *buf, size_t count)
4625 /* Open our prng source. */
4626 #if defined G_OS_WIN32
4627 HCRYPTPROV rnd;
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");
4632 return FALSE;
4634 #else
4635 int rnd;
4636 ssize_t ret;
4638 rnd = open("/dev/urandom", O_RDONLY);
4639 if (rnd == -1) {
4640 FILE_OP_ERROR("/dev/urandom", "open");
4641 debug_print("Could not open /dev/urandom.\n");
4642 return FALSE;
4644 #endif
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);
4651 return FALSE;
4653 #else
4654 ret = read(rnd, buf, count);
4655 if (ret != 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);
4658 close(rnd);
4659 return FALSE;
4661 #endif
4663 /* Close the prng source. */
4664 #if defined G_OS_WIN32
4665 CryptReleaseContext(rnd, 0);
4666 #else
4667 close(rnd);
4668 #endif
4670 return TRUE;
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
4675 user.
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) {
4685 if (*pos == '.') {
4686 if (!dotcert_pos) {
4687 /* match the .cert suffix */
4688 if (strcmp(pos, ".cert") == 0) {
4689 dotcert_pos = pos;
4691 } else {
4692 if (!dotfp_pos) {
4693 /* match an eventual fingerprint */
4694 /* or the port number */
4695 if (strncmp(pos + 3, ":", 1) == 0) {
4696 dotfp_pos = pos;
4697 } else {
4698 dotport_pos = pos;
4700 } else {
4701 /* match the port number */
4702 dotport_pos = pos;
4706 pos--;
4708 if (!dotport_pos || !dotcert_pos) {
4709 g_warning("could not parse filename %s", str);
4710 return FALSE;
4713 if (server != NULL)
4714 *server = g_strndup(str, dotport_pos - str);
4715 if (dotfp_pos) {
4716 if (port != NULL)
4717 *port = g_strndup(dotport_pos + 1, dotfp_pos - dotport_pos - 1);
4718 if (fp != NULL)
4719 *fp = g_strndup(dotfp_pos + 1, dotcert_pos - dotfp_pos - 1);
4720 } else {
4721 if (port != NULL)
4722 *port = g_strndup(dotport_pos + 1, dotcert_pos - dotport_pos - 1);
4723 if (fp != NULL)
4724 *fp = NULL;
4727 debug_print("filename='%s' => server='%s' port='%s' fp='%s'\n",
4728 str,
4729 (server ? *server : "(n/a)"),
4730 (port ? *port : "(n/a)"),
4731 (fp ? *fp : "(n/a)"));
4733 if (!(server && *server) || !(port && *port))
4734 return FALSE;
4735 else
4736 return TRUE;
4739 #ifdef G_OS_WIN32
4740 gchar *win32_debug_log_path(void)
4742 return g_strconcat(g_get_tmp_dir(), G_DIR_SEPARATOR_S,
4743 "claws-win32.log", NULL);
4745 #endif