Skip to content

Commit

Permalink
Fix error response template escapes
Browse files Browse the repository at this point in the history
  • Loading branch information
PatrickTaibel committed Aug 7, 2023
1 parent 1b91fc2 commit 9a70e57
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 26 deletions.
49 changes: 27 additions & 22 deletions gateway/handler_error.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package gateway
import (
"bytes"
"encoding/base64"
"encoding/json"
"encoding/xml"
"errors"
"html/template"
"io"
Expand Down Expand Up @@ -151,29 +153,27 @@ func (e *ErrorHandler) HandleError(w http.ResponseWriter, r *http.Request, errMs
contentType = header.ApplicationJSON
}

w.Header().Set(header.ContentType, contentType)
response.Header = http.Header{}
response.Header.Set(header.ContentType, contentType)
templateName := "error_" + strconv.Itoa(errCode) + "." + templateExtension

// Try to use an error template that matches the HTTP error code and the content type: 500.json, 400.xml, etc.
tmpl := e.Gw.templates.Lookup(templateName)
templateName := "error_" + strconv.Itoa(errCode) + "." + templateExtension
tmpl := e.Gw.templatesRaw.Lookup(templateName)

// Fallback to a generic error template, but match the content type: error.json, error.xml, etc.
if tmpl == nil {
templateName = defaultTemplateName + "." + templateExtension
tmpl = e.Gw.templates.Lookup(templateName)
tmpl = e.Gw.templatesRaw.Lookup(templateName)
}

// If no template is available for this content type, fallback to "error.json".
if tmpl == nil {
templateName = defaultTemplateName + "." + defaultTemplateFormat
tmpl = e.Gw.templates.Lookup(templateName)
w.Header().Set(header.ContentType, defaultContentType)
response.Header.Set(header.ContentType, defaultContentType)

tmpl = e.Gw.templatesRaw.Lookup(templateName)
contentType = defaultContentType
}

w.Header().Set(header.ContentType, contentType)
response.Header = http.Header{}
response.Header.Set(header.ContentType, contentType)

//If the config option is not set or is false, add the header
if !e.Spec.GlobalConfig.HideGeneratorHeader {
w.Header().Add(header.XGenerator, "tyk.io")
Expand All @@ -184,30 +184,35 @@ func (e *ErrorHandler) HandleError(w http.ResponseWriter, r *http.Request, errMs
if e.Spec.GlobalConfig.CloseConnections {
w.Header().Add(header.Connection, "close")
response.Header.Add(header.Connection, "close")

}

// If error is not customized write error in default way
if errMsg != errCustomBodyResponse.Error() {
w.WriteHeader(errCode)
response.StatusCode = errCode
var tmplExecutor TemplateExecutor
tmplExecutor = tmpl

apiError := APIError{template.HTML(template.JSEscapeString(errMsg))}

apiError := APIError{}
if contentType == header.ApplicationXML || contentType == header.TextXML {
apiError.Message = template.HTML(errMsg)

//we look up in the last defined templateName to obtain the template.
rawTmpl := e.Gw.templatesRaw.Lookup(templateName)
tmplExecutor = rawTmpl
escapedBuffer := &bytes.Buffer{}
err := xml.EscapeText(escapedBuffer, []byte(errMsg))
if err != nil {
log.WithError(err).Error("could not escape error message for XML")
}
apiError.Message = template.HTML(escapedBuffer.String())
} else {
escaped, err := json.Marshal(errMsg)
if err != nil {
log.WithError(err).Error("could not escape error message for JSON")
} else if escapedLen := len(escaped); escapedLen >= 2 {
escaped = escaped[1 : escapedLen-1]
apiError.Message = template.HTML(escaped)
}
}

var log bytes.Buffer

rsp := io.MultiWriter(w, &log)
tmplExecutor.Execute(rsp, &apiError)
tmpl.Execute(rsp, &apiError)
response.Body = ioutil.NopCloser(&log)
}
}
Expand Down
14 changes: 10 additions & 4 deletions gateway/mw_validate_json_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,13 @@ var testJsonSchema = `{
"type": "integer",
"minimum": 0
},
"objs":{
"enum":["a","b","c"],
"type":"string"
"objs": {
"enum": ["a", "b", "c"],
"type": "string"
},
"regex": {
"type": "string",
"pattern": "^[a-z]+[0-9]+$"
}
},
"required": ["firstName", "lastName"]
Expand Down Expand Up @@ -61,9 +65,11 @@ func TestValidateJSONSchema(t *testing.T) {
{Method: http.MethodPost, Path: "/without_validation", Data: "{not_valid}", Code: http.StatusOK},
{Method: http.MethodPost, Path: "/v", Data: `{"age":23}`, BodyMatch: `firstName: firstName is required; lastName: lastName is required`, Code: http.StatusUnprocessableEntity},
{Method: http.MethodPost, Path: "/v", Data: `[]`, BodyMatch: `Expected: object, given: array`, Code: http.StatusUnprocessableEntity},
{Method: http.MethodPost, Path: "/v", Data: `not_json`, Code: http.StatusBadRequest},
{Method: http.MethodPost, Path: "/v", Data: `not_json`, Code: http.StatusBadRequest, BodyMatch: `JSON parsing error: invalid character 'o' in literal null \(expecting 'u'\)`},
{Method: http.MethodPost, Path: "/v", Data: `{"age":23, "firstName": "Harry", "lastName": "Potter"}`, Code: http.StatusOK},
{Method: http.MethodPost, Path: "/v", Data: `{"age":23, "firstName": "Harry", "lastName": "Potter", "objs": "d"}`, Code: http.StatusUnprocessableEntity, BodyMatch: `objs: objs must be one of the following: \\"a\\", \\"b\\", \\"c\\"`},
{Method: http.MethodPost, Path: "/v", Data: `{"age":23, "firstName": "dummy", "lastName": "dummy", "regex": "d1"}`, Code: http.StatusOK},
{Method: http.MethodPost, Path: "/v", Data: `{"age":23, "firstName": "dummy", "lastName": "dummy", "regex": "D1"}`, Code: http.StatusUnprocessableEntity, BodyMatch: `regex: Does not match pattern '\^\[a-z\]\+\[0-9\]\+\$'`},
}...)

t.Run("disabled", func(t *testing.T) {
Expand Down

0 comments on commit 9a70e57

Please sign in to comment.