Merge branch 'master' of ssh://git.exim.org/home/git/exim
[exim.git] / src / src / string.c
index 62678b14500f68c9752a2b38909b325b82d8227b..914390255f43f59e55a919f00a3fb3d0d55d8689 100644 (file)
@@ -1,10 +1,8 @@
-/* $Cambridge: exim/src/src/string.c,v 1.1 2004/10/07 10:39:01 ph10 Exp $ */
-
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2004 */
+/* Copyright (c) University of Cambridge 1995 - 2014 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Miscellaneous string-handling functions. Some are not required for
@@ -28,6 +26,7 @@ Arguments:
   s         a string
   maskptr   NULL if no mask is permitted to follow
             otherwise, points to an int where the offset of '/' is placed
+            if there is no / followed by trailing digits, *maskptr is set 0
 
 Returns:    0 if the string is not a textual representation of an IP address
             4 if it is an IPv4 address
@@ -35,7 +34,7 @@ Returns:    0 if the string is not a textual representation of an IP address
 */
 
 int
-string_is_ip_address(uschar *s, int *maskptr)
+string_is_ip_address(const uschar *s, int *maskptr)
 {
 int i;
 int yield = 4;
@@ -45,7 +44,7 @@ offset. */
 
 if (maskptr != NULL)
   {
-  uschar *ss = s + Ustrlen(s);
+  const uschar *ss = s + Ustrlen(s);
   *maskptr = 0;
   if (s != ss && isdigit(*(--ss)))
     {
@@ -127,7 +126,9 @@ if (Ustrchr(s, ':') != NULL)
   sign, which introduces the interface specifier (scope id) of a link local
   address. */
 
-  if (!v4end) return (*s == 0 || *s == '%' || *s == '/')? yield : 0;
+  if (!v4end)
+    return (*s == 0 || *s == '%' ||
+           (*s == '/' && maskptr != NULL && *maskptr != 0))? yield : 0;
   }
 
 /* Test for IPv4 address, which may be the tail-end of an IPv6 address. */
@@ -139,7 +140,8 @@ for (i = 0; i < 4; i++)
   if (isdigit(*s) && isdigit(*(++s))) s++;
   }
 
-return (*s == 0 || *s == '/')? yield : 0;
+return (*s == 0 || (*s == '/' && maskptr != NULL && *maskptr != 0))?
+  yield : 0;
 }
 #endif  /* COMPILE_UTILITY */
 
@@ -208,7 +210,6 @@ return yield;
 
 
 
-#ifndef COMPILE_UTILITY
 /*************************************************
 *          Interpret escape sequence             *
 *************************************************/
@@ -225,6 +226,9 @@ Returns:   the value of the character escape
 int
 string_interpret_escape(uschar **pp)
 {
+#ifdef COMPILE_UTILITY
+const uschar *hex_digits= CUS"0123456789abcdef";
+#endif
 int ch;
 uschar *p = *pp;
 ch = *(++p);
@@ -240,9 +244,12 @@ if (isdigit(ch) && ch != '8' && ch != '9')
   }
 else switch(ch)
   {
+  case 'b':  ch = '\b'; break;
+  case 'f':  ch = '\f'; break;
   case 'n':  ch = '\n'; break;
   case 'r':  ch = '\r'; break;
   case 't':  ch = '\t'; break;
+  case 'v':  ch = '\v'; break;
   case 'x':
   ch = 0;
   if (isxdigit(p[1]))
@@ -257,7 +264,6 @@ else switch(ch)
 *pp = p;
 return ch;
 }
-#endif  /* COMPILE_UTILITY */
 
 
 
@@ -329,6 +335,73 @@ return ss;
 }
 #endif  /* COMPILE_UTILITY */
 
+/*************************************************
+*        Undo printing escapes in string         *
+*************************************************/
+
+/* This function is the reverse of string_printing2.  It searches for
+backslash characters and if any are found, it makes a new copy of the
+string with escape sequences parsed.  Otherwise it returns the original
+string.
+
+Arguments:
+  s             the input string
+
+Returns:        string with printing escapes parsed back
+*/
+
+uschar *
+string_unprinting(uschar *s)
+{
+uschar *p, *q, *r, *ss;
+int len, off;
+
+p = Ustrchr(s, '\\');
+if (!p) return s;
+
+len = Ustrlen(s) + 1;
+ss = store_get(len);
+
+q = ss;
+off = p - s;
+if (off)
+  {
+  memcpy(q, s, off);
+  q += off;
+  }
+
+while (*p)
+  {
+  if (*p == '\\')
+    {
+    *q++ = string_interpret_escape(&p);
+    p++;
+    }
+  else
+    {
+    r = Ustrchr(p, '\\');
+    if (!r)
+      {
+      off = Ustrlen(p);
+      memcpy(q, p, off);
+      p += off;
+      q += off;
+      break;
+      }
+    else
+      {
+      off = r - p;
+      memcpy(q, p, off);
+      q += off;
+      p = r;
+      }
+    }
+  }
+*q = '\0';
+
+return ss;
+}
+
 
 
 
@@ -343,7 +416,7 @@ Returns:  copy of string in new store
 */
 
 uschar *
-string_copy(uschar *s)
+string_copy(const uschar *s)
 {
 int len = Ustrlen(s) + 1;
 uschar *ss = store_get(len);
@@ -445,6 +518,67 @@ return ss;
 
 
 
+/*************************************************
+*    Copy string if long, inserting newlines     *
+*************************************************/
+
+/* If the given string is longer than 75 characters, it is copied, and within
+the copy, certain space characters are converted into newlines.
+
+Argument:  pointer to the string
+Returns:   pointer to the possibly altered string
+*/
+
+uschar *
+string_split_message(uschar *msg)
+{
+uschar *s, *ss;
+
+if (msg == NULL || Ustrlen(msg) <= 75) return msg;
+s = ss = msg = string_copy(msg);
+
+for (;;)
+  {
+  int i = 0;
+  while (i < 75 && *ss != 0 && *ss != '\n') ss++, i++;
+  if (*ss == 0) break;
+  if (*ss == '\n')
+    s = ++ss;
+  else
+    {
+    uschar *t = ss + 1;
+    uschar *tt = NULL;
+    while (--t > s + 35)
+      {
+      if (*t == ' ')
+        {
+        if (t[-1] == ':') { tt = t; break; }
+        if (tt == NULL) tt = t;
+        }
+      }
+
+    if (tt == NULL)          /* Can't split behind - try ahead */
+      {
+      t = ss + 1;
+      while (*t != 0)
+        {
+        if (*t == ' ' || *t == '\n')
+          { tt = t; break; }
+        t++;
+        }
+      }
+
+    if (tt == NULL) break;   /* Can't find anywhere to split */
+    *tt = '\n';
+    s = ss = tt+1;
+    }
+  }
+
+return msg;
+}
+
+
+
 /*************************************************
 *   Copy returned DNS domain name, de-escaping   *
 *************************************************/
@@ -576,14 +710,15 @@ Returns:    pointer to fresh piece of store containing sprintf'ed string
 */
 
 uschar *
-string_sprintf(char *format, ...)
+string_sprintf(const char *format, ...)
 {
 va_list ap;
 uschar buffer[STRING_SPRINTF_BUFFER_SIZE];
 va_start(ap, format);
 if (!string_vformat(buffer, sizeof(buffer), format, ap))
   log_write(0, LOG_MAIN|LOG_PANIC_DIE,
-    "string_sprintf expansion was longer than %d", sizeof(buffer));
+    "string_sprintf expansion was longer than " SIZE_T_FMT " (%s)",
+    sizeof(buffer), format);
 va_end(ap);
 return string_copy(buffer);
 }
@@ -604,7 +739,7 @@ Returns:    < 0, = 0, or > 0, according to the comparison
 */
 
 int
-strncmpic(uschar *s, uschar *t, int n)
+strncmpic(const uschar *s, const uschar *t, int n)
 {
 while (n--)
   {
@@ -628,7 +763,7 @@ Returns:    < 0, = 0, or > 0, according to the comparison
 */
 
 int
-strcmpic(uschar *s, uschar *t)
+strcmpic(const uschar *s, const uschar *t)
 {
 while (*s != 0)
   {
@@ -699,19 +834,26 @@ return NULL;
 /* Leading and trailing space is removed from each item. The separator in the
 list is controlled by the int pointed to by the separator argument as follows:
 
-  If its value is > 0 it is used as the delimiter.
-    (If its value is actually > UCHAR_MAX there is only one item in the list.
+  If the value is > 0 it is used as the separator. This is typically used for
+  sublists such as slash-separated options. The value is always a printing
+  character.
+
+    (If the value is actually > UCHAR_MAX there is only one item in the list.
     This is used for some cases when called via functions that sometimes
     plough through lists, and sometimes are given single items.)
-  If its value is <= 0, the string is inspected for a leading <x, where
-    x is an ispunct() value. If found, it is used as the delimiter. If not
-    found: (a) if separator == 0, ':' is used
-           (b) if separator <0, then -separator is used
-    In all cases the value of the separator that is used is written back to
-      the int so that it is used on subsequent calls as we progress through
-      the list.
 
-The separator can always be represented in the string by doubling.
+  If the value is <= 0, the string is inspected for a leading <x, where x is an
+  ispunct() or an iscntrl() character. If found, x is used as the separator. If
+  not found:
+
+      (a) if separator == 0, ':' is used
+      (b) if separator <0, -separator is used
+
+  In all cases the value of the separator that is used is written back to the
+  int so that it is used on subsequent calls as we progress through the list.
+
+A literal ispunct() separator can be represented in an item by doubling, but
+there is no way to include an iscntrl() separator as part of the data.
 
 Arguments:
   listptr    points to a pointer to the current start of the list; the
@@ -728,20 +870,28 @@ Returns:     pointer to buffer, containing the next substring,
 uschar *
 string_nextinlist(uschar **listptr, int *separator, uschar *buffer, int buflen)
 {
-register int p = 0;
 register int sep = *separator;
 register uschar *s = *listptr;
+BOOL sep_is_special;
 
 if (s == NULL) return NULL;
-while (isspace(*s)) s++;
+
+/* This allows for a fixed specified separator to be an iscntrl() character,
+but at the time of implementation, this is never the case. However, it's best
+to be conservative. */
+
+while (isspace(*s) && *s != sep) s++;
+
+/* A change of separator is permitted, so look for a leading '<' followed by an
+allowed character. */
 
 if (sep <= 0)
   {
-  if (*s == '<' && ispunct(s[1]))
+  if (*s == '<' && (ispunct(s[1]) || iscntrl(s[1])))
     {
     sep = s[1];
     s += 2;
-    while (isspace(*s)) s++;
+    while (isspace(*s) && *s != sep) s++;
     }
   else
     {
@@ -750,15 +900,22 @@ if (sep <= 0)
   *separator = sep;
   }
 
+/* An empty string has no list elements */
+
 if (*s == 0) return NULL;
 
+/* Note whether whether or not the separator is an iscntrl() character. */
+
+sep_is_special = iscntrl(sep);
+
 /* Handle the case when a buffer is provided. */
 
 if (buffer != NULL)
   {
+  register int p = 0;
   for (; *s != 0; s++)
     {
-    if (*s == sep && *(++s) != sep) break;
+    if (*s == sep && (*(++s) != sep || sep_is_special)) break;
     if (p < buflen - 1) buffer[p++] = *s;
     }
   while (p > 0 && isspace(buffer[p-1])) p--;
@@ -769,31 +926,37 @@ if (buffer != NULL)
 
 else
   {
+  int size = 0;
+  int ptr = 0;
+  uschar *ss;
+
   /* We know that *s != 0 at this point. However, it might be pointing to a
-  separator, which could indicate an empty string, or could be doubled to
-  indicate a separator character as data at the start of a string. */
+  separator, which could indicate an empty string, or (if an ispunct()
+  character) could be doubled to indicate a separator character as data at the
+  start of a string. Avoid getting working memory for an empty item. */
 
   if (*s == sep)
     {
     s++;
-    if (*s != sep) buffer = string_copy(US"");
+    if (*s != sep || sep_is_special)
+      {
+      *listptr = s;
+      return string_copy(US"");
+      }
     }
 
-  if (buffer == NULL)
+  /* Not an empty string; the first character is guaranteed to be a data
+  character. */
+
+  for (;;)
     {
-    int size = 0;
-    int ptr = 0;
-    uschar *ss;
-    for (;;)
-      {
-      for (ss = s + 1; *ss != 0 && *ss != sep; ss++);
-      buffer = string_cat(buffer, &size, &ptr, s, ss-s);
-      s = ss;
-      if (*s == 0 || *(++s) != sep) break;
-      }
-    while (ptr > 0 && isspace(buffer[ptr-1])) ptr--;
-    buffer[ptr] = 0;
+    for (ss = s + 1; *ss != 0 && *ss != sep; ss++);
+    buffer = string_cat(buffer, &size, &ptr, s, ss-s);
+    s = ss;
+    if (*s == 0 || *(++s) != sep || sep_is_special) break;
     }
+  while (ptr > 0 && isspace(buffer[ptr-1])) ptr--;
+  buffer[ptr] = 0;
   }
 
 /* Update the current pointer and return the new string */
@@ -804,6 +967,50 @@ return buffer;
 #endif  /* COMPILE_UTILITY */
 
 
+#ifndef COMPILE_UTILITY
+/************************************************
+*      Add element to seperated list           *
+************************************************/
+/* This function is used to build a list, returning
+an allocated null-terminated growable string. The
+given element has any embedded seperator characters
+doubled.
+
+Arguments:
+  list points to the start of the list that is being built, or NULL
+       if this is a new list that has no contents yet
+  sep  list seperator charactoer
+  ele  new lement to be appended to the list
+
+Returns:  pointer to the start of the list, changed if copied for expansion.
+*/
+
+uschar *
+string_append_listele(uschar * list, uschar sep, const uschar * ele)
+{
+uschar * new = NULL;
+int sz = 0, off = 0;
+uschar * sp;
+
+if (list)
+  {
+  new = string_cat(new, &sz, &off, list, Ustrlen(list));
+  new = string_cat(new, &sz, &off, &sep, 1);
+  }
+
+while((sp = Ustrchr(ele, sep)))
+  {
+  new = string_cat(new, &sz, &off, ele, sp-ele+1);
+  new = string_cat(new, &sz, &off, &sep, 1);
+  ele = sp+1;
+  }
+new = string_cat(new, &sz, &off, ele, Ustrlen(ele));
+new[off] = '\0';
+return new;
+}
+#endif  /* COMPILE_UTILITY */
+
+
 
 #ifndef COMPILE_UTILITY
 /*************************************************
@@ -944,9 +1151,9 @@ on whether the variable length list of data arguments are given explicitly or
 as a va_list item.
 
 The formats are the usual printf() ones, with some omissions (never used) and
-two additions for strings: %S forces lower case, %#s or %#S prints nothing for
-a NULL string. Without the # "NULL" is printed (useful in debugging). There is
-also the addition of %D, which inserts the date in the form used for
+two additions for strings: %S forces lower case, and %#s or %#S prints nothing
+for a NULL string. Without the # "NULL" is printed (useful in debugging). There
+is also the addition of %D and %M, which insert the date in the form used for
 datestamped log files.
 
 Arguments:
@@ -959,7 +1166,7 @@ Returns:       TRUE if the result fitted in the buffer
 */
 
 BOOL
-string_format(uschar *buffer, int buflen, char *format, ...)
+string_format(uschar *buffer, int buflen, const char *format, ...)
 {
 BOOL yield;
 va_list ap;
@@ -971,24 +1178,30 @@ return yield;
 
 
 BOOL
-string_vformat(uschar *buffer, int buflen, char *format, va_list ap)
+string_vformat(uschar *buffer, int buflen, const char *format, va_list ap)
 {
+/* We assume numbered ascending order, C does not guarantee that */
+enum { L_NORMAL=1, L_SHORT=2, L_LONG=3, L_LONGLONG=4, L_LONGDOUBLE=5, L_SIZE=6 };
+
 BOOL yield = TRUE;
 int width, precision;
-char *fp = format;             /* Deliberately not unsigned */
+const char *fp = format;       /* Deliberately not unsigned */
 uschar *p = buffer;
 uschar *last = buffer + buflen - 1;
 
 string_datestamp_offset = -1;  /* Datestamp not inserted */
+string_datestamp_length = 0;   /* Datestamp not inserted */
+string_datestamp_type = 0;     /* Datestamp not inserted */
 
 /* Scan the format and handle the insertions */
 
 while (*fp != 0)
   {
+  int length = L_NORMAL;
   int *nptr;
   int slen;
-  char *null = "NULL";         /* ) These variables */
-  char *item_start, *s;        /* ) are deliberately */
+  const char *null = "NULL";   /* ) These variables */
+  const char *item_start, *s;  /* ) are deliberately */
   char newformat[16];          /* ) not unsigned */
 
   /* Non-% characters just get copied verbatim */
@@ -1038,7 +1251,27 @@ while (*fp != 0)
       }
     }
 
-  if (strchr("hlL", *fp) != NULL) fp++;
+  /* Skip over 'h', 'L', 'l', 'll' and 'z', remembering the item length */
+
+  if (*fp == 'h')
+    { fp++; length = L_SHORT; }
+  else if (*fp == 'L')
+    { fp++; length = L_LONGDOUBLE; }
+  else if (*fp == 'l')
+    {
+    if (fp[1] == 'l')
+      {
+      fp += 2;
+      length = L_LONGLONG;
+      }
+    else
+      {
+      fp++;
+      length = L_LONG;
+      }
+    }
+  else if (*fp == 'z')
+    { fp++; length = L_SIZE; }
 
   /* Handle each specific format type. */
 
@@ -1054,10 +1287,22 @@ while (*fp != 0)
     case 'u':
     case 'x':
     case 'X':
-    if (p >= last - 12) { yield = FALSE; goto END_FORMAT; }
+    if (p >= last - ((length > L_LONG)? 24 : 12))
+      { yield = FALSE; goto END_FORMAT; }
     strncpy(newformat, item_start, fp - item_start);
     newformat[fp - item_start] = 0;
-    sprintf(CS p, newformat, va_arg(ap, int));
+
+    /* Short int is promoted to int when passing through ..., so we must use
+    int for va_arg(). */
+
+    switch(length)
+      {
+      case L_SHORT:
+      case L_NORMAL:   sprintf(CS p, newformat, va_arg(ap, int)); break;
+      case L_LONG:     sprintf(CS p, newformat, va_arg(ap, long int)); break;
+      case L_LONGLONG: sprintf(CS p, newformat, va_arg(ap, LONGLONG_T)); break;
+      case L_SIZE:     sprintf(CS p, newformat, va_arg(ap, size_t)); break;
+      }
     while (*p) p++;
     break;
 
@@ -1070,9 +1315,11 @@ while (*fp != 0)
     break;
 
     /* %f format is inherently insecure if the numbers that it may be
-    handed are unknown (e.g. 1e300). However, in Exim, the only use of %f
-    is for printing load averages, and these are actually stored as integers
-    (load average * 1000) so the size of the numbers is constrained. */
+    handed are unknown (e.g. 1e300). However, in Exim, %f is used for
+    printing load averages, and these are actually stored as integers
+    (load average * 1000) so the size of the numbers is constrained.
+    It is also used for formatting sending rates, where the simplicity
+    of the format prevents overflow. */
 
     case 'f':
     case 'e':
@@ -1083,7 +1330,10 @@ while (*fp != 0)
     if (p >= last - precision - 8) { yield = FALSE; goto END_FORMAT; }
     strncpy(newformat, item_start, fp - item_start);
     newformat[fp-item_start] = 0;
-    sprintf(CS p, newformat, va_arg(ap, double));
+    if (length == L_LONGDOUBLE)
+      sprintf(CS p, newformat, va_arg(ap, long double));
+    else
+      sprintf(CS p, newformat, va_arg(ap, double));
     while (*p) p++;
     break;
 
@@ -1099,19 +1349,31 @@ while (*fp != 0)
     *p++ = va_arg(ap, int);
     break;
 
-    case 'D':                   /* Insert datestamp for log file names */
-    s = CS tod_stamp(tod_log_datestamp);
+    case 'D':                   /* Insert daily datestamp for log file names */
+    s = CS tod_stamp(tod_log_datestamp_daily);
+    string_datestamp_offset = p - buffer;   /* Passed back via global */
+    string_datestamp_length = Ustrlen(s);   /* Passed back via global */
+    string_datestamp_type = tod_log_datestamp_daily;
+    slen = string_datestamp_length;
+    goto INSERT_STRING;
+
+    case 'M':                   /* Insert monthly datestamp for log file names */
+    s = CS tod_stamp(tod_log_datestamp_monthly);
     string_datestamp_offset = p - buffer;   /* Passed back via global */
+    string_datestamp_length = Ustrlen(s);   /* Passed back via global */
+    string_datestamp_type = tod_log_datestamp_monthly;
+    slen = string_datestamp_length;
     goto INSERT_STRING;
 
     case 's':
     case 'S':                   /* Forces *lower* case */
     s = va_arg(ap, char *);
 
-    INSERT_STRING:              /* Come to from %D above */
     if (s == NULL) s = null;
     slen = Ustrlen(s);
 
+    INSERT_STRING:              /* Come to from %D or %M above */
+
     /* If the width is specified, check that there is a precision
     set; if not, set it to the width to prevent overruns of long
     strings. */
@@ -1137,10 +1399,17 @@ while (*fp != 0)
     not OK, add part of the string (debugging uses this to show as
     much as possible). */
 
+    if (p == last)
+      {
+      yield = FALSE;
+      goto END_FORMAT;
+      }
     if (p >= last - width)
       {
       yield = FALSE;
       width = precision = last - p - 1;
+      if (width < 0) width = 0;
+      if (precision < 0) precision = 0;
       }
     sprintf(CS p, "%*.*s", width, precision, s);
     if (fp[-1] == 'S')
@@ -1189,7 +1458,7 @@ Returns:        a message, in dynamic store
 */
 
 uschar *
-string_open_failed(int eno, char *format, ...)
+string_open_failed(int eno, const char *format, ...)
 {
 va_list ap;
 uschar buffer[1024];
@@ -1431,8 +1700,10 @@ printf("Testing string_format\n");
 while (fgets(CS buffer, sizeof(buffer), stdin) != NULL)
   {
   void *args[3];
+  long long llargs[3];
   double dargs[3];
   int dflag = 0;
+  int llflag = 0;
   int n = 0;
   int count;
   int countset = 0;
@@ -1463,6 +1734,11 @@ while (fgets(CS buffer, sizeof(buffer), stdin) != NULL)
         dflag = 1;
         dargs[n++] = Ustrtod(outbuf, NULL);
         }
+      else if (Ustrstr(outbuf, "ll") != NULL)
+        {
+        llflag = 1;
+        llargs[n++] = strtoull(CS outbuf, NULL, 10);
+        }
       else
         {
         args[n++] = (void *)Uatoi(outbuf);
@@ -1485,11 +1761,16 @@ while (fgets(CS buffer, sizeof(buffer), stdin) != NULL)
     if (*s == ',') s++;
     }
 
-  if (!dflag) printf("%s\n", string_format(outbuf, sizeof(outbuf), CS format,
-    args[0], args[1], args[2])? "True" : "False");
+  if (!dflag && !llflag)
+    printf("%s\n", string_format(outbuf, sizeof(outbuf), CS format,
+      args[0], args[1], args[2])? "True" : "False");
+
+  else if (dflag)
+    printf("%s\n", string_format(outbuf, sizeof(outbuf), CS format,
+      dargs[0], dargs[1], dargs[2])? "True" : "False");
 
   else printf("%s\n", string_format(outbuf, sizeof(outbuf), CS format,
-    dargs[0], dargs[1], dargs[2])? "True" : "False");
+    llargs[0], llargs[1], llargs[2])? "True" : "False");
 
   printf("%s\n", CS outbuf);
   if (countset) printf("count=%d\n", count);