Merge commit from fork

proxy: bind bearer realms to upstream trust boundary
This commit is contained in:
Milos Gajdos authored and GitHub committed 2026-04-05 13:30:20 -07:00
commit cc5d5fa4ba
11 files changed
+560 -2

No files matched your search

@@ -122,3 +122,44 @@ func testAuthChallengeConcurrent(t *testing.T, host string) {
}()
s.Wait()
}
func TestFilteringManager(t *testing.T) {
t.Parallel()
base := NewSimpleManager()
manager := NewFilteringManager(base, func(c Challenge) bool {
return c.Parameters["service"] == "keep.example.com"
})
endpoint, err := url.Parse("https://registry.example.com/v2/")
if err != nil {
t.Fatal(err)
}
resp := &http.Response{
Request: &http.Request{
URL: endpoint,
},
Header: make(http.Header),
StatusCode: http.StatusUnauthorized,
}
resp.Header.Add("WWW-Authenticate", `Bearer realm="https://auth.example.com/token",service="keep.example.com"`)
resp.Header.Add("WWW-Authenticate", `Bearer realm="https://evil.example.net/token",service="drop.example.net"`)
if err := manager.AddResponse(resp); err != nil {
t.Fatal(err)
}
challenges, err := manager.GetChallenges(*endpoint)
if err != nil {
t.Fatal(err)
}
if len(challenges) != 1 {
t.Fatalf("unexpected challenge count: got %d want 1", len(challenges))
}
if got := challenges[0].Parameters["service"]; got != "keep.example.com" {
t.Fatalf("unexpected surviving challenge service: %q", got)
}
}
@@ -0,0 +1,46 @@
package challenge
import (
"net/http"
"net/url"
)
// FilteringManager decorates another Manager and drops challenges that do not
// satisfy the configured predicate.
type FilteringManager struct {
base Manager
keep func(Challenge) bool
}
// NewFilteringManager returns a Manager that delegates storage to base and
// filters challenges on reads. If keep is nil, the base manager is returned.
func NewFilteringManager(base Manager, keep func(Challenge) bool) Manager {
if keep == nil {
return base
}
return FilteringManager{
base: base,
keep: keep,
}
}
func (m FilteringManager) GetChallenges(endpoint url.URL) ([]Challenge, error) {
challenges, err := m.base.GetChallenges(endpoint)
if err != nil {
return nil, err
}
filtered := make([]Challenge, 0, len(challenges))
for _, c := range challenges {
if m.keep(c) {
filtered = append(filtered, c)
}
}
return filtered, nil
}
func (m FilteringManager) AddResponse(resp *http.Response) error {
return m.base.AddResponse(resp)
}
+51 -1
View File
@@ -1,6 +1,7 @@
package proxy
import (
"net"
"net/http"
"net/url"
"strings"
@@ -8,6 +9,7 @@ import (
"github.com/distribution/distribution/v3/internal/client/auth"
"github.com/distribution/distribution/v3/internal/client/auth/challenge"
"github.com/distribution/distribution/v3/internal/dcontext"
"golang.org/x/net/publicsuffix"
)
const challengeHeader = "Docker-Distribution-Api-Version"
@@ -66,6 +68,11 @@ func configureAuth(username, password, remoteURL string) (auth.CredentialStore,
func getAuthURLs(remoteURL string) ([]string, error) {
authURLs := []string{}
remote, err := url.Parse(remoteURL)
if err != nil {
return nil, err
}
resp, err := http.Get(remoteURL + "/v2/")
if err != nil {
return nil, err
@@ -73,7 +80,7 @@ func getAuthURLs(remoteURL string) ([]string, error) {
defer resp.Body.Close()
for _, c := range challenge.ResponseChallenges(resp) {
if strings.EqualFold(c.Scheme, "bearer") {
if strings.EqualFold(c.Scheme, "bearer") && realmAllowed(remote, c.Parameters["realm"]) {
authURLs = append(authURLs, c.Parameters["realm"])
}
}
@@ -81,6 +88,49 @@ func getAuthURLs(remoteURL string) ([]string, error) {
return authURLs, nil
}
func realmAllowed(remote *url.URL, realm string) bool {
realmURL, err := url.Parse(realm)
if err != nil {
return false
}
if realmURL.Host == "" || remote == nil || remote.Host == "" {
return false
}
if strings.EqualFold(remote.Host, realmURL.Host) {
return true
}
remoteHost := strings.ToLower(remote.Hostname())
realmHost := strings.ToLower(realmURL.Hostname())
if remoteHost == "" || realmHost == "" {
return false
}
if isLiteralOrLocal(remoteHost) || isLiteralOrLocal(realmHost) {
return false
}
return strings.EqualFold(registrableDomain(remoteHost), registrableDomain(realmHost))
}
func isLiteralOrLocal(host string) bool {
if host == "localhost" {
return true
}
return net.ParseIP(host) != nil
}
func registrableDomain(host string) string {
domain, err := publicsuffix.EffectiveTLDPlusOne(host)
if err != nil {
return ""
}
return domain
}
func ping(manager challenge.Manager, endpoint, versionHeader string) error {
resp, err := http.Get(endpoint)
if err != nil {
+136
View File
@@ -0,0 +1,136 @@
package proxy
import (
"fmt"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
"github.com/distribution/distribution/v3/internal/client/auth/challenge"
)
func TestConfigureAuthAllowsSameAuthorityRealm(t *testing.T) {
t.Parallel()
var serverURL string
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/v2/" {
http.NotFound(w, r)
return
}
w.Header().Set("WWW-Authenticate", fmt.Sprintf(`Bearer realm="%s/token",service="test-service"`, serverURL))
w.WriteHeader(http.StatusUnauthorized)
}))
t.Cleanup(upstream.Close)
serverURL = upstream.URL
tokenCreds, _, err := configureAuth("user", "pass", upstream.URL)
if err != nil {
t.Fatalf("configureAuth: %v", err)
}
realmURL, err := url.Parse(serverURL + "/token")
if err != nil {
t.Fatalf("parse realm: %v", err)
}
username, password := tokenCreds.Basic(realmURL)
if username != "user" || password != "pass" {
t.Fatalf("unexpected credentials for trusted realm: got (%q, %q)", username, password)
}
}
func TestConfigureAuthRejectsLoopbackRealmOnDifferentAuthority(t *testing.T) {
t.Parallel()
evil := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.NotFound(w, r)
}))
t.Cleanup(evil.Close)
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/v2/" {
http.NotFound(w, r)
return
}
w.Header().Set("WWW-Authenticate", fmt.Sprintf(`Bearer realm="%s/token",service="test-service"`, evil.URL))
w.WriteHeader(http.StatusUnauthorized)
}))
t.Cleanup(upstream.Close)
tokenCreds, _, err := configureAuth("user", "pass", upstream.URL)
if err != nil {
t.Fatalf("configureAuth: %v", err)
}
realmURL, err := url.Parse(evil.URL + "/token")
if err != nil {
t.Fatalf("parse realm: %v", err)
}
username, password := tokenCreds.Basic(realmURL)
if username != "" || password != "" {
t.Fatalf("unexpected credentials for off-origin realm: got (%q, %q)", username, password)
}
}
func TestRealmAllowedForDockerHubStyleAuthService(t *testing.T) {
t.Parallel()
remoteURL, err := url.Parse("https://registry-1.docker.io")
if err != nil {
t.Fatalf("parse remote url: %v", err)
}
if !realmAllowed(remoteURL, "https://auth.docker.io/token") {
t.Fatal("expected docker hub auth realm to remain allowed")
}
}
func TestRealmFilteringChallengeManagerDropsOffOriginBearer(t *testing.T) {
t.Parallel()
remoteURL, err := url.Parse("https://registry.example.com")
if err != nil {
t.Fatalf("parse remote url: %v", err)
}
endpoint, err := url.Parse("https://registry.example.com/v2/")
if err != nil {
t.Fatalf("parse endpoint: %v", err)
}
resp := &http.Response{
StatusCode: http.StatusUnauthorized,
Header: make(http.Header),
Request: &http.Request{URL: endpoint},
}
resp.Header.Add("Www-Authenticate", `Bearer realm="https://auth.example.com/token",service="registry.example.com"`)
resp.Header.Add("Www-Authenticate", `Bearer realm="https://evil.example.net/token",service="registry.example.com"`)
manager := challenge.NewFilteringManager(challenge.NewSimpleManager(), func(c challenge.Challenge) bool {
return !strings.EqualFold(c.Scheme, "bearer") || realmAllowed(remoteURL, c.Parameters["realm"])
})
if err := manager.AddResponse(resp); err != nil {
t.Fatalf("add response: %v", err)
}
challenges, err := manager.GetChallenges(*endpoint)
if err != nil {
t.Fatalf("get challenges: %v", err)
}
if len(challenges) != 1 {
t.Fatalf("unexpected challenge count: got %d want 1", len(challenges))
}
if challenges[0].Scheme != "bearer" {
t.Fatalf("unexpected surviving challenge: %+v", challenges[0])
}
if challenges[0].Parameters["realm"] != "https://auth.example.com/token" {
t.Fatalf("unexpected surviving realm: %q", challenges[0].Parameters["realm"])
}
}
+4 -1
View File
@@ -5,6 +5,7 @@ import (
"fmt"
"net/http"
"net/url"
"strings"
"sync"
"time"
@@ -263,7 +264,9 @@ func (r *remoteAuthChallenger) credentialStore() auth.CredentialStore {
}
func (r *remoteAuthChallenger) challengeManager() challenge.Manager {
return r.cm
return challenge.NewFilteringManager(r.cm, func(c challenge.Challenge) bool {
return !strings.EqualFold(c.Scheme, "bearer") || realmAllowed(&r.remoteURL, c.Parameters["realm"])
})
}
// tryEstablishChallenges will attempt to get a challenge type for the upstream if none currently exist
Binary file not shown.
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
bolzano-altoadigevje-og-hornnes3-website-us-west-2bomlocustomer-ocienciabonavstackarasjoketokuyamashikokuchuobondigitaloceanspacesakurastoragextraspace-to-rentalstomakomaibarabonesakuratanishikatakazakindustriesteinkjerepbodynaliasnesoddeno-staginglobodoes-itcouldbeworfarsundiskussionsbereichateblobanazawarszawashtenawsapprunnerdpoliticaarparliamenthickarasuyamasoybookonlineboomladeskierniewiceboschristmasakilovecollegefantasyleaguedagestangebostik-serveronagasukeyword-oncillahppictetcieszynishikatsuragit-repostre-totendofinternet-dnsakurawebredirectmeiwamizawabostonakijinsekikogentlentapisa-geekaratsuginamikatagamimozaporizhzhegurinfinitigooglecode-builder-stg-buildereporthruhereclaimsakyotanabellunord-odalvdalcest-le-patron-k3salangenishikawazukamishihorobotdashgabadaddjabbotthuathienhuebouncemerckmsdscloudisrechtrafficplexus-4boutiquebecologialaichaugianglogowegroweibolognagasakikugawaltervistaikillondonetskarelianceboutireserve-onlineboyfriendoftheinternetflixn--11b4c3ditchyouriparmabozen-sudtirolondrinaplesknsalatrobeneventoeidsvollorenskogloomy-gatewaybozen-suedtirolovableprojectjeldsundivtasvuodnakamai-stagingloppennebplaceditorxn--12c1fe0bradescotaruinternationalovepoparochernihivgubamblebtimnetzjaworznotebook-fips3-fips-us-gov-east-1brandivttasvuotnakamuratajirintlon-2brasiliadboxoslodingenishimerabravendbarcelonagawakuyabukikiraragusabaerobatickets3-fips-us-gov-west-1bresciaogashimadachicappabianiceobridgestonebrindisiciliabroadwaybroke-itvedestrandixn--12cfi8ixb8lovesickarlsoybrokerevistathellebrothermesserlidlplfinancialpusercontentjmaxxxn--12co0c3b4evalleaostargets-itjomeldalucaniabrumunddaluccampobassociatesalon-1brusselsaloonishinomiyashironobryanskiervadsoccerhcloudyclusterbrynebweirbzhitomirumaintenanceclothingdustdatadetectoyouracngovtoystre-slidrettozawacnpyatigorskjakamaiedge-stagingreatercnsapporocntozsdeliverycodebergrayjayleaguesardegnarutoshimatta-varjjatranatalcodespotenzakopanecoffeedbackanagawatsonrendercommunity-prochowicecomockashiharacompanyantaishinomakimobetsulifestylefrakkestadurumisakindlegnicahcesuolohmusashimurayamaizuruhr-uni-bochuminamiechizenisshingucciminamifuranocomparemarkerryhotelsardiniacomputercomsecretrosnubarclays3-me-south-1condoshiibabymilk3conferenceconstructioniyodogawaconsuladobeio-static-accesscamdvrcampaniaconsultantranbyconsultingretakamoriokakudamatsuecontactivetrail-central-1contagematsubaracontractorstabacgiangiangryconvexecute-apictureshinordkappaviacookingrimstadynathomebuiltwithdarklangevagrarchitectestingripeeweeklylotterycooperativano-frankivskjervoyagecoprofesionalchikugodaddyn-o-saureadymadethis-a-anarchistjordalshalsenl-ams-1corsicafederationfabricable-modemoneycosenzamamidorivnecosidnsdojoburgriwataraindroppdalcouchpotatofriesarlcouncilcouponstackitagawacozoracpservernamegataitogodoesntexisteingeekashiwaracqcxn--1lqs71dyndns-at-homedepotrani-andria-barletta-trani-andriacrankyotobetsulubin-dsldyndns-at-workisboringsakershusrcfdyndns-blogsiteleaf-south-1crdyndns-freeboxosarpsborgroks-theatrentin-sud-tirolcreditcardyndns-homednsarufutsunomiyawakasaikaitakokonoecreditunioncremonasharis-a-bulls-fancrewp2cricketnedalcrimeast-kazakhstanangercrispawnextdirectraniandriabarlettatraniandriacrminamiiseharacrotonecrownipfizercrsasayamacruisesaseboknowsitallcryptonomichiharacuisinellamdongnairflowersassaris-a-candidatecuneocuritibackdropalermobarag-cloud-charitydalp1cutegirlfriendyndns-ipgwangjulvikashiwazakizunokuniminamiashigarafedoraprojectransiphdfcbankasserverrankoshigayakagefeirafembetsukubankasukabeautypedreamhosterscrapper-sitefermodalenferraraferraris-a-celticsfanferreroticallynxn--2scrj9cargoboavistanbulsan-sudtiroluhanskarmoyfetsundyndns-remotewdhlx3fgroundhandlingroznyfhvalerfilegear-sg-1filminamiminowafinalfinancefinnoyfirebaseapphilipscrappingrphonefosscryptedyndns-serverdalfirenetgamerscrysecuritytacticscwestus2firenzeaburfirestonefirmdaleilaocairportranslatedyndns-webhareidsbergroks-thisayamanobearalvahkikonaikawachinaganoharamcoachampionshiphoplixn--1qqw23afishingokasellfyresdalfitjarfitnessettsurugashimamurogawafjalerfkasumigaurayasudaflesbergrueflickragerotikagoshimandalflierneflirflogintohmangoldpoint2thisamitsukefloppymntransportefloraclegovcloudappservehttpbincheonflorencefloripadualstackasuyakumoduminamioguni5floristanohatakaharunservehumourfloromskoguidefinimalopolskanittedalfltransurlflutterflowhitesnowflakeflyfncarrdiyfndyndns-wikinkobayashimofusadojin-the-bandairlinemurorangecloudplatformshakotanpachihayaakasakawaharacingrondarfoolfor-ourfor-somedusajserveircasacampinagrandebulsan-suedtirolukowesleyfor-theaterfordebianforexrotheworkpccwhminamisanrikubetsupersaleksvikaszubytemarketingvollforgotdnserveminecraftrapanikkoelnforli-cesena-forlicesenaforlikescandypopensocialforsalesforceforsandasuoloisirservemp3fortalfosneservep2photographysiofotravelersinsurancefoxn--30rr7yfozfr-1fr-par-1fr-par-2franalytics-gatewayfredrikstadyndns-worksauheradyndns-mailfreedesktopazimuthaibinhphuocprapidyndns1freemyiphostyhostinguitarservepicservequaLine truncated
+210
View File
@@ -0,0 +1,210 @@
// Copyright 2012 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//go:generate go run gen.go
// Package publicsuffix provides a public suffix list based on data from
// https://publicsuffix.org/
//
// A public suffix is one under which Internet users can directly register
// names. It is related to, but different from, a TLD (top level domain).
//
// "com" is a TLD (top level domain). Top level means it has no dots.
//
// "com" is also a public suffix. Amazon and Google have registered different
// siblings under that domain: "amazon.com" and "google.com".
//
// "au" is another TLD, again because it has no dots. But it's not "amazon.au".
// Instead, it's "amazon.com.au".
//
// "com.au" isn't an actual TLD, because it's not at the top level (it has
// dots). But it is an eTLD (effective TLD), because that's the branching point
// for domain name registrars.
//
// Another name for "an eTLD" is "a public suffix". Often, what's more of
// interest is the eTLD+1, or one more label than the public suffix. For
// example, browsers partition read/write access to HTTP cookies according to
// the eTLD+1. Web pages served from "amazon.com.au" can't read cookies from
// "google.com.au", but web pages served from "maps.google.com" can share
// cookies from "www.google.com", so you don't have to sign into Google Maps
// separately from signing into Google Web Search. Note that all four of those
// domains have 3 labels and 2 dots. The first two domains are each an eTLD+1,
// the last two are not (but share the same eTLD+1: "google.com").
//
// All of these domains have the same eTLD+1:
// - "www.books.amazon.co.uk"
// - "books.amazon.co.uk"
// - "amazon.co.uk"
//
// Specifically, the eTLD+1 is "amazon.co.uk", because the eTLD is "co.uk".
//
// There is no closed form algorithm to calculate the eTLD of a domain.
// Instead, the calculation is data driven. This package provides a
// pre-compiled snapshot of Mozilla's PSL (Public Suffix List) data at
// https://publicsuffix.org/
package publicsuffix // import "golang.org/x/net/publicsuffix"
// TODO: specify case sensitivity and leading/trailing dot behavior for
// func PublicSuffix and func EffectiveTLDPlusOne.
import (
"fmt"
"net/http/cookiejar"
"net/netip"
"strings"
)
// List implements the cookiejar.PublicSuffixList interface by calling the
// PublicSuffix function.
var List cookiejar.PublicSuffixList = list{}
type list struct{}
func (list) PublicSuffix(domain string) string {
ps, _ := PublicSuffix(domain)
return ps
}
func (list) String() string {
return version
}
// PublicSuffix returns the public suffix of the domain using a copy of the
// publicsuffix.org database compiled into the library.
//
// icann is whether the public suffix is managed by the Internet Corporation
// for Assigned Names and Numbers. If not, the public suffix is either a
// privately managed domain (and in practice, not a top level domain) or an
// unmanaged top level domain (and not explicitly mentioned in the
// publicsuffix.org list). For example, "foo.org" and "foo.co.uk" are ICANN
// domains, "foo.dyndns.org" is a private domain and
// "cromulent" is an unmanaged top level domain.
//
// Use cases for distinguishing ICANN domains like "foo.com" from private
// domains like "foo.appspot.com" can be found at
// https://wiki.mozilla.org/Public_Suffix_List/Use_Cases
func PublicSuffix(domain string) (publicSuffix string, icann bool) {
if _, err := netip.ParseAddr(domain); err == nil {
return domain, false
}
lo, hi := uint32(0), uint32(numTLD)
s, suffix, icannNode, wildcard := domain, len(domain), false, false
loop:
for {
dot := strings.LastIndexByte(s, '.')
if wildcard {
icann = icannNode
suffix = 1 + dot
}
if lo == hi {
break
}
f := find(s[1+dot:], lo, hi)
if f == notFound {
break
}
u := uint32(nodes.get(f) >> (nodesBitsTextOffset + nodesBitsTextLength))
icannNode = u&(1<<nodesBitsICANN-1) != 0
u >>= nodesBitsICANN
u = children.get(u & (1<<nodesBitsChildren - 1))
lo = u & (1<<childrenBitsLo - 1)
u >>= childrenBitsLo
hi = u & (1<<childrenBitsHi - 1)
u >>= childrenBitsHi
switch u & (1<<childrenBitsNodeType - 1) {
case nodeTypeNormal:
suffix = 1 + dot
case nodeTypeException:
suffix = 1 + len(s)
break loop
}
u >>= childrenBitsNodeType
wildcard = u&(1<<childrenBitsWildcard-1) != 0
if !wildcard {
icann = icannNode
}
if dot == -1 {
break
}
s = s[:dot]
}
if suffix == len(domain) {
// If no rules match, the prevailing rule is "*".
return domain[1+strings.LastIndexByte(domain, '.'):], icann
}
return domain[suffix:], icann
}
const notFound uint32 = 1<<32 - 1
// find returns the index of the node in the range [lo, hi) whose label equals
// label, or notFound if there is no such node. The range is assumed to be in
// strictly increasing node label order.
func find(label string, lo, hi uint32) uint32 {
for lo < hi {
mid := lo + (hi-lo)/2
s := nodeLabel(mid)
if s < label {
lo = mid + 1
} else if s == label {
return mid
} else {
hi = mid
}
}
return notFound
}
// nodeLabel returns the label for the i'th node.
func nodeLabel(i uint32) string {
x := nodes.get(i)
length := x & (1<<nodesBitsTextLength - 1)
x >>= nodesBitsTextLength
offset := x & (1<<nodesBitsTextOffset - 1)
return text[offset : offset+length]
}
// EffectiveTLDPlusOne returns the effective top level domain plus one more
// label. For example, the eTLD+1 for "foo.bar.golang.org" is "golang.org".
func EffectiveTLDPlusOne(domain string) (string, error) {
if strings.HasPrefix(domain, ".") || strings.HasSuffix(domain, ".") || strings.Contains(domain, "..") {
return "", fmt.Errorf("publicsuffix: empty label in domain %q", domain)
}
suffix, _ := PublicSuffix(domain)
if len(domain) <= len(suffix) {
return "", fmt.Errorf("publicsuffix: cannot derive eTLD+1 for domain %q", domain)
}
i := len(domain) - len(suffix) - 1
if domain[i] != '.' {
return "", fmt.Errorf("publicsuffix: invalid public suffix %q for domain %q", suffix, domain)
}
return domain[1+strings.LastIndexByte(domain[:i], '.'):], nil
}
type uint32String string
func (u uint32String) get(i uint32) uint32 {
off := i * 4
u = u[off:] // help the compiler reduce bounds checks
return uint32(u[3]) |
uint32(u[2])<<8 |
uint32(u[1])<<16 |
uint32(u[0])<<24
}
type uint40String string
func (u uint40String) get(i uint32) uint64 {
off := uint64(i * (nodesBits / 8))
u = u[off:] // help the compiler reduce bounds checks
return uint64(u[4]) |
uint64(u[3])<<8 |
uint64(u[2])<<16 |
uint64(u[1])<<24 |
uint64(u[0])<<32
}
+70
View File
@@ -0,0 +1,70 @@
// generated by go run gen.go; DO NOT EDIT
package publicsuffix
import _ "embed"
const version = "publicsuffix.org's public_suffix_list.dat, git revision 2c960dac3d39ba521eb5db9da192968f5be0aded (2025-03-18T07:22:13Z)"
const (
nodesBits = 40
nodesBitsChildren = 10
nodesBitsICANN = 1
nodesBitsTextOffset = 16
nodesBitsTextLength = 6
childrenBitsWildcard = 1
childrenBitsNodeType = 2
childrenBitsHi = 14
childrenBitsLo = 14
)
const (
nodeTypeNormal = 0
nodeTypeException = 1
nodeTypeParentOnly = 2
)
// numTLD is the number of top level domains.
const numTLD = 1454
// text is the combined text of all labels.
//
//go:embed data/text
var text string
// nodes is the list of nodes. Each node is represented as a 40-bit integer,
// which encodes the node's children, wildcard bit and node type (as an index
// into the children array), ICANN bit and text.
//
// The layout within the node, from MSB to LSB, is:
//
// [ 7 bits] unused
// [10 bits] children index
// [ 1 bits] ICANN bit
// [16 bits] text index
// [ 6 bits] text length
//
//go:embed data/nodes
var nodes uint40String
// children is the list of nodes' children, the parent's wildcard bit and the
// parent's node type. If a node has no children then their children index
// will be in the range [0, 6), depending on the wildcard bit and node type.
//
// The layout within the uint32, from MSB to LSB, is:
//
// [ 1 bits] unused
// [ 1 bits] wildcard bit
// [ 2 bits] node type
// [14 bits] high nodes index (exclusive) of children
// [14 bits] low nodes index (inclusive) of children
//
//go:embed data/children
var children uint32String
// max children 870 (capacity 1023)
// max text offset 31785 (capacity 65535)
// max text length 31 (capacity 63)
// max hi 10100 (capacity 16383)
// max lo 10095 (capacity 16383)
+1
View File
@@ -680,6 +680,7 @@ golang.org/x/net/idna
golang.org/x/net/internal/httpcommon
golang.org/x/net/internal/httpsfv
golang.org/x/net/internal/timeseries
golang.org/x/net/publicsuffix
golang.org/x/net/trace
# golang.org/x/oauth2 v0.35.0
## explicit; go 1.24.0