BUG/MINOR: cli: avoid O(bufsize) parsing cost on pipelined commands

Sending pipelined commands on the CLI using a semi-colon as a delimiter
has a cost that grows linearly with the buffer size, because co_getline()
is called for each word and looks up a '\n' in the whole buffer while
copying its contents into a temporary buffer.

This causes huge parsing delays, for example 3s for 100k "show version"
versus 110ms if parsed only once for a default 16k buffer.

This patch makes use of the new co_getdelim() function to support both
an LF and a semi-colon as delimiters so that it's no more needed to parse
the whole buffer, and that commands are instantly retrieved. We still
need to rely on co_getline() in payload mode as escapes and semi-colons
are not used there.

It should likely be backported where CLI processing speed matters, but
will require to also backport previous patch "MINOR: channel: add new
function co_getdelim() to support multiple delimiters". It's worth noting
that backporting it without "MEDIUM: cli: yield between each pipelined
command" would significantly increase the ratio of disconnections caused
by empty request buffers, for the sole reason that the currently slow
parsing grants more time to request data to come in. As such it would
be better to backport the patch above before taking this one.

(cherry picked from commit 0011c251448929180216761c760a552c81162226)
Signed-off-by: Willy Tarreau <w@1wt.eu>
(cherry picked from commit 99d12ea4f2e28fd9bcae1b4505c3f9f533161c5c)
Signed-off-by: Willy Tarreau <w@1wt.eu>
diff --git a/src/cli.c b/src/cli.c
index 454347a..503c980 100644
--- a/src/cli.c
+++ b/src/cli.c
@@ -877,9 +877,20 @@
 				break;
 			}
 
-			/* '- 1' is to ensure a null byte can always be inserted at the end */
-			reql = co_getline(si_oc(si), str,
-					  appctx->chunk->size - appctx->chunk->data - 1);
+			/* payload doesn't take escapes nor does it end on semi-colons, so
+			 * we use the regular getline. Normal mode however must stop on
+			 * LFs and semi-colons that are not prefixed by a backslash. Note
+			 * that we reserve one byte at the end to insert a trailing nul byte.
+			 */
+
+			if (appctx->st1 & APPCTX_CLI_ST1_PAYLOAD)
+				reql = co_getline(si_oc(si), str,
+				                  appctx->chunk->size - appctx->chunk->data - 1);
+			else
+				reql = co_getdelim(si_oc(si), str,
+				                   appctx->chunk->size - appctx->chunk->data - 1,
+				                   "\n;", '\\');
+
 			if (reql <= 0) { /* closed or EOL not found */
 				if (reql == 0)
 					break;