Oopeneuler-sync-botfix CVE-2024-37298
dcf082df创建于 2025年1月13日历史提交
From 3821d30cf1647fe886aa5e2fe06ac992df460a30 Mon Sep 17 00:00:00 2001
From: Matt Heon <mheon@redhat.com>
Date: Wed, 3 Jul 2024 13:31:50 -0400
Subject: [PATCH] Update gorilla/schema to v1.4.1 to fix CVE-2024-37298

Ref: RH Jira RHEL-45916

Signed-off-by: Matt Heon <mheon@redhat.com>
---
 go.mod                                        |   2 +-
 go.sum                                        |   4 +-
 .../github.com/gorilla/schema/.editorconfig   |  20 ++++
 vendor/github.com/gorilla/schema/.gitignore   |   1 +
 vendor/github.com/gorilla/schema/LICENSE      |   2 +-
 vendor/github.com/gorilla/schema/Makefile     |  34 ++++++
 vendor/github.com/gorilla/schema/README.md    |  39 ++++++-
 vendor/github.com/gorilla/schema/cache.go     |  28 +++--
 vendor/github.com/gorilla/schema/converter.go |  77 +++++++++++++
 vendor/github.com/gorilla/schema/decoder.go   | 103 +++++++++++++++++-
 vendor/github.com/gorilla/schema/encoder.go   |  17 ++-
 vendor/modules.txt                            |   4 +-
 12 files changed, 307 insertions(+), 24 deletions(-)
 create mode 100644 vendor/github.com/gorilla/schema/.editorconfig
 create mode 100644 vendor/github.com/gorilla/schema/.gitignore
 create mode 100644 vendor/github.com/gorilla/schema/Makefile

diff --git a/go.mod b/go.mod
index a84fbbb1b4..253caecf3b 100644
--- a/go.mod
+++ b/go.mod
@@ -36,7 +36,7 @@ require (
 	github.com/google/uuid v1.4.0
 	github.com/gorilla/handlers v1.5.2
 	github.com/gorilla/mux v1.8.1
-	github.com/gorilla/schema v1.2.0
+	github.com/gorilla/schema v1.4.1
 	github.com/hashicorp/go-multierror v1.1.1
 	github.com/hugelgupf/p9 v0.3.1-0.20230822151754-54f5c5530921
 	github.com/json-iterator/go v1.1.12
diff --git a/go.sum b/go.sum
index 495035a32b..b1cd4f77dc 100644
--- a/go.sum
+++ b/go.sum
@@ -622,8 +622,8 @@ github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2z
 github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
 github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
 github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
-github.com/gorilla/schema v1.2.0 h1:YufUaxZYCKGFuAq3c96BOhjgd5nmXiOY9NGzF247Tsc=
-github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
+github.com/gorilla/schema v1.4.1 h1:jUg5hUjCSDZpNGLuXQOgIWGdlgrIdYvgQ0wZtdK1M3E=
+github.com/gorilla/schema v1.4.1/go.mod h1:Dg5SSm5PV60mhF2NFaTV1xuYYj8tV8NOPRo4FggUMnM=
 github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
 github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
 github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
diff --git a/vendor/github.com/gorilla/schema/.editorconfig b/vendor/github.com/gorilla/schema/.editorconfig
new file mode 100644
index 0000000000..c6b74c3e0d
--- /dev/null
+++ b/vendor/github.com/gorilla/schema/.editorconfig
@@ -0,0 +1,20 @@
+; https://editorconfig.org/
+
+root = true
+
+[*]
+insert_final_newline = true
+charset = utf-8
+trim_trailing_whitespace = true
+indent_style = space
+indent_size = 2
+
+[{Makefile,go.mod,go.sum,*.go,.gitmodules}]
+indent_style = tab
+indent_size = 4
+
+[*.md]
+indent_size = 4
+trim_trailing_whitespace = false
+
+eclint_indent_style = unset
\ No newline at end of file
diff --git a/vendor/github.com/gorilla/schema/.gitignore b/vendor/github.com/gorilla/schema/.gitignore
new file mode 100644
index 0000000000..84039fec68
--- /dev/null
+++ b/vendor/github.com/gorilla/schema/.gitignore
@@ -0,0 +1 @@
+coverage.coverprofile
diff --git a/vendor/github.com/gorilla/schema/LICENSE b/vendor/github.com/gorilla/schema/LICENSE
index 0e5fb87280..bb9d80bc9b 100644
--- a/vendor/github.com/gorilla/schema/LICENSE
+++ b/vendor/github.com/gorilla/schema/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2012 Rodrigo Moraes. All rights reserved.
+Copyright (c) 2023 The Gorilla Authors. All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
 modification, are permitted provided that the following conditions are
diff --git a/vendor/github.com/gorilla/schema/Makefile b/vendor/github.com/gorilla/schema/Makefile
new file mode 100644
index 0000000000..98f5ab75f9
--- /dev/null
+++ b/vendor/github.com/gorilla/schema/Makefile
@@ -0,0 +1,34 @@
+GO_LINT=$(shell which golangci-lint 2> /dev/null || echo '')
+GO_LINT_URI=github.com/golangci/golangci-lint/cmd/golangci-lint@latest
+
+GO_SEC=$(shell which gosec 2> /dev/null || echo '')
+GO_SEC_URI=github.com/securego/gosec/v2/cmd/gosec@latest
+
+GO_VULNCHECK=$(shell which govulncheck 2> /dev/null || echo '')
+GO_VULNCHECK_URI=golang.org/x/vuln/cmd/govulncheck@latest
+
+.PHONY: golangci-lint
+golangci-lint:
+	$(if $(GO_LINT), ,go install $(GO_LINT_URI))
+	@echo "##### Running golangci-lint"
+	golangci-lint run -v
+	
+.PHONY: gosec
+gosec:
+	$(if $(GO_SEC), ,go install $(GO_SEC_URI))
+	@echo "##### Running gosec"
+	gosec ./...
+
+.PHONY: govulncheck
+govulncheck:
+	$(if $(GO_VULNCHECK), ,go install $(GO_VULNCHECK_URI))
+	@echo "##### Running govulncheck"
+	govulncheck ./...
+
+.PHONY: verify
+verify: golangci-lint gosec govulncheck
+
+.PHONY: test
+test:
+	@echo "##### Running tests"
+	go test -race -cover -coverprofile=coverage.coverprofile -covermode=atomic -v ./...
\ No newline at end of file
diff --git a/vendor/github.com/gorilla/schema/README.md b/vendor/github.com/gorilla/schema/README.md
index aefdd66996..58786ba502 100644
--- a/vendor/github.com/gorilla/schema/README.md
+++ b/vendor/github.com/gorilla/schema/README.md
@@ -1,8 +1,12 @@
-schema
-======
-[![GoDoc](https://godoc.org/github.com/gorilla/schema?status.svg)](https://godoc.org/github.com/gorilla/schema) [![Build Status](https://travis-ci.org/gorilla/schema.png?branch=master)](https://travis-ci.org/gorilla/schema)
-[![Sourcegraph](https://sourcegraph.com/github.com/gorilla/schema/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/schema?badge)
+# gorilla/schema
 
+![testing](https://github.com/gorilla/schema/actions/workflows/test.yml/badge.svg)
+[![codecov](https://codecov.io/github/gorilla/schema/branch/main/graph/badge.svg)](https://codecov.io/github/gorilla/schema)
+[![godoc](https://godoc.org/github.com/gorilla/schema?status.svg)](https://godoc.org/github.com/gorilla/schema)
+[![sourcegraph](https://sourcegraph.com/github.com/gorilla/schema/-/badge.svg)](https://sourcegraph.com/github.com/gorilla/schema?badge)
+
+
+![Gorilla Logo](https://github.com/gorilla/.github/assets/53367916/d92caabf-98e0-473e-bfbf-ab554ba435e5)
 
 Package gorilla/schema converts structs to and from form values.
 
@@ -83,7 +87,32 @@ The supported field types in the struct are:
 
 Unsupported types are simply ignored, however custom types can be registered to be converted.
 
-More examples are available on the Gorilla website: https://www.gorillatoolkit.org/pkg/schema
+## Setting Defaults
+
+It is possible to set default values when encoding/decoding by using the `default` tag option. The value of `default` is applied when a field has a zero value, a pointer has a nil value, or a slice is empty.
+
+```go
+type Person struct {
+    Phone string `schema:"phone,default:+123456"`          // custom name
+    Age int     `schema:"age,default:21"`
+	Admin bool    `schema:"admin,default:false"`
+	Balance float64 `schema:"balance,default:10.0"`
+    Friends []string `schema:friends,default:john|bob`
+}
+```
+
+The `default` tag option is supported for the following types:
+
+* bool
+* float variants (float32, float64)
+* int variants (int, int8, int16, int32, int64)
+* uint variants (uint, uint8, uint16, uint32, uint64)
+* string
+* a slice of the above types. As shown in the example above, `|` should be used to separate between slice items. 
+* a pointer to one of the above types (pointer to slice and slice of pointers are not supported).
+
+> [!NOTE]  
+> Because primitive types like int, float, bool, unint and their variants have their default (or zero) values set by Golang, it is not possible to distinguish them from a provided value when decoding/encoding form values. In this case, the value provided by the `default` option tag will be always applied. For example, let's assume that the value submitted in the form for `balance` is `0.0` then the default of `10.0` will be applied, even if `0.0` is part of the form data for the `balance` field. In such cases, it is highly recommended to use pointers to allow schema to distinguish between when a form field has no provided value and when a form has a value equal to the corresponding default set by Golang for a particular type. If the type of the `Balance` field above is changed to `*float64`, then the zero value would be `nil`. In this case, if the form data value for `balance` is `0.0`, then the default will not be applied.
 
 ## License
 
diff --git a/vendor/github.com/gorilla/schema/cache.go b/vendor/github.com/gorilla/schema/cache.go
index 0746c1202c..065b8d6ee6 100644
--- a/vendor/github.com/gorilla/schema/cache.go
+++ b/vendor/github.com/gorilla/schema/cache.go
@@ -12,7 +12,7 @@ import (
 	"sync"
 )
 
-var invalidPath = errors.New("schema: invalid path")
+var errInvalidPath = errors.New("schema: invalid path")
 
 // newCache returns a new cache.
 func newCache() *cache {
@@ -53,13 +53,13 @@ func (c *cache) parsePath(p string, t reflect.Type) ([]pathPart, error) {
 	keys := strings.Split(p, ".")
 	for i := 0; i < len(keys); i++ {
 		if t.Kind() != reflect.Struct {
-			return nil, invalidPath
+			return nil, errInvalidPath
 		}
 		if struc = c.get(t); struc == nil {
-			return nil, invalidPath
+			return nil, errInvalidPath
 		}
 		if field = struc.get(keys[i]); field == nil {
-			return nil, invalidPath
+			return nil, errInvalidPath
 		}
 		// Valid field. Append index.
 		path = append(path, field.name)
@@ -72,10 +72,10 @@ func (c *cache) parsePath(p string, t reflect.Type) ([]pathPart, error) {
 			// So checking i+2 is not necessary anymore.
 			i++
 			if i+1 > len(keys) {
-				return nil, invalidPath
+				return nil, errInvalidPath
 			}
 			if index64, err = strconv.ParseInt(keys[i], 10, 0); err != nil {
-				return nil, invalidPath
+				return nil, errInvalidPath
 			}
 			parts = append(parts, pathPart{
 				path:  path,
@@ -197,6 +197,7 @@ func (c *cache) createField(field reflect.StructField, parentAlias string) *fiel
 		isSliceOfStructs: isSlice && isStruct,
 		isAnonymous:      field.Anonymous,
 		isRequired:       options.Contains("required"),
+		defaultValue:     options.getDefaultOptionValue(),
 	}
 }
 
@@ -246,8 +247,9 @@ type fieldInfo struct {
 	// isSliceOfStructs indicates if the field type is a slice of structs.
 	isSliceOfStructs bool
 	// isAnonymous indicates whether the field is embedded in the struct.
-	isAnonymous bool
-	isRequired  bool
+	isAnonymous  bool
+	isRequired   bool
+	defaultValue string
 }
 
 func (f *fieldInfo) paths(prefix string) []string {
@@ -303,3 +305,13 @@ func (o tagOptions) Contains(option string) bool {
 	}
 	return false
 }
+
+func (o tagOptions) getDefaultOptionValue() string {
+	for _, s := range o {
+		if strings.HasPrefix(s, "default:") {
+			return strings.Split(s, ":")[1]
+		}
+	}
+
+	return ""
+}
diff --git a/vendor/github.com/gorilla/schema/converter.go b/vendor/github.com/gorilla/schema/converter.go
index 4f2116a15e..4bae6df962 100644
--- a/vendor/github.com/gorilla/schema/converter.go
+++ b/vendor/github.com/gorilla/schema/converter.go
@@ -143,3 +143,80 @@ func convertUint64(value string) reflect.Value {
 	}
 	return invalidValue
 }
+
+func convertPointer(k reflect.Kind, value string) reflect.Value {
+	switch k {
+	case boolType:
+		if v := convertBool(value); v.IsValid() {
+			converted := v.Bool()
+			return reflect.ValueOf(&converted)
+		}
+	case float32Type:
+		if v := convertFloat32(value); v.IsValid() {
+			converted := float32(v.Float())
+			return reflect.ValueOf(&converted)
+		}
+	case float64Type:
+		if v := convertFloat64(value); v.IsValid() {
+			converted := float64(v.Float())
+			return reflect.ValueOf(&converted)
+		}
+	case intType:
+		if v := convertInt(value); v.IsValid() {
+			converted := int(v.Int())
+			return reflect.ValueOf(&converted)
+		}
+	case int8Type:
+		if v := convertInt8(value); v.IsValid() {
+			converted := int8(v.Int())
+			return reflect.ValueOf(&converted)
+		}
+	case int16Type:
+		if v := convertInt16(value); v.IsValid() {
+			converted := int16(v.Int())
+			return reflect.ValueOf(&converted)
+		}
+	case int32Type:
+		if v := convertInt32(value); v.IsValid() {
+			converted := int32(v.Int())
+			return reflect.ValueOf(&converted)
+		}
+	case int64Type:
+		if v := convertInt64(value); v.IsValid() {
+			converted := int64(v.Int())
+			return reflect.ValueOf(&converted)
+		}
+	case stringType:
+		if v := convertString(value); v.IsValid() {
+			converted := v.String()
+			return reflect.ValueOf(&converted)
+		}
+	case uintType:
+		if v := convertUint(value); v.IsValid() {
+			converted := uint(v.Uint())
+			return reflect.ValueOf(&converted)
+		}
+	case uint8Type:
+		if v := convertUint8(value); v.IsValid() {
+			converted := uint8(v.Uint())
+			return reflect.ValueOf(&converted)
+		}
+	case uint16Type:
+		if v := convertUint16(value); v.IsValid() {
+			converted := uint16(v.Uint())
+			return reflect.ValueOf(&converted)
+		}
+	case uint32Type:
+		if v := convertUint32(value); v.IsValid() {
+			converted := uint32(v.Uint())
+			return reflect.ValueOf(&converted)
+		}
+	case uint64Type:
+		if v := convertUint64(value); v.IsValid() {
+			converted := uint64(v.Uint())
+			return reflect.ValueOf(&converted)
+		}
+	}
+
+	return invalidValue
+}
diff --git a/vendor/github.com/gorilla/schema/decoder.go b/vendor/github.com/gorilla/schema/decoder.go
index 025e438b56..54c88ecb34 100644
--- a/vendor/github.com/gorilla/schema/decoder.go
+++ b/vendor/github.com/gorilla/schema/decoder.go
@@ -12,9 +12,13 @@ import (
 	"strings"
 )
 
+const (
+	defaultMaxSize = 16000
+)
+
 // NewDecoder returns a new Decoder.
 func NewDecoder() *Decoder {
-	return &Decoder{cache: newCache()}
+	return &Decoder{cache: newCache(), maxSize: defaultMaxSize}
 }
 
 // Decoder decodes values from a map[string][]string to a struct.
@@ -22,6 +26,7 @@ type Decoder struct {
 	cache             *cache
 	zeroEmpty         bool
 	ignoreUnknownKeys bool
+	maxSize           int
 }
 
 // SetAliasTag changes the tag used to locate custom field aliases.
@@ -54,6 +59,13 @@ func (d *Decoder) IgnoreUnknownKeys(i bool) {
 	d.ignoreUnknownKeys = i
 }
 
+// MaxSize limits the size of slices for URL nested arrays or object arrays.
+// Choose MaxSize carefully; large values may create many zero-value slice elements.
+// Example: "items.100000=apple" would create a slice with 100,000 empty strings.
+func (d *Decoder) MaxSize(size int) {
+	d.maxSize = size
+}
+
 // RegisterConverter registers a converter function for a custom type.
 func (d *Decoder) RegisterConverter(value interface{}, converterFunc Converter) {
 	d.cache.registerConverter(value, converterFunc)
@@ -84,6 +96,7 @@ func (d *Decoder) Decode(dst interface{}, src map[string][]string) error {
 			errors[path] = UnknownKeyError{Key: path}
 		}
 	}
+	errors.merge(d.setDefaults(t, v))
 	errors.merge(d.checkRequired(t, src))
 	if len(errors) > 0 {
 		return errors
@@ -91,6 +104,88 @@ func (d *Decoder) Decode(dst interface{}, src map[string][]string) error {
 	return nil
 }
 
+// setDefaults sets the default values when the `default` tag is specified,
+// default is supported on basic/primitive types and their pointers,
+// nested structs can also have default tags
+func (d *Decoder) setDefaults(t reflect.Type, v reflect.Value) MultiError {
+	struc := d.cache.get(t)
+	if struc == nil {
+		// unexpect, cache.get never return nil
+		return MultiError{"default-" + t.Name(): errors.New("cache fail")}
+	}
+
+	errs := MultiError{}
+
+	if v.Type().Kind() == reflect.Struct {
+		for i := 0; i < v.NumField(); i++ {
+			field := v.Field(i)
+			if field.Type().Kind() == reflect.Ptr && field.IsNil() && v.Type().Field(i).Anonymous {
+				field.Set(reflect.New(field.Type().Elem()))
+			}
+		}
+	}
+
+	for _, f := range struc.fields {
+		vCurrent := v.FieldByName(f.name)
+
+		if vCurrent.Type().Kind() == reflect.Struct && f.defaultValue == "" {
+			errs.merge(d.setDefaults(vCurrent.Type(), vCurrent))
+		} else if isPointerToStruct(vCurrent) && f.defaultValue == "" {
+			errs.merge(d.setDefaults(vCurrent.Elem().Type(), vCurrent.Elem()))
+		}
+
+		if f.defaultValue != "" && f.isRequired {
+			errs.merge(MultiError{"default-" + f.name: errors.New("required fields cannot have a default value")})
+		} else if f.defaultValue != "" && vCurrent.IsZero() && !f.isRequired {
+			if f.typ.Kind() == reflect.Struct {
+				errs.merge(MultiError{"default-" + f.name: errors.New("default option is supported only on: bool, float variants, string, unit variants types or their corresponding pointers or slices")})
+			} else if f.typ.Kind() == reflect.Slice {
+				vals := strings.Split(f.defaultValue, "|")
+
+				// check if slice has one of the supported types for defaults
+				if _, ok := builtinConverters[f.typ.Elem().Kind()]; !ok {
+					errs.merge(MultiError{"default-" + f.name: errors.New("default option is supported only on: bool, float variants, string, unit variants types or their corresponding pointers or slices")})
+					continue
+				}
+
+				defaultSlice := reflect.MakeSlice(f.typ, 0, cap(vals))
+				for _, val := range vals {
+					// this check is to handle if the wrong value is provided
+					convertedVal := builtinConverters[f.typ.Elem().Kind()](val)
+					if !convertedVal.IsValid() {
+						errs.merge(MultiError{"default-" + f.name: fmt.Errorf("failed setting default: %s is not compatible with field %s type", val, f.name)})
+						break
+					}
+					defaultSlice = reflect.Append(defaultSlice, convertedVal)
+				}
+				vCurrent.Set(defaultSlice)
+			} else if f.typ.Kind() == reflect.Ptr {
+				t1 := f.typ.Elem()
+
+				if t1.Kind() == reflect.Struct || t1.Kind() == reflect.Slice {
+					errs.merge(MultiError{"default-" + f.name: errors.New("default option is supported only on: bool, float variants, string, unit variants types or their corresponding pointers or slices")})
+				}
+
+				// this check is to handle if the wrong value is provided
+				if convertedVal := convertPointer(t1.Kind(), f.defaultValue); convertedVal.IsValid() {
+					vCurrent.Set(convertedVal)
+				}
+			} else {
+				// this check is to handle if the wrong value is provided
+				if convertedVal := builtinConverters[f.typ.Kind()](f.defaultValue); convertedVal.IsValid() {
+					vCurrent.Set(builtinConverters[f.typ.Kind()](f.defaultValue))
+				}
+			}
+		}
+	}
+
+	return errs
+}
+
+func isPointerToStruct(v reflect.Value) bool {
+	return !v.IsZero() && v.Type().Kind() == reflect.Ptr && v.Elem().Type().Kind() == reflect.Struct
+}
+
 // checkRequired checks whether required fields are empty
 //
 // check type t recursively if t has struct fields.
@@ -193,7 +288,7 @@ func (d *Decoder) decode(v reflect.Value, path string, parts []pathPart, values
 		if v.Type().Kind() == reflect.Struct {
 			for i := 0; i < v.NumField(); i++ {
 				field := v.Field(i)
-				if field.Type().Kind() == reflect.Ptr && field.IsNil() && v.Type().Field(i).Anonymous == true {
+				if field.Type().Kind() == reflect.Ptr && field.IsNil() && v.Type().Field(i).Anonymous {
 					field.Set(reflect.New(field.Type().Elem()))
 				}
 			}
@@ -219,6 +314,10 @@ func (d *Decoder) decode(v reflect.Value, path string, parts []pathPart, values
 	// Slice of structs. Let's go recursive.
 	if len(parts) > 1 {
 		idx := parts[0].index
+		// a defensive check to avoid creating a large slice based on user input index
+		if idx > d.maxSize {
+			return fmt.Errorf("%v index %d is larger than the configured maxSize %d", v.Kind(), idx, d.maxSize)
+		}
 		if v.IsNil() || v.Len() < idx+1 {
 			value := reflect.MakeSlice(t, idx+1, idx+1)
 			if v.Len() < idx+1 {
diff --git a/vendor/github.com/gorilla/schema/encoder.go b/vendor/github.com/gorilla/schema/encoder.go
index f0ed631210..52f2c108e4 100644
--- a/vendor/github.com/gorilla/schema/encoder.go
+++ b/vendor/github.com/gorilla/schema/encoder.go
@@ -93,8 +93,11 @@ func (e *Encoder) encode(v reflect.Value, dst map[string][]string) error {
 		}
 
 		// Encode struct pointer types if the field is a valid pointer and a struct.
-		if isValidStructPointer(v.Field(i)) {
-			e.encode(v.Field(i).Elem(), dst)
+		if isValidStructPointer(v.Field(i)) && !e.hasCustomEncoder(v.Field(i).Type()) {
+			err := e.encode(v.Field(i).Elem(), dst)
+			if err != nil {
+				errors[v.Field(i).Elem().Type().String()] = err
+			}
 			continue
 		}
 
@@ -112,7 +115,10 @@ func (e *Encoder) encode(v reflect.Value, dst map[string][]string) error {
 		}
 
 		if v.Field(i).Type().Kind() == reflect.Struct {
-			e.encode(v.Field(i), dst)
+			err := e.encode(v.Field(i), dst)
+			if err != nil {
+				errors[v.Field(i).Type().String()] = err
+			}
 			continue
 		}
 
@@ -142,6 +148,11 @@ func (e *Encoder) encode(v reflect.Value, dst map[string][]string) error {
 	return nil
 }
 
+func (e *Encoder) hasCustomEncoder(t reflect.Type) bool {
+	_, exists := e.regenc[t]
+	return exists
+}
+
 func typeEncoder(t reflect.Type, reg map[reflect.Type]encoderFunc) encoderFunc {
 	if f, ok := reg[t]; ok {
 		return f
diff --git a/vendor/modules.txt b/vendor/modules.txt
index d99eb4f521..6e2aa65371 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -673,8 +673,8 @@ github.com/gorilla/handlers
 # github.com/gorilla/mux v1.8.1
 ## explicit; go 1.20
 github.com/gorilla/mux
-# github.com/gorilla/schema v1.2.0
-## explicit
+# github.com/gorilla/schema v1.4.1
+## explicit; go 1.20
 github.com/gorilla/schema
 # github.com/hashicorp/errwrap v1.1.0
 ## explicit