1 /*
2  * Copyright 2004 James Bursa <bursa@users.sourceforge.net>
3  * Copyright 2003 Phil Mellor <monkeyson@users.sourceforge.net>
4  * Copyright 2004 John Tytgat <joty@netsurf-browser.org>
5  * Copyright 2005-9 John-Mark Bell <jmb@netsurf-browser.org>
6  * Copyright 2009 Paul Blokus <paul_pl@users.sourceforge.net>
7  * Copyright 2010 Michael Drake <tlsa@netsurf-browser.org>
8  *
9  * This file is part of NetSurf, http://www.netsurf-browser.org/
10  *
11  * NetSurf is free software; you can redistribute it and/or modify
12  * it under the terms of the GNU General Public License as published by
13  * the Free Software Foundation; version 2 of the License.
14  *
15  * NetSurf is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
22  */
23 
24 /**
25  * \file
26  * Form handling functions (implementation).
27  */
28 
29 #include <assert.h>
30 #include <limits.h>
31 #include <stdbool.h>
32 #include <stdio.h>
33 #include <string.h>
34 #include <dom/dom.h>
35 
36 #include "utils/corestrings.h"
37 #include "utils/log.h"
38 #include "utils/messages.h"
39 #include "utils/talloc.h"
40 #include "utils/url.h"
41 #include "utils/utf8.h"
42 #include "utils/ascii.h"
43 #include "netsurf/browser_window.h"
44 #include "netsurf/mouse.h"
45 #include "netsurf/plotters.h"
46 #include "netsurf/misc.h"
47 #include "content/fetch.h"
48 #include "content/hlcache.h"
49 #include "css/utils.h"
50 #include "desktop/knockout.h"
51 #include "desktop/scrollbar.h"
52 #include "desktop/textarea.h"
53 #include "desktop/gui_internal.h"
54 
55 #include "html/html.h"
56 #include "html/private.h"
57 #include "html/layout.h"
58 #include "html/box.h"
59 #include "html/box_inspect.h"
60 #include "html/font.h"
61 #include "html/form_internal.h"
62 
63 #define MAX_SELECT_HEIGHT 210
64 #define SELECT_LINE_SPACING 0.2
65 #define SELECT_BORDER_WIDTH 1
66 #define SELECT_SELECTED_COLOUR 0xDB9370
67 
68 struct form_select_menu {
69 	int line_height;
70 	int width, height;
71 	struct scrollbar *scrollbar;
72 	int f_size;
73 	bool scroll_capture;
74 	select_menu_redraw_callback callback;
75 	void *client_data;
76 	struct content *c;
77 };
78 
79 static plot_style_t plot_style_fill_selected = {
80 	.fill_type = PLOT_OP_TYPE_SOLID,
81 	.fill_colour = SELECT_SELECTED_COLOUR,
82 };
83 
84 static plot_font_style_t plot_fstyle_entry = {
85 	.family = PLOT_FONT_FAMILY_SANS_SERIF,
86 	.weight = 400,
87 	.flags = FONTF_NONE,
88 	.background = 0xffffff,
89 	.foreground = 0x000000,
90 };
91 
92 
93 /**
94  * Convert a string from UTF-8 to the specified charset
95  * As a final fallback, this will attempt to convert to ISO-8859-1.
96  *
97  * \todo Return charset used?
98  *
99  * \param item String to convert
100  * \param len Length of string to convert
101  * \param charset Destination charset
102  * \param fallback Fallback charset (may be NULL),
103  *                 used iff converting to charset fails
104  * \return Pointer to converted string (on heap, caller frees), or NULL
105  */
106 static char *
form_encode_item(const char * item,uint32_t len,const char * charset,const char * fallback)107 form_encode_item(const char *item,
108 		 uint32_t len,
109 		 const char *charset,
110 		 const char *fallback)
111 {
112 	nserror err;
113 	char *ret = NULL;
114 	char cset[256];
115 
116 	if (!item || !charset)
117 		return NULL;
118 
119 	snprintf(cset, sizeof cset, "%s//TRANSLIT", charset);
120 
121 	err = utf8_to_enc(item, cset, 0, &ret);
122 	if (err == NSERROR_BAD_ENCODING) {
123 		/* charset not understood, try without transliteration */
124 		snprintf(cset, sizeof cset, "%s", charset);
125 		err = utf8_to_enc(item, cset, len, &ret);
126 
127 		if (err == NSERROR_BAD_ENCODING) {
128 			/* nope, try fallback charset (if any) */
129 			if (fallback) {
130 				snprintf(cset, sizeof cset,
131 						"%s//TRANSLIT", fallback);
132 				err = utf8_to_enc(item, cset, 0, &ret);
133 
134 				if (err == NSERROR_BAD_ENCODING) {
135 					/* and without transliteration */
136 					snprintf(cset, sizeof cset,
137 							"%s", fallback);
138 					err = utf8_to_enc(item, cset, 0, &ret);
139 				}
140 			}
141 
142 			if (err == NSERROR_BAD_ENCODING) {
143 				/* that also failed, use 8859-1 */
144 				err = utf8_to_enc(item, "ISO-8859-1//TRANSLIT",
145 						0, &ret);
146 				if (err == NSERROR_BAD_ENCODING) {
147 					/* and without transliteration */
148 					err = utf8_to_enc(item, "ISO-8859-1",
149 							0, &ret);
150 				}
151 			}
152 		}
153 	}
154 	if (err == NSERROR_NOMEM) {
155 		return NULL;
156 	}
157 
158 	return ret;
159 }
160 
161 
162 /**
163  * string allocation size for numeric values in multipart data
164  */
165 #define FETCH_DATA_INT_VALUE_SIZE 20
166 
167 
168 /**
169  * append split key name and integer value to a multipart data list
170  *
171  * \param name key name
172  * \param ksfx key name suffix
173  * \param value The value to encode
174  * \param fetch_data_next_ptr The multipart data list to append to.
175  */
176 static nserror
fetch_data_list_add_sname(const char * name,const char * ksfx,int value,struct fetch_multipart_data *** fetch_data_next_ptr)177 fetch_data_list_add_sname(const char *name,
178 			  const char *ksfx,
179 			  int value,
180 			  struct fetch_multipart_data ***fetch_data_next_ptr)
181 {
182 	struct fetch_multipart_data *fetch_data;
183 	int keysize;
184 
185 	fetch_data = calloc(1, sizeof(*fetch_data));
186 	if (fetch_data == NULL) {
187 		NSLOG(netsurf, INFO, "failed allocation for fetch data");
188 		return NSERROR_NOMEM;
189 	}
190 
191 	/* key name */
192 	keysize = snprintf(fetch_data->name, 0, "%s%s", name, ksfx);
193 	fetch_data->name = malloc(keysize + 1); /* allow for null */
194 	if (fetch_data->name == NULL) {
195 		free(fetch_data);
196 		NSLOG(netsurf, INFO,
197 		      "keyname allocation failure for %s%s", name, ksfx);
198 		return NSERROR_NOMEM;
199 	}
200 	snprintf(fetch_data->name, keysize + 1, "%s%s", name, ksfx);
201 
202 	/* value */
203 	fetch_data->value = malloc(FETCH_DATA_INT_VALUE_SIZE);
204 	if (fetch_data->value == NULL) {
205 		free(fetch_data->name);
206 		free(fetch_data);
207 		NSLOG(netsurf, INFO, "value allocation failure");
208 		return NSERROR_NOMEM;
209 	}
210 	snprintf(fetch_data->value, FETCH_DATA_INT_VALUE_SIZE, "%d", value);
211 
212 	/* link into list */
213 	**fetch_data_next_ptr = fetch_data;
214 	*fetch_data_next_ptr = &fetch_data->next;
215 
216 	return NSERROR_OK;
217 }
218 
219 
220 /**
221  * append DOM string name/value pair to a multipart data list
222  *
223  * \param name key name
224  * \param value the value to associate with the key
225  * \param rawfile the raw file value to associate with the key.
226  * \param form_charset The form character set
227  * \param docu_charset The document character set for fallback
228  * \param fetch_data_next_ptr The multipart data list being constructed.
229  * \return NSERROR_OK on success or appropriate error code.
230  */
231 static nserror
fetch_data_list_add(dom_string * name,dom_string * value,const char * rawfile,const char * form_charset,const char * docu_charset,struct fetch_multipart_data *** fetch_data_next_ptr)232 fetch_data_list_add(dom_string *name,
233 		    dom_string *value,
234 		    const char *rawfile,
235 		    const char *form_charset,
236 		    const char *docu_charset,
237 		    struct fetch_multipart_data ***fetch_data_next_ptr)
238 {
239 	struct fetch_multipart_data *fetch_data;
240 
241 	assert(name != NULL);
242 
243 	fetch_data = calloc(1, sizeof(*fetch_data));
244 	if (fetch_data == NULL) {
245 		NSLOG(netsurf, INFO, "failed allocation for fetch data");
246 		return NSERROR_NOMEM;
247 	}
248 
249 	fetch_data->name = form_encode_item(dom_string_data(name),
250 					    dom_string_byte_length(name),
251 					    form_charset,
252 					    docu_charset);
253 	if (fetch_data->name == NULL) {
254 		NSLOG(netsurf, INFO, "Could not encode name for fetch data");
255 		free(fetch_data);
256 		return NSERROR_NOMEM;
257 	}
258 
259 	if (value == NULL) {
260 		fetch_data->value = strdup("");
261 	} else {
262 		fetch_data->value = form_encode_item(dom_string_data(value),
263 						     dom_string_byte_length(value),
264 						     form_charset,
265 						     docu_charset);
266 	}
267 	if (fetch_data->value == NULL) {
268 		NSLOG(netsurf, INFO, "Could not encode value for fetch data");
269 		free(fetch_data->name);
270 		free(fetch_data);
271 		return NSERROR_NOMEM;
272 	}
273 
274 	/* deal with raw file name */
275 	if (rawfile != NULL) {
276 		fetch_data->file = true;
277 		fetch_data->rawfile = strdup(rawfile);
278 		if (fetch_data->rawfile == NULL) {
279 			NSLOG(netsurf, INFO,
280 			      "Could not encode rawfile value for fetch data");
281 			free(fetch_data->value);
282 			free(fetch_data->name);
283 			free(fetch_data);
284 			return NSERROR_NOMEM;
285 		}
286 	}
287 
288 	/* link into list */
289 	**fetch_data_next_ptr = fetch_data;
290 	*fetch_data_next_ptr = &fetch_data->next;
291 
292 	return NSERROR_OK;
293 }
294 
295 
296 /**
297  * process form HTMLTextAreaElement into multipart data.
298  *
299  * \param text_area_element The form select DOM element to convert.
300  * \param form_charset The form character set
301  * \param doc_charset The document character set for fallback
302  * \param fetch_data_next_ptr The multipart data list being constructed.
303  * \return NSERROR_OK on success or appropriate error code.
304  */
305 static nserror
form_dom_to_data_textarea(dom_html_text_area_element * text_area_element,const char * form_charset,const char * doc_charset,struct fetch_multipart_data *** fetch_data_next_ptr)306 form_dom_to_data_textarea(dom_html_text_area_element *text_area_element,
307 			  const char *form_charset,
308 			  const char *doc_charset,
309 			  struct fetch_multipart_data ***fetch_data_next_ptr)
310 {
311 	dom_exception exp; /* the result from DOM operations */
312 	bool element_disabled;
313 	dom_string *inputname;
314 	dom_string *inputvalue;
315 	nserror res;
316 
317 	/* check if element is disabled */
318 	exp = dom_html_text_area_element_get_disabled(text_area_element,
319 						      &element_disabled);
320 	if (exp != DOM_NO_ERR) {
321 		NSLOG(netsurf, INFO,
322 		      "Could not get text area disabled property. exp %d", exp);
323 		return NSERROR_DOM;
324 	}
325 
326 	if (element_disabled) {
327 		/* allow enumeration to continue after disabled element */
328 		return NSERROR_OK;
329 	}
330 
331 	/* obtain name property */
332 	exp = dom_html_text_area_element_get_name(text_area_element,
333 						  &inputname);
334 	if (exp != DOM_NO_ERR) {
335 		NSLOG(netsurf, INFO,
336 		      "Could not get text area name property. exp %d", exp);
337 		return NSERROR_DOM;
338 	}
339 
340 	if (inputname == NULL) {
341 		/* allow enumeration to continue after element with no name */
342 		return NSERROR_OK;
343 	}
344 
345 	/* obtain text area value */
346 	exp = dom_html_text_area_element_get_value(text_area_element,
347 						   &inputvalue);
348 	if (exp != DOM_NO_ERR) {
349 		NSLOG(netsurf, INFO,
350 		      "Could not get text area content. exp %d", exp);
351 		dom_string_unref(inputname);
352 		return NSERROR_DOM;
353 	}
354 
355 	/* add key/value pair to fetch data list */
356 	res = fetch_data_list_add(inputname,
357 				  inputvalue,
358 				  NULL,
359 				  form_charset,
360 				  doc_charset,
361 				  fetch_data_next_ptr);
362 
363 	dom_string_unref(inputvalue);
364 	dom_string_unref(inputname);
365 
366 	return res;
367 }
368 
369 
370 static nserror
form_dom_to_data_select_option(dom_html_option_element * option_element,dom_string * keyname,const char * form_charset,const char * docu_charset,struct fetch_multipart_data *** fetch_data_next_ptr)371 form_dom_to_data_select_option(dom_html_option_element *option_element,
372 			      dom_string *keyname,
373 			      const char *form_charset,
374 			      const char *docu_charset,
375 			      struct fetch_multipart_data ***fetch_data_next_ptr)
376 {
377 	nserror res;
378 	dom_exception exp; /* the result from DOM operations */
379 	dom_string *value;
380 	bool selected;
381 
382 	exp = dom_html_option_element_get_selected(option_element, &selected);
383 	if (exp != DOM_NO_ERR) {
384 		NSLOG(netsurf, INFO, "Could not get option selected property");
385 		return NSERROR_DOM;
386 	}
387 
388 	if (!selected) {
389 		/* unselected options do not add fetch data entries */
390 		return NSERROR_OK;
391 	}
392 
393 	exp = dom_html_option_element_get_value(option_element, &value);
394 	if (exp != DOM_NO_ERR) {
395 		NSLOG(netsurf, INFO, "Could not get option value");
396 		return NSERROR_DOM;
397 	}
398 
399 	/* add key/value pair to fetch data list */
400 	res = fetch_data_list_add(keyname,
401 				  value,
402 				  NULL,
403 				  form_charset,
404 				  docu_charset,
405 				  fetch_data_next_ptr);
406 
407 	dom_string_unref(value);
408 
409 	return res;
410 }
411 
412 
413 /**
414  * process form HTMLSelectElement into multipart data.
415  *
416  * \param select_element The form select DOM element to convert.
417  * \param form_charset The form character set
418  * \param doc_charset The document character set for fallback
419  * \param fetch_data_next_ptr The multipart data list being constructed.
420  * \return NSERROR_OK on success or appropriate error code.
421  */
422 static nserror
form_dom_to_data_select(dom_html_select_element * select_element,const char * form_charset,const char * doc_charset,struct fetch_multipart_data *** fetch_data_next_ptr)423 form_dom_to_data_select(dom_html_select_element *select_element,
424 			const char *form_charset,
425 			const char *doc_charset,
426 			struct fetch_multipart_data ***fetch_data_next_ptr)
427 {
428 	nserror res = NSERROR_OK;
429 	dom_exception exp; /* the result from DOM operations */
430 	bool element_disabled;
431 	dom_string *inputname;
432 	dom_html_options_collection *options = NULL;
433 	uint32_t options_count;
434 	uint32_t option_index;
435 	dom_node *option_element = NULL;
436 
437 	/* check if element is disabled */
438 	exp = dom_html_select_element_get_disabled(select_element,
439 						   &element_disabled);
440 	if (exp != DOM_NO_ERR) {
441 		NSLOG(netsurf, INFO,
442 		      "Could not get select disabled property. exp %d", exp);
443 		return NSERROR_DOM;
444 	}
445 
446 	if (element_disabled) {
447 		/* allow enumeration to continue after disabled element */
448 		return NSERROR_OK;
449 	}
450 
451 	/* obtain name property */
452 	exp = dom_html_select_element_get_name(select_element, &inputname);
453 	if (exp != DOM_NO_ERR) {
454 		NSLOG(netsurf, INFO,
455 		      "Could not get select name property. exp %d", exp);
456 		return NSERROR_DOM;
457 	}
458 
459 	if (inputname == NULL) {
460 		/* allow enumeration to continue after element with no name */
461 		return NSERROR_OK;
462 	}
463 
464 	/* get options collection */
465 	exp = dom_html_select_element_get_options(select_element, &options);
466 	if (exp != DOM_NO_ERR) {
467 		NSLOG(netsurf, INFO,
468 		      "Could not get select options collection");
469 		dom_string_unref(inputname);
470 		return NSERROR_DOM;
471 	}
472 
473 	/* get options collection length */
474 	exp = dom_html_options_collection_get_length(options, &options_count);
475 	if (exp != DOM_NO_ERR) {
476 		NSLOG(netsurf, INFO,
477 		      "Could not get select options collection length");
478 		dom_html_options_collection_unref(options);
479 		dom_string_unref(inputname);
480 		return NSERROR_DOM;
481 	}
482 
483 	/* iterate over options collection */
484 	for (option_index = 0; option_index < options_count; ++option_index) {
485 		exp = dom_html_options_collection_item(options,
486 						       option_index,
487 						       &option_element);
488 		if (exp != DOM_NO_ERR) {
489 			NSLOG(netsurf, INFO,
490 			      "Could not get options item %d", option_index);
491 			res = NSERROR_DOM;
492 		} else {
493 			res = form_dom_to_data_select_option(
494 				(dom_html_option_element *)option_element,
495 				inputname,
496 				form_charset,
497 				doc_charset,
498 				fetch_data_next_ptr);
499 
500 			dom_node_unref(option_element);
501 		}
502 
503 		if (res != NSERROR_OK) {
504 			break;
505 		}
506 	}
507 
508 	dom_html_options_collection_unref(options);
509 	dom_string_unref(inputname);
510 
511 	return res;
512 }
513 
514 
515 static nserror
form_dom_to_data_input_submit(dom_html_input_element * input_element,dom_string * inputname,const char * charset,const char * document_charset,dom_html_element ** submit_button,struct fetch_multipart_data *** fetch_data_next_ptr)516 form_dom_to_data_input_submit(dom_html_input_element *input_element,
517 			      dom_string *inputname,
518 			      const char *charset,
519 			      const char *document_charset,
520 			      dom_html_element **submit_button,
521 			      struct fetch_multipart_data ***fetch_data_next_ptr)
522 {
523 	dom_exception exp; /* the result from DOM operations */
524 	dom_string *inputvalue;
525 	nserror res;
526 
527 	if (*submit_button == NULL) {
528 		/* caller specified no button so use this one */
529 		*submit_button = (dom_html_element *)input_element;
530 	} else if (*submit_button != (dom_html_element *)input_element) {
531 		return NSERROR_OK;
532 	}
533 
534 	/* matched button used to submit form */
535 	exp = dom_html_input_element_get_value(input_element, &inputvalue);
536 	if (exp != DOM_NO_ERR) {
537 		NSLOG(netsurf, INFO, "Could not get submit button value");
538 		return NSERROR_DOM;
539 	}
540 
541 	/* add key/value pair to fetch data list */
542 	res = fetch_data_list_add(inputname,
543 				  inputvalue,
544 				  NULL,
545 				  charset,
546 				  document_charset,
547 				  fetch_data_next_ptr);
548 
549 	dom_string_unref(inputvalue);
550 
551 	return res;
552 }
553 
554 
555 static nserror
form_dom_to_data_input_image(dom_html_input_element * input_element,dom_string * inputname,const char * charset,const char * document_charset,dom_html_element ** submit_button,struct fetch_multipart_data *** fetch_data_next_ptr)556 form_dom_to_data_input_image(dom_html_input_element *input_element,
557 			     dom_string *inputname,
558 			     const char *charset,
559 			     const char *document_charset,
560 			     dom_html_element **submit_button,
561 			     struct fetch_multipart_data ***fetch_data_next_ptr)
562 {
563 	nserror res;
564 	dom_exception exp; /* the result from DOM operations */
565 	struct image_input_coords *coords;
566 	char *basename;
567 
568 	/* Only use an image input if it was the thing which activated us */
569 	if (*submit_button != (dom_html_element *)input_element) {
570 		return NSERROR_OK;
571 	}
572 
573 	exp = dom_node_get_user_data((dom_node *)input_element,
574 				     corestring_dom___ns_key_image_coords_node_data,
575 				     &coords);
576 	if (exp != DOM_NO_ERR) {
577 		NSLOG(netsurf, INFO, "Could not get image XY data");
578 		return NSERROR_DOM;
579 	}
580 
581 	if (coords == NULL) {
582 		NSLOG(netsurf, INFO, "No XY data on the image input");
583 		return NSERROR_DOM;
584 	}
585 
586 	/* encode input name once */
587 	basename = form_encode_item(dom_string_data(inputname),
588 				    dom_string_byte_length(inputname),
589 				    charset,
590 				    document_charset);
591 	if (basename == NULL) {
592 		NSLOG(netsurf, INFO, "Could not encode basename");
593 		return NSERROR_NOMEM;
594 	}
595 
596 	res = fetch_data_list_add_sname(basename, ".x",
597 					coords->x,
598 					fetch_data_next_ptr);
599 
600 	if (res == NSERROR_OK) {
601 		res = fetch_data_list_add_sname(basename, ".y",
602 						coords->y,
603 						fetch_data_next_ptr);
604 	}
605 
606 	free(basename);
607 
608 	return res;
609 }
610 
611 
612 static nserror
form_dom_to_data_input_checkbox(dom_html_input_element * input_element,dom_string * inputname,const char * charset,const char * document_charset,struct fetch_multipart_data *** fetch_data_next_ptr)613 form_dom_to_data_input_checkbox(dom_html_input_element *input_element,
614 				dom_string *inputname,
615 				const char *charset,
616 				const char *document_charset,
617 				struct fetch_multipart_data ***fetch_data_next_ptr)
618 {
619 	nserror res;
620 	dom_exception exp; /* the result from DOM operations */
621 	bool checked;
622 	dom_string *inputvalue;
623 
624 	exp = dom_html_input_element_get_checked(input_element, &checked);
625 	if (exp != DOM_NO_ERR) {
626 		NSLOG(netsurf, INFO,
627 		      "Could not get input element checked");
628 		return NSERROR_DOM;
629 	}
630 
631 	if (!checked) {
632 		/* unchecked items do not generate a data entry */
633 		return NSERROR_OK;
634 	}
635 
636 	exp = dom_html_input_element_get_value(input_element, &inputvalue);
637 	if (exp != DOM_NO_ERR) {
638 		NSLOG(netsurf, INFO,
639 		      "Could not get input element value");
640 		return NSERROR_DOM;
641 	}
642 
643 	/* ensure a default value */
644 	if (inputvalue == NULL) {
645 		inputvalue = dom_string_ref(corestring_dom_on);
646 	}
647 
648 	/* add key/value pair to fetch data list */
649 	res = fetch_data_list_add(inputname,
650 				  inputvalue,
651 				  NULL,
652 				  charset,
653 				  document_charset,
654 				  fetch_data_next_ptr);
655 
656 	dom_string_unref(inputvalue);
657 
658 	return res;
659 }
660 
661 
662 static nserror
form_dom_to_data_input_file(dom_html_input_element * input_element,dom_string * inputname,const char * charset,const char * document_charset,struct fetch_multipart_data *** fetch_data_next_ptr)663 form_dom_to_data_input_file(dom_html_input_element *input_element,
664 			    dom_string *inputname,
665 			    const char *charset,
666 			    const char *document_charset,
667 			    struct fetch_multipart_data ***fetch_data_next_ptr)
668 {
669 	nserror res;
670 	dom_exception exp; /* the result from DOM operations */
671 	dom_string *inputvalue;
672 	const char *rawfile = NULL;
673 
674 	exp = dom_html_input_element_get_value(input_element, &inputvalue);
675 	if (exp != DOM_NO_ERR) {
676 		NSLOG(netsurf, INFO, "Could not get file value");
677 		return NSERROR_DOM;
678 	}
679 
680 	exp = dom_node_get_user_data((dom_node *)input_element,
681 				     corestring_dom___ns_key_file_name_node_data,
682 				     &rawfile);
683 	if (exp != DOM_NO_ERR) {
684 		NSLOG(netsurf, INFO, "Could not get file rawname");
685 		return NSERROR_DOM;
686 	}
687 
688 	if (rawfile == NULL) {
689 		rawfile = "";
690 	}
691 
692 	/* add key/value pair to fetch data list */
693 	res = fetch_data_list_add(inputname,
694 				  inputvalue,
695 				  rawfile,
696 				  charset,
697 				  document_charset,
698 				  fetch_data_next_ptr);
699 
700 	dom_string_unref(inputvalue);
701 
702 	return res;
703 }
704 
705 
706 static nserror
form_dom_to_data_input_text(dom_html_input_element * input_element,dom_string * inputname,const char * charset,const char * document_charset,struct fetch_multipart_data *** fetch_data_next_ptr)707 form_dom_to_data_input_text(dom_html_input_element *input_element,
708 			    dom_string *inputname,
709 			    const char *charset,
710 			    const char *document_charset,
711 			    struct fetch_multipart_data ***fetch_data_next_ptr)
712 {
713 	nserror res;
714 	dom_exception exp; /* the result from DOM operations */
715 	dom_string *inputvalue;
716 
717 	exp = dom_html_input_element_get_value(input_element, &inputvalue);
718 	if (exp != DOM_NO_ERR) {
719 		NSLOG(netsurf, INFO, "Could not get input value");
720 		return NSERROR_DOM;
721 	}
722 
723 	/* add key/value pair to fetch data list */
724 	res = fetch_data_list_add(inputname,
725 				  inputvalue,
726 				  NULL,
727 				  charset,
728 				  document_charset,
729 				  fetch_data_next_ptr);
730 
731 	dom_string_unref(inputvalue);
732 
733 	return res;
734 }
735 
736 
737 /**
738  * process form input element into multipart data.
739  *
740  * \param input_element The form input DOM element to convert.
741  * \param charset The form character set
742  * \param document_charset The document character set for fallback
743  * \param submit_button The DOM element of the button submitting the form
744  * \param had_submit A boolean value indicating if the submit button
745  *                   has already been processed in the form element enumeration.
746  * \param fetch_data_next_ptr The multipart data list being constructed.
747  * \return NSERROR_OK on success or appropriate error code.
748  */
749 static nserror
form_dom_to_data_input(dom_html_input_element * input_element,const char * charset,const char * document_charset,dom_html_element ** submit_button,struct fetch_multipart_data *** fetch_data_next_ptr)750 form_dom_to_data_input(dom_html_input_element *input_element,
751 		       const char *charset,
752 		       const char *document_charset,
753 		       dom_html_element **submit_button,
754 		       struct fetch_multipart_data ***fetch_data_next_ptr)
755 {
756 	dom_exception exp; /* the result from DOM operations */
757 	bool element_disabled;
758 	dom_string *inputname;
759 	dom_string *inputtype;
760 	nserror res;
761 
762 	/* check if element is disabled */
763 	exp = dom_html_input_element_get_disabled(input_element,
764 						  &element_disabled);
765 	if (exp != DOM_NO_ERR) {
766 		NSLOG(netsurf, INFO,
767 		      "Could not get input disabled property. exp %d", exp);
768 		return NSERROR_DOM;
769 	}
770 
771 	if (element_disabled) {
772 		/* disabled element requires no more processing */
773 		return NSERROR_OK;
774 	}
775 
776 	/* obtain name property */
777 	exp = dom_html_input_element_get_name(input_element, &inputname);
778 	if (exp != DOM_NO_ERR) {
779 		NSLOG(netsurf, INFO,
780 		      "Could not get input name property. exp %d", exp);
781 		return NSERROR_DOM;
782 	}
783 
784 	if (inputname == NULL) {
785 		/* element with no name is not converted */
786 		return NSERROR_OK;
787 	}
788 
789 	/* get input type */
790 	exp = dom_html_input_element_get_type(input_element, &inputtype);
791 	if (exp != DOM_NO_ERR) {
792 		NSLOG(netsurf, INFO, "Could not get input element type");
793 		dom_string_unref(inputname);
794 		return NSERROR_DOM;
795 	}
796 
797 	/* process according to input element type */
798 	if (dom_string_caseless_isequal(inputtype, corestring_dom_submit)) {
799 
800 		res = form_dom_to_data_input_submit(input_element,
801 						    inputname,
802 						    charset,
803 						    document_charset,
804 						    submit_button,
805 						    fetch_data_next_ptr);
806 
807 	} else if (dom_string_caseless_isequal(inputtype,
808 					       corestring_dom_image)) {
809 
810 		res = form_dom_to_data_input_image(input_element,
811 						   inputname,
812 						   charset,
813 						   document_charset,
814 						   submit_button,
815 						   fetch_data_next_ptr);
816 
817 	} else if (dom_string_caseless_isequal(inputtype,
818 					       corestring_dom_radio) ||
819 		   dom_string_caseless_isequal(inputtype,
820 					       corestring_dom_checkbox)) {
821 
822 		res = form_dom_to_data_input_checkbox(input_element,
823 						      inputname,
824 						      charset,
825 						      document_charset,
826 						      fetch_data_next_ptr);
827 
828 	} else if (dom_string_caseless_isequal(inputtype,
829 					       corestring_dom_file)) {
830 
831 		res = form_dom_to_data_input_file(input_element,
832 						  inputname,
833 						  charset,
834 						  document_charset,
835 						  fetch_data_next_ptr);
836 
837 	} else if (dom_string_caseless_isequal(inputtype,
838 					       corestring_dom_reset) ||
839 		   dom_string_caseless_isequal(inputtype,
840 					       corestring_dom_button)) {
841 		/* Skip these */
842 		NSLOG(netsurf, INFO, "Skipping RESET and BUTTON");
843 		res = NSERROR_OK;
844 
845 	} else {
846 		/* Everything else is treated as text values */
847 		res = form_dom_to_data_input_text(input_element,
848 						  inputname,
849 						  charset,
850 						  document_charset,
851 						  fetch_data_next_ptr);
852 
853 	}
854 
855 	dom_string_unref(inputtype);
856 	dom_string_unref(inputname);
857 
858 	return res;
859 }
860 
861 
862 /**
863  * process form HTMLButtonElement into multipart data.
864  *
865  * https://html.spec.whatwg.org/multipage/form-elements.html#the-button-element
866  *
867  * \param button_element The form button DOM element to convert.
868  * \param form_charset The form character set
869  * \param doc_charset The document character set for fallback
870  * \param submit_button The DOM element of the button submitting the form
871  * \param fetch_data_next_ptr The multipart data list being constructed.
872  * \return NSERROR_OK on success or appropriate error code.
873  */
874 static nserror
form_dom_to_data_button(dom_html_button_element * button_element,const char * form_charset,const char * doc_charset,dom_html_element ** submit_button,struct fetch_multipart_data *** fetch_data_next_ptr)875 form_dom_to_data_button(dom_html_button_element *button_element,
876 			const char *form_charset,
877 			const char *doc_charset,
878 			dom_html_element **submit_button,
879 			struct fetch_multipart_data ***fetch_data_next_ptr)
880 {
881 	dom_exception exp; /* the result from DOM operations */
882 	bool element_disabled;
883 	dom_string *inputname;
884 	dom_string *inputvalue;
885 	dom_string *inputtype;
886 	nserror res = NSERROR_OK;
887 
888 	/* check if element is disabled */
889 	exp = dom_html_button_element_get_disabled(button_element,
890 						   &element_disabled);
891 	if (exp != DOM_NO_ERR) {
892 		NSLOG(netsurf, INFO,
893 		      "Unable to get disabled property. exp %d", exp);
894 		return NSERROR_DOM;
895 	}
896 
897 	if (element_disabled) {
898 		/* allow enumeration to continue after disabled element */
899 		return NSERROR_OK;
900 	}
901 
902 	/* get the type attribute */
903 	exp = dom_html_button_element_get_type(button_element, &inputtype);
904 	if (exp != DOM_NO_ERR) {
905 		NSLOG(netsurf, INFO, "Could not get button element type");
906 		return NSERROR_DOM;
907 	}
908 
909 	/* If the type attribute is "reset" or "button" the element is
910 	 *  barred from constraint validation. Specification says
911 	 *  default and invalid values result in submit which will
912 	 *  be considered.
913 	 */
914 	if (dom_string_caseless_isequal(inputtype, corestring_dom_reset)) {
915 		/* multipart data entry not required for reset type */
916 		dom_string_unref(inputtype);
917 		return NSERROR_OK;
918 	}
919 	if (dom_string_caseless_isequal(inputtype, corestring_dom_button)) {
920 		/* multipart data entry not required for button type */
921 		dom_string_unref(inputtype);
922 		return NSERROR_OK;
923 	}
924 	dom_string_unref(inputtype);
925 
926 	/* only submision button generates an element */
927 	if (*submit_button == NULL) {
928 		/* no submission button selected yet so use this one */
929 		*submit_button = (dom_html_element *)button_element;
930 	}
931 	if (*submit_button != (dom_html_element *)button_element) {
932 		return NSERROR_OK;
933 	}
934 
935 	/* obtain name property */
936 	exp = dom_html_button_element_get_name(button_element, &inputname);
937 	if (exp != DOM_NO_ERR) {
938 		NSLOG(netsurf, INFO,
939 		      "Could not get button name property. exp %d", exp);
940 		return NSERROR_DOM;
941 	}
942 
943 	if (inputname == NULL) {
944 		/* allow enumeration to continue after element with no name */
945 		return NSERROR_OK;
946 	}
947 
948 	/* get button value and add to fetch data list */
949 	exp = dom_html_button_element_get_value(button_element, &inputvalue);
950 	if (exp != DOM_NO_ERR) {
951 		NSLOG(netsurf, INFO, "Could not get submit button value");
952 		res = NSERROR_DOM;
953 	} else {
954 		res = fetch_data_list_add(inputname,
955 					  inputvalue,
956 					  NULL,
957 					  form_charset,
958 					  doc_charset,
959 					  fetch_data_next_ptr);
960 
961 		dom_string_unref(inputvalue);
962 	}
963 
964 	dom_string_unref(inputname);
965 
966 	return res;
967 }
968 
969 
970 /**
971  * Find an acceptable character set encoding with which to submit the form
972  *
973  * \param form  The form
974  * \return Pointer to charset name (on heap, caller should free) or NULL
975  */
form_acceptable_charset(struct form * form)976 static char *form_acceptable_charset(struct form *form)
977 {
978 	char *temp, *c;
979 
980 	if (!form->accept_charsets) {
981 		/* no accept-charsets attribute for this form */
982 		if (form->document_charset) {
983 			/* document charset present, so use it */
984 			return strdup(form->document_charset);
985 		} else {
986 			/* no document charset, so default to 8859-1 */
987 			return strdup("ISO-8859-1");
988 		}
989 	}
990 
991 	/* make temporary copy of accept-charsets attribute */
992 	temp = strdup(form->accept_charsets);
993 	if (!temp)
994 		return NULL;
995 
996 	/* make it upper case */
997 	for (c = temp; *c; c++) {
998 		*c = ascii_to_upper(*c);
999 	}
1000 
1001 	/* is UTF-8 specified? */
1002 	c = strstr(temp, "UTF-8");
1003 	if (c) {
1004 		free(temp);
1005 		return strdup("UTF-8");
1006 	}
1007 
1008 	/* dispense with temporary copy */
1009 	free(temp);
1010 
1011 	/* according to RFC2070, the accept-charsets attribute of the
1012 	 * form element contains a space and/or comma separated list */
1013 	c = form->accept_charsets;
1014 
1015 	/** \todo an improvement would be to choose an encoding
1016 	 * acceptable to the server which covers as much of the input
1017 	 * values as possible. Additionally, we need to handle the
1018 	 * case where none of the acceptable encodings cover all the
1019 	 * textual input values.  For now, we just extract the first
1020 	 * element of the charset list
1021 	 */
1022 	while (*c && !ascii_is_space(*c)) {
1023 		if (*c == ',')
1024 			break;
1025 		c++;
1026 	}
1027 
1028 	return strndup(form->accept_charsets, c - form->accept_charsets);
1029 }
1030 
1031 
1032 /**
1033  * Construct multipart data list from 'successful' controls via the DOM.
1034  *
1035  * All text strings in the successful controls list will be in the charset most
1036  * appropriate for submission. Therefore, no utf8_to_* processing should be
1037  * performed upon them.
1038  *
1039  * \todo The chosen charset needs to be made available such that it can be
1040  * included in the submission request (e.g. in the fetch's Content-Type header)
1041  *
1042  * See HTML 4.01 section 17.13.2.
1043  *
1044  * \note care is taken to abort even if the error is recoverable as it
1045  *       is not desirable to submit incomplete form data.
1046  *
1047  * \param[in] form form to search for successful controls
1048  * \param[in] submit_button control used to submit the form, if any
1049  * \param[out] fetch_data_out updated to point to linked list of
1050  *                             fetch_multipart_data, NULL if no controls
1051  * \return NSERROR_OK on success or appropriate error code
1052  */
1053 static nserror
form_dom_to_data(struct form * form,struct form_control * submit_control,struct fetch_multipart_data ** fetch_data_out)1054 form_dom_to_data(struct form *form,
1055 		 struct form_control *submit_control,
1056 		 struct fetch_multipart_data **fetch_data_out)
1057 {
1058 	nserror res = NSERROR_OK;
1059 	char *charset; /* form characterset */
1060 	dom_exception exp; /* the result from DOM operations */
1061 	dom_html_collection *elements = NULL; /* the dom form elements */
1062 	uint32_t element_count; /* the number of elements in the DOM form */
1063 	uint32_t element_idx; /* the index of thr enumerated element */
1064 	dom_node *element = NULL; /* the DOM form element */
1065 	dom_string *nodename = NULL; /* the DOM node name of the element */
1066 	struct fetch_multipart_data *fetch_data = NULL; /* fetch data list */
1067 	struct fetch_multipart_data **fetch_data_next = &fetch_data;
1068 	dom_html_element *submit_button;
1069 
1070 	/* obtain the submit_button DOM node from the control */
1071 	if (submit_control != NULL) {
1072 		submit_button = submit_control->node;
1073 	} else {
1074 		submit_button = NULL;
1075 	}
1076 
1077 	/** \todo Replace this call with something DOMish */
1078 	charset = form_acceptable_charset(form);
1079 	if (charset == NULL) {
1080 		NSLOG(netsurf, INFO, "failed to find charset");
1081 		return NSERROR_NOMEM;
1082 	}
1083 
1084 	/* obtain the form elements and count */
1085 	exp = dom_html_form_element_get_elements(form->node, &elements);
1086 	if (exp != DOM_NO_ERR) {
1087 		NSLOG(netsurf, INFO, "Could not get form elements");
1088 		free(charset);
1089 		return NSERROR_DOM;
1090 	}
1091 
1092 	exp = dom_html_collection_get_length(elements, &element_count);
1093 	if (exp != DOM_NO_ERR) {
1094 		NSLOG(netsurf, INFO, "Could not get form element count");
1095 		res = NSERROR_DOM;
1096 		goto form_dom_to_data_error;
1097 	}
1098 
1099 	for (element_idx = 0; element_idx < element_count; element_idx++) {
1100 		/* obtain a form element */
1101 		exp = dom_html_collection_item(elements, element_idx, &element);
1102 		if (exp != DOM_NO_ERR) {
1103 			NSLOG(netsurf, INFO,
1104 			      "retrieving form element %d failed with %d",
1105 			      element_idx, exp);
1106 			res = NSERROR_DOM;
1107 			goto form_dom_to_data_error;
1108 		}
1109 
1110 		/* node name from element */
1111 		exp = dom_node_get_node_name(element, &nodename);
1112 		if (exp != DOM_NO_ERR) {
1113 			NSLOG(netsurf, INFO,
1114 			      "getting element node name %d failed with %d",
1115 			      element_idx, exp);
1116 			dom_node_unref(element);
1117 			res = NSERROR_DOM;
1118 			goto form_dom_to_data_error;
1119 		}
1120 
1121 		if (dom_string_isequal(nodename, corestring_dom_TEXTAREA)) {
1122 			/* Form element is HTMLTextAreaElement */
1123 			res = form_dom_to_data_textarea(
1124 				(dom_html_text_area_element *)element,
1125 				charset,
1126 				form->document_charset,
1127 				&fetch_data_next);
1128 
1129 		} else if (dom_string_isequal(nodename, corestring_dom_SELECT)) {
1130 			/* Form element is HTMLSelectElement */
1131 			res = form_dom_to_data_select(
1132 				(dom_html_select_element *)element,
1133 				charset,
1134 				form->document_charset,
1135 				&fetch_data_next);
1136 
1137 		} else if (dom_string_isequal(nodename, corestring_dom_INPUT)) {
1138 			/* Form element is HTMLInputElement */
1139 			res = form_dom_to_data_input(
1140 				(dom_html_input_element *)element,
1141 				charset,
1142 				form->document_charset,
1143 				&submit_button,
1144 				&fetch_data_next);
1145 
1146 		} else if (dom_string_isequal(nodename, corestring_dom_BUTTON)) {
1147 			/* Form element is HTMLButtonElement */
1148 			res = form_dom_to_data_button(
1149 				(dom_html_button_element *)element,
1150 				charset,
1151 				form->document_charset,
1152 				&submit_button,
1153 				&fetch_data_next);
1154 
1155 		} else {
1156 			/* Form element is not handled */
1157 			NSLOG(netsurf, INFO,
1158 			      "Unhandled element type: %*s",
1159 			      (int)dom_string_byte_length(nodename),
1160 			      dom_string_data(nodename));
1161 			res = NSERROR_DOM;
1162 
1163 		}
1164 
1165 		dom_string_unref(nodename);
1166 		dom_node_unref(element);
1167 
1168 		/* abort form element enumeration on error */
1169 		if (res != NSERROR_OK) {
1170 			goto form_dom_to_data_error;
1171 		}
1172 	}
1173 
1174 	*fetch_data_out = fetch_data;
1175 	dom_html_collection_unref(elements);
1176 	free(charset);
1177 
1178 	return NSERROR_OK;
1179 
1180 form_dom_to_data_error:
1181 	fetch_multipart_data_destroy(fetch_data);
1182 	dom_html_collection_unref(elements);
1183 	free(charset);
1184 
1185 	return res;
1186 }
1187 
1188 /**
1189  * Encode controls using application/x-www-form-urlencoded.
1190  *
1191  * \param[in] form form to which successful controls relate
1192  * \param[in] control linked list of fetch_multipart_data
1193  * \param[out] encoded_out URL-encoded form data
1194  * \return NSERROR_OK on success and \a encoded_out updated else appropriate error code
1195  */
1196 static nserror
form_url_encode(struct form * form,struct fetch_multipart_data * control,char ** encoded_out)1197 form_url_encode(struct form *form,
1198 		struct fetch_multipart_data *control,
1199 		char **encoded_out)
1200 {
1201 	char *name, *value;
1202 	char *s, *s2;
1203 	unsigned int len, len1, len_init;
1204 	nserror res;
1205 
1206 	s = malloc(1);
1207 
1208 	if (s == NULL) {
1209 		return NSERROR_NOMEM;
1210 	}
1211 
1212 	s[0] = '\0';
1213 	len_init = len = 0;
1214 
1215 	for (; control; control = control->next) {
1216 		res = url_escape(control->name, true, NULL, &name);
1217 		if (res != NSERROR_OK) {
1218 			free(s);
1219 			return res;
1220 		}
1221 
1222 		res = url_escape(control->value, true, NULL, &value);
1223 		if (res != NSERROR_OK) {
1224 			free(name);
1225 			free(s);
1226 			return res;
1227 		}
1228 
1229 		/* resize string to allow for new key/value pair,
1230 		 *  equals, amphersand and terminator
1231 		 */
1232 		len1 = len + strlen(name) + strlen(value) + 2;
1233 		s2 = realloc(s, len1 + 1);
1234 		if (s2 == NULL) {
1235 			free(value);
1236 			free(name);
1237 			free(s);
1238 			return NSERROR_NOMEM;
1239 		}
1240 		s = s2;
1241 
1242 		snprintf(s + len, (len1 + 1) - len, "%s=%s&", name, value);
1243 		len = len1;
1244 		free(name);
1245 		free(value);
1246 	}
1247 
1248 	if (len > len_init) {
1249 		/* Replace trailing '&' */
1250 		s[len - 1] = '\0';
1251 	}
1252 
1253 	*encoded_out = s;
1254 
1255 	return NSERROR_OK;
1256 }
1257 
1258 
1259 /**
1260  * Callback for the select menus scroll
1261  */
1262 static void
form_select_menu_scroll_callback(void * client_data,struct scrollbar_msg_data * scrollbar_data)1263 form_select_menu_scroll_callback(void *client_data,
1264 				 struct scrollbar_msg_data *scrollbar_data)
1265 {
1266 	struct form_control *control = client_data;
1267 	struct form_select_menu *menu = control->data.select.menu;
1268 	html_content *html = (html_content *)menu->c;
1269 
1270 	switch (scrollbar_data->msg) {
1271 		case SCROLLBAR_MSG_MOVED:
1272 			menu->callback(menu->client_data,
1273 					0, 0,
1274 					menu->width,
1275 					menu->height);
1276 			break;
1277 		case SCROLLBAR_MSG_SCROLL_START:
1278 		{
1279 			struct rect rect = {
1280 				.x0 = scrollbar_data->x0,
1281 				.y0 = scrollbar_data->y0,
1282 				.x1 = scrollbar_data->x1,
1283 				.y1 = scrollbar_data->y1
1284 			};
1285 
1286 			browser_window_set_drag_type(html->bw,
1287 					DRAGGING_CONTENT_SCROLLBAR, &rect);
1288 
1289 			menu->scroll_capture = true;
1290 		}
1291 			break;
1292 		case SCROLLBAR_MSG_SCROLL_FINISHED:
1293 			menu->scroll_capture = false;
1294 
1295 			browser_window_set_drag_type(html->bw,
1296 					DRAGGING_NONE, NULL);
1297 			break;
1298 		default:
1299 			break;
1300 	}
1301 }
1302 
1303 
1304 /**
1305  * Process a selection from a form select menu.
1306  *
1307  * \param  html The html content handle for the form
1308  * \param  control  form control with menu
1309  * \param  item	    index of item selected from the menu
1310  * \return NSERROR_OK or appropriate error code.
1311  */
1312 static nserror
form__select_process_selection(html_content * html,struct form_control * control,int item)1313 form__select_process_selection(html_content *html,
1314 			       struct form_control *control,
1315 			       int item)
1316 {
1317 	struct box *inline_box;
1318 	struct form_option *o;
1319 	int count;
1320 	nserror ret = NSERROR_OK;
1321 
1322 	assert(control != NULL);
1323 	assert(html != NULL);
1324 
1325 	/**
1326 	 * \todo Even though the form code is effectively part of the html
1327 	 *        content handler, poking around inside contents is not good
1328 	 */
1329 
1330 	inline_box = control->box->children->children;
1331 
1332 	for (count = 0, o = control->data.select.items;
1333 			o != NULL;
1334 			count++, o = o->next) {
1335 		if (!control->data.select.multiple && o->selected) {
1336 			o->selected = false;
1337 			dom_html_option_element_set_selected(o->node, false);
1338 		}
1339 
1340 		if (count == item) {
1341 			if (control->data.select.multiple) {
1342 				if (o->selected) {
1343 					o->selected = false;
1344 					dom_html_option_element_set_selected(
1345 							o->node, false);
1346 					control->data.select.num_selected--;
1347 				} else {
1348 					o->selected = true;
1349 					dom_html_option_element_set_selected(
1350 							o->node, true);
1351 					control->data.select.num_selected++;
1352 				}
1353 			} else {
1354 				dom_html_option_element_set_selected(
1355 						o->node, true);
1356 				o->selected = true;
1357 			}
1358 		}
1359 
1360 		if (o->selected) {
1361 			control->data.select.current = o;
1362 		}
1363 	}
1364 
1365 	talloc_free(inline_box->text);
1366 	inline_box->text = 0;
1367 
1368 	if (control->data.select.num_selected == 0) {
1369 		inline_box->text = talloc_strdup(html->bctx,
1370 				messages_get("Form_None"));
1371 	} else if (control->data.select.num_selected == 1) {
1372 		inline_box->text = talloc_strdup(html->bctx,
1373 				control->data.select.current->text);
1374 	} else {
1375 		inline_box->text = talloc_strdup(html->bctx,
1376 				messages_get("Form_Many"));
1377 	}
1378 
1379 	if (!inline_box->text) {
1380 		ret = NSERROR_NOMEM;
1381 		inline_box->length = 0;
1382 	} else {
1383 		inline_box->length = strlen(inline_box->text);
1384 	}
1385 	inline_box->width = control->box->width;
1386 
1387 	html__redraw_a_box(html, control->box);
1388 
1389 	return ret;
1390 }
1391 
1392 
1393 /**
1394  * Handle a click on the area of the currently opened select menu.
1395  *
1396  * \param control the select menu which received the click
1397  * \param x X coordinate of click
1398  * \param y Y coordinate of click
1399  */
form_select_menu_clicked(struct form_control * control,int x,int y)1400 static void form_select_menu_clicked(struct form_control *control, int x, int y)
1401 {
1402 	struct form_select_menu *menu = control->data.select.menu;
1403 	struct form_option *option;
1404 	html_content *html = (html_content *)menu->c;
1405 	int line_height, line_height_with_spacing;
1406 	int item_bottom_y;
1407 	int scroll, i;
1408 
1409 	scroll = scrollbar_get_offset(menu->scrollbar);
1410 
1411 	line_height = menu->line_height;
1412 	line_height_with_spacing = line_height +
1413 			line_height * SELECT_LINE_SPACING;
1414 
1415 	option = control->data.select.items;
1416 	item_bottom_y = line_height_with_spacing;
1417 	i = 0;
1418 	while (option && item_bottom_y < scroll + y) {
1419 		item_bottom_y += line_height_with_spacing;
1420 		option = option->next;
1421 		i++;
1422 	}
1423 
1424 	if (option != NULL) {
1425 		form__select_process_selection(html, control, i);
1426 	}
1427 
1428 	menu->callback(menu->client_data, 0, 0, menu->width, menu->height);
1429 }
1430 
1431 
1432 /* exported interface documented in html/form_internal.h */
form_add_control(struct form * form,struct form_control * control)1433 void form_add_control(struct form *form, struct form_control *control)
1434 {
1435 	if (form == NULL) {
1436 		return;
1437 	}
1438 
1439 	control->form = form;
1440 
1441 	if (form->controls != NULL) {
1442 		assert(form->last_control);
1443 
1444 		form->last_control->next = control;
1445 		control->prev = form->last_control;
1446 		control->next = NULL;
1447 		form->last_control = control;
1448 	} else {
1449 		form->controls = form->last_control = control;
1450 	}
1451 }
1452 
1453 
1454 /* exported interface documented in html/form_internal.h */
form_free_control(struct form_control * control)1455 void form_free_control(struct form_control *control)
1456 {
1457 	struct form_control *c;
1458 	assert(control != NULL);
1459 
1460 	NSLOG(netsurf, INFO, "Control:%p name:%p value:%p initial:%p",
1461 	      control, control->name, control->value, control->initial_value);
1462 	free(control->name);
1463 	free(control->value);
1464 	free(control->initial_value);
1465 	if (control->last_synced_value != NULL) {
1466 		free(control->last_synced_value);
1467 	}
1468 
1469 	if (control->type == GADGET_SELECT) {
1470 		struct form_option *option, *next;
1471 
1472 		for (option = control->data.select.items; option;
1473 				option = next) {
1474 			next = option->next;
1475 			NSLOG(netsurf, INFO,
1476 			      "select option:%p text:%p value:%p", option,
1477 			      option->text, option->value);
1478 			free(option->text);
1479 			free(option->value);
1480 			free(option);
1481 		}
1482 		if (control->data.select.menu != NULL) {
1483 			form_free_select_menu(control);
1484 		}
1485 	}
1486 
1487 	if (control->type == GADGET_TEXTAREA ||
1488 			control->type == GADGET_TEXTBOX ||
1489 			control->type == GADGET_PASSWORD) {
1490 
1491 		if (control->data.text.initial != NULL) {
1492 			dom_string_unref(control->data.text.initial);
1493 		}
1494 
1495 		if (control->data.text.ta != NULL) {
1496 			textarea_destroy(control->data.text.ta);
1497 		}
1498 	}
1499 
1500 	/* unlink the control from the form */
1501 	if (control->form != NULL) {
1502 		for (c = control->form->controls; c != NULL; c = c->next) {
1503 			if (c->next == control) {
1504 				c->next = control->next;
1505 				if (control->form->last_control == control)
1506 					control->form->last_control = c;
1507 				break;
1508 			}
1509 			if (c == control) {
1510 				/* can only happen if control was first control */
1511 				control->form->controls = control->next;
1512 				if (control->form->last_control == control)
1513 					control->form->controls =
1514 						control->form->last_control = NULL;
1515 				break;
1516 			}
1517 		}
1518 	}
1519 
1520 	if (control->node_value != NULL) {
1521 		dom_string_unref(control->node_value);
1522 	}
1523 
1524 	free(control);
1525 }
1526 
1527 
1528 /* exported interface documented in html/form_internal.h */
form_add_option(struct form_control * control,char * value,char * text,bool selected,void * node)1529 bool form_add_option(struct form_control *control, char *value, char *text,
1530 		     bool selected, void *node)
1531 {
1532 	struct form_option *option;
1533 
1534 	assert(control);
1535 	assert(control->type == GADGET_SELECT);
1536 
1537 	option = calloc(1, sizeof *option);
1538 	if (!option)
1539 		return false;
1540 
1541 	option->value = value;
1542 	option->text = text;
1543 
1544 	/* add to linked list */
1545 	if (control->data.select.items == 0)
1546 		control->data.select.items = option;
1547 	else
1548 		control->data.select.last_item->next = option;
1549 	control->data.select.last_item = option;
1550 
1551 	/* set selected */
1552 	if (selected && (control->data.select.num_selected == 0 ||
1553 			control->data.select.multiple)) {
1554 		option->selected = option->initial_selected = true;
1555 		control->data.select.num_selected++;
1556 		control->data.select.current = option;
1557 	}
1558 
1559 	control->data.select.num_items++;
1560 
1561 	option->node = node;
1562 
1563 	return true;
1564 }
1565 
1566 
1567 /* exported interface documented in html/form_internal.h */
1568 nserror
form_open_select_menu(void * client_data,struct form_control * control,select_menu_redraw_callback callback,struct content * c)1569 form_open_select_menu(void *client_data,
1570 		      struct form_control *control,
1571 		      select_menu_redraw_callback callback,
1572 		      struct content *c)
1573 {
1574 	int line_height_with_spacing;
1575 	struct box *box;
1576 	plot_font_style_t fstyle;
1577 	int total_height;
1578 	struct form_select_menu *menu;
1579 	html_content *html = (html_content *)c;
1580 	nserror res;
1581 
1582 	/* if the menu is opened for the first time */
1583 	if (control->data.select.menu == NULL) {
1584 
1585 		menu = calloc(1, sizeof (struct form_select_menu));
1586 		if (menu == NULL) {
1587 			return NSERROR_NOMEM;
1588 		}
1589 
1590 		control->data.select.menu = menu;
1591 
1592 		box = control->box;
1593 
1594 		menu->width = box->width +
1595 			box->border[RIGHT].width + box->padding[RIGHT] +
1596 			box->border[LEFT].width + box->padding[LEFT];
1597 
1598 		font_plot_style_from_css(&html->len_ctx, control->box->style,
1599 				&fstyle);
1600 		menu->f_size = fstyle.size;
1601 
1602 		menu->line_height = FIXTOINT(FDIV((FMUL(FLTTOFIX(1.2),
1603 				FMUL(nscss_screen_dpi,
1604 				INTTOFIX(fstyle.size / PLOT_STYLE_SCALE)))),
1605 				F_72));
1606 
1607 		line_height_with_spacing = menu->line_height +
1608 				menu->line_height *
1609 				SELECT_LINE_SPACING;
1610 
1611 		total_height = control->data.select.num_items *
1612 				line_height_with_spacing;
1613 		menu->height = total_height;
1614 
1615 		if (menu->height > MAX_SELECT_HEIGHT) {
1616 			menu->height = MAX_SELECT_HEIGHT;
1617 		}
1618 
1619 		menu->client_data = client_data;
1620 		menu->callback = callback;
1621 		res = scrollbar_create(false,
1622 				       menu->height,
1623 				       total_height,
1624 				       menu->height,
1625 				       control,
1626 				       form_select_menu_scroll_callback,
1627 				       &(menu->scrollbar));
1628 		if (res != NSERROR_OK) {
1629 			control->data.select.menu = NULL;
1630 			free(menu);
1631 			return res;
1632 		}
1633 		menu->c = c;
1634 	} else {
1635 		menu = control->data.select.menu;
1636 	}
1637 
1638 	menu->callback(client_data, 0, 0, menu->width, menu->height);
1639 
1640 	return NSERROR_OK;
1641 }
1642 
1643 
1644 /* exported interface documented in html/form_internal.h */
form_free_select_menu(struct form_control * control)1645 void form_free_select_menu(struct form_control *control)
1646 {
1647 	if (control->data.select.menu->scrollbar != NULL)
1648 		scrollbar_destroy(control->data.select.menu->scrollbar);
1649 	free(control->data.select.menu);
1650 	control->data.select.menu = NULL;
1651 }
1652 
1653 
1654 /* exported interface documented in html/form_internal.h */
1655 bool
form_redraw_select_menu(struct form_control * control,int x,int y,float scale,const struct rect * clip,const struct redraw_context * ctx)1656 form_redraw_select_menu(struct form_control *control,
1657 			int x, int y,
1658 			float scale,
1659 			const struct rect *clip,
1660 			const struct redraw_context *ctx)
1661 {
1662 	struct box *box;
1663 	struct form_select_menu *menu = control->data.select.menu;
1664 	struct form_option *option;
1665 	int line_height, line_height_with_spacing;
1666 	int width, height;
1667 	int x0, y0, x1, scrollbar_x, y1, y2, y3;
1668 	int item_y;
1669 	int text_pos_offset, text_x;
1670 	int scrollbar_width = SCROLLBAR_WIDTH;
1671 	int i;
1672 	int scroll;
1673 	int x_cp, y_cp;
1674 	struct rect r;
1675 	struct rect rect;
1676 	nserror res;
1677 
1678 	box = control->box;
1679 
1680 	x_cp = x;
1681 	y_cp = y;
1682 	width = menu->width;
1683 	height = menu->height;
1684 	line_height = menu->line_height;
1685 
1686 	line_height_with_spacing = line_height +
1687 			line_height * SELECT_LINE_SPACING;
1688 	scroll = scrollbar_get_offset(menu->scrollbar);
1689 
1690 	if (scale != 1.0) {
1691 		x *= scale;
1692 		y *= scale;
1693 		width *= scale;
1694 		height *= scale;
1695 		scrollbar_width *= scale;
1696 
1697 		i = scroll / line_height_with_spacing;
1698 		scroll -= i * line_height_with_spacing;
1699 		line_height *= scale;
1700 		line_height_with_spacing *= scale;
1701 		scroll *= scale;
1702 		scroll += i * line_height_with_spacing;
1703 	}
1704 
1705 
1706 	x0 = x;
1707 	y0 = y;
1708 	x1 = x + width - 1;
1709 	y1 = y + height - 1;
1710 	scrollbar_x = x1 - scrollbar_width;
1711 
1712 	r.x0 = x0;
1713 	r.y0 = y0;
1714 	r.x1 = x1 + 1;
1715 	r.y1 = y1 + 1;
1716 	res = ctx->plot->clip(ctx, &r);
1717 	if (res != NSERROR_OK) {
1718 		return false;
1719 	}
1720 
1721 	rect.x0 = x0;
1722 	rect.y0 = y0;
1723 	rect.x1 = x1;
1724 	rect.y1 = y1;
1725 	res = ctx->plot->rectangle(ctx, plot_style_stroke_darkwbasec, &rect);
1726 	if (res != NSERROR_OK) {
1727 		return false;
1728 	}
1729 
1730 	x0 = x0 + SELECT_BORDER_WIDTH;
1731 	y0 = y0 + SELECT_BORDER_WIDTH;
1732 	x1 = x1 - SELECT_BORDER_WIDTH;
1733 	y1 = y1 - SELECT_BORDER_WIDTH;
1734 	height = height - 2 * SELECT_BORDER_WIDTH;
1735 
1736 	r.x0 = x0;
1737 	r.y0 = y0;
1738 	r.x1 = x1 + 1;
1739 	r.y1 = y1 + 1;
1740 	res = ctx->plot->clip(ctx, &r);
1741 	if (res != NSERROR_OK) {
1742 		return false;
1743 	}
1744 
1745 	res = ctx->plot->rectangle(ctx, plot_style_fill_lightwbasec, &r);
1746 	if (res != NSERROR_OK) {
1747 		return false;
1748 	}
1749 
1750 	option = control->data.select.items;
1751 	item_y = line_height_with_spacing;
1752 
1753 	while (item_y < scroll) {
1754 		option = option->next;
1755 		item_y += line_height_with_spacing;
1756 	}
1757 	item_y -= line_height_with_spacing;
1758 	text_pos_offset = y - scroll +
1759 			(int) (line_height * (0.75 + SELECT_LINE_SPACING));
1760 	text_x = x + (box->border[LEFT].width + box->padding[LEFT]) * scale;
1761 
1762 	plot_fstyle_entry.size = menu->f_size;
1763 
1764 	while (option && item_y - scroll < height) {
1765 
1766 		if (option->selected) {
1767 			y2 = y + item_y - scroll;
1768 			y3 = y + item_y + line_height_with_spacing - scroll;
1769 
1770 			rect.x0 = x0;
1771 			rect.y0 = y0 > y2 ? y0 : y2;
1772 			rect.x1 = scrollbar_x + 1;
1773 			rect.y1 = y3 < y1 + 1 ? y3 : y1 + 1;
1774 			res = ctx->plot->rectangle(ctx, &plot_style_fill_selected, &rect);
1775 			if (res != NSERROR_OK) {
1776 				return false;
1777 			}
1778 		}
1779 
1780 		y2 = text_pos_offset + item_y;
1781 		res = ctx->plot->text(ctx,
1782 				      &plot_fstyle_entry,
1783 				      text_x, y2,
1784 				      option->text, strlen(option->text));
1785 		if (res != NSERROR_OK) {
1786 			return false;
1787 		}
1788 
1789 		item_y += line_height_with_spacing;
1790 		option = option->next;
1791 	}
1792 
1793 	res = scrollbar_redraw(menu->scrollbar,
1794 			       x_cp + menu->width - SCROLLBAR_WIDTH,
1795 			       y_cp,
1796 			       clip, scale, ctx);
1797 	if (res != NSERROR_OK) {
1798 		return false;
1799 	}
1800 
1801 	return true;
1802 }
1803 
1804 
1805 /* private interface described in html/form_internal.h */
1806 bool
form_clip_inside_select_menu(struct form_control * control,float scale,const struct rect * clip)1807 form_clip_inside_select_menu(struct form_control *control,
1808 			     float scale,
1809 			     const struct rect *clip)
1810 {
1811 	struct form_select_menu *menu = control->data.select.menu;
1812 	int width, height;
1813 
1814 
1815 	width = menu->width;
1816 	height = menu->height;
1817 
1818 	if (scale != 1.0) {
1819 		width *= scale;
1820 		height *= scale;
1821 	}
1822 
1823 	if (clip->x0 >= 0 &&
1824 	    clip->x1 <= width &&
1825 	    clip->y0 >= 0 &&
1826 	    clip->y1 <= height)
1827 		return true;
1828 
1829 	return false;
1830 }
1831 
1832 
1833 /* exported interface documented in netsurf/form.h */
form_select_process_selection(struct form_control * control,int item)1834 nserror form_select_process_selection(struct form_control *control, int item)
1835 {
1836 	assert(control != NULL);
1837 
1838 	return form__select_process_selection(control->html, control, item);
1839 }
1840 
1841 
1842 /* exported interface documented in netsurf/form.h */
1843 struct form_option *
form_select_get_option(struct form_control * control,int item)1844 form_select_get_option(struct form_control *control, int item)
1845 {
1846 	struct form_option *opt;
1847 
1848 	opt = control->data.select.items;
1849 	while ((opt != NULL) && (item > 0)) {
1850 		opt = opt->next;
1851 		item--;
1852 	}
1853 	return opt;
1854 }
1855 
1856 
1857 /* exported interface documented in netsurf/form.h */
form_control_get_name(struct form_control * control)1858 char *form_control_get_name(struct form_control *control)
1859 {
1860 	return control->name;
1861 }
1862 
1863 
1864 /* exported interface documented in netsurf/form.h */
form_control_bounding_rect(struct form_control * control,struct rect * r)1865 nserror form_control_bounding_rect(struct form_control *control, struct rect *r)
1866 {
1867 	box_bounds( control->box, r );
1868 	return NSERROR_OK;
1869 }
1870 
1871 
1872 /* private interface described in html/form_internal.h */
1873 const char *
form_select_mouse_action(struct form_control * control,browser_mouse_state mouse,int x,int y)1874 form_select_mouse_action(struct form_control *control,
1875 			 browser_mouse_state mouse,
1876 			 int x, int y)
1877 {
1878 	struct form_select_menu *menu = control->data.select.menu;
1879 	int x0, y0, x1, y1, scrollbar_x;
1880 	const char *status = NULL;
1881 	bool multiple = control->data.select.multiple;
1882 
1883 	x0 = 0;
1884 	y0 = 0;
1885 	x1 = menu->width;
1886 	y1 = menu->height;
1887 	scrollbar_x = x1 - SCROLLBAR_WIDTH;
1888 
1889 	if (menu->scroll_capture ||
1890 			(x > scrollbar_x && x < x1 && y > y0 && y < y1)) {
1891 		/* The scroll is currently capturing all events or the mouse
1892 		 * event is taking place on the scrollbar widget area
1893 		 */
1894 		x -= scrollbar_x;
1895 		return scrollbar_mouse_status_to_message(
1896 				scrollbar_mouse_action(menu->scrollbar,
1897 						mouse, x, y));
1898 	}
1899 
1900 
1901 	if (x > x0 && x < scrollbar_x && y > y0 && y < y1) {
1902 		/* over option area */
1903 
1904 		if (mouse & (BROWSER_MOUSE_CLICK_1 | BROWSER_MOUSE_CLICK_2))
1905 			/* button 1 or 2 click */
1906 			form_select_menu_clicked(control, x, y);
1907 
1908 		if (!(mouse & BROWSER_MOUSE_CLICK_1 && !multiple))
1909 			/* anything but a button 1 click over a single select
1910 			   menu */
1911 			status = messages_get(control->data.select.multiple ?
1912 					"SelectMClick" : "SelectClick");
1913 
1914 	} else if (!(mouse & (BROWSER_MOUSE_CLICK_1 | BROWSER_MOUSE_CLICK_2)))
1915 		/* if not a button 1 or 2 click*/
1916 		status = messages_get("SelectClose");
1917 
1918 	return status;
1919 }
1920 
1921 
1922 /* private interface described in html/form_internal.h */
1923 void
form_select_mouse_drag_end(struct form_control * control,browser_mouse_state mouse,int x,int y)1924 form_select_mouse_drag_end(struct form_control *control,
1925 			   browser_mouse_state mouse,
1926 			   int x, int y)
1927 {
1928 	int x0, y0, x1, y1;
1929 	int box_x, box_y;
1930 	struct box *box;
1931 	struct form_select_menu *menu = control->data.select.menu;
1932 
1933 	box = control->box;
1934 
1935 	/* Get global coords of scrollbar */
1936 	box_coords(box, &box_x, &box_y);
1937 	box_x -= box->border[LEFT].width;
1938 	box_y += box->height + box->border[BOTTOM].width +
1939 			box->padding[BOTTOM] + box->padding[TOP];
1940 
1941 	/* Get drag end coords relative to scrollbar */
1942 	x = x - box_x;
1943 	y = y - box_y;
1944 
1945 	if (menu->scroll_capture) {
1946 		x -= menu->width - SCROLLBAR_WIDTH;
1947 		scrollbar_mouse_drag_end(menu->scrollbar, mouse, x, y);
1948 		return;
1949 	}
1950 
1951 	x0 = 0;
1952 	y0 = 0;
1953 	x1 = menu->width;
1954 	y1 = menu->height;
1955 
1956 
1957 	if (x > x0 && x < x1 - SCROLLBAR_WIDTH && y >  y0 && y < y1) {
1958 		/* handle drag end above the option area like a regular click */
1959 		form_select_menu_clicked(control, x, y);
1960 	}
1961 }
1962 
1963 
1964 /* private interface described in html/form_internal.h */
form_select_get_dimensions(struct form_control * control,int * width,int * height)1965 void form_select_get_dimensions(struct form_control *control,
1966 		int *width, int *height)
1967 {
1968 	*width = control->data.select.menu->width;
1969 	*height = control->data.select.menu->height;
1970 }
1971 
1972 
1973 /* private interface described in html/form_internal.h */
form_select_menu_callback(void * client_data,int x,int y,int width,int height)1974 void form_select_menu_callback(void *client_data,
1975 		int x, int y, int width, int height)
1976 {
1977 	html_content *html = client_data;
1978 	int menu_x, menu_y;
1979 	struct box *box;
1980 
1981 	box = html->visible_select_menu->box;
1982 	box_coords(box, &menu_x, &menu_y);
1983 
1984 	menu_x -= box->border[LEFT].width;
1985 	menu_y += box->height + box->border[BOTTOM].width +
1986 			box->padding[BOTTOM] +
1987 			box->padding[TOP];
1988 	content__request_redraw((struct content *)html, menu_x + x, menu_y + y,
1989 			width, height);
1990 }
1991 
1992 
1993 /* private interface described in html/form_internal.h */
form_radio_set(struct form_control * radio)1994 void form_radio_set(struct form_control *radio)
1995 {
1996 	struct form_control *control;
1997 
1998 	assert(radio);
1999 	if (!radio->form)
2000 		return;
2001 
2002 	if (radio->selected)
2003 		return;
2004 
2005 	for (control = radio->form->controls; control;
2006 			control = control->next) {
2007 		if (control->type != GADGET_RADIO)
2008 			continue;
2009 		if (control == radio)
2010 			continue;
2011 		if (strcmp(control->name, radio->name) != 0)
2012 			continue;
2013 
2014 		if (control->selected) {
2015 			control->selected = false;
2016 			dom_html_input_element_set_checked(control->node, false);
2017 			html__redraw_a_box(radio->html, control->box);
2018 		}
2019 	}
2020 
2021 	radio->selected = true;
2022 	dom_html_input_element_set_checked(radio->node, true);
2023 	html__redraw_a_box(radio->html, radio->box);
2024 }
2025 
2026 
2027 /* private interface described in html/form_internal.h */
2028 nserror
form_submit(nsurl * page_url,struct browser_window * target,struct form * form,struct form_control * submit_button)2029 form_submit(nsurl *page_url,
2030 	    struct browser_window *target,
2031 	    struct form *form,
2032 	    struct form_control *submit_button)
2033 {
2034 	nserror res;
2035 	char *data = NULL; /* encoded form data */
2036 	struct fetch_multipart_data *success = NULL; /* gcc is incapable of correctly reasoning about use and generates "maybe used uninitialised" warnings */
2037 	nsurl *action_url;
2038 	nsurl *query_url;
2039 
2040 	assert(form != NULL);
2041 
2042 	/* obtain list of controls from DOM */
2043 	res = form_dom_to_data(form, submit_button, &success);
2044 	if (res != NSERROR_OK) {
2045 		return res;
2046 	}
2047 
2048 	/* Decompose action */
2049 	res = nsurl_create(form->action, &action_url);
2050 	if (res != NSERROR_OK) {
2051 		fetch_multipart_data_destroy(success);
2052 		return res;
2053 	}
2054 
2055 	switch (form->method) {
2056 	case method_GET:
2057 		res = form_url_encode(form, success, &data);
2058 		if (res == NSERROR_OK) {
2059 			/* Replace query segment */
2060 			res = nsurl_replace_query(action_url, data, &query_url);
2061 			if (res == NSERROR_OK) {
2062 				res = browser_window_navigate(target,
2063 							      query_url,
2064 							      page_url,
2065 							      BW_NAVIGATE_HISTORY,
2066 							      NULL,
2067 							      NULL,
2068 							      NULL);
2069 
2070 				nsurl_unref(query_url);
2071 			}
2072 			free(data);
2073 		}
2074 		break;
2075 
2076 	case method_POST_URLENC:
2077 		res = form_url_encode(form, success, &data);
2078 		if (res == NSERROR_OK) {
2079 			res = browser_window_navigate(target,
2080 						      action_url,
2081 						      page_url,
2082 						      BW_NAVIGATE_HISTORY,
2083 						      data,
2084 						      NULL,
2085 						      NULL);
2086 			free(data);
2087 		}
2088 		break;
2089 
2090 	case method_POST_MULTIPART:
2091 		res = browser_window_navigate(target,
2092 					      action_url,
2093 					      page_url,
2094 					      BW_NAVIGATE_HISTORY,
2095 					      NULL,
2096 					      success,
2097 					      NULL);
2098 
2099 		break;
2100 	}
2101 
2102 	nsurl_unref(action_url);
2103 	fetch_multipart_data_destroy(success);
2104 
2105 	return res;
2106 }
2107 
2108 
2109 /* exported interface documented in html/form_internal.h */
form_gadget_update_value(struct form_control * control,char * value)2110 void form_gadget_update_value(struct form_control *control, char *value)
2111 {
2112 	switch (control->type) {
2113 	case GADGET_HIDDEN:
2114 	case GADGET_TEXTBOX:
2115 	case GADGET_TEXTAREA:
2116 	case GADGET_PASSWORD:
2117 	case GADGET_FILE:
2118 		if (control->value != NULL) {
2119 			free(control->value);
2120 		}
2121 		control->value = value;
2122 		if (control->node != NULL) {
2123 			dom_exception err;
2124 			dom_string *str;
2125 			err = dom_string_create((uint8_t *)value,
2126 						strlen(value), &str);
2127 			if (err == DOM_NO_ERR) {
2128 				if (control->type == GADGET_TEXTAREA)
2129 					err = dom_html_text_area_element_set_value(
2130 						(dom_html_text_area_element *)(control->node),
2131 						str);
2132 				else
2133 					err = dom_html_input_element_set_value(
2134 						(dom_html_input_element *)(control->node),
2135 						str);
2136 				dom_string_unref(str);
2137 			}
2138 		}
2139 		break;
2140 	default:
2141 		/* Do nothing */
2142 		break;
2143 	}
2144 
2145 	/* Finally, sync this with the DOM */
2146 	form_gadget_sync_with_dom(control);
2147 }
2148 
2149 
2150 /* Exported API, see html/form_internal.h */
2151 void
form_gadget_sync_with_dom(struct form_control * control)2152 form_gadget_sync_with_dom(struct form_control *control)
2153 {
2154 	dom_exception exc;
2155 	dom_string *value = NULL;
2156 	bool changed_dom = false;
2157 
2158 	if (control->syncing ||
2159 	    (control->type != GADGET_TEXTBOX &&
2160 	     control->type != GADGET_PASSWORD &&
2161 	     control->type != GADGET_HIDDEN &&
2162 	     control->type != GADGET_TEXTAREA)) {
2163 		/* Not a control we support, or the control is already
2164 		 * mid-sync so we don't want to disrupt that
2165 		 */
2166 		return;
2167 	}
2168 
2169 	control->syncing = true;
2170 
2171 	/* If we've changed value, sync that toward the DOM */
2172 	if ((control->last_synced_value == NULL &&
2173 	     control->value != NULL &&
2174 	     control->value[0] != '\0') ||
2175 	    (control->last_synced_value != NULL &&
2176 	     control->value != NULL &&
2177 	     strcmp(control->value, control->last_synced_value) != 0)) {
2178 		char *dup = strdup(control->value);
2179 		if (dup == NULL) {
2180 			goto out;
2181 		}
2182 		if (control->last_synced_value != NULL) {
2183 			free(control->last_synced_value);
2184 		}
2185 		control->last_synced_value = dup;
2186 		exc = dom_string_create((uint8_t *)(control->value),
2187 					strlen(control->value), &value);
2188 		if (exc != DOM_NO_ERR) {
2189 			goto out;
2190 		}
2191 		if (control->node_value != NULL) {
2192 			dom_string_unref(control->node_value);
2193 		}
2194 		control->node_value = value;
2195 		value = NULL;
2196 		if (control->type == GADGET_TEXTAREA) {
2197 			exc = dom_html_text_area_element_set_value(control->node, control->node_value);
2198 		} else {
2199 			exc = dom_html_input_element_set_value(control->node, control->node_value);
2200 		}
2201 		if (exc != DOM_NO_ERR) {
2202 			goto out;
2203 		}
2204 		changed_dom = true;
2205 	}
2206 
2207 	/* Now check if the DOM has changed since our last go */
2208 	if (control->type == GADGET_TEXTAREA) {
2209 		exc = dom_html_text_area_element_get_value(control->node, &value);
2210 	} else {
2211 		exc = dom_html_input_element_get_value(control->node, &value);
2212 	}
2213 
2214 	if (exc != DOM_NO_ERR) {
2215 		/* Nothing much we can do here */
2216 		goto out;
2217 	}
2218 
2219 	if (!dom_string_isequal(control->node_value, value)) {
2220 		/* The DOM has changed */
2221 		if (!changed_dom) {
2222 			/* And it wasn't us */
2223 			char *value_s = strndup(
2224 				dom_string_data(value),
2225 				dom_string_byte_length(value));
2226 			char *dup = NULL;
2227 			if (value_s == NULL) {
2228 				goto out;
2229 			}
2230 			dup = strdup(value_s);
2231 			if (dup == NULL) {
2232 				free(value_s);
2233 				goto out;
2234 			}
2235 			free(control->value);
2236 			control->value = value_s;
2237 			free(control->last_synced_value);
2238 			control->last_synced_value = dup;
2239 			if (control->type != GADGET_HIDDEN &&
2240 			    control->data.text.ta != NULL) {
2241 				textarea_set_text(control->data.text.ta,
2242 						  value_s);
2243 			}
2244 		}
2245 		control->node_value = value;
2246 		value = NULL;
2247 	}
2248 
2249 out:
2250 	if (value != NULL)
2251 		dom_string_unref(value);
2252 	control->syncing = false;
2253 }
2254 
2255 
2256 /* exported interface documented in html/form_internal.h */
2257 struct form *
form_new(void * node,const char * action,const char * target,form_method method,const char * charset,const char * doc_charset)2258 form_new(void *node,
2259 	 const char *action,
2260 	 const char *target,
2261 	 form_method method,
2262 	 const char *charset,
2263 	 const char *doc_charset)
2264 {
2265 	struct form *form;
2266 
2267 	form = calloc(1, sizeof *form);
2268 	if (!form)
2269 		return NULL;
2270 
2271 	form->action = strdup(action != NULL ? action : "");
2272 	if (form->action == NULL) {
2273 		free(form);
2274 		return NULL;
2275 	}
2276 
2277 	form->target = target != NULL ? strdup(target) : NULL;
2278 	if (target != NULL && form->target == NULL) {
2279 		free(form->action);
2280 		free(form);
2281 		return NULL;
2282 	}
2283 
2284 	form->method = method;
2285 
2286 	form->accept_charsets = charset != NULL ? strdup(charset) : NULL;
2287 	if (charset != NULL && form->accept_charsets == NULL) {
2288 		free(form->target);
2289 		free(form->action);
2290 		free(form);
2291 		return NULL;
2292 	}
2293 
2294 	form->document_charset = doc_charset != NULL ? strdup(doc_charset)
2295 						     : NULL;
2296 	if (doc_charset && form->document_charset == NULL) {
2297 		free(form->accept_charsets);
2298 		free(form->target);
2299 		free(form->action);
2300 		free(form);
2301 		return NULL;
2302 	}
2303 
2304 	form->node = node;
2305 
2306 	return form;
2307 }
2308 
2309 
2310 /* exported interface documented in html/form_internal.h */
form_free(struct form * form)2311 void form_free(struct form *form)
2312 {
2313 	struct form_control *c, *d;
2314 
2315 	for (c = form->controls; c != NULL; c = d) {
2316 		d = c->next;
2317 
2318 		form_free_control(c);
2319 	}
2320 
2321 	free(form->action);
2322 	free(form->target);
2323 	free(form->accept_charsets);
2324 	free(form->document_charset);
2325 
2326 	free(form);
2327 }
2328 
2329 
2330 /* exported interface documented in html/form_internal.h */
form_new_control(void * node,form_control_type type)2331 struct form_control *form_new_control(void *node, form_control_type type)
2332 {
2333 	struct form_control *control;
2334 
2335 	control = calloc(1, sizeof *control);
2336 	if (control == NULL)
2337 		return NULL;
2338 
2339 	control->node = node;
2340 	control->type = type;
2341 
2342 	return control;
2343 }
2344