mirror of
https://github.com/FiloSottile/age.git
synced 2026-08-29 20:27:25 +00:00
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>
131 lines
3.5 KiB
Go
131 lines
3.5 KiB
Go
package inspect
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"testing"
|
|
|
|
"filippo.io/age/internal/format"
|
|
"filippo.io/age/internal/stream"
|
|
)
|
|
|
|
// buildFile serializes a header with a single stanza of the given type,
|
|
// followed by the minimal valid encrypted payload (a 16-byte stream nonce
|
|
// and a single empty ChaCha20-Poly1305 chunk).
|
|
func buildFile(t *testing.T, stanzaType string) []byte {
|
|
t.Helper()
|
|
hdr := &format.Header{
|
|
Recipients: []*format.Stanza{{Type: stanzaType}},
|
|
MAC: make([]byte, 32),
|
|
}
|
|
buf := &bytes.Buffer{}
|
|
if err := hdr.Marshal(buf); err != nil {
|
|
t.Fatalf("Header.Marshal: %v", err)
|
|
}
|
|
// Append nonce (16 bytes) + poly1305 tag for empty chunk (16 bytes).
|
|
buf.Write(make([]byte, 16+16))
|
|
return buf.Bytes()
|
|
}
|
|
|
|
func TestInspectTagStanzas(t *testing.T) {
|
|
tests := []struct {
|
|
stanzaType string
|
|
want string
|
|
}{
|
|
{stanzaType: "p256tag", want: "no"},
|
|
{stanzaType: "mlkem768p256tag", want: "yes"},
|
|
}
|
|
for _, tt := range tests {
|
|
t.Run(tt.stanzaType, func(t *testing.T) {
|
|
f := buildFile(t, tt.stanzaType)
|
|
md, err := Inspect(bytes.NewReader(f), int64(len(f)))
|
|
if err != nil {
|
|
t.Fatalf("Inspect: %v", err)
|
|
}
|
|
if got := md.Postquantum; got != tt.want {
|
|
t.Errorf("Postquantum = %q, want %q", got, tt.want)
|
|
}
|
|
if len(md.StanzaTypes) != 1 || md.StanzaTypes[0] != tt.stanzaType {
|
|
t.Errorf("StanzaTypes = %v, want [%q]", md.StanzaTypes, tt.stanzaType)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// 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
|
|
want int64
|
|
wantErr bool
|
|
}{
|
|
{payloadSize: 0, wantErr: true},
|
|
{payloadSize: 15, wantErr: true},
|
|
{payloadSize: 16, wantErr: true},
|
|
{payloadSize: 16 + 15, wantErr: true},
|
|
{payloadSize: 16 + 16, want: 16 + 16}, // empty plaintext
|
|
{payloadSize: 16 + 1 + 16, want: 16 + 16},
|
|
{payloadSize: 16 + stream.ChunkSize + 16, want: 16 + 16},
|
|
{payloadSize: 16 + stream.ChunkSize + 16 + 1, wantErr: true},
|
|
{payloadSize: 16 + stream.ChunkSize + 16 + 15, wantErr: true},
|
|
{payloadSize: 16 + stream.ChunkSize + 16 + 16, wantErr: true}, // empty final chunk
|
|
{payloadSize: 16 + stream.ChunkSize + 16 + 1 + 16, want: 16 + 16 + 16},
|
|
}
|
|
for _, tt := range tests {
|
|
name := "payloadSize=" + fmt.Sprint(tt.payloadSize)
|
|
t.Run(name, func(t *testing.T) {
|
|
got, gotErr := streamOverhead(tt.payloadSize)
|
|
if gotErr != nil {
|
|
if !tt.wantErr {
|
|
t.Errorf("streamOverhead() failed: %v", gotErr)
|
|
}
|
|
return
|
|
}
|
|
if tt.wantErr {
|
|
t.Fatal("streamOverhead() succeeded unexpectedly")
|
|
}
|
|
if got != tt.want {
|
|
t.Errorf("streamOverhead() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|