1 const char cgiedit_rcs[] = "$Id: cgiedit.c,v 1.8 2001/11/30 23:35:51 jongfoster Exp $";
2 /*********************************************************************
4 * File : $Source: /cvsroot/ijbswa/current/cgiedit.c,v $
6 * Purpose : CGI-based actionsfile editor.
8 * Functions declared include:
11 * Copyright : Written by and Copyright (C) 2001 the SourceForge
12 * IJBSWA team. http://ijbswa.sourceforge.net
14 * Based on the Internet Junkbuster originally written
15 * by and Copyright (C) 1997 Anonymous Coders and
16 * Junkbusters Corporation. http://www.junkbusters.com
18 * This program is free software; you can redistribute it
19 * and/or modify it under the terms of the GNU General
20 * Public License as published by the Free Software
21 * Foundation; either version 2 of the License, or (at
22 * your option) any later version.
24 * This program is distributed in the hope that it will
25 * be useful, but WITHOUT ANY WARRANTY; without even the
26 * implied warranty of MERCHANTABILITY or FITNESS FOR A
27 * PARTICULAR PURPOSE. See the GNU General Public
28 * License for more details.
30 * The GNU General Public License should be included with
31 * this file. If not, you can view it at
32 * http://www.gnu.org/copyleft/gpl.html
33 * or write to the Free Software Foundation, Inc., 59
34 * Temple Place - Suite 330, Boston, MA 02111-1307, USA.
38 * Revision 1.8 2001/11/30 23:35:51 jongfoster
39 * Renaming actionsfile to ijb.action
41 * Revision 1.7 2001/11/13 00:28:24 jongfoster
42 * - Renaming parameters from edit-actions-for-url so that they only
43 * contain legal JavaScript characters. If we wanted to write
44 * JavaScript that worked with Netscape 4, this is nessacery.
45 * (Note that at the moment the JavaScript doesn't actually work
46 * with Netscape 4, but now this is purely a template issue, not
47 * one affecting code).
48 * - Adding new CGIs for use by non-JavaScript browsers:
49 * edit-actions-url-form
50 * edit-actions-add-url-form
51 * edit-actions-remove-url-form
54 * Revision 1.6 2001/10/29 03:48:09 david__schmidt
55 * OS/2 native needed a snprintf() routine. Added one to miscutil, brackedted
56 * by and __OS2__ ifdef.
58 * Revision 1.5 2001/10/25 03:40:48 david__schmidt
59 * Change in porting tactics: OS/2's EMX porting layer doesn't allow multiple
60 * threads to call select() simultaneously. So, it's time to do a real, live,
61 * native OS/2 port. See defines for __EMX__ (the porting layer) vs. __OS2__
62 * (native). Both versions will work, but using __OS2__ offers multi-threading.
64 * Revision 1.4 2001/10/23 21:48:19 jongfoster
65 * Cleaning up error handling in CGI functions - they now send back
66 * a HTML error page and should never cause a FATAL error. (Fixes one
67 * potential source of "denial of service" attacks).
69 * CGI actions file editor that works and is actually useful.
71 * Ability to toggle JunkBuster remotely using a CGI call.
73 * You can turn off both the above features in the main configuration
74 * file, e.g. if you are running a multi-user proxy.
76 * Revision 1.3 2001/10/14 22:12:49 jongfoster
77 * New version of CGI-based actionsfile editor.
78 * Major changes, including:
79 * - Completely new file parser and file output routines
80 * - edit-actions CGI renamed edit-actions-for-url
81 * - All CGIs now need a filename parameter, except for...
82 * - New CGI edit-actions which doesn't need a filename,
83 * to allow you to start the editor up.
84 * - edit-actions-submit now works, and now automatically
85 * redirects you back to the main edit-actions-list handler.
87 * Revision 1.2 2001/09/16 17:05:14 jongfoster
88 * Removing unused #include showarg.h
90 * Revision 1.1 2001/09/16 15:47:37 jongfoster
91 * First version of CGI-based edit interface. This is very much a
92 * work-in-progress, and you can't actually use it to edit anything
93 * yet. You must #define FEATURE_CGI_EDIT_ACTIONS for these changes
97 **********************************************************************/
103 * FIXME: Following includes copied from cgi.c - which are actually needed?
108 #include <sys/types.h>
113 #include <sys/stat.h>
116 #define snprintf _snprintf
117 #endif /* def _WIN32 */
122 #include "cgisimple.h"
126 #include "miscutil.h"
129 /* loadcfg.h is for g_bToggleIJB only */
130 #include "urlmatch.h"
132 const char cgiedit_h_rcs[] = CGIEDIT_H_VERSION;
135 #ifdef FEATURE_CGI_EDIT_ACTIONS
139 struct file_line * next;
147 struct action_spec action[1];
156 /* Add more data types here... e.g.
159 struct url_spec url[1];
163 struct action_spec action[1];
172 #define FILE_LINE_UNPROCESSED 1
173 #define FILE_LINE_BLANK 2
174 #define FILE_LINE_ALIAS_HEADER 3
175 #define FILE_LINE_ALIAS_ENTRY 4
176 #define FILE_LINE_ACTION 5
177 #define FILE_LINE_URL 6
178 #define FILE_LINE_SETTINGS_HEADER 7
179 #define FILE_LINE_SETTINGS_ENTRY 8
180 #define FILE_LINE_DESCRIPTION_HEADER 9
181 #define FILE_LINE_DESCRIPTION_ENTRY 10
186 struct file_line * lines;
187 const char * filename; /* Full pathname - e.g. "/etc/junkbuster/wibble.action" */
188 const char * identifier; /* Filename stub - e.g. "wibble". Use for CGI param. */
189 const char * version_str; /* Last modification time, as a string. For CGI param */
190 unsigned version; /* Last modification time - prevents chaos with
191 * the browser's "back" button. Note that this is a
192 * time_t cast to an unsigned. When comparing, always
193 * cast the time_t to an unsigned, and *NOT* vice-versa.
194 * This may lose the top few bits, but they're not
195 * significant anyway.
197 struct file_line * parse_error; /* On parse error, this is the offending line. */
198 const char * parse_error_text; /* On parse error, this is the problem.
199 * (Statically allocated) */
202 /* FIXME: Following non-static functions should be prototyped in .h or made static */
204 /* Functions to read and write arbitrary config files */
205 jb_err edit_read_file(struct client_state *csp,
206 const struct map *parameters,
209 struct editable_file **pfile);
210 jb_err edit_write_file(struct editable_file * file);
211 void edit_free_file(struct editable_file * file);
213 /* Functions to read and write actions files */
214 jb_err edit_parse_actions_file(struct editable_file * file);
215 jb_err edit_read_actions_file(struct client_state *csp,
216 struct http_response *rsp,
217 const struct map *parameters,
219 struct editable_file **pfile);
222 jb_err cgi_error_modified(struct client_state *csp,
223 struct http_response *rsp,
224 const char *filename);
225 jb_err cgi_error_parse(struct client_state *csp,
226 struct http_response *rsp,
227 struct editable_file *file);
228 jb_err cgi_error_file(struct client_state *csp,
229 struct http_response *rsp,
230 const char *filename);
231 jb_err cgi_error_disabled(struct client_state *csp,
232 struct http_response *rsp);
234 /* Internal arbitrary config file support functions */
235 static jb_err edit_read_file_lines(FILE *fp, struct file_line ** pfile);
236 static void edit_free_file_lines(struct file_line * first_line);
237 static jb_err simple_read_line(char **dest, FILE *fp);
238 static jb_err edit_read_line(FILE *fp, char **raw_out, char **prefix_out, char **data_out);
240 /* Internal actions file support functions */
241 static int match_actions_file_header_line(const char * line, const char * name);
242 static jb_err split_line_on_equals(const char * line, char ** pname, char ** pvalue);
244 /* Internal parameter parsing functions */
245 static jb_err get_file_name_param(struct client_state *csp,
246 const struct map *parameters,
247 const char *param_name,
250 const char **pparam);
251 static jb_err get_number_param(struct client_state *csp,
252 const struct map *parameters,
256 /* Internal actionsfile <==> HTML conversion functions */
257 static jb_err map_radio(struct map * exports,
258 const char * optionname,
261 static jb_err actions_to_radio(struct map * exports,
262 const struct action_spec *action);
263 static jb_err actions_from_radio(const struct map * parameters,
264 struct action_spec *action);
267 static jb_err map_copy_parameter_html(struct map *out,
268 const struct map *in,
270 static jb_err map_copy_parameter_url(struct map *out,
271 const struct map *in,
275 /*********************************************************************
277 * Function : map_copy_parameter_html
279 * Description : Copy a CGI parameter from one map to another, HTML
283 * 1 : out = target map
284 * 2 : in = source map
285 * 3 : name = name of cgi parameter to copy
287 * Returns : JB_ERR_OK on success
288 * JB_ERR_MEMORY on out-of-memory
289 * JB_ERR_CGI_PARAMS if the parameter doesn't exist
292 *********************************************************************/
293 static jb_err map_copy_parameter_html(struct map *out,
294 const struct map *in,
304 value = lookup(in, name);
305 err = map(out, name, 1, html_encode(value), 0);
312 else if (*value == '\0')
314 return JB_ERR_CGI_PARAMS;
323 /*********************************************************************
325 * Function : map_copy_parameter_html
327 * Description : Copy a CGI parameter from one map to another, URL
331 * 1 : out = target map
332 * 2 : in = source map
333 * 3 : name = name of cgi parameter to copy
335 * Returns : JB_ERR_OK on success
336 * JB_ERR_MEMORY on out-of-memory
337 * JB_ERR_CGI_PARAMS if the parameter doesn't exist
340 *********************************************************************/
341 static jb_err map_copy_parameter_url(struct map *out,
342 const struct map *in,
352 value = lookup(in, name);
353 err = map(out, name, 1, url_encode(value), 0);
360 else if (*value == '\0')
362 return JB_ERR_CGI_PARAMS;
371 /*********************************************************************
373 * Function : cgi_edit_actions_url_form
375 * Description : CGI function that displays a form for
379 * 1 : csp = Current client state (buffers, headers, etc...)
380 * 2 : rsp = http_response data structure for output
381 * 3 : parameters = map of cgi parameters
384 * filename : Identifies the file to edit
385 * ver : File's last-modified time
386 * section : Line number of section to edit
387 * pattern : Line number of pattern to edit
388 * oldval : Current value for pattern
390 * Returns : JB_ERR_OK on success
391 * JB_ERR_MEMORY on out-of-memory
392 * JB_ERR_CGI_PARAMS if the CGI parameters are not
393 * specified or not valid.
395 *********************************************************************/
396 jb_err cgi_edit_actions_url_form(struct client_state *csp,
397 struct http_response *rsp,
398 const struct map *parameters)
407 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
409 return cgi_error_disabled(csp, rsp);
412 if (NULL == (exports = default_exports(csp, NULL)))
414 return JB_ERR_MEMORY;
417 err = map_copy_parameter_html(exports, parameters, "section");
418 if (!err) err = map_copy_parameter_html(exports, parameters, "pattern");
419 if (!err) err = map_copy_parameter_html(exports, parameters, "ver");
420 if (!err) err = map_copy_parameter_html(exports, parameters, "filename");
421 if (!err) err = map_copy_parameter_html(exports, parameters, "oldval");
429 return template_fill_for_cgi(csp, "edit-actions-url-form", exports, rsp);
433 /*********************************************************************
435 * Function : cgi_edit_actions_add_url_form
437 * Description : CGI function that displays a form for
441 * 1 : csp = Current client state (buffers, headers, etc...)
442 * 2 : rsp = http_response data structure for output
443 * 3 : parameters = map of cgi parameters
446 * filename : Identifies the file to edit
447 * ver : File's last-modified time
448 * section : Line number of section to edit
450 * Returns : JB_ERR_OK on success
451 * JB_ERR_MEMORY on out-of-memory
452 * JB_ERR_CGI_PARAMS if the CGI parameters are not
453 * specified or not valid.
455 *********************************************************************/
456 jb_err cgi_edit_actions_add_url_form(struct client_state *csp,
457 struct http_response *rsp,
458 const struct map *parameters)
467 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
469 return cgi_error_disabled(csp, rsp);
472 if (NULL == (exports = default_exports(csp, NULL)))
474 return JB_ERR_MEMORY;
477 err = map_copy_parameter_html(exports, parameters, "section");
478 if (!err) err = map_copy_parameter_html(exports, parameters, "ver");
479 if (!err) err = map_copy_parameter_html(exports, parameters, "filename");
487 return template_fill_for_cgi(csp, "edit-actions-add-url-form", exports, rsp);
491 /*********************************************************************
493 * Function : cgi_edit_actions_remove_url_form
495 * Description : CGI function that displays a form for
499 * 1 : csp = Current client state (buffers, headers, etc...)
500 * 2 : rsp = http_response data structure for output
501 * 3 : parameters = map of cgi parameters
504 * filename : Identifies the file to edit
505 * ver : File's last-modified time
506 * section : Line number of section to edit
507 * pattern : Line number of pattern to edit
508 * oldval : Current value for pattern
510 * Returns : JB_ERR_OK on success
511 * JB_ERR_MEMORY on out-of-memory
512 * JB_ERR_CGI_PARAMS if the CGI parameters are not
513 * specified or not valid.
515 *********************************************************************/
516 jb_err cgi_edit_actions_remove_url_form(struct client_state *csp,
517 struct http_response *rsp,
518 const struct map *parameters)
527 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
529 return cgi_error_disabled(csp, rsp);
532 if (NULL == (exports = default_exports(csp, NULL)))
534 return JB_ERR_MEMORY;
537 err = map_copy_parameter_url(exports, parameters, "section");
538 if (!err) err = map_copy_parameter_url(exports, parameters, "pattern");
539 if (!err) err = map_copy_parameter_url(exports, parameters, "ver");
540 if (!err) err = map_copy_parameter_url(exports, parameters, "filename");
541 if (!err) err = map_copy_parameter_html(exports, parameters, "oldval");
549 return template_fill_for_cgi(csp, "edit-actions-remove-url-form", exports, rsp);
553 /*********************************************************************
555 * Function : simple_read_line
557 * Description : Read a single line from a file and return it.
558 * This is basically a version of fgets() that malloc()s
559 * it's own line buffer. Note that the buffer will
560 * always be a multiple of BUFFER_SIZE bytes long.
561 * Therefore if you are going to keep the string for
562 * an extended period of time, you should probably
563 * strdup() it and free() the original, to save memory.
567 * 1 : dest = destination for newly malloc'd pointer to
568 * line data. Will be set to NULL on error.
569 * 2 : fp = File to read from
571 * Returns : JB_ERR_OK on success
572 * JB_ERR_MEMORY on out-of-memory
573 * JB_ERR_FILE on EOF.
575 *********************************************************************/
576 static jb_err simple_read_line(char **dest, FILE *fp)
587 if (NULL == (buf = malloc(BUFFER_SIZE)))
589 return JB_ERR_MEMORY;
598 if ((!fgets(newbuf, BUFFER_SIZE, fp)) || (*newbuf == '\0'))
600 /* (*newbuf == '\0') should never happen unless fgets fails */
613 if ((buf[len - 1] == '\n') || (buf[len - 1] == '\r'))
619 if (NULL == (newbuf = realloc(buf, len + BUFFER_SIZE)))
622 return JB_ERR_MEMORY;
629 /*********************************************************************
631 * Function : edit_read_line
633 * Description : Read a single non-empty line from a file and return
634 * it. Trims comments, leading and trailing whitespace
635 * and respects escaping of newline and comment char.
636 * Provides the line in 2 alternative forms: raw and
638 * - raw is the raw data read from the file. If the
639 * line is not modified, then this should be written
641 * - prefix is any comments and blank lines that were
642 * read from the file. If the line is modified, then
643 * this should be written out to the file followed
644 * by the modified data. (If this string is non-empty
645 * then it will have a newline at the end).
646 * - data is the actual data that will be parsed
647 * further by appropriate routines.
648 * On EOF, the 3 strings will all be set to NULL and
649 * 0 will be returned.
652 * 1 : fp = File to read from
653 * 2 : raw_out = destination for newly malloc'd pointer to
654 * raw line data. May be NULL if you don't want it.
655 * 3 : prefix_out = destination for newly malloc'd pointer to
656 * comments. May be NULL if you don't want it.
657 * 4 : data_out = destination for newly malloc'd pointer to
658 * line data with comments and leading/trailing spaces
659 * removed, and line continuation performed. May be
660 * NULL if you don't want it.
662 * Returns : JB_ERR_OK on success
663 * JB_ERR_MEMORY on out-of-memory
664 * JB_ERR_FILE on EOF.
666 *********************************************************************/
667 static jb_err edit_read_line(FILE *fp, char **raw_out, char **prefix_out, char **data_out)
669 char *p; /* Temporary pointer */
670 char *linebuf; /* Line read from file */
671 char *linestart; /* Start of linebuf, usually first non-whitespace char */
672 char newline[3]; /* Used to store the newline - "\n", "\r", or "\r\n" */
673 int contflag = 0; /* Nonzero for line continuation - i.e. line ends '\' */
674 char *raw; /* String to be stored in raw_out */
675 char *prefix; /* String to be stored in prefix_out */
676 char *data; /* String to be stored in data_out */
677 jb_err rval = JB_ERR_OK;
681 /* Set output parameters to NULL */
695 /* Set string variables to new, empty strings. */
701 if ((raw == NULL) || (prefix == NULL) || (data == NULL))
706 return JB_ERR_MEMORY;
713 /* Main loop. Loop while we need more data & it's not EOF. */
715 while ( (contflag || (*data == '\0'))
716 && (JB_ERR_OK == (rval = simple_read_line(&linebuf, fp))))
718 if (string_append(&raw,linebuf))
723 return JB_ERR_MEMORY;
726 /* Trim off newline */
727 p = linebuf + strlen(linebuf);
728 if ((p != linebuf) && ((p[-1] == '\r') || (p[-1] == '\n')))
731 if ((p != linebuf) && ((p[-1] == '\r') || (p[-1] == '\n')))
739 /* Line continuation? Trim escape and set flag. */
740 contflag = ((p != linebuf) && (*--p == '\\'));
746 /* Trim leading spaces if we're at the start of the line */
750 /* Trim leading spaces */
751 while (*linestart && isspace((int)(unsigned char)*linestart))
757 /* Handle comment characters. */
759 while ((p = strchr(p, '#')) != NULL)
761 /* Found a comment char.. */
762 if ((p != linebuf) && (*(p-1) == '\\'))
764 /* ..and it's escaped, left-shift the line over the escape. */
766 while ((*q = *(q + 1)) != '\0')
770 /* Now scan from just after the "#". */
774 /* Real comment. Save it... */
777 /* Special case: Line only contains a comment, so all the
778 * previous whitespace is considered part of the comment.
779 * Undo the whitespace skipping, if any.
784 string_append(&prefix,p);
785 if (string_append(&prefix,newline))
790 return JB_ERR_MEMORY;
794 /* ... and chop off the rest of the line */
797 } /* END while (there's a # character) */
799 /* Write to the buffer */
802 if (string_append(&data, linestart))
807 return JB_ERR_MEMORY;
812 } /* END while(we need more data) */
814 /* Handle simple_read_line() errors - ignore EOF */
815 if ((rval != JB_ERR_OK) && (rval != JB_ERR_FILE))
826 /* Got at least some data */
828 /* Remove trailing whitespace */
841 *prefix_out = prefix;
859 /* EOF and no data there. */
870 /*********************************************************************
872 * Function : edit_write_file
874 * Description : Write a complete file to disk.
877 * 1 : filename = File to write to.
878 * 2 : file = Data structure to write.
880 * Returns : JB_ERR_OK on success
881 * JB_ERR_FILE on error writing to file.
882 * JB_ERR_MEMORY on out of memory
884 *********************************************************************/
885 jb_err edit_write_file(struct editable_file * file)
888 struct file_line * cur_line;
889 struct stat statbuf[1];
890 char version_buf[22]; /* 22 = ceil(log10(2^64)) + 2 = max number of
891 digits in time_t, assuming this is a 64-bit
892 machine, plus null terminator, plus one
896 assert(file->filename);
898 if (NULL == (fp = fopen(file->filename, "wt")))
903 cur_line = file->lines;
904 while (cur_line != NULL)
908 if (fputs(cur_line->raw, fp) < 0)
916 if (cur_line->prefix)
918 if (fputs(cur_line->prefix, fp) < 0)
924 if (cur_line->unprocessed)
926 if (fputs(cur_line->unprocessed, fp) < 0)
931 if (fputs("\n", fp) < 0)
939 /* FIXME: Write data from file->data->whatever */
943 cur_line = cur_line->next;
949 /* Update the version stamp in the file structure, since we just
950 * wrote to the file & changed it's date.
952 if (stat(file->filename, statbuf) < 0)
954 /* Error, probably file not found. */
957 file->version = (unsigned)statbuf->st_mtime;
959 /* Correct file->version_str */
960 freez(file->version_str);
961 snprintf(version_buf, 22, "%u", file->version);
962 version_buf[21] = '\0';
963 file->version_str = strdup(version_buf);
964 if (version_buf == NULL)
966 return JB_ERR_MEMORY;
973 /*********************************************************************
975 * Function : edit_free_file
977 * Description : Free a complete file in memory.
980 * 1 : file = Data structure to free.
984 *********************************************************************/
985 void edit_free_file(struct editable_file * file)
989 /* Silently ignore NULL pointer */
993 edit_free_file_lines(file->lines);
994 freez(file->filename);
995 freez(file->identifier);
996 freez(file->version_str);
998 file->parse_error_text = NULL; /* Statically allocated */
999 file->parse_error = NULL;
1005 /*********************************************************************
1007 * Function : edit_free_file
1009 * Description : Free an entire linked list of file lines.
1012 * 1 : first_line = Data structure to free.
1016 *********************************************************************/
1017 static void edit_free_file_lines(struct file_line * first_line)
1019 struct file_line * next_line;
1021 while (first_line != NULL)
1023 next_line = first_line->next;
1024 first_line->next = NULL;
1025 freez(first_line->raw);
1026 freez(first_line->prefix);
1027 freez(first_line->unprocessed);
1028 switch(first_line->type)
1030 case 0: /* special case if memory zeroed */
1031 case FILE_LINE_UNPROCESSED:
1032 case FILE_LINE_BLANK:
1033 case FILE_LINE_ALIAS_HEADER:
1034 case FILE_LINE_SETTINGS_HEADER:
1035 case FILE_LINE_DESCRIPTION_HEADER:
1036 case FILE_LINE_DESCRIPTION_ENTRY:
1037 case FILE_LINE_ALIAS_ENTRY:
1039 /* No data is stored for these */
1042 case FILE_LINE_ACTION:
1043 free_action(first_line->data.action);
1046 case FILE_LINE_SETTINGS_ENTRY:
1047 freez(first_line->data.setting.name);
1048 freez(first_line->data.setting.svalue);
1051 /* Should never happen */
1055 first_line->type = 0; /* paranoia */
1057 first_line = next_line;
1062 /*********************************************************************
1064 * Function : match_actions_file_header_line
1066 * Description : Match an actions file {{header}} line
1069 * 1 : line - String from file
1070 * 2 : name - Header to match against
1072 * Returns : 0 iff they match.
1074 *********************************************************************/
1075 static int match_actions_file_header_line(const char * line, const char * name)
1083 if ((line[0] != '{') || (line[1] != '{'))
1089 /* Look for optional whitespace */
1090 while ( (*line == ' ') || (*line == '\t') )
1095 /* Look for the specified name (case-insensitive) */
1097 if (0 != strncmpic(line, name, len))
1103 /* Look for optional whitespace */
1104 while ( (*line == ' ') || (*line == '\t') )
1109 /* Look for "}}" and end of string*/
1110 if ((line[0] != '}') || (line[1] != '}') || (line[2] != '\0'))
1120 /*********************************************************************
1122 * Function : match_actions_file_header_line
1124 * Description : Match an actions file {{header}} line
1127 * 1 : line - String from file. Must not start with
1128 * whitespace (else infinite loop!)
1129 * 2 : name - Destination for name
1130 * 2 : name - Destination for value
1132 * Returns : JB_ERR_OK on success
1133 * JB_ERR_MEMORY on out-of-memory
1134 * JB_ERR_PARSE if there's no "=" sign, or if there's
1135 * nothing before the "=" sign (but empty
1136 * values *after* the "=" sign are legal).
1138 *********************************************************************/
1139 static jb_err split_line_on_equals(const char * line, char ** pname, char ** pvalue)
1141 const char * name_end;
1142 const char * value_start;
1148 assert(*line != ' ');
1149 assert(*line != '\t');
1154 value_start = strchr(line, '=');
1155 if ((value_start == NULL) || (value_start == line))
1157 return JB_ERR_PARSE;
1160 name_end = value_start - 1;
1162 /* Eat any whitespace before the '=' */
1163 while ((*name_end == ' ') || (*name_end == '\t'))
1166 * we already know we must have at least 1 non-ws char
1167 * at start of buf - no need to check
1172 name_len = name_end - line + 1; /* Length excluding \0 */
1173 if (NULL == (*pname = (char *) malloc(name_len + 1)))
1175 return JB_ERR_MEMORY;
1177 strncpy(*pname, line, name_len);
1178 (*pname)[name_len] = '\0';
1180 /* Eat any the whitespace after the '=' */
1182 while ((*value_start == ' ') || (*value_start == '\t'))
1187 if (NULL == (*pvalue = strdup(value_start)))
1191 return JB_ERR_MEMORY;
1198 /*********************************************************************
1200 * Function : edit_parse_actions_file
1202 * Description : Parse an actions file in memory.
1204 * Passed linked list must have the "data" member
1205 * zeroed, and must contain valid "next" and
1206 * "unprocessed" fields. The "raw" and "prefix"
1207 * fields are ignored, and "type" is just overwritten.
1209 * Note that on error the file may have been
1213 * 1 : file = Actions file to be parsed in-place.
1215 * Returns : JB_ERR_OK on success
1216 * JB_ERR_MEMORY on out-of-memory
1217 * JB_ERR_PARSE on error
1219 *********************************************************************/
1220 jb_err edit_parse_actions_file(struct editable_file * file)
1222 struct file_line * cur_line;
1224 const char * text; /* Text from a line */
1225 char * name; /* For lines of the form name=value */
1226 char * value; /* For lines of the form name=value */
1227 struct action_alias * alias_list = NULL;
1228 jb_err err = JB_ERR_OK;
1230 /* alias_list contains the aliases defined in this file.
1231 * It might be better to use the "file_line.data" fields
1232 * in the relavent places instead.
1235 cur_line = file->lines;
1237 /* A note about blank line support: Blank lines should only
1238 * ever occur as the last line in the file. This function
1239 * is more forgiving than that - FILE_LINE_BLANK can occur
1243 /* Skip leading blanks. Should only happen if file is
1244 * empty (which is valid, but pointless).
1246 while ( (cur_line != NULL)
1247 && (cur_line->unprocessed[0] == '\0') )
1250 cur_line->type = FILE_LINE_BLANK;
1251 cur_line = cur_line->next;
1254 if ( (cur_line != NULL)
1255 && (cur_line->unprocessed[0] != '{') )
1257 /* File doesn't start with a header */
1258 file->parse_error = cur_line;
1259 file->parse_error_text = "First (non-comment) line of the file must contain a header.";
1260 return JB_ERR_PARSE;
1263 if ( (cur_line != NULL) && (0 ==
1264 match_actions_file_header_line(cur_line->unprocessed, "settings") ) )
1266 cur_line->type = FILE_LINE_SETTINGS_HEADER;
1268 cur_line = cur_line->next;
1269 while ((cur_line != NULL) && (cur_line->unprocessed[0] != '{'))
1271 if (cur_line->unprocessed[0])
1273 cur_line->type = FILE_LINE_SETTINGS_ENTRY;
1275 err = split_line_on_equals(cur_line->unprocessed,
1276 &cur_line->data.setting.name,
1277 &cur_line->data.setting.svalue);
1278 if (err == JB_ERR_MEMORY)
1282 else if (err != JB_ERR_OK)
1284 /* Line does not contain a name=value pair */
1285 file->parse_error = cur_line;
1286 file->parse_error_text = "Expected a name=value pair on this {{description}} line, but couldn't find one.";
1287 return JB_ERR_PARSE;
1292 cur_line->type = FILE_LINE_BLANK;
1294 cur_line = cur_line->next;
1298 if ( (cur_line != NULL) && (0 ==
1299 match_actions_file_header_line(cur_line->unprocessed, "description") ) )
1301 cur_line->type = FILE_LINE_DESCRIPTION_HEADER;
1303 cur_line = cur_line->next;
1304 while ((cur_line != NULL) && (cur_line->unprocessed[0] != '{'))
1306 if (cur_line->unprocessed[0])
1308 cur_line->type = FILE_LINE_DESCRIPTION_ENTRY;
1312 cur_line->type = FILE_LINE_BLANK;
1314 cur_line = cur_line->next;
1318 if ( (cur_line != NULL) && (0 ==
1319 match_actions_file_header_line(cur_line->unprocessed, "alias") ) )
1321 cur_line->type = FILE_LINE_ALIAS_HEADER;
1323 cur_line = cur_line->next;
1324 while ((cur_line != NULL) && (cur_line->unprocessed[0] != '{'))
1326 if (cur_line->unprocessed[0])
1328 /* define an alias */
1329 struct action_alias * new_alias;
1331 cur_line->type = FILE_LINE_ALIAS_ENTRY;
1333 err = split_line_on_equals(cur_line->unprocessed, &name, &value);
1334 if (err == JB_ERR_MEMORY)
1338 else if (err != JB_ERR_OK)
1340 /* Line does not contain a name=value pair */
1341 file->parse_error = cur_line;
1342 file->parse_error_text = "Expected a name=value pair on this {{alias}} line, but couldn't find one.";
1343 return JB_ERR_PARSE;
1346 if ((new_alias = zalloc(sizeof(*new_alias))) == NULL)
1351 free_alias_list(alias_list);
1352 return JB_ERR_MEMORY;
1355 err = get_actions(value, alias_list, new_alias->action);
1358 /* Invalid action or out of memory */
1362 free_alias_list(alias_list);
1363 if (err == JB_ERR_MEMORY)
1369 /* Line does not contain a name=value pair */
1370 file->parse_error = cur_line;
1371 file->parse_error_text = "This alias does not specify a valid set of actions.";
1372 return JB_ERR_PARSE;
1378 new_alias->name = name;
1381 new_alias->next = alias_list;
1382 alias_list = new_alias;
1386 cur_line->type = FILE_LINE_BLANK;
1388 cur_line = cur_line->next;
1392 /* Header done, process the main part of the file */
1393 while (cur_line != NULL)
1395 /* At this point, (cur_line->unprocessed[0] == '{') */
1396 assert(cur_line->unprocessed[0] == '{');
1397 text = cur_line->unprocessed + 1;
1398 len = strlen(text) - 1;
1399 if (text[len] != '}')
1401 /* No closing } on header */
1402 free_alias_list(alias_list);
1403 file->parse_error = cur_line;
1404 file->parse_error_text = "Headers starting with '{' must have a "
1405 "closing bracket ('}'). Headers starting with two brackets ('{{') "
1406 "must close with two brackets ('}}').";
1407 return JB_ERR_PARSE;
1412 /* An invalid {{ header. */
1413 free_alias_list(alias_list);
1414 file->parse_error = cur_line;
1415 file->parse_error_text = "Unknown or unexpected two-bracket header. "
1416 "Please remember that the system (two-bracket) headers must "
1417 "appear in the order {{settings}}, {{description}}, {{alias}}, "
1418 "and must appear before any actions (one-bracket) headers. "
1419 "Also note that system headers may not be repeated.";
1420 return JB_ERR_PARSE;
1423 while ( (*text == ' ') || (*text == '\t') )
1429 && ( (text[len - 1] == ' ')
1430 || (text[len - 1] == '\t') ) )
1435 cur_line->type = FILE_LINE_ACTION;
1437 /* Remove {} and make copy */
1438 if (NULL == (value = (char *) malloc(len + 1)))
1441 free_alias_list(alias_list);
1442 return JB_ERR_MEMORY;
1444 strncpy(value, text, len);
1448 err = get_actions(value, alias_list, cur_line->data.action);
1451 /* Invalid action or out of memory */
1453 free_alias_list(alias_list);
1454 if (err == JB_ERR_MEMORY)
1460 /* Line does not contain a name=value pair */
1461 file->parse_error = cur_line;
1462 file->parse_error_text = "This header does not specify a valid set of actions.";
1463 return JB_ERR_PARSE;
1467 /* Done with string - it was clobbered anyway */
1470 /* Process next line */
1471 cur_line = cur_line->next;
1473 /* Loop processing URL patterns */
1474 while ((cur_line != NULL) && (cur_line->unprocessed[0] != '{'))
1476 if (cur_line->unprocessed[0])
1478 /* Could parse URL here, but this isn't currently needed */
1480 cur_line->type = FILE_LINE_URL;
1484 cur_line->type = FILE_LINE_BLANK;
1486 cur_line = cur_line->next;
1488 } /* End main while(cur_line != NULL) loop */
1490 free_alias_list(alias_list);
1496 /*********************************************************************
1498 * Function : edit_read_file_lines
1500 * Description : Read all the lines of a file into memory.
1501 * Handles whitespace, comments and line continuation.
1504 * 1 : fp = File to read from. On return, this will be
1505 * at EOF but it will not have been closed.
1506 * 2 : pfile = Destination for a linked list of file_lines.
1507 * Will be set to NULL on error.
1509 * Returns : JB_ERR_OK on success
1510 * JB_ERR_MEMORY on out-of-memory
1512 *********************************************************************/
1513 jb_err edit_read_file_lines(FILE *fp, struct file_line ** pfile)
1515 struct file_line * first_line; /* Keep for return value or to free */
1516 struct file_line * cur_line; /* Current line */
1517 struct file_line * prev_line; /* Entry with prev_line->next = cur_line */
1525 cur_line = first_line = zalloc(sizeof(struct file_line));
1526 if (cur_line == NULL)
1528 return JB_ERR_MEMORY;
1531 cur_line->type = FILE_LINE_UNPROCESSED;
1533 rval = edit_read_line(fp, &cur_line->raw, &cur_line->prefix, &cur_line->unprocessed);
1536 /* Out of memory or empty file. */
1537 /* Note that empty file is not an error we propogate up */
1539 return ((rval == JB_ERR_FILE) ? JB_ERR_OK : rval);
1544 prev_line = cur_line;
1545 cur_line = prev_line->next = zalloc(sizeof(struct file_line));
1546 if (cur_line == NULL)
1549 edit_free_file_lines(first_line);
1550 return JB_ERR_MEMORY;
1553 cur_line->type = FILE_LINE_UNPROCESSED;
1555 rval = edit_read_line(fp, &cur_line->raw, &cur_line->prefix, &cur_line->unprocessed);
1556 if ((rval != JB_ERR_OK) && (rval != JB_ERR_FILE))
1559 edit_free_file_lines(first_line);
1560 return JB_ERR_MEMORY;
1564 while (rval != JB_ERR_FILE);
1568 /* We allocated one too many - free it */
1569 prev_line->next = NULL;
1572 *pfile = first_line;
1577 /*********************************************************************
1579 * Function : edit_read_file
1581 * Description : Read a complete file into memory.
1582 * Handles CGI parameter parsing. If requested, also
1583 * checks the file's modification timestamp.
1586 * 1 : csp = Current client state (buffers, headers, etc...)
1587 * 2 : parameters = map of cgi parameters.
1588 * 3 : require_version = true to check "ver" parameter.
1589 * 4 : suffix = File extension, e.g. ".action".
1590 * 5 : pfile = Destination for the file. Will be set
1594 * filename : The name of the file to read, without the
1595 * path or ".action" extension.
1596 * ver : (Only if require_version is nonzero)
1597 * Timestamp of the actions file. If wrong, this
1598 * function fails with JB_ERR_MODIFIED.
1600 * Returns : JB_ERR_OK on success
1601 * JB_ERR_MEMORY on out-of-memory
1602 * JB_ERR_CGI_PARAMS if "filename" was not specified
1604 * JB_ERR_FILE if the file cannot be opened or
1606 * JB_ERR_MODIFIED if version checking was requested and
1607 * failed - the file was modified outside
1608 * of this CGI editor instance.
1610 *********************************************************************/
1611 jb_err edit_read_file(struct client_state *csp,
1612 const struct map *parameters,
1613 int require_version,
1615 struct editable_file **pfile)
1617 struct file_line * lines;
1621 const char * identifier;
1622 struct editable_file * file;
1623 unsigned version = 0;
1624 struct stat statbuf[1];
1625 char version_buf[22];
1633 err = get_file_name_param(csp, parameters, "filename", suffix,
1634 &filename, &identifier);
1640 if (stat(filename, statbuf) < 0)
1642 /* Error, probably file not found. */
1646 version = (unsigned) statbuf->st_mtime;
1648 if (require_version)
1650 unsigned specified_version;
1651 err = get_number_param(csp, parameters, "ver", &specified_version);
1658 if (version != specified_version)
1660 return JB_ERR_MODIFIED;
1664 if (NULL == (fp = fopen(filename,"rt")))
1670 err = edit_read_file_lines(fp, &lines);
1680 file = (struct editable_file *) zalloc(sizeof(*file));
1684 edit_free_file_lines(lines);
1688 file->lines = lines;
1689 file->filename = filename;
1690 file->version = version;
1691 file->identifier = strdup(identifier);
1693 if (file->identifier == NULL)
1695 edit_free_file(file);
1696 return JB_ERR_MEMORY;
1699 /* Correct file->version_str */
1700 freez(file->version_str);
1701 snprintf(version_buf, 22, "%u", file->version);
1702 version_buf[21] = '\0';
1703 file->version_str = strdup(version_buf);
1704 if (version_buf == NULL)
1706 edit_free_file(file);
1707 return JB_ERR_MEMORY;
1715 /*********************************************************************
1717 * Function : edit_read_actions_file
1719 * Description : Read a complete actions file into memory.
1720 * Handles CGI parameter parsing. If requested, also
1721 * checks the file's modification timestamp.
1723 * If this function detects an error in the categories
1724 * JB_ERR_FILE, JB_ERR_MODIFIED, or JB_ERR_PARSE,
1725 * then it handles it by filling in the specified
1726 * response structure and returning JB_ERR_FILE.
1729 * 1 : csp = Current client state (buffers, headers, etc...)
1730 * 2 : rsp = HTTP response. Only filled in on error.
1731 * 2 : parameters = map of cgi parameters.
1732 * 3 : require_version = true to check "ver" parameter.
1733 * 4 : pfile = Destination for the file. Will be set
1737 * filename : The name of the actions file to read, without the
1738 * path or ".action" extension.
1739 * ver : (Only if require_version is nonzero)
1740 * Timestamp of the actions file. If wrong, this
1741 * function fails with JB_ERR_MODIFIED.
1743 * Returns : JB_ERR_OK on success
1744 * JB_ERR_MEMORY on out-of-memory
1745 * JB_ERR_CGI_PARAMS if "filename" was not specified
1747 * JB_ERR_FILE if the file does not contain valid data,
1748 * or if file cannot be opened or
1749 * contains no data, or if version
1750 * checking was requested and failed.
1752 *********************************************************************/
1753 jb_err edit_read_actions_file(struct client_state *csp,
1754 struct http_response *rsp,
1755 const struct map *parameters,
1756 int require_version,
1757 struct editable_file **pfile)
1760 struct editable_file *file;
1768 err = edit_read_file(csp, parameters, require_version, ".action", &file);
1771 /* Try to handle if possible */
1772 if (err == JB_ERR_FILE)
1774 err = cgi_error_file(csp, rsp, lookup(parameters, "filename"));
1776 else if (err == JB_ERR_MODIFIED)
1778 err = cgi_error_modified(csp, rsp, lookup(parameters, "filename"));
1780 if (err == JB_ERR_OK)
1783 * Signal to higher-level CGI code that there was a problem but we
1784 * handled it, they should just return JB_ERR_OK.
1791 err = edit_parse_actions_file(file);
1794 if (err == JB_ERR_PARSE)
1796 err = cgi_error_parse(csp, rsp, file);
1797 if (err == JB_ERR_OK)
1800 * Signal to higher-level CGI code that there was a problem but we
1801 * handled it, they should just return JB_ERR_OK.
1806 edit_free_file(file);
1815 /*********************************************************************
1817 * Function : get_file_name_param
1819 * Description : Get the name of the file to edit from the parameters
1820 * passed to a CGI function. This function handles
1821 * security checks such as blocking urls containing
1822 * "/" or ".", prepending the config file directory,
1823 * and adding the specified suffix.
1825 * (This is an essential security check, otherwise
1826 * users may be able to pass "../../../etc/passwd"
1827 * and overwrite the password file [linux], "prn:"
1828 * and print random data [Windows], etc...)
1830 * This function only allows filenames contining the
1831 * characters '-', '_', 'A'-'Z', 'a'-'z', and '0'-'9'.
1832 * That's probably too restrictive but at least it's
1836 * 1 : csp = Current client state (buffers, headers, etc...)
1837 * 2 : parameters = map of cgi parameters
1838 * 3 : suffix = File extension, e.g. ".actions"
1839 * 4 : pfilename = destination for full filename. Caller
1840 * free()s. Set to NULL on error.
1841 * 5 : pparam = destination for partial filename,
1842 * suitable for use in another URL. Allocated as part
1843 * of the map "parameters", so don't free it.
1844 * Set to NULL if not specified.
1846 * Returns : JB_ERR_OK on success
1847 * JB_ERR_MEMORY on out-of-memory
1848 * JB_ERR_CGI_PARAMS if "filename" was not specified
1851 *********************************************************************/
1852 static jb_err get_file_name_param(struct client_state *csp,
1853 const struct map *parameters,
1854 const char *param_name,
1857 const char **pparam)
1875 param = lookup(parameters, param_name);
1878 return JB_ERR_CGI_PARAMS;
1883 len = strlen(param);
1884 if (len >= FILENAME_MAX)
1887 return JB_ERR_CGI_PARAMS;
1890 /* Check every character to see if it's legal */
1892 while ((ch = *s++) != '\0')
1894 if ( ((ch < 'A') || (ch > 'Z'))
1895 && ((ch < 'a') || (ch > 'z'))
1896 && ((ch < '0') || (ch > '9'))
1900 /* Probable hack attempt. */
1901 return JB_ERR_CGI_PARAMS;
1905 /* Append extension */
1906 name = malloc(len + strlen(suffix) + 1);
1909 return JB_ERR_MEMORY;
1911 strcpy(name, param);
1912 strcpy(name + len, suffix);
1915 fullpath = make_path(csp->config->confdir, name);
1917 if (fullpath == NULL)
1919 return JB_ERR_MEMORY;
1923 *pfilename = fullpath;
1929 /*********************************************************************
1931 * Function : get_number_param
1933 * Description : Get a non-negative integer from the parameters
1934 * passed to a CGI function.
1937 * 1 : csp = Current client state (buffers, headers, etc...)
1938 * 2 : parameters = map of cgi parameters
1939 * 3 : name = Name of CGI parameter to read
1940 * 4 : pvalue = destination for value.
1941 * Set to -1 on error.
1943 * Returns : JB_ERR_OK on success
1944 * JB_ERR_MEMORY on out-of-memory
1945 * JB_ERR_CGI_PARAMS if the parameter was not specified
1948 *********************************************************************/
1949 static jb_err get_number_param(struct client_state *csp,
1950 const struct map *parameters,
1965 param = lookup(parameters, name);
1968 return JB_ERR_CGI_PARAMS;
1971 /* We don't use atoi because I want to check this carefully... */
1974 while ((ch = *param++) != '\0')
1976 if ((ch < '0') || (ch > '9'))
1978 return JB_ERR_CGI_PARAMS;
1985 * <limits.h> defines UINT_MAX
1987 * (UINT_MAX - ch) / 10 is the largest number that
1988 * can be safely multiplied by 10 then have ch added.
1990 if (value > ((UINT_MAX - (unsigned)ch) / 10U))
1992 return JB_ERR_CGI_PARAMS;
1995 value = value * 10 + ch;
2005 /*********************************************************************
2007 * Function : map_radio
2009 * Description : Map a set of radio button values. E.g. if you have
2010 * 3 radio buttons, declare them as:
2011 * <option type="radio" name="xyz" @xyz-a@>
2012 * <option type="radio" name="xyz" @xyz-b@>
2013 * <option type="radio" name="xyz" @xyz-c@>
2014 * Then map one of the @xyz-?@ variables to "checked"
2015 * and all the others to empty by calling:
2016 * map_radio(exports, "xyz", "abc", sel)
2017 * Where 'sel' is 'a', 'b', or 'c'.
2020 * 1 : exports = Exports map to modify.
2021 * 2 : optionname = name for map
2022 * 3 : values = null-terminated list of values;
2023 * 4 : value = Selected value.
2025 * CGI Parameters : None
2027 * Returns : JB_ERR_OK on success
2028 * JB_ERR_MEMORY on out-of-memory
2030 *********************************************************************/
2031 static jb_err map_radio(struct map * exports,
2032 const char * optionname,
2033 const char * values,
2045 len = strlen(optionname);
2046 buf = malloc(len + 3);
2049 return JB_ERR_MEMORY;
2052 strcpy(buf, optionname);
2057 while ((c = *values++) != '\0')
2062 if (map(exports, buf, 1, "", 1))
2065 return JB_ERR_MEMORY;
2071 if (map(exports, buf, 0, "checked", 1))
2074 return JB_ERR_MEMORY;
2081 /*********************************************************************
2083 * Function : actions_to_radio
2085 * Description : Converts a actionsfile entry into settings for
2086 * radio buttons and edit boxes on a HTML form.
2089 * 1 : exports = List of substitutions to add to.
2090 * 2 : action = Action to read
2092 * Returns : JB_ERR_OK on success
2093 * JB_ERR_MEMORY on out-of-memory
2095 *********************************************************************/
2096 static jb_err actions_to_radio(struct map * exports,
2097 const struct action_spec *action)
2099 unsigned mask = action->mask;
2100 unsigned add = action->add;
2108 mask = action->mask;
2111 /* sanity - prevents "-feature +feature" */
2115 #define DEFINE_ACTION_BOOL(name, bit) \
2116 if (!(mask & bit)) \
2118 current_mode = 'n'; \
2120 else if (add & bit) \
2122 current_mode = 'y'; \
2126 current_mode = 'x'; \
2128 if (map_radio(exports, name, "ynx", current_mode)) \
2130 return JB_ERR_MEMORY; \
2133 #define DEFINE_ACTION_STRING(name, bit, index) \
2134 DEFINE_ACTION_BOOL(name, bit); \
2137 #define DEFINE_CGI_PARAM_RADIO(name, bit, index, value, is_default) \
2140 checked = !strcmp(action->string[index], value); \
2144 checked = is_default; \
2146 mapped_param |= checked; \
2147 if (map(exports, name "-param-" value, 1, (checked ? "checked" : ""), 1)) \
2149 return JB_ERR_MEMORY; \
2152 #define DEFINE_CGI_PARAM_CUSTOM(name, bit, index, default_val) \
2153 if (map(exports, name "-param-custom", 1, \
2154 ((!mapped_param) ? "checked" : ""), 1)) \
2156 return JB_ERR_MEMORY; \
2158 if (map(exports, name "-param", 1, \
2159 (((add & bit) && !mapped_param) ? \
2160 action->string[index] : default_val), 1)) \
2162 return JB_ERR_MEMORY; \
2165 #define DEFINE_CGI_PARAM_NO_RADIO(name, bit, index, default_val) \
2166 if (map(exports, name "-param", 1, \
2167 ((add & bit) ? action->string[index] : default_val), 1)) \
2169 return JB_ERR_MEMORY; \
2172 #define DEFINE_ACTION_MULTI(name, index) \
2173 if (action->multi_add[index]->first) \
2175 current_mode = 'y'; \
2177 else if (action->multi_remove_all[index]) \
2179 current_mode = 'n'; \
2181 else if (action->multi_remove[index]->first) \
2183 current_mode = 'y'; \
2187 current_mode = 'x'; \
2189 if (map_radio(exports, name, "ynx", current_mode)) \
2191 return JB_ERR_MEMORY; \
2194 #define DEFINE_ACTION_ALIAS 0 /* No aliases for output */
2196 #include "actionlist.h"
2198 #undef DEFINE_ACTION_MULTI
2199 #undef DEFINE_ACTION_STRING
2200 #undef DEFINE_ACTION_BOOL
2201 #undef DEFINE_ACTION_ALIAS
2202 #undef DEFINE_CGI_PARAM_CUSTOM
2203 #undef DEFINE_CGI_PARAM_RADIO
2204 #undef DEFINE_CGI_PARAM_NO_RADIO
2210 /*********************************************************************
2212 * Function : javascriptify
2214 * Description : Converts a string into a form JavaScript will like.
2216 * Netscape 4's JavaScript sucks - it doesn't use
2217 * "id" parameters, so you have to set the "name"
2218 * used to submit a form element to something JavaScript
2219 * will like. (Or access the elements by index in an
2220 * array. That array contains >60 elements and will
2221 * be changed whenever we add a new action to the
2222 * editor, so I'm NOT going to use indexes that have
2223 * to be figured out by hand.)
2225 * Currently the only thing we have to worry about
2226 * is "-" ==> "_" conversion.
2228 * This is a length-preserving operation so it is
2229 * carried out in-place, no memory is allocated
2233 * 1 : identifier = String to make JavaScript-friendly.
2237 *********************************************************************/
2238 static void javascriptify(char * identifier)
2240 char * p = identifier;
2241 while (NULL != (p = strchr(p, '-')))
2248 /*********************************************************************
2250 * Function : actions_from_radio
2252 * Description : Converts a map of parameters passed to a CGI function
2253 * into an actionsfile entry.
2256 * 1 : parameters = parameters to the CGI call
2257 * 2 : action = Action to change. Must be valid before
2258 * the call, actions not specified will be
2261 * Returns : JB_ERR_OK on success
2262 * JB_ERR_MEMORY on out-of-memory
2264 *********************************************************************/
2265 static jb_err actions_from_radio(const struct map * parameters,
2266 struct action_spec *action)
2268 static int first_time = 1;
2272 const char * js_name;
2277 /* Statics are generally a potential race condition,
2278 * but in this case we're safe and don't need semaphores.
2279 * Be careful if you modify this function.
2283 #define JAVASCRIPTIFY(dest_var, string) \
2285 static char js_name_arr[] = string; \
2288 javascriptify(js_name_arr); \
2290 dest_var = js_name_arr; \
2293 #define DEFINE_ACTION_BOOL(name, bit) \
2294 JAVASCRIPTIFY(js_name, name); \
2295 param = lookup(parameters, js_name); \
2296 ch = ijb_toupper(param[0]); \
2299 action->add |= bit; \
2300 action->mask |= bit; \
2302 else if (ch == 'N') \
2304 action->add &= ~bit; \
2305 action->mask &= ~bit; \
2307 else if (ch == 'X') \
2309 action->add &= ~bit; \
2310 action->mask |= bit; \
2313 #define DEFINE_ACTION_STRING(name, bit, index) \
2314 JAVASCRIPTIFY(js_name, name); \
2315 param = lookup(parameters, js_name); \
2316 ch = ijb_toupper(param[0]); \
2319 JAVASCRIPTIFY(js_name, name "-mode"); \
2320 param = lookup(parameters, js_name); \
2321 if ((*param == '\0') || (0 == strcmp(param, "CUSTOM"))) \
2323 JAVASCRIPTIFY(js_name, name "-param"); \
2324 param = lookup(parameters, js_name); \
2326 if (*param != '\0') \
2328 if (NULL == (param_dup = strdup(param))) \
2330 return JB_ERR_MEMORY; \
2332 freez(action->string[index]); \
2333 action->add |= bit; \
2334 action->mask |= bit; \
2335 action->string[index] = param_dup; \
2338 else if (ch == 'N') \
2340 if (action->add & bit) \
2342 freez(action->string[index]); \
2344 action->add &= ~bit; \
2345 action->mask &= ~bit; \
2347 else if (ch == 'X') \
2349 if (action->add & bit) \
2351 freez(action->string[index]); \
2353 action->add &= ~bit; \
2354 action->mask |= bit; \
2357 #define DEFINE_ACTION_MULTI(name, index) \
2358 JAVASCRIPTIFY(js_name, name); \
2359 param = lookup(parameters, js_name); \
2360 ch = ijb_toupper((int)param[0]); \
2365 else if (ch == 'N') \
2367 list_remove_all(action->multi_add[index]); \
2368 list_remove_all(action->multi_remove[index]); \
2369 action->multi_remove_all[index] = 1; \
2371 else if (ch == 'X') \
2373 list_remove_all(action->multi_add[index]); \
2374 list_remove_all(action->multi_remove[index]); \
2375 action->multi_remove_all[index] = 0; \
2378 #define DEFINE_ACTION_ALIAS 0 /* No aliases for URL parsing */
2380 #include "actionlist.h"
2382 #undef DEFINE_ACTION_MULTI
2383 #undef DEFINE_ACTION_STRING
2384 #undef DEFINE_ACTION_BOOL
2385 #undef DEFINE_ACTION_ALIAS
2386 #undef JAVASCRIPTIFY
2394 /*********************************************************************
2396 * Function : cgi_error_modified
2398 * Description : CGI function that is called when a file is modified
2399 * outside the CGI editor.
2402 * 1 : csp = Current client state (buffers, headers, etc...)
2403 * 2 : rsp = http_response data structure for output
2404 * 3 : filename = The file that was modified.
2406 * CGI Parameters : none
2408 * Returns : JB_ERR_OK on success
2409 * JB_ERR_MEMORY on out-of-memory error.
2411 *********************************************************************/
2412 jb_err cgi_error_modified(struct client_state *csp,
2413 struct http_response *rsp,
2414 const char *filename)
2416 struct map *exports;
2423 if (NULL == (exports = default_exports(csp, NULL)))
2425 return JB_ERR_MEMORY;
2428 err = map(exports, "filename", 1, filename, 1);
2435 return template_fill_for_cgi(csp, "cgi-error-modified", exports, rsp);
2439 /*********************************************************************
2441 * Function : cgi_error_parse
2443 * Description : CGI function that is called when a file cannot
2444 * be parsed by the CGI editor.
2447 * 1 : csp = Current client state (buffers, headers, etc...)
2448 * 2 : rsp = http_response data structure for output
2449 * 3 : file = The file that was modified.
2451 * CGI Parameters : none
2453 * Returns : JB_ERR_OK on success
2454 * JB_ERR_MEMORY on out-of-memory error.
2456 *********************************************************************/
2457 jb_err cgi_error_parse(struct client_state *csp,
2458 struct http_response *rsp,
2459 struct editable_file *file)
2461 struct map *exports;
2463 struct file_line *cur_line;
2469 if (NULL == (exports = default_exports(csp, NULL)))
2471 return JB_ERR_MEMORY;
2474 err = map(exports, "filename", 1, file->identifier, 1);
2475 if (!err) err = map(exports, "parse-error", 1, file->parse_error_text, 1);
2477 cur_line = file->parse_error;
2480 if (!err) err = map(exports, "line-raw", 1, html_encode(cur_line->raw), 0);
2481 if (!err) err = map(exports, "line-data", 1, html_encode(cur_line->unprocessed), 0);
2489 return template_fill_for_cgi(csp, "cgi-error-parse", exports, rsp);
2493 /*********************************************************************
2495 * Function : cgi_error_file
2497 * Description : CGI function that is called when a file cannot be
2498 * opened by the CGI editor.
2501 * 1 : csp = Current client state (buffers, headers, etc...)
2502 * 2 : rsp = http_response data structure for output
2503 * 3 : filename = The file that was modified.
2505 * CGI Parameters : none
2507 * Returns : JB_ERR_OK on success
2508 * JB_ERR_MEMORY on out-of-memory error.
2510 *********************************************************************/
2511 jb_err cgi_error_file(struct client_state *csp,
2512 struct http_response *rsp,
2513 const char *filename)
2515 struct map *exports;
2522 if (NULL == (exports = default_exports(csp, NULL)))
2524 return JB_ERR_MEMORY;
2527 err = map(exports, "filename", 1, filename, 1);
2534 return template_fill_for_cgi(csp, "cgi-error-file", exports, rsp);
2538 /*********************************************************************
2540 * Function : cgi_error_bad_param
2542 * Description : CGI function that is called if the parameters
2543 * (query string) for a CGI were wrong.
2546 * 1 : csp = Current client state (buffers, headers, etc...)
2547 * 2 : rsp = http_response data structure for output
2549 * CGI Parameters : none
2551 * Returns : JB_ERR_OK on success
2552 * JB_ERR_MEMORY on out-of-memory error.
2554 *********************************************************************/
2555 jb_err cgi_error_disabled(struct client_state *csp,
2556 struct http_response *rsp)
2558 struct map *exports;
2563 if (NULL == (exports = default_exports(csp, NULL)))
2565 return JB_ERR_MEMORY;
2568 return template_fill_for_cgi(csp, "cgi-error-disabled", exports, rsp);
2572 /*********************************************************************
2574 * Function : cgi_edit_actions
2576 * Description : CGI function that allows the user to choose which
2577 * actions file to edit.
2580 * 1 : csp = Current client state (buffers, headers, etc...)
2581 * 2 : rsp = http_response data structure for output
2582 * 3 : parameters = map of cgi parameters
2584 * CGI Parameters : None
2586 * Returns : JB_ERR_OK on success
2587 * JB_ERR_MEMORY on out-of-memory error
2589 *********************************************************************/
2590 jb_err cgi_edit_actions(struct client_state *csp,
2591 struct http_response *rsp,
2592 const struct map *parameters)
2595 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
2597 return cgi_error_disabled(csp, rsp);
2600 /* FIXME: Incomplete */
2601 rsp->status = strdup("302 Local Redirect from Junkbuster");
2602 if (rsp->status == NULL)
2604 return JB_ERR_MEMORY;
2606 if (enlist_unique_header(rsp->headers, "Location",
2607 CGI_PREFIX "edit-actions-list?filename=ijb"))
2611 return JB_ERR_MEMORY;
2618 /*********************************************************************
2620 * Function : cgi_edit_actions_list
2622 * Description : CGI function that edits the actions list.
2623 * FIXME: This function shouldn't FATAL ever.
2624 * FIXME: This function doesn't check the retval of map()
2626 * 1 : csp = Current client state (buffers, headers, etc...)
2627 * 2 : rsp = http_response data structure for output
2628 * 3 : parameters = map of cgi parameters
2630 * CGI Parameters : filename
2632 * Returns : JB_ERR_OK on success
2633 * JB_ERR_MEMORY on out-of-memory
2634 * JB_ERR_FILE if the file cannot be opened or
2636 * JB_ERR_CGI_PARAMS if "filename" was not specified
2639 *********************************************************************/
2640 jb_err cgi_edit_actions_list(struct client_state *csp,
2641 struct http_response *rsp,
2642 const struct map *parameters)
2644 char * section_template;
2645 char * url_template;
2650 struct map * exports;
2651 struct map * section_exports;
2652 struct map * url_exports;
2653 struct editable_file * file;
2654 struct file_line * cur_line;
2655 unsigned line_number = 0;
2659 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
2661 return cgi_error_disabled(csp, rsp);
2664 err = edit_read_actions_file(csp, rsp, parameters, 0, &file);
2667 /* No filename specified, can't read file, or out of memory. */
2668 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
2671 if (NULL == (exports = default_exports(csp, NULL)))
2673 edit_free_file(file);
2674 return JB_ERR_MEMORY;
2677 err = map(exports, "filename", 1, file->identifier, 1);
2678 if (!err) err = map(exports, "ver", 1, file->version_str, 1);
2682 edit_free_file(file);
2687 /* Should do all global exports above this point */
2689 err = template_load(csp, §ion_template, "edit-actions-list-section");
2692 edit_free_file(file);
2694 if (err == JB_ERR_FILE)
2696 return cgi_error_no_template(csp, rsp, "edit-actions-list-section");
2701 err = template_load(csp, &url_template, "edit-actions-list-url");
2704 free(section_template);
2705 edit_free_file(file);
2707 if (err == JB_ERR_FILE)
2709 return cgi_error_no_template(csp, rsp, "edit-actions-list-url");
2714 err = template_fill(§ion_template, exports);
2718 edit_free_file(file);
2724 err = template_fill(&url_template, exports);
2727 free(section_template);
2728 edit_free_file(file);
2733 /* Find start of actions in file */
2734 cur_line = file->lines;
2736 while ((cur_line != NULL) && (cur_line->type != FILE_LINE_ACTION))
2738 cur_line = cur_line->next;
2742 if (NULL == (sections = strdup("")))
2744 free(section_template);
2746 edit_free_file(file);
2748 return JB_ERR_MEMORY;
2751 while ((cur_line != NULL) && (cur_line->type == FILE_LINE_ACTION))
2753 if (NULL == (section_exports = new_map()))
2756 free(section_template);
2758 edit_free_file(file);
2760 return JB_ERR_MEMORY;
2763 snprintf(buf, 50, "%d", line_number);
2764 err = map(section_exports, "sectionid", 1, buf, 1);
2765 if (!err) err = map(section_exports, "actions", 1,
2766 actions_to_html(cur_line->data.action), 0);
2769 && (cur_line->next != NULL)
2770 && (cur_line->next->type == FILE_LINE_URL))
2772 /* This section contains at least one URL, don't allow delete */
2773 err = map_block_killer(section_exports, "empty-section");
2779 free(section_template);
2781 edit_free_file(file);
2783 free_map(section_exports);
2787 /* Should do all section-specific exports above this point */
2789 if (NULL == (urls = strdup("")))
2792 free(section_template);
2794 edit_free_file(file);
2796 free_map(section_exports);
2797 return JB_ERR_MEMORY;
2802 cur_line = cur_line->next;
2805 while ((cur_line != NULL) && (cur_line->type == FILE_LINE_URL))
2807 if (NULL == (url_exports = new_map()))
2811 free(section_template);
2813 edit_free_file(file);
2815 free_map(section_exports);
2816 return JB_ERR_MEMORY;
2819 snprintf(buf, 50, "%d", line_number);
2820 err = map(url_exports, "urlid", 1, buf, 1);
2822 snprintf(buf, 50, "%d", url_1_2);
2823 if (!err) err = map(url_exports, "url-1-2", 1, buf, 1);
2825 if (!err) err = map(url_exports, "url-html", 1,
2826 html_encode(cur_line->unprocessed), 0);
2827 if (!err) err = map(url_exports, "url", 1,
2828 url_encode(cur_line->unprocessed), 0);
2834 free(section_template);
2836 edit_free_file(file);
2838 free_map(section_exports);
2839 free_map(url_exports);
2843 if (NULL == (s = strdup(url_template)))
2847 free(section_template);
2849 edit_free_file(file);
2851 free_map(section_exports);
2852 free_map(url_exports);
2853 return JB_ERR_MEMORY;
2856 err = template_fill(&s, section_exports);
2857 if (!err) err = template_fill(&s, url_exports);
2858 if (!err) err = string_append(&urls, s);
2860 free_map(url_exports);
2867 free(section_template);
2869 edit_free_file(file);
2871 free_map(section_exports);
2875 url_1_2 = 3 - url_1_2;
2877 cur_line = cur_line->next;
2881 err = map(section_exports, "urls", 1, urls, 0);
2886 free(section_template);
2888 edit_free_file(file);
2890 free_map(section_exports);
2894 /* Could also do section-specific exports here, but it wouldn't be as fast */
2896 if (NULL == (s = strdup(section_template)))
2899 free(section_template);
2901 edit_free_file(file);
2903 free_map(section_exports);
2904 return JB_ERR_MEMORY;
2907 err = template_fill(&s, section_exports);
2908 if (!err) err = string_append(§ions, s);
2911 free_map(section_exports);
2916 free(section_template);
2918 edit_free_file(file);
2924 edit_free_file(file);
2925 free(section_template);
2928 err = map(exports, "sections", 1, sections, 0);
2935 /* Could also do global exports here, but it wouldn't be as fast */
2937 return template_fill_for_cgi(csp, "edit-actions-list", exports, rsp);
2941 /*********************************************************************
2943 * Function : cgi_edit_actions
2945 * Description : CGI function that edits the Actions list.
2948 * 1 : csp = Current client state (buffers, headers, etc...)
2949 * 2 : rsp = http_response data structure for output
2950 * 3 : parameters = map of cgi parameters
2952 * CGI Parameters : None
2954 * Returns : JB_ERR_OK on success
2955 * JB_ERR_MEMORY on out-of-memory
2956 * JB_ERR_CGI_PARAMS if the CGI parameters are not
2957 * specified or not valid.
2959 *********************************************************************/
2960 jb_err cgi_edit_actions_for_url(struct client_state *csp,
2961 struct http_response *rsp,
2962 const struct map *parameters)
2964 struct map * exports;
2966 struct editable_file * file;
2967 struct file_line * cur_line;
2968 unsigned line_number;
2971 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
2973 return cgi_error_disabled(csp, rsp);
2976 err = get_number_param(csp, parameters, "section", §ionid);
2982 err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
2985 /* No filename specified, can't read file, modified, or out of memory. */
2986 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
2989 cur_line = file->lines;
2991 for (line_number = 1; (cur_line != NULL) && (line_number < sectionid); line_number++)
2993 cur_line = cur_line->next;
2996 if ( (cur_line == NULL)
2997 || (line_number != sectionid)
2999 || (cur_line->type != FILE_LINE_ACTION))
3001 /* Invalid "sectionid" parameter */
3002 edit_free_file(file);
3003 return JB_ERR_CGI_PARAMS;
3006 if (NULL == (exports = default_exports(csp, NULL)))
3008 edit_free_file(file);
3009 return JB_ERR_MEMORY;
3012 err = map(exports, "filename", 1, file->identifier, 1);
3013 if (!err) err = map(exports, "ver", 1, file->version_str, 1);
3014 if (!err) err = map(exports, "section", 1, lookup(parameters, "section"), 1);
3016 if (!err) err = actions_to_radio(exports, cur_line->data.action);
3018 edit_free_file(file);
3026 return template_fill_for_cgi(csp, "edit-actions-for-url", exports, rsp);
3030 /*********************************************************************
3032 * Function : cgi_edit_actions_submit
3034 * Description : CGI function that actually edits the Actions list.
3037 * 1 : csp = Current client state (buffers, headers, etc...)
3038 * 2 : rsp = http_response data structure for output
3039 * 3 : parameters = map of cgi parameters
3041 * CGI Parameters : None
3043 * Returns : JB_ERR_OK on success
3044 * JB_ERR_MEMORY on out-of-memory
3045 * JB_ERR_CGI_PARAMS if the CGI parameters are not
3046 * specified or not valid.
3048 *********************************************************************/
3049 jb_err cgi_edit_actions_submit(struct client_state *csp,
3050 struct http_response *rsp,
3051 const struct map *parameters)
3057 struct editable_file * file;
3058 struct file_line * cur_line;
3059 unsigned line_number;
3063 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3065 return cgi_error_disabled(csp, rsp);
3068 err = get_number_param(csp, parameters, "section", §ionid);
3074 err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3077 /* No filename specified, can't read file, modified, or out of memory. */
3078 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3081 cur_line = file->lines;
3083 for (line_number = 1; (cur_line != NULL) && (line_number < sectionid); line_number++)
3085 cur_line = cur_line->next;
3088 if ( (cur_line == NULL)
3089 || (line_number != sectionid)
3091 || (cur_line->type != FILE_LINE_ACTION))
3093 /* Invalid "sectionid" parameter */
3094 edit_free_file(file);
3095 return JB_ERR_CGI_PARAMS;
3098 err = actions_from_radio(parameters, cur_line->data.action);
3102 edit_free_file(file);
3106 if (NULL == (actiontext = actions_to_text(cur_line->data.action)))
3109 edit_free_file(file);
3110 return JB_ERR_MEMORY;
3113 len = strlen(actiontext);
3117 * Empty action - must special-case this.
3118 * Simply setting len to 1 is sufficient...
3123 if (NULL == (newtext = malloc(len + 2)))
3127 edit_free_file(file);
3128 return JB_ERR_MEMORY;
3130 strcpy(newtext, actiontext);
3134 newtext[len + 1] = '\0';
3136 freez(cur_line->raw);
3137 freez(cur_line->unprocessed);
3138 cur_line->unprocessed = newtext;
3140 err = edit_write_file(file);
3143 /* Error writing file */
3144 edit_free_file(file);
3148 target = strdup(CGI_PREFIX "edit-actions-list?filename=");
3149 string_append(&target, file->identifier);
3151 edit_free_file(file);
3156 return JB_ERR_MEMORY;
3159 rsp->status = strdup("302 Local Redirect from Junkbuster");
3160 if (rsp->status == NULL)
3163 return JB_ERR_MEMORY;
3165 err = enlist_unique_header(rsp->headers, "Location", target);
3172 /*********************************************************************
3174 * Function : cgi_edit_actions_url
3176 * Description : CGI function that actually edits a URL pattern in
3180 * 1 : csp = Current client state (buffers, headers, etc...)
3181 * 2 : rsp = http_response data structure for output
3182 * 3 : parameters = map of cgi parameters
3185 * filename : Identifies the file to edit
3186 * ver : File's last-modified time
3187 * section : Line number of section to edit
3188 * pattern : Line number of pattern to edit
3189 * newval : New value for pattern
3191 * Returns : JB_ERR_OK on success
3192 * JB_ERR_MEMORY on out-of-memory
3193 * JB_ERR_CGI_PARAMS if the CGI parameters are not
3194 * specified or not valid.
3196 *********************************************************************/
3197 jb_err cgi_edit_actions_url(struct client_state *csp,
3198 struct http_response *rsp,
3199 const struct map *parameters)
3203 const char * newval;
3205 struct editable_file * file;
3206 struct file_line * cur_line;
3207 unsigned line_number;
3211 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3213 return cgi_error_disabled(csp, rsp);
3216 err = get_number_param(csp, parameters, "section", §ionid);
3222 err = get_number_param(csp, parameters, "pattern", &patternid);
3228 newval = lookup(parameters, "newval");
3230 if ((*newval == '\0') || (sectionid < 1U) || (patternid < 1U))
3232 return JB_ERR_CGI_PARAMS;
3235 err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3238 /* No filename specified, can't read file, modified, or out of memory. */
3239 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3243 cur_line = file->lines;
3245 while ((cur_line != NULL) && (line_number < sectionid))
3247 cur_line = cur_line->next;
3251 if ( (cur_line == NULL)
3252 || (cur_line->type != FILE_LINE_ACTION))
3254 /* Invalid "sectionid" parameter */
3255 edit_free_file(file);
3256 return JB_ERR_CGI_PARAMS;
3259 while (line_number < patternid)
3261 cur_line = cur_line->next;
3264 if ( (cur_line == NULL)
3265 || ( (cur_line->type != FILE_LINE_URL)
3266 && (cur_line->type != FILE_LINE_BLANK) ) )
3268 /* Invalid "patternid" parameter */
3269 edit_free_file(file);
3270 return JB_ERR_CGI_PARAMS;
3274 if (cur_line->type != FILE_LINE_URL)
3276 /* Invalid "patternid" parameter */
3277 edit_free_file(file);
3278 return JB_ERR_CGI_PARAMS;
3281 /* At this point, the line to edit is in cur_line */
3283 new_pattern = strdup(newval);
3284 if (NULL == new_pattern)
3286 edit_free_file(file);
3287 return JB_ERR_MEMORY;
3290 freez(cur_line->raw);
3291 freez(cur_line->unprocessed);
3292 cur_line->unprocessed = new_pattern;
3294 err = edit_write_file(file);
3297 /* Error writing file */
3298 edit_free_file(file);
3302 target = strdup(CGI_PREFIX "edit-actions-list?filename=");
3303 string_append(&target, file->identifier);
3305 edit_free_file(file);
3310 return JB_ERR_MEMORY;
3313 rsp->status = strdup("302 Local Redirect from Junkbuster");
3314 if (rsp->status == NULL)
3317 return JB_ERR_MEMORY;
3319 err = enlist_unique_header(rsp->headers, "Location", target);
3326 /*********************************************************************
3328 * Function : cgi_edit_actions_add_url
3330 * Description : CGI function that actually adds a URL pattern to
3334 * 1 : csp = Current client state (buffers, headers, etc...)
3335 * 2 : rsp = http_response data structure for output
3336 * 3 : parameters = map of cgi parameters
3339 * filename : Identifies the file to edit
3340 * ver : File's last-modified time
3341 * section : Line number of section to edit
3342 * newval : New pattern
3344 * Returns : JB_ERR_OK on success
3345 * JB_ERR_MEMORY on out-of-memory
3346 * JB_ERR_CGI_PARAMS if the CGI parameters are not
3347 * specified or not valid.
3349 *********************************************************************/
3350 jb_err cgi_edit_actions_add_url(struct client_state *csp,
3351 struct http_response *rsp,
3352 const struct map *parameters)
3355 const char * newval;
3357 struct file_line * new_line;
3358 struct editable_file * file;
3359 struct file_line * cur_line;
3360 unsigned line_number;
3363 struct url_spec compiled[1];
3365 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3367 return cgi_error_disabled(csp, rsp);
3370 err = get_number_param(csp, parameters, "section", §ionid);
3376 newval = lookup(parameters, "newval");
3378 if ((*newval == '\0') || (sectionid < 1U))
3380 return JB_ERR_CGI_PARAMS;
3383 /* Check that regex is valid */
3384 err = create_url_spec(compiled, newval);
3387 return (err == JB_ERR_MEMORY) ? JB_ERR_MEMORY : JB_ERR_CGI_PARAMS;
3389 free_url_spec(compiled);
3391 err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3394 /* No filename specified, can't read file, modified, or out of memory. */
3395 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3399 cur_line = file->lines;
3401 while ((cur_line != NULL) && (line_number < sectionid))
3403 cur_line = cur_line->next;
3407 if ( (cur_line == NULL)
3408 || (cur_line->type != FILE_LINE_ACTION))
3410 /* Invalid "sectionid" parameter */
3411 edit_free_file(file);
3412 return JB_ERR_CGI_PARAMS;
3415 /* At this point, the section header is in cur_line - add after this. */
3417 new_pattern = strdup(newval);
3418 if (NULL == new_pattern)
3420 edit_free_file(file);
3421 return JB_ERR_MEMORY;
3424 /* Allocate the new line */
3425 new_line = (struct file_line *)zalloc(sizeof(*new_line));
3426 if (new_line == NULL)
3429 edit_free_file(file);
3430 return JB_ERR_MEMORY;
3433 /* Fill in the data members of the new line */
3434 new_line->raw = NULL;
3435 new_line->prefix = NULL;
3436 new_line->unprocessed = new_pattern;
3437 new_line->type = FILE_LINE_URL;
3439 /* Link new_line into the list, after cur_line */
3440 new_line->next = cur_line->next;
3441 cur_line->next = new_line;
3443 /* Done making changes, now commit */
3445 err = edit_write_file(file);
3448 /* Error writing file */
3449 edit_free_file(file);
3453 target = strdup(CGI_PREFIX "edit-actions-list?filename=");
3454 string_append(&target, file->identifier);
3456 edit_free_file(file);
3461 return JB_ERR_MEMORY;
3464 rsp->status = strdup("302 Local Redirect from Junkbuster");
3465 if (rsp->status == NULL)
3468 return JB_ERR_MEMORY;
3470 err = enlist_unique_header(rsp->headers, "Location", target);
3477 /*********************************************************************
3479 * Function : cgi_edit_actions_remove_url
3481 * Description : CGI function that actually removes a URL pattern from
3485 * 1 : csp = Current client state (buffers, headers, etc...)
3486 * 2 : rsp = http_response data structure for output
3487 * 3 : parameters = map of cgi parameters
3490 * filename : Identifies the file to edit
3491 * ver : File's last-modified time
3492 * section : Line number of section to edit
3493 * pattern : Line number of pattern to edit
3495 * Returns : JB_ERR_OK on success
3496 * JB_ERR_MEMORY on out-of-memory
3497 * JB_ERR_CGI_PARAMS if the CGI parameters are not
3498 * specified or not valid.
3500 *********************************************************************/
3501 jb_err cgi_edit_actions_remove_url(struct client_state *csp,
3502 struct http_response *rsp,
3503 const struct map *parameters)
3507 struct editable_file * file;
3508 struct file_line * cur_line;
3509 struct file_line * prev_line;
3510 unsigned line_number;
3514 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3516 return cgi_error_disabled(csp, rsp);
3519 err = get_number_param(csp, parameters, "section", §ionid);
3525 err = get_number_param(csp, parameters, "pattern", &patternid);
3532 err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3535 /* No filename specified, can't read file, modified, or out of memory. */
3536 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3540 cur_line = file->lines;
3542 while ((cur_line != NULL) && (line_number < sectionid))
3544 cur_line = cur_line->next;
3548 if ( (cur_line == NULL)
3549 || (cur_line->type != FILE_LINE_ACTION))
3551 /* Invalid "sectionid" parameter */
3552 edit_free_file(file);
3553 return JB_ERR_CGI_PARAMS;
3557 while (line_number < patternid)
3559 prev_line = cur_line;
3560 cur_line = cur_line->next;
3563 if ( (cur_line == NULL)
3564 || ( (cur_line->type != FILE_LINE_URL)
3565 && (cur_line->type != FILE_LINE_BLANK) ) )
3567 /* Invalid "patternid" parameter */
3568 edit_free_file(file);
3569 return JB_ERR_CGI_PARAMS;
3573 if (cur_line->type != FILE_LINE_URL)
3575 /* Invalid "patternid" parameter */
3576 edit_free_file(file);
3577 return JB_ERR_CGI_PARAMS;
3582 /* At this point, the line to remove is in cur_line, and the previous
3583 * one is in prev_line
3586 /* Unlink cur_line */
3587 prev_line->next = cur_line->next;
3588 cur_line->next = NULL;
3591 edit_free_file_lines(cur_line);
3593 err = edit_write_file(file);
3596 /* Error writing file */
3597 edit_free_file(file);
3601 target = strdup(CGI_PREFIX "edit-actions-list?filename=");
3602 string_append(&target, file->identifier);
3604 edit_free_file(file);
3609 return JB_ERR_MEMORY;
3612 rsp->status = strdup("302 Local Redirect from Junkbuster");
3613 if (rsp->status == NULL)
3616 return JB_ERR_MEMORY;
3618 err = enlist_unique_header(rsp->headers, "Location", target);
3625 /*********************************************************************
3627 * Function : cgi_edit_actions_section_remove
3629 * Description : CGI function that actually removes a whole section from
3630 * the actions file. The section must be empty first
3631 * (else JB_ERR_CGI_PARAMS).
3634 * 1 : csp = Current client state (buffers, headers, etc...)
3635 * 2 : rsp = http_response data structure for output
3636 * 3 : parameters = map of cgi parameters
3639 * filename : Identifies the file to edit
3640 * ver : File's last-modified time
3641 * section : Line number of section to edit
3642 * pattern : Line number of pattern to edit
3644 * Returns : JB_ERR_OK on success
3645 * JB_ERR_MEMORY on out-of-memory
3646 * JB_ERR_CGI_PARAMS if the CGI parameters are not
3647 * specified or not valid.
3649 *********************************************************************/
3650 jb_err cgi_edit_actions_section_remove(struct client_state *csp,
3651 struct http_response *rsp,
3652 const struct map *parameters)
3655 struct editable_file * file;
3656 struct file_line * cur_line;
3657 struct file_line * prev_line;
3658 unsigned line_number;
3662 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3664 return cgi_error_disabled(csp, rsp);
3667 err = get_number_param(csp, parameters, "section", §ionid);
3673 err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3676 /* No filename specified, can't read file, modified, or out of memory. */
3677 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3681 cur_line = file->lines;
3684 while ((cur_line != NULL) && (line_number < sectionid))
3686 prev_line = cur_line;
3687 cur_line = cur_line->next;
3691 if ( (cur_line == NULL)
3692 || (cur_line->type != FILE_LINE_ACTION) )
3694 /* Invalid "sectionid" parameter */
3695 edit_free_file(file);
3696 return JB_ERR_CGI_PARAMS;
3699 if ( (cur_line->next != NULL)
3700 && (cur_line->next->type == FILE_LINE_URL) )
3702 /* Section not empty. */
3703 edit_free_file(file);
3704 return JB_ERR_CGI_PARAMS;
3707 /* At this point, the line to remove is in cur_line, and the previous
3708 * one is in prev_line
3711 /* Unlink cur_line */
3712 if (prev_line == NULL)
3714 /* Removing the first line from the file */
3715 file->lines = cur_line->next;
3719 prev_line->next = cur_line->next;
3721 cur_line->next = NULL;
3724 edit_free_file_lines(cur_line);
3726 err = edit_write_file(file);
3729 /* Error writing file */
3730 edit_free_file(file);
3734 target = strdup(CGI_PREFIX "edit-actions-list?filename=");
3735 string_append(&target, file->identifier);
3737 edit_free_file(file);
3742 return JB_ERR_MEMORY;
3745 rsp->status = strdup("302 Local Redirect from Junkbuster");
3746 if (rsp->status == NULL)
3749 return JB_ERR_MEMORY;
3751 err = enlist_unique_header(rsp->headers, "Location", target);
3758 /*********************************************************************
3760 * Function : cgi_edit_actions_section_add
3762 * Description : CGI function that adds a new empty section to
3766 * 1 : csp = Current client state (buffers, headers, etc...)
3767 * 2 : rsp = http_response data structure for output
3768 * 3 : parameters = map of cgi parameters
3771 * filename : Identifies the file to edit
3772 * ver : File's last-modified time
3773 * section : Line number of section to add after, 0 for start
3776 * Returns : JB_ERR_OK on success
3777 * JB_ERR_MEMORY on out-of-memory
3778 * JB_ERR_CGI_PARAMS if the CGI parameters are not
3779 * specified or not valid.
3781 *********************************************************************/
3782 jb_err cgi_edit_actions_section_add(struct client_state *csp,
3783 struct http_response *rsp,
3784 const struct map *parameters)
3787 struct file_line * new_line;
3789 struct editable_file * file;
3790 struct file_line * cur_line;
3791 unsigned line_number;
3795 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3797 return cgi_error_disabled(csp, rsp);
3800 err = get_number_param(csp, parameters, "section", §ionid);
3806 err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3809 /* No filename specified, can't read file, modified, or out of memory. */
3810 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3814 cur_line = file->lines;
3818 /* Add to start of file */
3819 if (cur_line != NULL)
3821 /* There's something in the file, find the line before the first
3824 while ( (cur_line->next != NULL)
3825 && (cur_line->next->type != FILE_LINE_ACTION) )
3827 cur_line = cur_line->next;
3834 /* Add after stated section. */
3835 while ((cur_line != NULL) && (line_number < sectionid))
3837 cur_line = cur_line->next;
3841 if ( (cur_line == NULL)
3842 || (cur_line->type != FILE_LINE_ACTION))
3844 /* Invalid "sectionid" parameter */
3845 edit_free_file(file);
3846 return JB_ERR_CGI_PARAMS;
3849 /* Skip through the section to find the last line in it. */
3850 while ( (cur_line->next != NULL)
3851 && (cur_line->next->type != FILE_LINE_ACTION) )
3853 cur_line = cur_line->next;
3858 /* At this point, the last line in the previous section is in cur_line
3859 * - add after this. (Or if we need to add as the first line, cur_line
3863 new_text = strdup("{}");
3864 if (NULL == new_text)
3866 edit_free_file(file);
3867 return JB_ERR_MEMORY;
3870 /* Allocate the new line */
3871 new_line = (struct file_line *)zalloc(sizeof(*new_line));
3872 if (new_line == NULL)
3875 edit_free_file(file);
3876 return JB_ERR_MEMORY;
3879 /* Fill in the data members of the new line */
3880 new_line->raw = NULL;
3881 new_line->prefix = NULL;
3882 new_line->unprocessed = new_text;
3883 new_line->type = FILE_LINE_ACTION;
3885 if (cur_line != NULL)
3887 /* Link new_line into the list, after cur_line */
3888 new_line->next = cur_line->next;
3889 cur_line->next = new_line;
3893 /* Link new_line into the list, as first line */
3894 new_line->next = file->lines;
3895 file->lines = new_line;
3898 /* Done making changes, now commit */
3900 err = edit_write_file(file);
3903 /* Error writing file */
3904 edit_free_file(file);
3908 target = strdup(CGI_PREFIX "edit-actions-list?filename=");
3909 string_append(&target, file->identifier);
3911 edit_free_file(file);
3916 return JB_ERR_MEMORY;
3919 rsp->status = strdup("302 Local Redirect from Junkbuster");
3920 if (rsp->status == NULL)
3923 return JB_ERR_MEMORY;
3925 err = enlist_unique_header(rsp->headers, "Location", target);
3932 /*********************************************************************
3934 * Function : cgi_toggle
3936 * Description : CGI function that adds a new empty section to
3940 * 1 : csp = Current client state (buffers, headers, etc...)
3941 * 2 : rsp = http_response data structure for output
3942 * 3 : parameters = map of cgi parameters
3945 * set : If present, how to change toggle setting:
3946 * "enable", "disable", "toggle", or none (default).
3947 * mini : If present, use mini reply template.
3949 * Returns : JB_ERR_OK on success
3950 * JB_ERR_MEMORY on out-of-memory
3952 *********************************************************************/
3953 jb_err cgi_toggle(struct client_state *csp,
3954 struct http_response *rsp,
3955 const struct map *parameters)
3957 struct map *exports;
3959 const char *template_name;
3966 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_TOGGLE))
3968 return cgi_error_disabled(csp, rsp);
3971 if (NULL == (exports = default_exports(csp, "toggle")))
3973 return JB_ERR_MEMORY;
3976 mode = *(lookup(parameters, "set"));
3983 else if (mode == 'd')
3988 else if (mode == 't')
3991 g_bToggleIJB = !g_bToggleIJB;
3994 err = map_conditional(exports, "enabled", g_bToggleIJB);
4001 template_name = (*(lookup(parameters, "mini"))
4005 return template_fill_for_cgi(csp, template_name, exports, rsp);
4009 #endif /* def FEATURE_CGI_EDIT_ACTIONS */