Repository: getkin/kin-openapi
Pull request #1059 (2025-03-19 12:13:18)

openapi3filter: de-register ZipFileBodyDecoder and make a few decoders public


Patch:
diff --git a/.github/docs/openapi3filter.txt b/.github/docs/openapi3filter.txt
index 1eb4d41..42c6249 100644
--- a/.github/docs/openapi3filter.txt
+++ b/.github/docs/openapi3filter.txt
@@ -42,6 +42,9 @@ FUNCTIONS
 func ConvertErrors(err error) error
     ConvertErrors converts all errors to the appropriate error format.
 
+func CsvBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (any, error)
+    CsvBodyDecoder is a body decoder that decodes a csv body to a string.
+
 func DefaultErrorEncoder(_ context.Context, err error, w http.ResponseWriter)
     DefaultErrorEncoder writes the error to the ResponseWriter, by default a
     content type of text/plain, a body of the plain text of the error, and a
@@ -58,9 +61,11 @@ func JSONBodyDecoder(body io.Reader, header http.Header, schema *openapi3.Schema
     JSONBodyDecoder decodes a JSON formatted body. It is public so that is easy
     to register additional JSON based formats.
 
+func MultipartBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (any, error)
 func NoopAuthenticationFunc(context.Context, *AuthenticationInput) error
     NoopAuthenticationFunc is an AuthenticationFunc
 
+func PlainBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (any, error)
 func RegisterBodyDecoder(contentType string, decoder BodyDecoder)
     RegisterBodyDecoder registers a request body's decoder for a content type.
 
@@ -84,6 +89,7 @@ func UnregisterBodyDecoder(contentType string)
 func UnregisterBodyEncoder(contentType string)
     UnregisterBodyEncoder disables package-wide decoding of contentType values
 
+func UrlencodedBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (any, error)
 func ValidateParameter(ctx context.Context, input *RequestValidationInput, parameter *openapi3.Parameter) error
     ValidateParameter validates a parameter's value by JSON schema. The function
     returns RequestError with a ParseError cause when unable to parse a value.
@@ -121,6 +127,12 @@ func ValidateSecurityRequirements(ctx context.Context, input *RequestValidationI
     requirements in order and returns nil on the first valid requirement.
     If no requirement is met, errors are returned in order.
 
+func YamlBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (any, error)
+func ZipFileBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (any, error)
+    ZipFileBodyDecoder is a body decoder that decodes a zip file body to a
+    string. Use with caution as this implementation may be susceptible to a zip
+    bomb attack.
+
 
 TYPES
 
diff --git a/README.md b/README.md
index d135ffe..00c75b2 100644
--- a/README.md
+++ b/README.md
@@ -130,8 +130,7 @@ func main() {
 
 ## Custom content type for body of HTTP request/response
 
-By default, the library parses a body of the HTTP request and response
-if it has one of the following content types: `"text/plain"` or `"application/json"`.
+By default, the library parses a body of the HTTP request and response of [a few content types](https://github.com/getkin/kin-openapi/blob/6da871e0e170b7637eb568c265c08bc2b5d6e7a3/openapi3filter/req_resp_decoder.go#L1264) e.g. `"text/plain"` or `"application/json"`.
 To support other content types you must register decoders for them:
 
 ```go
@@ -295,6 +294,9 @@ for _, path := range doc.Paths.InMatchingOrder() {
 
 ## CHANGELOG: Sub-v1 breaking API changes
 
+### v0.131.0
+* No longer `openapi3filter.RegisterBodyDecoder` the `openapi3filter.ZipFileBodyDecoder` by default.
+
 ### v0.129.0
 * `openapi3.Discriminator.Mapping` and `openapi3.OAuthFlow.Scopes` fields went from a `map[string]string` to the new type `StringMap`
 
diff --git a/openapi3filter/req_resp_decoder.go b/openapi3filter/req_resp_decoder.go
index 998a91f..7ed5221 100644
--- a/openapi3filter/req_resp_decoder.go
+++ b/openapi3filter/req_resp_decoder.go
@@ -1269,16 +1269,15 @@ func init() {
 	RegisterBodyDecoder("application/vnd.api+json", JSONBodyDecoder)
 	RegisterBodyDecoder("application/octet-stream", FileBodyDecoder)
 	RegisterBodyDecoder("application/problem+json", JSONBodyDecoder)
-	RegisterBodyDecoder("application/x-www-form-urlencoded", urlencodedBodyDecoder)
-	RegisterBodyDecoder("application/x-yaml", yamlBodyDecoder)
-	RegisterBodyDecoder("application/yaml", yamlBodyDecoder)
-	RegisterBodyDecoder("application/zip", zipFileBodyDecoder)
-	RegisterBodyDecoder("multipart/form-data", multipartBodyDecoder)
-	RegisterBodyDecoder("text/csv", csvBodyDecoder)
-	RegisterBodyDecoder("text/plain", plainBodyDecoder)
+	RegisterBodyDecoder("application/x-www-form-urlencoded", UrlencodedBodyDecoder)
+	RegisterBodyDecoder("application/x-yaml", YamlBodyDecoder)
+	RegisterBodyDecoder("application/yaml", YamlBodyDecoder)
+	RegisterBodyDecoder("multipart/form-data", MultipartBodyDecoder)
+	RegisterBodyDecoder("text/csv", CsvBodyDecoder)
+	RegisterBodyDecoder("text/plain", PlainBodyDecoder)
 }
 
-func plainBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (any, error) {
+func PlainBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (any, error) {
 	data, err := io.ReadAll(body)
 	if err != nil {
 		return nil, &ParseError{Kind: KindInvalidFormat, Cause: err}
@@ -1298,7 +1297,7 @@ func JSONBodyDecoder(body io.Reader, header http.Header, schema *openapi3.Schema
 	return value, nil
 }
 
-func yamlBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (any, error) {
+func YamlBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (any, error) {
 	var value any
 	if err := yaml.NewDecoder(body).Decode(&value); err != nil {
 		return nil, &ParseError{Kind: KindInvalidFormat, Cause: err}
@@ -1306,7 +1305,7 @@ func yamlBodyDecoder(body io.Reader, header http.Header, schema *openapi3.Schema
 	return value, nil
 }
 
-func urlencodedBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (any, error) {
+func UrlencodedBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (any, error) {
 	// Validate schema of request body.
 	// By the OpenAPI 3 specification request body's schema must have type "object".
 	// Properties of the schema describes individual parts of request body.
@@ -1391,7 +1390,7 @@ func decodeProperty(dec valueDecoder, name string, prop *openapi3.SchemaRef, enc
 	return decodeValue(dec, name, sm, prop, false)
 }
 
-func multipartBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (any, error) {
+func MultipartBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (any, error) {
 	if !schema.Value.Type.Is("object") {
 		return nil, errors.New("unsupported schema of request body")
 	}
@@ -1519,8 +1518,9 @@ func FileBodyDecoder(body io.Reader, header http.Header, schema *openapi3.Schema
 	return string(data), nil
 }
 
-// zipFileBodyDecoder is a body decoder that decodes a zip file body to a string.
-func zipFileBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (any, error) {
+// ZipFileBodyDecoder is a body decoder that decodes a zip file body to a string.
+// Use with caution as this implementation may be susceptible to a zip bomb attack.
+func ZipFileBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (any, error) {
 	buff := bytes.NewBuffer([]byte{})
 	size, err := io.Copy(buff, body)
 	if err != nil {
@@ -1569,8 +1569,8 @@ func zipFileBodyDecoder(body io.Reader, header http.Header, schema *openapi3.Sch
 	return string(content), nil
 }
 
-// csvBodyDecoder is a body decoder that decodes a csv body to a string.
-func csvBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (any, error) {
+// CsvBodyDecoder is a body decoder that decodes a csv body to a string.
+func CsvBodyDecoder(body io.Reader, header http.Header, schema *openapi3.SchemaRef, encFn EncodingFn) (any, error) {
 	r := csv.NewReader(body)
 
 	var sb strings.Builder
diff --git a/openapi3filter/validate_request.go b/openapi3filter/validate_request.go
index 672c6ea..7456931 100644
--- a/openapi3filter/validate_request.go
+++ b/openapi3filter/validate_request.go
@@ -113,9 +113,9 @@ func appendToQueryValues[T any](q url.Values, parameterName string, v []T) {
 }
 
 func joinValues(values []any, sep string) string {
-	strValues := make([]string, len(values))
-	for i, v := range values {
-		strValues[i] = fmt.Sprintf("%v", v)
+	strValues := make([]string, 0, len(values))
+	for _, v := range values {
+		strValues = append(strValues, fmt.Sprintf("%v", v))
 	}
 	return strings.Join(strValues, sep)
 }


Test patch:
diff --git a/openapi3filter/zip_file_upload_test.go b/openapi3filter/zip_file_upload_test.go
index 69c6419..b2bf17e 100644
--- a/openapi3filter/zip_file_upload_test.go
+++ b/openapi3filter/zip_file_upload_test.go
@@ -17,6 +17,8 @@ import (
 )
 
 func TestValidateZipFileUpload(t *testing.T) {
+	openapi3filter.RegisterBodyDecoder("application/zip", openapi3filter.ZipFileBodyDecoder)
+
 	const spec = `
 openapi: 3.0.0
 info: