From: Martin Buck <mbuck@debian.org>
Date: Fri, 19 Jan 2024 02:32:35 +0100
Subject: Fix multi-line paste

Origin: https://github.com/lcn2/calc/commit/43fc022dc881fa014965bf6e68905d8455086028
Forwarded: https://github.com/lcn2/calc/commit/43fc022dc881fa014965bf6e68905d8455086028
Author: Martin Buck <mbuck@debian.org>
---
 hist.c | 78 +++++++++++++++++++++++++++++++++++++++++++++++-------------------
 1 file changed, 56 insertions(+), 22 deletions(-)

diff --git a/hist.c b/hist.c
index e55918b..c05c88e 100644
--- a/hist.c
+++ b/hist.c
@@ -73,6 +73,8 @@
 #include "banned.h"	/* include after system header <> includes */
 
 
+#define MIN(a,b) (((a) <= (b)) ? (a) : (b))
+
 #if !defined(USE_READLINE)
 
 E_FUNC FILE *curstream(void);
@@ -1472,37 +1474,69 @@ quit_calc(int UNUSED(ch))
  * The readline/history libs do most of the dirty work for us, so we can
  * replace hist_init() and hist_term() with dummies when using readline.
  * For hist_getline() we have to add a newline that readline removed but
- * calc expects. For hist_saveline(), we have to undo this.  hist_getline()
+ * calc expects. For hist_saveline(), we have to undo this. hist_getline()
  * also has to cope with the different memory management schemes of calc and
- * readline.
+ * readline (pointer to target buffer passed to hist_getline() vs. returned
+ * malloc()ed buffer from readline()). While doing that, we also split
+ * multi-line strings potentially returned by readline() in case of
+ * bracketed paste mode even though its documentation promises to only return
+ * single lines. For details, see https://github.com/lcn2/calc/issues/138
+ * and https://lists.gnu.org/archive/html/bug-readline/2024-01/msg00000.html
  */
 
 
 size_t
 hist_getline(char *prompt, char *buf, size_t len)
 {
-	char *line;
-
-	buf[0] = '\0';
-	line = readline(prompt);
-	if (!line) {
-		switch (conf->ctrl_d) {
-		case CTRL_D_NEVER_EOF:
-			return 0;
-		case CTRL_D_VIRGIN_EOF:
-		case CTRL_D_EMPTY_EOF:
-		default:
-			quit_calc(0);
-			not_reached();
+	STATIC char *rlbuf, *rlcur;
+
+	if (!rlbuf) {
+		rlbuf = rlcur = readline(prompt);
+		if (!rlbuf) {
+			buf[0] = '\0';
+			switch (conf->ctrl_d) {
+			case CTRL_D_NEVER_EOF:
+				return 0;
+			case CTRL_D_VIRGIN_EOF:
+			case CTRL_D_EMPTY_EOF:
+			default:
+				quit_calc(0);
+				not_reached();
+			}
 		}
 	}
-	strlcpy(buf, line, len);
-	buf[len - 2] = '\0';
-	len = strlen(buf);
-	buf[len] = '\n';
-	buf[len + 1] = '\0';
-	free(line);
-	return len + 1;
+
+	/* eol: pointer to trailing newline (if there is one) or \0 */
+	char *eol = strchr(rlcur, '\n');
+	if (!eol) {
+		eol = rlcur + strlen(rlcur);
+	}
+	/* len: length of line in target buffer including (possibly added)
+	 * newline, truncated if buffer is too small. Note that we reduce
+	 * the available buffer size by 1 so that we can safely add the
+	 * newline below.
+	 */
+	len = MIN(len - 1, (size_t)(eol - rlcur + 1));
+	strlcpy(buf, rlcur, len);
+	/* make sure we have a newline and NUL */
+	buf[len - 1] = '\n';
+	buf[len] = '\0';
+
+	/* skip over newline in readline buffer */
+	if (*eol) {
+		eol++;
+	}
+	/* prepare for next invocation: point to next line or free readline
+	 * buffer if we've reached EOL
+	 */
+	if (*eol) {
+		rlcur = eol;
+	} else {
+		free(rlbuf);
+		rlbuf = rlcur = NULL;
+	}
+
+	return len;
 }
 
 
