internal/inspect: handle reads after EOF in trackReader

trackReader.Read panicked if called again after a read returned io.EOF,
which can happen with readers that do not return EOF consistently, such
as a terminal after Ctrl-D. Latch the EOF instead, per the io.Reader
contract, so count stays consistent with the file size.

Fixes #719

Co-authored-by: Filippo Valsorda <hi@filippo.io>
This commit is contained in:
itsVentie
2026-08-29 00:22:02 +02:00
committed by Filippo Valsorda
co-authored by Filippo Valsorda
parent 6d29a923f3
commit 933c44d4d5
2 changed files with 42 additions and 2 deletions
+3 -2
View File
@@ -103,12 +103,13 @@ type trackReader struct {
}
func (tr *trackReader) Read(p []byte) (int, error) {
if tr.done {
return 0, io.EOF
}
n, err := tr.r.Read(p)
tr.count += int64(n)
if err == io.EOF {
tr.done = true
} else if tr.done {
panic("non-EOF read after EOF")
}
return n, err
}
+39
View File
@@ -3,6 +3,7 @@ package inspect
import (
"bytes"
"fmt"
"io"
"testing"
"filippo.io/age/internal/format"
@@ -52,6 +53,44 @@ func TestInspectTagStanzas(t *testing.T) {
}
}
// readAfterEOFReader returns io.EOF along with the last of its data, and then
// more data from subsequent Reads, like a terminal that received Ctrl-D
// followed by more input.
type readAfterEOFReader struct {
data []byte
eof bool
}
func (r *readAfterEOFReader) Read(p []byte) (int, error) {
if r.eof {
return copy(p, "\n"), nil
}
n := copy(p, r.data)
r.data = r.data[n:]
if len(r.data) == 0 {
r.eof = true
return n, io.EOF
}
return n, nil
}
func TestInspectReadAfterEOF(t *testing.T) {
f := buildFile(t, "X25519")
r := &readAfterEOFReader{data: f}
md, err := Inspect(r, -1)
if err != nil {
t.Fatalf("Inspect: %v", err)
}
if md.Version != "age-encryption.org/v1" {
t.Errorf("Version = %q, want age-encryption.org/v1", md.Version)
}
// If the reads after EOF were counted towards the file size, the extra
// bytes would show up in the payload size.
if md.Sizes.MinPayload != 0 {
t.Errorf("MinPayload = %d, want 0", md.Sizes.MinPayload)
}
}
func TestStreamOverhead(t *testing.T) {
tests := []struct {
payloadSize int64