Reject trailing record JSON without decoding a second value
This commit is contained in:
@@ -7,7 +7,6 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@@ -32,15 +31,10 @@ func LoadEvidence(path string) (Evidence, error) {
|
||||
if err != nil {
|
||||
return Evidence{}, &RecoveryError{Kind: "record_read", RecordPath: path, cause: fmt.Errorf("read record %q: %w", path, err)}
|
||||
}
|
||||
decoder := json.NewDecoder(strings.NewReader(string(data)))
|
||||
var envelope recordEnvelope
|
||||
if err := decoder.Decode(&envelope); err != nil {
|
||||
if err := json.Unmarshal(data, &envelope); err != nil {
|
||||
return Evidence{}, &RecoveryError{Kind: "record_format", RecordPath: path, cause: fmt.Errorf("decode record %q: %w", path, err)}
|
||||
}
|
||||
var trailing any
|
||||
if err := decoder.Decode(&trailing); err != io.EOF {
|
||||
return Evidence{}, &RecoveryError{Kind: "record_format", RecordPath: path, cause: fmt.Errorf("record %q contains trailing JSON", path)}
|
||||
}
|
||||
if envelope.FormatVersion != recordFormatVersion && envelope.FormatVersion != recordByteFormatVersion {
|
||||
return Evidence{}, &RecoveryError{Kind: "record_format", RecordPath: path, cause: fmt.Errorf("record %q has unsupported format version %d", path, envelope.FormatVersion)}
|
||||
}
|
||||
|
||||
@@ -138,6 +138,38 @@ func TestLoadEvidenceAcceptsPartialAndRejectsMalformedRecords(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadEvidenceRequiresOneDocumentAndPreservesSyntaxCause(t *testing.T) {
|
||||
const valid = `{"format_version":1,"evidence":{"tag":"v1.2.3","future":{}},"future":true}`
|
||||
for _, test := range []struct {
|
||||
name, data string
|
||||
valid bool
|
||||
}{
|
||||
{"single", valid, true}, {"whitespace", valid + "\n \t\r", true},
|
||||
{"first incomplete", "{", false}, {"first syntax", "x", false},
|
||||
{"second object", valid + ` {"unused":[1,2,3]}`, false},
|
||||
{"incomplete suffix", valid + " {", false}, {"invalid suffix", valid + " x", false},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
path := filepath.Join(t.TempDir(), "record.json")
|
||||
if err := os.WriteFile(path, []byte(test.data), 0600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
loaded, err := LoadEvidence(path)
|
||||
if test.valid {
|
||||
if err != nil || loaded.Tag != "v1.2.3" {
|
||||
t.Fatalf("valid record = %#v, %v", loaded, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
var recovery *RecoveryError
|
||||
var syntax *json.SyntaxError
|
||||
if !errors.As(err, &recovery) || recovery.Kind != "record_format" || !errors.As(err, &syntax) {
|
||||
t.Fatalf("lost format/parser cause: %v", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadEvidenceReadFailureHasTypedError(t *testing.T) {
|
||||
_, err := LoadEvidence(filepath.Join(t.TempDir(), "missing.json"))
|
||||
var recovery *RecoveryError
|
||||
|
||||
Reference in New Issue
Block a user