-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathfill.go
365 lines (331 loc) · 8.67 KB
/
fill.go
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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
package gosubmit
import (
"bytes"
"context"
"fmt"
"io"
"mime/multipart"
"net/http"
"net/http/httptest"
"net/url"
)
type Option func(f *filler) error
type multipartFile struct {
Contents []byte
Name string
}
type filler struct {
context context.Context
form Form
values url.Values
url string
method string
clicked bool
multipart map[string][]multipartFile
required map[string]struct{}
isMultipart bool
}
// Creates a new form filler. It is preferred to use Form.Fill() instead.
func newFiller(form Form, opts []Option) (f *filler, err error) {
values := make(url.Values)
f = &filler{
form: form,
values: values,
required: make(map[string]struct{}),
multipart: make(map[string][]multipartFile),
isMultipart: form.ContentType == ContentTypeMultipart,
}
f.prefill(form.Inputs)
err = f.apply(opts)
return
}
func (f *filler) apply(opts []Option) (err error) {
for _, opt := range opts {
err = opt(f)
if err != nil {
return
}
}
return
}
func (f *filler) prefill(inputs Inputs) {
for name, input := range inputs {
if input.Required() {
f.required[name] = struct{}{}
}
if input.Multipart() {
continue
}
for _, value := range input.Values() {
f.values.Add(name, value)
}
}
}
func (f *filler) createRequest(test bool, method string, url string, body io.Reader) (r *http.Request, err error) {
defer func() {
p := recover()
if p != nil {
err = fmt.Errorf("Caught panic when creating request: %s", p)
}
return
}()
if test {
r = httptest.NewRequest(method, url, body)
return
}
ctx := f.context
if ctx == nil {
ctx = context.Background()
}
r, err = http.NewRequestWithContext(ctx, method, url, body)
return
}
func (f *filler) NewTestRequest() (*http.Request, error) {
return f.prepareRequest(true)
}
func (f *filler) NewRequest() (*http.Request, error) {
return f.prepareRequest(false)
}
// Builds a form depeding on the enctype and creates a new test request.
func (f *filler) prepareRequest(test bool) (r *http.Request, err error) {
form := f.form
switch form.Method {
case http.MethodPost:
if !f.isMultipart {
body, err := f.BuildPost()
if err != nil {
return nil, err
}
r, err = f.createRequest(test, "POST", form.URL, bytes.NewReader(body))
if err != nil {
err = fmt.Errorf("Error creating post request: %w", err)
return nil, err
}
r.Header.Add("Content-Type", form.ContentType)
} else {
boundary, body, err := f.BuildMultipart()
if err != nil {
return nil, err
}
r, err = f.createRequest(test, "POST", form.URL, bytes.NewReader(body))
if err != nil {
return nil, fmt.Errorf("Error creating multipart request: %w", err)
}
r.Header.Add("Content-Type",
fmt.Sprintf("%s; boundary=%s", ContentTypeMultipart, boundary))
}
default:
query, err := f.BuildGet()
if err != nil {
return nil, err
}
url := fmt.Sprintf("%s?%s", form.URL, query)
r, err = f.createRequest(test, "GET", url, nil)
if err != nil {
return nil, fmt.Errorf("Error creating get request: %w", err)
}
}
return
}
// Builds form body for a multipart request
func (f *filler) BuildMultipart() (boundary string, data []byte, err error) {
if err = f.validateForm(); err != nil {
return "", nil, err
}
var body bytes.Buffer
writer := multipart.NewWriter(&body)
boundary = writer.Boundary()
defer func() {
e := writer.Close()
if e != nil && err == nil {
err = fmt.Errorf("Error closing multipart writer: %s", e)
}
data = body.Bytes()
}()
for field, files := range f.multipart {
for _, file := range files {
w, e := writer.CreateFormFile(field, file.Name)
if e != nil {
err = fmt.Errorf("Error creating multipart for field '%s': %w", field, e)
return
}
_, err = w.Write(file.Contents)
if err != nil {
err = fmt.Errorf("Error writing multipart data for field '%s': %w", field, err)
return
}
}
}
for field, values := range f.values {
for _, value := range values {
err := writer.WriteField(field, value)
if err != nil {
err = fmt.Errorf("Error writing multipart string for field '%s': %w", field, err)
}
}
}
return
}
func WithContext(ctx context.Context) Option {
return func(f *filler) error {
f.context = ctx
return nil
}
}
// // Adds value to all empty required fields.
// func (f *filler) AutoFill(defaultValue string) {
// for requiredField, _ := range f.required {
// value := f.values.Get(requiredField)
// if value != "" {
// continue
// }
// f.Set(requiredField, fmt.Sprintf("%s-%s", requiredField, defaultValue))
// }
// }
// Validates the form (for a plain form request). No need to call this method
// directly if BuildForm or NewTestRequest are used.
func (f *filler) validateForm() error {
for requiredField, _ := range f.required {
hasTextValue := f.values.Get(requiredField) != ""
hasByteValue := false
if f.isMultipart {
_, hasByteValue = f.multipart[requiredField]
}
if !hasTextValue && !hasByteValue {
return fmt.Errorf("Required field '%s' has no value", requiredField)
}
}
return nil
}
// Build values for form submission
func (f *filler) BuildGet() (params string, err error) {
err = f.validateForm()
params = f.values.Encode()
return params, err
}
// Build form body for post request
func (f *filler) BuildPost() (body []byte, err error) {
err = f.validateForm()
body = []byte(f.values.Encode())
return
}
func AutoFill() Option {
return func(f *filler) error {
for requiredField, _ := range f.required {
value := f.values.Get(requiredField)
input := f.form.Inputs[requiredField]
if value == "" {
add := false
for _, value := range input.AutoFill() {
var opt Option
if input.Type() == InputTypeFile {
opt = AddFile(requiredField, "auto-filename", []byte(value))
} else {
opt = setOrAdd(requiredField, value, add)
}
if err := opt(f); err != nil {
return err
}
add = true
}
}
}
return nil
}
}
// Adds the submit buttons name=value combination to the form submission.
// Useful when there are two or more buttons on a form and their values
// make a difference on how the server's going to process the form data.
func Click(buttonValue string) Option {
return func(f *filler) error {
if f.clicked == true {
return fmt.Errorf("Already clicked on one button")
}
ok := false
var b Button
for _, button := range f.form.Buttons {
if button.Value == buttonValue {
ok = true
b = button
break
}
}
if !ok {
return fmt.Errorf("Cannot find button with value: '%s'", buttonValue)
}
f.clicked = true
f.values.Set(b.Name, b.Value)
return nil
}
}
// Deletes a field from the form. Useful to remove preselected values
func Reset(name string) Option {
return func(f *filler) error {
f.values.Del(name)
delete(f.multipart, name)
return nil
}
}
// Adds a name=value pair to the form. If there is an empty value it will
// be replaced, otherwise a second value will be added, but only if the
// element supports multiple values, like checkboxes or <select multiple>
// elements.
func Add(name string, value string) Option {
return setOrAdd(name, value, true)
}
// Set a name=value pair to the form and replace any set value(s).
func Set(name string, value string) Option {
return setOrAdd(name, value, false)
}
func setOrAdd(name string, value string, add bool) Option {
return func(f *filler) error {
input, ok := f.form.Inputs[name]
if !ok {
return fmt.Errorf("Cannot find input name='%s'", name)
}
result, ok := input.Fill(value)
if !ok {
return fmt.Errorf("Value '%s' for input name='%s' is invalid", value, name)
}
values, ok := f.values[name]
hasEmptyValue := ok && len(values) == 1 && values[0] == ""
if add && !hasEmptyValue {
if f.values.Get(name) != "" && !input.Multiple() {
return fmt.Errorf("Cannot fill input name='%s' twice (multiple=false)", name)
}
f.values.Add(name, result)
} else {
f.values.Set(name, result)
}
return nil
}
}
// Set a value without validation
func UnsafeSet(name string, value string) Option {
return func(f *filler) error {
f.values.Set(name, value)
return nil
}
}
// Fill data for multipart request
func AddFile(fieldname string, filename string, contents []byte) Option {
return func(f *filler) error {
input, ok := f.form.Inputs[fieldname]
if !ok {
return fmt.Errorf("Cannot find input fieldname='%s'", fieldname)
}
_, ok = input.(FileInput)
if !ok {
return fmt.Errorf("Cannot fill bytes - input fieldname='%s' is not a file input", fieldname)
}
filesArray, ok := f.multipart[fieldname]
if !ok {
filesArray = []multipartFile{}
}
f.multipart[fieldname] = append(filesArray, multipartFile{
Name: filename,
Contents: contents,
})
return nil
}
}