Do not translate messages printed with log_debug.
[gnupg.git] / common / stringhelp.c
index 7128de5..e8b990a 100644 (file)
@@ -2,10 +2,11 @@
  * Copyright (C) 1998, 1999, 2000, 2001, 2003, 2004, 2005, 2006, 2007,
  *               2008, 2009, 2010  Free Software Foundation, Inc.
  * Copyright (C) 2014 Werner Koch
+ * Copyright (C) 2015  g10 Code GmbH
  *
- * This file is part of JNLIB, which is a subsystem of GnuPG.
+ * This file is part of GnuPG.
  *
- * JNLIB is free software; you can redistribute it and/or modify it
+ * GnuPG is free software; you can redistribute it and/or modify it
  * under the terms of either
  *
  *   - the GNU Lesser General Public License as published by the Free
@@ -20,7 +21,7 @@
  *
  * or both in parallel, as here.
  *
- * JNLIB is distributed in the hope that it will be useful, but
+ * GnuPG is distributed in the hope that it will be useful, but
  * WITHOUT ANY WARRANTY; without even the implied warranty of
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  * General Public License for more details.
 # endif
 # include <windows.h>
 #endif
+#include <assert.h>
 
-#include "libjnlib-config.h"
+#include "util.h"
+#include "common-defs.h"
 #include "utf8conv.h"
 #include "sysutils.h"
 #include "stringhelp.h"
@@ -158,7 +161,7 @@ ascii_memistr ( const void *buffer, size_t buflen, const char *sub )
 /* This function is similar to strncpy().  However it won't copy more
    than N - 1 characters and makes sure that a '\0' is appended. With
    N given as 0, nothing will happen.  With DEST given as NULL, memory
-   will be allocated using jnlib_xmalloc (i.e. if it runs out of core
+   will be allocated using xmalloc (i.e. if it runs out of core
    the function terminates).  Returns DES or a pointer to the
    allocated memory.
  */
@@ -170,7 +173,7 @@ mem2str( char *dest , const void *src , size_t n )
 
     if( n ) {
        if( !dest )
-           dest = jnlib_xmalloc( n ) ;
+           dest = xmalloc( n ) ;
        d = dest;
        s = src ;
        for(n--; n && *s; n-- )
@@ -318,10 +321,10 @@ make_basename(const char *filepath, const char *inputpath)
            if ( !(p=strrchr(filepath, ':')) )
 #endif
              {
-               return jnlib_xstrdup(filepath);
+               return xstrdup(filepath);
              }
 
-    return jnlib_xstrdup(p+1);
+    return xstrdup(p+1);
 #endif
 }
 
@@ -347,11 +350,11 @@ make_dirname(const char *filepath)
            if ( !(p=strrchr(filepath, ':')) )
 #endif
              {
-               return jnlib_xstrdup(".");
+               return xstrdup(".");
              }
 
     dirname_length = p-filepath;
-    dirname = jnlib_xmalloc(dirname_length+1);
+    dirname = xmalloc(dirname_length+1);
     strncpy(dirname, filepath, dirname_length);
     dirname[dirname_length] = 0;
 
@@ -384,9 +387,9 @@ get_pwdir (int xmode, const char *name)
   if (pwd)
     {
       if (xmode)
-        result = jnlib_xstrdup (pwd->pw_dir);
+        result = xstrdup (pwd->pw_dir);
       else
-        result = jnlib_strdup (pwd->pw_dir);
+        result = xtrystrdup (pwd->pw_dir);
     }
 #else /*!HAVE_PWD_H*/
   /* No support at all.  */
@@ -425,7 +428,7 @@ do_make_filename (int xmode, const char *first_part, va_list arg_ptr)
         {
           if (xmode)
             BUG ();
-          jnlib_set_errno (EINVAL);
+          gpg_err_set_errno (EINVAL);
           return NULL;
         }
       argc++;
@@ -450,10 +453,10 @@ do_make_filename (int xmode, const char *first_part, va_list arg_ptr)
           char *user;
 
           if (xmode)
-            user = jnlib_xstrdup (first_part+1);
+            user = xstrdup (first_part+1);
           else
             {
-              user = jnlib_strdup (first_part+1);
+              user = xtrystrdup (first_part+1);
               if (!user)
                 return NULL;
             }
@@ -463,7 +466,7 @@ do_make_filename (int xmode, const char *first_part, va_list arg_ptr)
           skip = 1 + strlen (user);
 
           home = home_buffer = get_pwdir (xmode, user);
-          jnlib_free (user);
+          xfree (user);
           if (home)
             n += strlen (home);
           else
@@ -472,13 +475,13 @@ do_make_filename (int xmode, const char *first_part, va_list arg_ptr)
     }
 
   if (xmode)
-    name = jnlib_xmalloc (n);
+    name = xmalloc (n);
   else
     {
-      name = jnlib_malloc (n);
+      name = xtrymalloc (n);
       if (!name)
         {
-          jnlib_free (home_buffer);
+          xfree (home_buffer);
           return NULL;
         }
     }
@@ -488,9 +491,15 @@ do_make_filename (int xmode, const char *first_part, va_list arg_ptr)
   else
     p = stpcpy (name, first_part);
 
-  jnlib_free (home_buffer);
+  xfree (home_buffer);
   for (argc=0; argv[argc]; argc++)
-    p = stpcpy (stpcpy (p, "/"), argv[argc]);
+    {
+      /* Avoid a leading double slash if the first part was "/".  */
+      if (!argc && name[0] == '/' && !name[1])
+        p = stpcpy (p, argv[argc]);
+      else
+        p = stpcpy (stpcpy (p, "/"), argv[argc]);
+    }
 
   if (want_abs)
     {
@@ -518,18 +527,18 @@ do_make_filename (int xmode, const char *first_part, va_list arg_ptr)
                            strerror (errno));
                   exit(2);
                 }
-              jnlib_free (name);
+              xfree (name);
               return NULL;
             }
           n = strlen (home) + 1 + strlen (name) + 1;
           if (xmode)
-            home_buffer = jnlib_xmalloc (n);
+            home_buffer = xmalloc (n);
           else
             {
-              home_buffer = jnlib_malloc (n);
+              home_buffer = xtrymalloc (n);
               if (!home_buffer)
                 {
-                  jnlib_free (name);
+                  xfree (name);
                   return NULL;
                 }
             }
@@ -540,8 +549,14 @@ do_make_filename (int xmode, const char *first_part, va_list arg_ptr)
               memcpy (home_buffer, p, p - name + 1);
               p = home_buffer + (p - name + 1);
             }
-          strcpy (stpcpy (stpcpy (p, home), "/"), name);
-          jnlib_free (name);
+
+          /* Avoid a leading double slash if the cwd is "/".  */
+          if (home[0] == '/' && !home[1])
+            strcpy (stpcpy (p, "/"), name);
+          else
+            strcpy (stpcpy (stpcpy (p, home), "/"), name);
+
+          xfree (name);
           name = home_buffer;
           /* Let's do a simple compression to catch the most common
              case of using "." for gpg's --homedir option.  */
@@ -699,7 +714,7 @@ sanitize_buffer (const void *p_arg, size_t n, int delim)
   p = save_p;
   n = save_n;
   /* And now make the string */
-  d = buffer = jnlib_xmalloc( buflen );
+  d = buffer = xmalloc( buflen );
   for ( ; n; n--, p++ )
     {
       if (*p < 0x20 || *p == 0x7f || *p == delim || (delim && *p=='\\')) {
@@ -731,16 +746,30 @@ sanitize_buffer (const void *p_arg, size_t n, int delim)
 
 /* Given a string containing an UTF-8 encoded text, return the number
    of characters in this string.  It differs from strlen in that it
-   only counts complete UTF-8 characters.  Note, that this function
-   does not take combined characters into account.  */
+   only counts complete UTF-8 characters.  SIZE is the maximum length
+   of the string in bytes.  If SIZE is -1, then a NUL character is
+   taken to be the end of the string.  Note, that this function does
+   not take combined characters into account.  */
 size_t
-utf8_charcount (const char *s)
+utf8_charcount (const char *s, int len)
 {
   size_t n;
 
+  if (len == 0)
+    return 0;
+
   for (n=0; *s; s++)
-    if ( (*s&0xc0) != 0x80 ) /* Exclude continuation bytes: 10xxxxxx */
-      n++;
+    {
+      if ( (*s&0xc0) != 0x80 ) /* Exclude continuation bytes: 10xxxxxx */
+        n++;
+
+      if (len != -1)
+        {
+          len --;
+          if (len == 0)
+            break;
+        }
+    }
 
   return n;
 }
@@ -804,6 +833,18 @@ ascii_tolower (int c)
     return c;
 }
 
+/* Lowercase all ASCII characters in S.  */
+char *
+ascii_strlwr (char *s)
+{
+  char *p = s;
+
+  for (p=s; *p; p++ )
+    if (isascii (*p) && *p >= 'A' && *p <= 'Z')
+      *p |= 0x20;
+
+  return s;
+}
 
 int
 ascii_strcasecmp( const char *a, const char *b )
@@ -1050,10 +1091,10 @@ do_percent_escape (const char *str, const char *extra, int die)
     if (str[i] == ':' || str[i] == '%' || (extra && strchr (extra, str[i])))
       j++;
   if (die)
-    ptr = jnlib_xmalloc (i + 2 * j + 1);
+    ptr = xmalloc (i + 2 * j + 1);
   else
     {
-      ptr = jnlib_malloc (i + 2 * j + 1);
+      ptr = xtrymalloc (i + 2 * j + 1);
       if (!ptr)
         return NULL;
     }
@@ -1122,13 +1163,13 @@ do_strconcat (const char *s1, va_list arg_ptr)
       needed += strlen (argv[argc]);
       if (argc >= DIM (argv)-1)
         {
-          jnlib_set_errno (EINVAL);
+          gpg_err_set_errno (EINVAL);
           return NULL;
         }
       argc++;
     }
   needed++;
-  buffer = jnlib_malloc (needed);
+  buffer = xtrymalloc (needed);
   if (buffer)
     {
       for (p = buffer, argc=0; argv[argc]; argc++)
@@ -1148,7 +1189,7 @@ strconcat (const char *s1, ...)
   char *result;
 
   if (!s1)
-    result = jnlib_strdup ("");
+    result = xtrystrdup ("");
   else
     {
       va_start (arg_ptr, s1);
@@ -1167,7 +1208,7 @@ xstrconcat (const char *s1, ...)
   char *result;
 
   if (!s1)
-    result = jnlib_xstrdup ("");
+    result = xstrdup ("");
   else
     {
       va_start (arg_ptr, s1);
@@ -1184,3 +1225,238 @@ xstrconcat (const char *s1, ...)
     }
   return result;
 }
+
+/* Split a string into fields at DELIM.  REPLACEMENT is the character
+   to replace the delimiter with (normally: '\0' so that each field is
+   NUL terminated).  The caller is responsible for freeing the result.
+   Note: this function modifies STRING!  If you need the original
+   value, then you should pass a copy to this function.
+
+   If malloc fails, this function returns NULL.  */
+char **
+strsplit (char *string, char delim, char replacement, int *count)
+{
+  int fields = 1;
+  char *t;
+  char **result;
+
+  /* First, count the number of fields.  */
+  for (t = strchr (string, delim); t; t = strchr (t + 1, delim))
+    fields ++;
+
+  result = xtrycalloc ((fields + 1), sizeof (*result));
+  if (! result)
+    return NULL;
+
+  result[0] = string;
+  fields = 1;
+  for (t = strchr (string, delim); t; t = strchr (t + 1, delim))
+    {
+      result[fields ++] = t + 1;
+      *t = replacement;
+    }
+
+  if (count)
+    *count = fields;
+
+  return result;
+}
+
+
+/* Tokenize STRING using the set of delimiters in DELIM.  Leading
+ * spaces and tabs are removed from all tokens.  The caller must xfree
+ * the result.
+ *
+ * Returns: A malloced and NULL delimited array with the tokens.  On
+ *          memory error NULL is returned and ERRNO is set.
+ */
+char **
+strtokenize (const char *string, const char *delim)
+{
+  const char *s;
+  size_t fields;
+  size_t bytes, n;
+  char *buffer;
+  char *p, *px, *pend;
+  char **result;
+
+  /* Count the number of fields.  */
+  for (fields = 1, s = strpbrk (string, delim); s; s = strpbrk (s + 1, delim))
+    fields++;
+  fields++; /* Add one for the terminating NULL.  */
+
+  /* Allocate an array for all fields, a terminating NULL, and space
+     for a copy of the string.  */
+  bytes = fields * sizeof *result;
+  if (bytes / sizeof *result != fields)
+    {
+      gpg_err_set_errno (ENOMEM);
+      return NULL;
+    }
+  n = strlen (string) + 1;
+  bytes += n;
+  if (bytes < n)
+    {
+      gpg_err_set_errno (ENOMEM);
+      return NULL;
+    }
+  result = xtrymalloc (bytes);
+  if (!result)
+    return NULL;
+  buffer = (char*)(result + fields);
+
+  /* Copy and parse the string.  */
+  strcpy (buffer, string);
+  for (n = 0, p = buffer; (pend = strpbrk (p, delim)); p = pend + 1)
+    {
+      *pend = 0;
+      while (spacep (p))
+        p++;
+      for (px = pend - 1; px >= p && spacep (px); px--)
+        *px = 0;
+      result[n++] = p;
+    }
+  while (spacep (p))
+    p++;
+  for (px = p + strlen (p) - 1; px >= p && spacep (px); px--)
+    *px = 0;
+  result[n++] = p;
+  result[n] = NULL;
+
+  assert ((char*)(result + n + 1) == buffer);
+
+  return result;
+}
+
+
+/* Format a string so that it fits within about TARGET_COLS columns.
+   If IN_PLACE is 0, then TEXT is copied to a new buffer, which is
+   returned.  Otherwise, TEXT is modified in place and returned.
+   Normally, target_cols will be 72 and max_cols is 80.  */
+char *
+format_text (char *text, int in_place, int target_cols, int max_cols)
+{
+  const int do_debug = 0;
+
+  /* The character under consideration.  */
+  char *p;
+  /* The start of the current line.  */
+  char *line;
+  /* The last space that we saw.  */
+  char *last_space = NULL;
+  int last_space_cols = 0;
+  int copied_last_space = 0;
+
+  if (! in_place)
+    text = xstrdup (text);
+
+  p = line = text;
+  while (1)
+    {
+      /* The number of columns including any trailing space.  */
+      int cols;
+
+      p = p + strcspn (p, "\n ");
+      if (! p)
+        /* P now points to the NUL character.  */
+        p = &text[strlen (text)];
+
+      if (*p == '\n')
+        /* Pass through any newlines.  */
+        {
+          p ++;
+          line = p;
+          last_space = NULL;
+          last_space_cols = 0;
+          copied_last_space = 1;
+          continue;
+        }
+
+      /* Have a space or a NUL.  Note: we don't count the trailing
+         space.  */
+      cols = utf8_charcount (line, (uintptr_t) p - (uintptr_t) line);
+      if (cols < target_cols)
+        {
+          if (! *p)
+            /* Nothing left to break.  */
+            break;
+
+          last_space = p;
+          last_space_cols = cols;
+          p ++;
+          /* Skip any immediately following spaces.  If we break:
+             "... foo bar ..." between "foo" and "bar" then we want:
+             "... foo\nbar ...", which means that the left space has
+             to be the first space after foo, not the last space
+             before bar.  */
+          while (*p == ' ')
+            p ++;
+        }
+      else
+        {
+          int cols_with_left_space;
+          int cols_with_right_space;
+          int left_penalty;
+          int right_penalty;
+
+          cols_with_left_space = last_space_cols;
+          cols_with_right_space = cols;
+
+          if (do_debug)
+            log_debug ("Breaking: '%.*s'\n",
+                       (int) ((uintptr_t) p - (uintptr_t) line), line);
+
+          /* The number of columns away from TARGET_COLS.  We prefer
+             to underflow than to overflow.  */
+          left_penalty = target_cols - cols_with_left_space;
+          right_penalty = 2 * (cols_with_right_space - target_cols);
+
+          if (cols_with_right_space > max_cols)
+            /* Add a large penalty for each column that exceeds
+               max_cols.  */
+            right_penalty += 4 * (cols_with_right_space - max_cols);
+
+          if (do_debug)
+            log_debug ("Left space => %d cols (penalty: %d); right space => %d cols (penalty: %d)\n",
+                       cols_with_left_space, left_penalty,
+                       cols_with_right_space, right_penalty);
+          if (last_space_cols && left_penalty <= right_penalty)
+            /* Prefer the left space.  */
+            {
+              if (do_debug)
+                log_debug ("Breaking at left space.\n");
+              p = last_space;
+            }
+          else
+            {
+              if (do_debug)
+                log_debug ("Breaking at right space.\n");
+            }
+
+          if (! *p)
+            break;
+
+          *p = '\n';
+          p ++;
+          if (*p == ' ')
+            {
+              int spaces;
+              for (spaces = 1; p[spaces] == ' '; spaces ++)
+                ;
+              memmove (p, &p[spaces], strlen (&p[spaces]) + 1);
+            }
+          line = p;
+          last_space = NULL;
+          last_space_cols = 0;
+          copied_last_space = 0;
+        }
+    }
+
+  /* Chop off any trailing space.  */
+  trim_trailing_chars (text, strlen (text), " ");
+  /* If we inserted the trailing newline, then remove it.  */
+  if (! copied_last_space && *text && text[strlen (text) - 1] == '\n')
+    text[strlen (text) - 1] = '\0';
+
+  return text;
+}