tidying
[exim.git] / src / src / expand.c
index cd753ef0615e402856dea2b04c10719eee397623..f32425b3f8bc8fb959ca971f1beb8e6674be97d9 100644 (file)
@@ -1133,20 +1133,20 @@ Returns:    NULL if the subfield was not found, or
 */
 
 static uschar *
-expand_getkeyed(uschar *key, const uschar *s)
+expand_getkeyed(uschar * key, const uschar * s)
 {
 int length = Ustrlen(key);
 while (isspace(*s)) s++;
 
 /* Loop to search for the key */
 
-while (*s != 0)
+while (*s)
   {
   int dkeylength;
-  uschar *data;
-  const uschar *dkey = s;
+  uschar * data;
+  const uschar * dkey = s;
 
-  while (*s != 0 && *s != '=' && !isspace(*s)) s++;
+  while (*s && *s != '=' && !isspace(*s)) s++;
   dkeylength = s - dkey;
   while (isspace(*s)) s++;
   if (*s == '=') while (isspace((*(++s))));
@@ -1257,17 +1257,17 @@ return fieldtext;
 static uschar *
 expand_getlistele(int field, const uschar * list)
 {
-const uschar * tlist= list;
-int sep= 0;
+const uschar * tlist = list;
+int sep = 0;
 uschar dummy;
 
-if(field<0)
+if (field < 0)
   {
-  for(field++; string_nextinlist(&tlist, &sep, &dummy, 1); ) field++;
-  sep= 0;
+  for (field++; string_nextinlist(&tlist, &sep, &dummy, 1); ) field++;
+  sep = 0;
   }
-if(field==0) return NULL;
-while(--field>0 && (string_nextinlist(&list, &sep, &dummy, 1))) ;
+if (field == 0) return NULL;
+while (--field > 0 && (string_nextinlist(&list, &sep, &dummy, 1))) ;
 return string_nextinlist(&list, &sep, NULL, 0);
 }
 
@@ -1687,7 +1687,7 @@ else
   return g;
 
 if (sender_host_address)
-  g = string_append(g, 2, US" smtp.client-ip=", sender_host_address);
+  g = string_append(g, 2, US" smtp.remote-ip=", sender_host_address);
 return g;
 }
 
@@ -3849,6 +3849,87 @@ return x;
 
 
 
+/* Return pointer to dewrapped string, with enclosing specified chars removed.
+The given string is modified on return.  Leading whitespace is skipped while
+looking for the opening wrap character, then the rest is scanned for the trailing
+(non-escaped) wrap character.  A backslash in the string will act as an escape.
+
+A nul is written over the trailing wrap, and a pointer to the char after the
+leading wrap is returned.
+
+Arguments:
+  s    String for de-wrapping
+  wrap  Two-char string, the first being the opener, second the closer wrapping
+        character
+Return:
+  Pointer to de-wrapped string, or NULL on error (with expand_string_message set).
+*/
+
+static uschar *
+dewrap(uschar * s, const uschar * wrap)
+{
+uschar * p = s;
+unsigned depth = 0;
+BOOL quotesmode = wrap[0] == wrap[1];
+
+while (isspace(*p)) p++;
+
+if (*p == *wrap)
+  {
+  s = ++p;
+  wrap++;
+  while (*p)
+    {
+    if (*p == '\\') p++;
+    else if (!quotesmode && *p == wrap[-1]) depth++;
+    else if (*p == *wrap)
+      if (depth == 0)
+       {
+       *p = '\0';
+       return s;
+       }
+      else
+       depth--;
+    p++;
+    }
+  }
+expand_string_message = string_sprintf("missing '%c'", *wrap);
+return NULL;
+}
+
+
+/* Pull off the leading array or object element, returning
+a copy in an allocated string.  Update the list pointer.
+
+The element may itself be an abject or array.
+*/
+
+uschar *
+json_nextinlist(const uschar ** list)
+{
+unsigned array_depth = 0, object_depth = 0;
+const uschar * s = *list, * item;
+
+while (isspace(*s)) s++;
+
+for (item = s;
+     *s && (*s != ',' || array_depth != 0 || object_depth != 0);
+     s++)
+  switch (*s)
+    {
+    case '[': array_depth++; break;
+    case ']': array_depth--; break;
+    case '{': object_depth++; break;
+    case '}': object_depth--; break;
+    }
+*list = *s ? s+1 : s;
+item = string_copyn(item, s - item);
+DEBUG(D_expand) debug_printf_indent("  json ele: '%s'\n", item);
+return US item;
+}
+
+
+
 /*************************************************
 *                 Expand string                  *
 *************************************************/
@@ -3925,11 +4006,15 @@ BOOL resetok = TRUE;
 
 expand_level++;
 DEBUG(D_expand)
-  debug_printf_indent(UTF8_DOWN_RIGHT "%s: %s\n",
-    skipping
-    ? UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ "scanning"
-    : "considering",
-    string);
+  DEBUG(D_noutf8)
+    debug_printf_indent("/%s: %s\n",
+      skipping ? "---scanning" : "considering", string);
+  else
+    debug_printf_indent(UTF8_DOWN_RIGHT "%s: %s\n",
+      skipping
+      ? UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ "scanning"
+      : "considering",
+      string);
 
 f.expand_string_forcedfail = FALSE;
 expand_string_message = US"";
@@ -4218,15 +4303,21 @@ while (*s != 0)
       if (next_s == NULL) goto EXPAND_FAILED;  /* message already set */
 
       DEBUG(D_expand)
-       {
-        debug_printf_indent(UTF8_VERT_RIGHT UTF8_HORIZ UTF8_HORIZ
-         "condition: %.*s\n",
-         (int)(next_s - s), s);
-        debug_printf_indent(UTF8_VERT_RIGHT UTF8_HORIZ UTF8_HORIZ
-         UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ
-         "result: %s\n",
-         cond ? "true" : "false");
-       }
+       DEBUG(D_noutf8)
+         {
+         debug_printf_indent("|--condition: %.*s\n", (int)(next_s - s), s);
+         debug_printf_indent("|-----result: %s\n", cond ? "true" : "false");
+         }
+       else
+         {
+         debug_printf_indent(UTF8_VERT_RIGHT UTF8_HORIZ UTF8_HORIZ
+           "condition: %.*s\n",
+           (int)(next_s - s), s);
+         debug_printf_indent(UTF8_VERT_RIGHT UTF8_HORIZ UTF8_HORIZ
+           UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ
+           "result: %s\n",
+           cond ? "true" : "false");
+         }
 
       s = next_s;
 
@@ -5554,6 +5645,16 @@ while (*s != 0)
       uschar *sub[3];
       int save_expand_nmax =
         save_expand_strings(save_expand_nstring, save_expand_nlength);
+      enum {extract_basic, extract_json} fmt = extract_basic;
+
+      while (isspace(*s)) s++;
+
+      /* Check for a format-variant specifier */
+
+      if (*s != '{')                                   /*}*/
+       {
+       if (Ustrncmp(s, "json", 4) == 0) {fmt = extract_json; s += 4;}
+       }
 
       /* While skipping we cannot rely on the data for expansions being
       available (eg. $item) hence cannot decide on numeric vs. keyed.
@@ -5561,11 +5662,10 @@ while (*s != 0)
 
       if (skipping)
        {
-        while (isspace(*s)) s++;
-        for (j = 5; j > 0 && *s == '{'; j--)
+        for (j = 5; j > 0 && *s == '{'; j--)                   /*'}'*/
          {
           if (!expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok))
-           goto EXPAND_FAILED;                                 /*{*/
+           goto EXPAND_FAILED;                                 /*'{'*/
           if (*s++ != '}')
            {
            expand_string_message = US"missing '{' for arg of extract";
@@ -5573,13 +5673,13 @@ while (*s != 0)
            }
          while (isspace(*s)) s++;
          }
-       if (  Ustrncmp(s, "fail", 4) == 0
+       if (  Ustrncmp(s, "fail", 4) == 0                       /*'{'*/
           && (s[4] == '}' || s[4] == ' ' || s[4] == '\t' || !s[4])
           )
          {
          s += 4;
          while (isspace(*s)) s++;
-         }
+         }                                                     /*'{'*/
        if (*s != '}')
          {
          expand_string_message = US"missing '}' closing extract";
@@ -5589,11 +5689,11 @@ while (*s != 0)
 
       else for (i = 0, j = 2; i < j; i++) /* Read the proper number of arguments */
         {
-        while (isspace(*s)) s++;
-        if (*s == '{')                                                 /*}*/
+       while (isspace(*s)) s++;
+        if (*s == '{')                                                 /*'}'*/
           {
           sub[i] = expand_string_internal(s+1, TRUE, &s, skipping, TRUE, &resetok);
-          if (sub[i] == NULL) goto EXPAND_FAILED;              /*{*/
+          if (sub[i] == NULL) goto EXPAND_FAILED;              /*'{'*/
           if (*s++ != '}')
            {
            expand_string_message = string_sprintf(
@@ -5604,7 +5704,7 @@ while (*s != 0)
           /* After removal of leading and trailing white space, the first
           argument must not be empty; if it consists entirely of digits
           (optionally preceded by a minus sign), this is a numerical
-          extraction, and we expect 3 arguments. */
+          extraction, and we expect 3 arguments (normal) or 2 (json). */
 
           if (i == 0)
             {
@@ -5635,7 +5735,7 @@ while (*s != 0)
            if (*p == 0)
              {
              field_number *= x;
-             j = 3;               /* Need 3 args */
+             if (fmt != extract_json) j = 3;               /* Need 3 args */
              field_number_set = TRUE;
              }
             }
@@ -5651,9 +5751,83 @@ while (*s != 0)
       /* Extract either the numbered or the keyed substring into $value. If
       skipping, just pretend the extraction failed. */
 
-      lookup_value = skipping? NULL : field_number_set?
-        expand_gettokened(field_number, sub[1], sub[2]) :
-        expand_getkeyed(sub[0], sub[1]);
+      if (skipping)
+       lookup_value = NULL;
+      else switch (fmt)
+       {
+       case extract_basic:
+         lookup_value = field_number_set
+           ? expand_gettokened(field_number, sub[1], sub[2])
+           : expand_getkeyed(sub[0], sub[1]);
+         break;
+
+       case extract_json:
+         {
+         uschar * s, * item;
+         const uschar * list;
+
+         /* Array: Bracket-enclosed and comma-separated.
+         Object: Brace-enclosed, comma-sep list of name:value pairs */
+
+         if (!(s = dewrap(sub[1], field_number_set ? US"[]" : US"{}")))
+           {
+           expand_string_message =
+             string_sprintf("%s wrapping %s for extract json",
+               expand_string_message,
+               field_number_set ? "array" : "object");
+           goto EXPAND_FAILED_CURLY;
+           }
+
+         list = s;
+         if (field_number_set)
+           {
+           if (field_number <= 0)
+             {
+             expand_string_message = US"first argument of \"extract\" must "
+               "be greater than zero";
+             goto EXPAND_FAILED;
+             }
+           while (field_number > 0 && (item = json_nextinlist(&list)))
+             field_number--;
+           s = item;
+           lookup_value = s;
+           while (*s) s++;
+           while (--s >= lookup_value && isspace(*s)) *s = '\0';
+           }
+         else
+           {
+           lookup_value = NULL;
+           while ((item = json_nextinlist(&list)))
+             {
+             /* Item is:  string name-sep value.  string is quoted.
+             Dequote the string and compare with the search key. */
+
+             if (!(item = dewrap(item, US"\"\"")))
+               {
+               expand_string_message =
+                 string_sprintf("%s wrapping string key for extract json",
+                   expand_string_message);
+               goto EXPAND_FAILED_CURLY;
+               }
+             if (Ustrcmp(item, sub[0]) == 0)   /*XXX should be a UTF8-compare */
+               {
+               s = item + Ustrlen(item) + 1;
+               while (isspace(*s)) s++;
+               if (*s != ':')
+                 {
+                 expand_string_message = string_sprintf(
+                   "missing object value-separator for extract json");
+                 goto EXPAND_FAILED_CURLY;
+                 }
+               s++;
+               while (isspace(*s)) s++;
+               lookup_value = s;
+               break;
+               }
+             }
+           }
+         }
+       }
 
       /* If no string follows, $value gets substituted; otherwise there can
       be yes/no strings, as for lookup or if. */
@@ -5753,7 +5927,7 @@ while (*s != 0)
       /* Extract the numbered element into $value. If
       skipping, just pretend the extraction failed. */
 
-      lookup_value = skipping? NULL : expand_getlistele(field_number, sub[1]);
+      lookup_value = skipping ? NULL : expand_getlistele(field_number, sub[1]);
 
       /* If no string follows, $value gets substituted; otherwise there can
       be yes/no strings, as for lookup or if. */
@@ -7667,19 +7841,28 @@ if (resetok) store_reset(yield->s + (yield->size = yield->ptr + 1));
 else if (resetok_p) *resetok_p = FALSE;
 
 DEBUG(D_expand)
-  {
-  debug_printf_indent(UTF8_VERT_RIGHT UTF8_HORIZ UTF8_HORIZ
-    "expanding: %.*s\n",
-    (int)(s - string), string);
-  debug_printf_indent("%s"
-    UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ
-    "result: %s\n",
-    skipping ? UTF8_VERT_RIGHT : UTF8_UP_RIGHT,
-    yield->s);
-  if (skipping)
-    debug_printf_indent(UTF8_UP_RIGHT UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ
-      "skipping: result is not used\n");
-  }
+  DEBUG(D_noutf8)
+    {
+    debug_printf_indent("|--expanding: %.*s\n", (int)(s - string), string);
+    debug_printf_indent("%sresult: %s\n",
+      skipping ? "|-----" : "\\_____", yield->s);
+    if (skipping)
+      debug_printf_indent("\\___skipping: result is not used\n");
+    }
+  else
+    {
+    debug_printf_indent(UTF8_VERT_RIGHT UTF8_HORIZ UTF8_HORIZ
+      "expanding: %.*s\n",
+      (int)(s - string), string);
+    debug_printf_indent("%s"
+      UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ
+      "result: %s\n",
+      skipping ? UTF8_VERT_RIGHT : UTF8_UP_RIGHT,
+      yield->s);
+    if (skipping)
+      debug_printf_indent(UTF8_UP_RIGHT UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ
+       "skipping: result is not used\n");
+    }
 expand_level--;
 return yield->s;
 
@@ -7701,16 +7884,25 @@ that is a bad idea, because expand_string_message is in dynamic store. */
 EXPAND_FAILED:
 if (left) *left = s;
 DEBUG(D_expand)
-  {
-  debug_printf_indent(UTF8_VERT_RIGHT "failed to expand: %s\n",
-    string);
-  debug_printf_indent("%s" UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ
-    "error message: %s\n",
-    f.expand_string_forcedfail ? UTF8_VERT_RIGHT : UTF8_UP_RIGHT,
-    expand_string_message);
-  if (f.expand_string_forcedfail)
-    debug_printf_indent(UTF8_UP_RIGHT "failure was forced\n");
-  }
+  DEBUG(D_noutf8)
+    {
+    debug_printf_indent("|failed to expand: %s\n", string);
+    debug_printf_indent("%serror message: %s\n",
+      f.expand_string_forcedfail ? "|---" : "\\___", expand_string_message);
+    if (f.expand_string_forcedfail)
+      debug_printf_indent("\\failure was forced\n");
+    }
+  else
+    {
+    debug_printf_indent(UTF8_VERT_RIGHT "failed to expand: %s\n",
+      string);
+    debug_printf_indent("%s" UTF8_HORIZ UTF8_HORIZ UTF8_HORIZ
+      "error message: %s\n",
+      f.expand_string_forcedfail ? UTF8_VERT_RIGHT : UTF8_UP_RIGHT,
+      expand_string_message);
+    if (f.expand_string_forcedfail)
+      debug_printf_indent(UTF8_UP_RIGHT "failure was forced\n");
+    }
 if (resetok_p && !resetok) *resetok_p = FALSE;
 expand_level--;
 return NULL;