-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathform.go
More file actions
158 lines (139 loc) · 3.86 KB
/
form.go
File metadata and controls
158 lines (139 loc) · 3.86 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
package shiftapi
import (
"fmt"
"mime/multipart"
"net/http"
"reflect"
"slices"
"strings"
)
var (
fileHeaderType = reflect.TypeFor[*multipart.FileHeader]()
fileHeaderSliceType = reflect.TypeFor[[]*multipart.FileHeader]()
)
// hasFormTag returns true if the struct field has a `form` tag.
func hasFormTag(f reflect.StructField) bool {
return f.Tag.Get("form") != ""
}
// formFieldName returns the form field name from the struct tag.
func formFieldName(f reflect.StructField) string {
name, _, _ := strings.Cut(f.Tag.Get("form"), ",")
if name == "" {
return f.Name
}
return name
}
// isFileField returns true if the field type is *multipart.FileHeader or []*multipart.FileHeader.
func isFileField(f reflect.StructField) bool {
return f.Type == fileHeaderType || f.Type == fileHeaderSliceType
}
// acceptTypes returns the accepted MIME types from the `accept` struct tag.
// Returns nil if no accept tag is present.
func acceptTypes(f reflect.StructField) []string {
tag := f.Tag.Get("accept")
if tag == "" {
return nil
}
var types []string
for part := range strings.SplitSeq(tag, ",") {
t := strings.TrimSpace(part)
if t != "" {
types = append(types, t)
}
}
return types
}
// checkFileContentType validates the Content-Type of an uploaded file
// against the accepted types. Returns an error if the type is not allowed.
func checkFileContentType(fh *multipart.FileHeader, name string, allowed []string) error {
if len(allowed) == 0 {
return nil
}
ct := fh.Header.Get("Content-Type")
if slices.Contains(allowed, ct) {
return nil
}
return &formParseError{
Field: name,
Err: fmt.Errorf("content type %q not allowed, accepted: %s", ct, strings.Join(allowed, ", ")),
}
}
// parseFormInto parses a multipart form request into struct fields tagged with `form`.
func parseFormInto(rv reflect.Value, r *http.Request, maxMemory int64) error {
if err := r.ParseMultipartForm(maxMemory); err != nil {
return &formParseError{Err: fmt.Errorf("failed to parse multipart form: %w", err)}
}
for rv.Kind() == reflect.Pointer {
if rv.IsNil() {
rv.Set(reflect.New(rv.Type().Elem()))
}
rv = rv.Elem()
}
rt := rv.Type()
if rt.Kind() != reflect.Struct {
return fmt.Errorf("form type must be a struct, got %s", rt.Kind())
}
for i := range rt.NumField() {
field := rt.Field(i)
if !field.IsExported() || !hasFormTag(field) {
continue
}
name := formFieldName(field)
fv := rv.Field(i)
if field.Type == fileHeaderType {
// Single file: *multipart.FileHeader
_, fh, err := r.FormFile(name)
if err != nil {
if err == http.ErrMissingFile {
continue
}
return &formParseError{Field: name, Err: err}
}
if allowed := acceptTypes(field); allowed != nil {
if err := checkFileContentType(fh, name, allowed); err != nil {
return err
}
}
fv.Set(reflect.ValueOf(fh))
continue
}
if field.Type == fileHeaderSliceType {
// Multiple files: []*multipart.FileHeader
if r.MultipartForm != nil && r.MultipartForm.File != nil {
files := r.MultipartForm.File[name]
if allowed := acceptTypes(field); allowed != nil {
for _, fh := range files {
if err := checkFileContentType(fh, name, allowed); err != nil {
return err
}
}
}
if len(files) > 0 {
fv.Set(reflect.ValueOf(files))
}
}
continue
}
// Text form field — use r.FormValue and setScalarValue
raw := r.FormValue(name)
if raw == "" {
continue
}
if err := setScalarValue(fv, raw); err != nil {
return &formParseError{Field: name, Err: err}
}
}
return nil
}
// formParseError is returned when a form field cannot be parsed.
type formParseError struct {
Field string
Err error
}
func (e *formParseError) Error() string {
if e.Field == "" {
return e.Err.Error()
}
return fmt.Sprintf("invalid form field %q: %v", e.Field, e.Err)
}
func (e *formParseError) Unwrap() error { return e.Err }