update to latest adns, synced with Go's net

This commit is contained in:
Mechiel Lukkien 2024-03-08 15:31:54 +01:00
parent a00b0ba6cd
commit 4fbd7abb57
No known key found for this signature in database
17 changed files with 187 additions and 85 deletions

2
go.mod
View file

@ -3,7 +3,7 @@ module github.com/mjl-/mox
go 1.21 go 1.21
require ( require (
github.com/mjl-/adns v0.0.0-20231109160910-82839fe3e6ae github.com/mjl-/adns v0.0.0-20240308133139-6bfb89c3f854
github.com/mjl-/autocert v0.0.0-20231214125928-31b7400acb05 github.com/mjl-/autocert v0.0.0-20231214125928-31b7400acb05
github.com/mjl-/bstore v0.0.4 github.com/mjl-/bstore v0.0.4
github.com/mjl-/sconf v0.0.5 github.com/mjl-/sconf v0.0.5

4
go.sum
View file

@ -24,8 +24,8 @@ github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFB
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg= github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k= github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
github.com/mjl-/adns v0.0.0-20231109160910-82839fe3e6ae h1:P/kTaQbDFSbmDK+RVjwu7mPyr6a6XyHqHu1PlRMEbAU= github.com/mjl-/adns v0.0.0-20240308133139-6bfb89c3f854 h1:BeYqeRkulstKKKCdzk7IOYNeuJPhqmLnaMhu69GxQUs=
github.com/mjl-/adns v0.0.0-20231109160910-82839fe3e6ae/go.mod h1:v47qUMJnipnmDTRGaHwpCwzE6oypa5K33mUvBfzZBn8= github.com/mjl-/adns v0.0.0-20240308133139-6bfb89c3f854/go.mod h1:v47qUMJnipnmDTRGaHwpCwzE6oypa5K33mUvBfzZBn8=
github.com/mjl-/autocert v0.0.0-20231214125928-31b7400acb05 h1:s6ay4bh4tmpPLdxjyeWG45mcwHfEluBMuGPkqxHWUJ4= github.com/mjl-/autocert v0.0.0-20231214125928-31b7400acb05 h1:s6ay4bh4tmpPLdxjyeWG45mcwHfEluBMuGPkqxHWUJ4=
github.com/mjl-/autocert v0.0.0-20231214125928-31b7400acb05/go.mod h1:taMFU86abMxKLPV4Bynhv8enbYmS67b8LG80qZv2Qus= github.com/mjl-/autocert v0.0.0-20231214125928-31b7400acb05/go.mod h1:taMFU86abMxKLPV4Bynhv8enbYmS67b8LG80qZv2Qus=
github.com/mjl-/bstore v0.0.4 h1:q+R1oAr8+E9yf9q+zxkVjQ18VFqD/E9KmGVoe4FIOBA= github.com/mjl-/bstore v0.0.4 h1:q+R1oAr8+E9yf9q+zxkVjQ18VFqD/E9KmGVoe4FIOBA=

13
vendor/github.com/mjl-/adns/conf.go generated vendored
View file

@ -157,7 +157,7 @@ func initConfVal() {
} }
} }
// goosPreferCgo reports whether the GOOS value passed in prefers // goosPrefersCgo reports whether the GOOS value passed in prefers
// the cgo resolver. // the cgo resolver.
func goosPrefersCgo() bool { func goosPrefersCgo() bool {
switch runtime.GOOS { switch runtime.GOOS {
@ -225,16 +225,7 @@ func (c *conf) lookupOrder(r *Resolver, hostname string) (ret hostLookupOrder, d
// Go resolver was explicitly requested // Go resolver was explicitly requested
// or cgo resolver is not available. // or cgo resolver is not available.
// Figure out the order below. // Figure out the order below.
switch c.goos { fallbackOrder = hostLookupFilesDNS
case "windows":
// TODO(bradfitz): implement files-based
// lookup on Windows too? I guess /etc/hosts
// kinda exists on Windows. But for now, only
// do DNS.
fallbackOrder = hostLookupDNS
default:
fallbackOrder = hostLookupFilesDNS
}
canUseCgo = false canUseCgo = false
} else if c.netCgo { } else if c.netCgo {
// Cgo resolver was explicitly requested. // Cgo resolver was explicitly requested.

View file

@ -7,10 +7,12 @@ package adns
import ( import (
"context" "context"
"net" "net"
"github.com/mjl-/adns/internal/bytealg"
) )
func parseNetwork(ctx context.Context, network string, needsProto bool) (afnet string, proto int, err error) { func parseNetwork(ctx context.Context, network string, needsProto bool) (afnet string, proto int, err error) {
i := last(network, ':') i := bytealg.LastIndexByteString(network, ':')
if i < 0 { // no colon if i < 0 { // no colon
switch network { switch network {
case "tcp", "tcp4", "tcp6": case "tcp", "tcp4", "tcp6":

View file

@ -29,6 +29,7 @@ import (
"golang.org/x/net/dns/dnsmessage" "golang.org/x/net/dns/dnsmessage"
"github.com/mjl-/adns/internal/bytealg"
"github.com/mjl-/adns/internal/itoa" "github.com/mjl-/adns/internal/itoa"
) )
@ -205,7 +206,14 @@ func (r *Resolver) exchange(ctx context.Context, server string, q dnsmessage.Que
if err := p.SkipQuestion(); err != dnsmessage.ErrSectionDone { if err := p.SkipQuestion(); err != dnsmessage.ErrSectionDone {
return dnsmessage.Parser{}, dnsmessage.Header{}, errInvalidDNSResponse return dnsmessage.Parser{}, dnsmessage.Header{}, errInvalidDNSResponse
} }
if h.Truncated { // see RFC 5966 // RFC 5966 indicates that when a client receives a UDP response with
// the TC flag set, it should take the TC flag as an indication that it
// should retry over TCP instead.
// The case when the TC flag is set in a TCP response is not well specified,
// so this implements the glibc resolver behavior, returning the existing
// dns response instead of returning a "errNoAnswerFromDNSServer" error.
// See go.dev/issue/64896
if h.Truncated && network == "udp" {
continue continue
} }
return p, h, nil return p, h, nil
@ -215,7 +223,9 @@ func (r *Resolver) exchange(ctx context.Context, server string, q dnsmessage.Que
// checkHeader performs basic sanity checks on the header. // checkHeader performs basic sanity checks on the header.
func checkHeader(p *dnsmessage.Parser, h dnsmessage.Header) error { func checkHeader(p *dnsmessage.Parser, h dnsmessage.Header) error {
if h.RCode == dnsmessage.RCodeNameError { rcode, hasAdd := extractExtendedRCode(*p, h)
if rcode == dnsmessage.RCodeNameError {
return errNoSuchHost return errNoSuchHost
} }
@ -226,17 +236,17 @@ func checkHeader(p *dnsmessage.Parser, h dnsmessage.Header) error {
// libresolv continues to the next server when it receives // libresolv continues to the next server when it receives
// an invalid referral response. See golang.org/issue/15434. // an invalid referral response. See golang.org/issue/15434.
if h.RCode == dnsmessage.RCodeSuccess && !h.Authoritative && !h.RecursionAvailable && err == dnsmessage.ErrSectionDone { if rcode == dnsmessage.RCodeSuccess && !h.Authoritative && !h.RecursionAvailable && err == dnsmessage.ErrSectionDone && !hasAdd {
return errLameReferral return errLameReferral
} }
if h.RCode != dnsmessage.RCodeSuccess && h.RCode != dnsmessage.RCodeNameError { if rcode != dnsmessage.RCodeSuccess && h.RCode != dnsmessage.RCodeNameError {
// None of the error codes make sense // None of the error codes make sense
// for the query we sent. If we didn't get // for the query we sent. If we didn't get
// a name error and we didn't get success, // a name error and we didn't get success,
// the server is behaving incorrectly or // the server is behaving incorrectly or
// having temporary trouble. // having temporary trouble.
if h.RCode == dnsmessage.RCodeServerFailure { if rcode == dnsmessage.RCodeServerFailure {
// Look for Extended DNS Error (EDE), RFC 8914. // Look for Extended DNS Error (EDE), RFC 8914.
if p.SkipAllAnswers() != nil || p.SkipAllAuthorities() != nil { if p.SkipAllAnswers() != nil || p.SkipAllAuthorities() != nil {
@ -302,6 +312,26 @@ func skipToAnswer(p *dnsmessage.Parser, qtype dnsmessage.Type) error {
} }
} }
// extractExtendedRCode extracts the extended RCode from the OPT resource (EDNS(0))
// If an OPT record is not found, the RCode from the hdr is returned.
// Another return value indicates whether an additional resource was found.
func extractExtendedRCode(p dnsmessage.Parser, hdr dnsmessage.Header) (dnsmessage.RCode, bool) {
p.SkipAllAnswers()
p.SkipAllAuthorities()
hasAdd := false
for {
ahdr, err := p.AdditionalHeader()
if err != nil {
return hdr.RCode, hasAdd
}
hasAdd = true
if ahdr.Type == dnsmessage.TypeOPT {
return ahdr.ExtendedRCode(hdr.RCode), hasAdd
}
p.SkipAdditional()
}
}
// Do a lookup for a single name, which must be rooted // Do a lookup for a single name, which must be rooted
// (otherwise answer will not find the answers). // (otherwise answer will not find the answers).
func (r *Resolver) tryOneName(ctx context.Context, cfg *dnsConfig, name string, qtype dnsmessage.Type) (dnsmessage.Parser, string, Result, error) { func (r *Resolver) tryOneName(ctx context.Context, cfg *dnsConfig, name string, qtype dnsmessage.Type) (dnsmessage.Parser, string, Result, error) {
@ -542,10 +572,6 @@ func avoidDNS(name string) bool {
// nameList returns a list of names for sequential DNS queries. // nameList returns a list of names for sequential DNS queries.
func (conf *dnsConfig) nameList(name string) []string { func (conf *dnsConfig) nameList(name string) []string {
if avoidDNS(name) {
return nil
}
// Check name length (see isDomainName). // Check name length (see isDomainName).
l := len(name) l := len(name)
rooted := l > 0 && name[l-1] == '.' rooted := l > 0 && name[l-1] == '.'
@ -555,27 +581,31 @@ func (conf *dnsConfig) nameList(name string) []string {
// If name is rooted (trailing dot), try only that name. // If name is rooted (trailing dot), try only that name.
if rooted { if rooted {
if avoidDNS(name) {
return nil
}
return []string{name} return []string{name}
} }
hasNdots := count(name, '.') >= conf.ndots hasNdots := bytealg.CountString(name, '.') >= conf.ndots
name += "." name += "."
l++ l++
// Build list of search choices. // Build list of search choices.
names := make([]string, 0, 1+len(conf.search)) names := make([]string, 0, 1+len(conf.search))
// If name has enough dots, try unsuffixed first. // If name has enough dots, try unsuffixed first.
if hasNdots { if hasNdots && !avoidDNS(name) {
names = append(names, name) names = append(names, name)
} }
// Try suffixes that are not too long (see isDomainName). // Try suffixes that are not too long (see isDomainName).
for _, suffix := range conf.search { for _, suffix := range conf.search {
if l+len(suffix) <= 254 { fqdn := name + suffix
names = append(names, name+suffix) if !avoidDNS(fqdn) && len(fqdn) <= 254 {
names = append(names, fqdn)
} }
} }
// Try unsuffixed, if not tried first above. // Try unsuffixed, if not tried first above.
if !hasNdots { if !hasNdots && !avoidDNS(name) {
names = append(names, name) names = append(names, name)
} }
return names return names
@ -767,7 +797,7 @@ func (r *Resolver) goLookupIPCNAMEOrder(ctx context.Context, network, name strin
h, err := result0.p.AnswerHeader() h, err := result0.p.AnswerHeader()
if err != nil && err != dnsmessage.ErrSectionDone { if err != nil && err != dnsmessage.ErrSectionDone {
lastErr = &DNSError{ lastErr = &DNSError{
Err: "cannot marshal DNS message", Err: errCannotUnmarshalDNSMessage.Error(),
Name: name, Name: name,
Server: result0.server, Server: result0.server,
} }
@ -780,7 +810,7 @@ func (r *Resolver) goLookupIPCNAMEOrder(ctx context.Context, network, name strin
a, err := result0.p.AResource() a, err := result0.p.AResource()
if err != nil { if err != nil {
lastErr = &DNSError{ lastErr = &DNSError{
Err: "cannot marshal DNS message", Err: errCannotUnmarshalDNSMessage.Error(),
Name: name, Name: name,
Server: result0.server, Server: result0.server,
} }
@ -795,7 +825,7 @@ func (r *Resolver) goLookupIPCNAMEOrder(ctx context.Context, network, name strin
aaaa, err := result0.p.AAAAResource() aaaa, err := result0.p.AAAAResource()
if err != nil { if err != nil {
lastErr = &DNSError{ lastErr = &DNSError{
Err: "cannot marshal DNS message", Err: errCannotUnmarshalDNSMessage.Error(),
Name: name, Name: name,
Server: result0.server, Server: result0.server,
} }
@ -810,7 +840,7 @@ func (r *Resolver) goLookupIPCNAMEOrder(ctx context.Context, network, name strin
c, err := result0.p.CNAMEResource() c, err := result0.p.CNAMEResource()
if err != nil { if err != nil {
lastErr = &DNSError{ lastErr = &DNSError{
Err: "cannot marshal DNS message", Err: errCannotUnmarshalDNSMessage.Error(),
Name: name, Name: name,
Server: result0.server, Server: result0.server,
} }
@ -823,7 +853,7 @@ func (r *Resolver) goLookupIPCNAMEOrder(ctx context.Context, network, name strin
default: default:
if err := result0.p.SkipAnswer(); err != nil { if err := result0.p.SkipAnswer(); err != nil {
lastErr = &DNSError{ lastErr = &DNSError{
Err: "cannot marshal DNS message", Err: errCannotUnmarshalDNSMessage.Error(),
Name: name, Name: name,
Server: result0.server, Server: result0.server,
} }
@ -915,7 +945,7 @@ func (r *Resolver) goLookupPTR(ctx context.Context, addr string, order hostLooku
} }
if err != nil { if err != nil {
return nil, result, &DNSError{ return nil, result, &DNSError{
Err: "cannot marshal DNS message", Err: errCannotUnmarshalDNSMessage.Error(),
Name: addr, Name: addr,
Server: server, Server: server,
} }
@ -924,7 +954,7 @@ func (r *Resolver) goLookupPTR(ctx context.Context, addr string, order hostLooku
err := p.SkipAnswer() err := p.SkipAnswer()
if err != nil { if err != nil {
return nil, result, &DNSError{ return nil, result, &DNSError{
Err: "cannot marshal DNS message", Err: errCannotUnmarshalDNSMessage.Error(),
Name: addr, Name: addr,
Server: server, Server: server,
} }
@ -934,7 +964,7 @@ func (r *Resolver) goLookupPTR(ctx context.Context, addr string, order hostLooku
ptr, err := p.PTRResource() ptr, err := p.PTRResource()
if err != nil { if err != nil {
return nil, result, &DNSError{ return nil, result, &DNSError{
Err: "cannot marshal DNS message", Err: errCannotUnmarshalDNSMessage.Error(),
Name: addr, Name: addr,
Server: server, Server: server,
} }

View file

@ -10,8 +10,7 @@ import (
) )
var ( var (
testHookHostsPath = "/etc/hosts" testHookLookupIP = func(
testHookLookupIP = func(
ctx context.Context, ctx context.Context,
fn func(context.Context, string, string) ([]net.IPAddr, Result, error), fn func(context.Context, string, string) ([]net.IPAddr, Result, error),
network string, network string,

9
vendor/github.com/mjl-/adns/hook_plan9.go generated vendored Normal file
View file

@ -0,0 +1,9 @@
// Copyright 2015 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.
package net
var (
hostsFilePath = "/etc/hosts"
)

11
vendor/github.com/mjl-/adns/hook_unix.go generated vendored Normal file
View file

@ -0,0 +1,11 @@
// Copyright 2015 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:build unix || js || wasip1
package adns
var (
hostsFilePath = "/etc/hosts"
)

18
vendor/github.com/mjl-/adns/hook_windows.go generated vendored Normal file
View file

@ -0,0 +1,18 @@
// Copyright 2015 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.
package adns
import (
"golang.org/x/sys/windows"
)
func windowsGetSystemDirectory() string {
p, _ := windows.GetSystemDirectory()
return p
}
var (
hostsFilePath = windowsGetSystemDirectory() + "/Drivers/etc/hosts"
)

View file

@ -52,7 +52,7 @@ var hosts struct {
func readHosts() { func readHosts() {
now := time.Now() now := time.Now()
hp := testHookHostsPath hp := hostsFilePath
if now.Before(hosts.expire) && hosts.path == hp && len(hosts.byName) > 0 { if now.Before(hosts.expire) && hosts.path == hp && len(hosts.byName) > 0 {
return return

View file

@ -0,0 +1,25 @@
// Copyright 2018 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.
package bytealg
func Count(b []byte, c byte) int {
n := 0
for _, x := range b {
if x == c {
n++
}
}
return n
}
func CountString(s string, c byte) int {
n := 0
for i := 0; i < len(s); i++ {
if s[i] == c {
n++
}
}
return n
}

View file

@ -0,0 +1,23 @@
// Copyright 2023 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.
package bytealg
func LastIndexByte(s []byte, c byte) int {
for i := len(s) - 1; i >= 0; i-- {
if s[i] == c {
return i
}
}
return -1
}
func LastIndexByteString(s string, c byte) int {
for i := len(s) - 1; i >= 0; i-- {
if s[i] == c {
return i
}
}
return -1
}

View file

@ -68,7 +68,7 @@ func SplitHostPort(hostport string) (host, port string, err error) {
j, k := 0, 0 j, k := 0, 0
// The port starts after the last colon. // The port starts after the last colon.
i := last(hostport, ':') i := bytealg.LastIndexByteString(hostport, ':')
if i < 0 { if i < 0 {
return addrErr(hostport, missingPort) return addrErr(hostport, missingPort)
} }
@ -115,7 +115,7 @@ func SplitHostPort(hostport string) (host, port string, err error) {
func splitHostZone(s string) (host, zone string) { func splitHostZone(s string) (host, zone string) {
// The IPv6 scoped addressing zone identifier starts after the // The IPv6 scoped addressing zone identifier starts after the
// last percent sign. // last percent sign.
if i := last(s, '%'); i > 0 { if i := bytealg.LastIndexByteString(s, '%'); i > 0 {
host, zone = s[:i], s[i+1:] host, zone = s[:i], s[i+1:]
} else { } else {
host = s host = s

View file

@ -42,19 +42,20 @@ var services = map[string]map[string]int{
"domain": 53, "domain": 53,
}, },
"tcp": { "tcp": {
"ftp": 21, "ftp": 21,
"ftps": 990, "ftps": 990,
"gopher": 70, // ʕ◔ϖ◔ʔ "gopher": 70, // ʕ◔ϖ◔ʔ
"http": 80, "http": 80,
"https": 443, "https": 443,
"imap2": 143, "imap2": 143,
"imap3": 220, "imap3": 220,
"imaps": 993, "imaps": 993,
"pop3": 110, "pop3": 110,
"pop3s": 995, "pop3s": 995,
"smtp": 25, "smtp": 25,
"ssh": 22, "submissions": 465,
"telnet": 23, "ssh": 22,
"telnet": 23,
}, },
} }
@ -84,12 +85,20 @@ const maxPortBufSize = len("mobility-header") + 10
func lookupPortMap(network, service string) (port int, error error) { func lookupPortMap(network, service string) (port int, error error) {
switch network { switch network {
case "tcp4", "tcp6": case "ip": // no hints
network = "tcp" if p, err := lookupPortMapWithNetwork("tcp", "ip", service); err == nil {
case "udp4", "udp6": return p, nil
network = "udp" }
return lookupPortMapWithNetwork("udp", "ip", service)
case "tcp", "tcp4", "tcp6":
return lookupPortMapWithNetwork("tcp", "tcp", service)
case "udp", "udp4", "udp6":
return lookupPortMapWithNetwork("udp", "udp", service)
} }
return 0, &DNSError{Err: "unknown network", Name: network + "/" + service}
}
func lookupPortMapWithNetwork(network, errNetwork, service string) (port int, error error) {
if m, ok := services[network]; ok { if m, ok := services[network]; ok {
var lowerService [maxPortBufSize]byte var lowerService [maxPortBufSize]byte
n := copy(lowerService[:], service) n := copy(lowerService[:], service)
@ -97,8 +106,9 @@ func lookupPortMap(network, service string) (port int, error error) {
if port, ok := m[string(lowerService[:n])]; ok && n == len(service) { if port, ok := m[string(lowerService[:n])]; ok && n == len(service) {
return port, nil return port, nil
} }
return 0, &DNSError{Err: "unknown port", Name: errNetwork + "/" + service, IsNotFound: true}
} }
return 0, &net.AddrError{Err: "unknown port", Addr: network + "/" + service} return 0, &DNSError{Err: "unknown network", Name: errNetwork + "/" + service}
} }
// ipVersion returns the provided network's IP version: '4', '6' or 0 // ipVersion returns the provided network's IP version: '4', '6' or 0
@ -399,11 +409,13 @@ func LookupPort(network, service string) (port int, err error) {
} }
// LookupPort looks up the port for the given network and service. // LookupPort looks up the port for the given network and service.
//
// The network must be one of "tcp", "tcp4", "tcp6", "udp", "udp4", "udp6" or "ip".
func (r *Resolver) LookupPort(ctx context.Context, network, service string) (port int, err error) { func (r *Resolver) LookupPort(ctx context.Context, network, service string) (port int, err error) {
port, needsLookup := parsePort(service) port, needsLookup := parsePort(service)
if needsLookup { if needsLookup {
switch network { switch network {
case "tcp", "tcp4", "tcp6", "udp", "udp4", "udp6": case "tcp", "tcp4", "tcp6", "udp", "udp4", "udp6", "ip":
case "": // a hint wildcard for Go 1.0 undocumented behavior case "": // a hint wildcard for Go 1.0 undocumented behavior
network = "ip" network = "ip"
default: default:

6
vendor/github.com/mjl-/adns/net.go generated vendored
View file

@ -79,7 +79,11 @@ type DNSError struct {
Server string // server used Server string // server used
IsTimeout bool // if true, timed out; not all timeouts set this IsTimeout bool // if true, timed out; not all timeouts set this
IsTemporary bool // if true, error is temporary; not all errors set this IsTemporary bool // if true, error is temporary; not all errors set this
IsNotFound bool // if true, host could not be found
// IsNotFound is set to true when the requested name does not
// contain any records of the requested type (data not found),
// or the name itself was not found (NXDOMAIN).
IsNotFound bool
} }
// Unwrap returns the underlying error, which could be an ExtendedError. // Unwrap returns the underlying error, which could be an ExtendedError.

22
vendor/github.com/mjl-/adns/parse.go generated vendored
View file

@ -142,28 +142,6 @@ func dtoi(s string) (n int, i int, ok bool) {
return n, i, true return n, i, true
} }
// Number of occurrences of b in s.
func count(s string, b byte) int {
n := 0
for i := 0; i < len(s); i++ {
if s[i] == b {
n++
}
}
return n
}
// Index of rightmost occurrence of b in s.
func last(s string, b byte) int {
i := len(s)
for i--; i >= 0; i-- {
if s[i] == b {
break
}
}
return i
}
// hasUpperCase tells whether the given string contains at least one upper-case. // hasUpperCase tells whether the given string contains at least one upper-case.
func hasUpperCase(s string) bool { func hasUpperCase(s string) bool {
for i := range s { for i := range s {

2
vendor/modules.txt vendored
View file

@ -7,7 +7,7 @@ github.com/cespare/xxhash/v2
# github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 # github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0
## explicit; go 1.19 ## explicit; go 1.19
github.com/matttproud/golang_protobuf_extensions/v2/pbutil github.com/matttproud/golang_protobuf_extensions/v2/pbutil
# github.com/mjl-/adns v0.0.0-20231109160910-82839fe3e6ae # github.com/mjl-/adns v0.0.0-20240308133139-6bfb89c3f854
## explicit; go 1.20 ## explicit; go 1.20
github.com/mjl-/adns github.com/mjl-/adns
github.com/mjl-/adns/internal/bytealg github.com/mjl-/adns/internal/bytealg