--- src/expand.c~	Mon Jul 22 08:59:48 2002
+++ src/expand.c	Mon Sep 23 16:15:29 2002
@@ -43,26 +43,34 @@
 
 static uschar *item_table[] = {
   US"extract",
+  US"hash",
   US"if",
+  US"length",
   US"lookup",
+  US"nhash",
   #ifdef EXIM_PERL
   US"perl",
   #endif
   US"readfile",
   US"run",
   US"sg",
+  US"substr",
   US"tr" };
 
 enum {
   EITEM_EXTRACT,
+  EITEM_HASH,
   EITEM_IF,
+  EITEM_LENGTH,
   EITEM_LOOKUP,
+  EITEM_NHASH,
   #ifdef EXIM_PERL
   EITEM_PERL,
   #endif
   EITEM_READFILE,
   EITEM_RUN,
   EITEM_SG,
+  EITEM_SUBSTR,
   EITEM_TR };
 
 /* Table of operator names, and corresponding switch numbers */
@@ -563,6 +571,190 @@
 
 
 /*************************************************
+*        Extract a substring from a string       *
+*************************************************/
+
+/* Perform the ${substr or ${length expansion operations.
+
+Arguments:
+  len     will contain the length of the returned string
+  sub     the input string
+  value1  the offset from the start of the input string to the start of
+            the output string; if negative, count from the right instead.
+  value2  the length of the output string which is truncated at the right if
+            value1 is positive or the left if value1 is negative; if value2
+            is negative (i.e. unset) then the output string is the right-hand
+            part of the input if value1 is positive or the left-hand part if
+            value1 is negative
+
+Returns:
+  A pointer to the output string, or NULL if there is an error.
+
+*/
+
+static uschar *
+expand_substr (int *len, uschar *sub, int value1, int value2)
+{
+int sublen = Ustrlen(sub);
+
+if (value1 < 0)    /* count from right */
+  {
+  value1 += sublen;
+
+  /* If the position is before the start, skip to the start, unless
+  length is unset, in which case the substring is null. */
+
+  if (value1 < 0)
+    {
+    if (value2 < 0) value2 = 0; else value2 += value1;
+    value1 = 0;
+    }
+
+  /* Otherwise an unset length => characters before the value1 */
+
+  else if (value2 < 0)
+    {
+    value2 = value1;
+    value1 = 0;
+    }
+  }
+
+/* For a non-negative offset, no length means "rest"; just set
+it to the maximum. */
+
+else if (value2 < 0) value2 = sublen;
+
+/* Cut the length down to the maximum possible for the offset value,
+and get the required characters. */
+
+if (sublen < value1 + value2) value2 = sublen - value1;
+if (value2 > 0)
+  {
+  *len = value2;
+  return sub + value1;
+  }
+else
+  {
+  *len = 0;
+  return sub;
+  }
+}
+
+
+
+
+/*************************************************
+*            Old-style hash of a string          *
+*************************************************/
+
+/* Perform the ${hash expansion operation.
+
+Arguments:
+  len     will contain the length of the returned string
+  sub     the input string
+  value1  the length of the output string; if greater or equal to the input
+             string length then the input string is returned.
+  value2  the number of hash characters to use, or 26 if negative
+
+Returns:
+  A pointer to the output string, or NULL if there is an error.
+
+*/
+
+static uschar *
+expand_hash (int *len, uschar *sub, int value1, int value2)
+{
+int sublen = Ustrlen(sub);
+
+if (value2 < 0) value2 = 26;
+else if (value2 > Ustrlen(hashcodes))
+  {
+  expand_string_message =
+    string_sprintf("hash count \"%d\" too big", value2);
+  return NULL;
+  }
+
+/* Calculate the hash text. We know it is shorter than the original
+string, so can safely place it in sub[]. */
+
+if (value1 < sublen)
+  {
+  int c;
+  int i = 0;
+  int j = value1;
+  while ((c = (sub[j])) != 0)
+    {
+    int shift = (c + j++) & 7;
+    sub[i] ^= (c << shift) | (c >> (8-shift));
+    if (++i >= value1) i = 0;
+    }
+  for (i = 0; i < value1; i++)
+    sub[i] = hashcodes[(sub[i]) % value2];
+  }
+else value1 = sublen;
+
+*len = value1;
+return sub;
+}
+
+
+
+
+/*************************************************
+*             Numeric hash of a string           *
+*************************************************/
+
+/* Perform the ${nhash expansion operation. The first characters of the
+string are treated as most important, and get the highest prime numbers.
+
+Arguments:
+  len     will contain the length of the returned string
+  sub     the input string
+  value1  the maximum value of the first part of the result
+  value2  the maximum value of the second part of the result,
+            or negative to produce only a one-part result
+
+Returns:
+  A pointer to the output string, or NULL if there is an error.
+
+*/
+
+static uschar *
+expand_nhash (int *len, uschar *sub, int value1, int value2)
+{
+uschar *s = sub;
+int i = 0;
+unsigned long int total = 0; /* no overflow */
+
+while (*s != 0)
+  {
+  if (i == 0) i = sizeof(prime)/sizeof(int) - 1;
+  total += prime[i--] * (unsigned int)(*s++);
+  }
+
+/* If value2 is unset, just compute one number */
+
+if (value2 < 0)
+  {
+  s = string_sprintf("%d", total % value1);
+  }
+
+/* Otherwise do a div/mod hash */
+
+else
+  {
+  total = total % (value1 * value2);
+  s = string_sprintf("%d/%d", total/value2, total % value2);
+  }
+
+*len = Ustrlen(s);
+return s;
+}
+
+
+
+
+/*************************************************
 *     Find the value of a header or headers      *
 *************************************************/
 
@@ -2355,6 +2547,73 @@
       continue;
       }
 
+    /* Handle "hash", "length", "nhash", "substr" with expanded parameters. */
+
+    case EITEM_HASH:
+    case EITEM_LENGTH:
+    case EITEM_NHASH:
+    case EITEM_SUBSTR:
+      {
+      int i;
+      int len;
+      uschar *ret;
+      int val[2] = { 0, -1 };
+      uschar *sub[3];
+
+      /* "length" takes only 2 arguments whereas the others take 2 or 3.
+      Ensure that sub[2] is set in the ${length case. */
+
+      sub[2] = NULL;
+      switch(read_subs(sub, (name[0] == 'l') ? 2 : 3, 2, &s, skipping))
+        {
+        case 1: /* Treat as 2 */
+        case 2: goto EXPAND_FAILED_CURLY;
+        case 3: goto EXPAND_FAILED;
+        }
+
+      /* Juggle the arguments if there are only two of them: always move the
+      string to the last position; and make ${length{n}{str}} equivalent to
+      ${substr{0}{n}{str}}. See the defaults for val[] above. */
+
+      if (sub[2] == NULL)
+        {
+        sub[2] = sub[1];
+        sub[1] = NULL;
+        if (name[0] == 'l')
+          {
+          sub[1] = sub[0];
+          sub[0] = NULL;
+          }
+        }
+
+      for (i = 0; i < 2; i++)
+        {
+        if (sub[i] == NULL)
+          continue;
+        val[i] = (int)Ustrtol(sub[i], &ret, 10);
+        if (*ret != '\0' || (i && val[i] < 0))
+          {
+          expand_string_message = string_sprintf("\"%s\" is not a%s number",
+            sub[i], i ? " positive" : "");
+          goto EXPAND_FAILED;
+          }
+        }
+
+      if (name[0] == 'h')
+        ret = expand_hash(&len, sub[2], val[0], val[1]);
+      else if (name[0] == 'n')
+        ret = expand_nhash(&len, sub[2], val[0], val[1]);
+      else
+        ret = expand_substr(&len, sub[2], val[0], val[1]);
+
+      if (ret == NULL)
+        goto EXPAND_FAILED;
+      else
+        yield = string_cat(yield, &size, &ptr, ret, len);
+
+      continue;
+      }
+
     /* Handle global substitution for "sg" - like Perl's s/xxx/yyy/g operator.
     We have to save the numerical variables and restore them afterwards. */
 
@@ -2826,7 +3085,8 @@
         int value1 = 0;
         int value2 = -1;
         int *pn;
-        int sublen = Ustrlen(sub);
+        int len;
+        uschar *ret;
 
         if (arg == NULL)
           {
@@ -2873,113 +3133,20 @@
           }
         value1 *= sign;
 
-        /* For a text hash, the first argument is the length, and the
-        second is the number of characters to use, defaulting to 26. */
+        /* Perform the actual operation depending on the name. */
 
         if (name[0] == 'h')
-          {
-          if (value2 < 0) value2 = 26;
-          else if (value2 > Ustrlen(hashcodes))
-            {
-            expand_string_message =
-              string_sprintf("hash uschar count too big in \"%s\"", name);
-            goto EXPAND_FAILED;
-            }
-
-          /* Calculate the hash text. We know it is shorter than the original
-          string, so can safely place it in sub[]. */
-
-          if (value1 < sublen)
-            {
-            int c;
-            int i = 0;
-            int j = value1;
-            while ((c = (sub[j])) != 0)
-              {
-              int shift = (c + j++) & 7;
-              sub[i] ^= (c << shift) | (c >> (8-shift));
-              if (++i >= value1) i = 0;
-              }
-            for (i = 0; i < value1; i++)
-              sub[i] = hashcodes[(sub[i]) % value2];
-            }
-          else value1 = sublen;
-
-          yield = string_cat(yield, &size, &ptr, sub, value1);
-          continue;
-          }
-
-        /* Numeric hash. The first characters of the string are treated
-        as most important, and get the highest prime numbers. */
-
-        if (name[0] == 'n')
-          {
-          uschar *s = sub;
-          int i = 0;
-          unsigned long int total = 0; /* no overflow */
-
-          while (*s != 0)
-            {
-            if (i == 0) i = sizeof(prime)/sizeof(int) - 1;
-            total += prime[i--] * (unsigned int)(*s++);
-            }
-
-          /* If value2 is unset, just compute one number */
-
-          if (value2 < 0)
-            {
-            s = string_sprintf("%d", total % value1);
-            }
-
-          /* Otherwise do a div/mod hash */
-
+          ret = expand_hash(&len, sub, value1, value2);
+        else if (name[0] == 'n')
+          ret = expand_nhash(&len, sub, value1, value2);
           else
-            {
-            total = total % (value1 * value2);
-            s = string_sprintf("%d/%d", total/value2, total % value2);
-            }
+          ret = expand_substr(&len, sub, value1, value2);
 
-          yield = string_cat(yield, &size, &ptr, s, Ustrlen(s));
-          continue;
-          }
-
-        /* Otherwise we are handling a substring or subbit operation, with
-        value1 the offset and value2 the length. A negative offset positions
-        from the rhs. (For "length" and "bits" the offset is always 0.) */
-
-        if (value1 < 0)    /* substr only */
-          {
-          value1 += sublen;
-
-          /* If the position is before the start, skip to the start, unless
-          length is unset, in which case the substring is null. */
-
-          if (value1 < 0)
-            {
-            if (value2 < 0) value2 = 0; else value2 += value1;
-            value1 = 0;
-            }
-
-          /* Otherwise an unset length => characters before the value1 */
-
-          else if (value2 < 0)
-            {
-            value2 = value1;
-            value1 = 0;
-            }
-          }
-
-        /* For a non-negative offset, no length means "rest"; just set
-        it to the maximum. */
-
-        else if (value2 < 0) value2 = sublen;
-
-        /* For a substring operation, cut length down to maximum possible for
-        the offset value, and get the required characters. */
+        if (ret == NULL)
+          goto EXPAND_FAILED;
+        else
+          yield = string_cat(yield, &size, &ptr, ret, len);
 
-        if (sublen < value1 + value2) value2 = sublen - value1;
-        if (value2 > 0)
-          yield = string_cat(yield, &size, &ptr, sub + value1, value2);
         continue;
         }
 
--- doc/spec.txt~	Mon Jul 22 08:59:53 2002
+++ doc/spec.txt	Mon Sep 23 16:22:56 2002
@@ -5686,6 +5686,13 @@
    yields '99'. Two successive separators mean that the field between them is
    empty (for example, the fifth field above).
 
+${hash{<n>}{<string>}}
+
+${hash{<n>}{<m>}{<string>}}
+
+   These are the same as ${hash_<m>:<string>} and ${hash_<n>_<m>:<string>}
+   below, except that <n> and <m> are expanded before use.
+
 $header_<header name>: or $h_<header name>:
 
    Substitute the contents of the named message header line, for example
@@ -5734,6 +5741,11 @@
    In this case, the expansion is forced to fail if the condition is not true.
    The available conditions are described in section 11.6 below.
 
+${length{<n>}{<string>}}
+
+   This is the same as ${length_<n>:<string>} below, except that <n> is
+   expanded before use.
+
 ${lookup{<key>} <search type> {<file>} {<string1>} {<string2>}}
 
 ${lookup <search type> {<query>} {<string1>} {<string2>}}
@@ -5788,6 +5800,13 @@
      ${lookup nisplus {[name=$local_part],passwd.org_dir:gcos} \
        {$value}fail}
 
+${nhash{<n>}{<string>}}
+
+${nhash{<n>}{<m>}{<string>}}
+
+   These are the same as ${nhash_<n>:<string>} and ${nhash_<n>_<m>:<string>}
+   below, except that <n> and <m> are expanded before use.
+
 ${perl{<subroutine>}{<arg>}{<arg>}...}
 
    This item is available only if Exim has been built to include an embedded
@@ -5862,6 +5881,13 @@
      ${sg{1=A 4=D 3=C}{\N(\d+)=\N}{K\$1=}}
 
    yields 'K1=A K4=D K3=C'.
+
+${substr{<n>}{<string>}}
+
+${substr{<n>}{<m>}{<string>}}
+
+   These are the same as ${substr_<n>:<string>} and ${substr_<n>_<m>:<string>}
+   below, except that <n> and <m> are expanded before use.
 
 ${tr{<subject>}{<characters>}{<replacements>}}
 

