fix bugs 4373, 'attach mailto URI double free' and 4374, ' insert mailto URI misses...
[claws.git] / src / common / utils.c
index aa296597d170282a8bc56606e8feea5e9deac59c..848f0ce8690c27bde1bd1f035c6241552da0fb6d 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
- * Copyright (C) 1999-2016 Hiroyuki Yamamoto & The Claws Mail Team
+ * Copyright (C) 1999-2020 The Claws Mail Team and Hiroyuki Yamamoto
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
 #include <ctype.h>
 #include <errno.h>
 #include <sys/param.h>
-#ifndef G_OS_WIN32
-#include <sys/socket.h>
+#ifdef G_OS_WIN32
+#  include <ws2tcpip.h>
+#else
+#  include <sys/socket.h>
 #endif
 
 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
@@ -191,7 +193,7 @@ gchar *itos(gint n)
 gchar *to_human_readable(goffset size)
 {
        static gchar str[14];
-       static gchar *b_format = NULL, *kb_format = NULL, 
+       static gchar *b_format = NULL, *kb_format = NULL,
                     *mb_format = NULL, *gb_format = NULL;
        register int t = 0, r = 0;
        if (b_format == NULL) {
@@ -200,7 +202,7 @@ gchar *to_human_readable(goffset size)
                mb_format = _("%d.%02dMB");
                gb_format = _("%.2fGB");
        }
-       
+
        if (size < (goffset)1024) {
                g_snprintf(str, sizeof(str), b_format, (gint)size);
                return str;
@@ -218,14 +220,6 @@ gchar *to_human_readable(goffset size)
        }
 }
 
-/* strstr with NULL-checking */
-gchar *strstr2(const gchar *s1, const gchar *s2)
-{
-       if (s1 == NULL || s2 == NULL)
-               return NULL;
-       else
-               return strstr(s1, s2);
-}
 /* compare paths */
 gint path_cmp(const gchar *s1, const gchar *s2)
 {
@@ -306,6 +300,7 @@ gchar *strcrchomp(gchar *str)
        return str;
 }
 
+#ifndef HAVE_STRCASESTR
 /* Similar to `strstr' but this function ignores the case of both strings.  */
 gchar *strcasestr(const gchar *haystack, const gchar *needle)
 {
@@ -313,6 +308,7 @@ gchar *strcasestr(const gchar *haystack, const gchar *needle)
 
        return strncasestr(haystack, haystack_len, needle);
 }
+#endif /* HAVE_STRCASESTR */
 
 gchar *strncasestr(const gchar *haystack, gint haystack_len, const gchar *needle)
 {
@@ -1417,7 +1413,8 @@ gint scan_mailto_url(const gchar *mailto, gchar **from, gchar **to, gchar **cc,
                                          "../",
                                          NULL };
        gint num_attach = 0;
-       gchar **my_att = NULL;
+
+       cm_return_val_if_fail(mailto != NULL, -1);
 
        Xstrdup_a(tmp_mailto, mailto, return -1);
 
@@ -1433,9 +1430,6 @@ gint scan_mailto_url(const gchar *mailto, gchar **from, gchar **to, gchar **cc,
        if (to && !*to)
                *to = decode_uri_gdup(tmp_mailto);
 
-       my_att = g_malloc(sizeof(char *));
-       my_att[0] = NULL;
-
        while (p) {
                gchar *field, *value;
 
@@ -1462,6 +1456,7 @@ gint scan_mailto_url(const gchar *mailto, gchar **from, gchar **to, gchar **cc,
                        } else {
                                gchar *tmp = decode_uri_gdup(value);
                                gchar *new_from = g_strdup_printf("%s, %s", *from, tmp);
+                               g_free(tmp);
                                g_free(*from);
                                *from = new_from;
                        }
@@ -1471,6 +1466,7 @@ gint scan_mailto_url(const gchar *mailto, gchar **from, gchar **to, gchar **cc,
                        } else {
                                gchar *tmp = decode_uri_gdup(value);
                                gchar *new_cc = g_strdup_printf("%s, %s", *cc, tmp);
+                               g_free(tmp);
                                g_free(*cc);
                                *cc = new_cc;
                        }
@@ -1480,6 +1476,7 @@ gint scan_mailto_url(const gchar *mailto, gchar **from, gchar **to, gchar **cc,
                        } else {
                                gchar *tmp = decode_uri_gdup(value);
                                gchar *new_bcc = g_strdup_printf("%s, %s", *bcc, tmp);
+                               g_free(tmp);
                                g_free(*bcc);
                                *bcc = new_bcc;
                        }
@@ -1489,19 +1486,42 @@ gint scan_mailto_url(const gchar *mailto, gchar **from, gchar **to, gchar **cc,
                } else if (body && !*body && !g_ascii_strcasecmp(field, "body")) {
                        *body = decode_uri_gdup(value);
                } else if (body && !*body && !g_ascii_strcasecmp(field, "insert")) {
+                       int i = 0;
                        gchar *tmp = decode_uri_gdup(value);
-                       if (!g_file_get_contents(tmp, body, NULL, NULL)) {
-                               g_warning("couldn't set insert file '%s' in body", value);
+
+                       for (; forbidden_uris[i]; i++) {
+                               if (strstr(tmp, forbidden_uris[i])) {
+                                       g_print("Refusing to insert '%s', potential private data leak\n",
+                                                       tmp);
+                                       g_free(tmp);
+                                       tmp = NULL;
+                                       break;
+                               }
+                       }
+
+                       if (tmp) {
+                               if (!is_file_entry_regular(tmp)) {
+                                       g_warning("Refusing to insert '%s', not a regular file\n", tmp);
+                               } else if (!g_file_get_contents(tmp, body, NULL, NULL)) {
+                                       g_warning("couldn't set insert file '%s' in body", value);
+                               }
+
+                               g_free(tmp);
                        }
-                       g_free(tmp);
                } else if (attach && !g_ascii_strcasecmp(field, "attach")) {
                        int i = 0;
                        gchar *tmp = decode_uri_gdup(value);
+                       gchar **my_att = g_malloc(sizeof(char *));
+
+                       my_att[0] = NULL;
+
                        for (; forbidden_uris[i]; i++) {
                                if (strstr(tmp, forbidden_uris[i])) {
                                        g_print("Refusing to attach '%s', potential private data leak\n",
                                                        tmp);
                                        g_free(tmp);
+                                       g_free(my_att);
+                                       tmp = NULL;
                                        break;
                                }
                        }
@@ -1511,6 +1531,7 @@ gint scan_mailto_url(const gchar *mailto, gchar **from, gchar **to, gchar **cc,
                                my_att = g_realloc(my_att, (sizeof(char *))*(num_attach+1));
                                my_att[num_attach-1] = tmp;
                                my_att[num_attach] = NULL;
+                               *attach = my_att;
                        }
                } else if (inreplyto && !*inreplyto &&
                           !g_ascii_strcasecmp(field, "in-reply-to")) {
@@ -1518,8 +1539,6 @@ gint scan_mailto_url(const gchar *mailto, gchar **from, gchar **to, gchar **cc,
                }
        }
 
-       if (attach)
-               *attach = my_att;
        return 0;
 }
 
@@ -1663,7 +1682,7 @@ const gchar *get_locale_dir(void)
 #endif
        if (!loc_dir)
                loc_dir = LOCALEDIR;
-       
+
        return loc_dir;
 }
 
@@ -1726,11 +1745,11 @@ void set_rc_dir(const gchar *dir)
                rc_dir_alt = TRUE;
 
                claws_rc_dir = canonical_dir;
-               
+
                len = strlen(claws_rc_dir);
                if (claws_rc_dir[len - 1] == G_DIR_SEPARATOR)
                        claws_rc_dir[len - 1] = '\0';
-               
+
                debug_print("set rc_dir to %s\n", claws_rc_dir);
                if (!is_dir_exist(claws_rc_dir)) {
                        if (make_dir_hier(claws_rc_dir) != 0) {
@@ -1802,7 +1821,7 @@ const gchar *w32_get_cert_file(void)
                cert_file = g_strconcat(w32_get_module_dir(),
                                 "\\share\\claws-mail\\",
                                "ca-certificates.crt",
-                               NULL);  
+                               NULL);
        return cert_file;
 }
 #endif
@@ -1834,10 +1853,10 @@ const gchar *get_plugin_dir(void)
        else {
                static gchar *plugin_dir = NULL;
                if (!plugin_dir)
-                       plugin_dir = g_strconcat(get_rc_dir(), 
-                               G_DIR_SEPARATOR_S, "plugins", 
+                       plugin_dir = g_strconcat(get_rc_dir(),
+                               G_DIR_SEPARATOR_S, "plugins",
                                G_DIR_SEPARATOR_S, NULL);
-               return plugin_dir;                      
+               return plugin_dir;
        }
 #endif
 }
@@ -1916,6 +1935,29 @@ const gchar *get_domain_name(void)
 #endif
 }
 
+/* Tells whether the given host address string is a valid representation of a
+ * numerical IP (v4 or, if supported, v6) address.
+ */
+gboolean is_numeric_host_address(const gchar *hostaddress)
+{
+       struct addrinfo hints, *res;
+       int err;
+
+       /* See what getaddrinfo makes of the string when told that it is a
+        * numeric IP address representation. */
+       memset(&hints, 0, sizeof(struct addrinfo));
+       hints.ai_family = AF_UNSPEC;
+       hints.ai_socktype = 0;
+       hints.ai_flags = AI_NUMERICHOST;
+       hints.ai_protocol = 0;
+
+       err = getaddrinfo(hostaddress, NULL, &hints, &res);
+       if (err == 0)
+               freeaddrinfo(res);
+
+       return (err == 0);
+}
+
 off_t get_file_size(const gchar *file)
 {
 #ifdef G_OS_WIN32
@@ -2020,6 +2062,14 @@ gboolean is_file_entry_exist(const gchar *file)
        return g_file_test(file, G_FILE_TEST_EXISTS);
 }
 
+gboolean is_file_entry_regular(const gchar *file)
+{
+       if (file == NULL)
+               return FALSE;
+
+       return g_file_test(file, G_FILE_TEST_IS_REGULAR);
+}
+
 gboolean dirent_is_regular_file(struct dirent *d)
 {
 #if !defined(G_OS_WIN32) && defined(HAVE_DIRENT_D_TYPE)
@@ -2504,7 +2554,7 @@ char *fgets_crlf(char *buf, int size, FILE *stream)
 
        *cs = '\0';
 
-       return buf;     
+       return buf;
 }
 
 static gint execute_async(gchar *const argv[], const gchar *working_directory)
@@ -2756,7 +2806,7 @@ time_t tzoffset_sec(time_t *now)
 #ifdef G_OS_WIN32
        if (now && *now < 0)
                return 0;
-#endif 
+#endif
        gmt = *gmtime_r(now, &buf1);
        lt = localtime_r(now, &buf2);
 
@@ -3464,12 +3514,11 @@ static GHashTable *create_domain_tab(void)
 
 static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gchar *last)
 {
-       const gint MAX_LVL_DOM_NAME_LEN = 6;
-       gchar buf[MAX_LVL_DOM_NAME_LEN + 1];
-       const gchar *m = buf + MAX_LVL_DOM_NAME_LEN + 1;
+       gchar buf[BUFFSIZE + 1];
+       const gchar *m = buf + BUFFSIZE + 1;
        register gchar *p;
 
-       if (last - first > MAX_LVL_DOM_NAME_LEN || first > last)
+       if (last - first > BUFFSIZE || first > last)
                return FALSE;
 
        for (p = buf; p < m &&  first < last; *p++ = *first++)
@@ -3519,7 +3568,7 @@ search_again:
                        start++;
 
                *bp = start;
-               
+
                /* check if there are quotes (to skip , in them) */
                if (*start == '"') {
                        start_quote = start;
@@ -3529,12 +3578,12 @@ search_again:
                        start_quote = NULL;
                        end_quote = NULL;
                }
-               
+
                /* skip anything between quotes */
                if (start_quote && end_quote) {
                        start = end_quote;
-                       
-               } 
+
+               }
 
                /* find end (either , or ; or end of line) */
                if (strstr(start, ",") && strstr(start, ";"))
@@ -3789,13 +3838,13 @@ static gchar *mailcap_get_command_in_file(const gchar *path, const gchar *type,
                                        testcmd[strlen(testcmd)-1] = '\0';
                                while (testcmd[strlen(testcmd)-1] == ' ' || testcmd[strlen(testcmd)-1] == '\t')
                                        testcmd[strlen(testcmd)-1] = '\0';
-                                       
+
                                if (strstr(testcmd, "%s")) {
                                        gchar *tmp = g_strdup_printf(testcmd, file_to_open);
                                        gint res = system(tmp);
                                        g_free(tmp);
                                        g_free(orig_testcmd);
-                                       
+
                                        if (res != 0) {
                                                g_strfreev(parts);
                                                continue;
@@ -3803,14 +3852,14 @@ static gchar *mailcap_get_command_in_file(const gchar *path, const gchar *type,
                                } else {
                                        gint res = system(testcmd);
                                        g_free(orig_testcmd);
-                                       
+
                                        if (res != 0) {
                                                g_strfreev(parts);
                                                continue;
                                        }
                                }
                        }
-                       
+
                        trimmed = parts[1];
                        while (trimmed[0] == ' ' || trimmed[0] == '\t')
                                trimmed++;
@@ -3913,7 +3962,7 @@ void mailcap_update_default(const gchar *type, const gchar *command)
 
        if (claws_safe_fclose(outfp) == EOF)
                err = TRUE;
-               
+
        if (!err)
                g_rename(outpath, path);
 
@@ -3971,10 +4020,10 @@ gboolean sc_g_slist_bigger(GSList *list, gint max)
 }
 
 const gchar *daynames[] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL};
-const gchar *monthnames[] = {NULL, NULL, NULL, NULL, NULL, NULL, 
+const gchar *monthnames[] = {NULL, NULL, NULL, NULL, NULL, NULL,
                             NULL, NULL, NULL, NULL, NULL, NULL};
 const gchar *s_daynames[] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL};
-const gchar *s_monthnames[] = {NULL, NULL, NULL, NULL, NULL, NULL, 
+const gchar *s_monthnames[] = {NULL, NULL, NULL, NULL, NULL, NULL,
                             NULL, NULL, NULL, NULL, NULL, NULL};
 
 gint daynames_len[] =     {0,0,0,0,0,0,0};
@@ -4027,7 +4076,7 @@ static void init_time_names(void)
        s_daynames[4] = C_("Abbr. day name for use by strftime", "Thu");
        s_daynames[5] = C_("Abbr. day name for use by strftime", "Fri");
        s_daynames[6] = C_("Abbr. day name for use by strftime", "Sat");
-       
+
        s_monthnames[0] = C_("Abbr. month name for use by strftime", "Jan");
        s_monthnames[1] = C_("Abbr. month name for use by strftime", "Feb");
        s_monthnames[2] = C_("Abbr. month name for use by strftime", "Mar");
@@ -4054,7 +4103,7 @@ static void init_time_names(void)
        s_pm_up = C_("For use by strftime (afternoon)", "PM");
        s_am_low = C_("For use by strftime (morning, lowercase)", "am");
        s_pm_low = C_("For use by strftime (afternoon, lowercase)", "pm");
-       
+
        s_am_up_len = strlen(s_am_up);
        s_pm_up_len = strlen(s_pm_up);
        s_am_low_len = strlen(s_am_low);
@@ -4077,13 +4126,13 @@ size_t fast_strftime(gchar *buf, gint buflen, const gchar *format, struct tm *lt
        gint total_done = 0;
        gchar subbuf[64], subfmt[64];
        static time_t last_tzset = (time_t)0;
-       
+
        if (!time_names_init_done)
                init_time_names();
-       
+
        if (format == NULL || lt == NULL)
                return 0;
-               
+
        if (last_tzset != time(NULL)) {
                tzset();
                last_tzset = time(NULL);
@@ -4148,7 +4197,7 @@ size_t fast_strftime(gchar *buf, gint buflen, const gchar *format, struct tm *lt
                                break;
                        case 'F':
                                len = 10; CHECK_SIZE();
-                               snprintf(curpos, buflen - total_done, "%4d-%02d-%02d", 
+                               snprintf(curpos, buflen - total_done, "%4d-%02d-%02d",
                                        lt->tm_year + 1900, lt->tm_mon +1, lt->tm_mday);
                                break;
                        case 'H':
@@ -4217,7 +4266,11 @@ size_t fast_strftime(gchar *buf, gint buflen, const gchar *format, struct tm *lt
                                }
                                break;
                        case 'r':
+#ifdef G_OS_WIN32
+                               strftime(subbuf, 64, "%I:%M:%S %p", lt);
+#else
                                strftime(subbuf, 64, "%r", lt);
+#endif
                                len = strlen(subbuf); CHECK_SIZE();
                                strncpy2(curpos, subbuf, buflen - total_done);
                                break;
@@ -4314,7 +4367,7 @@ size_t fast_strftime(gchar *buf, gint buflen, const gchar *format, struct tm *lt
                        format++;
                } else {
                        int len = 1; CHECK_SIZE();
-                       *curpos++ = *format++; 
+                       *curpos++ = *format++;
                }
        }
        *curpos = '\0';
@@ -4415,7 +4468,7 @@ static GSList *cm_split_path(const gchar *filename, int depth)
                                return NULL;
                        }
                        else /* Remove the last inserted element */
-                               canonical_parts = 
+                               canonical_parts =
                                        g_slist_delete_link(canonical_parts,
                                                            canonical_parts);
                } else {
@@ -4448,7 +4501,7 @@ static GSList *cm_split_path(const gchar *filename, int depth)
 
                                if (!g_path_is_absolute(target)) {
                                        /* remove the last inserted element */
-                                       canonical_parts = 
+                                       canonical_parts =
                                                g_slist_delete_link(canonical_parts,
                                                            canonical_parts);
                                        /* add the target */
@@ -4502,7 +4555,7 @@ int cm_canonicalize_filename(const gchar *filename, gchar **canonical_name) {
                gchar *cur = g_get_current_dir();
                gchar *absolute_filename = g_strconcat(cur, G_DIR_SEPARATOR_S,
                                                       filename, NULL);
-               
+
                canonical_parts = cm_split_path(absolute_filename, 0);
                g_free(absolute_filename);
                g_free(cur);
@@ -4526,7 +4579,7 @@ guchar *g_base64_decode_zero(const gchar *text, gsize *out_len)
        g_free(tmp);
 
        if (strlen(out) != *out_len) {
-               g_warning ("strlen(out) %zd != *out_len %" G_GSIZE_FORMAT, strlen(out), *out_len);
+               g_warning ("strlen(out) %"G_GSIZE_FORMAT" != *out_len %"G_GSIZE_FORMAT, strlen(out), *out_len);
        }
 
        return out;
@@ -4596,7 +4649,7 @@ get_random_bytes(void *buf, size_t count)
        /* Read data from the source into buf. */
 #if defined G_OS_WIN32
        if (!CryptGenRandom(rnd, count, buf)) {
-               debug_print("Could not read %zd random bytes.\n", count);
+               debug_print("Could not read %"G_GSIZE_FORMAT" random bytes.\n", count);
                CryptReleaseContext(rnd, 0);
                return FALSE;
        }
@@ -4619,3 +4672,77 @@ get_random_bytes(void *buf, size_t count)
 
        return TRUE;
 }
+
+/* returns FALSE if parsing failed, otherwise returns TRUE and sets *server, *port
+   and eventually *fp from filename (if not NULL, they must be free'd by caller after
+   user.
+   filenames we expect: 'host.name.port.cert' or 'host.name.port.f:i:n:g:e:r:p:r:i:n:t.cert' */
+gboolean get_serverportfp_from_filename(const gchar *str, gchar **server, gchar **port, gchar **fp)
+{
+       const gchar *pos, *dotport_pos = NULL, *dotcert_pos = NULL, *dotfp_pos = NULL;
+
+       g_return_val_if_fail(str != NULL, FALSE);
+
+       pos = str + strlen(str) - 1;
+       while ((pos > str) && !dotport_pos) {
+               if (*pos == '.') {
+                       if (!dotcert_pos) {
+                               /* match the .cert suffix */
+                               if (strcmp(pos, ".cert") == 0) {
+                                       dotcert_pos = pos;
+                               }
+                       } else {
+                               if (!dotfp_pos) {
+                                       /* match an eventual fingerprint */
+                                       /* or the port number */
+                                       if (strncmp(pos + 3, ":", 1) == 0) {
+                                               dotfp_pos = pos;
+                                       } else {
+                                               dotport_pos = pos;
+                                       }
+                               } else {
+                                       /* match the port number */
+                                       dotport_pos = pos;
+                               }
+                       }
+               }
+               pos--;
+       }
+       if (!dotport_pos || !dotcert_pos) {
+               g_warning("could not parse filename %s", str);
+               return FALSE;
+       }
+
+       if (server != NULL)
+               *server = g_strndup(str, dotport_pos - str);
+       if (dotfp_pos) {
+               if (port != NULL)
+                       *port = g_strndup(dotport_pos + 1, dotfp_pos - dotport_pos - 1);
+               if (fp != NULL)
+                       *fp = g_strndup(dotfp_pos + 1, dotcert_pos - dotfp_pos - 1);
+       } else {
+               if (port != NULL)
+                       *port = g_strndup(dotport_pos + 1, dotcert_pos - dotport_pos - 1);
+               if (fp != NULL)
+                       *fp = NULL;
+       }
+
+       debug_print("filename='%s' => server='%s' port='%s' fp='%s'\n",
+                       str,
+                       (server ? *server : "(n/a)"),
+                       (port ? *port : "(n/a)"),
+                       (fp ? *fp : "(n/a)"));
+
+       if (!(server && *server) || !(port && *port))
+               return FALSE;
+       else
+               return TRUE;
+}
+
+#ifdef G_OS_WIN32
+gchar *win32_debug_log_path(void)
+{
+       return g_strconcat(g_get_tmp_dir(), G_DIR_SEPARATOR_S,
+                       "claws-win32.log", NULL);
+}
+#endif