From 4f5896b2cfd316cb388d39e3772439f48b215fda Mon Sep 17 00:00:00 2001 From: Shulhan Date: Tue, 19 Apr 2022 22:18:26 +0700 Subject: all: refactoring the hosts blocks format and location This changes add new directory called "block.d" under rescached. This directory contains hosts file fetched from URL defined in hosts block configuration. In this way, we did not mixed between user created hosts file and external hosts file. In the configuration file, we changes the hosts block format from single line hosts_block = into subsection with dynamic name, [block.d ""] name = url = --- Makefile | 2 + _www/hosts_blocks/index.html | 14 ++-- cmd/rescached/rescached.cfg | 19 ++++- cmd/rescached/rescached.cfg.test | 23 ++++-- environment.go | 84 +++++++++------------ environment_test.go | 74 +++++++++++++++++++ go.mod | 2 +- go.sum | 4 +- hosts_block.go | 153 +++++++++++++++++++-------------------- hosts_block_test.go | 104 ++++++++++++++++++++++++++ httpd.go | 52 +++++-------- memfs_generate.go | 8 +- rescached.go | 28 ++++++- testdata/rescached.cfg.test.out | 37 ++++++++++ 14 files changed, 419 insertions(+), 185 deletions(-) create mode 100644 hosts_block_test.go create mode 100644 testdata/rescached.cfg.test.out diff --git a/Makefile b/Makefile index eed784e..02fcb9a 100644 --- a/Makefile +++ b/Makefile @@ -100,8 +100,10 @@ serve-doc: install-common: mkdir -p $(PREFIX)/etc/rescached + mkdir -p $(PREFIX)/etc/rescached/block.d mkdir -p $(PREFIX)/etc/rescached/hosts.d mkdir -p $(PREFIX)/etc/rescached/zone.d + cp $(RESCACHED_CFG) $(PREFIX)/etc/rescached/ cp testdata/localhost.cert.pem $(PREFIX)/etc/rescached/ cp testdata/localhost.key.pem $(PREFIX)/etc/rescached/ diff --git a/_www/hosts_blocks/index.html b/_www/hosts_blocks/index.html index 331ec2b..79b79de 100644 --- a/_www/hosts_blocks/index.html +++ b/_www/hosts_blocks/index.html @@ -90,8 +90,8 @@ renderHostsBlocks(env.HostsBlocks) } - function onCheckHostblock(idx, val) { - resc.env.HostsBlocks[idx].IsEnabled = val + function onCheckHostblock(key, val) { + resc.env.HostsBlocks[key].IsEnabled = val } async function updateHostsBlocks() { @@ -109,8 +109,12 @@ let parent = document.getElementById("HostsBlocks") parent.innerHTML = "" - for (let x = 0; x < hostsBlocks.length; x++) { - let hostsBlock = hostsBlocks[x] + for (var k in hostsBlocks) { + if (!hostsBlocks.hasOwnProperty(k)) { + continue; + } + + let hostsBlock = hostsBlocks[k] let item = document.createElement("div") item.classList.add("item") item.innerHTML = ` @@ -118,7 +122,7 @@ diff --git a/cmd/rescached/rescached.cfg b/cmd/rescached/rescached.cfg index bbca672..fb60b6c 100644 --- a/cmd/rescached/rescached.cfg +++ b/cmd/rescached/rescached.cfg @@ -12,10 +12,21 @@ file.resolvconf= debug=0 wui.listen = 127.0.0.1:5380 -hosts_block = http://pgl.yoyo.org/adservers/serverlist.php?hostformat=hosts&showintro=0&startdate[day]=&startdate[month]=&startdate[year]=&mimetype=plaintext -hosts_block = http://www.malwaredomainlist.com/hostslist/hosts.txt -hosts_block = http://winhelp2002.mvps.org/hosts.txt -hosts_block = http://someonewhocares.org/hosts/hosts +[block.d "pgl.yoyo.org"] +name = pgl.yoyo.org +url = http://pgl.yoyo.org/adservers/serverlist.php?hostformat=hosts&showintro=0&startdate[day]=&startdate[month]=&startdate[year]=&mimetype=plaintext + +[block.d "someonewhocares.org"] +name = someonewhocares.org +url = http://someonewhocares.org/hosts/hosts + +[block.d "winhelp2002.mvps.org"] +name = winhelp2002.mvps.org +url = http://winhelp2002.mvps.org/hosts.txt + +[block.d "www.malwaredomainlist.com"] +name = www.malwaredomainlist.com +url = http://www.malwaredomainlist.com/hostslist/hosts.txt [dns "server"] parent=udp://1.1.1.1 diff --git a/cmd/rescached/rescached.cfg.test b/cmd/rescached/rescached.cfg.test index a99ae95..d1f37ba 100644 --- a/cmd/rescached/rescached.cfg.test +++ b/cmd/rescached/rescached.cfg.test @@ -12,16 +12,27 @@ file.resolvconf= debug=2 wui.listen = 127.0.0.1:5381 -hosts_block = http://pgl.yoyo.org/adservers/serverlist.php?hostformat=hosts&showintro=0&startdate[day]=&startdate[month]=&startdate[year]=&mimetype=plaintext -hosts_block = http://www.malwaredomainlist.com/hostslist/hosts.txt -hosts_block = http://winhelp2002.mvps.org/hosts.txt -hosts_block = http://someonewhocares.org/hosts/hosts +[block.d "pgl.yoyo.org"] +name = pgl.yoyo.org +url = http://pgl.yoyo.org/adservers/serverlist.php?hostformat=hosts&showintro=0&startdate[day]=&startdate[month]=&startdate[year]=&mimetype=plaintext + +[block.d "www.malwaredomainlist.com"] +name = www.malwaredomainlist.com +url = http://www.malwaredomainlist.com/hostslist/hosts.txt + +[block.d "winhelp2002.mvps.org"] +name = winhelp2002.mvps.org +url = http://winhelp2002.mvps.org/hosts.txt + +[block.d "someonewhocares.org"] +name = someonewhocares.org +url = http://someonewhocares.org/hosts/hosts [dns "server"] -#parent=udp://62.171.181.13 +parent=udp://10.8.0.1 #parent=tcp://62.171.181.13 ## DNS over TLS -parent=https://62.171.181.13 +#parent=https://10.8.0.1 ## DNS over HTTPS #parent=https://kilabit.info/dns-query diff --git a/environment.go b/environment.go index a4d7923..aac538e 100644 --- a/environment.go +++ b/environment.go @@ -23,6 +23,7 @@ const ( ) const ( + sectionNameBlockd = "block.d" sectionNameDNS = "dns" sectionNameRescached = "rescached" @@ -30,11 +31,12 @@ const ( keyDebug = "debug" keyFileResolvConf = "file.resolvconf" + keyName = "name" + keyUrl = "url" keyCachePruneDelay = "cache.prune_delay" keyCachePruneThreshold = "cache.prune_threshold" keyDohBehindProxy = "doh.behind_proxy" - keyHostsBlock = "hosts_block" keyHTTPPort = "http.port" keyListen = "listen" keyParent = "parent" @@ -44,6 +46,7 @@ const ( keyTLSPort = "tls.port" keyTLSPrivateKey = "tls.private_key" + dirBlock = "/etc/rescached/block.d" dirHosts = "/etc/rescached/hosts.d" dirZone = "/etc/rescached/zone.d" ) @@ -52,9 +55,7 @@ var ( mfsWww *memfs.MemFS ) -// // Environment for running rescached. -// type Environment struct { HostsFiles map[string]*dns.HostsFile Zones map[string]*dns.Zone @@ -63,8 +64,8 @@ type Environment struct { FileResolvConf string `ini:"rescached::file.resolvconf"` WUIListen string `ini:"rescached::wui.listen"` - HostsBlocksRaw []string `ini:"rescached::hosts_block" json:"-"` - HostsBlocks []*hostsBlock + HostsBlocks map[string]*hostsBlock `ini:"block.d"` + hostsBlocksFile map[string]*dns.HostsFile // The options for WUI HTTP server. HttpdOptions *libhttp.ServerOptions `json:"-"` @@ -74,9 +75,7 @@ type Environment struct { Debug int `ini:"rescached::debug"` } -// // LoadEnvironment initialize environment from configuration file. -// func LoadEnvironment(fileConfig string) (env *Environment, err error) { var ( logp = "LoadEnvironment" @@ -104,11 +103,10 @@ func LoadEnvironment(fileConfig string) (env *Environment, err error) { return env, nil } -// // newEnvironment create and initialize options with default values. -// func newEnvironment() *Environment { return &Environment{ + hostsBlocksFile: make(map[string]*dns.HostsFile), HttpdOptions: &libhttp.ServerOptions{ Memfs: mfsWww, Address: defWuiAddress, @@ -119,9 +117,7 @@ func newEnvironment() *Environment { } } -// // init check and initialize the environment instance with default values. -// func (env *Environment) init() (err error) { if len(env.WUIListen) == 0 { env.WUIListen = defWuiAddress @@ -172,14 +168,13 @@ func (env *Environment) init() (err error) { } func (env *Environment) initHostsBlock() { - env.HostsBlocks = hostsBlockSources - - for x, v := range env.HostsBlocksRaw { - env.HostsBlocksRaw[x] = strings.ToLower(v) - } + var ( + dirBase = "" - for _, hb := range env.HostsBlocks { - hb.init(env.HostsBlocksRaw) + hb *hostsBlock + ) + for _, hb = range env.HostsBlocks { + hb.init(dirBase) } } @@ -216,8 +211,8 @@ func (env *Environment) save(file string) (in *ini.Ini, err error) { var ( logp = "save" - hb *hostsBlock - ns string + hb *hostsBlock + vstr string ) if len(file) == 0 { @@ -233,38 +228,28 @@ func (env *Environment) save(file string) (in *ini.Ini, err error) { in.Set(sectionNameRescached, "", keyDebug, strconv.Itoa(env.Debug)) in.Set(sectionNameRescached, "", keyWUIListen, strings.TrimSpace(env.WUIListen)) - in.UnsetAll(sectionNameRescached, "", keyHostsBlock) - for _, hb = range env.HostsBlocks { - if hb.IsEnabled { - in.Add(sectionNameRescached, "", keyHostsBlock, hb.URL) - } + in.Set(sectionNameBlockd, hb.Name, keyName, hb.Name) + in.Set(sectionNameBlockd, hb.Name, keyUrl, hb.URL) } in.UnsetAll(sectionNameDNS, subNameServer, keyParent) - for _, ns = range env.NameServers { - in.Add(sectionNameDNS, subNameServer, keyParent, ns) + for _, vstr = range env.NameServers { + in.Add(sectionNameDNS, subNameServer, keyParent, vstr) } - in.Set(sectionNameDNS, subNameServer, keyListen, - env.ServerOptions.ListenAddress) + in.Set(sectionNameDNS, subNameServer, keyListen, env.ListenAddress) - in.Set(sectionNameDNS, subNameServer, keyHTTPPort, - strconv.Itoa(int(env.ServerOptions.HTTPPort))) + in.Set(sectionNameDNS, subNameServer, keyHTTPPort, strconv.Itoa(int(env.HTTPPort))) - in.Set(sectionNameDNS, subNameServer, keyTLSPort, - strconv.Itoa(int(env.ServerOptions.TLSPort))) - in.Set(sectionNameDNS, subNameServer, keyTLSCertificate, - env.ServerOptions.TLSCertFile) - in.Set(sectionNameDNS, subNameServer, keyTLSPrivateKey, - env.ServerOptions.TLSPrivateKey) - in.Set(sectionNameDNS, subNameServer, keyTLSAllowInsecure, - fmt.Sprintf("%t", env.ServerOptions.TLSAllowInsecure)) - in.Set(sectionNameDNS, subNameServer, keyDohBehindProxy, - fmt.Sprintf("%t", env.ServerOptions.DoHBehindProxy)) + in.Set(sectionNameDNS, subNameServer, keyTLSPort, strconv.Itoa(int(env.TLSPort))) + in.Set(sectionNameDNS, subNameServer, keyTLSCertificate, env.TLSCertFile) + in.Set(sectionNameDNS, subNameServer, keyTLSPrivateKey, env.TLSPrivateKey) + in.Set(sectionNameDNS, subNameServer, keyTLSAllowInsecure, fmt.Sprintf("%t", env.TLSAllowInsecure)) + in.Set(sectionNameDNS, subNameServer, keyDohBehindProxy, fmt.Sprintf("%t", env.DoHBehindProxy)) - in.Set(sectionNameDNS, subNameServer, keyCachePruneDelay, env.ServerOptions.PruneDelay.String()) - in.Set(sectionNameDNS, subNameServer, keyCachePruneThreshold, env.ServerOptions.PruneThreshold.String()) + in.Set(sectionNameDNS, subNameServer, keyCachePruneDelay, env.PruneDelay.String()) + in.Set(sectionNameDNS, subNameServer, keyCachePruneThreshold, env.PruneThreshold.String()) return in, nil } @@ -277,20 +262,23 @@ func (env *Environment) Write(w io.Writer) (err error) { var ( logp = "Environment.Write" - in *ini.Ini + outb []byte ) - in, err = env.save(env.fileConfig) + outb, err = ini.Marshal(env) if err != nil { return fmt.Errorf("%s: %w", logp, err) } - return in.Write(w) + _, err = w.Write(outb) + if err != nil { + return fmt.Errorf("%s: %w", logp, err) + } + + return nil } -// // write the options values back to file. -// func (env *Environment) write(file string) (err error) { var ( in *ini.Ini diff --git a/environment_test.go b/environment_test.go index e104ece..39b497e 100644 --- a/environment_test.go +++ b/environment_test.go @@ -4,9 +4,12 @@ package rescached import ( + "bytes" + "os" "testing" "github.com/shuLhan/share/lib/dns" + libhttp "github.com/shuLhan/share/lib/http" "github.com/shuLhan/share/lib/ini" "github.com/shuLhan/share/lib/test" ) @@ -54,3 +57,74 @@ parent = https://kilabit.info/dns-query test.Assert(t, "environment", c.exp, got) } } + +func TestLoadEnvironment(t *testing.T) { + var ( + expEnv = &Environment{ + fileConfig: "cmd/rescached/rescached.cfg.test", + + WUIListen: "127.0.0.1:5381", + HostsBlocks: map[string]*hostsBlock{ + "pgl.yoyo.org": &hostsBlock{ + Name: "pgl.yoyo.org", + URL: `http://pgl.yoyo.org/adservers/serverlist.php?hostformat=hosts&showintro=0&startdate[day]=&startdate[month]=&startdate[year]=&mimetype=plaintext`, + }, + "www.malwaredomainlist.com": &hostsBlock{ + Name: "www.malwaredomainlist.com", + URL: `http://www.malwaredomainlist.com/hostslist/hosts.txt`, + }, + "winhelp2002.mvps.org": &hostsBlock{ + Name: "winhelp2002.mvps.org", + URL: `http://winhelp2002.mvps.org/hosts.txt`, + }, + "someonewhocares.org": &hostsBlock{ + Name: "someonewhocares.org", + URL: `http://someonewhocares.org/hosts/hosts`, + }, + }, + HttpdOptions: &libhttp.ServerOptions{ + Address: defWuiAddress, + }, + ServerOptions: dns.ServerOptions{ + ListenAddress: "127.0.0.1:5350", + NameServers: []string{ + "udp://10.8.0.1", + }, + TLSAllowInsecure: true, + }, + + Debug: 2, + } + expBuffer []byte + + gotEnv *Environment + gotBuffer bytes.Buffer + err error + ) + + expBuffer, err = os.ReadFile("testdata/rescached.cfg.test.out") + if err != nil { + t.Fatal(err) + } + + gotEnv, err = LoadEnvironment("cmd/rescached/rescached.cfg.test") + if err != nil { + t.Fatal(err) + } + + gotEnv.HttpdOptions.Memfs = nil + + test.Assert(t, "LoadEnvironment", expEnv, gotEnv) + + gotEnv.HostsBlocks["test"] = &hostsBlock{ + Name: "test", + URL: "http://someurl", + } + + err = gotEnv.Write(&gotBuffer) + if err != nil { + t.Fatal(err) + } + + test.Assert(t, "Write", expBuffer, gotBuffer.Bytes()) +} diff --git a/go.mod b/go.mod index 4822aa2..054bdf7 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,6 @@ module github.com/shuLhan/rescached-go/v4 go 1.16 -require github.com/shuLhan/share v0.36.1-0.20220414192731-ceba0704c6fc +require github.com/shuLhan/share v0.36.1-0.20220418175009-e5c85cdbfb18 //replace github.com/shuLhan/share => ../share diff --git a/go.sum b/go.sum index 1ce3cad..c6d95c1 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/shuLhan/share v0.36.1-0.20220414192731-ceba0704c6fc h1:PRCQ/x8ZzOv+dlSNl3NVZgkq3rpQd4Wwz33Q73T6P2k= -github.com/shuLhan/share v0.36.1-0.20220414192731-ceba0704c6fc/go.mod h1:laKGR1DNboj8+INRIC9VFYRiVEu/IIjrLurUmTHXkw0= +github.com/shuLhan/share v0.36.1-0.20220418175009-e5c85cdbfb18 h1:kC1DQeauBOs830pBOx0oD7u4WWdV74dpy1/QR3kPNqE= +github.com/shuLhan/share v0.36.1-0.20220418175009-e5c85cdbfb18/go.mod h1:laKGR1DNboj8+INRIC9VFYRiVEu/IIjrLurUmTHXkw0= golang.org/x/crypto v0.0.0-20220131195533-30dcbda58838/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk= diff --git a/hosts_block.go b/hosts_block.go index 4a829be..d4c6722 100644 --- a/hosts_block.go +++ b/hosts_block.go @@ -14,43 +14,84 @@ import ( "time" ) -// -// List of blocked hosts sources. -// -var hostsBlockSources = []*hostsBlock{{ - Name: "pgl.yoyo.org", - URL: `http://pgl.yoyo.org/adservers/serverlist.php?hostformat=hosts&showintro=0&startdate[day]=&startdate[month]=&startdate[year]=&mimetype=plaintext`, -}, { - Name: "www.malwaredomainlist.com", - URL: `http://www.malwaredomainlist.com/hostslist/hosts.txt`, -}, { - Name: "winhelp2002.mvps.org", - URL: `http://winhelp2002.mvps.org/hosts.txt`, -}, { - Name: "someonewhocares.org", - URL: `http://someonewhocares.org/hosts/hosts`, -}} +const ( + lastUpdatedFormat = "2006-01-02 15:04:05 MST" +) type hostsBlock struct { lastUpdated time.Time - Name string // Derived from hostname in URL. - URL string - LastUpdated string - file string + Name string `ini:"::name"` // Derived from hostname in URL. + URL string `ini:"::url"` + + file string + fileDisabled string + LastUpdated string + + IsEnabled bool // True if the hosts file un-hidden in block.d directory. + isFileExist bool // True if the file exist and enabled or disabled. +} - IsEnabled bool +// disable the hosts block by prefixing the file name with single dot. +func (hb *hostsBlock) disable() (err error) { + err = os.Rename(hb.file, hb.fileDisabled) + if err != nil { + return fmt.Errorf("disable: %w", err) + } + hb.IsEnabled = false + return nil } -func (hb *hostsBlock) init(sources []string) { - for _, src := range sources { - if hb.URL == src { - hb.IsEnabled = true - break +// enable the hosts block file by removing the dot prefix from file name. +func (hb *hostsBlock) enable() (err error) { + if hb.isFileExist { + err = os.Rename(hb.fileDisabled, hb.file) + } else { + err = os.WriteFile(hb.file, []byte(""), 0600) + } + if err != nil { + return fmt.Errorf("enable: %w", err) + } + hb.IsEnabled = true + hb.isFileExist = true + return nil +} + +func (hb *hostsBlock) init(dirBase string) { + var ( + fi os.FileInfo + err error + ) + + hb.file = filepath.Join(dirBase, dirBlock, hb.Name) + hb.fileDisabled = filepath.Join(dirBase, dirBlock, "."+hb.Name) + + fi, err = os.Stat(hb.file) + if err != nil { + hb.IsEnabled = false + + fi, err = os.Stat(hb.fileDisabled) + if err != nil { + return } + + hb.isFileExist = true + } else { + hb.IsEnabled = true + hb.isFileExist = true } - hb.initLastUpdated() + hb.lastUpdated = fi.ModTime() + hb.LastUpdated = hb.lastUpdated.Format(lastUpdatedFormat) +} + +// isOld will return true if the host file has not been updated since seven +// days. +func (hb *hostsBlock) isOld() bool { + oneWeek := 7 * 24 * time.Hour + lastWeek := time.Now().Add(-1 * oneWeek) + + return hb.lastUpdated.Before(lastWeek) } func (hb *hostsBlock) update() (err error) { @@ -86,61 +127,17 @@ func (hb *hostsBlock) update() (err error) { body = bytes.ReplaceAll(body, []byte("\r\n"), []byte("\n")) - err = os.WriteFile(hb.file, body, 0644) - if err != nil { - return fmt.Errorf("%s %q: %w", logp, hb.Name, err) + if hb.IsEnabled { + err = os.WriteFile(hb.file, body, 0644) + } else { + err = os.WriteFile(hb.fileDisabled, body, 0644) } - - hb.initLastUpdated() - - return nil -} - -func (hb *hostsBlock) hide() (err error) { - oldFileName := filepath.Join(dirHosts, hb.Name) - newFileName := filepath.Join(dirHosts, "."+hb.Name) - err = os.Rename(oldFileName, newFileName) if err != nil { - return err - } - - hb.file = newFileName - - return nil -} - -func (hb *hostsBlock) isOld() bool { - oneWeek := 7 * 24 * time.Hour - lastWeek := time.Now().Add(-1 * oneWeek) - - return hb.lastUpdated.Before(lastWeek) -} - -// -// unhide the hosts block file. -// -func (hb *hostsBlock) unhide() (err error) { - oldFileName := filepath.Join(dirHosts, "."+hb.Name) - newFileName := filepath.Join(dirHosts, hb.Name) - err = os.Rename(oldFileName, newFileName) - if err != nil { - return err + return fmt.Errorf("%s %q: %w", logp, hb.Name, err) } - hb.file = newFileName - hb.initLastUpdated() + hb.lastUpdated = time.Now() + hb.LastUpdated = hb.lastUpdated.Format(lastUpdatedFormat) return nil } - -func (hb *hostsBlock) initLastUpdated() { - hb.file = filepath.Join(dirHosts, hb.Name) - fi, err := os.Stat(hb.file) - if err != nil { - hb.IsEnabled = false - return - } - - hb.lastUpdated = fi.ModTime() - hb.LastUpdated = hb.lastUpdated.Format("2006-01-02 15:04:05 MST") -} diff --git a/hosts_block_test.go b/hosts_block_test.go new file mode 100644 index 0000000..456fb63 --- /dev/null +++ b/hosts_block_test.go @@ -0,0 +1,104 @@ +// SPDX-FileCopyrightText: 2020 M. Shulhan +// SPDX-License-Identifier: GPL-3.0-or-later + +package rescached + +import ( + "os" + "path/filepath" + "testing" + + "github.com/shuLhan/share/lib/test" +) + +func TestHostsBlock_init(t *testing.T) { + type testCase struct { + desc string + hb hostsBlock + exp hostsBlock + } + + var ( + testDirBase = t.TempDir() + testDirBlock = filepath.Join(testDirBase, dirBlock) + fileEnabled = "fileEnabled" + fileDisabled = "fileDisabled" + fileNotExist = "fileNotExist" + hostsFileEnabled = filepath.Join(testDirBlock, fileEnabled) + hostsFileDisabled = filepath.Join(testDirBlock, "."+fileDisabled) + + fiEnabled os.FileInfo + fiDisabled os.FileInfo + cases []testCase + c testCase + err error + ) + + err = os.MkdirAll(testDirBlock, 0700) + if err != nil { + t.Fatal(err) + } + + err = os.WriteFile(hostsFileEnabled, []byte("127.0.0.2 localhost"), 0600) + if err != nil { + t.Fatal(err) + } + err = os.WriteFile(hostsFileDisabled, []byte("127.0.0.2 localhost"), 0600) + if err != nil { + t.Fatal(err) + } + + fiEnabled, err = os.Stat(hostsFileEnabled) + if err != nil { + t.Fatal(err) + } + fiDisabled, err = os.Stat(hostsFileDisabled) + if err != nil { + t.Fatal(err) + } + + cases = []testCase{{ + desc: "With hosts block file enabled", + hb: hostsBlock{ + Name: fileEnabled, + }, + exp: hostsBlock{ + Name: fileEnabled, + lastUpdated: fiEnabled.ModTime(), + LastUpdated: fiEnabled.ModTime().Format(lastUpdatedFormat), + file: hostsFileEnabled, + fileDisabled: filepath.Join(testDirBlock, "."+fileEnabled), + IsEnabled: true, + isFileExist: true, + }, + }, { + desc: "With hosts block file disabled", + hb: hostsBlock{ + Name: fileDisabled, + }, + exp: hostsBlock{ + Name: fileDisabled, + lastUpdated: fiDisabled.ModTime(), + LastUpdated: fiDisabled.ModTime().Format(lastUpdatedFormat), + file: filepath.Join(testDirBlock, fileDisabled), + fileDisabled: hostsFileDisabled, + isFileExist: true, + }, + }, { + desc: "With hosts block file not exist", + hb: hostsBlock{ + Name: fileNotExist, + }, + exp: hostsBlock{ + Name: fileNotExist, + file: filepath.Join(testDirBlock, fileNotExist), + fileDisabled: filepath.Join(testDirBlock, "."+fileNotExist), + }, + }} + + for _, c = range cases { + c.hb.init(testDirBase) + + test.Assert(t, c.desc, c.exp, c.hb) + } +} diff --git a/httpd.go b/httpd.go index 6e51ddf..17a8442 100644 --- a/httpd.go +++ b/httpd.go @@ -5,11 +5,9 @@ package rescached import ( "encoding/json" - "errors" "fmt" "log" "net/http" - "os" "path/filepath" "regexp" "strconv" @@ -376,19 +374,17 @@ func (srv *Server) httpApiEnvironmentUpdate(epr *libhttp.EndpointRequest) (resBo return json.Marshal(&res) } -// // apiHostsBlockUpdate set the HostsBlock to be enabled or disabled. // // If its status changes to enabled, unhide the hosts block file, populate the -// hosts back to caches, and add it to list of HostsFiles. +// hosts back to caches, and add it to list of hostsBlocksFile. // // If its status changes to disabled, remove the hosts from caches, hide it, -// and remove it from list of HostsFiles. -// +// and remove it from list of hostsBlocksFile. func (srv *Server) apiHostsBlockUpdate(epr *libhttp.EndpointRequest) (resBody []byte, err error) { var ( res = libhttp.EndpointResponse{} - hostsBlocks = make([]*hostsBlock, 0) + hostsBlocks = make(map[string]*hostsBlock, 0) hbx *hostsBlock hby *hostsBlock @@ -444,39 +440,32 @@ func (srv *Server) apiHostsBlockUpdate(epr *libhttp.EndpointRequest) (resBody [] func (srv *Server) hostsBlockEnable(hb *hostsBlock) (err error) { var ( + logp = "hostsBlockEnable" + hfile *dns.HostsFile ) - hb.IsEnabled = true - - err = hb.unhide() + err = hb.enable() if err != nil { - if !errors.Is(err, os.ErrNotExist) { - return err - } - // File not exist, fetch new from server. - err = hb.update() - if err != nil { - return err - } + return fmt.Errorf("%s: %w", logp, err) } - hfile, err = dns.ParseHostsFile(filepath.Join(dirHosts, hb.Name)) + err = hb.update() if err != nil { - return err + return fmt.Errorf("%s: %w", logp, err) } - err = srv.dns.PopulateCachesByRR(hfile.Records, hfile.Path) + hfile, err = dns.ParseHostsFile(hb.file) if err != nil { - return err + return fmt.Errorf("%s: %w", logp, err) } - err = hb.update() + err = srv.dns.PopulateCachesByRR(hfile.Records, hfile.Path) if err != nil { - return err + return fmt.Errorf("%s: %w", logp, err) } - srv.env.HostsFiles[hfile.Name] = hfile + srv.env.hostsBlocksFile[hfile.Name] = hfile return nil } @@ -488,18 +477,19 @@ func (srv *Server) hostsBlockDisable(hb *hostsBlock) (err error) { hfile *dns.HostsFile ) - hfile = srv.env.HostsFiles[hb.Name] + hfile = srv.env.hostsBlocksFile[hb.Name] if hfile == nil { return fmt.Errorf("%s: unknown hosts block: %q", logp, hb.Name) } srv.dns.RemoveLocalCachesByNames(hfile.Names()) - err = hb.hide() + err = hb.disable() if err != nil { return fmt.Errorf("%s: %w", logp, err) } - delete(srv.env.HostsFiles, hb.Name) + + delete(srv.env.hostsBlocksFile, hfile.Name) return nil } @@ -602,9 +592,7 @@ func (srv *Server) apiHostsFileDelete(epr *libhttp.EndpointRequest) (resbody []b return json.Marshal(&res) } -// // apiHostsFileRRCreate create new record and save it to the hosts file. -// func (srv *Server) apiHostsFileRRCreate(epr *libhttp.EndpointRequest) (resbody []byte, err error) { var ( res = libhttp.EndpointResponse{} @@ -806,9 +794,7 @@ func (srv *Server) apiZoneDelete(epr *libhttp.EndpointRequest) (resbody []byte, return json.Marshal(&res) } -// // apiZoneRRCreate create new RR for the zone file. -// func (srv *Server) apiZoneRRCreate(epr *libhttp.EndpointRequest) (resbody []byte, err error) { var ( res = libhttp.EndpointResponse{} @@ -908,9 +894,7 @@ func (srv *Server) apiZoneRRCreate(epr *libhttp.EndpointRequest) (resbody []byte return json.Marshal(&res) } -// // apiZoneRRDelete delete RR from the zone file. -// func (srv *Server) apiZoneRRDelete(epr *libhttp.EndpointRequest) (resbody []byte, err error) { var ( res = libhttp.EndpointResponse{} diff --git a/memfs_generate.go b/memfs_generate.go index 9f1aad1..b9652ff 100644 --- a/memfs_generate.go +++ b/memfs_generate.go @@ -127,12 +127,12 @@ func generate__www_hosts_blocks_index_html() *memfs.Node { Path: "/hosts_blocks/index.html", ContentType: "text/html; charset=utf-8", GenFuncName: "generate__www_hosts_blocks_index_html", - Content: []byte("\x3C\x21\x44\x4F\x43\x54\x59\x50\x45\x20\x68\x74\x6D\x6C\x3E\x0A\x3C\x21\x2D\x2D\x20\x53\x50\x44\x58\x2D\x46\x69\x6C\x65\x43\x6F\x70\x79\x72\x69\x67\x68\x74\x54\x65\x78\x74\x3A\x20\x32\x30\x32\x31\x20\x4D\x2E\x20\x53\x68\x75\x6C\x68\x61\x6E\x20\x3C\x6D\x73\x40\x6B\x69\x6C\x61\x62\x69\x74\x2E\x69\x6E\x66\x6F\x20\x2D\x2D\x3E\x0A\x3C\x21\x2D\x2D\x20\x53\x50\x44\x58\x2D\x4C\x69\x63\x65\x6E\x73\x65\x2D\x49\x64\x65\x6E\x74\x69\x66\x69\x65\x72\x3A\x20\x47\x50\x4C\x2D\x33\x2E\x30\x2D\x6F\x72\x2D\x6C\x61\x74\x65\x72\x20\x2D\x2D\x3E\x0A\x3C\x68\x74\x6D\x6C\x20\x6C\x61\x6E\x67\x3D\x22\x65\x6E\x22\x3E\x0A\x09\x3C\x68\x65\x61\x64\x3E\x0A\x09\x09\x3C\x6D\x65\x74\x61\x20\x63\x68\x61\x72\x73\x65\x74\x3D\x22\x75\x74\x66\x2D\x38\x22\x20\x2F\x3E\x0A\x09\x09\x3C\x6D\x65\x74\x61\x20\x6E\x61\x6D\x65\x3D\x22\x76\x69\x65\x77\x70\x6F\x72\x74\x22\x20\x63\x6F\x6E\x74\x65\x6E\x74\x3D\x22\x77\x69\x64\x74\x68\x3D\x64\x65\x76\x69\x63\x65\x2D\x77\x69\x64\x74\x68\x2C\x20\x69\x6E\x69\x74\x69\x61\x6C\x2D\x73\x63\x61\x6C\x65\x3D\x31\x2C\x20\x73\x68\x72\x69\x6E\x6B\x2D\x74\x6F\x2D\x66\x69\x74\x3D\x6E\x6F\x22\x20\x2F\x3E\x0A\x09\x09\x3C\x6C\x69\x6E\x6B\x20\x72\x65\x6C\x3D\x22\x69\x63\x6F\x6E\x22\x20\x74\x79\x70\x65\x3D\x22\x69\x6D\x61\x67\x65\x2F\x70\x6E\x67\x22\x20\x68\x72\x65\x66\x3D\x22\x2F\x66\x61\x76\x69\x63\x6F\x6E\x2E\x70\x6E\x67\x22\x20\x2F\x3E\x0A\x09\x09\x3C\x6C\x69\x6E\x6B\x20\x72\x65\x6C\x3D\x22\x73\x74\x79\x6C\x65\x73\x68\x65\x65\x74\x22\x20\x68\x72\x65\x66\x3D\x22\x2F\x69\x6E\x64\x65\x78\x2E\x63\x73\x73\x22\x20\x2F\x3E\x0A\x09\x09\x3C\x74\x69\x74\x6C\x65\x3E\x72\x65\x73\x63\x61\x63\x68\x65\x64\x20\x7C\x20\x68\x6F\x73\x74\x73\x20\x62\x6C\x6F\x63\x6B\x73\x3C\x2F\x74\x69\x74\x6C\x65\x3E\x0A\x0A\x09\x09\x3C\x73\x74\x79\x6C\x65\x3E\x0A\x09\x09\x09\x2E\x62\x6C\x6F\x63\x6B\x5F\x73\x6F\x75\x72\x63\x65\x20\x7B\x0A\x09\x09\x09\x09\x77\x69\x64\x74\x68\x3A\x20\x63\x61\x6C\x63\x28\x31\x30\x30\x25\x20\x2D\x20\x32\x65\x6D\x29\x3B\x0A\x09\x09\x09\x09\x6F\x76\x65\x72\x66\x6C\x6F\x77\x3A\x20\x61\x75\x74\x6F\x3B\x0A\x09\x09\x09\x7D\x0A\x09\x09\x09\x2E\x62\x6C\x6F\x63\x6B\x5F\x73\x6F\x75\x72\x63\x65\x20\x69\x6E\x70\x75\x74\x3A\x64\x69\x73\x61\x62\x6C\x65\x64\x20\x7B\x0A\x09\x09\x09\x09\x63\x6F\x6C\x6F\x72\x3A\x20\x62\x6C\x61\x63\x6B\x3B\x0A\x09\x09\x09\x7D\x0A\x09\x09\x09\x2E\x69\x74\x65\x6D\x20\x7B\x0A\x09\x09\x09\x09\x77\x69\x64\x74\x68\x3A\x20\x31\x30\x30\x25\x3B\x0A\x09\x09\x09\x09\x6D\x61\x72\x67\x69\x6E\x2D\x62\x6F\x74\x74\x6F\x6D\x3A\x20\x31\x65\x6D\x3B\x0A\x09\x09\x09\x7D\x0A\x09\x09\x09\x2E\x69\x74\x65\x6D\x2E\x68\x65\x61\x64\x65\x72\x20\x7B\x0A\x09\x09\x09\x09\x66\x6F\x6E\x74\x2D\x77\x65\x69\x67\x68\x74\x3A\x20\x62\x6F\x6C\x64\x3B\x0A\x09\x09\x09\x09\x6D\x61\x72\x67\x69\x6E\x2D\x62\x6F\x74\x74\x6F\x6D\x3A\x20\x31\x65\x6D\x3B\x0A\x09\x09\x09\x09\x62\x6F\x72\x64\x65\x72\x2D\x62\x6F\x74\x74\x6F\x6D\x3A\x20\x31\x70\x78\x20\x73\x6F\x6C\x69\x64\x20\x73\x69\x6C\x76\x65\x72\x3B\x0A\x09\x09\x09\x7D\x0A\x09\x09\x09\x2E\x69\x74\x65\x6D\x20\x2E\x69\x73\x2D\x65\x6E\x61\x62\x6C\x65\x64\x20\x7B\x0A\x09\x09\x09\x09\x64\x69\x73\x70\x6C\x61\x79\x3A\x20\x69\x6E\x6C\x69\x6E\x65\x2D\x62\x6C\x6F\x63\x6B\x3B\x0A\x09\x09\x09\x09\x77\x69\x64\x74\x68\x3A\x20\x34\x65\x6D\x3B\x0A\x09\x09\x09\x09\x76\x65\x72\x74\x69\x63\x61\x6C\x2D\x61\x6C\x69\x67\x6E\x3A\x20\x74\x6F\x70\x3B\x0A\x09\x09\x09\x7D\x0A\x09\x09\x09\x2E\x69\x74\x65\x6D\x20\x2E\x69\x6E\x66\x6F\x20\x7B\x0A\x09\x09\x09\x09\x64\x69\x73\x70\x6C\x61\x79\x3A\x20\x69\x6E\x6C\x69\x6E\x65\x2D\x62\x6C\x6F\x63\x6B\x3B\x0A\x09\x09\x09\x09\x77\x69\x64\x74\x68\x3A\x20\x63\x61\x6C\x63\x28\x31\x30\x30\x25\x20\x2D\x20\x36\x65\x6D\x29\x3B\x0A\x09\x09\x09\x7D\x0A\x09\x09\x09\x2E\x69\x74\x65\x6D\x20\x2E\x69\x6E\x66\x6F\x20\x69\x6E\x70\x75\x74\x20\x7B\x0A\x09\x09\x09\x09\x77\x69\x64\x74\x68\x3A\x20\x63\x61\x6C\x63\x28\x31\x30\x30\x25\x20\x2D\x20\x34\x65\x6D\x29\x3B\x0A\x09\x09\x09\x7D\x0A\x09\x09\x3C\x2F\x73\x74\x79\x6C\x65\x3E\x0A\x09\x3C\x2F\x68\x65\x61\x64\x3E\x0A\x09\x3C\x62\x6F\x64\x79\x20\x6F\x6E\x6C\x6F\x61\x64\x3D\x22\x6F\x6E\x4C\x6F\x61\x64\x28\x29\x22\x3E\x0A\x09\x09\x3C\x6E\x61\x76\x20\x63\x6C\x61\x73\x73\x3D\x22\x6D\x65\x6E\x75\x22\x3E\x0A\x09\x09\x09\x3C\x61\x20\x68\x72\x65\x66\x3D\x22\x2F\x22\x3E\x20\x72\x65\x73\x63\x61\x63\x68\x65\x64\x20\x3C\x2F\x61\x3E\x0A\x09\x09\x09\x2F\x0A\x09\x09\x09\x3C\x61\x20\x68\x72\x65\x66\x3D\x22\x2F\x65\x6E\x76\x69\x72\x6F\x6E\x6D\x65\x6E\x74\x2F\x22\x3E\x20\x45\x6E\x76\x69\x72\x6F\x6E\x6D\x65\x6E\x74\x20\x3C\x2F\x61\x3E\x0A\x09\x09\x09\x2F\x0A\x09\x09\x09\x3C\x61\x20\x68\x72\x65\x66\x3D\x22\x2F\x68\x6F\x73\x74\x73\x5F\x62\x6C\x6F\x63\x6B\x73\x2F\x22\x20\x63\x6C\x61\x73\x73\x3D\x22\x61\x63\x74\x69\x76\x65\x22\x3E\x20\x48\x6F\x73\x74\x73\x20\x62\x6C\x6F\x63\x6B\x73\x20\x3C\x2F\x61\x3E\x0A\x09\x09\x09\x2F\x0A\x09\x09\x09\x3C\x61\x20\x68\x72\x65\x66\x3D\x22\x2F\x68\x6F\x73\x74\x73\x2E\x64\x2F\x22\x3E\x20\x68\x6F\x73\x74\x73\x2E\x64\x20\x3C\x2F\x61\x3E\x0A\x09\x09\x09\x2F\x0A\x09\x09\x09\x3C\x61\x20\x68\x72\x65\x66\x3D\x22\x2F\x7A\x6F\x6E\x65\x2E\x64\x2F\x22\x3E\x20\x7A\x6F\x6E\x65\x2E\x64\x20\x3C\x2F\x61\x3E\x0A\x09\x09\x3C\x2F\x6E\x61\x76\x3E\x0A\x0A\x09\x09\x3C\x64\x69\x76\x20\x69\x64\x3D\x22\x6E\x6F\x74\x69\x66\x22\x3E\x3C\x2F\x64\x69\x76\x3E\x0A\x0A\x09\x09\x3C\x70\x3E\x43\x6F\x6E\x66\x69\x67\x75\x72\x65\x20\x74\x68\x65\x20\x73\x6F\x75\x72\x63\x65\x20\x6F\x66\x20\x62\x6C\x6F\x63\x6B\x65\x64\x20\x68\x6F\x73\x74\x73\x20\x66\x69\x6C\x65\x2E\x3C\x2F\x70\x3E\x0A\x0A\x09\x09\x3C\x64\x69\x76\x20\x63\x6C\x61\x73\x73\x3D\x22\x62\x6C\x6F\x63\x6B\x5F\x73\x6F\x75\x72\x63\x65\x22\x3E\x0A\x09\x09\x09\x3C\x64\x69\x76\x20\x63\x6C\x61\x73\x73\x3D\x22\x69\x74\x65\x6D\x20\x68\x65\x61\x64\x65\x72\x22\x3E\x0A\x09\x09\x09\x09\x3C\x73\x70\x61\x6E\x20\x63\x6C\x61\x73\x73\x3D\x22\x69\x73\x2D\x65\x6E\x61\x62\x6C\x65\x64\x22\x3E\x20\x45\x6E\x61\x62\x6C\x65\x64\x20\x3C\x2F\x73\x70\x61\x6E\x3E\x0A\x09\x09\x09\x09\x3C\x73\x70\x61\x6E\x20\x63\x6C\x61\x73\x73\x3D\x22\x69\x6E\x66\x6F\x22\x3E\x20\x4E\x61\x6D\x65\x20\x3C\x2F\x73\x70\x61\x6E\x3E\x0A\x09\x09\x09\x3C\x2F\x64\x69\x76\x3E\x0A\x09\x09\x3C\x2F\x64\x69\x76\x3E\x0A\x09\x09\x3C\x64\x69\x76\x20\x69\x64\x3D\x22\x48\x6F\x73\x74\x73\x42\x6C\x6F\x63\x6B\x73\x22\x20\x63\x6C\x61\x73\x73\x3D\x22\x62\x6C\x6F\x63\x6B\x5F\x73\x6F\x75\x72\x63\x65\x22\x3E\x3C\x2F\x64\x69\x76\x3E\x0A\x0A\x09\x09\x3C\x64\x69\x76\x3E\x0A\x09\x09\x09\x3C\x62\x75\x74\x74\x6F\x6E\x20\x6F\x6E\x63\x6C\x69\x63\x6B\x3D\x22\x75\x70\x64\x61\x74\x65\x48\x6F\x73\x74\x73\x42\x6C\x6F\x63\x6B\x73\x28\x29\x22\x3E\x53\x61\x76\x65\x3C\x2F\x62\x75\x74\x74\x6F\x6E\x3E\x0A\x09\x09\x3C\x2F\x64\x69\x76\x3E\x0A\x0A\x09\x09\x3C\x73\x63\x72\x69\x70\x74\x20\x73\x72\x63\x3D\x22\x2F\x69\x6E\x64\x65\x78\x2E\x6A\x73\x22\x3E\x3C\x2F\x73\x63\x72\x69\x70\x74\x3E\x0A\x09\x09\x3C\x73\x63\x72\x69\x70\x74\x20\x73\x72\x63\x3D\x22\x2F\x72\x65\x73\x63\x61\x63\x68\x65\x64\x2E\x6A\x73\x22\x3E\x3C\x2F\x73\x63\x72\x69\x70\x74\x3E\x0A\x09\x09\x3C\x73\x63\x72\x69\x70\x74\x3E\x0A\x09\x09\x09\x6C\x65\x74\x20\x72\x65\x73\x63\x20\x3D\x20\x6E\x75\x6C\x6C\x0A\x0A\x09\x09\x09\x66\x75\x6E\x63\x74\x69\x6F\x6E\x20\x6F\x6E\x4C\x6F\x61\x64\x28\x29\x20\x7B\x0A\x09\x09\x09\x09\x72\x65\x73\x63\x20\x3D\x20\x6E\x65\x77\x20\x52\x65\x73\x63\x61\x63\x68\x65\x64\x28\x22\x22\x29\x0A\x09\x09\x09\x09\x67\x65\x74\x45\x6E\x76\x69\x72\x6F\x6E\x6D\x65\x6E\x74\x28\x29\x0A\x09\x09\x09\x7D\x0A\x0A\x09\x09\x09\x61\x73\x79\x6E\x63\x20\x66\x75\x6E\x63\x74\x69\x6F\x6E\x20\x67\x65\x74\x45\x6E\x76\x69\x72\x6F\x6E\x6D\x65\x6E\x74\x28\x29\x20\x7B\x0A\x09\x09\x09\x09\x63\x6F\x6E\x73\x74\x20\x72\x65\x73\x20\x3D\x20\x61\x77\x61\x69\x74\x20\x72\x65\x73\x63\x2E\x67\x65\x74\x45\x6E\x76\x69\x72\x6F\x6E\x6D\x65\x6E\x74\x28\x29\x0A\x09\x09\x09\x09\x69\x66\x20\x28\x72\x65\x73\x2E\x63\x6F\x64\x65\x20\x21\x3D\x20\x32\x30\x30\x29\x20\x7B\x0A\x09\x09\x09\x09\x09\x6E\x6F\x74\x69\x66\x45\x72\x72\x6F\x72\x28\x72\x65\x73\x2E\x6D\x65\x73\x73\x61\x67\x65\x29\x0A\x09\x09\x09\x09\x09\x72\x65\x74\x75\x72\x6E\x0A\x09\x09\x09\x09\x7D\x0A\x0A\x09\x09\x09\x09\x6C\x65\x74\x20\x65\x6E\x76\x20\x3D\x20\x72\x65\x73\x2E\x64\x61\x74\x61\x0A\x09\x09\x09\x09\x72\x65\x6E\x64\x65\x72\x48\x6F\x73\x74\x73\x42\x6C\x6F\x63\x6B\x73\x28\x65\x6E\x76\x2E\x48\x6F\x73\x74\x73\x42\x6C\x6F\x63\x6B\x73\x29\x0A\x09\x09\x09\x7D\x0A\x0A\x09\x09\x09\x66\x75\x6E\x63\x74\x69\x6F\x6E\x20\x6F\x6E\x43\x68\x65\x63\x6B\x48\x6F\x73\x74\x62\x6C\x6F\x63\x6B\x28\x69\x64\x78\x2C\x20\x76\x61\x6C\x29\x20\x7B\x0A\x09\x09\x09\x09\x72\x65\x73\x63\x2E\x65\x6E\x76\x2E\x48\x6F\x73\x74\x73\x42\x6C\x6F\x63\x6B\x73\x5B\x69\x64\x78\x5D\x2E\x49\x73\x45\x6E\x61\x62\x6C\x65\x64\x20\x3D\x20\x76\x61\x6C\x0A\x09\x09\x09\x7D\x0A\x0A\x09\x09\x09\x61\x73\x79\x6E\x63\x20\x66\x75\x6E\x63\x74\x69\x6F\x6E\x20\x75\x70\x64\x61\x74\x65\x48\x6F\x73\x74\x73\x42\x6C\x6F\x63\x6B\x73\x28\x29\x20\x7B\x0A\x09\x09\x09\x09\x63\x6F\x6E\x73\x74\x20\x72\x65\x73\x20\x3D\x20\x61\x77\x61\x69\x74\x20\x72\x65\x73\x63\x2E\x75\x70\x64\x61\x74\x65\x48\x6F\x73\x74\x73\x42\x6C\x6F\x63\x6B\x73\x28\x72\x65\x73\x63\x2E\x65\x6E\x76\x2E\x48\x6F\x73\x74\x73\x42\x6C\x6F\x63\x6B\x73\x29\x0A\x09\x09\x09\x09\x69\x66\x20\x28\x72\x65\x73\x2E\x63\x6F\x64\x65\x20\x21\x3D\x20\x32\x30\x30\x29\x20\x7B\x0A\x09\x09\x09\x09\x09\x6E\x6F\x74\x69\x66\x45\x72\x72\x6F\x72\x28\x22\x75\x70\x64\x61\x74\x65\x48\x6F\x73\x74\x73\x42\x6C\x6F\x63\x6B\x73\x3A\x20\x22\x2C\x20\x72\x65\x73\x2E\x6D\x65\x73\x73\x61\x67\x65\x29\x0A\x09\x09\x09\x09\x09\x72\x65\x74\x75\x72\x6E\x0A\x09\x09\x09\x09\x7D\x0A\x0A\x09\x09\x09\x09\x72\x65\x6E\x64\x65\x72\x48\x6F\x73\x74\x73\x42\x6C\x6F\x63\x6B\x73\x28\x72\x65\x73\x2E\x64\x61\x74\x61\x29\x0A\x09\x09\x09\x09\x6E\x6F\x74\x69\x66\x49\x6E\x66\x6F\x28\x22\x54\x68\x65\x20\x68\x6F\x73\x74\x73\x20\x62\x6C\x6F\x63\x6B\x73\x20\x68\x61\x73\x20\x62\x65\x65\x6E\x20\x75\x70\x64\x61\x74\x65\x64\x2E\x22\x29\x0A\x09\x09\x09\x7D\x0A\x0A\x09\x09\x09\x66\x75\x6E\x63\x74\x69\x6F\x6E\x20\x72\x65\x6E\x64\x65\x72\x48\x6F\x73\x74\x73\x42\x6C\x6F\x63\x6B\x73\x28\x68\x6F\x73\x74\x73\x42\x6C\x6F\x63\x6B\x73\x29\x20\x7B\x0A\x09\x09\x09\x09\x6C\x65\x74\x20\x70\x61\x72\x65\x6E\x74\x20\x3D\x20\x64\x6F\x63\x75\x6D\x65\x6E\x74\x2E\x67\x65\x74\x45\x6C\x65\x6D\x65\x6E\x74\x42\x79\x49\x64\x28\x22\x48\x6F\x73\x74\x73\x42\x6C\x6F\x63\x6B\x73\x22\x29\x0A\x09\x09\x09\x09\x70\x61\x72\x65\x6E\x74\x2E\x69\x6E\x6E\x65\x72\x48\x54\x4D\x4C\x20\x3D\x20\x22\x22\x0A\x0A\x09\x09\x09\x09\x66\x6F\x72\x20\x28\x6C\x65\x74\x20\x78\x20\x3D\x20\x30\x3B\x20\x78\x20\x3C\x20\x68\x6F\x73\x74\x73\x42\x6C\x6F\x63\x6B\x73\x2E\x6C\x65\x6E\x67\x74\x68\x3B\x20\x78\x2B\x2B\x29\x20\x7B\x0A\x09\x09\x09\x09\x09\x6C\x65\x74\x20\x68\x6F\x73\x74\x73\x42\x6C\x6F\x63\x6B\x20\x3D\x20\x68\x6F\x73\x74\x73\x42\x6C\x6F\x63\x6B\x73\x5B\x78\x5D\x0A\x09\x09\x09\x09\x09\x6C\x65\x74\x20\x69\x74\x65\x6D\x20\x3D\x20\x64\x6F\x63\x75\x6D\x65\x6E\x74\x2E\x63\x72\x65\x61\x74\x65\x45\x6C\x65\x6D\x65\x6E\x74\x28\x22\x64\x69\x76\x22\x29\x0A\x09\x09\x09\x09\x09\x69\x74\x65\x6D\x2E\x63\x6C\x61\x73\x73\x4C\x69\x73\x74\x2E\x61\x64\x64\x28\x22\x69\x74\x65\x6D\x22\x29\x0A\x09\x09\x09\x09\x09\x69\x74\x65\x6D\x2E\x69\x6E\x6E\x65\x72\x48\x54\x4D\x4C\x20\x3D\x20\x60\x0A\x09\x09\x09\x09\x09\x09\x3C\x73\x70\x61\x6E\x20\x63\x6C\x61\x73\x73\x3D\x22\x69\x73\x2D\x65\x6E\x61\x62\x6C\x65\x64\x22\x3E\x0A\x09\x09\x09\x09\x09\x09\x09\x3C\x69\x6E\x70\x75\x74\x0A\x09\x09\x09\x09\x09\x09\x09\x09\x74\x79\x70\x65\x3D\x22\x63\x68\x65\x63\x6B\x62\x6F\x78\x22\x0A\x09\x09\x09\x09\x09\x09\x09\x09\x24\x7B\x68\x6F\x73\x74\x73\x42\x6C\x6F\x63\x6B\x2E\x49\x73\x45\x6E\x61\x62\x6C\x65\x64\x20\x3F\x20\x22\x63\x68\x65\x63\x6B\x65\x64\x22\x20\x3A\x20\x22\x22\x7D\x0A\x09\x09\x09\x09\x09\x09\x09\x09\x6F\x6E\x69\x6E\x70\x75\x74\x3D\x22\x6F\x6E\x43\x68\x65\x63\x6B\x48\x6F\x73\x74\x62\x6C\x6F\x63\x6B\x28\x27\x24\x7B\x78\x7D\x27\x2C\x20\x74\x68\x69\x73\x2E\x63\x68\x65\x63\x6B\x65\x64\x29\x22\x0A\x09\x09\x09\x09\x09\x09\x09\x2F\x3E\x0A\x09\x09\x09\x09\x09\x09\x3C\x2F\x73\x70\x61\x6E\x3E\x0A\x09\x09\x09\x09\x09\x09\x3C\x73\x70\x61\x6E\x20\x63\x6C\x61\x73\x73\x3D\x22\x69\x6E\x66\x6F\x22\x3E\x0A\x09\x09\x09\x09\x09\x09\x09\x3C\x64\x69\x76\x3E\x24\x7B\x68\x6F\x73\x74\x73\x42\x6C\x6F\x63\x6B\x2E\x4E\x61\x6D\x65\x7D\x3C\x2F\x64\x69\x76\x3E\x0A\x09\x09\x09\x09\x09\x09\x09\x3C\x64\x69\x76\x3E\x20\x55\x52\x4C\x3A\x20\x3C\x69\x6E\x70\x75\x74\x20\x76\x61\x6C\x75\x65\x3D\x22\x24\x7B\x68\x6F\x73\x74\x73\x42\x6C\x6F\x63\x6B\x2E\x55\x52\x4C\x7D\x22\x20\x64\x69\x73\x61\x62\x6C\x65\x64\x20\x2F\x3E\x20\x3C\x2F\x64\x69\x76\x3E\x0A\x09\x09\x09\x09\x09\x09\x09\x3C\x64\x69\x76\x3E\x20\x4C\x61\x73\x74\x20\x75\x70\x64\x61\x74\x65\x64\x20\x61\x74\x20\x24\x7B\x68\x6F\x73\x74\x73\x42\x6C\x6F\x63\x6B\x2E\x4C\x61\x73\x74\x55\x70\x64\x61\x74\x65\x64\x7D\x20\x3C\x2F\x64\x69\x76\x3E\x0A\x09\x09\x09\x09\x09\x09\x3C\x2F\x73\x70\x61\x6E\x3E\x60\x0A\x0A\x09\x09\x09\x09\x09\x70\x61\x72\x65\x6E\x74\x2E\x61\x70\x70\x65\x6E\x64\x43\x68\x69\x6C\x64\x28\x69\x74\x65\x6D\x29\x0A\x09\x09\x09\x09\x7D\x0A\x09\x09\x09\x7D\x0A\x09\x09\x3C\x2F\x73\x63\x72\x69\x70\x74\x3E\x0A\x09\x3C\x2F\x62\x6F\x64\x79\x3E\x0A\x3C\x2F\x68\x74\x6D\x6C\x3E\x0A"), + Content: []byte("\x3C\x21\x44\x4F\x43\x54\x59\x50\x45\x20\x68\x74\x6D\x6C\x3E\x0A\x3C\x21\x2D\x2D\x20\x53\x50\x44\x58\x2D\x46\x69\x6C\x65\x43\x6F\x70\x79\x72\x69\x67\x68\x74\x54\x65\x78\x74\x3A\x20\x32\x30\x32\x31\x20\x4D\x2E\x20\x53\x68\x75\x6C\x68\x61\x6E\x20\x3C\x6D\x73\x40\x6B\x69\x6C\x61\x62\x69\x74\x2E\x69\x6E\x66\x6F\x20\x2D\x2D\x3E\x0A\x3C\x21\x2D\x2D\x20\x53\x50\x44\x58\x2D\x4C\x69\x63\x65\x6E\x73\x65\x2D\x49\x64\x65\x6E\x74\x69\x66\x69\x65\x72\x3A\x20\x47\x50\x4C\x2D\x33\x2E\x30\x2D\x6F\x72\x2D\x6C\x61\x74\x65\x72\x20\x2D\x2D\x3E\x0A\x3C\x68\x74\x6D\x6C\x20\x6C\x61\x6E\x67\x3D\x22\x65\x6E\x22\x3E\x0A\x09\x3C\x68\x65\x61\x64\x3E\x0A\x09\x09\x3C\x6D\x65\x74\x61\x20\x63\x68\x61\x72\x73\x65\x74\x3D\x22\x75\x74\x66\x2D\x38\x22\x20\x2F\x3E\x0A\x09\x09\x3C\x6D\x65\x74\x61\x20\x6E\x61\x6D\x65\x3D\x22\x76\x69\x65\x77\x70\x6F\x72\x74\x22\x20\x63\x6F\x6E\x74\x65\x6E\x74\x3D\x22\x77\x69\x64\x74\x68\x3D\x64\x65\x76\x69\x63\x65\x2D\x77\x69\x64\x74\x68\x2C\x20\x69\x6E\x69\x74\x69\x61\x6C\x2D\x73\x63\x61\x6C\x65\x3D\x31\x2C\x20\x73\x68\x72\x69\x6E\x6B\x2D\x74\x6F\x2D\x66\x69\x74\x3D\x6E\x6F\x22\x20\x2F\x3E\x0A\x09\x09\x3C\x6C\x69\x6E\x6B\x20\x72\x65\x6C\x3D\x22\x69\x63\x6F\x6E\x22\x20\x74\x79\x70\x65\x3D\x22\x69\x6D\x61\x67\x65\x2F\x70\x6E\x67\x22\x20\x68\x72\x65\x66\x3D\x22\x2F\x66\x61\x76\x69\x63\x6F\x6E\x2E\x70\x6E\x67\x22\x20\x2F\x3E\x0A\x09\x09\x3C\x6C\x69\x6E\x6B\x20\x72\x65\x6C\x3D\x22\x73\x74\x79\x6C\x65\x73\x68\x65\x65\x74\x22\x20\x68\x72\x65\x66\x3D\x22\x2F\x69\x6E\x64\x65\x78\x2E\x63\x73\x73\x22\x20\x2F\x3E\x0A\x09\x09\x3C\x74\x69\x74\x6C\x65\x3E\x72\x65\x73\x63\x61\x63\x68\x65\x64\x20\x7C\x20\x68\x6F\x73\x74\x73\x20\x62\x6C\x6F\x63\x6B\x73\x3C\x2F\x74\x69\x74\x6C\x65\x3E\x0A\x0A\x09\x09\x3C\x73\x74\x79\x6C\x65\x3E\x0A\x09\x09\x09\x2E\x62\x6C\x6F\x63\x6B\x5F\x73\x6F\x75\x72\x63\x65\x20\x7B\x0A\x09\x09\x09\x09\x77\x69\x64\x74\x68\x3A\x20\x63\x61\x6C\x63\x28\x31\x30\x30\x25\x20\x2D\x20\x32\x65\x6D\x29\x3B\x0A\x09\x09\x09\x09\x6F\x76\x65\x72\x66\x6C\x6F\x77\x3A\x20\x61\x75\x74\x6F\x3B\x0A\x09\x09\x09\x7D\x0A\x09\x09\x09\x2E\x62\x6C\x6F\x63\x6B\x5F\x73\x6F\x75\x72\x63\x65\x20\x69\x6E\x70\x75\x74\x3A\x64\x69\x73\x61\x62\x6C\x65\x64\x20\x7B\x0A\x09\x09\x09\x09\x63\x6F\x6C\x6F\x72\x3A\x20\x62\x6C\x61\x63\x6B\x3B\x0A\x09\x09\x09\x7D\x0A\x09\x09\x09\x2E\x69\x74\x65\x6D\x20\x7B\x0A\x09\x09\x09\x09\x77\x69\x64\x74\x68\x3A\x20\x31\x30\x30\x25\x3B\x0A\x09\x09\x09\x09\x6D\x61\x72\x67\x69\x6E\x2D\x62\x6F\x74\x74\x6F\x6D\x3A\x20\x31\x65\x6D\x3B\x0A\x09\x09\x09\x7D\x0A\x09\x09\x09\x2E\x69\x74\x65\x6D\x2E\x68\x65\x61\x64\x65\x72\x20\x7B\x0A\x09\x09\x09\x09\x66\x6F\x6E\x74\x2D\x77\x65\x69\x67\x68\x74\x3A\x20\x62\x6F\x6C\x64\x3B\x0A\x09\x09\x09\x09\x6D\x61\x72\x67\x69\x6E\x2D\x62\x6F\x74\x74\x6F\x6D\x3A\x20\x31\x65\x6D\x3B\x0A\x09\x09\x09\x09\x62\x6F\x72\x64\x65\x72\x2D\x62\x6F\x74\x74\x6F\x6D\x3A\x20\x31\x70\x78\x20\x73\x6F\x6C\x69\x64\x20\x73\x69\x6C\x76\x65\x72\x3B\x0A\x09\x09\x09\x7D\x0A\x09\x09\x09\x2E\x69\x74\x65\x6D\x20\x2E\x69\x73\x2D\x65\x6E\x61\x62\x6C\x65\x64\x20\x7B\x0A\x09\x09\x09\x09\x64\x69\x73\x70\x6C\x61\x79\x3A\x20\x69\x6E\x6C\x69\x6E\x65\x2D\x62\x6C\x6F\x63\x6B\x3B\x0A\x09\x09\x09\x09\x77\x69\x64\x74\x68\x3A\x20\x34\x65\x6D\x3B\x0A\x09\x09\x09\x09\x76\x65\x72\x74\x69\x63\x61\x6C\x2D\x61\x6C\x69\x67\x6E\x3A\x20\x74\x6F\x70\x3B\x0A\x09\x09\x09\x7D\x0A\x09\x09\x09\x2E\x69\x74\x65\x6D\x20\x2E\x69\x6E\x66\x6F\x20\x7B\x0A\x09\x09\x09\x09\x64\x69\x73\x70\x6C\x61\x79\x3A\x20\x69\x6E\x6C\x69\x6E\x65\x2D\x62\x6C\x6F\x63\x6B\x3B\x0A\x09\x09\x09\x09\x77\x69\x64\x74\x68\x3A\x20\x63\x61\x6C\x63\x28\x31\x30\x30\x25\x20\x2D\x20\x36\x65\x6D\x29\x3B\x0A\x09\x09\x09\x7D\x0A\x09\x09\x09\x2E\x69\x74\x65\x6D\x20\x2E\x69\x6E\x66\x6F\x20\x69\x6E\x70\x75\x74\x20\x7B\x0A\x09\x09\x09\x09\x77\x69\x64\x74\x68\x3A\x20\x63\x61\x6C\x63\x28\x31\x30\x30\x25\x20\x2D\x20\x34\x65\x6D\x29\x3B\x0A\x09\x09\x09\x7D\x0A\x09\x09\x3C\x2F\x73\x74\x79\x6C\x65\x3E\x0A\x09\x3C\x2F\x68\x65\x61\x64\x3E\x0A\x09\x3C\x62\x6F\x64\x79\x20\x6F\x6E\x6C\x6F\x61\x64\x3D\x22\x6F\x6E\x4C\x6F\x61\x64\x28\x29\x22\x3E\x0A\x09\x09\x3C\x6E\x61\x76\x20\x63\x6C\x61\x73\x73\x3D\x22\x6D\x65\x6E\x75\x22\x3E\x0A\x09\x09\x09\x3C\x61\x20\x68\x72\x65\x66\x3D\x22\x2F\x22\x3E\x20\x72\x65\x73\x63\x61\x63\x68\x65\x64\x20\x3C\x2F\x61\x3E\x0A\x09\x09\x09\x2F\x0A\x09\x09\x09\x3C\x61\x20\x68\x72\x65\x66\x3D\x22\x2F\x65\x6E\x76\x69\x72\x6F\x6E\x6D\x65\x6E\x74\x2F\x22\x3E\x20\x45\x6E\x76\x69\x72\x6F\x6E\x6D\x65\x6E\x74\x20\x3C\x2F\x61\x3E\x0A\x09\x09\x09\x2F\x0A\x09\x09\x09\x3C\x61\x20\x68\x72\x65\x66\x3D\x22\x2F\x68\x6F\x73\x74\x73\x5F\x62\x6C\x6F\x63\x6B\x73\x2F\x22\x20\x63\x6C\x61\x73\x73\x3D\x22\x61\x63\x74\x69\x76\x65\x22\x3E\x20\x48\x6F\x73\x74\x73\x20\x62\x6C\x6F\x63\x6B\x73\x20\x3C\x2F\x61\x3E\x0A\x09\x09\x09\x2F\x0A\x09\x09\x09\x3C\x61\x20\x68\x72\x65\x66\x3D\x22\x2F\x68\x6F\x73\x74\x73\x2E\x64\x2F\x22\x3E\x20\x68\x6F\x73\x74\x73\x2E\x64\x20\x3C\x2F\x61\x3E\x0A\x09\x09\x09\x2F\x0A\x09\x09\x09\x3C\x61\x20\x68\x72\x65\x66\x3D\x22\x2F\x7A\x6F\x6E\x65\x2E\x64\x2F\x22\x3E\x20\x7A\x6F\x6E\x65\x2E\x64\x20\x3C\x2F\x61\x3E\x0A\x09\x09\x3C\x2F\x6E\x61\x76\x3E\x0A\x0A\x09\x09\x3C\x64\x69\x76\x20\x69\x64\x3D\x22\x6E\x6F\x74\x69\x66\x22\x3E\x3C\x2F\x64\x69\x76\x3E\x0A\x0A\x09\x09\x3C\x70\x3E\x43\x6F\x6E\x66\x69\x67\x75\x72\x65\x20\x74\x68\x65\x20\x73\x6F\x75\x72\x63\x65\x20\x6F\x66\x20\x62\x6C\x6F\x63\x6B\x65\x64\x20\x68\x6F\x73\x74\x73\x20\x66\x69\x6C\x65\x2E\x3C\x2F\x70\x3E\x0A\x0A\x09\x09\x3C\x64\x69\x76\x20\x63\x6C\x61\x73\x73\x3D\x22\x62\x6C\x6F\x63\x6B\x5F\x73\x6F\x75\x72\x63\x65\x22\x3E\x0A\x09\x09\x09\x3C\x64\x69\x76\x20\x63\x6C\x61\x73\x73\x3D\x22\x69\x74\x65\x6D\x20\x68\x65\x61\x64\x65\x72\x22\x3E\x0A\x09\x09\x09\x09\x3C\x73\x70\x61\x6E\x20\x63\x6C\x61\x73\x73\x3D\x22\x69\x73\x2D\x65\x6E\x61\x62\x6C\x65\x64\x22\x3E\x20\x45\x6E\x61\x62\x6C\x65\x64\x20\x3C\x2F\x73\x70\x61\x6E\x3E\x0A\x09\x09\x09\x09\x3C\x73\x70\x61\x6E\x20\x63\x6C\x61\x73\x73\x3D\x22\x69\x6E\x66\x6F\x22\x3E\x20\x4E\x61\x6D\x65\x20\x3C\x2F\x73\x70\x61\x6E\x3E\x0A\x09\x09\x09\x3C\x2F\x64\x69\x76\x3E\x0A\x09\x09\x3C\x2F\x64\x69\x76\x3E\x0A\x09\x09\x3C\x64\x69\x76\x20\x69\x64\x3D\x22\x48\x6F\x73\x74\x73\x42\x6C\x6F\x63\x6B\x73\x22\x20\x63\x6C\x61\x73\x73\x3D\x22\x62\x6C\x6F\x63\x6B\x5F\x73\x6F\x75\x72\x63\x65\x22\x3E\x3C\x2F\x64\x69\x76\x3E\x0A\x0A\x09\x09\x3C\x64\x69\x76\x3E\x0A\x09\x09\x09\x3C\x62\x75\x74\x74\x6F\x6E\x20\x6F\x6E\x63\x6C\x69\x63\x6B\x3D\x22\x75\x70\x64\x61\x74\x65\x48\x6F\x73\x74\x73\x42\x6C\x6F\x63\x6B\x73\x28\x29\x22\x3E\x53\x61\x76\x65\x3C\x2F\x62\x75\x74\x74\x6F\x6E\x3E\x0A\x09\x09\x3C\x2F\x64\x69\x76\x3E\x0A\x0A\x09\x09\x3C\x73\x63\x72\x69\x70\x74\x20\x73\x72\x63\x3D\x22\x2F\x69\x6E\x64\x65\x78\x2E\x6A\x73\x22\x3E\x3C\x2F\x73\x63\x72\x69\x70\x74\x3E\x0A\x09\x09\x3C\x73\x63\x72\x69\x70\x74\x20\x73\x72\x63\x3D\x22\x2F\x72\x65\x73\x63\x61\x63\x68\x65\x64\x2E\x6A\x73\x22\x3E\x3C\x2F\x73\x63\x72\x69\x70\x74\x3E\x0A\x09\x09\x3C\x73\x63\x72\x69\x70\x74\x3E\x0A\x09\x09\x09\x6C\x65\x74\x20\x72\x65\x73\x63\x20\x3D\x20\x6E\x75\x6C\x6C\x0A\x0A\x09\x09\x09\x66\x75\x6E\x63\x74\x69\x6F\x6E\x20\x6F\x6E\x4C\x6F\x61\x64\x28\x29\x20\x7B\x0A\x09\x09\x09\x09\x72\x65\x73\x63\x20\x3D\x20\x6E\x65\x77\x20\x52\x65\x73\x63\x61\x63\x68\x65\x64\x28\x22\x22\x29\x0A\x09\x09\x09\x09\x67\x65\x74\x45\x6E\x76\x69\x72\x6F\x6E\x6D\x65\x6E\x74\x28\x29\x0A\x09\x09\x09\x7D\x0A\x0A\x09\x09\x09\x61\x73\x79\x6E\x63\x20\x66\x75\x6E\x63\x74\x69\x6F\x6E\x20\x67\x65\x74\x45\x6E\x76\x69\x72\x6F\x6E\x6D\x65\x6E\x74\x28\x29\x20\x7B\x0A\x09\x09\x09\x09\x63\x6F\x6E\x73\x74\x20\x72\x65\x73\x20\x3D\x20\x61\x77\x61\x69\x74\x20\x72\x65\x73\x63\x2E\x67\x65\x74\x45\x6E\x76\x69\x72\x6F\x6E\x6D\x65\x6E\x74\x28\x29\x0A\x09\x09\x09\x09\x69\x66\x20\x28\x72\x65\x73\x2E\x63\x6F\x64\x65\x20\x21\x3D\x20\x32\x30\x30\x29\x20\x7B\x0A\x09\x09\x09\x09\x09\x6E\x6F\x74\x69\x66\x45\x72\x72\x6F\x72\x28\x72\x65\x73\x2E\x6D\x65\x73\x73\x61\x67\x65\x29\x0A\x09\x09\x09\x09\x09\x72\x65\x74\x75\x72\x6E\x0A\x09\x09\x09\x09\x7D\x0A\x0A\x09\x09\x09\x09\x6C\x65\x74\x20\x65\x6E\x76\x20\x3D\x20\x72\x65\x73\x2E\x64\x61\x74\x61\x0A\x09\x09\x09\x09\x72\x65\x6E\x64\x65\x72\x48\x6F\x73\x74\x73\x42\x6C\x6F\x63\x6B\x73\x28\x65\x6E\x76\x2E\x48\x6F\x73\x74\x73\x42\x6C\x6F\x63\x6B\x73\x29\x0A\x09\x09\x09\x7D\x0A\x0A\x09\x09\x09\x66\x75\x6E\x63\x74\x69\x6F\x6E\x20\x6F\x6E\x43\x68\x65\x63\x6B\x48\x6F\x73\x74\x62\x6C\x6F\x63\x6B\x28\x6B\x65\x79\x2C\x20\x76\x61\x6C\x29\x20\x7B\x0A\x09\x09\x09\x09\x72\x65\x73\x63\x2E\x65\x6E\x76\x2E\x48\x6F\x73\x74\x73\x42\x6C\x6F\x63\x6B\x73\x5B\x6B\x65\x79\x5D\x2E\x49\x73\x45\x6E\x61\x62\x6C\x65\x64\x20\x3D\x20\x76\x61\x6C\x0A\x09\x09\x09\x7D\x0A\x0A\x09\x09\x09\x61\x73\x79\x6E\x63\x20\x66\x75\x6E\x63\x74\x69\x6F\x6E\x20\x75\x70\x64\x61\x74\x65\x48\x6F\x73\x74\x73\x42\x6C\x6F\x63\x6B\x73\x28\x29\x20\x7B\x0A\x09\x09\x09\x09\x63\x6F\x6E\x73\x74\x20\x72\x65\x73\x20\x3D\x20\x61\x77\x61\x69\x74\x20\x72\x65\x73\x63\x2E\x75\x70\x64\x61\x74\x65\x48\x6F\x73\x74\x73\x42\x6C\x6F\x63\x6B\x73\x28\x72\x65\x73\x63\x2E\x65\x6E\x76\x2E\x48\x6F\x73\x74\x73\x42\x6C\x6F\x63\x6B\x73\x29\x0A\x09\x09\x09\x09\x69\x66\x20\x28\x72\x65\x73\x2E\x63\x6F\x64\x65\x20\x21\x3D\x20\x32\x30\x30\x29\x20\x7B\x0A\x09\x09\x09\x09\x09\x6E\x6F\x74\x69\x66\x45\x72\x72\x6F\x72\x28\x22\x75\x70\x64\x61\x74\x65\x48\x6F\x73\x74\x73\x42\x6C\x6F\x63\x6B\x73\x3A\x20\x22\x2C\x20\x72\x65\x73\x2E\x6D\x65\x73\x73\x61\x67\x65\x29\x0A\x09\x09\x09\x09\x09\x72\x65\x74\x75\x72\x6E\x0A\x09\x09\x09\x09\x7D\x0A\x0A\x09\x09\x09\x09\x72\x65\x6E\x64\x65\x72\x48\x6F\x73\x74\x73\x42\x6C\x6F\x63\x6B\x73\x28\x72\x65\x73\x2E\x64\x61\x74\x61\x29\x0A\x09\x09\x09\x09\x6E\x6F\x74\x69\x66\x49\x6E\x66\x6F\x28\x22\x54\x68\x65\x20\x68\x6F\x73\x74\x73\x20\x62\x6C\x6F\x63\x6B\x73\x20\x68\x61\x73\x20\x62\x65\x65\x6E\x20\x75\x70\x64\x61\x74\x65\x64\x2E\x22\x29\x0A\x09\x09\x09\x7D\x0A\x0A\x09\x09\x09\x66\x75\x6E\x63\x74\x69\x6F\x6E\x20\x72\x65\x6E\x64\x65\x72\x48\x6F\x73\x74\x73\x42\x6C\x6F\x63\x6B\x73\x28\x68\x6F\x73\x74\x73\x42\x6C\x6F\x63\x6B\x73\x29\x20\x7B\x0A\x09\x09\x09\x09\x6C\x65\x74\x20\x70\x61\x72\x65\x6E\x74\x20\x3D\x20\x64\x6F\x63\x75\x6D\x65\x6E\x74\x2E\x67\x65\x74\x45\x6C\x65\x6D\x65\x6E\x74\x42\x79\x49\x64\x28\x22\x48\x6F\x73\x74\x73\x42\x6C\x6F\x63\x6B\x73\x22\x29\x0A\x09\x09\x09\x09\x70\x61\x72\x65\x6E\x74\x2E\x69\x6E\x6E\x65\x72\x48\x54\x4D\x4C\x20\x3D\x20\x22\x22\x0A\x0A\x09\x09\x09\x09\x66\x6F\x72\x20\x28\x76\x61\x72\x20\x6B\x20\x69\x6E\x20\x68\x6F\x73\x74\x73\x42\x6C\x6F\x63\x6B\x73\x29\x20\x7B\x0A\x09\x09\x09\x09\x09\x69\x66\x20\x28\x21\x68\x6F\x73\x74\x73\x42\x6C\x6F\x63\x6B\x73\x2E\x68\x61\x73\x4F\x77\x6E\x50\x72\x6F\x70\x65\x72\x74\x79\x28\x6B\x29\x29\x20\x7B\x0A\x09\x09\x09\x09\x09\x09\x63\x6F\x6E\x74\x69\x6E\x75\x65\x3B\x0A\x09\x09\x09\x09\x09\x7D\x0A\x0A\x09\x09\x09\x09\x09\x6C\x65\x74\x20\x68\x6F\x73\x74\x73\x42\x6C\x6F\x63\x6B\x20\x3D\x20\x68\x6F\x73\x74\x73\x42\x6C\x6F\x63\x6B\x73\x5B\x6B\x5D\x0A\x09\x09\x09\x09\x09\x6C\x65\x74\x20\x69\x74\x65\x6D\x20\x3D\x20\x64\x6F\x63\x75\x6D\x65\x6E\x74\x2E\x63\x72\x65\x61\x74\x65\x45\x6C\x65\x6D\x65\x6E\x74\x28\x22\x64\x69\x76\x22\x29\x0A\x09\x09\x09\x09\x09\x69\x74\x65\x6D\x2E\x63\x6C\x61\x73\x73\x4C\x69\x73\x74\x2E\x61\x64\x64\x28\x22\x69\x74\x65\x6D\x22\x29\x0A\x09\x09\x09\x09\x09\x69\x74\x65\x6D\x2E\x69\x6E\x6E\x65\x72\x48\x54\x4D\x4C\x20\x3D\x20\x60\x0A\x09\x09\x09\x09\x09\x09\x3C\x73\x70\x61\x6E\x20\x63\x6C\x61\x73\x73\x3D\x22\x69\x73\x2D\x65\x6E\x61\x62\x6C\x65\x64\x22\x3E\x0A\x09\x09\x09\x09\x09\x09\x09\x3C\x69\x6E\x70\x75\x74\x0A\x09\x09\x09\x09\x09\x09\x09\x09\x74\x79\x70\x65\x3D\x22\x63\x68\x65\x63\x6B\x62\x6F\x78\x22\x0A\x09\x09\x09\x09\x09\x09\x09\x09\x24\x7B\x68\x6F\x73\x74\x73\x42\x6C\x6F\x63\x6B\x2E\x49\x73\x45\x6E\x61\x62\x6C\x65\x64\x20\x3F\x20\x22\x63\x68\x65\x63\x6B\x65\x64\x22\x20\x3A\x20\x22\x22\x7D\x0A\x09\x09\x09\x09\x09\x09\x09\x09\x6F\x6E\x69\x6E\x70\x75\x74\x3D\x22\x6F\x6E\x43\x68\x65\x63\x6B\x48\x6F\x73\x74\x62\x6C\x6F\x63\x6B\x28\x27\x24\x7B\x6B\x7D\x27\x2C\x20\x74\x68\x69\x73\x2E\x63\x68\x65\x63\x6B\x65\x64\x29\x22\x0A\x09\x09\x09\x09\x09\x09\x09\x2F\x3E\x0A\x09\x09\x09\x09\x09\x09\x3C\x2F\x73\x70\x61\x6E\x3E\x0A\x09\x09\x09\x09\x09\x09\x3C\x73\x70\x61\x6E\x20\x63\x6C\x61\x73\x73\x3D\x22\x69\x6E\x66\x6F\x22\x3E\x0A\x09\x09\x09\x09\x09\x09\x09\x3C\x64\x69\x76\x3E\x24\x7B\x68\x6F\x73\x74\x73\x42\x6C\x6F\x63\x6B\x2E\x4E\x61\x6D\x65\x7D\x3C\x2F\x64\x69\x76\x3E\x0A\x09\x09\x09\x09\x09\x09\x09\x3C\x64\x69\x76\x3E\x20\x55\x52\x4C\x3A\x20\x3C\x69\x6E\x70\x75\x74\x20\x76\x61\x6C\x75\x65\x3D\x22\x24\x7B\x68\x6F\x73\x74\x73\x42\x6C\x6F\x63\x6B\x2E\x55\x52\x4C\x7D\x22\x20\x64\x69\x73\x61\x62\x6C\x65\x64\x20\x2F\x3E\x20\x3C\x2F\x64\x69\x76\x3E\x0A\x09\x09\x09\x09\x09\x09\x09\x3C\x64\x69\x76\x3E\x20\x4C\x61\x73\x74\x20\x75\x70\x64\x61\x74\x65\x64\x20\x61\x74\x20\x24\x7B\x68\x6F\x73\x74\x73\x42\x6C\x6F\x63\x6B\x2E\x4C\x61\x73\x74\x55\x70\x64\x61\x74\x65\x64\x7D\x20\x3C\x2F\x64\x69\x76\x3E\x0A\x09\x09\x09\x09\x09\x09\x3C\x2F\x73\x70\x61\x6E\x3E\x60\x0A\x0A\x09\x09\x09\x09\x09\x70\x61\x72\x65\x6E\x74\x2E\x61\x70\x70\x65\x6E\x64\x43\x68\x69\x6C\x64\x28\x69\x74\x65\x6D\x29\x0A\x09\x09\x09\x09\x7D\x0A\x09\x09\x09\x7D\x0A\x09\x09\x3C\x2F\x73\x63\x72\x69\x70\x74\x3E\x0A\x09\x3C\x2F\x62\x6F\x64\x79\x3E\x0A\x3C\x2F\x68\x74\x6D\x6C\x3E\x0A"), } node.SetMode(420) - node.SetModTimeUnix(1647276210, 803014611) + node.SetModTimeUnix(1650380258, 721203491) node.SetName("index.html") - node.SetSize(3229) + node.SetSize(3278) return node } @@ -190,7 +190,7 @@ func generate__www_rescached_js() *memfs.Node { Content: []byte("\x2F\x2F\x20\x53\x50\x44\x58\x2D\x46\x69\x6C\x65\x43\x6F\x70\x79\x72\x69\x67\x68\x74\x54\x65\x78\x74\x3A\x20\x32\x30\x32\x31\x20\x4D\x2E\x20\x53\x68\x75\x6C\x68\x61\x6E\x20\x3C\x6D\x73\x40\x6B\x69\x6C\x61\x62\x69\x74\x2E\x69\x6E\x66\x6F\x3E\x0A\x2F\x2F\x20\x53\x50\x44\x58\x2D\x4C\x69\x63\x65\x6E\x73\x65\x2D\x49\x64\x65\x6E\x74\x69\x66\x69\x65\x72\x3A\x20\x47\x50\x4C\x2D\x33\x2E\x30\x2D\x6F\x72\x2D\x6C\x61\x74\x65\x72\x0A\x0A\x63\x6F\x6E\x73\x74\x20\x52\x52\x54\x79\x70\x65\x73\x20\x3D\x20\x7B\x0A\x09\x31\x3A\x20\x22\x41\x22\x2C\x0A\x09\x32\x3A\x20\x22\x4E\x53\x22\x2C\x0A\x09\x33\x3A\x20\x22\x4D\x44\x22\x2C\x0A\x09\x34\x3A\x20\x22\x4D\x46\x22\x2C\x0A\x09\x35\x3A\x20\x22\x43\x4E\x41\x4D\x45\x22\x2C\x0A\x09\x36\x3A\x20\x22\x53\x4F\x41\x22\x2C\x0A\x09\x37\x3A\x20\x22\x4D\x42\x22\x2C\x0A\x09\x38\x3A\x20\x22\x4D\x47\x22\x2C\x0A\x09\x39\x3A\x20\x22\x4D\x52\x22\x2C\x0A\x09\x31\x30\x3A\x20\x22\x4E\x55\x4C\x4C\x22\x2C\x0A\x09\x31\x31\x3A\x20\x22\x57\x4B\x53\x22\x2C\x0A\x09\x31\x32\x3A\x20\x22\x50\x54\x52\x22\x2C\x0A\x09\x31\x33\x3A\x20\x22\x48\x49\x4E\x46\x4F\x22\x2C\x0A\x09\x31\x34\x3A\x20\x22\x4D\x49\x4E\x46\x4F\x22\x2C\x0A\x09\x31\x35\x3A\x20\x22\x4D\x58\x22\x2C\x0A\x09\x31\x36\x3A\x20\x22\x54\x58\x54\x22\x2C\x0A\x09\x32\x38\x3A\x20\x22\x41\x41\x41\x41\x22\x2C\x0A\x09\x33\x33\x3A\x20\x22\x53\x52\x56\x22\x2C\x0A\x09\x34\x31\x3A\x20\x22\x4F\x50\x54\x22\x2C\x0A\x7D\x0A\x0A\x66\x75\x6E\x63\x74\x69\x6F\x6E\x20\x67\x65\x74\x52\x52\x54\x79\x70\x65\x4E\x61\x6D\x65\x28\x6B\x29\x20\x7B\x0A\x09\x6C\x65\x74\x20\x76\x20\x3D\x20\x52\x52\x54\x79\x70\x65\x73\x5B\x6B\x5D\x0A\x09\x69\x66\x20\x28\x76\x20\x3D\x3D\x3D\x20\x22\x22\x29\x20\x7B\x0A\x09\x09\x72\x65\x74\x75\x72\x6E\x20\x6B\x0A\x09\x7D\x0A\x09\x72\x65\x74\x75\x72\x6E\x20\x76\x0A\x7D\x0A\x0A\x63\x6C\x61\x73\x73\x20\x52\x65\x73\x63\x61\x63\x68\x65\x64\x20\x7B\x0A\x09\x73\x74\x61\x74\x69\x63\x20\x6E\x61\x6E\x6F\x53\x65\x63\x6F\x6E\x64\x73\x20\x3D\x20\x31\x30\x30\x30\x30\x30\x30\x30\x30\x30\x0A\x09\x73\x74\x61\x74\x69\x63\x20\x61\x70\x69\x43\x61\x63\x68\x65\x73\x20\x3D\x20\x22\x2F\x61\x70\x69\x2F\x63\x61\x63\x68\x65\x73\x22\x0A\x09\x73\x74\x61\x74\x69\x63\x20\x61\x70\x69\x43\x61\x63\x68\x65\x73\x53\x65\x61\x72\x63\x68\x20\x3D\x20\x22\x2F\x61\x70\x69\x2F\x63\x61\x63\x68\x65\x73\x2F\x73\x65\x61\x72\x63\x68\x22\x0A\x09\x73\x74\x61\x74\x69\x63\x20\x61\x70\x69\x48\x6F\x73\x74\x73\x64\x20\x3D\x20\x22\x2F\x61\x70\x69\x2F\x68\x6F\x73\x74\x73\x2E\x64\x2F\x22\x0A\x09\x73\x74\x61\x74\x69\x63\x20\x61\x70\x69\x5A\x6F\x6E\x65\x64\x20\x3D\x20\x22\x2F\x61\x70\x69\x2F\x7A\x6F\x6E\x65\x2E\x64\x2F\x22\x0A\x0A\x09\x63\x6F\x6E\x73\x74\x72\x75\x63\x74\x6F\x72\x28\x73\x65\x72\x76\x65\x72\x29\x20\x7B\x0A\x09\x09\x74\x68\x69\x73\x2E\x73\x65\x72\x76\x65\x72\x20\x3D\x20\x73\x65\x72\x76\x65\x72\x0A\x09\x09\x74\x68\x69\x73\x2E\x65\x6E\x76\x20\x3D\x20\x7B\x7D\x0A\x09\x7D\x0A\x0A\x09\x61\x73\x79\x6E\x63\x20\x43\x61\x63\x68\x65\x73\x28\x29\x20\x7B\x0A\x09\x09\x63\x6F\x6E\x73\x74\x20\x72\x65\x73\x20\x3D\x20\x61\x77\x61\x69\x74\x20\x66\x65\x74\x63\x68\x28\x74\x68\x69\x73\x2E\x73\x65\x72\x76\x65\x72\x20\x2B\x20\x52\x65\x73\x63\x61\x63\x68\x65\x64\x2E\x61\x70\x69\x43\x61\x63\x68\x65\x73\x2C\x20\x7B\x0A\x09\x09\x09\x68\x65\x61\x64\x65\x72\x73\x3A\x20\x7B\x0A\x09\x09\x09\x09\x43\x6F\x6E\x6E\x65\x63\x74\x69\x6F\x6E\x3A\x20\x22\x6B\x65\x65\x70\x2D\x61\x6C\x69\x76\x65\x22\x2C\x0A\x09\x09\x09\x7D\x2C\x0A\x09\x09\x7D\x29\x0A\x09\x09\x72\x65\x74\x75\x72\x6E\x20\x61\x77\x61\x69\x74\x20\x72\x65\x73\x2E\x6A\x73\x6F\x6E\x28\x29\x0A\x09\x7D\x0A\x0A\x09\x61\x73\x79\x6E\x63\x20\x43\x61\x63\x68\x65\x52\x65\x6D\x6F\x76\x65\x28\x71\x6E\x61\x6D\x65\x29\x20\x7B\x0A\x09\x09\x63\x6F\x6E\x73\x74\x20\x72\x65\x73\x20\x3D\x20\x61\x77\x61\x69\x74\x20\x66\x65\x74\x63\x68\x28\x0A\x09\x09\x09\x74\x68\x69\x73\x2E\x73\x65\x72\x76\x65\x72\x20\x2B\x20\x52\x65\x73\x63\x61\x63\x68\x65\x64\x2E\x61\x70\x69\x43\x61\x63\x68\x65\x73\x20\x2B\x20\x22\x3F\x6E\x61\x6D\x65\x3D\x22\x20\x2B\x20\x71\x6E\x61\x6D\x65\x2C\x0A\x09\x09\x09\x7B\x0A\x09\x09\x09\x09\x6D\x65\x74\x68\x6F\x64\x3A\x20\x22\x44\x45\x4C\x45\x54\x45\x22\x2C\x0A\x09\x09\x09\x7D\x2C\x0A\x09\x09\x29\x0A\x09\x09\x72\x65\x74\x75\x72\x6E\x20\x61\x77\x61\x69\x74\x20\x72\x65\x73\x2E\x6A\x73\x6F\x6E\x28\x29\x0A\x09\x7D\x0A\x0A\x09\x61\x73\x79\x6E\x63\x20\x53\x65\x61\x72\x63\x68\x28\x71\x75\x65\x72\x79\x29\x20\x7B\x0A\x09\x09\x63\x6F\x6E\x73\x6F\x6C\x65\x2E\x6C\x6F\x67\x28\x22\x53\x65\x61\x72\x63\x68\x3A\x20\x22\x2C\x20\x71\x75\x65\x72\x79\x29\x0A\x09\x09\x63\x6F\x6E\x73\x74\x20\x72\x65\x73\x20\x3D\x20\x61\x77\x61\x69\x74\x20\x66\x65\x74\x63\x68\x28\x0A\x09\x09\x09\x74\x68\x69\x73\x2E\x73\x65\x72\x76\x65\x72\x20\x2B\x0A\x09\x09\x09\x09\x52\x65\x73\x63\x61\x63\x68\x65\x64\x2E\x61\x70\x69\x43\x61\x63\x68\x65\x73\x53\x65\x61\x72\x63\x68\x20\x2B\x0A\x09\x09\x09\x09\x22\x3F\x71\x75\x65\x72\x79\x3D\x22\x20\x2B\x0A\x09\x09\x09\x09\x71\x75\x65\x72\x79\x2C\x0A\x09\x09\x29\x0A\x09\x09\x72\x65\x74\x75\x72\x6E\x20\x61\x77\x61\x69\x74\x20\x72\x65\x73\x2E\x6A\x73\x6F\x6E\x28\x29\x0A\x09\x7D\x0A\x0A\x09\x61\x73\x79\x6E\x63\x20\x48\x6F\x73\x74\x73\x46\x69\x6C\x65\x43\x72\x65\x61\x74\x65\x28\x6E\x61\x6D\x65\x29\x20\x7B\x0A\x09\x09\x63\x6F\x6E\x73\x74\x20\x68\x74\x74\x70\x52\x65\x73\x20\x3D\x20\x61\x77\x61\x69\x74\x20\x66\x65\x74\x63\x68\x28\x0A\x09\x09\x09\x74\x68\x69\x73\x2E\x73\x65\x72\x76\x65\x72\x20\x2B\x20\x52\x65\x73\x63\x61\x63\x68\x65\x64\x2E\x61\x70\x69\x48\x6F\x73\x74\x73\x64\x20\x2B\x20\x6E\x61\x6D\x65\x2C\x0A\x09\x09\x09\x7B\x0A\x09\x09\x09\x09\x6D\x65\x74\x68\x6F\x64\x3A\x20\x22\x50\x55\x54\x22\x2C\x0A\x09\x09\x09\x7D\x2C\x0A\x09\x09\x29\x0A\x09\x09\x6C\x65\x74\x20\x72\x65\x73\x20\x3D\x20\x61\x77\x61\x69\x74\x20\x68\x74\x74\x70\x52\x65\x73\x2E\x6A\x73\x6F\x6E\x28\x29\x0A\x09\x09\x69\x66\x20\x28\x72\x65\x73\x2E\x63\x6F\x64\x65\x20\x3D\x3D\x3D\x20\x32\x30\x30\x29\x20\x7B\x0A\x09\x09\x09\x74\x68\x69\x73\x2E\x65\x6E\x76\x2E\x48\x6F\x73\x74\x73\x46\x69\x6C\x65\x73\x5B\x6E\x61\x6D\x65\x5D\x20\x3D\x20\x7B\x0A\x09\x09\x09\x09\x4E\x61\x6D\x65\x3A\x20\x6E\x61\x6D\x65\x2C\x0A\x09\x09\x09\x09\x52\x65\x63\x6F\x72\x64\x73\x3A\x20\x5B\x5D\x2C\x0A\x09\x09\x09\x7D\x0A\x09\x09\x7D\x0A\x09\x09\x72\x65\x74\x75\x72\x6E\x20\x72\x65\x73\x0A\x09\x7D\x0A\x0A\x09\x61\x73\x79\x6E\x63\x20\x67\x65\x74\x45\x6E\x76\x69\x72\x6F\x6E\x6D\x65\x6E\x74\x28\x29\x20\x7B\x0A\x09\x09\x63\x6F\x6E\x73\x74\x20\x68\x74\x74\x70\x52\x65\x73\x20\x3D\x20\x61\x77\x61\x69\x74\x20\x66\x65\x74\x63\x68\x28\x74\x68\x69\x73\x2E\x73\x65\x72\x76\x65\x72\x20\x2B\x20\x22\x2F\x61\x70\x69\x2F\x65\x6E\x76\x69\x72\x6F\x6E\x6D\x65\x6E\x74\x22\x29\x0A\x09\x09\x63\x6F\x6E\x73\x74\x20\x72\x65\x73\x20\x3D\x20\x61\x77\x61\x69\x74\x20\x68\x74\x74\x70\x52\x65\x73\x2E\x6A\x73\x6F\x6E\x28\x29\x0A\x0A\x09\x09\x69\x66\x20\x28\x68\x74\x74\x70\x52\x65\x73\x2E\x73\x74\x61\x74\x75\x73\x20\x3D\x3D\x3D\x20\x32\x30\x30\x29\x20\x7B\x0A\x09\x09\x09\x72\x65\x73\x2E\x64\x61\x74\x61\x2E\x50\x72\x75\x6E\x65\x44\x65\x6C\x61\x79\x20\x3D\x0A\x09\x09\x09\x09\x72\x65\x73\x2E\x64\x61\x74\x61\x2E\x50\x72\x75\x6E\x65\x44\x65\x6C\x61\x79\x20\x2F\x20\x52\x65\x73\x63\x61\x63\x68\x65\x64\x2E\x6E\x61\x6E\x6F\x53\x65\x63\x6F\x6E\x64\x73\x0A\x09\x09\x09\x72\x65\x73\x2E\x64\x61\x74\x61\x2E\x50\x72\x75\x6E\x65\x54\x68\x72\x65\x73\x68\x6F\x6C\x64\x20\x3D\x0A\x09\x09\x09\x09\x72\x65\x73\x2E\x64\x61\x74\x61\x2E\x50\x72\x75\x6E\x65\x54\x68\x72\x65\x73\x68\x6F\x6C\x64\x20\x2F\x0A\x09\x09\x09\x09\x52\x65\x73\x63\x61\x63\x68\x65\x64\x2E\x6E\x61\x6E\x6F\x53\x65\x63\x6F\x6E\x64\x73\x0A\x0A\x09\x09\x09\x66\x6F\x72\x20\x28\x6C\x65\x74\x20\x6B\x20\x69\x6E\x20\x72\x65\x73\x2E\x64\x61\x74\x61\x2E\x48\x6F\x73\x74\x73\x46\x69\x6C\x65\x73\x29\x20\x7B\x0A\x09\x09\x09\x09\x69\x66\x20\x28\x21\x72\x65\x73\x2E\x64\x61\x74\x61\x2E\x48\x6F\x73\x74\x73\x46\x69\x6C\x65\x73\x2E\x68\x61\x73\x4F\x77\x6E\x50\x72\x6F\x70\x65\x72\x74\x79\x28\x6B\x29\x29\x20\x7B\x0A\x09\x09\x09\x09\x09\x63\x6F\x6E\x74\x69\x6E\x75\x65\x0A\x09\x09\x09\x09\x7D\x0A\x09\x09\x09\x09\x6C\x65\x74\x20\x68\x66\x20\x3D\x20\x72\x65\x73\x2E\x64\x61\x74\x61\x2E\x48\x6F\x73\x74\x73\x46\x69\x6C\x65\x73\x5B\x6B\x5D\x0A\x09\x09\x09\x09\x69\x66\x20\x28\x74\x79\x70\x65\x6F\x66\x20\x68\x66\x2E\x52\x65\x63\x6F\x72\x64\x73\x20\x3D\x3D\x3D\x20\x22\x75\x6E\x64\x65\x66\x69\x6E\x65\x64\x22\x29\x20\x7B\x0A\x09\x09\x09\x09\x09\x68\x66\x2E\x52\x65\x63\x6F\x72\x64\x73\x20\x3D\x20\x5B\x5D\x0A\x09\x09\x09\x09\x7D\x0A\x09\x09\x09\x7D\x0A\x09\x09\x09\x74\x68\x69\x73\x2E\x65\x6E\x76\x20\x3D\x20\x72\x65\x73\x2E\x64\x61\x74\x61\x0A\x09\x09\x7D\x0A\x09\x09\x72\x65\x74\x75\x72\x6E\x20\x72\x65\x73\x0A\x09\x7D\x0A\x0A\x09\x47\x65\x74\x52\x52\x54\x79\x70\x65\x4E\x61\x6D\x65\x28\x6B\x29\x20\x7B\x0A\x09\x09\x6C\x65\x74\x20\x76\x20\x3D\x20\x52\x52\x54\x79\x70\x65\x73\x5B\x6B\x5D\x0A\x09\x09\x69\x66\x20\x28\x76\x20\x3D\x3D\x3D\x20\x22\x22\x29\x20\x7B\x0A\x09\x09\x09\x72\x65\x74\x75\x72\x6E\x20\x6B\x0A\x09\x09\x7D\x0A\x09\x09\x72\x65\x74\x75\x72\x6E\x20\x76\x0A\x09\x7D\x0A\x0A\x09\x61\x73\x79\x6E\x63\x20\x48\x6F\x73\x74\x73\x46\x69\x6C\x65\x44\x65\x6C\x65\x74\x65\x28\x6E\x61\x6D\x65\x29\x20\x7B\x0A\x09\x09\x63\x6F\x6E\x73\x74\x20\x68\x74\x74\x70\x52\x65\x73\x20\x3D\x20\x61\x77\x61\x69\x74\x20\x66\x65\x74\x63\x68\x28\x0A\x09\x09\x09\x74\x68\x69\x73\x2E\x73\x65\x72\x76\x65\x72\x20\x2B\x20\x52\x65\x73\x63\x61\x63\x68\x65\x64\x2E\x61\x70\x69\x48\x6F\x73\x74\x73\x64\x20\x2B\x20\x6E\x61\x6D\x65\x2C\x0A\x09\x09\x09\x7B\x0A\x09\x09\x09\x09\x6D\x65\x74\x68\x6F\x64\x3A\x20\x22\x44\x45\x4C\x45\x54\x45\x22\x2C\x0A\x09\x09\x09\x7D\x2C\x0A\x09\x09\x29\x0A\x09\x09\x63\x6F\x6E\x73\x74\x20\x72\x65\x73\x20\x3D\x20\x61\x77\x61\x69\x74\x20\x68\x74\x74\x70\x52\x65\x73\x2E\x6A\x73\x6F\x6E\x28\x29\x0A\x09\x09\x69\x66\x20\x28\x68\x74\x74\x70\x52\x65\x73\x2E\x73\x74\x61\x74\x75\x73\x20\x3D\x3D\x3D\x20\x32\x30\x30\x29\x20\x7B\x0A\x09\x09\x09\x64\x65\x6C\x65\x74\x65\x20\x74\x68\x69\x73\x2E\x65\x6E\x76\x2E\x48\x6F\x73\x74\x73\x46\x69\x6C\x65\x73\x5B\x6E\x61\x6D\x65\x5D\x0A\x09\x09\x7D\x0A\x09\x09\x72\x65\x74\x75\x72\x6E\x20\x72\x65\x73\x0A\x09\x7D\x0A\x0A\x09\x61\x73\x79\x6E\x63\x20\x48\x6F\x73\x74\x73\x46\x69\x6C\x65\x47\x65\x74\x28\x6E\x61\x6D\x65\x29\x20\x7B\x0A\x09\x09\x63\x6F\x6E\x73\x74\x20\x68\x74\x74\x70\x52\x65\x73\x20\x3D\x20\x61\x77\x61\x69\x74\x20\x66\x65\x74\x63\x68\x28\x0A\x09\x09\x09\x74\x68\x69\x73\x2E\x73\x65\x72\x76\x65\x72\x20\x2B\x20\x52\x65\x73\x63\x61\x63\x68\x65\x64\x2E\x61\x70\x69\x48\x6F\x73\x74\x73\x64\x20\x2B\x20\x6E\x61\x6D\x65\x2C\x0A\x09\x09\x29\x0A\x09\x09\x6C\x65\x74\x20\x72\x65\x73\x20\x3D\x20\x61\x77\x61\x69\x74\x20\x68\x74\x74\x70\x52\x65\x73\x2E\x6A\x73\x6F\x6E\x28\x29\x0A\x09\x09\x69\x66\x20\x28\x68\x74\x74\x70\x52\x65\x73\x2E\x53\x74\x61\x74\x75\x73\x20\x3D\x3D\x3D\x20\x32\x30\x30\x29\x20\x7B\x0A\x09\x09\x09\x74\x68\x69\x73\x2E\x65\x6E\x76\x2E\x48\x6F\x73\x74\x73\x46\x69\x6C\x65\x73\x5B\x6E\x61\x6D\x65\x5D\x20\x3D\x20\x7B\x0A\x09\x09\x09\x09\x4E\x61\x6D\x65\x3A\x20\x6E\x61\x6D\x65\x2C\x0A\x09\x09\x09\x09\x52\x65\x63\x6F\x72\x64\x73\x3A\x20\x72\x65\x73\x2E\x64\x61\x74\x61\x2C\x0A\x09\x09\x09\x7D\x0A\x09\x09\x7D\x0A\x09\x09\x72\x65\x74\x75\x72\x6E\x20\x72\x65\x73\x0A\x09\x7D\x0A\x0A\x09\x61\x73\x79\x6E\x63\x20\x48\x6F\x73\x74\x73\x46\x69\x6C\x65\x52\x65\x63\x6F\x72\x64\x41\x64\x64\x28\x68\x6F\x73\x74\x73\x46\x69\x6C\x65\x2C\x20\x64\x6F\x6D\x61\x69\x6E\x2C\x20\x76\x61\x6C\x75\x65\x29\x20\x7B\x0A\x09\x09\x6C\x65\x74\x20\x70\x61\x72\x61\x6D\x73\x20\x3D\x20\x6E\x65\x77\x20\x55\x52\x4C\x53\x65\x61\x72\x63\x68\x50\x61\x72\x61\x6D\x73\x28\x29\x0A\x09\x09\x70\x61\x72\x61\x6D\x73\x2E\x73\x65\x74\x28\x22\x64\x6F\x6D\x61\x69\x6E\x22\x2C\x20\x64\x6F\x6D\x61\x69\x6E\x29\x0A\x09\x09\x70\x61\x72\x61\x6D\x73\x2E\x73\x65\x74\x28\x22\x76\x61\x6C\x75\x65\x22\x2C\x20\x76\x61\x6C\x75\x65\x29\x0A\x0A\x09\x09\x63\x6F\x6E\x73\x74\x20\x61\x70\x69\x20\x3D\x0A\x09\x09\x09\x74\x68\x69\x73\x2E\x73\x65\x72\x76\x65\x72\x20\x2B\x0A\x09\x09\x09\x52\x65\x73\x63\x61\x63\x68\x65\x64\x2E\x61\x70\x69\x48\x6F\x73\x74\x73\x64\x20\x2B\x0A\x09\x09\x09\x68\x6F\x73\x74\x73\x46\x69\x6C\x65\x20\x2B\x0A\x09\x09\x09\x22\x2F\x72\x72\x22\x20\x2B\x0A\x09\x09\x09\x22\x3F\x22\x20\x2B\x0A\x09\x09\x09\x70\x61\x72\x61\x6D\x73\x2E\x74\x6F\x53\x74\x72\x69\x6E\x67\x28\x29\x0A\x0A\x09\x09\x63\x6F\x6E\x73\x74\x20\x68\x74\x74\x70\x52\x65\x73\x20\x3D\x20\x61\x77\x61\x69\x74\x20\x66\x65\x74\x63\x68\x28\x61\x70\x69\x2C\x20\x7B\x0A\x09\x09\x09\x6D\x65\x74\x68\x6F\x64\x3A\x20\x22\x50\x4F\x53\x54\x22\x2C\x0A\x09\x09\x7D\x29\x0A\x09\x09\x63\x6F\x6E\x73\x74\x20\x72\x65\x73\x20\x3D\x20\x61\x77\x61\x69\x74\x20\x68\x74\x74\x70\x52\x65\x73\x2E\x6A\x73\x6F\x6E\x28\x29\x0A\x09\x09\x69\x66\x20\x28\x68\x74\x74\x70\x52\x65\x73\x2E\x53\x74\x61\x74\x75\x73\x20\x3D\x3D\x3D\x20\x32\x30\x30\x29\x20\x7B\x0A\x09\x09\x09\x6C\x65\x74\x20\x68\x66\x20\x3D\x20\x74\x68\x69\x73\x2E\x65\x6E\x76\x2E\x48\x6F\x73\x74\x73\x46\x69\x6C\x65\x73\x5B\x68\x6F\x73\x74\x73\x46\x69\x6C\x65\x5D\x0A\x09\x09\x09\x68\x66\x2E\x52\x65\x63\x6F\x72\x64\x73\x2E\x70\x75\x73\x68\x28\x72\x65\x73\x2E\x64\x61\x74\x61\x29\x0A\x09\x09\x7D\x0A\x09\x09\x72\x65\x74\x75\x72\x6E\x20\x72\x65\x73\x0A\x09\x7D\x0A\x0A\x09\x61\x73\x79\x6E\x63\x20\x48\x6F\x73\x74\x73\x46\x69\x6C\x65\x52\x65\x63\x6F\x72\x64\x44\x65\x6C\x65\x74\x65\x28\x68\x6F\x73\x74\x73\x46\x69\x6C\x65\x2C\x20\x64\x6F\x6D\x61\x69\x6E\x29\x20\x7B\x0A\x09\x09\x6C\x65\x74\x20\x70\x61\x72\x61\x6D\x73\x20\x3D\x20\x6E\x65\x77\x20\x55\x52\x4C\x53\x65\x61\x72\x63\x68\x50\x61\x72\x61\x6D\x73\x28\x29\x0A\x09\x09\x70\x61\x72\x61\x6D\x73\x2E\x73\x65\x74\x28\x22\x64\x6F\x6D\x61\x69\x6E\x22\x2C\x20\x64\x6F\x6D\x61\x69\x6E\x29\x0A\x0A\x09\x09\x63\x6F\x6E\x73\x74\x20\x61\x70\x69\x20\x3D\x0A\x09\x09\x09\x74\x68\x69\x73\x2E\x73\x65\x72\x76\x65\x72\x20\x2B\x0A\x09\x09\x09\x52\x65\x73\x63\x61\x63\x68\x65\x64\x2E\x61\x70\x69\x48\x6F\x73\x74\x73\x64\x20\x2B\x0A\x09\x09\x09\x68\x6F\x73\x74\x73\x46\x69\x6C\x65\x20\x2B\x0A\x09\x09\x09\x22\x2F\x72\x72\x22\x20\x2B\x0A\x09\x09\x09\x22\x3F\x22\x20\x2B\x0A\x09\x09\x09\x70\x61\x72\x61\x6D\x73\x2E\x74\x6F\x53\x74\x72\x69\x6E\x67\x28\x29\x0A\x0A\x09\x09\x63\x6F\x6E\x73\x74\x20\x68\x74\x74\x70\x52\x65\x73\x20\x3D\x20\x61\x77\x61\x69\x74\x20\x66\x65\x74\x63\x68\x28\x61\x70\x69\x2C\x20\x7B\x0A\x09\x09\x09\x6D\x65\x74\x68\x6F\x64\x3A\x20\x22\x44\x45\x4C\x45\x54\x45\x22\x2C\x0A\x09\x09\x7D\x29\x0A\x09\x09\x63\x6F\x6E\x73\x74\x20\x72\x65\x73\x20\x3D\x20\x61\x77\x61\x69\x74\x20\x68\x74\x74\x70\x52\x65\x73\x2E\x6A\x73\x6F\x6E\x28\x29\x0A\x09\x09\x69\x66\x20\x28\x68\x74\x74\x70\x52\x65\x73\x2E\x53\x74\x61\x74\x75\x73\x20\x3D\x3D\x3D\x20\x32\x30\x30\x29\x20\x7B\x0A\x09\x09\x09\x6C\x65\x74\x20\x68\x66\x20\x3D\x20\x74\x68\x69\x73\x2E\x65\x6E\x76\x2E\x48\x6F\x73\x74\x73\x46\x69\x6C\x65\x73\x5B\x68\x6F\x73\x74\x73\x46\x69\x6C\x65\x5D\x0A\x09\x09\x09\x66\x6F\x72\x20\x28\x6C\x65\x74\x20\x78\x20\x3D\x20\x30\x3B\x20\x78\x20\x3C\x20\x68\x66\x2E\x52\x65\x63\x6F\x72\x64\x73\x2E\x6C\x65\x6E\x67\x74\x68\x3B\x20\x78\x2B\x2B\x29\x20\x7B\x0A\x09\x09\x09\x09\x69\x66\x20\x28\x68\x66\x2E\x52\x65\x63\x6F\x72\x64\x73\x5B\x78\x5D\x2E\x4E\x61\x6D\x65\x20\x3D\x3D\x3D\x20\x64\x6F\x6D\x61\x69\x6E\x29\x20\x7B\x0A\x09\x09\x09\x09\x09\x68\x66\x2E\x52\x65\x63\x6F\x72\x64\x73\x2E\x73\x70\x6C\x69\x63\x65\x28\x78\x2C\x20\x31\x29\x0A\x09\x09\x09\x09\x7D\x0A\x09\x09\x09\x7D\x0A\x09\x09\x7D\x0A\x09\x09\x72\x65\x74\x75\x72\x6E\x20\x72\x65\x73\x0A\x09\x7D\x0A\x0A\x09\x61\x73\x79\x6E\x63\x20\x75\x70\x64\x61\x74\x65\x45\x6E\x76\x69\x72\x6F\x6E\x6D\x65\x6E\x74\x28\x29\x20\x7B\x0A\x09\x09\x6C\x65\x74\x20\x67\x6F\x74\x20\x3D\x20\x7B\x7D\x0A\x0A\x09\x09\x4F\x62\x6A\x65\x63\x74\x2E\x61\x73\x73\x69\x67\x6E\x28\x67\x6F\x74\x2C\x20\x74\x68\x69\x73\x2E\x65\x6E\x76\x29\x0A\x0A\x09\x09\x67\x6F\x74\x2E\x50\x72\x75\x6E\x65\x44\x65\x6C\x61\x79\x20\x3D\x20\x67\x6F\x74\x2E\x50\x72\x75\x6E\x65\x44\x65\x6C\x61\x79\x20\x2A\x20\x74\x68\x69\x73\x2E\x6E\x61\x6E\x6F\x53\x65\x63\x6F\x6E\x64\x73\x0A\x09\x09\x67\x6F\x74\x2E\x50\x72\x75\x6E\x65\x54\x68\x72\x65\x73\x68\x6F\x6C\x64\x20\x3D\x20\x67\x6F\x74\x2E\x50\x72\x75\x6E\x65\x54\x68\x72\x65\x73\x68\x6F\x6C\x64\x20\x2A\x20\x74\x68\x69\x73\x2E\x6E\x61\x6E\x6F\x53\x65\x63\x6F\x6E\x64\x73\x0A\x0A\x09\x09\x63\x6F\x6E\x73\x74\x20\x68\x74\x74\x70\x52\x65\x73\x20\x3D\x20\x61\x77\x61\x69\x74\x20\x66\x65\x74\x63\x68\x28\x0A\x09\x09\x09\x74\x68\x69\x73\x2E\x73\x65\x72\x76\x65\x72\x20\x2B\x20\x22\x2F\x61\x70\x69\x2F\x65\x6E\x76\x69\x72\x6F\x6E\x6D\x65\x6E\x74\x22\x2C\x0A\x09\x09\x09\x7B\x0A\x09\x09\x09\x09\x6D\x65\x74\x68\x6F\x64\x3A\x20\x22\x50\x4F\x53\x54\x22\x2C\x0A\x09\x09\x09\x09\x68\x65\x61\x64\x65\x72\x73\x3A\x20\x7B\x0A\x09\x09\x09\x09\x09\x22\x43\x6F\x6E\x74\x65\x6E\x74\x2D\x54\x79\x70\x65\x22\x3A\x20\x22\x61\x70\x70\x6C\x69\x63\x61\x74\x69\x6F\x6E\x2F\x6A\x73\x6F\x6E\x22\x2C\x0A\x09\x09\x09\x09\x7D\x2C\x0A\x09\x09\x09\x09\x62\x6F\x64\x79\x3A\x20\x4A\x53\x4F\x4E\x2E\x73\x74\x72\x69\x6E\x67\x69\x66\x79\x28\x67\x6F\x74\x29\x2C\x0A\x09\x09\x09\x7D\x2C\x0A\x09\x09\x29\x0A\x0A\x09\x09\x72\x65\x74\x75\x72\x6E\x20\x61\x77\x61\x69\x74\x20\x68\x74\x74\x70\x52\x65\x73\x2E\x6A\x73\x6F\x6E\x28\x29\x0A\x09\x7D\x0A\x0A\x09\x61\x73\x79\x6E\x63\x20\x75\x70\x64\x61\x74\x65\x48\x6F\x73\x74\x73\x42\x6C\x6F\x63\x6B\x73\x28\x68\x6F\x73\x74\x73\x42\x6C\x6F\x63\x6B\x73\x29\x20\x7B\x0A\x09\x09\x63\x6F\x6E\x73\x74\x20\x68\x74\x74\x70\x52\x65\x73\x20\x3D\x20\x61\x77\x61\x69\x74\x20\x66\x65\x74\x63\x68\x28\x0A\x09\x09\x09\x74\x68\x69\x73\x2E\x73\x65\x72\x76\x65\x72\x20\x2B\x20\x22\x2F\x61\x70\x69\x2F\x68\x6F\x73\x74\x73\x5F\x62\x6C\x6F\x63\x6B\x22\x2C\x0A\x09\x09\x09\x7B\x0A\x09\x09\x09\x09\x6D\x65\x74\x68\x6F\x64\x3A\x20\x22\x50\x4F\x53\x54\x22\x2C\x0A\x09\x09\x09\x09\x68\x65\x61\x64\x65\x72\x73\x3A\x20\x7B\x0A\x09\x09\x09\x09\x09\x22\x43\x6F\x6E\x74\x65\x6E\x74\x2D\x54\x79\x70\x65\x22\x3A\x20\x22\x61\x70\x70\x6C\x69\x63\x61\x74\x69\x6F\x6E\x2F\x6A\x73\x6F\x6E\x22\x2C\x0A\x09\x09\x09\x09\x7D\x2C\x0A\x09\x09\x09\x09\x62\x6F\x64\x79\x3A\x20\x4A\x53\x4F\x4E\x2E\x73\x74\x72\x69\x6E\x67\x69\x66\x79\x28\x68\x6F\x73\x74\x73\x42\x6C\x6F\x63\x6B\x73\x29\x2C\x0A\x09\x09\x09\x7D\x2C\x0A\x09\x09\x29\x0A\x09\x09\x72\x65\x74\x75\x72\x6E\x20\x61\x77\x61\x69\x74\x20\x68\x74\x74\x70\x52\x65\x73\x2E\x6A\x73\x6F\x6E\x28\x29\x0A\x09\x7D\x0A\x0A\x09\x61\x73\x79\x6E\x63\x20\x5A\x6F\x6E\x65\x46\x69\x6C\x65\x43\x72\x65\x61\x74\x65\x28\x6E\x61\x6D\x65\x29\x20\x7B\x0A\x09\x09\x63\x6F\x6E\x73\x74\x20\x68\x74\x74\x70\x52\x65\x73\x20\x3D\x20\x61\x77\x61\x69\x74\x20\x66\x65\x74\x63\x68\x28\x0A\x09\x09\x09\x74\x68\x69\x73\x2E\x73\x65\x72\x76\x65\x72\x20\x2B\x20\x52\x65\x73\x63\x61\x63\x68\x65\x64\x2E\x61\x70\x69\x5A\x6F\x6E\x65\x64\x20\x2B\x20\x6E\x61\x6D\x65\x2C\x0A\x09\x09\x09\x7B\x0A\x09\x09\x09\x09\x6D\x65\x74\x68\x6F\x64\x3A\x20\x22\x50\x55\x54\x22\x2C\x0A\x09\x09\x09\x7D\x2C\x0A\x09\x09\x29\x0A\x09\x09\x6C\x65\x74\x20\x72\x65\x73\x20\x3D\x20\x61\x77\x61\x69\x74\x20\x68\x74\x74\x70\x52\x65\x73\x2E\x6A\x73\x6F\x6E\x28\x29\x0A\x09\x09\x69\x66\x20\x28\x72\x65\x73\x2E\x63\x6F\x64\x65\x20\x3D\x3D\x20\x32\x30\x30\x29\x20\x7B\x0A\x09\x09\x09\x74\x68\x69\x73\x2E\x65\x6E\x76\x2E\x5A\x6F\x6E\x65\x46\x69\x6C\x65\x73\x5B\x6E\x61\x6D\x65\x5D\x20\x3D\x20\x72\x65\x73\x2E\x64\x61\x74\x61\x0A\x09\x09\x7D\x0A\x09\x09\x72\x65\x74\x75\x72\x6E\x20\x72\x65\x73\x0A\x09\x7D\x0A\x0A\x09\x61\x73\x79\x6E\x63\x20\x5A\x6F\x6E\x65\x46\x69\x6C\x65\x44\x65\x6C\x65\x74\x65\x28\x6E\x61\x6D\x65\x29\x20\x7B\x0A\x09\x09\x63\x6F\x6E\x73\x74\x20\x68\x74\x74\x70\x52\x65\x73\x20\x3D\x20\x61\x77\x61\x69\x74\x20\x66\x65\x74\x63\x68\x28\x0A\x09\x09\x09\x74\x68\x69\x73\x2E\x73\x65\x72\x76\x65\x72\x20\x2B\x20\x52\x65\x73\x63\x61\x63\x68\x65\x64\x2E\x61\x70\x69\x5A\x6F\x6E\x65\x64\x20\x2B\x20\x6E\x61\x6D\x65\x2C\x0A\x09\x09\x09\x7B\x0A\x09\x09\x09\x09\x6D\x65\x74\x68\x6F\x64\x3A\x20\x22\x44\x45\x4C\x45\x54\x45\x22\x2C\x0A\x09\x09\x09\x7D\x2C\x0A\x09\x09\x29\x0A\x09\x09\x6C\x65\x74\x20\x72\x65\x73\x20\x3D\x20\x61\x77\x61\x69\x74\x20\x68\x74\x74\x70\x52\x65\x73\x2E\x6A\x73\x6F\x6E\x28\x29\x0A\x09\x09\x69\x66\x20\x28\x72\x65\x73\x2E\x63\x6F\x64\x65\x20\x3D\x3D\x20\x32\x30\x30\x29\x20\x7B\x0A\x09\x09\x09\x64\x65\x6C\x65\x74\x65\x20\x74\x68\x69\x73\x2E\x65\x6E\x76\x2E\x5A\x6F\x6E\x65\x46\x69\x6C\x65\x73\x5B\x6E\x61\x6D\x65\x5D\x0A\x09\x09\x7D\x0A\x09\x09\x72\x65\x74\x75\x72\x6E\x20\x72\x65\x73\x0A\x09\x7D\x0A\x0A\x09\x61\x73\x79\x6E\x63\x20\x5A\x6F\x6E\x65\x46\x69\x6C\x65\x52\x65\x63\x6F\x72\x64\x43\x72\x65\x61\x74\x65\x28\x6E\x61\x6D\x65\x2C\x20\x72\x72\x29\x20\x7B\x0A\x09\x09\x6C\x65\x74\x20\x61\x70\x69\x20\x3D\x0A\x09\x09\x09\x74\x68\x69\x73\x2E\x73\x65\x72\x76\x65\x72\x20\x2B\x0A\x09\x09\x09\x52\x65\x73\x63\x61\x63\x68\x65\x64\x2E\x61\x70\x69\x5A\x6F\x6E\x65\x64\x20\x2B\x0A\x09\x09\x09\x6E\x61\x6D\x65\x20\x2B\x0A\x09\x09\x09\x22\x2F\x72\x72\x2F\x22\x20\x2B\x0A\x09\x09\x09\x72\x72\x2E\x54\x79\x70\x65\x0A\x09\x09\x63\x6F\x6E\x73\x74\x20\x68\x74\x74\x70\x52\x65\x73\x20\x3D\x20\x61\x77\x61\x69\x74\x20\x66\x65\x74\x63\x68\x28\x61\x70\x69\x2C\x20\x7B\x0A\x09\x09\x09\x6D\x65\x74\x68\x6F\x64\x3A\x20\x22\x50\x4F\x53\x54\x22\x2C\x0A\x09\x09\x09\x68\x65\x61\x64\x65\x72\x73\x3A\x20\x7B\x0A\x09\x09\x09\x09\x22\x43\x6F\x6E\x74\x65\x6E\x74\x2D\x54\x79\x70\x65\x22\x3A\x20\x22\x61\x70\x70\x6C\x69\x63\x61\x74\x69\x6F\x6E\x2F\x6A\x73\x6F\x6E\x22\x2C\x0A\x09\x09\x09\x7D\x2C\x0A\x09\x09\x09\x62\x6F\x64\x79\x3A\x20\x4A\x53\x4F\x4E\x2E\x73\x74\x72\x69\x6E\x67\x69\x66\x79\x28\x72\x72\x29\x2C\x0A\x09\x09\x7D\x29\x0A\x09\x09\x6C\x65\x74\x20\x72\x65\x73\x20\x3D\x20\x61\x77\x61\x69\x74\x20\x68\x74\x74\x70\x52\x65\x73\x2E\x6A\x73\x6F\x6E\x28\x29\x0A\x09\x09\x69\x66\x20\x28\x68\x74\x74\x70\x52\x65\x73\x2E\x73\x74\x61\x74\x75\x73\x20\x3D\x3D\x3D\x20\x32\x30\x30\x29\x20\x7B\x0A\x09\x09\x09\x6C\x65\x74\x20\x7A\x66\x20\x3D\x20\x74\x68\x69\x73\x2E\x65\x6E\x76\x2E\x5A\x6F\x6E\x65\x46\x69\x6C\x65\x73\x5B\x6E\x61\x6D\x65\x5D\x0A\x09\x09\x09\x69\x66\x20\x28\x72\x72\x2E\x54\x79\x70\x65\x20\x3D\x3D\x20\x36\x29\x20\x7B\x0A\x09\x09\x09\x09\x2F\x2F\x20\x53\x4F\x41\x2E\x0A\x09\x09\x09\x09\x7A\x66\x2E\x53\x4F\x41\x20\x3D\x20\x72\x65\x73\x2E\x64\x61\x74\x61\x0A\x09\x09\x09\x7D\x20\x65\x6C\x73\x65\x20\x7B\x0A\x09\x09\x09\x09\x7A\x66\x2E\x52\x65\x63\x6F\x72\x64\x73\x20\x3D\x20\x72\x65\x73\x2E\x64\x61\x74\x61\x0A\x09\x09\x09\x7D\x0A\x09\x09\x7D\x0A\x09\x09\x72\x65\x74\x75\x72\x6E\x20\x72\x65\x73\x0A\x09\x7D\x0A\x0A\x09\x61\x73\x79\x6E\x63\x20\x5A\x6F\x6E\x65\x46\x69\x6C\x65\x52\x65\x63\x6F\x72\x64\x44\x65\x6C\x65\x74\x65\x28\x6E\x61\x6D\x65\x2C\x20\x72\x72\x29\x20\x7B\x0A\x09\x09\x6C\x65\x74\x20\x61\x70\x69\x20\x3D\x0A\x09\x09\x09\x74\x68\x69\x73\x2E\x73\x65\x72\x76\x65\x72\x20\x2B\x0A\x09\x09\x09\x52\x65\x73\x63\x61\x63\x68\x65\x64\x2E\x61\x70\x69\x5A\x6F\x6E\x65\x64\x20\x2B\x0A\x09\x09\x09\x6E\x61\x6D\x65\x20\x2B\x0A\x09\x09\x09\x22\x2F\x72\x72\x2F\x22\x20\x2B\x0A\x09\x09\x09\x72\x72\x2E\x54\x79\x70\x65\x0A\x09\x09\x63\x6F\x6E\x73\x74\x20\x68\x74\x74\x70\x52\x65\x73\x20\x3D\x20\x61\x77\x61\x69\x74\x20\x66\x65\x74\x63\x68\x28\x61\x70\x69\x2C\x20\x7B\x0A\x09\x09\x09\x6D\x65\x74\x68\x6F\x64\x3A\x20\x22\x44\x45\x4C\x45\x54\x45\x22\x2C\x0A\x09\x09\x09\x68\x65\x61\x64\x65\x72\x73\x3A\x20\x7B\x0A\x09\x09\x09\x09\x22\x43\x6F\x6E\x74\x65\x6E\x74\x2D\x54\x79\x70\x65\x22\x3A\x20\x22\x61\x70\x70\x6C\x69\x63\x61\x74\x69\x6F\x6E\x2F\x6A\x73\x6F\x6E\x22\x2C\x0A\x09\x09\x09\x7D\x2C\x0A\x09\x09\x09\x62\x6F\x64\x79\x3A\x20\x4A\x53\x4F\x4E\x2E\x73\x74\x72\x69\x6E\x67\x69\x66\x79\x28\x72\x72\x29\x2C\x0A\x09\x09\x7D\x29\x0A\x09\x09\x6C\x65\x74\x20\x72\x65\x73\x20\x3D\x20\x61\x77\x61\x69\x74\x20\x68\x74\x74\x70\x52\x65\x73\x2E\x6A\x73\x6F\x6E\x28\x29\x0A\x09\x09\x69\x66\x20\x28\x68\x74\x74\x70\x52\x65\x73\x2E\x73\x74\x61\x74\x75\x73\x20\x3D\x3D\x3D\x20\x32\x30\x30\x29\x20\x7B\x0A\x09\x09\x09\x74\x68\x69\x73\x2E\x65\x6E\x76\x2E\x5A\x6F\x6E\x65\x46\x69\x6C\x65\x73\x5B\x6E\x61\x6D\x65\x5D\x2E\x52\x65\x63\x6F\x72\x64\x73\x20\x3D\x20\x72\x65\x73\x2E\x64\x61\x74\x61\x0A\x09\x09\x7D\x0A\x09\x09\x72\x65\x74\x75\x72\x6E\x20\x72\x65\x73\x0A\x09\x7D\x0A\x7D\x0A"), } node.SetMode(420) - node.SetModTimeUnix(1647276264, 46986583) + node.SetModTimeUnix(1650380257, 104529981) node.SetName("rescached.js") node.SetSize(5919) return node diff --git a/rescached.go b/rescached.go index 232d340..5b8951b 100644 --- a/rescached.go +++ b/rescached.go @@ -65,7 +65,12 @@ func New(env *Environment) (srv *Server, err error) { // it. // func (srv *Server) Start() (err error) { - logp := "Start" + var ( + logp = "Start" + + hb *hostsBlock + hfile *dns.HostsFile + ) srv.dns, err = dns.NewServer(&srv.env.ServerOptions) if err != nil { @@ -94,12 +99,29 @@ func (srv *Server) Start() (err error) { if err != nil { return err } - err = srv.dns.PopulateCachesByRR(systemHostsFile.Records, - systemHostsFile.Path) + err = srv.dns.PopulateCachesByRR(systemHostsFile.Records, systemHostsFile.Path) if err != nil { return err } + for _, hb = range srv.env.HostsBlocks { + if !hb.IsEnabled { + continue + } + + hfile, err = dns.ParseHostsFile(hb.file) + if err != nil { + return fmt.Errorf("%s: %w", logp, err) + } + + err = srv.dns.PopulateCachesByRR(hfile.Records, hfile.Path) + if err != nil { + return fmt.Errorf("%s: %w", logp, err) + } + + srv.env.hostsBlocksFile[hfile.Name] = hfile + } + srv.env.HostsFiles, err = dns.LoadHostsDir(dirHosts) if err != nil { return err diff --git a/testdata/rescached.cfg.test.out b/testdata/rescached.cfg.test.out new file mode 100644 index 0000000..adfe7c5 --- /dev/null +++ b/testdata/rescached.cfg.test.out @@ -0,0 +1,37 @@ +[rescached] +file.resolvconf = +wui.listen = 127.0.0.1:5381 +debug = 2 + +[block.d "pgl.yoyo.org"] +name = pgl.yoyo.org +url = http://pgl.yoyo.org/adservers/serverlist.php?hostformat=hosts&showintro=0&startdate[day]=&startdate[month]=&startdate[year]=&mimetype=plaintext + +[block.d "someonewhocares.org"] +name = someonewhocares.org +url = http://someonewhocares.org/hosts/hosts + +[block.d "test"] +name = test +url = http://someurl + +[block.d "winhelp2002.mvps.org"] +name = winhelp2002.mvps.org +url = http://winhelp2002.mvps.org/hosts.txt + +[block.d "www.malwaredomainlist.com"] +name = www.malwaredomainlist.com +url = http://www.malwaredomainlist.com/hostslist/hosts.txt + +[dns "server"] +listen = 127.0.0.1:5350 +tls.certificate = +tls.private_key = +parent = udp://10.8.0.1 +http.idle_timeout = 0s +cache.prune_delay = 0s +cache.prune_threshold = 0s +http.port = 0 +tls.port = 0 +tls.allow_insecure = true +doh.behind_proxy = false -- cgit v1.3