You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
232 lines
5.4 KiB
232 lines
5.4 KiB
package gock |
|
|
|
import ( |
|
"compress/gzip" |
|
"encoding/json" |
|
"io" |
|
"io/ioutil" |
|
"net/http" |
|
"reflect" |
|
"regexp" |
|
"strings" |
|
) |
|
|
|
// 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 strings.EqualFold(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 |
|
} |
|
}
|
|
|