From 7ea9572f033308a7fae6e58d6eb208039ff7dfb7 Mon Sep 17 00:00:00 2001 From: jochen Date: Thu, 5 Nov 2026 14:40:09 +0100 Subject: [PATCH] =?UTF-8?q?keys:=20the=20version=20check,=20CheckName=20an?= =?UTF-8?q?d=20IsHex64=20move=20here=20from=20the=20Delivery=20domain's=20?= =?UTF-8?q?kept=20package,=20since=20Delivery=20and=20Identity=20and=20acc?= =?UTF-8?q?ess=20share=20them=20(ADR=200634=20=C2=A71,=20=C2=A74;=20issue?= =?UTF-8?q?=201966)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i --- go/keys/keys.go | 39 +++++++++++++++++++++++++++++++++++++++ go/keys/keys_test.go | 21 +++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/go/keys/keys.go b/go/keys/keys.go index 489b37c..d80aae5 100644 --- a/go/keys/keys.go +++ b/go/keys/keys.go @@ -5,6 +5,8 @@ package keys import ( "encoding/hex" + "encoding/json" + "errors" "fmt" "regexp" "strings" @@ -15,6 +17,43 @@ const Version = 1 var nameOK = regexp.MustCompile(`^[-/_=a-zA-Z0-9]+$`) +// ErrUnknownVersion is what CheckVersion returns for a value whose version is missing or not Version. Test it with +// errors.Is. +var ErrUnknownVersion = errors.New("keys: unknown value version") + +// CheckVersion refuses a value with no version or one this reader does not read. +func CheckVersion(data []byte) error { + var probe struct { + V *int `json:"v"` + } + if err := json.Unmarshal(data, &probe); err != nil { + return fmt.Errorf("keys: value is not JSON: %w", err) + } + if probe.V == nil { + return fmt.Errorf("%w: no version in the value, this reader reads %d", ErrUnknownVersion, Version) + } + if *probe.V != Version { + return fmt.Errorf("%w: the value is version %d, this reader reads %d", ErrUnknownVersion, *probe.V, Version) + } + return nil +} + +// CheckName refuses a name that is empty or holds a dot, a wildcard, whitespace or a character a bus key refuses. +func CheckName(kind, name string) error { + if name == "" { + return fmt.Errorf("keys: the %s name is empty", kind) + } + if !nameOK.MatchString(name) { + return fmt.Errorf("keys: the %s name %q holds a dot, a wildcard, whitespace or a character a key refuses", kind, name) + } + return nil +} + +var hex64 = regexp.MustCompile(`^[0-9a-f]{64}$`) + +// IsHex64 says whether s is 64 lowercase hex digits. +func IsHex64(s string) bool { return hex64.MatchString(s) } + // EscapeName is name as a key part: a character outside letters, digits, `-`, `/` and `_` is written `=` and its // two lowercase hex digits per byte, and `=` itself is escaped, so any non-empty name keys and a name of allowed // characters only keys as it is. UnescapeName is its inverse. diff --git a/go/keys/keys_test.go b/go/keys/keys_test.go index 25ed7f3..2f0a962 100644 --- a/go/keys/keys_test.go +++ b/go/keys/keys_test.go @@ -1,6 +1,7 @@ package keys import ( + "errors" "strings" "testing" ) @@ -27,3 +28,23 @@ func TestNamesEscapeIntoKeyPartsAndBack(t *testing.T) { t.Errorf("Version = %d: a kept value's readers read the one version", Version) } } + +func TestAKeptValueIsReadOnlyAtItsVersionAndAKeyPartOnlyOfKeyCharacters(t *testing.T) { + for data, ok := range map[string]bool{`{"v":1}`: true, `{"v":2}`: false, `{}`: false} { + err := CheckVersion([]byte(data)) + if (err == nil) != ok || (!ok && !errors.Is(err, ErrUnknownVersion)) { + t.Errorf("CheckVersion(%s) = %v", data, err) + } + } + if CheckVersion([]byte(`nope`)) == nil { + t.Error("a value that is no JSON is read") + } + for name, ok := range map[string]bool{"a-b_c/d=": true, "": false, "a.b": false, "a b": false, "a*": false} { + if (CheckName("module", name) == nil) != ok { + t.Errorf("CheckName(%q) = %v", name, CheckName("module", name)) + } + } + if !IsHex64(strings.Repeat("a", 64)) || IsHex64(strings.Repeat("A", 64)) || IsHex64("a") { + t.Error("IsHex64 reads a wrong form") + } +}