1 const char cgiedit_rcs[] = "$Id: cgiedit.c,v 1.9 2002/01/17 20:56:22 jongfoster Exp $";
2 /*********************************************************************
4 * File : $Source: /cvsroot/ijbswa/current/cgiedit.c,v $
6 * Purpose : CGI-based actionsfile editor.
8 * Functions declared include: cgi_edit_*
10 * NOTE: The CGIs in this file use parameter names
11 * such as "f" and "s" which are really *BAD* choices.
12 * However, I'm trying to save bytes in the
13 * edit-actions-list HTML page - the standard actions
14 * file generated a 550kbyte page, which is ridiculous.
16 * Stick to the short names in this file for consistency.
18 * Copyright : Written by and Copyright (C) 2001 the SourceForge
19 * IJBSWA team. http://ijbswa.sourceforge.net
21 * Based on the Internet Junkbuster originally written
22 * by and Copyright (C) 1997 Anonymous Coders and
23 * Junkbusters Corporation. http://www.junkbusters.com
25 * This program is free software; you can redistribute it
26 * and/or modify it under the terms of the GNU General
27 * Public License as published by the Free Software
28 * Foundation; either version 2 of the License, or (at
29 * your option) any later version.
31 * This program is distributed in the hope that it will
32 * be useful, but WITHOUT ANY WARRANTY; without even the
33 * implied warranty of MERCHANTABILITY or FITNESS FOR A
34 * PARTICULAR PURPOSE. See the GNU General Public
35 * License for more details.
37 * The GNU General Public License should be included with
38 * this file. If not, you can view it at
39 * http://www.gnu.org/copyleft/gpl.html
40 * or write to the Free Software Foundation, Inc., 59
41 * Temple Place - Suite 330, Boston, MA 02111-1307, USA.
45 * Revision 1.9 2002/01/17 20:56:22 jongfoster
46 * Replacing hard references to the URL of the config interface
47 * with #defines from project.h
49 * Revision 1.8 2001/11/30 23:35:51 jongfoster
50 * Renaming actionsfile to ijb.action
52 * Revision 1.7 2001/11/13 00:28:24 jongfoster
53 * - Renaming parameters from edit-actions-for-url so that they only
54 * contain legal JavaScript characters. If we wanted to write
55 * JavaScript that worked with Netscape 4, this is nessacery.
56 * (Note that at the moment the JavaScript doesn't actually work
57 * with Netscape 4, but now this is purely a template issue, not
58 * one affecting code).
59 * - Adding new CGIs for use by non-JavaScript browsers:
60 * edit-actions-url-form
61 * edit-actions-add-url-form
62 * edit-actions-remove-url-form
65 * Revision 1.6 2001/10/29 03:48:09 david__schmidt
66 * OS/2 native needed a snprintf() routine. Added one to miscutil, brackedted
67 * by and __OS2__ ifdef.
69 * Revision 1.5 2001/10/25 03:40:48 david__schmidt
70 * Change in porting tactics: OS/2's EMX porting layer doesn't allow multiple
71 * threads to call select() simultaneously. So, it's time to do a real, live,
72 * native OS/2 port. See defines for __EMX__ (the porting layer) vs. __OS2__
73 * (native). Both versions will work, but using __OS2__ offers multi-threading.
75 * Revision 1.4 2001/10/23 21:48:19 jongfoster
76 * Cleaning up error handling in CGI functions - they now send back
77 * a HTML error page and should never cause a FATAL error. (Fixes one
78 * potential source of "denial of service" attacks).
80 * CGI actions file editor that works and is actually useful.
82 * Ability to toggle JunkBuster remotely using a CGI call.
84 * You can turn off both the above features in the main configuration
85 * file, e.g. if you are running a multi-user proxy.
87 * Revision 1.3 2001/10/14 22:12:49 jongfoster
88 * New version of CGI-based actionsfile editor.
89 * Major changes, including:
90 * - Completely new file parser and file output routines
91 * - edit-actions CGI renamed edit-actions-for-url
92 * - All CGIs now need a filename parameter, except for...
93 * - New CGI edit-actions which doesn't need a filename,
94 * to allow you to start the editor up.
95 * - edit-actions-submit now works, and now automatically
96 * redirects you back to the main edit-actions-list handler.
98 * Revision 1.2 2001/09/16 17:05:14 jongfoster
99 * Removing unused #include showarg.h
101 * Revision 1.1 2001/09/16 15:47:37 jongfoster
102 * First version of CGI-based edit interface. This is very much a
103 * work-in-progress, and you can't actually use it to edit anything
104 * yet. You must #define FEATURE_CGI_EDIT_ACTIONS for these changes
105 * to have any effect.
108 **********************************************************************/
114 * FIXME: Following includes copied from cgi.c - which are actually needed?
119 #include <sys/types.h>
124 #include <sys/stat.h>
127 #define snprintf _snprintf
128 #endif /* def _WIN32 */
133 #include "cgisimple.h"
137 #include "miscutil.h"
141 /* loadcfg.h is for g_bToggleIJB only */
142 #include "urlmatch.h"
144 const char cgiedit_h_rcs[] = CGIEDIT_H_VERSION;
147 #ifdef FEATURE_CGI_EDIT_ACTIONS
151 struct file_line * next;
159 struct action_spec action[1];
168 /* Add more data types here... e.g.
171 struct url_spec url[1];
175 struct action_spec action[1];
184 #define FILE_LINE_UNPROCESSED 1
185 #define FILE_LINE_BLANK 2
186 #define FILE_LINE_ALIAS_HEADER 3
187 #define FILE_LINE_ALIAS_ENTRY 4
188 #define FILE_LINE_ACTION 5
189 #define FILE_LINE_URL 6
190 #define FILE_LINE_SETTINGS_HEADER 7
191 #define FILE_LINE_SETTINGS_ENTRY 8
192 #define FILE_LINE_DESCRIPTION_HEADER 9
193 #define FILE_LINE_DESCRIPTION_ENTRY 10
198 struct file_line * lines;
199 const char * filename; /* Full pathname - e.g. "/etc/junkbuster/wibble.action" */
200 const char * identifier; /* Filename stub - e.g. "wibble". Use for CGI param. */
201 /* Pre-encoded with url_encode() for ease of use. */
202 const char * version_str; /* Last modification time, as a string. For CGI param */
203 /* Can be used in URL without using url_param(). */
204 unsigned version; /* Last modification time - prevents chaos with
205 * the browser's "back" button. Note that this is a
206 * time_t cast to an unsigned. When comparing, always
207 * cast the time_t to an unsigned, and *NOT* vice-versa.
208 * This may lose the top few bits, but they're not
209 * significant anyway.
211 int newline; /* Newline convention - one of the NEWLINE_xxx constants.
212 * Note that changing this after the file has been
213 * read in will cause a mess.
215 struct file_line * parse_error; /* On parse error, this is the offending line. */
216 const char * parse_error_text; /* On parse error, this is the problem.
217 * (Statically allocated) */
220 /* FIXME: Following non-static functions should be prototyped in .h or made static */
222 /* Functions to read and write arbitrary config files */
223 jb_err edit_read_file(struct client_state *csp,
224 const struct map *parameters,
227 struct editable_file **pfile);
228 jb_err edit_write_file(struct editable_file * file);
229 void edit_free_file(struct editable_file * file);
231 /* Functions to read and write actions files */
232 jb_err edit_parse_actions_file(struct editable_file * file);
233 jb_err edit_read_actions_file(struct client_state *csp,
234 struct http_response *rsp,
235 const struct map *parameters,
237 struct editable_file **pfile);
240 jb_err cgi_error_modified(struct client_state *csp,
241 struct http_response *rsp,
242 const char *filename);
243 jb_err cgi_error_parse(struct client_state *csp,
244 struct http_response *rsp,
245 struct editable_file *file);
246 jb_err cgi_error_file(struct client_state *csp,
247 struct http_response *rsp,
248 const char *filename);
249 jb_err cgi_error_disabled(struct client_state *csp,
250 struct http_response *rsp);
252 /* Internal arbitrary config file support functions */
253 static jb_err edit_read_file_lines(FILE *fp, struct file_line ** pfile, int *newline);
254 static void edit_free_file_lines(struct file_line * first_line);
256 /* Internal actions file support functions */
257 static int match_actions_file_header_line(const char * line, const char * name);
258 static jb_err split_line_on_equals(const char * line, char ** pname, char ** pvalue);
260 /* Internal parameter parsing functions */
261 static jb_err get_file_name_param(struct client_state *csp,
262 const struct map *parameters,
263 const char *param_name,
266 const char **pparam);
267 static jb_err get_number_param(struct client_state *csp,
268 const struct map *parameters,
271 static jb_err get_url_spec_param(struct client_state *csp,
272 const struct map *parameters,
276 /* Internal actionsfile <==> HTML conversion functions */
277 static jb_err map_radio(struct map * exports,
278 const char * optionname,
281 static jb_err actions_to_radio(struct map * exports,
282 const struct action_spec *action);
283 static jb_err actions_from_radio(const struct map * parameters,
284 struct action_spec *action);
287 static jb_err map_copy_parameter_html(struct map *out,
288 const struct map *in,
290 static jb_err map_copy_parameter_url(struct map *out,
291 const struct map *in,
295 /*********************************************************************
297 * Function : map_copy_parameter_html
299 * Description : Copy a CGI parameter from one map to another, HTML
303 * 1 : out = target map
304 * 2 : in = source map
305 * 3 : name = name of cgi parameter to copy
307 * Returns : JB_ERR_OK on success
308 * JB_ERR_MEMORY on out-of-memory
309 * JB_ERR_CGI_PARAMS if the parameter doesn't exist
312 *********************************************************************/
313 static jb_err map_copy_parameter_html(struct map *out,
314 const struct map *in,
324 value = lookup(in, name);
325 err = map(out, name, 1, html_encode(value), 0);
332 else if (*value == '\0')
334 return JB_ERR_CGI_PARAMS;
343 /*********************************************************************
345 * Function : map_copy_parameter_html
347 * Description : Copy a CGI parameter from one map to another, URL
351 * 1 : out = target map
352 * 2 : in = source map
353 * 3 : name = name of cgi parameter to copy
355 * Returns : JB_ERR_OK on success
356 * JB_ERR_MEMORY on out-of-memory
357 * JB_ERR_CGI_PARAMS if the parameter doesn't exist
360 *********************************************************************/
361 static jb_err map_copy_parameter_url(struct map *out,
362 const struct map *in,
372 value = lookup(in, name);
373 err = map(out, name, 1, url_encode(value), 0);
380 else if (*value == '\0')
382 return JB_ERR_CGI_PARAMS;
391 /*********************************************************************
393 * Function : cgi_edit_actions_url_form
395 * Description : CGI function that displays a form for
399 * 1 : csp = Current client state (buffers, headers, etc...)
400 * 2 : rsp = http_response data structure for output
401 * 3 : parameters = map of cgi parameters
404 * f : (filename) Identifies the file to edit
405 * v : (version) File's last-modified time
406 * p : (pattern) Line number of pattern to edit
408 * Returns : JB_ERR_OK on success
409 * JB_ERR_MEMORY on out-of-memory
410 * JB_ERR_CGI_PARAMS if the CGI parameters are not
411 * specified or not valid.
413 *********************************************************************/
414 jb_err cgi_edit_actions_url_form(struct client_state *csp,
415 struct http_response *rsp,
416 const struct map *parameters)
418 struct map * exports;
420 struct editable_file * file;
421 struct file_line * cur_line;
422 unsigned line_number;
429 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
431 return cgi_error_disabled(csp, rsp);
434 err = get_number_param(csp, parameters, "p", &patternid);
440 err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
443 /* No filename specified, can't read file, modified, or out of memory. */
444 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
447 cur_line = file->lines;
449 for (line_number = 1; (cur_line != NULL) && (line_number < patternid); line_number++)
451 cur_line = cur_line->next;
454 if ( (cur_line == NULL)
455 || (line_number != patternid)
457 || (cur_line->type != FILE_LINE_URL))
459 /* Invalid "patternid" parameter */
460 edit_free_file(file);
461 return JB_ERR_CGI_PARAMS;
464 if (NULL == (exports = default_exports(csp, NULL)))
466 edit_free_file(file);
467 return JB_ERR_MEMORY;
470 err = map(exports, "f", 1, file->identifier, 1);
471 if (!err) err = map(exports, "v", 1, file->version_str, 1);
472 if (!err) err = map(exports, "p", 1, url_encode(lookup(parameters, "p")), 0);
473 if (!err) err = map(exports, "u", 1, html_encode(cur_line->unprocessed), 0);
475 edit_free_file(file);
483 return template_fill_for_cgi(csp, "edit-actions-url-form", exports, rsp);
487 /*********************************************************************
489 * Function : cgi_edit_actions_add_url_form
491 * Description : CGI function that displays a form for
495 * 1 : csp = Current client state (buffers, headers, etc...)
496 * 2 : rsp = http_response data structure for output
497 * 3 : parameters = map of cgi parameters
500 * f : (filename) Identifies the file to edit
501 * v : (version) File's last-modified time
502 * s : (section) Line number of section to edit
504 * Returns : JB_ERR_OK on success
505 * JB_ERR_MEMORY on out-of-memory
506 * JB_ERR_CGI_PARAMS if the CGI parameters are not
507 * specified or not valid.
509 *********************************************************************/
510 jb_err cgi_edit_actions_add_url_form(struct client_state *csp,
511 struct http_response *rsp,
512 const struct map *parameters)
521 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
523 return cgi_error_disabled(csp, rsp);
526 if (NULL == (exports = default_exports(csp, NULL)))
528 return JB_ERR_MEMORY;
531 err = map_copy_parameter_html(exports, parameters, "f");
532 if (!err) err = map_copy_parameter_html(exports, parameters, "v");
533 if (!err) err = map_copy_parameter_html(exports, parameters, "s");
541 return template_fill_for_cgi(csp, "edit-actions-add-url-form", exports, rsp);
545 /*********************************************************************
547 * Function : cgi_edit_actions_remove_url_form
549 * Description : CGI function that displays a form for
553 * 1 : csp = Current client state (buffers, headers, etc...)
554 * 2 : rsp = http_response data structure for output
555 * 3 : parameters = map of cgi parameters
558 * f : (filename) Identifies the file to edit
559 * v : (version) File's last-modified time
560 * p : (pattern) Line number of pattern to edit
562 * Returns : JB_ERR_OK on success
563 * JB_ERR_MEMORY on out-of-memory
564 * JB_ERR_CGI_PARAMS if the CGI parameters are not
565 * specified or not valid.
567 *********************************************************************/
568 jb_err cgi_edit_actions_remove_url_form(struct client_state *csp,
569 struct http_response *rsp,
570 const struct map *parameters)
572 struct map * exports;
574 struct editable_file * file;
575 struct file_line * cur_line;
576 unsigned line_number;
583 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
585 return cgi_error_disabled(csp, rsp);
588 err = get_number_param(csp, parameters, "p", &patternid);
594 err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
597 /* No filename specified, can't read file, modified, or out of memory. */
598 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
601 cur_line = file->lines;
603 for (line_number = 1; (cur_line != NULL) && (line_number < patternid); line_number++)
605 cur_line = cur_line->next;
608 if ( (cur_line == NULL)
609 || (line_number != patternid)
611 || (cur_line->type != FILE_LINE_URL))
613 /* Invalid "patternid" parameter */
614 edit_free_file(file);
615 return JB_ERR_CGI_PARAMS;
618 if (NULL == (exports = default_exports(csp, NULL)))
620 edit_free_file(file);
621 return JB_ERR_MEMORY;
624 err = map(exports, "f", 1, file->identifier, 1);
625 if (!err) err = map(exports, "v", 1, file->version_str, 1);
626 if (!err) err = map(exports, "s", 1, url_encode(lookup(parameters, "s")), 0);
627 if (!err) err = map(exports, "u", 1, html_encode(cur_line->unprocessed), 0);
629 edit_free_file(file);
637 return template_fill_for_cgi(csp, "edit-actions-remove-url-form", exports, rsp);
641 /*********************************************************************
643 * Function : edit_write_file
645 * Description : Write a complete file to disk.
648 * 1 : filename = File to write to.
649 * 2 : file = Data structure to write.
651 * Returns : JB_ERR_OK on success
652 * JB_ERR_FILE on error writing to file.
653 * JB_ERR_MEMORY on out of memory
655 *********************************************************************/
656 jb_err edit_write_file(struct editable_file * file)
659 struct file_line * cur_line;
660 struct stat statbuf[1];
661 char version_buf[22]; /* 22 = ceil(log10(2^64)) + 2 = max number of
662 digits in time_t, assuming this is a 64-bit
663 machine, plus null terminator, plus one
667 assert(file->filename);
669 if (NULL == (fp = fopen(file->filename, "wt")))
674 cur_line = file->lines;
675 while (cur_line != NULL)
679 if (fputs(cur_line->raw, fp) < 0)
687 if (cur_line->prefix)
689 if (fputs(cur_line->prefix, fp) < 0)
695 if (cur_line->unprocessed)
697 /* This should be a single line - sanity check. */
698 assert(NULL == strchr(cur_line->unprocessed, '\r'));
699 assert(NULL == strchr(cur_line->unprocessed, '\n'));
701 if (NULL != strchr(cur_line->unprocessed, '#'))
703 /* Must quote '#' characters */
710 /* Count number of # characters, so we know length of output string */
711 src = cur_line->unprocessed;
712 while (NULL != (src = strchr(src, '#')))
719 /* Allocate new memory for string */
720 len = strlen(cur_line->unprocessed);
721 if (NULL == (str = malloc(len + 1 + numhash)))
723 /* Uh oh, just trashed file! */
725 return JB_ERR_MEMORY;
728 /* Loop through string from end */
729 src = cur_line->unprocessed + len;
730 dest = str + len + numhash;
731 for ( ; len >= 0; len--)
733 if ((*dest-- = *src--) == '#')
737 assert(numhash >= 0);
740 assert(numhash == 0);
741 assert(src + 1 == cur_line->unprocessed);
742 assert(dest + 1 == str);
744 if (fputs(str, fp) < 0)
755 /* Can write without quoting '#' characters. */
756 if (fputs(cur_line->unprocessed, fp) < 0)
762 if (fputs(NEWLINE(file->newline), fp) < 0)
770 /* FIXME: Write data from file->data->whatever */
774 cur_line = cur_line->next;
780 /* Update the version stamp in the file structure, since we just
781 * wrote to the file & changed it's date.
783 if (stat(file->filename, statbuf) < 0)
785 /* Error, probably file not found. */
788 file->version = (unsigned)statbuf->st_mtime;
790 /* Correct file->version_str */
791 freez(file->version_str);
792 snprintf(version_buf, 22, "%u", file->version);
793 version_buf[21] = '\0';
794 file->version_str = strdup(version_buf);
795 if (version_buf == NULL)
797 return JB_ERR_MEMORY;
804 /*********************************************************************
806 * Function : edit_free_file
808 * Description : Free a complete file in memory.
811 * 1 : file = Data structure to free.
815 *********************************************************************/
816 void edit_free_file(struct editable_file * file)
820 /* Silently ignore NULL pointer */
824 edit_free_file_lines(file->lines);
825 freez(file->filename);
826 freez(file->identifier);
827 freez(file->version_str);
829 file->parse_error_text = NULL; /* Statically allocated */
830 file->parse_error = NULL;
836 /*********************************************************************
838 * Function : edit_free_file
840 * Description : Free an entire linked list of file lines.
843 * 1 : first_line = Data structure to free.
847 *********************************************************************/
848 static void edit_free_file_lines(struct file_line * first_line)
850 struct file_line * next_line;
852 while (first_line != NULL)
854 next_line = first_line->next;
855 first_line->next = NULL;
856 freez(first_line->raw);
857 freez(first_line->prefix);
858 freez(first_line->unprocessed);
859 switch(first_line->type)
861 case 0: /* special case if memory zeroed */
862 case FILE_LINE_UNPROCESSED:
863 case FILE_LINE_BLANK:
864 case FILE_LINE_ALIAS_HEADER:
865 case FILE_LINE_SETTINGS_HEADER:
866 case FILE_LINE_DESCRIPTION_HEADER:
867 case FILE_LINE_DESCRIPTION_ENTRY:
868 case FILE_LINE_ALIAS_ENTRY:
870 /* No data is stored for these */
873 case FILE_LINE_ACTION:
874 free_action(first_line->data.action);
877 case FILE_LINE_SETTINGS_ENTRY:
878 freez(first_line->data.setting.name);
879 freez(first_line->data.setting.svalue);
882 /* Should never happen */
886 first_line->type = 0; /* paranoia */
888 first_line = next_line;
893 /*********************************************************************
895 * Function : match_actions_file_header_line
897 * Description : Match an actions file {{header}} line
900 * 1 : line - String from file
901 * 2 : name - Header to match against
903 * Returns : 0 iff they match.
905 *********************************************************************/
906 static int match_actions_file_header_line(const char * line, const char * name)
914 if ((line[0] != '{') || (line[1] != '{'))
920 /* Look for optional whitespace */
921 while ( (*line == ' ') || (*line == '\t') )
926 /* Look for the specified name (case-insensitive) */
928 if (0 != strncmpic(line, name, len))
934 /* Look for optional whitespace */
935 while ( (*line == ' ') || (*line == '\t') )
940 /* Look for "}}" and end of string*/
941 if ((line[0] != '}') || (line[1] != '}') || (line[2] != '\0'))
951 /*********************************************************************
953 * Function : match_actions_file_header_line
955 * Description : Match an actions file {{header}} line
958 * 1 : line - String from file. Must not start with
959 * whitespace (else infinite loop!)
960 * 2 : name - Destination for name
961 * 2 : name - Destination for value
963 * Returns : JB_ERR_OK on success
964 * JB_ERR_MEMORY on out-of-memory
965 * JB_ERR_PARSE if there's no "=" sign, or if there's
966 * nothing before the "=" sign (but empty
967 * values *after* the "=" sign are legal).
969 *********************************************************************/
970 static jb_err split_line_on_equals(const char * line, char ** pname, char ** pvalue)
972 const char * name_end;
973 const char * value_start;
979 assert(*line != ' ');
980 assert(*line != '\t');
985 value_start = strchr(line, '=');
986 if ((value_start == NULL) || (value_start == line))
991 name_end = value_start - 1;
993 /* Eat any whitespace before the '=' */
994 while ((*name_end == ' ') || (*name_end == '\t'))
997 * we already know we must have at least 1 non-ws char
998 * at start of buf - no need to check
1003 name_len = name_end - line + 1; /* Length excluding \0 */
1004 if (NULL == (*pname = (char *) malloc(name_len + 1)))
1006 return JB_ERR_MEMORY;
1008 strncpy(*pname, line, name_len);
1009 (*pname)[name_len] = '\0';
1011 /* Eat any the whitespace after the '=' */
1013 while ((*value_start == ' ') || (*value_start == '\t'))
1018 if (NULL == (*pvalue = strdup(value_start)))
1022 return JB_ERR_MEMORY;
1029 /*********************************************************************
1031 * Function : edit_parse_actions_file
1033 * Description : Parse an actions file in memory.
1035 * Passed linked list must have the "data" member
1036 * zeroed, and must contain valid "next" and
1037 * "unprocessed" fields. The "raw" and "prefix"
1038 * fields are ignored, and "type" is just overwritten.
1040 * Note that on error the file may have been
1044 * 1 : file = Actions file to be parsed in-place.
1046 * Returns : JB_ERR_OK on success
1047 * JB_ERR_MEMORY on out-of-memory
1048 * JB_ERR_PARSE on error
1050 *********************************************************************/
1051 jb_err edit_parse_actions_file(struct editable_file * file)
1053 struct file_line * cur_line;
1055 const char * text; /* Text from a line */
1056 char * name; /* For lines of the form name=value */
1057 char * value; /* For lines of the form name=value */
1058 struct action_alias * alias_list = NULL;
1059 jb_err err = JB_ERR_OK;
1061 /* alias_list contains the aliases defined in this file.
1062 * It might be better to use the "file_line.data" fields
1063 * in the relavent places instead.
1066 cur_line = file->lines;
1068 /* A note about blank line support: Blank lines should only
1069 * ever occur as the last line in the file. This function
1070 * is more forgiving than that - FILE_LINE_BLANK can occur
1074 /* Skip leading blanks. Should only happen if file is
1075 * empty (which is valid, but pointless).
1077 while ( (cur_line != NULL)
1078 && (cur_line->unprocessed[0] == '\0') )
1081 cur_line->type = FILE_LINE_BLANK;
1082 cur_line = cur_line->next;
1085 if ( (cur_line != NULL)
1086 && (cur_line->unprocessed[0] != '{') )
1088 /* File doesn't start with a header */
1089 file->parse_error = cur_line;
1090 file->parse_error_text = "First (non-comment) line of the file must contain a header.";
1091 return JB_ERR_PARSE;
1094 if ( (cur_line != NULL) && (0 ==
1095 match_actions_file_header_line(cur_line->unprocessed, "settings") ) )
1097 cur_line->type = FILE_LINE_SETTINGS_HEADER;
1099 cur_line = cur_line->next;
1100 while ((cur_line != NULL) && (cur_line->unprocessed[0] != '{'))
1102 if (cur_line->unprocessed[0])
1104 cur_line->type = FILE_LINE_SETTINGS_ENTRY;
1106 err = split_line_on_equals(cur_line->unprocessed,
1107 &cur_line->data.setting.name,
1108 &cur_line->data.setting.svalue);
1109 if (err == JB_ERR_MEMORY)
1113 else if (err != JB_ERR_OK)
1115 /* Line does not contain a name=value pair */
1116 file->parse_error = cur_line;
1117 file->parse_error_text = "Expected a name=value pair on this {{description}} line, but couldn't find one.";
1118 return JB_ERR_PARSE;
1123 cur_line->type = FILE_LINE_BLANK;
1125 cur_line = cur_line->next;
1129 if ( (cur_line != NULL) && (0 ==
1130 match_actions_file_header_line(cur_line->unprocessed, "description") ) )
1132 cur_line->type = FILE_LINE_DESCRIPTION_HEADER;
1134 cur_line = cur_line->next;
1135 while ((cur_line != NULL) && (cur_line->unprocessed[0] != '{'))
1137 if (cur_line->unprocessed[0])
1139 cur_line->type = FILE_LINE_DESCRIPTION_ENTRY;
1143 cur_line->type = FILE_LINE_BLANK;
1145 cur_line = cur_line->next;
1149 if ( (cur_line != NULL) && (0 ==
1150 match_actions_file_header_line(cur_line->unprocessed, "alias") ) )
1152 cur_line->type = FILE_LINE_ALIAS_HEADER;
1154 cur_line = cur_line->next;
1155 while ((cur_line != NULL) && (cur_line->unprocessed[0] != '{'))
1157 if (cur_line->unprocessed[0])
1159 /* define an alias */
1160 struct action_alias * new_alias;
1162 cur_line->type = FILE_LINE_ALIAS_ENTRY;
1164 err = split_line_on_equals(cur_line->unprocessed, &name, &value);
1165 if (err == JB_ERR_MEMORY)
1169 else if (err != JB_ERR_OK)
1171 /* Line does not contain a name=value pair */
1172 file->parse_error = cur_line;
1173 file->parse_error_text = "Expected a name=value pair on this {{alias}} line, but couldn't find one.";
1174 return JB_ERR_PARSE;
1177 if ((new_alias = zalloc(sizeof(*new_alias))) == NULL)
1182 free_alias_list(alias_list);
1183 return JB_ERR_MEMORY;
1186 err = get_actions(value, alias_list, new_alias->action);
1189 /* Invalid action or out of memory */
1193 free_alias_list(alias_list);
1194 if (err == JB_ERR_MEMORY)
1200 /* Line does not contain a name=value pair */
1201 file->parse_error = cur_line;
1202 file->parse_error_text = "This alias does not specify a valid set of actions.";
1203 return JB_ERR_PARSE;
1209 new_alias->name = name;
1212 new_alias->next = alias_list;
1213 alias_list = new_alias;
1217 cur_line->type = FILE_LINE_BLANK;
1219 cur_line = cur_line->next;
1223 /* Header done, process the main part of the file */
1224 while (cur_line != NULL)
1226 /* At this point, (cur_line->unprocessed[0] == '{') */
1227 assert(cur_line->unprocessed[0] == '{');
1228 text = cur_line->unprocessed + 1;
1229 len = strlen(text) - 1;
1230 if (text[len] != '}')
1232 /* No closing } on header */
1233 free_alias_list(alias_list);
1234 file->parse_error = cur_line;
1235 file->parse_error_text = "Headers starting with '{' must have a "
1236 "closing bracket ('}'). Headers starting with two brackets ('{{') "
1237 "must close with two brackets ('}}').";
1238 return JB_ERR_PARSE;
1243 /* An invalid {{ header. */
1244 free_alias_list(alias_list);
1245 file->parse_error = cur_line;
1246 file->parse_error_text = "Unknown or unexpected two-bracket header. "
1247 "Please remember that the system (two-bracket) headers must "
1248 "appear in the order {{settings}}, {{description}}, {{alias}}, "
1249 "and must appear before any actions (one-bracket) headers. "
1250 "Also note that system headers may not be repeated.";
1251 return JB_ERR_PARSE;
1254 while ( (*text == ' ') || (*text == '\t') )
1260 && ( (text[len - 1] == ' ')
1261 || (text[len - 1] == '\t') ) )
1266 cur_line->type = FILE_LINE_ACTION;
1268 /* Remove {} and make copy */
1269 if (NULL == (value = (char *) malloc(len + 1)))
1272 free_alias_list(alias_list);
1273 return JB_ERR_MEMORY;
1275 strncpy(value, text, len);
1279 err = get_actions(value, alias_list, cur_line->data.action);
1282 /* Invalid action or out of memory */
1284 free_alias_list(alias_list);
1285 if (err == JB_ERR_MEMORY)
1291 /* Line does not contain a name=value pair */
1292 file->parse_error = cur_line;
1293 file->parse_error_text = "This header does not specify a valid set of actions.";
1294 return JB_ERR_PARSE;
1298 /* Done with string - it was clobbered anyway */
1301 /* Process next line */
1302 cur_line = cur_line->next;
1304 /* Loop processing URL patterns */
1305 while ((cur_line != NULL) && (cur_line->unprocessed[0] != '{'))
1307 if (cur_line->unprocessed[0])
1309 /* Could parse URL here, but this isn't currently needed */
1311 cur_line->type = FILE_LINE_URL;
1315 cur_line->type = FILE_LINE_BLANK;
1317 cur_line = cur_line->next;
1319 } /* End main while(cur_line != NULL) loop */
1321 free_alias_list(alias_list);
1327 /*********************************************************************
1329 * Function : edit_read_file_lines
1331 * Description : Read all the lines of a file into memory.
1332 * Handles whitespace, comments and line continuation.
1335 * 1 : fp = File to read from. On return, this will be
1336 * at EOF but it will not have been closed.
1337 * 2 : pfile = Destination for a linked list of file_lines.
1338 * Will be set to NULL on error.
1340 * Returns : JB_ERR_OK on success
1341 * JB_ERR_MEMORY on out-of-memory
1343 *********************************************************************/
1344 jb_err edit_read_file_lines(FILE *fp, struct file_line ** pfile, int *newline)
1346 struct file_line * first_line; /* Keep for return value or to free */
1347 struct file_line * cur_line; /* Current line */
1348 struct file_line * prev_line; /* Entry with prev_line->next = cur_line */
1356 cur_line = first_line = zalloc(sizeof(struct file_line));
1357 if (cur_line == NULL)
1359 return JB_ERR_MEMORY;
1362 cur_line->type = FILE_LINE_UNPROCESSED;
1364 rval = edit_read_line(fp, &cur_line->raw, &cur_line->prefix, &cur_line->unprocessed, newline, NULL);
1367 /* Out of memory or empty file. */
1368 /* Note that empty file is not an error we propogate up */
1370 return ((rval == JB_ERR_FILE) ? JB_ERR_OK : rval);
1375 prev_line = cur_line;
1376 cur_line = prev_line->next = zalloc(sizeof(struct file_line));
1377 if (cur_line == NULL)
1380 edit_free_file_lines(first_line);
1381 return JB_ERR_MEMORY;
1384 cur_line->type = FILE_LINE_UNPROCESSED;
1386 rval = edit_read_line(fp, &cur_line->raw, &cur_line->prefix, &cur_line->unprocessed, newline, NULL);
1387 if ((rval != JB_ERR_OK) && (rval != JB_ERR_FILE))
1390 edit_free_file_lines(first_line);
1391 return JB_ERR_MEMORY;
1395 while (rval != JB_ERR_FILE);
1399 /* We allocated one too many - free it */
1400 prev_line->next = NULL;
1403 *pfile = first_line;
1408 /*********************************************************************
1410 * Function : edit_read_file
1412 * Description : Read a complete file into memory.
1413 * Handles CGI parameter parsing. If requested, also
1414 * checks the file's modification timestamp.
1417 * 1 : csp = Current client state (buffers, headers, etc...)
1418 * 2 : parameters = map of cgi parameters.
1419 * 3 : require_version = true to check "ver" parameter.
1420 * 4 : suffix = File extension, e.g. ".action".
1421 * 5 : pfile = Destination for the file. Will be set
1425 * filename : The name of the file to read, without the
1426 * path or ".action" extension.
1427 * ver : (Only if require_version is nonzero)
1428 * Timestamp of the actions file. If wrong, this
1429 * function fails with JB_ERR_MODIFIED.
1431 * Returns : JB_ERR_OK on success
1432 * JB_ERR_MEMORY on out-of-memory
1433 * JB_ERR_CGI_PARAMS if "filename" was not specified
1435 * JB_ERR_FILE if the file cannot be opened or
1437 * JB_ERR_MODIFIED if version checking was requested and
1438 * failed - the file was modified outside
1439 * of this CGI editor instance.
1441 *********************************************************************/
1442 jb_err edit_read_file(struct client_state *csp,
1443 const struct map *parameters,
1444 int require_version,
1446 struct editable_file **pfile)
1448 struct file_line * lines;
1452 const char * identifier;
1453 struct editable_file * file;
1454 unsigned version = 0;
1455 struct stat statbuf[1];
1456 char version_buf[22];
1457 int newline = NEWLINE_UNKNOWN;
1465 err = get_file_name_param(csp, parameters, "f", suffix,
1466 &filename, &identifier);
1472 if (stat(filename, statbuf) < 0)
1474 /* Error, probably file not found. */
1478 version = (unsigned) statbuf->st_mtime;
1480 if (require_version)
1482 unsigned specified_version;
1483 err = get_number_param(csp, parameters, "v", &specified_version);
1490 if (version != specified_version)
1492 return JB_ERR_MODIFIED;
1496 if (NULL == (fp = fopen(filename,"rt")))
1502 err = edit_read_file_lines(fp, &lines, &newline);
1512 file = (struct editable_file *) zalloc(sizeof(*file));
1516 edit_free_file_lines(lines);
1520 file->lines = lines;
1521 file->newline = newline;
1522 file->filename = filename;
1523 file->version = version;
1524 file->identifier = url_encode(identifier);
1526 if (file->identifier == NULL)
1528 edit_free_file(file);
1529 return JB_ERR_MEMORY;
1532 /* Correct file->version_str */
1533 freez(file->version_str);
1534 snprintf(version_buf, 22, "%u", file->version);
1535 version_buf[21] = '\0';
1536 file->version_str = strdup(version_buf);
1537 if (version_buf == NULL)
1539 edit_free_file(file);
1540 return JB_ERR_MEMORY;
1548 /*********************************************************************
1550 * Function : edit_read_actions_file
1552 * Description : Read a complete actions file into memory.
1553 * Handles CGI parameter parsing. If requested, also
1554 * checks the file's modification timestamp.
1556 * If this function detects an error in the categories
1557 * JB_ERR_FILE, JB_ERR_MODIFIED, or JB_ERR_PARSE,
1558 * then it handles it by filling in the specified
1559 * response structure and returning JB_ERR_FILE.
1562 * 1 : csp = Current client state (buffers, headers, etc...)
1563 * 2 : rsp = HTTP response. Only filled in on error.
1564 * 2 : parameters = map of cgi parameters.
1565 * 3 : require_version = true to check "ver" parameter.
1566 * 4 : pfile = Destination for the file. Will be set
1570 * filename : The name of the actions file to read, without the
1571 * path or ".action" extension.
1572 * ver : (Only if require_version is nonzero)
1573 * Timestamp of the actions file. If wrong, this
1574 * function fails with JB_ERR_MODIFIED.
1576 * Returns : JB_ERR_OK on success
1577 * JB_ERR_MEMORY on out-of-memory
1578 * JB_ERR_CGI_PARAMS if "filename" was not specified
1580 * JB_ERR_FILE if the file does not contain valid data,
1581 * or if file cannot be opened or
1582 * contains no data, or if version
1583 * checking was requested and failed.
1585 *********************************************************************/
1586 jb_err edit_read_actions_file(struct client_state *csp,
1587 struct http_response *rsp,
1588 const struct map *parameters,
1589 int require_version,
1590 struct editable_file **pfile)
1593 struct editable_file *file;
1601 err = edit_read_file(csp, parameters, require_version, ".action", &file);
1604 /* Try to handle if possible */
1605 if (err == JB_ERR_FILE)
1607 err = cgi_error_file(csp, rsp, lookup(parameters, "f"));
1609 else if (err == JB_ERR_MODIFIED)
1611 err = cgi_error_modified(csp, rsp, lookup(parameters, "f"));
1613 if (err == JB_ERR_OK)
1616 * Signal to higher-level CGI code that there was a problem but we
1617 * handled it, they should just return JB_ERR_OK.
1624 err = edit_parse_actions_file(file);
1627 if (err == JB_ERR_PARSE)
1629 err = cgi_error_parse(csp, rsp, file);
1630 if (err == JB_ERR_OK)
1633 * Signal to higher-level CGI code that there was a problem but we
1634 * handled it, they should just return JB_ERR_OK.
1639 edit_free_file(file);
1648 /*********************************************************************
1650 * Function : get_file_name_param
1652 * Description : Get the name of the file to edit from the parameters
1653 * passed to a CGI function. This function handles
1654 * security checks such as blocking urls containing
1655 * "/" or ".", prepending the config file directory,
1656 * and adding the specified suffix.
1658 * (This is an essential security check, otherwise
1659 * users may be able to pass "../../../etc/passwd"
1660 * and overwrite the password file [linux], "prn:"
1661 * and print random data [Windows], etc...)
1663 * This function only allows filenames contining the
1664 * characters '-', '_', 'A'-'Z', 'a'-'z', and '0'-'9'.
1665 * That's probably too restrictive but at least it's
1669 * 1 : csp = Current client state (buffers, headers, etc...)
1670 * 2 : parameters = map of cgi parameters
1671 * 3 : param_name = The name of the parameter to read
1672 * 4 : suffix = File extension, e.g. ".actions"
1673 * 5 : pfilename = destination for full filename. Caller
1674 * free()s. Set to NULL on error.
1675 * 6 : pparam = destination for partial filename,
1676 * suitable for use in another URL. Allocated as part
1677 * of the map "parameters", so don't free it.
1678 * Set to NULL if not specified.
1680 * Returns : JB_ERR_OK on success
1681 * JB_ERR_MEMORY on out-of-memory
1682 * JB_ERR_CGI_PARAMS if "filename" was not specified
1685 *********************************************************************/
1686 static jb_err get_file_name_param(struct client_state *csp,
1687 const struct map *parameters,
1688 const char *param_name,
1691 const char **pparam)
1709 param = lookup(parameters, param_name);
1712 return JB_ERR_CGI_PARAMS;
1717 len = strlen(param);
1718 if (len >= FILENAME_MAX)
1721 return JB_ERR_CGI_PARAMS;
1724 /* Check every character to see if it's legal */
1726 while ((ch = *s++) != '\0')
1728 if ( ((ch < 'A') || (ch > 'Z'))
1729 && ((ch < 'a') || (ch > 'z'))
1730 && ((ch < '0') || (ch > '9'))
1734 /* Probable hack attempt. */
1735 return JB_ERR_CGI_PARAMS;
1739 /* Append extension */
1740 name = malloc(len + strlen(suffix) + 1);
1743 return JB_ERR_MEMORY;
1745 strcpy(name, param);
1746 strcpy(name + len, suffix);
1749 fullpath = make_path(csp->config->confdir, name);
1751 if (fullpath == NULL)
1753 return JB_ERR_MEMORY;
1757 *pfilename = fullpath;
1763 /*********************************************************************
1765 * Function : get_number_param
1767 * Description : Get a non-negative integer from the parameters
1768 * passed to a CGI function.
1771 * 1 : csp = Current client state (buffers, headers, etc...)
1772 * 2 : parameters = map of cgi parameters
1773 * 3 : name = Name of CGI parameter to read
1774 * 4 : pvalue = destination for value.
1775 * Set to -1 on error.
1777 * Returns : JB_ERR_OK on success
1778 * JB_ERR_MEMORY on out-of-memory
1779 * JB_ERR_CGI_PARAMS if the parameter was not specified
1782 *********************************************************************/
1783 static jb_err get_number_param(struct client_state *csp,
1784 const struct map *parameters,
1799 param = lookup(parameters, name);
1802 return JB_ERR_CGI_PARAMS;
1805 /* We don't use atoi because I want to check this carefully... */
1808 while ((ch = *param++) != '\0')
1810 if ((ch < '0') || (ch > '9'))
1812 return JB_ERR_CGI_PARAMS;
1819 * <limits.h> defines UINT_MAX
1821 * (UINT_MAX - ch) / 10 is the largest number that
1822 * can be safely multiplied by 10 then have ch added.
1824 if (value > ((UINT_MAX - (unsigned)ch) / 10U))
1826 return JB_ERR_CGI_PARAMS;
1829 value = value * 10 + ch;
1839 /*********************************************************************
1841 * Function : get_url_spec_param
1843 * Description : Get a URL pattern from the parameters
1844 * passed to a CGI function. Removes leading/trailing
1845 * spaces and validates it.
1848 * 1 : csp = Current client state (buffers, headers, etc...)
1849 * 2 : parameters = map of cgi parameters
1850 * 3 : name = Name of CGI parameter to read
1851 * 4 : pvalue = destination for value. Will be malloc()'d.
1852 * Set to NULL on error.
1854 * Returns : JB_ERR_OK on success
1855 * JB_ERR_MEMORY on out-of-memory
1856 * JB_ERR_CGI_PARAMS if the parameter was not specified
1859 *********************************************************************/
1860 static jb_err get_url_spec_param(struct client_state *csp,
1861 const struct map *parameters,
1865 const char *orig_param;
1868 struct url_spec compiled[1];
1878 orig_param = lookup(parameters, name);
1881 return JB_ERR_CGI_PARAMS;
1884 /* Copy and trim whitespace */
1885 param = strdup(orig_param);
1888 return JB_ERR_MEMORY;
1892 /* Must be non-empty, and can't allow 1st character to be '{' */
1893 if (param[0] == '\0' || param[0] == '{')
1896 return JB_ERR_CGI_PARAMS;
1899 /* Check for embedded newlines */
1900 for (s = param; *s != '\0'; s++)
1902 if ((*s == '\r') || (*s == '\n'))
1905 return JB_ERR_CGI_PARAMS;
1909 /* Check that regex is valid */
1914 return JB_ERR_MEMORY;
1916 err = create_url_spec(compiled, s);
1921 return (err == JB_ERR_MEMORY) ? JB_ERR_MEMORY : JB_ERR_CGI_PARAMS;
1923 free_url_spec(compiled);
1925 if (param[strlen(param) - 1] == '\\')
1928 * Must protect trailing '\\' from becoming line continuation character.
1929 * Two methods: 1) If it's a domain only, add a trailing '/'.
1930 * 2) For path, add the do-nothing PCRE expression (?:) to the end
1932 if (strchr(param, '/') == NULL)
1934 err = string_append(¶m, "/");
1938 err = string_append(¶m, "(?:)");
1945 /* Check that the modified regex is valid */
1950 return JB_ERR_MEMORY;
1952 err = create_url_spec(compiled, s);
1957 return (err == JB_ERR_MEMORY) ? JB_ERR_MEMORY : JB_ERR_CGI_PARAMS;
1959 free_url_spec(compiled);
1966 /*********************************************************************
1968 * Function : map_radio
1970 * Description : Map a set of radio button values. E.g. if you have
1971 * 3 radio buttons, declare them as:
1972 * <option type="radio" name="xyz" @xyz-a@>
1973 * <option type="radio" name="xyz" @xyz-b@>
1974 * <option type="radio" name="xyz" @xyz-c@>
1975 * Then map one of the @xyz-?@ variables to "checked"
1976 * and all the others to empty by calling:
1977 * map_radio(exports, "xyz", "abc", sel)
1978 * Where 'sel' is 'a', 'b', or 'c'.
1981 * 1 : exports = Exports map to modify.
1982 * 2 : optionname = name for map
1983 * 3 : values = null-terminated list of values;
1984 * 4 : value = Selected value.
1986 * CGI Parameters : None
1988 * Returns : JB_ERR_OK on success
1989 * JB_ERR_MEMORY on out-of-memory
1991 *********************************************************************/
1992 static jb_err map_radio(struct map * exports,
1993 const char * optionname,
1994 const char * values,
2006 len = strlen(optionname);
2007 buf = malloc(len + 3);
2010 return JB_ERR_MEMORY;
2013 strcpy(buf, optionname);
2018 while ((c = *values++) != '\0')
2023 if (map(exports, buf, 1, "", 1))
2026 return JB_ERR_MEMORY;
2032 if (map(exports, buf, 0, "checked", 1))
2035 return JB_ERR_MEMORY;
2042 /*********************************************************************
2044 * Function : cgi_error_modified
2046 * Description : CGI function that is called when a file is modified
2047 * outside the CGI editor.
2050 * 1 : csp = Current client state (buffers, headers, etc...)
2051 * 2 : rsp = http_response data structure for output
2052 * 3 : filename = The file that was modified.
2054 * CGI Parameters : none
2056 * Returns : JB_ERR_OK on success
2057 * JB_ERR_MEMORY on out-of-memory error.
2059 *********************************************************************/
2060 jb_err cgi_error_modified(struct client_state *csp,
2061 struct http_response *rsp,
2062 const char *filename)
2064 struct map *exports;
2071 if (NULL == (exports = default_exports(csp, NULL)))
2073 return JB_ERR_MEMORY;
2076 err = map(exports, "f", 1, html_encode(filename), 0);
2083 return template_fill_for_cgi(csp, "cgi-error-modified", exports, rsp);
2087 /*********************************************************************
2089 * Function : cgi_error_parse
2091 * Description : CGI function that is called when a file cannot
2092 * be parsed by the CGI editor.
2095 * 1 : csp = Current client state (buffers, headers, etc...)
2096 * 2 : rsp = http_response data structure for output
2097 * 3 : file = The file that was modified.
2099 * CGI Parameters : none
2101 * Returns : JB_ERR_OK on success
2102 * JB_ERR_MEMORY on out-of-memory error.
2104 *********************************************************************/
2105 jb_err cgi_error_parse(struct client_state *csp,
2106 struct http_response *rsp,
2107 struct editable_file *file)
2109 struct map *exports;
2111 struct file_line *cur_line;
2117 if (NULL == (exports = default_exports(csp, NULL)))
2119 return JB_ERR_MEMORY;
2122 err = map(exports, "f", 1, file->identifier, 1);
2123 if (!err) err = map(exports, "parse-error", 1, html_encode(file->parse_error_text), 0);
2125 cur_line = file->parse_error;
2128 if (!err) err = map(exports, "line-raw", 1, html_encode(cur_line->raw), 0);
2129 if (!err) err = map(exports, "line-data", 1, html_encode(cur_line->unprocessed), 0);
2137 return template_fill_for_cgi(csp, "cgi-error-parse", exports, rsp);
2141 /*********************************************************************
2143 * Function : cgi_error_file
2145 * Description : CGI function that is called when a file cannot be
2146 * opened by the CGI editor.
2149 * 1 : csp = Current client state (buffers, headers, etc...)
2150 * 2 : rsp = http_response data structure for output
2151 * 3 : filename = The file that was modified.
2153 * CGI Parameters : none
2155 * Returns : JB_ERR_OK on success
2156 * JB_ERR_MEMORY on out-of-memory error.
2158 *********************************************************************/
2159 jb_err cgi_error_file(struct client_state *csp,
2160 struct http_response *rsp,
2161 const char *filename)
2163 struct map *exports;
2170 if (NULL == (exports = default_exports(csp, NULL)))
2172 return JB_ERR_MEMORY;
2175 err = map(exports, "f", 1, html_encode(filename), 0);
2182 return template_fill_for_cgi(csp, "cgi-error-file", exports, rsp);
2186 /*********************************************************************
2188 * Function : cgi_error_bad_param
2190 * Description : CGI function that is called if the parameters
2191 * (query string) for a CGI were wrong.
2194 * 1 : csp = Current client state (buffers, headers, etc...)
2195 * 2 : rsp = http_response data structure for output
2197 * CGI Parameters : none
2199 * Returns : JB_ERR_OK on success
2200 * JB_ERR_MEMORY on out-of-memory error.
2202 *********************************************************************/
2203 jb_err cgi_error_disabled(struct client_state *csp,
2204 struct http_response *rsp)
2206 struct map *exports;
2211 if (NULL == (exports = default_exports(csp, NULL)))
2213 return JB_ERR_MEMORY;
2216 return template_fill_for_cgi(csp, "cgi-error-disabled", exports, rsp);
2220 /*********************************************************************
2222 * Function : cgi_edit_actions
2224 * Description : CGI function that allows the user to choose which
2225 * actions file to edit.
2228 * 1 : csp = Current client state (buffers, headers, etc...)
2229 * 2 : rsp = http_response data structure for output
2230 * 3 : parameters = map of cgi parameters
2232 * CGI Parameters : None
2234 * Returns : JB_ERR_OK on success
2235 * JB_ERR_MEMORY on out-of-memory error
2237 *********************************************************************/
2238 jb_err cgi_edit_actions(struct client_state *csp,
2239 struct http_response *rsp,
2240 const struct map *parameters)
2243 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
2245 return cgi_error_disabled(csp, rsp);
2248 /* FIXME: Incomplete */
2249 rsp->status = strdup("302 Local Redirect from Junkbuster");
2250 if (rsp->status == NULL)
2252 return JB_ERR_MEMORY;
2254 if (enlist_unique_header(rsp->headers, "Location",
2255 CGI_PREFIX "edit-actions-list?f=ijb"))
2259 return JB_ERR_MEMORY;
2266 /*********************************************************************
2268 * Function : cgi_edit_actions_list
2270 * Description : CGI function that edits the actions list.
2271 * FIXME: This function shouldn't FATAL ever.
2272 * FIXME: This function doesn't check the retval of map()
2274 * 1 : csp = Current client state (buffers, headers, etc...)
2275 * 2 : rsp = http_response data structure for output
2276 * 3 : parameters = map of cgi parameters
2278 * CGI Parameters : filename
2280 * Returns : JB_ERR_OK on success
2281 * JB_ERR_MEMORY on out-of-memory
2282 * JB_ERR_FILE if the file cannot be opened or
2284 * JB_ERR_CGI_PARAMS if "filename" was not specified
2287 *********************************************************************/
2288 jb_err cgi_edit_actions_list(struct client_state *csp,
2289 struct http_response *rsp,
2290 const struct map *parameters)
2292 char * section_template;
2293 char * url_template;
2298 struct map * exports;
2299 struct map * section_exports;
2300 struct map * url_exports;
2301 struct editable_file * file;
2302 struct file_line * cur_line;
2303 unsigned line_number = 0;
2304 unsigned prev_section_line_number = ((unsigned) (-1));
2308 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
2310 return cgi_error_disabled(csp, rsp);
2313 err = edit_read_actions_file(csp, rsp, parameters, 0, &file);
2316 /* No filename specified, can't read file, or out of memory. */
2317 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
2320 if (NULL == (exports = default_exports(csp, NULL)))
2322 edit_free_file(file);
2323 return JB_ERR_MEMORY;
2326 err = map(exports, "f", 1, file->identifier, 1);
2327 if (!err) err = map(exports, "v", 1, file->version_str, 1);
2331 edit_free_file(file);
2336 /* Should do all global exports above this point */
2338 err = template_load(csp, §ion_template, "edit-actions-list-section");
2341 edit_free_file(file);
2343 if (err == JB_ERR_FILE)
2345 return cgi_error_no_template(csp, rsp, "edit-actions-list-section");
2350 err = template_load(csp, &url_template, "edit-actions-list-url");
2353 free(section_template);
2354 edit_free_file(file);
2356 if (err == JB_ERR_FILE)
2358 return cgi_error_no_template(csp, rsp, "edit-actions-list-url");
2363 err = template_fill(§ion_template, exports);
2367 edit_free_file(file);
2373 err = template_fill(&url_template, exports);
2376 free(section_template);
2377 edit_free_file(file);
2382 /* Find start of actions in file */
2383 cur_line = file->lines;
2385 while ((cur_line != NULL) && (cur_line->type != FILE_LINE_ACTION))
2387 cur_line = cur_line->next;
2391 if (NULL == (sections = strdup("")))
2393 free(section_template);
2395 edit_free_file(file);
2397 return JB_ERR_MEMORY;
2400 while ((cur_line != NULL) && (cur_line->type == FILE_LINE_ACTION))
2402 if (NULL == (section_exports = new_map()))
2405 free(section_template);
2407 edit_free_file(file);
2409 return JB_ERR_MEMORY;
2412 snprintf(buf, 50, "%d", line_number);
2413 err = map(section_exports, "s", 1, buf, 1);
2414 if (!err) err = map(section_exports, "actions", 1,
2415 actions_to_html(cur_line->data.action), 0);
2418 && (cur_line->next != NULL)
2419 && (cur_line->next->type == FILE_LINE_URL))
2421 /* This section contains at least one URL, don't allow delete */
2422 err = map_block_killer(section_exports, "empty-section");
2426 if (!err) err = map_block_keep(section_exports, "empty-section");
2429 if (prev_section_line_number != ((unsigned)(-1)))
2431 /* Not last section */
2432 snprintf(buf, 50, "%d", prev_section_line_number);
2433 if (!err) err = map(section_exports, "s-prev", 1, buf, 1);
2434 if (!err) err = map_block_keep(section_exports, "s-prev-exists");
2439 if (!err) err = map_block_killer(section_exports, "s-prev-exists");
2441 prev_section_line_number = line_number;
2446 free(section_template);
2448 edit_free_file(file);
2450 free_map(section_exports);
2454 /* Should do all section-specific exports above this point */
2456 if (NULL == (urls = strdup("")))
2459 free(section_template);
2461 edit_free_file(file);
2463 free_map(section_exports);
2464 return JB_ERR_MEMORY;
2469 cur_line = cur_line->next;
2472 while ((cur_line != NULL) && (cur_line->type == FILE_LINE_URL))
2474 if (NULL == (url_exports = new_map()))
2478 free(section_template);
2480 edit_free_file(file);
2482 free_map(section_exports);
2483 return JB_ERR_MEMORY;
2486 snprintf(buf, 50, "%d", line_number);
2487 err = map(url_exports, "p", 1, buf, 1);
2489 snprintf(buf, 50, "%d", url_1_2);
2490 if (!err) err = map(url_exports, "url-1-2", 1, buf, 1);
2492 if (!err) err = map(url_exports, "url-html", 1,
2493 html_encode(cur_line->unprocessed), 0);
2494 if (!err) err = map(url_exports, "url", 1,
2495 url_encode(cur_line->unprocessed), 0);
2501 free(section_template);
2503 edit_free_file(file);
2505 free_map(section_exports);
2506 free_map(url_exports);
2510 if (NULL == (s = strdup(url_template)))
2514 free(section_template);
2516 edit_free_file(file);
2518 free_map(section_exports);
2519 free_map(url_exports);
2520 return JB_ERR_MEMORY;
2523 err = template_fill(&s, section_exports);
2524 if (!err) err = template_fill(&s, url_exports);
2525 if (!err) err = string_append(&urls, s);
2527 free_map(url_exports);
2534 free(section_template);
2536 edit_free_file(file);
2538 free_map(section_exports);
2542 url_1_2 = 3 - url_1_2;
2544 cur_line = cur_line->next;
2548 err = map(section_exports, "urls", 1, urls, 0);
2550 /* Could also do section-specific exports here, but it wouldn't be as fast */
2552 if ( (cur_line != NULL)
2553 && (cur_line->type == FILE_LINE_ACTION))
2555 /* Not last section */
2556 snprintf(buf, 50, "%d", line_number);
2557 if (!err) err = map(section_exports, "s-next", 1, buf, 1);
2558 if (!err) err = map_block_keep(section_exports, "s-next-exists");
2563 if (!err) err = map_block_killer(section_exports, "s-next-exists");
2569 free(section_template);
2571 edit_free_file(file);
2573 free_map(section_exports);
2577 if (NULL == (s = strdup(section_template)))
2580 free(section_template);
2582 edit_free_file(file);
2584 free_map(section_exports);
2585 return JB_ERR_MEMORY;
2588 err = template_fill(&s, section_exports);
2589 if (!err) err = string_append(§ions, s);
2592 free_map(section_exports);
2597 free(section_template);
2599 edit_free_file(file);
2605 edit_free_file(file);
2606 free(section_template);
2609 err = map(exports, "sections", 1, sections, 0);
2616 /* Could also do global exports here, but it wouldn't be as fast */
2618 return template_fill_for_cgi(csp, "edit-actions-list", exports, rsp);
2622 /*********************************************************************
2624 * Function : cgi_edit_actions
2626 * Description : CGI function that edits the Actions list.
2629 * 1 : csp = Current client state (buffers, headers, etc...)
2630 * 2 : rsp = http_response data structure for output
2631 * 3 : parameters = map of cgi parameters
2633 * CGI Parameters : None
2635 * Returns : JB_ERR_OK on success
2636 * JB_ERR_MEMORY on out-of-memory
2637 * JB_ERR_CGI_PARAMS if the CGI parameters are not
2638 * specified or not valid.
2640 *********************************************************************/
2641 jb_err cgi_edit_actions_for_url(struct client_state *csp,
2642 struct http_response *rsp,
2643 const struct map *parameters)
2645 struct map * exports;
2647 struct editable_file * file;
2648 struct file_line * cur_line;
2649 unsigned line_number;
2652 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
2654 return cgi_error_disabled(csp, rsp);
2657 err = get_number_param(csp, parameters, "s", §ionid);
2663 err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
2666 /* No filename specified, can't read file, modified, or out of memory. */
2667 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
2670 cur_line = file->lines;
2672 for (line_number = 1; (cur_line != NULL) && (line_number < sectionid); line_number++)
2674 cur_line = cur_line->next;
2677 if ( (cur_line == NULL)
2678 || (line_number != sectionid)
2680 || (cur_line->type != FILE_LINE_ACTION))
2682 /* Invalid "sectionid" parameter */
2683 edit_free_file(file);
2684 return JB_ERR_CGI_PARAMS;
2687 if (NULL == (exports = default_exports(csp, NULL)))
2689 edit_free_file(file);
2690 return JB_ERR_MEMORY;
2693 err = map(exports, "f", 1, file->identifier, 1);
2694 if (!err) err = map(exports, "v", 1, file->version_str, 1);
2695 if (!err) err = map(exports, "s", 1, url_encode(lookup(parameters, "s")), 0);
2697 if (!err) err = actions_to_radio(exports, cur_line->data.action);
2699 edit_free_file(file);
2707 return template_fill_for_cgi(csp, "edit-actions-for-url", exports, rsp);
2711 /*********************************************************************
2713 * Function : cgi_edit_actions_submit
2715 * Description : CGI function that actually edits the Actions list.
2718 * 1 : csp = Current client state (buffers, headers, etc...)
2719 * 2 : rsp = http_response data structure for output
2720 * 3 : parameters = map of cgi parameters
2722 * CGI Parameters : None
2724 * Returns : JB_ERR_OK on success
2725 * JB_ERR_MEMORY on out-of-memory
2726 * JB_ERR_CGI_PARAMS if the CGI parameters are not
2727 * specified or not valid.
2729 *********************************************************************/
2730 jb_err cgi_edit_actions_submit(struct client_state *csp,
2731 struct http_response *rsp,
2732 const struct map *parameters)
2738 struct editable_file * file;
2739 struct file_line * cur_line;
2740 unsigned line_number;
2744 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
2746 return cgi_error_disabled(csp, rsp);
2749 err = get_number_param(csp, parameters, "s", §ionid);
2755 err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
2758 /* No filename specified, can't read file, modified, or out of memory. */
2759 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
2762 cur_line = file->lines;
2764 for (line_number = 1; (cur_line != NULL) && (line_number < sectionid); line_number++)
2766 cur_line = cur_line->next;
2769 if ( (cur_line == NULL)
2770 || (line_number != sectionid)
2772 || (cur_line->type != FILE_LINE_ACTION))
2774 /* Invalid "sectionid" parameter */
2775 edit_free_file(file);
2776 return JB_ERR_CGI_PARAMS;
2779 err = actions_from_radio(parameters, cur_line->data.action);
2783 edit_free_file(file);
2787 if (NULL == (actiontext = actions_to_text(cur_line->data.action)))
2790 edit_free_file(file);
2791 return JB_ERR_MEMORY;
2794 len = strlen(actiontext);
2798 * Empty action - must special-case this.
2799 * Simply setting len to 1 is sufficient...
2804 if (NULL == (newtext = malloc(len + 2)))
2808 edit_free_file(file);
2809 return JB_ERR_MEMORY;
2811 strcpy(newtext, actiontext);
2815 newtext[len + 1] = '\0';
2817 freez(cur_line->raw);
2818 freez(cur_line->unprocessed);
2819 cur_line->unprocessed = newtext;
2821 err = edit_write_file(file);
2824 /* Error writing file */
2825 edit_free_file(file);
2829 target = strdup(CGI_PREFIX "edit-actions-list?f=");
2830 string_append(&target, file->identifier);
2832 edit_free_file(file);
2837 return JB_ERR_MEMORY;
2840 rsp->status = strdup("302 Local Redirect from Junkbuster");
2841 if (rsp->status == NULL)
2844 return JB_ERR_MEMORY;
2846 err = enlist_unique_header(rsp->headers, "Location", target);
2853 /*********************************************************************
2855 * Function : cgi_edit_actions_url
2857 * Description : CGI function that actually edits a URL pattern in
2861 * 1 : csp = Current client state (buffers, headers, etc...)
2862 * 2 : rsp = http_response data structure for output
2863 * 3 : parameters = map of cgi parameters
2866 * filename : Identifies the file to edit
2867 * ver : File's last-modified time
2868 * section : Line number of section to edit
2869 * pattern : Line number of pattern to edit
2870 * newval : New value for pattern
2872 * Returns : JB_ERR_OK on success
2873 * JB_ERR_MEMORY on out-of-memory
2874 * JB_ERR_CGI_PARAMS if the CGI parameters are not
2875 * specified or not valid.
2877 *********************************************************************/
2878 jb_err cgi_edit_actions_url(struct client_state *csp,
2879 struct http_response *rsp,
2880 const struct map *parameters)
2884 struct editable_file * file;
2885 struct file_line * cur_line;
2886 unsigned line_number;
2890 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
2892 return cgi_error_disabled(csp, rsp);
2895 err = get_number_param(csp, parameters, "p", &patternid);
2902 return JB_ERR_CGI_PARAMS;
2905 err = get_url_spec_param(csp, parameters, "u", &new_pattern);
2911 err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
2914 /* No filename specified, can't read file, modified, or out of memory. */
2916 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
2920 cur_line = file->lines;
2922 while ((cur_line != NULL) && (line_number < patternid))
2924 cur_line = cur_line->next;
2928 if ( (cur_line == NULL)
2929 || (cur_line->type != FILE_LINE_URL))
2931 /* Invalid "patternid" parameter */
2933 edit_free_file(file);
2934 return JB_ERR_CGI_PARAMS;
2937 /* At this point, the line to edit is in cur_line */
2939 freez(cur_line->raw);
2940 freez(cur_line->unprocessed);
2941 cur_line->unprocessed = new_pattern;
2943 err = edit_write_file(file);
2946 /* Error writing file */
2947 edit_free_file(file);
2951 target = strdup(CGI_PREFIX "edit-actions-list?f=");
2952 string_append(&target, file->identifier);
2954 edit_free_file(file);
2959 return JB_ERR_MEMORY;
2962 rsp->status = strdup("302 Local Redirect from Junkbuster");
2963 if (rsp->status == NULL)
2966 return JB_ERR_MEMORY;
2968 err = enlist_unique_header(rsp->headers, "Location", target);
2975 /*********************************************************************
2977 * Function : cgi_edit_actions_add_url
2979 * Description : CGI function that actually adds a URL pattern to
2983 * 1 : csp = Current client state (buffers, headers, etc...)
2984 * 2 : rsp = http_response data structure for output
2985 * 3 : parameters = map of cgi parameters
2988 * filename : Identifies the file to edit
2989 * ver : File's last-modified time
2990 * section : Line number of section to edit
2991 * newval : New pattern
2993 * Returns : JB_ERR_OK on success
2994 * JB_ERR_MEMORY on out-of-memory
2995 * JB_ERR_CGI_PARAMS if the CGI parameters are not
2996 * specified or not valid.
2998 *********************************************************************/
2999 jb_err cgi_edit_actions_add_url(struct client_state *csp,
3000 struct http_response *rsp,
3001 const struct map *parameters)
3005 struct file_line * new_line;
3006 struct editable_file * file;
3007 struct file_line * cur_line;
3008 unsigned line_number;
3012 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3014 return cgi_error_disabled(csp, rsp);
3017 err = get_number_param(csp, parameters, "s", §ionid);
3024 return JB_ERR_CGI_PARAMS;
3027 err = get_url_spec_param(csp, parameters, "u", &new_pattern);
3033 err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3036 /* No filename specified, can't read file, modified, or out of memory. */
3038 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3042 cur_line = file->lines;
3044 while ((cur_line != NULL) && (line_number < sectionid))
3046 cur_line = cur_line->next;
3050 if ( (cur_line == NULL)
3051 || (cur_line->type != FILE_LINE_ACTION))
3053 /* Invalid "sectionid" parameter */
3055 edit_free_file(file);
3056 return JB_ERR_CGI_PARAMS;
3059 /* At this point, the section header is in cur_line - add after this. */
3061 /* Allocate the new line */
3062 new_line = (struct file_line *)zalloc(sizeof(*new_line));
3063 if (new_line == NULL)
3066 edit_free_file(file);
3067 return JB_ERR_MEMORY;
3070 /* Fill in the data members of the new line */
3071 new_line->raw = NULL;
3072 new_line->prefix = NULL;
3073 new_line->unprocessed = new_pattern;
3074 new_line->type = FILE_LINE_URL;
3076 /* Link new_line into the list, after cur_line */
3077 new_line->next = cur_line->next;
3078 cur_line->next = new_line;
3080 /* Done making changes, now commit */
3082 err = edit_write_file(file);
3085 /* Error writing file */
3086 edit_free_file(file);
3090 target = strdup(CGI_PREFIX "edit-actions-list?f=");
3091 string_append(&target, file->identifier);
3093 edit_free_file(file);
3098 return JB_ERR_MEMORY;
3101 rsp->status = strdup("302 Local Redirect from Junkbuster");
3102 if (rsp->status == NULL)
3105 return JB_ERR_MEMORY;
3107 err = enlist_unique_header(rsp->headers, "Location", target);
3114 /*********************************************************************
3116 * Function : cgi_edit_actions_remove_url
3118 * Description : CGI function that actually removes a URL pattern from
3122 * 1 : csp = Current client state (buffers, headers, etc...)
3123 * 2 : rsp = http_response data structure for output
3124 * 3 : parameters = map of cgi parameters
3127 * f : (filename) Identifies the file to edit
3128 * v : (version) File's last-modified time
3129 * p : (pattern) Line number of pattern to remove
3131 * Returns : JB_ERR_OK on success
3132 * JB_ERR_MEMORY on out-of-memory
3133 * JB_ERR_CGI_PARAMS if the CGI parameters are not
3134 * specified or not valid.
3136 *********************************************************************/
3137 jb_err cgi_edit_actions_remove_url(struct client_state *csp,
3138 struct http_response *rsp,
3139 const struct map *parameters)
3142 struct editable_file * file;
3143 struct file_line * cur_line;
3144 struct file_line * prev_line;
3145 unsigned line_number;
3149 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3151 return cgi_error_disabled(csp, rsp);
3154 err = get_number_param(csp, parameters, "p", &patternid);
3160 err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3163 /* No filename specified, can't read file, modified, or out of memory. */
3164 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3169 cur_line = file->lines;
3171 while ((cur_line != NULL) && (line_number < patternid))
3173 prev_line = cur_line;
3174 cur_line = cur_line->next;
3178 if ( (cur_line == NULL)
3179 || (prev_line == NULL)
3180 || (cur_line->type != FILE_LINE_URL))
3182 /* Invalid "patternid" parameter */
3183 edit_free_file(file);
3184 return JB_ERR_CGI_PARAMS;
3187 /* At this point, the line to remove is in cur_line, and the previous
3188 * one is in prev_line
3191 /* Unlink cur_line */
3192 prev_line->next = cur_line->next;
3193 cur_line->next = NULL;
3196 edit_free_file_lines(cur_line);
3198 err = edit_write_file(file);
3201 /* Error writing file */
3202 edit_free_file(file);
3206 target = strdup(CGI_PREFIX "edit-actions-list?f=");
3207 string_append(&target, file->identifier);
3209 edit_free_file(file);
3214 return JB_ERR_MEMORY;
3217 rsp->status = strdup("302 Local Redirect from Junkbuster");
3218 if (rsp->status == NULL)
3221 return JB_ERR_MEMORY;
3223 err = enlist_unique_header(rsp->headers, "Location", target);
3230 /*********************************************************************
3232 * Function : cgi_edit_actions_section_remove
3234 * Description : CGI function that actually removes a whole section from
3235 * the actions file. The section must be empty first
3236 * (else JB_ERR_CGI_PARAMS).
3239 * 1 : csp = Current client state (buffers, headers, etc...)
3240 * 2 : rsp = http_response data structure for output
3241 * 3 : parameters = map of cgi parameters
3244 * f : (filename) Identifies the file to edit
3245 * v : (version) File's last-modified time
3246 * s : (section) Line number of section to edit
3248 * Returns : JB_ERR_OK on success
3249 * JB_ERR_MEMORY on out-of-memory
3250 * JB_ERR_CGI_PARAMS if the CGI parameters are not
3251 * specified or not valid.
3253 *********************************************************************/
3254 jb_err cgi_edit_actions_section_remove(struct client_state *csp,
3255 struct http_response *rsp,
3256 const struct map *parameters)
3259 struct editable_file * file;
3260 struct file_line * cur_line;
3261 struct file_line * prev_line;
3262 unsigned line_number;
3266 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3268 return cgi_error_disabled(csp, rsp);
3271 err = get_number_param(csp, parameters, "s", §ionid);
3277 err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3280 /* No filename specified, can't read file, modified, or out of memory. */
3281 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3285 cur_line = file->lines;
3288 while ((cur_line != NULL) && (line_number < sectionid))
3290 prev_line = cur_line;
3291 cur_line = cur_line->next;
3295 if ( (cur_line == NULL)
3296 || (cur_line->type != FILE_LINE_ACTION) )
3298 /* Invalid "sectionid" parameter */
3299 edit_free_file(file);
3300 return JB_ERR_CGI_PARAMS;
3303 if ( (cur_line->next != NULL)
3304 && (cur_line->next->type == FILE_LINE_URL) )
3306 /* Section not empty. */
3307 edit_free_file(file);
3308 return JB_ERR_CGI_PARAMS;
3311 /* At this point, the line to remove is in cur_line, and the previous
3312 * one is in prev_line
3315 /* Unlink cur_line */
3316 if (prev_line == NULL)
3318 /* Removing the first line from the file */
3319 file->lines = cur_line->next;
3323 prev_line->next = cur_line->next;
3325 cur_line->next = NULL;
3328 edit_free_file_lines(cur_line);
3330 err = edit_write_file(file);
3333 /* Error writing file */
3334 edit_free_file(file);
3338 target = strdup(CGI_PREFIX "edit-actions-list?f=");
3339 string_append(&target, file->identifier);
3341 edit_free_file(file);
3346 return JB_ERR_MEMORY;
3349 rsp->status = strdup("302 Local Redirect from Junkbuster");
3350 if (rsp->status == NULL)
3353 return JB_ERR_MEMORY;
3355 err = enlist_unique_header(rsp->headers, "Location", target);
3362 /*********************************************************************
3364 * Function : cgi_edit_actions_section_add
3366 * Description : CGI function that adds a new empty section to
3370 * 1 : csp = Current client state (buffers, headers, etc...)
3371 * 2 : rsp = http_response data structure for output
3372 * 3 : parameters = map of cgi parameters
3375 * f : (filename) Identifies the file to edit
3376 * v : (version) File's last-modified time
3377 * s : (section) Line number of section to add after, 0 for
3380 * Returns : JB_ERR_OK on success
3381 * JB_ERR_MEMORY on out-of-memory
3382 * JB_ERR_CGI_PARAMS if the CGI parameters are not
3383 * specified or not valid.
3385 *********************************************************************/
3386 jb_err cgi_edit_actions_section_add(struct client_state *csp,
3387 struct http_response *rsp,
3388 const struct map *parameters)
3391 struct file_line * new_line;
3393 struct editable_file * file;
3394 struct file_line * cur_line;
3395 unsigned line_number;
3399 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3401 return cgi_error_disabled(csp, rsp);
3404 err = get_number_param(csp, parameters, "s", §ionid);
3410 err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3413 /* No filename specified, can't read file, modified, or out of memory. */
3414 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3418 cur_line = file->lines;
3422 /* Add to start of file */
3423 if (cur_line != NULL)
3425 /* There's something in the file, find the line before the first
3428 while ( (cur_line->next != NULL)
3429 && (cur_line->next->type != FILE_LINE_ACTION) )
3431 cur_line = cur_line->next;
3438 /* Add after stated section. */
3439 while ((cur_line != NULL) && (line_number < sectionid))
3441 cur_line = cur_line->next;
3445 if ( (cur_line == NULL)
3446 || (cur_line->type != FILE_LINE_ACTION))
3448 /* Invalid "sectionid" parameter */
3449 edit_free_file(file);
3450 return JB_ERR_CGI_PARAMS;
3453 /* Skip through the section to find the last line in it. */
3454 while ( (cur_line->next != NULL)
3455 && (cur_line->next->type != FILE_LINE_ACTION) )
3457 cur_line = cur_line->next;
3462 /* At this point, the last line in the previous section is in cur_line
3463 * - add after this. (Or if we need to add as the first line, cur_line
3467 new_text = strdup("{}");
3468 if (NULL == new_text)
3470 edit_free_file(file);
3471 return JB_ERR_MEMORY;
3474 /* Allocate the new line */
3475 new_line = (struct file_line *)zalloc(sizeof(*new_line));
3476 if (new_line == NULL)
3479 edit_free_file(file);
3480 return JB_ERR_MEMORY;
3483 /* Fill in the data members of the new line */
3484 new_line->raw = NULL;
3485 new_line->prefix = NULL;
3486 new_line->unprocessed = new_text;
3487 new_line->type = FILE_LINE_ACTION;
3489 if (cur_line != NULL)
3491 /* Link new_line into the list, after cur_line */
3492 new_line->next = cur_line->next;
3493 cur_line->next = new_line;
3497 /* Link new_line into the list, as first line */
3498 new_line->next = file->lines;
3499 file->lines = new_line;
3502 /* Done making changes, now commit */
3504 err = edit_write_file(file);
3507 /* Error writing file */
3508 edit_free_file(file);
3512 target = strdup(CGI_PREFIX "edit-actions-list?f=");
3513 string_append(&target, file->identifier);
3515 edit_free_file(file);
3520 return JB_ERR_MEMORY;
3523 rsp->status = strdup("302 Local Redirect from Junkbuster");
3524 if (rsp->status == NULL)
3527 return JB_ERR_MEMORY;
3529 err = enlist_unique_header(rsp->headers, "Location", target);
3536 /*********************************************************************
3538 * Function : cgi_edit_actions_section_swap
3540 * Description : CGI function that swaps the order of two sections
3541 * in the actions file. Note that this CGI can actually
3542 * swap any two arbitrary sections, but the GUI interface
3543 * currently only allows consecutive sections to be
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
3552 * f : (filename) Identifies the file to edit
3553 * v : (version) File's last-modified time
3554 * s1 : (section1) Line number of first section to swap
3555 * s2 : (section2) Line number of second section to swap
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.
3562 *********************************************************************/
3563 jb_err cgi_edit_actions_section_swap(struct client_state *csp,
3564 struct http_response *rsp,
3565 const struct map *parameters)
3569 struct editable_file * file;
3570 struct file_line * cur_line;
3571 struct file_line * prev_line;
3572 struct file_line * line_before_section1;
3573 struct file_line * line_start_section1;
3574 struct file_line * line_end_section1;
3575 struct file_line * line_after_section1;
3576 struct file_line * line_before_section2;
3577 struct file_line * line_start_section2;
3578 struct file_line * line_end_section2;
3579 struct file_line * line_after_section2;
3580 unsigned line_number;
3584 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3586 return cgi_error_disabled(csp, rsp);
3589 err = get_number_param(csp, parameters, "s1", §ion1);
3590 if (!err) err = get_number_param(csp, parameters, "s2", §ion2);
3596 if (section1 > section2)
3598 unsigned temp = section2;
3599 section2 = section1;
3603 err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3606 /* No filename specified, can't read file, modified, or out of memory. */
3607 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3610 /* Start at the beginning... */
3612 cur_line = file->lines;
3615 /* ... find section1 ... */
3616 while ((cur_line != NULL) && (line_number < section1))
3618 prev_line = cur_line;
3619 cur_line = cur_line->next;
3623 if ( (cur_line == NULL)
3624 || (cur_line->type != FILE_LINE_ACTION) )
3626 /* Invalid "section1" parameter */
3627 edit_free_file(file);
3628 return JB_ERR_CGI_PARAMS;
3631 /* If no-op, we've validated params and can skip the rest. */
3632 if (section1 != section2)
3634 /* ... find the end of section1 ... */
3635 line_before_section1 = prev_line;
3636 line_start_section1 = cur_line;
3639 prev_line = cur_line;
3640 cur_line = cur_line->next;
3643 while ((cur_line != NULL) && (cur_line->type == FILE_LINE_URL));
3644 line_end_section1 = prev_line;
3645 line_after_section1 = cur_line;
3647 /* ... find section2 ... */
3648 while ((cur_line != NULL) && (line_number < section2))
3650 prev_line = cur_line;
3651 cur_line = cur_line->next;
3655 if ( (cur_line == NULL)
3656 || (cur_line->type != FILE_LINE_ACTION) )
3658 /* Invalid "section2" parameter */
3659 edit_free_file(file);
3660 return JB_ERR_CGI_PARAMS;
3663 /* ... find the end of section2 ... */
3664 line_before_section2 = prev_line;
3665 line_start_section2 = cur_line;
3668 prev_line = cur_line;
3669 cur_line = cur_line->next;
3672 while ((cur_line != NULL) && (cur_line->type == FILE_LINE_URL));
3673 line_end_section2 = prev_line;
3674 line_after_section2 = cur_line;
3676 /* Now have all the pointers we need. Do the swap. */
3678 /* Change the pointer to section1 to point to section2 instead */
3679 if (line_before_section1 == NULL)
3681 file->lines = line_start_section2;
3685 line_before_section1->next = line_start_section2;
3688 if (line_before_section2 == line_end_section1)
3690 /* Consecutive sections */
3691 line_end_section2->next = line_start_section1;
3695 line_end_section2->next = line_after_section1;
3696 line_before_section2->next = line_start_section1;
3699 /* Set the pointer from the end of section1 to the rest of the file */
3700 line_end_section1->next = line_after_section2;
3702 err = edit_write_file(file);
3705 /* Error writing file */
3706 edit_free_file(file);
3709 } /* END if (section1 != section2) */
3711 target = strdup(CGI_PREFIX "edit-actions-list?f=");
3712 string_append(&target, file->identifier);
3714 edit_free_file(file);
3719 return JB_ERR_MEMORY;
3722 rsp->status = strdup("302 Local Redirect from Junkbuster");
3723 if (rsp->status == NULL)
3726 return JB_ERR_MEMORY;
3728 err = enlist_unique_header(rsp->headers, "Location", target);
3735 /*********************************************************************
3737 * Function : cgi_toggle
3739 * Description : CGI function that adds a new empty section to
3743 * 1 : csp = Current client state (buffers, headers, etc...)
3744 * 2 : rsp = http_response data structure for output
3745 * 3 : parameters = map of cgi parameters
3748 * set : If present, how to change toggle setting:
3749 * "enable", "disable", "toggle", or none (default).
3750 * mini : If present, use mini reply template.
3752 * Returns : JB_ERR_OK on success
3753 * JB_ERR_MEMORY on out-of-memory
3755 *********************************************************************/
3756 jb_err cgi_toggle(struct client_state *csp,
3757 struct http_response *rsp,
3758 const struct map *parameters)
3760 struct map *exports;
3762 const char *template_name;
3769 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_TOGGLE))
3771 return cgi_error_disabled(csp, rsp);
3774 if (NULL == (exports = default_exports(csp, "toggle")))
3776 return JB_ERR_MEMORY;
3779 mode = *(lookup(parameters, "set"));
3786 else if (mode == 'd')
3791 else if (mode == 't')
3794 g_bToggleIJB = !g_bToggleIJB;
3797 err = map_conditional(exports, "enabled", g_bToggleIJB);
3804 template_name = (*(lookup(parameters, "mini"))
3808 return template_fill_for_cgi(csp, template_name, exports, rsp);
3812 /*********************************************************************
3814 * Function : actions_to_radio
3816 * Description : Converts a actionsfile entry into settings for
3817 * radio buttons and edit boxes on a HTML form.
3820 * 1 : exports = List of substitutions to add to.
3821 * 2 : action = Action to read
3823 * Returns : JB_ERR_OK on success
3824 * JB_ERR_MEMORY on out-of-memory
3826 *********************************************************************/
3827 static jb_err actions_to_radio(struct map * exports,
3828 const struct action_spec *action)
3830 unsigned mask = action->mask;
3831 unsigned add = action->add;
3839 mask = action->mask;
3842 /* sanity - prevents "-feature +feature" */
3846 #define DEFINE_ACTION_BOOL(name, bit) \
3847 if (!(mask & bit)) \
3849 current_mode = 'n'; \
3851 else if (add & bit) \
3853 current_mode = 'y'; \
3857 current_mode = 'x'; \
3859 if (map_radio(exports, name, "ynx", current_mode)) \
3861 return JB_ERR_MEMORY; \
3864 #define DEFINE_ACTION_STRING(name, bit, index) \
3865 DEFINE_ACTION_BOOL(name, bit); \
3868 #define DEFINE_CGI_PARAM_RADIO(name, bit, index, value, is_default) \
3871 checked = !strcmp(action->string[index], value); \
3875 checked = is_default; \
3877 mapped_param |= checked; \
3878 if (map(exports, name "-param-" value, 1, (checked ? "checked" : ""), 1)) \
3880 return JB_ERR_MEMORY; \
3883 #define DEFINE_CGI_PARAM_CUSTOM(name, bit, index, default_val) \
3884 if (map(exports, name "-param-custom", 1, \
3885 ((!mapped_param) ? "checked" : ""), 1)) \
3887 return JB_ERR_MEMORY; \
3889 if (map(exports, name "-param", 1, \
3890 (((add & bit) && !mapped_param) ? \
3891 action->string[index] : default_val), 1)) \
3893 return JB_ERR_MEMORY; \
3896 #define DEFINE_CGI_PARAM_NO_RADIO(name, bit, index, default_val) \
3897 if (map(exports, name "-param", 1, \
3898 ((add & bit) ? action->string[index] : default_val), 1)) \
3900 return JB_ERR_MEMORY; \
3903 #define DEFINE_ACTION_MULTI(name, index) \
3904 if (action->multi_add[index]->first) \
3906 current_mode = 'y'; \
3908 else if (action->multi_remove_all[index]) \
3910 current_mode = 'n'; \
3912 else if (action->multi_remove[index]->first) \
3914 current_mode = 'y'; \
3918 current_mode = 'x'; \
3920 if (map_radio(exports, name, "ynx", current_mode)) \
3922 return JB_ERR_MEMORY; \
3925 #define DEFINE_ACTION_ALIAS 0 /* No aliases for output */
3927 #include "actionlist.h"
3929 #undef DEFINE_ACTION_MULTI
3930 #undef DEFINE_ACTION_STRING
3931 #undef DEFINE_ACTION_BOOL
3932 #undef DEFINE_ACTION_ALIAS
3933 #undef DEFINE_CGI_PARAM_CUSTOM
3934 #undef DEFINE_CGI_PARAM_RADIO
3935 #undef DEFINE_CGI_PARAM_NO_RADIO
3941 /*********************************************************************
3943 * Function : javascriptify
3945 * Description : Converts a string into a form JavaScript will like.
3947 * Netscape 4's JavaScript sucks - it doesn't use
3948 * "id" parameters, so you have to set the "name"
3949 * used to submit a form element to something JavaScript
3950 * will like. (Or access the elements by index in an
3951 * array. That array contains >60 elements and will
3952 * be changed whenever we add a new action to the
3953 * editor, so I'm NOT going to use indexes that have
3954 * to be figured out by hand.)
3956 * Currently the only thing we have to worry about
3957 * is "-" ==> "_" conversion.
3959 * This is a length-preserving operation so it is
3960 * carried out in-place, no memory is allocated
3964 * 1 : identifier = String to make JavaScript-friendly.
3968 *********************************************************************/
3969 static void javascriptify(char * identifier)
3971 char * p = identifier;
3972 while (NULL != (p = strchr(p, '-')))
3979 /*********************************************************************
3981 * Function : actions_from_radio
3983 * Description : Converts a map of parameters passed to a CGI function
3984 * into an actionsfile entry.
3987 * 1 : parameters = parameters to the CGI call
3988 * 2 : action = Action to change. Must be valid before
3989 * the call, actions not specified will be
3992 * Returns : JB_ERR_OK on success
3993 * JB_ERR_MEMORY on out-of-memory
3995 *********************************************************************/
3996 static jb_err actions_from_radio(const struct map * parameters,
3997 struct action_spec *action)
3999 static int first_time = 1;
4003 const char * js_name;
4008 /* Statics are generally a potential race condition,
4009 * but in this case we're safe and don't need semaphores.
4010 * Be careful if you modify this function.
4014 #define JAVASCRIPTIFY(dest_var, string) \
4016 static char js_name_arr[] = string; \
4019 javascriptify(js_name_arr); \
4021 dest_var = js_name_arr; \
4024 #define DEFINE_ACTION_BOOL(name, bit) \
4025 JAVASCRIPTIFY(js_name, name); \
4026 param = lookup(parameters, js_name); \
4027 ch = ijb_toupper(param[0]); \
4030 action->add |= bit; \
4031 action->mask |= bit; \
4033 else if (ch == 'N') \
4035 action->add &= ~bit; \
4036 action->mask &= ~bit; \
4038 else if (ch == 'X') \
4040 action->add &= ~bit; \
4041 action->mask |= bit; \
4044 #define DEFINE_ACTION_STRING(name, bit, index) \
4045 JAVASCRIPTIFY(js_name, name); \
4046 param = lookup(parameters, js_name); \
4047 ch = ijb_toupper(param[0]); \
4050 JAVASCRIPTIFY(js_name, name "-mode"); \
4051 param = lookup(parameters, js_name); \
4052 if ((*param == '\0') || (0 == strcmp(param, "CUSTOM"))) \
4054 JAVASCRIPTIFY(js_name, name "-param"); \
4055 param = lookup(parameters, js_name); \
4057 if (*param != '\0') \
4059 if (NULL == (param_dup = strdup(param))) \
4061 return JB_ERR_MEMORY; \
4063 freez(action->string[index]); \
4064 action->add |= bit; \
4065 action->mask |= bit; \
4066 action->string[index] = param_dup; \
4069 else if (ch == 'N') \
4071 if (action->add & bit) \
4073 freez(action->string[index]); \
4075 action->add &= ~bit; \
4076 action->mask &= ~bit; \
4078 else if (ch == 'X') \
4080 if (action->add & bit) \
4082 freez(action->string[index]); \
4084 action->add &= ~bit; \
4085 action->mask |= bit; \
4088 #define DEFINE_ACTION_MULTI(name, index) \
4089 JAVASCRIPTIFY(js_name, name); \
4090 param = lookup(parameters, js_name); \
4091 ch = ijb_toupper((int)param[0]); \
4096 else if (ch == 'N') \
4098 list_remove_all(action->multi_add[index]); \
4099 list_remove_all(action->multi_remove[index]); \
4100 action->multi_remove_all[index] = 1; \
4102 else if (ch == 'X') \
4104 list_remove_all(action->multi_add[index]); \
4105 list_remove_all(action->multi_remove[index]); \
4106 action->multi_remove_all[index] = 0; \
4109 #define DEFINE_ACTION_ALIAS 0 /* No aliases for URL parsing */
4111 #include "actionlist.h"
4113 #undef DEFINE_ACTION_MULTI
4114 #undef DEFINE_ACTION_STRING
4115 #undef DEFINE_ACTION_BOOL
4116 #undef DEFINE_ACTION_ALIAS
4117 #undef JAVASCRIPTIFY
4125 #endif /* def FEATURE_CGI_EDIT_ACTIONS */