This commit is contained in:
Darren Shepherd
2020-01-30 22:37:59 -07:00
parent 19c6732de0
commit 8b42d0aff8
71 changed files with 4024 additions and 507 deletions

View File

@@ -0,0 +1,18 @@
package parse
import (
"net/http"
"strings"
)
func IsBrowser(req *http.Request, checkAccepts bool) bool {
accepts := strings.ToLower(req.Header.Get("Accept"))
userAgent := strings.ToLower(req.Header.Get("User-Agent"))
if accepts == "" || !checkAccepts {
accepts = "*/*"
}
// User agent has Mozilla and browser accepts */*
return strings.Contains(userAgent, "mozilla") && strings.Contains(accepts, "*/*")
}

View File

@@ -0,0 +1,23 @@
package parse
import (
"net/http"
"github.com/gorilla/mux"
"github.com/rancher/steve/pkg/schemaserver/types"
)
func MuxURLParser(rw http.ResponseWriter, req *http.Request, schemas *types.APISchemas) (ParsedURL, error) {
vars := mux.Vars(req)
url := ParsedURL{
Type: vars["type"],
Name: vars["name"],
Link: vars["link"],
Prefix: vars["prefix"],
Method: req.Method,
Action: vars["action"],
Query: req.URL.Query(),
}
return url, nil
}

View File

@@ -0,0 +1,168 @@
package parse
import (
"net/http"
"net/url"
"strings"
"github.com/rancher/steve/pkg/schemaserver/types"
"github.com/rancher/steve/pkg/schemaserver/urlbuilder"
)
const (
maxFormSize = 2 * 1 << 20
)
var (
allowedFormats = map[string]bool{
"html": true,
"json": true,
"yaml": true,
}
)
type ParsedURL struct {
Type string
Name string
Link string
Method string
Action string
Prefix string
SubContext map[string]string
Query url.Values
}
type URLParser func(rw http.ResponseWriter, req *http.Request, schemas *types.APISchemas) (ParsedURL, error)
type Parser func(apiOp *types.APIRequest, urlParser URLParser) error
func Parse(apiOp *types.APIRequest, urlParser URLParser) error {
var err error
if apiOp.Request == nil {
apiOp.Request, err = http.NewRequest("GET", "/", nil)
if err != nil {
return err
}
}
apiOp = types.StoreAPIContext(apiOp)
if apiOp.Method == "" {
apiOp.Method = parseMethod(apiOp.Request)
}
if apiOp.ResponseFormat == "" {
apiOp.ResponseFormat = parseResponseFormat(apiOp.Request)
}
// The response format is guaranteed to be set even in the event of an error
parsedURL, err := urlParser(apiOp.Response, apiOp.Request, apiOp.Schemas)
// wait to check error, want to set as much as possible
if apiOp.Type == "" {
apiOp.Type = parsedURL.Type
}
if apiOp.Name == "" {
apiOp.Name = parsedURL.Name
}
if apiOp.Link == "" {
apiOp.Link = parsedURL.Link
}
if apiOp.Action == "" {
apiOp.Action = parsedURL.Action
}
if apiOp.Query == nil {
apiOp.Query = parsedURL.Query
}
if apiOp.Method == "" && parsedURL.Method != "" {
apiOp.Method = parsedURL.Method
}
if apiOp.URLPrefix == "" {
apiOp.URLPrefix = parsedURL.Prefix
}
if apiOp.URLBuilder == nil {
// make error local to not override the outer error we have yet to check
var err error
apiOp.URLBuilder, err = urlbuilder.New(apiOp.Request, &urlbuilder.DefaultPathResolver{
Prefix: apiOp.URLPrefix,
}, apiOp.Schemas)
if err != nil {
return err
}
}
if err != nil {
return err
}
if apiOp.Schema == nil && apiOp.Schemas != nil {
apiOp.Schema = apiOp.Schemas.LookupSchema(apiOp.Type)
}
if apiOp.Schema != nil && apiOp.Type == "" {
apiOp.Type = apiOp.Schema.ID
}
if err := ValidateMethod(apiOp); err != nil {
return err
}
return nil
}
func parseResponseFormat(req *http.Request) string {
format := req.URL.Query().Get("_format")
if format != "" {
format = strings.TrimSpace(strings.ToLower(format))
}
/* Format specified */
if allowedFormats[format] {
return format
}
// User agent has Mozilla and browser accepts */*
if IsBrowser(req, true) {
return "html"
}
if isYaml(req) {
return "yaml"
}
return "json"
}
func isYaml(req *http.Request) bool {
return strings.Contains(req.Header.Get("Accept"), "application/yaml")
}
func parseMethod(req *http.Request) string {
method := req.URL.Query().Get("_method")
if method == "" {
method = req.Method
}
return method
}
func Body(req *http.Request) (types.APIObject, error) {
req.ParseMultipartForm(maxFormSize)
if req.MultipartForm != nil {
return valuesToBody(req.MultipartForm.Value), nil
}
if req.PostForm != nil && len(req.PostForm) > 0 {
return valuesToBody(map[string][]string(req.Form)), nil
}
return ReadBody(req)
}
func valuesToBody(input map[string][]string) types.APIObject {
result := map[string]interface{}{}
for k, v := range input {
result[k] = v
}
return toAPI(result)
}

View File

@@ -0,0 +1,56 @@
package parse
import (
"encoding/json"
"fmt"
"io"
"net/http"
"github.com/rancher/steve/pkg/schemaserver/httperror"
"github.com/rancher/steve/pkg/schemaserver/types"
"github.com/rancher/wrangler/pkg/data/convert"
"github.com/rancher/wrangler/pkg/schemas/validation"
"k8s.io/apimachinery/pkg/util/yaml"
)
const reqMaxSize = (2 * 1 << 20) + 1
var bodyMethods = map[string]bool{
http.MethodPut: true,
http.MethodPost: true,
}
type Decode func(interface{}) error
func ReadBody(req *http.Request) (types.APIObject, error) {
if !bodyMethods[req.Method] {
return types.APIObject{}, nil
}
decode := getDecoder(req, io.LimitReader(req.Body, maxFormSize))
data := map[string]interface{}{}
if err := decode(&data); err != nil {
return types.APIObject{}, httperror.NewAPIError(validation.InvalidBodyContent,
fmt.Sprintf("Failed to parse body: %v", err))
}
return toAPI(data), nil
}
func toAPI(data map[string]interface{}) types.APIObject {
return types.APIObject{
Type: convert.ToString(data["type"]),
ID: convert.ToString(data["id"]),
Object: data,
}
}
func getDecoder(req *http.Request, reader io.Reader) Decode {
if req.Header.Get("Content-type") == "application/yaml" {
return yaml.NewYAMLToJSONDecoder(reader).Decode
}
decoder := json.NewDecoder(reader)
decoder.UseNumber()
return decoder.Decode
}

View File

@@ -0,0 +1,47 @@
package parse
import (
"fmt"
"net/http"
"github.com/rancher/steve/pkg/schemaserver/httperror"
"github.com/rancher/steve/pkg/schemaserver/types"
"github.com/rancher/wrangler/pkg/schemas/validation"
)
var (
supportedMethods = map[string]bool{
http.MethodPost: true,
http.MethodGet: true,
http.MethodPut: true,
http.MethodPatch: true,
http.MethodDelete: true,
}
)
func ValidateMethod(request *types.APIRequest) error {
if request.Action != "" && request.Method == http.MethodPost {
return nil
}
if !supportedMethods[request.Method] {
return httperror.NewAPIError(validation.MethodNotAllowed, fmt.Sprintf("Invalid method %s not supported", request.Method))
}
if request.Type == "" || request.Schema == nil || request.Link != "" {
return nil
}
allowed := request.Schema.ResourceMethods
if request.Name == "" {
allowed = request.Schema.CollectionMethods
}
for _, method := range allowed {
if method == request.Method {
return nil
}
}
return httperror.NewAPIError(validation.MethodNotAllowed, fmt.Sprintf("Method %s not supported", request.Method))
}