Fix ${readsocket } eol-replacement. Bug 2630
authorJeremy Harris <jgh146exb@wizmail.org>
Fri, 14 Aug 2020 12:09:53 +0000 (13:09 +0100)
committerJeremy Harris <jgh146exb@wizmail.org>
Fri, 14 Aug 2020 16:22:47 +0000 (17:22 +0100)
(cherry picked from commit 7f83b348ccf4cd815e9758ab9ca1012e66324e9d)

doc/doc-txt/ChangeLog
src/src/expand.c
src/src/functions.h
src/src/lookups/readsock.c
src/src/macros.h
src/src/readconf.c
src/src/string.c
test/scripts/0000-Basic/0373
test/stdout/0373

index 703f4b9ee0a896a460c71abfe03eab8ea40eda01..2d2dc1f9f25bea2d4311b0004a2f09af66de2932 100644 (file)
@@ -78,6 +78,10 @@ JH/18 Bug 2617: Fix a taint trap in parse_fix_phrase().  Previously when the
       rewrite with the "h" flag, by using the "-F" command-line option, or
       by using a "name=" option on a control=submission ACL modifier.
 
+JH/21 Bug 2630: Fix eol-replacement string for the ${readsocket } expansion.
+      Previously when a whitespace character was specified it was not inserted
+      after removing the newline.
+
 
 Exim version 4.94
 -----------------
index 791222324b6f261d35aeef8dc9ef47d0f6673b37..bb9fd79efa6f6a2202b50eb52dcee2411e360861 100644 (file)
@@ -4920,7 +4920,7 @@ while (*s != 0)
           {
           expand_string_message =
             string_sprintf("lookup of \"%s\" gave DEFER: %s",
-              string_printing2(key, FALSE), search_error_message);
+              string_printing2(key, SP_TAB), search_error_message);
           goto EXPAND_FAILED;
           }
         if (expand_setup > 0) expand_nmax = expand_setup;
@@ -5334,11 +5334,14 @@ while (*s != 0)
          while ((item = string_nextinlist(&list, &sep, NULL, 0)))
            g = string_append_listele(g, ',', item);
 
-         /* possibly plus an EOL string */
+         /* possibly plus an EOL string.  Process with escapes, to protect
+         from list-processing.  The only current user of eol= in search
+         options is the readsock expansion. */
+
          if (sub_arg[3] && *sub_arg[3])
            g = string_append_listele(g, ',',
-                 string_sprintf("eol=%s", sub_arg[3]));
-
+                 string_sprintf("eol=%s",
+                   string_printing2(sub_arg[3], SP_TAB|SP_SPACE)));
          }
 
        /* Gat a (possibly cached) handle for the connection */
index f4d1622dc205fbee34d042e19d08c4a08e9acdd8..51bb17a091520fe45cc62e0e65bf80c2d01cec7e 100644 (file)
@@ -529,7 +529,7 @@ extern int     string_is_ip_address(const uschar *, int *);
 #ifdef SUPPORT_I18N
 extern BOOL    string_is_utf8(const uschar *);
 #endif
-extern const uschar *string_printing2(const uschar *, BOOL);
+extern const uschar *string_printing2(const uschar *, int);
 extern uschar *string_split_message(uschar *);
 extern uschar *string_unprinting(uschar *);
 #ifdef SUPPORT_I18N
index c2088b7a5e2d13593d65c1e21291b10dd3e7703d..cfc9b4ad82b5a62e65d21d01d6c41d0c446f304d 100644 (file)
@@ -186,7 +186,9 @@ FILE * fp;
 gstring * yield;
 int ret = DEFER;
 
-DEBUG(D_lookup) debug_printf_indent("readsock: file=\"%s\" key=\"%s\" len=%d opts=\"%s\"\n", filename, keystring, length, opts);
+DEBUG(D_lookup)
+  debug_printf_indent("readsock: file=\"%s\" key=\"%s\" len=%d opts=\"%s\"\n",
+    filename, keystring, length, opts);
 
 /* Parse options */
 
@@ -200,7 +202,7 @@ if (opts) for (uschar * s; s = string_nextinlist(&opts, &sep, NULL, 0); )
     lf.do_tls = TRUE;
 #endif
   else if (Ustrncmp(s, "eol=", 4) == 0)
-    eol = s + 4;
+    eol = string_unprinting(s + 4);
   else if (Ustrcmp(s, "cache=yes") == 0)
     lf.cache = TRUE;
   else if (Ustrcmp(s, "send=no") == 0)
index a507bbf836914a316544b879d0a0d4b1e2892a4f..2378773cb2517115b77b3f05affb4ed01bad72e1 100644 (file)
@@ -41,9 +41,11 @@ manipulate them. */
 
 
 /* For almost all calls to convert things to printing characters, we want to
-allow tabs. A macro just makes life a bit easier. */
+allow tabs & spaces. A macro just makes life a bit easier. */
 
-#define string_printing(s) string_printing2((s), TRUE)
+#define string_printing(s) string_printing2((s), 0)
+#define SP_TAB         BIT(0)
+#define SP_SPACE       BIT(1)
 
 
 /* We need a special return code for "no recipients and failed to send an error
index 0d0769c88d3f04c597058dda1cd72a1d57d47f68..948fa2403b04252cf2f56190353a5b5d1a9ce3f8 100644 (file)
@@ -1546,7 +1546,7 @@ if (flags & opt_fn_print)
   {
   if (flags & opt_fn_print_label) printf("%s = ", name);
   printf("%s\n", smtp_receive_timeout_s
-    ? string_printing2(smtp_receive_timeout_s, FALSE)
+    ? string_printing2(smtp_receive_timeout_s, SP_TAB)
     : readconf_printtime(smtp_receive_timeout));
   }
 else if (*str == '$')
@@ -2463,7 +2463,7 @@ switch(ol->type & opt_mask)
   case opt_rewrite:        /* Show the text value */
     s = *(USS value);
     if (!no_labels) printf("%s = ", name);
-    printf("%s\n", s ? string_printing2(s, FALSE) : US"");
+    printf("%s\n", s ? string_printing2(s, SP_TAB) : US"");
     break;
 
   case opt_int:
index 5acee1b00100600bbfd03bb15231f1b62d0c3328..f91a6a4280571426682c5c7ca85b9e28d4441e6c 100644 (file)
@@ -281,17 +281,17 @@ return ch;
 /* This function is called for critical strings. It checks for any
 non-printing characters, and if any are found, it makes a new copy
 of the string with suitable escape sequences. It is most often called by the
-macro string_printing(), which sets allow_tab TRUE.
+macro string_printing(), which sets flags to 0.
 
 Arguments:
   s             the input string
-  allow_tab     TRUE to allow tab as a printing character
+  flags                Bit 0: convert tabs.  Bit 1: convert spaces.
 
 Returns:        string with non-printers encoded as printing sequences
 */
 
 const uschar *
-string_printing2(const uschar *s, BOOL allow_tab)
+string_printing2(const uschar *s, int flags)
 {
 int nonprintcount = 0;
 int length = 0;
@@ -301,7 +301,10 @@ uschar *ss, *tt;
 while (*t != 0)
   {
   int c = *t++;
-  if (!mac_isprint(c) || (!allow_tab && c == '\t')) nonprintcount++;
+  if (  !mac_isprint(c)
+     || flags & SP_TAB && c == '\t'
+     || flags & SP_SPACE && c == ' '
+     ) nonprintcount++;
   length++;
   }
 
@@ -310,17 +313,19 @@ if (nonprintcount == 0) return s;
 /* Get a new block of store guaranteed big enough to hold the
 expanded string. */
 
-ss = store_get(length + nonprintcount * 3 + 1, is_tainted(s));
+tt = ss = store_get(length + nonprintcount * 3 + 1, is_tainted(s));
 
 /* Copy everything, escaping non printers. */
 
-t = s;
-tt = ss;
-
-while (*t != 0)
+for (t = s; *t; )
   {
   int c = *t;
-  if (mac_isprint(c) && (allow_tab || c != '\t')) *tt++ = *t++; else
+  if (  mac_isprint(c)
+     && (!(flags & SP_TAB) || c != '\t')
+     && (!(flags & SP_SPACE) || c != ' ')
+     )
+    *tt++ = *t++;
+  else
     {
     *tt++ = '\\';
     switch (*t)
@@ -947,7 +952,10 @@ else
     s = ss;
     if (!*s || *++s != sep || sep_is_special) break;
     }
-  while (g->ptr > 0 && isspace(g->s[g->ptr-1])) g->ptr--;
+  /* while (g->ptr > 0 && isspace(g->s[g->ptr-1])) g->ptr--; */
+  while (  g->ptr > 0 && isspace(g->s[g->ptr-1])
+       && (g->ptr == 1 || g->s[g->ptr-2] != '\\') )
+    g->ptr--;
   buffer = string_from_gstring(g);
   gstring_release_unused(g);
   }
index 5d8bbee880eaf3e361246374221cb755e8b68011..37c98e0ab3e93b47d4643cb761803ec962d698f3 100644 (file)
@@ -60,7 +60,7 @@ quit
 #
 # Tests of IPv4 sockets
 #
-server PORT_S 11
+server PORT_S 17
 QUERY-1
 >LF>ANSWER-1
 >*eof
@@ -89,6 +89,24 @@ QUERY-10
 >*eof
 >LF>ANSWER-11
 >*eof
+QUERY-12
+>>ANSWER-12\x0d\x0aANSWER-12\x0d\x0a
+>*eof
+QUERY-13
+>>ANSWER-13\x0d\x0aANSWER-13\x0d\x0a
+>*eof
+QUERY-14
+>>ANSWER-14\x0d\x0aANSWER-14\x0d\x0a
+>*eof
+QUERY-15
+>>ANSWER-15\x0d\x0aANSWER-15\x0d\x0a
+>*eof
+QUERY-16
+>>ANSWER-16\x0d\x0aANSWER-16\x0d\x0a
+>*eof
+QUERY-17
+>>ANSWER-17\x0d\x0aANSWER-17\x0d\x0a
+>*eof
 ****
 millisleep 500
 exim -be
@@ -104,6 +122,14 @@ ipv4 cases
 9  sock error    >>${readsocket{inet:127.0.0.1:PORT_S}{QUERY-9\n}{1s}{}{sock error}}<<
 10 ANSWER-10\\n     >>${readsocket{inet:badloop:PORT_S}{QUERY-10\n}}<<
 11 ANSWER-11     >>${readsocket{inet:thisloop:PORT_S}{QUERY-11\n}{2s:shutdown=no}}<<
+
+eol-replacement arg
+12 ANSWER-12x2  (no arg)     >>${escape:${readsocket{inet:127.0.0.1:PORT_S}{QUERY-12\n}{2s}}}<<
+13 ANSWER-13x2  (empty arg)  >>${escape:${readsocket{inet:127.0.0.1:PORT_S}{QUERY-13\n}{2s}{}}}<<
+14 ANSWER-14x2  X            >>${escape:${readsocket{inet:127.0.0.1:PORT_S}{QUERY-14\n}{2s}{X}}}<<
+15 ANSWER-15x2  XYZZY        >>${escape:${readsocket{inet:127.0.0.1:PORT_S}{QUERY-15\n}{2s}{XYZZY}}}<<
+16 ANSWER-16x2  (space)      >>${escape:${readsocket{inet:127.0.0.1:PORT_S}{QUERY-16\n}{2s}{ }}}<<
+17 ANSWER-17x2  (newline)    >>${escape:${readsocket{inet:127.0.0.1:PORT_S}{QUERY-17\n}{2s}{\n}}}<<
 ****
 #
 exim -be
index 513f36468c29ef0517c8574b02d66d30767049cd..1e97d0decc859d5e203e300f2bd9d206f8055fc4 100644 (file)
 <<
 > 11 ANSWER-11     >><<
 > 
+> eol-replacement arg
+> 12 ANSWER-12x2  (no arg)     >>ANSWER-12\r\nANSWER-12\r\n<<
+> 13 ANSWER-13x2  (empty arg)  >>ANSWER-13\r\nANSWER-13\r\n<<
+> 14 ANSWER-14x2  X            >>ANSWER-14\rXANSWER-14\rX<<
+> 15 ANSWER-15x2  XYZZY        >>ANSWER-15\rXYZZYANSWER-15\rXYZZY<<
+> 16 ANSWER-16x2  (space)      >>ANSWER-16\r ANSWER-16\r <<
+> 17 ANSWER-17x2  (newline)    >>ANSWER-17\r\nANSWER-17\r\n<<
+> 
 > Failed: failed to connect to any address for 127.0.0.1: Connection refused
 > 
 > caching of response value
@@ -144,6 +152,36 @@ Listening on port 1224 ...
 Connection request from [ip4.ip4.ip4.ip4]
 >LF>ANSWER-11
 >*eof
+Listening on port 1224 ... 
+Connection request from [127.0.0.1]
+QUERY-12
+>>ANSWER-12\x0d\x0aANSWER-12\x0d\x0a
+>*eof
+Listening on port 1224 ... 
+Connection request from [127.0.0.1]
+QUERY-13
+>>ANSWER-13\x0d\x0aANSWER-13\x0d\x0a
+>*eof
+Listening on port 1224 ... 
+Connection request from [127.0.0.1]
+QUERY-14
+>>ANSWER-14\x0d\x0aANSWER-14\x0d\x0a
+>*eof
+Listening on port 1224 ... 
+Connection request from [127.0.0.1]
+QUERY-15
+>>ANSWER-15\x0d\x0aANSWER-15\x0d\x0a
+>*eof
+Listening on port 1224 ... 
+Connection request from [127.0.0.1]
+QUERY-16
+>>ANSWER-16\x0d\x0aANSWER-16\x0d\x0a
+>*eof
+Listening on port 1224 ... 
+Connection request from [127.0.0.1]
+QUERY-17
+>>ANSWER-17\x0d\x0aANSWER-17\x0d\x0a
+>*eof
 End of script
 Listening on TESTSUITE/test-socket ... 
 Connection request