diff --git a/CHANGELOG.md b/CHANGELOG.md index 00fc442..f7e3e9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,12 @@ branch `v0.13` and not approved yet. It changes no format and no verdict. address of NAT64 written in a locator is still rejected. This module downloads nothing: the function is for the readers that do, as the application. +- **Two addresses that the text of v0.12 already refused**, and the + reference accepted (§44.1): a CID with a character more, of zero bits, + which decodes to the same bytes and is not its canonical form; and + `https://[[2000::]/`, whose brackets `checkHost` trimmed all at once. + `isCIDv1` refuses 5 or more bits left over, and `checkHost` takes one pair + of brackets. No normative change: `TestAddressCanonicalForms`. - **Test data.** `vectors/resolved_ip.json`, new: 42 addresses, with the prefix of the network or none, and the result of `CheckResolvedIP`, with its text. The other files do not change. diff --git a/locator/locator.go b/locator/locator.go index 1cd72e3..36db798 100644 --- a/locator/locator.go +++ b/locator/locator.go @@ -345,7 +345,9 @@ func checkHost(host string) error { return errors.New("locator: an https address without a host") } if strings.HasPrefix(host, "[") { - a, err := netip.ParseAddr(strings.Trim(host, "[]")) + // One pair of brackets: splitAuthority ends the literal at the + // first ']', and "[[2000::]" is not an IPv6 literal. + a, err := netip.ParseAddr(strings.TrimSuffix(strings.TrimPrefix(host, "["), "]")) if err != nil || !a.Is6() || a.Zone() != "" || !publicIP(a) { return errors.New("locator: an https address with an IPv6 literal that is not public") } @@ -469,7 +471,10 @@ func isCIDv1(s string) bool { acc &= 1<= 5 || acc != 0 { return false } version, out, ok := uvarint(out) diff --git a/locator/locator_test.go b/locator/locator_test.go index 9f0ed0f..dc2f7ef 100644 --- a/locator/locator_test.go +++ b/locator/locator_test.go @@ -169,6 +169,32 @@ func TestAddresses(t *testing.T) { // Spec §44.1: the locator is an age file with one tlock stanza for the round // of the DateKey: it opens with that release and with no other. +// Spec v0.12, §44.1: a CID in its canonical form, and an IPv6 literal of one +// pair of brackets. A CID with a character more, of zero bits, decodes to the +// same bytes and is another encoding of them; "[[2000::]" was read as +// 2000:: because the brackets were trimmed all at once. +func TestAddressCanonicalForms(t *testing.T) { + const cid = "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi" + for _, c := range []struct { + uri string + ok bool + }{ + {"ipfs://" + cid, true}, + {"ipfs://" + cid + "a", false}, + {"ipfs://" + cid + "aa", false}, + {"ipfs://bafkreibq6d3olcwyloabkavyuz437v52i4m7ukb4ijk6p3mcuw3zbzfppu", true}, + {"ipfs://bafkreibq6d3olcwyloabkavyuz437v52i4m7ukb4ijk6p3mcuw3zbzfppua", false}, + {"https://[2a01:4f8::1]/x", true}, + {"https://[[2a01:4f8::1]/x", false}, + {"https://[[2000::]/", false}, + {"https://[2a01:4f8::1]]/x", false}, + } { + if err := locator.CheckURI(c.uri); (err == nil) != c.ok { + t.Errorf("CheckURI(%q) = %v, want ok %v", c.uri, err, c.ok) + } + } +} + func TestSealedLocator(t *testing.T) { p := profile.Quicknet() loc, _, _ := sample(t)