various fixes and features

This commit is contained in:
Mzack9999 2020-08-23 20:42:20 +02:00
parent 077cc70226
commit bac670a949
9 changed files with 201 additions and 115 deletions

View File

@ -17,16 +17,17 @@ import (
"github.com/projectdiscovery/httpx/common/customheader"
customport "github.com/projectdiscovery/httpx/common/customports"
"github.com/projectdiscovery/httpx/common/fileutil"
"github.com/projectdiscovery/httpx/common/httputilz"
"github.com/projectdiscovery/httpx/common/httpx"
"github.com/projectdiscovery/httpx/common/iputil"
"github.com/projectdiscovery/httpx/common/slice"
"github.com/projectdiscovery/httpx/common/stringz"
"github.com/projectdiscovery/mapcidr"
"github.com/remeh/sizedwaitgroup"
)
func main() {
options := ParseOptions()
options.validateOptions()
httpxOptions := httpx.DefaultOptions
httpxOptions.Timeout = time.Duration(options.Timeout) * time.Second
@ -34,11 +35,12 @@ func main() {
httpxOptions.FollowRedirects = options.FollowRedirects
httpxOptions.FollowHostRedirects = options.FollowHostRedirects
httpxOptions.HttpProxy = options.HttpProxy
httpxOptions.Unsafe = options.Unsafe
var key, value string
httpxOptions.CustomHeaders = make(map[string]string)
for _, customHeader := range options.CustomHeaders {
tokens := strings.Split(customHeader, ":")
tokens := strings.SplitN(customHeader, ":", 2)
// if it's an invalid header skip it
if len(tokens) < 2 {
continue
@ -55,6 +57,27 @@ func main() {
}
var scanopts scanOptions
if options.InputRawRequest != "" {
var rawRequest []byte
rawRequest, err = ioutil.ReadFile(options.InputRawRequest)
if err != nil {
gologger.Fatalf("Could not read raw request from '%s': %s\n", options.InputRawRequest, err)
}
rrMethod, rrPath, rrHeaders, rrBody, err := httputilz.ParseRequest(string(rawRequest))
if err != nil {
gologger.Fatalf("Could not parse raw request: %s\n", err)
}
scanopts.Method = rrMethod
scanopts.RequestURI = rrPath
for name, value := range rrHeaders {
httpxOptions.CustomHeaders[name] = value
}
scanopts.RequestBody = rrBody
options.rawRequest = string(rawRequest)
}
scanopts.Method = options.Method
protocol := "https"
scanopts.VHost = options.VHost
@ -64,14 +87,21 @@ func main() {
scanopts.OutputContentLength = options.ContentLength
scanopts.StoreResponse = options.StoreResponse
scanopts.StoreResponseDirectory = options.StoreResponseDir
scanopts.Method = options.Method
if options.Method != "" {
scanopts.Method = options.Method
}
scanopts.OutputServerHeader = options.OutputServerHeader
scanopts.OutputWithNoColor = options.NoColor
scanopts.ResponseInStdout = options.responseInStdout
scanopts.OutputWebSocket = options.OutputWebSocket
scanopts.TlsProbe = options.TLSProbe
scanopts.RequestURI = options.RequestURI
scanopts.CspProbe = options.CSPProbe
if options.RequestURI != "" {
scanopts.RequestURI = options.RequestURI
}
scanopts.OutputContentType = options.OutputContentType
scanopts.RequestBody = options.RequestBody
scanopts.Unsafe = options.Unsafe
// Try to create output folder if it doesnt exist
if options.StoreResponse && !fileutil.FolderExists(options.StoreResponseDir) {
@ -98,6 +128,7 @@ func main() {
}
for r := range output {
if r.err != nil {
//log.Println(r.err)
continue
}
@ -173,6 +204,12 @@ func process(t string, wg *sizedwaitgroup.SizedWaitGroup, hp *httpx.HTTPX, proto
process(tt, wg, hp, protocol, scanopts, output)
}
}
if scanopts.CspProbe && r.CspData != nil {
scanopts.CspProbe = false
for _, tt := range r.CspData.Domains {
process(tt, wg, hp, protocol, scanopts, output)
}
}
}(target)
}
@ -217,7 +254,7 @@ func targets(target string) chan string {
// test if the target is a cidr
if iputil.IsCidr(target) {
cidrIps, err := iputil.Ips(target)
cidrIps, err := mapcidr.Ips(target)
if err != nil {
return
}
@ -237,7 +274,7 @@ type scanOptions struct {
VHost bool
OutputTitle bool
OutputStatusCode bool
OutputLocation bool
OutputLocation bool
OutputContentLength bool
StoreResponse bool
StoreResponseDirectory string
@ -246,8 +283,11 @@ type scanOptions struct {
OutputWithNoColor bool
ResponseInStdout bool
TlsProbe bool
CspProbe bool
RequestURI string
OutputContentType bool
RequestBody string
Unsafe bool
}
func analyze(hp *httpx.HTTPX, protocol string, domain string, port int, scanopts *scanOptions) Result {
@ -255,7 +295,7 @@ func analyze(hp *httpx.HTTPX, protocol string, domain string, port int, scanopts
retry:
URL := fmt.Sprintf("%s://%s%s", protocol, domain, scanopts.RequestURI)
if port > 0 {
URL = fmt.Sprintf("%s:%d", URL, port)
URL = fmt.Sprintf("%s://%s:%d%s", protocol, domain, port, scanopts.RequestURI)
}
req, err := hp.NewRequest(scanopts.Method, URL)
@ -264,6 +304,9 @@ retry:
}
hp.SetCustomHeaders(req, hp.CustomHeaders)
if scanopts.RequestBody != "" {
req.Body = ioutil.NopCloser(strings.NewReader(scanopts.RequestBody))
}
resp, err := hp.Do(req)
if err != nil {
@ -381,7 +424,11 @@ retry:
// store responses in directory
if scanopts.StoreResponse {
var domainFile = strings.Replace(domain+scanopts.RequestURI, "/", "_", -1) + ".txt"
domainFile := fmt.Sprintf("%s%s", domain, scanopts.RequestURI)
if port > 0 {
domainFile = fmt.Sprintf("%s.%d%s", domain, port, scanopts.RequestURI)
}
domainFile = strings.Replace(domainFile, "/", "_", -1) + ".txt"
responsePath := path.Join(scanopts.StoreResponseDirectory, domainFile)
err := ioutil.WriteFile(responsePath, []byte(resp.Raw), 0644)
if err != nil {
@ -402,6 +449,7 @@ retry:
Response: serverResponseRaw,
WebSocket: isWebSocket,
TlsData: resp.TlsData,
CspData: resp.CspData,
}
}
@ -420,6 +468,7 @@ type Result struct {
WebSocket bool `json:"websocket,omitempty"`
ContentType string `json:"content-type,omitempty"`
TlsData *httpx.TlsData `json:"tls,omitempty"`
CspData *httpx.CspData `json:"csp,omitempty"`
}
// JSON the result
@ -433,7 +482,6 @@ func (r *Result) JSON() string {
// Options contains configuration options for chaos client.
type Options struct {
RawRequestFile string
VHost bool
Smuggling bool
ExtractTitle bool
@ -463,6 +511,7 @@ type Options struct {
responseInStdout bool
FollowHostRedirects bool
TLSProbe bool
CSPProbe bool
RequestURI string
OutputContentType bool
OutputMatchStatusCode string
@ -473,6 +522,10 @@ type Options struct {
filterStatusCode []int
OutputFilterContentLength string
filterContentLength []int
InputRawRequest string
rawRequest string
Unsafe bool
RequestBody string
}
// ParseOptions parses the command line options for application
@ -497,7 +550,7 @@ func ParseOptions() *Options {
flag.StringVar(&options.HttpProxy, "http-proxy", "", "HTTP Proxy, eg http://127.0.0.1:8080")
flag.BoolVar(&options.JSONOutput, "json", false, "JSON Output")
flag.StringVar(&options.InputFile, "l", "", "File containing domains")
flag.StringVar(&options.Method, "x", "GET", "Request Method")
flag.StringVar(&options.Method, "x", "", "Request Method")
flag.BoolVar(&options.Silent, "silent", false, "Silent mode")
flag.BoolVar(&options.Version, "version", false, "Show version of httpx")
flag.BoolVar(&options.Verbose, "verbose", false, "Verbose Mode")
@ -506,12 +559,16 @@ func ParseOptions() *Options {
flag.BoolVar(&options.OutputWebSocket, "websocket", false, "Prints out if the server exposes a websocket")
flag.BoolVar(&options.responseInStdout, "response-in-json", false, "Server response directly in the tool output (-json only)")
flag.BoolVar(&options.TLSProbe, "tls-probe", false, "Send HTTP probes on the extracted TLS domains")
flag.BoolVar(&options.CSPProbe, "csp-probe", false, "Send HTTP probes on the extracted CSP domains")
flag.StringVar(&options.RequestURI, "path", "", "Request path/file (example '/api')")
flag.BoolVar(&options.OutputContentType, "content-type", false, "Extracts content-type")
flag.StringVar(&options.OutputMatchStatusCode, "mc", "", "Match status code")
flag.StringVar(&options.OutputMatchStatusCode, "ml", "", "Match content length")
flag.StringVar(&options.OutputFilterStatusCode, "fc", "", "Filter status code")
flag.StringVar(&options.OutputFilterContentLength, "fl", "", "Filter content length")
flag.StringVar(&options.InputRawRequest, "request", "", "File containing raw request")
flag.BoolVar(&options.Unsafe, "unsafe", false, "Send raw requests skipping golang normalization")
flag.StringVar(&options.RequestBody, "body", "", "Request Body")
flag.Parse()
// Read the inputs and configure the logging
@ -534,6 +591,10 @@ func (options *Options) validateOptions() {
gologger.Fatalf("File %s does not exist!\n", options.InputFile)
}
if options.InputRawRequest != "" && !fileutil.FileExists(options.InputRawRequest) {
gologger.Fatalf("File %s does not exist!\n", options.InputRawRequest)
}
var err error
if options.matchStatusCode, err = stringz.StringToSliceInt(options.OutputMatchStatusCode); err != nil {
gologger.Fatalf("Invalid value for match status code option: %s\n", err)

View File

@ -1,9 +1,13 @@
package httputilz
import (
"bufio"
"fmt"
"io/ioutil"
"net/http"
"net/http/httputil"
"net/url"
"strings"
"github.com/projectdiscovery/retryablehttp-go"
)
@ -29,3 +33,67 @@ func DumpResponse(resp *http.Response) (string, error) {
raw, err := httputil.DumpResponse(resp, true)
return string(raw), err
}
// ParseRequest from raw string
func ParseRequest(req string) (method string, path string, headers map[string]string, body string, err error) {
headers = make(map[string]string)
reader := bufio.NewReader(strings.NewReader(req))
s, err := reader.ReadString('\n')
if err != nil {
err = fmt.Errorf("could not read request: %s", err)
return
}
parts := strings.Split(s, " ")
if len(parts) < 3 {
err = fmt.Errorf("malformed request supplied")
return
}
method = parts[0]
for {
line, err := reader.ReadString('\n')
line = strings.TrimSpace(line)
if err != nil || line == "" {
break
}
p := strings.SplitN(line, ":", 2)
if len(p) != 2 {
continue
}
if strings.EqualFold(p[0], "content-length") {
continue
}
p[0] = strings.TrimSpace(p[0])
p[1] = strings.TrimSpace(p[1])
headers[p[0]] = p[1]
}
// Handle case with the full http url in path. In that case,
// ignore any host header that we encounter and use the path as request URL
if strings.HasPrefix(parts[1], "http") {
var parsed *url.URL
parsed, err = url.Parse(parts[1])
if err != nil {
err = fmt.Errorf("could not parse request URL: %s", err)
return
}
path = parts[1]
headers["Host"] = parsed.Host
} else {
path = parts[1]
}
// Set the request body
b, err := ioutil.ReadAll(reader)
if err != nil {
err = fmt.Errorf("could not read request body: %s", err)
return
}
body = string(b)
return
}

35
common/httpx/csp.go Normal file
View File

@ -0,0 +1,35 @@
package httpx
import (
"net/http"
"strings"
)
type CspData struct {
Domains []string `json:"domains,omitempty"`
}
func (h *HTTPX) CspGrab(r *http.Response) *CspData {
cspRaw := r.Header.Get("Content-Security-Policy")
if cspRaw != "" {
var domains []string
rules := strings.Split(cspRaw, ";")
for _, rule := range rules {
// rule is like aa bb domain1 domain2 domain3
tokens := strings.Split(rule, " ")
// we extracts only potential domains
for _, t := range tokens {
if isPotentialDomain(t) {
domains = append(domains, t)
}
}
}
return &CspData{Domains: domains}
}
return nil
}
// bare minimum conditions to filter potential domains
func isPotentialDomain(s string) bool {
return strings.Contains(s, ".") || strings.HasPrefix(s, "http")
}

View File

@ -12,6 +12,7 @@ import (
"github.com/microcosm-cc/bluemonday"
"github.com/projectdiscovery/httpx/common/cache"
"github.com/projectdiscovery/httpx/common/httputilz"
"github.com/projectdiscovery/rawhttp"
retryablehttp "github.com/projectdiscovery/retryablehttp-go"
)
@ -92,7 +93,15 @@ func New(options *Options) (*HTTPX, error) {
// Do http request
func (h *HTTPX) Do(req *retryablehttp.Request) (*Response, error) {
httpresp, err := h.client.Do(req)
var (
httpresp *http.Response
err error
)
if h.Options.Unsafe {
httpresp, err = h.doUnsafe(req)
} else {
httpresp, err = h.client.Do(req)
}
if err != nil {
return nil, err
}
@ -134,12 +143,21 @@ func (h *HTTPX) Do(req *retryablehttp.Request) (*Response, error) {
// number of lines
resp.Lines = len(strings.Split(respbodystr, "\n"))
// extracts TLS data if any
resp.TlsData = h.TlsGrab(httpresp)
if !h.Options.Unsafe {
// extracts TLS data if any
resp.TlsData = h.TlsGrab(httpresp)
}
resp.CspData = h.CspGrab(httpresp)
return &resp, nil
}
// Do http request
func (h *HTTPX) doUnsafe(req *retryablehttp.Request) (*http.Response, error) {
return rawhttp.Dor(req)
}
// Verify the http calls and apply-cascade all the filters, as soon as one matches it returns true
func (h *HTTPX) Verify(req *retryablehttp.Request) (bool, error) {
resp, err := h.Do(req)

View File

@ -12,10 +12,11 @@ type Options struct {
// RetryMax is the maximum number of retries
RetryMax int
CustomHeaders map[string]string
FollowRedirects bool
CustomHeaders map[string]string
FollowRedirects bool
FollowHostRedirects bool
DefaultUserAgent string
DefaultUserAgent string
Unsafe bool
HttpProxy string
SocksProxy string
@ -36,6 +37,7 @@ var DefaultOptions = Options{
Threads: 25,
Timeout: 30 * time.Second,
RetryMax: 5,
Unsafe: false,
// VHOSTs options
VHostIgnoreStatusCode: false,
VHostIgnoreContentLength: true,

View File

@ -14,6 +14,7 @@ type Response struct {
Words int
Lines int
TlsData *TlsData
CspData *CspData
}
// GetHeader value

View File

@ -16,27 +16,3 @@ func IsCidr(ip string) bool {
func IsIP(ip string) bool {
return net.ParseIP(ip) != nil
}
// Ips of a cidr
func Ips(cidr string) ([]string, error) {
ip, ipnet, err := net.ParseCIDR(cidr)
if err != nil {
return nil, err
}
var ips []string
for ip := ip.Mask(ipnet.Mask); ipnet.Contains(ip); inc(ip) {
ips = append(ips, ip.String())
}
// remove network address and broadcast address
return ips[1 : len(ips)-1], nil
}
func inc(ip net.IP) {
for j := len(ip) - 1; j >= 0; j-- {
ip[j]++
if ip[j] > 0 {
break
}
}
}

17
go.mod
View File

@ -1,17 +0,0 @@
module github.com/projectdiscovery/httpx
go 1.14
require (
github.com/coocood/freecache v1.1.1
github.com/hbakhtiyor/strsim v0.0.0-20190107154042-4d2bbb273edf
github.com/logrusorgru/aurora v2.0.3+incompatible
github.com/microcosm-cc/bluemonday v1.0.3
github.com/miekg/dns v1.1.31
github.com/projectdiscovery/gologger v1.0.1
github.com/projectdiscovery/retryablehttp-go v1.0.1
github.com/remeh/sizedwaitgroup v1.0.0
github.com/rs/xid v1.2.1
golang.org/x/net v0.0.0-20200707034311-ab3426394381
golang.org/x/text v0.3.3
)

58
go.sum
View File

@ -1,58 +0,0 @@
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/chris-ramon/douceur v0.2.0 h1:IDMEdxlEUUBYBKE4z/mJnFyVXox+MjuEVDJNN27glkU=
github.com/chris-ramon/douceur v0.2.0/go.mod h1:wDW5xjJdeoMm1mRt4sD4c/LbF/mWdEpRXQKjTR8nIBE=
github.com/coocood/freecache v1.1.1 h1:uukNF7QKCZEdZ9gAV7WQzvh0SbjwdMF6m3x3rxEkaPc=
github.com/coocood/freecache v1.1.1/go.mod h1:OKrEjkGVoxZhyWAJoeFi5BMLUJm2Tit0kpGkIr7NGYY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
github.com/hbakhtiyor/strsim v0.0.0-20190107154042-4d2bbb273edf h1:umfGUaWdFP2s6457fz1+xXYIWDxdGc7HdkLS9aJ1skk=
github.com/hbakhtiyor/strsim v0.0.0-20190107154042-4d2bbb273edf/go.mod h1:V99KdStnMHZsvVOwIvhfcUzYgYkRZeQWUtumtL+SKxA=
github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/logrusorgru/aurora v2.0.3+incompatible h1:tOpm7WcpBTn4fjmVfgpQq0EfczGlG91VSDkswnjF5A8=
github.com/logrusorgru/aurora v2.0.3+incompatible/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4=
github.com/microcosm-cc/bluemonday v1.0.3 h1:EjVH7OqbU219kdm8acbveoclh2zZFqPJTJw6VUlTLAQ=
github.com/microcosm-cc/bluemonday v1.0.3/go.mod h1:8iwZnFn2CDDNZ0r6UXhF4xawGvzaqzCRa1n3/lO3W2w=
github.com/miekg/dns v1.1.31 h1:sJFOl9BgwbYAWOGEwr61FU28pqsBNdpRBnhGXtO06Oo=
github.com/miekg/dns v1.1.31/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/projectdiscovery/gologger v1.0.1 h1:FzoYQZnxz9DCvSi/eg5A6+ET4CQ0CDUs27l6Exr8zMQ=
github.com/projectdiscovery/gologger v1.0.1/go.mod h1:Ok+axMqK53bWNwDSU1nTNwITLYMXMdZtRc8/y1c7sWE=
github.com/projectdiscovery/retryablehttp-go v1.0.1 h1:V7wUvsZNq1Rcz7+IlcyoyQlNwshuwptuBVYWw9lx8RE=
github.com/projectdiscovery/retryablehttp-go v1.0.1/go.mod h1:SrN6iLZilNG1X4neq1D+SBxoqfAF4nyzvmevkTkWsek=
github.com/remeh/sizedwaitgroup v1.0.0 h1:VNGGFwNo/R5+MJBf6yrsr110p0m4/OX4S3DCy7Kyl5E=
github.com/remeh/sizedwaitgroup v1.0.0/go.mod h1:3j2R4OIe/SeS6YDhICBy22RWjJC5eNCJ1V+9+NVNYlo=
github.com/rs/xid v1.2.1 h1:mhH9Nq+C1fY2l1XIpgxIiUOfNpRBYH1kKcr+qfKgjRc=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
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-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
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=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=