From 1cf8c568b74147f5b2b804c08f67f1dc1cab6a76 Mon Sep 17 00:00:00 2001
From: Grisha Levit <grishalevit@gmail.com>
Date: Thu, 21 Apr 2022 04:58:48 -0400
Subject: [PATCH] read: non-raw-mode fixes

This patches addresses a few issues with `read' when not in raw mode.

If the last character read was an (unescaped) backslash, store it as
such instead of as a CTLESC.  Avoids:

$ printf '\\' | { read; echo "${REPLY@Q}"; }
bash: DEBUG warning: dequote_string: string with bare CTLESC
$'\001'

If an escaped null byte is read, skip it as we do with an unescaped one,
instead of adding it to input_string.  Avoids:

$ printf 'A\\\0B\nC\n' | while read; do echo "${REPLY@Q}"; done
'A'
'C'

$ printf '\\\0' | { read; echo "${REPLY@Q}"; }
bash: DEBUG warning: dequote_string: string with bare CTLESC
$'\001'     # even after fix for first issue

If IFS contains \177 and the input consists of only backslash-newline
pairs and a sole \177, prevent the bare CTLNUL from being turned into an
empty string.  Avoids:

$ printf '\\\n\177' | { IFS=$'\177' read; echo "${REPLY@Q}"; }
''
---
 builtins/read.def | 16 +++++++++++-----
 1 file changed, 11 insertions(+), 5 deletions(-)

diff --git a/builtins/read.def b/builtins/read.def
index 5b2621fe..84878699 100644
--- a/builtins/read.def
+++ b/builtins/read.def
@@ -404,7 +404,7 @@ read_builtin (WORD_LIST *list)
   input_string[0] = '\0';
 
   pass_next = 0;	/* Non-zero signifies last char was backslash. */
-  saw_escape = 0;	/* Non-zero signifies that we saw an escape char */
+  saw_escape = 0;	/* Index+1 of when we last saw an escape char. */
 
   /* More input and options validation */
   if (nflag == 1 && nchars == 0)
@@ -751,11 +751,11 @@ read_builtin (WORD_LIST *list)
       if (pass_next)
 	{
 	  pass_next = 0;
-	  if (c == '\n')
+	  if (c == '\n' || c == '\0')
 	    {
 	      if (skip_ctlesc == 0 && i > 0)
 		i--;		/* back up over the CTLESC */
-	      if (interactive && input_is_tty && raw == 0)
+	      if (interactive && input_is_tty && raw == 0 && c == '\n')
 		print_ps2 = 1;
 	    }
 	  else
@@ -769,8 +769,8 @@ read_builtin (WORD_LIST *list)
 	  pass_next++;
 	  if (skip_ctlesc == 0)
 	    {
-	      saw_escape++;
 	      input_string[i++] = CTLESC;
+	      saw_escape=i;
 	    }
 	  continue;
 	}
@@ -783,8 +783,8 @@ read_builtin (WORD_LIST *list)
 
       if ((skip_ctlesc == 0 && c == CTLESC) || (skip_ctlnul == 0 && c == CTLNUL))
 	{
-	  saw_escape++;
 	  input_string[i++] = CTLESC;
+	  saw_escape=i;
 	}
 
 add_char:
@@ -825,6 +825,12 @@ add_char:
       if (nchars > 0 && nr >= nchars)
 	break;
     }
+
+  if (i && saw_escape == i && input_string[i-1] == CTLESC)
+    input_string[i-1] = '\\';	/* Preserve trailing backslash */
+  else if (skip_ctlnul && i == 1 & saw_escape == 1 && input_string[0] == CTLNUL)
+    saw_escape = 0;		/* Avoid dequoting bare CTLNUL */
+
   input_string[i] = '\0';
   check_read_timeout ();
 
-- 
2.41.0

