import (
"io/fs"
- "strings"
+ "os"
"testing"
"time"
)
want hostLookupOrder
}
-func nssStr(s string) *nssConf { return parseNSSConf(strings.NewReader(s)) }
+func nssStr(t *testing.T, s string) *nssConf {
+ f, err := os.CreateTemp(t.TempDir(), "nss")
+ if err != nil {
+ t.Fatal(err)
+ }
+ if _, err := f.WriteString(s); err != nil {
+ t.Fatal(err)
+ }
+ if err := f.Close(); err != nil {
+ t.Fatal(err)
+ }
+ return parseNSSConfFile(f.Name())
+}
// represents a dnsConfig returned by parsing a nonexistent resolv.conf
var defaultResolvConf = &dnsConfig{
forceCgoLookupHost: true,
},
resolv: defaultResolvConf,
- nss: nssStr("foo: bar"),
+ nss: nssStr(t, "foo: bar"),
hostTests: []nssHostTest{
{"foo.local", "myhostname", hostLookupCgo},
{"google.com", "myhostname", hostLookupCgo},
netGo: true,
},
resolv: defaultResolvConf,
- nss: nssStr("hosts: dns files"),
+ nss: nssStr(t, "hosts: dns files"),
hostTests: []nssHostTest{
{"x.com", "myhostname", hostLookupDNSFiles},
},
netGo: true,
},
resolv: defaultResolvConf,
- nss: nssStr("hosts: dns files something_custom"),
+ nss: nssStr(t, "hosts: dns files something_custom"),
hostTests: []nssHostTest{
{"x.com", "myhostname", hostLookupFilesDNS},
},
name: "ubuntu_trusty_avahi",
c: &conf{},
resolv: defaultResolvConf,
- nss: nssStr("hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4"),
+ nss: nssStr(t, "hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4"),
hostTests: []nssHostTest{
{"foo.local", "myhostname", hostLookupCgo},
{"foo.local.", "myhostname", hostLookupCgo},
goos: "freebsd",
},
resolv: defaultResolvConf,
- nss: nssStr("foo: bar"),
+ nss: nssStr(t, "foo: bar"),
hostTests: []nssHostTest{{"google.com", "myhostname", hostLookupFilesDNS}},
},
// On OpenBSD, no resolv.conf means no DNS.
goos: "linux",
},
resolv: defaultResolvConf,
- nss: nssStr(""),
+ nss: nssStr(t, ""),
hostTests: []nssHostTest{{"google.com", "myhostname", hostLookupFilesDNS}},
},
{
name: "files_mdns_dns",
c: &conf{},
resolv: defaultResolvConf,
- nss: nssStr("hosts: files mdns dns"),
+ nss: nssStr(t, "hosts: files mdns dns"),
hostTests: []nssHostTest{
{"x.com", "myhostname", hostLookupFilesDNS},
{"x.local", "myhostname", hostLookupCgo},
name: "dns_special_hostnames",
c: &conf{},
resolv: defaultResolvConf,
- nss: nssStr("hosts: dns"),
+ nss: nssStr(t, "hosts: dns"),
hostTests: []nssHostTest{
{"x.com", "myhostname", hostLookupDNS},
{"x\\.com", "myhostname", hostLookupCgo}, // punt on weird glibc escape
hasMDNSAllow: true,
},
resolv: defaultResolvConf,
- nss: nssStr("hosts: files mdns dns"),
+ nss: nssStr(t, "hosts: files mdns dns"),
hostTests: []nssHostTest{
{"x.com", "myhostname", hostLookupCgo},
{"x.local", "myhostname", hostLookupCgo},
name: "files_dns",
c: &conf{},
resolv: defaultResolvConf,
- nss: nssStr("hosts: files dns"),
+ nss: nssStr(t, "hosts: files dns"),
hostTests: []nssHostTest{
{"x.com", "myhostname", hostLookupFilesDNS},
{"x", "myhostname", hostLookupFilesDNS},
name: "dns_files",
c: &conf{},
resolv: defaultResolvConf,
- nss: nssStr("hosts: dns files"),
+ nss: nssStr(t, "hosts: dns files"),
hostTests: []nssHostTest{
{"x.com", "myhostname", hostLookupDNSFiles},
{"x", "myhostname", hostLookupDNSFiles},
name: "something_custom",
c: &conf{},
resolv: defaultResolvConf,
- nss: nssStr("hosts: dns files something_custom"),
+ nss: nssStr(t, "hosts: dns files something_custom"),
hostTests: []nssHostTest{
{"x.com", "myhostname", hostLookupCgo},
},
name: "myhostname",
c: &conf{},
resolv: defaultResolvConf,
- nss: nssStr("hosts: files dns myhostname"),
+ nss: nssStr(t, "hosts: files dns myhostname"),
hostTests: []nssHostTest{
{"x.com", "myhostname", hostLookupFilesDNS},
{"myhostname", "myhostname", hostLookupCgo},
name: "ubuntu14.04.02",
c: &conf{},
resolv: defaultResolvConf,
- nss: nssStr("hosts: files myhostname mdns4_minimal [NOTFOUND=return] dns mdns4"),
+ nss: nssStr(t, "hosts: files myhostname mdns4_minimal [NOTFOUND=return] dns mdns4"),
hostTests: []nssHostTest{
{"x.com", "myhostname", hostLookupFilesDNS},
{"somehostname", "myhostname", hostLookupFilesDNS},
name: "debian_squeeze",
c: &conf{},
resolv: defaultResolvConf,
- nss: nssStr("hosts: dns [success=return notfound=continue unavail=continue tryagain=continue] files [notfound=return]"),
+ nss: nssStr(t, "hosts: dns [success=return notfound=continue unavail=continue tryagain=continue] files [notfound=return]"),
hostTests: []nssHostTest{
{"x.com", "myhostname", hostLookupDNSFiles},
{"somehostname", "myhostname", hostLookupDNSFiles},
name: "resolv.conf-unknown",
c: &conf{},
resolv: &dnsConfig{servers: defaultNS, ndots: 1, timeout: 5, attempts: 2, unknownOpt: true},
- nss: nssStr("foo: bar"),
+ nss: nssStr(t, "foo: bar"),
hostTests: []nssHostTest{{"google.com", "myhostname", hostLookupCgo}},
},
// Android should always use cgo.
goos: "android",
},
resolv: defaultResolvConf,
- nss: nssStr(""),
+ nss: nssStr(t, ""),
hostTests: []nssHostTest{
{"x.com", "myhostname", hostLookupCgo},
},
netCgo: true,
},
resolv: defaultResolvConf,
- nss: nssStr(""),
+ nss: nssStr(t, ""),
hostTests: []nssHostTest{
{"localhost", "myhostname", hostLookupFilesDNS},
},
import (
"errors"
"internal/bytealg"
- "io"
"os"
"sync"
"time"
}
func parseNSSConfFile(file string) *nssConf {
- f, err := os.Open(file)
+ f, err := open(file)
if err != nil {
return &nssConf{err: err}
}
- defer f.Close()
- stat, err := f.Stat()
+ defer f.close()
+ mtime, _, err := f.stat()
if err != nil {
return &nssConf{err: err}
}
conf := parseNSSConf(f)
- conf.mtime = stat.ModTime()
+ conf.mtime = mtime
return conf
}
-func parseNSSConf(r io.Reader) *nssConf {
- slurp, err := readFull(r)
- if err != nil {
- return &nssConf{err: err}
- }
+func parseNSSConf(f *file) *nssConf {
conf := new(nssConf)
- conf.err = foreachLine(slurp, func(line []byte) error {
+ for line, ok := f.readLine(); ok; line, ok = f.readLine() {
line = trimSpace(removeComment(line))
if len(line) == 0 {
- return nil
+ continue
}
- colon := bytealg.IndexByte(line, ':')
+ colon := bytealg.IndexByteString(line, ':')
if colon == -1 {
- return errors.New("no colon on line")
+ conf.err = errors.New("no colon on line")
+ return conf
}
- db := string(trimSpace(line[:colon]))
+ db := trimSpace(line[:colon])
srcs := line[colon+1:]
for {
srcs = trimSpace(srcs)
if len(srcs) == 0 {
break
}
- sp := bytealg.IndexByte(srcs, ' ')
+ sp := bytealg.IndexByteString(srcs, ' ')
var src string
if sp == -1 {
- src = string(srcs)
- srcs = nil // done
+ src = srcs
+ srcs = "" // done
} else {
- src = string(srcs[:sp])
+ src = srcs[:sp]
srcs = trimSpace(srcs[sp+1:])
}
var criteria []nssCriterion
// See if there's a criteria block in brackets.
if len(srcs) > 0 && srcs[0] == '[' {
- bclose := bytealg.IndexByte(srcs, ']')
+ bclose := bytealg.IndexByteString(srcs, ']')
if bclose == -1 {
- return errors.New("unclosed criterion bracket")
+ conf.err = errors.New("unclosed criterion bracket")
+ return conf
}
var err error
criteria, err = parseCriteria(srcs[1:bclose])
if err != nil {
- return errors.New("invalid criteria: " + string(srcs[1:bclose]))
+ conf.err = errors.New("invalid criteria: " + srcs[1:bclose])
+ return conf
}
srcs = srcs[bclose+1:]
}
criteria: criteria,
})
}
- return nil
- })
+ }
return conf
}
// parses "foo=bar !foo=bar"
-func parseCriteria(x []byte) (c []nssCriterion, err error) {
- err = foreachField(x, func(f []byte) error {
+func parseCriteria(x string) (c []nssCriterion, err error) {
+ err = foreachField(x, func(f string) error {
not := false
if len(f) > 0 && f[0] == '!' {
not = true
if len(f) < 3 {
return errors.New("criterion too short")
}
- eq := bytealg.IndexByte(f, '=')
+ eq := bytealg.IndexByteString(f, '=')
if eq == -1 {
return errors.New("criterion lacks equal sign")
}
- lowerASCIIBytes(f)
+ if hasUpperCase(f) {
+ lower := []byte(f)
+ lowerASCIIBytes(lower)
+ f = string(lower)
+ }
c = append(c, nssCriterion{
negate: not,
- status: string(f[:eq]),
- action: string(f[eq+1:]),
+ status: f[:eq],
+ action: f[eq+1:],
})
return nil
})
return
}
+func (f *file) stat() (mtime time.Time, size int64, err error) {
+ st, err := f.file.Stat()
+ if err != nil {
+ return time.Time{}, 0, err
+ }
+ return st.ModTime(), st.Size(), nil
+}
+
func open(name string) (*file, error) {
fd, err := os.Open(name)
if err != nil {
}
// trimSpace returns x without any leading or trailing ASCII whitespace.
-func trimSpace(x []byte) []byte {
+func trimSpace(x string) string {
for len(x) > 0 && isSpace(x[0]) {
x = x[1:]
}
// removeComment returns line, removing any '#' byte and any following
// bytes.
-func removeComment(line []byte) []byte {
- if i := bytealg.IndexByte(line, '#'); i != -1 {
+func removeComment(line string) string {
+ if i := bytealg.IndexByteString(line, '#'); i != -1 {
return line[:i]
}
return line
}
-// foreachLine runs fn on each line of x.
-// Each line (except for possibly the last) ends in '\n'.
-// It returns the first non-nil error returned by fn.
-func foreachLine(x []byte, fn func(line []byte) error) error {
- for len(x) > 0 {
- nl := bytealg.IndexByte(x, '\n')
- if nl == -1 {
- return fn(x)
- }
- line := x[:nl+1]
- x = x[nl+1:]
- if err := fn(line); err != nil {
- return err
- }
- }
- return nil
-}
-
// foreachField runs fn on each non-empty run of non-space bytes in x.
// It returns the first non-nil error returned by fn.
-func foreachField(x []byte, fn func(field []byte) error) error {
+func foreachField(x string, fn func(field string) error) error {
x = trimSpace(x)
for len(x) > 0 {
- sp := bytealg.IndexByte(x, ' ')
+ sp := bytealg.IndexByteString(x, ' ')
if sp == -1 {
return fn(x)
}
}
return true
}
-
-func readFull(r io.Reader) (all []byte, err error) {
- buf := make([]byte, 1024)
- for {
- n, err := r.Read(buf)
- all = append(all, buf[:n]...)
- if err == io.EOF {
- return all, nil
- }
- if err != nil {
- return nil, err
- }
- }
-}