update copyright year
[claws.git] / src / common / utils.c
index 52a0eb3f561658af51f9eb7f535d50582259f7ab..a9aa2cdfa00f90ca85d25555e28b49b8c12cb430 100644 (file)
@@ -1,6 +1,6 @@
 /*
- * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
- * Copyright (C) 1999-2020 The Claws Mail Team and Hiroyuki Yamamoto
+ * Claws Mail -- a GTK based, lightweight, and fast e-mail client
+ * Copyright (C) 1999-2024 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
@@ -67,7 +67,6 @@
 #if HAVE_SYS_WAIT_H
 #  include <sys/wait.h>
 #endif
-#include <dirent.h>
 #include <time.h>
 #include <regex.h>
 
@@ -80,7 +79,6 @@
 #ifdef G_OS_WIN32
 #  include <direct.h>
 #  include <io.h>
-#  include <w32lib.h>
 #endif
 
 #include "utils.h"
 
 static gboolean debug_mode = FALSE;
 
-GSList *slist_copy_deep(GSList *list, GCopyFunc func)
-{
-#if GLIB_CHECK_VERSION(2, 34, 0)
-       return g_slist_copy_deep(list, func, NULL);
-#else
-       GSList *res = g_slist_copy(list);
-       GSList *walk = res;
-       while (walk) {
-               walk->data = func(walk->data, NULL);
-               walk = walk->next;
-       }
-       return res;
-#endif
-}
-
 void list_free_strings_full(GList *list)
 {
        g_list_free_full(list, (GDestroyNotify)g_free);
@@ -198,9 +181,9 @@ gchar *to_human_readable(goffset size)
        register int t = 0, r = 0;
        if (b_format == NULL) {
                b_format  = _("%dB");
-               kb_format = _("%d.%02dKB");
-               mb_format = _("%d.%02dMB");
-               gb_format = _("%.2fGB");
+               kb_format = _("%d.%02dKiB");
+               mb_format = _("%d.%02dMiB");
+               gb_format = _("%.2fGiB");
        }
 
        if (size < (goffset)1024) {
@@ -300,6 +283,21 @@ gchar *strcrchomp(gchar *str)
        return str;
 }
 
+/* truncates string at first CR (carriage return) or LF (line feed) */
+gchar *strcrlftrunc(gchar *str)
+{
+       gchar *p = NULL;
+
+       if ((str == NULL) || (!*str)) return str;
+
+       if ((p = strstr(str, "\r")) != NULL)
+               *p = '\0';
+       if ((p = strstr(str, "\n")) != NULL)
+               *p = '\0';
+
+       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)
@@ -452,6 +450,14 @@ gint subject_compare_for_sort(const gchar *s1, const gchar *s2)
        trim_subject_for_sort(str1);
        trim_subject_for_sort(str2);
 
+       if (!g_utf8_validate(str1, -1, NULL)) {
+               g_warning("message subject \"%s\" failed UTF-8 validation", str1);
+               return 0;
+       } else if (!g_utf8_validate(str2, -1, NULL)) {
+               g_warning("message subject \"%s\" failed UTF-8 validation", str2);
+               return 0;
+       }
+
        return g_utf8_collate(str1, str2);
 }
 
@@ -601,6 +607,9 @@ gchar *escape_internal_quotes(gchar *str, gchar quote_chr)
        if (str == NULL || *str == '\0')
                return str;
 
+       g_strstrip(str);
+       if (*str == '\0')
+               return str;
        /* search for unescaped quote_chr */
        p = str;
        if (*p == quote_chr)
@@ -1327,6 +1336,11 @@ gboolean is_uri_string(const gchar *str)
        return (g_ascii_strncasecmp(str, "http://", 7) == 0 ||
                g_ascii_strncasecmp(str, "https://", 8) == 0 ||
                g_ascii_strncasecmp(str, "ftp://", 6) == 0 ||
+               g_ascii_strncasecmp(str, "ftps://", 7) == 0 ||
+               g_ascii_strncasecmp(str, "sftp://", 7) == 0 ||
+               g_ascii_strncasecmp(str, "ftp.", 4) == 0 ||
+               g_ascii_strncasecmp(str, "webcal://", 9) == 0 ||
+               g_ascii_strncasecmp(str, "webcals://", 10) == 0 ||
                g_ascii_strncasecmp(str, "www.", 4) == 0);
 }
 
@@ -1340,6 +1354,14 @@ gchar *get_uri_path(const gchar *uri)
                return (gchar *)(uri + 8);
        else if (g_ascii_strncasecmp(uri, "ftp://", 6) == 0)
                return (gchar *)(uri + 6);
+       else if (g_ascii_strncasecmp(uri, "ftps://", 7) == 0)
+               return (gchar *)(uri + 7);
+       else if (g_ascii_strncasecmp(uri, "sftp://", 7) == 0)
+               return (gchar *)(uri + 7);
+       else if (g_ascii_strncasecmp(uri, "webcal://", 9) == 0)
+               return (gchar *)(uri + 7);
+       else if (g_ascii_strncasecmp(uri, "webcals://", 10) == 0)
+               return (gchar *)(uri + 7);
        else
                return (gchar *)uri;
 }
@@ -1350,7 +1372,7 @@ gint get_uri_len(const gchar *str)
 
        if (is_uri_string(str)) {
                for (p = str; *p != '\0'; p++) {
-                       if (!g_ascii_isgraph(*p) || strchr("()<>\"", *p))
+                       if (strchr("<>\"", *p))
                                break;
                }
                return p - str;
@@ -1501,7 +1523,7 @@ gint scan_mailto_url(const gchar *mailto, gchar **from, gchar **to, gchar **cc,
 
                        if (tmp) {
                                if (!is_file_entry_regular(tmp)) {
-                                       g_warning("Refusing to insert '%s', not a regular file\n", tmp);
+                                       g_warning("refusing to insert '%s', not a regular file", tmp);
                                } else if (!g_file_get_contents(tmp, body, NULL, NULL)) {
                                        g_warning("couldn't set insert file '%s' in body", value);
                                }
@@ -1520,7 +1542,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);
-                                       g_free(my_att);
                                        tmp = NULL;
                                        break;
                                }
@@ -1533,6 +1554,8 @@ gint scan_mailto_url(const gchar *mailto, gchar **from, gchar **to, gchar **cc,
                                my_att[num_attach] = NULL;
                                *attach = my_att;
                        }
+            else
+                               g_free(my_att);
                } else if (inreplyto && !*inreplyto &&
                           !g_ascii_strcasecmp(field, "in-reply-to")) {
                        *inreplyto = decode_uri_gdup(value);
@@ -2070,18 +2093,6 @@ gboolean is_file_entry_regular(const gchar *file)
        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)
-       if (d->d_type == DT_REG)
-               return TRUE;
-       else if (d->d_type != DT_UNKNOWN)
-               return FALSE;
-#endif
-
-       return g_file_test(d->d_name, G_FILE_TEST_IS_REGULAR);
-}
-
 gint change_dir(const gchar *dir)
 {
        gchar *prevdir = NULL;
@@ -2364,6 +2375,7 @@ gint remove_dir_recursive(const gchar *dir)
 
                        if ((ret = remove_dir_recursive(dir_name)) < 0) {
                                g_warning("can't remove directory: %s", dir_name);
+                               g_dir_close(dp);
                                return ret;
                        }
                } else {
@@ -2449,7 +2461,6 @@ gchar *get_outgoing_rfc2822_str(FILE *fp)
 {
        gchar buf[BUFFSIZE];
        GString *str;
-       gchar *ret;
 
        str = g_string_new(NULL);
 
@@ -2487,10 +2498,7 @@ gchar *get_outgoing_rfc2822_str(FILE *fp)
                g_string_append(str, "\r\n");
        }
 
-       ret = str->str;
-       g_string_free(str, FALSE);
-
-       return ret;
+       return g_string_free(str, FALSE);
 }
 
 /*
@@ -2607,7 +2615,9 @@ gint execute_command_line(const gchar *cmdline, gboolean async,
        gchar **argv;
        gint ret;
 
-       debug_print("execute_command_line(): executing: %s\n", cmdline?cmdline:"(null)");
+       cm_return_val_if_fail(cmdline != NULL, -1);
+
+       debug_print("execute_command_line(): executing: %s\n", cmdline);
 
        argv = strsplit_with_quote(cmdline, " ", 0);
 
@@ -2639,6 +2649,39 @@ gchar *get_command_output(const gchar *cmdline)
        return child_stdout;
 }
 
+FILE *get_command_output_stream(const char* cmdline)
+{
+    GPid pid;
+       GError *err = NULL;
+       gchar **argv = NULL;
+    int fd;
+
+       cm_return_val_if_fail(cmdline != NULL, NULL);
+
+       debug_print("get_command_output_stream(): executing: %s\n", cmdline);
+
+       /* turn the command-line string into an array */
+       if (!g_shell_parse_argv(cmdline, NULL, &argv, &err)) {
+               g_warning("could not parse command line from '%s': %s", cmdline, err->message);
+        g_error_free(err);
+               return NULL;
+       }
+
+    if (!g_spawn_async_with_pipes(NULL, argv, NULL, G_SPAWN_SEARCH_PATH,
+                                  NULL, NULL, &pid, NULL, &fd, NULL, &err)
+        && err)
+    {
+        g_warning("could not spawn '%s': %s", cmdline, err->message);
+        g_error_free(err);
+               g_strfreev(argv);
+        return NULL;
+    }
+
+       g_strfreev(argv);
+       return fdopen(fd, "r");
+}
+
+#ifndef G_OS_WIN32
 static gint is_unchanged_uri_char(char c)
 {
        switch (c) {
@@ -2674,6 +2717,7 @@ static void encode_uri(gchar *encoded_uri, gint bufsize, const gchar *uri)
        }
        encoded_uri[k] = 0;
 }
+#endif
 
 gint open_uri(const gchar *uri, const gchar *cmdline)
 {
@@ -2911,6 +2955,24 @@ gboolean debug_get_mode(void)
        return debug_mode;
 }
 
+#ifdef HAVE_VA_OPT
+void debug_print_real(const char *file, int line, const gchar *format, ...)
+{
+       va_list args;
+       gchar buf[BUFFSIZE];
+       gint prefix_len;
+
+       if (!debug_mode) return;
+
+       prefix_len = g_snprintf(buf, sizeof(buf), "%s:%d:", debug_srcname(file), line);
+
+       va_start(args, format);
+       g_vsnprintf(buf + prefix_len, sizeof(buf) - prefix_len, format, args);
+       va_end(args);
+
+       g_print("%s", buf);
+}
+#else
 void debug_print_real(const gchar *format, ...)
 {
        va_list args;
@@ -2924,6 +2986,7 @@ void debug_print_real(const gchar *format, ...)
 
        g_print("%s", buf);
 }
+#endif
 
 
 const char * debug_srcname(const char *file)
@@ -3417,19 +3480,6 @@ void g_auto_pointer_free(GAuto *auto_ptr)
        g_free(ptr);
 }
 
-void replace_returns(gchar *str)
-{
-       if (!str)
-               return;
-
-       while (strstr(str, "\n")) {
-               *strstr(str, "\n") = ' ';
-       }
-       while (strstr(str, "\r")) {
-               *strstr(str, "\r") = ' ';
-       }
-}
-
 /* get_uri_part() - retrieves a URI starting from scanpos.
                    Returns TRUE if successful */
 gboolean get_uri_part(const gchar *start, const gchar *scanpos,
@@ -3468,7 +3518,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));
@@ -3949,6 +3999,7 @@ void mailcap_update_default(const gchar *type, const gchar *command)
                else {
                        if(claws_fputs(buf, outfp) == EOF) {
                                err = TRUE;
+                               g_strfreev(parts);
                                break;
                        }
                }
@@ -3975,13 +4026,12 @@ gboolean file_is_email (const gchar *filename)
 {
        FILE *fp = NULL;
        gchar buffer[2048];
-       gint i = 0;
        gint score = 0;
        if (filename == NULL)
                return FALSE;
        if ((fp = claws_fopen(filename, "rb")) == NULL)
                return FALSE;
-       while (i < 60 && score < 3
+       while (score < 3
               && claws_fgets(buffer, sizeof (buffer), fp) != NULL) {
                if (!strncmp(buffer, "From:", strlen("From:")))
                        score++;
@@ -3991,7 +4041,10 @@ gboolean file_is_email (const gchar *filename)
                        score++;
                else if (!strncmp(buffer, "Subject:", strlen("Subject:")))
                        score++;
-               i++;
+               else if (!strcmp(buffer, "\r\n")) {
+                       debug_print("End of headers\n");
+                       break;
+               }
        }
        claws_fclose(fp);
        return (score >= 3);
@@ -4283,7 +4336,7 @@ size_t fast_strftime(gchar *buf, gint buflen, const gchar *format, struct tm *lt
                                *curpos++ = '0'+(lt->tm_min % 10);
                                break;
                        case 's':
-                               snprintf(subbuf, 64, "%lld", (long long)mktime(lt));
+                               snprintf(subbuf, 64, "%" CM_TIME_FORMAT, mktime(lt));
                                len = strlen(subbuf); CHECK_SIZE();
                                strncpy2(curpos, subbuf, buflen - total_done);
                                break;
@@ -4378,31 +4431,11 @@ size_t fast_strftime(gchar *buf, gint buflen, const gchar *format, struct tm *lt
 #define WEXITSTATUS(x) (x)
 #endif
 
-GMutex *cm_mutex_new(void) {
-#if GLIB_CHECK_VERSION(2,32,0)
-       GMutex *m = g_new0(GMutex, 1);
-       g_mutex_init(m);
-       return m;
-#else
-       return g_mutex_new();
-#endif
-}
-
-void cm_mutex_free(GMutex *mutex) {
-#if GLIB_CHECK_VERSION(2,32,0)
-       g_mutex_clear(mutex);
-       g_free(mutex);
-#else
-       g_mutex_free(mutex);
-#endif
-}
-
 static gchar *canonical_list_to_file(GSList *list)
 {
        GString *result = g_string_new(NULL);
        GSList *pathlist = g_slist_reverse(g_slist_copy(list));
        GSList *cur;
-       gchar *str;
 
 #ifndef G_OS_WIN32
        result = g_string_append(result, G_DIR_SEPARATOR_S);
@@ -4425,10 +4458,7 @@ static gchar *canonical_list_to_file(GSList *list)
        }
        g_slist_free(pathlist);
 
-       str = result->str;
-       g_string_free(result, FALSE);
-
-       return str;
+       return g_string_free(result, FALSE);
 }
 
 static GSList *cm_split_path(const gchar *filename, int depth)
@@ -4465,6 +4495,7 @@ static GSList *cm_split_path(const gchar *filename, int depth)
                else if (!strcmp(path_parts[i], "..")) {
                        if (i == 0) {
                                errno =ENOTDIR;
+                               g_strfreev(path_parts);
                                return NULL;
                        }
                        else /* Remove the last inserted element */
@@ -4579,46 +4610,12 @@ guchar *g_base64_decode_zero(const gchar *text, gsize *out_len)
        g_free(tmp);
 
        if (strlen(out) != *out_len) {
-               g_warning ("strlen(out) %"G_GSIZE_FORMAT" != *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;
 }
 
-#if !GLIB_CHECK_VERSION(2, 30, 0)
-/**
- * g_utf8_substring:
- * @str: a UTF-8 encoded string
- * @start_pos: a character offset within @str
- * @end_pos: another character offset within @str
- *
- * Copies a substring out of a UTF-8 encoded string.
- * The substring will contain @end_pos - @start_pos
- * characters.
- *
- * Returns: a newly allocated copy of the requested
- *     substring. Free with g_free() when no longer needed.
- *
- * Since: GLIB 2.30
- */
-gchar *
-g_utf8_substring (const gchar *str,
-                                 glong            start_pos,
-                                 glong            end_pos)
-{
-  gchar *start, *end, *out;
-
-  start = g_utf8_offset_to_pointer (str, start_pos);
-  end = g_utf8_offset_to_pointer (start, end_pos - start_pos);
-
-  out = g_malloc (end - start + 1);
-  memcpy (out, start, end - start);
-  out[end - start] = 0;
-
-  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. */