github.com/kumasuke120/mockuma@v1.1.9/internal/server/matcher.go (about) 1 package server 2 3 import ( 4 "bytes" 5 "io/ioutil" 6 "log" 7 "net/http" 8 "regexp" 9 10 "github.com/kumasuke120/mockuma/internal/mckmaps" 11 "github.com/kumasuke120/mockuma/internal/myhttp" 12 "github.com/kumasuke120/mockuma/internal/myjson" 13 ) 14 15 type pathMatcher struct { 16 mappings *mckmaps.MockuMappings 17 directPath map[string][]*mckmaps.Mapping 18 patternPath map[*regexp.Regexp][]*mckmaps.Mapping 19 } 20 21 var pathVarRegexp = regexp.MustCompile(`{(\d+)}`) 22 23 func newPathMatcher(mappings *mckmaps.MockuMappings) *pathMatcher { 24 directPath := make(map[string][]*mckmaps.Mapping) 25 patternPath := make(map[*regexp.Regexp][]*mckmaps.Mapping) 26 for _, m := range mappings.Mappings { 27 if theURI := pathVarRegexp.ReplaceAllString(m.URI, "(?P<v$1>.*?)"); theURI == m.URI { 28 mappingsOfURI := directPath[theURI] 29 mappingsOfURI = append(mappingsOfURI, m) 30 directPath[theURI] = mappingsOfURI 31 } else { 32 regexpURI := regexp.MustCompile("^" + theURI + "$") 33 mappingsOfURI := patternPath[regexpURI] 34 mappingsOfURI = append(mappingsOfURI, m) 35 patternPath[regexpURI] = mappingsOfURI 36 } 37 } 38 39 return &pathMatcher{ 40 mappings: mappings, 41 directPath: directPath, 42 patternPath: patternPath, 43 } 44 } 45 46 func (m *pathMatcher) bind(r *http.Request) *boundMatcher { 47 return &boundMatcher{m: m, r: r, conf: m.mappings.Config} 48 } 49 50 type boundMatcher struct { 51 m *pathMatcher 52 conf *mckmaps.Config 53 r *http.Request 54 method myhttp.HTTPMethod 55 uri string 56 uriPattern *regexp.Regexp 57 58 matchedMapping *mckmaps.Mapping 59 matchState matchState 60 bodyCache []byte 61 } 62 63 type matchState int 64 65 const ( 66 matchExact = iota 67 matchURI 68 matchHead 69 matchCORSOptions 70 matchNone 71 ) 72 73 func (bm *boundMatcher) matches() bool { 74 bm.uri = bm.r.URL.Path 75 bm.method = myhttp.ToHTTPMethod(bm.r.Method) 76 77 var possibleMappings []*mckmaps.Mapping 78 possibleMappings = bm.matchURIDirect() 79 80 var possibleURIPattern *regexp.Regexp 81 if len(possibleMappings) == 0 { 82 possibleMappings, possibleURIPattern = bm.matchURIPattern() 83 } 84 85 if len(possibleMappings) != 0 { // if finds any mapping 86 if matched := bm.matchByMethod(possibleMappings); matched != nil { 87 bm.matchedMapping = matched 88 bm.uriPattern = possibleURIPattern 89 bm.matchState = matchExact 90 } else if matched := bm.matchHead(possibleMappings); matched != nil { 91 bm.matchedMapping = matched 92 bm.matchState = matchHead 93 } else if bm.matchCORSOptions() { 94 bm.matchedMapping = nil 95 bm.matchState = matchCORSOptions 96 } else { 97 bm.matchedMapping = nil 98 bm.matchState = matchURI 99 } 100 } else { 101 bm.matchedMapping = nil 102 bm.matchState = matchNone 103 } 104 105 return bm.matchState != matchNone 106 } 107 108 func (bm *boundMatcher) matchURIDirect() (pm []*mckmaps.Mapping) { 109 if mappingsOfURI, ok := bm.m.directPath[bm.uri]; ok { // matching for direct path 110 pm = mappingsOfURI 111 } else if bm.conf.MatchTrailingSlash { // matches /path to /path/ 112 if mappingsOfURI, ok := bm.m.directPath[bm.uri+"/"]; ok { 113 bm.uri += "/" 114 pm = mappingsOfURI 115 } 116 } 117 return 118 } 119 120 func (bm *boundMatcher) matchURIPattern() (pm []*mckmaps.Mapping, pp *regexp.Regexp) { 121 for pattern, mappingsOfURI := range bm.m.patternPath { // matching for pattern path 122 if pattern.MatchString(bm.uri) { 123 pm = mappingsOfURI 124 pp = pattern 125 } else if bm.conf.MatchTrailingSlash && pattern.MatchString(bm.uri+"/") { 126 bm.uri += "/" 127 pm = mappingsOfURI 128 pp = pattern 129 } 130 } 131 return 132 } 133 134 func (bm *boundMatcher) headMatches() bool { 135 return bm.matchState == matchHead 136 } 137 138 func (bm *boundMatcher) matchByMethod(mappings []*mckmaps.Mapping) *mckmaps.Mapping { 139 return matchByMethod(mappings, bm.method) 140 } 141 142 func (bm *boundMatcher) matchHead(mappings []*mckmaps.Mapping) *mckmaps.Mapping { 143 if bm.method != myhttp.MethodHead { 144 return nil 145 } 146 147 return matchByMethod(mappings, myhttp.MethodGet) 148 } 149 150 func (bm *boundMatcher) matchCORSOptions() bool { 151 return bm.conf.CORS.Enabled && bm.method == myhttp.MethodOptions 152 } 153 154 func matchByMethod(mappings []*mckmaps.Mapping, method myhttp.HTTPMethod) *mckmaps.Mapping { 155 for _, m := range mappings { 156 if m.Method.Matches(method) { 157 return m 158 } 159 } 160 return nil 161 } 162 163 func (bm *boundMatcher) matchPolicy() *mckmaps.Policy { 164 switch bm.matchState { 165 case matchHead: 166 fallthrough 167 case matchExact: 168 p := bm.matchExactPolicy() 169 if p == nil { 170 return pNoPolicyMatched 171 } else { 172 return p 173 } 174 case matchCORSOptions: 175 return pEmptyOK 176 case matchURI: 177 return pMethodNotAllowed 178 } 179 return pNotFound 180 } 181 182 func (bm *boundMatcher) matchExactPolicy() *mckmaps.Policy { 183 bm.cacheBody() 184 185 err := bm.r.ParseForm() 186 if err != nil { 187 log.Println("[server ] fail to parse form:", err) 188 return nil 189 } 190 191 var policy *mckmaps.Policy 192 for _, p := range bm.matchedMapping.Policies { 193 when := p.When 194 195 if when != nil { 196 if bm.uriPattern != nil && !bm.pathVarsMatch(when) { 197 continue 198 } 199 200 if !bm.paramsMatch(when) { 201 continue 202 } 203 204 if !bm.headersMatch(when) { 205 continue 206 } 207 208 if !bm.bodyMatches(when) { 209 continue 210 } 211 } 212 213 policy = p 214 break 215 } 216 217 // resets body for later use in executor 218 bm.resetBodyFromCache() 219 220 return policy 221 } 222 223 func (bm *boundMatcher) cacheBody() { 224 body, err := ioutil.ReadAll(bm.r.Body) 225 if err == nil { 226 bm.bodyCache = body 227 bm.resetBodyFromCache() 228 } 229 } 230 231 func (bm *boundMatcher) resetBodyFromCache() { 232 if bm.bodyCache != nil { 233 bm.r.Body = ioutil.NopCloser(bytes.NewReader(bm.bodyCache)) 234 } 235 } 236 237 func (bm *boundMatcher) pathVarsMatch(when *mckmaps.When) bool { 238 pathVars := bm.extractPathVars() 239 240 if !valuesMatch(when.PathVars, pathVars) { 241 return false 242 } 243 if !regexpsMatch(when.PathVarRegexps, pathVars) { 244 return false 245 } 246 247 return true 248 } 249 250 func (bm *boundMatcher) extractPathVars() map[string][]string { 251 mValues := bm.uriPattern.FindStringSubmatch(bm.uri) 252 if len(mValues) == 0 { 253 panic("Shouldn't happen") 254 } 255 mNames := bm.uriPattern.SubexpNames() 256 pathVars := make(map[string][]string, len(mValues)) 257 for i := 1; i < len(mValues); i++ { 258 pathVars[mNames[i][1:]] = []string{mValues[i]} // [1:] to remove the prefix v 259 } 260 return pathVars 261 } 262 263 func (bm *boundMatcher) paramsMatch(when *mckmaps.When) bool { 264 if !valuesMatch(when.Params, bm.r.Form) { 265 return false 266 } 267 if !regexpsMatch(when.ParamRegexps, bm.r.Form) { 268 return false 269 } 270 if !asJSONsMatch(when.ParamJSONs, bm.r.Form) { 271 return false 272 } 273 274 return true 275 } 276 277 func (bm *boundMatcher) headersMatch(when *mckmaps.When) bool { 278 if !valuesMatch(when.Headers, bm.r.Header) { 279 return false 280 } 281 if !regexpsMatch(when.HeaderRegexps, bm.r.Header) { 282 return false 283 } 284 if !asJSONsMatch(when.HeaderJSONs, bm.r.Header) { 285 return false 286 } 287 288 return true 289 } 290 291 func (bm *boundMatcher) bodyMatches(when *mckmaps.When) bool { 292 body := bm.bodyCache 293 if when.Body != nil { 294 return bytes.Equal(when.Body, body) 295 } else if when.BodyRegexp != nil { 296 return when.BodyRegexp.Match(body) 297 } else if when.BodyJSON != nil { 298 json, err := myjson.Unmarshal(body) 299 if err != nil { 300 return false 301 } 302 return when.BodyJSON.Matches(json) 303 } else { 304 return true 305 } 306 } 307 308 func valuesMatch(expected []*mckmaps.NameValuesPair, actual map[string][]string) bool { 309 for _, e := range expected { 310 formValues := actual[e.Name] 311 312 if !stringSlicesEqualIgnoreOrder(e.Values, formValues) { 313 return false 314 } 315 } 316 317 return true 318 } 319 320 // tests if two []string share same elements, ignoring the order 321 func stringSlicesEqualIgnoreOrder(l, r []string) bool { 322 if len(l) != len(r) { 323 return false 324 } 325 326 diff := make(map[string]int, len(l)) 327 for _, _x := range l { 328 diff[_x]++ 329 } 330 331 for _, _y := range r { 332 if _, ok := diff[_y]; !ok { 333 return false 334 } 335 336 diff[_y] -= 1 337 if diff[_y] == 0 { 338 delete(diff, _y) 339 } 340 } 341 342 return len(diff) == 0 343 } 344 345 func regexpsMatch(expected []*mckmaps.NameRegexpPair, actual map[string][]string) bool { 346 for _, e := range expected { 347 formValues := actual[e.Name] 348 349 if !regexpAnyMatches(e.Regexp, formValues) { 350 return false 351 } 352 } 353 354 return true 355 } 356 357 func regexpAnyMatches(r *regexp.Regexp, values []string) bool { 358 for _, v := range values { 359 if r.Match([]byte(v)) { 360 return true 361 } 362 } 363 return false 364 } 365 366 func asJSONsMatch(expected []*mckmaps.NameJSONPair, actual map[string][]string) bool { 367 for _, e := range expected { 368 formValues := actual[e.Name] 369 370 if !asJSONAnyMatches(e.JSON, formValues) { 371 return false 372 } 373 } 374 375 return true 376 } 377 378 func asJSONAnyMatches(m myjson.ExtJSONMatcher, values []string) bool { 379 for _, v := range values { 380 json, err := myjson.Unmarshal([]byte(v)) 381 if err != nil { 382 continue 383 } 384 385 if m.Matches(json) { 386 return true 387 } 388 } 389 return false 390 }