1 /*
2  * Copyright 2012 Vincent Sanders <vince@netsurf-browser.org>
3  *
4  * This file is part of NetSurf, http://www.netsurf-browser.org/
5  *
6  * NetSurf is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; version 2 of the License.
9  *
10  * NetSurf is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17  */
18 
19 /**
20  * \file
21  * Option reading and saving (implementation).
22  *
23  * Options are stored in the format key:value, one per line.
24  *
25  * For bool options, value is "0" or "1".
26  */
27 
28 #include <stdio.h>
29 #include <stdint.h>
30 #include <stdbool.h>
31 #include <stdlib.h>
32 #include <string.h>
33 #include <strings.h>
34 
35 #include "netsurf/plot_style.h"
36 #include "utils/errors.h"
37 #include "utils/log.h"
38 #include "utils/utils.h"
39 #include "utils/nsoption.h"
40 
41 /** Length of buffer used to read lines from input file */
42 #define NSOPTION_MAX_LINE_LEN 1024
43 
44 struct nsoption_s *nsoptions = NULL;
45 struct nsoption_s *nsoptions_default = NULL;
46 
47 #define NSOPTION_BOOL(NAME, DEFAULT) \
48 	{ #NAME, sizeof(#NAME) - 1, OPTION_BOOL, { .b = DEFAULT } },
49 
50 #define NSOPTION_STRING(NAME, DEFAULT) \
51 	{ #NAME, sizeof(#NAME) - 1, OPTION_STRING, { .cs = DEFAULT } },
52 
53 #define NSOPTION_INTEGER(NAME, DEFAULT) \
54 	{ #NAME, sizeof(#NAME) - 1, OPTION_INTEGER, { .i = DEFAULT } },
55 
56 #define NSOPTION_UINT(NAME, DEFAULT) \
57 	{ #NAME, sizeof(#NAME) - 1, OPTION_UINT, { .u = DEFAULT } },
58 
59 #define NSOPTION_COLOUR(NAME, DEFAULT) \
60 	{ #NAME, sizeof(#NAME) - 1, OPTION_COLOUR, { .c = DEFAULT } },
61 
62 /** The table of compiled in default options */
63 static struct nsoption_s defaults[] = {
64 #include "desktop/options.h"
65 
66 #if defined(riscos)
67 #include "riscos/options.h"
68 #elif defined(nsgtk)
69 #include "gtk/options.h"
70 #elif defined(nsbeos)
71 #include "beos/options.h"
72 #elif defined(nsamiga)
73 #include "amiga/options.h"
74 #elif defined(nsframebuffer)
75 #include "framebuffer/options.h"
76 #elif defined(nsatari)
77 #include "atari/options.h"
78 #elif defined(nsmonkey)
79 #include "monkey/options.h"
80 #elif defined(nswin32)
81 #include "windows/options.h"
82 #endif
83 	{ NULL, 0, OPTION_INTEGER, { 0 } }
84 };
85 
86 #undef NSOPTION_BOOL
87 #undef NSOPTION_STRING
88 #undef NSOPTION_INTEGER
89 #undef NSOPTION_UINT
90 #undef NSOPTION_COLOUR
91 
92 /**
93  * Set an option value based on a string
94  */
95 static bool
strtooption(const char * value,struct nsoption_s * option)96 strtooption(const char *value, struct nsoption_s *option)
97 {
98 	bool ret = true;
99 	colour rgbcolour; /* RRGGBB */
100 
101 	switch (option->type) {
102 	case OPTION_BOOL:
103 		option->value.b = (value[0] == '1');
104 		break;
105 
106 	case OPTION_INTEGER:
107 		option->value.i = atoi(value);
108 		break;
109 
110 	case OPTION_UINT:
111 		option->value.u = strtoul(value, NULL, 0);
112 		break;
113 
114 	case OPTION_COLOUR:
115 		if (sscanf(value, "%x", &rgbcolour) == 1) {
116 			option->value.c = (((0x000000FF & rgbcolour) << 16) |
117 					   ((0x0000FF00 & rgbcolour) << 0) |
118 					   ((0x00FF0000 & rgbcolour) >> 16));
119 		}
120 		break;
121 
122 	case OPTION_STRING:
123 		if (option->value.s != NULL) {
124 			free(option->value.s);
125 		}
126 
127 		if (*value == 0) {
128 			/* do not allow empty strings in text options */
129 			option->value.s = NULL;
130 		} else {
131 			option->value.s = strdup(value);
132 		}
133 		break;
134 
135 	default:
136 		ret = false;
137 		break;
138 	}
139 
140 	return ret;
141 }
142 
143 /* validate options to sane values */
nsoption_validate(struct nsoption_s * opts,struct nsoption_s * defs)144 static void nsoption_validate(struct nsoption_s *opts, struct nsoption_s *defs)
145 {
146 	int cloop;
147 	bool black = true;
148 
149 	if (opts[NSOPTION_treeview_font_size].value.i  < 50) {
150 		opts[NSOPTION_treeview_font_size].value.i = 50;
151 	}
152 
153 	if (opts[NSOPTION_treeview_font_size].value.i > 1000) {
154 		opts[NSOPTION_treeview_font_size].value.i = 1000;
155 	}
156 
157 	if (opts[NSOPTION_font_size].value.i  < 50) {
158 		opts[NSOPTION_font_size].value.i = 50;
159 	}
160 
161 	if (opts[NSOPTION_font_size].value.i > 1000) {
162 		opts[NSOPTION_font_size].value.i = 1000;
163 	}
164 
165 	if (opts[NSOPTION_font_min_size].value.i < 10) {
166 		opts[NSOPTION_font_min_size].value.i = 10;
167 	}
168 
169 	if (opts[NSOPTION_font_min_size].value.i > 500) {
170 		opts[NSOPTION_font_min_size].value.i = 500;
171 	}
172 
173 	if (opts[NSOPTION_memory_cache_size].value.i < 0) {
174 		opts[NSOPTION_memory_cache_size].value.i = 0;
175 	}
176 
177 	/* to aid migration from old, broken, configuration files this
178 	 * checks to see if all the system colours are set to black
179 	 * and returns them to defaults instead
180 	 */
181 
182 	for (cloop = NSOPTION_SYS_COLOUR_START;
183 	     cloop <= NSOPTION_SYS_COLOUR_END;
184 	     cloop++) {
185 		if (opts[cloop].value.c != 0) {
186 			black = false;
187 			break;
188 		}
189 	}
190 	if (black == true && defs != NULL) {
191 		for (cloop = NSOPTION_SYS_COLOUR_START;
192 		     cloop <= NSOPTION_SYS_COLOUR_END;
193 		     cloop++) {
194 			opts[cloop].value.c = defs[cloop].value.c;
195 		}
196 	}
197 
198 	/* To aid migration and ensure that timeouts don't go crazy,
199 	 * ensure that (a) we allow at least 1 attempt and
200 	 * (b) the total time that we spend should not exceed 60s
201 	 */
202 	if (opts[NSOPTION_max_retried_fetches].value.u == 0)
203 		opts[NSOPTION_max_retried_fetches].value.u = 1;
204 	if (opts[NSOPTION_curl_fetch_timeout].value.u < 5)
205 		opts[NSOPTION_curl_fetch_timeout].value.u = 5;
206 	if (opts[NSOPTION_curl_fetch_timeout].value.u > 60)
207 		opts[NSOPTION_curl_fetch_timeout].value.u = 60;
208 	while (((opts[NSOPTION_curl_fetch_timeout].value.u *
209 		 opts[NSOPTION_max_retried_fetches].value.u) > 60) &&
210 		(opts[NSOPTION_max_retried_fetches].value.u > 1))
211 		opts[NSOPTION_max_retried_fetches].value.u--;
212 
213 	/* We ignore the result because we can't fail to validate. Yay */
214 	(void)nslog_set_filter_by_options();
215 }
216 
217 /**
218  * Determines if an option is different between two option tables.
219  *
220  * @param opts The first table to compare.
221  * @param defs The second table to compare.
222  * @param entry The option to compare.
223  * @return true if the option differs false if not.
224  */
225 static bool
nsoption_is_set(const struct nsoption_s * opts,const struct nsoption_s * defs,const enum nsoption_e entry)226 nsoption_is_set(const struct nsoption_s *opts,
227 		const struct nsoption_s *defs,
228 		const enum nsoption_e entry)
229 {
230 	bool ret = false;
231 
232 	switch (opts[entry].type) {
233 	case OPTION_BOOL:
234 		if (opts[entry].value.b != defs[entry].value.b) {
235 			ret = true;
236 		}
237 		break;
238 
239 	case OPTION_INTEGER:
240 		if (opts[entry].value.i != defs[entry].value.i) {
241 			ret = true;
242 		}
243 		break;
244 
245 	case OPTION_UINT:
246 		if (opts[entry].value.u != defs[entry].value.u) {
247 			ret = true;
248 		}
249 		break;
250 
251 	case OPTION_COLOUR:
252 		if (opts[entry].value.c != defs[entry].value.c) {
253 			ret = true;
254 		}
255 		break;
256 
257 	case OPTION_STRING:
258 		/* set if:
259 		 *  - defs is null.
260 		 *  - default is null but value is not.
261 		 *  - default and value pointers are different
262 		 *    (acts as a null check because of previous check)
263 		 *    and the strings content differ.
264 		 */
265 		if (((defs[entry].value.s == NULL) &&
266 		     (opts[entry].value.s != NULL)) ||
267 		    ((defs[entry].value.s != NULL) &&
268 		     (opts[entry].value.s == NULL)) ||
269 		    ((defs[entry].value.s != opts[entry].value.s) &&
270 		     (strcmp(opts[entry].value.s, defs[entry].value.s) != 0))) {
271 			ret = true;
272 		}
273 		break;
274 
275 	}
276 	return ret;
277 }
278 
279 /**
280  * Output choices to file stream
281  *
282  * @param fp The file stream to write to.
283  * @param opts The options table to write.
284  * @param defs The default value table to compare with.
285  * @param all Output all entries not just ones changed from defaults
286  */
287 static nserror
nsoption_output(FILE * fp,struct nsoption_s * opts,struct nsoption_s * defs,bool all)288 nsoption_output(FILE *fp,
289 		struct nsoption_s *opts,
290 		struct nsoption_s *defs,
291 		bool all)
292 {
293 	unsigned int entry; /* index to option being output */
294 	colour rgbcolour; /* RRGGBB */
295 
296 	for (entry = 0; entry < NSOPTION_LISTEND; entry++) {
297 		if ((all == false) &&
298 		    (nsoption_is_set(opts, defs, entry) == false)) {
299 			continue;
300 		}
301 
302 		switch (opts[entry].type) {
303 		case OPTION_BOOL:
304 			fprintf(fp, "%s:%c\n",
305 				opts[entry].key,
306 				opts[entry].value.b ? '1' : '0');
307 			break;
308 
309 		case OPTION_INTEGER:
310 			fprintf(fp, "%s:%i\n",
311 				opts[entry].key,
312 				opts[entry].value.i);
313 
314 			break;
315 
316 		case OPTION_UINT:
317 			fprintf(fp, "%s:%u\n",
318 				opts[entry].key,
319 				opts[entry].value.u);
320 			break;
321 
322 		case OPTION_COLOUR:
323 			rgbcolour = (((0x000000FF & opts[entry].value.c) << 16) |
324 				     ((0x0000FF00 & opts[entry].value.c) << 0) |
325 				     ((0x00FF0000 & opts[entry].value.c) >> 16));
326 			fprintf(fp, "%s:%06x\n",
327 				opts[entry].key,
328 				rgbcolour);
329 
330 			break;
331 
332 		case OPTION_STRING:
333 			fprintf(fp, "%s:%s\n",
334 				opts[entry].key,
335 				((opts[entry].value.s == NULL) ||
336 				 (*opts[entry].value.s == 0)) ? "" : opts[entry].value.s);
337 
338 			break;
339 		}
340 	}
341 
342 	return NSERROR_OK;
343 }
344 
345 /**
346  * Output an option value into a string, in HTML format.
347  *
348  * @param option The option to output the value of.
349  * @param size The size of the string buffer.
350  * @param pos The current position in string
351  * @param string The string in which to output the value.
352  * @return The number of bytes written to string or -1 on error
353  */
354 static size_t
nsoption_output_value_html(struct nsoption_s * option,size_t size,size_t pos,char * string)355 nsoption_output_value_html(struct nsoption_s *option,
356 			   size_t size,
357 			   size_t pos,
358 			   char *string)
359 {
360 	size_t slen = 0; /* length added to string */
361 	colour rgbcolour; /* RRGGBB */
362 
363 	switch (option->type) {
364 	case OPTION_BOOL:
365 		slen = snprintf(string + pos,
366 				size - pos,
367 				"%s",
368 				option->value.b ? "true" : "false");
369 		break;
370 
371 	case OPTION_INTEGER:
372 		slen = snprintf(string + pos,
373 				size - pos,
374 				"%i",
375 				option->value.i);
376 		break;
377 
378 	case OPTION_UINT:
379 		slen = snprintf(string + pos,
380 				size - pos,
381 				"%u",
382 				option->value.u);
383 		break;
384 
385 	case OPTION_COLOUR:
386 		rgbcolour = colour_rb_swap(option->value.c);
387 		slen = snprintf(string + pos,
388 				size - pos,
389 				"<span style=\"font-family:Monospace;\">"
390 					"#%06X"
391 				"</span> "
392 				"<span style=\"background-color: #%06x; "
393 					"border: 1px solid #%06x; "
394 					"display: inline-block; "
395 					"width: 1em; height: 1em;\">"
396 				"</span>",
397 				rgbcolour,
398 				rgbcolour,
399 				colour_to_bw_furthest(rgbcolour));
400 		break;
401 
402 	case OPTION_STRING:
403 		if (option->value.s != NULL) {
404 			slen = snprintf(string + pos, size - pos, "%s",
405 					option->value.s);
406 		} else {
407 			slen = snprintf(string + pos, size - pos,
408 					"<span class=\"null-content\">NULL"
409 					"</span>");
410 		}
411 		break;
412 	}
413 
414 	return slen;
415 }
416 
417 
418 /**
419  * Output an option value into a string, in plain text format.
420  *
421  * @param option The option to output the value of.
422  * @param size The size of the string buffer.
423  * @param pos The current position in string
424  * @param string The string in which to output the value.
425  * @return The number of bytes written to string or -1 on error
426  */
427 static size_t
nsoption_output_value_text(struct nsoption_s * option,size_t size,size_t pos,char * string)428 nsoption_output_value_text(struct nsoption_s *option,
429 			   size_t size,
430 			   size_t pos,
431 			   char *string)
432 {
433 	size_t slen = 0; /* length added to string */
434 	colour rgbcolour; /* RRGGBB */
435 
436 	switch (option->type) {
437 	case OPTION_BOOL:
438 		slen = snprintf(string + pos,
439 				size - pos,
440 				"%c",
441 				option->value.b ? '1' : '0');
442 		break;
443 
444 	case OPTION_INTEGER:
445 		slen = snprintf(string + pos,
446 				size - pos,
447 				"%i",
448 				option->value.i);
449 		break;
450 
451 	case OPTION_UINT:
452 		slen = snprintf(string + pos,
453 				size - pos,
454 				"%u",
455 				option->value.u);
456 		break;
457 
458 	case OPTION_COLOUR:
459 		rgbcolour = (((0x000000FF & option->value.c) << 16) |
460 			     ((0x0000FF00 & option->value.c) << 0) |
461 			     ((0x00FF0000 & option->value.c) >> 16));
462 		slen = snprintf(string + pos, size - pos, "%06x", rgbcolour);
463 		break;
464 
465 	case OPTION_STRING:
466 		if (option->value.s != NULL) {
467 			slen = snprintf(string + pos,
468 					size - pos,
469 					"%s",
470 					option->value.s);
471 		}
472 		break;
473 	}
474 
475 	return slen;
476 }
477 
478 /**
479  * Duplicates an option table.
480  *
481  * Allocates a new option table and copies an existing one into it.
482  *
483  * \param[in] src The source table to copy
484  * \param[out] pdst The output table
485  * \return NSERROR_OK on success or appropriate error code.
486  */
487 static nserror
nsoption_dup(struct nsoption_s * src,struct nsoption_s ** pdst)488 nsoption_dup(struct nsoption_s *src, struct nsoption_s **pdst)
489 {
490 	struct nsoption_s *dst;
491 	dst = malloc(sizeof(defaults));
492 	if (dst == NULL) {
493 		return NSERROR_NOMEM;
494 	}
495 	*pdst = dst;
496 
497 	/* copy the source table into the destination table */
498 	memcpy(dst, src, sizeof(defaults));
499 
500 	while (src->key != NULL) {
501 		if ((src->type == OPTION_STRING) &&
502 		    (src->value.s != NULL)) {
503 			dst->value.s = strdup(src->value.s);
504 		}
505 		src++;
506 		dst++;
507 	}
508 
509 	return NSERROR_OK;
510 }
511 
512 /**
513  * frees an option table.
514  *
515  * Iterates through an option table a freeing resources as required
516  * finally freeing the option table itself.
517  *
518  * @param opts The option table to free.
519  */
520 static nserror
nsoption_free(struct nsoption_s * opts)521 nsoption_free(struct nsoption_s *opts)
522 {
523 	struct nsoption_s *cur; /* option being freed */
524 
525 	if (opts == NULL) {
526 		return NSERROR_BAD_PARAMETER;
527 	}
528 
529 	cur = opts;
530 
531 	while (cur->key != NULL) {
532 		if ((cur->type == OPTION_STRING) && (cur->value.s != NULL)) {
533 			free(cur->value.s);
534 		}
535 		cur++;
536 	}
537 	free(opts);
538 
539 	return NSERROR_OK;
540 }
541 
542 
543 /* exported interface documented in utils/nsoption.h */
544 nserror
nsoption_init(nsoption_set_default_t * set_defaults,struct nsoption_s ** popts,struct nsoption_s ** pdefs)545 nsoption_init(nsoption_set_default_t *set_defaults,
546 	      struct nsoption_s **popts,
547 	      struct nsoption_s **pdefs)
548 {
549 	nserror ret;
550 	struct nsoption_s *defs;
551 	struct nsoption_s *opts;
552 
553 	ret = nsoption_dup(&defaults[0], &defs);
554 	if (ret != NSERROR_OK) {
555 		return ret;
556 	}
557 
558 	/* update the default table */
559 	if (set_defaults != NULL) {
560 		/** @todo it would be better if the frontends actually
561 		 * set values in the passed in table instead of
562 		 * assuming the global one.
563 		 */
564 		opts = nsoptions;
565 		nsoptions = defs;
566 
567 		ret = set_defaults(defs);
568 
569 		if (ret != NSERROR_OK) {
570 			nsoptions = opts;
571 			nsoption_free(defs);
572 			return ret;
573 		}
574 	}
575 
576 	/* copy the default values into the working set */
577 	ret = nsoption_dup(defs, &opts);
578 	if (ret != NSERROR_OK) {
579 		nsoption_free(defs);
580 		return ret;
581 	}
582 
583 	/* return values if wanted */
584 	if (popts != NULL) {
585 		*popts = opts;
586 	} else {
587 		nsoptions = opts;
588 	}
589 
590 	if (pdefs != NULL) {
591 		*pdefs = defs;
592 	} else {
593 		nsoptions_default = defs;
594 	}
595 
596 	return NSERROR_OK;
597 }
598 
599 /* exported interface documented in utils/nsoption.h */
nsoption_finalise(struct nsoption_s * opts,struct nsoption_s * defs)600 nserror nsoption_finalise(struct nsoption_s *opts, struct nsoption_s *defs)
601 {
602 	nserror res;
603 
604 	/* check to see if global table selected */
605 	if (opts == NULL) {
606 		res = nsoption_free(nsoptions);
607 		if (res == NSERROR_OK) {
608 			nsoptions = NULL;
609 		}
610 	} else {
611 		res = nsoption_free(opts);
612 	}
613 	if (res != NSERROR_OK) {
614 		return res;
615 	}
616 
617 	/* check to see if global table selected */
618 	if (defs == NULL) {
619 		res = nsoption_free(nsoptions_default);
620 		if (res == NSERROR_OK) {
621 			nsoptions_default = NULL;
622 		}
623 	} else {
624 		res = nsoption_free(defs);
625 	}
626 
627 	return res;
628 }
629 
630 
631 /* exported interface documented in utils/nsoption.h */
632 nserror
nsoption_read(const char * path,struct nsoption_s * opts)633 nsoption_read(const char *path, struct nsoption_s *opts)
634 {
635 	char s[NSOPTION_MAX_LINE_LEN];
636 	FILE *fp;
637 	struct nsoption_s *defs;
638 
639 	if (path == NULL) {
640 		return NSERROR_BAD_PARAMETER;
641 	}
642 
643 	/* check to see if global table selected */
644 	if (opts == NULL) {
645 		opts = nsoptions;
646 	}
647 
648 	/** @todo is this and API bug not being a parameter */
649 	defs = nsoptions_default;
650 
651 	if ((opts == NULL) || (defs == NULL)) {
652 		return NSERROR_BAD_PARAMETER;
653 	}
654 
655 	fp = fopen(path, "r");
656 	if (!fp) {
657 		NSLOG(netsurf, INFO, "Failed to open file '%s'", path);
658 		return NSERROR_NOT_FOUND;
659 	}
660 
661 	NSLOG(netsurf, INFO, "Successfully opened '%s' for Options file",
662 	      path);
663 
664 	while (fgets(s, NSOPTION_MAX_LINE_LEN, fp)) {
665 		char *colon, *value;
666 		unsigned int idx;
667 
668 		if ((s[0] == 0) || (s[0] == '#')) {
669 			continue;
670 		}
671 
672 		colon = strchr(s, ':');
673 		if (colon == 0) {
674 			continue;
675 		}
676 
677 		s[strlen(s) - 1] = 0;  /* remove \n at end */
678 		*colon = 0;  /* terminate key */
679 		value = colon + 1;
680 
681 		for (idx = 0; opts[idx].key != NULL; idx++) {
682 			if (strcasecmp(s, opts[idx].key) != 0) {
683 				continue;
684 			}
685 
686 			strtooption(value, &opts[idx]);
687 			break;
688 		}
689 	}
690 
691 	fclose(fp);
692 
693 	nsoption_validate(opts, defs);
694 
695 	return NSERROR_OK;
696 }
697 
698 
699 /* exported interface documented in utils/nsoption.h */
700 nserror
nsoption_write(const char * path,struct nsoption_s * opts,struct nsoption_s * defs)701 nsoption_write(const char *path,
702 	       struct nsoption_s *opts,
703 	       struct nsoption_s *defs)
704 {
705 	FILE *fp;
706 	nserror ret;
707 
708 	if (path == NULL) {
709 		return NSERROR_BAD_PARAMETER;
710 	}
711 
712 	/* check to see if global table selected */
713 	if (opts == NULL) {
714 		opts = nsoptions;
715 	}
716 
717 	/* check to see if global table selected */
718 	if (defs == NULL) {
719 		defs = nsoptions_default;
720 	}
721 
722 	if ((opts == NULL) || (defs == NULL)) {
723 		return NSERROR_BAD_PARAMETER;
724 	}
725 
726 	fp = fopen(path, "w");
727 	if (!fp) {
728 		NSLOG(netsurf, INFO, "failed to open file '%s' for writing",
729 		      path);
730 		return NSERROR_NOT_FOUND;
731 	}
732 
733 	ret = nsoption_output(fp, opts, defs, false);
734 
735 	fclose(fp);
736 
737 	return ret;
738 }
739 
740 /* exported interface documented in utils/nsoption.h */
741 nserror
nsoption_dump(FILE * outf,struct nsoption_s * opts)742 nsoption_dump(FILE *outf, struct nsoption_s *opts)
743 {
744 	if (outf == NULL) {
745 		return NSERROR_BAD_PARAMETER;
746 	}
747 
748 	/* check to see if global table selected and available */
749 	if (opts == NULL) {
750 		opts = nsoptions;
751 	}
752 	if (opts == NULL) {
753 		return NSERROR_BAD_PARAMETER;
754 	}
755 
756 	return nsoption_output(outf, opts, NULL, true);
757 }
758 
759 
760 /* exported interface documented in utils/nsoption.h */
761 nserror
nsoption_commandline(int * pargc,char ** argv,struct nsoption_s * opts)762 nsoption_commandline(int *pargc, char **argv, struct nsoption_s *opts)
763 {
764 	char *arg;
765 	char *val;
766 	int arglen;
767 	int idx = 1;
768 	int mv_loop;
769 	unsigned int entry_loop;
770 
771 	if ((pargc == NULL) || (argv == NULL)) {
772 		return NSERROR_BAD_PARAMETER;
773 	}
774 
775 	/* check to see if global table selected and available */
776 	if (opts == NULL) {
777 		opts = nsoptions;
778 	}
779 	if (opts == NULL) {
780 		return NSERROR_BAD_PARAMETER;
781 	}
782 
783 	while (idx < *pargc) {
784 		arg = argv[idx];
785 		arglen = strlen(arg);
786 
787 		/* check we have an option */
788 		/* option must start -- and be as long as the shortest option*/
789 		if ((arglen < (2+5) ) || (arg[0] != '-') || (arg[1] != '-'))
790 			break;
791 
792 		arg += 2; /* skip -- */
793 
794 		val = strchr(arg, '=');
795 		if (val == NULL) {
796 			/* no equals sign - next parameter is val */
797 			idx++;
798 			if (idx >= *pargc)
799 				break;
800 			val = argv[idx];
801 		} else {
802 			/* equals sign */
803 			arglen = val - arg ;
804 			val++;
805 		}
806 
807 		/* arg+arglen is the option to set, val is the value */
808 
809 		NSLOG(netsurf, INFO, "%.*s = %s", arglen, arg, val);
810 
811 		for (entry_loop = 0;
812 		     entry_loop < NSOPTION_LISTEND;
813 		     entry_loop++) {
814 			if (strncmp(arg, opts[entry_loop].key, arglen) == 0) {
815 				strtooption(val, opts + entry_loop);
816 				break;
817 			}
818 		}
819 
820 		idx++;
821 	}
822 
823 	/* remove processed options from argv */
824 	for (mv_loop=0; mv_loop < (*pargc - idx); mv_loop++) {
825 		argv[mv_loop + 1] = argv[mv_loop + idx];
826 	}
827 	*pargc -= (idx - 1);
828 
829 	nsoption_validate(opts, nsoptions_default);
830 
831 	return NSERROR_OK;
832 }
833 
834 /* exported interface documented in options.h */
835 int
nsoption_snoptionf(char * string,size_t size,enum nsoption_e option_idx,const char * fmt)836 nsoption_snoptionf(char *string,
837 		   size_t size,
838 		   enum nsoption_e option_idx,
839 		   const char *fmt)
840 {
841 	size_t slen = 0; /* current output string length */
842 	int fmtc = 0; /* current index into format string */
843 	struct nsoption_s *option;
844 
845 	if (fmt == NULL) {
846 		return -1;
847 	}
848 
849 	if (option_idx >= NSOPTION_LISTEND) {
850 		return -1;
851 	}
852 
853 	if (nsoptions == NULL) {
854 		return -1;
855 	}
856 
857 	option = &nsoptions[option_idx]; /* assume the global table */
858 	if (option == NULL || option->key == NULL) {
859 		return -1;
860 	}
861 
862 	while ((slen < size) && (fmt[fmtc] != 0)) {
863 		if (fmt[fmtc] == '%') {
864 			fmtc++;
865 			switch (fmt[fmtc]) {
866 			case 'k':
867 				slen += snprintf(string + slen,
868 						 size - slen,
869 						 "%s",
870 						 option->key);
871 				break;
872 
873 			case 'p':
874 				if (nsoption_is_set(nsoptions,
875 						    nsoptions_default,
876 						    option_idx)) {
877 					slen += snprintf(string + slen,
878 							 size - slen,
879 							 "user");
880 				} else {
881 					slen += snprintf(string + slen,
882 							 size - slen,
883 							 "default");
884 				}
885 				break;
886 
887 			case 't':
888 				switch (option->type) {
889 				case OPTION_BOOL:
890 					slen += snprintf(string + slen,
891 							 size - slen,
892 							 "boolean");
893 					break;
894 
895 				case OPTION_INTEGER:
896 					slen += snprintf(string + slen,
897 							 size - slen,
898 							 "integer");
899 					break;
900 
901 				case OPTION_UINT:
902 					slen += snprintf(string + slen,
903 							 size - slen,
904 							 "unsigned integer");
905 					break;
906 
907 				case OPTION_COLOUR:
908 					slen += snprintf(string + slen,
909 							 size - slen,
910 							 "colour");
911 					break;
912 
913 				case OPTION_STRING:
914 					slen += snprintf(string + slen,
915 							 size - slen,
916 							 "string");
917 					break;
918 
919 				}
920 				break;
921 
922 
923 			case 'V':
924 				slen += nsoption_output_value_html(option,
925 								   size,
926 								   slen,
927 								   string);
928 				break;
929 			case 'v':
930 				slen += nsoption_output_value_text(option,
931 								   size,
932 								   slen,
933 								   string);
934 				break;
935 			}
936 			fmtc++;
937 		} else {
938 			string[slen] = fmt[fmtc];
939 			slen++;
940 			fmtc++;
941 		}
942 	}
943 
944 	/* Ensure that we NUL-terminate the output */
945 	string[min(slen, size - 1)] = '\0';
946 
947 	return slen;
948 }
949 
950 /* exported interface documented in options.h */
951 nserror
nsoption_set_tbl_charp(struct nsoption_s * opts,enum nsoption_e option_idx,char * s)952 nsoption_set_tbl_charp(struct nsoption_s *opts,
953 		       enum nsoption_e option_idx,
954 		       char *s)
955 {
956 	struct nsoption_s *option;
957 
958 	option = &opts[option_idx];
959 
960 	/* ensure it is a string option */
961 	if (option->type != OPTION_STRING) {
962 		return NSERROR_BAD_PARAMETER;
963 	}
964 
965 	/* free any existing string */
966 	if (option->value.s != NULL) {
967 		free(option->value.s);
968 	}
969 
970 	option->value.s = s;
971 
972 	/* check for empty string */
973 	if ((option->value.s != NULL) && (*option->value.s == 0)) {
974 		free(option->value.s);
975 		option->value.s = NULL;
976 	}
977 	return NSERROR_OK;
978 }
979