Revert "Disallow '?' in filename when saving message part on Windows."
[claws.git] / src / common / utils.c
index 5dba2a4a4b7280bb6ef6862aa30e8e4d21dd703c..7edaf236c421d39b1840000583228cb6896a4a6f 100644 (file)
@@ -1,6 +1,6 @@
 /*
  * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
- * Copyright (C) 1999-2015 Hiroyuki Yamamoto & The Claws Mail Team
+ * Copyright (C) 1999-2016 Hiroyuki Yamamoto & The Claws Mail Team
  *
  * 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
@@ -49,7 +49,9 @@
 #include <ctype.h>
 #include <errno.h>
 #include <sys/param.h>
+#ifndef G_OS_WIN32
 #include <sys/socket.h>
+#endif
 
 #if (HAVE_WCTYPE_H && HAVE_WCHAR_H)
 #  include <wchar.h>
@@ -87,9 +89,6 @@
 #define BUFFSIZE       8192
 
 static gboolean debug_mode = FALSE;
-#ifdef G_OS_WIN32
-static GSList *tempfiles=NULL;
-#endif
 
 #if !GLIB_CHECK_VERSION(2, 26, 0)
 guchar *g_base64_decode_wa(const gchar *text, gsize *out_len)
@@ -994,19 +993,37 @@ void remove_space(gchar *str)
 
 void unfold_line(gchar *str)
 {
-       register gchar *p = str;
-       register gint spc;
+       register gchar *ch;
+       register gunichar c;
+       register gint len;
 
-       while (*p) {
-               if (*p == '\n' || *p == '\r') {
-                       *p++ = ' ';
-                       spc = 0;
-                       while (g_ascii_isspace(*(p + spc)))
-                               spc++;
-                       if (spc)
-                               memmove(p, p + spc, strlen(p + spc) + 1);
-               } else
-                       p++;
+       ch = str; /* iterator for source string */
+
+       while (*ch != 0) {
+               c = g_utf8_get_char_validated(ch, -1);
+
+               if (c == (gunichar)-1 || c == (gunichar)-2) {
+                       /* non-unicode byte, move past it */
+                       ch++;
+                       continue;
+               }
+
+               len = g_unichar_to_utf8(c, NULL);
+
+               if (!g_unichar_isdefined(c) || !g_unichar_isprint(c) ||
+                               g_unichar_isspace(c)) {
+                       /* replace anything bad or whitespacey with a single space */
+                       *ch = ' ';
+                       ch++;
+                       if (len > 1) {
+                               /* move rest of the string forwards, since we just replaced
+                                * a multi-byte sequence with one byte */
+                               memmove(ch, ch + len-1, strlen(ch + len-1) + 1);
+                       }
+               } else {
+                       /* A valid unicode character, copy it. */
+                       ch += len;
+               }
        }
 }
 
@@ -1072,13 +1089,13 @@ static const gchar * line_has_quote_char_last(const gchar * str, const gchar *qu
        gchar * tmp_pos = NULL;
        int i;
 
-       if (quote_chars == NULL)
+       if (str == NULL || quote_chars == NULL)
                return NULL;
 
        for (i = 0; i < strlen(quote_chars); i++) {
-               tmp_pos = strrchr (str, quote_chars[i]);
+               tmp_pos = strrchr (str, quote_chars[i]);
                if(position == NULL
-                  || (tmp_pos != NULL && position <= tmp_pos) )
+                               || (tmp_pos != NULL && position <= tmp_pos) )
                        position = tmp_pos;
        }
        return position;
@@ -1161,13 +1178,13 @@ const gchar * line_has_quote_char(const gchar * str, const gchar *quote_chars)
        gchar * tmp_pos = NULL;
        int i;
 
-       if (quote_chars == NULL)
-               return FALSE;
+       if (str == NULL || quote_chars == NULL)
+               return NULL;
 
        for (i = 0; i < strlen(quote_chars); i++) {
-               tmp_pos = strchr (str,  quote_chars[i]);
+               tmp_pos = strchr (str, quote_chars[i]);
                if(position == NULL
-                  || (tmp_pos != NULL && position >= tmp_pos) )
+                               || (tmp_pos != NULL && position >= tmp_pos) )
                        position = tmp_pos;
        }
        return position;
@@ -1357,7 +1374,7 @@ GList *uri_list_extract_filenames(const gchar *uri_list)
                                        *file = '\0';
                                        strncpy(escaped_utf8uri, p, q - p + 1);
                                        escaped_utf8uri[q - p + 1] = '\0';
-                                       decode_uri(file, escaped_utf8uri);
+                                       decode_uri_with_plus(file, escaped_utf8uri, FALSE);
                    /*
                     * g_filename_from_uri() rejects escaped/locale encoded uri
                     * string which come from Nautilus.
@@ -1587,7 +1604,6 @@ gint scan_mailto_url(const gchar *mailto, gchar **from, gchar **to, gchar **cc,
                                g_warning("couldn't set insert file '%s' in body", value);
                        }
                        g_free(tmp);
-                       tmp = NULL;
                } else if (attach && !g_ascii_strcasecmp(field, "attach")) {
                        int i = 0;
                        gchar *tmp = decode_uri_gdup(value);
@@ -1596,7 +1612,6 @@ gint scan_mailto_url(const gchar *mailto, gchar **from, gchar **to, gchar **cc,
                                        g_print("Refusing to attach '%s', potential private data leak\n",
                                                        tmp);
                                        g_free(tmp);
-                                       tmp = NULL;
                                        break;
                                }
                        }
@@ -1772,8 +1787,8 @@ const gchar *get_home_dir(void)
                if (w32_shgetfolderpath
                            (NULL, CSIDL_APPDATA|CSIDL_FLAG_CREATE,
                             NULL, 0, home_dir_utf16) < 0)
-                               strcpy (home_dir_utf16, "C:\\Sylpheed");
-               home_dir_utf8 = g_utf16_to_utf8 ((const gunichar *)home_dir_utf16, -1, NULL, NULL, NULL);
+                               strcpy (home_dir_utf16, "C:\\Claws Mail");
+               home_dir_utf8 = g_utf16_to_utf8 ((const gunichar2 *)home_dir_utf16, -1, NULL, NULL, NULL);
        }
        return home_dir_utf8;
 #else
@@ -1890,7 +1905,7 @@ const gchar *get_template_dir(void)
 }
 
 #ifdef G_OS_WIN32
-const gchar *get_cert_file(void)
+const gchar *w32_get_cert_file(void)
 {
        const gchar *cert_file = NULL;
        if (!cert_file)
@@ -1940,7 +1955,7 @@ const gchar *get_plugin_dir(void)
 
 #ifdef G_OS_WIN32
 /* Return the default directory for Themes. */
-const gchar *get_themes_dir(void)
+const gchar *w32_get_themes_dir(void)
 {
        static gchar *themes_dir = NULL;
 
@@ -2035,32 +2050,6 @@ time_t get_file_mtime(const gchar *file)
        return s.st_mtime;
 }
 
-off_t get_file_size_as_crlf(const gchar *file)
-{
-       FILE *fp;
-       off_t size = 0;
-       gchar buf[BUFFSIZE];
-
-       if ((fp = g_fopen(file, "rb")) == NULL) {
-               FILE_OP_ERROR(file, "g_fopen");
-               return -1;
-       }
-
-       while (fgets(buf, sizeof(buf), fp) != NULL) {
-               strretchomp(buf);
-               size += strlen(buf) + 2;
-       }
-
-       if (ferror(fp)) {
-               FILE_OP_ERROR(file, "fgets");
-               size = -1;
-       }
-
-       fclose(fp);
-
-       return size;
-}
-
 gboolean file_exist(const gchar *file, gboolean allow_fifo)
 {
        GStatBuf s;
@@ -3226,11 +3215,11 @@ char *fgets_crlf(char *buf, int size, FILE *stream)
        return buf;     
 }
 
-static gint execute_async(gchar *const argv[])
+static gint execute_async(gchar *const argv[], const gchar *working_directory)
 {
        cm_return_val_if_fail(argv != NULL && argv[0] != NULL, -1);
 
-       if (g_spawn_async(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH,
+       if (g_spawn_async(working_directory, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH,
                          NULL, NULL, NULL, FALSE) == FALSE) {
                g_warning("couldn't execute command: %s", argv[0]);
                return -1;
@@ -3239,14 +3228,14 @@ static gint execute_async(gchar *const argv[])
        return 0;
 }
 
-static gint execute_sync(gchar *const argv[])
+static gint execute_sync(gchar *const argv[], const gchar *working_directory)
 {
        gint status;
 
        cm_return_val_if_fail(argv != NULL && argv[0] != NULL, -1);
 
 #ifdef G_OS_UNIX
-       if (g_spawn_sync(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH,
+       if (g_spawn_sync(working_directory, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH,
                         NULL, NULL, NULL, NULL, &status, NULL) == FALSE) {
                g_warning("couldn't execute command: %s", argv[0]);
                return -1;
@@ -3257,8 +3246,10 @@ static gint execute_sync(gchar *const argv[])
        else
                return -1;
 #else
-       if (g_spawn_sync(NULL, (gchar **)argv, NULL, G_SPAWN_SEARCH_PATH| 
-                        G_SPAWN_CHILD_INHERITS_STDIN|G_SPAWN_LEAVE_DESCRIPTORS_OPEN,
+       if (g_spawn_sync(working_directory, (gchar **)argv, NULL,
+                               G_SPAWN_SEARCH_PATH|
+                               G_SPAWN_CHILD_INHERITS_STDIN|
+                               G_SPAWN_LEAVE_DESCRIPTORS_OPEN,
                         NULL, NULL, NULL, NULL, &status, NULL) == FALSE) {
                g_warning("couldn't execute command: %s", argv[0]);
                return -1;
@@ -3268,7 +3259,8 @@ static gint execute_sync(gchar *const argv[])
 #endif
 }
 
-gint execute_command_line(const gchar *cmdline, gboolean async)
+gint execute_command_line(const gchar *cmdline, gboolean async,
+               const gchar *working_directory)
 {
        gchar **argv;
        gint ret;
@@ -3278,9 +3270,9 @@ gint execute_command_line(const gchar *cmdline, gboolean async)
        argv = strsplit_with_quote(cmdline, " ", 0);
 
        if (async)
-               ret = execute_async(argv);
+               ret = execute_async(argv, working_directory);
        else
-               ret = execute_sync(argv);
+               ret = execute_sync(argv, working_directory);
 
        g_strfreev(argv);
 
@@ -3365,7 +3357,7 @@ gint open_uri(const gchar *uri, const gchar *cmdline)
                g_snprintf(buf, sizeof(buf), DEFAULT_BROWSER_CMD, encoded_uri);
        }
 
-       execute_command_line(buf, TRUE);
+       execute_command_line(buf, TRUE, NULL);
 #else
        ShellExecute(NULL, "open", uri, NULL, NULL, SW_SHOW);
 #endif
@@ -3391,7 +3383,7 @@ gint open_txt_editor(const gchar *filepath, const gchar *cmdline)
                g_snprintf(buf, sizeof(buf), DEFAULT_EDITOR_CMD, filepath);
        }
 
-       execute_command_line(buf, TRUE);
+       execute_command_line(buf, TRUE, NULL);
 
        return 0;
 }
@@ -3534,14 +3526,14 @@ gchar *tzoffset(time_t *now)
        return offset_string;
 }
 
-void get_rfc822_date(gchar *buf, gint len)
+static void _get_rfc822_date(gchar *buf, gint len, gboolean hidetz)
 {
        struct tm *lt;
        time_t t;
        gchar day[4], mon[4];
        gint dd, hh, mm, ss, yyyy;
        struct tm buf1;
-       gchar buf2[BUFFSIZE];
+       gchar buf2[RFC822_DATE_BUFFSIZE];
 
        t = time(NULL);
        lt = localtime_r(&t, &buf1);
@@ -3550,7 +3542,17 @@ void get_rfc822_date(gchar *buf, gint len)
               day, mon, &dd, &hh, &mm, &ss, &yyyy);
 
        g_snprintf(buf, len, "%s, %d %s %d %02d:%02d:%02d %s",
-                  day, dd, mon, yyyy, hh, mm, ss, tzoffset(&t));
+                  day, dd, mon, yyyy, hh, mm, ss, (hidetz? "-0000": tzoffset(&t)));
+}
+
+void get_rfc822_date(gchar *buf, gint len)
+{
+       _get_rfc822_date(buf, len, FALSE);
+}
+
+void get_rfc822_date_hide_tz(gchar *buf, gint len)
+{
+       _get_rfc822_date(buf, len, TRUE);
 }
 
 void debug_set_mode(gboolean mode)
@@ -3724,39 +3726,6 @@ gint g_int_compare(gconstpointer a, gconstpointer b)
        return GPOINTER_TO_INT(a) - GPOINTER_TO_INT(b);
 }
 
-gchar *generate_msgid(gchar *buf, gint len, gchar *user_addr)
-{
-       struct tm *lt;
-       time_t t;
-       gchar *addr;
-       struct tm buft;
-
-       t = time(NULL);
-       lt = localtime_r(&t, &buft);
-
-       if (user_addr != NULL)
-             addr = g_strdup_printf(".%s", user_addr);
-       else if (strlen(buf) != 0)
-             addr = g_strdup_printf("@%s", buf);
-       else
-             addr = g_strdup_printf("@%s", get_domain_name());
-
-       /* Replace all @ but the last one in addr, with underscores.
-        * RFC 2822 States that msg-id syntax only allows one @.
-        */
-       while (strchr(addr, '@') != NULL && strchr(addr, '@') != strrchr(addr, '@'))
-               *(strchr(addr, '@')) = '_';
-
-       g_snprintf(buf, len, "%04d%02d%02d%02d%02d%02d.%08x%s",
-                  lt->tm_year + 1900, lt->tm_mon + 1,
-                  lt->tm_mday, lt->tm_hour,
-                  lt->tm_min, lt->tm_sec,
-                  (guint) rand(), addr);
-
-       g_free(addr);
-       return buf;
-}
-
 /*
    quote_cmd_argument()
 
@@ -4116,7 +4085,7 @@ void replace_returns(gchar *str)
 }
 
 /* get_uri_part() - retrieves a URI starting from scanpos.
-                   Returns TRUE if succesful */
+                   Returns TRUE if successful */
 gboolean get_uri_part(const gchar *start, const gchar *scanpos,
                             const gchar **bp, const gchar **ep, gboolean hdr)
 {
@@ -4152,7 +4121,7 @@ gboolean get_uri_part(const gchar *start, const gchar *scanpos,
         * should pass some URI type to this function and decide on that whether
         * to perform punctuation stripping */
 
-#define IS_REAL_PUNCT(ch)      (g_ascii_ispunct(ch) && !strchr("/?=-_)", ch))
+#define IS_REAL_PUNCT(ch)      (g_ascii_ispunct(ch) && !strchr("/?=-_~)", ch))
 
        for (; ep_ - 1 > scanpos + 1 &&
               IS_REAL_PUNCT(*(ep_ - 1));
@@ -4213,7 +4182,7 @@ static gboolean is_toplvl_domain(GHashTable *tab, const gchar *first, const gcha
        return g_hash_table_lookup(tab, buf) != NULL;
 }
 
-/* get_email_part() - retrieves an email address. Returns TRUE if succesful */
+/* get_email_part() - retrieves an email address. Returns TRUE if successful */
 gboolean get_email_part(const gchar *start, const gchar *scanpos,
                               const gchar **bp, const gchar **ep, gboolean hdr)
 {
@@ -4364,7 +4333,7 @@ search_again:
                ep_ += 3;
 
                /* go to matching '>' (or next non-rfc822 char, like \n) */
-               for (; *ep_ != '>' && *ep != '\0' && IS_RFC822_CHAR(*ep_); ep_++)
+               for (; *ep_ != '>' && *ep_ != '\0' && IS_RFC822_CHAR(*ep_); ep_++)
                        ;
 
                /* include the bracket */
@@ -4557,22 +4526,6 @@ static gchar *mailcap_get_command_in_file(const gchar *path, const gchar *type,
                        result = g_strdup(trimmed);
                        g_strfreev(parts);
                        fclose(fp);
-                       /* if there are no single quotes around %s, add them.
-                        * '.*%s.*' is ok, as in display 'png:%s'
-                        */
-                       if (strstr(result, "%s") 
-                       && !(strstr(result, "'") < strstr(result,"%s") &&
-                            strstr(strstr(result,"%s"), "'"))) {
-                               gchar *start = g_strdup(result);
-                               gchar *end = g_strdup(strstr(result, "%s")+2);
-                               gchar *tmp;
-                               *strstr(start, "%s") = '\0';
-                               tmp = g_strconcat(start,"'%s'",end, NULL);
-                               g_free(start);
-                               g_free(end);
-                               g_free(result);
-                               result = tmp;
-                       }
                        if (needsterminal) {
                                gchar *tmp = g_strdup_printf("xterm -e %s", result);
                                g_free(result);
@@ -4737,7 +4690,7 @@ gboolean file_is_email (const gchar *filename)
        if ((fp = g_fopen(filename, "rb")) == NULL)
                return FALSE;
        while (i < 60 && score < 3
-              && fgets(buffer, sizeof (buffer), fp) > 0) {
+              && fgets(buffer, sizeof (buffer), fp) != NULL) {
                if (!strncmp(buffer, "From:", strlen("From:")))
                        score++;
                else if (!strncmp(buffer, "Date:", strlen("Date:")))
@@ -5227,7 +5180,9 @@ static GSList *cm_split_path(const gchar *filename, int depth)
        GSList *canonical_parts = NULL;
        GStatBuf st;
        int i;
+#ifndef G_OS_WIN32
        gboolean follow_symlinks = TRUE;
+#endif
 
        if (depth > 32) {
 #ifndef G_OS_WIN32
@@ -5270,7 +5225,9 @@ static GSList *cm_split_path(const gchar *filename, int depth)
                        if(g_stat(tmp_path, &st) < 0) {
                                if (errno == ENOENT) {
                                        errno = 0;
+#ifndef G_OS_WIN32
                                        follow_symlinks = FALSE;
+#endif
                                }
                                if (errno != 0) {
                                        g_free(tmp_path);
@@ -5404,3 +5361,57 @@ g_utf8_substring (const gchar *str,
   return out;
 }
 #endif
+
+/* Attempts to read count bytes from a PRNG into memory area starting at buf.
+ * It is up to the caller to make sure there is at least count bytes
+ * available at buf. */
+gboolean
+get_random_bytes(void *buf, size_t count)
+{
+       /* Open our prng source. */
+#if defined G_OS_WIN32
+       HCRYPTPROV rnd;
+
+       if (!CryptAcquireContext(&rnd, NULL, NULL, PROV_RSA_FULL, 0) &&
+                       !CryptAcquireContext(&rnd, NULL, NULL, PROV_RSA_FULL, CRYPT_NEWKEYSET)) {
+               debug_print("Could not acquire a CSP handle.\n");
+               return FALSE;
+       }
+#else
+       int rnd;
+       ssize_t ret;
+
+       rnd = open("/dev/urandom", O_RDONLY);
+       if (rnd == -1) {
+               FILE_OP_ERROR("/dev/urandom", "open");
+               debug_print("Could not open /dev/urandom.\n");
+               return FALSE;
+       }
+#endif
+
+       /* 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);
+               CryptReleaseContext(rnd, 0);
+               return FALSE;
+       }
+#else
+       ret = read(rnd, buf, count);
+       if (ret != count) {
+               FILE_OP_ERROR("/dev/urandom", "read");
+               debug_print("Could not read enough data from /dev/urandom, read only %ld of %lu bytes.\n", ret, count);
+               close(rnd);
+               return FALSE;
+       }
+#endif
+
+       /* Close the prng source. */
+#if defined G_OS_WIN32
+       CryptReleaseContext(rnd, 0);
+#else
+       close(rnd);
+#endif
+
+       return TRUE;
+}