mirror of
https://github.com/ceph/ceph-csi.git
synced 2025-04-11 18:13:00 +00:00
Several packages are only used while running the e2e suite. These packages are less important to update, as the they can not influence the final executable that is part of the Ceph-CSI container-image. By moving these dependencies out of the main Ceph-CSI go.mod, it is easier to identify if a reported CVE affects Ceph-CSI, or only the testing (like most of the Kubernetes CVEs). Signed-off-by: Niels de Vos <ndevos@ibm.com>
327 lines
10 KiB
Go
327 lines
10 KiB
Go
package restful
|
||
|
||
// Copyright 2013 Ernest Micklei. All rights reserved.
|
||
// Use of this source code is governed by a license
|
||
// that can be found in the LICENSE file.
|
||
|
||
import (
|
||
"errors"
|
||
"fmt"
|
||
"net/http"
|
||
"sort"
|
||
"strings"
|
||
)
|
||
|
||
// RouterJSR311 implements the flow for matching Requests to Routes (and consequently Resource Functions)
|
||
// as specified by the JSR311 http://jsr311.java.net/nonav/releases/1.1/spec/spec.html.
|
||
// RouterJSR311 implements the Router interface.
|
||
// Concept of locators is not implemented.
|
||
type RouterJSR311 struct{}
|
||
|
||
// SelectRoute is part of the Router interface and returns the best match
|
||
// for the WebService and its Route for the given Request.
|
||
func (r RouterJSR311) SelectRoute(
|
||
webServices []*WebService,
|
||
httpRequest *http.Request) (selectedService *WebService, selectedRoute *Route, err error) {
|
||
|
||
// Identify the root resource class (WebService)
|
||
dispatcher, finalMatch, err := r.detectDispatcher(httpRequest.URL.Path, webServices)
|
||
if err != nil {
|
||
return nil, nil, NewError(http.StatusNotFound, "")
|
||
}
|
||
// Obtain the set of candidate methods (Routes)
|
||
routes := r.selectRoutes(dispatcher, finalMatch)
|
||
if len(routes) == 0 {
|
||
return dispatcher, nil, NewError(http.StatusNotFound, "404: Page Not Found")
|
||
}
|
||
|
||
// Identify the method (Route) that will handle the request
|
||
route, ok := r.detectRoute(routes, httpRequest)
|
||
return dispatcher, route, ok
|
||
}
|
||
|
||
// ExtractParameters is used to obtain the path parameters from the route using the same matching
|
||
// engine as the JSR 311 router.
|
||
func (r RouterJSR311) ExtractParameters(route *Route, webService *WebService, urlPath string) map[string]string {
|
||
webServiceExpr := webService.pathExpr
|
||
webServiceMatches := webServiceExpr.Matcher.FindStringSubmatch(urlPath)
|
||
pathParameters := r.extractParams(webServiceExpr, webServiceMatches)
|
||
routeExpr := route.pathExpr
|
||
routeMatches := routeExpr.Matcher.FindStringSubmatch(webServiceMatches[len(webServiceMatches)-1])
|
||
routeParams := r.extractParams(routeExpr, routeMatches)
|
||
for key, value := range routeParams {
|
||
pathParameters[key] = value
|
||
}
|
||
return pathParameters
|
||
}
|
||
|
||
func (RouterJSR311) extractParams(pathExpr *pathExpression, matches []string) map[string]string {
|
||
params := map[string]string{}
|
||
for i := 1; i < len(matches); i++ {
|
||
if len(pathExpr.VarNames) >= i {
|
||
params[pathExpr.VarNames[i-1]] = matches[i]
|
||
}
|
||
}
|
||
return params
|
||
}
|
||
|
||
// http://jsr311.java.net/nonav/releases/1.1/spec/spec3.html#x3-360003.7.2
|
||
func (r RouterJSR311) detectRoute(routes []Route, httpRequest *http.Request) (*Route, error) {
|
||
candidates := make([]*Route, 0, 8)
|
||
for i, each := range routes {
|
||
ok := true
|
||
for _, fn := range each.If {
|
||
if !fn(httpRequest) {
|
||
ok = false
|
||
break
|
||
}
|
||
}
|
||
if ok {
|
||
candidates = append(candidates, &routes[i])
|
||
}
|
||
}
|
||
if len(candidates) == 0 {
|
||
if trace {
|
||
traceLogger.Printf("no Route found (from %d) that passes conditional checks", len(routes))
|
||
}
|
||
return nil, NewError(http.StatusNotFound, "404: Not Found")
|
||
}
|
||
|
||
// http method
|
||
previous := candidates
|
||
candidates = candidates[:0]
|
||
for _, each := range previous {
|
||
if httpRequest.Method == each.Method {
|
||
candidates = append(candidates, each)
|
||
}
|
||
}
|
||
if len(candidates) == 0 {
|
||
if trace {
|
||
traceLogger.Printf("no Route found (in %d routes) that matches HTTP method %s\n", len(previous), httpRequest.Method)
|
||
}
|
||
allowed := []string{}
|
||
allowedLoop:
|
||
for _, candidate := range previous {
|
||
for _, method := range allowed {
|
||
if method == candidate.Method {
|
||
continue allowedLoop
|
||
}
|
||
}
|
||
allowed = append(allowed, candidate.Method)
|
||
}
|
||
header := http.Header{"Allow": []string{strings.Join(allowed, ", ")}}
|
||
return nil, NewErrorWithHeader(http.StatusMethodNotAllowed, "405: Method Not Allowed", header)
|
||
}
|
||
|
||
// content-type
|
||
contentType := httpRequest.Header.Get(HEADER_ContentType)
|
||
previous = candidates
|
||
candidates = candidates[:0]
|
||
for _, each := range previous {
|
||
if each.matchesContentType(contentType) {
|
||
candidates = append(candidates, each)
|
||
}
|
||
}
|
||
if len(candidates) == 0 {
|
||
if trace {
|
||
traceLogger.Printf("no Route found (from %d) that matches HTTP Content-Type: %s\n", len(previous), contentType)
|
||
}
|
||
if httpRequest.ContentLength > 0 {
|
||
return nil, NewError(http.StatusUnsupportedMediaType, "415: Unsupported Media Type")
|
||
}
|
||
}
|
||
|
||
// accept
|
||
previous = candidates
|
||
candidates = candidates[:0]
|
||
accept := httpRequest.Header.Get(HEADER_Accept)
|
||
if len(accept) == 0 {
|
||
accept = "*/*"
|
||
}
|
||
for _, each := range previous {
|
||
if each.matchesAccept(accept) {
|
||
candidates = append(candidates, each)
|
||
}
|
||
}
|
||
if len(candidates) == 0 {
|
||
if trace {
|
||
traceLogger.Printf("no Route found (from %d) that matches HTTP Accept: %s\n", len(previous), accept)
|
||
}
|
||
available := []string{}
|
||
for _, candidate := range previous {
|
||
available = append(available, candidate.Produces...)
|
||
}
|
||
// if POST,PUT,PATCH without body
|
||
method, length := httpRequest.Method, httpRequest.Header.Get("Content-Length")
|
||
if (method == http.MethodPost ||
|
||
method == http.MethodPut ||
|
||
method == http.MethodPatch) && (length == "" || length == "0") {
|
||
return nil, NewError(
|
||
http.StatusUnsupportedMediaType,
|
||
fmt.Sprintf("415: Unsupported Media Type\n\nAvailable representations: %s", strings.Join(available, ", ")),
|
||
)
|
||
}
|
||
return nil, NewError(
|
||
http.StatusNotAcceptable,
|
||
fmt.Sprintf("406: Not Acceptable\n\nAvailable representations: %s", strings.Join(available, ", ")),
|
||
)
|
||
}
|
||
// return r.bestMatchByMedia(outputMediaOk, contentType, accept), nil
|
||
return candidates[0], nil
|
||
}
|
||
|
||
// http://jsr311.java.net/nonav/releases/1.1/spec/spec3.html#x3-360003.7.2
|
||
// n/m > n/* > */*
|
||
func (r RouterJSR311) bestMatchByMedia(routes []Route, contentType string, accept string) *Route {
|
||
// TODO
|
||
return &routes[0]
|
||
}
|
||
|
||
// http://jsr311.java.net/nonav/releases/1.1/spec/spec3.html#x3-360003.7.2 (step 2)
|
||
func (r RouterJSR311) selectRoutes(dispatcher *WebService, pathRemainder string) []Route {
|
||
filtered := &sortableRouteCandidates{}
|
||
for _, each := range dispatcher.Routes() {
|
||
pathExpr := each.pathExpr
|
||
matches := pathExpr.Matcher.FindStringSubmatch(pathRemainder)
|
||
if matches != nil {
|
||
lastMatch := matches[len(matches)-1]
|
||
if len(lastMatch) == 0 || lastMatch == "/" { // do not include if value is neither empty nor ‘/’.
|
||
filtered.candidates = append(filtered.candidates,
|
||
routeCandidate{each, len(matches) - 1, pathExpr.LiteralCount, pathExpr.VarCount})
|
||
}
|
||
}
|
||
}
|
||
if len(filtered.candidates) == 0 {
|
||
if trace {
|
||
traceLogger.Printf("WebService on path %s has no routes that match URL path remainder:%s\n", dispatcher.rootPath, pathRemainder)
|
||
}
|
||
return []Route{}
|
||
}
|
||
sort.Sort(sort.Reverse(filtered))
|
||
|
||
// select other routes from candidates whoes expression matches rmatch
|
||
matchingRoutes := []Route{filtered.candidates[0].route}
|
||
for c := 1; c < len(filtered.candidates); c++ {
|
||
each := filtered.candidates[c]
|
||
if each.route.pathExpr.Matcher.MatchString(pathRemainder) {
|
||
matchingRoutes = append(matchingRoutes, each.route)
|
||
}
|
||
}
|
||
return matchingRoutes
|
||
}
|
||
|
||
// http://jsr311.java.net/nonav/releases/1.1/spec/spec3.html#x3-360003.7.2 (step 1)
|
||
func (r RouterJSR311) detectDispatcher(requestPath string, dispatchers []*WebService) (*WebService, string, error) {
|
||
filtered := &sortableDispatcherCandidates{}
|
||
for _, each := range dispatchers {
|
||
matches := each.pathExpr.Matcher.FindStringSubmatch(requestPath)
|
||
if matches != nil {
|
||
filtered.candidates = append(filtered.candidates,
|
||
dispatcherCandidate{each, matches[len(matches)-1], len(matches), each.pathExpr.LiteralCount, each.pathExpr.VarCount})
|
||
}
|
||
}
|
||
if len(filtered.candidates) == 0 {
|
||
if trace {
|
||
traceLogger.Printf("no WebService was found to match URL path:%s\n", requestPath)
|
||
}
|
||
return nil, "", errors.New("not found")
|
||
}
|
||
sort.Sort(sort.Reverse(filtered))
|
||
return filtered.candidates[0].dispatcher, filtered.candidates[0].finalMatch, nil
|
||
}
|
||
|
||
// Types and functions to support the sorting of Routes
|
||
|
||
type routeCandidate struct {
|
||
route Route
|
||
matchesCount int // the number of capturing groups
|
||
literalCount int // the number of literal characters (means those not resulting from template variable substitution)
|
||
nonDefaultCount int // the number of capturing groups with non-default regular expressions (i.e. not ‘([^ /]+?)’)
|
||
}
|
||
|
||
func (r routeCandidate) expressionToMatch() string {
|
||
return r.route.pathExpr.Source
|
||
}
|
||
|
||
func (r routeCandidate) String() string {
|
||
return fmt.Sprintf("(m=%d,l=%d,n=%d)", r.matchesCount, r.literalCount, r.nonDefaultCount)
|
||
}
|
||
|
||
type sortableRouteCandidates struct {
|
||
candidates []routeCandidate
|
||
}
|
||
|
||
func (rcs *sortableRouteCandidates) Len() int {
|
||
return len(rcs.candidates)
|
||
}
|
||
func (rcs *sortableRouteCandidates) Swap(i, j int) {
|
||
rcs.candidates[i], rcs.candidates[j] = rcs.candidates[j], rcs.candidates[i]
|
||
}
|
||
func (rcs *sortableRouteCandidates) Less(i, j int) bool {
|
||
ci := rcs.candidates[i]
|
||
cj := rcs.candidates[j]
|
||
// primary key
|
||
if ci.literalCount < cj.literalCount {
|
||
return true
|
||
}
|
||
if ci.literalCount > cj.literalCount {
|
||
return false
|
||
}
|
||
// secundary key
|
||
if ci.matchesCount < cj.matchesCount {
|
||
return true
|
||
}
|
||
if ci.matchesCount > cj.matchesCount {
|
||
return false
|
||
}
|
||
// tertiary key
|
||
if ci.nonDefaultCount < cj.nonDefaultCount {
|
||
return true
|
||
}
|
||
if ci.nonDefaultCount > cj.nonDefaultCount {
|
||
return false
|
||
}
|
||
// quaternary key ("source" is interpreted as Path)
|
||
return ci.route.Path < cj.route.Path
|
||
}
|
||
|
||
// Types and functions to support the sorting of Dispatchers
|
||
|
||
type dispatcherCandidate struct {
|
||
dispatcher *WebService
|
||
finalMatch string
|
||
matchesCount int // the number of capturing groups
|
||
literalCount int // the number of literal characters (means those not resulting from template variable substitution)
|
||
nonDefaultCount int // the number of capturing groups with non-default regular expressions (i.e. not ‘([^ /]+?)’)
|
||
}
|
||
type sortableDispatcherCandidates struct {
|
||
candidates []dispatcherCandidate
|
||
}
|
||
|
||
func (dc *sortableDispatcherCandidates) Len() int {
|
||
return len(dc.candidates)
|
||
}
|
||
func (dc *sortableDispatcherCandidates) Swap(i, j int) {
|
||
dc.candidates[i], dc.candidates[j] = dc.candidates[j], dc.candidates[i]
|
||
}
|
||
func (dc *sortableDispatcherCandidates) Less(i, j int) bool {
|
||
ci := dc.candidates[i]
|
||
cj := dc.candidates[j]
|
||
// primary key
|
||
if ci.matchesCount < cj.matchesCount {
|
||
return true
|
||
}
|
||
if ci.matchesCount > cj.matchesCount {
|
||
return false
|
||
}
|
||
// secundary key
|
||
if ci.literalCount < cj.literalCount {
|
||
return true
|
||
}
|
||
if ci.literalCount > cj.literalCount {
|
||
return false
|
||
}
|
||
// tertiary key
|
||
return ci.nonDefaultCount < cj.nonDefaultCount
|
||
}
|