1 const char cgiedit_rcs[] = "$Id: cgiedit.c,v 1.7 2001/11/13 00:28:24 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.7 2001/11/13 00:28:24 jongfoster
39 * - Renaming parameters from edit-actions-for-url so that they only
40 * contain legal JavaScript characters. If we wanted to write
41 * JavaScript that worked with Netscape 4, this is nessacery.
42 * (Note that at the moment the JavaScript doesn't actually work
43 * with Netscape 4, but now this is purely a template issue, not
44 * one affecting code).
45 * - Adding new CGIs for use by non-JavaScript browsers:
46 * edit-actions-url-form
47 * edit-actions-add-url-form
48 * edit-actions-remove-url-form
51 * Revision 1.6 2001/10/29 03:48:09 david__schmidt
52 * OS/2 native needed a snprintf() routine. Added one to miscutil, brackedted
53 * by and __OS2__ ifdef.
55 * Revision 1.5 2001/10/25 03:40:48 david__schmidt
56 * Change in porting tactics: OS/2's EMX porting layer doesn't allow multiple
57 * threads to call select() simultaneously. So, it's time to do a real, live,
58 * native OS/2 port. See defines for __EMX__ (the porting layer) vs. __OS2__
59 * (native). Both versions will work, but using __OS2__ offers multi-threading.
61 * Revision 1.4 2001/10/23 21:48:19 jongfoster
62 * Cleaning up error handling in CGI functions - they now send back
63 * a HTML error page and should never cause a FATAL error. (Fixes one
64 * potential source of "denial of service" attacks).
66 * CGI actions file editor that works and is actually useful.
68 * Ability to toggle JunkBuster remotely using a CGI call.
70 * You can turn off both the above features in the main configuration
71 * file, e.g. if you are running a multi-user proxy.
73 * Revision 1.3 2001/10/14 22:12:49 jongfoster
74 * New version of CGI-based actionsfile editor.
75 * Major changes, including:
76 * - Completely new file parser and file output routines
77 * - edit-actions CGI renamed edit-actions-for-url
78 * - All CGIs now need a filename parameter, except for...
79 * - New CGI edit-actions which doesn't need a filename,
80 * to allow you to start the editor up.
81 * - edit-actions-submit now works, and now automatically
82 * redirects you back to the main edit-actions-list handler.
84 * Revision 1.2 2001/09/16 17:05:14 jongfoster
85 * Removing unused #include showarg.h
87 * Revision 1.1 2001/09/16 15:47:37 jongfoster
88 * First version of CGI-based edit interface. This is very much a
89 * work-in-progress, and you can't actually use it to edit anything
90 * yet. You must #define FEATURE_CGI_EDIT_ACTIONS for these changes
94 **********************************************************************/
100 * FIXME: Following includes copied from cgi.c - which are actually needed?
105 #include <sys/types.h>
110 #include <sys/stat.h>
113 #define snprintf _snprintf
114 #endif /* def _WIN32 */
119 #include "cgisimple.h"
123 #include "miscutil.h"
126 /* loadcfg.h is for g_bToggleIJB only */
128 const char cgiedit_h_rcs[] = CGIEDIT_H_VERSION;
131 #ifdef FEATURE_CGI_EDIT_ACTIONS
135 struct file_line * next;
143 struct action_spec action[1];
152 /* Add more data types here... e.g.
155 struct url_spec url[1];
159 struct action_spec action[1];
168 #define FILE_LINE_UNPROCESSED 1
169 #define FILE_LINE_BLANK 2
170 #define FILE_LINE_ALIAS_HEADER 3
171 #define FILE_LINE_ALIAS_ENTRY 4
172 #define FILE_LINE_ACTION 5
173 #define FILE_LINE_URL 6
174 #define FILE_LINE_SETTINGS_HEADER 7
175 #define FILE_LINE_SETTINGS_ENTRY 8
176 #define FILE_LINE_DESCRIPTION_HEADER 9
177 #define FILE_LINE_DESCRIPTION_ENTRY 10
182 struct file_line * lines;
183 const char * filename; /* Full pathname - e.g. "/etc/junkbuster/wibble.action" */
184 const char * identifier; /* Filename stub - e.g. "wibble". Use for CGI param. */
185 const char * version_str; /* Last modification time, as a string. For CGI param */
186 unsigned version; /* Last modification time - prevents chaos with
187 * the browser's "back" button. Note that this is a
188 * time_t cast to an unsigned. When comparing, always
189 * cast the time_t to an unsigned, and *NOT* vice-versa.
190 * This may lose the top few bits, but they're not
191 * significant anyway.
193 struct file_line * parse_error; /* On parse error, this is the offending line. */
194 const char * parse_error_text; /* On parse error, this is the problem.
195 * (Statically allocated) */
198 /* FIXME: Following non-static functions should be prototyped in .h or made static */
200 /* Functions to read and write arbitrary config files */
201 jb_err edit_read_file(struct client_state *csp,
202 const struct map *parameters,
205 struct editable_file **pfile);
206 jb_err edit_write_file(struct editable_file * file);
207 void edit_free_file(struct editable_file * file);
209 /* Functions to read and write actions files */
210 jb_err edit_parse_actions_file(struct editable_file * file);
211 jb_err edit_read_actions_file(struct client_state *csp,
212 struct http_response *rsp,
213 const struct map *parameters,
215 struct editable_file **pfile);
218 jb_err cgi_error_modified(struct client_state *csp,
219 struct http_response *rsp,
220 const char *filename);
221 jb_err cgi_error_parse(struct client_state *csp,
222 struct http_response *rsp,
223 struct editable_file *file);
224 jb_err cgi_error_file(struct client_state *csp,
225 struct http_response *rsp,
226 const char *filename);
227 jb_err cgi_error_disabled(struct client_state *csp,
228 struct http_response *rsp);
230 /* Internal arbitrary config file support functions */
231 static jb_err edit_read_file_lines(FILE *fp, struct file_line ** pfile);
232 static void edit_free_file_lines(struct file_line * first_line);
233 static jb_err simple_read_line(char **dest, FILE *fp);
234 static jb_err edit_read_line(FILE *fp, char **raw_out, char **prefix_out, char **data_out);
236 /* Internal actions file support functions */
237 static int match_actions_file_header_line(const char * line, const char * name);
238 static jb_err split_line_on_equals(const char * line, char ** pname, char ** pvalue);
240 /* Internal parameter parsing functions */
241 static jb_err get_file_name_param(struct client_state *csp,
242 const struct map *parameters,
243 const char *param_name,
246 const char **pparam);
247 static jb_err get_number_param(struct client_state *csp,
248 const struct map *parameters,
252 /* Internal actionsfile <==> HTML conversion functions */
253 static jb_err map_radio(struct map * exports,
254 const char * optionname,
257 static jb_err actions_to_radio(struct map * exports,
258 const struct action_spec *action);
259 static jb_err actions_from_radio(const struct map * parameters,
260 struct action_spec *action);
263 static jb_err map_copy_parameter_html(struct map *out,
264 const struct map *in,
266 static jb_err map_copy_parameter_url(struct map *out,
267 const struct map *in,
271 /*********************************************************************
273 * Function : map_copy_parameter_html
275 * Description : Copy a CGI parameter from one map to another, HTML
279 * 1 : out = target map
280 * 2 : in = source map
281 * 3 : name = name of cgi parameter to copy
283 * Returns : JB_ERR_OK on success
284 * JB_ERR_MEMORY on out-of-memory
285 * JB_ERR_CGI_PARAMS if the parameter doesn't exist
288 *********************************************************************/
289 static jb_err map_copy_parameter_html(struct map *out,
290 const struct map *in,
300 value = lookup(in, name);
301 err = map(out, name, 1, html_encode(value), 0);
308 else if (*value == '\0')
310 return JB_ERR_CGI_PARAMS;
319 /*********************************************************************
321 * Function : map_copy_parameter_html
323 * Description : Copy a CGI parameter from one map to another, URL
327 * 1 : out = target map
328 * 2 : in = source map
329 * 3 : name = name of cgi parameter to copy
331 * Returns : JB_ERR_OK on success
332 * JB_ERR_MEMORY on out-of-memory
333 * JB_ERR_CGI_PARAMS if the parameter doesn't exist
336 *********************************************************************/
337 static jb_err map_copy_parameter_url(struct map *out,
338 const struct map *in,
348 value = lookup(in, name);
349 err = map(out, name, 1, url_encode(value), 0);
356 else if (*value == '\0')
358 return JB_ERR_CGI_PARAMS;
367 /*********************************************************************
369 * Function : cgi_edit_actions_url_form
371 * Description : CGI function that displays a form for
375 * 1 : csp = Current client state (buffers, headers, etc...)
376 * 2 : rsp = http_response data structure for output
377 * 3 : parameters = map of cgi parameters
380 * filename : Identifies the file to edit
381 * ver : File's last-modified time
382 * section : Line number of section to edit
383 * pattern : Line number of pattern to edit
384 * oldval : Current value for pattern
386 * Returns : JB_ERR_OK on success
387 * JB_ERR_MEMORY on out-of-memory
388 * JB_ERR_CGI_PARAMS if the CGI parameters are not
389 * specified or not valid.
391 *********************************************************************/
392 jb_err cgi_edit_actions_url_form(struct client_state *csp,
393 struct http_response *rsp,
394 const struct map *parameters)
403 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
405 return cgi_error_disabled(csp, rsp);
408 if (NULL == (exports = default_exports(csp, NULL)))
410 return JB_ERR_MEMORY;
413 err = map_copy_parameter_html(exports, parameters, "section");
414 if (!err) err = map_copy_parameter_html(exports, parameters, "pattern");
415 if (!err) err = map_copy_parameter_html(exports, parameters, "ver");
416 if (!err) err = map_copy_parameter_html(exports, parameters, "filename");
417 if (!err) err = map_copy_parameter_html(exports, parameters, "oldval");
425 return template_fill_for_cgi(csp, "edit-actions-url-form", exports, rsp);
429 /*********************************************************************
431 * Function : cgi_edit_actions_add_url_form
433 * Description : CGI function that displays a form for
437 * 1 : csp = Current client state (buffers, headers, etc...)
438 * 2 : rsp = http_response data structure for output
439 * 3 : parameters = map of cgi parameters
442 * filename : Identifies the file to edit
443 * ver : File's last-modified time
444 * section : Line number of section to edit
446 * Returns : JB_ERR_OK on success
447 * JB_ERR_MEMORY on out-of-memory
448 * JB_ERR_CGI_PARAMS if the CGI parameters are not
449 * specified or not valid.
451 *********************************************************************/
452 jb_err cgi_edit_actions_add_url_form(struct client_state *csp,
453 struct http_response *rsp,
454 const struct map *parameters)
463 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
465 return cgi_error_disabled(csp, rsp);
468 if (NULL == (exports = default_exports(csp, NULL)))
470 return JB_ERR_MEMORY;
473 err = map_copy_parameter_html(exports, parameters, "section");
474 if (!err) err = map_copy_parameter_html(exports, parameters, "ver");
475 if (!err) err = map_copy_parameter_html(exports, parameters, "filename");
483 return template_fill_for_cgi(csp, "edit-actions-add-url-form", exports, rsp);
487 /*********************************************************************
489 * Function : cgi_edit_actions_remove_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 * filename : Identifies the file to edit
501 * ver : File's last-modified time
502 * section : Line number of section to edit
503 * pattern : Line number of pattern to edit
504 * oldval : Current value for pattern
506 * Returns : JB_ERR_OK on success
507 * JB_ERR_MEMORY on out-of-memory
508 * JB_ERR_CGI_PARAMS if the CGI parameters are not
509 * specified or not valid.
511 *********************************************************************/
512 jb_err cgi_edit_actions_remove_url_form(struct client_state *csp,
513 struct http_response *rsp,
514 const struct map *parameters)
523 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
525 return cgi_error_disabled(csp, rsp);
528 if (NULL == (exports = default_exports(csp, NULL)))
530 return JB_ERR_MEMORY;
533 err = map_copy_parameter_url(exports, parameters, "section");
534 if (!err) err = map_copy_parameter_url(exports, parameters, "pattern");
535 if (!err) err = map_copy_parameter_url(exports, parameters, "ver");
536 if (!err) err = map_copy_parameter_url(exports, parameters, "filename");
537 if (!err) err = map_copy_parameter_html(exports, parameters, "oldval");
545 return template_fill_for_cgi(csp, "edit-actions-remove-url-form", exports, rsp);
549 /*********************************************************************
551 * Function : simple_read_line
553 * Description : Read a single line from a file and return it.
554 * This is basically a version of fgets() that malloc()s
555 * it's own line buffer. Note that the buffer will
556 * always be a multiple of BUFFER_SIZE bytes long.
557 * Therefore if you are going to keep the string for
558 * an extended period of time, you should probably
559 * strdup() it and free() the original, to save memory.
563 * 1 : dest = destination for newly malloc'd pointer to
564 * line data. Will be set to NULL on error.
565 * 2 : fp = File to read from
567 * Returns : JB_ERR_OK on success
568 * JB_ERR_MEMORY on out-of-memory
569 * JB_ERR_FILE on EOF.
571 *********************************************************************/
572 static jb_err simple_read_line(char **dest, FILE *fp)
583 if (NULL == (buf = malloc(BUFFER_SIZE)))
585 return JB_ERR_MEMORY;
594 if ((!fgets(newbuf, BUFFER_SIZE, fp)) || (*newbuf == '\0'))
596 /* (*newbuf == '\0') should never happen unless fgets fails */
609 if ((buf[len - 1] == '\n') || (buf[len - 1] == '\r'))
615 if (NULL == (newbuf = realloc(buf, len + BUFFER_SIZE)))
618 return JB_ERR_MEMORY;
625 /*********************************************************************
627 * Function : edit_read_line
629 * Description : Read a single non-empty line from a file and return
630 * it. Trims comments, leading and trailing whitespace
631 * and respects escaping of newline and comment char.
632 * Provides the line in 2 alternative forms: raw and
634 * - raw is the raw data read from the file. If the
635 * line is not modified, then this should be written
637 * - prefix is any comments and blank lines that were
638 * read from the file. If the line is modified, then
639 * this should be written out to the file followed
640 * by the modified data. (If this string is non-empty
641 * then it will have a newline at the end).
642 * - data is the actual data that will be parsed
643 * further by appropriate routines.
644 * On EOF, the 3 strings will all be set to NULL and
645 * 0 will be returned.
648 * 1 : fp = File to read from
649 * 2 : raw_out = destination for newly malloc'd pointer to
650 * raw line data. May be NULL if you don't want it.
651 * 3 : prefix_out = destination for newly malloc'd pointer to
652 * comments. May be NULL if you don't want it.
653 * 4 : data_out = destination for newly malloc'd pointer to
654 * line data with comments and leading/trailing spaces
655 * removed, and line continuation performed. May be
656 * NULL if you don't want it.
658 * Returns : JB_ERR_OK on success
659 * JB_ERR_MEMORY on out-of-memory
660 * JB_ERR_FILE on EOF.
662 *********************************************************************/
663 static jb_err edit_read_line(FILE *fp, char **raw_out, char **prefix_out, char **data_out)
665 char *p; /* Temporary pointer */
666 char *linebuf; /* Line read from file */
667 char *linestart; /* Start of linebuf, usually first non-whitespace char */
668 char newline[3]; /* Used to store the newline - "\n", "\r", or "\r\n" */
669 int contflag = 0; /* Nonzero for line continuation - i.e. line ends '\' */
670 char *raw; /* String to be stored in raw_out */
671 char *prefix; /* String to be stored in prefix_out */
672 char *data; /* String to be stored in data_out */
673 jb_err rval = JB_ERR_OK;
677 /* Set output parameters to NULL */
691 /* Set string variables to new, empty strings. */
697 if ((raw == NULL) || (prefix == NULL) || (data == NULL))
702 return JB_ERR_MEMORY;
709 /* Main loop. Loop while we need more data & it's not EOF. */
711 while ( (contflag || (*data == '\0'))
712 && (JB_ERR_OK == (rval = simple_read_line(&linebuf, fp))))
714 if (string_append(&raw,linebuf))
719 return JB_ERR_MEMORY;
722 /* Trim off newline */
723 p = linebuf + strlen(linebuf);
724 if ((p != linebuf) && ((p[-1] == '\r') || (p[-1] == '\n')))
727 if ((p != linebuf) && ((p[-1] == '\r') || (p[-1] == '\n')))
735 /* Line continuation? Trim escape and set flag. */
736 contflag = ((p != linebuf) && (*--p == '\\'));
742 /* Trim leading spaces if we're at the start of the line */
746 /* Trim leading spaces */
747 while (*linestart && isspace((int)(unsigned char)*linestart))
753 /* Handle comment characters. */
755 while ((p = strchr(p, '#')) != NULL)
757 /* Found a comment char.. */
758 if ((p != linebuf) && (*(p-1) == '\\'))
760 /* ..and it's escaped, left-shift the line over the escape. */
762 while ((*q = *(q + 1)) != '\0')
766 /* Now scan from just after the "#". */
770 /* Real comment. Save it... */
773 /* Special case: Line only contains a comment, so all the
774 * previous whitespace is considered part of the comment.
775 * Undo the whitespace skipping, if any.
780 string_append(&prefix,p);
781 if (string_append(&prefix,newline))
786 return JB_ERR_MEMORY;
790 /* ... and chop off the rest of the line */
793 } /* END while (there's a # character) */
795 /* Write to the buffer */
798 if (string_append(&data, linestart))
803 return JB_ERR_MEMORY;
808 } /* END while(we need more data) */
810 /* Handle simple_read_line() errors - ignore EOF */
811 if ((rval != JB_ERR_OK) && (rval != JB_ERR_FILE))
822 /* Got at least some data */
824 /* Remove trailing whitespace */
837 *prefix_out = prefix;
855 /* EOF and no data there. */
866 /*********************************************************************
868 * Function : edit_write_file
870 * Description : Write a complete file to disk.
873 * 1 : filename = File to write to.
874 * 2 : file = Data structure to write.
876 * Returns : JB_ERR_OK on success
877 * JB_ERR_FILE on error writing to file.
878 * JB_ERR_MEMORY on out of memory
880 *********************************************************************/
881 jb_err edit_write_file(struct editable_file * file)
884 struct file_line * cur_line;
885 struct stat statbuf[1];
886 char version_buf[22]; /* 22 = ceil(log10(2^64)) + 2 = max number of
887 digits in time_t, assuming this is a 64-bit
888 machine, plus null terminator, plus one
892 assert(file->filename);
894 if (NULL == (fp = fopen(file->filename, "wt")))
899 cur_line = file->lines;
900 while (cur_line != NULL)
904 if (fputs(cur_line->raw, fp) < 0)
912 if (cur_line->prefix)
914 if (fputs(cur_line->prefix, fp) < 0)
920 if (cur_line->unprocessed)
922 if (fputs(cur_line->unprocessed, fp) < 0)
927 if (fputs("\n", fp) < 0)
935 /* FIXME: Write data from file->data->whatever */
939 cur_line = cur_line->next;
945 /* Update the version stamp in the file structure, since we just
946 * wrote to the file & changed it's date.
948 if (stat(file->filename, statbuf) < 0)
950 /* Error, probably file not found. */
953 file->version = (unsigned)statbuf->st_mtime;
955 /* Correct file->version_str */
956 freez(file->version_str);
957 snprintf(version_buf, 22, "%u", file->version);
958 version_buf[21] = '\0';
959 file->version_str = strdup(version_buf);
960 if (version_buf == NULL)
962 return JB_ERR_MEMORY;
969 /*********************************************************************
971 * Function : edit_free_file
973 * Description : Free a complete file in memory.
976 * 1 : file = Data structure to free.
980 *********************************************************************/
981 void edit_free_file(struct editable_file * file)
985 /* Silently ignore NULL pointer */
989 edit_free_file_lines(file->lines);
990 freez(file->filename);
991 freez(file->identifier);
992 freez(file->version_str);
994 file->parse_error_text = NULL; /* Statically allocated */
995 file->parse_error = NULL;
1001 /*********************************************************************
1003 * Function : edit_free_file
1005 * Description : Free an entire linked list of file lines.
1008 * 1 : first_line = Data structure to free.
1012 *********************************************************************/
1013 static void edit_free_file_lines(struct file_line * first_line)
1015 struct file_line * next_line;
1017 while (first_line != NULL)
1019 next_line = first_line->next;
1020 first_line->next = NULL;
1021 freez(first_line->raw);
1022 freez(first_line->prefix);
1023 freez(first_line->unprocessed);
1024 switch(first_line->type)
1026 case 0: /* special case if memory zeroed */
1027 case FILE_LINE_UNPROCESSED:
1028 case FILE_LINE_BLANK:
1029 case FILE_LINE_ALIAS_HEADER:
1030 case FILE_LINE_SETTINGS_HEADER:
1031 case FILE_LINE_DESCRIPTION_HEADER:
1032 case FILE_LINE_DESCRIPTION_ENTRY:
1033 case FILE_LINE_ALIAS_ENTRY:
1035 /* No data is stored for these */
1038 case FILE_LINE_ACTION:
1039 free_action(first_line->data.action);
1042 case FILE_LINE_SETTINGS_ENTRY:
1043 freez(first_line->data.setting.name);
1044 freez(first_line->data.setting.svalue);
1047 /* Should never happen */
1051 first_line->type = 0; /* paranoia */
1053 first_line = next_line;
1058 /*********************************************************************
1060 * Function : match_actions_file_header_line
1062 * Description : Match an actions file {{header}} line
1065 * 1 : line - String from file
1066 * 2 : name - Header to match against
1068 * Returns : 0 iff they match.
1070 *********************************************************************/
1071 static int match_actions_file_header_line(const char * line, const char * name)
1079 if ((line[0] != '{') || (line[1] != '{'))
1085 /* Look for optional whitespace */
1086 while ( (*line == ' ') || (*line == '\t') )
1091 /* Look for the specified name (case-insensitive) */
1093 if (0 != strncmpic(line, name, len))
1099 /* Look for optional whitespace */
1100 while ( (*line == ' ') || (*line == '\t') )
1105 /* Look for "}}" and end of string*/
1106 if ((line[0] != '}') || (line[1] != '}') || (line[2] != '\0'))
1116 /*********************************************************************
1118 * Function : match_actions_file_header_line
1120 * Description : Match an actions file {{header}} line
1123 * 1 : line - String from file. Must not start with
1124 * whitespace (else infinite loop!)
1125 * 2 : name - Destination for name
1126 * 2 : name - Destination for value
1128 * Returns : JB_ERR_OK on success
1129 * JB_ERR_MEMORY on out-of-memory
1130 * JB_ERR_PARSE if there's no "=" sign, or if there's
1131 * nothing before the "=" sign (but empty
1132 * values *after* the "=" sign are legal).
1134 *********************************************************************/
1135 static jb_err split_line_on_equals(const char * line, char ** pname, char ** pvalue)
1137 const char * name_end;
1138 const char * value_start;
1144 assert(*line != ' ');
1145 assert(*line != '\t');
1150 value_start = strchr(line, '=');
1151 if ((value_start == NULL) || (value_start == line))
1153 return JB_ERR_PARSE;
1156 name_end = value_start - 1;
1158 /* Eat any whitespace before the '=' */
1159 while ((*name_end == ' ') || (*name_end == '\t'))
1162 * we already know we must have at least 1 non-ws char
1163 * at start of buf - no need to check
1168 name_len = name_end - line + 1; /* Length excluding \0 */
1169 if (NULL == (*pname = (char *) malloc(name_len + 1)))
1171 return JB_ERR_MEMORY;
1173 strncpy(*pname, line, name_len);
1174 (*pname)[name_len] = '\0';
1176 /* Eat any the whitespace after the '=' */
1178 while ((*value_start == ' ') || (*value_start == '\t'))
1183 if (NULL == (*pvalue = strdup(value_start)))
1187 return JB_ERR_MEMORY;
1194 /*********************************************************************
1196 * Function : edit_parse_actions_file
1198 * Description : Parse an actions file in memory.
1200 * Passed linked list must have the "data" member
1201 * zeroed, and must contain valid "next" and
1202 * "unprocessed" fields. The "raw" and "prefix"
1203 * fields are ignored, and "type" is just overwritten.
1205 * Note that on error the file may have been
1209 * 1 : file = Actions file to be parsed in-place.
1211 * Returns : JB_ERR_OK on success
1212 * JB_ERR_MEMORY on out-of-memory
1213 * JB_ERR_PARSE on error
1215 *********************************************************************/
1216 jb_err edit_parse_actions_file(struct editable_file * file)
1218 struct file_line * cur_line;
1220 const char * text; /* Text from a line */
1221 char * name; /* For lines of the form name=value */
1222 char * value; /* For lines of the form name=value */
1223 struct action_alias * alias_list = NULL;
1224 jb_err err = JB_ERR_OK;
1226 /* alias_list contains the aliases defined in this file.
1227 * It might be better to use the "file_line.data" fields
1228 * in the relavent places instead.
1231 cur_line = file->lines;
1233 /* A note about blank line support: Blank lines should only
1234 * ever occur as the last line in the file. This function
1235 * is more forgiving than that - FILE_LINE_BLANK can occur
1239 /* Skip leading blanks. Should only happen if file is
1240 * empty (which is valid, but pointless).
1242 while ( (cur_line != NULL)
1243 && (cur_line->unprocessed[0] == '\0') )
1246 cur_line->type = FILE_LINE_BLANK;
1247 cur_line = cur_line->next;
1250 if ( (cur_line != NULL)
1251 && (cur_line->unprocessed[0] != '{') )
1253 /* File doesn't start with a header */
1254 file->parse_error = cur_line;
1255 file->parse_error_text = "First (non-comment) line of the file must contain a header.";
1256 return JB_ERR_PARSE;
1259 if ( (cur_line != NULL) && (0 ==
1260 match_actions_file_header_line(cur_line->unprocessed, "settings") ) )
1262 cur_line->type = FILE_LINE_SETTINGS_HEADER;
1264 cur_line = cur_line->next;
1265 while ((cur_line != NULL) && (cur_line->unprocessed[0] != '{'))
1267 if (cur_line->unprocessed[0])
1269 cur_line->type = FILE_LINE_SETTINGS_ENTRY;
1271 err = split_line_on_equals(cur_line->unprocessed,
1272 &cur_line->data.setting.name,
1273 &cur_line->data.setting.svalue);
1274 if (err == JB_ERR_MEMORY)
1278 else if (err != JB_ERR_OK)
1280 /* Line does not contain a name=value pair */
1281 file->parse_error = cur_line;
1282 file->parse_error_text = "Expected a name=value pair on this {{description}} line, but couldn't find one.";
1283 return JB_ERR_PARSE;
1288 cur_line->type = FILE_LINE_BLANK;
1290 cur_line = cur_line->next;
1294 if ( (cur_line != NULL) && (0 ==
1295 match_actions_file_header_line(cur_line->unprocessed, "description") ) )
1297 cur_line->type = FILE_LINE_DESCRIPTION_HEADER;
1299 cur_line = cur_line->next;
1300 while ((cur_line != NULL) && (cur_line->unprocessed[0] != '{'))
1302 if (cur_line->unprocessed[0])
1304 cur_line->type = FILE_LINE_DESCRIPTION_ENTRY;
1308 cur_line->type = FILE_LINE_BLANK;
1310 cur_line = cur_line->next;
1314 if ( (cur_line != NULL) && (0 ==
1315 match_actions_file_header_line(cur_line->unprocessed, "alias") ) )
1317 cur_line->type = FILE_LINE_ALIAS_HEADER;
1319 cur_line = cur_line->next;
1320 while ((cur_line != NULL) && (cur_line->unprocessed[0] != '{'))
1322 if (cur_line->unprocessed[0])
1324 /* define an alias */
1325 struct action_alias * new_alias;
1327 cur_line->type = FILE_LINE_ALIAS_ENTRY;
1329 err = split_line_on_equals(cur_line->unprocessed, &name, &value);
1330 if (err == JB_ERR_MEMORY)
1334 else if (err != JB_ERR_OK)
1336 /* Line does not contain a name=value pair */
1337 file->parse_error = cur_line;
1338 file->parse_error_text = "Expected a name=value pair on this {{alias}} line, but couldn't find one.";
1339 return JB_ERR_PARSE;
1342 if ((new_alias = zalloc(sizeof(*new_alias))) == NULL)
1347 free_alias_list(alias_list);
1348 return JB_ERR_MEMORY;
1351 err = get_actions(value, alias_list, new_alias->action);
1354 /* Invalid action or out of memory */
1358 free_alias_list(alias_list);
1359 if (err == JB_ERR_MEMORY)
1365 /* Line does not contain a name=value pair */
1366 file->parse_error = cur_line;
1367 file->parse_error_text = "This alias does not specify a valid set of actions.";
1368 return JB_ERR_PARSE;
1374 new_alias->name = name;
1377 new_alias->next = alias_list;
1378 alias_list = new_alias;
1382 cur_line->type = FILE_LINE_BLANK;
1384 cur_line = cur_line->next;
1388 /* Header done, process the main part of the file */
1389 while (cur_line != NULL)
1391 /* At this point, (cur_line->unprocessed[0] == '{') */
1392 assert(cur_line->unprocessed[0] == '{');
1393 text = cur_line->unprocessed + 1;
1394 len = strlen(text) - 1;
1395 if (text[len] != '}')
1397 /* No closing } on header */
1398 free_alias_list(alias_list);
1399 file->parse_error = cur_line;
1400 file->parse_error_text = "Headers starting with '{' must have a "
1401 "closing bracket ('}'). Headers starting with two brackets ('{{') "
1402 "must close with two brackets ('}}').";
1403 return JB_ERR_PARSE;
1408 /* An invalid {{ header. */
1409 free_alias_list(alias_list);
1410 file->parse_error = cur_line;
1411 file->parse_error_text = "Unknown or unexpected two-bracket header. "
1412 "Please remember that the system (two-bracket) headers must "
1413 "appear in the order {{settings}}, {{description}}, {{alias}}, "
1414 "and must appear before any actions (one-bracket) headers. "
1415 "Also note that system headers may not be repeated.";
1416 return JB_ERR_PARSE;
1419 while ( (*text == ' ') || (*text == '\t') )
1425 && ( (text[len - 1] == ' ')
1426 || (text[len - 1] == '\t') ) )
1431 cur_line->type = FILE_LINE_ACTION;
1433 /* Remove {} and make copy */
1434 if (NULL == (value = (char *) malloc(len + 1)))
1437 free_alias_list(alias_list);
1438 return JB_ERR_MEMORY;
1440 strncpy(value, text, len);
1444 err = get_actions(value, alias_list, cur_line->data.action);
1447 /* Invalid action or out of memory */
1449 free_alias_list(alias_list);
1450 if (err == JB_ERR_MEMORY)
1456 /* Line does not contain a name=value pair */
1457 file->parse_error = cur_line;
1458 file->parse_error_text = "This header does not specify a valid set of actions.";
1459 return JB_ERR_PARSE;
1463 /* Done with string - it was clobbered anyway */
1466 /* Process next line */
1467 cur_line = cur_line->next;
1469 /* Loop processing URL patterns */
1470 while ((cur_line != NULL) && (cur_line->unprocessed[0] != '{'))
1472 if (cur_line->unprocessed[0])
1474 /* Could parse URL here, but this isn't currently needed */
1476 cur_line->type = FILE_LINE_URL;
1480 cur_line->type = FILE_LINE_BLANK;
1482 cur_line = cur_line->next;
1484 } /* End main while(cur_line != NULL) loop */
1486 free_alias_list(alias_list);
1492 /*********************************************************************
1494 * Function : edit_read_file_lines
1496 * Description : Read all the lines of a file into memory.
1497 * Handles whitespace, comments and line continuation.
1500 * 1 : fp = File to read from. On return, this will be
1501 * at EOF but it will not have been closed.
1502 * 2 : pfile = Destination for a linked list of file_lines.
1503 * Will be set to NULL on error.
1505 * Returns : JB_ERR_OK on success
1506 * JB_ERR_MEMORY on out-of-memory
1508 *********************************************************************/
1509 jb_err edit_read_file_lines(FILE *fp, struct file_line ** pfile)
1511 struct file_line * first_line; /* Keep for return value or to free */
1512 struct file_line * cur_line; /* Current line */
1513 struct file_line * prev_line; /* Entry with prev_line->next = cur_line */
1521 cur_line = first_line = zalloc(sizeof(struct file_line));
1522 if (cur_line == NULL)
1524 return JB_ERR_MEMORY;
1527 cur_line->type = FILE_LINE_UNPROCESSED;
1529 rval = edit_read_line(fp, &cur_line->raw, &cur_line->prefix, &cur_line->unprocessed);
1532 /* Out of memory or empty file. */
1533 /* Note that empty file is not an error we propogate up */
1535 return ((rval == JB_ERR_FILE) ? JB_ERR_OK : rval);
1540 prev_line = cur_line;
1541 cur_line = prev_line->next = zalloc(sizeof(struct file_line));
1542 if (cur_line == NULL)
1545 edit_free_file_lines(first_line);
1546 return JB_ERR_MEMORY;
1549 cur_line->type = FILE_LINE_UNPROCESSED;
1551 rval = edit_read_line(fp, &cur_line->raw, &cur_line->prefix, &cur_line->unprocessed);
1552 if ((rval != JB_ERR_OK) && (rval != JB_ERR_FILE))
1555 edit_free_file_lines(first_line);
1556 return JB_ERR_MEMORY;
1560 while (rval != JB_ERR_FILE);
1564 /* We allocated one too many - free it */
1565 prev_line->next = NULL;
1568 *pfile = first_line;
1573 /*********************************************************************
1575 * Function : edit_read_file
1577 * Description : Read a complete file into memory.
1578 * Handles CGI parameter parsing. If requested, also
1579 * checks the file's modification timestamp.
1582 * 1 : csp = Current client state (buffers, headers, etc...)
1583 * 2 : parameters = map of cgi parameters.
1584 * 3 : require_version = true to check "ver" parameter.
1585 * 4 : suffix = File extension, e.g. ".action".
1586 * 5 : pfile = Destination for the file. Will be set
1590 * filename : The name of the file to read, without the
1591 * path or ".action" extension.
1592 * ver : (Only if require_version is nonzero)
1593 * Timestamp of the actions file. If wrong, this
1594 * function fails with JB_ERR_MODIFIED.
1596 * Returns : JB_ERR_OK on success
1597 * JB_ERR_MEMORY on out-of-memory
1598 * JB_ERR_CGI_PARAMS if "filename" was not specified
1600 * JB_ERR_FILE if the file cannot be opened or
1602 * JB_ERR_MODIFIED if version checking was requested and
1603 * failed - the file was modified outside
1604 * of this CGI editor instance.
1606 *********************************************************************/
1607 jb_err edit_read_file(struct client_state *csp,
1608 const struct map *parameters,
1609 int require_version,
1611 struct editable_file **pfile)
1613 struct file_line * lines;
1617 const char * identifier;
1618 struct editable_file * file;
1619 unsigned version = 0;
1620 struct stat statbuf[1];
1621 char version_buf[22];
1629 err = get_file_name_param(csp, parameters, "filename", suffix,
1630 &filename, &identifier);
1636 if (stat(filename, statbuf) < 0)
1638 /* Error, probably file not found. */
1642 version = (unsigned) statbuf->st_mtime;
1644 if (require_version)
1646 unsigned specified_version;
1647 err = get_number_param(csp, parameters, "ver", &specified_version);
1654 if (version != specified_version)
1656 return JB_ERR_MODIFIED;
1660 if (NULL == (fp = fopen(filename,"rt")))
1666 err = edit_read_file_lines(fp, &lines);
1676 file = (struct editable_file *) zalloc(sizeof(*file));
1680 edit_free_file_lines(lines);
1684 file->lines = lines;
1685 file->filename = filename;
1686 file->version = version;
1687 file->identifier = strdup(identifier);
1689 if (file->identifier == NULL)
1691 edit_free_file(file);
1692 return JB_ERR_MEMORY;
1695 /* Correct file->version_str */
1696 freez(file->version_str);
1697 snprintf(version_buf, 22, "%u", file->version);
1698 version_buf[21] = '\0';
1699 file->version_str = strdup(version_buf);
1700 if (version_buf == NULL)
1702 edit_free_file(file);
1703 return JB_ERR_MEMORY;
1711 /*********************************************************************
1713 * Function : edit_read_actions_file
1715 * Description : Read a complete actions file into memory.
1716 * Handles CGI parameter parsing. If requested, also
1717 * checks the file's modification timestamp.
1719 * If this function detects an error in the categories
1720 * JB_ERR_FILE, JB_ERR_MODIFIED, or JB_ERR_PARSE,
1721 * then it handles it by filling in the specified
1722 * response structure and returning JB_ERR_FILE.
1725 * 1 : csp = Current client state (buffers, headers, etc...)
1726 * 2 : rsp = HTTP response. Only filled in on error.
1727 * 2 : parameters = map of cgi parameters.
1728 * 3 : require_version = true to check "ver" parameter.
1729 * 4 : pfile = Destination for the file. Will be set
1733 * filename : The name of the actions file to read, without the
1734 * path or ".action" extension.
1735 * ver : (Only if require_version is nonzero)
1736 * Timestamp of the actions file. If wrong, this
1737 * function fails with JB_ERR_MODIFIED.
1739 * Returns : JB_ERR_OK on success
1740 * JB_ERR_MEMORY on out-of-memory
1741 * JB_ERR_CGI_PARAMS if "filename" was not specified
1743 * JB_ERR_FILE if the file does not contain valid data,
1744 * or if file cannot be opened or
1745 * contains no data, or if version
1746 * checking was requested and failed.
1748 *********************************************************************/
1749 jb_err edit_read_actions_file(struct client_state *csp,
1750 struct http_response *rsp,
1751 const struct map *parameters,
1752 int require_version,
1753 struct editable_file **pfile)
1756 struct editable_file *file;
1764 err = edit_read_file(csp, parameters, require_version, ".action", &file);
1767 /* Try to handle if possible */
1768 if (err == JB_ERR_FILE)
1770 err = cgi_error_file(csp, rsp, lookup(parameters, "filename"));
1772 else if (err == JB_ERR_MODIFIED)
1774 err = cgi_error_modified(csp, rsp, lookup(parameters, "filename"));
1776 if (err == JB_ERR_OK)
1779 * Signal to higher-level CGI code that there was a problem but we
1780 * handled it, they should just return JB_ERR_OK.
1787 err = edit_parse_actions_file(file);
1790 if (err == JB_ERR_PARSE)
1792 err = cgi_error_parse(csp, rsp, file);
1793 if (err == JB_ERR_OK)
1796 * Signal to higher-level CGI code that there was a problem but we
1797 * handled it, they should just return JB_ERR_OK.
1802 edit_free_file(file);
1811 /*********************************************************************
1813 * Function : get_file_name_param
1815 * Description : Get the name of the file to edit from the parameters
1816 * passed to a CGI function. This function handles
1817 * security checks such as blocking urls containing
1818 * "/" or ".", prepending the config file directory,
1819 * and adding the specified suffix.
1821 * (This is an essential security check, otherwise
1822 * users may be able to pass "../../../etc/passwd"
1823 * and overwrite the password file [linux], "prn:"
1824 * and print random data [Windows], etc...)
1826 * This function only allows filenames contining the
1827 * characters '-', '_', 'A'-'Z', 'a'-'z', and '0'-'9'.
1828 * That's probably too restrictive but at least it's
1832 * 1 : csp = Current client state (buffers, headers, etc...)
1833 * 2 : parameters = map of cgi parameters
1834 * 3 : suffix = File extension, e.g. ".actions"
1835 * 4 : pfilename = destination for full filename. Caller
1836 * free()s. Set to NULL on error.
1837 * 5 : pparam = destination for partial filename,
1838 * suitable for use in another URL. Allocated as part
1839 * of the map "parameters", so don't free it.
1840 * Set to NULL if not specified.
1842 * Returns : JB_ERR_OK on success
1843 * JB_ERR_MEMORY on out-of-memory
1844 * JB_ERR_CGI_PARAMS if "filename" was not specified
1847 *********************************************************************/
1848 static jb_err get_file_name_param(struct client_state *csp,
1849 const struct map *parameters,
1850 const char *param_name,
1853 const char **pparam)
1871 param = lookup(parameters, param_name);
1874 return JB_ERR_CGI_PARAMS;
1879 len = strlen(param);
1880 if (len >= FILENAME_MAX)
1883 return JB_ERR_CGI_PARAMS;
1886 /* Check every character to see if it's legal */
1888 while ((ch = *s++) != '\0')
1890 if ( ((ch < 'A') || (ch > 'Z'))
1891 && ((ch < 'a') || (ch > 'z'))
1892 && ((ch < '0') || (ch > '9'))
1896 /* Probable hack attempt. */
1897 return JB_ERR_CGI_PARAMS;
1901 /* Append extension */
1902 name = malloc(len + strlen(suffix) + 1);
1905 return JB_ERR_MEMORY;
1907 strcpy(name, param);
1908 strcpy(name + len, suffix);
1911 fullpath = make_path(csp->config->confdir, name);
1913 if (fullpath == NULL)
1915 return JB_ERR_MEMORY;
1919 *pfilename = fullpath;
1925 /*********************************************************************
1927 * Function : get_number_param
1929 * Description : Get a non-negative integer from the parameters
1930 * passed to a CGI function.
1933 * 1 : csp = Current client state (buffers, headers, etc...)
1934 * 2 : parameters = map of cgi parameters
1935 * 3 : name = Name of CGI parameter to read
1936 * 4 : pvalue = destination for value.
1937 * Set to -1 on error.
1939 * Returns : JB_ERR_OK on success
1940 * JB_ERR_MEMORY on out-of-memory
1941 * JB_ERR_CGI_PARAMS if the parameter was not specified
1944 *********************************************************************/
1945 static jb_err get_number_param(struct client_state *csp,
1946 const struct map *parameters,
1961 param = lookup(parameters, name);
1964 return JB_ERR_CGI_PARAMS;
1967 /* We don't use atoi because I want to check this carefully... */
1970 while ((ch = *param++) != '\0')
1972 if ((ch < '0') || (ch > '9'))
1974 return JB_ERR_CGI_PARAMS;
1981 * <limits.h> defines UINT_MAX
1983 * (UINT_MAX - ch) / 10 is the largest number that
1984 * can be safely multiplied by 10 then have ch added.
1986 if (value > ((UINT_MAX - (unsigned)ch) / 10U))
1988 return JB_ERR_CGI_PARAMS;
1991 value = value * 10 + ch;
2001 /*********************************************************************
2003 * Function : map_radio
2005 * Description : Map a set of radio button values. E.g. if you have
2006 * 3 radio buttons, declare them as:
2007 * <option type="radio" name="xyz" @xyz-a@>
2008 * <option type="radio" name="xyz" @xyz-b@>
2009 * <option type="radio" name="xyz" @xyz-c@>
2010 * Then map one of the @xyz-?@ variables to "checked"
2011 * and all the others to empty by calling:
2012 * map_radio(exports, "xyz", "abc", sel)
2013 * Where 'sel' is 'a', 'b', or 'c'.
2016 * 1 : exports = Exports map to modify.
2017 * 2 : optionname = name for map
2018 * 3 : values = null-terminated list of values;
2019 * 4 : value = Selected value.
2021 * CGI Parameters : None
2023 * Returns : JB_ERR_OK on success
2024 * JB_ERR_MEMORY on out-of-memory
2026 *********************************************************************/
2027 static jb_err map_radio(struct map * exports,
2028 const char * optionname,
2029 const char * values,
2041 len = strlen(optionname);
2042 buf = malloc(len + 3);
2045 return JB_ERR_MEMORY;
2048 strcpy(buf, optionname);
2053 while ((c = *values++) != '\0')
2058 if (map(exports, buf, 1, "", 1))
2061 return JB_ERR_MEMORY;
2067 if (map(exports, buf, 0, "checked", 1))
2070 return JB_ERR_MEMORY;
2077 /*********************************************************************
2079 * Function : actions_to_radio
2081 * Description : Converts a actionsfile entry into settings for
2082 * radio buttons and edit boxes on a HTML form.
2085 * 1 : exports = List of substitutions to add to.
2086 * 2 : action = Action to read
2088 * Returns : JB_ERR_OK on success
2089 * JB_ERR_MEMORY on out-of-memory
2091 *********************************************************************/
2092 static jb_err actions_to_radio(struct map * exports,
2093 const struct action_spec *action)
2095 unsigned mask = action->mask;
2096 unsigned add = action->add;
2104 mask = action->mask;
2107 /* sanity - prevents "-feature +feature" */
2111 #define DEFINE_ACTION_BOOL(name, bit) \
2112 if (!(mask & bit)) \
2114 current_mode = 'n'; \
2116 else if (add & bit) \
2118 current_mode = 'y'; \
2122 current_mode = 'x'; \
2124 if (map_radio(exports, name, "ynx", current_mode)) \
2126 return JB_ERR_MEMORY; \
2129 #define DEFINE_ACTION_STRING(name, bit, index) \
2130 DEFINE_ACTION_BOOL(name, bit); \
2133 #define DEFINE_CGI_PARAM_RADIO(name, bit, index, value, is_default) \
2136 checked = !strcmp(action->string[index], value); \
2140 checked = is_default; \
2142 mapped_param |= checked; \
2143 if (map(exports, name "-param-" value, 1, (checked ? "checked" : ""), 1)) \
2145 return JB_ERR_MEMORY; \
2148 #define DEFINE_CGI_PARAM_CUSTOM(name, bit, index, default_val) \
2149 if (map(exports, name "-param-custom", 1, \
2150 ((!mapped_param) ? "checked" : ""), 1)) \
2152 return JB_ERR_MEMORY; \
2154 if (map(exports, name "-param", 1, \
2155 (((add & bit) && !mapped_param) ? \
2156 action->string[index] : default_val), 1)) \
2158 return JB_ERR_MEMORY; \
2161 #define DEFINE_CGI_PARAM_NO_RADIO(name, bit, index, default_val) \
2162 if (map(exports, name "-param", 1, \
2163 ((add & bit) ? action->string[index] : default_val), 1)) \
2165 return JB_ERR_MEMORY; \
2168 #define DEFINE_ACTION_MULTI(name, index) \
2169 if (action->multi_add[index]->first) \
2171 current_mode = 'y'; \
2173 else if (action->multi_remove_all[index]) \
2175 current_mode = 'n'; \
2177 else if (action->multi_remove[index]->first) \
2179 current_mode = 'y'; \
2183 current_mode = 'x'; \
2185 if (map_radio(exports, name, "ynx", current_mode)) \
2187 return JB_ERR_MEMORY; \
2190 #define DEFINE_ACTION_ALIAS 0 /* No aliases for output */
2192 #include "actionlist.h"
2194 #undef DEFINE_ACTION_MULTI
2195 #undef DEFINE_ACTION_STRING
2196 #undef DEFINE_ACTION_BOOL
2197 #undef DEFINE_ACTION_ALIAS
2198 #undef DEFINE_CGI_PARAM_CUSTOM
2199 #undef DEFINE_CGI_PARAM_RADIO
2200 #undef DEFINE_CGI_PARAM_NO_RADIO
2206 /*********************************************************************
2208 * Function : javascriptify
2210 * Description : Converts a string into a form JavaScript will like.
2212 * Netscape 4's JavaScript sucks - it doesn't use
2213 * "id" parameters, so you have to set the "name"
2214 * used to submit a form element to something JavaScript
2215 * will like. (Or access the elements by index in an
2216 * array. That array contains >60 elements and will
2217 * be changed whenever we add a new action to the
2218 * editor, so I'm NOT going to use indexes that have
2219 * to be figured out by hand.)
2221 * Currently the only thing we have to worry about
2222 * is "-" ==> "_" conversion.
2224 * This is a length-preserving operation so it is
2225 * carried out in-place, no memory is allocated
2229 * 1 : identifier = String to make JavaScript-friendly.
2233 *********************************************************************/
2234 static void javascriptify(char * identifier)
2236 char * p = identifier;
2237 while (NULL != (p = strchr(p, '-')))
2244 /*********************************************************************
2246 * Function : actions_from_radio
2248 * Description : Converts a map of parameters passed to a CGI function
2249 * into an actionsfile entry.
2252 * 1 : parameters = parameters to the CGI call
2253 * 2 : action = Action to change. Must be valid before
2254 * the call, actions not specified will be
2257 * Returns : JB_ERR_OK on success
2258 * JB_ERR_MEMORY on out-of-memory
2260 *********************************************************************/
2261 static jb_err actions_from_radio(const struct map * parameters,
2262 struct action_spec *action)
2264 static int first_time = 1;
2268 const char * js_name;
2273 /* Statics are generally a potential race condition,
2274 * but in this case we're safe and don't need semaphores.
2275 * Be careful if you modify this function.
2279 #define JAVASCRIPTIFY(dest_var, string) \
2281 static char js_name_arr[] = string; \
2284 javascriptify(js_name_arr); \
2286 dest_var = js_name_arr; \
2289 #define DEFINE_ACTION_BOOL(name, bit) \
2290 JAVASCRIPTIFY(js_name, name); \
2291 param = lookup(parameters, js_name); \
2292 ch = ijb_toupper(param[0]); \
2295 action->add |= bit; \
2296 action->mask |= bit; \
2298 else if (ch == 'N') \
2300 action->add &= ~bit; \
2301 action->mask &= ~bit; \
2303 else if (ch == 'X') \
2305 action->add &= ~bit; \
2306 action->mask |= bit; \
2309 #define DEFINE_ACTION_STRING(name, bit, index) \
2310 JAVASCRIPTIFY(js_name, name); \
2311 param = lookup(parameters, js_name); \
2312 ch = ijb_toupper(param[0]); \
2315 JAVASCRIPTIFY(js_name, name "-mode"); \
2316 param = lookup(parameters, js_name); \
2317 if ((*param == '\0') || (0 == strcmp(param, "CUSTOM"))) \
2319 JAVASCRIPTIFY(js_name, name "-param"); \
2320 param = lookup(parameters, js_name); \
2322 if (*param != '\0') \
2324 if (NULL == (param_dup = strdup(param))) \
2326 return JB_ERR_MEMORY; \
2328 freez(action->string[index]); \
2329 action->add |= bit; \
2330 action->mask |= bit; \
2331 action->string[index] = param_dup; \
2334 else if (ch == 'N') \
2336 if (action->add & bit) \
2338 freez(action->string[index]); \
2340 action->add &= ~bit; \
2341 action->mask &= ~bit; \
2343 else if (ch == 'X') \
2345 if (action->add & bit) \
2347 freez(action->string[index]); \
2349 action->add &= ~bit; \
2350 action->mask |= bit; \
2353 #define DEFINE_ACTION_MULTI(name, index) \
2354 JAVASCRIPTIFY(js_name, name); \
2355 param = lookup(parameters, js_name); \
2356 ch = ijb_toupper((int)param[0]); \
2361 else if (ch == 'N') \
2363 list_remove_all(action->multi_add[index]); \
2364 list_remove_all(action->multi_remove[index]); \
2365 action->multi_remove_all[index] = 1; \
2367 else if (ch == 'X') \
2369 list_remove_all(action->multi_add[index]); \
2370 list_remove_all(action->multi_remove[index]); \
2371 action->multi_remove_all[index] = 0; \
2374 #define DEFINE_ACTION_ALIAS 0 /* No aliases for URL parsing */
2376 #include "actionlist.h"
2378 #undef DEFINE_ACTION_MULTI
2379 #undef DEFINE_ACTION_STRING
2380 #undef DEFINE_ACTION_BOOL
2381 #undef DEFINE_ACTION_ALIAS
2382 #undef JAVASCRIPTIFY
2390 /*********************************************************************
2392 * Function : cgi_error_modified
2394 * Description : CGI function that is called when a file is modified
2395 * outside the CGI editor.
2398 * 1 : csp = Current client state (buffers, headers, etc...)
2399 * 2 : rsp = http_response data structure for output
2400 * 3 : filename = The file that was modified.
2402 * CGI Parameters : none
2404 * Returns : JB_ERR_OK on success
2405 * JB_ERR_MEMORY on out-of-memory error.
2407 *********************************************************************/
2408 jb_err cgi_error_modified(struct client_state *csp,
2409 struct http_response *rsp,
2410 const char *filename)
2412 struct map *exports;
2419 if (NULL == (exports = default_exports(csp, NULL)))
2421 return JB_ERR_MEMORY;
2424 err = map(exports, "filename", 1, filename, 1);
2431 return template_fill_for_cgi(csp, "cgi-error-modified", exports, rsp);
2435 /*********************************************************************
2437 * Function : cgi_error_parse
2439 * Description : CGI function that is called when a file cannot
2440 * be parsed by the CGI editor.
2443 * 1 : csp = Current client state (buffers, headers, etc...)
2444 * 2 : rsp = http_response data structure for output
2445 * 3 : file = The file that was modified.
2447 * CGI Parameters : none
2449 * Returns : JB_ERR_OK on success
2450 * JB_ERR_MEMORY on out-of-memory error.
2452 *********************************************************************/
2453 jb_err cgi_error_parse(struct client_state *csp,
2454 struct http_response *rsp,
2455 struct editable_file *file)
2457 struct map *exports;
2459 struct file_line *cur_line;
2465 if (NULL == (exports = default_exports(csp, NULL)))
2467 return JB_ERR_MEMORY;
2470 err = map(exports, "filename", 1, file->identifier, 1);
2471 if (!err) err = map(exports, "parse-error", 1, file->parse_error_text, 1);
2473 cur_line = file->parse_error;
2476 if (!err) err = map(exports, "line-raw", 1, html_encode(cur_line->raw), 0);
2477 if (!err) err = map(exports, "line-data", 1, html_encode(cur_line->unprocessed), 0);
2485 return template_fill_for_cgi(csp, "cgi-error-parse", exports, rsp);
2489 /*********************************************************************
2491 * Function : cgi_error_file
2493 * Description : CGI function that is called when a file cannot be
2494 * opened by the CGI editor.
2497 * 1 : csp = Current client state (buffers, headers, etc...)
2498 * 2 : rsp = http_response data structure for output
2499 * 3 : filename = The file that was modified.
2501 * CGI Parameters : none
2503 * Returns : JB_ERR_OK on success
2504 * JB_ERR_MEMORY on out-of-memory error.
2506 *********************************************************************/
2507 jb_err cgi_error_file(struct client_state *csp,
2508 struct http_response *rsp,
2509 const char *filename)
2511 struct map *exports;
2518 if (NULL == (exports = default_exports(csp, NULL)))
2520 return JB_ERR_MEMORY;
2523 err = map(exports, "filename", 1, filename, 1);
2530 return template_fill_for_cgi(csp, "cgi-error-file", exports, rsp);
2534 /*********************************************************************
2536 * Function : cgi_error_bad_param
2538 * Description : CGI function that is called if the parameters
2539 * (query string) for a CGI were wrong.
2542 * 1 : csp = Current client state (buffers, headers, etc...)
2543 * 2 : rsp = http_response data structure for output
2545 * CGI Parameters : none
2547 * Returns : JB_ERR_OK on success
2548 * JB_ERR_MEMORY on out-of-memory error.
2550 *********************************************************************/
2551 jb_err cgi_error_disabled(struct client_state *csp,
2552 struct http_response *rsp)
2554 struct map *exports;
2559 if (NULL == (exports = default_exports(csp, NULL)))
2561 return JB_ERR_MEMORY;
2564 return template_fill_for_cgi(csp, "cgi-error-disabled", exports, rsp);
2568 /*********************************************************************
2570 * Function : cgi_edit_actions
2572 * Description : CGI function that allows the user to choose which
2573 * actions file to edit.
2576 * 1 : csp = Current client state (buffers, headers, etc...)
2577 * 2 : rsp = http_response data structure for output
2578 * 3 : parameters = map of cgi parameters
2580 * CGI Parameters : None
2582 * Returns : JB_ERR_OK on success
2583 * JB_ERR_MEMORY on out-of-memory error
2585 *********************************************************************/
2586 jb_err cgi_edit_actions(struct client_state *csp,
2587 struct http_response *rsp,
2588 const struct map *parameters)
2591 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
2593 return cgi_error_disabled(csp, rsp);
2596 /* FIXME: Incomplete */
2597 rsp->status = strdup("302 Local Redirect from Junkbuster");
2598 if (rsp->status == NULL)
2600 return JB_ERR_MEMORY;
2602 if (enlist_unique_header(rsp->headers, "Location", "http://ijbswa.sourceforge.net/config/edit-actions-list?filename=ijb"))
2606 return JB_ERR_MEMORY;
2613 /*********************************************************************
2615 * Function : cgi_edit_actions_list
2617 * Description : CGI function that edits the actions list.
2618 * FIXME: This function shouldn't FATAL ever.
2619 * FIXME: This function doesn't check the retval of map()
2621 * 1 : csp = Current client state (buffers, headers, etc...)
2622 * 2 : rsp = http_response data structure for output
2623 * 3 : parameters = map of cgi parameters
2625 * CGI Parameters : filename
2627 * Returns : JB_ERR_OK on success
2628 * JB_ERR_MEMORY on out-of-memory
2629 * JB_ERR_FILE if the file cannot be opened or
2631 * JB_ERR_CGI_PARAMS if "filename" was not specified
2634 *********************************************************************/
2635 jb_err cgi_edit_actions_list(struct client_state *csp,
2636 struct http_response *rsp,
2637 const struct map *parameters)
2639 char * section_template;
2640 char * url_template;
2645 struct map * exports;
2646 struct map * section_exports;
2647 struct map * url_exports;
2648 struct editable_file * file;
2649 struct file_line * cur_line;
2650 unsigned line_number = 0;
2654 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
2656 return cgi_error_disabled(csp, rsp);
2659 err = edit_read_actions_file(csp, rsp, parameters, 0, &file);
2662 /* No filename specified, can't read file, or out of memory. */
2663 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
2666 if (NULL == (exports = default_exports(csp, NULL)))
2668 edit_free_file(file);
2669 return JB_ERR_MEMORY;
2672 err = map(exports, "filename", 1, file->identifier, 1);
2673 if (!err) err = map(exports, "ver", 1, file->version_str, 1);
2677 edit_free_file(file);
2682 /* Should do all global exports above this point */
2684 err = template_load(csp, §ion_template, "edit-actions-list-section");
2687 edit_free_file(file);
2689 if (err == JB_ERR_FILE)
2691 return cgi_error_no_template(csp, rsp, "edit-actions-list-section");
2696 err = template_load(csp, &url_template, "edit-actions-list-url");
2699 free(section_template);
2700 edit_free_file(file);
2702 if (err == JB_ERR_FILE)
2704 return cgi_error_no_template(csp, rsp, "edit-actions-list-url");
2709 err = template_fill(§ion_template, exports);
2713 edit_free_file(file);
2719 err = template_fill(&url_template, exports);
2722 free(section_template);
2723 edit_free_file(file);
2728 /* Find start of actions in file */
2729 cur_line = file->lines;
2731 while ((cur_line != NULL) && (cur_line->type != FILE_LINE_ACTION))
2733 cur_line = cur_line->next;
2737 if (NULL == (sections = strdup("")))
2739 free(section_template);
2741 edit_free_file(file);
2743 return JB_ERR_MEMORY;
2746 while ((cur_line != NULL) && (cur_line->type == FILE_LINE_ACTION))
2748 if (NULL == (section_exports = new_map()))
2751 free(section_template);
2753 edit_free_file(file);
2755 return JB_ERR_MEMORY;
2758 snprintf(buf, 50, "%d", line_number);
2759 err = map(section_exports, "sectionid", 1, buf, 1);
2760 if (!err) err = map(section_exports, "actions", 1,
2761 actions_to_html(cur_line->data.action), 0);
2764 && (cur_line->next != NULL)
2765 && (cur_line->next->type == FILE_LINE_URL))
2767 /* This section contains at least one URL, don't allow delete */
2768 err = map_block_killer(section_exports, "empty-section");
2774 free(section_template);
2776 edit_free_file(file);
2778 free_map(section_exports);
2782 /* Should do all section-specific exports above this point */
2784 if (NULL == (urls = strdup("")))
2787 free(section_template);
2789 edit_free_file(file);
2791 free_map(section_exports);
2792 return JB_ERR_MEMORY;
2797 cur_line = cur_line->next;
2800 while ((cur_line != NULL) && (cur_line->type == FILE_LINE_URL))
2802 if (NULL == (url_exports = new_map()))
2806 free(section_template);
2808 edit_free_file(file);
2810 free_map(section_exports);
2811 return JB_ERR_MEMORY;
2814 snprintf(buf, 50, "%d", line_number);
2815 err = map(url_exports, "urlid", 1, buf, 1);
2817 snprintf(buf, 50, "%d", url_1_2);
2818 if (!err) err = map(url_exports, "url-1-2", 1, buf, 1);
2820 if (!err) err = map(url_exports, "url-html", 1,
2821 html_encode(cur_line->unprocessed), 0);
2822 if (!err) err = map(url_exports, "url", 1,
2823 url_encode(cur_line->unprocessed), 0);
2829 free(section_template);
2831 edit_free_file(file);
2833 free_map(section_exports);
2834 free_map(url_exports);
2838 if (NULL == (s = strdup(url_template)))
2842 free(section_template);
2844 edit_free_file(file);
2846 free_map(section_exports);
2847 free_map(url_exports);
2848 return JB_ERR_MEMORY;
2851 err = template_fill(&s, section_exports);
2852 if (!err) err = template_fill(&s, url_exports);
2853 if (!err) err = string_append(&urls, s);
2855 free_map(url_exports);
2862 free(section_template);
2864 edit_free_file(file);
2866 free_map(section_exports);
2870 url_1_2 = 3 - url_1_2;
2872 cur_line = cur_line->next;
2876 err = map(section_exports, "urls", 1, urls, 0);
2881 free(section_template);
2883 edit_free_file(file);
2885 free_map(section_exports);
2889 /* Could also do section-specific exports here, but it wouldn't be as fast */
2891 if (NULL == (s = strdup(section_template)))
2894 free(section_template);
2896 edit_free_file(file);
2898 free_map(section_exports);
2899 return JB_ERR_MEMORY;
2902 err = template_fill(&s, section_exports);
2903 if (!err) err = string_append(§ions, s);
2906 free_map(section_exports);
2911 free(section_template);
2913 edit_free_file(file);
2919 edit_free_file(file);
2920 free(section_template);
2923 err = map(exports, "sections", 1, sections, 0);
2930 /* Could also do global exports here, but it wouldn't be as fast */
2932 return template_fill_for_cgi(csp, "edit-actions-list", exports, rsp);
2936 /*********************************************************************
2938 * Function : cgi_edit_actions
2940 * Description : CGI function that edits the Actions list.
2943 * 1 : csp = Current client state (buffers, headers, etc...)
2944 * 2 : rsp = http_response data structure for output
2945 * 3 : parameters = map of cgi parameters
2947 * CGI Parameters : None
2949 * Returns : JB_ERR_OK on success
2950 * JB_ERR_MEMORY on out-of-memory
2951 * JB_ERR_CGI_PARAMS if the CGI parameters are not
2952 * specified or not valid.
2954 *********************************************************************/
2955 jb_err cgi_edit_actions_for_url(struct client_state *csp,
2956 struct http_response *rsp,
2957 const struct map *parameters)
2959 struct map * exports;
2961 struct editable_file * file;
2962 struct file_line * cur_line;
2963 unsigned line_number;
2966 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
2968 return cgi_error_disabled(csp, rsp);
2971 err = get_number_param(csp, parameters, "section", §ionid);
2977 err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
2980 /* No filename specified, can't read file, modified, or out of memory. */
2981 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
2984 cur_line = file->lines;
2986 for (line_number = 1; (cur_line != NULL) && (line_number < sectionid); line_number++)
2988 cur_line = cur_line->next;
2991 if ( (cur_line == NULL)
2992 || (line_number != sectionid)
2994 || (cur_line->type != FILE_LINE_ACTION))
2996 /* Invalid "sectionid" parameter */
2997 edit_free_file(file);
2998 return JB_ERR_CGI_PARAMS;
3001 if (NULL == (exports = default_exports(csp, NULL)))
3003 edit_free_file(file);
3004 return JB_ERR_MEMORY;
3007 err = map(exports, "filename", 1, file->identifier, 1);
3008 if (!err) err = map(exports, "ver", 1, file->version_str, 1);
3009 if (!err) err = map(exports, "section", 1, lookup(parameters, "section"), 1);
3011 if (!err) err = actions_to_radio(exports, cur_line->data.action);
3013 edit_free_file(file);
3021 return template_fill_for_cgi(csp, "edit-actions-for-url", exports, rsp);
3025 /*********************************************************************
3027 * Function : cgi_edit_actions_submit
3029 * Description : CGI function that actually edits the Actions list.
3032 * 1 : csp = Current client state (buffers, headers, etc...)
3033 * 2 : rsp = http_response data structure for output
3034 * 3 : parameters = map of cgi parameters
3036 * CGI Parameters : None
3038 * Returns : JB_ERR_OK on success
3039 * JB_ERR_MEMORY on out-of-memory
3040 * JB_ERR_CGI_PARAMS if the CGI parameters are not
3041 * specified or not valid.
3043 *********************************************************************/
3044 jb_err cgi_edit_actions_submit(struct client_state *csp,
3045 struct http_response *rsp,
3046 const struct map *parameters)
3052 struct editable_file * file;
3053 struct file_line * cur_line;
3054 unsigned line_number;
3058 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3060 return cgi_error_disabled(csp, rsp);
3063 err = get_number_param(csp, parameters, "section", §ionid);
3069 err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3072 /* No filename specified, can't read file, modified, or out of memory. */
3073 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3076 cur_line = file->lines;
3078 for (line_number = 1; (cur_line != NULL) && (line_number < sectionid); line_number++)
3080 cur_line = cur_line->next;
3083 if ( (cur_line == NULL)
3084 || (line_number != sectionid)
3086 || (cur_line->type != FILE_LINE_ACTION))
3088 /* Invalid "sectionid" parameter */
3089 edit_free_file(file);
3090 return JB_ERR_CGI_PARAMS;
3093 err = actions_from_radio(parameters, cur_line->data.action);
3097 edit_free_file(file);
3101 if (NULL == (actiontext = actions_to_text(cur_line->data.action)))
3104 edit_free_file(file);
3105 return JB_ERR_MEMORY;
3108 len = strlen(actiontext);
3112 * Empty action - must special-case this.
3113 * Simply setting len to 1 is sufficient...
3118 if (NULL == (newtext = malloc(len + 2)))
3122 edit_free_file(file);
3123 return JB_ERR_MEMORY;
3125 strcpy(newtext, actiontext);
3129 newtext[len + 1] = '\0';
3131 freez(cur_line->raw);
3132 freez(cur_line->unprocessed);
3133 cur_line->unprocessed = newtext;
3135 err = edit_write_file(file);
3138 /* Error writing file */
3139 edit_free_file(file);
3143 target = strdup("http://ijbswa.sourceforge.net/config/edit-actions-list?filename=");
3144 string_append(&target, file->identifier);
3146 edit_free_file(file);
3151 return JB_ERR_MEMORY;
3154 rsp->status = strdup("302 Local Redirect from Junkbuster");
3155 if (rsp->status == NULL)
3158 return JB_ERR_MEMORY;
3160 err = enlist_unique_header(rsp->headers, "Location", target);
3167 /*********************************************************************
3169 * Function : cgi_edit_actions_url
3171 * Description : CGI function that actually edits a URL pattern in
3175 * 1 : csp = Current client state (buffers, headers, etc...)
3176 * 2 : rsp = http_response data structure for output
3177 * 3 : parameters = map of cgi parameters
3180 * filename : Identifies the file to edit
3181 * ver : File's last-modified time
3182 * section : Line number of section to edit
3183 * pattern : Line number of pattern to edit
3184 * newval : New value for pattern
3186 * Returns : JB_ERR_OK on success
3187 * JB_ERR_MEMORY on out-of-memory
3188 * JB_ERR_CGI_PARAMS if the CGI parameters are not
3189 * specified or not valid.
3191 *********************************************************************/
3192 jb_err cgi_edit_actions_url(struct client_state *csp,
3193 struct http_response *rsp,
3194 const struct map *parameters)
3198 const char * newval;
3200 struct editable_file * file;
3201 struct file_line * cur_line;
3202 unsigned line_number;
3206 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3208 return cgi_error_disabled(csp, rsp);
3211 err = get_number_param(csp, parameters, "section", §ionid);
3217 err = get_number_param(csp, parameters, "pattern", &patternid);
3223 newval = lookup(parameters, "newval");
3225 if ((*newval == '\0') || (sectionid < 1U) || (patternid < 1U))
3227 return JB_ERR_CGI_PARAMS;
3230 err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3233 /* No filename specified, can't read file, modified, or out of memory. */
3234 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3238 cur_line = file->lines;
3240 while ((cur_line != NULL) && (line_number < sectionid))
3242 cur_line = cur_line->next;
3246 if ( (cur_line == NULL)
3247 || (cur_line->type != FILE_LINE_ACTION))
3249 /* Invalid "sectionid" parameter */
3250 edit_free_file(file);
3251 return JB_ERR_CGI_PARAMS;
3254 while (line_number < patternid)
3256 cur_line = cur_line->next;
3259 if ( (cur_line == NULL)
3260 || ( (cur_line->type != FILE_LINE_URL)
3261 && (cur_line->type != FILE_LINE_BLANK) ) )
3263 /* Invalid "patternid" parameter */
3264 edit_free_file(file);
3265 return JB_ERR_CGI_PARAMS;
3269 if (cur_line->type != FILE_LINE_URL)
3271 /* Invalid "patternid" parameter */
3272 edit_free_file(file);
3273 return JB_ERR_CGI_PARAMS;
3276 /* At this point, the line to edit is in cur_line */
3278 new_pattern = strdup(newval);
3279 if (NULL == new_pattern)
3281 edit_free_file(file);
3282 return JB_ERR_MEMORY;
3285 freez(cur_line->raw);
3286 freez(cur_line->unprocessed);
3287 cur_line->unprocessed = new_pattern;
3289 err = edit_write_file(file);
3292 /* Error writing file */
3293 edit_free_file(file);
3297 target = strdup("http://ijbswa.sourceforge.net/config/edit-actions-list?filename=");
3298 string_append(&target, file->identifier);
3300 edit_free_file(file);
3305 return JB_ERR_MEMORY;
3308 rsp->status = strdup("302 Local Redirect from Junkbuster");
3309 if (rsp->status == NULL)
3312 return JB_ERR_MEMORY;
3314 err = enlist_unique_header(rsp->headers, "Location", target);
3321 /*********************************************************************
3323 * Function : cgi_edit_actions_add_url
3325 * Description : CGI function that actually adds a URL pattern to
3329 * 1 : csp = Current client state (buffers, headers, etc...)
3330 * 2 : rsp = http_response data structure for output
3331 * 3 : parameters = map of cgi parameters
3334 * filename : Identifies the file to edit
3335 * ver : File's last-modified time
3336 * section : Line number of section to edit
3337 * newval : New pattern
3339 * Returns : JB_ERR_OK on success
3340 * JB_ERR_MEMORY on out-of-memory
3341 * JB_ERR_CGI_PARAMS if the CGI parameters are not
3342 * specified or not valid.
3344 *********************************************************************/
3345 jb_err cgi_edit_actions_add_url(struct client_state *csp,
3346 struct http_response *rsp,
3347 const struct map *parameters)
3350 const char * newval;
3352 struct file_line * new_line;
3353 struct editable_file * file;
3354 struct file_line * cur_line;
3355 unsigned line_number;
3359 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3361 return cgi_error_disabled(csp, rsp);
3364 err = get_number_param(csp, parameters, "section", §ionid);
3370 newval = lookup(parameters, "newval");
3372 if ((*newval == '\0') || (sectionid < 1U))
3374 return JB_ERR_CGI_PARAMS;
3377 err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3380 /* No filename specified, can't read file, modified, or out of memory. */
3381 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3385 cur_line = file->lines;
3387 while ((cur_line != NULL) && (line_number < sectionid))
3389 cur_line = cur_line->next;
3393 if ( (cur_line == NULL)
3394 || (cur_line->type != FILE_LINE_ACTION))
3396 /* Invalid "sectionid" parameter */
3397 edit_free_file(file);
3398 return JB_ERR_CGI_PARAMS;
3401 /* At this point, the section header is in cur_line - add after this. */
3403 new_pattern = strdup(newval);
3404 if (NULL == new_pattern)
3406 edit_free_file(file);
3407 return JB_ERR_MEMORY;
3410 /* Allocate the new line */
3411 new_line = (struct file_line *)zalloc(sizeof(*new_line));
3412 if (new_line == NULL)
3415 edit_free_file(file);
3416 return JB_ERR_MEMORY;
3419 /* Fill in the data members of the new line */
3420 new_line->raw = NULL;
3421 new_line->prefix = NULL;
3422 new_line->unprocessed = new_pattern;
3423 new_line->type = FILE_LINE_URL;
3425 /* Link new_line into the list, after cur_line */
3426 new_line->next = cur_line->next;
3427 cur_line->next = new_line;
3429 /* Done making changes, now commit */
3431 err = edit_write_file(file);
3434 /* Error writing file */
3435 edit_free_file(file);
3439 target = strdup("http://ijbswa.sourceforge.net/config/edit-actions-list?filename=");
3440 string_append(&target, file->identifier);
3442 edit_free_file(file);
3447 return JB_ERR_MEMORY;
3450 rsp->status = strdup("302 Local Redirect from Junkbuster");
3451 if (rsp->status == NULL)
3454 return JB_ERR_MEMORY;
3456 err = enlist_unique_header(rsp->headers, "Location", target);
3463 /*********************************************************************
3465 * Function : cgi_edit_actions_remove_url
3467 * Description : CGI function that actually removes a URL pattern from
3471 * 1 : csp = Current client state (buffers, headers, etc...)
3472 * 2 : rsp = http_response data structure for output
3473 * 3 : parameters = map of cgi parameters
3476 * filename : Identifies the file to edit
3477 * ver : File's last-modified time
3478 * section : Line number of section to edit
3479 * pattern : Line number of pattern to edit
3481 * Returns : JB_ERR_OK on success
3482 * JB_ERR_MEMORY on out-of-memory
3483 * JB_ERR_CGI_PARAMS if the CGI parameters are not
3484 * specified or not valid.
3486 *********************************************************************/
3487 jb_err cgi_edit_actions_remove_url(struct client_state *csp,
3488 struct http_response *rsp,
3489 const struct map *parameters)
3493 struct editable_file * file;
3494 struct file_line * cur_line;
3495 struct file_line * prev_line;
3496 unsigned line_number;
3500 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3502 return cgi_error_disabled(csp, rsp);
3505 err = get_number_param(csp, parameters, "section", §ionid);
3511 err = get_number_param(csp, parameters, "pattern", &patternid);
3518 err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3521 /* No filename specified, can't read file, modified, or out of memory. */
3522 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3526 cur_line = file->lines;
3528 while ((cur_line != NULL) && (line_number < sectionid))
3530 cur_line = cur_line->next;
3534 if ( (cur_line == NULL)
3535 || (cur_line->type != FILE_LINE_ACTION))
3537 /* Invalid "sectionid" parameter */
3538 edit_free_file(file);
3539 return JB_ERR_CGI_PARAMS;
3543 while (line_number < patternid)
3545 prev_line = cur_line;
3546 cur_line = cur_line->next;
3549 if ( (cur_line == NULL)
3550 || ( (cur_line->type != FILE_LINE_URL)
3551 && (cur_line->type != FILE_LINE_BLANK) ) )
3553 /* Invalid "patternid" parameter */
3554 edit_free_file(file);
3555 return JB_ERR_CGI_PARAMS;
3559 if (cur_line->type != FILE_LINE_URL)
3561 /* Invalid "patternid" parameter */
3562 edit_free_file(file);
3563 return JB_ERR_CGI_PARAMS;
3568 /* At this point, the line to remove is in cur_line, and the previous
3569 * one is in prev_line
3572 /* Unlink cur_line */
3573 prev_line->next = cur_line->next;
3574 cur_line->next = NULL;
3577 edit_free_file_lines(cur_line);
3579 err = edit_write_file(file);
3582 /* Error writing file */
3583 edit_free_file(file);
3587 target = strdup("http://ijbswa.sourceforge.net/config/edit-actions-list?filename=");
3588 string_append(&target, file->identifier);
3590 edit_free_file(file);
3595 return JB_ERR_MEMORY;
3598 rsp->status = strdup("302 Local Redirect from Junkbuster");
3599 if (rsp->status == NULL)
3602 return JB_ERR_MEMORY;
3604 err = enlist_unique_header(rsp->headers, "Location", target);
3611 /*********************************************************************
3613 * Function : cgi_edit_actions_section_remove
3615 * Description : CGI function that actually removes a whole section from
3616 * the actions file. The section must be empty first
3617 * (else JB_ERR_CGI_PARAMS).
3620 * 1 : csp = Current client state (buffers, headers, etc...)
3621 * 2 : rsp = http_response data structure for output
3622 * 3 : parameters = map of cgi parameters
3625 * filename : Identifies the file to edit
3626 * ver : File's last-modified time
3627 * section : Line number of section to edit
3628 * pattern : Line number of pattern to edit
3630 * Returns : JB_ERR_OK on success
3631 * JB_ERR_MEMORY on out-of-memory
3632 * JB_ERR_CGI_PARAMS if the CGI parameters are not
3633 * specified or not valid.
3635 *********************************************************************/
3636 jb_err cgi_edit_actions_section_remove(struct client_state *csp,
3637 struct http_response *rsp,
3638 const struct map *parameters)
3641 struct editable_file * file;
3642 struct file_line * cur_line;
3643 struct file_line * prev_line;
3644 unsigned line_number;
3648 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3650 return cgi_error_disabled(csp, rsp);
3653 err = get_number_param(csp, parameters, "section", §ionid);
3659 err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3662 /* No filename specified, can't read file, modified, or out of memory. */
3663 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3667 cur_line = file->lines;
3670 while ((cur_line != NULL) && (line_number < sectionid))
3672 prev_line = cur_line;
3673 cur_line = cur_line->next;
3677 if ( (cur_line == NULL)
3678 || (cur_line->type != FILE_LINE_ACTION) )
3680 /* Invalid "sectionid" parameter */
3681 edit_free_file(file);
3682 return JB_ERR_CGI_PARAMS;
3685 if ( (cur_line->next != NULL)
3686 && (cur_line->next->type == FILE_LINE_URL) )
3688 /* Section not empty. */
3689 edit_free_file(file);
3690 return JB_ERR_CGI_PARAMS;
3693 /* At this point, the line to remove is in cur_line, and the previous
3694 * one is in prev_line
3697 /* Unlink cur_line */
3698 if (prev_line == NULL)
3700 /* Removing the first line from the file */
3701 file->lines = cur_line->next;
3705 prev_line->next = cur_line->next;
3707 cur_line->next = NULL;
3710 edit_free_file_lines(cur_line);
3712 err = edit_write_file(file);
3715 /* Error writing file */
3716 edit_free_file(file);
3720 target = strdup("http://ijbswa.sourceforge.net/config/edit-actions-list?filename=");
3721 string_append(&target, file->identifier);
3723 edit_free_file(file);
3728 return JB_ERR_MEMORY;
3731 rsp->status = strdup("302 Local Redirect from Junkbuster");
3732 if (rsp->status == NULL)
3735 return JB_ERR_MEMORY;
3737 err = enlist_unique_header(rsp->headers, "Location", target);
3744 /*********************************************************************
3746 * Function : cgi_edit_actions_section_add
3748 * Description : CGI function that adds a new empty section to
3752 * 1 : csp = Current client state (buffers, headers, etc...)
3753 * 2 : rsp = http_response data structure for output
3754 * 3 : parameters = map of cgi parameters
3757 * filename : Identifies the file to edit
3758 * ver : File's last-modified time
3759 * section : Line number of section to add after, 0 for start
3762 * Returns : JB_ERR_OK on success
3763 * JB_ERR_MEMORY on out-of-memory
3764 * JB_ERR_CGI_PARAMS if the CGI parameters are not
3765 * specified or not valid.
3767 *********************************************************************/
3768 jb_err cgi_edit_actions_section_add(struct client_state *csp,
3769 struct http_response *rsp,
3770 const struct map *parameters)
3773 struct file_line * new_line;
3775 struct editable_file * file;
3776 struct file_line * cur_line;
3777 unsigned line_number;
3781 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
3783 return cgi_error_disabled(csp, rsp);
3786 err = get_number_param(csp, parameters, "section", §ionid);
3792 err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
3795 /* No filename specified, can't read file, modified, or out of memory. */
3796 return (err == JB_ERR_FILE ? JB_ERR_OK : err);
3800 cur_line = file->lines;
3804 /* Add to start of file */
3805 if (cur_line != NULL)
3807 /* There's something in the file, find the line before the first
3810 while ( (cur_line->next != NULL)
3811 && (cur_line->next->type != FILE_LINE_ACTION) )
3813 cur_line = cur_line->next;
3820 /* Add after stated section. */
3821 while ((cur_line != NULL) && (line_number < sectionid))
3823 cur_line = cur_line->next;
3827 if ( (cur_line == NULL)
3828 || (cur_line->type != FILE_LINE_ACTION))
3830 /* Invalid "sectionid" parameter */
3831 edit_free_file(file);
3832 return JB_ERR_CGI_PARAMS;
3835 /* Skip through the section to find the last line in it. */
3836 while ( (cur_line->next != NULL)
3837 && (cur_line->next->type != FILE_LINE_ACTION) )
3839 cur_line = cur_line->next;
3844 /* At this point, the last line in the previous section is in cur_line
3845 * - add after this. (Or if we need to add as the first line, cur_line
3849 new_text = strdup("{}");
3850 if (NULL == new_text)
3852 edit_free_file(file);
3853 return JB_ERR_MEMORY;
3856 /* Allocate the new line */
3857 new_line = (struct file_line *)zalloc(sizeof(*new_line));
3858 if (new_line == NULL)
3861 edit_free_file(file);
3862 return JB_ERR_MEMORY;
3865 /* Fill in the data members of the new line */
3866 new_line->raw = NULL;
3867 new_line->prefix = NULL;
3868 new_line->unprocessed = new_text;
3869 new_line->type = FILE_LINE_ACTION;
3871 if (cur_line != NULL)
3873 /* Link new_line into the list, after cur_line */
3874 new_line->next = cur_line->next;
3875 cur_line->next = new_line;
3879 /* Link new_line into the list, as first line */
3880 new_line->next = file->lines;
3881 file->lines = new_line;
3884 /* Done making changes, now commit */
3886 err = edit_write_file(file);
3889 /* Error writing file */
3890 edit_free_file(file);
3894 target = strdup("http://ijbswa.sourceforge.net/config/edit-actions-list?filename=");
3895 string_append(&target, file->identifier);
3897 edit_free_file(file);
3902 return JB_ERR_MEMORY;
3905 rsp->status = strdup("302 Local Redirect from Junkbuster");
3906 if (rsp->status == NULL)
3909 return JB_ERR_MEMORY;
3911 err = enlist_unique_header(rsp->headers, "Location", target);
3918 /*********************************************************************
3920 * Function : cgi_toggle
3922 * Description : CGI function that adds a new empty section to
3926 * 1 : csp = Current client state (buffers, headers, etc...)
3927 * 2 : rsp = http_response data structure for output
3928 * 3 : parameters = map of cgi parameters
3931 * set : If present, how to change toggle setting:
3932 * "enable", "disable", "toggle", or none (default).
3933 * mini : If present, use mini reply template.
3935 * Returns : JB_ERR_OK on success
3936 * JB_ERR_MEMORY on out-of-memory
3938 *********************************************************************/
3939 jb_err cgi_toggle(struct client_state *csp,
3940 struct http_response *rsp,
3941 const struct map *parameters)
3943 struct map *exports;
3945 const char *template_name;
3952 if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_TOGGLE))
3954 return cgi_error_disabled(csp, rsp);
3957 if (NULL == (exports = default_exports(csp, "toggle")))
3959 return JB_ERR_MEMORY;
3962 mode = *(lookup(parameters, "set"));
3969 else if (mode == 'd')
3974 else if (mode == 't')
3977 g_bToggleIJB = !g_bToggleIJB;
3980 err = map_conditional(exports, "enabled", g_bToggleIJB);
3987 template_name = (*(lookup(parameters, "mini"))
3991 return template_fill_for_cgi(csp, template_name, exports, rsp);
3995 #endif /* def FEATURE_CGI_EDIT_ACTIONS */