tusd/vendor/gopkg.in/h2non/gock.v1/matchers.go

232 lines
5.4 KiB
Go

package gock
import (
"compress/gzip"
"encoding/json"
"io"
"io/ioutil"
"net/http"
"reflect"
"regexp"
)
// EOL represents the end of line character.
const EOL = 0xa
// BodyTypes stores the supported MIME body types for matching.
// Currently only text-based types.
var BodyTypes = []string{
"text/html",
"text/plain",
"application/json",
"application/xml",
"multipart/form-data",
"application/x-www-form-urlencoded",
}
// BodyTypeAliases stores a generic MIME type by alias.
var BodyTypeAliases = map[string]string{
"html": "text/html",
"text": "text/plain",
"json": "application/json",
"xml": "application/xml",
"form": "multipart/form-data",
"url": "application/x-www-form-urlencoded",
}
// CompressionSchemes stores the supported Content-Encoding types for decompression.
var CompressionSchemes = []string{
"gzip",
}
// MatchMethod matches the HTTP method of the given request.
func MatchMethod(req *http.Request, ereq *Request) (bool, error) {
return ereq.Method == "" || req.Method == ereq.Method, nil
}
// MatchScheme matches the request URL protocol scheme.
func MatchScheme(req *http.Request, ereq *Request) (bool, error) {
return ereq.URLStruct.Scheme == "" || req.URL.Scheme == "" || ereq.URLStruct.Scheme == req.URL.Scheme, nil
}
// MatchHost matches the HTTP host header field of the given request.
func MatchHost(req *http.Request, ereq *Request) (bool, error) {
url := ereq.URLStruct
if url.Host == req.URL.Host {
return true, nil
}
return regexp.MatchString(url.Host, req.URL.Host)
}
// MatchPath matches the HTTP URL path of the given request.
func MatchPath(req *http.Request, ereq *Request) (bool, error) {
return regexp.MatchString(ereq.URLStruct.Path, req.URL.Path)
}
// MatchHeaders matches the headers fields of the given request.
func MatchHeaders(req *http.Request, ereq *Request) (bool, error) {
for key, value := range ereq.Header {
var err error
var match bool
for _, field := range req.Header[key] {
match, err = regexp.MatchString(value[0], field)
if err != nil {
return false, err
}
if match {
break
}
}
if !match {
return false, nil
}
}
return true, nil
}
// MatchQueryParams matches the URL query params fields of the given request.
func MatchQueryParams(req *http.Request, ereq *Request) (bool, error) {
for key, value := range ereq.URLStruct.Query() {
var err error
var match bool
for _, field := range req.URL.Query()[key] {
match, err = regexp.MatchString(value[0], field)
if err != nil {
return false, err
}
if match {
break
}
}
if !match {
return false, nil
}
}
return true, nil
}
// MatchBody tries to match the request body.
// TODO: not too smart now, needs several improvements.
func MatchBody(req *http.Request, ereq *Request) (bool, error) {
// If match body is empty, just continue
if req.Method == "HEAD" || len(ereq.BodyBuffer) == 0 {
return true, nil
}
// Only can match certain MIME body types
if !supportedType(req) {
return false, nil
}
// Can only match certain compression schemes
if !supportedCompressionScheme(req) {
return false, nil
}
// Create a reader for the body depending on compression type
bodyReader := req.Body
if ereq.CompressionScheme != "" {
if ereq.CompressionScheme != req.Header.Get("Content-Encoding") {
return false, nil
}
compressedBodyReader, err := compressionReader(req.Body, ereq.CompressionScheme)
if err != nil {
return false, err
}
bodyReader = compressedBodyReader
}
// Read the whole request body
body, err := ioutil.ReadAll(bodyReader)
if err != nil {
return false, err
}
// Restore body reader stream
req.Body = createReadCloser(body)
// If empty, ignore the match
if len(body) == 0 && len(ereq.BodyBuffer) != 0 {
return false, nil
}
// Match body by atomic string comparison
bodyStr := castToString(body)
matchStr := castToString(ereq.BodyBuffer)
if bodyStr == matchStr {
return true, nil
}
// Match request body by regexp
match, _ := regexp.MatchString(matchStr, bodyStr)
if match == true {
return true, nil
}
// todo - add conditional do only perform the conversion of body bytes
// representation of JSON to a map and then compare them for equality.
// Check if the key + value pairs match
var bodyMap map[string]interface{}
var matchMap map[string]interface{}
// Ensure that both byte bodies that that should be JSON can be converted to maps.
umErr := json.Unmarshal(body, &bodyMap)
umErr2 := json.Unmarshal(ereq.BodyBuffer, &matchMap)
if umErr == nil && umErr2 == nil && reflect.DeepEqual(bodyMap, matchMap) {
return true, nil
}
return false, nil
}
func supportedType(req *http.Request) bool {
mime := req.Header.Get("Content-Type")
if mime == "" {
return true
}
for _, kind := range BodyTypes {
if match, _ := regexp.MatchString(kind, mime); match {
return true
}
}
return false
}
func supportedCompressionScheme(req *http.Request) bool {
encoding := req.Header.Get("Content-Encoding")
if encoding == "" {
return true
}
for _, kind := range CompressionSchemes {
if match, _ := regexp.MatchString(kind, encoding); match {
return true
}
}
return false
}
func castToString(buf []byte) string {
str := string(buf)
tail := len(str) - 1
if str[tail] == EOL {
str = str[:tail]
}
return str
}
func compressionReader(r io.ReadCloser, scheme string) (io.ReadCloser, error) {
switch scheme {
case "gzip":
return gzip.NewReader(r)
default:
return r, nil
}
}