Line data Source code
1 : /*********************************************************************
2 : *
3 : * File : $Source: /cvsroot/ijbswa/current/cgiedit.c,v $
4 : *
5 : * Purpose : CGI-based actionsfile editor.
6 : *
7 : * NOTE: The CGIs in this file use parameter names
8 : * such as "f" and "s" which are really *BAD* choices.
9 : * However, I'm trying to save bytes in the
10 : * edit-actions-list HTML page - the standard actions
11 : * file generated a 550kbyte page, which is ridiculous.
12 : *
13 : * Stick to the short names in this file for consistency.
14 : *
15 : * Copyright : Written by and Copyright (C) 2001-2014 the
16 : * Privoxy team. https://www.privoxy.org/
17 : *
18 : * Based on the Internet Junkbuster originally written
19 : * by and Copyright (C) 1997 Anonymous Coders and
20 : * Junkbusters Corporation. http://www.junkbusters.com
21 : *
22 : * This program is free software; you can redistribute it
23 : * and/or modify it under the terms of the GNU General
24 : * Public License as published by the Free Software
25 : * Foundation; either version 2 of the License, or (at
26 : * your option) any later version.
27 : *
28 : * This program is distributed in the hope that it will
29 : * be useful, but WITHOUT ANY WARRANTY; without even the
30 : * implied warranty of MERCHANTABILITY or FITNESS FOR A
31 : * PARTICULAR PURPOSE. See the GNU General Public
32 : * License for more details.
33 : *
34 : * The GNU General Public License should be included with
35 : * this file. If not, you can view it at
36 : * http://www.gnu.org/copyleft/gpl.html
37 : * or write to the Free Software Foundation, Inc., 59
38 : * Temple Place - Suite 330, Boston, MA 02111-1307, USA.
39 : *
40 : **********************************************************************/
41 :
42 :
43 : #include "config.h"
44 :
45 : /*
46 : * FIXME: Following includes copied from cgi.c - which are actually needed?
47 : */
48 :
49 : #include <stdio.h>
50 : #include <stdlib.h>
51 : #include <sys/types.h>
52 : #include <ctype.h>
53 630 : #include <string.h>
54 630 : #include <assert.h>
55 11 : #include <sys/stat.h>
56 630 :
57 11 : #include "project.h"
58 11 : #include "cgi.h"
59 630 : #include "cgiedit.h"
60 630 : #include "cgisimple.h"
61 630 : #include "list.h"
62 630 : #include "encode.h"
63 11 : #include "actions.h"
64 630 : #include "miscutil.h"
65 11 : #include "errlog.h"
66 630 : #include "loaders.h"
67 630 : #ifdef FEATURE_TOGGLE
68 630 : /* loadcfg.h is for global_toggle_state only */
69 630 : #include "loadcfg.h"
70 11 : #endif /* def FEATURE_TOGGLE */
71 630 : #include "urlmatch.h"
72 11 :
73 630 :
74 11 : #ifdef FEATURE_CGI_EDIT_ACTIONS
75 11 :
76 630 : /**
77 : * A line in an editable_file.
78 630 : */
79 : struct file_line
80 : {
81 630 : /** Next entry in the linked list */
82 11 : struct file_line * next;
83 11 :
84 : /** The raw data, to write out if this line is unmodified. */
85 630 : char * raw;
86 630 :
87 630 : /** Comments and/or whitespace to put before this line if it's modified
88 11 : and then written out. */
89 630 : char * prefix;
90 630 :
91 630 : /** The actual data, as a string. Line continuation and comment removal
92 11 : are performed on the data read from file before it's stored here, so
93 11 : it will be a single line of data. */
94 630 : char * unprocessed;
95 11 :
96 11 : /** The type of data on this line. One of the FILE_LINE_xxx constants. */
97 630 : int type;
98 11 :
99 11 : /** The actual data, processed into some sensible data type. */
100 630 : union
101 11 : {
102 11 :
103 630 : /** An action specification. */
104 11 : struct action_spec action[1];
105 11 :
106 11 : /** A name=value pair. */
107 11 : struct
108 11 : {
109 630 :
110 11 : /** The name in the name=value pair. */
111 : char * name;
112 630 :
113 630 : /** The value in the name=value pair, as a string. */
114 : char * svalue;
115 630 :
116 11 : /** The value in the name=value pair, as an integer. */
117 630 : int ivalue;
118 11 :
119 630 : } setting;
120 11 :
121 11 : } data;
122 11 :
123 630 : };
124 630 :
125 11 : /** This file_line has not been processed yet. */
126 630 : #define FILE_LINE_UNPROCESSED 1
127 630 :
128 630 : /** This file_line is blank. Can only appear at the end of a file, due to
129 630 : the way the parser works. */
130 11 : #define FILE_LINE_BLANK 2
131 11 :
132 11 : /** This file_line says {{alias}}. */
133 630 : #define FILE_LINE_ALIAS_HEADER 3
134 :
135 : /** This file_line defines an alias. */
136 : #define FILE_LINE_ALIAS_ENTRY 4
137 :
138 : /** This file_line defines an {action}. */
139 : #define FILE_LINE_ACTION 5
140 :
141 : /** This file_line specifies a URL pattern. */
142 : #define FILE_LINE_URL 6
143 :
144 : /** This file_line says {{settings}}. */
145 : #define FILE_LINE_SETTINGS_HEADER 7
146 :
147 : /** This file_line is in a {{settings}} block. */
148 : #define FILE_LINE_SETTINGS_ENTRY 8
149 :
150 : /** This file_line says {{description}}. */
151 : #define FILE_LINE_DESCRIPTION_HEADER 9
152 :
153 : /** This file_line is in a {{description}} block. */
154 : #define FILE_LINE_DESCRIPTION_ENTRY 10
155 :
156 : /*
157 : * Number of file modification time mismatches
158 : * before the CGI editor gets turned off.
159 : */
160 : #define ACCEPTABLE_TIMESTAMP_MISMATCHES 3
161 :
162 : /**
163 : * A configuration file, in a format that can be edited and written back to
164 : * disk.
165 : */
166 : struct editable_file
167 : {
168 : struct file_line * lines; /**< The contents of the file. A linked list of lines. */
169 : const char * filename; /**< Full pathname - e.g. "/etc/privoxy/wibble.action". */
170 : unsigned identifier; /**< The file name's position in csp->config->actions_file[]. */
171 : const char * version_str; /**< Last modification time, as a string. For CGI param. */
172 : /**< Can be used in URL without using url_param(). */
173 : unsigned version; /**< Last modification time - prevents chaos with
174 : the browser's "back" button. Note that this is a
175 : time_t cast to an unsigned. When comparing, always
176 : cast the time_t to an unsigned, and *NOT* vice-versa.
177 : This may lose the top few bits, but they're not
178 : significant anyway. */
179 : int newline; /**< Newline convention - one of the NEWLINE_xxx constants.
180 : Note that changing this after the file has been
181 : read in will cause a mess. */
182 : struct file_line * parse_error; /**< On parse error, this is the offending line. */
183 : const char * parse_error_text; /**< On parse error, this is the problem.
184 : (Statically allocated) */
185 : };
186 :
187 : /**
188 : * Information about the filter types.
189 : * Used for macro replacement in cgi_edit_actions_for_url.
190 : */
191 : struct filter_type_info
192 : {
193 : const int multi_action_index; /**< The multi action index as defined in project.h */
194 : const char *macro_name; /**< Name of the macro that has to be replaced
195 : with the prepared templates.
196 : For example "content-filter-params" */
197 : const char *type; /**< Name of the filter type,
198 : for example "server-header-filter". */
199 : /* XXX: check if these two can be combined. */
200 : const char *disable_all_option; /**< Name of the catch-all radio option that has
201 : to be checked or unchecked for this filter type. */
202 : const char *disable_all_param; /**< Name of the parameter that causes all filters of
203 : this type to be disabled. */
204 : const char *abbr_type; /**< Abbreviation of the filter type, usually the
205 : first or second character capitalized */
206 : const char *anchor; /**< Anchor for the User Manual link,
207 : for example "SERVER-HEADER-FILTER" */
208 : };
209 :
210 : /* Accessed by index, keep the order in the way the FT_ macros are defined. */
211 : static const struct filter_type_info filter_type_info[] =
212 : {
213 : {
214 : ACTION_MULTI_FILTER,
215 : "content-filter-params", "filter",
216 : "filter-all", "filter_all",
217 : "F", "FILTER"
218 : },
219 : {
220 : ACTION_MULTI_CLIENT_HEADER_FILTER,
221 : "client-header-filter-params", "client-header-filter",
222 : "client-header-filter-all", "client_header_filter_all",
223 : "C", "CLIENT-HEADER-FILTER"
224 : },
225 : {
226 : ACTION_MULTI_SERVER_HEADER_FILTER,
227 : "server-header-filter-params", "server-header-filter",
228 : "server-header-filter-all", "server_header_filter_all",
229 : "S", "SERVER-HEADER-FILTER"
230 : },
231 : {
232 : ACTION_MULTI_CLIENT_HEADER_TAGGER,
233 : "client-header-tagger-params", "client-header-tagger",
234 : "client-header-tagger-all", "client_header_tagger_all",
235 : "L", "CLIENT-HEADER-TAGGER"
236 : },
237 : {
238 : ACTION_MULTI_SERVER_HEADER_TAGGER,
239 : "server-header-tagger-params", "server-header-tagger",
240 : "server-header-tagger-all", "server_header_tagger_all",
241 : "E", "SERVER-HEADER-TAGGER"
242 : },
243 : {
244 : ACTION_MULTI_SUPPRESS_TAG,
245 : "suppress-tag-params", "suppress-tag",
246 : "suppress-tag-all", "suppress_tag_all",
247 : "U", "SUPPRESS-TAG"
248 : },
249 : {
250 : ACTION_MULTI_CLIENT_BODY_FILTER,
251 : "client-body-filter-params", "client-body-filter",
252 : "client-body-filter-all", "client_body_filter_all",
253 : "P", "CLIENT-BODY-FILTER"
254 : },
255 : #ifdef FEATURE_EXTERNAL_FILTERS
256 : {
257 : ACTION_MULTI_EXTERNAL_FILTER,
258 : "external-content-filter-params", "external-filter",
259 : "external-content-filter-all", "external_content_filter_all",
260 : "E", "EXTERNAL-CONTENT-FILTER"
261 : },
262 : #endif
263 : };
264 :
265 : /* FIXME: Following non-static functions should be prototyped in .h or made static */
266 :
267 : /* Functions to read and write arbitrary config files */
268 : jb_err edit_read_file(struct client_state *csp,
269 : const struct map *parameters,
270 : int require_version,
271 : struct editable_file **pfile);
272 : jb_err edit_write_file(struct editable_file * file);
273 : void edit_free_file(struct editable_file * file);
274 :
275 : /* Functions to read and write actions files */
276 : jb_err edit_parse_actions_file(struct editable_file * file);
277 : jb_err edit_read_actions_file(struct client_state *csp,
278 : struct http_response *rsp,
279 : const struct map *parameters,
280 : int require_version,
281 : struct editable_file **pfile);
282 :
283 : /* Error handlers */
284 : jb_err cgi_error_modified(struct client_state *csp,
285 : struct http_response *rsp,
286 : const char *filename);
287 : jb_err cgi_error_parse(struct client_state *csp,
288 : struct http_response *rsp,
289 : struct editable_file *file);
290 : jb_err cgi_error_file(struct client_state *csp,
291 : struct http_response *rsp,
292 : const char *filename);
293 : jb_err cgi_error_file_read_only(struct client_state *csp,
294 : struct http_response *rsp,
295 : const char *filename);
296 :
297 : /* Internal arbitrary config file support functions */
298 : static jb_err edit_read_file_lines(FILE *fp, struct file_line ** pfile, int *newline);
299 : static void edit_free_file_lines(struct file_line * first_line);
300 :
301 : /* Internal actions file support functions */
302 : static int match_actions_file_header_line(const char * line, const char * name);
303 : static jb_err split_line_on_equals(const char * line, char ** pname, char ** pvalue);
304 :
305 : /* Internal parameter parsing functions */
306 : static jb_err get_url_spec_param(struct client_state *csp,
307 : const struct map *parameters,
308 : const char *name,
309 : char **pvalue);
310 :
311 :
312 : /* Internal actionsfile <==> HTML conversion functions */
313 : static jb_err map_radio(struct map * exports,
314 : const char * optionname,
315 : const char * values,
316 : int value);
317 : static jb_err actions_to_radio(struct map * exports,
318 : const struct action_spec *action);
319 : static jb_err actions_from_radio(const struct map * parameters,
320 : struct action_spec *action);
321 : static jb_err action_render_string_filters_template(struct map * exports,
322 : const struct action_spec *action,
323 : const char* flter_template,
324 : const struct filter_type_info *type);
325 :
326 :
327 : static jb_err map_copy_parameter_html(struct map *out,
328 : const struct map *in,
329 : const char *name);
330 :
331 : static jb_err get_file_name_param(struct client_state *csp,
332 : const struct map *parameters,
333 : const char *param_name,
334 : const char **pfilename);
335 :
336 : /* Internal convenience functions */
337 : static char *section_target(const unsigned sectionid);
338 :
339 : /*********************************************************************
340 : *
341 : * Function : section_target
342 : *
343 : * Description : Given an unsigned (section id) n, produce a dynamically
344 : * allocated string of the form #l<n>, for use in link
345 : * targets.
346 : *
347 : * XXX: The hash should be moved into the templates
348 : * to make this function more generic and render
349 : * stringify() obsolete.
350 : *
351 : * Parameters :
352 : * 1 : sectionid = start line number of section
353 : *
354 : * Returns : String with link target, or NULL if out of
355 : * memory
356 : *
357 : *********************************************************************/
358 23 : static char *section_target(const unsigned sectionid)
359 : {
360 : char buf[30];
361 :
362 23 : snprintf(buf, sizeof(buf), "#l%u", sectionid);
363 23 : return(strdup(buf));
364 :
365 : }
366 :
367 :
368 : /*********************************************************************
369 : *
370 : * Function : stringify
371 : *
372 : * Description : Convert a number into a dynamically allocated string.
373 : *
374 : * Parameters :
375 : * 1 : number = The number to convert.
376 : *
377 : * Returns : String with link target, or NULL if out of memory
378 : *
379 : *********************************************************************/
380 2640 : static char *stringify(const unsigned number)
381 : {
382 : char buf[6];
383 :
384 2640 : snprintf(buf, sizeof(buf), "%u", number);
385 2640 : return strdup(buf);
386 : }
387 :
388 :
389 : /*********************************************************************
390 : *
391 : * Function : map_copy_parameter_html
392 : *
393 : * Description : Copy a CGI parameter from one map to another, HTML
394 : * encoding it.
395 : *
396 : * Parameters :
397 : * 1 : out = target map
398 : * 2 : in = source map
399 : * 3 : name = name of cgi parameter to copy
400 : *
401 : * Returns : JB_ERR_OK on success
402 : * JB_ERR_MEMORY on out-of-memory
403 : * JB_ERR_CGI_PARAMS if the parameter doesn't exist
404 : * in the source map
405 : *
406 : *********************************************************************/
407 65 : static jb_err map_copy_parameter_html(struct map *out,
408 : const struct map *in,
409 : const char *name)
410 : {
411 : const char * value;
412 : jb_err err;
413 :
414 65 : assert(out);
415 65 : assert(in);
416 65 : assert(name);
417 :
418 65 : value = lookup(in, name);
419 65 : err = map(out, name, 1, html_encode(value), 0);
420 :
421 65 : if (err)
422 : {
423 : /* Out of memory */
424 0 : return err;
425 : }
426 65 : else if (*value == '\0')
427 : {
428 34 : return JB_ERR_CGI_PARAMS;
429 : }
430 : else
431 : {
432 31 : return JB_ERR_OK;
433 : }
434 : }
435 :
436 :
437 : /*********************************************************************
438 : *
439 : * Function : cgi_edit_actions_url_form
440 : *
441 : * Description : CGI function that displays a form for
442 : * edit-actions-url
443 : *
444 : * Parameters :
445 : * 1 : csp = Current client state (buffers, headers, etc...)
446 : * 2 : rsp = http_response data structure for output
447 : * 3 : parameters = map of cgi parameters
448 : *
449 : * CGI Parameters
450 : * i : (action index) Identifies the file to edit
451 : * v : (version) File's last-modified time
452 : * p : (pattern) Line number of pattern to edit
453 : *
454 : * Returns : JB_ERR_OK on success
455 : * JB_ERR_MEMORY on out-of-memory
456 : * JB_ERR_CGI_PARAMS if the CGI parameters are not
457 : * specified or not valid.
458 : *
459 : *********************************************************************/
460 224 : jb_err cgi_edit_actions_url_form(struct client_state *csp,
461 : struct http_response *rsp,
462 : const struct map *parameters)
463 : {
464 : struct map * exports;
465 : unsigned patternid;
466 : struct editable_file * file;
467 : struct file_line * cur_line;
468 : unsigned line_number;
469 224 : unsigned section_start_line_number = 0;
470 : jb_err err;
471 :
472 224 : assert(csp);
473 224 : assert(rsp);
474 224 : assert(parameters);
475 :
476 224 : if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
477 : {
478 0 : return cgi_error_disabled(csp, rsp);
479 : }
480 :
481 224 : err = get_number_param(csp, parameters, "p", &patternid);
482 224 : if (err)
483 : {
484 130 : return err;
485 : }
486 :
487 94 : err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
488 94 : if (err)
489 : {
490 : /* No filename specified, can't read file, modified, or out of memory. */
491 68 : return (err == JB_ERR_FILE ? JB_ERR_OK : err);
492 : }
493 :
494 26 : cur_line = file->lines;
495 :
496 188 : for (line_number = 1; (cur_line != NULL) && (line_number < patternid); line_number++)
497 : {
498 162 : if (cur_line->type == FILE_LINE_ACTION)
499 : {
500 69 : section_start_line_number = line_number;
501 : }
502 162 : cur_line = cur_line->next;
503 : }
504 :
505 26 : if ( (cur_line == NULL)
506 22 : || (line_number != patternid)
507 11 : || (patternid < 1U)
508 11 : || (cur_line->type != FILE_LINE_URL))
509 : {
510 : /* Invalid "patternid" parameter */
511 17 : edit_free_file(file);
512 17 : return JB_ERR_CGI_PARAMS;
513 : }
514 :
515 9 : if (NULL == (exports = default_exports(csp, NULL)))
516 : {
517 0 : edit_free_file(file);
518 0 : return JB_ERR_MEMORY;
519 : }
520 :
521 9 : err = map(exports, "f", 1, stringify(file->identifier), 0);
522 9 : if (!err) err = map(exports, "v", 1, file->version_str, 1);
523 9 : if (!err) err = map(exports, "p", 1, url_encode(lookup(parameters, "p")), 0);
524 9 : if (!err) err = map(exports, "u", 1, html_encode(cur_line->unprocessed), 0);
525 9 : if (!err) err = map(exports, "jumptarget", 1, section_target(section_start_line_number), 0);
526 :
527 9 : edit_free_file(file);
528 :
529 9 : if (err)
530 : {
531 0 : free_map(exports);
532 0 : return err;
533 : }
534 :
535 9 : return template_fill_for_cgi(csp, "edit-actions-url-form", exports, rsp);
536 : }
537 :
538 :
539 : /*********************************************************************
540 : *
541 : * Function : cgi_edit_actions_add_url_form
542 : *
543 : * Description : CGI function that displays a form for
544 : * edit-actions-url
545 : *
546 : * Parameters :
547 : * 1 : csp = Current client state (buffers, headers, etc...)
548 : * 2 : rsp = http_response data structure for output
549 : * 3 : parameters = map of cgi parameters
550 : *
551 : * CGI Parameters :
552 : * f : (filename) Identifies the file to edit
553 : * v : (version) File's last-modified time
554 : * s : (section) Line number of section to edit
555 : *
556 : * Returns : JB_ERR_OK on success
557 : * JB_ERR_MEMORY on out-of-memory
558 : * JB_ERR_CGI_PARAMS if the CGI parameters are not
559 : * specified or not valid.
560 : *
561 : *********************************************************************/
562 41 : jb_err cgi_edit_actions_add_url_form(struct client_state *csp,
563 : struct http_response *rsp,
564 : const struct map *parameters)
565 : {
566 : struct map *exports;
567 : jb_err err;
568 :
569 41 : assert(csp);
570 41 : assert(rsp);
571 41 : assert(parameters);
572 :
573 41 : if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
574 : {
575 0 : return cgi_error_disabled(csp, rsp);
576 : }
577 :
578 41 : if (NULL == (exports = default_exports(csp, NULL)))
579 : {
580 0 : return JB_ERR_MEMORY;
581 : }
582 :
583 41 : err = map_copy_parameter_html(exports, parameters, "f");
584 41 : if (!err) err = map_copy_parameter_html(exports, parameters, "v");
585 41 : if (!err) err = map_copy_parameter_html(exports, parameters, "s");
586 :
587 41 : if (err)
588 : {
589 34 : free_map(exports);
590 34 : return err;
591 : }
592 :
593 7 : return template_fill_for_cgi(csp, "edit-actions-add-url-form", exports, rsp);
594 : }
595 :
596 :
597 : /*********************************************************************
598 : *
599 : * Function : cgi_edit_actions_remove_url_form
600 : *
601 : * Description : CGI function that displays a form for
602 : * edit-actions-url
603 : *
604 : * Parameters :
605 : * 1 : csp = Current client state (buffers, headers, etc...)
606 : * 2 : rsp = http_response data structure for output
607 : * 3 : parameters = map of cgi parameters
608 : *
609 : * CGI Parameters :
610 : * f : (number) The action file identifier.
611 : * v : (version) File's last-modified time
612 : * p : (pattern) Line number of pattern to edit
613 : *
614 : * Returns : JB_ERR_OK on success
615 : * JB_ERR_MEMORY on out-of-memory
616 : * JB_ERR_CGI_PARAMS if the CGI parameters are not
617 : * specified or not valid.
618 : *
619 : *********************************************************************/
620 106 : jb_err cgi_edit_actions_remove_url_form(struct client_state *csp,
621 : struct http_response *rsp,
622 : const struct map *parameters)
623 : {
624 : struct map * exports;
625 : unsigned patternid;
626 : struct editable_file * file;
627 : struct file_line * cur_line;
628 : unsigned line_number;
629 106 : unsigned section_start_line_number = 0;
630 : jb_err err;
631 :
632 106 : assert(csp);
633 106 : assert(rsp);
634 106 : assert(parameters);
635 :
636 106 : if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
637 : {
638 0 : return cgi_error_disabled(csp, rsp);
639 : }
640 :
641 106 : err = get_number_param(csp, parameters, "p", &patternid);
642 106 : if (err)
643 : {
644 71 : return err;
645 : }
646 :
647 35 : err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
648 35 : if (err)
649 : {
650 : /* No filename specified, can't read file, modified, or out of memory. */
651 10 : return (err == JB_ERR_FILE ? JB_ERR_OK : err);
652 : }
653 :
654 25 : cur_line = file->lines;
655 :
656 194 : for (line_number = 1; (cur_line != NULL) && (line_number < patternid); line_number++)
657 : {
658 169 : if (cur_line->type == FILE_LINE_ACTION)
659 : {
660 76 : section_start_line_number = line_number;
661 : }
662 169 : cur_line = cur_line->next;
663 : }
664 :
665 25 : if ( (cur_line == NULL)
666 18 : || (line_number != patternid)
667 16 : || (patternid < 1U)
668 16 : || (cur_line->type != FILE_LINE_URL))
669 : {
670 : /* Invalid "patternid" parameter */
671 11 : edit_free_file(file);
672 11 : return JB_ERR_CGI_PARAMS;
673 : }
674 :
675 14 : if (NULL == (exports = default_exports(csp, NULL)))
676 : {
677 0 : edit_free_file(file);
678 0 : return JB_ERR_MEMORY;
679 : }
680 :
681 14 : err = map(exports, "f", 1, stringify(file->identifier), 0);
682 14 : if (!err) err = map(exports, "v", 1, file->version_str, 1);
683 14 : if (!err) err = map(exports, "p", 1, url_encode(lookup(parameters, "p")), 0);
684 14 : if (!err) err = map(exports, "u", 1, html_encode(cur_line->unprocessed), 0);
685 14 : if (!err) err = map(exports, "jumptarget", 1, section_target(section_start_line_number), 0);
686 14 : if (!err) err = map(exports, "actions-file", 1, html_encode(file->filename), 0);
687 :
688 14 : edit_free_file(file);
689 :
690 14 : if (err)
691 : {
692 0 : free_map(exports);
693 0 : return err;
694 : }
695 :
696 14 : return template_fill_for_cgi(csp, "edit-actions-remove-url-form", exports, rsp);
697 : }
698 :
699 :
700 : /*********************************************************************
701 : *
702 : * Function : edit_write_file
703 : *
704 : * Description : Write a complete file to disk.
705 : *
706 : * Parameters :
707 : * 1 : file = File to write.
708 : *
709 : * Returns : JB_ERR_OK on success
710 : * JB_ERR_FILE on error writing to file.
711 : * JB_ERR_MEMORY on out of memory
712 : *
713 : *********************************************************************/
714 743 : jb_err edit_write_file(struct editable_file * file)
715 : {
716 :
717 : //Let's.. not actually change our file around.
718 743 : return JB_ERR_OK;
719 :
720 : FILE * fp;
721 : struct file_line * cur_line;
722 : struct stat statbuf[1];
723 : char version_buf[22]; /* 22 = ceil(log10(2^64)) + 2 = max number of
724 : digits in time_t, assuming this is a 64-bit
725 : machine, plus null terminator, plus one
726 : for paranoia */
727 :
728 : assert(file);
729 : assert(file->filename);
730 :
731 : if (NULL == (fp = fopen(file->filename, "wb")))
732 : {
733 : return JB_ERR_FILE;
734 : }
735 :
736 : cur_line = file->lines;
737 : while (cur_line != NULL)
738 : {
739 : if (cur_line->raw)
740 : {
741 : if (fputs(cur_line->raw, fp) < 0)
742 : {
743 : fclose(fp);
744 : return JB_ERR_FILE;
745 : }
746 : }
747 : else
748 : {
749 : if (cur_line->prefix)
750 : {
751 : if (fputs(cur_line->prefix, fp) < 0)
752 : {
753 : fclose(fp);
754 : return JB_ERR_FILE;
755 : }
756 : }
757 : if (cur_line->unprocessed)
758 : {
759 :
760 : if (NULL != strchr(cur_line->unprocessed, '#'))
761 : {
762 : /* Must quote '#' characters */
763 : int numhash = 0;
764 : size_t len;
765 : char * src;
766 : char * dest;
767 : char * str;
768 :
769 : /* Count number of # characters, so we know length of output string */
770 : src = cur_line->unprocessed;
771 : while (NULL != (src = strchr(src, '#')))
772 : {
773 : numhash++;
774 : src++;
775 : }
776 : assert(numhash > 0);
777 :
778 : /* Allocate new memory for string */
779 : len = strlen(cur_line->unprocessed) + (size_t)numhash;
780 : str = malloc_or_die(len + 1);
781 :
782 : /* Copy string but quote hashes */
783 : src = cur_line->unprocessed;
784 : dest = str;
785 : while (*src)
786 : {
787 : if (*src == '#')
788 : {
789 : *dest++ = '\\';
790 : numhash--;
791 : assert(numhash >= 0);
792 : }
793 : *dest++ = *src++;
794 : }
795 : *dest = '\0';
796 :
797 : assert(numhash == 0);
798 : assert(strlen(str) == len);
799 : assert(str == dest - len);
800 : assert(src - len <= cur_line->unprocessed);
801 :
802 : if ((strlen(str) != len) || (numhash != 0))
803 : {
804 : /*
805 : * Escaping didn't work as expected, go spread the news.
806 : * Only reached in non-debugging builds.
807 : */
808 : log_error(LOG_LEVEL_ERROR,
809 : "Looks like hash escaping failed. %s might be corrupted now.",
810 : file->filename);
811 : }
812 :
813 : if (fputs(str, fp) < 0)
814 : {
815 : free(str);
816 : fclose(fp);
817 : return JB_ERR_FILE;
818 : }
819 :
820 : free(str);
821 : }
822 : else
823 : {
824 : /* Can write without quoting '#' characters. */
825 : if (fputs(cur_line->unprocessed, fp) < 0)
826 : {
827 : fclose(fp);
828 : return JB_ERR_FILE;
829 : }
830 : }
831 : if (fputs(NEWLINE(file->newline), fp) < 0)
832 : {
833 : fclose(fp);
834 : return JB_ERR_FILE;
835 : }
836 : }
837 : else
838 : {
839 : /* FIXME: Write data from file->data->whatever */
840 : assert(0);
841 : }
842 : }
843 : cur_line = cur_line->next;
844 : }
845 :
846 : fclose(fp);
847 :
848 :
849 : /* Update the version stamp in the file structure, since we just
850 : * wrote to the file & changed it's date.
851 : */
852 : if (stat(file->filename, statbuf) < 0)
853 : {
854 : /* Error, probably file not found. */
855 : return JB_ERR_FILE;
856 : }
857 : file->version = (unsigned)statbuf->st_mtime;
858 :
859 : /* Correct file->version_str */
860 : freez(file->version_str);
861 : snprintf(version_buf, sizeof(version_buf), "%u", file->version);
862 : version_buf[sizeof(version_buf)-1] = '\0';
863 : file->version_str = strdup_or_die(version_buf);
864 :
865 : return JB_ERR_OK;
866 : }
867 :
868 :
869 : /*********************************************************************
870 : *
871 : * Function : edit_free_file
872 : *
873 : * Description : Free a complete file in memory.
874 : *
875 : * Parameters :
876 : * 1 : file = Data structure to free.
877 : *
878 : * Returns : N/A
879 : *
880 : *********************************************************************/
881 3886 : void edit_free_file(struct editable_file * file)
882 : {
883 3886 : if (!file)
884 : {
885 : /* Silently ignore NULL pointer */
886 0 : return;
887 : }
888 :
889 3886 : edit_free_file_lines(file->lines);
890 3886 : freez(file->version_str);
891 3886 : file->version = 0;
892 3886 : file->parse_error_text = NULL; /* Statically allocated */
893 3886 : file->parse_error = NULL;
894 :
895 3886 : free(file);
896 : }
897 :
898 :
899 : /*********************************************************************
900 : *
901 : * Function : edit_free_file_lines
902 : *
903 : * Description : Free an entire linked list of file lines.
904 : *
905 : * Parameters :
906 : * 1 : first_line = Data structure to free.
907 : *
908 : * Returns : N/A
909 : *
910 : *********************************************************************/
911 3887 : static void edit_free_file_lines(struct file_line * first_line)
912 : {
913 : struct file_line * next_line;
914 :
915 95300 : while (first_line != NULL)
916 : {
917 91413 : next_line = first_line->next;
918 91413 : first_line->next = NULL;
919 91413 : freez(first_line->raw);
920 91413 : freez(first_line->prefix);
921 91413 : freez(first_line->unprocessed);
922 91413 : switch(first_line->type)
923 : {
924 53525 : case 0: /* special case if memory zeroed */
925 : case FILE_LINE_UNPROCESSED:
926 : case FILE_LINE_BLANK:
927 : case FILE_LINE_ALIAS_HEADER:
928 : case FILE_LINE_SETTINGS_HEADER:
929 : case FILE_LINE_DESCRIPTION_HEADER:
930 : case FILE_LINE_DESCRIPTION_ENTRY:
931 : case FILE_LINE_ALIAS_ENTRY:
932 : case FILE_LINE_URL:
933 : /* No data is stored for these */
934 53525 : break;
935 :
936 36170 : case FILE_LINE_ACTION:
937 36170 : free_action(first_line->data.action);
938 36170 : break;
939 :
940 1718 : case FILE_LINE_SETTINGS_ENTRY:
941 1718 : freez(first_line->data.setting.name);
942 1718 : freez(first_line->data.setting.svalue);
943 1718 : break;
944 0 : default:
945 : /* Should never happen */
946 0 : assert(0);
947 : break;
948 : }
949 91413 : first_line->type = 0; /* paranoia */
950 91413 : free(first_line);
951 91413 : first_line = next_line;
952 : }
953 3887 : }
954 :
955 :
956 : /*********************************************************************
957 : *
958 : * Function : match_actions_file_header_line
959 : *
960 : * Description : Match an actions file {{header}} line
961 : *
962 : * Parameters :
963 : * 1 : line = String from file
964 : * 2 : name = Header to match against
965 : *
966 : * Returns : 0 iff they match.
967 : *
968 : *********************************************************************/
969 11658 : static int match_actions_file_header_line(const char * line, const char * name)
970 : {
971 : size_t len;
972 :
973 11658 : assert(line);
974 11658 : assert(name);
975 :
976 : /* Look for "{{" */
977 11658 : if ((line[0] != '{') || (line[1] != '{'))
978 : {
979 6504 : return 1;
980 : }
981 5154 : line += 2;
982 :
983 : /* Look for optional whitespace */
984 5154 : while ((*line == ' ') || (*line == '\t'))
985 : {
986 0 : line++;
987 : }
988 :
989 : /* Look for the specified name (case-insensitive) */
990 5154 : len = strlen(name);
991 5154 : if (0 != strncmpic(line, name, len))
992 : {
993 1718 : return 1;
994 : }
995 3436 : line += len;
996 :
997 : /* Look for optional whitespace */
998 3436 : while ((*line == ' ') || (*line == '\t'))
999 : {
1000 0 : line++;
1001 : }
1002 :
1003 : /* Look for "}}" and end of string*/
1004 3436 : if ((line[0] != '}') || (line[1] != '}') || (line[2] != '\0'))
1005 : {
1006 0 : return 1;
1007 : }
1008 :
1009 : /* It matched!! */
1010 3436 : return 0;
1011 : }
1012 :
1013 :
1014 : /*********************************************************************
1015 : *
1016 : * Function : match_actions_file_header_line
1017 : *
1018 : * Description : Match an actions file {{header}} line
1019 : *
1020 : * Parameters :
1021 : * 1 : line = String from file. Must not start with
1022 : * whitespace (else infinite loop!)
1023 : * 2 : pname = Destination for name
1024 : * 2 : pvalue = Destination for value
1025 : *
1026 : * Returns : JB_ERR_OK on success
1027 : * JB_ERR_MEMORY on out-of-memory
1028 : * JB_ERR_PARSE if there's no "=" sign, or if there's
1029 : * nothing before the "=" sign (but empty
1030 : * values *after* the "=" sign are legal).
1031 : *
1032 : *********************************************************************/
1033 13744 : static jb_err split_line_on_equals(const char * line, char ** pname, char ** pvalue)
1034 : {
1035 : const char * name_end;
1036 : const char * value_start;
1037 : size_t name_len;
1038 :
1039 13744 : assert(line);
1040 13744 : assert(pname);
1041 13744 : assert(pvalue);
1042 13744 : assert(*line != ' ');
1043 13744 : assert(*line != '\t');
1044 :
1045 13744 : *pname = NULL;
1046 13744 : *pvalue = NULL;
1047 :
1048 13744 : value_start = strchr(line, '=');
1049 13744 : if ((value_start == NULL) || (value_start == line))
1050 : {
1051 0 : return JB_ERR_PARSE;
1052 : }
1053 :
1054 13744 : name_end = value_start - 1;
1055 :
1056 : /* Eat any whitespace before the '=' */
1057 54976 : while ((*name_end == ' ') || (*name_end == '\t'))
1058 : {
1059 : /*
1060 : * we already know we must have at least 1 non-ws char
1061 : * at start of buf - no need to check
1062 : */
1063 41232 : name_end--;
1064 : }
1065 :
1066 13744 : name_len = (size_t)(name_end - line) + 1; /* Length excluding \0 */
1067 13744 : *pname = malloc_or_die(name_len + 1);
1068 13744 : strncpy(*pname, line, name_len);
1069 13744 : (*pname)[name_len] = '\0';
1070 :
1071 : /* Eat any the whitespace after the '=' */
1072 13744 : value_start++;
1073 25770 : while ((*value_start == ' ') || (*value_start == '\t'))
1074 : {
1075 12026 : value_start++;
1076 : }
1077 :
1078 13744 : if (NULL == (*pvalue = strdup(value_start)))
1079 : {
1080 0 : free(*pname);
1081 0 : *pname = NULL;
1082 0 : return JB_ERR_MEMORY;
1083 : }
1084 :
1085 13744 : return JB_ERR_OK;
1086 : }
1087 :
1088 :
1089 : /*********************************************************************
1090 : *
1091 : * Function : edit_parse_actions_file
1092 : *
1093 : * Description : Parse an actions file in memory.
1094 : *
1095 : * Passed linked list must have the "data" member
1096 : * zeroed, and must contain valid "next" and
1097 : * "unprocessed" fields. The "raw" and "prefix"
1098 : * fields are ignored, and "type" is just overwritten.
1099 : *
1100 : * Note that on error the file may have been
1101 : * partially parsed.
1102 : *
1103 : * Parameters :
1104 : * 1 : file = Actions file to be parsed in-place.
1105 : *
1106 : * Returns : JB_ERR_OK on success
1107 : * JB_ERR_MEMORY on out-of-memory
1108 : * JB_ERR_PARSE on error
1109 : *
1110 : *********************************************************************/
1111 3886 : jb_err edit_parse_actions_file(struct editable_file * file)
1112 : {
1113 : struct file_line * cur_line;
1114 : size_t len;
1115 : const char * text; /* Text from a line */
1116 : char * name; /* For lines of the form name=value */
1117 : char * value; /* For lines of the form name=value */
1118 3886 : struct action_alias * alias_list = NULL;
1119 3886 : jb_err err = JB_ERR_OK;
1120 :
1121 : /* alias_list contains the aliases defined in this file.
1122 : * It might be better to use the "file_line.data" fields
1123 : * in the relevant places instead.
1124 : */
1125 :
1126 3886 : cur_line = file->lines;
1127 :
1128 : /* A note about blank line support: Blank lines should only
1129 : * ever occur as the last line in the file. This function
1130 : * is more forgiving than that - FILE_LINE_BLANK can occur
1131 : * anywhere.
1132 : */
1133 :
1134 : /* Skip leading blanks. Should only happen if file is
1135 : * empty (which is valid, but pointless).
1136 : */
1137 3886 : while ((cur_line != NULL)
1138 3886 : && (cur_line->unprocessed[0] == '\0'))
1139 : {
1140 : /* Blank line */
1141 0 : cur_line->type = FILE_LINE_BLANK;
1142 0 : cur_line = cur_line->next;
1143 : }
1144 :
1145 3886 : if ((cur_line != NULL)
1146 3886 : && (cur_line->unprocessed[0] != '{'))
1147 : {
1148 : /* File doesn't start with a header */
1149 0 : file->parse_error = cur_line;
1150 0 : file->parse_error_text = "First (non-comment) line of the file must contain a header.";
1151 0 : return JB_ERR_PARSE;
1152 : }
1153 :
1154 7772 : if ((cur_line != NULL) && (0 ==
1155 3886 : match_actions_file_header_line(cur_line->unprocessed, "settings")))
1156 : {
1157 1718 : cur_line->type = FILE_LINE_SETTINGS_HEADER;
1158 :
1159 1718 : cur_line = cur_line->next;
1160 3436 : while ((cur_line != NULL) && (cur_line->unprocessed[0] != '{'))
1161 : {
1162 1718 : if (cur_line->unprocessed[0])
1163 : {
1164 1718 : cur_line->type = FILE_LINE_SETTINGS_ENTRY;
1165 :
1166 1718 : err = split_line_on_equals(cur_line->unprocessed,
1167 : &cur_line->data.setting.name,
1168 : &cur_line->data.setting.svalue);
1169 1718 : if (err == JB_ERR_MEMORY)
1170 : {
1171 0 : return err;
1172 : }
1173 1718 : else if (err != JB_ERR_OK)
1174 : {
1175 : /* Line does not contain a name=value pair */
1176 0 : file->parse_error = cur_line;
1177 0 : file->parse_error_text = "Expected a name=value pair on this {{description}} line, but couldn't find one.";
1178 0 : return JB_ERR_PARSE;
1179 : }
1180 : }
1181 : else
1182 : {
1183 0 : cur_line->type = FILE_LINE_BLANK;
1184 : }
1185 1718 : cur_line = cur_line->next;
1186 : }
1187 : }
1188 :
1189 7772 : if ((cur_line != NULL) && (0 ==
1190 3886 : match_actions_file_header_line(cur_line->unprocessed, "description")))
1191 : {
1192 0 : cur_line->type = FILE_LINE_DESCRIPTION_HEADER;
1193 :
1194 0 : cur_line = cur_line->next;
1195 0 : while ((cur_line != NULL) && (cur_line->unprocessed[0] != '{'))
1196 : {
1197 0 : if (cur_line->unprocessed[0])
1198 : {
1199 0 : cur_line->type = FILE_LINE_DESCRIPTION_ENTRY;
1200 : }
1201 : else
1202 : {
1203 0 : cur_line->type = FILE_LINE_BLANK;
1204 : }
1205 0 : cur_line = cur_line->next;
1206 : }
1207 : }
1208 :
1209 7772 : if ((cur_line != NULL) && (0 ==
1210 3886 : match_actions_file_header_line(cur_line->unprocessed, "alias")))
1211 : {
1212 1718 : cur_line->type = FILE_LINE_ALIAS_HEADER;
1213 :
1214 1718 : cur_line = cur_line->next;
1215 13744 : while ((cur_line != NULL) && (cur_line->unprocessed[0] != '{'))
1216 : {
1217 12026 : if (cur_line->unprocessed[0])
1218 : {
1219 : /* define an alias */
1220 : struct action_alias * new_alias;
1221 :
1222 12026 : cur_line->type = FILE_LINE_ALIAS_ENTRY;
1223 :
1224 12026 : err = split_line_on_equals(cur_line->unprocessed, &name, &value);
1225 12026 : if (err == JB_ERR_MEMORY)
1226 : {
1227 0 : free_alias_list(alias_list);
1228 0 : return err;
1229 : }
1230 12026 : else if (err != JB_ERR_OK)
1231 : {
1232 : /* Line does not contain a name=value pair */
1233 0 : file->parse_error = cur_line;
1234 0 : file->parse_error_text = "Expected a name=value pair on this {{alias}} line, but couldn't find one.";
1235 0 : free_alias_list(alias_list);
1236 0 : return JB_ERR_PARSE;
1237 : }
1238 :
1239 12026 : new_alias = zalloc_or_die(sizeof(*new_alias));
1240 :
1241 12026 : err = get_actions(value, alias_list, new_alias->action);
1242 12026 : if (err)
1243 : {
1244 : /* Invalid action or out of memory */
1245 0 : free(name);
1246 0 : free(value);
1247 0 : free(new_alias);
1248 0 : free_alias_list(alias_list);
1249 0 : if (err == JB_ERR_MEMORY)
1250 : {
1251 0 : return err;
1252 : }
1253 : else
1254 : {
1255 : /* Line does not contain a name=value pair */
1256 0 : file->parse_error = cur_line;
1257 0 : file->parse_error_text = "This alias does not specify a valid set of actions.";
1258 0 : return JB_ERR_PARSE;
1259 : }
1260 : }
1261 :
1262 12026 : free(value);
1263 :
1264 12026 : new_alias->name = name;
1265 :
1266 : /* add to list */
1267 12026 : new_alias->next = alias_list;
1268 12026 : alias_list = new_alias;
1269 : }
1270 : else
1271 : {
1272 0 : cur_line->type = FILE_LINE_BLANK;
1273 : }
1274 12026 : cur_line = cur_line->next;
1275 : }
1276 : }
1277 :
1278 : /* Header done, process the main part of the file */
1279 40046 : while (cur_line != NULL)
1280 : {
1281 : /* At this point, (cur_line->unprocessed[0] == '{') */
1282 36160 : assert(cur_line->unprocessed[0] == '{');
1283 36160 : text = cur_line->unprocessed + 1;
1284 36160 : len = strlen(text) - 1;
1285 36160 : if (text[len] != '}')
1286 : {
1287 : /* No closing } on header */
1288 0 : free_alias_list(alias_list);
1289 0 : file->parse_error = cur_line;
1290 0 : file->parse_error_text = "Headers starting with '{' must have a "
1291 : "closing bracket ('}'). Headers starting with two brackets ('{{') "
1292 : "must close with two brackets ('}}').";
1293 0 : return JB_ERR_PARSE;
1294 : }
1295 :
1296 36160 : if (text[0] == '{')
1297 : {
1298 : /* An invalid {{ header. */
1299 0 : free_alias_list(alias_list);
1300 0 : file->parse_error = cur_line;
1301 0 : file->parse_error_text = "Unknown or unexpected two-bracket header. "
1302 : "Please remember that the system (two-bracket) headers must "
1303 : "appear in the order {{settings}}, {{description}}, {{alias}}, "
1304 : "and must appear before any actions (one-bracket) headers. "
1305 : "Also note that system headers may not be repeated.";
1306 0 : return JB_ERR_PARSE;
1307 : }
1308 :
1309 38328 : while ((*text == ' ') || (*text == '\t'))
1310 : {
1311 2168 : text++;
1312 2168 : len--;
1313 : }
1314 38328 : while ((len > (size_t)0)
1315 38328 : && ((text[len - 1] == ' ')
1316 36160 : || (text[len - 1] == '\t')))
1317 : {
1318 2168 : len--;
1319 : }
1320 :
1321 36160 : cur_line->type = FILE_LINE_ACTION;
1322 :
1323 : /* Remove {} and make copy */
1324 36160 : value = malloc_or_die(len + 1);
1325 36160 : strncpy(value, text, len);
1326 36160 : value[len] = '\0';
1327 :
1328 : /* Get actions */
1329 36160 : err = get_actions(value, alias_list, cur_line->data.action);
1330 36160 : if (err)
1331 : {
1332 : /* Invalid action or out of memory */
1333 0 : free(value);
1334 0 : free_alias_list(alias_list);
1335 0 : if (err == JB_ERR_MEMORY)
1336 : {
1337 0 : return err;
1338 : }
1339 : else
1340 : {
1341 : /* Line does not contain a name=value pair */
1342 0 : file->parse_error = cur_line;
1343 0 : file->parse_error_text = "This header does not specify a valid set of actions.";
1344 0 : return JB_ERR_PARSE;
1345 : }
1346 : }
1347 :
1348 : /* Done with string - it was clobbered anyway */
1349 36160 : free(value);
1350 :
1351 : /* Process next line */
1352 36160 : cur_line = cur_line->next;
1353 :
1354 : /* Loop processing URL patterns */
1355 74038 : while ((cur_line != NULL) && (cur_line->unprocessed[0] != '{'))
1356 : {
1357 37878 : if (cur_line->unprocessed[0])
1358 : {
1359 : /* Could parse URL here, but this isn't currently needed */
1360 :
1361 37878 : cur_line->type = FILE_LINE_URL;
1362 : }
1363 : else
1364 : {
1365 0 : cur_line->type = FILE_LINE_BLANK;
1366 : }
1367 37878 : cur_line = cur_line->next;
1368 : }
1369 : } /* End main while(cur_line != NULL) loop */
1370 :
1371 3886 : free_alias_list(alias_list);
1372 :
1373 3886 : return JB_ERR_OK;
1374 : }
1375 :
1376 :
1377 : /*********************************************************************
1378 : *
1379 : * Function : edit_read_file_lines
1380 : *
1381 : * Description : Read all the lines of a file into memory.
1382 : * Handles whitespace, comments and line continuation.
1383 : *
1384 : * Parameters :
1385 : * 1 : fp = File to read from. On return, this will be
1386 : * at EOF but it will not have been closed.
1387 : * 2 : pfile = Destination for a linked list of file_lines.
1388 : * Will be set to NULL on error.
1389 : * 3 : newline = How to handle newlines.
1390 : *
1391 : * Returns : JB_ERR_OK on success
1392 : * JB_ERR_MEMORY on out-of-memory
1393 : *
1394 : *********************************************************************/
1395 3886 : jb_err edit_read_file_lines(FILE *fp, struct file_line ** pfile, int *newline)
1396 : {
1397 : struct file_line * first_line; /* Keep for return value or to free */
1398 : struct file_line * cur_line; /* Current line */
1399 : struct file_line * prev_line; /* Entry with prev_line->next = cur_line */
1400 : jb_err rval;
1401 :
1402 3886 : assert(fp);
1403 3886 : assert(pfile);
1404 :
1405 3886 : *pfile = NULL;
1406 :
1407 3886 : cur_line = first_line = zalloc_or_die(sizeof(struct file_line));
1408 :
1409 3886 : cur_line->type = FILE_LINE_UNPROCESSED;
1410 :
1411 3886 : rval = edit_read_line(fp, &cur_line->raw, &cur_line->prefix, &cur_line->unprocessed, newline, NULL);
1412 3886 : if (rval)
1413 : {
1414 : /* Out of memory or empty file. */
1415 : /* Note that empty file is not an error we propagate up */
1416 0 : free(cur_line);
1417 0 : return ((rval == JB_ERR_FILE) ? JB_ERR_OK : rval);
1418 : }
1419 :
1420 : do
1421 : {
1422 91218 : prev_line = cur_line;
1423 91218 : cur_line = prev_line->next = zalloc_or_die(sizeof(struct file_line));
1424 :
1425 91218 : cur_line->type = FILE_LINE_UNPROCESSED;
1426 :
1427 91218 : rval = edit_read_line(fp, &cur_line->raw, &cur_line->prefix, &cur_line->unprocessed, newline, NULL);
1428 91218 : if ((rval != JB_ERR_OK) && (rval != JB_ERR_FILE))
1429 : {
1430 : /* Out of memory */
1431 0 : edit_free_file_lines(first_line);
1432 0 : return JB_ERR_MEMORY;
1433 : }
1434 :
1435 : }
1436 91218 : while (rval != JB_ERR_FILE);
1437 :
1438 : /* EOF */
1439 :
1440 : /* We allocated one too many - free it */
1441 3886 : prev_line->next = NULL;
1442 3886 : free(cur_line);
1443 :
1444 3886 : *pfile = first_line;
1445 3886 : return JB_ERR_OK;
1446 : }
1447 :
1448 :
1449 : /*********************************************************************
1450 : *
1451 : * Function : edit_read_file
1452 : *
1453 : * Description : Read a complete file into memory.
1454 : * Handles CGI parameter parsing. If requested, also
1455 : * checks the file's modification timestamp.
1456 : *
1457 : * Parameters :
1458 : * 1 : csp = Current client state (buffers, headers, etc...)
1459 : * 2 : parameters = map of cgi parameters.
1460 : * 3 : require_version = true to check "ver" parameter.
1461 : * 4 : pfile = Destination for the file. Will be set
1462 : * to NULL on error.
1463 : *
1464 : * CGI Parameters :
1465 : * f : The action file identifier.
1466 : * ver : (Only if require_version is nonzero)
1467 : * Timestamp of the actions file. If wrong, this
1468 : * function fails with JB_ERR_MODIFIED.
1469 : *
1470 : * Returns : JB_ERR_OK on success
1471 : * JB_ERR_MEMORY on out-of-memory
1472 : * JB_ERR_CGI_PARAMS if "filename" was not specified
1473 : * or is not valid.
1474 : * JB_ERR_FILE if the file cannot be opened or
1475 : * contains no data
1476 : * JB_ERR_MODIFIED if version checking was requested and
1477 : * failed - the file was modified outside
1478 : * of this CGI editor instance.
1479 : *
1480 : *********************************************************************/
1481 5081 : jb_err edit_read_file(struct client_state *csp,
1482 : const struct map *parameters,
1483 : int require_version,
1484 : struct editable_file **pfile)
1485 : {
1486 : struct file_line * lines;
1487 : FILE * fp;
1488 : jb_err err;
1489 5081 : const char *filename = NULL;
1490 : struct editable_file * file;
1491 5081 : unsigned version = 0;
1492 : struct stat statbuf[1];
1493 : char version_buf[22];
1494 5081 : int newline = NEWLINE_UNKNOWN;
1495 : unsigned i;
1496 :
1497 5081 : assert(csp);
1498 5081 : assert(parameters);
1499 5081 : assert(pfile);
1500 :
1501 5081 : *pfile = NULL;
1502 :
1503 5081 : err = get_number_param(csp, parameters, "f", &i);
1504 5081 : if ((JB_ERR_OK == err) && (i < MAX_AF_FILES) && (NULL != csp->config->actions_file[i]))
1505 : {
1506 3778 : filename = csp->config->actions_file[i];
1507 : }
1508 1303 : else if (JB_ERR_CGI_PARAMS == err)
1509 : {
1510 : /*
1511 : * Probably an old-school URL like
1512 : * http://config.privoxy.org/edit-actions-list?f=default
1513 : */
1514 1199 : get_file_name_param(csp, parameters, "f", &filename);
1515 : }
1516 :
1517 5081 : if (NULL == filename || stat(filename, statbuf) < 0)
1518 : {
1519 : /* Error, probably file not found. */
1520 1195 : return JB_ERR_FILE;
1521 : }
1522 3886 : version = (unsigned) statbuf->st_mtime;
1523 :
1524 : /* if (require_version)
1525 : {
1526 : unsigned specified_version;
1527 : err = get_number_param(csp, parameters, "v", &specified_version);
1528 : if (err)
1529 : {
1530 : return err;
1531 : }
1532 :
1533 : if (version != specified_version)
1534 : {
1535 : return JB_ERR_MODIFIED;
1536 : }
1537 : }*/ //Pretend we don't need require_version
1538 :
1539 3886 : if (NULL == (fp = fopen(filename,"rb")))
1540 : {
1541 0 : return JB_ERR_FILE;
1542 : }
1543 :
1544 3886 : err = edit_read_file_lines(fp, &lines, &newline);
1545 :
1546 3886 : fclose(fp);
1547 :
1548 3886 : if (err)
1549 : {
1550 0 : return err;
1551 : }
1552 :
1553 3886 : file = zalloc_or_die(sizeof(*file));
1554 :
1555 3886 : file->lines = lines;
1556 3886 : file->newline = newline;
1557 3886 : file->filename = filename;
1558 3886 : file->version = version;
1559 3886 : file->identifier = i;
1560 :
1561 : /* Correct file->version_str */
1562 3886 : freez(file->version_str);
1563 3886 : snprintf(version_buf, sizeof(version_buf), "%u", file->version);
1564 3886 : version_buf[sizeof(version_buf)-1] = '\0';
1565 3886 : file->version_str = strdup_or_die(version_buf);
1566 :
1567 3886 : *pfile = file;
1568 3886 : return JB_ERR_OK;
1569 : }
1570 :
1571 :
1572 : /*********************************************************************
1573 : *
1574 : * Function : edit_read_actions_file
1575 : *
1576 : * Description : Read a complete actions file into memory.
1577 : * Handles CGI parameter parsing. If requested, also
1578 : * checks the file's modification timestamp.
1579 : *
1580 : * If this function detects an error in the categories
1581 : * JB_ERR_FILE, JB_ERR_MODIFIED, or JB_ERR_PARSE,
1582 : * then it handles it by filling in the specified
1583 : * response structure and returning JB_ERR_FILE.
1584 : *
1585 : * Parameters :
1586 : * 1 : csp = Current client state (buffers, headers, etc...)
1587 : * 2 : rsp = HTTP response. Only filled in on error.
1588 : * 2 : parameters = map of cgi parameters.
1589 : * 3 : require_version = true to check "ver" parameter.
1590 : * 4 : pfile = Destination for the file. Will be set
1591 : * to NULL on error.
1592 : *
1593 : * CGI Parameters :
1594 : * f : The actions file identifier.
1595 : * ver : (Only if require_version is nonzero)
1596 : * Timestamp of the actions file. If wrong, this
1597 : * function fails with JB_ERR_MODIFIED.
1598 : *
1599 : * Returns : JB_ERR_OK on success
1600 : * JB_ERR_MEMORY on out-of-memory
1601 : * JB_ERR_CGI_PARAMS if "filename" was not specified
1602 : * or is not valid.
1603 : * JB_ERR_FILE if the file does not contain valid data,
1604 : * or if file cannot be opened or
1605 : * contains no data, or if version
1606 : * checking was requested and failed.
1607 : *
1608 : *********************************************************************/
1609 5081 : jb_err edit_read_actions_file(struct client_state *csp,
1610 : struct http_response *rsp,
1611 : const struct map *parameters,
1612 : int require_version,
1613 : struct editable_file **pfile)
1614 : {
1615 : jb_err err;
1616 : struct editable_file *file;
1617 : static int acceptable_failures = ACCEPTABLE_TIMESTAMP_MISMATCHES - 1;
1618 :
1619 5081 : assert(csp);
1620 5081 : assert(parameters);
1621 5081 : assert(pfile);
1622 :
1623 5081 : *pfile = NULL;
1624 :
1625 5081 : err = edit_read_file(csp, parameters, require_version, &file);
1626 5081 : if (err)
1627 : {
1628 : /* Try to handle if possible */
1629 1195 : if (err == JB_ERR_FILE)
1630 : {
1631 1195 : err = cgi_error_file(csp, rsp, lookup(parameters, "f"));
1632 : }
1633 0 : else if (err == JB_ERR_MODIFIED)
1634 : {
1635 0 : assert(require_version);
1636 0 : err = cgi_error_modified(csp, rsp, lookup(parameters, "f"));
1637 0 : log_error(LOG_LEVEL_ERROR,
1638 : "Blocking CGI edit request due to modification time mismatch.");
1639 0 : if (acceptable_failures > 0)
1640 : {
1641 0 : log_error(LOG_LEVEL_INFO,
1642 : "The CGI editor will be turned off after another %d mismatche(s).",
1643 : acceptable_failures);
1644 0 : acceptable_failures--;
1645 : }
1646 : else
1647 : {
1648 0 : log_error(LOG_LEVEL_INFO,
1649 : "Timestamp mismatch limit reached, turning CGI editor off. "
1650 : "Reload the configuration file to re-enable it.");
1651 0 : csp->config->feature_flags &= ~RUNTIME_FEATURE_CGI_EDIT_ACTIONS;
1652 : }
1653 : }
1654 1195 : if (err == JB_ERR_OK)
1655 : {
1656 : /*
1657 : * Signal to higher-level CGI code that there was a problem but we
1658 : * handled it, they should just return JB_ERR_OK.
1659 : */
1660 1195 : err = JB_ERR_FILE;
1661 : }
1662 1195 : return err;
1663 : }
1664 :
1665 3886 : err = edit_parse_actions_file(file);
1666 3886 : if (err)
1667 : {
1668 0 : if (err == JB_ERR_PARSE)
1669 : {
1670 0 : err = cgi_error_parse(csp, rsp, file);
1671 0 : if (err == JB_ERR_OK)
1672 : {
1673 : /*
1674 : * Signal to higher-level CGI code that there was a problem but we
1675 : * handled it, they should just return JB_ERR_OK.
1676 : */
1677 0 : err = JB_ERR_FILE;
1678 : }
1679 : }
1680 0 : edit_free_file(file);
1681 0 : return err;
1682 : }
1683 :
1684 3886 : *pfile = file;
1685 3886 : return JB_ERR_OK;
1686 : }
1687 :
1688 :
1689 : /*********************************************************************
1690 : *
1691 : * Function : get_file_name_param
1692 : *
1693 : * Description : Get the name of the file to edit from the parameters
1694 : * passed to a CGI function using the old syntax.
1695 : * This function handles security checks and only
1696 : * accepts files that Privoxy already knows.
1697 : *
1698 : * Parameters :
1699 : * 1 : csp = Current client state (buffers, headers, etc...)
1700 : * 2 : parameters = map of cgi parameters
1701 : * 3 : param_name = The name of the parameter to read
1702 : * 4 : pfilename = pointer to the filename in
1703 : * csp->config->actions_file[] if found. Set to NULL on error.
1704 : *
1705 : * Returns : JB_ERR_OK on success
1706 : * JB_ERR_MEMORY on out-of-memory
1707 : * JB_ERR_CGI_PARAMS if "filename" was not specified
1708 : * or is not valid.
1709 : *
1710 : *********************************************************************/
1711 1199 : static jb_err get_file_name_param(struct client_state *csp,
1712 : const struct map *parameters,
1713 : const char *param_name,
1714 : const char **pfilename)
1715 : {
1716 : const char *param;
1717 1199 : const char suffix[] = ".action";
1718 : const char *s;
1719 : char *name;
1720 : char *fullpath;
1721 : char ch;
1722 : size_t len;
1723 : size_t name_size;
1724 : int i;
1725 :
1726 1199 : assert(csp);
1727 1199 : assert(parameters);
1728 1199 : assert(pfilename);
1729 :
1730 1199 : *pfilename = NULL;
1731 :
1732 1199 : param = lookup(parameters, param_name);
1733 1199 : if (!*param)
1734 : {
1735 607 : return JB_ERR_CGI_PARAMS;
1736 : }
1737 :
1738 592 : len = strlen(param);
1739 592 : if (len >= FILENAME_MAX)
1740 : {
1741 : /* Too long. */
1742 1 : return JB_ERR_CGI_PARAMS;
1743 : }
1744 :
1745 : /*
1746 : * Check every character to see if it's legal.
1747 : * Totally unnecessary but we do it anyway.
1748 : */
1749 591 : s = param;
1750 5288 : while ((ch = *s++) != '\0')
1751 : {
1752 5004 : if ( ((ch < 'A') || (ch > 'Z'))
1753 4997 : && ((ch < 'a') || (ch > 'z'))
1754 1301 : && ((ch < '0') || (ch > '9'))
1755 1073 : && (ch != '-')
1756 604 : && (ch != '_'))
1757 : {
1758 : /* Probable hack attempt. */
1759 307 : return JB_ERR_CGI_PARAMS;
1760 : }
1761 : }
1762 :
1763 : /* Append extension */
1764 284 : name_size = len + strlen(suffix) + 1;
1765 284 : name = malloc_or_die(name_size);
1766 284 : strlcpy(name, param, name_size);
1767 284 : strlcat(name, suffix, name_size);
1768 :
1769 : /* Prepend path */
1770 284 : fullpath = make_path(csp->config->confdir, name);
1771 284 : free(name);
1772 :
1773 284 : if (fullpath == NULL)
1774 : {
1775 0 : return JB_ERR_MEMORY;
1776 : }
1777 :
1778 : /* Check if the file is known */
1779 17992 : for (i = 0; i < MAX_AF_FILES; i++)
1780 : {
1781 17816 : if (NULL != csp->config->actions_file[i] &&
1782 568 : !strcmp(fullpath, csp->config->actions_file[i]))
1783 : {
1784 : /* Success */
1785 108 : *pfilename = csp->config->actions_file[i];
1786 108 : freez(fullpath);
1787 :
1788 108 : return JB_ERR_OK;
1789 : }
1790 : }
1791 176 : freez(fullpath);
1792 :
1793 176 : return JB_ERR_CGI_PARAMS;
1794 : }
1795 :
1796 :
1797 : /*********************************************************************
1798 : *
1799 : * Function : get_url_spec_param
1800 : *
1801 : * Description : Get a URL pattern from the parameters
1802 : * passed to a CGI function. Removes leading/trailing
1803 : * spaces and validates it.
1804 : *
1805 : * Parameters :
1806 : * 1 : csp = Current client state (buffers, headers, etc...)
1807 : * 2 : parameters = map of cgi parameters
1808 : * 3 : name = Name of CGI parameter to read
1809 : * 4 : pvalue = destination for value. Will be malloc()'d.
1810 : * Set to NULL on error.
1811 : *
1812 : * Returns : JB_ERR_OK on success
1813 : * JB_ERR_MEMORY on out-of-memory
1814 : * JB_ERR_CGI_PARAMS if the parameter was not specified
1815 : * or is not valid.
1816 : *
1817 : *********************************************************************/
1818 694 : static jb_err get_url_spec_param(struct client_state *csp,
1819 : const struct map *parameters,
1820 : const char *name,
1821 : char **pvalue)
1822 : {
1823 : const char *orig_param;
1824 : char *param;
1825 : char *s;
1826 : struct pattern_spec compiled[1];
1827 : jb_err err;
1828 :
1829 694 : assert(csp);
1830 694 : assert(parameters);
1831 694 : assert(name);
1832 694 : assert(pvalue);
1833 :
1834 694 : *pvalue = NULL;
1835 :
1836 694 : orig_param = lookup(parameters, name);
1837 694 : if (!*orig_param)
1838 : {
1839 221 : return JB_ERR_CGI_PARAMS;
1840 : }
1841 :
1842 : /* Copy and trim whitespace */
1843 473 : param = strdup(orig_param);
1844 473 : if (param == NULL)
1845 : {
1846 0 : return JB_ERR_MEMORY;
1847 : }
1848 473 : chomp(param);
1849 :
1850 : /* Must be non-empty, and can't allow 1st character to be '{' */
1851 473 : if (param[0] == '\0' || param[0] == '{')
1852 : {
1853 2 : free(param);
1854 2 : return JB_ERR_CGI_PARAMS;
1855 : }
1856 :
1857 : /* Check for embedded newlines */
1858 36669 : for (s = param; *s != '\0'; s++)
1859 : {
1860 36200 : if ((*s == '\r') || (*s == '\n'))
1861 : {
1862 2 : free(param);
1863 2 : return JB_ERR_CGI_PARAMS;
1864 : }
1865 : }
1866 :
1867 : /* Check that regex is valid */
1868 469 : s = strdup(param);
1869 469 : if (s == NULL)
1870 : {
1871 0 : free(param);
1872 0 : return JB_ERR_MEMORY;
1873 : }
1874 469 : err = create_pattern_spec(compiled, s);
1875 469 : free(s);
1876 469 : if (err)
1877 : {
1878 95 : free(param);
1879 95 : return (err == JB_ERR_MEMORY) ? JB_ERR_MEMORY : JB_ERR_CGI_PARAMS;
1880 : }
1881 374 : free_pattern_spec(compiled);
1882 :
1883 374 : if (param[strlen(param) - 1] == '\\')
1884 : {
1885 : /*
1886 : * Must protect trailing '\\' from becoming line continuation character.
1887 : * Two methods: 1) If it's a domain only, add a trailing '/'.
1888 : * 2) For path, add the do-nothing PCRE expression (?:) to the end
1889 : */
1890 106 : if (strchr(param, '/') == NULL)
1891 : {
1892 16 : err = string_append(¶m, "/");
1893 : }
1894 : else
1895 : {
1896 90 : err = string_append(¶m, "(?:)");
1897 : }
1898 106 : if (err)
1899 : {
1900 0 : return err;
1901 : }
1902 :
1903 : /* Check that the modified regex is valid */
1904 106 : s = strdup(param);
1905 106 : if (s == NULL)
1906 : {
1907 0 : free(param);
1908 0 : return JB_ERR_MEMORY;
1909 : }
1910 106 : err = create_pattern_spec(compiled, s);
1911 106 : free(s);
1912 106 : if (err)
1913 : {
1914 0 : free(param);
1915 0 : return (err == JB_ERR_MEMORY) ? JB_ERR_MEMORY : JB_ERR_CGI_PARAMS;
1916 : }
1917 106 : free_pattern_spec(compiled);
1918 : }
1919 :
1920 374 : *pvalue = param;
1921 374 : return JB_ERR_OK;
1922 : }
1923 :
1924 : /*********************************************************************
1925 : *
1926 : * Function : map_radio
1927 : *
1928 : * Description : Map a set of radio button values. E.g. if you have
1929 : * 3 radio buttons, declare them as:
1930 : * <option type="radio" name="xyz" @xyz-a@>
1931 : * <option type="radio" name="xyz" @xyz-b@>
1932 : * <option type="radio" name="xyz" @xyz-c@>
1933 : * Then map one of the @xyz-?@ variables to "checked"
1934 : * and all the others to empty by calling:
1935 : * map_radio(exports, "xyz", "abc", sel)
1936 : * Where 'sel' is 'a', 'b', or 'c'.
1937 : *
1938 : * Parameters :
1939 : * 1 : exports = Exports map to modify.
1940 : * 2 : optionname = name for map
1941 : * 3 : values = null-terminated list of values;
1942 : * 4 : value = Selected value.
1943 : *
1944 : * CGI Parameters : None
1945 : *
1946 : * Returns : JB_ERR_OK on success
1947 : * JB_ERR_MEMORY on out-of-memory
1948 : *
1949 : *********************************************************************/
1950 660 : static jb_err map_radio(struct map * exports,
1951 : const char * optionname,
1952 : const char * values,
1953 : int value)
1954 : {
1955 : char * buf;
1956 : char * p;
1957 : char c;
1958 660 : const size_t len = strlen(optionname);
1959 660 : const size_t buf_size = len + 3;
1960 :
1961 660 : assert(exports);
1962 660 : assert(optionname);
1963 660 : assert(values);
1964 :
1965 660 : buf = malloc_or_die(buf_size);
1966 :
1967 660 : strlcpy(buf, optionname, buf_size);
1968 :
1969 : /* XXX: this looks ... interesting */
1970 660 : p = buf + len;
1971 660 : *p++ = '-';
1972 660 : p[1] = '\0';
1973 :
1974 2552 : while ((c = *values++) != '\0')
1975 : {
1976 1892 : if (c != value)
1977 : {
1978 1232 : *p = c;
1979 1232 : if (map(exports, buf, 1, "", 1))
1980 : {
1981 0 : return JB_ERR_MEMORY;
1982 : }
1983 : }
1984 : }
1985 :
1986 660 : *p = (char)value;
1987 660 : return map(exports, buf, 0, "checked", 1);
1988 : }
1989 :
1990 :
1991 : /*********************************************************************
1992 : *
1993 : * Function : cgi_error_modified
1994 : *
1995 : * Description : CGI function that is called when a file is modified
1996 : * outside the CGI editor.
1997 : *
1998 : * Parameters :
1999 : * 1 : csp = Current client state (buffers, headers, etc...)
2000 : * 2 : rsp = http_response data structure for output
2001 : * 3 : filename = The file that was modified.
2002 : *
2003 : * CGI Parameters : none
2004 : *
2005 : * Returns : JB_ERR_OK on success
2006 : * JB_ERR_MEMORY on out-of-memory error.
2007 : *
2008 : *********************************************************************/
2009 0 : jb_err cgi_error_modified(struct client_state *csp,
2010 : struct http_response *rsp,
2011 : const char *filename)
2012 : {
2013 : struct map *exports;
2014 : jb_err err;
2015 :
2016 0 : assert(csp);
2017 0 : assert(rsp);
2018 0 : assert(filename);
2019 :
2020 0 : if (NULL == (exports = default_exports(csp, NULL)))
2021 : {
2022 0 : return JB_ERR_MEMORY;
2023 : }
2024 :
2025 0 : err = map(exports, "f", 1, html_encode(filename), 0);
2026 0 : if (err)
2027 : {
2028 0 : free_map(exports);
2029 0 : return err;
2030 : }
2031 :
2032 0 : return template_fill_for_cgi(csp, "cgi-error-modified", exports, rsp);
2033 : }
2034 :
2035 :
2036 : /*********************************************************************
2037 : *
2038 : * Function : cgi_error_parse
2039 : *
2040 : * Description : CGI function that is called when a file cannot
2041 : * be parsed by the CGI editor.
2042 : *
2043 : * Parameters :
2044 : * 1 : csp = Current client state (buffers, headers, etc...)
2045 : * 2 : rsp = http_response data structure for output
2046 : * 3 : file = The file that was modified.
2047 : *
2048 : * CGI Parameters : none
2049 : *
2050 : * Returns : JB_ERR_OK on success
2051 : * JB_ERR_MEMORY on out-of-memory error.
2052 : *
2053 : *********************************************************************/
2054 0 : jb_err cgi_error_parse(struct client_state *csp,
2055 : struct http_response *rsp,
2056 : struct editable_file *file)
2057 : {
2058 : struct map *exports;
2059 : jb_err err;
2060 : struct file_line *cur_line;
2061 :
2062 0 : assert(csp);
2063 0 : assert(rsp);
2064 0 : assert(file);
2065 :
2066 0 : if (NULL == (exports = default_exports(csp, NULL)))
2067 : {
2068 0 : return JB_ERR_MEMORY;
2069 : }
2070 :
2071 0 : err = map(exports, "f", 1, stringify(file->identifier), 0);
2072 0 : if (!err) err = map(exports, "parse-error", 1, html_encode(file->parse_error_text), 0);
2073 :
2074 0 : cur_line = file->parse_error;
2075 0 : assert(cur_line);
2076 :
2077 0 : if (!err) err = map(exports, "line-raw", 1, html_encode(cur_line->raw), 0);
2078 0 : if (!err) err = map(exports, "line-data", 1, html_encode(cur_line->unprocessed), 0);
2079 :
2080 0 : if (err)
2081 : {
2082 0 : free_map(exports);
2083 0 : return err;
2084 : }
2085 :
2086 0 : return template_fill_for_cgi(csp, "cgi-error-parse", exports, rsp);
2087 : }
2088 :
2089 :
2090 : /*********************************************************************
2091 : *
2092 : * Function : cgi_error_file
2093 : *
2094 : * Description : CGI function that is called when a file cannot be
2095 : * opened by the CGI editor.
2096 : *
2097 : * Parameters :
2098 : * 1 : csp = Current client state (buffers, headers, etc...)
2099 : * 2 : rsp = http_response data structure for output
2100 : * 3 : filename = The file that was modified.
2101 : *
2102 : * CGI Parameters : none
2103 : *
2104 : * Returns : JB_ERR_OK on success
2105 : * JB_ERR_MEMORY on out-of-memory error.
2106 : *
2107 : *********************************************************************/
2108 1195 : jb_err cgi_error_file(struct client_state *csp,
2109 : struct http_response *rsp,
2110 : const char *filename)
2111 : {
2112 : struct map *exports;
2113 : jb_err err;
2114 :
2115 1195 : assert(csp);
2116 1195 : assert(rsp);
2117 1195 : assert(filename);
2118 :
2119 1195 : if (NULL == (exports = default_exports(csp, NULL)))
2120 : {
2121 0 : return JB_ERR_MEMORY;
2122 : }
2123 :
2124 1195 : err = map(exports, "f", 1, html_encode(filename), 0);
2125 1195 : if (err)
2126 : {
2127 0 : free_map(exports);
2128 0 : return err;
2129 : }
2130 :
2131 1195 : return template_fill_for_cgi(csp, "cgi-error-file", exports, rsp);
2132 : }
2133 :
2134 :
2135 : /*********************************************************************
2136 : *
2137 : * Function : cgi_error_file_read_only
2138 : *
2139 : * Description : CGI function that is called when a file cannot be
2140 : * opened for writing by the CGI editor.
2141 : *
2142 : * Parameters :
2143 : * 1 : csp = Current client state (buffers, headers, etc...)
2144 : * 2 : rsp = http_response data structure for output
2145 : * 3 : filename = The file that we can't write to
2146 : *
2147 : * CGI Parameters : none
2148 : *
2149 : * Returns : JB_ERR_OK on success
2150 : * JB_ERR_MEMORY on out-of-memory error.
2151 : *
2152 : *********************************************************************/
2153 0 : jb_err cgi_error_file_read_only(struct client_state *csp,
2154 : struct http_response *rsp,
2155 : const char *filename)
2156 : {
2157 : struct map *exports;
2158 : jb_err err;
2159 :
2160 0 : assert(csp);
2161 0 : assert(rsp);
2162 0 : assert(filename);
2163 :
2164 0 : if (NULL == (exports = default_exports(csp, NULL)))
2165 : {
2166 0 : return JB_ERR_MEMORY;
2167 : }
2168 :
2169 0 : err = map(exports, "f", 1, html_encode(filename), 0);
2170 0 : if (err)
2171 : {
2172 0 : free_map(exports);
2173 0 : return err;
2174 : }
2175 :
2176 0 : return template_fill_for_cgi(csp, "cgi-error-file-read-only", exports, rsp);
2177 : }
2178 :
2179 :
2180 : /*********************************************************************
2181 : *
2182 : * Function : cgi_edit_actions
2183 : *
2184 : * Description : CGI function that allows the user to choose which
2185 : * actions file to edit.
2186 : *
2187 : * Parameters :
2188 : * 1 : csp = Current client state (buffers, headers, etc...)
2189 : * 2 : rsp = http_response data structure for output
2190 : * 3 : parameters = map of cgi parameters
2191 : *
2192 : * CGI Parameters : None
2193 : *
2194 : * Returns : JB_ERR_OK on success
2195 : * JB_ERR_MEMORY on out-of-memory error
2196 : *
2197 : *********************************************************************/
2198 63 : jb_err cgi_edit_actions(struct client_state *csp,
2199 : struct http_response *rsp,
2200 : const struct map *parameters)
2201 : {
2202 : (void)parameters;
2203 :
2204 63 : if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
2205 : {
2206 0 : return cgi_error_disabled(csp, rsp);
2207 : }
2208 :
2209 : /* FIXME: Incomplete */
2210 :
2211 63 : return cgi_redirect(rsp, CGI_PREFIX "edit-actions-list?f=default");
2212 :
2213 : }
2214 :
2215 :
2216 : /*********************************************************************
2217 : *
2218 : * Function : cgi_edit_actions_list
2219 : *
2220 : * Description : CGI function that edits the actions list.
2221 : * FIXME: This function shouldn't FATAL ever.
2222 : * FIXME: This function doesn't check the retval of map()
2223 : * Parameters :
2224 : * 1 : csp = Current client state (buffers, headers, etc...)
2225 : * 2 : rsp = http_response data structure for output
2226 : * 3 : parameters = map of cgi parameters
2227 : *
2228 : * CGI Parameters : filename
2229 : *
2230 : * Returns : JB_ERR_OK on success
2231 : * JB_ERR_MEMORY on out-of-memory
2232 : * JB_ERR_FILE if the file cannot be opened or
2233 : * contains no data
2234 : * JB_ERR_CGI_PARAMS if "filename" was not specified
2235 : * or is not valid.
2236 : *
2237 : *********************************************************************/
2238 3519 : jb_err cgi_edit_actions_list(struct client_state *csp,
2239 : struct http_response *rsp,
2240 : const struct map *parameters)
2241 : {
2242 : char * section_template;
2243 : char * url_template;
2244 : char * sections;
2245 : char * urls;
2246 : char buf[150];
2247 : char * s;
2248 : struct map * exports;
2249 : struct map * section_exports;
2250 : struct map * url_exports;
2251 : struct editable_file * file;
2252 : struct file_line * cur_line;
2253 3519 : unsigned line_number = 0;
2254 3519 : unsigned prev_section_line_number = ((unsigned) (-1));
2255 : int i, url_1_2;
2256 : struct file_list * fl;
2257 : struct url_actions * b;
2258 3519 : char * buttons = NULL;
2259 : jb_err err;
2260 :
2261 3519 : if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
2262 : {
2263 0 : return cgi_error_disabled(csp, rsp);
2264 : }
2265 :
2266 3519 : if (NULL == (exports = default_exports(csp, NULL)))
2267 : {
2268 0 : return JB_ERR_MEMORY;
2269 : }
2270 :
2271 : /* Load actions file */
2272 3519 : err = edit_read_actions_file(csp, rsp, parameters, 0, &file);
2273 3519 : if (err)
2274 : {
2275 : /* No filename specified, can't read file, or out of memory. */
2276 913 : free_map(exports);
2277 913 : return (err == JB_ERR_FILE ? JB_ERR_OK : err);
2278 : }
2279 :
2280 : /* Find start of actions in file */
2281 2606 : cur_line = file->lines;
2282 2606 : line_number = 1;
2283 19566 : while ((cur_line != NULL) && (cur_line->type != FILE_LINE_ACTION))
2284 : {
2285 16960 : cur_line = cur_line->next;
2286 16960 : line_number++;
2287 : }
2288 :
2289 : /*
2290 : * Conventional actions files should have a match all block
2291 : * at the start:
2292 : * cur_line = {...global actions...}
2293 : * cur_line->next = /
2294 : * cur_line->next->next = {...actions...} or EOF
2295 : */
2296 2606 : if ( (cur_line != NULL)
2297 2606 : && (cur_line->type == FILE_LINE_ACTION)
2298 2606 : && (cur_line->next != NULL)
2299 2606 : && (cur_line->next->type == FILE_LINE_URL)
2300 2606 : && (0 == strcmp(cur_line->next->unprocessed, "/"))
2301 910 : && ( (cur_line->next->next == NULL)
2302 910 : || (cur_line->next->next->type != FILE_LINE_URL)
2303 : ) )
2304 : {
2305 : /*
2306 : * Generate string with buttons to set actions for "/" to
2307 : * any predefined set of actions (named standard.*, probably
2308 : * residing in standard.action).
2309 : */
2310 :
2311 910 : err = template_load(csp, §ion_template, "edit-actions-list-button", 0);
2312 910 : if (err)
2313 : {
2314 0 : edit_free_file(file);
2315 0 : free_map(exports);
2316 0 : if (err == JB_ERR_FILE)
2317 : {
2318 0 : return cgi_error_no_template(csp, rsp, "edit-actions-list-button");
2319 : }
2320 0 : return err;
2321 : }
2322 :
2323 910 : err = template_fill(§ion_template, exports);
2324 910 : if (err)
2325 : {
2326 0 : edit_free_file(file);
2327 0 : free_map(exports);
2328 0 : return err;
2329 : }
2330 :
2331 910 : buttons = strdup("");
2332 91910 : for (i = 0; i < MAX_AF_FILES; i++)
2333 : {
2334 91000 : if (((fl = csp->actions_list[i]) != NULL) && ((b = fl->f) != NULL))
2335 : {
2336 20930 : for (b = b->next; NULL != b; b = b->next)
2337 : {
2338 19110 : if (!strncmp(b->url->spec, "standard.", 9) && *(b->url->spec + 9) != '\0')
2339 : {
2340 0 : if (err)
2341 : {
2342 0 : freez(buttons);
2343 0 : free(section_template);
2344 0 : edit_free_file(file);
2345 0 : free_map(exports);
2346 0 : return JB_ERR_MEMORY;
2347 : }
2348 :
2349 0 : section_exports = new_map();
2350 0 : err = map(section_exports, "button-name", 1, b->url->spec + 9, 1);
2351 :
2352 0 : if (err || (NULL == (s = strdup(section_template))))
2353 : {
2354 0 : free_map(section_exports);
2355 0 : freez(buttons);
2356 0 : free(section_template);
2357 0 : edit_free_file(file);
2358 0 : free_map(exports);
2359 0 : return JB_ERR_MEMORY;
2360 : }
2361 :
2362 0 : if (!err) err = template_fill(&s, section_exports);
2363 0 : free_map(section_exports);
2364 0 : if (!err) err = string_join(&buttons, s);
2365 : }
2366 : }
2367 : }
2368 : }
2369 910 : freez(section_template);
2370 910 : if (!err) err = map(exports, "all-urls-buttons", 1, buttons, 0);
2371 :
2372 : /*
2373 : * Conventional actions file, supply extra editing help.
2374 : * (e.g. don't allow them to make it an unconventional one).
2375 : */
2376 910 : if (!err) err = map_conditional(exports, "all-urls-present", 1);
2377 :
2378 910 : snprintf(buf, sizeof(buf), "%u", line_number);
2379 910 : if (!err) err = map(exports, "all-urls-s", 1, buf, 1);
2380 910 : snprintf(buf, sizeof(buf), "%u", line_number + 2);
2381 910 : if (!err) err = map(exports, "all-urls-s-next", 1, buf, 1);
2382 1820 : if (!err) err = map(exports, "all-urls-actions", 1,
2383 910 : actions_to_html(csp, cur_line->data.action), 0);
2384 :
2385 : /* Skip the 2 lines */
2386 910 : cur_line = cur_line->next->next;
2387 910 : line_number += 2;
2388 :
2389 : /*
2390 : * Note that prev_section_line_number is NOT set here.
2391 : * This is deliberate and not a bug. It stops a "Move up"
2392 : * option appearing on the next section. Clicking "Move
2393 : * up" would make the actions file unconventional, which
2394 : * we don't want, so we hide this option.
2395 : */
2396 : }
2397 : else
2398 : {
2399 : /*
2400 : * Non-standard actions file - does not begin with
2401 : * the "All URLs" section.
2402 : */
2403 1696 : if (!err) err = map_conditional(exports, "all-urls-present", 0);
2404 : }
2405 :
2406 : /* Set up global exports */
2407 :
2408 2606 : if (!err) err = map(exports, "actions-file", 1, html_encode(file->filename), 0);
2409 2606 : if (!err) err = map(exports, "f", 1, stringify(file->identifier), 0);
2410 2606 : if (!err) err = map(exports, "v", 1, file->version_str, 1);
2411 :
2412 : /* Discourage private additions to default.action */
2413 :
2414 2606 : if (!err) err = map_conditional(exports, "default-action",
2415 2606 : (strstr("default.action", file->filename) != NULL));
2416 2606 : if (err)
2417 : {
2418 0 : edit_free_file(file);
2419 0 : free_map(exports);
2420 0 : return err;
2421 : }
2422 :
2423 : /* Should do all global exports above this point */
2424 :
2425 : /* Load templates */
2426 :
2427 2606 : err = template_load(csp, §ion_template, "edit-actions-list-section", 0);
2428 2606 : if (err)
2429 : {
2430 0 : edit_free_file(file);
2431 0 : free_map(exports);
2432 0 : if (err == JB_ERR_FILE)
2433 : {
2434 0 : return cgi_error_no_template(csp, rsp, "edit-actions-list-section");
2435 : }
2436 0 : return err;
2437 : }
2438 :
2439 2606 : err = template_load(csp, &url_template, "edit-actions-list-url", 0);
2440 2606 : if (err)
2441 : {
2442 0 : free(section_template);
2443 0 : edit_free_file(file);
2444 0 : free_map(exports);
2445 0 : if (err == JB_ERR_FILE)
2446 : {
2447 0 : return cgi_error_no_template(csp, rsp, "edit-actions-list-url");
2448 : }
2449 0 : return err;
2450 : }
2451 :
2452 2606 : err = template_fill(§ion_template, exports);
2453 2606 : if (err)
2454 : {
2455 0 : free(url_template);
2456 0 : edit_free_file(file);
2457 0 : free_map(exports);
2458 0 : return err;
2459 : }
2460 :
2461 2606 : err = template_fill(&url_template, exports);
2462 2606 : if (err)
2463 : {
2464 0 : free(section_template);
2465 0 : edit_free_file(file);
2466 0 : free_map(exports);
2467 0 : return err;
2468 : }
2469 :
2470 2606 : if (NULL == (sections = strdup("")))
2471 : {
2472 0 : free(section_template);
2473 0 : free(url_template);
2474 0 : edit_free_file(file);
2475 0 : free_map(exports);
2476 0 : return JB_ERR_MEMORY;
2477 : }
2478 :
2479 32472 : while ((cur_line != NULL) && (cur_line->type == FILE_LINE_ACTION))
2480 : {
2481 29866 : section_exports = new_map();
2482 :
2483 29866 : snprintf(buf, sizeof(buf), "%u", line_number);
2484 29866 : err = map(section_exports, "s", 1, buf, 1);
2485 59732 : if (!err) err = map(section_exports, "actions", 1,
2486 29866 : actions_to_html(csp, cur_line->data.action), 0);
2487 :
2488 29866 : if ((!err)
2489 29866 : && (cur_line->next != NULL)
2490 29866 : && (cur_line->next->type == FILE_LINE_URL))
2491 : {
2492 : /* This section contains at least one URL, don't allow delete */
2493 29866 : err = map_block_killer(section_exports, "empty-section");
2494 : }
2495 : else
2496 : {
2497 0 : if (!err) err = map_block_keep(section_exports, "empty-section");
2498 : }
2499 :
2500 29866 : if (prev_section_line_number != ((unsigned)(-1)))
2501 : {
2502 : /* Not last section */
2503 27260 : snprintf(buf, sizeof(buf), "%u", prev_section_line_number);
2504 27260 : if (!err) err = map(section_exports, "s-prev", 1, buf, 1);
2505 27260 : if (!err) err = map_block_keep(section_exports, "s-prev-exists");
2506 : }
2507 : else
2508 : {
2509 : /* Last section */
2510 2606 : if (!err) err = map_block_killer(section_exports, "s-prev-exists");
2511 : }
2512 29866 : prev_section_line_number = line_number;
2513 :
2514 29866 : if (err)
2515 : {
2516 0 : free(sections);
2517 0 : free(section_template);
2518 0 : free(url_template);
2519 0 : edit_free_file(file);
2520 0 : free_map(exports);
2521 0 : free_map(section_exports);
2522 0 : return err;
2523 : }
2524 :
2525 : /* Should do all section-specific exports above this point */
2526 :
2527 29866 : if (NULL == (urls = strdup("")))
2528 : {
2529 0 : free(sections);
2530 0 : free(section_template);
2531 0 : free(url_template);
2532 0 : edit_free_file(file);
2533 0 : free_map(exports);
2534 0 : free_map(section_exports);
2535 0 : return JB_ERR_MEMORY;
2536 : }
2537 :
2538 29866 : url_1_2 = 2;
2539 :
2540 29866 : cur_line = cur_line->next;
2541 29866 : line_number++;
2542 :
2543 61428 : while ((cur_line != NULL) && (cur_line->type == FILE_LINE_URL))
2544 : {
2545 31562 : url_exports = new_map();
2546 :
2547 31562 : snprintf(buf, sizeof(buf), "%u", line_number);
2548 31562 : err = map(url_exports, "p", 1, buf, 1);
2549 :
2550 31562 : snprintf(buf, sizeof(buf), "%d", url_1_2);
2551 31562 : if (!err) err = map(url_exports, "url-1-2", 1, buf, 1);
2552 :
2553 63124 : if (!err) err = map(url_exports, "url-html", 1,
2554 31562 : html_encode(cur_line->unprocessed), 0);
2555 63124 : if (!err) err = map(url_exports, "url", 1,
2556 31562 : url_encode(cur_line->unprocessed), 0);
2557 :
2558 31562 : if (err)
2559 : {
2560 0 : free(urls);
2561 0 : free(sections);
2562 0 : free(section_template);
2563 0 : free(url_template);
2564 0 : edit_free_file(file);
2565 0 : free_map(exports);
2566 0 : free_map(section_exports);
2567 0 : free_map(url_exports);
2568 0 : return err;
2569 : }
2570 :
2571 31562 : if (NULL == (s = strdup(url_template)))
2572 : {
2573 0 : free(urls);
2574 0 : free(sections);
2575 0 : free(section_template);
2576 0 : free(url_template);
2577 0 : edit_free_file(file);
2578 0 : free_map(exports);
2579 0 : free_map(section_exports);
2580 0 : free_map(url_exports);
2581 0 : return JB_ERR_MEMORY;
2582 : }
2583 :
2584 31562 : err = template_fill(&s, section_exports);
2585 31562 : if (!err) err = template_fill(&s, url_exports);
2586 31562 : if (!err) err = string_append(&urls, s);
2587 :
2588 31562 : free_map(url_exports);
2589 31562 : freez(s);
2590 :
2591 31562 : if (err)
2592 : {
2593 0 : freez(urls);
2594 0 : free(sections);
2595 0 : free(section_template);
2596 0 : free(url_template);
2597 0 : edit_free_file(file);
2598 0 : free_map(exports);
2599 0 : free_map(section_exports);
2600 0 : return err;
2601 : }
2602 :
2603 31562 : url_1_2 = 3 - url_1_2;
2604 :
2605 31562 : cur_line = cur_line->next;
2606 31562 : line_number++;
2607 : }
2608 :
2609 29866 : err = map(section_exports, "urls", 1, urls, 0);
2610 :
2611 : /* Could also do section-specific exports here, but it wouldn't be as fast */
2612 :
2613 29866 : snprintf(buf, sizeof(buf), "%u", line_number);
2614 29866 : if (!err) err = map(section_exports, "s-next", 1, buf, 1);
2615 :
2616 29866 : if ((cur_line != NULL)
2617 27260 : && (cur_line->type == FILE_LINE_ACTION))
2618 : {
2619 : /* Not last section */
2620 27260 : if (!err) err = map_block_keep(section_exports, "s-next-exists");
2621 : }
2622 : else
2623 : {
2624 : /* Last section */
2625 2606 : if (!err) err = map_block_killer(section_exports, "s-next-exists");
2626 : }
2627 :
2628 29866 : if (err)
2629 : {
2630 0 : free(sections);
2631 0 : free(section_template);
2632 0 : free(url_template);
2633 0 : edit_free_file(file);
2634 0 : free_map(exports);
2635 0 : free_map(section_exports);
2636 0 : return err;
2637 : }
2638 :
2639 29866 : if (NULL == (s = strdup(section_template)))
2640 : {
2641 0 : free(sections);
2642 0 : free(section_template);
2643 0 : free(url_template);
2644 0 : edit_free_file(file);
2645 0 : free_map(exports);
2646 0 : free_map(section_exports);
2647 0 : return JB_ERR_MEMORY;
2648 : }
2649 :
2650 29866 : err = template_fill(&s, section_exports);
2651 29866 : if (!err) err = string_append(§ions, s);
2652 :
2653 29866 : freez(s);
2654 29866 : free_map(section_exports);
2655 :
2656 29866 : if (err)
2657 : {
2658 0 : freez(sections);
2659 0 : free(section_template);
2660 0 : free(url_template);
2661 0 : edit_free_file(file);
2662 0 : free_map(exports);
2663 0 : return err;
2664 : }
2665 : }
2666 :
2667 2606 : edit_free_file(file);
2668 2606 : free(section_template);
2669 2606 : free(url_template);
2670 :
2671 2606 : err = map(exports, "sections", 1, sections, 0);
2672 2606 : if (err)
2673 : {
2674 0 : free_map(exports);
2675 0 : return err;
2676 : }
2677 :
2678 : /* Could also do global exports here, but it wouldn't be as fast */
2679 :
2680 2606 : return template_fill_for_cgi(csp, "edit-actions-list", exports, rsp);
2681 : }
2682 :
2683 :
2684 : /*********************************************************************
2685 : *
2686 : * Function : cgi_edit_actions_for_url
2687 : *
2688 : * Description : CGI function that edits the Actions list.
2689 : *
2690 : * Parameters :
2691 : * 1 : csp = Current client state (buffers, headers, etc...)
2692 : * 2 : rsp = http_response data structure for output
2693 : * 3 : parameters = map of cgi parameters
2694 : *
2695 : * CGI Parameters : None
2696 : *
2697 : * Returns : JB_ERR_OK on success
2698 : * JB_ERR_MEMORY on out-of-memory
2699 : * JB_ERR_CGI_PARAMS if the CGI parameters are not
2700 : * specified or not valid.
2701 : *
2702 : *********************************************************************/
2703 395 : jb_err cgi_edit_actions_for_url(struct client_state *csp,
2704 : struct http_response *rsp,
2705 : const struct map *parameters)
2706 : {
2707 : struct map * exports;
2708 : char *filter_template;
2709 : unsigned sectionid;
2710 : struct editable_file * file;
2711 : struct file_line * cur_line;
2712 : unsigned line_number;
2713 : jb_err err;
2714 : struct re_filterfile_spec *filter_group;
2715 395 : int i, have_filters = 0;
2716 :
2717 395 : if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
2718 : {
2719 0 : return cgi_error_disabled(csp, rsp);
2720 : }
2721 :
2722 395 : err = get_number_param(csp, parameters, "s", §ionid);
2723 395 : if (err)
2724 : {
2725 47 : return err;
2726 : }
2727 :
2728 348 : err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
2729 348 : if (err)
2730 : {
2731 : /* No filename specified, can't read file, modified, or out of memory. */
2732 20 : return (err == JB_ERR_FILE ? JB_ERR_OK : err);
2733 : }
2734 :
2735 328 : cur_line = file->lines;
2736 :
2737 414 : for (line_number = 1; (cur_line != NULL) && (line_number < sectionid); line_number++)
2738 : {
2739 86 : cur_line = cur_line->next;
2740 : }
2741 :
2742 328 : if ( (cur_line == NULL)
2743 327 : || (line_number != sectionid)
2744 22 : || (sectionid < 1)
2745 22 : || (cur_line->type != FILE_LINE_ACTION))
2746 : {
2747 : /* Invalid "sectionid" parameter */
2748 317 : edit_free_file(file);
2749 317 : return JB_ERR_CGI_PARAMS;
2750 : }
2751 :
2752 11 : if (NULL == (exports = default_exports(csp, NULL)))
2753 : {
2754 0 : edit_free_file(file);
2755 0 : return JB_ERR_MEMORY;
2756 : }
2757 :
2758 11 : err = template_load(csp, &filter_template, "edit-actions-for-url-string-filter", 0);
2759 11 : if (err)
2760 : {
2761 0 : edit_free_file(file);
2762 0 : free_map(exports);
2763 0 : return cgi_error_no_template(csp, rsp, "edit-actions-for-url-string-filter");
2764 : }
2765 :
2766 11 : err = map(exports, "f", 1, stringify(file->identifier), 0);
2767 11 : if (!err) err = map(exports, "v", 1, file->version_str, 1);
2768 11 : if (!err) err = map(exports, "s", 1, url_encode(lookup(parameters, "s")), 0);
2769 :
2770 11 : if (!err) err = actions_to_radio(exports, cur_line->data.action);
2771 :
2772 11 : if (!err) err = action_render_string_filters_template(exports, cur_line->data.action, filter_template,
2773 : &filter_type_info[FT_SUPPRESS_TAG]);
2774 11 : freez(filter_template);
2775 :
2776 : /*
2777 : * XXX: Some browsers (at least IE6 and IE7) have an artificial URL
2778 : * length limitation and ignore clicks on the Submit buttons if
2779 : * the resulting GET URL would be longer than their limit.
2780 : *
2781 : * In Privoxy 3.0.5 beta the standard edit-actions-for-url template
2782 : * reached this limit and action editing stopped working in these
2783 : * browsers (BR #1570678).
2784 : *
2785 : * The config option split-large-forms works around this browser
2786 : * bug (HTTP has no URL length limitation) by dividing the action
2787 : * list form into multiple smaller ones. It means the URLs are shorter
2788 : * and work in broken browsers as well, but the user can no longer change
2789 : * all actions with one submit.
2790 : *
2791 : * A better solution would be to switch to POST requests,
2792 : * but this will do for now.
2793 : */
2794 11 : if (!err && (csp->config->feature_flags & RUNTIME_FEATURE_SPLIT_LARGE_FORMS))
2795 : {
2796 : /* Generate multiple smaller form by killing the big one. */
2797 0 : err = map_block_killer(exports, "one-form-only");
2798 : }
2799 : else
2800 : {
2801 : /* Default: Generate one large form by killing the smaller ones. */
2802 11 : err = map_block_killer(exports, "multiple-forms");
2803 : }
2804 :
2805 11 : for (i = 0; i < MAX_AF_FILES; i++)
2806 : {
2807 11 : if ((csp->rlist[i] != NULL) && (csp->rlist[i]->f != NULL))
2808 : {
2809 11 : if (!err) err = map_conditional(exports, "any-filters-defined", 1);
2810 11 : have_filters = 1;
2811 11 : break;
2812 : }
2813 : }
2814 :
2815 : #ifndef FEATURE_EXTERNAL_FILTERS
2816 : if (!err) err = map_block_killer(exports, "external-content-filters");
2817 : #endif
2818 : #ifndef FEATURE_HTTPS_INSPECTION
2819 : if (!err) err = map_block_killer(exports, "https-inspection");
2820 : #endif
2821 :
2822 11 : if (err)
2823 : {
2824 0 : edit_free_file(file);
2825 0 : free_map(exports);
2826 0 : return err;
2827 : }
2828 :
2829 11 : if (0 == have_filters)
2830 : {
2831 0 : err = map(exports, "filter-params", 1, "", 1);
2832 : }
2833 : else
2834 : {
2835 : /*
2836 : * List available filters and their settings.
2837 : */
2838 11 : int filter_identifier = 0;
2839 : char *prepared_templates[MAX_FILTER_TYPES];
2840 :
2841 99 : for (i = 0; i < MAX_FILTER_TYPES; i++)
2842 : {
2843 88 : prepared_templates[i] = strdup("");
2844 : }
2845 :
2846 11 : err = template_load(csp, &filter_template, "edit-actions-for-url-filter", 0);
2847 11 : if (err)
2848 : {
2849 0 : edit_free_file(file);
2850 0 : free_map(exports);
2851 0 : if (err == JB_ERR_FILE)
2852 : {
2853 0 : return cgi_error_no_template(csp, rsp, "edit-actions-for-url-filter");
2854 : }
2855 0 : return err;
2856 : }
2857 :
2858 11 : err = template_fill(&filter_template, exports);
2859 :
2860 1111 : for (i = 0; i < MAX_AF_FILES; i++)
2861 : {
2862 1100 : if ((csp->rlist[i] != NULL) && (csp->rlist[i]->f != NULL))
2863 : {
2864 11 : filter_group = csp->rlist[i]->f;
2865 143 : for (;(!err) && (filter_group != NULL); filter_group = filter_group->next)
2866 : {
2867 132 : char current_mode = 'x';
2868 : char number[20];
2869 : struct list_entry *filter_name;
2870 : struct map *line_exports;
2871 132 : const enum filter_type type = filter_group->type;
2872 132 : const int multi_action_index = filter_type_info[type].multi_action_index;
2873 :
2874 132 : assert(type < MAX_FILTER_TYPES);
2875 :
2876 132 : filter_name = cur_line->data.action->multi_add[multi_action_index]->first;
2877 198 : while ((filter_name != NULL)
2878 126 : && (0 != strcmp(filter_group->name, filter_name->str)))
2879 : {
2880 66 : filter_name = filter_name->next;
2881 : }
2882 :
2883 132 : if (filter_name != NULL)
2884 : {
2885 60 : current_mode = 'y';
2886 : }
2887 : else
2888 : {
2889 72 : filter_name = cur_line->data.action->multi_remove[multi_action_index]->first;
2890 72 : while ((filter_name != NULL)
2891 0 : && (0 != strcmp(filter_group->name, filter_name->str)))
2892 : {
2893 0 : filter_name = filter_name->next;
2894 : }
2895 72 : if (filter_name != NULL)
2896 : {
2897 0 : current_mode = 'n';
2898 : }
2899 : }
2900 :
2901 : /* Generate a unique serial number */
2902 132 : snprintf(number, sizeof(number), "%x", filter_identifier++);
2903 132 : number[sizeof(number) - 1] = '\0';
2904 :
2905 132 : line_exports = new_map();
2906 132 : if (line_exports == NULL)
2907 : {
2908 0 : err = JB_ERR_MEMORY;
2909 : }
2910 : else
2911 : {
2912 : char *filter_line;
2913 :
2914 132 : if (!err) err = map(line_exports, "index", 1, number, 1);
2915 132 : if (!err) err = map(line_exports, "name", 1, filter_group->name, 1);
2916 132 : if (!err) err = map(line_exports, "description", 1, filter_group->description, 1);
2917 132 : if (!err) err = map_radio(line_exports, "this-filter", "ynx", current_mode);
2918 132 : if (!err) err = map(line_exports, "filter-type", 1, filter_type_info[type].type, 1);
2919 132 : if (!err) err = map(line_exports, "abbr-filter-type", 1, filter_type_info[type].abbr_type, 1);
2920 132 : if (!err) err = map(line_exports, "anchor", 1, filter_type_info[type].anchor, 1);
2921 :
2922 132 : if (!err)
2923 : {
2924 132 : filter_line = strdup(filter_template);
2925 132 : if (filter_line == NULL) err = JB_ERR_MEMORY;
2926 : }
2927 132 : if (!err) err = template_fill(&filter_line, line_exports);
2928 132 : if (!err) err = string_join(&prepared_templates[type], filter_line);
2929 :
2930 132 : free_map(line_exports);
2931 : }
2932 : }
2933 : }
2934 : }
2935 11 : freez(filter_template);
2936 :
2937 : /* Replace all filter macros with the aggregated templates */
2938 99 : for (i = 0; i < MAX_FILTER_TYPES; i++)
2939 : {
2940 88 : if (err) break;
2941 88 : err = map(exports, filter_type_info[i].macro_name, 1, prepared_templates[i], 0);
2942 : }
2943 :
2944 11 : if (err)
2945 : {
2946 : /* Free aggregated templates */
2947 0 : for (i = 0; i < MAX_FILTER_TYPES; i++)
2948 : {
2949 0 : freez(prepared_templates[i]);
2950 : }
2951 : }
2952 : }
2953 :
2954 : /* Check or uncheck the "disable all of this type" radio buttons. */
2955 99 : for (i = 0; i < MAX_FILTER_TYPES; i++)
2956 : {
2957 88 : const int a = filter_type_info[i].multi_action_index;
2958 88 : const int disable_all = cur_line->data.action->multi_remove_all[a];
2959 88 : if (err) break;
2960 88 : err = map_radio(exports, filter_type_info[i].disable_all_option, "nx", (disable_all ? 'n' : 'x'));
2961 : }
2962 :
2963 11 : edit_free_file(file);
2964 :
2965 11 : if (err)
2966 : {
2967 0 : free_map(exports);
2968 0 : return err;
2969 : }
2970 :
2971 11 : return template_fill_for_cgi(csp, "edit-actions-for-url", exports, rsp);
2972 : }
2973 :
2974 :
2975 : /*********************************************************************
2976 : *
2977 : * Function : get_number_of_filters
2978 : *
2979 : * Description : Counts the number of filter available.
2980 : *
2981 : * Parameters :
2982 : * 1 : csp = Current client state (buffers, headers, etc...)
2983 : *
2984 : * Returns : Number of filters available.
2985 : *
2986 : *********************************************************************/
2987 557 : static int get_number_of_filters(const struct client_state *csp)
2988 : {
2989 : int i;
2990 : struct re_filterfile_spec *b;
2991 : struct file_list *fl;
2992 557 : int number_of_filters = 0;
2993 :
2994 56257 : for (i = 0; i < MAX_AF_FILES; i++)
2995 : {
2996 55700 : fl = csp->rlist[i];
2997 55700 : if ((NULL == fl) || (NULL == fl->f))
2998 : {
2999 : /*
3000 : * Either there are no filter files left or this
3001 : * filter file just contains no valid filters.
3002 : *
3003 : * Continue to be sure we don't miss valid filter
3004 : * files that are chained after empty or invalid ones.
3005 : */
3006 55143 : continue;
3007 : }
3008 :
3009 7241 : for (b = fl->f; b != NULL; b = b->next)
3010 : {
3011 6684 : number_of_filters++;
3012 : }
3013 : }
3014 :
3015 557 : return number_of_filters;
3016 :
3017 : }
3018 :
3019 :
3020 : /*********************************************************************
3021 : *
3022 : * Function : cgi_edit_actions_submit
3023 : *
3024 : * Description : CGI function that actually edits the Actions list.
3025 : *
3026 : * Parameters :
3027 : * 1 : csp = Current client state (buffers, headers, etc...)
3028 : * 2 : rsp = http_response data structure for output
3029 : * 3 : parameters = map of cgi parameters
3030 : *
3031 : * CGI Parameters : None
3032 : *
3033 : * Returns : JB_ERR_OK on success
3034 : * JB_ERR_MEMORY on out-of-memory
3035 : * JB_ERR_CGI_PARAMS if the CGI parameters are not
3036 : * specified or not valid.
3037 : *
3038 : *********************************************************************/
3039 697 : jb_err cgi_edit_actions_submit(struct client_state *csp,
3040 : struct http_response *rsp,
3041 : const struct map *parameters)
3042 : {
3043 : unsigned sectionid;
3044 : char * actiontext;
3045 : char * newtext;
3046 : size_t newtext_size;
3047 : size_t len;
3048 : struct editable_file * file;
3049 : struct file_line * cur_line;
3050 : unsigned line_number;
3051 : char target[1024];
3052 : jb_err err;
3053 : int filter_identifier;
3054 : int i;
3055 : const char * action_set_name;
3056 : struct file_list * fl;
3057 : struct url_actions * b;
3058 : int number_of_filters;
3059 :
3060 697 : if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3061 : {
3062 0 : return cgi_error_disabled(csp, rsp);
3063 : }
3064 :
3065 697 : err = get_number_param(csp, parameters, "s", §ionid);
3066 697 : if (err)
3067 : {
3068 32 : return err;
3069 : }
3070 :
3071 665 : err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3072 665 : if (err)
3073 : {
3074 : /* No filename specified, can't read file, modified, or out of memory. */
3075 11 : return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3076 : }
3077 :
3078 654 : cur_line = file->lines;
3079 :
3080 2766 : for (line_number = 1; (cur_line != NULL) && (line_number < sectionid); line_number++)
3081 : {
3082 2112 : cur_line = cur_line->next;
3083 : }
3084 :
3085 654 : if ( (cur_line == NULL)
3086 653 : || (line_number != sectionid)
3087 626 : || (sectionid < 1)
3088 626 : || (cur_line->type != FILE_LINE_ACTION))
3089 : {
3090 : /* Invalid "sectionid" parameter */
3091 29 : edit_free_file(file);
3092 29 : return JB_ERR_CGI_PARAMS;
3093 : }
3094 :
3095 625 : get_string_param(parameters, "p", &action_set_name);
3096 625 : if (action_set_name != NULL)
3097 : {
3098 606 : for (filter_identifier = 0; filter_identifier < MAX_AF_FILES; filter_identifier++)
3099 : {
3100 600 : if (((fl = csp->actions_list[filter_identifier]) != NULL) && ((b = fl->f) != NULL))
3101 : {
3102 138 : for (b = b->next; NULL != b; b = b->next)
3103 : {
3104 126 : if (!strncmp(b->url->spec, "standard.", 9) && !strcmp(b->url->spec + 9, action_set_name))
3105 : {
3106 0 : copy_action(cur_line->data.action, b->action);
3107 0 : goto found;
3108 : }
3109 : }
3110 : }
3111 : }
3112 6 : edit_free_file(file);
3113 6 : return JB_ERR_CGI_PARAMS;
3114 :
3115 0 : found: ;
3116 : }
3117 : else
3118 : {
3119 619 : err = actions_from_radio(parameters, cur_line->data.action);
3120 : }
3121 :
3122 619 : if (err)
3123 : {
3124 : /* Out of memory */
3125 62 : edit_free_file(file);
3126 62 : return err;
3127 : }
3128 :
3129 : /* Check the "disable all of this type" parameters. */
3130 5013 : for (i = 0; i < MAX_FILTER_TYPES; i++)
3131 : {
3132 4456 : const int multi_action_index = filter_type_info[i].multi_action_index;
3133 4456 : const char ch = get_char_param(parameters, filter_type_info[i].disable_all_param);
3134 :
3135 4456 : if (ch == 'N')
3136 : {
3137 59 : list_remove_all(cur_line->data.action->multi_add[multi_action_index]);
3138 59 : list_remove_all(cur_line->data.action->multi_remove[multi_action_index]);
3139 59 : cur_line->data.action->multi_remove_all[multi_action_index] = 1;
3140 : }
3141 4397 : else if (ch == 'X')
3142 : {
3143 1089 : cur_line->data.action->multi_remove_all[multi_action_index] = 0;
3144 : }
3145 : }
3146 :
3147 557 : number_of_filters = get_number_of_filters(csp);
3148 :
3149 7108 : for (filter_identifier = 0; filter_identifier < number_of_filters && !err; filter_identifier++)
3150 : {
3151 : char key_value[30];
3152 : char key_name[30];
3153 : char key_type[30];
3154 : const char *name;
3155 : char value; /*
3156 : * Filter state. Valid states are: 'Y' (active),
3157 : * 'N' (inactive) and 'X' (no change).
3158 : * XXX: bad name.
3159 : */
3160 : char type; /*
3161 : * Abbreviated filter type. Valid types are: 'F' (content filter),
3162 : * 'S' (server-header filter) and 'C' (client-header filter).
3163 : */
3164 6566 : int multi_action_index = 0;
3165 :
3166 : /* Generate the keys */
3167 6566 : snprintf(key_value, sizeof(key_value), "filter_r%x", filter_identifier);
3168 6566 : key_value[sizeof(key_value) - 1] = '\0'; /* XXX: Why? */
3169 6566 : snprintf(key_name, sizeof(key_name), "filter_n%x", filter_identifier);
3170 6566 : key_name[sizeof(key_name) - 1] = '\0'; /* XXX: Why? */
3171 6566 : snprintf(key_type, sizeof(key_type), "filter_t%x", filter_identifier);
3172 :
3173 6566 : err = get_string_param(parameters, key_name, &name);
3174 6566 : if (err) break;
3175 :
3176 6551 : if (name == NULL)
3177 : {
3178 : /* The filter identifier isn't present. Try the next one. */
3179 5308 : continue;
3180 : }
3181 :
3182 1303 : type = get_char_param(parameters, key_type);
3183 1303 : switch (type)
3184 : {
3185 1163 : case 'F':
3186 1163 : multi_action_index = ACTION_MULTI_FILTER;
3187 1163 : break;
3188 18 : case 'S':
3189 18 : multi_action_index = ACTION_MULTI_SERVER_HEADER_FILTER;
3190 18 : break;
3191 16 : case 'C':
3192 16 : multi_action_index = ACTION_MULTI_CLIENT_HEADER_FILTER;
3193 16 : break;
3194 11 : case 'L':
3195 11 : multi_action_index = ACTION_MULTI_CLIENT_HEADER_TAGGER;
3196 11 : break;
3197 24 : case 'E':
3198 24 : multi_action_index = ACTION_MULTI_SERVER_HEADER_TAGGER;
3199 24 : break;
3200 11 : case 'P':
3201 11 : multi_action_index = ACTION_MULTI_CLIENT_BODY_FILTER;
3202 11 : break;
3203 60 : default:
3204 60 : log_error(LOG_LEVEL_ERROR,
3205 : "Unknown filter type: %c for filter %s. Filter ignored.", type, name);
3206 60 : continue;
3207 : }
3208 1243 : assert(multi_action_index);
3209 :
3210 1243 : value = get_char_param(parameters, key_value);
3211 1243 : if (value == 'Y')
3212 : {
3213 12 : list_remove_item(cur_line->data.action->multi_add[multi_action_index], name);
3214 12 : if (!err) err = enlist(cur_line->data.action->multi_add[multi_action_index], name);
3215 12 : list_remove_item(cur_line->data.action->multi_remove[multi_action_index], name);
3216 : }
3217 1231 : else if (value == 'N')
3218 : {
3219 72 : list_remove_item(cur_line->data.action->multi_add[multi_action_index], name);
3220 72 : if (!cur_line->data.action->multi_remove_all[multi_action_index])
3221 : {
3222 53 : list_remove_item(cur_line->data.action->multi_remove[multi_action_index], name);
3223 53 : if (!err) err = enlist(cur_line->data.action->multi_remove[multi_action_index], name);
3224 : }
3225 : }
3226 1159 : else if (value == 'X')
3227 : {
3228 1098 : list_remove_item(cur_line->data.action->multi_add[multi_action_index], name);
3229 1098 : list_remove_item(cur_line->data.action->multi_remove[multi_action_index], name);
3230 : }
3231 : }
3232 :
3233 : /* process existing suppress tag */
3234 557 : for (filter_identifier = 0; !err; filter_identifier++)
3235 : {
3236 : char key_value[30];
3237 : char key_name[30];
3238 : char old_name[30];
3239 : char key_type[30];
3240 : const char *name, *new_name;
3241 : char value; /*
3242 : * Filter state. Valid states are: 'Y' (active),
3243 : * 'N' (inactive) and 'X' (no change).
3244 : * XXX: bad name.
3245 : */
3246 : char type; /*
3247 : * Abbreviated filter type. Valid types are: 'U' (suppress tag).
3248 : */
3249 542 : int multi_action_index = 0;
3250 :
3251 : /* Generate the keys */
3252 542 : snprintf(key_value, sizeof(key_value), "string_filter_r%x", filter_identifier);
3253 542 : snprintf(key_name, sizeof(key_name), "string_filter_n%x", filter_identifier);
3254 542 : snprintf(old_name, sizeof(old_name), "string_filter_o%x", filter_identifier);
3255 542 : snprintf(key_type, sizeof(key_type), "string_filter_t%x", filter_identifier);
3256 :
3257 542 : err = get_string_param(parameters, old_name, &name);
3258 1084 : if (err) break;
3259 :
3260 542 : if (name == NULL)
3261 : {
3262 : /* The filter identifier isn't present: we're done! */
3263 542 : break;
3264 : }
3265 :
3266 0 : err = get_string_param(parameters, key_name, &new_name);
3267 0 : if (err) break;
3268 0 : if (new_name == NULL) new_name = name;
3269 :
3270 0 : type = get_char_param(parameters, key_type);
3271 0 : switch (type)
3272 : {
3273 0 : case 'U':
3274 0 : multi_action_index = ACTION_MULTI_SUPPRESS_TAG;
3275 0 : break;
3276 0 : default:
3277 0 : log_error(LOG_LEVEL_ERROR,
3278 : "Unknown filter type: %c for filter %s. Filter ignored.", type, name);
3279 0 : continue;
3280 : }
3281 0 : assert(multi_action_index);
3282 :
3283 0 : value = get_char_param(parameters, key_value);
3284 0 : if (value == 'X' || value == 'Y' || value == 'N')
3285 : {
3286 0 : list_remove_item(cur_line->data.action->multi_add[multi_action_index], name);
3287 0 : list_remove_item(cur_line->data.action->multi_remove[multi_action_index], name);
3288 : }
3289 :
3290 0 : if (value == 'Y')
3291 : {
3292 0 : err = enlist(cur_line->data.action->multi_add[multi_action_index], new_name);
3293 : }
3294 0 : else if (value == 'N')
3295 : {
3296 0 : err = enlist(cur_line->data.action->multi_remove[multi_action_index], new_name);
3297 : }
3298 : }
3299 :
3300 : /* process new string filters */
3301 557 : for (filter_identifier = 0; !err; filter_identifier++)
3302 : {
3303 : char key_value[30];
3304 : char key_name[30];
3305 : char key_type[30];
3306 : const char *name;
3307 : char value; /*
3308 : * Filter state. Valid states are: 'Y' (active),
3309 : * 'N' (inactive) and 'X' (no change).
3310 : * XXX: bad name.
3311 : */
3312 : char type; /*
3313 : * Abbreviated filter type. Valid types are: 'U' (suppress tag).
3314 : */
3315 542 : int multi_action_index = 0;
3316 :
3317 : /* Generate the keys */
3318 542 : snprintf(key_value, sizeof(key_value), "new_string_filter_r%x", filter_identifier);
3319 542 : snprintf(key_name, sizeof(key_name), "new_string_filter_n%x", filter_identifier);
3320 542 : snprintf(key_type, sizeof(key_type), "new_string_filter_t%x", filter_identifier);
3321 :
3322 542 : err = get_string_param(parameters, key_name, &name);
3323 1084 : if (err) break;
3324 :
3325 542 : if (name == NULL)
3326 : {
3327 : /* The filter identifier isn't present: we've done! */
3328 542 : break;
3329 : }
3330 :
3331 0 : type = get_char_param(parameters, key_type);
3332 0 : switch (type)
3333 : {
3334 0 : case 'U':
3335 0 : multi_action_index = ACTION_MULTI_SUPPRESS_TAG;
3336 0 : break;
3337 0 : default:
3338 0 : log_error(LOG_LEVEL_ERROR,
3339 : "Unknown filter type: %c for filter %s. Filter ignored.", type, name);
3340 0 : continue;
3341 : }
3342 0 : assert(multi_action_index);
3343 :
3344 0 : value = get_char_param(parameters, key_value);
3345 0 : if (value == 'Y')
3346 : {
3347 0 : list_remove_item(cur_line->data.action->multi_add[multi_action_index], name);
3348 0 : if (!err) err = enlist(cur_line->data.action->multi_add[multi_action_index], name);
3349 0 : list_remove_item(cur_line->data.action->multi_remove[multi_action_index], name);
3350 : }
3351 0 : else if (value == 'N')
3352 : {
3353 0 : list_remove_item(cur_line->data.action->multi_add[multi_action_index], name);
3354 0 : list_remove_item(cur_line->data.action->multi_remove[multi_action_index], name);
3355 0 : if (!err) err = enlist(cur_line->data.action->multi_remove[multi_action_index], name);
3356 : }
3357 : /* nothing to do if the value is 'X' */
3358 : }
3359 :
3360 557 : if (err)
3361 : {
3362 : /* Out of memory */
3363 15 : edit_free_file(file);
3364 15 : return err;
3365 : }
3366 :
3367 542 : if (NULL == (actiontext = actions_to_text(cur_line->data.action)))
3368 : {
3369 : /* Out of memory */
3370 0 : edit_free_file(file);
3371 0 : return JB_ERR_MEMORY;
3372 : }
3373 :
3374 542 : len = strlen(actiontext);
3375 542 : if (len == 0)
3376 : {
3377 : /*
3378 : * Empty action - must special-case this.
3379 : * Simply setting len to 1 is sufficient...
3380 : */
3381 5 : len = 1;
3382 : }
3383 :
3384 542 : newtext_size = len + 2;
3385 542 : newtext = malloc_or_die(newtext_size);
3386 542 : strlcpy(newtext, actiontext, newtext_size);
3387 542 : free(actiontext);
3388 542 : newtext[0] = '{';
3389 542 : newtext[len] = '}';
3390 542 : newtext[len + 1] = '\0';
3391 :
3392 542 : freez(cur_line->raw);
3393 542 : freez(cur_line->unprocessed);
3394 542 : cur_line->unprocessed = newtext;
3395 :
3396 542 : err = edit_write_file(file);
3397 542 : if (err)
3398 : {
3399 : /* Error writing file */
3400 0 : if (err == JB_ERR_FILE)
3401 : {
3402 : /* Read-only file. */
3403 0 : err = cgi_error_file_read_only(csp, rsp, file->filename);
3404 : }
3405 0 : edit_free_file(file);
3406 0 : return err;
3407 : }
3408 :
3409 542 : snprintf(target, sizeof(target), CGI_PREFIX "edit-actions-list?foo=%lu&f=%u#l%u",
3410 542 : (unsigned long) time(NULL), file->identifier, sectionid);
3411 :
3412 542 : edit_free_file(file);
3413 :
3414 542 : return cgi_redirect(rsp, target);
3415 : }
3416 :
3417 :
3418 : /*********************************************************************
3419 : *
3420 : * Function : cgi_edit_actions_url
3421 : *
3422 : * Description : CGI function that actually edits a URL pattern in
3423 : * an actions file.
3424 : *
3425 : * Parameters :
3426 : * 1 : csp = Current client state (buffers, headers, etc...)
3427 : * 2 : rsp = http_response data structure for output
3428 : * 3 : parameters = map of cgi parameters
3429 : *
3430 : * CGI Parameters :
3431 : * filename : Identifies the file to edit
3432 : * ver : File's last-modified time
3433 : * section : Line number of section to edit
3434 : * pattern : Line number of pattern to edit
3435 : * newval : New value for pattern
3436 : *
3437 : * Returns : JB_ERR_OK on success
3438 : * JB_ERR_MEMORY on out-of-memory
3439 : * JB_ERR_CGI_PARAMS if the CGI parameters are not
3440 : * specified or not valid.
3441 : *
3442 : *********************************************************************/
3443 156 : jb_err cgi_edit_actions_url(struct client_state *csp,
3444 : struct http_response *rsp,
3445 : const struct map *parameters)
3446 : {
3447 : unsigned patternid;
3448 : char * new_pattern;
3449 : struct editable_file * file;
3450 : struct file_line * cur_line;
3451 : unsigned line_number;
3452 156 : unsigned section_start_line_number = 0;
3453 : char target[1024];
3454 : jb_err err;
3455 :
3456 156 : assert(csp);
3457 156 : assert(rsp);
3458 156 : assert(parameters);
3459 :
3460 156 : if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3461 : {
3462 0 : return cgi_error_disabled(csp, rsp);
3463 : }
3464 :
3465 156 : err = get_number_param(csp, parameters, "p", &patternid);
3466 156 : if (err)
3467 : {
3468 144 : return err;
3469 : }
3470 12 : if (patternid < 1U)
3471 : {
3472 2 : return JB_ERR_CGI_PARAMS;
3473 : }
3474 :
3475 10 : err = get_url_spec_param(csp, parameters, "u", &new_pattern);
3476 10 : if (err)
3477 : {
3478 1 : return err;
3479 : }
3480 :
3481 9 : err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3482 9 : if (err)
3483 : {
3484 : /* No filename specified, can't read file, modified, or out of memory. */
3485 2 : free(new_pattern);
3486 2 : return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3487 : }
3488 :
3489 7 : line_number = 1;
3490 7 : cur_line = file->lines;
3491 :
3492 96 : while ((cur_line != NULL) && (line_number < patternid))
3493 : {
3494 89 : if (cur_line->type == FILE_LINE_ACTION)
3495 : {
3496 30 : section_start_line_number = line_number;
3497 : }
3498 89 : cur_line = cur_line->next;
3499 89 : line_number++;
3500 : }
3501 :
3502 7 : if ((cur_line == NULL)
3503 6 : || (cur_line->type != FILE_LINE_URL))
3504 : {
3505 : /* Invalid "patternid" parameter */
3506 5 : free(new_pattern);
3507 5 : edit_free_file(file);
3508 5 : return JB_ERR_CGI_PARAMS;
3509 : }
3510 :
3511 : /* At this point, the line to edit is in cur_line */
3512 :
3513 2 : freez(cur_line->raw);
3514 2 : freez(cur_line->unprocessed);
3515 2 : cur_line->unprocessed = new_pattern;
3516 :
3517 2 : err = edit_write_file(file);
3518 2 : if (err)
3519 : {
3520 : /* Error writing file */
3521 0 : if (err == JB_ERR_FILE)
3522 : {
3523 : /* Read-only file. */
3524 0 : err = cgi_error_file_read_only(csp, rsp, file->filename);
3525 : }
3526 0 : edit_free_file(file);
3527 0 : return err;
3528 : }
3529 :
3530 2 : snprintf(target, sizeof(target), CGI_PREFIX "edit-actions-list?foo=%lu&f=%u#l%u",
3531 2 : (unsigned long) time(NULL), file->identifier, section_start_line_number);
3532 :
3533 2 : edit_free_file(file);
3534 :
3535 2 : return cgi_redirect(rsp, target);
3536 : }
3537 :
3538 :
3539 : /*********************************************************************
3540 : *
3541 : * Function : cgi_edit_actions_add_url
3542 : *
3543 : * Description : CGI function that actually adds a URL pattern to
3544 : * an actions file.
3545 : *
3546 : * Parameters :
3547 : * 1 : csp = Current client state (buffers, headers, etc...)
3548 : * 2 : rsp = http_response data structure for output
3549 : * 3 : parameters = map of cgi parameters
3550 : *
3551 : * CGI Parameters :
3552 : * filename : Identifies the file to edit
3553 : * ver : File's last-modified time
3554 : * section : Line number of section to edit
3555 : * newval : New pattern
3556 : *
3557 : * Returns : JB_ERR_OK on success
3558 : * JB_ERR_MEMORY on out-of-memory
3559 : * JB_ERR_CGI_PARAMS if the CGI parameters are not
3560 : * specified or not valid.
3561 : *
3562 : *********************************************************************/
3563 767 : jb_err cgi_edit_actions_add_url(struct client_state *csp,
3564 : struct http_response *rsp,
3565 : const struct map *parameters)
3566 : {
3567 : unsigned sectionid;
3568 : char * new_pattern;
3569 : struct file_line * new_line;
3570 : struct editable_file * file;
3571 : struct file_line * cur_line;
3572 : unsigned line_number;
3573 : char target[1024];
3574 : jb_err err;
3575 :
3576 767 : if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3577 : {
3578 0 : return cgi_error_disabled(csp, rsp);
3579 : }
3580 :
3581 767 : err = get_number_param(csp, parameters, "s", §ionid);
3582 767 : if (err)
3583 : {
3584 82 : return err;
3585 : }
3586 685 : if (sectionid < 1U)
3587 : {
3588 1 : return JB_ERR_CGI_PARAMS;
3589 : }
3590 :
3591 684 : err = get_url_spec_param(csp, parameters, "u", &new_pattern);
3592 684 : if (err)
3593 : {
3594 319 : return err;
3595 : }
3596 :
3597 365 : err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3598 365 : if (err)
3599 : {
3600 : /* No filename specified, can't read file, modified, or out of memory. */
3601 160 : free(new_pattern);
3602 160 : return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3603 : }
3604 :
3605 205 : line_number = 1;
3606 205 : cur_line = file->lines;
3607 :
3608 742 : while ((cur_line != NULL) && (line_number < sectionid))
3609 : {
3610 537 : cur_line = cur_line->next;
3611 537 : line_number++;
3612 : }
3613 :
3614 205 : if ((cur_line == NULL)
3615 186 : || (cur_line->type != FILE_LINE_ACTION))
3616 : {
3617 : /* Invalid "sectionid" parameter */
3618 20 : free(new_pattern);
3619 20 : edit_free_file(file);
3620 20 : return JB_ERR_CGI_PARAMS;
3621 : }
3622 :
3623 : /* At this point, the section header is in cur_line - add after this. */
3624 :
3625 : /* Allocate the new line */
3626 185 : new_line = zalloc_or_die(sizeof(*new_line));
3627 :
3628 : /* Fill in the data members of the new line */
3629 185 : new_line->raw = NULL;
3630 185 : new_line->prefix = NULL;
3631 185 : new_line->unprocessed = new_pattern;
3632 185 : new_line->type = FILE_LINE_URL;
3633 :
3634 : /* Link new_line into the list, after cur_line */
3635 185 : new_line->next = cur_line->next;
3636 185 : cur_line->next = new_line;
3637 :
3638 : /* Done making changes, now commit */
3639 :
3640 185 : err = edit_write_file(file);
3641 185 : if (err)
3642 : {
3643 : /* Error writing file */
3644 0 : if (err == JB_ERR_FILE)
3645 : {
3646 : /* Read-only file. */
3647 0 : err = cgi_error_file_read_only(csp, rsp, file->filename);
3648 : }
3649 0 : edit_free_file(file);
3650 0 : return err;
3651 : }
3652 :
3653 185 : snprintf(target, sizeof(target), CGI_PREFIX "edit-actions-list?foo=%lu&f=%u#l%u",
3654 185 : (unsigned long) time(NULL), file->identifier, sectionid);
3655 :
3656 185 : edit_free_file(file);
3657 :
3658 185 : return cgi_redirect(rsp, target);
3659 : }
3660 :
3661 :
3662 : /*********************************************************************
3663 : *
3664 : * Function : cgi_edit_actions_remove_url
3665 : *
3666 : * Description : CGI function that actually removes a URL pattern from
3667 : * the actions file.
3668 : *
3669 : * Parameters :
3670 : * 1 : csp = Current client state (buffers, headers, etc...)
3671 : * 2 : rsp = http_response data structure for output
3672 : * 3 : parameters = map of cgi parameters
3673 : *
3674 : * CGI Parameters :
3675 : * f : (filename) Identifies the file to edit
3676 : * v : (version) File's last-modified time
3677 : * p : (pattern) Line number of pattern to remove
3678 : *
3679 : * Returns : JB_ERR_OK on success
3680 : * JB_ERR_MEMORY on out-of-memory
3681 : * JB_ERR_CGI_PARAMS if the CGI parameters are not
3682 : * specified or not valid.
3683 : *
3684 : *********************************************************************/
3685 38 : jb_err cgi_edit_actions_remove_url(struct client_state *csp,
3686 : struct http_response *rsp,
3687 : const struct map *parameters)
3688 : {
3689 : unsigned patternid;
3690 : struct editable_file * file;
3691 : struct file_line * cur_line;
3692 : struct file_line * prev_line;
3693 : unsigned line_number;
3694 38 : unsigned section_start_line_number = 0;
3695 : char target[1024];
3696 : jb_err err;
3697 :
3698 38 : if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3699 : {
3700 0 : return cgi_error_disabled(csp, rsp);
3701 : }
3702 :
3703 38 : err = get_number_param(csp, parameters, "p", &patternid);
3704 38 : if (err)
3705 : {
3706 35 : return err;
3707 : }
3708 :
3709 3 : err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3710 3 : if (err)
3711 : {
3712 : /* No filename specified, can't read file, modified, or out of memory. */
3713 1 : return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3714 : }
3715 :
3716 2 : line_number = 1;
3717 2 : prev_line = NULL;
3718 2 : cur_line = file->lines;
3719 :
3720 5 : while ((cur_line != NULL) && (line_number < patternid))
3721 : {
3722 3 : if (cur_line->type == FILE_LINE_ACTION)
3723 : {
3724 2 : section_start_line_number = line_number;
3725 : }
3726 3 : prev_line = cur_line;
3727 3 : cur_line = cur_line->next;
3728 3 : line_number++;
3729 : }
3730 :
3731 2 : if ( (cur_line == NULL)
3732 2 : || (prev_line == NULL)
3733 1 : || (cur_line->type != FILE_LINE_URL))
3734 : {
3735 : /* Invalid "patternid" parameter */
3736 1 : edit_free_file(file);
3737 1 : return JB_ERR_CGI_PARAMS;
3738 : }
3739 :
3740 : /* At this point, the line to remove is in cur_line, and the previous
3741 : * one is in prev_line
3742 : */
3743 :
3744 : /* Unlink cur_line */
3745 1 : prev_line->next = cur_line->next;
3746 1 : cur_line->next = NULL;
3747 :
3748 : /* Free cur_line */
3749 1 : edit_free_file_lines(cur_line);
3750 :
3751 1 : err = edit_write_file(file);
3752 1 : if (err)
3753 : {
3754 : /* Error writing file */
3755 0 : if (err == JB_ERR_FILE)
3756 : {
3757 : /* Read-only file. */
3758 0 : err = cgi_error_file_read_only(csp, rsp, file->filename);
3759 : }
3760 0 : edit_free_file(file);
3761 0 : return err;
3762 : }
3763 :
3764 1 : snprintf(target, sizeof(target), CGI_PREFIX "edit-actions-list?foo=%lu&f=%u#l%u",
3765 1 : (unsigned long) time(NULL), file->identifier, section_start_line_number);
3766 :
3767 1 : edit_free_file(file);
3768 :
3769 1 : return cgi_redirect(rsp, target);
3770 : }
3771 :
3772 :
3773 : /*********************************************************************
3774 : *
3775 : * Function : cgi_edit_actions_section_remove
3776 : *
3777 : * Description : CGI function that actually removes a whole section from
3778 : * the actions file. The section must be empty first
3779 : * (else JB_ERR_CGI_PARAMS).
3780 : *
3781 : * Parameters :
3782 : * 1 : csp = Current client state (buffers, headers, etc...)
3783 : * 2 : rsp = http_response data structure for output
3784 : * 3 : parameters = map of cgi parameters
3785 : *
3786 : * CGI Parameters :
3787 : * f : (filename) Identifies the file to edit
3788 : * v : (version) File's last-modified time
3789 : * s : (section) Line number of section to edit
3790 : *
3791 : * Returns : JB_ERR_OK on success
3792 : * JB_ERR_MEMORY on out-of-memory
3793 : * JB_ERR_CGI_PARAMS if the CGI parameters are not
3794 : * specified or not valid.
3795 : *
3796 : *********************************************************************/
3797 124 : jb_err cgi_edit_actions_section_remove(struct client_state *csp,
3798 : struct http_response *rsp,
3799 : const struct map *parameters)
3800 : {
3801 : unsigned sectionid;
3802 : struct editable_file * file;
3803 : struct file_line * cur_line;
3804 : struct file_line * prev_line;
3805 : unsigned line_number;
3806 : char target[1024];
3807 : jb_err err;
3808 :
3809 124 : if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3810 : {
3811 0 : return cgi_error_disabled(csp, rsp);
3812 : }
3813 :
3814 124 : err = get_number_param(csp, parameters, "s", §ionid);
3815 124 : if (err)
3816 : {
3817 104 : return err;
3818 : }
3819 :
3820 20 : err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3821 20 : if (err)
3822 : {
3823 : /* No filename specified, can't read file, modified, or out of memory. */
3824 8 : return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3825 : }
3826 :
3827 12 : line_number = 1;
3828 12 : cur_line = file->lines;
3829 :
3830 12 : prev_line = NULL;
3831 114 : while ((cur_line != NULL) && (line_number < sectionid))
3832 : {
3833 102 : prev_line = cur_line;
3834 102 : cur_line = cur_line->next;
3835 102 : line_number++;
3836 : }
3837 :
3838 12 : if ((cur_line == NULL)
3839 10 : || (cur_line->type != FILE_LINE_ACTION))
3840 : {
3841 : /* Invalid "sectionid" parameter */
3842 5 : edit_free_file(file);
3843 5 : return JB_ERR_CGI_PARAMS;
3844 : }
3845 :
3846 7 : if ((cur_line->next != NULL)
3847 7 : && (cur_line->next->type == FILE_LINE_URL))
3848 : {
3849 : /* Section not empty. */
3850 7 : edit_free_file(file);
3851 7 : return JB_ERR_CGI_PARAMS;
3852 : }
3853 :
3854 : /* At this point, the line to remove is in cur_line, and the previous
3855 : * one is in prev_line
3856 : */
3857 :
3858 : /* Unlink cur_line */
3859 0 : if (prev_line == NULL)
3860 : {
3861 : /* Removing the first line from the file */
3862 0 : file->lines = cur_line->next;
3863 : }
3864 : else
3865 : {
3866 0 : prev_line->next = cur_line->next;
3867 : }
3868 0 : cur_line->next = NULL;
3869 :
3870 : /* Free cur_line */
3871 0 : edit_free_file_lines(cur_line);
3872 :
3873 0 : err = edit_write_file(file);
3874 0 : if (err)
3875 : {
3876 : /* Error writing file */
3877 0 : if (err == JB_ERR_FILE)
3878 : {
3879 : /* Read-only file. */
3880 0 : err = cgi_error_file_read_only(csp, rsp, file->filename);
3881 : }
3882 0 : edit_free_file(file);
3883 0 : return err;
3884 : }
3885 :
3886 0 : snprintf(target, sizeof(target), CGI_PREFIX "edit-actions-list?foo=%lu&f=%u",
3887 0 : (unsigned long) time(NULL), file->identifier);
3888 :
3889 0 : edit_free_file(file);
3890 :
3891 0 : return cgi_redirect(rsp, target);
3892 : }
3893 :
3894 :
3895 : /*********************************************************************
3896 : *
3897 : * Function : cgi_edit_actions_section_add
3898 : *
3899 : * Description : CGI function that adds a new empty section to
3900 : * an actions file.
3901 : *
3902 : * Parameters :
3903 : * 1 : csp = Current client state (buffers, headers, etc...)
3904 : * 2 : rsp = http_response data structure for output
3905 : * 3 : parameters = map of cgi parameters
3906 : *
3907 : * CGI Parameters :
3908 : * f : (filename) Identifies the file to edit
3909 : * v : (version) File's last-modified time
3910 : * s : (section) Line number of section to add after, 0 for
3911 : * start of file.
3912 : *
3913 : * Returns : JB_ERR_OK on success
3914 : * JB_ERR_MEMORY on out-of-memory
3915 : * JB_ERR_CGI_PARAMS if the CGI parameters are not
3916 : * specified or not valid.
3917 : *
3918 : *********************************************************************/
3919 24 : jb_err cgi_edit_actions_section_add(struct client_state *csp,
3920 : struct http_response *rsp,
3921 : const struct map *parameters)
3922 : {
3923 : unsigned sectionid;
3924 : struct file_line * new_line;
3925 : char * new_text;
3926 : struct editable_file * file;
3927 : struct file_line * cur_line;
3928 : unsigned line_number;
3929 : char target[1024];
3930 : jb_err err;
3931 :
3932 24 : if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3933 : {
3934 0 : return cgi_error_disabled(csp, rsp);
3935 : }
3936 :
3937 24 : err = get_number_param(csp, parameters, "s", §ionid);
3938 24 : if (err)
3939 : {
3940 10 : return err;
3941 : }
3942 :
3943 14 : err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3944 14 : if (err)
3945 : {
3946 : /* No filename specified, can't read file, modified, or out of memory. */
3947 1 : return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3948 : }
3949 :
3950 13 : line_number = 1;
3951 13 : cur_line = file->lines;
3952 :
3953 13 : if (sectionid <= 1U)
3954 : {
3955 : /* Add to start of file */
3956 3 : if (cur_line != NULL && cur_line->type != FILE_LINE_ACTION)
3957 : {
3958 : /* There's something in the file, find the line before the first
3959 : * action.
3960 : */
3961 20 : while ((cur_line->next != NULL)
3962 20 : && (cur_line->next->type != FILE_LINE_ACTION))
3963 : {
3964 18 : cur_line = cur_line->next;
3965 18 : line_number++;
3966 : }
3967 : }
3968 : else
3969 : {
3970 : /* File starts with action line, so insert at top */
3971 1 : cur_line = NULL;
3972 : }
3973 : }
3974 : else
3975 : {
3976 : /* Add after stated section. */
3977 109 : while ((cur_line != NULL) && (line_number < sectionid))
3978 : {
3979 99 : cur_line = cur_line->next;
3980 99 : line_number++;
3981 : }
3982 :
3983 10 : if ((cur_line == NULL)
3984 9 : || (cur_line->type != FILE_LINE_ACTION))
3985 : {
3986 : /* Invalid "sectionid" parameter */
3987 3 : edit_free_file(file);
3988 3 : return JB_ERR_CGI_PARAMS;
3989 : }
3990 :
3991 : /* Skip through the section to find the last line in it. */
3992 14 : while ((cur_line->next != NULL)
3993 13 : && (cur_line->next->type != FILE_LINE_ACTION))
3994 : {
3995 7 : cur_line = cur_line->next;
3996 7 : line_number++;
3997 : }
3998 : }
3999 :
4000 : /* At this point, the last line in the previous section is in cur_line
4001 : * - add after this. (Or if we need to add as the first line, cur_line
4002 : * will be NULL).
4003 : */
4004 :
4005 10 : new_text = strdup("{}");
4006 10 : if (NULL == new_text)
4007 : {
4008 0 : edit_free_file(file);
4009 0 : return JB_ERR_MEMORY;
4010 : }
4011 :
4012 : /* Allocate the new line */
4013 10 : new_line = zalloc_or_die(sizeof(*new_line));
4014 :
4015 : /* Fill in the data members of the new line */
4016 10 : new_line->raw = NULL;
4017 10 : new_line->prefix = NULL;
4018 10 : new_line->unprocessed = new_text;
4019 10 : new_line->type = FILE_LINE_ACTION;
4020 :
4021 10 : if (cur_line != NULL)
4022 : {
4023 : /* Link new_line into the list, after cur_line */
4024 9 : new_line->next = cur_line->next;
4025 9 : cur_line->next = new_line;
4026 : }
4027 : else
4028 : {
4029 : /* Link new_line into the list, as first line */
4030 1 : new_line->next = file->lines;
4031 1 : file->lines = new_line;
4032 : }
4033 :
4034 : /* Done making changes, now commit */
4035 :
4036 10 : err = edit_write_file(file);
4037 10 : if (err)
4038 : {
4039 : /* Error writing file */
4040 0 : if (err == JB_ERR_FILE)
4041 : {
4042 : /* Read-only file. */
4043 0 : err = cgi_error_file_read_only(csp, rsp, file->filename);
4044 : }
4045 0 : edit_free_file(file);
4046 0 : return err;
4047 : }
4048 :
4049 10 : snprintf(target, sizeof(target), CGI_PREFIX "edit-actions-list?foo=%lu&f=%u",
4050 10 : (unsigned long) time(NULL), file->identifier);
4051 :
4052 10 : edit_free_file(file);
4053 :
4054 10 : return cgi_redirect(rsp, target);
4055 : }
4056 :
4057 :
4058 : /*********************************************************************
4059 : *
4060 : * Function : cgi_edit_actions_section_swap
4061 : *
4062 : * Description : CGI function that swaps the order of two sections
4063 : * in the actions file. Note that this CGI can actually
4064 : * swap any two arbitrary sections, but the GUI interface
4065 : * currently only allows consecutive sections to be
4066 : * specified.
4067 : *
4068 : * Parameters :
4069 : * 1 : csp = Current client state (buffers, headers, etc...)
4070 : * 2 : rsp = http_response data structure for output
4071 : * 3 : parameters = map of cgi parameters
4072 : *
4073 : * CGI Parameters :
4074 : * f : (filename) Identifies the file to edit
4075 : * v : (version) File's last-modified time
4076 : * s1 : (section1) Line number of first section to swap
4077 : * s2 : (section2) Line number of second section to swap
4078 : *
4079 : * Returns : JB_ERR_OK on success
4080 : * JB_ERR_MEMORY on out-of-memory
4081 : * JB_ERR_CGI_PARAMS if the CGI parameters are not
4082 : * specified or not valid.
4083 : *
4084 : *********************************************************************/
4085 23 : jb_err cgi_edit_actions_section_swap(struct client_state *csp,
4086 : struct http_response *rsp,
4087 : const struct map *parameters)
4088 : {
4089 : unsigned section1;
4090 : unsigned section2;
4091 : struct editable_file * file;
4092 : struct file_line * cur_line;
4093 : struct file_line * prev_line;
4094 : struct file_line * line_before_section1;
4095 : struct file_line * line_start_section1;
4096 : struct file_line * line_end_section1;
4097 : struct file_line * line_after_section1;
4098 : struct file_line * line_before_section2;
4099 : struct file_line * line_start_section2;
4100 : struct file_line * line_end_section2;
4101 : struct file_line * line_after_section2;
4102 : unsigned line_number;
4103 : char target[1024];
4104 : jb_err err;
4105 :
4106 23 : if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
4107 : {
4108 0 : return cgi_error_disabled(csp, rsp);
4109 : }
4110 :
4111 23 : err = get_number_param(csp, parameters, "s1", §ion1);
4112 23 : if (!err) err = get_number_param(csp, parameters, "s2", §ion2);
4113 23 : if (err)
4114 : {
4115 14 : return err;
4116 : }
4117 :
4118 9 : if (section1 > section2)
4119 : {
4120 1 : unsigned temp = section2;
4121 1 : section2 = section1;
4122 1 : section1 = temp;
4123 : }
4124 :
4125 9 : err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
4126 9 : if (err)
4127 : {
4128 : /* No filename specified, can't read file, modified, or out of memory. */
4129 1 : return (err == JB_ERR_FILE ? JB_ERR_OK : err);
4130 : }
4131 :
4132 : /* Start at the beginning... */
4133 8 : line_number = 1;
4134 8 : cur_line = file->lines;
4135 8 : prev_line = NULL;
4136 :
4137 : /* ... find section1 ... */
4138 12 : while ((cur_line != NULL) && (line_number < section1))
4139 : {
4140 4 : prev_line = cur_line;
4141 4 : cur_line = cur_line->next;
4142 4 : line_number++;
4143 : }
4144 :
4145 8 : if ((cur_line == NULL)
4146 8 : || (cur_line->type != FILE_LINE_ACTION))
4147 : {
4148 : /* Invalid "section1" parameter */
4149 2 : edit_free_file(file);
4150 2 : return JB_ERR_CGI_PARAMS;
4151 : }
4152 :
4153 : /* If no-op, we've validated params and can skip the rest. */
4154 6 : if (section1 != section2)
4155 : {
4156 : /* ... find the end of section1 ... */
4157 5 : line_before_section1 = prev_line;
4158 5 : line_start_section1 = cur_line;
4159 : do
4160 : {
4161 10 : prev_line = cur_line;
4162 10 : cur_line = cur_line->next;
4163 10 : line_number++;
4164 : }
4165 10 : while ((cur_line != NULL) && (cur_line->type == FILE_LINE_URL));
4166 5 : line_end_section1 = prev_line;
4167 5 : line_after_section1 = cur_line;
4168 :
4169 : /* ... find section2 ... */
4170 20 : while ((cur_line != NULL) && (line_number < section2))
4171 : {
4172 15 : prev_line = cur_line;
4173 15 : cur_line = cur_line->next;
4174 15 : line_number++;
4175 : }
4176 :
4177 5 : if ((cur_line == NULL)
4178 4 : || (cur_line->type != FILE_LINE_ACTION))
4179 : {
4180 : /* Invalid "section2" parameter */
4181 2 : edit_free_file(file);
4182 2 : return JB_ERR_CGI_PARAMS;
4183 : }
4184 :
4185 : /* ... find the end of section2 ... */
4186 3 : line_before_section2 = prev_line;
4187 3 : line_start_section2 = cur_line;
4188 : do
4189 : {
4190 6 : prev_line = cur_line;
4191 6 : cur_line = cur_line->next;
4192 6 : line_number++;
4193 : }
4194 6 : while ((cur_line != NULL) && (cur_line->type == FILE_LINE_URL));
4195 3 : line_end_section2 = prev_line;
4196 3 : line_after_section2 = cur_line;
4197 :
4198 : /* Now have all the pointers we need. Do the swap. */
4199 :
4200 : /* Change the pointer to section1 to point to section2 instead */
4201 3 : if (line_before_section1 == NULL)
4202 : {
4203 3 : file->lines = line_start_section2;
4204 : }
4205 : else
4206 : {
4207 0 : line_before_section1->next = line_start_section2;
4208 : }
4209 :
4210 3 : if (line_before_section2 == line_end_section1)
4211 : {
4212 : /* Consecutive sections */
4213 1 : line_end_section2->next = line_start_section1;
4214 : }
4215 : else
4216 : {
4217 2 : line_end_section2->next = line_after_section1;
4218 2 : line_before_section2->next = line_start_section1;
4219 : }
4220 :
4221 : /* Set the pointer from the end of section1 to the rest of the file */
4222 3 : line_end_section1->next = line_after_section2;
4223 :
4224 3 : err = edit_write_file(file);
4225 3 : if (err)
4226 : {
4227 : /* Error writing file */
4228 0 : if (err == JB_ERR_FILE)
4229 : {
4230 : /* Read-only file. */
4231 0 : err = cgi_error_file_read_only(csp, rsp, file->filename);
4232 : }
4233 0 : edit_free_file(file);
4234 0 : return err;
4235 : }
4236 : } /* END if (section1 != section2) */
4237 :
4238 4 : snprintf(target, sizeof(target), CGI_PREFIX "edit-actions-list?foo=%lu&f=%u",
4239 4 : (unsigned long) time(NULL), file->identifier);
4240 :
4241 4 : edit_free_file(file);
4242 :
4243 4 : return cgi_redirect(rsp, target);
4244 : }
4245 :
4246 :
4247 : /*********************************************************************
4248 : *
4249 : * Function : javascriptify
4250 : *
4251 : * Description : Converts a string into a form JavaScript will like.
4252 : *
4253 : * Netscape 4's JavaScript sucks - it doesn't use
4254 : * "id" parameters, so you have to set the "name"
4255 : * used to submit a form element to something JavaScript
4256 : * will like. (Or access the elements by index in an
4257 : * array. That array contains >60 elements and will
4258 : * be changed whenever we add a new action to the
4259 : * editor, so I'm NOT going to use indexes that have
4260 : * to be figured out by hand.)
4261 : *
4262 : * Currently the only thing we have to worry about
4263 : * is "-" ==> "_" conversion.
4264 : *
4265 : * This is a length-preserving operation so it is
4266 : * carried out in-place, no memory is allocated
4267 : * or freed.
4268 : *
4269 : * Parameters :
4270 : * 1 : identifier = String to make JavaScript-friendly.
4271 : *
4272 : * Returns : N/A
4273 : *
4274 : *********************************************************************/
4275 13548 : static void javascriptify(char * identifier)
4276 : {
4277 13548 : char * p = identifier;
4278 36907 : while (NULL != (p = strchr(p, '-')))
4279 : {
4280 23359 : *p++ = '_';
4281 : }
4282 13548 : }
4283 :
4284 :
4285 : /*********************************************************************
4286 : *
4287 : * Function : actions_to_radio
4288 : *
4289 : * Description : Converts a actionsfile entry into settings for
4290 : * radio buttons and edit boxes on a HTML form.
4291 : *
4292 : * Parameters :
4293 : * 1 : exports = List of substitutions to add to.
4294 : * 2 : action = Action to read
4295 : *
4296 : * Returns : JB_ERR_OK on success
4297 : * JB_ERR_MEMORY on out-of-memory
4298 : *
4299 : *********************************************************************/
4300 11 : static jb_err actions_to_radio(struct map * exports,
4301 : const struct action_spec *action)
4302 : {
4303 : unsigned long mask;
4304 : unsigned long add;
4305 : int mapped_param;
4306 : int checked;
4307 : char current_mode;
4308 :
4309 11 : assert(exports);
4310 11 : assert(action);
4311 :
4312 11 : mask = action->mask;
4313 11 : add = action->add;
4314 :
4315 : /* sanity - prevents "-feature +feature" */
4316 11 : mask |= add;
4317 :
4318 :
4319 : #define DEFINE_ACTION_BOOL(name, bit) \
4320 : if (!(mask & bit)) \
4321 : { \
4322 : current_mode = 'n'; \
4323 : } \
4324 : else if (add & bit) \
4325 : { \
4326 : current_mode = 'y'; \
4327 : } \
4328 : else \
4329 : { \
4330 : current_mode = 'x'; \
4331 : } \
4332 : if (map_radio(exports, name, "ynx", current_mode)) \
4333 : { \
4334 : return JB_ERR_MEMORY; \
4335 : }
4336 :
4337 : #define DEFINE_ACTION_STRING(name, bit, index) \
4338 : DEFINE_ACTION_BOOL(name, bit); \
4339 : mapped_param = 0;
4340 :
4341 : #define DEFINE_CGI_PARAM_RADIO(name, bit, index, value, is_default) \
4342 : if (add & bit) \
4343 : { \
4344 : checked = !strcmp(action->string[index], value); \
4345 : } \
4346 : else \
4347 : { \
4348 : checked = is_default; \
4349 : } \
4350 : mapped_param |= checked; \
4351 : if (map(exports, name "-param-" value, 1, (checked ? "checked" : ""), 1)) \
4352 : { \
4353 : return JB_ERR_MEMORY; \
4354 : }
4355 :
4356 : #define DEFINE_CGI_PARAM_CUSTOM(name, bit, index, default_val) \
4357 : if (map(exports, name "-param-custom", 1, \
4358 : ((!mapped_param) ? "checked" : ""), 1)) \
4359 : { \
4360 : return JB_ERR_MEMORY; \
4361 : } \
4362 : if (map(exports, name "-param", 1, \
4363 : (((add & bit) && !mapped_param) ? \
4364 : action->string[index] : default_val), 1)) \
4365 : { \
4366 : return JB_ERR_MEMORY; \
4367 : }
4368 :
4369 : #define DEFINE_CGI_PARAM_NO_RADIO(name, bit, index, default_val) \
4370 : if (map(exports, name "-param", 1, \
4371 : ((add & bit) ? action->string[index] : default_val), 1)) \
4372 : { \
4373 : return JB_ERR_MEMORY; \
4374 : }
4375 :
4376 : #define DEFINE_ACTION_MULTI(name, index) \
4377 : if (action->multi_add[index]->first) \
4378 : { \
4379 : current_mode = 'y'; \
4380 : } \
4381 : else if (action->multi_remove_all[index]) \
4382 : { \
4383 : current_mode = 'n'; \
4384 : } \
4385 : else if (action->multi_remove[index]->first) \
4386 : { \
4387 : current_mode = 'y'; \
4388 : } \
4389 : else \
4390 : { \
4391 : current_mode = 'x'; \
4392 : } \
4393 : if (map_radio(exports, name, "ynx", current_mode)) \
4394 : { \
4395 : return JB_ERR_MEMORY; \
4396 : }
4397 :
4398 : #define DEFINE_ACTION_ALIAS 0 /* No aliases for output */
4399 :
4400 : #include "actionlist.h"
4401 :
4402 : #undef DEFINE_ACTION_MULTI
4403 : #undef DEFINE_ACTION_STRING
4404 : #undef DEFINE_ACTION_BOOL
4405 : #undef DEFINE_ACTION_ALIAS
4406 : #undef DEFINE_CGI_PARAM_CUSTOM
4407 : #undef DEFINE_CGI_PARAM_RADIO
4408 : #undef DEFINE_CGI_PARAM_NO_RADIO
4409 :
4410 11 : return JB_ERR_OK;
4411 : }
4412 :
4413 : /*********************************************************************
4414 : *
4415 : * Function : action_render_string_filters_template
4416 : *
4417 : * Description : Converts a actionsfile entry into HTML template for actions with string
4418 : * filters (currently SUPPRESS-TAG actions only)
4419 : *
4420 : * Parameters :
4421 : * 1 : exports = List of substitutions to add to.
4422 : * 2 : action = Action to read
4423 : * 3 : filter_template = template to fill
4424 : * 4 : type = filter type info for rendered values/macro name
4425 : *
4426 : * Returns : JB_ERR_OK on success
4427 : * JB_ERR_MEMORY on out-of-memory
4428 : *
4429 : *********************************************************************/
4430 11 : static jb_err action_render_string_filters_template(struct map * exports,
4431 : const struct action_spec *action,
4432 : const char* filter_template,
4433 : const struct filter_type_info *type)
4434 : {
4435 11 : jb_err err = JB_ERR_OK;
4436 11 : int filter_identifier = 0;
4437 : int i;
4438 11 : char *prepared_template = strdup("");
4439 :
4440 : struct action_multi {
4441 : char radio;
4442 : struct list_entry *list;
4443 : };
4444 :
4445 11 : struct action_multi desc[] = {
4446 11 : { 'y', action->multi_add[type->multi_action_index][0].first },
4447 11 : { 'n', action->multi_remove[type->multi_action_index][0].first }
4448 : };
4449 :
4450 33 : for (i = 0; i < SZ(desc); ++i)
4451 : {
4452 22 : const char radio = desc[i].radio;
4453 22 : struct list_entry *entry = desc[i].list;
4454 22 : for (;(!err) && (entry != NULL); entry = entry->next)
4455 : {
4456 : char number[20];
4457 : struct map *line_exports;
4458 :
4459 : /* Generate a unique serial number */
4460 0 : snprintf(number, sizeof(number), "%x", filter_identifier++);
4461 :
4462 0 : line_exports = new_map();
4463 0 : if (line_exports == NULL)
4464 : {
4465 0 : err = JB_ERR_MEMORY;
4466 : }
4467 : else
4468 : {
4469 : char *filter_line;
4470 0 : if (!err) err = map(line_exports, "index", 1, number, 1);
4471 0 : if (!err) err = map(line_exports, "name", 1, entry->str, 1);
4472 0 : if (!err) err = map_radio(line_exports, "this-filter", "ynx", radio);
4473 0 : if (!err) err = map(line_exports, "filter-type", 1, type->type, 1);
4474 0 : if (!err) err = map(line_exports, "abbr-filter-type", 1, type->abbr_type, 1);
4475 0 : if (!err) err = map(line_exports, "anchor", 1, type->anchor, 1);
4476 0 : if (!err)
4477 : {
4478 0 : filter_line = strdup(filter_template);
4479 0 : if (filter_line == NULL) err = JB_ERR_MEMORY;
4480 : }
4481 0 : if (!err) err = template_fill(&filter_line, line_exports);
4482 0 : if (!err) err = string_join(&prepared_template, filter_line);
4483 :
4484 0 : free_map(line_exports);
4485 : }
4486 : }
4487 : }
4488 11 : if (!err) map(exports, type->macro_name, 1, prepared_template, 1);
4489 11 : freez(prepared_template);
4490 11 : return err;
4491 : }
4492 :
4493 : /*********************************************************************
4494 : *
4495 : * Function : actions_from_radio
4496 : *
4497 : * Description : Converts a map of parameters passed to a CGI function
4498 : * into an actionsfile entry.
4499 : *
4500 : * Parameters :
4501 : * 1 : parameters = parameters to the CGI call
4502 : * 2 : action = Action to change. Must be valid before
4503 : * the call, actions not specified will be
4504 : * left unchanged.
4505 : *
4506 : * Returns : JB_ERR_OK on success
4507 : * JB_ERR_MEMORY on out-of-memory
4508 : *
4509 : *********************************************************************/
4510 619 : static jb_err actions_from_radio(const struct map * parameters,
4511 : struct action_spec *action)
4512 : {
4513 : const char * param;
4514 : char * param_dup;
4515 : char ch;
4516 : const char * js_name;
4517 619 : jb_err err = JB_ERR_OK;
4518 :
4519 619 : assert(parameters);
4520 619 : assert(action);
4521 :
4522 : /* Statics are generally a potential race condition,
4523 : * but in this case we're safe and don't need semaphores.
4524 : * Be careful if you modify this function.
4525 : * - Jon
4526 : * The js_name_arr's are never free()d, but this is no
4527 : * problem, since they will only be created once and
4528 : * used by all threads thereafter. -oes
4529 : */
4530 :
4531 : #define JAVASCRIPTIFY(dest_var, string) \
4532 : { \
4533 : static int first_time = 1; \
4534 : static char *js_name_arr; \
4535 : if (first_time) \
4536 : { \
4537 : js_name_arr = strdup(string); \
4538 : javascriptify(js_name_arr); \
4539 : } \
4540 : dest_var = js_name_arr; \
4541 : first_time = 0; \
4542 : } \
4543 :
4544 : #define DEFINE_ACTION_BOOL(name, bit) \
4545 : JAVASCRIPTIFY(js_name, name); \
4546 : ch = get_char_param(parameters, js_name); \
4547 : if (ch == 'Y') \
4548 : { \
4549 : action->add |= bit; \
4550 : action->mask |= bit; \
4551 : } \
4552 : else if (ch == 'N') \
4553 : { \
4554 : action->add &= ~bit; \
4555 : action->mask &= ~bit; \
4556 : } \
4557 : else if (ch == 'X') \
4558 : { \
4559 : action->add &= ~bit; \
4560 : action->mask |= bit; \
4561 : } \
4562 :
4563 : #define DEFINE_ACTION_STRING(name, bit, index) \
4564 : JAVASCRIPTIFY(js_name, name); \
4565 : ch = get_char_param(parameters, js_name); \
4566 : if (ch == 'Y') \
4567 : { \
4568 : param = NULL; \
4569 : JAVASCRIPTIFY(js_name, name "-mode"); \
4570 : if (!err) err = get_string_param(parameters, js_name, ¶m); \
4571 : if ((param == NULL) || (0 == strcmp(param, "CUSTOM"))) \
4572 : { \
4573 : JAVASCRIPTIFY(js_name, name "-param"); \
4574 : if (!err) err = get_string_param(parameters, js_name, ¶m); \
4575 : } \
4576 : if (param != NULL) \
4577 : { \
4578 : if (NULL == (param_dup = strdup(param))) \
4579 : { \
4580 : return JB_ERR_MEMORY; \
4581 : } \
4582 : freez(action->string[index]); \
4583 : action->add |= bit; \
4584 : action->mask |= bit; \
4585 : action->string[index] = param_dup; \
4586 : } \
4587 : } \
4588 : else if (ch == 'N') \
4589 : { \
4590 : if (action->add & bit) \
4591 : { \
4592 : freez(action->string[index]); \
4593 : } \
4594 : action->add &= ~bit; \
4595 : action->mask &= ~bit; \
4596 : } \
4597 : else if (ch == 'X') \
4598 : { \
4599 : if (action->add & bit) \
4600 : { \
4601 : freez(action->string[index]); \
4602 : } \
4603 : action->add &= ~bit; \
4604 : action->mask |= bit; \
4605 : } \
4606 :
4607 : #define DEFINE_ACTION_MULTI(name, index) \
4608 : JAVASCRIPTIFY(js_name, name); \
4609 : ch = get_char_param(parameters, js_name); \
4610 : if (ch == 'Y') \
4611 : { \
4612 : /* FIXME */ \
4613 : } \
4614 : else if (ch == 'N') \
4615 : { \
4616 : list_remove_all(action->multi_add[index]); \
4617 : list_remove_all(action->multi_remove[index]); \
4618 : action->multi_remove_all[index] = 1; \
4619 : } \
4620 : else if (ch == 'X') \
4621 : { \
4622 : list_remove_all(action->multi_add[index]); \
4623 : list_remove_all(action->multi_remove[index]); \
4624 : action->multi_remove_all[index] = 0; \
4625 : } \
4626 :
4627 : #define DEFINE_ACTION_ALIAS 0 /* No aliases for URL parsing */
4628 :
4629 : #include "actionlist.h"
4630 :
4631 : #undef DEFINE_ACTION_MULTI
4632 : #undef DEFINE_ACTION_STRING
4633 : #undef DEFINE_ACTION_BOOL
4634 : #undef DEFINE_ACTION_ALIAS
4635 : #undef JAVASCRIPTIFY
4636 :
4637 619 : return err;
4638 : }
4639 : #endif /* def FEATURE_CGI_EDIT_ACTIONS */
4640 :
4641 :
4642 : #ifdef FEATURE_TOGGLE
4643 : /*********************************************************************
4644 : *
4645 : * Function : cgi_toggle
4646 : *
4647 : * Description : CGI function that adds a new empty section to
4648 : * an actions file.
4649 : *
4650 : * Parameters :
4651 : * 1 : csp = Current client state (buffers, headers, etc...)
4652 : * 2 : rsp = http_response data structure for output
4653 : * 3 : parameters = map of cgi parameters
4654 : *
4655 : * CGI Parameters :
4656 : * set : If present, how to change toggle setting:
4657 : * "enable", "disable", "toggle", or none (default).
4658 : * mini : If present, use mini reply template.
4659 : *
4660 : * Returns : JB_ERR_OK on success
4661 : * JB_ERR_MEMORY on out-of-memory
4662 : *
4663 : *********************************************************************/
4664 384 : jb_err cgi_toggle(struct client_state *csp,
4665 : struct http_response *rsp,
4666 : const struct map *parameters)
4667 : {
4668 : struct map *exports;
4669 : char mode;
4670 : const char *template_name;
4671 :
4672 384 : assert(csp);
4673 384 : assert(rsp);
4674 384 : assert(parameters);
4675 :
4676 384 : if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_TOGGLE))
4677 : {
4678 0 : return cgi_error_disabled(csp, rsp);
4679 : }
4680 :
4681 384 : mode = get_char_param(parameters, "set");
4682 :
4683 384 : if (mode == 'E')
4684 : {
4685 : /* Enable */
4686 1 : global_toggle_state = 1;
4687 : }
4688 383 : else if (mode == 'D')
4689 : {
4690 : /* Disable */
4691 5 : global_toggle_state = 0;
4692 : }
4693 378 : else if (mode == 'T')
4694 : {
4695 : /* Toggle */
4696 127 : global_toggle_state = !global_toggle_state;
4697 : }
4698 :
4699 384 : log_error(LOG_LEVEL_INFO, "Now toggled %s.", global_toggle_state ? "ON" : "OFF");
4700 :
4701 384 : if (NULL == (exports = default_exports(csp, "toggle")))
4702 : {
4703 0 : return JB_ERR_MEMORY;
4704 : }
4705 :
4706 768 : template_name = (get_char_param(parameters, "mini")
4707 : ? "toggle-mini"
4708 384 : : "toggle");
4709 :
4710 384 : return template_fill_for_cgi(csp, template_name, exports, rsp);
4711 : }
4712 : #endif /* def FEATURE_TOGGLE */
4713 :
4714 :
4715 : /*
4716 : Local Variables:
4717 : tab-width: 3
4718 : end:
4719 : */
|