diff --git a/00-main.go b/00-main.go index 653212c..71ca830 100644 --- a/00-main.go +++ b/00-main.go @@ -66,6 +66,7 @@ func main() { HTTPRetryDelay: httpRetryDelay, LogContext: false, LogFunctions: false, + Matchers: make(map[string]string), MaxHTTPAttempts: maxHTTPAttempts, Pathnames: flag.Args(), ResultGathererChannel: make(chan context), @@ -75,6 +76,12 @@ func main() { WaitGroup: &sync.WaitGroup{}, } + err = loadMatchers(context) + + if err != nil { + panic(err) + } + context.log("00 main") go resultGatherer(*context) diff --git a/05-expected-response-match-parser.go b/05-expected-response-match-parser.go index c29d2cb..0e5fc61 100644 --- a/05-expected-response-match-parser.go +++ b/05-expected-response-match-parser.go @@ -63,29 +63,11 @@ func expectedResponseMatchParser(context *context) { reString := "(" - switch parts[i+1] { - case ":date": - reString += - "(Mon|Tue|Wed|Thu|Fri|Sat|Sun), " + - "(0\\d|1\\d|2\\d|3[01]) " + - "(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) " + - "2\\d{3} " + - "(0\\d|1\\d|2[0-3]):" + - "(0\\d|1\\d|2\\d|3\\d|4\\d|5\\d):" + - "(0\\d|1\\d|2\\d|3\\d|4\\d|5\\d) " + - "(A|M|N|Y|Z|UT|GMT|[A-Z]{3}|[+-](0\\d|1[012]))" - case ":b62:22": - reString += "[0-9A-Za-z]{22}" - case ":iso8601:µs:z": - reString += "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}[.]\\d{6}Z" - case ":uuid": - reString += - "[[:xdigit:]]{8}-" + - "[[:xdigit:]]{4}-" + - "[[:xdigit:]]{4}-" + - "[[:xdigit:]]{4}-" + - "[[:xdigit:]]{12}" - default: + _, ok := context.Matchers[parts[i+1]] + + if ok { + reString += context.Matchers[parts[i+1]] + } else { reString += parts[i+1] } diff --git a/Dockerfile b/Dockerfile index f1de485..664188a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,6 +8,7 @@ RUN apk update && \ COPY * /go/src/github.com/tmornini/http-spec/ RUN cd /go/src/github.com/tmornini/http-spec && \ + go get ./... && \ go install . WORKDIR / diff --git a/README.md b/README.md index 65e7c86..0be1865 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,8 @@ MAINTAINER Tom Mornini COPY run-http-specs /run-http-specs COPY *.htsf / + +COPY matchers.yml / ``` ## Basic Usage @@ -155,6 +157,24 @@ for 128+ bit UUIDs. ⧆optional-name⧆:iso8601:µs:z⧆ is a matcher for ISO 8601 format timestamps with microsecond resolution and zulu (Z) timezone. +## Custom Matchers + +Custom matchers work just like built-in matchers and are loaded from an optionally +provided matchers.yml file in the docker container. See the example-Dockerfile and +matchers.yml file. + +The matchers.yml file is a basic YAML file with the keys coresponding to the mandatory-regexp +portion of the matcher and the value corresponding to the actual regexp. + +matchers.yml example: +``` +RFC4122v4: "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}" +``` +usage of the custom matcher: +``` +⧆optional-name⧆:RFC4122v4⧆ +``` + ## Built-in Substitutes ⧈YYYY-MM-DD⧈ is a substitute for today's date diff --git a/example-Dockerfile b/example-Dockerfile index d17562f..76fda95 100644 --- a/example-Dockerfile +++ b/example-Dockerfile @@ -4,3 +4,5 @@ MAINTAINER Tom Mornini COPY run-http-specs /run-http-specs COPY *.htsf / + +COPY matchers.yml / diff --git a/example-custom-matcher.htsf b/example-custom-matcher.htsf new file mode 100644 index 0000000..2c89ed0 --- /dev/null +++ b/example-custom-matcher.htsf @@ -0,0 +1,15 @@ +> GET https://api.subledger.com/v3/system/status HTTP/1.1 +> + +< HTTP/1.1 200 OK +< Connection: keep-alive +< Content-Security-Policy: default-src https: data: 'unsafe-inline' 'unsafe-eval' +< Date: ⧆⧆:date⧆ +< Public-Key-Pins: pin-sha256="⧆⧆[^"]+⧆"; pin-sha256="⧆⧆[^"]+⧆"; max-age=300 +< Server: nginx +< Strict-Transport-Security: max-age=10886400; includeSubDomains; preload +< X-Content-Type-Options: nosniff +< X-Frame-Options: ⧆⧆:same-origin⧆ +< X-Xss-Protection: 1; mode=block +< +< {"dynamodb":true,"sqs":true,"redis":true} diff --git a/example-do-not-follow-redirect.htsf b/example-do-not-follow-redirect.htsf index 7d9bfb0..223910d 100644 --- a/example-do-not-follow-redirect.htsf +++ b/example-do-not-follow-redirect.htsf @@ -6,7 +6,7 @@ < Content-Type: text/html;charset=ISO-8859-1 < Date: ⧆⧆:date⧆ < Location: https://jigsaw.w3.org/HTTP/300/Overview.html -< Public-Key-Pins: pin-sha256="cN0QSpPIkuwpT6iP2YjEo1bEwGpH/yiUn6yhdy+HNto="; pin-sha256="WGJkyYjx1QMdMe0UqlyOKXtydPDVrk7sl2fV+nNm1r4="; pin-sha256="LrKdTxZLRTvyHM4/atX2nquX9BeHRZMCxg3cf4rhc2I="; max-age=864000 +< Public-Key-Pins: pin-sha256="⧆⧆[^"]+⧆"; pin-sha256="⧆⧆[^"]+⧆"; pin-sha256="⧆⧆[^"]+⧆"; max-age=864000 < Server: Jigsaw/2.3.0-beta⧆⧆\d+⧆ < Strict-Transport-Security: max-age=15552015; includeSubDomains; preload < X-Frame-Options: deny diff --git a/example-not-found.htsf b/example-not-found.htsf index 55f76e2..b73764d 100644 --- a/example-not-found.htsf +++ b/example-not-found.htsf @@ -7,7 +7,7 @@ < Content-Security-Policy: default-src https: data: 'unsafe-inline' 'unsafe-eval' < Content-Type: text/html < Date: ⧆⧆:date⧆ -< Public-Key-Pins: pin-sha256="NRx3RbA+pNNyfX8GvCJGysRCFQJnqiNhM+u8dVaqn90="; pin-sha256="wn7UqCaQr4O5YYeHLnz52CD6jasiTYyFz0plWceOlgM="; max-age=300 +< Public-Key-Pins: pin-sha256="⧆⧆[^"]+⧆"; pin-sha256="⧆⧆[^"]+⧆"; max-age=300 < Server: nginx < Strict-Transport-Security: max-age=10886400; includeSubDomains; preload < X-Content-Type-Options: nosniff diff --git a/example-post.htsf b/example-post.htsf index 8f35919..dd6dbb5 100644 --- a/example-post.htsf +++ b/example-post.htsf @@ -14,7 +14,7 @@ < Content-Security-Policy: default-src https: data: 'unsafe-inline' 'unsafe-eval' < Content-Type: application/json < Date: ⧆⧆:date⧆ -< Public-Key-Pins: pin-sha256="NRx3RbA+pNNyfX8GvCJGysRCFQJnqiNhM+u8dVaqn90="; pin-sha256="wn7UqCaQr4O5YYeHLnz52CD6jasiTYyFz0plWceOlgM="; max-age=300 +< Public-Key-Pins: pin-sha256="⧆⧆[^"]+⧆"; pin-sha256="⧆⧆[^"]+⧆"; max-age=300 < Server: nginx < Strict-Transport-Security: max-age=10886400; includeSubDomains; preload < Vary: Origin @@ -34,7 +34,7 @@ < Connection: keep-alive < Content-Security-Policy: default-src https: data: 'unsafe-inline' 'unsafe-eval' < Date: ⧆⧆:date⧆ -< Public-Key-Pins: pin-sha256="NRx3RbA+pNNyfX8GvCJGysRCFQJnqiNhM+u8dVaqn90="; pin-sha256="wn7UqCaQr4O5YYeHLnz52CD6jasiTYyFz0plWceOlgM="; max-age=300 +< Public-Key-Pins: pin-sha256="⧆⧆[^"]+⧆"; pin-sha256="⧆⧆[^"]+⧆"; max-age=300 < Server: nginx < Strict-Transport-Security: max-age=10886400; includeSubDomains; preload < Www-Authenticate: Basic realm="Subledger API v2" diff --git a/example-regexp-and-substitution.htsf b/example-regexp-and-substitution.htsf index 2384399..0f157b3 100644 --- a/example-regexp-and-substitution.htsf +++ b/example-regexp-and-substitution.htsf @@ -5,7 +5,7 @@ < Connection: keep-alive < Content-Security-Policy: default-src https: data: 'unsafe-inline' 'unsafe-eval' < Date: ⧆⧆:date⧆ -< Public-Key-Pins: pin-sha256="NRx3RbA+pNNyfX8GvCJGysRCFQJnqiNhM+u8dVaqn90="; pin-sha256="wn7UqCaQr4O5YYeHLnz52CD6jasiTYyFz0plWceOlgM="; max-age=300 +< Public-Key-Pins: pin-sha256="⧆⧆[^"]+⧆"; pin-sha256="⧆⧆[^"]+⧆"; max-age=300 < Server: nginx < Strict-Transport-Security: max-age=10886400; includeSubDomains; preload < X-Content-Type-Options: nosniff @@ -21,7 +21,7 @@ < Connection: keep-alive < Content-Security-Policy: default-src https: data: 'unsafe-inline' 'unsafe-eval' < Date: ⧆⧆:date⧆ -< Public-Key-Pins: pin-sha256="NRx3RbA+pNNyfX8GvCJGysRCFQJnqiNhM+u8dVaqn90="; pin-sha256="wn7UqCaQr4O5YYeHLnz52CD6jasiTYyFz0plWceOlgM="; max-age=300 +< Public-Key-Pins: pin-sha256="⧆⧆[^"]+⧆"; pin-sha256="⧆⧆[^"]+⧆"; max-age=300 < Server: nginx < Strict-Transport-Security: max-age=10886400; includeSubDomains; preload < X-Content-Type-Options: nosniff diff --git a/example-scheme-and-hostname-2.htsf b/example-scheme-and-hostname-2.htsf index 65eedef..516e06c 100644 --- a/example-scheme-and-hostname-2.htsf +++ b/example-scheme-and-hostname-2.htsf @@ -5,7 +5,7 @@ < Connection: keep-alive < Content-Security-Policy: default-src https: data: 'unsafe-inline' 'unsafe-eval' < Date: ⧆⧆:date⧆ -< Public-Key-Pins: pin-sha256="NRx3RbA+pNNyfX8GvCJGysRCFQJnqiNhM+u8dVaqn90="; pin-sha256="wn7UqCaQr4O5YYeHLnz52CD6jasiTYyFz0plWceOlgM="; max-age=300 +< Public-Key-Pins: pin-sha256="⧆⧆[^"]+⧆"; pin-sha256="⧆⧆[^"]+⧆"; max-age=300 < Server: nginx < Strict-Transport-Security: max-age=10886400; includeSubDomains; preload < X-Content-Type-Options: nosniff diff --git a/example-sleep.htsf b/example-sleep.htsf index 8b7d321..19b668c 100644 --- a/example-sleep.htsf +++ b/example-sleep.htsf @@ -5,7 +5,7 @@ < Connection: keep-alive < Content-Security-Policy: default-src https: data: 'unsafe-inline' 'unsafe-eval' < Date: ⧆⧆:date⧆ -< Public-Key-Pins: pin-sha256="NRx3RbA+pNNyfX8GvCJGysRCFQJnqiNhM+u8dVaqn90="; pin-sha256="wn7UqCaQr4O5YYeHLnz52CD6jasiTYyFz0plWceOlgM="; max-age=300 +< Public-Key-Pins: pin-sha256="⧆⧆[^"]+⧆"; pin-sha256="⧆⧆[^"]+⧆"; max-age=300 < Server: nginx < Strict-Transport-Security: max-age=10886400; includeSubDomains; preload < X-Content-Type-Options: nosniff @@ -21,7 +21,7 @@ < Connection: keep-alive < Content-Security-Policy: default-src https: data: 'unsafe-inline' 'unsafe-eval' < Date: ⧆⧆:date⧆ -< Public-Key-Pins: pin-sha256="NRx3RbA+pNNyfX8GvCJGysRCFQJnqiNhM+u8dVaqn90="; pin-sha256="wn7UqCaQr4O5YYeHLnz52CD6jasiTYyFz0plWceOlgM="; max-age=300 +< Public-Key-Pins: pin-sha256="⧆⧆[^"]+⧆"; pin-sha256="⧆⧆[^"]+⧆"; max-age=300 < Server: nginx < Strict-Transport-Security: max-age=10886400; includeSubDomains; preload < X-Content-Type-Options: nosniff diff --git a/example-uuid.htsf b/example-uuid.htsf index 06d063e..d6a3aea 100644 --- a/example-uuid.htsf +++ b/example-uuid.htsf @@ -5,7 +5,7 @@ < Connection: keep-alive ⧆⧆:uuid⧆ < Content-Security-Policy: default-src https: data: 'unsafe-inline' 'unsafe-eval' < Date: ⧆⧆:date⧆ -< Public-Key-Pins: pin-sha256="hEJVeNxwqLnjseFv/doE3XU6Nr/hXJnoKdhn12iwavY="; pin-sha256="cTduB9g7EP4xd2glLUNhEq40f/jtyJF24dTzgVuKsZk="; max-age=300 +< Public-Key-Pins: pin-sha256="⧆⧆[^"]+⧆"; pin-sha256="⧆⧆[^"]+⧆"; max-age=300 < Server: nginx < Strict-Transport-Security: max-age=10886400; includeSubDomains; preload < X-Content-Type-Options: nosniff diff --git a/load-matchers.go b/load-matchers.go new file mode 100644 index 0000000..f0439f0 --- /dev/null +++ b/load-matchers.go @@ -0,0 +1,43 @@ +package main + +import ( + "errors" + "io/ioutil" + + yaml "gopkg.in/yaml.v2" +) + +func loadMatchers(context *context) error { + // load default matchers + context.Matchers[":date"] = "(Mon|Tue|Wed|Thu|Fri|Sat|Sun), (0\\d|1\\d|2\\d|3[01]) (Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec) 2\\d{3} (0\\d|1\\d|2[0-3]):(0\\d|1\\d|2\\d|3\\d|4\\d|5\\d):(0\\d|1\\d|2\\d|3\\d|4\\d|5\\d) (A|M|N|Y|Z|UT|GMT|[A-Z]{3}|[+-](0\\d|1[012]))" + context.Matchers[":b62:22"] = "[0-9A-Za-z]{22}" + context.Matchers[":iso8601:µs:z"] = "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}[.]\\d{6}Z" + context.Matchers[":uuid"] = "[[:xdigit:]]{8}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{4}-[[:xdigit:]]{12}" + + // load custom matchers if provided + var ymlData map[string]string + + ymlFile, err := ioutil.ReadFile("/matchers.yml") + // ignore error (perhaps they didn't provide a custom matcher file) + if err == nil { + err = yaml.Unmarshal(ymlFile, &ymlData) + + if err != nil { + return err + } + + for key, value := range ymlData { + trueKey := ":" + key + + _, ok := context.Matchers[trueKey] + + if ok { + return errors.New(key + " is a default matcher and cannot be used") + } + + context.Matchers[trueKey] = value + } + } + + return nil +} diff --git a/matchers.yml b/matchers.yml new file mode 100644 index 0000000..1ce7b8e --- /dev/null +++ b/matchers.yml @@ -0,0 +1,3 @@ +RFC3339: "\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z" +RFC4122v4: "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}" +same-origin: SAMEORIGIN diff --git a/run-http-specs b/run-http-specs index 4b360b3..b568475 100755 --- a/run-http-specs +++ b/run-http-specs @@ -17,6 +17,8 @@ time ( http-spec example-built-in-substitution.htsf || exit 1 + http-spec example-custom-matcher.htsf || exit 1 + http-spec -hostname api.subledger.com -scheme http \ example-scheme-and-hostname-1.htsf || exit 1 diff --git a/type-context.go b/type-context.go index 628cfd1..363665e 100644 --- a/type-context.go +++ b/type-context.go @@ -18,6 +18,7 @@ type context struct { ID *big.Int LogContext bool LogFunctions bool + Matchers map[string]string MaxHTTPAttempts int Pathname string Pathnames []string