You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mynewt.apache.org by ja...@apache.org on 2026/10/20 23:01:51 UTC

(mynewt-newt) 02/30: newt/install: Fix panic on long answer to upgrade prompt

This is an automated email from the ASF dual-hosted git repository.

sjanc pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/mynewt-newt.git

commit 07d10d463aa3413f9545f858a10af44b9ae05377
Author: Szymon Janc <sz...@codecoup.pl>
AuthorDate: Mon Oct 19 17:46:37 2026 +0200

    newt/install: Fix panic on long answer to upgrade prompt
    
    The "Proceed? [Y/n]" prompt read the answer with bufio ReadLine. For
    a line longer than the reader buffer ReadLine returns more=true with
    a nil error, which was then passed to util.ChildNewtError(nil) and
    caused a nil pointer dereference.
    
    Read whole lines with ReadString instead, using a single reader for
    all attempts, so a long answer is treated as an invalid response.
    An answer terminated by EOF without newline is still accepted.
    
    Assisted-by: Claude:claude-opus-5-5
---
 newt/install/install.go      | 13 +++++++--
 newt/install/install_test.go | 68 ++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 78 insertions(+), 3 deletions(-)

diff --git a/newt/install/install.go b/newt/install/install.go
index 36faf4fd..2d48eea5 100644
--- a/newt/install/install.go
+++ b/newt/install/install.go
@@ -66,6 +66,7 @@ package install
 import (
 	"bufio"
 	"fmt"
+	"io"
 	"os"
 	"strings"
 
@@ -376,14 +377,20 @@ func (inst *Installer) installPrompt(vm deprepo.VersionMap, op installOp,
 		}
 	}
 
+	return askProceed(bufio.NewReader(os.Stdin))
+}
+
+// Asks the user whether to proceed and reads the answer from the specified
+// reader.  Returns true if the user wants to proceed.
+func askProceed(rd *bufio.Reader) (bool, error) {
 	for {
 		fmt.Printf("Proceed? [Y/n] ")
-		line, more, err := bufio.NewReader(os.Stdin).ReadLine()
-		if more || err != nil {
+		line, err := rd.ReadString('\n')
+		if err != nil && (err != io.EOF || len(line) == 0) {
 			return false, util.ChildNewtError(err)
 		}
 
-		trimmed := strings.ToLower(strings.TrimSpace(string(line)))
+		trimmed := strings.ToLower(strings.TrimSpace(line))
 		if len(trimmed) == 0 || strings.HasPrefix(trimmed, "y") {
 			// User wants to proceed.
 			return true, nil
diff --git a/newt/install/install_test.go b/newt/install/install_test.go
new file mode 100644
index 00000000..2d776cd4
--- /dev/null
+++ b/newt/install/install_test.go
@@ -0,0 +1,68 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *  http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package install
+
+import (
+	"bufio"
+	"strings"
+	"testing"
+)
+
+func TestAskProceed(t *testing.T) {
+	long := strings.Repeat("x", 10000)
+
+	tests := []struct {
+		name    string
+		input   string
+		proceed bool
+		err     bool
+	}{
+		{"empty", "\n", true, false},
+		{"yes", "y\n", true, false},
+		{"no", "n\n", false, false},
+		{"yes without newline", "Y", true, false},
+		{"eof", "", false, true},
+		{"invalid then no", "foo\nn\n", false, false},
+		{"long line then yes", long + "\ny\n", true, false},
+		{"long line then no", long + "\nno\n", false, false},
+		{"long yes line", "y" + long + "\n", true, false},
+		{"long line then eof", long, false, true},
+	}
+
+	for _, tc := range tests {
+		t.Run(tc.name, func(t *testing.T) {
+			rd := bufio.NewReader(strings.NewReader(tc.input))
+			proceed, err := askProceed(rd)
+			if tc.err {
+				if err == nil {
+					t.Fatalf("expected error")
+				}
+				return
+			}
+			if err != nil {
+				t.Fatalf("unexpected error: %v", err)
+			}
+			if proceed != tc.proceed {
+				t.Fatalf("expected proceed=%v, got %v",
+					tc.proceed, proceed)
+			}
+		})
+	}
+}