LCOV - code coverage report
Current view: top level - fuzz - cgiedit.c (source / functions) Hit Total Coverage
Test: trace.lcov_info_final Lines: 1105 1502 73.6 %
Date: 2021-02-22 04:51:02 Functions: 35 38 92.1 %

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

Generated by: LCOV version 1.14