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