1
0
Fork 0
mirror of https://github.com/gocsaf/csaf.git synced 2025-12-22 18:15:42 +01:00

Merge pull request #135 from csaf-poc/link-checker

Link checker
This commit is contained in:
Sascha L. Teichmann 2022-05-17 17:26:35 +02:00 committed by GitHub
commit d779a3fd7e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 256 additions and 34 deletions

View file

@ -19,3 +19,5 @@
| golang.org/x/term | BSD-3-Clause |
| golang.org/x/text | BSD-3-Clause |
| github.com/gofrs/flock | BSD-3-Clause |
| github.com/PuerkitoBio/goquery | BSD-3-Clause |
| github.com/andybalholm/cascadia | BSD-2-Clause |

110
cmd/csaf_checker/links.go Normal file
View file

@ -0,0 +1,110 @@
// This file is Free Software under the MIT License
// without warranty, see README.md and LICENSES/MIT.txt for details.
//
// SPDX-License-Identifier: MIT
//
// SPDX-FileCopyrightText: 2022 German Federal Office for Information Security (BSI) <https://www.bsi.bund.de>
// Software-Engineering: 2022 Intevation GmbH <https://intevation.de>
package main
import (
"io"
"net/http"
"net/url"
"github.com/PuerkitoBio/goquery"
"github.com/csaf-poc/csaf_distribution/util"
)
type (
pageContent struct {
err error
links map[string]struct{}
}
pages map[string]*pageContent
)
func (pgs pages) listed(path string, pro *processor) (bool, error) {
base, err := util.BaseURL(path)
if err != nil {
return false, err
}
content := pgs[base]
if content != nil { // already loaded
if content.err != nil {
return false, nil
}
_, ok := content.links[path]
return ok, nil
}
baseURL, err := url.Parse(base)
if err != nil {
return false, err
}
// load page
client := pro.httpClient()
pro.checkTLS(base)
res, err := client.Get(base)
pro.badDirListings.use()
if err != nil {
pro.badDirListings.add("Fetching %s failed: %v", base, err)
return false, errContinue
}
if res.StatusCode != http.StatusOK {
pro.badDirListings.add("Fetching %s failed. Status code %d (%s)",
base, res.StatusCode, res.Status)
return false, errContinue
}
content = &pageContent{
links: map[string]struct{}{},
}
pgs[base] = content
// Build link index for this page.
if err := func() error {
defer res.Body.Close()
return linksOnPage(res.Body, func(link string) error {
u, err := url.Parse(link)
if err != nil {
return err
}
// Links may be relative
abs := baseURL.ResolveReference(u).String()
content.links[abs] = struct{}{}
return nil
})
}(); err != nil {
return false, errContinue
}
_, ok := content.links[path]
return ok, nil
}
func linksOnPage(r io.Reader, visit func(string) error) error {
doc, err := goquery.NewDocumentFromReader(r)
if err != nil {
return err
}
doc.Find("a").Each(func(_ int, s *goquery.Selection) {
if err != nil {
return
}
if link, ok := s.Attr("href"); ok {
err = visit(link)
}
})
return err
}

View file

@ -0,0 +1,52 @@
package main
import (
"fmt"
"strings"
"testing"
)
const page0 = `<html>
<body>
<a href="not-a-json">Not a JSON</a>
<a href="link0.json">link0</a>
<ol>
<li><a href="link1.json">link1</a></li>
<li><a href="link2.json">link1</a></li>
</ol>
<p>
<div>
<li><a href="link3.json">link1</a></li>
</div>
<p>
</body>
</html>`
func TestLinksOnPage(t *testing.T) {
var links []string
err := linksOnPage(
strings.NewReader(page0),
func(s string) error {
if strings.HasSuffix(s, ".json") {
links = append(links, s)
}
return nil
},
)
if err != nil {
t.Fatal(err)
}
if l := len(links); l != 4 {
t.Fatalf("Expected 4 links, go %d\n", l)
}
for i, link := range links {
href := fmt.Sprintf("link%d.json", i)
if href != link {
t.Fatalf("Expected link '%s', got '%s'\n", href, link)
}
}
}

View file

@ -59,6 +59,7 @@ type processor struct {
badFolders topicMessages
badWellknownMetadata topicMessages
badDNSPath topicMessages
badDirListings topicMessages
expr *util.PathEval
}
@ -85,6 +86,8 @@ const (
rolieChangesMask
indexMask
changesMask
listingMask
rolieListingMask
)
func (wt whereType) String() string {
@ -99,6 +102,10 @@ func (wt whereType) String() string {
return "index.txt"
case changesMask:
return "changes.csv"
case listingMask:
return "directory listing"
case rolieListingMask:
return "directory listing [ROLIE]"
default:
var mixed []string
for mask := rolieMask; mask <= changesMask; mask <<= 1 {
@ -157,6 +164,10 @@ func (p *processor) clean() {
p.badSecurity.reset()
p.badIndices.reset()
p.badChanges.reset()
p.badFolders.reset()
p.badWellknownMetadata.reset()
p.badDNSPath.reset()
p.badDirListings.reset()
}
// run calls checkDomain function for each domain in the given "domains" parameter.
@ -193,6 +204,7 @@ func (p *processor) checkDomain(domain string) error {
(*processor).checkSecurity,
(*processor).checkCSAFs,
(*processor).checkMissing,
(*processor).checkListing,
(*processor).checkWellknownMetadataReporter,
(*processor).checkDNSPathReporter,
} {
@ -241,7 +253,7 @@ func (p *processor) checkRedirect(r *http.Request, via []*http.Request) error {
p.redirects[url] = path.String()
if len(via) > 10 {
return errors.New("Too many redirections")
return errors.New("too many redirections")
}
return nil
}
@ -696,7 +708,7 @@ func (p *processor) checkMissing(string) error {
for _, f := range files {
v := p.alreadyChecked[f]
var where []string
for mask := rolieMask; mask <= changesMask; mask <<= 1 {
for mask := rolieMask; mask <= rolieListingMask; mask <<= 1 {
if maxMask&mask == mask {
var in string
if v&mask == mask {
@ -712,6 +724,35 @@ func (p *processor) checkMissing(string) error {
return nil
}
// checkListing wents over all found adivisories URLs and checks,
// if their parent directory is listable.
func (p *processor) checkListing(string) error {
p.badDirListings.use()
pgs := pages{}
var unlisted []string
for f := range p.alreadyChecked {
found, err := pgs.listed(f, p)
if err != nil && err != errContinue {
return err
}
if !found {
unlisted = append(unlisted, f)
}
}
if len(unlisted) > 0 {
sort.Strings(unlisted)
p.badDirListings.add("Not listed advisories: %s",
strings.Join(unlisted, ", "))
}
return nil
}
var providerMetadataLocations = [...]string{
".well-known/csaf",
"security/data/csaf",
@ -803,7 +844,7 @@ func (p *processor) extractProviderURL(r io.Reader) (string, error) {
return "", err
}
if len(urls) == 0 {
return "", errors.New("No provider-metadata.json found")
return "", errors.New("no provider-metadata.json found")
}
if len(urls) > 1 {

View file

@ -186,10 +186,17 @@ func (r *changesReporter) report(p *processor, domain *Domain) {
req.Messages = p.badChanges
}
func (r *directoryListingsReporter) report(_ *processor, domain *Domain) {
// TODO: Implement me!
func (r *directoryListingsReporter) report(p *processor, domain *Domain) {
req := r.requirement(domain)
_ = req
if !p.badDirListings.used() {
req.message("No directory listings checked.")
return
}
if len(p.badDirListings) == 0 {
req.message("All directory listings are valid.")
return
}
req.Messages = p.badDirListings
}
func (r *integrityReporter) report(p *processor, domain *Domain) {

23
go.mod
View file

@ -3,25 +3,26 @@ module github.com/csaf-poc/csaf_distribution
go 1.17
require (
github.com/BurntSushi/toml v0.4.1
github.com/BurntSushi/toml v1.1.0
github.com/PaesslerAG/gval v1.1.2
github.com/PaesslerAG/jsonpath v0.1.1
github.com/ProtonMail/gopenpgp/v2 v2.3.0
github.com/ProtonMail/gopenpgp/v2 v2.4.7
github.com/PuerkitoBio/goquery v1.8.0
github.com/gofrs/flock v0.8.1
github.com/jessevdk/go-flags v1.5.0
github.com/mitchellh/go-homedir v1.1.0
github.com/santhosh-tekuri/jsonschema/v5 v5.0.0
golang.org/x/crypto v0.0.0-20211202192323-5770296d904e
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8
golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9
golang.org/x/time v0.0.0-20220411224347-583f2d630306
)
require (
github.com/ProtonMail/go-crypto v0.0.0-20211112122917-428f8eabeeb3 // indirect
github.com/ProtonMail/go-mime v0.0.0-20190923161245-9b5a4261663a // indirect
github.com/konsorten/go-windows-terminal-sequences v1.0.1 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20220512085406-902f79d34c9f // indirect
github.com/ProtonMail/go-mime v0.0.0-20220429130430-2192574d760f // indirect
github.com/andybalholm/cascadia v1.3.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/sirupsen/logrus v1.4.2 // indirect
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 // indirect
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 // indirect
golang.org/x/text v0.3.6 // indirect
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect
golang.org/x/sys v0.0.0-20220513210249-45d2b4557a2a // indirect
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 // indirect
golang.org/x/text v0.3.7 // indirect
)

43
go.sum
View file

@ -1,5 +1,5 @@
github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw=
github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I=
github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/PaesslerAG/gval v1.0.0/go.mod h1:y/nm5yEyTeX6av0OfKJNp9rBNj2XrGhAf5+v24IBN1I=
github.com/PaesslerAG/gval v1.1.2 h1:EROKxV4/fAKWb0Qoj7NOxmHZA7gcpjOV9XgiRZMRCUU=
@ -7,12 +7,18 @@ github.com/PaesslerAG/gval v1.1.2/go.mod h1:Fa8gfkCmUsELXgayr8sfL/sw+VzCVoa03dcO
github.com/PaesslerAG/jsonpath v0.1.0/go.mod h1:4BzmtoM/PI8fPO4aQGIusjGxGir2BzcV0grWtFzq1Y8=
github.com/PaesslerAG/jsonpath v0.1.1 h1:c1/AToHQMVsduPAa4Vh6xp2U0evy4t8SWp8imEsylIk=
github.com/PaesslerAG/jsonpath v0.1.1/go.mod h1:lVboNxFGal/VwW6d9JzIy56bUsYAP6tH/x80vjnCseY=
github.com/ProtonMail/go-crypto v0.0.0-20211112122917-428f8eabeeb3 h1:XcF0cTDJeiuZ5NU8w7WUDge0HRwwNRmxj/GGk6KSA6g=
github.com/ProtonMail/go-crypto v0.0.0-20211112122917-428f8eabeeb3/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
github.com/ProtonMail/go-mime v0.0.0-20190923161245-9b5a4261663a h1:W6RrgN/sTxg1msqzFFb+G80MFmpjMw61IU+slm+wln4=
github.com/ProtonMail/go-mime v0.0.0-20190923161245-9b5a4261663a/go.mod h1:NYt+V3/4rEeDuaev/zw1zCq8uqVEuPHzDPo3OZrlGJ4=
github.com/ProtonMail/gopenpgp/v2 v2.3.0 h1:eniutitHk02Yn3GtaDfJTVm/Ca1e8s6zkS0SpeaocXI=
github.com/ProtonMail/gopenpgp/v2 v2.3.0/go.mod h1:F62x0m3akQuisX36pOgAtKOHZ1E7/MpnX8bZWCK+5dA=
github.com/ProtonMail/go-crypto v0.0.0-20220113124808-70ae35bab23f/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
github.com/ProtonMail/go-crypto v0.0.0-20220512085406-902f79d34c9f h1:CP4ZCltXExnPiLQFuj0oucZxRRH5ZLL+lT5Bl7Eh37g=
github.com/ProtonMail/go-crypto v0.0.0-20220512085406-902f79d34c9f/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
github.com/ProtonMail/go-mime v0.0.0-20220302105931-303f85f7fe0f/go.mod h1:NYt+V3/4rEeDuaev/zw1zCq8uqVEuPHzDPo3OZrlGJ4=
github.com/ProtonMail/go-mime v0.0.0-20220429130430-2192574d760f h1:4IWzKjHzZxdrW9k4zl/qCwenOVHDbVDADPPHFLjs0Oc=
github.com/ProtonMail/go-mime v0.0.0-20220429130430-2192574d760f/go.mod h1:qRZgbeASl2a9OwmsV85aWwRqic0NHPh+9ewGAzb4cgM=
github.com/ProtonMail/gopenpgp/v2 v2.4.7 h1:V3xeelvXgJiZXZuPtSSE+uYbtPw4RmbmyPqXDAESPhg=
github.com/ProtonMail/gopenpgp/v2 v2.4.7/go.mod h1:ZW1KxHNG6q5LMgFKf9Ap/d2eVYeyGf5+fAUEAjJWtmo=
github.com/PuerkitoBio/goquery v1.8.0 h1:PJTF7AmFCFKk1N6V6jmKfrNH9tV5pNE6lZMkG0gta/U=
github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI=
github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -20,7 +26,6 @@ github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc=
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
@ -30,7 +35,6 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/santhosh-tekuri/jsonschema/v5 v5.0.0 h1:TToq11gyfNlrMFZiYujSekIsPd9AmsA2Bj/iv+s4JHE=
github.com/santhosh-tekuri/jsonschema/v5 v5.0.0/go.mod h1:FKdcjfQW6rpZSnxxUvEA5H/cDPdvJ/SZJQLWWXWGrZ0=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@ -41,8 +45,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20211202192323-5770296d904e h1:MUP6MR3rJ7Gk9LEia0LP2ytiH6MuCfs7qYz+47jGdD8=
golang.org/x/crypto v0.0.0-20211202192323-5770296d904e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9 h1:NUzdAbFtCJSXU20AOXgeqaUwg8Ypg4MPYmL+d+rsB5c=
golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190731235908-ec7cb31e5a56/go.mod h1:JhuoJpWY28nO4Vef9tZUw9qufEGTyX1+7lmHxV5q5G4=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
@ -55,6 +59,8 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -63,17 +69,20 @@ golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/sys v0.0.0-20220513210249-45d2b4557a2a h1:N2T1jUrTQE9Re6TFF5PhvEHXHCguynGhKjWVsIUt5cY=
golang.org/x/sys v0.0.0-20220513210249-45d2b4557a2a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171 h1:EH1Deb8WZJ0xc0WK//leUHXcX9aLE5SymusoTmMZye8=
golang.org/x/term v0.0.0-20220411215600-e5f449aeb171/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8 h1:vVKdlvoWBphwdxWKrFZEuM0kGgGLxUOYcY4U/2Vjg44=
golang.org/x/time v0.0.0-20220210224613-90d013bbcef8/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/time v0.0.0-20220411224347-583f2d630306 h1:+gHMid33q6pen7kv9xvT+JRinntgeXO2AeZVd0AWD3w=
golang.org/x/time v0.0.0-20220411224347-583f2d630306/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=