diff --git a/config/json.go b/config/json.go index 7c197ade..7ae3b3fa 100644 --- a/config/json.go +++ b/config/json.go @@ -70,6 +70,7 @@ type RegexCaptureConf struct { type capturePath struct { JsonPath *string `json:"json_path"` XPath *string `json:"xpath"` + XpathHtml *string `json:"xpath_html"` RegExp *RegexCaptureConf `json:"regexp"` From string `json:"from"` // body,header,cookie CookieName *string `json:"cookie_name"` @@ -375,6 +376,7 @@ func stepToScenarioStep(s step) (types.ScenarioStep, error) { capConf := types.EnvCaptureConf{ JsonPath: path.JsonPath, Xpath: path.XPath, + XpathHtml: path.XpathHtml, Name: name, From: types.SourceType(path.From), Key: path.HeaderKey, diff --git a/core/scenario/scripting/assertion/assert_test.go b/core/scenario/scripting/assertion/assert_test.go index 69470779..d8de47be 100644 --- a/core/scenario/scripting/assertion/assert_test.go +++ b/core/scenario/scripting/assertion/assert_test.go @@ -488,7 +488,7 @@ func TestAssert(t *testing.T) { expected: true, }, { - input: `equals(xml_path("//item/title"),"ABC")`, + input: `equals(xpath("//item/title"),"ABC")`, envs: &evaluator.AssertEnv{ Body: ` @@ -502,6 +502,19 @@ func TestAssert(t *testing.T) { expected: true, }, + { + input: `equals(html_path("//body/h1"),"ABC")`, + envs: &evaluator.AssertEnv{ + Body: ` + + +

ABC

+ + `, + }, + + expected: true, + }, { input: "equals(cookies.test.value, \"value\")", envs: &evaluator.AssertEnv{ @@ -790,7 +803,12 @@ func TestAssert(t *testing.T) { expectedError: "ArgumentError", }, { - input: "xml_path(23)", // arg must be string + input: "xpath(23)", // arg must be string + expected: false, + expectedError: "ArgumentError", + }, + { + input: "html_path(23)", // arg must be string expected: false, expectedError: "ArgumentError", }, diff --git a/core/scenario/scripting/assertion/evaluator/evaluator.go b/core/scenario/scripting/assertion/evaluator/evaluator.go index 4b3a72c0..aed42227 100644 --- a/core/scenario/scripting/assertion/evaluator/evaluator.go +++ b/core/scenario/scripting/assertion/evaluator/evaluator.go @@ -152,6 +152,15 @@ func Eval(node ast.Node, env *AssertEnv, receivedMap map[string]interface{}) (in } } return xmlExtract(env.Body, xpath) + case HTMLPATH: + html, ok := args[0].(string) + if !ok { + return false, ArgumentError{ + msg: "htmlpath must be a string", + wrappedErr: nil, + } + } + return htmlExtract(env.Body, html) case REGEXP: regexp, ok := args[1].(string) if !ok { diff --git a/core/scenario/scripting/assertion/evaluator/function.go b/core/scenario/scripting/assertion/evaluator/function.go index a103be4b..17054be4 100644 --- a/core/scenario/scripting/assertion/evaluator/function.go +++ b/core/scenario/scripting/assertion/evaluator/function.go @@ -133,6 +133,11 @@ var xmlExtract = func(source interface{}, xPath string) (interface{}, error) { return val, err } +var htmlExtract = func(source interface{}, xPath string) (interface{}, error) { + val, err := extraction.ExtractFromHtml(source, xPath) + return val, err +} + var regexExtract = func(source interface{}, xPath string, matchNo int64) (interface{}, error) { val, err := extraction.ExtractWithRegex(source, types.RegexCaptureConf{ Exp: &xPath, @@ -194,6 +199,7 @@ var assertionFuncMap = map[string]struct{}{ IN: {}, JSONPATH: {}, XMLPATH: {}, + HTMLPATH: {}, REGEXP: {}, EXISTS: {}, CONTAINS: {}, @@ -216,7 +222,8 @@ const ( EQUALS = "equals" IN = "in" JSONPATH = "json_path" - XMLPATH = "xml_path" + XMLPATH = "xpath" + HTMLPATH = "html_path" REGEXP = "regexp" EXISTS = "exists" CONTAINS = "contains" diff --git a/core/scenario/scripting/extraction/base.go b/core/scenario/scripting/extraction/base.go index 63956aac..b118c2bc 100644 --- a/core/scenario/scripting/extraction/base.go +++ b/core/scenario/scripting/extraction/base.go @@ -49,6 +49,8 @@ func Extract(source interface{}, ce types.EnvCaptureConf) (val interface{}, err val, err = ExtractWithRegex(source, *ce.RegExp) } else if ce.Xpath != nil { val, err = ExtractFromXml(source, *ce.Xpath) + } else if ce.XpathHtml != nil { + val, err = ExtractFromHtml(source, *ce.XpathHtml) } case types.Cookie: cookies := source.(map[string]*http.Cookie) @@ -111,6 +113,18 @@ func ExtractFromXml(source interface{}, xPath string) (interface{}, error) { } } +func ExtractFromHtml(source interface{}, xPath string) (interface{}, error) { + xe := htmlExtractor{} + switch s := source.(type) { + case []byte: // from response body + return xe.extractFromByteSlice(s, xPath) + case string: // from response header + return xe.extractFromString(s, xPath) + default: + return "", fmt.Errorf("Unsupported type for extraction source") + } +} + type ExtractionError struct { // UnWrappable msg string wrappedErr error diff --git a/core/scenario/scripting/extraction/html.go b/core/scenario/scripting/extraction/html.go new file mode 100644 index 00000000..0599b4b6 --- /dev/null +++ b/core/scenario/scripting/extraction/html.go @@ -0,0 +1,43 @@ +package extraction + +import ( + "bytes" + "fmt" + + "github.com/antchfx/htmlquery" +) + +type htmlExtractor struct { +} + +func (xe htmlExtractor) extractFromByteSlice(source []byte, xPath string) (interface{}, error) { + reader := bytes.NewBuffer(source) + rootNode, err := htmlquery.Parse(reader) + if err != nil { + return nil, err + } + + // returns the first matched element + foundNode, err := htmlquery.Query(rootNode, xPath) + if foundNode == nil || err != nil { + return nil, fmt.Errorf("no match for the xPath_html: %s", xPath) + } + + return foundNode.FirstChild.Data, nil +} + +func (xe htmlExtractor) extractFromString(source string, xPath string) (interface{}, error) { + reader := bytes.NewBufferString(source) + rootNode, err := htmlquery.Parse(reader) + if err != nil { + return nil, err + } + + // returns the first matched element + foundNode, err := htmlquery.Query(rootNode, xPath) + if foundNode == nil || err != nil { + return nil, fmt.Errorf("no match for this xpath_html") + } + + return foundNode.FirstChild.Data, nil +} diff --git a/core/scenario/scripting/extraction/html_test.go b/core/scenario/scripting/extraction/html_test.go new file mode 100644 index 00000000..13730168 --- /dev/null +++ b/core/scenario/scripting/extraction/html_test.go @@ -0,0 +1,120 @@ +package extraction + +import ( + "fmt" + "strings" + "testing" +) + +func TestHtmlExtraction(t *testing.T) { + expected := "Html Title" + HtmlSource := fmt.Sprintf(` + + +

%s

+

My first paragraph.

+ + `, expected) + + xe := htmlExtractor{} + xpath := "//body/h1" + val, err := xe.extractFromByteSlice([]byte(HtmlSource), xpath) + + if err != nil { + t.Errorf("TestHtmlExtraction %v", err) + } + + if !strings.EqualFold(val.(string), expected) { + t.Errorf("TestHtmlExtraction expected: %s, got: %s", expected, val) + } +} + +func TestHtmlExtractionSeveralNode(t *testing.T) { + //should extract only the first one + expected := "Html Title" + HtmlSource := fmt.Sprintf(` + + +

%s

+

another node

+

My first paragraph.

+ + `, expected) + + xe := htmlExtractor{} + xpath := "//h1" + val, err := xe.extractFromByteSlice([]byte(HtmlSource), xpath) + + if err != nil { + t.Errorf("TestHtmlExtraction %v", err) + } + + if !strings.EqualFold(val.(string), expected) { + t.Errorf("TestHtmlExtraction expected: %s, got: %s", expected, val) + } +} + +func TestHtmlExtraction_PathNotFound(t *testing.T) { + expected := "XML Title" + xmlSource := fmt.Sprintf(` + + +

%s

+

another node

+

My first paragraph.

+ + `, expected) + + xe := htmlExtractor{} + xpath := "//h2" + _, err := xe.extractFromByteSlice([]byte(xmlSource), xpath) + + if err == nil { + t.Errorf("TestHtmlExtraction_PathNotFound, should be err, got :%v", err) + } +} + +func TestInvalidHtml(t *testing.T) { + xmlSource := `invalid html source` + + xe := htmlExtractor{} + xpath := "//input" + _, err := xe.extractFromByteSlice([]byte(xmlSource), xpath) + + if err == nil { + t.Errorf("TestInvalidXml, should be err, got :%v", err) + } +} + +func TestHtmlComplexExtraction(t *testing.T) { + expected := "Html Title" + HtmlSource := fmt.Sprintf(` + + + +

%s

+

My first paragraph.

+ + `, expected) + + xe := htmlExtractor{} + xpath := "//body/h1" + val, err := xe.extractFromByteSlice([]byte(HtmlSource), xpath) + + if err != nil { + t.Errorf("TestHtmlExtraction %v", err) + } + + if !strings.EqualFold(val.(string), expected) { + t.Errorf("TestHtmlExtraction expected: %s, got: %s", expected, val) + } +} diff --git a/core/types/scenario.go b/core/types/scenario.go index 7fb83c58..4f23398d 100644 --- a/core/types/scenario.go +++ b/core/types/scenario.go @@ -86,7 +86,7 @@ func (s *Scenario) validate() error { // add global envs for key := range s.Envs { - if !envVarNameRegexp.Match([]byte(key)) { // not a valid env definition + if !envVarNameRegexp.MatchString(key) { // not a valid env definition return fmt.Errorf("env key is not valid: %s", key) } definedEnvs[key] = struct{}{} // exist @@ -98,7 +98,7 @@ func (s *Scenario) validate() error { return fmt.Errorf("csv key can not have dot in it: %s", key) } for _, s := range splitted { - if !envVarNameRegexp.Match([]byte(s)) { // not a valid env definition + if !envVarNameRegexp.MatchString(s) { // not a valid env definition return fmt.Errorf("csv key is not valid: %s", key) } } @@ -112,7 +112,7 @@ func (s *Scenario) validate() error { // enrich Envs map with captured envs from each step for _, ce := range st.EnvsToCapture { - if !envVarNameRegexp.Match([]byte(ce.Name)) { // not a valid env definition + if !envVarNameRegexp.MatchString(ce.Name) { // not a valid env definition return fmt.Errorf("captured env key is not valid: %s", ce.Name) } definedEnvs[ce.Name] = struct{}{} @@ -251,6 +251,7 @@ type RegexCaptureConf struct { type EnvCaptureConf struct { JsonPath *string `json:"json_path"` Xpath *string `json:"xpath"` + XpathHtml *string `json:"xpath_html"` RegExp *RegexCaptureConf `json:"regexp"` Name string `json:"as"` From SourceType `json:"from"` @@ -339,9 +340,9 @@ func validateCaptureConf(conf EnvCaptureConf) error { } } - if conf.From == Body && conf.JsonPath == nil && conf.RegExp == nil && conf.Xpath == nil { + if conf.From == Body && conf.JsonPath == nil && conf.RegExp == nil && conf.Xpath == nil && conf.XpathHtml == nil { return CaptureConfigError{ - msg: fmt.Sprintf("%s, one of json_path, regexp, xpath key must be specified when extracting from body", conf.Name), + msg: fmt.Sprintf("%s, one of json_path, regexp, xpath or xpath_html key must be specified when extracting from body", conf.Name), } } diff --git a/engine_docs/README.md b/engine_docs/README.md index 29f98270..e797d9d1 100644 --- a/engine_docs/README.md +++ b/engine_docs/README.md @@ -681,7 +681,8 @@ If Ddosify can't receive the response for a request, that step is marked as Fail | `not` | ( param `bool` ) | returns converse of given param | | `range` | ( param `int`, low `int`,high `int` ) | returns param is in range of [low,high): low is included, high is not included. | | `json_path` | ( json_path `string`) | extracts from response body using given json path | -| `xml_path` | ( xpath `string` ) | extracts from response body using given xml path | +| `xpath` | ( xpath `string` ) | extracts from response body using given xml path | +| `html_path` | ( html `string` ) | extracts from response body using given html path | | `regexp` | ( param `any`, regexp `string`, matchNo `int` ) | extracts from given value in the first parameter using given regular expression | ### Operators @@ -707,6 +708,7 @@ If Ddosify can't receive the response for a request, that step is marked as Fail | `status_code != 500` | same as preceding one| | `equals(json_path(\"employees.0.name\"),\"Name\")` | checks if json extracted value is equal to "Name"| | `equals(xpath(\"//item/title\"),\"ABC\")` | checks if xml extracted value is equal to "ABC" | +| `equals(html_path(\"//body/h1\"),\"ABC\")` | checks if html extracted value is equal to "ABC" | | `equals(variables.x,100)` | checks if `x` variable coming from global or captured variables is equal to 100| | `equals(variables.x,variables.y)` | checks if variables `x` and `y` are equal to each other | | `equals_on_file(body,\"file.json\")` | reads from file.json and compares response body with read file | @@ -761,7 +763,7 @@ Unlike assertions focused on individual steps, which determine the success or fa | `less_than(fail_count_perc,0.05)` | Fail count percentage should be less than 5% | ## Correlation -Ddosify enables you to capture variables from steps using **json_path**, **xpath**, or **regular expressions**. Later, in the subsequent steps, you can inject both the captured variables and the scenario-scoped global variables. +Ddosify enables you to capture variables from steps using **json_path**, **xpath**, **xpath_html**, or **regular expressions**. Later, in the subsequent steps, you can inject both the captured variables and the scenario-scoped global variables. > **:warning: Points to keep in mind** > - You must specify **'header_key'** when capturing from header. @@ -792,7 +794,7 @@ ddosify -config ddosify_config_correlation.json -debug } ``` -### Capture With XPath +### Capture With XPath on xml ```json { "steps": [ @@ -805,6 +807,20 @@ ddosify -config ddosify_config_correlation.json -debug } ``` +### Capture With XPath on html +```json +{ + "steps": [ + { + "capture_env": { + "TITLE" :{"from":"body","xpath_html":"//body/h1"}, + } + } + ] +} +``` + + ### Capture With Regular Expressions ```json { diff --git a/go.mod b/go.mod index 100ad2a1..99d9160c 100644 --- a/go.mod +++ b/go.mod @@ -17,9 +17,10 @@ require ( ) require ( - github.com/antchfx/xpath v1.2.1 // indirect + github.com/antchfx/htmlquery v1.3.0 + github.com/antchfx/xpath v1.2.3 // indirect github.com/go-ole/go-ole v1.2.6 // indirect - github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/jaswdr/faker v1.10.2 // indirect github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect github.com/mattn/go-isatty v0.0.14 // indirect diff --git a/go.sum b/go.sum index ff40894d..b99e73b0 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,11 @@ +github.com/antchfx/htmlquery v1.3.0 h1:5I5yNFOVI+egyia5F2s/5Do2nFWxJz41Tr3DyfKD25E= +github.com/antchfx/htmlquery v1.3.0/go.mod h1:zKPDVTMhfOmcwxheXUsx4rKJy8KEY/PU6eXr/2SebQ8= github.com/antchfx/xmlquery v1.3.13 h1:wqhTv2BN5MzYg9rnPVtZb3IWP8kW6WV/ebAY0FCTI7Y= github.com/antchfx/xmlquery v1.3.13/go.mod h1:3w2RvQvTz+DaT5fSgsELkSJcdNgkmg6vuXDEuhdwsPQ= github.com/antchfx/xpath v1.2.1 h1:qhp4EW6aCOVr5XIkT+l6LJ9ck/JsUH/yyauNgTQkBF8= github.com/antchfx/xpath v1.2.1/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs= +github.com/antchfx/xpath v1.2.3 h1:CCZWOzv5bAqjVv0offZ2LVgVYFbeldKQVuLNbViZdes= +github.com/antchfx/xpath v1.2.3/go.mod h1:i54GszH55fYfBmoZXapTHN8T8tkcHfRgLyVwwqzXNcs= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d h1:Byv0BzEl3/e6D5CLfI0j/7hiIEtvGVFPCZ7Ei2oq8iQ= github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -17,6 +21,8 @@ github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -55,30 +61,53 @@ github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+Kd github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI= github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms= github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/exp v0.0.0-20230108222341-4b8118a2686a h1:tlXy25amD5A7gOfbXdqCGN5k8ESEed/Ee1E5RcrYnqU= golang.org/x/exp v0.0.0-20230108222341-4b8118a2686a/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +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-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/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-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ= golang.org/x/sys v0.6.0/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-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.4.0/go.mod h1:9P2UbLfCdcvo3p/nzKvsmas4TnlujnuoV9hGgYzW1lQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=