From 9a70e5700c56f00fd82dc4c3b9b89ccbf9df60f7 Mon Sep 17 00:00:00 2001 From: Patrick Taibel Date: Thu, 3 Aug 2023 15:25:05 +0200 Subject: [PATCH] Fix error response template escapes --- gateway/handler_error.go | 49 ++++++++++++++++++-------------- gateway/mw_validate_json_test.go | 14 ++++++--- 2 files changed, 37 insertions(+), 26 deletions(-) diff --git a/gateway/handler_error.go b/gateway/handler_error.go index 1adeaaa6307..2ef5c6c61a7 100644 --- a/gateway/handler_error.go +++ b/gateway/handler_error.go @@ -3,6 +3,8 @@ package gateway import ( "bytes" "encoding/base64" + "encoding/json" + "encoding/xml" "errors" "html/template" "io" @@ -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") @@ -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) } } diff --git a/gateway/mw_validate_json_test.go b/gateway/mw_validate_json_test.go index 70ed7593021..1809bffbb9e 100644 --- a/gateway/mw_validate_json_test.go +++ b/gateway/mw_validate_json_test.go @@ -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"] @@ -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) {