1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package org.htmlunit.javascript.host.xml;
16
17 import java.util.ArrayList;
18 import java.util.Collections;
19 import java.util.Iterator;
20 import java.util.List;
21
22 import org.htmlunit.FormEncodingType;
23 import org.htmlunit.WebRequest;
24 import org.htmlunit.corejs.javascript.ClassDescriptor;
25 import org.htmlunit.corejs.javascript.Context;
26 import org.htmlunit.corejs.javascript.ES6Iterator;
27 import org.htmlunit.corejs.javascript.Function;
28 import org.htmlunit.corejs.javascript.Scriptable;
29 import org.htmlunit.corejs.javascript.TopLevel;
30 import org.htmlunit.corejs.javascript.VarScope;
31 import org.htmlunit.javascript.HtmlUnitScriptable;
32 import org.htmlunit.javascript.JavaScriptEngine;
33 import org.htmlunit.javascript.configuration.JsxClass;
34 import org.htmlunit.javascript.configuration.JsxConstructor;
35 import org.htmlunit.javascript.configuration.JsxFunction;
36 import org.htmlunit.javascript.configuration.JsxSymbol;
37 import org.htmlunit.javascript.host.file.Blob;
38 import org.htmlunit.javascript.host.file.File;
39 import org.htmlunit.javascript.host.html.HTMLFormElement;
40 import org.htmlunit.util.NameValuePair;
41 import org.htmlunit.util.StringUtils;
42
43
44
45
46
47
48
49
50
51
52 @JsxClass
53 public class FormData extends HtmlUnitScriptable {
54
55
56 private static final String FORM_DATA_ITERATOR_TAG = "FormData Iterator";
57
58 private final List<NameValuePair> requestParameters_ = new ArrayList<>();
59
60
61
62
63 public static final class FormDataIterator extends ES6Iterator {
64 private static final ClassDescriptor DESCRIPTOR =
65 ES6Iterator.makeDescriptor(FORM_DATA_ITERATOR_TAG, FORM_DATA_ITERATOR_TAG);
66
67 enum Type { KEYS, VALUES, BOTH }
68
69 private final Type type_;
70 private final String className_;
71 private final List<NameValuePair> nameValuePairList_;
72 private int index_;
73
74
75
76
77
78
79
80
81 public static void init(final Context cx, final TopLevel scope, final String className) {
82 ES6Iterator.initialize(
83 DESCRIPTOR, cx, scope, new FormDataIterator(className), false, FORM_DATA_ITERATOR_TAG);
84 }
85
86
87
88
89
90
91 public FormDataIterator(final String className) {
92 super();
93
94 type_ = Type.BOTH;
95 index_ = 0;
96 nameValuePairList_ = Collections.emptyList();
97 className_ = className;
98 }
99
100
101
102
103
104
105
106
107
108 public FormDataIterator(final VarScope scope, final String className, final Type type,
109 final List<NameValuePair> nameValuePairList) {
110 super(scope, className);
111 type_ = type;
112 index_ = 0;
113 nameValuePairList_ = nameValuePairList;
114 className_ = className;
115 }
116
117
118
119
120 @Override
121 public String getClassName() {
122 return className_;
123 }
124
125
126
127
128 @Override
129 protected boolean isDone(final Context cx, final VarScope scope) {
130 return index_ >= nameValuePairList_.size();
131 }
132
133
134
135
136 @Override
137 protected Object nextValue(final Context cx, final VarScope scope) {
138 if (isDone(cx, scope)) {
139 return Context.getUndefinedValue();
140 }
141
142 final NameValuePair nextNameValuePair = nameValuePairList_.get(index_++);
143 return switch (type_) {
144 case KEYS -> nextNameValuePair.getName();
145 case VALUES -> nextNameValuePair.getValue();
146 case BOTH ->
147 cx.newArray(scope, new Object[]{nextNameValuePair.getName(), nextNameValuePair.getValue()});
148 };
149 }
150 }
151
152
153
154
155
156
157 @JsxConstructor
158 public void jsConstructor(final Object formObj) {
159 if (formObj instanceof HTMLFormElement form) {
160 requestParameters_.addAll(form.getHtmlForm().getParameterListForSubmit(null));
161 }
162 }
163
164
165
166
167
168
169
170
171 @JsxFunction
172 public void append(final String name, final Object value, final Object filename) {
173 if (value instanceof Blob blob) {
174 String fileName = "blob";
175 if (value instanceof File) {
176 fileName = null;
177 }
178 if (filename instanceof String string) {
179 fileName = string;
180 }
181 requestParameters_.add(blob.getKeyDataPair(name, fileName));
182 return;
183 }
184 requestParameters_.add(new NameValuePair(name, JavaScriptEngine.toString(value)));
185 }
186
187
188
189
190
191
192 @JsxFunction(functionName = "delete")
193 public void delete_js(final String name) {
194 if (StringUtils.isEmptyOrNull(name)) {
195 return;
196 }
197
198 requestParameters_.removeIf(pair -> name.equals(pair.getName()));
199 }
200
201
202
203
204
205
206
207 @JsxFunction
208 public String get(final String name) {
209 if (StringUtils.isEmptyOrNull(name)) {
210 return null;
211 }
212
213 for (final NameValuePair pair : requestParameters_) {
214 if (name.equals(pair.getName())) {
215 return pair.getValue();
216 }
217 }
218 return null;
219 }
220
221
222
223
224
225
226
227 @JsxFunction
228 public Scriptable getAll(final String name) {
229 if (StringUtils.isEmptyOrNull(name)) {
230 return JavaScriptEngine.newArray(getParentScope(), 0);
231 }
232
233 final List<Object> values = new ArrayList<>();
234 for (final NameValuePair pair : requestParameters_) {
235 if (name.equals(pair.getName())) {
236 values.add(pair.getValue());
237 }
238 }
239
240 final Object[] stringValues = values.toArray(new Object[0]);
241 return JavaScriptEngine.newArray(getParentScope(), stringValues);
242 }
243
244
245
246
247
248
249
250 @JsxFunction
251 public boolean has(final String name) {
252 if (StringUtils.isEmptyOrNull(name)) {
253 return false;
254 }
255
256 for (final NameValuePair pair : requestParameters_) {
257 if (name.equals(pair.getName())) {
258 return true;
259 }
260 }
261 return false;
262 }
263
264
265
266
267
268
269
270
271
272 @JsxFunction
273 public void set(final String name, final Object value, final Object filename) {
274 if (StringUtils.isEmptyOrNull(name)) {
275 return;
276 }
277
278 int pos = -1;
279
280 final Iterator<NameValuePair> iter = requestParameters_.iterator();
281 int idx = 0;
282 while (iter.hasNext()) {
283 final NameValuePair pair = iter.next();
284 if (name.equals(pair.getName())) {
285 iter.remove();
286 if (pos < 0) {
287 pos = idx;
288 }
289 }
290 idx++;
291 }
292
293 if (pos < 0) {
294 pos = requestParameters_.size();
295 }
296
297 if (value instanceof Blob blob) {
298 String fileName = "blob";
299 if (value instanceof File) {
300 fileName = null;
301 }
302 if (filename instanceof String string) {
303 fileName = string;
304 }
305 requestParameters_.add(pos, blob.getKeyDataPair(name, fileName));
306 }
307 else {
308 requestParameters_.add(pos, new NameValuePair(name, JavaScriptEngine.toString(value)));
309 }
310 }
311
312
313
314
315
316
317 @JsxFunction
318 @JsxSymbol(symbolName = "iterator")
319 public Scriptable entries() {
320 return new FormDataIterator(getParentScope(),
321 FORM_DATA_ITERATOR_TAG, FormDataIterator.Type.BOTH, requestParameters_);
322 }
323
324
325
326
327
328
329 public void fillRequest(final WebRequest webRequest) {
330 webRequest.setEncodingType(FormEncodingType.MULTIPART);
331 webRequest.setRequestParameters(requestParameters_);
332 }
333
334
335
336
337
338
339 @JsxFunction
340 public void forEach(final Object callback) {
341 if (!(callback instanceof Function fun)) {
342 throw JavaScriptEngine.typeError(
343 "Foreach callback '" + JavaScriptEngine.toString(callback) + "' is not a function");
344 }
345
346
347 for (int i = 0;; i++) {
348 if (i >= requestParameters_.size()) {
349 break;
350 }
351
352 final NameValuePair param = requestParameters_.get(i);
353 fun.call(Context.getCurrentContext(), getParentScope(), this,
354 new Object[] {param.getValue(), param.getName(), this});
355 }
356 }
357
358
359
360
361
362
363 @JsxFunction
364 public FormDataIterator keys() {
365 return new FormDataIterator(getParentScope(),
366 FORM_DATA_ITERATOR_TAG, FormDataIterator.Type.KEYS, requestParameters_);
367 }
368
369
370
371
372
373
374 @JsxFunction
375 public FormDataIterator values() {
376 return new FormDataIterator(getParentScope(),
377 FORM_DATA_ITERATOR_TAG, FormDataIterator.Type.VALUES, requestParameters_);
378 }
379 }