diff --git a/scripts/locator-go-vectors.go b/scripts/locator-go-vectors.go new file mode 100644 index 0000000..89b33cf --- /dev/null +++ b/scripts/locator-go-vectors.go @@ -0,0 +1,2485 @@ +//go:build ignore + +// Prints the vectors of the locator of datekeys-ts, src/lib/dkc/locator.ts, +// ipaddr.ts, ageio.ts and envelope.ts, into src/lib/dkc/testing/: the +// results, the normative codes and the texts of package locator of +// datekeys-go at the tag spec-v0.12 (spec §43 to §44.1), and of the parser of +// IP addresses of Go that it calls, on +// +// - locator-uris.json: CheckURI and Address.Host on the 247 addresses of +// testdata/vectors/locator.json, and on addresses built at every edge of +// §44.1 and drawn from a fixed seed: IPv4 and IPv6 in every notation +// that netip.ParseAddr accepts or rejects, with zones, mapped, +// compatible, of NAT64, 6to4 and Teredo, the first and the last address +// of every block of IANA and their neighbours, names with long and +// punycode labels, local names in any case, ports, percent-encodings, +// dot segments and CIDs, canonical or not; CheckURI on bytes with the +// three bytes of a surrogate, those of a JavaScript string with a lone +// surrogate; netip.ParseAddr itself, with the address, its zone and its +// String; and locator.publicIP on the bytes of an address, as a reader +// checks the IP that a name resolves to on every connection. publicIP +// is not exported: this program reaches it with go:linkname, which Go +// allows for a package outside the standard library. +// - locator-vectors.json: the texts of the other cases of +// testdata/vectors/locator.json; PlaintextLength on every base from +// -4100 to 16484, in runs; Unmarshal on plaintexts of three bases, valid +// and broken byte by byte and field by field; Marshal on locators at +// every limit and at every boundary of the padding; Open on sealed +// locators of four rounds, with the releases of the others, broken +// profiles and edited files; Open on files whose plaintext passes 1 MiB, +// which Go reads through io.LimitReader, written here with a file key +// that this program knows; OpenEnvelope, RestIn and Hide on envelopes +// and rests, valid and edited; the split of an age file into the header +// and the rest of an envelope, as NewEnvelope does it (headerEnd, +// reached with go:linkname too); Info.Extension, Info.OpenLocator with +// the default registry, and ParseInfo; the registry of locator.Standard +// through CheckNoncriticalIn, CheckCriticalIn and CheckWrite; and +// capsule.Open of a fixture with its .dkk carrying datekeys.capsule, +// with locator.Standard. +// +// It is the generator of the stage 7a of datekeys-dart, with the seeds of +// this repository, so that the cases drawn are other than those of Dart. +// Every expected value is what Go gives; none is written by hand. The +// randomness of age, tlock and NewEnvelope comes from crypto/rand.Reader, +// which this program replaces with a ChaCha8 of a fixed seed, and every +// other choice from another: every run writes the same bytes with Go 1.26.8. +// Binary values are lower-case hexadecimal, and a text is an index into +// texts, whose first entry, "", stands for no error. An edited value is a +// list of edits of a base, as in "Edited files" of testdata/README.md, with +// one more form: [at, 0, byte, count] inserts count copies of the byte. An +// address with a run of 32 or more equal bytes is written [before, byte, +// count, after]. The JSON is ASCII. +// +// It imports only public packages of the reference implementation, and runs +// in a module of it without changing anything there: an export of the tag +// spec-v0.12, so that no change in progress in datekeys-go is read. +// +// git -C ../datekeys-go archive spec-v0.12 | tar -x -C /tmp/dkgo +// (cd /tmp/dkgo && go run .../datekeys-ts/scripts/locator-go-vectors.go \ +// -testdata .../datekeys-ts/testdata -out .../datekeys-ts/src/lib/dkc/testing) +package main + +import ( + "bytes" + "context" + "crypto/hkdf" + cryptorand "crypto/rand" + "crypto/sha256" + "encoding/base32" + "encoding/base64" + "encoding/binary" + "encoding/hex" + "encoding/json" + "errors" + "flag" + "fmt" + "io" + "log" + "math/rand/v2" + "net/netip" + "os" + "path/filepath" + "runtime" + "slices" + "strconv" + "strings" + "time" + "unicode/utf16" + "unicode/utf8" + _ "unsafe" + + "filippo.io/age" + "golang.org/x/crypto/chacha20poly1305" + + datekeys "g.activething.com/go/DateKeys" + "g.activething.com/go/DateKeys/accesskey" + "g.activething.com/go/DateKeys/agewrap" + "g.activething.com/go/DateKeys/capsule" + "g.activething.com/go/DateKeys/datekey" + "g.activething.com/go/DateKeys/extension" + "g.activething.com/go/DateKeys/locator" + "g.activething.com/go/DateKeys/profile" + "g.activething.com/go/DateKeys/provider" +) + +//go:linkname publicIP g.activething.com/go/DateKeys/locator.publicIP +func publicIP(a netip.Addr) bool + +//go:linkname headerEnd g.activething.com/go/DateKeys/locator.headerEnd +func headerEnd(file []byte) (int, error) + +// locatorCID is the CID of locator.json: dag-pb, SHA-256. +const locatorCID = "bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi" + +// seed derives the seed of one part of the output, so that a change in one +// part leaves the others as they were. +func seed(label string) [32]byte { + return sha256.Sum256([]byte("datekeys-ts locator: " + label)) +} + +// newRand returns the generator of the choices of one part. +func newRand(label string) *rand.Rand { return rand.New(rand.NewChaCha8(seed(label))) } + +// setCryptoRand makes crypto/rand.Reader, which age, tlock and NewEnvelope +// read, a ChaCha8 of a fixed seed: crypto/rand.Read reads the Reader that +// replaces the default one. +func setCryptoRand(label string) { cryptorand.Reader = rand.NewChaCha8(seed("crypto/rand " + label)) } + +// recorder keeps every byte read through it. +type recorder struct { + r io.Reader + got []byte +} + +func (r *recorder) Read(p []byte) (int, error) { + n, err := r.r.Read(p) + r.got = append(r.got, p[:n]...) + return n, err +} + +func hx(b []byte) string { return hex.EncodeToString(b) } + +func sum(b []byte) string { + s := sha256.Sum256(b) + return hx(s[:]) +} + +func must[T any](v T, err error) T { + if err != nil { + log.Fatal(err) + } + return v +} + +// patterned returns n bytes that depend only on label. +func patterned(label string, n int) []byte { + out := make([]byte, 0, n+32) + for i := uint32(0); len(out) < n; i++ { + var c [4]byte + binary.BigEndian.PutUint32(c[:], i) + h := sha256.Sum256(append([]byte(label), c[:]...)) + out = append(out, h[:]...) + } + return out[:n] +} + +// edits writes b as edits of base: the bytes between their common prefix +// and their common suffix replace those of base, in pieces, each run of 32 +// or more equal bytes as an edit [at, 0, byte, count] that inserts count +// copies of the byte. +func edits(base, b []byte) [][]any { + if bytes.Equal(base, b) { + return [][]any{} + } + p := 0 + for p < len(base) && p < len(b) && base[p] == b[p] { + p++ + } + s := 0 + for s < len(base)-p && s < len(b)-p && base[len(base)-1-s] == b[len(b)-1-s] { + s++ + } + insert := b[p : len(b)-s] + out := [][]any{} + del := len(base) - p - s + at := p + flush := func(piece []byte, repeat int) { + e := []any{at, del, hx(piece)} + if repeat > 0 { + e = append(e, repeat) + } + out = append(out, e) + at += del + del = 0 + } + start := 0 + for i := 0; i < len(insert); { + j := i + for j < len(insert) && insert[j] == insert[i] { + j++ + } + if j-i >= 32 { + if start < i { + flush(insert[start:i], 0) + } + flush(insert[i:i+1], j-i) + start = j + } + i = j + } + if start < len(insert) || len(out) == 0 { + flush(insert[start:], 0) + } + return out +} + +// compactURI writes an address with a run of 32 or more equal bytes as +// [before, byte, count, after]. +func compactURI(u string) any { + for i := 0; i < len(u); { + j := i + for j < len(u) && u[j] == u[i] { + j++ + } + if j-i >= 32 { + return []any{u[:i], u[i : i+1], j - i, u[j:]} + } + i = j + } + return u +} + +// --------------------------------------------------------------------------- +// The texts + +// textTable numbers the texts of the errors: 0 is no error. +type textTable struct { + list []string + index map[string]int +} + +func newTexts() *textTable { return &textTable{list: []string{""}, index: map[string]int{"": 0}} } + +func (t *textTable) of(err error) int { + s := "" + if err != nil { + s = err.Error() + if s == "" { + log.Fatal("an error without text") + } + } + if i, ok := t.index[s]; ok { + return i + } + t.index[s] = len(t.list) + t.list = append(t.list, s) + return len(t.list) - 1 +} + +// --------------------------------------------------------------------------- +// The JSON + +// asciiJSON is the JSON of v in one line, ASCII: every other character and +// the apostrophe, escaped as the generator of datekeys-dart escapes it. +func asciiJSON(v any) string { + var b bytes.Buffer + enc := json.NewEncoder(&b) + enc.SetEscapeHTML(false) + if err := enc.Encode(v); err != nil { + log.Fatal(err) + } + s := strings.TrimSuffix(b.String(), "\n") + var out strings.Builder + for _, r := range s { + switch { + case r == '\'': + fmt.Fprintf(&out, "\\u%04x", r) + case r < 0x80: + out.WriteRune(r) + case r < 0x10000: + fmt.Fprintf(&out, "\\u%04x", r) + default: + r1, r2 := utf16.EncodeRune(r) + fmt.Fprintf(&out, "\\u%04x\\u%04x", r1, r2) + } + } + return out.String() +} + +// document writes a JSON object whose members are in the order given, each +// list one item per line. +type document struct { + b bytes.Buffer + first bool +} + +func newDocument() *document { + d := &document{first: true} + d.b.WriteString("{\n") + return d +} + +func (d *document) sep() { + if !d.first { + d.b.WriteString(",\n") + } + d.first = false +} + +func (d *document) value(key string, v any) { + d.sep() + fmt.Fprintf(&d.b, " %s: %s", asciiJSON(key), asciiJSON(v)) +} + +func (d *document) list(key string, items []any) { + d.sep() + fmt.Fprintf(&d.b, " %s: [", asciiJSON(key)) + for i, it := range items { + if i > 0 { + d.b.WriteByte(',') + } + d.b.WriteString("\n ") + d.b.WriteString(asciiJSON(it)) + } + d.b.WriteString("\n ]") +} + +func (d *document) bytes() []byte { + out := slices.Clone(d.b.Bytes()) + out = append(out, "\n}\n"...) + var parsed any + if err := json.Unmarshal(out, &parsed); err != nil { + log.Fatalf("the JSON does not parse: %v", err) + } + for _, c := range out { + if c >= 0x80 { + log.Fatal("the JSON is not ASCII") + } + } + if bytes.Contains(out, []byte("'''")) { + log.Fatal("the JSON holds three apostrophes") + } + return out +} + +func anyList[T any](xs []T) []any { + out := make([]any, len(xs)) + for i, x := range xs { + out[i] = x + } + return out +} + +// --------------------------------------------------------------------------- +// Addresses + +// uriCase is CheckURI and Host of an address: [uri, text, host]. +func uriCase(t *textTable, uri string) []any { + if !utf8.ValidString(uri) { + log.Fatalf("an address that is not UTF-8: %q", uri) + } + err := locator.CheckURI(uri) + host := locator.Address{URI: uri}.Host() + if (err == nil) != (host != "") { + log.Fatalf("%q: %v and host %q", uri, err, host) + } + return []any{uri, t.of(err), host} +} + +// prefixInfo is a block of addresses, its first and last address and their +// neighbours. +type prefixInfo struct { + first, last netip.Addr +} + +func blockOf(s string) prefixInfo { + p := netip.MustParsePrefix(s).Masked() + first := p.Addr() + b := first.AsSlice() + bits := p.Bits() + for i := bits; i < len(b)*8; i++ { + b[i/8] |= 0x80 >> (i % 8) + } + last, _ := netip.AddrFromSlice(b) + return prefixInfo{first, last} +} + +// The blocks of §44.1, those that hold an IPv4 address, and others outside +// 2000::/3, whose edges the cases visit. +var blocks4 = []string{"0.0.0.0/8", "10.0.0.0/8", "100.64.0.0/10", "127.0.0.0/8", "169.254.0.0/16", "172.16.0.0/12", "192.0.0.0/24", "192.0.2.0/24", + "192.88.99.0/24", "192.168.0.0/16", "198.18.0.0/15", "198.51.100.0/24", "203.0.113.0/24", "224.0.0.0/4", "240.0.0.0/4", + // Neighbours of interest that are public. + "1.0.0.0/24", "8.8.8.0/24", "192.0.1.0/24", "192.31.196.0/24", "192.52.193.0/24", "192.175.48.0/24"} + +var blocks6 = []string{"2000::/3", "2001::/23", "2001:db8::/32", "2002::/16", "3fff::/20", + "::/96", "::ffff:0:0/96", "64:ff9b::/96", "64:ff9b:1::/48", "2001::/32", "fc00::/7", "fe80::/10", "ff00::/8", "100::/64", "::1/128", + "2001:20::/28", "2001:2::/48", "2620:4f:8000::/48", "2001:4860::/32"} + +// edges gives the first and the last address of each block, and the +// addresses before and after them; inner reports the first and the last. +func edges(list []string) (out []netip.Addr, inner []bool) { + for _, s := range list { + b := blockOf(s) + for i, a := range []netip.Addr{b.first.Prev(), b.first, b.first.Next(), b.last.Prev(), b.last, b.last.Next()} { + if a.IsValid() { + out = append(out, a) + inner = append(inner, i == 1 || i == 4) + } + } + } + return out, inner +} + +func randomAddr4(r *rand.Rand) netip.Addr { + var b [4]byte + switch r.IntN(4) { + case 0: // inside a block of §44.1 + bl := blockOf(blocks4[r.IntN(15)]) + f, l := bl.first.As4(), bl.last.As4() + for i := range b { + b[i] = f[i] + byte(r.IntN(int(l[i]-f[i])+1)) + } + default: + for i := range b { + b[i] = byte(r.IntN(256)) + } + } + return netip.AddrFrom4(b) +} + +func randomAddr6(r *rand.Rand) netip.Addr { + var b [16]byte + for i := range b { + b[i] = byte(r.IntN(256)) + } + switch r.IntN(6) { + case 0: // inside a block + bl := blockOf(blocks6[r.IntN(len(blocks6))]) + f, l := bl.first.As16(), bl.last.As16() + for i := range b { + if f[i] == l[i] { + b[i] = f[i] + } else { + b[i] = f[i] | b[i]&(l[i]^f[i]) + } + } + case 1: // zeros, to compress + for i := range b { + if r.IntN(3) > 0 { + b[i] = 0 + } + } + case 2: // 2000::/3 + b[0] = 0x20 | b[0]&0x1f + } + return netip.AddrFrom16(b) +} + +func randomCase(r *rand.Rand, s string) string { + var out strings.Builder + for _, c := range s { + switch { + case c >= 'a' && c <= 'z' && r.IntN(3) == 0: + out.WriteRune(c - 32) + case c >= 'A' && c <= 'Z' && r.IntN(3) == 0: + out.WriteRune(c + 32) + default: + out.WriteRune(c) + } + } + return out.String() +} + +// notations4 writes an IPv4 address in the notations that netip and some +// clients read. +func notations4(r *rand.Rand, a netip.Addr) []string { + b := a.As4() + v := binary.BigEndian.Uint32(b[:]) + octet := func(x byte, form int) string { + switch form { + case 1: + return "0" + strconv.Itoa(int(x)) + case 2: + return "0x" + strconv.FormatUint(uint64(x), 16) + case 3: + return "0" + strconv.FormatUint(uint64(x), 8) + case 4: + return "00" + strconv.Itoa(int(x)) + } + return strconv.Itoa(int(x)) + } + dotted := func(forms [4]int) string { + parts := make([]string, 4) + for i := range parts { + parts[i] = octet(b[i], forms[i]) + } + return strings.Join(parts, ".") + } + out := []string{a.String(), dotted([4]int{r.IntN(5) * r.IntN(2), 0, 0, 0}), dotted([4]int{0, 0, 0, r.IntN(5)}), + fmt.Sprintf("%d.%d.%d", b[0], b[1], uint32(b[2])<<8|uint32(b[3])), fmt.Sprintf("%d.%d", b[0], v&0xffffff), strconv.FormatUint(uint64(v), 10), + "0x" + strconv.FormatUint(uint64(v), 16), "0" + strconv.FormatUint(uint64(v), 8), a.String() + ".", a.String() + ".0", + randomCase(r, "0X"+strconv.FormatUint(uint64(v), 16))} + return out +} + +// notations6 writes an IPv6 address in the notations that netip accepts and +// in some that it rejects. +func notations6(r *rand.Rand, a netip.Addr) []string { + b := a.As16() + groups := make([]string, 8) + for i := range groups { + groups[i] = strconv.FormatUint(uint64(binary.BigEndian.Uint16(b[2*i:])), 16) + } + full := strings.Join(groups, ":") + padded := make([]string, 8) + for i, g := range groups { + padded[i] = strings.Repeat("0", 4-len(g)) + g + } + out := []string{a.String(), full, strings.Join(padded, ":"), randomCase(r, a.String())} + // :: in place of each run of zeros, the longest or not. + for i := 0; i < 8; i++ { + if groups[i] != "0" { + continue + } + j := i + for j < 8 && groups[j] == "0" { + j++ + } + out = append(out, strings.Join(groups[:i], ":")+"::"+strings.Join(groups[j:], ":")) + i = j + } + // The last 32 bits as an IPv4 address. + v4 := fmt.Sprintf("%d.%d.%d.%d", b[12], b[13], b[14], b[15]) + out = append(out, strings.Join(groups[:6], ":")+":"+v4) + if a.Is4In6() || b[0] == 0 && b[1] == 0 { + out = append(out, "::"+strings.Join(groups[5:6], ":")+":"+v4) + } + // Rejected: a group of five digits, nine groups, two ::, a short form. + k := r.IntN(8) + five := slices.Clone(padded) + five[k] = "0" + five[k] + nine := append(slices.Clone(groups), "1") + out = append(out, strings.Join(five, ":"), strings.Join(nine, ":"), strings.Join(groups[:7], ":"), + strings.Join(groups[:3], ":")+"::"+strings.Join(groups[4:5], ":")+"::1", full+":", ":"+full, full+"::") + return out +} + +func randomLabel(r *rand.Rand) string { + const alnum = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + n := 1 + r.IntN(12) + switch r.IntN(10) { + case 0: + n = 61 + r.IntN(5) + case 1: + n = 1 + } + var s strings.Builder + for i := 0; i < n; i++ { + if i > 0 && i < n-1 && r.IntN(8) == 0 { + s.WriteByte('-') + } else { + s.WriteByte(alnum[r.IntN(len(alnum))]) + } + } + l := s.String() + switch r.IntN(30) { + case 0: + l = "-" + l + case 1: + l += "-" + case 2: + l = "xn--" + strings.ToLower(l) + case 3: + l = strconv.Itoa(r.IntN(1000)) + case 4: + l = randomCase(r, "0x") + strconv.FormatUint(r.Uint64N(1<<32), 16) + case 5: + l = strings.Replace(l, string(l[0]), "_", 1) + case 6: + l = "" + case 7: + l = randomCase(r, []string{"localhost", "local", "internal", "invalid", "test", "example", "onion", "arpa", "home"}[r.IntN(9)]) + } + return l +} + +var nonASCII = []string{"\xc3\xa9", "\xc3\xb1", "\xe2\x80\xae", "\xef\xbc\x8e", "\xe3\x80\x82", "\xf0\x9f\x8c\x8d", "\xc2\xa0", "\xd0\xb0", "\xe2\x80\x8b"} + +func randomName(r *rand.Rand) string { + n := 1 + r.IntN(4) + labels := make([]string, n) + for i := range labels { + labels[i] = randomLabel(r) + } + if n > 1 && r.IntN(3) == 0 { + labels[n-1] = []string{"org", "com", "es", "io", "net", "arpa", "local", "test", "0", "123", "0x1f", "0Xff", "1e3"}[r.IntN(13)] + } + if r.IntN(4) == 0 { + suffix := []string{"localhost", "local", "home.arpa", "internal", "invalid", "test", "example", "onion"}[r.IntN(8)] + labels = append(labels, strings.Split(randomCase(r, suffix), ".")...) + } + name := strings.Join(labels, ".") + switch r.IntN(25) { + case 0: + name += "." + case 1: + name = "." + name + case 2: + i := r.IntN(len(name) + 1) + name = name[:i] + nonASCII[r.IntN(len(nonASCII))] + name[i:] + case 3: + i := r.IntN(len(name) + 1) + name = name[:i] + fmt.Sprintf("%%%02X", r.IntN(256)) + name[i:] + } + return name +} + +func randomPort(r *rand.Rand) string { + switch r.IntN(12) { + case 0: + return ":" + case 1: + return ":0" + case 2: + return ":0" + strconv.Itoa(1+r.IntN(65535)) + case 3: + return ":" + strconv.Itoa(65536+r.IntN(40000)) + case 4: + return ":" + strings.Repeat("0", 1+r.IntN(3)) + strconv.Itoa(r.IntN(1000)) + case 5: + return []string{":65535", ":65536", ":1", ":99999", ":100000", ":443", ":8443", ":-1", ":+1", ":4a", ": 1", "::1"}[r.IntN(12)] + } + return ":" + strconv.Itoa(1+r.IntN(65535)) +} + +// The characters of a path: allowed, percent-encoded and not allowed. +func randomSegment(r *rand.Rand) string { + const allowed = "abcxyzABZ019-._~!$&'()*+,;=:@" + n := r.IntN(8) + var s strings.Builder + for i := 0; i < n; i++ { + switch r.IntN(20) { + case 0: + s.WriteString([]string{"%2e", "%2E", "%2f", "%41", "%00", "%ff", "%25", "%7e"}[r.IntN(8)]) + case 1: + s.WriteString([]string{"%", "%4", "%zz", "%g0", "%0g"}[r.IntN(5)]) + case 2: + s.WriteString([]string{" ", "\"", "<", ">", "\\", "^", "`", "{", "|", "}", "\x00", "\t", "\n", "\x7f", "\x1f"}[r.IntN(15)]) + case 3: + s.WriteString(nonASCII[r.IntN(len(nonASCII))]) + default: + s.WriteByte(allowed[r.IntN(len(allowed))]) + } + } + seg := s.String() + switch r.IntN(10) { + case 0: + seg = []string{".", "..", "%2e", "%2E%2e", ".%2E", "%2e.", "...", "..a"}[r.IntN(8)] + } + return seg +} + +func randomPath(r *rand.Rand) string { + var s strings.Builder + for n := r.IntN(4); n > 0; n-- { + s.WriteByte('/') + s.WriteString(randomSegment(r)) + } + if r.IntN(5) == 0 { + s.WriteString("?" + randomSegment(r) + "/" + randomSegment(r)) + } + if r.IntN(6) == 0 { + s.WriteString("#" + randomSegment(r) + "/" + randomSegment(r)) + } + return s.String() +} + +func cidV1(b []byte) string { + return "b" + strings.ToLower(base32.StdEncoding.WithPadding(base32.NoPadding).EncodeToString(b)) +} + +func uvarint(v uint64) []byte { return binary.AppendUvarint(nil, v) } + +// randomCID writes a CID v1 in base32, valid or with one defect of its bytes +// or of its text. +func randomCID(r *rand.Rand) string { + version := uint64(1) + if r.IntN(12) == 0 { + version = []uint64{0, 2, 3, 0x81}[r.IntN(4)] + } + codec := []uint64{0x55, 0x70, 0x71, 0x0129, 0x0200, r.Uint64N(1 << 14)}[r.IntN(6)] + hash := []uint64{0x12, 0x13, 0x00, 0xb220, r.Uint64N(1 << 21)}[r.IntN(5)] + n := []int{32, 64, 20, 1 + r.IntN(80)}[r.IntN(4)] + digest := make([]byte, n) + for i := range digest { + digest[i] = byte(r.IntN(256)) + } + parts := [][]byte{uvarint(version), uvarint(codec), uvarint(hash), uvarint(uint64(n)), digest} + switch r.IntN(10) { + case 0: // a varint that is not minimal + i := r.IntN(4) + v := parts[i] + parts[i] = append(append(slices.Clone(v[:len(v)-1]), v[len(v)-1]|0x80), 0) + case 1: // a varint of ten bytes, or of nine with the last continued + i := r.IntN(4) + parts[i] = append(bytes.Repeat([]byte{0xff}, 8+r.IntN(2)), 0x01) + case 2: // a length that is not that of the digest + parts[3] = uvarint(uint64(n + 1 - 2*r.IntN(2))) + case 3: // a byte after the digest + parts = append(parts, []byte{byte(r.IntN(256))}) + case 4: // an empty digest + parts[3], parts[4] = []byte{0}, nil + case 5: // cut + all := slices.Concat(parts...) + parts = [][]byte{all[:r.IntN(len(all))]} + } + s := cidV1(slices.Concat(parts...)) + switch r.IntN(14) { + case 0: // the last character with bits beyond a byte + s = s[:len(s)-1] + string("abcdefghijklmnopqrstuvwxyz234567"[r.IntN(32)]) + case 1: + s = "b" + strings.ToUpper(s[1:]) + case 2: + s += "=" + case 3: + s += "a" + case 4: + s = s[:len(s)-1] + case 5: + i := 1 + r.IntN(len(s)-1) + s = s[:i] + string("0189"[r.IntN(4)]) + s[i:] + case 6: + s = "B" + s[1:] + case 7: + s = "Qm" + s[1:] + } + return s +} + +func randomURI(r *rand.Rand) string { + scheme := "https" + switch x := r.IntN(20); { + case x < 4: + scheme = "ipfs" + case x < 6: + scheme = []string{"http", "HTTPS", "Https", "IPFS", "ipfS", "", "ftp", "file", "h", "https+x", "1https", "ht-tp", "https.", "data"}[r.IntN(14)] + } + sep := "://" + if r.IntN(25) == 0 { + sep = []string{":/", ":", "//", ":///", ":\\\\", "::/"}[r.IntN(6)] + } + var host string + if strings.EqualFold(scheme, "ipfs") && r.IntN(5) > 0 { + host = randomCID(r) + } else { + switch r.IntN(10) { + case 0, 1: + ns := notations4(r, randomAddr4(r)) + host = ns[r.IntN(len(ns))] + case 2, 3: + ns := notations6(r, randomAddr6(r)) + host = ns[r.IntN(len(ns))] + switch r.IntN(12) { + case 0: + host = "[[" + host + "]" + case 1: + host = "[" + host + case 2: + host += "]" + case 3: + host = "[" + host + "%25" + []string{"eth0", "1", "lo"}[r.IntN(3)] + "]" + case 4: + host = "[" + host + "]]" + case 5: + default: + host = "[" + host + "]" + } + default: + host = randomName(r) + } + } + authority := host + if r.IntN(25) == 0 { + authority = []string{"user@", "@", "a:b@", "u%40v@"}[r.IntN(4)] + authority + } + if r.IntN(5) == 0 { + authority += randomPort(r) + } + uri := scheme + sep + authority + randomPath(r) + if r.IntN(40) == 0 { + // At the limit of the length. + want := 1023 + r.IntN(3) + if len(uri) < want { + if !strings.Contains(uri[len(scheme)+len(sep):], "/") { + uri += "/" + } + uri += strings.Repeat("a", want-len(uri)) + } + } + return uri +} + +// fixedURIs are the addresses built at the edges of §44.1. +func fixedURIs(r *rand.Rand) []string { + var out []string + add := func(s ...string) { out = append(out, s...) } + // Every block of IPv4 at its edges; its first and last address in every + // notation, and inside IPv6. + as, inner := edges(blocks4) + for i, a := range as { + add("https://" + a.String() + "/") + if !inner[i] { + continue + } + for _, n := range notations4(r, a)[1:] { + add("https://" + n + "/") + } + add("https://"+a.String()+":443/x", "https://["+a.String()+"]/", "https://[::ffff:"+a.String()+"]/", "https://[::"+a.String()+"]/", + "https://[64:ff9b::"+a.String()+"]/", "https://[64:ff9b:1::"+a.String()+"]/") + } + // Every block of IPv6 at its edges; its first and last address in every + // notation, with and without brackets, with a zone and a port. + as, inner = edges(blocks6) + for i, a := range as { + s := a.String() + add("https://[" + s + "]/") + if !inner[i] { + continue + } + for _, n := range notations6(r, a)[1:] { + add("https://[" + n + "]/") + } + add("https://"+s+"/", "https://["+s+"]:8443/a", "https://["+s+"]:0443/", "https://["+s+"%25eth0]/", "https://["+s+"%eth0]/", + "https://[["+s+"]/", "https://[["+s+"]]/", "https://["+s+"]]/", "https://["+s+"/", "https://["+s+"]x/", "https://["+s+"]:/") + } + // Names. + for _, n := range []string{"a", "a.b", "ejemplo.org", "EJEMPLO.ORG", "xn--pple-43d.com", "xn--a.b", "a-b.c-d.e", "a--b.org", "1.org", "org.1", + "123.456", "0x.org", "org.0x", "org.0X", "org.0x1", "a.0b1", "a.1b", "a.b1", "a.b-1", "a.0", "a.00", "a.08", "a.1e1", + strings.Repeat("a", 63) + ".org", strings.Repeat("a", 64) + ".org", "org." + strings.Repeat("b", 63), "org." + strings.Repeat("b", 64), + strings.Repeat("a.", 120) + "org", "localhost", "LOCALHOST", "LocalHost", "a.localhost", "localhost.org", "a.local", "A.LOCAL", "local", + "home.arpa", "a.home.arpa", "a.HOME.ARPA", "home.arpa.org", "arpa", "a.arpa", "x.internal", "x.invalid", "x.test", "x.example", + "x.onion", "x.Onion", "onion.x", "example", "example.com", "test.org", "a.localhost.", "a.local-", "a._b.org", "a.b_.org"} { + add("https://"+n+"/", "https://"+n+":8443/x") + } + // Ports. + for _, p := range []string{"", ":", ":0", ":00", ":1", ":01", ":9", ":09", ":443", ":0443", ":00443", ":000443", ":65535", ":065535", + ":65536", ":99999", ":100000", ":4294967739", ":-443", ":+443", ":44 3", ":4a", ":443:80", "::443"} { + add("https://ejemplo.org"+p+"/x", "https://[2606:4700::1111]"+p+"/x", "ipfs://"+locatorCID+p+"/x") + } + // Schemes, separators and userinfo. + for _, u := range []string{"https://ejemplo.org/", "HTTPS://ejemplo.org/", "hTTps://ejemplo.org/", "https:/ejemplo.org/", "https:ejemplo.org/", + "https:///ejemplo.org/", "https:////ejemplo.org/", "://ejemplo.org/", "https//ejemplo.org/", "ipfs:/" + locatorCID, "ipfs:" + locatorCID, + "ipfs:///" + locatorCID, "https://user@ejemplo.org/", "https://@ejemplo.org/", "https://ejemplo.org@x/", "https://ejemplo.org/a@b", + "https://ejemplo.org?a@b", "https://ejemplo.org#a@b", "https://ejemplo.org/?", "https://ejemplo.org#", "https://ejemplo.org?", + "https://ejemplo.org/%", "https://ejemplo.org/%2", "https://ejemplo.org/%2G", "https://ejemplo.org/%2f%2F", "https://ejemplo.org%2f/", + "https://ejemplo.org/a%", "https://ejemplo.org/a%%41", "https://", "ipfs://", "https://:443/", "https://.:443/", "https://./"} { + add(u) + } + // Dot segments. + for _, p := range []string{"/.", "/..", "/./", "/../", "/a/.", "/a/..", "/a/./b", "/a/../b", "/%2e", "/%2E", "/%2e%2e", "/%2E%2E", "/.%2e", + "/%2e.", "/%2e%2E/", "/...", "/.a", "/a.", "/.%2", "/%2e%2e%2f", "/a?/../", "/a#/../", "/a?x=/..", "?/..", "#/..", "?a/../b", "//..", "/..//"} { + add("https://ejemplo.org"+p, "ipfs://"+locatorCID+p) + } + // Each byte that is not a character of RFC 3986, and the first byte of + // a character that is not ASCII. + for c := 0; c < 0x80; c++ { + add(fmt.Sprintf("https://ejemplo.org/a%cb", c)) + } + for _, n := range nonASCII { + add("https://ejemplo.org/"+n, "https://ejempl"+n+".org/") + } + // Lengths. + for _, n := range []int{1, 2, 1023, 1024, 1025, 2000} { + u := "https://ejemplo.org/" + if n > len(u) { + add(u + strings.Repeat("a", n-len(u))) + } else { + add(strings.Repeat("h", n)) + } + } + // CIDs at their limits. + digest := sha256.Sum256([]byte("locator cid")) + for _, b := range [][]byte{ + slices.Concat([]byte{0x01, 0x55, 0x12, 0x20}, digest[:]), slices.Concat([]byte{0x01, 0x70, 0x12, 0x20}, digest[:]), + slices.Concat([]byte{0x01, 0x55, 0x00, 0x01}, []byte{0}), slices.Concat([]byte{0x01, 0x55, 0x00, 0x00}), + slices.Concat([]byte{0x01, 0x55, 0x00, 0x4b}, patterned("cid 75", 75)), slices.Concat([]byte{0x01, 0x55, 0x00, 0x4c}, patterned("cid 76", 76)), + slices.Concat([]byte{0x01, 0x55, 0x00, 0x4d}, patterned("cid 77", 77)), slices.Concat([]byte{0x01, 0x80, 0x01, 0x12, 0x20}, digest[:]), + slices.Concat([]byte{0x01, 0x80, 0x00, 0x12, 0x20}, digest[:]), slices.Concat([]byte{0x81, 0x00, 0x55, 0x12, 0x20}, digest[:]), + slices.Concat([]byte{0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0x12, 0x20}, digest[:]), + slices.Concat([]byte{0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01, 0x12, 0x20}, digest[:]), + slices.Concat([]byte{0x01, 0x55, 0x12, 0x80, 0x00}), slices.Concat([]byte{0x01, 0x55, 0x12, 0xa0, 0x00}, digest[:]), + slices.Concat([]byte{0x01, 0x55, 0x12, 0x21}, digest[:]), slices.Concat([]byte{0x01, 0x55, 0x12, 0x1f}, digest[:]), + slices.Concat([]byte{0x01, 0x55, 0x12, 0x20}, digest[:], []byte{0}), {0x01}, {0x01, 0x55}, {0x01, 0x55, 0x12}, {}, + } { + s := cidV1(b) + add("ipfs://"+s, "ipfs://"+s+"/x", "ipfs://"+s+"a", "ipfs://"+s+"aa", "ipfs://"+s+"q", "ipfs://"+s[:max(1, len(s)-1)]) + } + add("ipfs://"+locatorCID+"a", "ipfs://"+locatorCID+"b", "ipfs://"+locatorCID+"aa", "ipfs://"+locatorCID+"7", "ipfs://"+strings.ToUpper(locatorCID), + "ipfs://b", "ipfs://", "ipfs://bb", "ipfs://"+locatorCID+":443", "ipfs://"+locatorCID+"@x", "ipfs://x@"+locatorCID, "ipfs://["+locatorCID+"]") + return out +} + +func uriCases(t *textTable, r *rand.Rand) []any { + seen := map[string]bool{} + var out []any + add := func(u string) { + if seen[u] || !utf8.ValidString(u) { + return + } + seen[u] = true + out = append(out, uriCase(t, u)) + } + for _, u := range fixedURIs(r) { + add(u) + } + for len(out) < 2800 { + add(randomURI(r)) + } + return out +} + +// wtf8Cases gives CheckURI on addresses with the three bytes of a +// surrogate, which are not UTF-8: the bytes of a JavaScript string with a lone +// surrogate, [bytes, text]. +func wtf8Cases(t *textTable) []any { + var out []any + for _, s := range []string{"https://a.org/\xed\xa0\x80", "https://a.org/\xed\xbf\xbf", "\xed\xa0\x80", "https://ejempl\xed\xb0\x80.org/"} { + out = append(out, []any{hx([]byte(s)), t.of(locator.CheckURI(s))}) + } + return out +} + +// ipCase is netip.ParseAddr of s: [s, false], or [s, true, the bytes of the +// address, its zone, its String, publicIP]. +func ipCase(s string) []any { + a, err := netip.ParseAddr(s) + if err != nil { + return []any{s, false} + } + return []any{s, true, hx(a.AsSlice()), a.Zone(), a.String(), publicIP(a)} +} + +func ipCases(r *rand.Rand) []any { + seen := map[string]bool{} + var out []any + add := func(s string) { + if seen[s] || !utf8.ValidString(s) { + return + } + seen[s] = true + out = append(out, ipCase(s)) + } + as, inner := edges(blocks4) + for i, a := range as { + add(a.String()) + if inner[i] { + for _, n := range notations4(r, a)[1:] { + add(n) + } + } + } + as, inner = edges(blocks6) + for i, a := range as { + add(a.String()) + if inner[i] { + for _, n := range notations6(r, a)[1:] { + add(n) + add(n + "%" + []string{"eth0", "1", "%", "\xc3\xa9", "a b", "]", ""}[r.IntN(7)]) + } + } + } + for _, s := range []string{"", ".", ":", "::", ":::", "%", "%eth0", "::%", "::%eth0", "::1%", "::1%%", "1.2.3.4%eth0", "::1.2.3.4%x", + "::ffff:1.2.3.4", "::ffff:01.2.3.4", "::ffff:1.2.3", "::ffff:1.2.3.4.5", "::1.2.3.4:5", "1.2.3.4::", "1::2.3.4.5", "1:2:3:4:5:6:1.2.3.4", + "1:2:3:4:5:6:7:1.2.3.4", "1:2:3:4:5:1.2.3.4", "1:2:3:4:5::1.2.3.4", "::1:2:3:4:5:6:1.2.3.4", "0:0:0:0:0:0:0:0", "0::0", "0:0::0:0:0:0:0:0", + "0:0:0:0:0:0:0::0", "1:2:3:4:5:6:7::", "::2:3:4:5:6:7:8", "1::3:4:5:6:7:8", "1:2:3:4:5:6:7:8::", "1:2:3:4:5:6:7:8:9", "fffff::", + "FFFF::", "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", "g::", "::g", "1.2.3.4", "01.2.3.4", "1.2.3.04", "1.2.3.00", "0.0.0.0", "00.0.0.0", + "255.255.255.255", "256.255.255.255", "1.2.3", "1.2.3.4.5", "1..2.3", ".1.2.3", "1.2.3.", "1.2.3.4 ", " 1.2.3.4", "1.2.3.4\x00", + "\xef\xbc\x91.2.3.4", "1\xe3\x80\x822.3.4", "0x1.2.3.4", "1e1.2.3.4", "2130706433", "0x7f000001", "[::1]", "::1]", "[::1"} { + add(s) + } + const alphabet = "0123456789abcdefABCDEFgx.:%" + for len(out) < 1700 { + n := 1 + r.IntN(24) + var s strings.Builder + for i := 0; i < n; i++ { + s.WriteByte(alphabet[r.IntN(len(alphabet))]) + } + add(s.String()) + a := randomAddr6(r) + ns := notations6(r, a) + add(ns[r.IntN(len(ns))]) + a4 := randomAddr4(r) + ns4 := notations4(r, a4) + add(ns4[r.IntN(len(ns4))]) + } + return out +} + +// publicCases is publicIP of the address of each byte string, as a reader +// checks the IP that a name resolves to: [hex, public, String], or [hex, +// null] for a length that is no address. +func publicCases(r *rand.Rand) []any { + var out []any + add := func(b []byte) { + a, ok := netip.AddrFromSlice(b) + if !ok { + out = append(out, []any{hx(b), nil}) + return + } + out = append(out, []any{hx(b), publicIP(a), a.String()}) + } + as, _ := edges(blocks4) + for _, a := range as { + add(a.AsSlice()) + m := netip.AddrFrom16(a.As16()) + add(m.AsSlice()) + } + as, _ = edges(blocks6) + for _, a := range as { + add(a.AsSlice()) + } + for i := 0; i < 250; i++ { + add(randomAddr4(r).AsSlice()) + add(randomAddr6(r).AsSlice()) + } + for _, n := range []int{0, 1, 3, 5, 8, 15, 17, 32} { + add(patterned("length", n)) + } + return out +} + +// --------------------------------------------------------------------------- +// Locators + +// enc writes CBOR by hand, so that a plaintext may break any rule. +type enc struct{ b []byte } + +// head writes a head of major type m and argument n in size bytes: 0 for the +// shortest. +func (e *enc) head(m byte, n uint64, size int) { + if size == 0 { + switch { + case n < 24: + size = 1 + case n < 1<<8: + size = 2 + case n < 1<<16: + size = 3 + case n < 1<<32: + size = 5 + default: + size = 9 + } + } + switch size { + case 1: + e.b = append(e.b, m<<5|byte(n)) + case 2: + e.b = append(e.b, m<<5|24, byte(n)) + case 3: + e.b = binary.BigEndian.AppendUint16(append(e.b, m<<5|25), uint16(n)) + case 5: + e.b = binary.BigEndian.AppendUint32(append(e.b, m<<5|26), uint32(n)) + default: + e.b = binary.BigEndian.AppendUint64(append(e.b, m<<5|27), n) + } +} + +func (e *enc) uint(n uint64) { e.head(0, n, 0) } +func (e *enc) bstr(b []byte) { e.head(2, uint64(len(b)), 0); e.b = append(e.b, b...) } +func (e *enc) text(s string) { e.head(3, uint64(len(s)), 0); e.b = append(e.b, s...) } +func (e *enc) raw(b []byte) { e.b = append(e.b, b...) } +func (e *enc) array(n uint64) { e.head(4, n, 0) } +func (e *enc) mapOf(n uint64) { e.head(5, n, 0) } + +// rawAddr is an address of a plaintext: its entries in order. +type rawAddr struct { + pairs []rawPair +} + +type rawPair struct { + key uint64 + val func(*enc) +} + +// rawLoc is a plaintext of a locator: the entries of its map in order, the +// count its head declares (-1 for the number of entries), and the padding. +type rawLoc struct { + pairs []rawPair + count int +} + +func addrPairs(uri string, offset uint64) rawAddr { + a := rawAddr{pairs: []rawPair{{0, func(e *enc) { e.text(uri) }}}} + if offset != 0 { + a.pairs = append(a.pairs, rawPair{1, func(e *enc) { e.uint(offset) }}) + } + return a +} + +func addrsValue(addrs []rawAddr) func(*enc) { + return func(e *enc) { + e.array(uint64(len(addrs))) + for _, a := range addrs { + e.mapOf(uint64(len(a.pairs))) + for _, p := range a.pairs { + e.uint(p.key) + p.val(e) + } + } + } +} + +func rawOf(loc *locator.Locator, addrs []rawAddr) rawLoc { + return rawLoc{count: -1, pairs: []rawPair{ + {0, addrsValue(addrs)}, + {1, func(e *enc) { e.bstr(loc.EnvelopeKey[:]) }}, + {2, func(e *enc) { e.bstr(loc.EnvelopeHeader) }}, + {3, func(e *enc) { e.bstr(loc.RestDigest[:]) }}, + {4, func(e *enc) { e.uint(loc.RestSize) }}, + {5, func(e *enc) { e.bstr(loc.CapsuleDigest[:]) }}, + }} +} + +func (l rawLoc) encode(extra ...rawPair) []byte { + var e enc + pairs := append(slices.Clone(l.pairs), extra...) + n := uint64(len(pairs)) + if l.count >= 0 { + n = uint64(l.count) + } + e.mapOf(n) + for _, p := range pairs { + e.uint(p.key) + p.val(&e) + } + return e.b +} + +func bstrHeadLen(n int) int { + switch { + case n < 24: + return 1 + case n < 256: + return 2 + case n < 65536: + return 3 + } + return 5 +} + +// padded is the plaintext with the key 6 that makes it the least multiple of +// 4096 that holds it, as Marshal pads it, or none when it already is one. +func (l rawLoc) padded() []byte { + base := l.encode() + total := locator.PlaintextLength(len(base)) + if total == len(base) { + return base + } + for pad := 1; ; pad++ { + if len(base)+1+bstrHeadLen(pad)+pad == total { + return l.encode(rawPair{6, func(e *enc) { e.bstr(make([]byte, pad)) }}) + } + } +} + +// summary is what a reader reads of a locator: [[uri, offset, host, +// usable]...], the key, the SHA-256 of the header, the digest of the rest, +// its size and capsule_digest. +func summary(l *locator.Locator) []any { + addrs := []any{} + for _, a := range l.Addresses { + addrs = append(addrs, []any{compactURI(a.URI), a.Offset, compactURI(a.Host()), locator.CheckURI(a.URI) == nil}) + } + usable := l.Usable() + n := 0 + for _, a := range l.Addresses { + if locator.CheckURI(a.URI) == nil { + if usable[n] != a { + log.Fatal("Usable is not the addresses that CheckURI accepts") + } + n++ + } + } + return []any{addrs, hx(l.EnvelopeKey[:]), sum(l.EnvelopeHeader), hx(l.RestDigest[:]), l.RestSize, hx(l.CapsuleDigest[:])} +} + +// readCase is Unmarshal of b: [base, edits, text, summary or null]. +func readCase(t *textTable, bases [][]byte, base int, b []byte) []any { + l, err := locator.Unmarshal(b) + var s any + if err == nil { + s = summary(l) + if again, err := locator.Unmarshal(b); err != nil || !slices.Equal(again.Addresses, l.Addresses) { + log.Fatal("Unmarshal is not stable") + } + } + return []any{base, edits(bases[base], b), t.of(err), s} +} + +type world struct { + p *profile.Profile + texts *textTable + releases map[uint64]provider.Release + dkc []byte + datas [][]byte // the data of datekeys.capsule of registry and capsule + loc *locator.Locator + rest []byte + file []byte // the envelope, header and rest +} + +func (w *world) release(round uint64) provider.Release { + r, ok := w.releases[round] + if !ok { + log.Fatalf("no release of round %d in the fixtures", round) + } + return r +} + +// plaintextBases are the plaintexts that the edits of plaintexts refer to: +// the locator of the envelope with two addresses, with eight, and with +// addresses that a reader rejects next to one it uses. +func (w *world) plaintextBases() ([][]byte, []rawLoc) { + two := rawOf(w.loc, []rawAddr{addrPairs("https://ejemplo.org/foto.jpg", 3000), addrPairs("ipfs://"+locatorCID, 0)}) + var eight []rawAddr + for i := 0; i < 8; i++ { + eight = append(eight, addrPairs(fmt.Sprintf("https://ejemplo%d.org/%s", i, strings.Repeat("x", 400+i)), uint64(i)*1000)) + } + eightLoc := rawOf(w.loc, eight) + mixed := rawOf(w.loc, []rawAddr{addrPairs("http://ejemplo.org/a", 7), addrPairs("https://[64:ff9b::7f00:1]/b", 0), + addrPairs("https://ejemplo.org/c", 9007199254740991), addrPairs("ipfs://"+locatorCID+"a", 1)}) + raws := []rawLoc{two, eightLoc, mixed} + var bases [][]byte + for _, l := range raws { + bases = append(bases, l.padded()) + } + return bases, raws +} + +// plaintextCases breaks each base field by field and byte by byte. +func (w *world) plaintextCases(bases [][]byte, raws []rawLoc) []any { + t := w.texts + r := newRand("plaintexts") + var out []any + add := func(base int, b []byte) { out = append(out, readCase(t, bases, base, b)) } + for i, b := range bases { + add(i, b) + } + loc := w.loc + two := raws[0] + addr := func(uri string, offset uint64) rawAddr { return addrPairs(uri, offset) } + foto := addr("https://ejemplo.org/foto.jpg", 3000) + with := func(key uint64, val func(*enc)) rawLoc { + l := two + l.pairs = slices.Clone(l.pairs) + for i, p := range l.pairs { + if p.key == key { + l.pairs[i] = rawPair{key, val} + return l + } + } + l.pairs = append(l.pairs, rawPair{key, val}) + slices.SortStableFunc(l.pairs, func(a, b rawPair) int { return int(a.key) - int(b.key) }) + return l + } + without := func(key uint64) rawLoc { + l := two + l.pairs = slices.DeleteFunc(slices.Clone(l.pairs), func(p rawPair) bool { return p.key == key }) + return l + } + addrs := func(as ...rawAddr) func(*enc) { return addrsValue(as) } + // The fields, each padded as Marshal would pad it. + for _, l := range []rawLoc{ + without(0), without(1), without(2), without(3), without(4), without(5), + with(0, addrs()), with(0, addrs(foto, foto, foto, foto, foto, foto, foto, foto)), with(0, addrs(foto, foto, foto, foto, foto, foto, foto, foto, foto)), + with(0, addrs(addr("", 0))), with(0, addrs(addr("h", 0))), with(0, addrs(addr("https://ejemplo.org/"+strings.Repeat("a", 1004), 0))), + with(0, addrs(addr("https://ejemplo.org/"+strings.Repeat("a", 1005), 0))), + with(0, addrs(addr("https://ejemplo.org/a", 1))), with(0, addrs(addr("https://ejemplo.org/a", 9007199254740991))), + with(0, addrs(addr("https://ejemplo.org/a", 9007199254740992))), with(0, addrs(addr("https://ejemplo.org/a", 1<<63))), + with(0, addrs(rawAddr{pairs: []rawPair{{0, func(e *enc) { e.text("https://ejemplo.org/a") }}, {1, func(e *enc) { e.uint(0) }}}})), + with(0, addrs(rawAddr{pairs: []rawPair{{1, func(e *enc) { e.uint(5) }}}})), + with(0, addrs(rawAddr{pairs: []rawPair{{0, func(e *enc) { e.text("https://ejemplo.org/a") }}, {2, func(e *enc) { e.uint(5) }}}})), + with(0, addrs(rawAddr{pairs: []rawPair{{1, func(e *enc) { e.uint(5) }}, {0, func(e *enc) { e.text("https://ejemplo.org/a") }}}})), + with(0, addrs(rawAddr{pairs: []rawPair{{0, func(e *enc) { e.bstr([]byte("https://ejemplo.org/a")) }}}})), + with(0, addrs(rawAddr{pairs: []rawPair{{0, func(e *enc) { e.head(3, 2, 0); e.raw([]byte{0xc3, 0x28}) }}}})), + with(0, addrs(rawAddr{pairs: []rawPair{{0, func(e *enc) { e.text("https://ejemplo.org/a") }}, {1, func(e *enc) { e.head(0, 5, 2) }}}})), + with(0, addrs(rawAddr{pairs: []rawPair{{0, func(e *enc) { e.text("https://ejemplo.org/a") }}, {0, func(e *enc) { e.text("https://ejemplo.org/b") }}}})), + with(0, func(e *enc) { e.mapOf(0) }), with(0, func(e *enc) { e.text("https://ejemplo.org/a") }), + with(0, func(e *enc) { e.head(4, 1, 2); addrPairsEnc(e, "https://ejemplo.org/a") }), + with(0, addrs(addr("http://ejemplo.org/a", 0))), with(0, addrs(addr("https://127.0.0.1/a", 0), addr("ipfs://"+locatorCID, 5))), + with(1, func(e *enc) { e.bstr(loc.EnvelopeKey[:31]) }), with(1, func(e *enc) { e.bstr(append(loc.EnvelopeKey[:], 0)) }), + with(1, func(e *enc) { e.text(strings.Repeat("k", 32)) }), + with(2, func(e *enc) { e.bstr(nil) }), with(2, func(e *enc) { e.bstr([]byte{'h'}) }), with(2, func(e *enc) { e.bstr(patterned("h", 1024)) }), + with(2, func(e *enc) { e.bstr(patterned("h", 1025)) }), + with(3, func(e *enc) { e.bstr(loc.RestDigest[:16]) }), with(5, func(e *enc) { e.bstr(nil) }), + with(4, func(e *enc) { e.uint(0) }), with(4, func(e *enc) { e.uint(9007199254740991) }), with(4, func(e *enc) { e.uint(9007199254740992) }), + with(4, func(e *enc) { e.uint(1<<64 - 1) }), with(4, func(e *enc) { e.head(0, loc.RestSize, 9) }), with(4, func(e *enc) { e.text("5") }), + with(7, func(e *enc) { e.uint(0) }), with(1<<53, func(e *enc) { e.uint(0) }), + } { + add(0, l.padded()) + } + // The map: its count, its keys out of order or repeated. + { + l := two + l.count = 5 + add(0, l.padded()) + l.count = 7 + add(0, l.padded()) + l = two + l.pairs = slices.Clone(l.pairs) + l.pairs[1], l.pairs[2] = l.pairs[2], l.pairs[1] + add(0, l.padded()) + l = two + l.pairs = append(slices.Clone(l.pairs), l.pairs[5]) + add(0, l.padded()) + } + // Key 6: absent, empty, of every length near the boundaries of its head, + // not zeros, in another type, not the least multiple. + base := two.encode() + for _, pad := range []int{0, 1, 2, 22, 23, 24, 25, 254, 255, 256, 257, 4096 - len(base) - 3, 4096 - len(base) - 4, 8192 - len(base) - 4} { + add(0, two.encode(rawPair{6, func(e *enc) { e.bstr(make([]byte, pad)) }})) + } + full := 4096 - len(base) - 1 + for _, size := range []int{2, 3, 5, 9} { + n := full - size + add(0, two.encode(rawPair{6, func(e *enc) { e.head(2, uint64(n), size); e.raw(make([]byte, n)) }})) + } + add(0, two.encode(rawPair{6, func(e *enc) { e.text(strings.Repeat("\x00", full-3)) }})) + nonzero := slices.Clone(bases[0]) + nonzero[len(nonzero)-1] = 1 + add(0, nonzero) + nonzero = slices.Clone(bases[0]) + nonzero[len(base)+4] = 0x80 + add(0, nonzero) + add(0, base) + add(0, append(slices.Clone(bases[0]), make([]byte, 4096)...)) + add(0, append(slices.Clone(bases[0]), 0)) + add(0, bases[0][:len(bases[0])-1]) + add(0, nil) + add(0, []byte{0xa0}) + add(0, []byte{0xa7}) + // Bases at the boundaries of the padding: the least multiple and the + // next one. + for _, target := range []int{3836, 3837, 3838, 4069, 4070, 4071, 4093, 4094, 4095, 4096, 4097, 8188, 8189, 8190, 8191, 8192} { + l := lengthFor(w.loc, target) + add(0, l.padded()) + if b := l.encode(); len(b)%4096 != 0 { + for _, total := range []int{(len(b)/4096 + 1) * 4096, (len(b)/4096 + 2) * 4096} { + if pad := fillTo(len(b), total); pad >= 0 { + add(0, l.encode(rawPair{6, func(e *enc) { e.bstr(make([]byte, pad)) }})) + } + } + } + } + // Bytes edited at random, mostly in the map before the padding. + for i := 0; i < 360; i++ { + bi := r.IntN(len(bases)) + b := slices.Clone(bases[bi]) + meaningful := len(raws[bi].encode()) + at := r.IntN(meaningful + 8) + if r.IntN(6) == 0 { + at = r.IntN(len(b)) + } + switch r.IntN(6) { + case 0, 1: + b[at] ^= 1 << r.IntN(8) + case 2: + b[at] = byte(r.IntN(256)) + case 3: + n := 1 + r.IntN(4) + b = slices.Insert(b, at, patterned(fmt.Sprint("insert", i), n)...) + case 4: + n := min(1+r.IntN(4), len(b)-at) + b = slices.Delete(b, at, at+n) + case 5: + b = b[:at] + } + add(bi, b) + } + return out +} + +func addrPairsEnc(e *enc, uri string) { + e.mapOf(1) + e.uint(0) + e.text(uri) +} + +// fillTo is the length of key 6, 0 included, that makes a CBOR of base bytes +// measure total, or -1. +func fillTo(base, total int) int { + for pad := 0; base+2+pad <= total; pad++ { + if base+1+bstrHeadLen(pad)+pad == total { + return pad + } + } + return -1 +} + +// lengthFor returns a plaintext of the envelope of loc whose CBOR without key +// 6 measures target bytes, with addresses that §44.1 accepts. +func lengthFor(loc *locator.Locator, target int) rawLoc { + for _, n := range []int{980, 880, 700} { + long := addrPairs("https://ejemplo.org/"+strings.Repeat("a", n), 0) + for count := 0; count < locator.MaxAddresses; count++ { + for k := 1; k <= locator.MaxURILen-20; k++ { + addrs := append(slices.Repeat([]rawAddr{long}, count), addrPairs("https://ejemplo.org/"+strings.Repeat("b", k), 0)) + l := rawOf(loc, addrs) + n := len(l.encode()) + if n == target { + return l + } + if n > target { + break + } + } + } + } + log.Fatalf("no addresses make a base of %d bytes", target) + return rawLoc{} +} + +// paddingRuns gives PlaintextLength on every base from -4100 to 16484, as +// runs [from, to, total] of bases with the same result. +func paddingRuns() []any { + var out []any + from, last := -4100, locator.PlaintextLength(-4100) + for b := -4099; b <= 16485; b++ { + var got int + if b <= 16484 { + got = locator.PlaintextLength(b) + } + if b == 16485 || got != last { + out = append(out, []any{from, b - 1, last}) + from, last = b, got + } + } + return out +} + +// marshalCases gives Marshal of locators at every limit: [addresses, +// header, rest_size, text, length, SHA-256], the header null for that of +// the envelope. +func (w *world) marshalCases() []any { + t := w.texts + var out []any + type addr = locator.Address + add := func(addrs []addr, header []byte, restSize uint64) { + l := *w.loc + l.Addresses = addrs + var h any + if header != nil { + l.EnvelopeHeader = header + h = hx(header) + } + l.RestSize = restSize + b, err := l.Marshal() + as := []any{} + for _, a := range addrs { + as = append(as, []any{compactURI(a.URI), a.Offset}) + } + c := []any{as, h, restSize, t.of(err), len(b), ""} + if err == nil { + c[5] = sum(b) + } + out = append(out, c) + } + rs := w.loc.RestSize + one := []addr{{URI: "https://ejemplo.org/foto.jpg"}} + foto := addr{URI: "https://ejemplo.org/foto.jpg", Offset: 3000} + add(one, nil, rs) + add([]addr{foto, {URI: "ipfs://" + locatorCID}}, nil, rs) + add(nil, nil, rs) + add(slices.Repeat([]addr{foto}, 8), nil, rs) + add(slices.Repeat([]addr{foto}, 9), nil, rs) + add([]addr{{URI: ""}}, nil, rs) + add([]addr{{URI: "https://ejemplo.org/" + strings.Repeat("a", 1004)}}, nil, rs) + add([]addr{{URI: "https://ejemplo.org/" + strings.Repeat("a", 1005)}}, nil, rs) + add([]addr{{URI: "http://ejemplo.org/a"}}, nil, rs) + add([]addr{foto, {URI: "https://[64:ff9b::7f00:1]/"}}, nil, rs) + add([]addr{{URI: "https://ejemplo.org/a", Offset: 9007199254740991}}, nil, rs) + add([]addr{{URI: "https://ejemplo.org/a", Offset: 9007199254740992}}, nil, rs) + add([]addr{{URI: "http://ejemplo.org/a", Offset: 9007199254740992}}, nil, rs) + add(one, nil, 0) + add(one, nil, 9007199254740991) + add(one, nil, 9007199254740992) + add([]addr{{URI: "http://x"}}, nil, 9007199254740992) + add(one, []byte{}, rs) + add([]addr{{URI: ""}}, []byte{}, rs) + add(one, []byte{'h'}, rs) + add(one, patterned("header", 1024), rs) + add(one, patterned("header", 1025), rs) + // The bases of the boundaries of the padding. + for _, target := range []int{3000, 3836, 3837, 3838, 4069, 4070, 4071, 4072, 4092, 4093, 4094, 4095, 4096, 4097, 4098, 7932, 7933, 7934, + 8165, 8166, 8167, 8188, 8189, 8190, 8191, 8192, 8193} { + l := lengthFor(w.loc, target) + // The addresses of l, read back from its encoding. + back, err := locator.Unmarshal(l.padded()) + if err != nil { + log.Fatalf("a base of %d: %v", target, err) + } + add(back.Addresses, nil, rs) + } + return out +} + +// --------------------------------------------------------------------------- +// Sealed locators + +// sealPlain seals the plaintext of a locator for round as locator.Seal does, +// for a plaintext that Marshal does not write. +func (w *world) sealPlain(round uint64, plain []byte) []byte { + r := must(agewrap.NewTimeRecipient(w.p, round)) + var buf bytes.Buffer + wr := must(age.Encrypt(&buf, r)) + must(wr.Write(plain)) + if err := wr.Close(); err != nil { + log.Fatal(err) + } + return buf.Bytes() +} + +// openCase is Open of base edited: [base, round, release, profile, edits, +// text, summary or null]; release is the round of the release given, or a +// name for another; profile names an edit of the profile, "" for none. +func (w *world) openCase(sealedBases [][]byte, base int, round uint64, release any, prof string, b []byte) []any { + var rel provider.Release + switch x := release.(type) { + case uint64: + rel = w.release(x) + case string: + switch x { + case "flipped": + rel = w.release(round) + rel.Signature = slices.Clone(rel.Signature) + rel.Signature[len(rel.Signature)-1] ^= 1 + case "short": + rel = w.release(round) + rel.Signature = rel.Signature[:47] + case "other round": + rel = w.release(1001) + rel.Round = round + default: + log.Fatalf("release %q", x) + } + } + p := w.p.Clone() + switch prof { + case "": + case "key not on the curve": + p.PublicKey = slices.Clone(p.PublicKey) + p.PublicKey[len(p.PublicKey)-1] ^= 1 + case "key at infinity": + p.PublicKey = append([]byte{0xc0}, make([]byte, 95)...) + case "key of another network": + // The generator of G2, compressed: a key that no release verifies under. + p.PublicKey = must(hex.DecodeString("93e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8")) + case "chain hash of another network": + p.ChainHash[0] ^= 1 + default: + log.Fatalf("profile %q", prof) + } + l, err := locator.Open(p, round, rel, b) + var s any + if err == nil { + s = summary(l) + } + return []any{base, round, release, prof, edits(sealedBases[base], b), w.texts.of(err), s} +} + +func (w *world) openCases(plainBases [][]byte) ([][]byte, []uint64, []any) { + setCryptoRand("sealed") + rounds := []uint64{1000, 1001, 2000, 1004} + plains := [][]byte{plainBases[0], plainBases[0], plainBases[2], plainBases[1]} + var sealed [][]byte + for i, round := range rounds { + sealed = append(sealed, w.sealPlain(round, plains[i])) + } + var out []any + add := func(base int, round uint64, release any, prof string, b []byte) { + out = append(out, w.openCase(sealed, base, round, release, prof, b)) + } + // Each with its release, and with the others. + for i, round := range rounds { + for _, other := range rounds { + add(i, round, other, "", sealed[i]) + if other != round { + add(i, other, other, "", sealed[i]) + } + } + add(i, round, "flipped", "", sealed[i]) + add(i, round, "short", "", sealed[i]) + add(i, round, "other round", "", sealed[i]) + } + add(0, 0, uint64(1000), "", sealed[0]) + for _, prof := range []string{"key not on the curve", "key at infinity", "key of another network", "chain hash of another network"} { + add(0, 1000, uint64(1000), prof, sealed[0]) + } + // The header of the first, edited line by line. + s := sealed[0] + end := must(headerEnd(s)) + header := string(s[:end]) + lines := strings.SplitAfter(header, "\n") + lines = lines[:len(lines)-1] + edit := func(f func(lines []string) []string) []byte { + ls := f(slices.Clone(lines)) + return append([]byte(strings.Join(ls, "")), s[end:]...) + } + stanza := strings.Fields(strings.TrimSpace(lines[1])) + setStanza := func(fields ...string) []byte { + return edit(func(ls []string) []string { ls[1] = strings.Join(fields, " ") + "\n"; return ls }) + } + add(0, 1000, uint64(1000), "", setStanza("->", "tlock", "1001", stanza[3])) + add(0, 1000, uint64(1000), "", setStanza("->", "tlock", "01000", stanza[3])) + add(0, 1000, uint64(1000), "", setStanza("->", "tlock", "1000", stanza[3][:63]+"0")) + add(0, 1000, uint64(1000), "", setStanza("->", "tlock", "1000")) + add(0, 1000, uint64(1000), "", setStanza("->", "tlock", "1000", stanza[3], "x")) + add(0, 1000, uint64(1000), "", setStanza("->", "tlocK", "1000", stanza[3])) + add(0, 1000, uint64(1000), "", setStanza("->", "X25519", "1000", stanza[3])) + add(0, 1000, uint64(1000), "", edit(func(ls []string) []string { + return slices.Insert(ls, 1, "-> X25519 "+base64.RawStdEncoding.EncodeToString(patterned("share", 32))+"\n", base64.RawStdEncoding.EncodeToString(patterned("body", 32))+"\n") + })) + add(0, 1000, uint64(1000), "", edit(func(ls []string) []string { return slices.Insert(ls, 1, slices.Clone(ls[1:len(ls)-1])...) })) + add(0, 1000, uint64(1000), "", edit(func(ls []string) []string { ls[0] = "age-encryption.org/v2\n"; return ls })) + add(0, 1000, uint64(1000), "", edit(func(ls []string) []string { ls[len(ls)-1] = "--- " + strings.Repeat("A", 43) + "\n"; return ls })) + add(0, 1000, uint64(1000), "", edit(func(ls []string) []string { ls[len(ls)-1] = "---\n"; return ls })) + add(0, 1000, uint64(1000), "", edit(func(ls []string) []string { return slices.Delete(ls, 2, 3) })) + add(0, 1000, uint64(1000), "", s[:end]) + add(0, 1000, uint64(1000), "", s[:end+15]) + add(0, 1000, uint64(1000), "", s[:end+16]) + add(0, 1000, uint64(1000), "", s[:end+16+15]) + add(0, 1000, uint64(1000), "", s[:len(s)-1]) + add(0, 1000, uint64(1000), "", append(slices.Clone(s), 0)) + add(0, 1000, uint64(1000), "", nil) + // The body of the stanza: U not on the curve, U at infinity, V and W + // changed, so that only the IBE check finds it. + bodyText := "" + for _, l := range lines[2 : len(lines)-1] { + bodyText += strings.TrimSuffix(l, "\n") + } + body := must(base64.RawStdEncoding.DecodeString(bodyText)) + if len(body) != 128 { + log.Fatalf("a tlock body of %d bytes", len(body)) + } + for _, f := range []func(b []byte){ + func(b []byte) { b[95] ^= 1 }, + func(b []byte) { copy(b, append([]byte{0xc0}, make([]byte, 95)...)) }, + func(b []byte) { b[100] ^= 1 }, + func(b []byte) { b[127] ^= 1 }, + func(b []byte) { b[0] ^= 0x20 }, + } { + nb := slices.Clone(body) + f(nb) + enc := base64.RawStdEncoding.EncodeToString(nb) + add(0, 1000, uint64(1000), "", edit(func(ls []string) []string { + body := []string{enc[:64] + "\n", enc[64:128] + "\n", enc[128:] + "\n"} + return slices.Concat(ls[:2], body, ls[len(ls)-1:]) + })) + } + // The payload, byte by byte at random. + r := newRand("open") + for i := 0; i < 48; i++ { + b := slices.Clone(s) + at := r.IntN(len(b)) + switch r.IntN(4) { + case 0, 1: + b[at] ^= 1 << r.IntN(8) + case 2: + b = slices.Delete(b, at, at+1) + case 3: + b = slices.Insert(b, at, byte(r.IntN(256))) + } + add(0, 1000, uint64(1000), "", b) + } + return sealed, rounds, out +} + +// limitCases are files of round 1000 whose plaintext is the first base and +// zeros after it, in chunks of the STREAM that this program writes, with a +// file key it knows: Go reads at most 1 MiB of plaintext through +// io.LimitReader, so a defect after it is never seen. +func (w *world) limitCases(plain []byte) map[string]any { + setCryptoRand("limit") + rec := &recorder{r: cryptorand.Reader} + cryptorand.Reader = rec + r := must(agewrap.NewTimeRecipient(w.p, 1000)) + var buf bytes.Buffer + wr := must(age.Encrypt(&buf, r)) + if err := wr.Close(); err != nil { + log.Fatal(err) + } + setCryptoRand("limit after") + file := buf.Bytes() + end := must(headerEnd(file)) + fileKey, nonce := rec.got[:16], rec.got[len(rec.got)-16:] + if !bytes.Equal(file[end:end+16], nonce) { + log.Fatal("the nonce is not the last read") + } + streamKey := must(hkdf.Key(sha256.New, fileKey, nonce, "payload", 32)) + aead := must(chacha20poly1305.New(streamKey)) + const chunk = 64 << 10 + content := func(n int) []byte { + b := make([]byte, n) + copy(b, plain) + return b + } + type chunkSpec struct { + n int + last bool + corrupt bool + } + build := func(chunks []chunkSpec, trailing int) []byte { + out := slices.Clone(file[:end+16]) + total := 0 + for _, c := range chunks { + total += c.n + } + text := content(total) + at := 0 + for i, c := range chunks { + var n [12]byte + binary.BigEndian.PutUint64(n[3:11], uint64(i)) + if c.last { + n[11] = 1 + } + ct := aead.Seal(nil, n[:], text[at:at+c.n], nil) + if c.corrupt { + ct[0] ^= 1 + } + out = append(out, ct...) + at += c.n + } + return append(out, make([]byte, trailing)...) + } + full := func(k int) []chunkSpec { return slices.Repeat([]chunkSpec{{n: chunk}}, k) } + var cases []any + add := func(name string, chunks []chunkSpec, trailing int) { + b := build(chunks, trailing) + _, err := locator.Open(w.p, 1000, w.release(1000), b) + cs := []any{} + for _, c := range chunks { + cs = append(cs, []any{c.n, c.last, c.corrupt}) + } + cases = append(cases, map[string]any{"name": name, "chunks": cs, "trailing": trailing, "length": len(b), "sha256": sum(b), "text": w.texts.of(err)}) + } + last := func(cs []chunkSpec) []chunkSpec { cs[len(cs)-1].last = true; return cs } + add("the first base in one chunk", []chunkSpec{{n: len(plain), last: true}}, 0) + add("16 chunks, the last of full length: 1 MiB", last(full(16)), 0) + add("16 chunks and a byte in a 17th", append(full(16), chunkSpec{n: 1, last: true}), 0) + add("16 chunks and a 17th that does not authenticate, which Go never reads", append(full(16), chunkSpec{n: 1, last: true, corrupt: true}), 0) + add("16 chunks and an empty 17th, which Go never reads", append(full(16), chunkSpec{n: 0, last: true}), 0) + add("16 chunks and nothing after them, which Go never reads", full(16), 0) + add("the 16th chunk does not authenticate", append(append(full(15), chunkSpec{n: chunk, corrupt: true}), chunkSpec{n: 1, last: true}), 0) + add("16 chunks, the last of full length, and a byte after it, which Go never reads", last(full(16)), 1) + add("15 chunks, the last of full length, and a byte after it", last(full(15)), 1) + add("15 chunks and a short 16th", append(full(15), chunkSpec{n: 10, last: true}), 0) + add("15 chunks and an empty 16th", append(full(15), chunkSpec{n: 0, last: true}), 0) + add("15 chunks and nothing after them", full(15), 0) + add("17 chunks, the last of full length", last(full(17)), 0) + add("2 chunks, the first marked as the last", []chunkSpec{{n: chunk, last: true}, {n: 5, last: true}}, 0) + return map[string]any{"round": 1000, "header": hx(file[:end]), "nonce": hx(nonce), "file_key": hx(fileKey), "stream_key": hx(streamKey), "cases": cases} +} + +// --------------------------------------------------------------------------- +// The envelope + +// envelopeCases gives OpenEnvelope of the locator of the envelope, each field +// edited, on its rest, edited: [name, key, header edits, rest digest, rest +// size, capsule digest, rest edits, text, SHA-256 of the .dkc]; a field +// empty is that of the envelope. +func (w *world) envelopeCases() []any { + t := w.texts + var out []any + other := must(age.GenerateX25519Identity()) + otherRaw := must(agewrap.RawX25519Identity(other)) + add := func(name string, mutate func(l *locator.Locator), rest []byte) { + l := *w.loc + l.EnvelopeHeader = slices.Clone(l.EnvelopeHeader) + mutate(&l) + dkc, err := l.OpenEnvelope(rest) + c := []any{name, "", edits(w.loc.EnvelopeHeader, l.EnvelopeHeader), "", l.RestSize, "", edits(w.rest, rest), t.of(err), ""} + if l.EnvelopeKey != w.loc.EnvelopeKey { + c[1] = hx(l.EnvelopeKey[:]) + } + if l.RestDigest != w.loc.RestDigest { + c[3] = hx(l.RestDigest[:]) + } + if l.CapsuleDigest != w.loc.CapsuleDigest { + c[5] = hx(l.CapsuleDigest[:]) + } + if err == nil { + c[8] = sum(dkc) + } + out = append(out, c) + } + none := func(*locator.Locator) {} + rest := w.rest + flip := func(b []byte, at int) []byte { b = slices.Clone(b); b[at] ^= 1; return b } + withDigest := func(r []byte) func(*locator.Locator) { + return func(l *locator.Locator) { l.RestDigest = sha256.Sum256(r); l.RestSize = uint64(len(r)) } + } + add("the rest", none, rest) + add("a byte of the rest changed", none, flip(rest, len(rest)/2)) + add("the rest cut by a byte", none, rest[:len(rest)-1]) + add("a byte after the rest", none, append(slices.Clone(rest), 0)) + add("no rest", none, nil) + add("rest_size one byte more", func(l *locator.Locator) { l.RestSize++ }, rest) + add("rest_size of 2^53 - 1", func(l *locator.Locator) { l.RestSize = 1<<53 - 1 }, rest) + add("resto_digest of another rest", func(l *locator.Locator) { l.RestDigest[0] ^= 1 }, rest) + add("capsule_digest of another .dkc", func(l *locator.Locator) { l.CapsuleDigest[31] ^= 1 }, rest) + add("the key of another envelope", func(l *locator.Locator) { copy(l.EnvelopeKey[:], otherRaw) }, rest) + add("the key and the digest wrong", func(l *locator.Locator) { copy(l.EnvelopeKey[:], otherRaw); l.CapsuleDigest[0] ^= 1 }, rest) + // A rest changed whose digest and size the locator gives: age finds it. + for _, at := range []int{0, 15, 16, 17, len(rest) / 2, len(rest) - 17, len(rest) - 1} { + r := flip(rest, at) + add(fmt.Sprintf("the byte %d of the rest changed, with its digest", at), withDigest(r), r) + } + for _, n := range []int{0, 1, 15, 16, 17, 32, len(rest) - 16, len(rest) - 1} { + r := rest[:n] + add(fmt.Sprintf("the rest cut to %d bytes, with its digest", n), withDigest(r), r) + } + r := append(slices.Clone(rest), 7) + add("a byte after the rest, with its digest", withDigest(r), r) + // The header, edited. + header := w.loc.EnvelopeHeader + lines := strings.SplitAfter(string(header), "\n") + lines = lines[:len(lines)-1] + setHeader := func(h string) func(*locator.Locator) { + return func(l *locator.Locator) { l.EnvelopeHeader = []byte(h) } + } + add("the MAC of the header changed", setHeader(strings.Join(lines[:len(lines)-1], "")+"--- "+strings.Repeat("A", 43)+"\n"), rest) + add("the header without its MAC line", setHeader(strings.Join(lines[:len(lines)-1], "")), rest) + add("the header without its last line feed", setHeader(string(header[:len(header)-1])), rest) + add("an X25519 stanza of another share", setHeader(strings.Replace(string(header), lines[1][10:20], strings.Repeat("A", 10), 1)), rest) + add("the intro line of another version", setHeader("age-encryption.org/v2\n"+strings.Join(lines[1:], "")), rest) + add("one byte of header", setHeader("a"), rest) + add("a scrypt stanza", setHeader("age-encryption.org/v1\n-> scrypt "+base64.RawStdEncoding.EncodeToString(patterned("salt", 16))+" 10\n"+ + base64.RawStdEncoding.EncodeToString(patterned("body", 32))+"\n--- "+strings.Repeat("A", 43)+"\n"), rest) + return out +} + +// restInCases gives RestIn on two resources, the host with the rest of the +// envelope after its 300 bytes and nothing: [resource, offset, rest_size, +// text, SHA-256 of the rest read]. +func (w *world) restInCases() ([]byte, []any) { + t := w.texts + var out []any + host := patterned("host file", 300) + file := append(slices.Clone(host), w.rest...) + add := func(resource []byte, offset, size uint64) { + l := *w.loc + l.RestSize = size + r, err := l.RestIn(resource, offset) + which := 0 + if resource == nil { + which = 1 + } + c := []any{which, offset, size, t.of(err), ""} + if err == nil { + c[4] = sum(r) + } + out = append(out, c) + } + n := uint64(len(file)) + rs := w.loc.RestSize + for _, o := range []uint64{0, 1, 299, 300, 301, n - rs - 1, n - rs, n - rs + 1, n - 1, n, n + 1, 1 << 53} { + add(file, o, rs) + } + add(file, 300, 0) + add(file, n, 0) + add(file, n+1, 0) + add(file, 0, n) + add(file, 0, n+1) + add(nil, 0, 0) + add(nil, 0, 1) + add(file, 1, 1<<53) + return file, out +} + +// hideCases gives Hide of hosts and rests: [host, rest, file, offset]. +func hideCases() []any { + var out []any + for _, c := range [][2][]byte{{nil, nil}, {nil, []byte("rest")}, {[]byte("host"), nil}, {patterned("h", 100), patterned("r", 50)}} { + f, off := locator.Hide(c[0], c[1]) + out = append(out, []any{hx(c[0]), hx(c[1]), hx(f), off}) + } + return out +} + +// splitCases gives the split of an age file, as NewEnvelope splits it with +// headerEnd: [file, text, end]. NewEnvelope itself writes the envelope with +// age; the file of the envelope is the first case. +func (w *world) splitCases() []any { + t := w.texts + var out []any + add := func(b []byte) { + n, err := headerEnd(b) + out = append(out, []any{hx(b), t.of(err), n}) + } + f := w.file + end := len(w.loc.EnvelopeHeader) + add(f) + add(f[:end]) + add(f[:end-1]) + add(f[:end-2]) + add(nil) + add([]byte("--- x\n")) + add([]byte("\n--- x\n")) + add([]byte("\n--- x")) + add([]byte("\n--- \n")) + add([]byte("a\n---x\n--- y\nz")) + add([]byte("a\n--- y\n--- z\n")) + return out +} + +// --------------------------------------------------------------------------- +// The extension + +// infoCases gives Info.Extension: [note, profile_id, round, sealed, text, +// length of the data, its SHA-256]; sealed is a hexadecimal, an index into +// the sealed bases, or the number of zeros; the data of the first cases +// that write one are in datas. +func (w *world) infoCases(sealed [][]byte, rounds []uint64) []any { + t := w.texts + var out []any + dk := func(round uint64) datekey.DateKey { + return must(datekey.Resolve(w.p, must(datekey.RoundTime(w.p, round)))) + } + add := func(note string, d datekey.DateKey, s any) { + var b []byte + switch x := s.(type) { + case nil: + case int: + b = sealed[x] + case []byte: + b = x + case string: + n := must(strconv.Atoi(x)) + b = make([]byte, n) + s = map[string]any{"zeros": n} + } + if x, ok := s.([]byte); ok { + s = hx(x) + } + x, err := (&locator.Info{Note: note, DateKey: d, Sealed: b}).Extension() + c := []any{note, d.ProfileID, d.Round, s, t.of(err), 0, ""} + if err == nil { + if x.ID != extension.CapsuleID || x.Version != 1 { + log.Fatal("not datekeys.capsule") + } + c[5], c[6] = len(x.Data), sum(x.Data) + if len(w.datas) < 4 { + w.datas = append(w.datas, x.Data) + } + } + out = append(out, c) + } + d1000 := dk(1000) + add("", d1000, nil) + add("Cartas del viaje a Lisboa", d1000, nil) + add("Cartas del viaje a Lisboa", d1000, 0) + add("", d1000, 0) + for i, round := range rounds { + add("n", dk(round), i) + if round != 1000 { + add("n", d1000, i) + } + } + add("", d1000, []byte("an age file")) + add("", d1000, []byte{}) + add("", d1000, "1048576") + add("", d1000, "1048577") + add("a\tb", d1000, nil) + add("a\nb", d1000, nil) + add(" a", d1000, nil) + add(strings.Repeat("n", 1024), d1000, nil) + add(strings.Repeat("n", 1025), d1000, nil) + add("\xc3\xb1and\xc3\xba", d1000, nil) + add("\xe2\x80\xae", d1000, nil) + add("", datekey.DateKey{ProfileID: "datekeys:quicknet:v1", Round: 0}, nil) + add("", datekey.DateKey{ProfileID: "datekeys:quicknet:v1", Round: 1 << 53}, nil) + add("", datekey.DateKey{ProfileID: "datekeys:quicknet:v1", Round: 1<<53 - 1}, nil) + add("", datekey.DateKey{ProfileID: "Datekeys:quicknet:v1", Round: 1000}, nil) + add("", datekey.DateKey{ProfileID: "datekeys:other:v1", Round: 1000}, nil) + add("", datekey.DateKey{}, nil) + add("a\tb", datekey.DateKey{}, []byte{}) + return out +} + +// openInfoCases gives Info.OpenLocator with the default registry of Go: +// [profile_id, round, sealed, release, text, summary]; sealed is an index +// into the sealed bases or null. +func (w *world) openInfoCases(sealed [][]byte) []any { + reg := must(profile.Default()) + dk := func(round uint64) datekey.DateKey { + return must(datekey.Resolve(w.p, must(datekey.RoundTime(w.p, round)))) + } + var out []any + add := func(d datekey.DateKey, base any, release uint64) { + var b []byte + if i, ok := base.(int); ok { + b = sealed[i] + } + l, err := (&locator.Info{DateKey: d, Sealed: b}).OpenLocator(reg, w.release(release)) + var s any + if err == nil { + s = summary(l) + } + out = append(out, []any{d.ProfileID, d.Round, base, release, w.texts.of(err), s}) + } + add(dk(1000), 0, 1000) + add(dk(1000), nil, 1000) + add(datekey.DateKey{ProfileID: "datekeys:other:v1", Round: 1000}, 0, 1000) + add(dk(1001), 1, 1001) + add(dk(1001), 1, 1000) + add(dk(1000), 1, 1000) + add(dk(2000), 2, 2000) + return out +} + +// parseCase is ParseInfo: [id, version, base, edits, text, note, compact +// datekey, round, SHA-256 of sealed or null]: the data is the edits of the +// base of parse_bases, or absent when base is -1. +func (w *world) parseCase(bases [][]byte, id string, version uint64, data []byte) []any { + x := extension.Extension{ID: id, Version: version, Data: data} + info, err := locator.ParseInfo(x) + base, d := -1, [][]any{} + if data != nil { + // The base whose edits are the shortest. + best := -1 + for i, b := range bases { + e := edits(b, data) + if n := len(asciiJSON(e)); best < 0 || n < best { + base, best, d = i, n, e + } + } + } + c := []any{id, version, base, d, w.texts.of(err), "", "", 0, nil} + if err != nil && !errors.Is(err, datekeys.ErrExtensionDataInvalid) { + log.Fatalf("ParseInfo: %v", err) + } + if err == nil { + c[5], c[6], c[7] = info.Note, info.DateKey.Compact(), info.DateKey.Round + if info.Sealed != nil { + c[8] = sum(info.Sealed) + } + } + return c +} + +func (w *world) parseCases(sealed [][]byte) ([][]byte, []any) { + var out []any + var bases [][]byte + add := func(data []byte) { out = append(out, w.parseCase(bases, extension.CapsuleID, 1, data)) } + dk := must(datekey.Resolve(w.p, must(datekey.RoundTime(w.p, 1000)))).Compact() + build := func(f func(e *enc)) []byte { var e enc; f(&e); return e.b } + pairs := func(ps ...rawPair) []byte { + return build(func(e *enc) { + e.mapOf(uint64(len(ps))) + for _, p := range ps { + e.uint(p.key) + p.val(e) + } + }) + } + text := func(k uint64, s string) rawPair { return rawPair{k, func(e *enc) { e.text(s) }} } + bstr := func(k uint64, b []byte) rawPair { return rawPair{k, func(e *enc) { e.bstr(b) }} } + good := pairs(text(0, "Cartas"), text(1, dk), bstr(2, sealed[0])) + bases = [][]byte{{}, good} + for _, x := range sealed[1:] { + bases = append(bases, pairs(text(1, dk), bstr(2, x))) + } + out = append(out, w.parseCase(bases, extension.NoteID, 1, good), w.parseCase(bases, extension.CapsuleID, 2, good), + w.parseCase(bases, extension.CapsuleID, 0, good), w.parseCase(bases, extension.CapsuleID, 1, nil), w.parseCase(bases, "datekeys.capsulE", 1, good)) + add(good) + add(pairs(text(1, dk))) + add(pairs(text(0, "n"), text(1, dk))) + add(pairs(text(1, dk), bstr(2, sealed[0]))) + add(pairs()) + add(pairs(text(0, "n"))) + add(pairs(text(0, ""), text(1, dk))) + add(pairs(text(0, strings.Repeat("n", 1024)), text(1, dk))) + add(pairs(text(0, strings.Repeat("n", 1025)), text(1, dk))) + add(pairs(text(0, "a\tb"), text(1, dk))) + add(pairs(text(0, "a "), text(1, dk))) + add(pairs(rawPair{0, func(e *enc) { e.head(3, 2, 0); e.raw([]byte{0xff, 0xfe}) }}, text(1, dk))) + add(pairs(rawPair{0, func(e *enc) { e.uint(5) }}, text(1, dk))) + add(pairs(text(1, dk), text(0, "n"))) + add(pairs(text(1, dk), text(1, dk))) + add(pairs(text(1, dk), bstr(3, []byte{0}))) + add(pairs(text(1, dk), text(1<<53, "x"))) + add(pairs(text(1, ""))) + add(pairs(text(1, "dk1_"))) + add(pairs(text(1, dk+"A"))) + add(pairs(text(1, strings.Repeat("d", 1024)))) + add(pairs(text(1, strings.Repeat("d", 1025)))) + add(pairs(bstr(1, []byte(dk)))) + add(pairs(text(1, strings.ToUpper(dk[:4])+dk[4:]))) + // A DateKey whose JSON has its members in another order (spec §19). + raw := must(base64.RawURLEncoding.DecodeString(strings.TrimPrefix(dk, datekey.Prefix))) + var members map[string]any + if err := json.Unmarshal(raw, &members); err != nil { + log.Fatal(err) + } + add(pairs(text(1, datekey.Prefix+base64.RawURLEncoding.EncodeToString(must(json.Marshal(members)))))) + add(pairs(text(1, datekey.Prefix+base64.URLEncoding.EncodeToString(raw)))) + // The locator: empty, another round, stanzas, garbage. + add(pairs(text(1, dk), bstr(2, nil))) + add(pairs(text(1, dk), bstr(2, []byte{0}))) + for i := range sealed { + add(pairs(text(1, dk), bstr(2, sealed[i]))) + } + s := sealed[0] + end := must(headerEnd(s)) + add(pairs(text(1, dk), bstr(2, s[:end]))) + add(pairs(text(1, dk), bstr(2, s[:end-1]))) + lines := strings.SplitAfter(string(s[:end]), "\n") + lines = lines[:len(lines)-1] + stanza := strings.Fields(strings.TrimSpace(lines[1])) + withLine := func(i int, line string) []byte { + ls := slices.Clone(lines) + ls[i] = line + return append([]byte(strings.Join(ls, "")), s[end:]...) + } + for _, line := range []string{"-> tlock 01000 " + stanza[3] + "\n", "-> tlock 1000\n", "-> tlock 1000 " + stanza[3] + " x\n", + "-> tlock 1000 " + strings.Repeat("0", 64) + "\n", "-> X25519 1000 " + stanza[3] + "\n", "-> tlock 1000 " + stanza[3] + "\n"} { + add(pairs(text(1, dk), bstr(2, withLine(1, line)))) + } + add(pairs(text(1, dk), bstr(2, withLine(len(lines)-1, "--- "+strings.Repeat("A", 43)+"\n")))) + two := slices.Clone(lines) + two = slices.Insert(two, 1, slices.Clone(two[1:len(two)-1])...) + add(pairs(text(1, dk), bstr(2, append([]byte(strings.Join(two, "")), s[end:]...)))) + // Data edited at random. + r := newRand("parse") + for i := 0; i < 80; i++ { + b := slices.Clone(good) + at := r.IntN(min(len(b), 600)) + switch r.IntN(4) { + case 0, 1: + b[at] ^= 1 << r.IntN(8) + case 2: + b = slices.Delete(b, at, at+1) + case 3: + b = b[:at] + } + add(b) + } + return bases, out +} + +// registryCases give the registry of locator.Standard on datekeys.capsule +// in a .dkk: [id, version, index into datas or -1 for none, +// CheckNoncriticalIn, CheckCriticalIn, +// CheckWrite noncritical, CheckWrite critical, CheckWrite noncritical with +// extension.Standard]; CheckNoncriticalIn as the texts of the unusable. +func (w *world) registryCases(datas [][]byte) []any { + t := w.texts + std := locator.Standard() + var out []any + add := func(x extension.Extension) { + exts := []extension.Extension{x} + unusable := []any{} + for _, u := range extension.CheckNoncriticalIn(extension.AccessKey, exts, std) { + if u.ID != x.ID || u.Version != x.Version || !errors.Is(u.Err, datekeys.ErrExtensionDataInvalid) { + log.Fatal("an unusable extension of another kind") + } + unusable = append(unusable, t.of(u.Err)) + } + d := slices.IndexFunc(datas, func(d []byte) bool { return x.Data != nil && bytes.Equal(d, x.Data) }) + out = append(out, []any{x.ID, x.Version, d, unusable, + t.of(extension.CheckCriticalIn(extension.AccessKey, exts, std)), + t.of(extension.CheckWrite(std, extension.AccessKey, extension.Noncritical, exts)), + t.of(extension.CheckWrite(std, extension.AccessKey, extension.Critical, exts)), + t.of(extension.CheckWrite(extension.Standard{}, extension.AccessKey, extension.Noncritical, exts))}) + } + for _, d := range datas { + add(extension.Extension{ID: extension.CapsuleID, Version: 1, Data: d}) + } + add(extension.Extension{ID: extension.CapsuleID, Version: 1}) + add(extension.Extension{ID: extension.CapsuleID, Version: 2, Data: []byte{0xa0}}) + return out +} + +// capsuleCases open a fixture of format 2 with its .dkk carrying +// datekeys.capsule, with locator.Standard: the result, the unusable +// extensions of the .dkk and the checks. +func (w *world) capsuleCases(testdata string, datas [][]byte) map[string]any { + t := w.texts + const name = "format2_time_and_key_portable" + dkc := must(os.ReadFile(filepath.Join(testdata, "fixtures", name+".dkc"))) + dkk := must(os.ReadFile(filepath.Join(testdata, "fixtures", name+".dkk"))) + now := time.Date(2026, 10, 6, 0, 0, 0, 0, time.UTC) + reg := must(profile.Default()) + var cases []any + add := func(exts []extension.Extension, std bool) { + k := must(accesskey.Decode(bytes.NewReader(dkk))) + k.Noncritical = exts + var buf bytes.Buffer + if err := accesskey.Encode(&buf, k); err != nil { + log.Fatal(err) + } + opts := capsule.OpenOptions{Registry: reg, Source: fixed{w.release(1000)}, Now: func() time.Time { return now }, AccessKeyFile: bytes.NewReader(buf.Bytes())} + if std { + opts.Extensions = locator.Standard() + } + var out bytes.Buffer + opened, err := capsule.Open(context.Background(), &out, bytes.NewReader(dkc), opts) + unusable := []any{} + checks := []any{} + if opened != nil { + for _, u := range opened.UnusableAccessKeyExtensions { + unusable = append(unusable, []any{u.ID, u.Version, t.of(u.Err)}) + } + for _, c := range opened.Inspection.Checks { + checks = append(checks, []any{c.Step, c.Name, c.OK, c.Detail, c.Error}) + } + } + var spec []any + for _, x := range exts { + i := slices.IndexFunc(datas, func(d []byte) bool { return bytes.Equal(d, x.Data) }) + spec = append(spec, []any{x.ID, x.Version, i, hx(x.Data)}) + } + if spec == nil { + spec = []any{} + } + cases = append(cases, map[string]any{"noncritical": spec, "dkk_sha256": sum(buf.Bytes()), "standard": std, "text": t.of(err), "unusable": unusable, + "checks": checks, "content": sum(out.Bytes())}) + } + for _, d := range datas { + x := extension.Extension{ID: extension.CapsuleID, Version: 1, Data: d} + add([]extension.Extension{x}, true) + } + add([]extension.Extension{{ID: extension.CapsuleID, Version: 1, Data: datas[0]}, {ID: "org.example", Version: 1, Data: []byte{1}}}, true) + add([]extension.Extension{{ID: extension.CapsuleID, Version: 1, Data: datas[len(datas)-1]}}, false) + add(nil, true) + return map[string]any{"fixture": name, "now": now.Format(time.RFC3339Nano), "cases": cases} +} + +type fixed struct{ r provider.Release } + +func (f fixed) Fetch(context.Context, *profile.Profile, provider.Condition) (provider.Release, error) { + return f.r, nil +} + +// --------------------------------------------------------------------------- +// testdata/vectors/locator.json + +type locatorFile struct { + Spec string `json:"spec"` + Round uint64 `json:"round"` + Sealed string `json:"locator_sealed"` + Plaintext string `json:"locator_plaintext"` + Extension string `json:"extension_data"` + Host string `json:"host"` + EnvelopeHeader string `json:"envelope_header"` + Rest string `json:"rest"` + URICases []struct { + URI string `json:"uri"` + OK bool `json:"ok"` + } `json:"uri_cases"` + Mixed struct { + Plaintext string `json:"locator_plaintext"` + Sealed string `json:"locator_sealed"` + } `json:"mixed"` + RestCases []struct { + Name string `json:"name"` + Resource string `json:"resource"` + Offset uint64 `json:"offset"` + Opens bool `json:"opens"` + } `json:"rest_cases"` + ExtensionCases []struct { + Name string `json:"name"` + Data string `json:"extension_data"` + OK bool `json:"ok"` + } `json:"extension_cases"` + PlaintextCases []struct { + Name string `json:"name"` + Plaintext string `json:"locator_plaintext"` + OK bool `json:"ok"` + } `json:"plaintext_cases"` +} + +func unhex(s string) []byte { return must(hex.DecodeString(s)) } + +// testdataCases give the texts of the cases of locator.json: the addresses +// in locator_uris.json, and here the extensions, the plaintexts and the +// rests. +func (w *world) testdataCases(v *locatorFile, uriTexts *textTable) ([]any, map[string]any) { + var uris []any + for _, c := range v.URICases { + u := uriCase(uriTexts, c.URI) + if (u[1] == 0) != c.OK { + log.Fatalf("%q: not the verdict of locator.json", c.URI) + } + uris = append(uris, u) + } + t := w.texts + main, err := locator.Open(w.p, v.Round, w.release(v.Round), unhex(v.Sealed)) + if err != nil { + log.Fatal(err) + } + mixed, err := locator.Open(w.p, v.Round, w.release(v.Round), unhex(v.Mixed.Sealed)) + if err != nil { + log.Fatal(err) + } + ext := []any{} + for _, c := range v.ExtensionCases { + _, err := locator.ParseInfo(extension.Extension{ID: extension.CapsuleID, Version: 1, Data: unhex(c.Data)}) + if (err == nil) != c.OK { + log.Fatalf("%s: not the verdict of locator.json", c.Name) + } + ext = append(ext, t.of(err)) + } + plain := []any{} + for _, c := range v.PlaintextCases { + _, err := locator.Unmarshal(unhex(c.Plaintext)) + if (err == nil) != c.OK { + log.Fatalf("%s: not the verdict of locator.json", c.Name) + } + plain = append(plain, t.of(err)) + } + rest := []any{} + for _, c := range v.RestCases { + r, err := main.RestIn(unhex(c.Resource), c.Offset) + openErr := errors.New("not read") + if err == nil { + _, openErr = main.OpenEnvelope(r) + } + rest = append(rest, []any{t.of(err), t.of(openErr)}) + } + return uris, map[string]any{"main": summary(main), "mixed": summary(mixed), "extension_cases": ext, "plaintext_cases": plain, "rest_cases": rest} +} + +// releases reads the releases of the fixtures, public data of drand. +func releases(testdata string) map[uint64]provider.Release { + out := map[uint64]provider.Release{} + files := must(filepath.Glob(filepath.Join(testdata, "fixtures", "*.json"))) + slices.Sort(files) + for _, f := range files { + var v struct { + Release *struct { + Round uint64 `json:"round"` + Signature string `json:"signature"` + } `json:"release"` + } + if err := json.Unmarshal(must(os.ReadFile(f)), &v); err != nil || v.Release == nil { + continue + } + r := provider.Release{Round: v.Release.Round, Signature: unhex(v.Release.Signature)} + if old, ok := out[r.Round]; ok && !bytes.Equal(old.Signature, r.Signature) { + log.Fatalf("two releases of round %d", r.Round) + } + out[r.Round] = r + } + return out +} + +// --------------------------------------------------------------------------- + +func writeFile(dir, name string, b []byte) { + if len(b) > 900<<10 { + log.Printf("TOO BIG %s: %d bytes", name, len(b)) + } + path := filepath.Join(dir, name) + if err := os.WriteFile(path, b, 0o644); err != nil { + log.Fatal(err) + } + fmt.Printf("wrote %s, %d bytes\n", path, len(b)) +} + +func main() { + testdata := flag.String("testdata", "", "the directory testdata of datekeys-ts") + outDir := flag.String("out", "", "the directory src/lib/dkc/testing of datekeys-ts") + flag.Parse() + if *testdata == "" || *outDir == "" { + log.Fatal("usage: go run locator-go-vectors.go -testdata DIR -out DIR") + } + var v locatorFile + if err := json.Unmarshal(must(os.ReadFile(filepath.Join(*testdata, "vectors", "locator.json"))), &v); err != nil { + log.Fatal(err) + } + + w := &world{p: profile.Quicknet(), texts: newTexts(), releases: releases(*testdata)} + setCryptoRand("envelope") + w.dkc = patterned("locator dkc", 777) + w.loc, w.rest = must2(locator.NewEnvelope(w.dkc)) + w.file = append(slices.Clone(w.loc.EnvelopeHeader), w.rest...) + w.loc.Addresses = []locator.Address{{URI: "https://ejemplo.org/foto.jpg", Offset: 3000}, {URI: "ipfs://" + locatorCID}} + + // The addresses. + ut := newTexts() + testdataURIs, testdataRest := w.testdataCases(&v, ut) + uris := uriCases(ut, newRand("uris")) + ips := ipCases(newRand("ips")) + public := publicCases(newRand("public")) + wtf8 := wtf8Cases(ut) + ud := newDocument() + ud.value("description", "The addresses of a locator (spec v0.12 §44.1) for src/lib/dkc/locator.ts; see scripts/locator-go-vectors.go. testdata: [uri, text, host] of locator.CheckURI and "+ + "Address.Host on the uri_cases of testdata/vectors/locator.json, in their order; uris: the same on addresses built at the edges of §44.1 and drawn from a seed; "+ + "wtf8: [bytes, text] of CheckURI on bytes with a surrogate. "+ + "A text is an index into texts, 0 for none. ips: [s, false] or [s, true, bytes, zone, String, publicIP] of netip.ParseAddr. public: [bytes, publicIP, String] "+ + "of netip.AddrFromSlice, or [bytes, null] for a length that is no address.") + ud.value("generator", "scripts/locator-go-vectors.go, "+runtime.Version()) + ud.list("texts", anyList(ut.list)) + ud.list("testdata", testdataURIs) + ud.list("uris", uris) + ud.list("wtf8", wtf8) + ud.list("ips", ips) + ud.list("public", public) + uj := ud.bytes() + writeFile(*outDir, "locator-uris.json", uj) + + // The locator. + plainBases, raws := w.plaintextBases() + plaintexts := w.plaintextCases(plainBases, raws) + marshal := w.marshalCases() + sealed, rounds, opens := w.openCases(plainBases) + limit := w.limitCases(plainBases[0]) + setCryptoRand("envelopes") + envelopes := w.envelopeCases() + restResource, restIn := w.restInCases() + split := w.splitCases() + setCryptoRand("info") + info := w.infoCases(sealed, rounds) + openInfo := w.openInfoCases(sealed) + parseBases, parse := w.parseCases(sealed) + datas := append(slices.Clone(w.datas), []byte{0xa0}, []byte("Cartas")) + registry := w.registryCases(datas) + capsules := w.capsuleCases(*testdata, datas) + + rel := map[string]string{} + for _, round := range []uint64{1000, 1001, 1004, 2000} { + rel[strconv.FormatUint(round, 10)] = hx(w.release(round).Signature) + } + envelope := map[string]any{"dkc": hx(w.dkc), "key": hx(w.loc.EnvelopeKey[:]), "header": hx(w.loc.EnvelopeHeader), "rest": hx(w.rest), + "rest_digest": hx(w.loc.RestDigest[:]), "rest_size": w.loc.RestSize, "capsule_digest": hx(w.loc.CapsuleDigest[:])} + var sealedBases []any + for i := range sealed { + sealedBases = append(sealedBases, map[string]any{"round": rounds[i], "sealed": hx(sealed[i])}) + } + + vd := newDocument() + vd.value("description", "The locator and the envelope of datekeys.capsule (spec v0.12 §44.1) for src/lib/dkc/locator.ts and envelope.ts; see scripts/locator-go-vectors.go for each list. "+ + "A text is an index into texts, 0 for none; an edited value is a list of edits [at, delete, insert] of its base.") + vd.value("generator", "scripts/locator-go-vectors.go, "+runtime.Version()) + vd.list("texts", anyList(w.texts.list)) + vd.value("releases", rel) + vd.value("envelope", envelope) + vd.list("padding", paddingRuns()) + vd.value("testdata", testdataRest) + vd.list("plaintext_bases", anyList(mapHex(plainBases))) + vd.list("sealed_bases", sealedBases) + vd.list("parse_bases", anyList(mapHex(parseBases))) + vd.list("datas", anyList(mapHex(datas))) + vd.value("rest_in_resource", hx(restResource)) + vd.list("plaintexts", plaintexts) + vd.list("marshal", marshal) + vd.list("open", opens) + vd.value("limit", limit) + vd.list("envelopes", envelopes) + vd.list("rest_in", restIn) + vd.list("hide", hideCases()) + vd.list("split", split) + vd.list("info", info) + vd.list("open_info", openInfo) + vd.list("parse", parse) + vd.list("registry", registry) + vd.value("capsule", capsules) + vj := vd.bytes() + writeFile(*outDir, "locator-vectors.json", vj) +} + +func must2[A, B any](a A, b B, err error) (A, B) { + if err != nil { + log.Fatal(err) + } + return a, b +} + +func mapHex(bs [][]byte) []string { + out := make([]string, len(bs)) + for i, b := range bs { + out[i] = hx(b) + } + return out +} diff --git a/src/lib/dkc/ipaddr.ts b/src/lib/dkc/ipaddr.ts new file mode 100644 index 0000000..14e4f83 --- /dev/null +++ b/src/lib/dkc/ipaddr.ts @@ -0,0 +1,289 @@ +// The IP addresses of the addresses of a locator (spec §44.1), as Go's +// net/netip reads and writes them and package locator of datekeys-go +// classifies them: parseIpAddress accepts exactly what netip.ParseAddr +// accepts, ipString is netip's String, and isPublicIp is publicIP, with the +// blocks of the IANA registries of special-purpose addresses that §44.1 +// lists. +// +// An address is held as its bytes, 4 for IPv4 and 16 for IPv6, and every +// operation works byte by byte: the 128 bits of an IPv6 address are never a +// number, which in JavaScript is a double with 53 bits and whose bit +// operators work on 32 bits. +// +// Internal: index.ts does not re-export it. + +import { utf8Bytes } from './bytes.ts'; + +/** + * An IP address, as Go's netip.Addr: an IPv4 address of 4 bytes, or an IPv6 + * address of 16 bytes with a zone, "" for none. An IPv4-mapped IPv6 address + * is an IPv6 address. + */ +export interface IpAddress { + readonly bytes: Uint8Array; + readonly zone: string; +} + +/** + * The address of the 4 or 16 bytes `b`, without a zone, as Go's + * netip.AddrFromSlice; any other length is a RangeError. It keeps a copy. + */ +export function ipFromBytes(b: Uint8Array): IpAddress { + if (!(b instanceof Uint8Array) || (b.length !== 4 && b.length !== 16)) { + throw new RangeError('ipaddr: an IP address is 4 or 16 bytes'); + } + return { bytes: b.slice(), zone: '' }; +} + +/** Whether `a` is an IPv4 address, as netip's Is4: an IPv4-mapped IPv6 address is not. */ +export const isIpv4 = (a: IpAddress): boolean => a.bytes.length === 4; + +/** Whether `a` is an IPv6 address, IPv4-mapped ones included. */ +export const isIpv6 = (a: IpAddress): boolean => a.bytes.length === 16; + +// Whether the IPv6 address a is an IPv4-mapped one, of ::ffff:0:0/96. +function is4In6(a: IpAddress): boolean { + for (let i = 0; i < 10; i++) if (a.bytes[i] !== 0) return false; + return a.bytes[10] === 0xff && a.bytes[11] === 0xff; +} + +const dotted = (b: Uint8Array, at: number): string => `${b[at]}.${b[at + 1]}.${b[at + 2]}.${b[at + 3]}`; + +/** + * The address as netip's String writes it: dotted decimal for IPv4; "::ffff:" + * and dotted decimal for an IPv4-mapped IPv6 address; and the form of RFC + * 5952 for the others, the first longest run of two or more groups of zeros + * written "::"; with "%" and the zone when it has one. + */ +export function ipString(a: IpAddress): string { + const b = a.bytes; + if (isIpv4(a)) return dotted(b, 0); + const zoned = a.zone === '' ? '' : `%${a.zone}`; + if (is4In6(a)) return `::ffff:${dotted(b, 12)}${zoned}`; + const groups: number[] = []; + for (let i = 0; i < 8; i++) groups.push((b[2 * i]! << 8) | b[2 * i + 1]!); + // The first longest run of two or more groups of zeros, as appendTo6. + let zeroStart = 255; + let zeroEnd = 255; + for (let i = 0; i < 8; i++) { + let j = i; + while (j < 8 && groups[j] === 0) j++; + if (j - i >= 2 && j - i > zeroEnd - zeroStart) { + zeroStart = i; + zeroEnd = j; + } + } + let out = ''; + for (let i = 0; i < 8; i++) { + if (i === zeroStart) { + out += '::'; + i = zeroEnd; + if (i >= 8) break; + } else if (i > 0) { + out += ':'; + } + out += groups[i]!.toString(16); + } + return out + zoned; +} + +const DOT = 0x2e; +const COLON = 0x3a; +const PERCENT = 0x25; +const isDigit = (c: number): boolean => c >= 0x30 && c <= 0x39; + +/** + * The address that `s` writes, or undefined when Go's netip.ParseAddr + * rejects it: dotted decimal IPv4 of four fields of 0 to 255 without leading + * zeros ("192.0.2.1"), or IPv6 of eight groups of one to four hexadecimal + * digits in either case, with at most one "::" that stands for one or more + * groups of zeros and the last two groups possibly written as an IPv4 + * address ("2001:db8::68", "::ffff:192.0.2.1"), and possibly "%" and a + * non-empty zone of any characters ("fe80::1%eth0"). The first ".", ":" or + * "%" of `s` decides which, as in Go. Nothing else is accepted: no brackets, + * no spaces, no other notation of IPv4. `s` is read as its UTF-8 bytes, as Go + * reads a string. + */ +export function parseIpAddress(s: string): IpAddress | undefined { + const b = utf8Bytes(s); + for (const c of b) { + if (c === DOT) { + const fields = new Uint8Array(4); + return ipv4Fields(b, 0, b.length, fields, 0) ? { bytes: fields, zone: '' } : undefined; + } + if (c === COLON) return parseIpv6(b, s); + // An IPv6 address with a zone and without the address. + if (c === PERCENT) return undefined; + } + return undefined; +} + +// Go's parseIPv4Fields: the four fields of b[off:end] into +// fields[at..at+4], or false. +function ipv4Fields(b: Uint8Array, off: number, end: number, fields: Uint8Array, at: number): boolean { + let val = 0; + let pos = 0; + let digLen = 0; + const n = end - off; + for (let i = 0; i < n; i++) { + const c = b[off + i]!; + if (isDigit(c)) { + // A field with a leading zero. + if (digLen === 1 && val === 0) return false; + val = val * 10 + c - 0x30; + digLen++; + if (val > 255) return false; + } else if (c === DOT) { + // .1.2.3, 1.2.3. and 1..2.3: a field without digits. + if (i === 0 || i === n - 1 || b[off + i - 1] === DOT) return false; + // 1.2.3.4.5 + if (pos === 3) return false; + fields[at + pos] = val; + pos++; + val = 0; + digLen = 0; + } else { + return false; + } + } + if (pos < 3) return false; + fields[at + 3] = val; + return true; +} + +function hexValue(c: number): number { + if (isDigit(c)) return c - 0x30; + if (c >= 0x61 && c <= 0x66) return c - 0x61 + 10; + if (c >= 0x41 && c <= 0x46) return c - 0x41 + 10; + return -1; +} + +// Go's parseIPv6 of the bytes b of the string s. +function parseIpv6(b: Uint8Array, s: string): IpAddress | undefined { + // The zone, split off first. Once the address before it parses, it is + // ASCII, so the byte offset of the % is also its index in s. + let end = b.length; + const pct = b.indexOf(PERCENT); + if (pct >= 0) { + if (pct + 1 === b.length) return undefined; + end = pct; + } + const zone = (): string => (pct < 0 ? '' : s.slice(pct + 1)); + const ip = new Uint8Array(16); + let ellipsis = -1; + let p = 0; + if (end - p >= 2 && b[p] === COLON && b[p + 1] === COLON) { + ellipsis = 0; + p += 2; + if (p === end) return { bytes: ip, zone: zone() }; + } + let i = 0; + while (i < 16) { + // One group of at most four hexadecimal digits. + let off = 0; + let acc = 0; + for (; p + off < end; off++) { + const v = hexValue(b[p + off]!); + if (v < 0) break; + acc = acc * 16 + v; + if (off > 3) return undefined; + } + if (off === 0) return undefined; + // A dot: the group starts the trailing IPv4 address. + if (p + off < end && b[p + off] === DOT) { + if (ellipsis < 0 && i !== 12) return undefined; + if (i + 4 > 16) return undefined; + if (!ipv4Fields(b, p, end, ip, i)) return undefined; + p = end; + i += 4; + break; + } + ip[i] = acc >> 8; + ip[i + 1] = acc & 0xff; + i += 2; + p += off; + if (p === end) break; + if (b[p] !== COLON) return undefined; + if (end - p === 1) return undefined; + p++; + if (b[p] === COLON) { + if (ellipsis >= 0) return undefined; + ellipsis = i; + p++; + if (p === end) break; + } + } + if (p !== end) return undefined; + if (i < 16) { + if (ellipsis < 0) return undefined; + const n = 16 - i; + for (let j = i - 1; j >= ellipsis; j--) ip[j + n] = ip[j]!; + ip.fill(0, ellipsis, ellipsis + n); + } else if (ellipsis >= 0) { + // "::" stands for at least one group of zeros. + return undefined; + } + return { bytes: ip, zone: zone() }; +} + +// A block of addresses, as Go's netip.Prefix: its first bytes and the number +// of bits that count. +interface Prefix { + readonly bytes: Uint8Array; + readonly bits: number; +} + +function prefix(s: string): Prefix { + const [addr, bits] = s.split('/'); + return { bytes: parseIpAddress(addr!)!.bytes, bits: Number(bits) }; +} + +// Whether a is in the block p, as netip's Prefix.Contains: an address of the +// other family, or with a zone, never is. +function contains(p: Prefix, a: IpAddress): boolean { + if (a.zone !== '' || a.bytes.length !== p.bytes.length) return false; + const full = p.bits >> 3; + for (let i = 0; i < full; i++) if (a.bytes[i] !== p.bytes[i]) return false; + const rest = p.bits & 7; + if (rest === 0) return true; + const mask = (0xff << (8 - rest)) & 0xff; + return (a.bytes[full]! & mask) === (p.bytes[full]! & mask); +} + +// The blocks of the IANA registries of special-purpose addresses that an +// address of a locator may not use (spec §44.1). An IPv6 address must also +// be a global unicast one, of 2000::/3. +const NOT_PUBLIC_4 = [ + '0.0.0.0/8', + '10.0.0.0/8', + '100.64.0.0/10', + '127.0.0.0/8', + '169.254.0.0/16', + '172.16.0.0/12', + '192.0.0.0/24', + '192.0.2.0/24', + '192.88.99.0/24', + '192.168.0.0/16', + '198.18.0.0/15', + '198.51.100.0/24', + '203.0.113.0/24', + '224.0.0.0/4', + '240.0.0.0/4', +].map(prefix); +const GLOBAL_6 = ['2000::/3'].map(prefix); +const NOT_PUBLIC_6 = ['2001::/23', '2001:db8::/32', '2002::/16', '3fff::/20'].map(prefix); + +const inAny = (a: IpAddress, ps: readonly Prefix[]): boolean => ps.some((p) => contains(p, a)); + +/** + * Whether `a` is an address of the public Internet (spec §44.1), as publicIP + * of Go: an IPv4 address outside the blocks of §44.1, or an IPv6 address of + * 2000::/3 outside 2001::/23, 2001:db8::/32, 2002::/16 and 3fff::/20. An IPv6 + * address that holds an IPv4 one, mapped, compatible, of NAT64, 6to4 or + * Teredo, is not: it would reach the IPv4 address without the check of an + * IPv4 address. Nor is one with a zone. + */ +export function isPublicIp(a: IpAddress): boolean { + if (isIpv4(a)) return !inAny(a, NOT_PUBLIC_4); + return inAny(a, GLOBAL_6) && !inAny(a, NOT_PUBLIC_6); +} diff --git a/src/lib/dkc/locator.test.ts b/src/lib/dkc/locator.test.ts new file mode 100644 index 0000000..774cbeb --- /dev/null +++ b/src/lib/dkc/locator.test.ts @@ -0,0 +1,412 @@ +// The locator without its cryptography against Go (spec §43 to §44.1): +// +// - testing/locator-uris.json, from scripts/locator-go-vectors.go: checkURI +// and addressHost on the 247 addresses of testdata/vectors/locator.json +// and on 2 800 built at the edges of §44.1 and drawn from a seed, with the +// texts of CheckURI and Host; parseIpAddress against netip.ParseAddr and +// String; and isPublicIp on the bytes of addresses, as checkResolvedIp +// checks the IP that a name resolves to; +// - testing/locator-vectors.json: PlaintextLength on every base from -4100 +// to 16484; Unmarshal on 482 plaintexts, valid and broken; Marshal at +// every limit; RestIn and Hide; Info.Extension and ParseInfo; the registry +// of locator.Standard; and capsule.Open of a fixture whose .dkk carries +// datekeys.capsule, with locator.Standard. +// +// Every text is Go's, byte for byte. + +import { describe, expect, it } from 'vitest'; +import { decodeAccessKey, encodeAccessKey } from './accesskey.ts'; +import { fromHex, toHex } from './bytes.ts'; +import { compactDateKey, parseRFC3339 } from './datekey.ts'; +import { DateKeysError } from './errors.ts'; +import { CAPSULE_ID, checkCritical, checkNoncritical, checkWrite, type Extension, NOTE_ID } from './extension.ts'; +import { ipFromBytes, ipString, isPublicIp, parseIpAddress } from './ipaddr.ts'; +import { + addressHost, + checkResolvedIp, + checkURI, + hide, + infoExtension, + type Locator, + LocatorError, + marshalLocator, + parseInfo, + plaintextLength, + restIn, + standardExtensions, + unmarshalLocator, + usableAddresses, +} from './locator.ts'; +import { MemorySink } from './sink.ts'; +import { open } from './open.ts'; +import { suppliedRelease } from './release.ts'; +import { + applyEdits, + errorText, + expandSummary, + fromWtf8, + type Json, + readVectors, + releaseOf, + type Row, + rows, + sha256Hex, + summaryOf, + textOf, + uriOf, +} from './testing/locator.ts'; +import { readBytes } from './testing/testdata.ts'; + +const uris = readVectors('locator-uris.json'); +const v = readVectors('locator-vectors.json'); + +describe('checkURI and addressHost (locator-uris.json)', () => { + function expectUri(c: Row): void { + const uri = c[0] as string; + const want = textOf(uris, c[1]); + expect(errorText(() => checkURI(uri)), uri).toBe(want); + const host = addressHost({ uri, offset: 0 }); + expect(host, uri).toBe(c[2]); + // As FuzzCheckURI of Go: the host shown is in the address as it is + // written, with no decoding. + if (want === '') expect(uri.includes(host) && host !== '').toBe(true); + } + + it('the addresses of locator.json, with the texts of Go', () => { + const cases = rows(uris, 'testdata'); + expect(cases.length).toBe(247); + cases.forEach(expectUri); + }); + + it('2 800 addresses at the edges of §44.1 and drawn from a seed', () => { + const cases = rows(uris, 'uris'); + expect(cases.length).toBe(2800); + cases.forEach(expectUri); + // Every rule rejects some and some pass. + expect(new Set(cases.map((c) => c[1])).size).toBeGreaterThan(20); + }); + + it('an address that is not ASCII is measured and quoted in UTF-8, and a lone surrogate as the bytes of WTF-8', () => { + const c = rows(uris, 'uris').find((x) => x[0] === 'https://ejemplo.org/é')!; + expectUri(c); + expect(textOf(uris, c[1])).toContain("'Ã'"); + const wtf8 = rows(uris, 'wtf8'); + expect(wtf8.length).toBe(4); + for (const w of wtf8) { + const uri = fromWtf8(fromHex(w[0] as string)); + expect(errorText(() => checkURI(uri))).toBe(textOf(uris, w[1])); + expect(addressHost({ uri, offset: 0 })).toBe(''); + } + }); + + it('Go accepts a doubled opening bracket, and a CID with one more character whose bits are zero (pending decisions 1 and 2 of the author)', () => { + for (const uri of ['https://[[2000::]/', 'ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdia']) { + const c = rows(uris, 'uris').find((x) => x[0] === uri); + expect(c?.[1], uri).toBe(0); + expectUri(c!); + } + }); +}); + +describe('IP addresses (locator-uris.json)', () => { + it('parseIpAddress accepts what netip.ParseAddr accepts, with its bytes, its zone and its String', () => { + const cases = rows(uris, 'ips'); + expect(cases.length).toBe(1700); + for (const c of cases) { + const s = c[0] as string; + const a = parseIpAddress(s); + if (c[1] === false) { + expect(a, s).toBeUndefined(); + continue; + } + expect([toHex(a!.bytes), a!.zone, ipString(a!), isPublicIp(a!)], s).toEqual(c.slice(2)); + } + }); + + it('checkResolvedIp checks the bytes of an address as publicIP of Go, IPv4-mapped and NAT64 addresses not public', () => { + const cases = rows(uris, 'public'); + expect(cases.length).toBe(868); + for (const c of cases) { + const b = fromHex(c[0] as string); + if (c[1] === null) { + expect(() => checkResolvedIp(b)).toThrow(RangeError); + continue; + } + const a = ipFromBytes(b); + expect(ipString(a)).toBe(c[2]); + expect(isPublicIp(a), ipString(a)).toBe(c[1]); + expect(errorText(() => checkResolvedIp(b))).toBe( + c[1] === true ? '' : `locator: an https address whose name resolves to ${c[2] as string}, an IP address that is not public`, + ); + } + expect(() => checkResolvedIp(Uint8Array.of(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, 8, 8, 8, 8))).toThrow(LocatorError); + expect(() => checkResolvedIp(fromHex('0064ff9b000000000000000008080808'))).toThrow(LocatorError); + checkResolvedIp(Uint8Array.of(8, 8, 8, 8)); + // An embedded IPv4 address with no room left, which netip refuses too. + expect(parseIpAddress('1::2:3:4:5:6:7:1.2.3.4')).toBeUndefined(); + expect(ipString(parseIpAddress('1:2:3:4:5:6:1.2.3.4')!)).toBe('1:2:3:4:5:6:102:304'); + expect(() => checkResolvedIp([8, 8, 8, 8] as unknown as Uint8Array)).toThrow(RangeError); + }); +}); + +describe('the locator against Go (locator-vectors.json)', () => { + const envelope = v.envelope as Json; + const plainBases = (v.plaintext_bases as string[]).map(fromHex); + const sealedBases = (v.sealed_bases as Json[]).map((s) => fromHex(s.sealed as string)); + const parseBases = (v.parse_bases as string[]).map(fromHex); + const datas = (v.datas as string[]).map(fromHex); + + function base(over: Partial = {}): Locator { + return { + addresses: [{ uri: 'https://ejemplo.org/foto.jpg', offset: 3000 }], + envelopeKey: fromHex(envelope.key as string), + envelopeHeader: fromHex(envelope.header as string), + restDigest: fromHex(envelope.rest_digest as string), + restSize: envelope.rest_size as number, + capsuleDigest: fromHex(envelope.capsule_digest as string), + ...over, + }; + } + + it('plaintextLength gives the least multiple that key 6 fills, on every base from -4100 to 16484', () => { + let n = 0; + for (const r of rows(v, 'padding')) { + for (let b = r[0] as number; b <= (r[1] as number); b++) { + expect(plaintextLength(b), String(b)).toBe(r[2]); + n++; + } + } + expect(n).toBe(4100 + 1 + 16484); + expect(() => plaintextLength(0.5)).toThrow(RangeError); + }); + + it('unmarshalLocator reads plaintexts as Unmarshal', () => { + const cases = rows(v, 'plaintexts'); + expect(cases.length).toBe(482); + for (const c of cases) { + const b = applyEdits(plainBases[c[0] as number]!, c[1]); + let l: Locator | undefined; + expect(errorText(() => (l = unmarshalLocator(b))), JSON.stringify(c[1])).toBe(textOf(v, c[2])); + if (c[3] !== null) { + expect(summaryOf(l!)).toEqual(expandSummary(c[3])); + // As FuzzUnmarshal of Go: an address that a reader uses passes the + // rules, and has a host to show. + for (const a of usableAddresses(l!)) { + checkURI(a.uri); + expect(addressHost(a)).not.toBe(''); + } + } + } + }); + + it('marshalLocator writes the plaintext of Marshal, or its error', () => { + const cases = rows(v, 'marshal'); + expect(cases.length).toBe(49); + for (const c of cases) { + const l = base({ + addresses: (c[0] as Row[]).map((a) => ({ uri: uriOf(a[0]), offset: a[1] as number })), + ...(c[1] === null ? {} : { envelopeHeader: fromHex(c[1] as string) }), + restSize: c[2] as number, + }); + let b: Uint8Array | undefined; + expect(errorText(() => (b = marshalLocator(l))), JSON.stringify(c.slice(1, 3))).toBe(textOf(v, c[3])); + if (b !== undefined) { + expect([b.length, sha256Hex(b)]).toEqual([c[4], c[5]]); + // It reads back as it was written. + expect(summaryOf(unmarshalLocator(b))).toEqual(summaryOf(l)); + } + } + }); + + it('restIn reads rest_size bytes from the offset, as RestIn', () => { + const resources = [fromHex(v.rest_in_resource as string), new Uint8Array(0)]; + const cases = rows(v, 'rest_in'); + expect(cases.length).toBe(20); + for (const c of cases) { + const l = base({ restSize: c[2] as number }); + let r: Uint8Array | undefined; + expect(errorText(() => (r = restIn(l, resources[c[0] as number]!, c[1] as number))), JSON.stringify(c)).toBe(textOf(v, c[3])); + if (r !== undefined) expect(sha256Hex(r)).toBe(c[4]); + } + expect(() => restIn(base(), new Uint8Array(3), -1)).toThrow(RangeError); + }); + + it('hide appends the rest, as Hide', () => { + const cases = rows(v, 'hide'); + expect(cases.length).toBe(4); + for (const c of cases) { + const { file, offset } = hide(fromHex(c[0] as string), fromHex(c[1] as string)); + expect([toHex(file), offset]).toEqual([c[2], c[3]]); + } + }); + + it('infoExtension writes the data of datekeys.capsule as Info.Extension', () => { + const cases = rows(v, 'info'); + expect(cases.length).toBe(29); + for (const c of cases) { + const s = c[3]; + let sealed: Uint8Array | undefined; + if (typeof s === 'number') sealed = sealedBases[s]; + else if (typeof s === 'string') sealed = fromHex(s); + else if (s !== null) sealed = new Uint8Array((s as Json).zeros as number); + const info = { note: c[0] as string, dateKey: { profileId: c[1] as string, round: c[2] as number }, sealed }; + let x: Extension | undefined; + expect(errorText(() => (x = infoExtension(info))), JSON.stringify(c.slice(0, 3))).toBe(textOf(v, c[4])); + if (x !== undefined) { + expect([x.id, x.version]).toEqual([CAPSULE_ID, 1]); + expect([x.data!.length, sha256Hex(x.data!)]).toEqual([c[5], c[6]]); + // A reader reads it back. + const back = parseInfo(x); + expect([back.note, back.dateKey]).toEqual([info.note, info.dateKey]); + expect(back.sealed).toEqual(info.sealed); + } + } + }); + + it('parseInfo reads the data of datekeys.capsule as ParseInfo, with ERR_EXTENSION_DATA_INVALID only', () => { + const std = standardExtensions(); + const cases = rows(v, 'parse'); + expect(cases.length).toBe(127); + for (const c of cases) { + const b = c[2] as number; + const data = b < 0 ? undefined : applyEdits(parseBases[b]!, c[3]); + const x: Extension = { id: c[0] as string, version: c[1] as number, data }; + const want = textOf(v, c[4]); + let info: ReturnType | undefined; + let err: unknown; + try { + info = parseInfo(x); + } catch (e) { + err = e; + } + expect(err === undefined ? '' : (err as Error).message, JSON.stringify(c.slice(0, 4))).toBe(want); + if (want !== '') { + expect((err as DateKeysError).code).toBe('ERR_EXTENSION_DATA_INVALID'); + if (x.id === CAPSULE_ID && x.version === 1 && x.data !== undefined) { + expect(std.validateData!(x)?.message).toBe(want); + } + continue; + } + expect(std.validateData!(x)).toBeUndefined(); + expect([info!.note, compactDateKey(info!.dateKey), info!.dateKey.round]).toEqual([c[5], c[6], c[7]]); + expect(info!.sealed === undefined ? null : sha256Hex(info!.sealed)).toBe(c[8]); + } + }); + + it('the registry of locator.Standard checks the data of datekeys.capsule in a .dkk, and an encoder only its presence', () => { + const std = standardExtensions(); + const presence = standardExtensions(null); + const validate = (e: Extension): void => { + parseInfo(e); + }; + // checkWrite of this library names its writer before the text of Go. + const write = (arr: 'critical_extensions' | 'noncritical_extensions', exts: Extension[], check?: (e: Extension) => void): string => { + const t = errorText(() => checkWrite('w', '.dkk', arr, exts, check)); + return t === '' ? '' : t.replace(/^w: /, ''); + }; + const cases = rows(v, 'registry'); + expect(cases.length).toBe(8); + for (const c of cases) { + const d = c[2] as number; + const x: Extension = { id: c[0] as string, version: c[1] as number, data: d < 0 ? undefined : datas[d] }; + expect( + checkNoncritical([x], std, '.dkk').map((u) => u.error.message), + JSON.stringify(c.slice(0, 3)), + ).toEqual((c[3] as number[]).map((i) => textOf(v, i))); + expect(errorText(() => checkCritical([x], std, '.dkk'))).toBe(textOf(v, c[4])); + expect(write('noncritical_extensions', [x], validate)).toBe(textOf(v, c[5])); + expect(write('critical_extensions', [x], validate)).toBe(textOf(v, c[6])); + expect(write('noncritical_extensions', [x])).toBe(textOf(v, c[7])); + // Without ValidateCapsule only the presence of the data counts. + expect(presence.validateData!(x)?.message ?? '').toBe(x.data === undefined && x.version === 1 ? 'datekeys.capsule without data: ERR_EXTENSION_DATA_INVALID' : ''); + } + // The note: in PUBLIC_HEADER only, with the rules of §24.1. + expect(std.registeredIn!(NOTE_ID, 1, 'PUBLIC_HEADER', 'noncritical_extensions')).toBe(true); + expect(std.registeredIn!(NOTE_ID, 1, '.dkk', 'noncritical_extensions')).toBe(false); + expect(std.known(NOTE_ID, 1) && !std.known(NOTE_ID, 2) && !std.known('org.example', 1)).toBe(true); + expect(std.validateData!({ id: NOTE_ID, version: 1, data: undefined })?.message).toBe('a public note without data: ERR_EXTENSION_DATA_INVALID'); + expect(std.validateData!({ id: NOTE_ID, version: 1, data: new TextEncoder().encode(' x') })?.message).toMatch(/^a public note that breaks the rules of text/); + expect(std.validateData!({ id: NOTE_ID, version: 1, data: new TextEncoder().encode('Cartas') })).toBeUndefined(); + expect(std.validateData!({ id: 'org.example', version: 1, data: Uint8Array.of(1) })).toBeUndefined(); + }); + + it('open of a capsule whose .dkk carries datekeys.capsule reports its data as capsule.Open does with locator.Standard', async () => { + const cap = v.capsule as Json; + const name = cap.fixture as string; + const dkc = readBytes(`fixtures/${name}.dkc`); + const dkk = decodeAccessKey(readBytes(`fixtures/${name}.dkk`)); + const now = parseRFC3339(cap.now as string); + const cases = cap.cases as Json[]; + expect(cases.length).toBe(9); + for (const c of cases) { + const file = encodeAccessKey({ + ...dkk, + material: dkk.material.slice(), + noncritical: (c.noncritical as Row[]).map((x) => ({ id: x[0] as string, version: x[1] as number, data: fromHex(x[3] as string) })), + }); + expect(sha256Hex(file)).toBe(c.dkk_sha256); + const opened = await open(dkc, { + source: suppliedRelease(releaseOf(v, 1000)), + now: () => now, + accessKeyFile: file, + sink: new MemorySink(), + ...(c.standard === true ? { extensions: standardExtensions() } : {}), + }); + expect(opened.error?.message ?? '').toBe(textOf(v, c.text)); + expect(opened.unusableAccessKeyExtensions.map((u) => [u.id, u.version, u.error.message])).toEqual( + (c.unusable as Row[]).map((u) => [u[0], u[1], textOf(v, u[2])]), + ); + expect(opened.inspection.checks.map((ch) => [ch.step, ch.name, ch.ok, ch.detail ?? '', ch.error ?? ''])).toEqual(c.checks); + expect(sha256Hex(opened.plaintext ?? new Uint8Array(0))).toBe(c.content); + } + }); +}); + +describe('the types of Go, and what a caller gets wrong', () => { + const l: Locator = { + addresses: [{ uri: 'https://a.org/', offset: 5 }], + envelopeKey: new Uint8Array(32), + envelopeHeader: Uint8Array.of(1), + restDigest: new Uint8Array(32), + restSize: 0, + capsuleDigest: new Uint8Array(32), + }; + + it('a key or a digest of another length, a negative size or offset and another type are errors of the caller', () => { + expect(marshalLocator(l).length).toBe(4096); + for (const bad of [ + { envelopeKey: new Uint8Array(31) }, + { restDigest: new Uint8Array(33) }, + { capsuleDigest: [0] as unknown as Uint8Array }, + { envelopeHeader: 'h' as unknown as Uint8Array }, + { restSize: -1 }, + { restSize: 1.5 }, + { addresses: [{ uri: 'https://a.org/', offset: -1 }] }, + { addresses: [{ uri: 5 as unknown as string, offset: 0 }] }, + { addresses: 'x' as unknown as Locator['addresses'] }, + ]) { + expect(() => marshalLocator({ ...l, ...bad }), JSON.stringify(Object.keys(bad))).toThrow(/^locator: /); + expect(() => marshalLocator({ ...l, ...bad })).not.toThrow(LocatorError); + } + }); + + it('a size or an offset above 2^53 - 1 is the error of Go', () => { + expect(errorText(() => marshalLocator({ ...l, restSize: 2 ** 53 }))).toBe('locator: a rest larger than 2^53 - 1 bytes'); + expect(errorText(() => marshalLocator({ ...l, addresses: [{ uri: 'https://a.org/', offset: 2 ** 53 }] }))).toBe('locator: an offset larger than 2^53 - 1'); + }); + + it('an address with a lone surrogate is three bytes that are not UTF-8, as Go reads them', () => { + expect(errorText(() => checkURI('https://a.org/\ud800'))).toBe("locator: an address with the character 'í', which RFC 3986 does not allow"); + expect(errorText(() => marshalLocator({ ...l, addresses: [{ uri: '\udc00'.repeat(342), offset: 0 }] }))).toBe('locator: an address of 1026 bytes, not 1 to 1024'); + expect(errorText(() => checkURI('https://a.org/\ud800a'))).toBe("locator: an address with the character 'í', which RFC 3986 does not allow"); + expect(errorText(() => checkURI('https://a.org/\u{1F600}'))).toBe("locator: an address with the character 'ð', which RFC 3986 does not allow"); + expect(errorText(() => checkURI('https://a.org/\u0080'))).toBe("locator: an address with the character 'Â', which RFC 3986 does not allow"); + }); + + it('infoExtension refuses a DateKey that is not canonical and a note that breaks its rules, with their errors', () => { + expect(errorText(() => infoExtension({ note: '', dateKey: { profileId: 'datekeys:quicknet:v1', round: 0 } }))).toBe( + 'locator: Info.DateKey is not a canonical DateKey', + ); + expect(() => infoExtension({ note: 'a\tb', dateKey: { profileId: 'datekeys:quicknet:v1', round: 1 } })).toThrow(DateKeysError); + }); +}); diff --git a/src/lib/dkc/locator.ts b/src/lib/dkc/locator.ts new file mode 100644 index 0000000..5c92edb --- /dev/null +++ b/src/lib/dkc/locator.ts @@ -0,0 +1,913 @@ +// The extension datekeys.capsule of a .dkk and what it points to (spec §43 +// to §44.1), as package locator of datekeys-go at the tag spec-v0.12, with +// the same checks in the same order, the same codes and the same texts: +// +// - the data of the extension (Info, parseInfo, infoExtension), which says +// what the capsule of a key is and when it opens; +// - the locator (Locator), an age file sealed with tlock for that date, +// whose plaintext says where the capsule is: the addresses of the rest, +// and the key, the header and the digests of the envelope; +// - the envelope, the .dkc encrypted with age and split into a header, which +// the locator carries, and a rest, the only thing kept outside, alone or +// inside another file (hide, restIn). +// +// This module holds what needs no cryptography. Opening a sealed locator, +// opening the envelope, sealing a locator and making an envelope are in +// envelope.ts, over the age reader and writer of ageio.ts. +// +// It downloads nothing. An application fetches the rest only when the +// person asks, after showing her the host or the CID of the address +// (addressHost), and only from an address that checkURI accepts +// (usableAddresses); it follows no redirect to an address that checkURI +// rejects, checks with checkResolvedIp on every connection that the IP a +// name resolves to is public, reads only the bytes of the rest, and gives +// them to openEnvelope, which checks their SHA-256, decrypts the .dkc and +// checks its SHA-256 (spec §44.1). +// +// The errors of the locator carry no normative code, as in Go: a locator +// that does not read or does not open, or an address that breaks the rules +// of §44.1, is unusable (spec §44.1, §57), and each is a LocatorError with +// the text of Go. The data of the extension has one code, +// ERR_EXTENSION_DATA_INVALID: data that does not read makes the extension +// unusable, never the .dkk (spec §54). +// +// Internal to the opening and the writing: index.ts does not re-export it, +// since the rules of the note bring the Unicode tables of pathrule.ts. + +import { ageStanzas, STANZA_TLOCK } from './age.ts'; +import { goQuote, goIsPrintNonASCII } from './bytes.ts'; +import { type Decoder, Encoder, MAX_SAFE_UINT, unmarshal } from './cbor.ts'; +import { compactDateKey, type DateKey, parseDateKey } from './datekey.ts'; +import { DateKeysError, withContext } from './errors.ts'; +import { CAPSULE_ID, type Extension, type ExtensionRegistry, newExtension, NOTE_ID } from './extension.ts'; +import { ipFromBytes, ipString, isIpv4, isIpv6, isPublicIp, parseIpAddress } from './ipaddr.ts'; +import { checkNote, checkNoteData, MAX_NOTE_LEN } from './note.ts'; + +/** The most addresses of a locator (spec §44.1). */ +export const MAX_ADDRESSES = 8; +/** The longest address, in bytes (spec §44.1). */ +export const MAX_URI_LEN = 1024; +/** The longest header of an envelope, in bytes (spec §44.1). */ +export const MAX_HEADER_LEN = 1024; +/** + * The unit of the plaintext of a locator: it measures exactly 4096 bytes, or + * the least multiple of 4096 that key 6 can fill (spec §44.1). + */ +export const BLOCK = 4096; +/** The largest sealed locator that a reader decrypts, and the most plaintext it reads of one: Go's maxSealed. */ +export const MAX_SEALED = 1 << 20; + +const DIGEST_LEN = 32; + +/** + * A failure of the locator, without a normative code, with the text of the + * error of Go, such as "locator: the rest is 808 bytes, not 809". + */ +export class LocatorError extends Error { + constructor(message: string) { + super(message); + this.name = 'LocatorError'; + } +} + +const fail = (detail: string): LocatorError => new LocatorError(`locator: ${detail}`); + +// A key or a field that the schema does not define or that is missing, as +// the decoders of Go write it. +const undefinedKey = (what: string): DateKeysError => new DateKeysError('ERR_NON_CANONICAL_CBOR', what); + +// --------------------------------------------------------------------------- +// The data of datekeys.capsule + +/** The data of the extension datekeys.capsule (spec §44.1), as Info of Go. */ +export interface Info { + /** Key 0, the copy of the public note of the capsule, "" for none. */ + readonly note: string; + /** Key 1, the DateKey of the capsule: it says when it opens. */ + readonly dateKey: DateKey; + /** Key 2, the age file of the locator, sealed with tlock for the round of dateKey, or undefined for none. */ + readonly sealed?: Uint8Array | undefined; +} + +function encodeInfo(e: Encoder, note: string, dk: string, sealed: Uint8Array | undefined): void { + let pairs = 1; + if (note !== '') pairs++; + if (sealed !== undefined) pairs++; + e.map(pairs); + if (note !== '') { + e.uint(0); + e.text(note); + } + e.uint(1); + e.text(dk); + if (sealed !== undefined) { + e.uint(2); + e.bstr(sealed); + } +} + +/** + * The extension for the noncritical array of a .dkk, as Info.Extension of + * Go: the DateKey must be canonical, the sealed locator of 1 byte to 1 MiB, + * and the note must meet the rules of spec §24.1; the extension is read back + * with the rules of a reader (parseInfo), which also ties the locator to the + * round of the DateKey (spec §72). The rules of the note throw their + * DateKeysError, ERR_EXTENSION_DATA_INVALID; the others a LocatorError. + */ +export function infoExtension(i: Info): Extension { + let d: DateKey | undefined; + try { + d = parseDateKey(compactDateKey(i.dateKey)); + } catch { + d = undefined; + } + if (d === undefined || d.profileId !== i.dateKey.profileId || d.round !== i.dateKey.round) { + throw fail('Info.DateKey is not a canonical DateKey'); + } + const s = i.sealed; + if (s !== undefined && (s.length < 1 || s.length > MAX_SEALED)) { + throw fail(`a sealed locator of ${s.length} bytes, not 1 to ${MAX_SEALED}`); + } + if (i.note !== '') checkNote(i.note); + const e = new Encoder(); + encodeInfo(e, i.note, compactDateKey(i.dateKey), s); + const x = newExtension(CAPSULE_ID, 1, e.out()); + // Spec §72: the encoder reads what it writes with the rules of a reader, + // which also ties the locator to the round of the DateKey. + try { + parseInfo(x); + } catch (err) { + throw fail(`self-check: a reader rejects this extension: ${(err as Error).message}`); + } + return x; +} + +const dataInvalid = (detail: string): DateKeysError => new DateKeysError('ERR_EXTENSION_DATA_INVALID', `locator: ${detail}`); + +/** + * Reads the data of a datekeys.capsule extension, as ParseInfo of Go: a map + * of the profile of spec §58 with the note, key 0, which meets the rules of + * spec §24.1, the canonical DateKey, key 1, and the sealed locator, key 2, + * an age file with one tlock stanza for the round of the DateKey; its chain + * is checked when it opens. A failure makes the extension unusable, not the + * .dkk (spec §54): it is a DateKeysError whose only code is + * ERR_EXTENSION_DATA_INVALID. + */ +export function parseInfo(x: Extension): Info { + const data = x.data; + if (x.id !== CAPSULE_ID || x.version !== 1 || data === undefined) { + throw dataInvalid('not datekeys.capsule version 1 with data'); + } + let note = ''; + let dk = ''; + let sealed: Uint8Array | undefined; + try { + unmarshal( + data, + (d: Decoder) => { + const pairs = d.map(3); + let seen = 0; + for (let i = 0; i < pairs; i++) { + const k = d.key(); + switch (k) { + case 0: + withContext('key 0', () => { + note = d.text(MAX_NOTE_LEN); + checkNote(note); + }); + break; + case 1: + dk = withContext('key 1', () => d.text(1024)); + break; + case 2: + sealed = withContext('key 2', () => d.bstr(1, MAX_SEALED)); + break; + default: + throw undefinedKey(`key ${k} is not defined`); + } + seen |= 1 << k; + } + if ((seen & 2) === 0) throw undefinedKey('key 1 is missing'); + d.endMap(); + }, + (e: Encoder) => encodeInfo(e, note, dk, sealed), + ); + } catch (err) { + throw dataInvalid(`datekeys.capsule: ${(err as Error).message}`); + } + let d: DateKey | undefined; + try { + d = parseDateKey(dk); + } catch { + d = undefined; + } + if (d === undefined || compactDateKey(d) !== dk) throw dataInvalid('compact_datekey is not a canonical DateKey'); + if (sealed !== undefined && !sealedFor(sealed, d.round)) { + throw dataInvalid(`the locator is not an age file with one tlock stanza for round ${d.round}, the one of its DateKey`); + } + return { note, dateKey: d, sealed }; +} + +// Whether sealed is an age file whose header holds one tlock stanza with two +// arguments, the first of them round. +function sealedFor(sealed: Uint8Array, round: number): boolean { + let st: ReturnType; + try { + st = ageStanzas(sealed); + } catch { + return false; + } + return st.length === 1 && st[0]!.type === STANZA_TLOCK && st[0]!.args.length === 2 && st[0]!.args[0] === String(round); +} + +/** + * The registry of the extensions that the specification defines (spec §72), + * as Go's extension.Standard: the public note in the noncritical array of + * PUBLIC_HEADER and datekeys.capsule in that of a .dkk, both of version 1, + * each known only there. It validates the data of a note with the rules of + * §24.1, and the data of datekeys.capsule with `validateCapsule`, which by + * default is parseInfo, as Go's locator.Standard does (spec §54): with it, + * a datekeys.capsule that does not read is unusable, with the text of Go. + * With `validateCapsule` null, only the presence of the data of + * datekeys.capsule is checked, as Go's extension.Standard without + * ValidateCapsule. + */ +export function standardExtensions(validateCapsule: ((e: Extension) => void) | null = (e) => void parseInfo(e)): ExtensionRegistry { + const errorOf = (check: () => void): Error | undefined => { + try { + check(); + return undefined; + } catch (err) { + return err as Error; + } + }; + return { + known: (id, version) => version === 1 && (id === NOTE_ID || id === CAPSULE_ID), + registeredIn: (id, version, obj, arr) => + arr === 'noncritical_extensions' && version === 1 && ((id === NOTE_ID && obj === 'PUBLIC_HEADER') || (id === CAPSULE_ID && obj === '.dkk')), + validateData(e: Extension): Error | undefined { + if (e.id === NOTE_ID) { + if (e.data === undefined) return new DateKeysError('ERR_EXTENSION_DATA_INVALID', 'a public note without data'); + const data = e.data; + return errorOf(() => checkNoteData(data)); + } + if (e.id === CAPSULE_ID) { + if (e.data === undefined) return new DateKeysError('ERR_EXTENSION_DATA_INVALID', `${CAPSULE_ID} without data`); + if (validateCapsule !== null) return errorOf(() => validateCapsule(e)); + } + return undefined; + }, + }; +} + +// --------------------------------------------------------------------------- +// Addresses + +/** An address of a locator: where the rest of the envelope is (spec §44.1), as Address of Go. */ +export interface Address { + /** ASCII of RFC 3986, with the scheme https or ipfs (a CID v1), and no userinfo, as checkURI checks it. */ + readonly uri: string; + /** The byte of the resource where the rest starts, 0 when the rest is the whole resource. */ + readonly offset: number; +} + +/** + * The bytes of `s` as Go holds a string: UTF-8, with a lone surrogate as the + * three bytes that would encode its code point (WTF-8), which are not valid + * UTF-8, so that an address with one is measured and refused as Go refuses + * those bytes, never read with U+FFFD in its place. + */ +function wtf8(s: string): Uint8Array { + const out: number[] = []; + for (let i = 0; i < s.length; i++) { + let c = s.charCodeAt(i); + if (c >= 0xd800 && c <= 0xdbff && i + 1 < s.length) { + const lo = s.charCodeAt(i + 1); + if (lo >= 0xdc00 && lo <= 0xdfff) { + c = 0x10000 + ((c - 0xd800) << 10) + (lo - 0xdc00); + i++; + } + } + if (c < 0x80) out.push(c); + else if (c < 0x800) out.push(0xc0 | (c >> 6), 0x80 | (c & 0x3f)); + else if (c < 0x10000) out.push(0xe0 | (c >> 12), 0x80 | ((c >> 6) & 0x3f), 0x80 | (c & 0x3f)); + else out.push(0xf0 | (c >> 18), 0x80 | ((c >> 12) & 0x3f), 0x80 | ((c >> 6) & 0x3f), 0x80 | (c & 0x3f)); + } + return Uint8Array.from(out); +} + +const accepts = (uri: string): boolean => { + try { + checkURI(uri); + return true; + } catch { + return false; + } +}; + +/** + * What a reader shows before it downloads (spec §44.1), as Host of Go: the + * host of an https address, without the brackets of an IPv6 literal, or the + * CID of an ipfs one, as the address writes it, with no decoding; "" when + * checkURI rejects the address. + */ +export function addressHost(a: Address): string { + if (!accepts(a.uri)) return ''; + return trimBrackets(splitAuthority(a.uri)[1]); +} + +/** + * Checks an address with the rules of spec §44.1, as CheckURI of Go, in its + * order and with its texts: + * - 1 to 1024 bytes of ASCII of RFC 3986, each percent sign followed by two + * hexadecimal digits; + * - a scheme and "://", and an authority without a percent sign, userinfo or + * a backslash, whose port, in an https address, is a number from 1 to + * 65535 without leading zeros; + * - no "." or ".." segment in its path, written or with %2e; + * - the scheme https, with a host of labels of 1 to 63 letters, digits and + * hyphens that neither start nor end with a hyphen, or a public IP + * address; a host whose last label is numeric or starts with 0x in either + * case only as a public IPv4 address in dotted decimal without leading + * zeros; and no name of one label or that only a machine or a local + * network resolves, in either case; + * - or the scheme ipfs, with a CID v1 of at most 128 characters in + * canonical base32. + * + * The address is read as it is written, with no decoding. Throws a + * LocatorError with the text of Go. + */ +export function checkURI(uri: string): void { + const b = wtf8(uri); + if (b.length === 0 || b.length > MAX_URI_LEN) throw fail(`an address of ${b.length} bytes, not 1 to ${MAX_URI_LEN}`); + for (let i = 0; i < b.length; i++) { + const c = b[i]!; + if (c === 0x25) { + if (i + 2 >= b.length || !isHex(b[i + 1]!) || !isHex(b[i + 2]!)) { + throw fail('an address with a percent sign not followed by two hexadecimal digits'); + } + } else if (!uriChar(c)) { + throw fail(`an address with the character ${quoteRune(c)}, which RFC 3986 does not allow`); + } + } + // From here on uri is ASCII: each code unit is a byte. + const [scheme, host] = splitAuthority(uri); + if (dotSegment(uri)) throw fail('an address with a "." or ".." segment in its path'); + if (scheme === 'https') return checkHost(host); + if (scheme === 'ipfs') { + if (!isCIDv1(host)) throw fail('an ipfs address without a CID v1 in base32'); + return; + } + throw fail(`the scheme ${goQuote(scheme)}: only https and ipfs`); +} + +const URI_PUNCTUATION = "-._~:/?#[]@!$&'()*+,;="; + +// Whether c may appear in a URI of RFC 3986, a percent sign apart: the +// unreserved characters, gen-delims and sub-delims. +function uriChar(c: number): boolean { + return (c >= 0x61 && c <= 0x7a) || (c >= 0x41 && c <= 0x5a) || (c >= 0x30 && c <= 0x39) || URI_PUNCTUATION.includes(String.fromCharCode(c)); +} + +const isHex = (c: number): boolean => (c >= 0x30 && c <= 0x39) || (c >= 0x61 && c <= 0x66) || (c >= 0x41 && c <= 0x46); + +const RUNE_ESCAPES: ReadonlyMap = new Map([ + [0x07, '\\a'], + [0x08, '\\b'], + [0x0c, '\\f'], + [0x0a, '\\n'], + [0x0d, '\\r'], + [0x09, '\\t'], + [0x0b, '\\v'], +]); + +// Go's %q of a byte: the rune of its value in single quotes, as +// strconv.QuoteRune writes it. The first byte above 0x7f of a string is a +// leading byte, 0xc2 to 0xf4, whose rune Go prints: a string never has a +// byte of continuation alone, so the escape \u of QuoteRune is never needed. +function quoteRune(c: number): string { + let body: string; + if (c === 0x27 || c === 0x5c) body = `\\${String.fromCharCode(c)}`; + else if (c < 0x80 ? c >= 0x20 && c < 0x7f : goIsPrintNonASCII(c)) body = String.fromCharCode(c); + else if (RUNE_ESCAPES.has(c)) body = RUNE_ESCAPES.get(c)!; + else body = `\\x${c.toString(16).padStart(2, '0')}`; + return `'${body}'`; +} + +function indexAny(s: string, chars: string): number { + for (let i = 0; i < s.length; i++) if (chars.includes(s[i]!)) return i; + return -1; +} + +// Whether the path of uri has a segment "." or "..", written or +// percent-encoded: a client or a gateway that resolves it would ask for +// something other than what the address shows, as another CID behind an +// ipfs address. splitAuthority has found the "://" of uri. +function dotSegment(uri: string): boolean { + const rest = uri.slice(uri.indexOf('://') + 3); + const j = rest.indexOf('/'); + if (j < 0) return false; + let path = rest.slice(j); + const k = indexAny(path, '?#'); + if (k >= 0) path = path.slice(0, k); + return path.split('/').some((s) => { + const t = s.replaceAll('%2e', '.').replaceAll('%2E', '.'); + return t === '.' || t === '..'; + }); +} + +// The scheme and the raw host of an address, without decoding anything: a +// percent sign in the authority, userinfo and a malformed port are refused, +// so that the host a reader shows is the host an HTTP client would use. +function splitAuthority(uri: string): [string, string] { + const i = uri.indexOf('://'); + if (i <= 0) throw fail('an address without a scheme and ://'); + const scheme = uri.slice(0, i); + const rest = uri.slice(i + 3); + const j = indexAny(rest, '/?#'); + const authority = j >= 0 ? rest.slice(0, j) : rest; + if (indexAny(authority, '%@\\') >= 0) throw fail('an address with a percent sign, userinfo or a backslash in its authority'); + let host = authority; + if (scheme === 'https') { + if (authority.startsWith('[')) { + const end = authority.indexOf(']'); + if (end < 0) throw fail('an address with an unclosed IPv6 literal'); + host = authority.slice(0, end + 1); + const tail = authority.slice(end + 1); + if (tail !== '') checkPort(tail); + } else { + const k = authority.indexOf(':'); + if (k >= 0) { + host = authority.slice(0, k); + checkPort(`:${authority.slice(k + 1)}`); + } + } + } + return [scheme, host]; +} + +// The port of an authority, its ':' included: a number from 1 to 65535 +// without leading zeros (spec v0.12, §44.1), so that a port is written in one +// way only. +function checkPort(s: string): void { + if (s.length < 2 || s[0] !== ':' || s.length > 6) throw fail('an address with a malformed port'); + let n = 0; + for (let i = 1; i < s.length; i++) { + const c = s.charCodeAt(i); + if (c < 0x30 || c > 0x39) throw fail('an address with a malformed port'); + n = n * 10 + c - 0x30; + } + if (n < 1 || n > 65535) throw fail('an address with a port outside 1 to 65535'); + if (s[1] === '0') throw fail('an address with a port written with a leading zero'); +} + +// Go's strings.Trim(s, "[]"): every [ and ] at either end. +function trimBrackets(s: string): string { + let a = 0; + let b = s.length; + while (a < b && (s[a] === '[' || s[a] === ']')) a++; + while (b > a && (s[b - 1] === '[' || s[b - 1] === ']')) b--; + return s.slice(a, b); +} + +// Lower case of an ASCII string, as strings.ToLower of Go on ASCII. +const lowerAscii = (s: string): string => s.replace(/[A-Z]/g, (c) => String.fromCharCode(c.charCodeAt(0) + 0x20)); + +// The host of an https address: a name of labels of 1 to 63 letters, digits +// and hyphens, none of which starts or ends with a hyphen, or an IP literal +// that is public: the spec forbids following a redirect to the others, and +// an address that starts there would defeat the same rule (§44.1). A name +// whose last label is numeric, or starts with 0x in either case, is refused +// unless it is a public IPv4 address in dotted decimal without leading +// zeros, the only form netip reads: some clients read the others, 0x7f000001 +// or 0177.0.0.1, as an IPv4 address too. So are the names that only resolve +// inside a machine or a local network, in either case. +function checkHost(host: string): void { + if (host === '') throw fail('an https address without a host'); + if (host.startsWith('[')) { + const a = parseIpAddress(trimBrackets(host)); + if (a === undefined || !isIpv6(a) || a.zone !== '' || !isPublicIp(a)) throw fail('an https address with an IPv6 literal that is not public'); + return; + } + const labels = host.split('.'); + for (const l of labels) { + if (l === '' || l.length > 63 || l[0] === '-' || l[l.length - 1] === '-') throw fail('an https address with a malformed host'); + if (!/^[A-Za-z0-9-]*$/.test(l)) throw fail('an https address whose host is not letters, digits and hyphens: write its punycode form'); + } + const last = labels[labels.length - 1]!; + if (/^[0-9]+$/.test(last) || lowerAscii(last).startsWith('0x')) { + const a = parseIpAddress(host); + if (a === undefined || !isIpv4(a) || !isPublicIp(a)) throw fail('an https address with a numeric host that is not a public IPv4 address'); + return; + } + if (labels.length === 1 || localName(lowerAscii(host))) { + throw fail('an https address with a name that only a machine or a local network resolves'); + } +} + +const LOCAL_NAMES = ['localhost', 'local', 'home.arpa', 'internal', 'invalid', 'test', 'example', 'onion']; + +// Whether name, in lower case, is one of the special-use names that never +// resolve on the public Internet, or below one of them: localhost (RFC +// 6761), .local (RFC 6762), .home.arpa (RFC 8375), .internal, .invalid, +// .test, .example and .onion (RFC 7686). +const localName = (name: string): boolean => LOCAL_NAMES.some((s) => name === s || name.endsWith(`.${s}`)); + +// Whether s is a CID v1 of at most 128 characters in canonical base32, which +// starts with 'b' (spec v0.12, §44.1): the alphabet in lower case, without +// padding, the bits left over set to zero, and decoded, minimal varints of +// at most 9 bytes, the version 1, a content codec and a multihash with a +// digest of at least one byte and of the length it gives, with nothing after +// it. As in Go, the count of the bits left over is not checked. +function isCIDv1(s: string): boolean { + if (s.length < 2 || s.length > 128 || s[0] !== 'b') return false; + const out: number[] = []; + let acc = 0; + let bits = 0; + for (let i = 1; i < s.length; i++) { + const c = s.charCodeAt(i); + let v: number; + if (c >= 0x61 && c <= 0x7a) v = c - 0x61; + else if (c >= 0x32 && c <= 0x37) v = c - 0x32 + 26; + else return false; + // acc holds at most 7 bits before the shift: 12 bits at most. + acc = (acc << 5) | v; + bits += 5; + if (bits >= 8) { + bits -= 8; + out.push(acc >> bits); + acc &= (1 << bits) - 1; + } + } + // The bits of the last character beyond a byte are zero. + if (acc !== 0) return false; + const version = uvarint(out, 0); + if (version === undefined || version[0] !== 1n) return false; + const codec = uvarint(out, version[1]); + if (codec === undefined) return false; + const hash = uvarint(out, codec[1]); + if (hash === undefined) return false; + const n = uvarint(out, hash[1]); + return n !== undefined && n[0] > 0n && BigInt(out.length - n[1]) === n[0]; +} + +// An unsigned varint of multiformats, minimal and of at most 9 bytes, from +// b[at]: its value, exact as a bigint up to 63 bits, and the offset after it. +function uvarint(b: readonly number[], at: number): [bigint, number] | undefined { + let v = 0n; + for (let i = 0; at + i < b.length && i < 9; i++) { + const x = b[at + i]!; + v |= BigInt(x & 0x7f) << BigInt(7 * i); + if ((x & 0x80) === 0) { + if (i > 0 && x === 0) return undefined; + return [v, at + i + 1]; + } + } + return undefined; +} + +/** + * Checks the IP address `ip`, the 4 bytes of an IPv4 address or the 16 of an + * IPv6 address, to which the name of an https address resolved: a reader + * connects only to a public one, and checks it on every connection, + * redirections included (spec §44.1). Public is what an IP literal of an + * address must be, as publicIP of Go: an IPv4 address outside the blocks of + * §44.1, or an IPv6 address of 2000::/3 outside 2001::/23, 2001:db8::/32, + * 2002::/16 and 3fff::/20. An IPv6 address that holds an IPv4 one is not, + * IPv4-mapped and NAT64 (64:ff9b::/96) included: an IPv4 address is checked + * as its 4 bytes. + * + * Throws a LocatorError for an address that is not public, with the text of + * datekeys-dart's checkResolvedIp, and a RangeError for any other length. Go + * has no such function: a reader of the reference does not download. + */ +export function checkResolvedIp(ip: Uint8Array): void { + const a = ipFromBytes(ip); + if (!isPublicIp(a)) throw fail(`an https address whose name resolves to ${ipString(a)}, an IP address that is not public`); +} + +// --------------------------------------------------------------------------- +// The locator + +/** + * The plaintext of the sealed locator (spec §44.1), as Locator of Go. The + * keys and digests are 32 bytes and the size and the offsets are integers + * from 0, as the types of Go make them; a value of another type is a + * TypeError or a RangeError of the caller. + */ +export interface Locator { + /** Key 0, where the rest of the envelope is, in their order. */ + readonly addresses: readonly Address[]; + /** Key 1, I_SOBRE, the raw X25519 identity of the envelope. SECRET: see wipeLocator. */ + readonly envelopeKey: Uint8Array; + /** Key 2, the age header of the envelope, MAC line included. */ + readonly envelopeHeader: Uint8Array; + /** Key 3, the SHA-256 of the rest. */ + readonly restDigest: Uint8Array; + /** Key 4, the length of the rest, in bytes. */ + readonly restSize: number; + /** Key 5, the SHA-256 of the .dkc (spec §43). */ + readonly capsuleDigest: Uint8Array; +} + +/** Clears the envelope key of `l`: the envelope cannot be opened afterwards. */ +export function wipeLocator(l: Locator): void { + l.envelopeKey.fill(0); +} + +/** + * The addresses that meet the rules of spec §44.1, in their order, as Usable + * of Go. A reader rejects each address that breaks them and uses the others: + * a locator whose addresses are all rejected has nothing to download. + */ +export function usableAddresses(l: Locator): Address[] { + return l.addresses.filter((a) => accepts(a.uri)); +} + +const isCount = (n: unknown): n is number => typeof n === 'number' && Number.isInteger(n) && n >= 0; + +// The types of Go: what Go's compiler would refuse. +function checkTypes(l: Locator): void { + const fixed = (b: unknown, name: string): void => { + if (!(b instanceof Uint8Array)) throw new TypeError(`locator: ${name} is not a Uint8Array`); + if (b.length !== DIGEST_LEN) throw new RangeError(`locator: ${name} is ${b.length} bytes, not ${DIGEST_LEN}`); + }; + if (!Array.isArray(l.addresses)) throw new TypeError('locator: addresses is not an array'); + for (const a of l.addresses) { + if (typeof a?.uri !== 'string') throw new TypeError('locator: an address without a string uri'); + if (!isCount(a.offset)) throw new RangeError(`locator: an offset of ${String(a.offset)}, not an integer from 0`); + } + fixed(l.envelopeKey, 'envelopeKey'); + if (!(l.envelopeHeader instanceof Uint8Array)) throw new TypeError('locator: envelopeHeader is not a Uint8Array'); + fixed(l.restDigest, 'restDigest'); + if (!isCount(l.restSize)) throw new RangeError(`locator: a rest size of ${String(l.restSize)}, not an integer from 0`); + fixed(l.capsuleDigest, 'capsuleDigest'); +} + +// validateForm of Go: the form that a reader requires of the whole locator; +// a broken address makes only that address unusable (usableAddresses). +function validateForm(l: Locator): void { + if (l.addresses.length < 1 || l.addresses.length > MAX_ADDRESSES) throw fail(`${l.addresses.length} addresses, not 1 to ${MAX_ADDRESSES}`); + for (const a of l.addresses) { + const n = wtf8(a.uri).length; + if (n === 0 || n > MAX_URI_LEN) throw fail(`an address of ${n} bytes, not 1 to ${MAX_URI_LEN}`); + } + const n = l.envelopeHeader.length; + if (n < 1 || n > MAX_HEADER_LEN) throw fail(`an envelope header of ${n} bytes, not 1 to ${MAX_HEADER_LEN}`); + if (l.restSize > MAX_SAFE_UINT) throw fail('a rest larger than 2^53 - 1 bytes'); + for (const a of l.addresses) if (a.offset > MAX_SAFE_UINT) throw fail('an offset larger than 2^53 - 1'); +} + +// The map; pad < 0 leaves key 6 out. +function encode(e: Encoder, l: Locator, pad: number): void { + e.map(pad >= 0 ? 7 : 6); + e.uint(0); + e.array(l.addresses.length); + for (const a of l.addresses) { + e.map(a.offset === 0 ? 1 : 2); + e.uint(0); + e.text(a.uri); + if (a.offset !== 0) { + e.uint(1); + e.uint(a.offset); + } + } + e.uint(1); + e.bstr(l.envelopeKey); + e.uint(2); + e.bstr(l.envelopeHeader); + e.uint(3); + e.bstr(l.restDigest); + e.uint(4); + e.uint(l.restSize); + e.uint(5); + e.bstr(l.capsuleDigest); + if (pad >= 0) { + e.uint(6); + e.bstr(new Uint8Array(pad)); + } +} + +// The length of the head of a byte string of n bytes, for the n of key 6, +// which is below two blocks. +function bstrHeadLen(n: number): number { + if (n < 24) return 1; + if (n < 256) return 2; + return 3; +} + +// padFor of Go: the length of key 6 that makes the plaintext measure the +// least multiple of BLOCK that holds it, or -1 when n0, the length without +// key 6, already is one. When no length of key 6 gives a given multiple, as +// happens at the boundaries of the CBOR length, it takes the next one. The +// division truncates toward zero, as Go's. +function padFor(n0: number): number { + if (n0 % BLOCK === 0) return -1; + for (let total = (Math.trunc(n0 / BLOCK) + 1) * BLOCK; ; total += BLOCK) { + for (let pad = 1; pad <= total - n0; pad++) { + if (n0 + 1 + bstrHeadLen(pad) + pad === total) return pad; + } + } +} + +/** + * The length of the plaintext of a locator whose CBOR without key 6 + * measures `base` bytes, as PlaintextLength of Go: `base` when it already is + * a multiple of 4096, and otherwise the least multiple that key 6 can fill + * exactly. Key 6 takes at least 3 bytes, and the head of its byte string + * grows at 24 and at 256 bytes: a base that lacks 1, 2, 26 or 259 bytes for + * a multiple takes the next one (spec §44.1). + */ +export function plaintextLength(base: number): number { + if (!Number.isSafeInteger(base)) throw new RangeError(`locator: a base of ${base}, not an integer`); + const pad = padFor(base); + return pad < 0 ? base : base + 1 + bstrHeadLen(pad) + pad; +} + +/** + * The plaintext of the locator, as Marshal of Go: CBOR with the profile of + * spec §58, completed with zeros in key 6 up to the least multiple of 4096 + * bytes that key 6 can fill, so that its length does not tell how many + * addresses there are. It checks the form of the locator, 1 to 8 addresses + * of 1 to 1024 bytes, a header of 1 to 1024 bytes and a size and offsets of + * at most 2^53 - 1, and each address with checkURI: a writer never writes + * one that a reader rejects. Throws a LocatorError. The plaintext holds + * I_SOBRE: the caller wipes it. + */ +export function marshalLocator(l: Locator): Uint8Array { + checkTypes(l); + validateForm(l); + for (const a of l.addresses) checkURI(a.uri); + return marshalChecked(l); +} + +// marshal of Go: Marshal once the locator is checked. +function marshalChecked(l: Locator): Uint8Array { + const e = new Encoder(); + encode(e, l, -1); + const base = e.out(); + e.wipe(); + const pad = padFor(base.length); + if (pad < 0) return base; + // It holds I_SOBRE. + base.fill(0); + const p = new Encoder(); + encode(p, l, pad); + const out = p.out(); + p.wipe(); + /* v8 ignore next 4 -- @preserve: padFor always reaches a multiple */ + if (out.length % BLOCK !== 0) { + out.fill(0); + throw fail(`internal error: ${out.length} bytes of plaintext`); + } + return out; +} + +// Something of the decoding of a locator that Go reports without a code. +class Plain extends Error {} + +/** + * Reads the plaintext of a locator, checking its profile and the length + * that marshalLocator gives, as Unmarshal of Go. Its errors carry no + * normative code: a locator that does not read is unusable (spec §44.1, + * §57). An address that breaks the rules of §44.1 is kept, and + * usableAddresses leaves it out. Throws a LocatorError. + */ +export function unmarshalLocator(b: Uint8Array): Locator { + const addresses: Address[] = []; + const envelopeKey = new Uint8Array(DIGEST_LEN); + let envelopeHeader: Uint8Array = new Uint8Array(0); + const restDigest = new Uint8Array(DIGEST_LEN); + let restSize = 0; + const capsuleDigest = new Uint8Array(DIGEST_LEN); + let pad = -1; + const l = (): Locator => ({ addresses, envelopeKey, envelopeHeader, restDigest, restSize, capsuleDigest }); + const copyFixed = (d: Decoder, dst: Uint8Array): void => { + const v = d.bstr(dst.length, dst.length); + dst.set(v); + v.fill(0); + }; + try { + unmarshal( + b, + (d: Decoder) => { + const pairs = d.map(7); + let seen = 0; + for (let i = 0; i < pairs; i++) { + const k = d.key(); + switch (k) { + case 0: + withContext('key 0', () => decodeAddresses(d, addresses)); + break; + case 1: + withContext('key 1', () => copyFixed(d, envelopeKey)); + break; + case 2: + envelopeHeader = withContext('key 2', () => d.bstr(1, MAX_HEADER_LEN)); + break; + case 3: + withContext('key 3', () => copyFixed(d, restDigest)); + break; + case 4: + restSize = withContext('key 4', () => d.uint(MAX_SAFE_UINT)); + break; + case 5: + withContext('key 5', () => copyFixed(d, capsuleDigest)); + break; + case 6: { + const z = withContext('key 6', () => d.bstr(1, 1 << 20)); + if (z.some((x) => x !== 0)) throw new Plain('the padding is not zeros'); + pad = z.length; + break; + } + default: + throw undefinedKey(`key ${k} is not defined`); + } + seen |= 1 << k; + } + if ((seen & 0x3f) !== 0x3f) throw undefinedKey('a key from 0 to 5 is missing'); + d.endMap(); + }, + (e: Encoder) => encode(e, l(), pad), + ); + } catch (err) { + envelopeKey.fill(0); + /* v8 ignore next -- @preserve: the decoder throws nothing else */ + if (!(err instanceof DateKeysError || err instanceof Plain)) throw err; + throw fail(err.message); + } + const out = l(); + try { + validateForm(out); + // The length is the one Marshal gives: nothing else is canonical. + const want = marshalChecked(out); + let same = want.length === b.length; + for (let i = 0; same && i < b.length; i++) same = want[i] === b[i]; + want.fill(0); + if (!same) throw fail(`the plaintext is not ${BLOCK} or the least multiple of ${BLOCK} that holds it`); + } catch (err) { + envelopeKey.fill(0); + throw err; + } + return out; +} + +function decodeAddresses(d: Decoder, out: Address[]): void { + const n = d.array(MAX_ADDRESSES); + for (let i = 0; i < n; i++) { + const pairs = d.map(2); + let uri = ''; + let offset = 0; + let seen = 0; + for (let j = 0; j < pairs; j++) { + const k = d.key(); + switch (k) { + case 0: + uri = d.text(MAX_URI_LEN); + break; + case 1: + offset = d.uint(MAX_SAFE_UINT); + if (offset === 0) throw undefinedKey('an offset of 0 is written by leaving it out'); + break; + default: + throw undefinedKey(`address key ${k} is not defined`); + } + seen |= 1 << k; + } + if ((seen & 1) === 0) throw undefinedKey('an address without URI'); + d.endMap(); + out.push({ uri, offset }); + } +} + +// --------------------------------------------------------------------------- +// The rest and its host + +/** + * The rest of the envelope that `l` describes from the resource `host`, + * which starts at the `offset` of its address: only restSize bytes are + * read, whatever follows (spec §44.1), as RestIn of Go. Throws a LocatorError + * when the resource is shorter. + */ +export function restIn(l: Locator, host: Uint8Array, offset: number): Uint8Array { + if (!isCount(offset)) throw new RangeError(`locator: an offset of ${offset}, not an integer from 0`); + if (offset > host.length || l.restSize > host.length - offset) { + throw fail(`the resource has ${host.length} bytes, and the rest is ${l.restSize} from ${offset}`); + } + return host.slice(offset, offset + l.restSize); +} + +/** + * Appends `rest` to `host`, a file of any kind, as Hide of Go: the result and + * the offset where the rest starts, which is what an address says (spec + * §44.1). It is hiding, not steganography: whoever analyses the host sees + * that it has extra bytes, but not what they are. Only a store that keeps + * the file byte by byte keeps it: a social network or a messaging app + * recompress or strip what is left over. + */ +export function hide(host: Uint8Array, rest: Uint8Array): { file: Uint8Array; offset: number } { + const file = new Uint8Array(host.length + rest.length); + file.set(host); + file.set(rest, host.length); + return { file, offset: host.length }; +} diff --git a/src/lib/dkc/testing/locator-uris.json b/src/lib/dkc/testing/locator-uris.json new file mode 100644 index 0000000..223777c --- /dev/null +++ b/src/lib/dkc/testing/locator-uris.json @@ -0,0 +1,5717 @@ +{ + "description": "The addresses of a locator (spec v0.12 \u00a744.1) for src/lib/dkc/locator.ts; see scripts/locator-go-vectors.go. testdata: [uri, text, host] of locator.CheckURI and Address.Host on the uri_cases of testdata/vectors/locator.json, in their order; uris: the same on addresses built at the edges of \u00a744.1 and drawn from a seed; wtf8: [bytes, text] of CheckURI on bytes with a surrogate. A text is an index into texts, 0 for none. ips: [s, false] or [s, true, bytes, zone, String, publicIP] of netip.ParseAddr. public: [bytes, publicIP, String] of netip.AddrFromSlice, or [bytes, null] for a length that is no address.", + "generator": "scripts/locator-go-vectors.go, go1.26.8", + "texts": [ + "", + "locator: an address of 0 bytes, not 1 to 1024", + "locator: the scheme \"http\": only https and ipfs", + "locator: the scheme \"file\": only https and ipfs", + "locator: the scheme \"ftp\": only https and ipfs", + "locator: an address with a percent sign, userinfo or a backslash in its authority", + "locator: an https address without a host", + "locator: an ipfs address without a CID v1 in base32", + "locator: an address with the character \u0027\u00c3\u0027, which RFC 3986 does not allow", + "locator: an address with the character \u0027 \u0027, which RFC 3986 does not allow", + "locator: an https address with a numeric host that is not a public IPv4 address", + "locator: an https address with an IPv6 literal that is not public", + "locator: an address with a malformed port", + "locator: an address with a port outside 1 to 65535", + "locator: an address without a scheme and ://", + "locator: an address with an unclosed IPv6 literal", + "locator: an address with the character \u0027\"\u0027, which RFC 3986 does not allow", + "locator: an address with the character \u0027<\u0027, which RFC 3986 does not allow", + "locator: an address with the character \u0027>\u0027, which RFC 3986 does not allow", + "locator: an address with the character \u0027\\\\\u0027, which RFC 3986 does not allow", + "locator: an address with the character \u0027^\u0027, which RFC 3986 does not allow", + "locator: an address with the character \u0027`\u0027, which RFC 3986 does not allow", + "locator: an address with the character \u0027{\u0027, which RFC 3986 does not allow", + "locator: an address with the character \u0027|\u0027, which RFC 3986 does not allow", + "locator: an address with the character \u0027}\u0027, which RFC 3986 does not allow", + "locator: an address with the character \u0027\\x00\u0027, which RFC 3986 does not allow", + "locator: an address with the character \u0027\\t\u0027, which RFC 3986 does not allow", + "locator: an address with the character \u0027\\n\u0027, which RFC 3986 does not allow", + "locator: an address with the character \u0027\\x7f\u0027, which RFC 3986 does not allow", + "locator: an address with a percent sign not followed by two hexadecimal digits", + "locator: an address with a \".\" or \"..\" segment in its path", + "locator: an https address with a name that only a machine or a local network resolves", + "locator: an https address whose host is not letters, digits and hyphens: write its punycode form", + "locator: an https address with a malformed host", + "locator: the scheme \"HTTPS\": only https and ipfs", + "locator: the scheme \"Https\": only https and ipfs", + "locator: the scheme \"IPFS\": only https and ipfs", + "locator: an address with a port written with a leading zero", + "locator: the scheme \"hTTps\": only https and ipfs", + "locator: an address with the character \u0027\\x01\u0027, which RFC 3986 does not allow", + "locator: an address with the character \u0027\\x02\u0027, which RFC 3986 does not allow", + "locator: an address with the character \u0027\\x03\u0027, which RFC 3986 does not allow", + "locator: an address with the character \u0027\\x04\u0027, which RFC 3986 does not allow", + "locator: an address with the character \u0027\\x05\u0027, which RFC 3986 does not allow", + "locator: an address with the character \u0027\\x06\u0027, which RFC 3986 does not allow", + "locator: an address with the character \u0027\\a\u0027, which RFC 3986 does not allow", + "locator: an address with the character \u0027\\b\u0027, which RFC 3986 does not allow", + "locator: an address with the character \u0027\\v\u0027, which RFC 3986 does not allow", + "locator: an address with the character \u0027\\f\u0027, which RFC 3986 does not allow", + "locator: an address with the character \u0027\\r\u0027, which RFC 3986 does not allow", + "locator: an address with the character \u0027\\x0e\u0027, which RFC 3986 does not allow", + "locator: an address with the character \u0027\\x0f\u0027, which RFC 3986 does not allow", + "locator: an address with the character \u0027\\x10\u0027, which RFC 3986 does not allow", + "locator: an address with the character \u0027\\x11\u0027, which RFC 3986 does not allow", + "locator: an address with the character \u0027\\x12\u0027, which RFC 3986 does not allow", + "locator: an address with the character \u0027\\x13\u0027, which RFC 3986 does not allow", + "locator: an address with the character \u0027\\x14\u0027, which RFC 3986 does not allow", + "locator: an address with the character \u0027\\x15\u0027, which RFC 3986 does not allow", + "locator: an address with the character \u0027\\x16\u0027, which RFC 3986 does not allow", + "locator: an address with the character \u0027\\x17\u0027, which RFC 3986 does not allow", + "locator: an address with the character \u0027\\x18\u0027, which RFC 3986 does not allow", + "locator: an address with the character \u0027\\x19\u0027, which RFC 3986 does not allow", + "locator: an address with the character \u0027\\x1a\u0027, which RFC 3986 does not allow", + "locator: an address with the character \u0027\\x1b\u0027, which RFC 3986 does not allow", + "locator: an address with the character \u0027\\x1c\u0027, which RFC 3986 does not allow", + "locator: an address with the character \u0027\\x1d\u0027, which RFC 3986 does not allow", + "locator: an address with the character \u0027\\x1e\u0027, which RFC 3986 does not allow", + "locator: an address with the character \u0027\\x1f\u0027, which RFC 3986 does not allow", + "locator: an address with the character \u0027\u00e2\u0027, which RFC 3986 does not allow", + "locator: an address with the character \u0027\u00ef\u0027, which RFC 3986 does not allow", + "locator: an address with the character \u0027\u00e3\u0027, which RFC 3986 does not allow", + "locator: an address with the character \u0027\u00f0\u0027, which RFC 3986 does not allow", + "locator: an address with the character \u0027\u00c2\u0027, which RFC 3986 does not allow", + "locator: an address with the character \u0027\u00d0\u0027, which RFC 3986 does not allow", + "locator: an address of 1025 bytes, not 1 to 1024", + "locator: an address of 2000 bytes, not 1 to 1024", + "locator: the scheme \"ht-tp\": only https and ipfs", + "locator: the scheme \"h\": only https and ipfs", + "locator: the scheme \"ipfS\": only https and ipfs", + "locator: the scheme \"https.\": only https and ipfs", + "locator: the scheme \"data\": only https and ipfs", + "locator: an address with the character \u0027\u00ed\u0027, which RFC 3986 does not allow" + ], + "testdata": [ + ["https://ejemplo.org/a.bin",0,"ejemplo.org"], + ["https://ejemplo.org:8443/x?y=1",0,"ejemplo.org"], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi",0,"bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi"], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi/ruta",0,"bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi"], + ["https://xn--pple-43d.com/",0,"xn--pple-43d.com"], + ["",1,""], + ["http://ejemplo.org/a",2,""], + ["file:///etc/passwd",3,""], + ["ftp://x/y",4,""], + ["https://user:pass@ejemplo.org/",5,""], + ["https://",6,""], + ["ipfs://notacid",7,""], + ["https://ejemplo.org/\u00f1",8,""], + ["https://ejemplo.org/a b",9,""], + ["https://%D0%B0pple.com/x",5,""], + ["https://ejemplo.org%E2%80%AEtxt.exe/",5,""], + ["https://127.0.0.1/",10,""], + ["https://[::1]/",11,""], + ["https://0x7f000001/",10,""], + ["https://a.com:99999999/",12,""], + ["https://a.com:0/",13,""], + ["https://ejemplo.org",0,"ejemplo.org"], + ["https://ejemplo.org/a#parte",0,"ejemplo.org"], + ["https://ejemplo.org/~ana/(1),x;y=z!$&\u0027*+",0,"ejemplo.org"], + ["https://ejemplo.org/a%20b",0,"ejemplo.org"], + ["https://ejemplo.org/%41",0,"ejemplo.org"], + ["https://ejemplo.org:65535/",0,"ejemplo.org"], + ["https://[2606:4700::1111]:8443/a",0,"2606:4700::1111"], + ["https:/ejemplo.org/a",14,""], + ["https:ejemplo.org/a",14,""], + ["//ejemplo.org/a",14,""], + ["ejemplo.org/a",14,""], + ["https://@ejemplo.org/",5,""], + ["https://ejemplo.org:443@otro.org/",5,""], + ["https://ejemplo.org:65536/",13,""], + ["https://ejemplo.org:/",12,""], + ["https://ejemplo.org:8a/",12,""], + ["https://[2606:4700::1111]x/",12,""], + ["https://[2606:4700::1111/",15,""], + ["https://ejemplo.org/a\"b",16,""], + ["https://ejemplo.org/ab",18,""], + ["https://ejemplo.org/a\\b",19,""], + ["https://ejemplo.org/a^b",20,""], + ["https://ejemplo.org/a`b",21,""], + ["https://ejemplo.org/a{b",22,""], + ["https://ejemplo.org/a|b",23,""], + ["https://ejemplo.org/a}b",24,""], + ["https://ejemplo.org/a\u0000b",25,""], + ["https://ejemplo.org/a\tb",26,""], + ["https://ejemplo.org/a\nb",27,""], + ["https://ejemplo.org/ab",28,""], + ["https://ej\u00e9mplo.org/",8,""], + ["https://ejemplo.org/%",29,""], + ["https://ejemplo.org/%4",29,""], + ["https://ejemplo.org/%zz",29,""], + ["https://ejemplo.org/%4g",29,""], + ["https://ejempl%6F.org/",5,""], + ["https://ejemplo.org\\otro.org/",19,""], + ["https://ejemplo.org/../a",30,""], + ["https://ejemplo.org/a/../b",30,""], + ["https://ejemplo.org/./a",30,""], + ["https://ejemplo.org/a/.",30,""], + ["https://ejemplo.org/a/..",30,""], + ["https://ejemplo.org/%2e%2e/a",30,""], + ["https://ejemplo.org/%2E%2E/a",30,""], + ["https://ejemplo.org/.%2e/a",30,""], + ["https://ejemplo.org/%2e/a",30,""], + ["https://ejemplo.org/a/..?b=1",30,""], + ["https://ejemplo.org/a/..#b",30,""], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi/../../ipns/x",30,""], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi/%2e%2e/x",30,""], + ["https://ejemplo.org/..a",0,"ejemplo.org"], + ["https://ejemplo.org/a..",0,"ejemplo.org"], + ["https://ejemplo.org/...",0,"ejemplo.org"], + ["https://ejemplo.org/.well-known/a",0,"ejemplo.org"], + ["https://ejemplo.org/%2e%2ea",0,"ejemplo.org"], + ["https://ejemplo.org/a//b",0,"ejemplo.org"], + ["https://ejemplo.org/a?b=/../c",0,"ejemplo.org"], + ["https://ejemplo.org/a#/../b",0,"ejemplo.org"], + ["https://0.0.0.0/",10,""], + ["https://0.255.255.255/",10,""], + ["https://1.0.0.0/",0,"1.0.0.0"], + ["https://10.0.0.0/",10,""], + ["https://10.255.255.255/",10,""], + ["https://9.255.255.255/",0,"9.255.255.255"], + ["https://11.0.0.0/",0,"11.0.0.0"], + ["https://100.64.0.0/",10,""], + ["https://100.127.255.255/",10,""], + ["https://100.63.255.255/",0,"100.63.255.255"], + ["https://100.128.0.0/",0,"100.128.0.0"], + ["https://127.0.0.0/",10,""], + ["https://127.255.255.255/",10,""], + ["https://126.255.255.255/",0,"126.255.255.255"], + ["https://128.0.0.0/",0,"128.0.0.0"], + ["https://169.254.0.0/",10,""], + ["https://169.254.255.255/",10,""], + ["https://169.253.255.255/",0,"169.253.255.255"], + ["https://169.255.0.0/",0,"169.255.0.0"], + ["https://172.16.0.0/",10,""], + ["https://172.31.255.255/",10,""], + ["https://172.15.255.255/",0,"172.15.255.255"], + ["https://172.32.0.0/",0,"172.32.0.0"], + ["https://192.0.0.0/",10,""], + ["https://192.0.0.255/",10,""], + ["https://191.255.255.255/",0,"191.255.255.255"], + ["https://192.0.1.0/",0,"192.0.1.0"], + ["https://192.0.2.0/",10,""], + ["https://192.0.2.255/",10,""], + ["https://192.0.1.255/",0,"192.0.1.255"], + ["https://192.0.3.0/",0,"192.0.3.0"], + ["https://192.88.99.0/",10,""], + ["https://192.88.99.255/",10,""], + ["https://192.88.98.255/",0,"192.88.98.255"], + ["https://192.88.100.0/",0,"192.88.100.0"], + ["https://192.168.0.0/",10,""], + ["https://192.168.255.255/",10,""], + ["https://192.167.255.255/",0,"192.167.255.255"], + ["https://192.169.0.0/",0,"192.169.0.0"], + ["https://198.18.0.0/",10,""], + ["https://198.19.255.255/",10,""], + ["https://198.17.255.255/",0,"198.17.255.255"], + ["https://198.20.0.0/",0,"198.20.0.0"], + ["https://198.51.100.0/",10,""], + ["https://198.51.100.255/",10,""], + ["https://198.51.99.255/",0,"198.51.99.255"], + ["https://198.51.101.0/",0,"198.51.101.0"], + ["https://203.0.113.0/",10,""], + ["https://203.0.113.255/",10,""], + ["https://203.0.112.255/",0,"203.0.112.255"], + ["https://203.0.114.0/",0,"203.0.114.0"], + ["https://224.0.0.0/",10,""], + ["https://239.255.255.255/",10,""], + ["https://223.255.255.255/",0,"223.255.255.255"], + ["https://240.0.0.0/",10,""], + ["https://255.255.255.255/",10,""], + ["https://10.1.2.3/",10,""], + ["https://172.20.0.1/",10,""], + ["https://192.168.1.1/",10,""], + ["https://169.254.169.254/",10,""], + ["https://100.100.100.200/",10,""], + ["https://8.8.8.8/",0,"8.8.8.8"], + ["https://1.1.1.1:8443/a",0,"1.1.1.1"], + ["https://[::]/",11,""], + ["https://[::ffff:127.0.0.1]/",11,""], + ["https://[::ffff:8.8.8.8]/",11,""], + ["https://[::8.8.8.8]/",11,""], + ["https://[64:ff9b::7f00:1]/",11,""], + ["https://[64:ff9b::808:808]/",11,""], + ["https://[64:ff9b:1::808:808]/",11,""], + ["https://[2002::1]/",11,""], + ["https://[2002:808:808::1]/",11,""], + ["https://[2001::1]/",11,""], + ["https://[2001:0:4136:e378:8000:63bf:3fff:fdd2]/",11,""], + ["https://[2001:1ff:ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[2001:db8::1]/",11,""], + ["https://[2001:db8:ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[3fff::1]/",11,""], + ["https://[3fff:fff:ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[fe80::1]/",11,""], + ["https://[fe80::1%25eth0]/",5,""], + ["https://[fec0::1]/",11,""], + ["https://[fc00::1]/",11,""], + ["https://[fd12:3456::1]/",11,""], + ["https://[ff02::1]/",11,""], + ["https://[100::1]/",11,""], + ["https://[1fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[4000::]/",11,""], + ["https://[1.2.3.4]/",11,""], + ["https://[v1.x]/",11,""], + ["https://[2000::]/",0,"2000::"], + ["https://[2001:200::]/",0,"2001:200::"], + ["https://[2001:db7:ffff:ffff:ffff:ffff:ffff:ffff]/",0,"2001:db7:ffff:ffff:ffff:ffff:ffff:ffff"], + ["https://[2001:db9::]/",0,"2001:db9::"], + ["https://[2003::1]/",0,"2003::1"], + ["https://[3ffe::1]/",0,"3ffe::1"], + ["https://[3fff:1000::]/",0,"3fff:1000::"], + ["https://[2606:4700:4700::1111]/",0,"2606:4700:4700::1111"], + ["https://[2a00:1450:4001:830::200e]/",0,"2a00:1450:4001:830::200e"], + ["https://localhost/",31,""], + ["https://foo.localhost/",31,""], + ["https://intranet/",31,""], + ["https://a.local/",31,""], + ["https://router.home.arpa/",31,""], + ["https://home.arpa/",31,""], + ["https://x.internal/",31,""], + ["https://x.invalid/",31,""], + ["https://x.test/",31,""], + ["https://x.example/",31,""], + ["https://duckduckgogg42xjoc72x3sjasowoarfbgcmvfimaftt6twagswzczad.onion/",31,""], + ["https://ejemplo.0x10/",10,""], + ["https://ejemplo.0xg/",10,""], + ["https://ejemplo.0x/",10,""], + ["https://2130706433/",10,""], + ["https://127.1/",10,""], + ["https://1.2.3.4.5/",10,""], + ["https://ejemplo.org.1/",10,""], + ["https://a_b.ejemplo.org/",32,""], + ["https://localhost.ejemplo.org/",0,"localhost.ejemplo.org"], + ["https://local.ejemplo.org/",0,"local.ejemplo.org"], + ["https://example.com/",0,"example.com"], + ["https://test.ejemplo.org/",0,"test.ejemplo.org"], + ["https://onion.ejemplo.org/",0,"onion.ejemplo.org"], + ["https://123.ejemplo.org/",0,"123.ejemplo.org"], + ["https://a-b.ejemplo.org/",0,"a-b.ejemplo.org"], + ["ipfs://bafkreibq6d3olcwyloabkavyuz437v52i4m7ukb4ijk6p3mcuw3zbzfppu",0,"bafkreibq6d3olcwyloabkavyuz437v52i4m7ukb4ijk6p3mcuw3zbzfppu"], + ["ipfs://bafybgqhdkcr2bxdj473pqqb7xxrdxm54xzvzbimdim3v4gmqr3h63nis5awqeeopkxwrptrkne3roojqfyh3dapzcismpzqjgp37exayr3yti",0,"bafybgqhdkcr2bxdj473pqqb7xxrdxm54xzvzbimdim3v4gmqr3h63nis5awqeeopkxwrptrkne3roojqfyh3dapzcismpzqjgp37exayr3yti"], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi/a/b.jpg",0,"bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi"], + ["ipfs://b",7,""], + ["ipfs://Qmaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",7,""], + ["ipfs://BAFYBEIGDYRZT5SFP7UDM7HU76UH7Y26NF3EFUYLQABF3OCLGTQY55FBZDI",7,""], + ["ipfs://babybeibq6d3olcwyloabkavyuz437v52i4m7ukb4ijk6p3mcuw3zbzfppu",7,""], + ["ipfs://bajybeibq6d3olcwyloabkavyuz437v52i4m7ukb4ijk6p3mcuw3zbzfppu",7,""], + ["ipfs://bafkreibq6d3olcwyloabkavyuz437v52i4m7ukb4ijk6p3mcuw3zbzfp",7,""], + ["ipfs://bafkreibq6d3olcwyloabkavyuz437v52i4m7ukb4ijk6p3mcuw3zbzfppuaa",7,""], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzd1",7,""], + ["ipfs://bafybeigdyrzt5sfp7ud07hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi",7,""], + ["https://aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.org/",0,"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.org"], + ["https://a--b.ejemplo.org/",0,"a--b.ejemplo.org"], + ["https://Ejemplo.ORG:443/",0,"Ejemplo.ORG"], + ["ipfs://bafkqas7ubxzm6jwiiyrnjxeyzzetrobqiqdqeq3qe4iug6hhwbyniubldbm2glmbw7nkmjd3p6yucda6lnnlexayp3mhiypyxxcz6a2c6nou42uwxkzagcsgeoovsty",0,"bafkqas7ubxzm6jwiiyrnjxeyzzetrobqiqdqeq3qe4iug6hhwbyniubldbm2glmbw7nkmjd3p6yucda6lnnlexayp3mhiypyxxcz6a2c6nou42uwxkzagcsgeoovsty"], + ["https://aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.org/",33,""], + ["https://-a.ejemplo.org/",33,""], + ["https://a-.ejemplo.org/",33,""], + ["https://ejemplo.org./",33,""], + ["https://a..b.org/",33,""], + ["https://.ejemplo.org/",33,""], + ["HTTPS://ejemplo.org/",34,""], + ["Https://ejemplo.org/",35,""], + ["IPFS://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi",36,""], + ["https://ejemplo.0X10/",10,""], + ["https://nas.LOCAL/",31,""], + ["https://x.Home.Arpa/",31,""], + ["https://foo.LocalHost/",31,""], + ["https://01.2.3.4/",10,""], + ["https://8.8.8.08/",10,""], + ["https://8.8.8.256/",10,""], + ["https://ejemplo.org:0443/",37,""], + ["https://ejemplo.org:00443/",37,""], + ["https://ejemplo.org:000443/",12,""], + ["https://[2606:4700::1111]:0443/",37,""], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdj",7,""], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi==",7,""], + ["ipfs://bAFYBEIGDYRZT5SFP7UDM7HU76UH7Y26NF3EFUYLQABF3OCLGTQY55FBZDI",7,""], + ["ipfs://bqeafkeragdypnzmk3bnyaficxcthtp6xxjdrt6rihrbflz7nqks3pehev56q",7,""], + ["ipfs://bafkqaaa",7,""], + ["ipfs://bafkqauhubxzm6jwiiyrnjxeyzzetrobqiqdqeq3qe4iug6hhwbyniubldbm2glmbw7nkmjd3p6yucda6lnnlexayp3mhiypyxxcz6a2c6nou42uwxkzagcsgeoovst5qsekwcva",7,""] + ], + "uris": [ + ["https://0.0.0.0/",10,""], + ["https://0.0.0.00/",10,""], + ["https://0.0.0/",10,""], + ["https://0.0/",10,""], + ["https://0/",10,""], + ["https://0x0/",10,""], + ["https://00/",10,""], + ["https://0.0.0.0./",33,""], + ["https://0.0.0.0.0/",10,""], + ["https://0.0.0.0:443/x",10,""], + ["https://[0.0.0.0]/",11,""], + ["https://[::ffff:0.0.0.0]/",11,""], + ["https://[::0.0.0.0]/",11,""], + ["https://[64:ff9b::0.0.0.0]/",11,""], + ["https://[64:ff9b:1::0.0.0.0]/",11,""], + ["https://0.0.0.1/",10,""], + ["https://0.255.255.254/",10,""], + ["https://0.255.255.255/",10,""], + ["https://00.255.255.255/",10,""], + ["https://0.255.255.00255/",10,""], + ["https://0.255.65535/",10,""], + ["https://0.16777215/",10,""], + ["https://16777215/",10,""], + ["https://0xffffff/",10,""], + ["https://077777777/",10,""], + ["https://0.255.255.255./",33,""], + ["https://0.255.255.255.0/",10,""], + ["https://0xFFfFFf/",10,""], + ["https://0.255.255.255:443/x",10,""], + ["https://[0.255.255.255]/",11,""], + ["https://[::ffff:0.255.255.255]/",11,""], + ["https://[::0.255.255.255]/",11,""], + ["https://[64:ff9b::0.255.255.255]/",11,""], + ["https://[64:ff9b:1::0.255.255.255]/",11,""], + ["https://1.0.0.0/",0,"1.0.0.0"], + ["https://9.255.255.255/",0,"9.255.255.255"], + ["https://10.0.0.0/",10,""], + ["https://10.0.0.00/",10,""], + ["https://10.0.0/",10,""], + ["https://10.0/",10,""], + ["https://167772160/",10,""], + ["https://0xa000000/",10,""], + ["https://01200000000/",10,""], + ["https://10.0.0.0./",33,""], + ["https://10.0.0.0.0/",10,""], + ["https://0XA000000/",10,""], + ["https://10.0.0.0:443/x",10,""], + ["https://[10.0.0.0]/",11,""], + ["https://[::ffff:10.0.0.0]/",11,""], + ["https://[::10.0.0.0]/",11,""], + ["https://[64:ff9b::10.0.0.0]/",11,""], + ["https://[64:ff9b:1::10.0.0.0]/",11,""], + ["https://10.0.0.1/",10,""], + ["https://10.255.255.254/",10,""], + ["https://10.255.255.255/",10,""], + ["https://10.255.65535/",10,""], + ["https://10.16777215/",10,""], + ["https://184549375/",10,""], + ["https://0xaffffff/",10,""], + ["https://01277777777/",10,""], + ["https://10.255.255.255./",33,""], + ["https://10.255.255.255.0/",10,""], + ["https://0XaffffFf/",10,""], + ["https://10.255.255.255:443/x",10,""], + ["https://[10.255.255.255]/",11,""], + ["https://[::ffff:10.255.255.255]/",11,""], + ["https://[::10.255.255.255]/",11,""], + ["https://[64:ff9b::10.255.255.255]/",11,""], + ["https://[64:ff9b:1::10.255.255.255]/",11,""], + ["https://11.0.0.0/",0,"11.0.0.0"], + ["https://100.63.255.255/",0,"100.63.255.255"], + ["https://100.64.0.0/",10,""], + ["https://0x64.64.0.0/",10,""], + ["https://100.64.0.0x0/",10,""], + ["https://100.64.0/",10,""], + ["https://100.4194304/",10,""], + ["https://1681915904/",10,""], + ["https://0x64400000/",10,""], + ["https://014420000000/",10,""], + ["https://100.64.0.0./",33,""], + ["https://100.64.0.0.0/",10,""], + ["https://0X64400000/",10,""], + ["https://100.64.0.0:443/x",10,""], + ["https://[100.64.0.0]/",11,""], + ["https://[::ffff:100.64.0.0]/",11,""], + ["https://[::100.64.0.0]/",11,""], + ["https://[64:ff9b::100.64.0.0]/",11,""], + ["https://[64:ff9b:1::100.64.0.0]/",11,""], + ["https://100.64.0.1/",10,""], + ["https://100.127.255.254/",10,""], + ["https://100.127.255.255/",10,""], + ["https://100.127.255.00255/",10,""], + ["https://100.127.65535/",10,""], + ["https://100.8388607/",10,""], + ["https://1686110207/",10,""], + ["https://0x647fffff/",10,""], + ["https://014437777777/",10,""], + ["https://100.127.255.255./",33,""], + ["https://100.127.255.255.0/",10,""], + ["https://0X647Fffff/",10,""], + ["https://100.127.255.255:443/x",10,""], + ["https://[100.127.255.255]/",11,""], + ["https://[::ffff:100.127.255.255]/",11,""], + ["https://[::100.127.255.255]/",11,""], + ["https://[64:ff9b::100.127.255.255]/",11,""], + ["https://[64:ff9b:1::100.127.255.255]/",11,""], + ["https://100.128.0.0/",0,"100.128.0.0"], + ["https://126.255.255.255/",0,"126.255.255.255"], + ["https://127.0.0.0/",10,""], + ["https://0177.0.0.0/",10,""], + ["https://127.0.0/",10,""], + ["https://127.0/",10,""], + ["https://2130706432/",10,""], + ["https://0x7f000000/",10,""], + ["https://017700000000/",10,""], + ["https://127.0.0.0./",33,""], + ["https://127.0.0.0.0/",10,""], + ["https://0x7F000000/",10,""], + ["https://127.0.0.0:443/x",10,""], + ["https://[127.0.0.0]/",11,""], + ["https://[::ffff:127.0.0.0]/",11,""], + ["https://[::127.0.0.0]/",11,""], + ["https://[64:ff9b::127.0.0.0]/",11,""], + ["https://[64:ff9b:1::127.0.0.0]/",11,""], + ["https://127.0.0.1/",10,""], + ["https://127.255.255.254/",10,""], + ["https://127.255.255.255/",10,""], + ["https://00127.255.255.255/",10,""], + ["https://127.255.255.0377/",10,""], + ["https://127.255.65535/",10,""], + ["https://127.16777215/",10,""], + ["https://2147483647/",10,""], + ["https://0x7fffffff/",10,""], + ["https://017777777777/",10,""], + ["https://127.255.255.255./",33,""], + ["https://127.255.255.255.0/",10,""], + ["https://0X7ffffFFF/",10,""], + ["https://127.255.255.255:443/x",10,""], + ["https://[127.255.255.255]/",11,""], + ["https://[::ffff:127.255.255.255]/",11,""], + ["https://[::127.255.255.255]/",11,""], + ["https://[64:ff9b::127.255.255.255]/",11,""], + ["https://[64:ff9b:1::127.255.255.255]/",11,""], + ["https://128.0.0.0/",0,"128.0.0.0"], + ["https://169.253.255.255/",0,"169.253.255.255"], + ["https://169.254.0.0/",10,""], + ["https://0169.254.0.0/",10,""], + ["https://169.254.0.000/",10,""], + ["https://169.254.0/",10,""], + ["https://169.16646144/",10,""], + ["https://2851995648/",10,""], + ["https://0xa9fe0000/",10,""], + ["https://025177400000/",10,""], + ["https://169.254.0.0./",33,""], + ["https://169.254.0.0.0/",10,""], + ["https://0XA9fe0000/",10,""], + ["https://169.254.0.0:443/x",10,""], + ["https://[169.254.0.0]/",11,""], + ["https://[::ffff:169.254.0.0]/",11,""], + ["https://[::169.254.0.0]/",11,""], + ["https://[64:ff9b::169.254.0.0]/",11,""], + ["https://[64:ff9b:1::169.254.0.0]/",11,""], + ["https://169.254.0.1/",10,""], + ["https://169.254.255.254/",10,""], + ["https://169.254.255.255/",10,""], + ["https://0xa9.254.255.255/",10,""], + ["https://169.254.255.0xff/",10,""], + ["https://169.254.65535/",10,""], + ["https://169.16711679/",10,""], + ["https://2852061183/",10,""], + ["https://0xa9feffff/",10,""], + ["https://025177577777/",10,""], + ["https://169.254.255.255./",33,""], + ["https://169.254.255.255.0/",10,""], + ["https://0XA9FEFFff/",10,""], + ["https://169.254.255.255:443/x",10,""], + ["https://[169.254.255.255]/",11,""], + ["https://[::ffff:169.254.255.255]/",11,""], + ["https://[::169.254.255.255]/",11,""], + ["https://[64:ff9b::169.254.255.255]/",11,""], + ["https://[64:ff9b:1::169.254.255.255]/",11,""], + ["https://169.255.0.0/",0,"169.255.0.0"], + ["https://172.15.255.255/",0,"172.15.255.255"], + ["https://172.16.0.0/",10,""], + ["https://172.16.0.00/",10,""], + ["https://172.16.0/",10,""], + ["https://172.1048576/",10,""], + ["https://2886729728/",10,""], + ["https://0xac100000/",10,""], + ["https://025404000000/",10,""], + ["https://172.16.0.0./",33,""], + ["https://172.16.0.0.0/",10,""], + ["https://0XAc100000/",10,""], + ["https://172.16.0.0:443/x",10,""], + ["https://[172.16.0.0]/",11,""], + ["https://[::ffff:172.16.0.0]/",11,""], + ["https://[::172.16.0.0]/",11,""], + ["https://[64:ff9b::172.16.0.0]/",11,""], + ["https://[64:ff9b:1::172.16.0.0]/",11,""], + ["https://172.16.0.1/",10,""], + ["https://172.31.255.254/",10,""], + ["https://172.31.255.255/",10,""], + ["https://172.31.255.00255/",10,""], + ["https://172.31.65535/",10,""], + ["https://172.2097151/",10,""], + ["https://2887778303/",10,""], + ["https://0xac1fffff/",10,""], + ["https://025407777777/",10,""], + ["https://172.31.255.255./",33,""], + ["https://172.31.255.255.0/",10,""], + ["https://0XAC1FFffF/",10,""], + ["https://172.31.255.255:443/x",10,""], + ["https://[172.31.255.255]/",11,""], + ["https://[::ffff:172.31.255.255]/",11,""], + ["https://[::172.31.255.255]/",11,""], + ["https://[64:ff9b::172.31.255.255]/",11,""], + ["https://[64:ff9b:1::172.31.255.255]/",11,""], + ["https://172.32.0.0/",0,"172.32.0.0"], + ["https://191.255.255.255/",0,"191.255.255.255"], + ["https://192.0.0.0/",10,""], + ["https://192.0.0.0x0/",10,""], + ["https://192.0.0/",10,""], + ["https://192.0/",10,""], + ["https://3221225472/",10,""], + ["https://0xc0000000/",10,""], + ["https://030000000000/",10,""], + ["https://192.0.0.0./",33,""], + ["https://192.0.0.0.0/",10,""], + ["https://0Xc0000000/",10,""], + ["https://192.0.0.0:443/x",10,""], + ["https://[192.0.0.0]/",11,""], + ["https://[::ffff:192.0.0.0]/",11,""], + ["https://[::192.0.0.0]/",11,""], + ["https://[64:ff9b::192.0.0.0]/",11,""], + ["https://[64:ff9b:1::192.0.0.0]/",11,""], + ["https://192.0.0.1/",10,""], + ["https://192.0.0.254/",10,""], + ["https://192.0.0.255/",10,""], + ["https://0300.0.0.255/",10,""], + ["https://192.0.0.0255/",10,""], + ["https://192.0.255/",10,""], + ["https://192.255/",10,""], + ["https://3221225727/",10,""], + ["https://0xc00000ff/",10,""], + ["https://030000000377/",10,""], + ["https://192.0.0.255./",33,""], + ["https://192.0.0.255.0/",10,""], + ["https://0XC00000ff/",10,""], + ["https://192.0.0.255:443/x",10,""], + ["https://[192.0.0.255]/",11,""], + ["https://[::ffff:192.0.0.255]/",11,""], + ["https://[::192.0.0.255]/",11,""], + ["https://[64:ff9b::192.0.0.255]/",11,""], + ["https://[64:ff9b:1::192.0.0.255]/",11,""], + ["https://192.0.1.0/",0,"192.0.1.0"], + ["https://192.0.1.255/",0,"192.0.1.255"], + ["https://192.0.2.0/",10,""], + ["https://192.0.2.0x0/",10,""], + ["https://192.0.512/",10,""], + ["https://192.512/",10,""], + ["https://3221225984/",10,""], + ["https://0xc0000200/",10,""], + ["https://030000001000/",10,""], + ["https://192.0.2.0./",33,""], + ["https://192.0.2.0.0/",10,""], + ["https://0XC0000200/",10,""], + ["https://192.0.2.0:443/x",10,""], + ["https://[192.0.2.0]/",11,""], + ["https://[::ffff:192.0.2.0]/",11,""], + ["https://[::192.0.2.0]/",11,""], + ["https://[64:ff9b::192.0.2.0]/",11,""], + ["https://[64:ff9b:1::192.0.2.0]/",11,""], + ["https://192.0.2.1/",10,""], + ["https://192.0.2.254/",10,""], + ["https://192.0.2.255/",10,""], + ["https://00192.0.2.255/",10,""], + ["https://192.0.2.0377/",10,""], + ["https://192.0.767/",10,""], + ["https://192.767/",10,""], + ["https://3221226239/",10,""], + ["https://0xc00002ff/",10,""], + ["https://030000001377/",10,""], + ["https://192.0.2.255./",33,""], + ["https://192.0.2.255.0/",10,""], + ["https://0Xc00002ff/",10,""], + ["https://192.0.2.255:443/x",10,""], + ["https://[192.0.2.255]/",11,""], + ["https://[::ffff:192.0.2.255]/",11,""], + ["https://[::192.0.2.255]/",11,""], + ["https://[64:ff9b::192.0.2.255]/",11,""], + ["https://[64:ff9b:1::192.0.2.255]/",11,""], + ["https://192.0.3.0/",0,"192.0.3.0"], + ["https://192.88.98.255/",0,"192.88.98.255"], + ["https://192.88.99.0/",10,""], + ["https://192.88.99.00/",10,""], + ["https://192.88.25344/",10,""], + ["https://192.5792512/",10,""], + ["https://3227017984/",10,""], + ["https://0xc0586300/",10,""], + ["https://030026061400/",10,""], + ["https://192.88.99.0./",33,""], + ["https://192.88.99.0.0/",10,""], + ["https://0Xc0586300/",10,""], + ["https://192.88.99.0:443/x",10,""], + ["https://[192.88.99.0]/",11,""], + ["https://[::ffff:192.88.99.0]/",11,""], + ["https://[::192.88.99.0]/",11,""], + ["https://[64:ff9b::192.88.99.0]/",11,""], + ["https://[64:ff9b:1::192.88.99.0]/",11,""], + ["https://192.88.99.1/",10,""], + ["https://192.88.99.254/",10,""], + ["https://192.88.99.255/",10,""], + ["https://0300.88.99.255/",10,""], + ["https://192.88.99.0xff/",10,""], + ["https://192.88.25599/",10,""], + ["https://192.5792767/",10,""], + ["https://3227018239/",10,""], + ["https://0xc05863ff/",10,""], + ["https://030026061777/",10,""], + ["https://192.88.99.255./",33,""], + ["https://192.88.99.255.0/",10,""], + ["https://0Xc05863ff/",10,""], + ["https://192.88.99.255:443/x",10,""], + ["https://[192.88.99.255]/",11,""], + ["https://[::ffff:192.88.99.255]/",11,""], + ["https://[::192.88.99.255]/",11,""], + ["https://[64:ff9b::192.88.99.255]/",11,""], + ["https://[64:ff9b:1::192.88.99.255]/",11,""], + ["https://192.88.100.0/",0,"192.88.100.0"], + ["https://192.167.255.255/",0,"192.167.255.255"], + ["https://192.168.0.0/",10,""], + ["https://0xc0.168.0.0/",10,""], + ["https://192.168.0/",10,""], + ["https://192.11010048/",10,""], + ["https://3232235520/",10,""], + ["https://0xc0a80000/",10,""], + ["https://030052000000/",10,""], + ["https://192.168.0.0./",33,""], + ["https://192.168.0.0.0/",10,""], + ["https://0Xc0A80000/",10,""], + ["https://192.168.0.0:443/x",10,""], + ["https://[192.168.0.0]/",11,""], + ["https://[::ffff:192.168.0.0]/",11,""], + ["https://[::192.168.0.0]/",11,""], + ["https://[64:ff9b::192.168.0.0]/",11,""], + ["https://[64:ff9b:1::192.168.0.0]/",11,""], + ["https://192.168.0.1/",10,""], + ["https://192.168.255.254/",10,""], + ["https://192.168.255.255/",10,""], + ["https://192.168.65535/",10,""], + ["https://192.11075583/",10,""], + ["https://3232301055/",10,""], + ["https://0xc0a8ffff/",10,""], + ["https://030052177777/",10,""], + ["https://192.168.255.255./",33,""], + ["https://192.168.255.255.0/",10,""], + ["https://0XC0a8fFFf/",10,""], + ["https://192.168.255.255:443/x",10,""], + ["https://[192.168.255.255]/",11,""], + ["https://[::ffff:192.168.255.255]/",11,""], + ["https://[::192.168.255.255]/",11,""], + ["https://[64:ff9b::192.168.255.255]/",11,""], + ["https://[64:ff9b:1::192.168.255.255]/",11,""], + ["https://192.169.0.0/",0,"192.169.0.0"], + ["https://198.17.255.255/",0,"198.17.255.255"], + ["https://198.18.0.0/",10,""], + ["https://198.18.0.000/",10,""], + ["https://198.18.0/",10,""], + ["https://198.1179648/",10,""], + ["https://3323068416/",10,""], + ["https://0xc6120000/",10,""], + ["https://030604400000/",10,""], + ["https://198.18.0.0./",33,""], + ["https://198.18.0.0.0/",10,""], + ["https://0Xc6120000/",10,""], + ["https://198.18.0.0:443/x",10,""], + ["https://[198.18.0.0]/",11,""], + ["https://[::ffff:198.18.0.0]/",11,""], + ["https://[::198.18.0.0]/",11,""], + ["https://[64:ff9b::198.18.0.0]/",11,""], + ["https://[64:ff9b:1::198.18.0.0]/",11,""], + ["https://198.18.0.1/",10,""], + ["https://198.19.255.254/",10,""], + ["https://198.19.255.255/",10,""], + ["https://0306.19.255.255/",10,""], + ["https://198.19.65535/",10,""], + ["https://198.1310719/",10,""], + ["https://3323199487/",10,""], + ["https://0xc613ffff/",10,""], + ["https://030604777777/",10,""], + ["https://198.19.255.255./",33,""], + ["https://198.19.255.255.0/",10,""], + ["https://0XC613ffff/",10,""], + ["https://198.19.255.255:443/x",10,""], + ["https://[198.19.255.255]/",11,""], + ["https://[::ffff:198.19.255.255]/",11,""], + ["https://[::198.19.255.255]/",11,""], + ["https://[64:ff9b::198.19.255.255]/",11,""], + ["https://[64:ff9b:1::198.19.255.255]/",11,""], + ["https://198.20.0.0/",0,"198.20.0.0"], + ["https://198.51.99.255/",0,"198.51.99.255"], + ["https://198.51.100.0/",10,""], + ["https://198.51.100.000/",10,""], + ["https://198.51.25600/",10,""], + ["https://198.3367936/",10,""], + ["https://3325256704/",10,""], + ["https://0xc6336400/",10,""], + ["https://030614662000/",10,""], + ["https://198.51.100.0./",33,""], + ["https://198.51.100.0.0/",10,""], + ["https://0Xc6336400/",10,""], + ["https://198.51.100.0:443/x",10,""], + ["https://[198.51.100.0]/",11,""], + ["https://[::ffff:198.51.100.0]/",11,""], + ["https://[::198.51.100.0]/",11,""], + ["https://[64:ff9b::198.51.100.0]/",11,""], + ["https://[64:ff9b:1::198.51.100.0]/",11,""], + ["https://198.51.100.1/",10,""], + ["https://198.51.100.254/",10,""], + ["https://198.51.100.255/",10,""], + ["https://198.51.100.0xff/",10,""], + ["https://198.51.25855/",10,""], + ["https://198.3368191/",10,""], + ["https://3325256959/",10,""], + ["https://0xc63364ff/",10,""], + ["https://030614662377/",10,""], + ["https://198.51.100.255./",33,""], + ["https://198.51.100.255.0/",10,""], + ["https://198.51.100.255:443/x",10,""], + ["https://[198.51.100.255]/",11,""], + ["https://[::ffff:198.51.100.255]/",11,""], + ["https://[::198.51.100.255]/",11,""], + ["https://[64:ff9b::198.51.100.255]/",11,""], + ["https://[64:ff9b:1::198.51.100.255]/",11,""], + ["https://198.51.101.0/",0,"198.51.101.0"], + ["https://203.0.112.255/",0,"203.0.112.255"], + ["https://203.0.113.0/",10,""], + ["https://00203.0.113.0/",10,""], + ["https://203.0.113.000/",10,""], + ["https://203.0.28928/",10,""], + ["https://203.28928/",10,""], + ["https://3405803776/",10,""], + ["https://0xcb007100/",10,""], + ["https://031300070400/",10,""], + ["https://203.0.113.0./",33,""], + ["https://203.0.113.0.0/",10,""], + ["https://0XcB007100/",10,""], + ["https://203.0.113.0:443/x",10,""], + ["https://[203.0.113.0]/",11,""], + ["https://[::ffff:203.0.113.0]/",11,""], + ["https://[::203.0.113.0]/",11,""], + ["https://[64:ff9b::203.0.113.0]/",11,""], + ["https://[64:ff9b:1::203.0.113.0]/",11,""], + ["https://203.0.113.1/",10,""], + ["https://203.0.113.254/",10,""], + ["https://203.0.113.255/",10,""], + ["https://203.0.113.0255/",10,""], + ["https://203.0.29183/",10,""], + ["https://203.29183/",10,""], + ["https://3405804031/",10,""], + ["https://0xcb0071ff/",10,""], + ["https://031300070777/",10,""], + ["https://203.0.113.255./",33,""], + ["https://203.0.113.255.0/",10,""], + ["https://0XCb0071ff/",10,""], + ["https://203.0.113.255:443/x",10,""], + ["https://[203.0.113.255]/",11,""], + ["https://[::ffff:203.0.113.255]/",11,""], + ["https://[::203.0.113.255]/",11,""], + ["https://[64:ff9b::203.0.113.255]/",11,""], + ["https://[64:ff9b:1::203.0.113.255]/",11,""], + ["https://203.0.114.0/",0,"203.0.114.0"], + ["https://223.255.255.255/",0,"223.255.255.255"], + ["https://224.0.0.0/",10,""], + ["https://224.0.0/",10,""], + ["https://224.0/",10,""], + ["https://3758096384/",10,""], + ["https://0xe0000000/",10,""], + ["https://034000000000/",10,""], + ["https://224.0.0.0./",33,""], + ["https://224.0.0.0.0/",10,""], + ["https://224.0.0.0:443/x",10,""], + ["https://[224.0.0.0]/",11,""], + ["https://[::ffff:224.0.0.0]/",11,""], + ["https://[::224.0.0.0]/",11,""], + ["https://[64:ff9b::224.0.0.0]/",11,""], + ["https://[64:ff9b:1::224.0.0.0]/",11,""], + ["https://224.0.0.1/",10,""], + ["https://239.255.255.254/",10,""], + ["https://239.255.255.255/",10,""], + ["https://239.255.255.0xff/",10,""], + ["https://239.255.65535/",10,""], + ["https://239.16777215/",10,""], + ["https://4026531839/",10,""], + ["https://0xefffffff/",10,""], + ["https://035777777777/",10,""], + ["https://239.255.255.255./",33,""], + ["https://239.255.255.255.0/",10,""], + ["https://0XEFfFFfFF/",10,""], + ["https://239.255.255.255:443/x",10,""], + ["https://[239.255.255.255]/",11,""], + ["https://[::ffff:239.255.255.255]/",11,""], + ["https://[::239.255.255.255]/",11,""], + ["https://[64:ff9b::239.255.255.255]/",11,""], + ["https://[64:ff9b:1::239.255.255.255]/",11,""], + ["https://240.0.0.0/",10,""], + ["https://240.0.0.0x0/",10,""], + ["https://240.0.0/",10,""], + ["https://240.0/",10,""], + ["https://4026531840/",10,""], + ["https://0xf0000000/",10,""], + ["https://036000000000/",10,""], + ["https://240.0.0.0./",33,""], + ["https://240.0.0.0.0/",10,""], + ["https://0XF0000000/",10,""], + ["https://240.0.0.0:443/x",10,""], + ["https://[240.0.0.0]/",11,""], + ["https://[::ffff:240.0.0.0]/",11,""], + ["https://[::240.0.0.0]/",11,""], + ["https://[64:ff9b::240.0.0.0]/",11,""], + ["https://[64:ff9b:1::240.0.0.0]/",11,""], + ["https://240.0.0.1/",10,""], + ["https://255.255.255.254/",10,""], + ["https://255.255.255.255/",10,""], + ["https://255.255.255.00255/",10,""], + ["https://255.255.65535/",10,""], + ["https://255.16777215/",10,""], + ["https://4294967295/",10,""], + ["https://0xffffffff/",10,""], + ["https://037777777777/",10,""], + ["https://255.255.255.255./",33,""], + ["https://255.255.255.255.0/",10,""], + ["https://0XfffffFff/",10,""], + ["https://255.255.255.255:443/x",10,""], + ["https://[255.255.255.255]/",11,""], + ["https://[::ffff:255.255.255.255]/",11,""], + ["https://[::255.255.255.255]/",11,""], + ["https://[64:ff9b::255.255.255.255]/",11,""], + ["https://[64:ff9b:1::255.255.255.255]/",11,""], + ["https://01.0.0.0/",10,""], + ["https://1.0.0.0x0/",10,""], + ["https://1.0.0/",10,""], + ["https://1.0/",10,""], + ["https://16777216/",10,""], + ["https://0x1000000/",10,""], + ["https://0100000000/",10,""], + ["https://1.0.0.0./",33,""], + ["https://1.0.0.0.0/",10,""], + ["https://0X1000000/",10,""], + ["https://1.0.0.0:443/x",0,"1.0.0.0"], + ["https://[1.0.0.0]/",11,""], + ["https://[::ffff:1.0.0.0]/",11,""], + ["https://[::1.0.0.0]/",11,""], + ["https://[64:ff9b::1.0.0.0]/",11,""], + ["https://[64:ff9b:1::1.0.0.0]/",11,""], + ["https://1.0.0.1/",0,"1.0.0.1"], + ["https://1.0.0.254/",0,"1.0.0.254"], + ["https://1.0.0.255/",0,"1.0.0.255"], + ["https://01.0.0.255/",10,""], + ["https://1.0.0.0255/",10,""], + ["https://1.0.255/",10,""], + ["https://1.255/",10,""], + ["https://16777471/",10,""], + ["https://0x10000ff/",10,""], + ["https://0100000377/",10,""], + ["https://1.0.0.255./",33,""], + ["https://1.0.0.255.0/",10,""], + ["https://0X10000fF/",10,""], + ["https://1.0.0.255:443/x",0,"1.0.0.255"], + ["https://[1.0.0.255]/",11,""], + ["https://[::ffff:1.0.0.255]/",11,""], + ["https://[::1.0.0.255]/",11,""], + ["https://[64:ff9b::1.0.0.255]/",11,""], + ["https://[64:ff9b:1::1.0.0.255]/",11,""], + ["https://1.0.1.0/",0,"1.0.1.0"], + ["https://8.8.7.255/",0,"8.8.7.255"], + ["https://8.8.8.0/",0,"8.8.8.0"], + ["https://8.8.8.00/",10,""], + ["https://8.8.2048/",10,""], + ["https://8.526336/",10,""], + ["https://134744064/",10,""], + ["https://0x8080800/",10,""], + ["https://01002004000/",10,""], + ["https://8.8.8.0./",33,""], + ["https://8.8.8.0.0/",10,""], + ["https://8.8.8.0:443/x",0,"8.8.8.0"], + ["https://[8.8.8.0]/",11,""], + ["https://[::ffff:8.8.8.0]/",11,""], + ["https://[::8.8.8.0]/",11,""], + ["https://[64:ff9b::8.8.8.0]/",11,""], + ["https://[64:ff9b:1::8.8.8.0]/",11,""], + ["https://8.8.8.1/",0,"8.8.8.1"], + ["https://8.8.8.254/",0,"8.8.8.254"], + ["https://8.8.8.255/",0,"8.8.8.255"], + ["https://8.8.8.00255/",10,""], + ["https://8.8.2303/",10,""], + ["https://8.526591/",10,""], + ["https://134744319/",10,""], + ["https://0x80808ff/",10,""], + ["https://01002004377/",10,""], + ["https://8.8.8.255./",33,""], + ["https://8.8.8.255.0/",10,""], + ["https://0X80808Ff/",10,""], + ["https://8.8.8.255:443/x",0,"8.8.8.255"], + ["https://[8.8.8.255]/",11,""], + ["https://[::ffff:8.8.8.255]/",11,""], + ["https://[::8.8.8.255]/",11,""], + ["https://[64:ff9b::8.8.8.255]/",11,""], + ["https://[64:ff9b:1::8.8.8.255]/",11,""], + ["https://8.8.9.0/",0,"8.8.9.0"], + ["https://192.0.256/",10,""], + ["https://192.256/",10,""], + ["https://3221225728/",10,""], + ["https://0xc0000100/",10,""], + ["https://030000000400/",10,""], + ["https://192.0.1.0./",33,""], + ["https://192.0.1.0.0/",10,""], + ["https://0XC0000100/",10,""], + ["https://192.0.1.0:443/x",0,"192.0.1.0"], + ["https://[192.0.1.0]/",11,""], + ["https://[::ffff:192.0.1.0]/",11,""], + ["https://[::192.0.1.0]/",11,""], + ["https://[64:ff9b::192.0.1.0]/",11,""], + ["https://[64:ff9b:1::192.0.1.0]/",11,""], + ["https://192.0.1.1/",0,"192.0.1.1"], + ["https://192.0.1.254/",0,"192.0.1.254"], + ["https://0300.0.1.255/",10,""], + ["https://192.0.1.00255/",10,""], + ["https://192.0.511/",10,""], + ["https://192.511/",10,""], + ["https://3221225983/",10,""], + ["https://0xc00001ff/",10,""], + ["https://030000000777/",10,""], + ["https://192.0.1.255./",33,""], + ["https://192.0.1.255.0/",10,""], + ["https://0XC00001ff/",10,""], + ["https://192.0.1.255:443/x",0,"192.0.1.255"], + ["https://[192.0.1.255]/",11,""], + ["https://[::ffff:192.0.1.255]/",11,""], + ["https://[::192.0.1.255]/",11,""], + ["https://[64:ff9b::192.0.1.255]/",11,""], + ["https://[64:ff9b:1::192.0.1.255]/",11,""], + ["https://192.31.195.255/",0,"192.31.195.255"], + ["https://192.31.196.0/",0,"192.31.196.0"], + ["https://0192.31.196.0/",10,""], + ["https://192.31.196.00/",10,""], + ["https://192.31.50176/",10,""], + ["https://192.2081792/",10,""], + ["https://3223307264/",10,""], + ["https://0xc01fc400/",10,""], + ["https://030007742000/",10,""], + ["https://192.31.196.0./",33,""], + ["https://192.31.196.0.0/",10,""], + ["https://0Xc01Fc400/",10,""], + ["https://192.31.196.0:443/x",0,"192.31.196.0"], + ["https://[192.31.196.0]/",11,""], + ["https://[::ffff:192.31.196.0]/",11,""], + ["https://[::192.31.196.0]/",11,""], + ["https://[64:ff9b::192.31.196.0]/",11,""], + ["https://[64:ff9b:1::192.31.196.0]/",11,""], + ["https://192.31.196.1/",0,"192.31.196.1"], + ["https://192.31.196.254/",0,"192.31.196.254"], + ["https://192.31.196.255/",0,"192.31.196.255"], + ["https://192.31.196.00255/",10,""], + ["https://192.31.50431/",10,""], + ["https://192.2082047/",10,""], + ["https://3223307519/",10,""], + ["https://0xc01fc4ff/",10,""], + ["https://030007742377/",10,""], + ["https://192.31.196.255./",33,""], + ["https://192.31.196.255.0/",10,""], + ["https://0xc01fC4fF/",10,""], + ["https://192.31.196.255:443/x",0,"192.31.196.255"], + ["https://[192.31.196.255]/",11,""], + ["https://[::ffff:192.31.196.255]/",11,""], + ["https://[::192.31.196.255]/",11,""], + ["https://[64:ff9b::192.31.196.255]/",11,""], + ["https://[64:ff9b:1::192.31.196.255]/",11,""], + ["https://192.31.197.0/",0,"192.31.197.0"], + ["https://192.52.192.255/",0,"192.52.192.255"], + ["https://192.52.193.0/",0,"192.52.193.0"], + ["https://0300.52.193.0/",10,""], + ["https://192.52.49408/",10,""], + ["https://192.3457280/",10,""], + ["https://3224682752/",10,""], + ["https://0xc034c100/",10,""], + ["https://030015140400/",10,""], + ["https://192.52.193.0./",33,""], + ["https://192.52.193.0.0/",10,""], + ["https://0Xc034c100/",10,""], + ["https://192.52.193.0:443/x",0,"192.52.193.0"], + ["https://[192.52.193.0]/",11,""], + ["https://[::ffff:192.52.193.0]/",11,""], + ["https://[::192.52.193.0]/",11,""], + ["https://[64:ff9b::192.52.193.0]/",11,""], + ["https://[64:ff9b:1::192.52.193.0]/",11,""], + ["https://192.52.193.1/",0,"192.52.193.1"], + ["https://192.52.193.254/",0,"192.52.193.254"], + ["https://192.52.193.255/",0,"192.52.193.255"], + ["https://00192.52.193.255/",10,""], + ["https://192.52.193.00255/",10,""], + ["https://192.52.49663/",10,""], + ["https://192.3457535/",10,""], + ["https://3224683007/",10,""], + ["https://0xc034c1ff/",10,""], + ["https://030015140777/",10,""], + ["https://192.52.193.255./",33,""], + ["https://192.52.193.255.0/",10,""], + ["https://0Xc034c1fF/",10,""], + ["https://192.52.193.255:443/x",0,"192.52.193.255"], + ["https://[192.52.193.255]/",11,""], + ["https://[::ffff:192.52.193.255]/",11,""], + ["https://[::192.52.193.255]/",11,""], + ["https://[64:ff9b::192.52.193.255]/",11,""], + ["https://[64:ff9b:1::192.52.193.255]/",11,""], + ["https://192.52.194.0/",0,"192.52.194.0"], + ["https://192.175.47.255/",0,"192.175.47.255"], + ["https://192.175.48.0/",0,"192.175.48.0"], + ["https://192.175.48.0x0/",10,""], + ["https://192.175.12288/",10,""], + ["https://192.11481088/",10,""], + ["https://3232706560/",10,""], + ["https://0xc0af3000/",10,""], + ["https://030053630000/",10,""], + ["https://192.175.48.0./",33,""], + ["https://192.175.48.0.0/",10,""], + ["https://0Xc0Af3000/",10,""], + ["https://192.175.48.0:443/x",0,"192.175.48.0"], + ["https://[192.175.48.0]/",11,""], + ["https://[::ffff:192.175.48.0]/",11,""], + ["https://[::192.175.48.0]/",11,""], + ["https://[64:ff9b::192.175.48.0]/",11,""], + ["https://[64:ff9b:1::192.175.48.0]/",11,""], + ["https://192.175.48.1/",0,"192.175.48.1"], + ["https://192.175.48.254/",0,"192.175.48.254"], + ["https://192.175.48.255/",0,"192.175.48.255"], + ["https://192.175.48.0xff/",10,""], + ["https://192.175.12543/",10,""], + ["https://192.11481343/",10,""], + ["https://3232706815/",10,""], + ["https://0xc0af30ff/",10,""], + ["https://030053630377/",10,""], + ["https://192.175.48.255./",33,""], + ["https://192.175.48.255.0/",10,""], + ["https://192.175.48.255:443/x",0,"192.175.48.255"], + ["https://[192.175.48.255]/",11,""], + ["https://[::ffff:192.175.48.255]/",11,""], + ["https://[::192.175.48.255]/",11,""], + ["https://[64:ff9b::192.175.48.255]/",11,""], + ["https://[64:ff9b:1::192.175.48.255]/",11,""], + ["https://192.175.49.0/",0,"192.175.49.0"], + ["https://[1fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[2000::]/",0,"2000::"], + ["https://[2000:0:0:0:0:0:0:0]/",0,"2000:0:0:0:0:0:0:0"], + ["https://[2000:0000:0000:0000:0000:0000:0000:0000]/",0,"2000:0000:0000:0000:0000:0000:0000:0000"], + ["https://[2000:0:0:0:0:0:0.0.0.0]/",0,"2000:0:0:0:0:0:0.0.0.0"], + ["https://[2000:0000:0000:0000:0000:0000:00000:0000]/",11,""], + ["https://[2000:0:0:0:0:0:0:0:1]/",11,""], + ["https://[2000:0:0:0:0:0:0]/",11,""], + ["https://[2000:0:0::0::1]/",11,""], + ["https://[2000:0:0:0:0:0:0:0:]/",11,""], + ["https://[:2000:0:0:0:0:0:0:0]/",11,""], + ["https://[2000:0:0:0:0:0:0:0::]/",11,""], + ["https://2000::/",12,""], + ["https://[2000::]:8443/a",0,"2000::"], + ["https://[2000::]:0443/",37,""], + ["https://[2000::%25eth0]/",5,""], + ["https://[2000::%eth0]/",29,""], + ["https://[[2000::]/",0,"2000::"], + ["https://[[2000::]]/",12,""], + ["https://[2000::]]/",12,""], + ["https://[2000::/",15,""], + ["https://[2000::]x/",12,""], + ["https://[2000::]:/",12,""], + ["https://[2000::1]/",0,"2000::1"], + ["https://[3fff:ffff:ffff:ffff:ffff:ffff:ffff:fffe]/",0,"3fff:ffff:ffff:ffff:ffff:ffff:ffff:fffe"], + ["https://[3fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]/",0,"3fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"], + ["https://[3FFF:ffFF:FFff:fFFf:FFff:ffff:Ffff:ffff]/",0,"3FFF:ffFF:FFff:fFFf:FFff:ffff:Ffff:ffff"], + ["https://[3fff:ffff:ffff:ffff:ffff:ffff:255.255.255.255]/",0,"3fff:ffff:ffff:ffff:ffff:ffff:255.255.255.255"], + ["https://[03fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[3fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff:1]/",11,""], + ["https://[3fff:ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[3fff:ffff:ffff::ffff::1]/",11,""], + ["https://[3fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff:]/",11,""], + ["https://[:3fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[3fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff::]/",11,""], + ["https://3fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/",12,""], + ["https://[3fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]:8443/a",0,"3fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"], + ["https://[3fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]:0443/",37,""], + ["https://[3fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff%25eth0]/",5,""], + ["https://[3fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff%eth0]/",29,""], + ["https://[[3fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]/",0,"3fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"], + ["https://[[3fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]]/",12,""], + ["https://[3fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]]/",12,""], + ["https://[3fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/",15,""], + ["https://[3fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]x/",12,""], + ["https://[3fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]:/",12,""], + ["https://[4000::]/",11,""], + ["https://[2000:ffff:ffff:ffff:ffff:ffff:ffff:ffff]/",0,"2000:ffff:ffff:ffff:ffff:ffff:ffff:ffff"], + ["https://[2001::]/",11,""], + ["https://[2001:0:0:0:0:0:0:0]/",11,""], + ["https://[2001:0000:0000:0000:0000:0000:0000:0000]/",11,""], + ["https://[2001:0:0:0:0:0:0.0.0.0]/",11,""], + ["https://[2001:0000:0000:0000:0000:00000:0000:0000]/",11,""], + ["https://[2001:0:0:0:0:0:0:0:1]/",11,""], + ["https://[2001:0:0:0:0:0:0]/",11,""], + ["https://[2001:0:0::0::1]/",11,""], + ["https://[2001:0:0:0:0:0:0:0:]/",11,""], + ["https://[:2001:0:0:0:0:0:0:0]/",11,""], + ["https://[2001:0:0:0:0:0:0:0::]/",11,""], + ["https://2001::/",12,""], + ["https://[2001::]:8443/a",11,""], + ["https://[2001::]:0443/",37,""], + ["https://[2001::%25eth0]/",5,""], + ["https://[2001::%eth0]/",29,""], + ["https://[[2001::]/",11,""], + ["https://[[2001::]]/",12,""], + ["https://[2001::]]/",12,""], + ["https://[2001::/",15,""], + ["https://[2001::]x/",12,""], + ["https://[2001::]:/",12,""], + ["https://[2001::1]/",11,""], + ["https://[2001:1ff:ffff:ffff:ffff:ffff:ffff:fffe]/",11,""], + ["https://[2001:1ff:ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[2001:01ff:ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[2001:1FF:ffff:Ffff:ffFf:fFfF:FFff:FFff]/",11,""], + ["https://[2001:1ff:ffff:ffff:ffff:ffff:255.255.255.255]/",11,""], + ["https://[2001:01ff:ffff:ffff:0ffff:ffff:ffff:ffff]/",11,""], + ["https://[2001:1ff:ffff:ffff:ffff:ffff:ffff:ffff:1]/",11,""], + ["https://[2001:1ff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[2001:1ff:ffff::ffff::1]/",11,""], + ["https://[2001:1ff:ffff:ffff:ffff:ffff:ffff:ffff:]/",11,""], + ["https://[:2001:1ff:ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[2001:1ff:ffff:ffff:ffff:ffff:ffff:ffff::]/",11,""], + ["https://2001:1ff:ffff:ffff:ffff:ffff:ffff:ffff/",12,""], + ["https://[2001:1ff:ffff:ffff:ffff:ffff:ffff:ffff]:8443/a",11,""], + ["https://[2001:1ff:ffff:ffff:ffff:ffff:ffff:ffff]:0443/",37,""], + ["https://[2001:1ff:ffff:ffff:ffff:ffff:ffff:ffff%25eth0]/",5,""], + ["https://[2001:1ff:ffff:ffff:ffff:ffff:ffff:ffff%eth0]/",29,""], + ["https://[[2001:1ff:ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[[2001:1ff:ffff:ffff:ffff:ffff:ffff:ffff]]/",12,""], + ["https://[2001:1ff:ffff:ffff:ffff:ffff:ffff:ffff]]/",12,""], + ["https://[2001:1ff:ffff:ffff:ffff:ffff:ffff:ffff/",15,""], + ["https://[2001:1ff:ffff:ffff:ffff:ffff:ffff:ffff]x/",12,""], + ["https://[2001:1ff:ffff:ffff:ffff:ffff:ffff:ffff]:/",12,""], + ["https://[2001:200::]/",0,"2001:200::"], + ["https://[2001:db7:ffff:ffff:ffff:ffff:ffff:ffff]/",0,"2001:db7:ffff:ffff:ffff:ffff:ffff:ffff"], + ["https://[2001:db8::]/",11,""], + ["https://[2001:db8:0:0:0:0:0:0]/",11,""], + ["https://[2001:0db8:0000:0000:0000:0000:0000:0000]/",11,""], + ["https://[2001:db8:0:0:0:0:0.0.0.0]/",11,""], + ["https://[2001:0db8:00000:0000:0000:0000:0000:0000]/",11,""], + ["https://[2001:db8:0:0:0:0:0:0:1]/",11,""], + ["https://[2001:db8:0:0:0:0:0]/",11,""], + ["https://[2001:db8:0::0::1]/",11,""], + ["https://[2001:db8:0:0:0:0:0:0:]/",11,""], + ["https://[:2001:db8:0:0:0:0:0:0]/",11,""], + ["https://[2001:db8:0:0:0:0:0:0::]/",11,""], + ["https://2001:db8::/",12,""], + ["https://[2001:db8::]:8443/a",11,""], + ["https://[2001:db8::]:0443/",37,""], + ["https://[2001:db8::%25eth0]/",5,""], + ["https://[2001:db8::%eth0]/",29,""], + ["https://[[2001:db8::]/",11,""], + ["https://[[2001:db8::]]/",12,""], + ["https://[2001:db8::]]/",12,""], + ["https://[2001:db8::/",15,""], + ["https://[2001:db8::]x/",12,""], + ["https://[2001:db8::]:/",12,""], + ["https://[2001:db8::1]/",11,""], + ["https://[2001:db8:ffff:ffff:ffff:ffff:ffff:fffe]/",11,""], + ["https://[2001:db8:ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[2001:0db8:ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[2001:Db8:ffFf:ffff:fFff:FFfF:fFff:FFFf]/",11,""], + ["https://[2001:db8:ffff:ffff:ffff:ffff:255.255.255.255]/",11,""], + ["https://[2001:0db8:ffff:ffff:ffff:ffff:0ffff:ffff]/",11,""], + ["https://[2001:db8:ffff:ffff:ffff:ffff:ffff:ffff:1]/",11,""], + ["https://[2001:db8:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[2001:db8:ffff::ffff::1]/",11,""], + ["https://[2001:db8:ffff:ffff:ffff:ffff:ffff:ffff:]/",11,""], + ["https://[:2001:db8:ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[2001:db8:ffff:ffff:ffff:ffff:ffff:ffff::]/",11,""], + ["https://2001:db8:ffff:ffff:ffff:ffff:ffff:ffff/",12,""], + ["https://[2001:db8:ffff:ffff:ffff:ffff:ffff:ffff]:8443/a",11,""], + ["https://[2001:db8:ffff:ffff:ffff:ffff:ffff:ffff]:0443/",37,""], + ["https://[2001:db8:ffff:ffff:ffff:ffff:ffff:ffff%25eth0]/",5,""], + ["https://[2001:db8:ffff:ffff:ffff:ffff:ffff:ffff%eth0]/",29,""], + ["https://[[2001:db8:ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[[2001:db8:ffff:ffff:ffff:ffff:ffff:ffff]]/",12,""], + ["https://[2001:db8:ffff:ffff:ffff:ffff:ffff:ffff]]/",12,""], + ["https://[2001:db8:ffff:ffff:ffff:ffff:ffff:ffff/",15,""], + ["https://[2001:db8:ffff:ffff:ffff:ffff:ffff:ffff]x/",12,""], + ["https://[2001:db8:ffff:ffff:ffff:ffff:ffff:ffff]:/",12,""], + ["https://[2001:db9::]/",0,"2001:db9::"], + ["https://[2001:ffff:ffff:ffff:ffff:ffff:ffff:ffff]/",0,"2001:ffff:ffff:ffff:ffff:ffff:ffff:ffff"], + ["https://[2002::]/",11,""], + ["https://[2002:0:0:0:0:0:0:0]/",11,""], + ["https://[2002:0000:0000:0000:0000:0000:0000:0000]/",11,""], + ["https://[2002:0:0:0:0:0:0.0.0.0]/",11,""], + ["https://[2002:0000:0000:0000:0000:0000:0000:00000]/",11,""], + ["https://[2002:0:0:0:0:0:0:0:1]/",11,""], + ["https://[2002:0:0:0:0:0:0]/",11,""], + ["https://[2002:0:0::0::1]/",11,""], + ["https://[2002:0:0:0:0:0:0:0:]/",11,""], + ["https://[:2002:0:0:0:0:0:0:0]/",11,""], + ["https://[2002:0:0:0:0:0:0:0::]/",11,""], + ["https://2002::/",12,""], + ["https://[2002::]:8443/a",11,""], + ["https://[2002::]:0443/",37,""], + ["https://[2002::%25eth0]/",5,""], + ["https://[2002::%eth0]/",29,""], + ["https://[[2002::]/",11,""], + ["https://[[2002::]]/",12,""], + ["https://[2002::]]/",12,""], + ["https://[2002::/",15,""], + ["https://[2002::]x/",12,""], + ["https://[2002::]:/",12,""], + ["https://[2002::1]/",11,""], + ["https://[2002:ffff:ffff:ffff:ffff:ffff:ffff:fffe]/",11,""], + ["https://[2002:ffff:ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[2002:FffF:fFff:fFfF:ffFF:Ffff:ffff:Ffff]/",11,""], + ["https://[2002:ffff:ffff:ffff:ffff:ffff:255.255.255.255]/",11,""], + ["https://[2002:ffff:0ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[2002:ffff:ffff:ffff:ffff:ffff:ffff:ffff:1]/",11,""], + ["https://[2002:ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[2002:ffff:ffff::ffff::1]/",11,""], + ["https://[2002:ffff:ffff:ffff:ffff:ffff:ffff:ffff:]/",11,""], + ["https://[:2002:ffff:ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[2002:ffff:ffff:ffff:ffff:ffff:ffff:ffff::]/",11,""], + ["https://2002:ffff:ffff:ffff:ffff:ffff:ffff:ffff/",12,""], + ["https://[2002:ffff:ffff:ffff:ffff:ffff:ffff:ffff]:8443/a",11,""], + ["https://[2002:ffff:ffff:ffff:ffff:ffff:ffff:ffff]:0443/",37,""], + ["https://[2002:ffff:ffff:ffff:ffff:ffff:ffff:ffff%25eth0]/",5,""], + ["https://[2002:ffff:ffff:ffff:ffff:ffff:ffff:ffff%eth0]/",29,""], + ["https://[[2002:ffff:ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[[2002:ffff:ffff:ffff:ffff:ffff:ffff:ffff]]/",12,""], + ["https://[2002:ffff:ffff:ffff:ffff:ffff:ffff:ffff]]/",12,""], + ["https://[2002:ffff:ffff:ffff:ffff:ffff:ffff:ffff/",15,""], + ["https://[2002:ffff:ffff:ffff:ffff:ffff:ffff:ffff]x/",12,""], + ["https://[2002:ffff:ffff:ffff:ffff:ffff:ffff:ffff]:/",12,""], + ["https://[2003::]/",0,"2003::"], + ["https://[3ffe:ffff:ffff:ffff:ffff:ffff:ffff:ffff]/",0,"3ffe:ffff:ffff:ffff:ffff:ffff:ffff:ffff"], + ["https://[3fff::]/",11,""], + ["https://[3fff:0:0:0:0:0:0:0]/",11,""], + ["https://[3fff:0000:0000:0000:0000:0000:0000:0000]/",11,""], + ["https://[3fff:0:0:0:0:0:0.0.0.0]/",11,""], + ["https://[3fff:00000:0000:0000:0000:0000:0000:0000]/",11,""], + ["https://[3fff:0:0:0:0:0:0:0:1]/",11,""], + ["https://[3fff:0:0:0:0:0:0]/",11,""], + ["https://[3fff:0:0::0::1]/",11,""], + ["https://[3fff:0:0:0:0:0:0:0:]/",11,""], + ["https://[:3fff:0:0:0:0:0:0:0]/",11,""], + ["https://[3fff:0:0:0:0:0:0:0::]/",11,""], + ["https://3fff::/",12,""], + ["https://[3fff::]:8443/a",11,""], + ["https://[3fff::]:0443/",37,""], + ["https://[3fff::%25eth0]/",5,""], + ["https://[3fff::%eth0]/",29,""], + ["https://[[3fff::]/",11,""], + ["https://[[3fff::]]/",12,""], + ["https://[3fff::]]/",12,""], + ["https://[3fff::/",15,""], + ["https://[3fff::]x/",12,""], + ["https://[3fff::]:/",12,""], + ["https://[3fff::1]/",11,""], + ["https://[3fff:fff:ffff:ffff:ffff:ffff:ffff:fffe]/",11,""], + ["https://[3fff:fff:ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[3fff:0fff:ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[3fFf:ffF:FfFF:ffff:fFff:fFFF:FfFf:ffff]/",11,""], + ["https://[3fff:fff:ffff:ffff:ffff:ffff:255.255.255.255]/",11,""], + ["https://[3fff:0fff:ffff:ffff:0ffff:ffff:ffff:ffff]/",11,""], + ["https://[3fff:fff:ffff:ffff:ffff:ffff:ffff:ffff:1]/",11,""], + ["https://[3fff:fff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[3fff:fff:ffff::ffff::1]/",11,""], + ["https://[3fff:fff:ffff:ffff:ffff:ffff:ffff:ffff:]/",11,""], + ["https://[:3fff:fff:ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[3fff:fff:ffff:ffff:ffff:ffff:ffff:ffff::]/",11,""], + ["https://3fff:fff:ffff:ffff:ffff:ffff:ffff:ffff/",12,""], + ["https://[3fff:fff:ffff:ffff:ffff:ffff:ffff:ffff]:8443/a",11,""], + ["https://[3fff:fff:ffff:ffff:ffff:ffff:ffff:ffff]:0443/",37,""], + ["https://[3fff:fff:ffff:ffff:ffff:ffff:ffff:ffff%25eth0]/",5,""], + ["https://[3fff:fff:ffff:ffff:ffff:ffff:ffff:ffff%eth0]/",29,""], + ["https://[[3fff:fff:ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[[3fff:fff:ffff:ffff:ffff:ffff:ffff:ffff]]/",12,""], + ["https://[3fff:fff:ffff:ffff:ffff:ffff:ffff:ffff]]/",12,""], + ["https://[3fff:fff:ffff:ffff:ffff:ffff:ffff:ffff/",15,""], + ["https://[3fff:fff:ffff:ffff:ffff:ffff:ffff:ffff]x/",12,""], + ["https://[3fff:fff:ffff:ffff:ffff:ffff:ffff:ffff]:/",12,""], + ["https://[3fff:1000::]/",0,"3fff:1000::"], + ["https://[::]/",11,""], + ["https://[0:0:0:0:0:0:0:0]/",11,""], + ["https://[0000:0000:0000:0000:0000:0000:0000:0000]/",11,""], + ["https://[0:0:0:0:0:0:0.0.0.0]/",11,""], + ["https://[::0:0.0.0.0]/",11,""], + ["https://[0000:00000:0000:0000:0000:0000:0000:0000]/",11,""], + ["https://[0:0:0:0:0:0:0:0:1]/",11,""], + ["https://[0:0:0:0:0:0:0]/",11,""], + ["https://[0:0:0::0::1]/",11,""], + ["https://[0:0:0:0:0:0:0:0:]/",11,""], + ["https://[:0:0:0:0:0:0:0:0]/",11,""], + ["https://[0:0:0:0:0:0:0:0::]/",11,""], + ["https://::/",12,""], + ["https://[::]:8443/a",11,""], + ["https://[::]:0443/",37,""], + ["https://[::%25eth0]/",5,""], + ["https://[::%eth0]/",29,""], + ["https://[[::]/",11,""], + ["https://[[::]]/",12,""], + ["https://[::]]/",12,""], + ["https://[::/",15,""], + ["https://[::]x/",12,""], + ["https://[::]:/",12,""], + ["https://[::1]/",11,""], + ["https://[::ffff:fffe]/",11,""], + ["https://[::ffff:ffff]/",11,""], + ["https://[0:0:0:0:0:0:ffff:ffff]/",11,""], + ["https://[0000:0000:0000:0000:0000:0000:ffff:ffff]/",11,""], + ["https://[::FffF:FfFf]/",11,""], + ["https://[0:0:0:0:0:0:255.255.255.255]/",11,""], + ["https://[::0:255.255.255.255]/",11,""], + ["https://[0000:0000:0000:0000:0000:0000:ffff:0ffff]/",11,""], + ["https://[0:0:0:0:0:0:ffff:ffff:1]/",11,""], + ["https://[0:0:0:0:0:0:ffff]/",11,""], + ["https://[0:0:0:0:0:0:ffff:ffff:]/",11,""], + ["https://[:0:0:0:0:0:0:ffff:ffff]/",11,""], + ["https://[0:0:0:0:0:0:ffff:ffff::]/",11,""], + ["https://::ffff:ffff/",12,""], + ["https://[::ffff:ffff]:8443/a",11,""], + ["https://[::ffff:ffff]:0443/",37,""], + ["https://[::ffff:ffff%25eth0]/",5,""], + ["https://[::ffff:ffff%eth0]/",29,""], + ["https://[[::ffff:ffff]/",11,""], + ["https://[[::ffff:ffff]]/",12,""], + ["https://[::ffff:ffff]]/",12,""], + ["https://[::ffff:ffff/",15,""], + ["https://[::ffff:ffff]x/",12,""], + ["https://[::ffff:ffff]:/",12,""], + ["https://[::1:0:0]/",11,""], + ["https://[::fffe:ffff:ffff]/",11,""], + ["https://[0:0:0:0:0:ffff:0:0]/",11,""], + ["https://[0000:0000:0000:0000:0000:ffff:0000:0000]/",11,""], + ["https://[::FfFf:0.0.0.0]/",11,""], + ["https://[::ffff:0:0]/",11,""], + ["https://[0:0:0:0:0:ffff::]/",11,""], + ["https://[0:0:0:0:0:ffff:0.0.0.0]/",11,""], + ["https://[0000:0000:00000:0000:0000:ffff:0000:0000]/",11,""], + ["https://[0:0:0:0:0:ffff:0:0:1]/",11,""], + ["https://[0:0:0:0:0:ffff:0]/",11,""], + ["https://[0:0:0:0:0:ffff:0:0:]/",11,""], + ["https://[:0:0:0:0:0:ffff:0:0]/",11,""], + ["https://[0:0:0:0:0:ffff:0:0::]/",11,""], + ["https://::ffff:0.0.0.0/",12,""], + ["https://[::ffff:0.0.0.0]:8443/a",11,""], + ["https://[::ffff:0.0.0.0]:0443/",37,""], + ["https://[::ffff:0.0.0.0%25eth0]/",5,""], + ["https://[::ffff:0.0.0.0%eth0]/",29,""], + ["https://[[::ffff:0.0.0.0]/",11,""], + ["https://[[::ffff:0.0.0.0]]/",12,""], + ["https://[::ffff:0.0.0.0]]/",12,""], + ["https://[::ffff:0.0.0.0/",15,""], + ["https://[::ffff:0.0.0.0]x/",12,""], + ["https://[::ffff:0.0.0.0]:/",12,""], + ["https://[::ffff:0.0.0.1]/",11,""], + ["https://[::ffff:255.255.255.254]/",11,""], + ["https://[0:0:0:0:0:ffff:ffff:ffff]/",11,""], + ["https://[0000:0000:0000:0000:0000:ffff:ffff:ffff]/",11,""], + ["https://[::ffFf:255.255.255.255]/",11,""], + ["https://[::ffff:ffff:ffff]/",11,""], + ["https://[0:0:0:0:0:ffff:255.255.255.255]/",11,""], + ["https://[0000:0000:0000:00000:0000:ffff:ffff:ffff]/",11,""], + ["https://[0:0:0:0:0:ffff:ffff:ffff:1]/",11,""], + ["https://[0:0:0:0:0:ffff:ffff]/",11,""], + ["https://[0:0:0:0:0:ffff:ffff:ffff:]/",11,""], + ["https://[:0:0:0:0:0:ffff:ffff:ffff]/",11,""], + ["https://[0:0:0:0:0:ffff:ffff:ffff::]/",11,""], + ["https://::ffff:255.255.255.255/",12,""], + ["https://[::ffff:255.255.255.255]:8443/a",11,""], + ["https://[::ffff:255.255.255.255]:0443/",37,""], + ["https://[::ffff:255.255.255.255%25eth0]/",5,""], + ["https://[::ffff:255.255.255.255%eth0]/",29,""], + ["https://[[::ffff:255.255.255.255]/",11,""], + ["https://[[::ffff:255.255.255.255]]/",12,""], + ["https://[::ffff:255.255.255.255]]/",12,""], + ["https://[::ffff:255.255.255.255/",15,""], + ["https://[::ffff:255.255.255.255]x/",12,""], + ["https://[::ffff:255.255.255.255]:/",12,""], + ["https://[::1:0:0:0]/",11,""], + ["https://[64:ff9a:ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[64:ff9b::]/",11,""], + ["https://[64:ff9b:0:0:0:0:0:0]/",11,""], + ["https://[0064:ff9b:0000:0000:0000:0000:0000:0000]/",11,""], + ["https://[64:ff9b:0:0:0:0:0.0.0.0]/",11,""], + ["https://[0064:ff9b:0000:0000:00000:0000:0000:0000]/",11,""], + ["https://[64:ff9b:0:0:0:0:0:0:1]/",11,""], + ["https://[64:ff9b:0:0:0:0:0]/",11,""], + ["https://[64:ff9b:0::0::1]/",11,""], + ["https://[64:ff9b:0:0:0:0:0:0:]/",11,""], + ["https://[:64:ff9b:0:0:0:0:0:0]/",11,""], + ["https://[64:ff9b:0:0:0:0:0:0::]/",11,""], + ["https://64:ff9b::/",12,""], + ["https://[64:ff9b::]:8443/a",11,""], + ["https://[64:ff9b::]:0443/",37,""], + ["https://[64:ff9b::%25eth0]/",5,""], + ["https://[64:ff9b::%eth0]/",29,""], + ["https://[[64:ff9b::]/",11,""], + ["https://[[64:ff9b::]]/",12,""], + ["https://[64:ff9b::]]/",12,""], + ["https://[64:ff9b::/",15,""], + ["https://[64:ff9b::]x/",12,""], + ["https://[64:ff9b::]:/",12,""], + ["https://[64:ff9b::1]/",11,""], + ["https://[64:ff9b::ffff:fffe]/",11,""], + ["https://[64:ff9b::ffff:ffff]/",11,""], + ["https://[64:ff9b:0:0:0:0:ffff:ffff]/",11,""], + ["https://[0064:ff9b:0000:0000:0000:0000:ffff:ffff]/",11,""], + ["https://[64:Ff9b::ffff:fFff]/",11,""], + ["https://[64:ff9b:0:0:0:0:255.255.255.255]/",11,""], + ["https://[0064:ff9b:0000:0000:00000:0000:ffff:ffff]/",11,""], + ["https://[64:ff9b:0:0:0:0:ffff:ffff:1]/",11,""], + ["https://[64:ff9b:0:0:0:0:ffff]/",11,""], + ["https://[64:ff9b:0:0:0:0:ffff:ffff:]/",11,""], + ["https://[:64:ff9b:0:0:0:0:ffff:ffff]/",11,""], + ["https://[64:ff9b:0:0:0:0:ffff:ffff::]/",11,""], + ["https://64:ff9b::ffff:ffff/",12,""], + ["https://[64:ff9b::ffff:ffff]:8443/a",11,""], + ["https://[64:ff9b::ffff:ffff]:0443/",37,""], + ["https://[64:ff9b::ffff:ffff%25eth0]/",5,""], + ["https://[64:ff9b::ffff:ffff%eth0]/",29,""], + ["https://[[64:ff9b::ffff:ffff]/",11,""], + ["https://[[64:ff9b::ffff:ffff]]/",12,""], + ["https://[64:ff9b::ffff:ffff]]/",12,""], + ["https://[64:ff9b::ffff:ffff/",15,""], + ["https://[64:ff9b::ffff:ffff]x/",12,""], + ["https://[64:ff9b::ffff:ffff]:/",12,""], + ["https://[64:ff9b::1:0:0]/",11,""], + ["https://[64:ff9b:0:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[64:ff9b:1::]/",11,""], + ["https://[64:ff9b:1:0:0:0:0:0]/",11,""], + ["https://[0064:ff9b:0001:0000:0000:0000:0000:0000]/",11,""], + ["https://[64:fF9B:1::]/",11,""], + ["https://[64:ff9b:1:0:0:0:0.0.0.0]/",11,""], + ["https://[0064:ff9b:0001:00000:0000:0000:0000:0000]/",11,""], + ["https://[64:ff9b:1:0:0:0:0:0:1]/",11,""], + ["https://[64:ff9b:1:0:0:0:0]/",11,""], + ["https://[64:ff9b:1::0::1]/",11,""], + ["https://[64:ff9b:1:0:0:0:0:0:]/",11,""], + ["https://[:64:ff9b:1:0:0:0:0:0]/",11,""], + ["https://[64:ff9b:1:0:0:0:0:0::]/",11,""], + ["https://64:ff9b:1::/",12,""], + ["https://[64:ff9b:1::]:8443/a",11,""], + ["https://[64:ff9b:1::]:0443/",37,""], + ["https://[64:ff9b:1::%25eth0]/",5,""], + ["https://[64:ff9b:1::%eth0]/",29,""], + ["https://[[64:ff9b:1::]/",11,""], + ["https://[[64:ff9b:1::]]/",12,""], + ["https://[64:ff9b:1::]]/",12,""], + ["https://[64:ff9b:1::/",15,""], + ["https://[64:ff9b:1::]x/",12,""], + ["https://[64:ff9b:1::]:/",12,""], + ["https://[64:ff9b:1::1]/",11,""], + ["https://[64:ff9b:1:ffff:ffff:ffff:ffff:fffe]/",11,""], + ["https://[64:ff9b:1:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[0064:ff9b:0001:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[64:fF9B:1:ffFF:ffFF:fffF:ffFf:fFff]/",11,""], + ["https://[64:ff9b:1:ffff:ffff:ffff:255.255.255.255]/",11,""], + ["https://[0064:ff9b:0001:ffff:0ffff:ffff:ffff:ffff]/",11,""], + ["https://[64:ff9b:1:ffff:ffff:ffff:ffff:ffff:1]/",11,""], + ["https://[64:ff9b:1:ffff:ffff:ffff:ffff]/",11,""], + ["https://[64:ff9b:1::ffff::1]/",11,""], + ["https://[64:ff9b:1:ffff:ffff:ffff:ffff:ffff:]/",11,""], + ["https://[:64:ff9b:1:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[64:ff9b:1:ffff:ffff:ffff:ffff:ffff::]/",11,""], + ["https://64:ff9b:1:ffff:ffff:ffff:ffff:ffff/",12,""], + ["https://[64:ff9b:1:ffff:ffff:ffff:ffff:ffff]:8443/a",11,""], + ["https://[64:ff9b:1:ffff:ffff:ffff:ffff:ffff]:0443/",37,""], + ["https://[64:ff9b:1:ffff:ffff:ffff:ffff:ffff%25eth0]/",5,""], + ["https://[64:ff9b:1:ffff:ffff:ffff:ffff:ffff%eth0]/",29,""], + ["https://[[64:ff9b:1:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[[64:ff9b:1:ffff:ffff:ffff:ffff:ffff]]/",12,""], + ["https://[64:ff9b:1:ffff:ffff:ffff:ffff:ffff]]/",12,""], + ["https://[64:ff9b:1:ffff:ffff:ffff:ffff:ffff/",15,""], + ["https://[64:ff9b:1:ffff:ffff:ffff:ffff:ffff]x/",12,""], + ["https://[64:ff9b:1:ffff:ffff:ffff:ffff:ffff]:/",12,""], + ["https://[64:ff9b:2::]/",11,""], + ["https://[2001:0:ffff:ffff:ffff:ffff:ffff:fffe]/",11,""], + ["https://[2001:0:ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[2001:0000:ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[2001:0:Ffff:fFFf:fffF:fFFF:FFFf:ffff]/",11,""], + ["https://[2001::ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[2001:0:ffff:ffff:ffff:ffff:255.255.255.255]/",11,""], + ["https://[2001:0000:ffff:0ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[2001:0:ffff:ffff:ffff:ffff:ffff:ffff:1]/",11,""], + ["https://[2001:0:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[2001:0:ffff::ffff::1]/",11,""], + ["https://[2001:0:ffff:ffff:ffff:ffff:ffff:ffff:]/",11,""], + ["https://[:2001:0:ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[2001:0:ffff:ffff:ffff:ffff:ffff:ffff::]/",11,""], + ["https://2001:0:ffff:ffff:ffff:ffff:ffff:ffff/",12,""], + ["https://[2001:0:ffff:ffff:ffff:ffff:ffff:ffff]:8443/a",11,""], + ["https://[2001:0:ffff:ffff:ffff:ffff:ffff:ffff]:0443/",37,""], + ["https://[2001:0:ffff:ffff:ffff:ffff:ffff:ffff%25eth0]/",5,""], + ["https://[2001:0:ffff:ffff:ffff:ffff:ffff:ffff%eth0]/",29,""], + ["https://[[2001:0:ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[[2001:0:ffff:ffff:ffff:ffff:ffff:ffff]]/",12,""], + ["https://[2001:0:ffff:ffff:ffff:ffff:ffff:ffff]]/",12,""], + ["https://[2001:0:ffff:ffff:ffff:ffff:ffff:ffff/",15,""], + ["https://[2001:0:ffff:ffff:ffff:ffff:ffff:ffff]x/",12,""], + ["https://[2001:0:ffff:ffff:ffff:ffff:ffff:ffff]:/",12,""], + ["https://[2001:1::]/",11,""], + ["https://[fbff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[fc00::]/",11,""], + ["https://[fc00:0:0:0:0:0:0:0]/",11,""], + ["https://[fc00:0000:0000:0000:0000:0000:0000:0000]/",11,""], + ["https://[Fc00::]/",11,""], + ["https://[fc00:0:0:0:0:0:0.0.0.0]/",11,""], + ["https://[fc00:0000:0000:0000:0000:0000:00000:0000]/",11,""], + ["https://[fc00:0:0:0:0:0:0:0:1]/",11,""], + ["https://[fc00:0:0:0:0:0:0]/",11,""], + ["https://[fc00:0:0::0::1]/",11,""], + ["https://[fc00:0:0:0:0:0:0:0:]/",11,""], + ["https://[:fc00:0:0:0:0:0:0:0]/",11,""], + ["https://[fc00:0:0:0:0:0:0:0::]/",11,""], + ["https://fc00::/",12,""], + ["https://[fc00::]:8443/a",11,""], + ["https://[fc00::]:0443/",37,""], + ["https://[fc00::%25eth0]/",5,""], + ["https://[fc00::%eth0]/",29,""], + ["https://[[fc00::]/",11,""], + ["https://[[fc00::]]/",12,""], + ["https://[fc00::]]/",12,""], + ["https://[fc00::/",15,""], + ["https://[fc00::]x/",12,""], + ["https://[fc00::]:/",12,""], + ["https://[fc00::1]/",11,""], + ["https://[fdff:ffff:ffff:ffff:ffff:ffff:ffff:fffe]/",11,""], + ["https://[fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[fdfF:ffff:FFfF:fFfF:FfFf:ffFf:FffF:fFfF]/",11,""], + ["https://[fdff:ffff:ffff:ffff:ffff:ffff:255.255.255.255]/",11,""], + ["https://[fdff:ffff:0ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff:1]/",11,""], + ["https://[fdff:ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[fdff:ffff:ffff::ffff::1]/",11,""], + ["https://[fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff:]/",11,""], + ["https://[:fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff::]/",11,""], + ["https://fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/",12,""], + ["https://[fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]:8443/a",11,""], + ["https://[fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]:0443/",37,""], + ["https://[fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff%25eth0]/",5,""], + ["https://[fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff%eth0]/",29,""], + ["https://[[fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[[fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]]/",12,""], + ["https://[fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]]/",12,""], + ["https://[fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/",15,""], + ["https://[fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]x/",12,""], + ["https://[fdff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]:/",12,""], + ["https://[fe00::]/",11,""], + ["https://[fe7f:ffff:ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[fe80::]/",11,""], + ["https://[fe80:0:0:0:0:0:0:0]/",11,""], + ["https://[fe80:0000:0000:0000:0000:0000:0000:0000]/",11,""], + ["https://[fe80:0:0:0:0:0:0.0.0.0]/",11,""], + ["https://[fe80:0000:0000:0000:0000:0000:00000:0000]/",11,""], + ["https://[fe80:0:0:0:0:0:0:0:1]/",11,""], + ["https://[fe80:0:0:0:0:0:0]/",11,""], + ["https://[fe80:0:0::0::1]/",11,""], + ["https://[fe80:0:0:0:0:0:0:0:]/",11,""], + ["https://[:fe80:0:0:0:0:0:0:0]/",11,""], + ["https://[fe80:0:0:0:0:0:0:0::]/",11,""], + ["https://fe80::/",12,""], + ["https://[fe80::]:8443/a",11,""], + ["https://[fe80::]:0443/",37,""], + ["https://[fe80::%25eth0]/",5,""], + ["https://[fe80::%eth0]/",29,""], + ["https://[[fe80::]/",11,""], + ["https://[[fe80::]]/",12,""], + ["https://[fe80::]]/",12,""], + ["https://[fe80::/",15,""], + ["https://[fe80::]x/",12,""], + ["https://[fe80::]:/",12,""], + ["https://[fe80::1]/",11,""], + ["https://[febf:ffff:ffff:ffff:ffff:ffff:ffff:fffe]/",11,""], + ["https://[febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[fEBF:ffff:fffF:ffff:Ffff:ffff:FfFf:fffF]/",11,""], + ["https://[febf:ffff:ffff:ffff:ffff:ffff:255.255.255.255]/",11,""], + ["https://[febf:ffff:ffff:ffff:ffff:ffff:0ffff:ffff]/",11,""], + ["https://[febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff:1]/",11,""], + ["https://[febf:ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[febf:ffff:ffff::ffff::1]/",11,""], + ["https://[febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff:]/",11,""], + ["https://[:febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff::]/",11,""], + ["https://febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff/",12,""], + ["https://[febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff]:8443/a",11,""], + ["https://[febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff]:0443/",37,""], + ["https://[febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff%25eth0]/",5,""], + ["https://[febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff%eth0]/",29,""], + ["https://[[febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[[febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff]]/",12,""], + ["https://[febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff]]/",12,""], + ["https://[febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff/",15,""], + ["https://[febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff]x/",12,""], + ["https://[febf:ffff:ffff:ffff:ffff:ffff:ffff:ffff]:/",12,""], + ["https://[fec0::]/",11,""], + ["https://[feff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[ff00::]/",11,""], + ["https://[ff00:0:0:0:0:0:0:0]/",11,""], + ["https://[ff00:0000:0000:0000:0000:0000:0000:0000]/",11,""], + ["https://[ff00:0:0:0:0:0:0.0.0.0]/",11,""], + ["https://[ff00:0000:0000:0000:0000:0000:0000:00000]/",11,""], + ["https://[ff00:0:0:0:0:0:0:0:1]/",11,""], + ["https://[ff00:0:0:0:0:0:0]/",11,""], + ["https://[ff00:0:0::0::1]/",11,""], + ["https://[ff00:0:0:0:0:0:0:0:]/",11,""], + ["https://[:ff00:0:0:0:0:0:0:0]/",11,""], + ["https://[ff00:0:0:0:0:0:0:0::]/",11,""], + ["https://ff00::/",12,""], + ["https://[ff00::]:8443/a",11,""], + ["https://[ff00::]:0443/",37,""], + ["https://[ff00::%25eth0]/",5,""], + ["https://[ff00::%eth0]/",29,""], + ["https://[[ff00::]/",11,""], + ["https://[[ff00::]]/",12,""], + ["https://[ff00::]]/",12,""], + ["https://[ff00::/",15,""], + ["https://[ff00::]x/",12,""], + ["https://[ff00::]:/",12,""], + ["https://[ff00::1]/",11,""], + ["https://[ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffe]/",11,""], + ["https://[ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[fFFf:fFFF:fFFf:fFff:Ffff:Ffff:Ffff:fFFF]/",11,""], + ["https://[ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255]/",11,""], + ["https://[ffff:ffff:ffff:ffff:ffff:ffff:ffff:0ffff]/",11,""], + ["https://[ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff:1]/",11,""], + ["https://[ffff:ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[ffff:ffff:ffff::ffff::1]/",11,""], + ["https://[ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff:]/",11,""], + ["https://[:ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff::]/",11,""], + ["https://ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/",12,""], + ["https://[ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]:8443/a",11,""], + ["https://[ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]:0443/",37,""], + ["https://[ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff%25eth0]/",5,""], + ["https://[ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff%eth0]/",29,""], + ["https://[[ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[[ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]]/",12,""], + ["https://[ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]]/",12,""], + ["https://[ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/",15,""], + ["https://[ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]x/",12,""], + ["https://[ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]:/",12,""], + ["https://[ff:ffff:ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[100::]/",11,""], + ["https://[100:0:0:0:0:0:0:0]/",11,""], + ["https://[0100:0000:0000:0000:0000:0000:0000:0000]/",11,""], + ["https://[100:0:0:0:0:0:0.0.0.0]/",11,""], + ["https://[0100:0000:0000:00000:0000:0000:0000:0000]/",11,""], + ["https://[100:0:0:0:0:0:0:0:1]/",11,""], + ["https://[100:0:0:0:0:0:0]/",11,""], + ["https://[100:0:0::0::1]/",11,""], + ["https://[100:0:0:0:0:0:0:0:]/",11,""], + ["https://[:100:0:0:0:0:0:0:0]/",11,""], + ["https://[100:0:0:0:0:0:0:0::]/",11,""], + ["https://100::/",12,""], + ["https://[100::]:8443/a",11,""], + ["https://[100::]:0443/",37,""], + ["https://[100::%25eth0]/",5,""], + ["https://[100::%eth0]/",29,""], + ["https://[[100::]/",11,""], + ["https://[[100::]]/",12,""], + ["https://[100::]]/",12,""], + ["https://[100::/",15,""], + ["https://[100::]x/",12,""], + ["https://[100::]:/",12,""], + ["https://[100::1]/",11,""], + ["https://[100::ffff:ffff:ffff:fffe]/",11,""], + ["https://[100::ffff:ffff:ffff:ffff]/",11,""], + ["https://[100:0:0:0:ffff:ffff:ffff:ffff]/",11,""], + ["https://[0100:0000:0000:0000:ffff:ffff:ffff:ffff]/",11,""], + ["https://[100::fffF:FFfF:ffFF:ffff]/",11,""], + ["https://[100:0:0:0:ffff:ffff:255.255.255.255]/",11,""], + ["https://[0100:0000:0000:0000:ffff:ffff:ffff:0ffff]/",11,""], + ["https://[100:0:0:0:ffff:ffff:ffff:ffff:1]/",11,""], + ["https://[100:0:0:0:ffff:ffff:ffff]/",11,""], + ["https://[100:0:0::ffff::1]/",11,""], + ["https://[100:0:0:0:ffff:ffff:ffff:ffff:]/",11,""], + ["https://[:100:0:0:0:ffff:ffff:ffff:ffff]/",11,""], + ["https://[100:0:0:0:ffff:ffff:ffff:ffff::]/",11,""], + ["https://100::ffff:ffff:ffff:ffff/",12,""], + ["https://[100::ffff:ffff:ffff:ffff]:8443/a",11,""], + ["https://[100::ffff:ffff:ffff:ffff]:0443/",37,""], + ["https://[100::ffff:ffff:ffff:ffff%25eth0]/",5,""], + ["https://[100::ffff:ffff:ffff:ffff%eth0]/",29,""], + ["https://[[100::ffff:ffff:ffff:ffff]/",11,""], + ["https://[[100::ffff:ffff:ffff:ffff]]/",12,""], + ["https://[100::ffff:ffff:ffff:ffff]]/",12,""], + ["https://[100::ffff:ffff:ffff:ffff/",15,""], + ["https://[100::ffff:ffff:ffff:ffff]x/",12,""], + ["https://[100::ffff:ffff:ffff:ffff]:/",12,""], + ["https://[100:0:0:1::]/",11,""], + ["https://[0:0:0:0:0:0:0:1]/",11,""], + ["https://[0000:0000:0000:0000:0000:0000:0000:0001]/",11,""], + ["https://[0:0:0:0:0:0:0.0.0.1]/",11,""], + ["https://[::0:0.0.0.1]/",11,""], + ["https://[0000:0000:0000:0000:0000:0000:0000:00001]/",11,""], + ["https://[0:0:0:0:0:0:0:1:1]/",11,""], + ["https://[0:0:0:0:0:0:0:1:]/",11,""], + ["https://[:0:0:0:0:0:0:0:1]/",11,""], + ["https://[0:0:0:0:0:0:0:1::]/",11,""], + ["https://::1/",12,""], + ["https://[::1]:8443/a",11,""], + ["https://[::1]:0443/",37,""], + ["https://[::1%25eth0]/",5,""], + ["https://[::1%eth0]/",29,""], + ["https://[[::1]/",11,""], + ["https://[[::1]]/",12,""], + ["https://[::1]]/",12,""], + ["https://[::1/",15,""], + ["https://[::1]x/",12,""], + ["https://[::1]:/",12,""], + ["https://[::2]/",11,""], + ["https://[2001:1f:ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[2001:20::]/",11,""], + ["https://[2001:20:0:0:0:0:0:0]/",11,""], + ["https://[2001:0020:0000:0000:0000:0000:0000:0000]/",11,""], + ["https://[2001:20:0:0:0:0:0.0.0.0]/",11,""], + ["https://[2001:0020:0000:0000:00000:0000:0000:0000]/",11,""], + ["https://[2001:20:0:0:0:0:0:0:1]/",11,""], + ["https://[2001:20:0:0:0:0:0]/",11,""], + ["https://[2001:20:0::0::1]/",11,""], + ["https://[2001:20:0:0:0:0:0:0:]/",11,""], + ["https://[:2001:20:0:0:0:0:0:0]/",11,""], + ["https://[2001:20:0:0:0:0:0:0::]/",11,""], + ["https://2001:20::/",12,""], + ["https://[2001:20::]:8443/a",11,""], + ["https://[2001:20::]:0443/",37,""], + ["https://[2001:20::%25eth0]/",5,""], + ["https://[2001:20::%eth0]/",29,""], + ["https://[[2001:20::]/",11,""], + ["https://[[2001:20::]]/",12,""], + ["https://[2001:20::]]/",12,""], + ["https://[2001:20::/",15,""], + ["https://[2001:20::]x/",12,""], + ["https://[2001:20::]:/",12,""], + ["https://[2001:20::1]/",11,""], + ["https://[2001:2f:ffff:ffff:ffff:ffff:ffff:fffe]/",11,""], + ["https://[2001:2f:ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[2001:002f:ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[2001:2f:FFFf:fFff:FFFf:FFFF:fFfF:ffff]/",11,""], + ["https://[2001:2f:ffff:ffff:ffff:ffff:255.255.255.255]/",11,""], + ["https://[2001:002f:ffff:0ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[2001:2f:ffff:ffff:ffff:ffff:ffff:ffff:1]/",11,""], + ["https://[2001:2f:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[2001:2f:ffff::ffff::1]/",11,""], + ["https://[2001:2f:ffff:ffff:ffff:ffff:ffff:ffff:]/",11,""], + ["https://[:2001:2f:ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[2001:2f:ffff:ffff:ffff:ffff:ffff:ffff::]/",11,""], + ["https://2001:2f:ffff:ffff:ffff:ffff:ffff:ffff/",12,""], + ["https://[2001:2f:ffff:ffff:ffff:ffff:ffff:ffff]:8443/a",11,""], + ["https://[2001:2f:ffff:ffff:ffff:ffff:ffff:ffff]:0443/",37,""], + ["https://[2001:2f:ffff:ffff:ffff:ffff:ffff:ffff%25eth0]/",5,""], + ["https://[2001:2f:ffff:ffff:ffff:ffff:ffff:ffff%eth0]/",29,""], + ["https://[[2001:2f:ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[[2001:2f:ffff:ffff:ffff:ffff:ffff:ffff]]/",12,""], + ["https://[2001:2f:ffff:ffff:ffff:ffff:ffff:ffff]]/",12,""], + ["https://[2001:2f:ffff:ffff:ffff:ffff:ffff:ffff/",15,""], + ["https://[2001:2f:ffff:ffff:ffff:ffff:ffff:ffff]x/",12,""], + ["https://[2001:2f:ffff:ffff:ffff:ffff:ffff:ffff]:/",12,""], + ["https://[2001:30::]/",11,""], + ["https://[2001:1:ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[2001:2::]/",11,""], + ["https://[2001:2:0:0:0:0:0:0]/",11,""], + ["https://[2001:0002:0000:0000:0000:0000:0000:0000]/",11,""], + ["https://[2001:2:0:0:0:0:0.0.0.0]/",11,""], + ["https://[2001:0002:00000:0000:0000:0000:0000:0000]/",11,""], + ["https://[2001:2:0:0:0:0:0:0:1]/",11,""], + ["https://[2001:2:0:0:0:0:0]/",11,""], + ["https://[2001:2:0::0::1]/",11,""], + ["https://[2001:2:0:0:0:0:0:0:]/",11,""], + ["https://[:2001:2:0:0:0:0:0:0]/",11,""], + ["https://[2001:2:0:0:0:0:0:0::]/",11,""], + ["https://2001:2::/",12,""], + ["https://[2001:2::]:8443/a",11,""], + ["https://[2001:2::]:0443/",37,""], + ["https://[2001:2::%25eth0]/",5,""], + ["https://[2001:2::%eth0]/",29,""], + ["https://[[2001:2::]/",11,""], + ["https://[[2001:2::]]/",12,""], + ["https://[2001:2::]]/",12,""], + ["https://[2001:2::/",15,""], + ["https://[2001:2::]x/",12,""], + ["https://[2001:2::]:/",12,""], + ["https://[2001:2::1]/",11,""], + ["https://[2001:2:0:ffff:ffff:ffff:ffff:fffe]/",11,""], + ["https://[2001:2:0:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[2001:0002:0000:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[2001:2:0:Ffff:Ffff:ffFf:fFFF:ffFF]/",11,""], + ["https://[2001:2::ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[2001:2:0:ffff:ffff:ffff:255.255.255.255]/",11,""], + ["https://[2001:0002:0000:0ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[2001:2:0:ffff:ffff:ffff:ffff:ffff:1]/",11,""], + ["https://[2001:2:0:ffff:ffff:ffff:ffff]/",11,""], + ["https://[2001:2:0::ffff::1]/",11,""], + ["https://[2001:2:0:ffff:ffff:ffff:ffff:ffff:]/",11,""], + ["https://[:2001:2:0:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[2001:2:0:ffff:ffff:ffff:ffff:ffff::]/",11,""], + ["https://2001:2:0:ffff:ffff:ffff:ffff:ffff/",12,""], + ["https://[2001:2:0:ffff:ffff:ffff:ffff:ffff]:8443/a",11,""], + ["https://[2001:2:0:ffff:ffff:ffff:ffff:ffff]:0443/",37,""], + ["https://[2001:2:0:ffff:ffff:ffff:ffff:ffff%25eth0]/",5,""], + ["https://[2001:2:0:ffff:ffff:ffff:ffff:ffff%eth0]/",29,""], + ["https://[[2001:2:0:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[[2001:2:0:ffff:ffff:ffff:ffff:ffff]]/",12,""], + ["https://[2001:2:0:ffff:ffff:ffff:ffff:ffff]]/",12,""], + ["https://[2001:2:0:ffff:ffff:ffff:ffff:ffff/",15,""], + ["https://[2001:2:0:ffff:ffff:ffff:ffff:ffff]x/",12,""], + ["https://[2001:2:0:ffff:ffff:ffff:ffff:ffff]:/",12,""], + ["https://[2001:2:1::]/",11,""], + ["https://[2620:4f:7fff:ffff:ffff:ffff:ffff:ffff]/",0,"2620:4f:7fff:ffff:ffff:ffff:ffff:ffff"], + ["https://[2620:4f:8000::]/",0,"2620:4f:8000::"], + ["https://[2620:4f:8000:0:0:0:0:0]/",0,"2620:4f:8000:0:0:0:0:0"], + ["https://[2620:004f:8000:0000:0000:0000:0000:0000]/",0,"2620:004f:8000:0000:0000:0000:0000:0000"], + ["https://[2620:4F:8000::]/",0,"2620:4F:8000::"], + ["https://[2620:4f:8000:0:0:0:0.0.0.0]/",0,"2620:4f:8000:0:0:0:0.0.0.0"], + ["https://[2620:004f:8000:0000:0000:0000:00000:0000]/",11,""], + ["https://[2620:4f:8000:0:0:0:0:0:1]/",11,""], + ["https://[2620:4f:8000:0:0:0:0]/",11,""], + ["https://[2620:4f:8000::0::1]/",11,""], + ["https://[2620:4f:8000:0:0:0:0:0:]/",11,""], + ["https://[:2620:4f:8000:0:0:0:0:0]/",11,""], + ["https://[2620:4f:8000:0:0:0:0:0::]/",11,""], + ["https://2620:4f:8000::/",12,""], + ["https://[2620:4f:8000::]:8443/a",0,"2620:4f:8000::"], + ["https://[2620:4f:8000::]:0443/",37,""], + ["https://[2620:4f:8000::%25eth0]/",5,""], + ["https://[2620:4f:8000::%eth0]/",29,""], + ["https://[[2620:4f:8000::]/",0,"2620:4f:8000::"], + ["https://[[2620:4f:8000::]]/",12,""], + ["https://[2620:4f:8000::]]/",12,""], + ["https://[2620:4f:8000::/",15,""], + ["https://[2620:4f:8000::]x/",12,""], + ["https://[2620:4f:8000::]:/",12,""], + ["https://[2620:4f:8000::1]/",0,"2620:4f:8000::1"], + ["https://[2620:4f:8000:ffff:ffff:ffff:ffff:fffe]/",0,"2620:4f:8000:ffff:ffff:ffff:ffff:fffe"], + ["https://[2620:4f:8000:ffff:ffff:ffff:ffff:ffff]/",0,"2620:4f:8000:ffff:ffff:ffff:ffff:ffff"], + ["https://[2620:004f:8000:ffff:ffff:ffff:ffff:ffff]/",0,"2620:004f:8000:ffff:ffff:ffff:ffff:ffff"], + ["https://[2620:4F:8000:fFfF:fFff:fFfF:FFFf:ffff]/",0,"2620:4F:8000:fFfF:fFff:fFfF:FFFf:ffff"], + ["https://[2620:4f:8000:ffff:ffff:ffff:255.255.255.255]/",0,"2620:4f:8000:ffff:ffff:ffff:255.255.255.255"], + ["https://[2620:004f:8000:ffff:ffff:ffff:0ffff:ffff]/",11,""], + ["https://[2620:4f:8000:ffff:ffff:ffff:ffff:ffff:1]/",11,""], + ["https://[2620:4f:8000:ffff:ffff:ffff:ffff]/",11,""], + ["https://[2620:4f:8000::ffff::1]/",11,""], + ["https://[2620:4f:8000:ffff:ffff:ffff:ffff:ffff:]/",11,""], + ["https://[:2620:4f:8000:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[2620:4f:8000:ffff:ffff:ffff:ffff:ffff::]/",11,""], + ["https://2620:4f:8000:ffff:ffff:ffff:ffff:ffff/",12,""], + ["https://[2620:4f:8000:ffff:ffff:ffff:ffff:ffff]:8443/a",0,"2620:4f:8000:ffff:ffff:ffff:ffff:ffff"], + ["https://[2620:4f:8000:ffff:ffff:ffff:ffff:ffff]:0443/",37,""], + ["https://[2620:4f:8000:ffff:ffff:ffff:ffff:ffff%25eth0]/",5,""], + ["https://[2620:4f:8000:ffff:ffff:ffff:ffff:ffff%eth0]/",29,""], + ["https://[[2620:4f:8000:ffff:ffff:ffff:ffff:ffff]/",0,"2620:4f:8000:ffff:ffff:ffff:ffff:ffff"], + ["https://[[2620:4f:8000:ffff:ffff:ffff:ffff:ffff]]/",12,""], + ["https://[2620:4f:8000:ffff:ffff:ffff:ffff:ffff]]/",12,""], + ["https://[2620:4f:8000:ffff:ffff:ffff:ffff:ffff/",15,""], + ["https://[2620:4f:8000:ffff:ffff:ffff:ffff:ffff]x/",12,""], + ["https://[2620:4f:8000:ffff:ffff:ffff:ffff:ffff]:/",12,""], + ["https://[2620:4f:8001::]/",0,"2620:4f:8001::"], + ["https://[2001:485f:ffff:ffff:ffff:ffff:ffff:ffff]/",0,"2001:485f:ffff:ffff:ffff:ffff:ffff:ffff"], + ["https://[2001:4860::]/",0,"2001:4860::"], + ["https://[2001:4860:0:0:0:0:0:0]/",0,"2001:4860:0:0:0:0:0:0"], + ["https://[2001:4860:0000:0000:0000:0000:0000:0000]/",0,"2001:4860:0000:0000:0000:0000:0000:0000"], + ["https://[2001:4860:0:0:0:0:0.0.0.0]/",0,"2001:4860:0:0:0:0:0.0.0.0"], + ["https://[2001:4860:00000:0000:0000:0000:0000:0000]/",11,""], + ["https://[2001:4860:0:0:0:0:0:0:1]/",11,""], + ["https://[2001:4860:0:0:0:0:0]/",11,""], + ["https://[2001:4860:0::0::1]/",11,""], + ["https://[2001:4860:0:0:0:0:0:0:]/",11,""], + ["https://[:2001:4860:0:0:0:0:0:0]/",11,""], + ["https://[2001:4860:0:0:0:0:0:0::]/",11,""], + ["https://2001:4860::/",12,""], + ["https://[2001:4860::]:8443/a",0,"2001:4860::"], + ["https://[2001:4860::]:0443/",37,""], + ["https://[2001:4860::%25eth0]/",5,""], + ["https://[2001:4860::%eth0]/",29,""], + ["https://[[2001:4860::]/",0,"2001:4860::"], + ["https://[[2001:4860::]]/",12,""], + ["https://[2001:4860::]]/",12,""], + ["https://[2001:4860::/",15,""], + ["https://[2001:4860::]x/",12,""], + ["https://[2001:4860::]:/",12,""], + ["https://[2001:4860::1]/",0,"2001:4860::1"], + ["https://[2001:4860:ffff:ffff:ffff:ffff:ffff:fffe]/",0,"2001:4860:ffff:ffff:ffff:ffff:ffff:fffe"], + ["https://[2001:4860:ffff:ffff:ffff:ffff:ffff:ffff]/",0,"2001:4860:ffff:ffff:ffff:ffff:ffff:ffff"], + ["https://[2001:4860:Ffff:fFFf:Ffff:fFff:ffFF:fFff]/",0,"2001:4860:Ffff:fFFf:Ffff:fFff:ffFF:fFff"], + ["https://[2001:4860:ffff:ffff:ffff:ffff:255.255.255.255]/",0,"2001:4860:ffff:ffff:ffff:ffff:255.255.255.255"], + ["https://[2001:04860:ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[2001:4860:ffff:ffff:ffff:ffff:ffff:ffff:1]/",11,""], + ["https://[2001:4860:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[2001:4860:ffff::ffff::1]/",11,""], + ["https://[2001:4860:ffff:ffff:ffff:ffff:ffff:ffff:]/",11,""], + ["https://[:2001:4860:ffff:ffff:ffff:ffff:ffff:ffff]/",11,""], + ["https://[2001:4860:ffff:ffff:ffff:ffff:ffff:ffff::]/",11,""], + ["https://2001:4860:ffff:ffff:ffff:ffff:ffff:ffff/",12,""], + ["https://[2001:4860:ffff:ffff:ffff:ffff:ffff:ffff]:8443/a",0,"2001:4860:ffff:ffff:ffff:ffff:ffff:ffff"], + ["https://[2001:4860:ffff:ffff:ffff:ffff:ffff:ffff]:0443/",37,""], + ["https://[2001:4860:ffff:ffff:ffff:ffff:ffff:ffff%25eth0]/",5,""], + ["https://[2001:4860:ffff:ffff:ffff:ffff:ffff:ffff%eth0]/",29,""], + ["https://[[2001:4860:ffff:ffff:ffff:ffff:ffff:ffff]/",0,"2001:4860:ffff:ffff:ffff:ffff:ffff:ffff"], + ["https://[[2001:4860:ffff:ffff:ffff:ffff:ffff:ffff]]/",12,""], + ["https://[2001:4860:ffff:ffff:ffff:ffff:ffff:ffff]]/",12,""], + ["https://[2001:4860:ffff:ffff:ffff:ffff:ffff:ffff/",15,""], + ["https://[2001:4860:ffff:ffff:ffff:ffff:ffff:ffff]x/",12,""], + ["https://[2001:4860:ffff:ffff:ffff:ffff:ffff:ffff]:/",12,""], + ["https://[2001:4861::]/",0,"2001:4861::"], + ["https://a/",31,""], + ["https://a:8443/x",31,""], + ["https://a.b/",0,"a.b"], + ["https://a.b:8443/x",0,"a.b"], + ["https://ejemplo.org/",0,"ejemplo.org"], + ["https://ejemplo.org:8443/x",0,"ejemplo.org"], + ["https://EJEMPLO.ORG/",0,"EJEMPLO.ORG"], + ["https://EJEMPLO.ORG:8443/x",0,"EJEMPLO.ORG"], + ["https://xn--pple-43d.com/",0,"xn--pple-43d.com"], + ["https://xn--pple-43d.com:8443/x",0,"xn--pple-43d.com"], + ["https://xn--a.b/",0,"xn--a.b"], + ["https://xn--a.b:8443/x",0,"xn--a.b"], + ["https://a-b.c-d.e/",0,"a-b.c-d.e"], + ["https://a-b.c-d.e:8443/x",0,"a-b.c-d.e"], + ["https://a--b.org/",0,"a--b.org"], + ["https://a--b.org:8443/x",0,"a--b.org"], + ["https://1.org/",0,"1.org"], + ["https://1.org:8443/x",0,"1.org"], + ["https://org.1/",10,""], + ["https://org.1:8443/x",10,""], + ["https://123.456/",10,""], + ["https://123.456:8443/x",10,""], + ["https://0x.org/",0,"0x.org"], + ["https://0x.org:8443/x",0,"0x.org"], + ["https://org.0x/",10,""], + ["https://org.0x:8443/x",10,""], + ["https://org.0X/",10,""], + ["https://org.0X:8443/x",10,""], + ["https://org.0x1/",10,""], + ["https://org.0x1:8443/x",10,""], + ["https://a.0b1/",0,"a.0b1"], + ["https://a.0b1:8443/x",0,"a.0b1"], + ["https://a.1b/",0,"a.1b"], + ["https://a.1b:8443/x",0,"a.1b"], + ["https://a.b1/",0,"a.b1"], + ["https://a.b1:8443/x",0,"a.b1"], + ["https://a.b-1/",0,"a.b-1"], + ["https://a.b-1:8443/x",0,"a.b-1"], + ["https://a.0/",10,""], + ["https://a.0:8443/x",10,""], + ["https://a.00/",10,""], + ["https://a.00:8443/x",10,""], + ["https://a.08/",10,""], + ["https://a.08:8443/x",10,""], + ["https://a.1e1/",0,"a.1e1"], + ["https://a.1e1:8443/x",0,"a.1e1"], + ["https://aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.org/",0,"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.org"], + ["https://aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.org:8443/x",0,"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.org"], + ["https://aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.org/",33,""], + ["https://aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.org:8443/x",33,""], + ["https://org.bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/",0,"org.bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"], + ["https://org.bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb:8443/x",0,"org.bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"], + ["https://org.bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/",33,""], + ["https://org.bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb:8443/x",33,""], + ["https://a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.org/",0,"a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.org"], + ["https://a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.org:8443/x",0,"a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.a.org"], + ["https://localhost/",31,""], + ["https://localhost:8443/x",31,""], + ["https://LOCALHOST/",31,""], + ["https://LOCALHOST:8443/x",31,""], + ["https://LocalHost/",31,""], + ["https://LocalHost:8443/x",31,""], + ["https://a.localhost/",31,""], + ["https://a.localhost:8443/x",31,""], + ["https://localhost.org/",0,"localhost.org"], + ["https://localhost.org:8443/x",0,"localhost.org"], + ["https://a.local/",31,""], + ["https://a.local:8443/x",31,""], + ["https://A.LOCAL/",31,""], + ["https://A.LOCAL:8443/x",31,""], + ["https://local/",31,""], + ["https://local:8443/x",31,""], + ["https://home.arpa/",31,""], + ["https://home.arpa:8443/x",31,""], + ["https://a.home.arpa/",31,""], + ["https://a.home.arpa:8443/x",31,""], + ["https://a.HOME.ARPA/",31,""], + ["https://a.HOME.ARPA:8443/x",31,""], + ["https://home.arpa.org/",0,"home.arpa.org"], + ["https://home.arpa.org:8443/x",0,"home.arpa.org"], + ["https://arpa/",31,""], + ["https://arpa:8443/x",31,""], + ["https://a.arpa/",0,"a.arpa"], + ["https://a.arpa:8443/x",0,"a.arpa"], + ["https://x.internal/",31,""], + ["https://x.internal:8443/x",31,""], + ["https://x.invalid/",31,""], + ["https://x.invalid:8443/x",31,""], + ["https://x.test/",31,""], + ["https://x.test:8443/x",31,""], + ["https://x.example/",31,""], + ["https://x.example:8443/x",31,""], + ["https://x.onion/",31,""], + ["https://x.onion:8443/x",31,""], + ["https://x.Onion/",31,""], + ["https://x.Onion:8443/x",31,""], + ["https://onion.x/",0,"onion.x"], + ["https://onion.x:8443/x",0,"onion.x"], + ["https://example/",31,""], + ["https://example:8443/x",31,""], + ["https://example.com/",0,"example.com"], + ["https://example.com:8443/x",0,"example.com"], + ["https://test.org/",0,"test.org"], + ["https://test.org:8443/x",0,"test.org"], + ["https://a.localhost./",33,""], + ["https://a.localhost.:8443/x",33,""], + ["https://a.local-/",33,""], + ["https://a.local-:8443/x",33,""], + ["https://a._b.org/",32,""], + ["https://a._b.org:8443/x",32,""], + ["https://a.b_.org/",32,""], + ["https://a.b_.org:8443/x",32,""], + ["https://ejemplo.org/x",0,"ejemplo.org"], + ["https://[2606:4700::1111]/x",0,"2606:4700::1111"], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi/x",0,"bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi"], + ["https://ejemplo.org:/x",12,""], + ["https://[2606:4700::1111]:/x",12,""], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi:/x",7,""], + ["https://ejemplo.org:0/x",13,""], + ["https://[2606:4700::1111]:0/x",13,""], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi:0/x",7,""], + ["https://ejemplo.org:00/x",13,""], + ["https://[2606:4700::1111]:00/x",13,""], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi:00/x",7,""], + ["https://ejemplo.org:1/x",0,"ejemplo.org"], + ["https://[2606:4700::1111]:1/x",0,"2606:4700::1111"], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi:1/x",7,""], + ["https://ejemplo.org:01/x",37,""], + ["https://[2606:4700::1111]:01/x",37,""], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi:01/x",7,""], + ["https://ejemplo.org:9/x",0,"ejemplo.org"], + ["https://[2606:4700::1111]:9/x",0,"2606:4700::1111"], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi:9/x",7,""], + ["https://ejemplo.org:09/x",37,""], + ["https://[2606:4700::1111]:09/x",37,""], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi:09/x",7,""], + ["https://ejemplo.org:443/x",0,"ejemplo.org"], + ["https://[2606:4700::1111]:443/x",0,"2606:4700::1111"], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi:443/x",7,""], + ["https://ejemplo.org:0443/x",37,""], + ["https://[2606:4700::1111]:0443/x",37,""], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi:0443/x",7,""], + ["https://ejemplo.org:00443/x",37,""], + ["https://[2606:4700::1111]:00443/x",37,""], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi:00443/x",7,""], + ["https://ejemplo.org:000443/x",12,""], + ["https://[2606:4700::1111]:000443/x",12,""], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi:000443/x",7,""], + ["https://ejemplo.org:65535/x",0,"ejemplo.org"], + ["https://[2606:4700::1111]:65535/x",0,"2606:4700::1111"], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi:65535/x",7,""], + ["https://ejemplo.org:065535/x",12,""], + ["https://[2606:4700::1111]:065535/x",12,""], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi:065535/x",7,""], + ["https://ejemplo.org:65536/x",13,""], + ["https://[2606:4700::1111]:65536/x",13,""], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi:65536/x",7,""], + ["https://ejemplo.org:99999/x",13,""], + ["https://[2606:4700::1111]:99999/x",13,""], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi:99999/x",7,""], + ["https://ejemplo.org:100000/x",12,""], + ["https://[2606:4700::1111]:100000/x",12,""], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi:100000/x",7,""], + ["https://ejemplo.org:4294967739/x",12,""], + ["https://[2606:4700::1111]:4294967739/x",12,""], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi:4294967739/x",7,""], + ["https://ejemplo.org:-443/x",12,""], + ["https://[2606:4700::1111]:-443/x",12,""], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi:-443/x",7,""], + ["https://ejemplo.org:+443/x",12,""], + ["https://[2606:4700::1111]:+443/x",12,""], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi:+443/x",7,""], + ["https://ejemplo.org:44 3/x",9,""], + ["https://[2606:4700::1111]:44 3/x",9,""], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi:44 3/x",9,""], + ["https://ejemplo.org:4a/x",12,""], + ["https://[2606:4700::1111]:4a/x",12,""], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi:4a/x",7,""], + ["https://ejemplo.org:443:80/x",12,""], + ["https://[2606:4700::1111]:443:80/x",12,""], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi:443:80/x",7,""], + ["https://ejemplo.org::443/x",12,""], + ["https://[2606:4700::1111]::443/x",12,""], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi::443/x",7,""], + ["HTTPS://ejemplo.org/",34,""], + ["hTTps://ejemplo.org/",38,""], + ["https:/ejemplo.org/",14,""], + ["https:ejemplo.org/",14,""], + ["https:///ejemplo.org/",6,""], + ["https:////ejemplo.org/",6,""], + ["://ejemplo.org/",14,""], + ["https//ejemplo.org/",14,""], + ["ipfs:/bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi",14,""], + ["ipfs:bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi",14,""], + ["ipfs:///bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi",7,""], + ["https://user@ejemplo.org/",5,""], + ["https://@ejemplo.org/",5,""], + ["https://ejemplo.org@x/",5,""], + ["https://ejemplo.org/a@b",0,"ejemplo.org"], + ["https://ejemplo.org?a@b",0,"ejemplo.org"], + ["https://ejemplo.org#a@b",0,"ejemplo.org"], + ["https://ejemplo.org/?",0,"ejemplo.org"], + ["https://ejemplo.org#",0,"ejemplo.org"], + ["https://ejemplo.org?",0,"ejemplo.org"], + ["https://ejemplo.org/%",29,""], + ["https://ejemplo.org/%2",29,""], + ["https://ejemplo.org/%2G",29,""], + ["https://ejemplo.org/%2f%2F",0,"ejemplo.org"], + ["https://ejemplo.org%2f/",5,""], + ["https://ejemplo.org/a%",29,""], + ["https://ejemplo.org/a%%41",29,""], + ["https://",6,""], + ["ipfs://",7,""], + ["https://:443/",6,""], + ["https://.:443/",33,""], + ["https://./",33,""], + ["https://ejemplo.org/.",30,""], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi/.",30,""], + ["https://ejemplo.org/..",30,""], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi/..",30,""], + ["https://ejemplo.org/./",30,""], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi/./",30,""], + ["https://ejemplo.org/../",30,""], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi/../",30,""], + ["https://ejemplo.org/a/.",30,""], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi/a/.",30,""], + ["https://ejemplo.org/a/..",30,""], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi/a/..",30,""], + ["https://ejemplo.org/a/./b",30,""], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi/a/./b",30,""], + ["https://ejemplo.org/a/../b",30,""], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi/a/../b",30,""], + ["https://ejemplo.org/%2e",30,""], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi/%2e",30,""], + ["https://ejemplo.org/%2E",30,""], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi/%2E",30,""], + ["https://ejemplo.org/%2e%2e",30,""], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi/%2e%2e",30,""], + ["https://ejemplo.org/%2E%2E",30,""], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi/%2E%2E",30,""], + ["https://ejemplo.org/.%2e",30,""], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi/.%2e",30,""], + ["https://ejemplo.org/%2e.",30,""], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi/%2e.",30,""], + ["https://ejemplo.org/%2e%2E/",30,""], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi/%2e%2E/",30,""], + ["https://ejemplo.org/...",0,"ejemplo.org"], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi/...",0,"bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi"], + ["https://ejemplo.org/.a",0,"ejemplo.org"], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi/.a",0,"bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi"], + ["https://ejemplo.org/a.",0,"ejemplo.org"], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi/a.",0,"bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi"], + ["https://ejemplo.org/.%2",29,""], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi/.%2",29,""], + ["https://ejemplo.org/%2e%2e%2f",0,"ejemplo.org"], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi/%2e%2e%2f",0,"bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi"], + ["https://ejemplo.org/a?/../",0,"ejemplo.org"], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi/a?/../",0,"bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi"], + ["https://ejemplo.org/a#/../",0,"ejemplo.org"], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi/a#/../",0,"bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi"], + ["https://ejemplo.org/a?x=/..",0,"ejemplo.org"], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi/a?x=/..",0,"bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi"], + ["https://ejemplo.org?/..",30,""], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi?/..",30,""], + ["https://ejemplo.org#/..",30,""], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi#/..",30,""], + ["https://ejemplo.org?a/../b",30,""], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi?a/../b",30,""], + ["https://ejemplo.org//..",30,""], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi//..",30,""], + ["https://ejemplo.org/..//",30,""], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi/..//",30,""], + ["https://ejemplo.org/a\u0000b",25,""], + ["https://ejemplo.org/a\u0001b",39,""], + ["https://ejemplo.org/a\u0002b",40,""], + ["https://ejemplo.org/a\u0003b",41,""], + ["https://ejemplo.org/a\u0004b",42,""], + ["https://ejemplo.org/a\u0005b",43,""], + ["https://ejemplo.org/a\u0006b",44,""], + ["https://ejemplo.org/a\u0007b",45,""], + ["https://ejemplo.org/a\bb",46,""], + ["https://ejemplo.org/a\tb",26,""], + ["https://ejemplo.org/a\nb",27,""], + ["https://ejemplo.org/a\u000bb",47,""], + ["https://ejemplo.org/a\fb",48,""], + ["https://ejemplo.org/a\rb",49,""], + ["https://ejemplo.org/a\u000eb",50,""], + ["https://ejemplo.org/a\u000fb",51,""], + ["https://ejemplo.org/a\u0010b",52,""], + ["https://ejemplo.org/a\u0011b",53,""], + ["https://ejemplo.org/a\u0012b",54,""], + ["https://ejemplo.org/a\u0013b",55,""], + ["https://ejemplo.org/a\u0014b",56,""], + ["https://ejemplo.org/a\u0015b",57,""], + ["https://ejemplo.org/a\u0016b",58,""], + ["https://ejemplo.org/a\u0017b",59,""], + ["https://ejemplo.org/a\u0018b",60,""], + ["https://ejemplo.org/a\u0019b",61,""], + ["https://ejemplo.org/a\u001ab",62,""], + ["https://ejemplo.org/a\u001bb",63,""], + ["https://ejemplo.org/a\u001cb",64,""], + ["https://ejemplo.org/a\u001db",65,""], + ["https://ejemplo.org/a\u001eb",66,""], + ["https://ejemplo.org/a\u001fb",67,""], + ["https://ejemplo.org/a b",9,""], + ["https://ejemplo.org/a!b",0,"ejemplo.org"], + ["https://ejemplo.org/a\"b",16,""], + ["https://ejemplo.org/a#b",0,"ejemplo.org"], + ["https://ejemplo.org/a$b",0,"ejemplo.org"], + ["https://ejemplo.org/a%b",29,""], + ["https://ejemplo.org/a&b",0,"ejemplo.org"], + ["https://ejemplo.org/a\u0027b",0,"ejemplo.org"], + ["https://ejemplo.org/a(b",0,"ejemplo.org"], + ["https://ejemplo.org/a)b",0,"ejemplo.org"], + ["https://ejemplo.org/a*b",0,"ejemplo.org"], + ["https://ejemplo.org/a+b",0,"ejemplo.org"], + ["https://ejemplo.org/a,b",0,"ejemplo.org"], + ["https://ejemplo.org/a-b",0,"ejemplo.org"], + ["https://ejemplo.org/a.b",0,"ejemplo.org"], + ["https://ejemplo.org/a/b",0,"ejemplo.org"], + ["https://ejemplo.org/a0b",0,"ejemplo.org"], + ["https://ejemplo.org/a1b",0,"ejemplo.org"], + ["https://ejemplo.org/a2b",0,"ejemplo.org"], + ["https://ejemplo.org/a3b",0,"ejemplo.org"], + ["https://ejemplo.org/a4b",0,"ejemplo.org"], + ["https://ejemplo.org/a5b",0,"ejemplo.org"], + ["https://ejemplo.org/a6b",0,"ejemplo.org"], + ["https://ejemplo.org/a7b",0,"ejemplo.org"], + ["https://ejemplo.org/a8b",0,"ejemplo.org"], + ["https://ejemplo.org/a9b",0,"ejemplo.org"], + ["https://ejemplo.org/a:b",0,"ejemplo.org"], + ["https://ejemplo.org/a;b",0,"ejemplo.org"], + ["https://ejemplo.org/ab",18,""], + ["https://ejemplo.org/a?b",0,"ejemplo.org"], + ["https://ejemplo.org/aAb",0,"ejemplo.org"], + ["https://ejemplo.org/aBb",0,"ejemplo.org"], + ["https://ejemplo.org/aCb",0,"ejemplo.org"], + ["https://ejemplo.org/aDb",0,"ejemplo.org"], + ["https://ejemplo.org/aEb",0,"ejemplo.org"], + ["https://ejemplo.org/aFb",0,"ejemplo.org"], + ["https://ejemplo.org/aGb",0,"ejemplo.org"], + ["https://ejemplo.org/aHb",0,"ejemplo.org"], + ["https://ejemplo.org/aIb",0,"ejemplo.org"], + ["https://ejemplo.org/aJb",0,"ejemplo.org"], + ["https://ejemplo.org/aKb",0,"ejemplo.org"], + ["https://ejemplo.org/aLb",0,"ejemplo.org"], + ["https://ejemplo.org/aMb",0,"ejemplo.org"], + ["https://ejemplo.org/aNb",0,"ejemplo.org"], + ["https://ejemplo.org/aOb",0,"ejemplo.org"], + ["https://ejemplo.org/aPb",0,"ejemplo.org"], + ["https://ejemplo.org/aQb",0,"ejemplo.org"], + ["https://ejemplo.org/aRb",0,"ejemplo.org"], + ["https://ejemplo.org/aSb",0,"ejemplo.org"], + ["https://ejemplo.org/aTb",0,"ejemplo.org"], + ["https://ejemplo.org/aUb",0,"ejemplo.org"], + ["https://ejemplo.org/aVb",0,"ejemplo.org"], + ["https://ejemplo.org/aWb",0,"ejemplo.org"], + ["https://ejemplo.org/aXb",0,"ejemplo.org"], + ["https://ejemplo.org/aYb",0,"ejemplo.org"], + ["https://ejemplo.org/aZb",0,"ejemplo.org"], + ["https://ejemplo.org/a[b",0,"ejemplo.org"], + ["https://ejemplo.org/a\\b",19,""], + ["https://ejemplo.org/a]b",0,"ejemplo.org"], + ["https://ejemplo.org/a^b",20,""], + ["https://ejemplo.org/a_b",0,"ejemplo.org"], + ["https://ejemplo.org/a`b",21,""], + ["https://ejemplo.org/aab",0,"ejemplo.org"], + ["https://ejemplo.org/abb",0,"ejemplo.org"], + ["https://ejemplo.org/acb",0,"ejemplo.org"], + ["https://ejemplo.org/adb",0,"ejemplo.org"], + ["https://ejemplo.org/aeb",0,"ejemplo.org"], + ["https://ejemplo.org/afb",0,"ejemplo.org"], + ["https://ejemplo.org/agb",0,"ejemplo.org"], + ["https://ejemplo.org/ahb",0,"ejemplo.org"], + ["https://ejemplo.org/aib",0,"ejemplo.org"], + ["https://ejemplo.org/ajb",0,"ejemplo.org"], + ["https://ejemplo.org/akb",0,"ejemplo.org"], + ["https://ejemplo.org/alb",0,"ejemplo.org"], + ["https://ejemplo.org/amb",0,"ejemplo.org"], + ["https://ejemplo.org/anb",0,"ejemplo.org"], + ["https://ejemplo.org/aob",0,"ejemplo.org"], + ["https://ejemplo.org/apb",0,"ejemplo.org"], + ["https://ejemplo.org/aqb",0,"ejemplo.org"], + ["https://ejemplo.org/arb",0,"ejemplo.org"], + ["https://ejemplo.org/asb",0,"ejemplo.org"], + ["https://ejemplo.org/atb",0,"ejemplo.org"], + ["https://ejemplo.org/aub",0,"ejemplo.org"], + ["https://ejemplo.org/avb",0,"ejemplo.org"], + ["https://ejemplo.org/awb",0,"ejemplo.org"], + ["https://ejemplo.org/axb",0,"ejemplo.org"], + ["https://ejemplo.org/ayb",0,"ejemplo.org"], + ["https://ejemplo.org/azb",0,"ejemplo.org"], + ["https://ejemplo.org/a{b",22,""], + ["https://ejemplo.org/a|b",23,""], + ["https://ejemplo.org/a}b",24,""], + ["https://ejemplo.org/a~b",0,"ejemplo.org"], + ["https://ejemplo.org/ab",28,""], + ["https://ejemplo.org/\u00e9",8,""], + ["https://ejempl\u00e9.org/",8,""], + ["https://ejemplo.org/\u00f1",8,""], + ["https://ejempl\u00f1.org/",8,""], + ["https://ejemplo.org/\u202e",68,""], + ["https://ejempl\u202e.org/",68,""], + ["https://ejemplo.org/\uff0e",69,""], + ["https://ejempl\uff0e.org/",69,""], + ["https://ejemplo.org/\u3002",70,""], + ["https://ejempl\u3002.org/",70,""], + ["https://ejemplo.org/\ud83c\udf0d",71,""], + ["https://ejempl\ud83c\udf0d.org/",71,""], + ["https://ejemplo.org/\u00a0",72,""], + ["https://ejempl\u00a0.org/",72,""], + ["https://ejemplo.org/\u0430",73,""], + ["https://ejempl\u0430.org/",73,""], + ["https://ejemplo.org/\u200b",68,""], + ["https://ejempl\u200b.org/",68,""], + ["h",14,""], + ["hh",14,""], + ["https://ejemplo.org/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",0,"ejemplo.org"], + ["https://ejemplo.org/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",0,"ejemplo.org"], + ["https://ejemplo.org/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",74,""], + ["https://ejemplo.org/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",75,""], + ["ipfs://bafkreiftzasotym35727cupoyoi6pikjw364gyvpexwpnj6k4dt7humgfu",0,"bafkreiftzasotym35727cupoyoi6pikjw364gyvpexwpnj6k4dt7humgfu"], + ["ipfs://bafkreiftzasotym35727cupoyoi6pikjw364gyvpexwpnj6k4dt7humgfu/x",0,"bafkreiftzasotym35727cupoyoi6pikjw364gyvpexwpnj6k4dt7humgfu"], + ["ipfs://bafkreiftzasotym35727cupoyoi6pikjw364gyvpexwpnj6k4dt7humgfua",0,"bafkreiftzasotym35727cupoyoi6pikjw364gyvpexwpnj6k4dt7humgfua"], + ["ipfs://bafkreiftzasotym35727cupoyoi6pikjw364gyvpexwpnj6k4dt7humgfuaa",7,""], + ["ipfs://bafkreiftzasotym35727cupoyoi6pikjw364gyvpexwpnj6k4dt7humgfuq",7,""], + ["ipfs://bafkreiftzasotym35727cupoyoi6pikjw364gyvpexwpnj6k4dt7humgf",7,""], + ["ipfs://bafybeiftzasotym35727cupoyoi6pikjw364gyvpexwpnj6k4dt7humgfu",0,"bafybeiftzasotym35727cupoyoi6pikjw364gyvpexwpnj6k4dt7humgfu"], + ["ipfs://bafybeiftzasotym35727cupoyoi6pikjw364gyvpexwpnj6k4dt7humgfu/x",0,"bafybeiftzasotym35727cupoyoi6pikjw364gyvpexwpnj6k4dt7humgfu"], + ["ipfs://bafybeiftzasotym35727cupoyoi6pikjw364gyvpexwpnj6k4dt7humgfua",0,"bafybeiftzasotym35727cupoyoi6pikjw364gyvpexwpnj6k4dt7humgfua"], + ["ipfs://bafybeiftzasotym35727cupoyoi6pikjw364gyvpexwpnj6k4dt7humgfuaa",7,""], + ["ipfs://bafybeiftzasotym35727cupoyoi6pikjw364gyvpexwpnj6k4dt7humgfuq",7,""], + ["ipfs://bafybeiftzasotym35727cupoyoi6pikjw364gyvpexwpnj6k4dt7humgf",7,""], + ["ipfs://bafkqaaia",0,"bafkqaaia"], + ["ipfs://bafkqaaia/x",0,"bafkqaaia"], + ["ipfs://bafkqaaiaa",0,"bafkqaaiaa"], + ["ipfs://bafkqaaiaaa",7,""], + ["ipfs://bafkqaaiaq",7,""], + ["ipfs://bafkqaai",7,""], + ["ipfs://bafkqaaa",7,""], + ["ipfs://bafkqaaa/x",7,""], + ["ipfs://bafkqaaaa",7,""], + ["ipfs://bafkqaaaaa",7,""], + ["ipfs://bafkqaaaq",7,""], + ["ipfs://bafkqaa",7,""], + ["ipfs://bafkqas2aabd2qfuiuosscvzijrsq6cakjus3u74zf3q3tjpazmpiiatsbbi4yq463xylmt4sc5ogzi24h5jx6myonjc6auki2j6ebrpcl32cexpvdwkiuxp2i5oa2ey",0,"bafkqas2aabd2qfuiuosscvzijrsq6cakjus3u74zf3q3tjpazmpiiatsbbi4yq463xylmt4sc5ogzi24h5jx6myonjc6auki2j6ebrpcl32cexpvdwkiuxp2i5oa2ey"], + ["ipfs://bafkqas2aabd2qfuiuosscvzijrsq6cakjus3u74zf3q3tjpazmpiiatsbbi4yq463xylmt4sc5ogzi24h5jx6myonjc6auki2j6ebrpcl32cexpvdwkiuxp2i5oa2ey/x",0,"bafkqas2aabd2qfuiuosscvzijrsq6cakjus3u74zf3q3tjpazmpiiatsbbi4yq463xylmt4sc5ogzi24h5jx6myonjc6auki2j6ebrpcl32cexpvdwkiuxp2i5oa2ey"], + ["ipfs://bafkqas2aabd2qfuiuosscvzijrsq6cakjus3u74zf3q3tjpazmpiiatsbbi4yq463xylmt4sc5ogzi24h5jx6myonjc6auki2j6ebrpcl32cexpvdwkiuxp2i5oa2eya",7,""], + ["ipfs://bafkqas2aabd2qfuiuosscvzijrsq6cakjus3u74zf3q3tjpazmpiiatsbbi4yq463xylmt4sc5ogzi24h5jx6myonjc6auki2j6ebrpcl32cexpvdwkiuxp2i5oa2eyaa",7,""], + ["ipfs://bafkqas2aabd2qfuiuosscvzijrsq6cakjus3u74zf3q3tjpazmpiiatsbbi4yq463xylmt4sc5ogzi24h5jx6myonjc6auki2j6ebrpcl32cexpvdwkiuxp2i5oa2eyq",7,""], + ["ipfs://bafkqas2aabd2qfuiuosscvzijrsq6cakjus3u74zf3q3tjpazmpiiatsbbi4yq463xylmt4sc5ogzi24h5jx6myonjc6auki2j6ebrpcl32cexpvdwkiuxp2i5oa2e",7,""], + ["ipfs://bafkqatem5jegg67edoisehj6c2i5jod2egy56t2hfxktldcet5o653pdxs56murl5fwhlevuoyxuxj5xz53uzqt6puhl2wm5t2okkqt43w25jzwh7lxl5qivwe3wpwhc",7,""], + ["ipfs://bafkqatem5jegg67edoisehj6c2i5jod2egy56t2hfxktldcet5o653pdxs56murl5fwhlevuoyxuxj5xz53uzqt6puhl2wm5t2okkqt43w25jzwh7lxl5qivwe3wpwhc/x",7,""], + ["ipfs://bafkqatem5jegg67edoisehj6c2i5jod2egy56t2hfxktldcet5o653pdxs56murl5fwhlevuoyxuxj5xz53uzqt6puhl2wm5t2okkqt43w25jzwh7lxl5qivwe3wpwhca",7,""], + ["ipfs://bafkqatem5jegg67edoisehj6c2i5jod2egy56t2hfxktldcet5o653pdxs56murl5fwhlevuoyxuxj5xz53uzqt6puhl2wm5t2okkqt43w25jzwh7lxl5qivwe3wpwhcaa",7,""], + ["ipfs://bafkqatem5jegg67edoisehj6c2i5jod2egy56t2hfxktldcet5o653pdxs56murl5fwhlevuoyxuxj5xz53uzqt6puhl2wm5t2okkqt43w25jzwh7lxl5qivwe3wpwhcq",7,""], + ["ipfs://bafkqatem5jegg67edoisehj6c2i5jod2egy56t2hfxktldcet5o653pdxs56murl5fwhlevuoyxuxj5xz53uzqt6puhl2wm5t2okkqt43w25jzwh7lxl5qivwe3wpwh",7,""], + ["ipfs://bafkqatpjpibl5apg4fdj4jdarqze7embowedr6qbbky7uq7k7xxkf4e44z2qqjguezfbsmd35oqn2v6lggl7ox4uk5tts5zhfc4yw5224mxq4nadekeilxw27rryozhzda",7,""], + ["ipfs://bafkqatpjpibl5apg4fdj4jdarqze7embowedr6qbbky7uq7k7xxkf4e44z2qqjguezfbsmd35oqn2v6lggl7ox4uk5tts5zhfc4yw5224mxq4nadekeilxw27rryozhzda/x",7,""], + ["ipfs://bafkqatpjpibl5apg4fdj4jdarqze7embowedr6qbbky7uq7k7xxkf4e44z2qqjguezfbsmd35oqn2v6lggl7ox4uk5tts5zhfc4yw5224mxq4nadekeilxw27rryozhzdaa",7,""], + ["ipfs://bafkqatpjpibl5apg4fdj4jdarqze7embowedr6qbbky7uq7k7xxkf4e44z2qqjguezfbsmd35oqn2v6lggl7ox4uk5tts5zhfc4yw5224mxq4nadekeilxw27rryozhzdaaa",7,""], + ["ipfs://bafkqatpjpibl5apg4fdj4jdarqze7embowedr6qbbky7uq7k7xxkf4e44z2qqjguezfbsmd35oqn2v6lggl7ox4uk5tts5zhfc4yw5224mxq4nadekeilxw27rryozhzdaq",7,""], + ["ipfs://bafkqatpjpibl5apg4fdj4jdarqze7embowedr6qbbky7uq7k7xxkf4e44z2qqjguezfbsmd35oqn2v6lggl7ox4uk5tts5zhfc4yw5224mxq4nadekeilxw27rryozhzd",7,""], + ["ipfs://bagaacerawpecj2pbtpx7l4kr53bzdz5bjg3p3q3cv4s6z5vhzlqop46rqywq",0,"bagaacerawpecj2pbtpx7l4kr53bzdz5bjg3p3q3cv4s6z5vhzlqop46rqywq"], + ["ipfs://bagaacerawpecj2pbtpx7l4kr53bzdz5bjg3p3q3cv4s6z5vhzlqop46rqywq/x",0,"bagaacerawpecj2pbtpx7l4kr53bzdz5bjg3p3q3cv4s6z5vhzlqop46rqywq"], + ["ipfs://bagaacerawpecj2pbtpx7l4kr53bzdz5bjg3p3q3cv4s6z5vhzlqop46rqywqa",7,""], + ["ipfs://bagaacerawpecj2pbtpx7l4kr53bzdz5bjg3p3q3cv4s6z5vhzlqop46rqywqaa",7,""], + ["ipfs://bagaacerawpecj2pbtpx7l4kr53bzdz5bjg3p3q3cv4s6z5vhzlqop46rqywqq",7,""], + ["ipfs://bagaacerawpecj2pbtpx7l4kr53bzdz5bjg3p3q3cv4s6z5vhzlqop46rqyw",7,""], + ["ipfs://bagaaaerawpecj2pbtpx7l4kr53bzdz5bjg3p3q3cv4s6z5vhzlqop46rqywq",7,""], + ["ipfs://bagaaaerawpecj2pbtpx7l4kr53bzdz5bjg3p3q3cv4s6z5vhzlqop46rqywq/x",7,""], + ["ipfs://bagaaaerawpecj2pbtpx7l4kr53bzdz5bjg3p3q3cv4s6z5vhzlqop46rqywqa",7,""], + ["ipfs://bagaaaerawpecj2pbtpx7l4kr53bzdz5bjg3p3q3cv4s6z5vhzlqop46rqywqaa",7,""], + ["ipfs://bagaaaerawpecj2pbtpx7l4kr53bzdz5bjg3p3q3cv4s6z5vhzlqop46rqywqq",7,""], + ["ipfs://bagaaaerawpecj2pbtpx7l4kr53bzdz5bjg3p3q3cv4s6z5vhzlqop46rqyw",7,""], + ["ipfs://bqeafkerawpecj2pbtpx7l4kr53bzdz5bjg3p3q3cv4s6z5vhzlqop46rqywq",7,""], + ["ipfs://bqeafkerawpecj2pbtpx7l4kr53bzdz5bjg3p3q3cv4s6z5vhzlqop46rqywq/x",7,""], + ["ipfs://bqeafkerawpecj2pbtpx7l4kr53bzdz5bjg3p3q3cv4s6z5vhzlqop46rqywqa",7,""], + ["ipfs://bqeafkerawpecj2pbtpx7l4kr53bzdz5bjg3p3q3cv4s6z5vhzlqop46rqywqaa",7,""], + ["ipfs://bqeafkerawpecj2pbtpx7l4kr53bzdz5bjg3p3q3cv4s6z5vhzlqop46rqywqq",7,""], + ["ipfs://bqeafkerawpecj2pbtpx7l4kr53bzdz5bjg3p3q3cv4s6z5vhzlqop46rqyw",7,""], + ["ipfs://bah77777777777737ciqlhsbe5hqzx37v6fi65q4r46qutnx5ynrk6jpm62t4vyhh6piymli",0,"bah77777777777737ciqlhsbe5hqzx37v6fi65q4r46qutnx5ynrk6jpm62t4vyhh6piymli"], + ["ipfs://bah77777777777737ciqlhsbe5hqzx37v6fi65q4r46qutnx5ynrk6jpm62t4vyhh6piymli/x",0,"bah77777777777737ciqlhsbe5hqzx37v6fi65q4r46qutnx5ynrk6jpm62t4vyhh6piymli"], + ["ipfs://bah77777777777737ciqlhsbe5hqzx37v6fi65q4r46qutnx5ynrk6jpm62t4vyhh6piymlia",7,""], + ["ipfs://bah77777777777737ciqlhsbe5hqzx37v6fi65q4r46qutnx5ynrk6jpm62t4vyhh6piymliaa",7,""], + ["ipfs://bah77777777777737ciqlhsbe5hqzx37v6fi65q4r46qutnx5ynrk6jpm62t4vyhh6piymliq",7,""], + ["ipfs://bah77777777777737ciqlhsbe5hqzx37v6fi65q4r46qutnx5ynrk6jpm62t4vyhh6piyml",7,""], + ["ipfs://bah77777777777777aejcbm6ietu6dg7p6xyvd3wdsht2csnw7xbwflzf5t3kpsxa47z5dbrn",7,""], + ["ipfs://bah77777777777777aejcbm6ietu6dg7p6xyvd3wdsht2csnw7xbwflzf5t3kpsxa47z5dbrn/x",7,""], + ["ipfs://bah77777777777777aejcbm6ietu6dg7p6xyvd3wdsht2csnw7xbwflzf5t3kpsxa47z5dbrna",7,""], + ["ipfs://bah77777777777777aejcbm6ietu6dg7p6xyvd3wdsht2csnw7xbwflzf5t3kpsxa47z5dbrnaa",7,""], + ["ipfs://bah77777777777777aejcbm6ietu6dg7p6xyvd3wdsht2csnw7xbwflzf5t3kpsxa47z5dbrnq",7,""], + ["ipfs://bah77777777777777aejcbm6ietu6dg7p6xyvd3wdsht2csnw7xbwflzf5t3kpsxa47z5dbr",7,""], + ["ipfs://bafkrfaaa",7,""], + ["ipfs://bafkrfaaa/x",7,""], + ["ipfs://bafkrfaaaa",7,""], + ["ipfs://bafkrfaaaaa",7,""], + ["ipfs://bafkrfaaaq",7,""], + ["ipfs://bafkrfaa",7,""], + ["ipfs://bafkrfiaawpecj2pbtpx7l4kr53bzdz5bjg3p3q3cv4s6z5vhzlqop46rqywq",7,""], + ["ipfs://bafkrfiaawpecj2pbtpx7l4kr53bzdz5bjg3p3q3cv4s6z5vhzlqop46rqywq/x",7,""], + ["ipfs://bafkrfiaawpecj2pbtpx7l4kr53bzdz5bjg3p3q3cv4s6z5vhzlqop46rqywqa",7,""], + ["ipfs://bafkrfiaawpecj2pbtpx7l4kr53bzdz5bjg3p3q3cv4s6z5vhzlqop46rqywqaa",7,""], + ["ipfs://bafkrfiaawpecj2pbtpx7l4kr53bzdz5bjg3p3q3cv4s6z5vhzlqop46rqywqq",7,""], + ["ipfs://bafkrfiaawpecj2pbtpx7l4kr53bzdz5bjg3p3q3cv4s6z5vhzlqop46rqyw",7,""], + ["ipfs://bafkreintzasotym35727cupoyoi6pikjw364gyvpexwpnj6k4dt7humgfu",7,""], + ["ipfs://bafkreintzasotym35727cupoyoi6pikjw364gyvpexwpnj6k4dt7humgfu/x",7,""], + ["ipfs://bafkreintzasotym35727cupoyoi6pikjw364gyvpexwpnj6k4dt7humgfua",7,""], + ["ipfs://bafkreintzasotym35727cupoyoi6pikjw364gyvpexwpnj6k4dt7humgfuaa",0,"bafkreintzasotym35727cupoyoi6pikjw364gyvpexwpnj6k4dt7humgfuaa"], + ["ipfs://bafkreintzasotym35727cupoyoi6pikjw364gyvpexwpnj6k4dt7humgfuq",7,""], + ["ipfs://bafkreintzasotym35727cupoyoi6pikjw364gyvpexwpnj6k4dt7humgf",7,""], + ["ipfs://bafkreh5tzasotym35727cupoyoi6pikjw364gyvpexwpnj6k4dt7humgfu",7,""], + ["ipfs://bafkreh5tzasotym35727cupoyoi6pikjw364gyvpexwpnj6k4dt7humgfu/x",7,""], + ["ipfs://bafkreh5tzasotym35727cupoyoi6pikjw364gyvpexwpnj6k4dt7humgfua",7,""], + ["ipfs://bafkreh5tzasotym35727cupoyoi6pikjw364gyvpexwpnj6k4dt7humgfuaa",7,""], + ["ipfs://bafkreh5tzasotym35727cupoyoi6pikjw364gyvpexwpnj6k4dt7humgfuq",7,""], + ["ipfs://bafkreh5tzasotym35727cupoyoi6pikjw364gyvpexwpnj6k4dt7humgf",7,""], + ["ipfs://bafkreiftzasotym35727cupoyoi6pikjw364gyvpexwpnj6k4dt7humgfuaa/x",7,""], + ["ipfs://bafkreiftzasotym35727cupoyoi6pikjw364gyvpexwpnj6k4dt7humgfuaaa",7,""], + ["ipfs://bafkreiftzasotym35727cupoyoi6pikjw364gyvpexwpnj6k4dt7humgfuaaaa",7,""], + ["ipfs://bafkreiftzasotym35727cupoyoi6pikjw364gyvpexwpnj6k4dt7humgfuaaq",7,""], + ["ipfs://bae",7,""], + ["ipfs://bae/x",7,""], + ["ipfs://baea",7,""], + ["ipfs://baeaa",7,""], + ["ipfs://baeq",7,""], + ["ipfs://ba",7,""], + ["ipfs://bafkq",7,""], + ["ipfs://bafkq/x",7,""], + ["ipfs://bafkqa",7,""], + ["ipfs://bafkqq",7,""], + ["ipfs://bafk",7,""], + ["ipfs://bafkre",7,""], + ["ipfs://bafkre/x",7,""], + ["ipfs://bafkrea",7,""], + ["ipfs://bafkreaa",7,""], + ["ipfs://bafkreq",7,""], + ["ipfs://bafkr",7,""], + ["ipfs://b",7,""], + ["ipfs://b/x",7,""], + ["ipfs://baa",7,""], + ["ipfs://bq",7,""], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdia",0,"bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdia"], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdib",7,""], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdiaa",7,""], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi7",7,""], + ["ipfs://BAFYBEIGDYRZT5SFP7UDM7HU76UH7Y26NF3EFUYLQABF3OCLGTQY55FBZDI",7,""], + ["ipfs://bb",7,""], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi:443",7,""], + ["ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi@x",5,""], + ["ipfs://x@bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi",5,""], + ["ipfs://[bafybeigdyrzt5sfp7udm7hu76uh7y26nf3efuylqabf3oclgtqy55fbzdi]",7,""], + ["https://x/./%41)cb9_/-&\u0027\u200b+",68,""], + ["https://55.109.11.236/0*/\u202exc9%4\t",68,""], + ["https://[2001:2:0:a873:9f2e:e5f6:f87c:e67]/@:A=",11,""], + ["ipfs://bagwsaabakbyh7v7ennj2dcw7irr56ubssmk2zevrjoemnf7aprx3d2k3lgba/z@+x+!",0,"bagwsaabakbyh7v7ennj2dcw7irr56ubssmk2zevrjoemnf7aprx3d2k3lgba"], + ["https://9-11-ibBUPSJcyfmO--JgFm--RZVyKd4Z5y-xKF-bcd5-b2--9dWqXF-m6yJSEY.y0k-a5Sp.org.teSt:1959/0\u00e9a$,",8,""], + ["https://[94ed:e387:fB60:7de8:D9e2:3691:647f:2c0d]/_Bc%2f0)./%2E%2e",30,""], + ["ipfs://baguqeequ5ylmqhicsrceom4sh5vszqddjag6y355//&/~?.../*~%2f%ff~%0g\uff0e",29,""], + ["https://78.63.85.0x96:64124/A@;",10,""], + ["https://[:2001:28:c65b:2a39:9987:a7b5:1f96:4807:0116/&/.../\u200b",68,""], + ["https://[23a2:9f9d:46e3:fbbb:aa15:7d46:d06c:cf91:1]:+1/~_%ff_y}9/...",24,""], + ["ipfs://babyrgfdtwtjg7jsj6yingoonigvmaz3kuduqbqa:18849",7,""], + ["https://3i.0x3700e94c",10,""], + ["ipfs://bafyreqf7matk6hdaua7lopeqvvscjndvrvjsgnmyymunbuf37qvllgbtbexh46iwlxsd47izpkl653fhqlnq3mvj5pdi2lp353yxdpgpb36n2oi/%2E%2e?=;B!/&~a%0g*",29,""], + ["https://an.0Xf93640b2.drbRinoc6J-u.8-S.invAlId/(%zz@/1A(,\u202e.",29,""], + ["h://199.143.156.170./.%2E",30,""], + ["ipfs://Qmaguqfiheajahacdxekjylmrxdr2iyfs3iqob2h6cver4vyejjp2352uwumpw544cf4b5a5ilztcpkdj4w4l6wn2nal3zks4l7pnjfl7tatu32e3dym/%2E%2e/:/..#!/\ta:-Z&aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",26,""], + ["ipfs://bafk7777777777777aecmwyg46/:/y.!Zb0/\t%4\u0027,#(%2E,1b,/",26,""], + ["https://Ch.locaLhOst/,\u001f/1.A*/#/\u202e(Bb",67,""], + ["https://inVaLid/^/1(bb%00_/%2E_Aa",20,""], + ["https://[00064:ff9b:0000:0000:0000:0000:38a2:9b8b%25eth0]:0/#y)-/@)A$=!",28,""], + ["ipfs:///baby2bzacaa/",7,""], + ["ipfs://baguqfiheaikoedfckaeyhqop6uzhhqsuogjtq5iidft/y/.",30,""], + ["ipfs://.-w-ul-NNTjNh.Z.Dt.m7:036956/=y|aBb",23,""], + ["https://a:b@:bdad:75d9:cbb8:7ef6:1896:3c7b:47f9:d1a5]#c.Z/BZ-+9x",5,""], + ["https://LWD.M0znU-T-JZ.-3:005",37,""], + ["ftp://S0owi--FY.0x1f/#%2ey\u00e9>\u0430_(.",18,""], + ["ipfs://baguqeeraxeigozkkk53fd7zpgp46yx7u3si4l2mpma5hqhhxg7vxapjotv37/%2e?&\ud83c\udf0d=/;@0=\t\u0027%41",71,""], + ["https://Hs-mySf8YVSl.ud.uQSL-bWmJk",0,"Hs-mySf8YVSl.ud.uQSL-bWmJk"], + ["https://d3bf:08827:aa95:2606:da16:37c2:a254:d6e8/%41!by1",12,""], + ["https://n9-tOMZw-crU.G0.0.Zf0A0-5/../0",30,""], + ["https://6-hR2TA88b.InvAlID.6oCfrr51u/0\n\u0430",27,""], + ["https://358151129/%2E%2e/,9\u202eb-\u0027",68,""], + ["ipfs://bagf4qafasbosa62vfcdvbxqfnvbbznk3vbpiek7niyqp4qkjp6uz5hzbtw5ym77f/Z:%2E&zA/x/y!!)~,0",7,""], + ["ipfs://local.OY-A86fVVPmZ.788.o-0gE4qKN/..a/#.%2E/+b-%g0%7e>",29,""], + ["https://LN7CCIUJT.xwTf18N---D.aWkscRD5m.dfSgPQDVD/\ud83c\udf0d$\\&9Z)",71,""], + ["https://bU3jSl0g/%ff/:%0gyb!\u00f1A#\u00f1)/;",29,""], + ["ipfs://bafkreqadbknriqdfa/y\u0027b%41+@/%2e",30,""], + ["https://05321741007#/y",10,""], + ["ipfs://bafyjl6jwecttvmrfmz2xlsg75nuuvxd46zpd7ijzcnog5ygn4n6qrzf2pten6/c++",0,"bafyjl6jwecttvmrfmz2xlsg75nuuvxd46zpd7ijzcnog5ygn4n6qrzf2pten6"], + ["ht-tp://qJ-B92sC9Az7IjP-hOKztkp-AeC----sR4OW---teuZ0---Y-I-sa8Y6-RcMl9AR.Home.arpa:00065/*$(",76,""], + ["https://[c4a4:06b8:d88d:9b45:a76f:7e0a:2274:cda6?/%2e#\u0027|+%2E\u00e9(&/.-a=B",23,""], + ["https://p.JyJ-4.0x103f8fbb",10,""], + ["https.://M4F--j-KUM6n.Ju3E-mHL-Ad-2bNJuCn2h6GR7xEn-B3-cx01tKeobgZRlVaQ0FI04xY0qEPBMNRnu.xn--fnij18q/\u0000+az:;%g0/%2E%2e",25,""], + ["h://_XD.tEst",77,""], + ["ipfs://bafkreicqbsqcuogtkwafynxrfr2sv4vpygmutetadaupst4vhujrlkcrrua/&91$\t&/%001:-a",26,""], + ["https://2449877736",10,""], + ["https://p.du-Mk.local:000684/*:$%+A/!A~aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",74,""], + ["https://7.-DHv-fhwg.locAL/%2eB",33,""], + ["https://E/*:%2f9zy",31,""], + ["https://.x-DC-p2sh.TtEPZZ-R/0)\u3002$//...",70,""], + ["https://xn--zg-v-uwttn1m.Cz1o.K9S5ML.TDx3uEB1h/~+",0,"xn--zg-v-uwttn1m.Cz1o.K9S5ML.TDx3uEB1h"], + ["https://93l98.xn--pdbo2-zh8s-h.com.LOcAl",31,""], + ["https://T4H-1K2an/z!.B/~=&?%00_@z/-=",31,""], + ["https://[79ac:2cae:9b72:c33:addd:8abb:178.27.41.123]:6074/-/.../\u200b9x\u00e9(!,",68,""], + ["ipfS://bagm26agdvzfrpxz65t2lzewlvz2yrgdim6lodanlnnwhcoxxlm/x%7e;A*x-/$-y@/(%2f(!A9%zz#./%2e.",29,""], + ["https://xn--xl-id.6xSU-x-QOGbva-S6-PMhETJFAQPyLWcdxTRC15yY-ulun5-OklQdD6IyygpHEoG.uFq-s.jDl3-nW/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",74,""], + ["ipfs://bafy2bzaccrtnyldrqhnamhnaz63wg4omx5d4ws6lty",0,"bafy2bzaccrtnyldrqhnamhnaz63wg4omx5d4ws6lty"], + ["https://e9ed:a6a1:c06b:5d6c:579e:861b:b87a:3148:/:*b~a/*Z=^)",20,""], + ["https://X?!z\ud83c\udf0d/%_",71,""], + ["ipfS://bAGUQEAAUBXD3RZKEQRLVB4JP6B3UFAHXIIE4BKFI",78,""], + ["ipfs://bagaaj77777777777777qcfbpdzmydocicqhvuzy0a4ijpqn2ewctjkkq/&a,",7,""], + ["ipfs://bafyrfiaassfu7auk3vkuk32t2vgetf2vjchnkzfoqao4gnbi2k2vcby4bdoa:52230/!b)~/\u00a0?/;:Ac!",72,""], + ["Https://X.d.BAss.Txj3QVv2-6nH/\u0027+%g0$c*=/9%4x",29,""], + ["ipfs://bafk2bzacaa/\t9//c-",26,""], + ["https://[[5e68:9c47:6319:c134:18ec:e55f:a3da:dc22:]/9%ff/_B\u0027",11,""], + ["https://c997:e0ca:28fb:03824:3959:4fb0:a3d9:b6e5]:41531?./_},0",24,""], + ["https://[[6f00:0:37:0:0:6ab2:aa:a200:1]/1-:.B$$",11,""], + ["ipfs://Bahyaaerakamavvi2oi3oors3ay5zylysp4qywo2qbluywbw3av6tdgnt3rnq/%2E%2e",30,""], + ["https://[63ec:ed66:cb87:fd60:e5a:fb3f:1658:67e8]",11,""], + ["https://i-.qfmMolvCu0Q._xMXeU-LU/...#\n*a0%g0=x/Z_aax9",27,""], + ["ipfs://user@bafyqaqf6n3jflv6a4tpvkxvjry2ffa5xzwjd6pjeowukbxfncniygnod7v444bwqs3usdy7ipmw3ychwko7nl52ishf2brbh542pn4s2g76l5z/~)#&AA-/A~",5,""], + ["https://0-.-g.0.ExaMpLe//A~\u0430b1ab/...#%4@/Z",73,""], + ["ipfs://baguqfiheaiqlu3sylbpg6so5aohzv4b22esxvcle3kbhxpdgz4jr4bwuotb66td/_,0\u00a01y/@%419?%2e./a",72,""], + ["1https://78--vU.4t-J-c-UN0mY.Ltuwz.net./`=9\u00f1/.%2E/+Ab",21,""], + ["https://207.254.95.201.0/c1:/@",10,""], + ["https://exVqS",31,""], + ["https://WiSD._//*~~%41*0",32,""], + ["ipfs://bag6uweya",7,""], + ["https://132.105.101.237.0/=\u00e99%zz%00.",8,""], + ["https://O.INvaLiD./Zb;z9$z/%2e/ZbZb((",30,""], + ["https://0x1eb2455c/zax%g0:y/.?%2E%2e/\u202e)%2EcZ0\u200b",29,""], + ["https://pUBNnGvcGErzUa---lpBX5mQbE9hP3-oQmF7qu6-vo2j51s5HOU-AQW-8V42L.z.C1sH6v9vx86j.dQT-j.iNternal:28938/.%2E/x0",30,""], + ["https://_-8qWiAFN8.CKfYWgnYSXC2SVdsvWFKPZgN-BYKr-fSg1SH8-isHQliiYpO-dN-BxH2RBeNu.0x1f/./...aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",30,""], + ["https://.DBN.MTn1jORIgcv.org/9/a\u001f/00",67,""], + ["ipfs://bAGUQEESALPUNKHD5DMBQMYPWZ3RP4RJPPIIVGT7N4VHUOOMXWSMHGUBVU4VHUJALWBP3VF45RLASFFZVP7P5MHYSMLNC7TRNHCDC5FVQVMJDAOY/:#\u00f1a0*(B/xzb*%2EZ",8,""], + ["https://0xc702AAE9",10,""], + ["https://192.0.0.26",10,""], + ["https://odOP-oP-4._6Ce-lIavM.WQV._u-W",32,""], + ["https://A-IoLY/%2e/..#\u0027y)y&),/x$",30,""], + ["ipfs://baguqfiheajakc4b7azmsqo2vosggextww5cvoixi43mjot3iqd72nopwko7iibfcwc7vb2ewj3jivql6sbv5x3pqhkoduqtyo3r6sgujadbaydgee7/z:\n);/&&.\u200bAz\u00a0?9;_0\u00f1/+*%41zaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",27,""], + ["ipfs://bagaaiabass7rjs2fz7d7wzsybaj6qmusip7x4anekuoezm7o2jw5ctywb6gq=:90403",7,""], + ["https://[0:10:3200:0:9000:0:3900:0]/bZ_Z=-0",11,""], + ["https://891.R.TeST/b",31,""], + ["ipfs://Bagaaj7777777777774arjfpbm2ai7tbalwlqeyeu2m64r7gm2xqgs",7,""], + ["https://icHM--35I.s--t.123.hOMe.Arpa#./aBb+B_",31,""], + ["ipfs://bqeavliheaible3a",7,""], + ["https://fU90Y/)%41/~9%25/0b_;*;-",31,""], + ["https://xn--v-2yg/)+",31,""], + ["ipfS://bafy2bzacedop5qecish3kauqubyztu5jegwhstfwc6itwnp7lqueqd5v3yybg:036214/z_",78,""], + ["https://Kw.l-j.xn--2srdz2xpta-v.Fi-iUzTR7Ny.invalid/(*A9<_/%25a(:/,",17,""], + ["ipfs://bqea3myyaaa=/%2e.",30,""], + ["https://B.5ExRNsxh.DLQ.aSJUGY7s.LocAlHosT",31,""], + ["ipfs://baguqfiheaikljybi3kqsly5cn7ecglpprq4i7i/!:/-",7,""], + ["https://243.211.19190/@//)*!_%25~",10,""], + ["https://PgzszI.R--POq-J.local:51901/..a/%ff>1$%ff1/!",18,""], + ["https://[[a3:9c00:0:0:6700::4400:1300]/&@=/z^_b",20,""], + ["ipfs://bagaiiafa4qbcbwybsstkdxtqlln2rqyuqw7jhh2za5wj6k3z5nu3o7zaktpkdfe2//b\u200b$:!%zz/ y%00}x:?Z:/-%2E9",68,""], + ["ipfs://Qmqeavkequvalq:56067",7,""], + ["ipfs://bqeahbn7qgzalwrx6zjbdov2jztfxz5bswfpjfims6owcarylcrjwjeu2co7ouydsuas32lwnb5qs4qb55odjy2lvub7h3nke3y3hgqqbnueajvlug4=",7,""], + ["https://[2620:4f:8000:2638:e8bc:7db7:d83a:c4c1:1]/;0b/%2ey=%2E/",11,""], + ["https://x2tn8-XmWz9bpr-i-eXt-G-RaFtYEvufp6GW4VTi-m9njfQq-UNQIg0zIs-Q1h19R.25.LvXWxbCc.irK/:",33,""], + ["https://224//*z%4z%zz/,9.",29,""], + ["https://7085:439f:ee5f:54fd:2023:2c8e:c6c6/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",12,""], + ["ipfs://arpa.68nM.xn--n6--eoa-7zt5snwinllntqutiqbvm-disrkj---rqmtfgik--nw9hf2mwyszyw.?.A0B,0&/&9",7,""], + ["ipfs://bAFYBEAA",7,""], + ["https://[0000:0000:00000:0000:0000:ffff:e23a:f0a3]]/~$/z-$\u3002$/?)@)x@\"/\uff0e#\u202ez\uff0ea\u3002/..a",70,""], + ["https://[1594:160b:e308:5940:4c9d:8d3:94e5:ee1e:]/ca@Ac/%2f/...",11,""], + ["https://253.142.151.228/xAA\u3002",70,""], + ["https://2eb2:6372:c6ae:f535:ff30:88d4:626e:843e:1:77468/=%g0/Z$@((%g0x/%4%zz!0!",29,""], + ["ipfs:\\\\baguqeaau7s755ombweznwiwecaghdcxtvdiopuun?..a/z}1!%a",19,""], + ["https://BokF7QvUd--e.tEsT/a/%/%2e",29,""], + ["ipfs://bafk2bzacd76l7llstdksdghflqnurse4h7rl2b4iziextjyqmz2vubp4xtqa/,9.a/@/-*?=\u001f{/\u0027a%2f#%2E%2e/0cB",67,""], + ["https://[375f:bb81:5117:82aa:da80:6642:2c4c:84d0]/aBb/..a/%zz#(\u00a0%4z.1-/",29,""], + ["https://203.199.22.48/=:;/*\u0027/yb\u0027,1",0,"203.199.22.48"], + ["https://[afb4:c0e7:aaec:ae09:36ef:7afa:8f16:d484:]",11,""], + ["https://PRr0YtmF--xrK2l8xe-4ls7UA-WLGjwj-V-tOFszNy--Ywi7y0eTTtTBea-gIw-T.9aSt0ZyFb-IY.LE-UV9R9OvR.p.oNion/!a(?../`~10@9#%zza\u202e%4/y1",21,""], + ["ipfs://77.202.77.162/9AAZAy/A/",7,""], + ["https://.8yuri.3oKi2avj./(xac%00=/@$%4?/A-x*Zb",29,""], + ["ipfs://bafyz5gyzal:10744",7,""], + ["https://u%40v@E-3.665.xj2ZycHO4bB5s8cli2bNm2gfo84TS-FUOlId6cd6o4gHJrfg-NVAge-KGd1xj2./9A_/=ab:\u202e,/",68,""], + ["https://0:0:0:0:0:0:0:1:1]",12,""], + ["ipfs://bAFYREFGQCRY3GYLKONW3FWAKGP2FLXYYV74FUTY:18998/%2e.?~;Z/:&%zz$\u00e9",29,""], + ["https://I.s8jlDD.LocAL/:.$+c./\uff0e%4$Z/%2e9#Z;0;A=b/B\ud83c\udf0d%0g",69,""], + ["https://.EKjGjded/...",33,""], + ["https://[53a:f304:747e:99e2:9875:3e33:516a:bbd5]]",12,""], + ["https://[:0:dcd9:0:0:366e:8e00:0:5500]/Bx%/",29,""], + ["https://B.HoME.aRpa:000419/%2E%2e/.0\u00a0%zz9)@?/%2e.",72,""], + ["https://[d026:d65:66cb:d47b:dfec:d42c:5de6:5ab5]:19380/%g0%zz9\u202e*%4",29,""], + ["https://ZW-BPuh#0*/!)-1!x%00",31,""], + ["https.://k4wTSj-4.dF3-O--2QXo.home.arpA",79,""], + ["ipfs://b:0?%2E%2e/(A@9-!",7,""], + ["https://_./\nA&*;\u00a0/%4?;/z(%g0",27,""], + ["https://95.91.188.202/%2E%2e",30,""], + ["https::/xY.mi-3-1N0r.w--mOvfTQZ.X-L4WF-y3Zg/..",14,""], + ["https://T.0X54dc5f50:000967//ca",12,""], + ["https://022443163636/Z-c",10,""], + ["file::/8-LjQGuC0ME7ikAyO%F2-9H--QDs2XjJdSJcE-c5HQ8gwPi6U1XC-CR94-rqf38ai1y.io.locaL/%2E%2e/;\"\u0027B.$?\ud83c\udf0d()%g0/0%zz!#-cx/0bx\u0027a+1",16,""], + ["https://[2620:4f:8000:7f0:56ed:1f66:883e:5f81]",0,"2620:4f:8000:7f0:56ed:1f66:883e:5f81"], + ["ipfs://baguqfiheajal4b7gekwqssfb3mfzaccbsecujriqzq5zmlmtrdmew7peock2c2saw4no3lwiubuq4lya65isn335j377vtgummx2ez4l4h6qx56hdzwqa",7,""], + ["ipfs://baguqee2aaelb2kok6xb7mv7wv6w6nubfamnh4ghgkcilz5y7d3co7ryg2byzxqpp5z74a6mfeqcyo6op73de7t5peqw2e2jbftaeqpu:-1",7,""], + ["https://57.201.155.117.0/",10,""], + ["data://AlBd7Eo.Q-XEo%3BX24sC/c+9+ A0",9,""], + ["https://[5b11:bea6:1546:e659:da99:69ab:a165:aeca/\u0027/%2e/,yz@@\u00e9#a\u200b*Ay.0/&;\u00279.%2E!",8,""], + ["https://d40e:9fe4:66f7:1e23:4d0:881:c60f:848d:/._\u0027/\"%zz%250Z:/..?%0g\"!B/*_",16,""], + ["https://0Xbd422bf7/b!?,$/%4*%25~B~#1/..",29,""], + ["https://h.Qb.luli.0x1f.LocaL/A/.%2E",30,""], + ["https//@e26c7-mtkAz.TesT/*&%g0=/*1\u00a0+c/<_1#/ax",29,""], + ["https://10Bc%D5YgKEgl/y%4z*Z+/,$\u00279",29,""], + ["https://2851999518/0Z0&cz",10,""], + ["https://9H.bhGr7NBZxgnXXq1MNGfBLPPaNnVNotQ5-yC-pYU2xLzbl-J6Gep-lC-WU7lnH48mk.T-5OwH/{b/\t/",22,""], + ["ipfs://baguqfiheaikowdiku4jysfxzg6qtqqa54iwgzhtrnnig#x!*_Zy;/.",30,""], + ["data://Q.nQh-bTnb.OB---mg6K.r-UmV:39301/:%g0z0#!/.",29,""], + ["https://yZlGpb3pi8-h-LvRF0c5hJoi2--6Ob-D8-HH7j7i-7-fP2--7NEx-tdN4D-IHz.vI-Bc.5M.fbEqcj5D--s/x0!_/{\u0027;/%00 ?B_/-aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",22,""], + ["https://[2002:7247:eabb:06c89:f59e:9ac4:0d46:4b58]/$%2E/c:/\u0027",11,""], + ["h://[8896:88Fc:5DD7:b97D:F55A:46b:4E29:d8bF%251]:052833/%g00Bx/%$.B$*Z/0-",29,""], + ["https://[fd3e:308f:1d46:a062:e7e6:b98d:fd25:5cdd]//B.*%41%zz!/!_1(",29,""], + ["https://jGdXqboRzC98E-gbNvSH--g--qzuNni-M1RgQAgp-q1ea1teTh-K7--86R8LcUO-j.test:000415/\ud83c\udf0d%/;`+by,&/#\u200b %g0(1Z/,_%4 b%0g",71,""], + ["https://X-.q/y%25ac/!0/;?,/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",74,""], + ["https://Df44XeH\u0430uf:31112//y\u0027b/;b0xyB",73,""], + ["https://100.xn--vvkpkuge6uwz.9O-TbRUru..",33,""], + ["ipfs://bafk2bzaciajufrjr37slteampp7hj472xx4iwt7sxsb6iophz5inpk7n5pn4uqtzit5l5iln2ijrbnkwwoy3ujdn3nrwqiblbm33gpy3g2amvdwt//%2E/.:a;%490?../;",30,""], + ["ipfs://bafy2ner2crobonhugtgq/.:",7,""], + ["https://[80f0:641d:c424:81af:e5ed:c247:140.185.117.151]/@:,?Z@:)1a/",11,""], + ["https://a:b@4aad:8c72:cc89::9384::1/)_1%g0B9/,:;y!/,{AB@=y",29,""], + ["ftp://O3u-j/\\~\u0027%4+/++?~/%4c~x",19,""], + ["ipfs://Baguqfiheai75usrzo6v32o7r5wbwvp5cm2gjka4mxa#9,a/\u200b~0;",68,""], + ["https://S3b-7M.huA.8AoZHg8/./c\u0027%41\u0027A&.",30,""], + ["https://V5e.._kUW1Dddx-Z-3j8RWwqGe2mTrxg-ZS-K42Z-xP-5bmZ0OMHFn-WGf-e-9o-kIw.com/\u0027./c./~0a!z",33,""], + ["https.://u%40v@0xa102f51/..a/,=?\u0027B-%7e(9a/.#%410A+/ ",9,""], + ["https://[2001:2:0:653d:9789:ad9a:eda1:4d18::]/y~%g0,",29,""], + ["https://[165e:34c9:36ce:838A:5e96:330A:E5e:37c3]]/%%2f:/y&/;1",29,""], + ["https://u%40v@_X-t1.07zmdJjtGpxL7BE6yEU-Ee8dARRxLV-M-ABduGQIFI-s-pyPKtoxC3yBYV69V-d/%2e.",5,""], + ["ipfs://.-zO.kwnVp.inValId.local/\ud83c\udf0d0",71,""], + ["https://135.106.252.6.0/.%2E/%25/A;?;&y;c--/=c:(b_Z",30,""], + ["https://Wk-.X4A-.0Xff.oniOn:013994/+)$/+;",12,""], + ["https://[:26fd:54:fdbb:f9a1:fac5:a439:21e:f0bc]/z./&-!\u00a0c\u200b/%7e@9ax",72,""], + ["ipfs://baguqfiheajhqm5l3flf3nq3yxm2kln6xkpvtgiewb6usjeforgzhbstwubmw3j2dlfrey5dwercp53nicvm5rsldg52lzsbaegiszmxbpioqjso6ypzckzvjdagrvjjvhpyfkdznia:018804",7,""], + ["https://3RnGDGypS.xn--i.k.com./***%7e/?%25!/.$+\u3002y",70,""], + ["https://Ln---xvFjgqX.YC37p/1bxZy/y;%001)ya?/",28,""], + ["https://3.aa5cJUYNYA8n///?*:^\u202ez;/x#.../y0-\u0430",20,""], + ["https://763.cT\u202e-Jzf/\u0027B/01ca#*%2f(9.\\%g0/",68,""], + ["https://MvH.LqOwK-hlG.iNvalid:034765/\u0430B1/!%25",73,""], + ["https://LocAlh\uff0eoSt/%2e.",69,""], + ["data://vzcLfvCEt-ns4-gA6F0tyXBYnSFItPZp-PQbpdbJ42--f7J3lbjn--dem0IxAH.LOCAL.C.1oPlhL.INterNal//;x+9A",80,""], + ["ipfs://0226034200:29925/%2E%2e/~>()#c/__=.\u3002",18,""], + ["https://1.0s-N4iKQY1.Ur/%/\u00e9Z!%ff%#%2e/.{",29,""], + ["Https://[c1a:dbe7:4f3e:9463:21f8:720e:112.40.130.3]:0001/ \u3002\u0430!cc*/",9,""], + ["ipfs://bafyreaa:/~Zx",7,""], + ["https://[203e:8ee9:5323:d121:5971:1319:7b8c:7675]/\uff0eA_/\u00001_/;y;,__#/...",69,""], + ["https://4kddtbG.p5pUm7fnX9-ZRf11E-nchIg-xq-IXrxgP0--Ns6-Ge-sfcYnre86yQ4-7EI3A.fi.9x/%7e.=Z/A(.",0,"4kddtbG.p5pUm7fnX9-ZRf11E-nchIg-xq-IXrxgP0--Ns6-Ge-sfcYnre86yQ4-7EI3A.fi.9x"], + ["https://[f10c:de42:5e08:c320:16fb:83:5183:5d2b::#y:9/(%00",15,""], + ["ipfs://bAFYREAA:49232/*B%410/%2e.",30,""], + ["https://upHDaItO.lOcAl/,/",31,""], + ["https://E1L8GLwR.No4bnWYeS7.8Y3YML0yYdo-KCM7mZ972nXCX9ZGOcb1J8Lr-GO8-FNpVvlJ-18pFX--h3xp94",0,"E1L8GLwR.No4bnWYeS7.8Y3YML0yYdo-KCM7mZ972nXCX9ZGOcb1J8Lr-GO8-FNpVvlJ-18pFX--h3xp94"], + ["https:\\\\6d--m.-ZncwU-J4SUfv.nA4l6wmM8DFVGzs3N-dYSKHlih81qv-rwZOz2Ni-OqppGZUqm0lXa9NNhPPOaR/, A@0",19,""], + ["https://tesT//+\u00f11",8,""], + ["https://141.203.142.239//,!\u00a0:(/),y_B%41",72,""], + ["https://[2700::1b00:0:d706:1c00:0:35]:/%zz\u001f%41Z(/Z",29,""], + ["https://mxzEQe1risE4RsUa6qzj-9jJ6btuSBNZwjhER--E-Db3oSfOgaW-C66Ixd-dC9-b..N.es",33,""], + ["https://[[0:af00:e6b5:ca00:0:0:3900:0]/-1\u00271<,/%2E%2e",17,""], + ["https://254.61.55835#A(@@/*b",10,""], + ["https://[0:0:0:b200:0:0:773b]/B;//b9A_z1:",11,""], + ["https://tjGSC-kVYMms:65059",31,""], + ["ipfs://baguqeerach3cm75cn73bwiizkljw3fxfkdgpf3zwk7guhcaijr445ryveh7qa",7,""], + ["https://[1206:52eD:8876:a9d5:BB5b:4e62:6c77:aD07:0818/~<;,=)",17,""], + ["https://[2e3d:a34b:9a46:e987:e5ad:894e:8c62:0c2b6]",11,""], + ["https://[5bd2:d953:6b1c:cc6c:cc8f:da93:f506]]",12,""], + ["ipfs://bafybhqaayge4q3gishb7xmijfqyvtjpig6ikkudlleock3jogizr5slvwb22ce4lgpibqbvqk3jrvjackjjhkhxsz2cz3gk3dw6ypgimeb6ampya/$/za\u202e_@=/&%g0(A",68,""], + ["https://ikK-lvbDFrB.lNxjFreRfTE9./%2e./%41a9,&%ff",30,""], + ["https://localhost.123.onion./b0+/_(9,&c!/\u0027$",33,""], + ["https://79.58.138.115:32400/z%0g\u00e9B&\u3002\u00f1/",29,""], + ["ipfs://Bag3coaaa",7,""], + ["ipfs://bafyrgfhovq54ayrzfxt2fsq276hxit92tgegzvqi/aA).b\u0027=/\uff0e(%2eb",69,""], + ["https://Z-zRgNv5lZt-OBfgQVNk4SXTaOAzxNtHUY-P4x--4rCRTdV8wrFG5p-WO-AkOfF5.onIon.OPgYTYxO.jM-V8R1fjt2oevO6WPBh7ldp2tPWr--WjFzWnd5mSSB8SYJX--S6c-nZFbdqv54kd::1/ca1c%4",29,""], + ["ipfs://bajykbzaciad4gvhxwrkqkiauebvl7youcilnjhrq2mkvs4mucf66zhq374?/:%7e",7,""], + ["data://xn--jkm4qv.0/ +*x %2f;/bc /.%2E",9,""], + ["HTTPS://20.107.215.0184/;*(\u00e9",8,""], + ["http://M.VKrrgc13z.c.es:1?1%41@/9",2,""], + ["https://inTeRNal/a(c/\tbz~)*",26,""], + ["https://192.537/;9**:9?&!/0_%zz;x9*",29,""], + ["https://Vd-gLQ._jCa:37979/%2E%2e",30,""], + ["ipfs://bah777777777777ybudsaesjkdksey3hwv7rbs6mehxt6lcc23jqwweogjbelyufyt3if5g6ytt5r2u6dd7ija2lkii4mzwjry7n4f6lv6czddsik56p54zyv2cqoymooxkazg7d66nja",7,""], + ["https://arPa.dMAvrLJ6tmS.xn--a.722/_(**~ $#\u0027/%g0%2f",9,""], + ["https://.tMgAO02.1W1Kdo/_/;#%2e./%2E%2e",33,""], + ["https://7/%0g_aA\uff0e\u202e\uff0e/\u202e/xaz",29,""], + ["https://[[34b5:a70f:2a6c:5c68:fe8c:6104:540a:fab1::]/_,y~/+/*",11,""], + ["https://[90f:567b:e139:42ea:63d7:c9e8:79ac:6015::]/",11,""], + ["ipfs://bafybgi2o3zdr26lf3252jtmkx4a3fkynet7ktlyr7qaorsoxtzzmio5ieyqgo3i/%2f,-b/&1+?Bzy.9,a/{xza%ffaB",22,""], + ["ipfs://-BUKtOy-a5-k/$A!y->?b\u3002~\t/9:AA;.#%g0(!&(&/_\ud83c\udf0d1{\u0027\"z",18,""], + ["https://-tWo0k.J--QZv2kaD-J.4vNJdez709.0Xff/x%g0=ZAy!/;%00$#=B.@%0g/&!",29,""], + ["https://214.149.21.174.0",10,""], + ["https://7XF5LzD-FQ.aotK--DLKH.ZX9YkPw.-ZwmhloamQIPhjjQ-8v3GYzYwZlS2G0-pL5T7eik9-L3iODzcYWVlcMJvC2aW-VHG/y;9z%ff;/%2e.",30,""], + ["ipfs://bafkreqgiag2dnlro4nqaecyd5ptidh5kceahxtj5y7byn2ldp4l24grifc5ok:10540/@{:%4",22,""], + ["https://ssOJKL-3OEQbLpAKeRaBWM--4wZM3-mEFU-r6-wUenJ-D-hx-rlJaV5kn-bs-G1Di./&%g0Ac%g0@-/\t\u0000&/.!y9=?\tA(b\u00e9\u0027+/_",29,""], + ["https://Zuxc-i-5D9d-.u/)=%zz%00;ZB",29,""], + ["https://Lm\u202eX0y-9-BW-i/...",68,""], + ["://242.9.176.0107/!/\u00a0y%00:==1#%g0z.%zz{.1/+",72,""], + ["https://HD.u6rY-79-vYu/y=9*/%41,aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",0,"HD.u6rY-79-vYu"], + ["HTTPS://169657795//..a/+^$!x0",20,""], + ["https://[1315:cbc0:5a05:2321:8e7b:feba:68.55.188.199]///b%",29,""], + ["ipfs://Qmafkyaabavcwwirnpdhgdn22syzngtixlijbatclxjpfmwftloq2u3xstygcq/A:.~a1/%2E%2e?.../0A*&",30,""], + ["ipfs://baguqeezaja3nh743nqsymjul4kt5gdhfuzphplvp3u67ccpeg5rqwdq67etam#%2e/y\u200bx+\u200b(",68,""], + ["https://46.232.136.94./%2e./\"B^z/a0+.z",16,""], + ["https://5qCdwCTodq.qKCr4d-oNyfM/z.%4Bx\u0430/%4%4\u0430",73,""], + ["https://xn--k9ldx2wc0.kkl8veNb.0Xff.lOcal/&()%zz=-/_",29,""], + ["https://0:0:0:b400:a8:0:36::]:8443/&/yB1",12,""], + ["https://[101b:be68:83de:c5de:53e5:486c:6d03:7440]",11,""], + ["ipfs://bafyrefhlhsta44ocm3xjx76ogqaqafhofqjrory:97837?..a/!",18,""], + ["https://.lqNsTy-u.123/%000",33,""], + ["https://VQO4N00UD2oa.xn--1ko8z/%7e9/B+",0,"VQO4N00UD2oa.xn--1ko8z"], + ["ipfs://o.dPfD5HyW-H8AX-FUP55yTbCwUSCzUL50uSuzH-Kz-JPwB-VXQM-FUPtMTz-V--AWK.inteRnal.test:/%g0/-0#/9__-9@",29,""], + ["ipfs://bacuqfiheaiqwqhh6gb6inrgfmai3x4dzv6odi7vbs3fgeujxrt6ydv5ljvfymlcm/z.\u001f\u00e9/a*~",67,""], + ["https://[f700:0:7785:cf00:1fff:::1",15,""], + ["https://[d836:8a4a:34da::3645::1%251]",5,""], + ["https://F-9sj--qCD.2.FGXWpOdyRA9zP--gSJuFu-UxfsXLl5GhC-H-wT0S-RqeAt02FJiJTalDQEnDsvuy.7j3-i-pGmQQbw-4Z6UNF-KlNs5--9-1--cNgPMi4lQckknTTem7TpSnfg1fQ8ro.:46490/z1)_$-",28,""], + ["https://[2002:214c:3fc5:5aef:17a6:32a:7fbe:23f6::]:055519/,\u0027\u200b/%zzy!\uff0e/..",68,""], + ["https:/169.254.210.109.:443/z#1@-/\u0027:.%g0b\u00a0",29,""], + ["https://[214f:4b04:e882:8873:8a7c:fe4e:ce30:c5f0:/\u200b\u00a0_0`/",68,""], + ["https://HT.86nK-8A-HdkL5xU8xxldfnGk-kO--tDUL2vkKh4-LY-WC-GT4Gvsa-CA-hB--saLq.io",33,""], + ["https://[1ffb:e4e2:75c4:b1f1:47c9:7356:be1a:3137:1]]//c#~@%g0\u3002)/*$",29,""], + ["https://2887002162:48980/b00x",10,""], + ["https://1XCngi8frGN.arpa.lOcalhOst?$/9=%2f",31,""], + ["ftp://bFiJWWsWK.n.nlI9R-m-y.com.TeSt/.~\u0027\uff0e",69,""], + ["https://15.202.206.223/\u00f1?%2E%2e/@9c-;x",8,""], + ["https://QU49Z-\u00a0VnRxZ//!",72,""], + ["IPFS://bqeaiabaaiceni2rfde3u6vnthilbzipi4uzuabrxturwwzitbgj7rtq7vwxlczvtfurntnctcmwvijklihwc3xheurbky4rnbzlwfxu2fehtr76r:23293/\ud83c\udf0dABx%2E@1/)zz9+/10Z!0;",71,""], + ["h://122.1868662/A/:%25%41*%4(/%41-:c1",29,""], + ["https://[108e:a807:dd45:e2cc:c70c:160e:228.83.154.249]:65536",13,""], + ["https://.OSNC5.3DZPmOxRYS.Aiq.com.home.Arpa:0/:$)%2e$Zx/%4/\u00a0x!?1@/%2e.#B%41&b%g0)/B99ZZ!",29,""], + ["https://i.arpa/%2e.",30,""], + ["https://l.2.0S-7jntQu.local:03/A$(_zz$",37,""], + ["https://TGyQpEaMN26Ep-O4YL88D17roic--xoDv9D-1TlzjQruZx-HPEY0-ZvpIqcME.inteRnal.arpa/10\u00a0",72,""], + ["https://LgMI-tua._fAHf.InTErnAL.:37218/\u00a0-_/%2e.",72,""], + ["https://.loCAlhoSt.QvdCpJtzBOpN2qT6wHvEO-h9QU3iRI-skMlxWNESJbcxzi-WKboCKhC-diHKd.xn--2rzxyo:35362",33,""], + ["https://s8-diKrTi._Y-PZB--JS.LVFnJ-Vo/=|Z",23,""], + ["https://.net:14968/z*&/Acy",33,""], + ["://1203209192/%zzA%0g",29,""], + ["https://tllI-J1-Lm-v-xz-hRWvJdNnjl-1-KlXRD-Q-zWnITc8bP6ScVSpVBpvF-4xnLjFL.loCAl.-mSPpT--S9YQ.teSt/.../,(A?,c@;Bc$/@+%41(1",28,""], + ["https://[72ce:9897:a22b::d89a::1]/%4_a/+A*?Z+&${A*/(-zc&%",29,""], + ["ipfs:\\\\bagaajiheajabqbrcfvwv4gni?/}",19,""], + ["https://[ae5d:03c31:05ec:9498:9b60:00c8:970a:3a74]:063461/z%%2E-/#./",29,""], + ["https://[:2002:cae6:bb67:c7de:379:12a0:1dde:a478]]/* \u0430",9,""], + ["https://htbZsf.zLG.SlDZ0\u00f12iPgp-4/(ya\u0027\u00e9",8,""], + ["https://192.88.99.250",10,""], + ["ipfs://bagaajiheaiqjvukjd4r6vwji5ruhu3nf7t3egshc3vwcrmgajaieiydgscmr7yy:?1;%g0,!!/%25%0g%g0A",29,""], + ["https://023331322746/(&b",28,""], + ["https://0xa4d72273.es.",33,""], + ["https://631.-bQ1saqtTlJzt-F3mF23hz-II-PnwDEgF-w-F-MB1-iKcox-yNN1SaMcdIfMEKk::1",12,""], + ["h://[:3fff:3fb:2266:66ac:bc54:b357:115:a65]]/./$;\u00a0;bZ%4",72,""], + ["https://f2rEVxiA.ioHEq.z.es.onION./c=c,/`Z\u00e99)/)(-.!;#}BZ1~/$",21,""], + ["https://255.118.146.116.0:59422?:_&:1y/,",10,""], + ["https://NVWkikwgyRrF.7pG-R3.TT3Cy2-%F1r39d2:0556/0~&Z/_?Zb1\u00e9A*/...#/.%2E",8,""], + ["https://130.131.12.0x98:029147/*\u00e9\u3002?!_(\u3002:1/@a$\u0027#\u200b/=&%4",8,""], + ["https://0xea4ad68a/&+,/%00~\n\uff0eZ&\u0027/%2e.#/",27,""], + ["https://BY3QS-Ga-DFN:0/(%a-Z%41\u00f1",29,""], + ["https://-y--plaoKd4r.0x1f.examplE/1,B\u00f1#\u00e9A/-Zc-!Zc",8,""], + ["https://OvrvN4o//:/?(%2f\u0430(A/",73,""], + ["https://-ws3-iOk6ioWYeFlZBR6J-2y0----vL7br-Cdf--Yd3SpD0hG29g4D--sl-S4Akd.H-JYd9q-x.RCdbT5kkh2B9-zHUw-B5297vWgF-ckAnQ-5sqWx1SZC-ZHPWkve-QVavdFnx4-n.8kJOUHKNoIstJNoE-Wpw-qn7-UPtzhd3ydpiIKk6qFgs7AWnVuwvMhHA7E-eBD--K:0/?=&%zzzyA+/=y",29,""], + ["https://0x789776f6.8..es/;%40(%g0/.%2E",29,""], + ["https://[14:c7:600:df::c6:b/%25\u200b0+\u0027%ff\u0000/_$@@{,,",68,""], + ["https://515.Z.intErnAl/;.`-@b%0g",21,""], + ["ipfs://Qmagaaieyjnmii7e2l5twkvfi/%2e./>\u0027$",18,""], + ["https://wZkW%1BmG0pU-n.com/xA!#b$/-(!);$=",5,""], + ["ipfs://LKn5bT045.arpa/-!/y\u0027\u00a0,/,_))c+",72,""], + ["https://2646669965/./-a%2e@~",30,""], + ["https://zh.aO72.Tzlvm8.poTPsh-XXT-yPNRtHn-k3sF-FE1NR-Zc-H3Wr4031utoc3gxEHUvVaNMI-E-F-/,yb",33,""], + ["ipfs://Qmaguqeequtn7sn6uz5fnk5szwyflrqfckw46xv4wm",7,""], + ["https://.sK.r.iDOy2-wnMm7s:28890/90",33,""], + ["https://[5650:25bf:e1C4:4B4A:8994:b7FF:7ec0:Efe5]",11,""], + ["https://61.198.107.117.0:177/!/%2EA(~-",28,""], + ["https://TQZ.R:49734//1:",0,"TQZ.R"], + ["https://0x5af57c5c:2911/,0!)9y/%g0.0))",29,""], + ["https://0x5051d23f.pqBiJD.892.local.InvALid/:*!/%2e./.",30,""], + ["ipfs:///bagaajiheal77777777777777agr2ie5df2jodenmzv4symmybqwstjasx4/%4.#~/@B",29,""], + ["https://3fff:8bb:e004::b2bd::1/\u202e=!B/%ffb~/*0z",68,""], + ["https://b6f3:48a4:ca96:2b9f:f51e:ac5e:f822:621b:]:0",12,""], + ["ipfs//d-vbb6BMNvWiKPKNf7xcffSGSAdqNwgaxtiH-vOIF4QqKIXhNDgCUUosxC--FN.1e3/%2E%2e/;%7e",14,""], + ["IPFS://011255122565/aaB__\u0000.:",25,""], + ["ipfs://b7777777777776aoymp6jupquavduu34s4jpup5a5lqsko2kharsj6pyba/*/../!",30,""], + ["https://Pr-iTv-0BX/$A\u00279%ff:,/$x/z9)#}B&,*%g0!/c%2e+*",24,""], + ["https://Sxa7Ko.arpa.Test/..//",30,""], + ["https://030026061443/B\u00f1:/+&/*?;%0g(/y0$9.0;",8,""], + ["ipfS://dbeb:4dfd:c297:c9a2:69d7:1b3d:b011:e9fe]//):\"a",16,""], + ["https::/158.236.77.16//$@*c-(/1A#/x",14,""], + ["https://0x8bf95558",10,""], + ["https://230.69.157.82/a)@+%00x/ba/%zzc\u0000:=9",29,""], + ["https://96nzo.xF5sL-w5.ZxO.net/%zz:--A?AA;z\u0027\u00a0/",29,""], + ["https://0X634891e1:26768/-$\u00279\u200b+/:(9_",68,""], + ["https://10.217.199.136.0// *}y",9,""], + ["https://rYvSeYqg-XpRYv-H3f9R1K3aWc3Qzpdc-Y086F-dQTA0-s-j-yNbid9dW1s-oD.S.9xEvZV1OI16bi86uIZ-NbFv2-hKgBsZ-9i7DkXp--xxgzLHQe-G-PcvWFeBa-ccNJ.m-Y-3c-dc3R4/_=!c/(;Z/..?&10/",30,""], + ["ipfs://bah4bweyuyssktvo3fu4msc2753iwu5mzbzy6lamk/\t;*",26,""], + ["https://eI.w._IwTdY/\u00a0Z\u00a0a)/$=z$",72,""], + ["https://[ee77:e734:dc90:2cc6:2f01:cab7:e542:d920::]:35196/.%2E/$%7e*y#/yZ`aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",74,""], + ["https://7.U.hcc-AY5w8:0/;\u0027_0/\u0000/0~c%001?{=&/+%25-@;$",28,""], + ["https://aRpa.Arpa.1rish9J.arpa/z:\u202e~b&/*b9!*",68,""], + ["https://JN:000387/`ya/0\u00a0B0?)(%zz/\"Z|)z",21,""], + ["ipfs://bahxviaca7ftuqjx4cxev3mr4chdoubw2lxlbbozjxx4rmddiov7qgappkmhgnij2syrifd4hr5xszujp7twmaibofyteaalgjxtnaahvlcu73xa:19672",7,""], + ["://x.123:000262/,",14,""], + ["https://99.A.0x149ac62.C-XmwW/",0,"99.A.0x149ac62.C-XmwW"], + ["https://0xac1efc7b/",10,""], + ["ipfs://bafkrefofdq2ksraj326twp24cglp745xcck6eby?by/.%2E",30,""], + ["ipfs://bAGUQEAA7TJLFF336VXW5RVRBRJHEG5SHAL4IOSV52A43CFQMG5YY5O7WK3MQ",7,""], + ["https://7.d-a.inTernaL/AB~:c:",31,""], + ["https://-z.1-CDO65.io//",33,""], + ["ipfs://YrHrX-Bv-2I-N2ld-0er8-E0NXWVdrurE-5QA216FLh2-ff-rxGOsfYwZnCFzs3.JJgrb7Rc3g-GDWqNiYJVb1-mH-zB2xca8bw217AiwF-rFY-7wOMsyRLfNQkFP.7b-jK-Mh/&\u00f1\n =/x\u0027*%4xy\u0027",8,""], + ["https://@_-lX\u00a0QMm.j/0:!y!%g0",72,""], + ["ipfs://bAFK2BZACCVJOP7CWLZG2PXAMCAQ2YMTERU7FWWFTIQ/a%g0\u200b0+/(~\u00e9",29,""], + ["https://jqAKq.xn--fqg.Ut5--a0ycxmqhY1R7MDbu33-g3kQ-UIgF-Ig-ZrtffGtHA-MBXiuyesQ8T-td.com.internal:#../aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",74,""], + ["https://192.88.25427/",10,""], + ["ipfs://bagrctiheajagazsxrtnfnfico6v7h6tm6a2t7b62oe4467gzbqskxwbat4dtc2krzmxuwya726gfq3bhjc6qwmipsvket3oy3gzaj65cydhk5oox4eca/%2f+$?-_\"/),",16,""], + ["https::/O8-Lp/.;?%41=/%41(",14,""], + ["ipfs://0xa568511?c.(1(/.&.\u0027A*",7,""], + ["https://235.254.58.13.//.%2E/+$_",30,""], + ["https://-Cg-aqXzB.9JbcumAulLTcFPOE4N64cKwelsu5EABUXtYcHKMll-wj2wzfT-UXKdsD-q-bKZ/$",33,""], + ["ipfs://bagtuueramo7hk5inwpwqi3u7i2j5ru37cffo5435qqvkfxc53ie7c564bvda:31304/\u00f1Z/x+",8,""], + ["ipfs://bAGJVOEYUNX6Y6YETS56HCPXWCHOAWBUKJ3XYR5Y5/..a/xz\u00f1\u0027&x!",8,""], + ["https://[:8a:0:0:e1:23:5c00:fbb1:4%25eth0]/Z!/",5,""], + ["https://-xs47QT-UZ.arpa.home.aRPA?%2e./%2e-9>(",18,""], + ["https://DDI.P91zlOv-YyIp.onion/\u00f1&=%4%4,1",8,""], + ["https://y-JWl.ucftpJAQpEg0.com/B=/-%00+?;)=^/!",20,""], + ["https://2736:e253:cb2c::64a9::1]:25271/@az0x%g0/_=;#%g0-0yBB%ff/)",29,""], + ["https://4NRX9qPeK.uUo3u1.JAQs.com",0,"4NRX9qPeK.uUo3u1.JAQs.com"], + ["ht-tp://[fe05:bbbb:c087:2398:6754:7c4d:e861:43aa]/bb9a%2f\u0000/_.9)/.(zb&A#z_/`x,%00",25,""], + ["https://r3U6iSdM.123./@\u0027c+:x@/a.@:)x@#\u0027yx\u0027/1",33,""], + ["ipfs://bafkrgajq/0/.%2E",30,""], + ["https://u%40v@vkS--Io3mj.g5d7.local.ONioN/;, ) ;=/\u00a0%zzb/b,_",9,""], + ["https://K-ja2T-L8q.o.u--fMJ.c4GEJQ4kliDniwTkiuuKPo4H-yGFMr-wZxlphlMzbN-QWN0-7147-ovU4LYEFcE/%2e/B=:!-%2e/\u0027zz%25",30,""], + ["https://37.200.246.0253//.",30,""], + ["https://ZLBPw6x/bA9.%%00/^!?==@_&/\u0000\t+$1Z",29,""], + ["ipfs://baguqeaca=:8443/$,y)/Z}$xb:A/",24,""], + ["https::/Nv-zBCS.K2Jr-G5-DzCSKckrmM--6xq5y-hcAEeq-NLjdhxkwf8u7HUm0S6-ZtO-g2q-M.C0-LTbG-596l.LOcalhost#.../@~\u00a0\u200b&*Z",72,""], + ["https://TP:?/1B\u30021|+",70,""], + ["https://2.i8-S7kwh0xb.8.123:102304//\u200b?\u00f1$\u202e*~/-",68,""], + ["https//I.221/;0%00=/~-",14,""], + ["https://u%40v@4LNRen4y1O.gZnq--zrZy.I.XVyl/&~-$|/..a?::0\u202e/~%00",23,""], + ["https://4w-3rPs?\ud83c\udf0d/%ff%2E",71,""], + ["https://25.12625619/+b0A0/?/=)1@%0g",29,""], + ["https://3648841002/($@y$!",10,""], + ["https://A-q.Jh-2Keqs-XS.Pq2Q.282/$cAa",10,""], + ["https://017152377555/)+Ay=/:b",10,""], + ["https://0xcb831a3/b%2E",10,""], + ["https://PM-4c1-.-giyYhpRO0/\u0027/%00/*%zz%2E+%0g&%",29,""], + ["https://u%40v@yZtvfHX.f-epJDZlSZw-.C.DWE-u:42690",5,""], + ["https://@l3Gg/(AA@( &/~<@%4",9,""], + ["https://xn--nsgj-e.loCalHoSt:83124",13,""], + ["https://nP3k5cl.com:00881/b0/@/ %%0gb;)\u3002",9,""], + ["https://K.469.loCalhOst:33952/Acx\u00f1;A/Z/\u00e9&+%zz)",8,""], + ["https://30BFyx-bZM.%9Es/ ",9,""], + ["https://PY1GZ-.sf._DEEyva.net:26721//\u00270\u00e9%ff*\u00a0",8,""], + ["https://E.pf-7Xm.INTernAL.-IOx-0-M",33,""], + ["https://ukPpSnXMGIBOT5Bn-ufxHFpbBF-ciYX-B6eSnhJ-S0g5zs-H8IxPTVG-rK-oIaeTL.G.Test/(a1",33,""], + ["https://192.88.25436:93536/\u0430a\u0027",73,""], + ["https://G-0oZeW8ejIy.1e3:048749/../*z.\u00f1Z/",8,""], + ["ipfs://bAFY6TIDXIGRPWBVH6TYPLIHO6Q4OOP5ROC3TFLUU5QL6E5S7QY7MTSWBEHHPEF6SUUV5RQDWFNV5R6MSMT357RI7GPXEA2VOGVBAEYBK4TYZ27FJ",7,""], + ["https://B.C5HFx9n.y.Hn?x)%2E(+~=/(9,=%2f9z",0,"B.C5HFx9n.y.Hn"], + ["ftp://uhG.xX4c/%2e)=/\u0430*$>a&/\\9",73,""], + ["https://R.IntErnAl.//x$_\"Z&*",16,""], + ["ipfs://baf1k2bzacaa:34634/&c-$y%41",7,""], + ["https.://64.134.191.125.:16172//%2e./_",30,""], + ["https://188.5801627/_/%411*$@==/~",10,""], + ["https://LqHI8g8tM.hOmE.aRPa/({%4c/\u202e;%2E(/11=\uff0e\u00a0a%zz",22,""], + ["https://[bf50:217e:d2a6:34a:66a1:51b8:232.82.51.247]/(/%2E%2e/B%41c%zz:A",29,""], + ["ipfs://Zgubah--EgJ.u\u202en.6CI/=*?>/*",68,""], + ["https://0x60d79cfD/..a",10,""], + ["https://@2ePcy.es",5,""], + ["https://[14c5:22e1:181f:2e50:dedd:8cf1:c2f9:416c]:4017/a9$b./Z\">y0*+",16,""], + ["https://1.RGnp%83ilA-h.test/@(/0Z",5,""], + ["https://0Xeec373d.Fu/=/%7e/..a",0,"0Xeec373d.Fu"], + ["https://10.245.248.181?y\ud83c\udf0d$-$/z_Z",71,""], + ["://[80be:6bbd:1bc0:9770:1266:1496:102]",14,""], + ["ipfs://bafyydurya2",7,""], + ["IPFS://baoaajfekd4kd4mc4slxedankl3idvouk5ektryemfiya:28409/Z)A@!9)/B\u3002?,A1\u200b(/;x$_",70,""], + ["https://SlmH.\u00f1intErNal/{../\tAA1/-=1*_B",8,""], + ["IPFS://bah777777777777ybudsaeqdand5p467rzdt4djhmyymfrjmzns5vl5ypqtsgbnbxdhnswh4w3jd2xa5ng6uufyn34mm6nco5milma5r7ijd5wkkfu4jtyvpl6krps/,",36,""], + ["https://0x5b85e8da/",10,""], + ["https://se-hLuJI.XXp0S6-.p3sLgcp378-W.o-BG05m-U\u0430Px/%2e/0%7e)#0/",73,""], + ["https://[221e:6924:fabb:799b:f9b4:7551:972d:2c82:1%25lo]/@%41)/.",5,""], + ["https://0x9e96e7b3/*%2e:b%x/&%y90=",29,""], + ["https://._Sbvk60HSqCOrwDx7Hq-yxkvRoEp9ylNUqdcBmlZSPa---tJ5Vh-w9-a2pHU5ly//@_a-_@1/%0g",29,""], + ["ipfs://baguqeerayibuh74ivti236xl46atjozw6uf5uqjjiapdqjvqfnidwywrggw8yy/(//+1%00;\u001f:",67,""], + ["ipfs://tUI.InNfItc-w%E8-I.hawk---qgAAUuP-Bq4-73cvLYsSM2--IEWCDK-WwZJ5-rKBTC9pKj3pgcLaZdDMp/%2E%2e/x*",5,""], + ["ht-tp://3fff:bf5:ae5a:1040:5c37:4362:3f37:ba28]/AA*Bc\u0027//+ya-b",76,""], + ["://k5-R5cl3rD.77Q6seNmrhcl.huB.A/+@%00%g0Z",29,""], + ["https://117.186.184.139.0/..a",10,""], + ["https://\u202exn--eu--frh-e.ML.gOIqT.G/_Zza\u0027/\u0027@/@\\AZ",68,""], + ["https://192.5792767//%ff\u0027~/c:y:? x&/z+\u0027%<%;",9,""], + ["https://5u2Vr14-77GH6rMZoFgWY-9yaabDSl6-w3tecGBniSrsqEFeJP-TDspZ1YVZXD.lC0Awt-Nh/(B0/1x0-1-?..a/cx&\u3002",70,""], + ["h://QvB-Ql-JtnJM-.WQJEs-aJzn//!\u202e=+A/.%2E",68,""], + ["ipfs://bafybghuvstu7hi5oklbbhugjrvwp2i3phkcgnx3dg5rp7nf5jxfyaey/_/%g01acc/-&~:,",29,""], + ["ipfs://bafyaat4gz4urgzub4dbfgxbs6cdolgygy6etjuot7abe2wicvxzrtg34gee7zevssdumjhq5tenclugsu3lgepfc4zik7izce3o2de7segofmo6ifkx3dq3eaabzgkyanvjzm=/-.*=%ff/+9_%g0!/\u00f1#1.b%g0_/BA,&Zy!",29,""], + ["https://.ARbkHp-0tnk.com/a/%0ga",29,""], + ["https://u6.OBR-05Y-1/_*$\tx%0g?%41B!/...",26,""], + ["https://HC.IzW-pp-qp.org.HOmE.arpA/+\u0027}+*/%2E%2e",24,""], + ["ipfs://bag0aajw5wlikpf5ftobtr7vzkfn2r66gkltf6zofhxzdqk:65422/a%41y0;B/@y,",7,""], + ["https://[::1f:0:d6:0:483e:0:a8%25eth0]/x\u202e/.!/Zz%ff?\u0027%2e,x\u00270/:-",68,""], + ["https://gWq--IZ7a8.xn--cch.4-3Fod--I-z/1c;-Z/(~%/@\u0027?(x\ud83c\udf0d@/c%25x(0",29,""], + ["https://r6UG.AIi7mo-qZ.R-zF-cd6JA9B.xn--cfu-lid9-d-jlhv-dw-coc3k0ahp2qw-hyb2-v0z8mi5ungsswnymcaoii-y--4",33,""], + ["ipfs://bagaaiah777777777777qcyepvhbpd746aze75i3llwxppoq535w6eaqvyz2bh4upap4todumyy=#/\u30021=*)",70,""], + ["https://463213813/:9\u200b/.%2E/",68,""], + ["https://user@CGede7Q.com",5,""], + ["https:///d6-y:34423/\ud83c\udf0d#\u202ex..A:z/Z%ff&~0Z",71,""], + ["https://[[0:c5:48:71:134a:0:0]/y:\u0027A%zzx)/;!/zA0*$",29,""], + ["https://c2b:7910:b04:8c63:9a5d:e2ac:3c03:7306::]/!",12,""], + ["://eS-s3hU.org/",14,""], + ["https://0X5F59cdc8/",10,""], + ["https://204/_/)\uff0e+:,Bc/b9%ff",69,""], + ["https://@inValid.fC.0-H8W2nju.123/cA~0+&#~%0g.0/.",29,""], + ["https://[[26c8:ae17:f39e:500c:fd6a:934e:c930]/01$_a\u00a0,/ ;&y/:Z.!&\u202e-",72,""], + ["https://-p8AQ.0x3c8b8dfe.ExaMPle//*A1",33,""], + ["ipfs://baguqfjmcp4qsb3zhdejq5wtj6d63wj56i6uio7tzqlj2ywcp3wowqlor6g7xr6i/\u202ey%41zB",68,""], + ["https://0X64527A8E/cz;)a!/Z",10,""], + ["https://3719611407:30069",10,""], + ["https://_Qig9ah6C7OE.t-%72.123",5,""], + ["ipfs://Bahyqbuxyaykbpdmpbtrrva7imvh4w3kaykmnvmla72fq/0%7ezc(/#/;=-1~*%4",29,""], + ["https://kgp1XhxSN-Tf..9D.org.TeST/../;_z",30,""], + ["https://p.ObLJyRmHs--Z4PrZZ1sjSVJPGFSiTW399ZYayxiNFEIrwIz--Iygk-XzS--Ok.PS1iV.tESt.:14622",33,""], + ["https://RV/%41zx&/;.+@~/Z1",31,""], + ["https://227.140.12.0x46:4a/,\u200b\u00a0%x9/y$+/",68,""], + ["https://aov8g4lGw.Cil:94518/!0A_",13,""], + ["https://203.0.113.0184/Z&xx%zz",29,""], + ["Https://0ed4/../xx-\ud83c\udf0d",71,""], + ["https+x://u.j5P.KPjVOtPqFM//0(9Z%2E0c#\n%2e%2E=/)x\u0027%ff\uff0eZ%0g",27,""], + ["ipfs://bafkrgfdw32qqfkvxucz4yo4sn5me2kc37kbmt3?\u0430}\u0027=@!/z!:.",73,""], + ["file://_ZMLzsOcfsRwZXpU-m-B2ag-BBo2igA-BXjdD3wj6wDmLAq7rtH--9mnmGT-0.hDvJBI-V-j-aV-vDiUh2s-60adElTC0G-i--I3ueAfLicAvEWJLaMHa1gInYtAovn.YdHEUz3.thCSM0SrSMX.oniON:48608/\u0027/^-&/",20,""], + ["https://64778492/!/0,~c%ff?=&A:/B&:",10,""], + ["https://0x6464b880/,/..a/\u00f10.B=#\u00f1-%40$$%4/:;",8,""], + ["https://199.0xca37df70.G743.io.oNion/y#0a0/@",31,""], + ["https://[253f:c9b9:6847:e70:a209:a8b1:f2F6:Aae]/%g0/Z\u0027b$,$1?%2e./9x0&.",29,""], + ["https://0x5371fD2B:1566/)%zz$x\u0027/",29,""], + ["https://..0X93ba3b1f.hoMe.arpA",33,""], + ["https://y-V/%2e/0A%ffybc\uff0e/\u0000Z\u0027:~?%g0*$\u0027b\ud83c\udf0d/\u00e99@$%4",69,""], + ["https://[b8a3:1e8a:c556::c643::1]:99999/a;0%7e)0/%7e=/a!,b",13,""], + ["https://3quVpJUGkiJ/\u200b@/\u0027A?_;/#x/\uff0e",68,""], + ["https://GhcZWuo.P4v11-u6Z6gXVIESPy5UG05w6tOx-KLyfefGOWpOl0q20jjB4POQgmrAa9j1g6.INvALid.a-JtxN---oL-rksJ2OJLE34qIgGxWGQ78Qahl3OiGi-66zrskgNn6-uEFlLdJJi-.examPLE/a!~a1\u3002#:/\u00a0!),$",28,""], + ["https://3--ow---J8-.BlsH%64.SHq:0/ba).@y/%g0_$$/%0g",29,""], + ["https://Tboo.a9.tEst//\ta&=$~",26,""], + ["HTTPS://user@8J.xn--7i/\u0027%00!b*//;",5,""], + ["https://[0:bd:0:0:0:ab:0]/%g0.0/&=%25_",29,""], + ["ipfs://[866d:b107:ed77:2e99:a3c3:e6c9:1d5f:f9d2]/%2fyx~/(%zz~;/!_y%4%g0#../(\u002790aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",29,""], + ["https://Ieuv4dMQEcNfxbJ6LTUCWEr-tig0DSwpACAGe9-bACeI5y0-98YE-o3-RIep-vLf.123/+~.~z%2e;/..a",33,""], + ["https://LGiV.4X-3x-Zg7/yB\n(\u00f1|/{\nxaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",27,""], + ["https://14.69.249.248./\u00a0c\u00a0/1)\uff0e_+;/x~\u001f?0A):0/,\ud83c\udf0d*x$\u0000\u0027",72,""], + ["https://euZ-Wbfm--P-:000699#\u001fx\u0027/c+..%2fc\u00a0",67,""], + ["https://Nj09rS-ya-6Rf20fL27-9XO58iETSUE97IN-KC6bVD45RjWCtZff7UdD-HxTjv.341.fAMCWUY.HoME.aRpA/; +export type Row = unknown[]; + +/** A JSON file of src/lib/dkc/testing. */ +export const readVectors = (name: string): Json => JSON.parse(readFileSync(new URL(`./${name}`, import.meta.url), 'utf8')) as Json; + +/** The list `key` of `v`, each item a list. */ +export const rows = (v: Json, key: string): Row[] => v[key] as Row[]; + +/** The text `i` of the table of `v`: "" for no error. */ +export const textOf = (v: Json, i: unknown): string => (v.texts as string[])[i as number]!; + +/** An address as the generators write it: the string, or [before, byte, count, after] for one with a run of count copies of a byte. */ +export function uriOf(x: unknown): string { + if (typeof x === 'string') return x; + const [before, byte, count, after] = x as [string, string, number, string]; + return before + byte.repeat(count) + after; +} + +/** + * The edits of `base` applied in one pass: each [at, delete, insert] + * replaces delete bytes at the offset at of the base with the bytes of the + * hexadecimal insert, and [at, delete, byte, count] with count copies of the + * byte. The offsets are those of the base, in order. + */ +export function applyEdits(base: Uint8Array, edits: unknown): Uint8Array { + const parts: Uint8Array[] = []; + let pos = 0; + for (const e of edits as Row[]) { + const at = e[0] as number; + const del = e[1] as number; + if (at < pos || at + del > base.length) throw new Error(`edit at ${at} out of order or beyond the base`); + parts.push(base.subarray(pos, at)); + const insert = fromHex(e[2] as string); + const repeat = e.length > 3 ? (e[3] as number) : 1; + for (let i = 0; i < repeat; i++) parts.push(insert); + pos = at + del; + } + parts.push(base.subarray(pos)); + const out = new Uint8Array(parts.reduce((n, p) => n + p.length, 0)); + let at = 0; + for (const p of parts) { + out.set(p, at); + at += p.length; + } + return out; +} + +/** The lower-case hexadecimal SHA-256 of `b`. */ +export function sha256Hex(b: Uint8Array): string { + const h = sha256Hasher(); + h.update(b); + return toHex(h.digest()); +} + +/** + * What a reader reads of `l`, as the generators write it: [[uri, offset, + * host, usable]...], the key, the SHA-256 of the header, the digest of the + * rest, its size and capsule_digest, with each address in full. + */ +export function summaryOf(l: Locator): Row { + const usable = usableAddresses(l); + return [ + l.addresses.map((a) => [a.uri, a.offset, addressHost(a), usable.includes(a)]), + toHex(l.envelopeKey), + sha256Hex(l.envelopeHeader), + toHex(l.restDigest), + l.restSize, + toHex(l.capsuleDigest), + ]; +} + +/** The summary `s` of a generator, with each address in full. */ +export function expandSummary(s: unknown): Row { + const l = s as Row; + return [(l[0] as Row[]).map((a) => [uriOf(a[0]), a[1], uriOf(a[2]), a[3]]), ...l.slice(1)]; +} + +/** The release of `round` of the vectors `v`, public data of drand. */ +export function releaseOf(v: Json, round: number): Release { + return { round, signature: fromHex((v.releases as Record)[String(round)]!) }; +} + +/** The pinned Quicknet profile, edited as the generator names the edit. */ +export function profileOf(edit: string): Profile { + const p = quicknet(); + switch (edit) { + case '': + return p; + case 'key not on the curve': { + const k = p.publicKey.slice(); + k[k.length - 1]! ^= 1; + return { ...p, publicKey: k }; + } + case 'key at infinity': + return { ...p, publicKey: Uint8Array.of(0xc0, ...new Uint8Array(95)) }; + case 'key of another network': + // The generator of G2, compressed. + return { + ...p, + publicKey: fromHex( + '93e02b6052719f607dacd3a088274f65596bd0d09920b61ab5da61bbdc7f5049334cf11213945d57e5ac7d055d042b7e024aa2b2f08f0a91260805272dc51051c6e47ad4fa403b02b4510b647ae3d1770bac0326a805bbefd48056c8c121bdb8', + ), + }; + case 'chain hash of another network': { + const h = p.chainHash.slice(); + h[0]! ^= 1; + return { ...p, chainHash: h }; + } + } + throw new Error(edit); +} + +/** The release that an open case names: a round, or an edit of the release of the case. */ +export function openRelease(v: Json, round: number, release: unknown): Release { + if (typeof release === 'number') return releaseOf(v, release); + const r = releaseOf(v, round); + switch (release) { + case 'flipped': { + const s = r.signature.slice(); + s[s.length - 1]! ^= 1; + return { round, signature: s }; + } + case 'short': + return { round, signature: r.signature.subarray(0, 47) }; + case 'other round': + return { round, signature: releaseOf(v, 1001).signature }; + } + throw new Error(String(release)); +} + +/** The text of the error that `body` throws, "" when it returns. */ +export function errorText(body: () => unknown): string { + try { + body(); + return ''; + } catch (err) { + return (err as Error).message; + } +} + +/** + * The string of the bytes `b`, UTF-8 with lone surrogates written as the + * three bytes of their code point (WTF-8), as the vectors write a string of + * JavaScript with a lone surrogate. + */ +export function fromWtf8(b: Uint8Array): string { + let out = ''; + for (let i = 0; i < b.length; ) { + const c = b[i]!; + if (c < 0x80) { + out += String.fromCharCode(c); + i++; + } else if (c >= 0xe0 && c < 0xf0) { + out += String.fromCharCode(((c & 0x0f) << 12) | ((b[i + 1]! & 0x3f) << 6) | (b[i + 2]! & 0x3f)); + i += 3; + } else if (c >= 0xc0 && c < 0xe0) { + out += String.fromCharCode(((c & 0x1f) << 6) | (b[i + 1]! & 0x3f)); + i += 2; + } else { + out += String.fromCodePoint(((c & 0x07) << 18) | ((b[i + 1]! & 0x3f) << 12) | ((b[i + 2]! & 0x3f) << 6) | (b[i + 3]! & 0x3f)); + i += 4; + } + } + return out; +} + +/** The plaintext of a .dkc of n bytes in the vectors of the sealing: (31·i + 7) mod 256 as byte i. */ +export function pattern(n: number): Uint8Array { + const b = new Uint8Array(n); + for (let i = 0; i < n; i++) b[i] = (31 * i + 7) & 0xff; + return b; +}