Moving source files to src/
authorjongfoster <jongfoster@users.sourceforge.net>
Tue, 4 Jun 2002 14:34:21 +0000 (14:34 +0000)
committerjongfoster <jongfoster@users.sourceforge.net>
Tue, 4 Jun 2002 14:34:21 +0000 (14:34 +0000)
52 files changed:
src/actionlist.h [new file with mode: 0644]
src/actions.c [new file with mode: 0644]
src/actions.h [new file with mode: 0644]
src/amiga.c [new file with mode: 0644]
src/amiga.h [new file with mode: 0644]
src/cgi.c [new file with mode: 0644]
src/cgi.h [new file with mode: 0644]
src/cgiedit.c [new file with mode: 0644]
src/cgiedit.h [new file with mode: 0644]
src/cgisimple.c [new file with mode: 0644]
src/cgisimple.h [new file with mode: 0644]
src/cygwin.h [new file with mode: 0644]
src/deanimate.c [new file with mode: 0644]
src/deanimate.h [new file with mode: 0644]
src/encode.c [new file with mode: 0644]
src/encode.h [new file with mode: 0644]
src/errlog.c [new file with mode: 0644]
src/errlog.h [new file with mode: 0644]
src/filters.c [new file with mode: 0644]
src/filters.h [new file with mode: 0644]
src/gateway.c [new file with mode: 0644]
src/gateway.h [new file with mode: 0644]
src/jbsockets.c [new file with mode: 0644]
src/jbsockets.h [new file with mode: 0644]
src/jcc.c [new file with mode: 0644]
src/jcc.h [new file with mode: 0644]
src/killpopup.c [new file with mode: 0644]
src/killpopup.h [new file with mode: 0644]
src/list.c [new file with mode: 0644]
src/list.h [new file with mode: 0644]
src/loadcfg.c [new file with mode: 0644]
src/loadcfg.h [new file with mode: 0644]
src/loaders.c [new file with mode: 0644]
src/loaders.h [new file with mode: 0644]
src/miscutil.c [new file with mode: 0644]
src/miscutil.h [new file with mode: 0644]
src/parsers.c [new file with mode: 0644]
src/parsers.h [new file with mode: 0644]
src/pcrs.c [new file with mode: 0644]
src/pcrs.h [new file with mode: 0644]
src/project.h [new file with mode: 0644]
src/ssplit.c [new file with mode: 0644]
src/ssplit.h [new file with mode: 0644]
src/urlmatch.c [new file with mode: 0644]
src/urlmatch.h [new file with mode: 0644]
src/w32log.c [new file with mode: 0644]
src/w32log.h [new file with mode: 0644]
src/w32res.h [new file with mode: 0644]
src/w32taskbar.c [new file with mode: 0644]
src/w32taskbar.h [new file with mode: 0644]
src/win32.c [new file with mode: 0644]
src/win32.h [new file with mode: 0644]

diff --git a/src/actionlist.h b/src/actionlist.h
new file mode 100644 (file)
index 0000000..c7247f9
--- /dev/null
@@ -0,0 +1,173 @@
+/*********************************************************************
+ *
+ * File        :  $Source: /cvsroot/ijbswa/current/actionlist.h,v $
+ *
+ * Purpose     :  Master list of supported actions.
+ *                Not really a header, since it generates code.
+ *                This is included (3 times!) from actions.c
+ *                Each time, the following macros are defined to
+ *                suitable values beforehand:
+ *                    DEFINE_ACTION_MULTI()
+ *                    DEFINE_ACTION_STRING()
+ *                    DEFINE_ACTION_BOOL()
+ *                    DEFINE_ACTION_ALIAS
+ *
+ * Copyright   :  Written by and Copyright (C) 2001 the SourceForge
+ *                Privoxy team. http://www.privoxy.org/
+ *
+ *                Based on the Internet Junkbuster originally written
+ *                by and Copyright (C) 1997 Anonymous Coders and 
+ *                Junkbusters Corporation.  http://www.junkbusters.com
+ *
+ *                This program is free software; you can redistribute it 
+ *                and/or modify it under the terms of the GNU General
+ *                Public License as published by the Free Software
+ *                Foundation; either version 2 of the License, or (at
+ *                your option) any later version.
+ *
+ *                This program is distributed in the hope that it will
+ *                be useful, but WITHOUT ANY WARRANTY; without even the
+ *                implied warranty of MERCHANTABILITY or FITNESS FOR A
+ *                PARTICULAR PURPOSE.  See the GNU General Public
+ *                License for more details.
+ *
+ *                The GNU General Public License should be included with
+ *                this file.  If not, you can view it at
+ *                http://www.gnu.org/copyleft/gpl.html
+ *                or write to the Free Software Foundation, Inc., 59
+ *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * Revisions   :
+ *    $Log: actionlist.h,v $
+ *    Revision 1.17  2002/05/14 21:25:55  oes
+ *    Renamed prevent-(setting/reading)-cookies to crunch-(incoming/outgoing)-cookies
+ *
+ *    Revision 1.16  2002/04/24 02:15:18  oes
+ *    Renamed actions as discussed, Aliased old action names to new ones.
+ *
+ *    Revision 1.15  2002/03/26 22:29:54  swa
+ *    we have a new homepage!
+ *
+ *    Revision 1.14  2002/03/24 16:32:08  jongfoster
+ *    Removing logo option
+ *
+ *    Revision 1.13  2002/03/24 15:23:33  jongfoster
+ *    Name changes
+ *
+ *    Revision 1.12  2002/03/24 13:25:43  swa
+ *    name change related issues
+ *
+ *    Revision 1.11  2002/03/12 01:42:49  oes
+ *    Introduced modular filters
+ *
+ *    Revision 1.10  2002/03/08 18:19:14  jongfoster
+ *    Adding +image-blocker{pattern} option to edit interface
+ *
+ *    Revision 1.9  2001/11/22 21:58:41  jongfoster
+ *    Adding action +no-cookies-keep
+ *
+ *    Revision 1.8  2001/10/10 16:42:52  oes
+ *    Fixed a bug, Added +limit-connect string action
+ *
+ *    Revision 1.7  2001/10/07 15:33:59  oes
+ *    Introduced a +no-compression action
+ *    Introduced a +downgrade action
+ *
+ *    Revision 1.6  2001/09/16 15:47:37  jongfoster
+ *    First version of CGI-based edit interface.  This is very much a
+ *    work-in-progress, and you can't actually use it to edit anything
+ *    yet.  You must #define FEATURE_CGI_EDIT_ACTIONS for these changes
+ *    to have any effect.
+ *
+ *    Revision 1.5  2001/07/18 12:27:03  oes
+ *    Changed deanimate-gifs to string action
+ *
+ *    Revision 1.4  2001/07/13 13:52:12  oes
+ *     - Formatting
+ *     - Introduced new action ACTION_DEANIMATE
+ *
+ *    Revision 1.3  2001/06/07 23:03:56  jongfoster
+ *    Added standard comment at top of file.
+ *
+ *
+ *********************************************************************/
+\f
+
+#if !(defined(DEFINE_ACTION_BOOL) && defined(DEFINE_ACTION_MULTI) && defined(DEFINE_ACTION_STRING))
+#error Please define lots of macros before including "actionlist.h".
+#endif /* !defined(all the DEFINE_ACTION_xxx macros) */
+
+#ifndef DEFINE_CGI_PARAM_RADIO
+#define DEFINE_CGI_PARAM_RADIO(name, bit, index, value, is_default)
+#define DEFINE_CGI_PARAM_CUSTOM(name, bit, index, default_val)
+#define DEFINE_CGI_PARAM_NO_RADIO(name, bit, index, default_val)
+#endif /* ndef DEFINE_CGI_PARAM_RADIO */
+
+DEFINE_ACTION_MULTI      ("add-header",                 ACTION_MULTI_ADD_HEADER)
+DEFINE_ACTION_BOOL       ("block",                      ACTION_BLOCK)
+DEFINE_ACTION_BOOL       ("crunch-incoming-cookies",    ACTION_NO_COOKIE_SET)
+DEFINE_ACTION_BOOL       ("crunch-outgoing-cookies",    ACTION_NO_COOKIE_READ)
+DEFINE_ACTION_STRING     ("deanimate-gifs",             ACTION_DEANIMATE,       ACTION_STRING_DEANIMATE)
+DEFINE_CGI_PARAM_RADIO   ("deanimate-gifs",             ACTION_DEANIMATE,       ACTION_STRING_DEANIMATE,     "first", 0)
+DEFINE_CGI_PARAM_RADIO   ("deanimate-gifs",             ACTION_DEANIMATE,       ACTION_STRING_DEANIMATE,     "last",  1)
+DEFINE_ACTION_BOOL       ("downgrade-http-version",     ACTION_DOWNGRADE)
+DEFINE_ACTION_BOOL       ("fast-redirects",             ACTION_FAST_REDIRECTS)
+DEFINE_ACTION_MULTI      ("filter",                     ACTION_MULTI_FILTER)
+DEFINE_ACTION_BOOL       ("handle-as-image",            ACTION_IMAGE)
+DEFINE_ACTION_BOOL       ("hide-forwarded-for-headers", ACTION_HIDE_FORWARDED)
+DEFINE_ACTION_STRING     ("hide-from-header",           ACTION_HIDE_FROM,       ACTION_STRING_FROM)
+DEFINE_CGI_PARAM_RADIO   ("hide-from-header",           ACTION_HIDE_FROM,       ACTION_STRING_FROM,          "block", 1)
+DEFINE_CGI_PARAM_CUSTOM  ("hide-from-header",           ACTION_HIDE_FROM,       ACTION_STRING_FROM,          "spam_me_senseless@sittingduck.xyz")
+DEFINE_ACTION_STRING     ("hide-referer",               ACTION_HIDE_REFERER,    ACTION_STRING_REFERER)
+DEFINE_CGI_PARAM_RADIO   ("hide-referer",               ACTION_HIDE_REFERER,    ACTION_STRING_REFERER,       "forge", 1)
+DEFINE_CGI_PARAM_RADIO   ("hide-referer",               ACTION_HIDE_REFERER,    ACTION_STRING_REFERER,       "block", 0)
+DEFINE_CGI_PARAM_CUSTOM  ("hide-referer",               ACTION_HIDE_REFERER,    ACTION_STRING_REFERER,       "http://www.google.com/")
+DEFINE_ACTION_STRING     ("hide-user-agent",            ACTION_HIDE_USER_AGENT, ACTION_STRING_USER_AGENT)
+DEFINE_CGI_PARAM_NO_RADIO("hide-user-agent",            ACTION_HIDE_USER_AGENT, ACTION_STRING_USER_AGENT,    "Privoxy/3.0 (Anonymous)")
+DEFINE_ACTION_BOOL       ("kill-popups",                ACTION_NO_POPUPS)
+DEFINE_ACTION_STRING     ("limit-connect",              ACTION_LIMIT_CONNECT,   ACTION_STRING_LIMIT_CONNECT)
+DEFINE_CGI_PARAM_NO_RADIO("limit-connect",              ACTION_LIMIT_CONNECT,   ACTION_STRING_LIMIT_CONNECT,  "443")
+DEFINE_ACTION_BOOL       ("prevent-compression",        ACTION_NO_COMPRESSION)
+DEFINE_ACTION_BOOL       ("send-vanilla-wafer",         ACTION_VANILLA_WAFER)
+DEFINE_ACTION_MULTI      ("send-wafer",                 ACTION_MULTI_WAFER)
+DEFINE_ACTION_BOOL       ("session-cookies-only",       ACTION_NO_COOKIE_KEEP)
+DEFINE_ACTION_STRING     ("set-image-blocker",          ACTION_IMAGE_BLOCKER,   ACTION_STRING_IMAGE_BLOCKER)
+DEFINE_CGI_PARAM_RADIO   ("set-image-blocker",          ACTION_IMAGE_BLOCKER,   ACTION_STRING_IMAGE_BLOCKER, "pattern", 1)
+DEFINE_CGI_PARAM_RADIO   ("set-image-blocker",          ACTION_IMAGE_BLOCKER,   ACTION_STRING_IMAGE_BLOCKER, "blank", 0)
+DEFINE_CGI_PARAM_CUSTOM  ("set-image-blocker",          ACTION_IMAGE_BLOCKER,   ACTION_STRING_IMAGE_BLOCKER,  CGI_PREFIX "show-banner?type=pattern")
+
+
+#if DEFINE_ACTION_ALIAS
+
+/* 
+ * Alternative spellings
+ */
+DEFINE_ACTION_BOOL       ("kill-popup",      ACTION_NO_POPUPS)
+DEFINE_ACTION_STRING     ("hide-referrer",   ACTION_HIDE_REFERER,    ACTION_STRING_REFERER)
+DEFINE_ACTION_BOOL       ("prevent-keeping-cookies", ACTION_NO_COOKIE_KEEP)
+
+/* 
+ * Pre-3.0 compatibility
+ */
+DEFINE_ACTION_BOOL       ("prevent-reading-cookies", ACTION_NO_COOKIE_READ)
+DEFINE_ACTION_BOOL       ("prevent-setting-cookies", ACTION_NO_COOKIE_SET)
+DEFINE_ACTION_BOOL       ("downgrade",               ACTION_DOWNGRADE)
+DEFINE_ACTION_BOOL       ("hide-forwarded",          ACTION_HIDE_FORWARDED)
+DEFINE_ACTION_STRING     ("hide-from",               ACTION_HIDE_FROM,       ACTION_STRING_FROM)
+DEFINE_ACTION_BOOL       ("image",                   ACTION_IMAGE)
+DEFINE_ACTION_STRING     ("image-blocker",           ACTION_IMAGE_BLOCKER,   ACTION_STRING_IMAGE_BLOCKER)
+DEFINE_ACTION_BOOL       ("no-compression",          ACTION_NO_COMPRESSION)
+DEFINE_ACTION_BOOL       ("no-cookies-keep",         ACTION_NO_COOKIE_KEEP)
+DEFINE_ACTION_BOOL       ("no-cookies-read",         ACTION_NO_COOKIE_READ)
+DEFINE_ACTION_BOOL       ("no-cookies-set",          ACTION_NO_COOKIE_SET)
+DEFINE_ACTION_BOOL       ("no-popups",               ACTION_NO_POPUPS)
+#endif /* if DEFINE_ACTION_ALIAS */
+
+#undef DEFINE_ACTION_MULTI
+#undef DEFINE_ACTION_STRING
+#undef DEFINE_ACTION_BOOL
+#undef DEFINE_ACTION_ALIAS
+#undef DEFINE_CGI_PARAM_CUSTOM
+#undef DEFINE_CGI_PARAM_RADIO
+#undef DEFINE_CGI_PARAM_NO_RADIO
+
diff --git a/src/actions.c b/src/actions.c
new file mode 100644 (file)
index 0000000..967faad
--- /dev/null
@@ -0,0 +1,1660 @@
+const char actions_rcs[] = "$Id: actions.c,v 1.33 2002/05/26 12:14:19 roro Exp $";
+/*********************************************************************
+ *
+ * File        :  $Source: /cvsroot/ijbswa/current/actions.c,v $
+ *
+ * Purpose     :  Declares functions to work with actions files
+ *                Functions declared include: FIXME
+ *
+ * Copyright   :  Written by and Copyright (C) 2001 the SourceForge
+ *                Privoxy team. http://www.privoxy.org/
+ *
+ *                Based on the Internet Junkbuster originally written
+ *                by and Copyright (C) 1997 Anonymous Coders and
+ *                Junkbusters Corporation.  http://www.junkbusters.com
+ *
+ *                This program is free software; you can redistribute it
+ *                and/or modify it under the terms of the GNU General
+ *                Public License as published by the Free Software
+ *                Foundation; either version 2 of the License, or (at
+ *                your option) any later version.
+ *
+ *                This program is distributed in the hope that it will
+ *                be useful, but WITHOUT ANY WARRANTY; without even the
+ *                implied warranty of MERCHANTABILITY or FITNESS FOR A
+ *                PARTICULAR PURPOSE.  See the GNU General Public
+ *                License for more details.
+ *
+ *                The GNU General Public License should be included with
+ *                this file.  If not, you can view it at
+ *                http://www.gnu.org/copyleft/gpl.html
+ *                or write to the Free Software Foundation, Inc., 59
+ *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * Revisions   :
+ *    $Log: actions.c,v $
+ *    Revision 1.33  2002/05/26 12:14:19  roro
+ *    Change unsigned to unsigned long in actions_name struct.  This closes
+ *    SourceForge Bug #539284.
+ *
+ *    Revision 1.32  2002/05/12 21:36:29  jongfoster
+ *    Correcting function comments
+ *
+ *    Revision 1.31  2002/05/06 07:56:50  oes
+ *    Made actions_to_html independent of FEATURE_CGI_EDIT_ACTIONS
+ *
+ *    Revision 1.30  2002/04/30 11:14:52  oes
+ *    Made csp the first parameter in *action_to_html
+ *
+ *    Revision 1.29  2002/04/26 19:30:54  jongfoster
+ *    - current_action_to_html(): Adding help link for the "-" form of
+ *      one-string actions.
+ *    - Some actions had "<br>-", some "<br> -" (note the space).
+ *      Standardizing on no space.
+ *    - Greatly simplifying some of the code by using string_join()
+ *      where appropriate.
+ *
+ *    Revision 1.28  2002/04/26 12:53:15  oes
+ *     - CGI AF editor now writes action lines split into
+ *       single lines with line continuation
+ *     - actions_to_html now embeds each action name in
+ *       link to chapter
+ *     - current_action_to_text is now called current_action_to_html
+ *       and acts like actions_to_html
+ *
+ *    Revision 1.27  2002/04/24 02:10:31  oes
+ *     - Jon's patch for multiple AFs:
+ *       - split load_actions_file, add load_one_actions_file
+ *       - make csp->actions_list files an array
+ *       - remember file id with each action
+ *     - Copy_action now frees dest action before copying
+ *
+ *    Revision 1.26  2002/03/26 22:29:54  swa
+ *    we have a new homepage!
+ *
+ *    Revision 1.25  2002/03/24 13:25:43  swa
+ *    name change related issues
+ *
+ *    Revision 1.24  2002/03/16 23:54:06  jongfoster
+ *    Adding graceful termination feature, to help look for memory leaks.
+ *    If you enable this (which, by design, has to be done by hand
+ *    editing config.h) and then go to http://i.j.b/die, then the program
+ *    will exit cleanly after the *next* request.  It should free all the
+ *    memory that was used.
+ *
+ *    Revision 1.23  2002/03/07 03:46:16  oes
+ *    Fixed compiler warnings
+ *
+ *    Revision 1.22  2002/01/21 00:27:02  jongfoster
+ *    Allowing free_action(NULL).
+ *    Moving the functions that #include actionlist.h to the end of the file,
+ *    because the Visual C++ 97 debugger gets extremely confused if you try
+ *    to debug any code that comes after them in the file.
+ *
+ *    Revision 1.21  2002/01/17 20:54:44  jongfoster
+ *    Renaming free_url to free_url_spec, since it frees a struct url_spec.
+ *
+ *    Revision 1.20  2001/11/22 21:56:49  jongfoster
+ *    Making action_spec->flags into an unsigned long rather than just an
+ *    unsigned int.
+ *    Fixing a bug in the display of -add-header and -wafer
+ *
+ *    Revision 1.19  2001/11/13 00:14:07  jongfoster
+ *    Fixing stupid bug now I've figured out what || means.
+ *    (It always returns 0 or 1, not one of it's paramaters.)
+ *
+ *    Revision 1.18  2001/11/07 00:06:06  steudten
+ *    Add line number in error output for lineparsing for
+ *    actionsfile.
+ *
+ *    Revision 1.17  2001/10/25 03:40:47  david__schmidt
+ *    Change in porting tactics: OS/2's EMX porting layer doesn't allow multiple
+ *    threads to call select() simultaneously.  So, it's time to do a real, live,
+ *    native OS/2 port.  See defines for __EMX__ (the porting layer) vs. __OS2__
+ *    (native). Both versions will work, but using __OS2__ offers multi-threading.
+ *
+ *    Revision 1.16  2001/10/23 21:30:30  jongfoster
+ *    Adding error-checking to selected functions.
+ *
+ *    Revision 1.15  2001/10/14 21:58:22  jongfoster
+ *    Adding support for the CGI-based editor:
+ *    - Exported get_actions()
+ *    - Added new function free_alias_list()
+ *    - Added support for {{settings}} and {{description}} blocks
+ *      in the actions file.  They are currently ignored.
+ *    - Added restriction to only one {{alias}} block which must appear
+ *      first in the file, to simplify the editor's rewriting rules.
+ *    - Note that load_actions_file() is no longer used by the CGI-based
+ *      editor, but some of the other routines in this file are.
+ *
+ *    Revision 1.14  2001/09/22 16:36:59  jongfoster
+ *    Removing unused parameter fs from read_config_line()
+ *
+ *    Revision 1.13  2001/09/16 15:47:37  jongfoster
+ *    First version of CGI-based edit interface.  This is very much a
+ *    work-in-progress, and you can't actually use it to edit anything
+ *    yet.  You must #define FEATURE_CGI_EDIT_ACTIONS for these changes
+ *    to have any effect.
+ *
+ *    Revision 1.12  2001/09/16 13:21:27  jongfoster
+ *    Changes to use new list functions.
+ *
+ *    Revision 1.11  2001/09/14 00:17:32  jongfoster
+ *    Tidying up memory allocation. New function init_action().
+ *
+ *    Revision 1.10  2001/09/10 10:14:34  oes
+ *    Removing unused variable
+ *
+ *    Revision 1.9  2001/07/30 22:08:36  jongfoster
+ *    Tidying up #defines:
+ *    - All feature #defines are now of the form FEATURE_xxx
+ *    - Permanently turned off WIN_GUI_EDIT
+ *    - Permanently turned on WEBDAV and SPLIT_PROXY_ARGS
+ *
+ *    Revision 1.8  2001/06/29 13:19:52  oes
+ *    Removed logentry from cancelled commit
+ *
+ *    Revision 1.7  2001/06/09 10:55:28  jongfoster
+ *    Changing BUFSIZ ==> BUFFER_SIZE
+ *
+ *    Revision 1.6  2001/06/07 23:04:34  jongfoster
+ *    Made get_actions() static.
+ *
+ *    Revision 1.5  2001/06/03 19:11:48  oes
+ *    adapted to new enlist_unique arg format
+ *
+ *    Revision 1.4  2001/06/01 20:03:42  jongfoster
+ *    Better memory management - current_action->strings[] now
+ *    contains copies of the strings, not the original.
+ *
+ *    Revision 1.3  2001/06/01 18:49:17  jongfoster
+ *    Replaced "list_share" with "list" - the tiny memory gain was not
+ *    worth the extra complexity.
+ *
+ *    Revision 1.2  2001/05/31 21:40:00  jongfoster
+ *    Removing some commented out, obsolete blocks of code.
+ *
+ *    Revision 1.1  2001/05/31 21:16:46  jongfoster
+ *    Moved functions to process the action list into this new file.
+ *
+ *
+ *********************************************************************/
+\f
+
+#include "config.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <stdlib.h>
+
+#include "project.h"
+#include "jcc.h"
+#include "list.h"
+#include "actions.h"
+#include "miscutil.h"
+#include "errlog.h"
+#include "loaders.h"
+#include "encode.h"
+#include "urlmatch.h"
+#include "cgi.h"
+
+const char actions_h_rcs[] = ACTIONS_H_VERSION;
+
+
+/*
+ * We need the main list of options.
+ *
+ * First, we need a way to tell between boolean, string, and multi-string
+ * options.  For string and multistring options, we also need to be
+ * able to tell the difference between a "+" and a "-".  (For bools,
+ * the "+"/"-" information is encoded in "add" and "mask").  So we use
+ * an enumerated type (well, the preprocessor equivalent).  Here are
+ * the values:
+ */
+#define AV_NONE       0 /* +opt -opt */
+#define AV_ADD_STRING 1 /* +stropt{string} */
+#define AV_REM_STRING 2 /* -stropt */
+#define AV_ADD_MULTI  3 /* +multiopt{string} +multiopt{string2} */
+#define AV_REM_MULTI  4 /* -multiopt{string} -multiopt          */
+
+/*
+ * We need a structure to hold the name, flag changes,
+ * type, and string index.
+ */
+struct action_name
+{
+   const char * name;
+   unsigned long mask;   /* a bit set to "0" = remove action */
+   unsigned long add;    /* a bit set to "1" = add action */
+   int takes_value;      /* an AV_... constant */
+   int index;            /* index into strings[] or multi[] */
+};
+
+/*
+ * And with those building blocks in place, here's the array.
+ */
+static const struct action_name action_names[] =
+{
+   /*
+    * Well actually there's no data here - it's in actionlist.h
+    * This keeps it together to make it easy to change.
+    *
+    * Here's the macros used to format it:
+    */
+#define DEFINE_ACTION_MULTI(name,index)                   \
+   { "+" name, ACTION_MASK_ALL, 0, AV_ADD_MULTI, index }, \
+   { "-" name, ACTION_MASK_ALL, 0, AV_REM_MULTI, index },
+#define DEFINE_ACTION_STRING(name,flag,index)                 \
+   { "+" name, ACTION_MASK_ALL, flag, AV_ADD_STRING, index }, \
+   { "-" name, ~flag, 0, AV_REM_STRING, index },
+#define DEFINE_ACTION_BOOL(name,flag)   \
+   { "+" name, ACTION_MASK_ALL, flag }, \
+   { "-" name, ~flag, 0 },
+#define DEFINE_ACTION_ALIAS 1 /* Want aliases please */
+
+#include "actionlist.h"
+
+#undef DEFINE_ACTION_MULTI
+#undef DEFINE_ACTION_STRING
+#undef DEFINE_ACTION_BOOL
+#undef DEFINE_ACTION_ALIAS
+
+   { NULL, 0, 0 } /* End marker */
+};
+
+
+static int load_one_actions_file(struct client_state *csp, int fileid);
+
+
+/*********************************************************************
+ *
+ * Function    :  merge_actions
+ *
+ * Description :  Merge two actions together.
+ *                Similar to "dest += src".
+ *
+ * Parameters  :
+ *          1  :  dest = Actions to modify.
+ *          2  :  src = Action to add.
+ *
+ * Returns     :  JB_ERR_OK or JB_ERR_MEMORY
+ *
+ *********************************************************************/
+jb_err merge_actions (struct action_spec *dest,
+                      const struct action_spec *src)
+{
+   int i;
+   jb_err err;
+
+   dest->mask &= src->mask;
+   dest->add  &= src->mask;
+   dest->add  |= src->add;
+
+   for (i = 0; i < ACTION_STRING_COUNT; i++)
+   {
+      char * str = src->string[i];
+      if (str)
+      {
+         freez(dest->string[i]);
+         dest->string[i] = strdup(str);
+         if (NULL == dest->string[i])
+         {
+            return JB_ERR_MEMORY;
+         }
+      }
+   }
+
+   for (i = 0; i < ACTION_MULTI_COUNT; i++)
+   {
+      if (src->multi_remove_all[i])
+      {
+         /* Remove everything from dest */
+         list_remove_all(dest->multi_remove[i]);
+         dest->multi_remove_all[i] = 1;
+
+         err = list_duplicate(dest->multi_add[i], src->multi_add[i]);
+      }
+      else if (dest->multi_remove_all[i])
+      {
+         /*
+          * dest already removes everything, so we only need to worry
+          * about what we add.
+          */
+         list_remove_list(dest->multi_add[i], src->multi_remove[i]);
+         err = list_append_list_unique(dest->multi_add[i], src->multi_add[i]);
+      }
+      else
+      {
+         /* No "remove all"s to worry about. */
+         list_remove_list(dest->multi_add[i], src->multi_remove[i]);
+         err = list_append_list_unique(dest->multi_remove[i], src->multi_remove[i]);
+         if (!err) err = list_append_list_unique(dest->multi_add[i], src->multi_add[i]);
+      }
+
+      if (err)
+      {
+         return err;
+      }
+   }
+
+   return JB_ERR_OK;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  copy_action
+ *
+ * Description :  Copy an action_specs.
+ *                Similar to "dest = src".
+ *
+ * Parameters  :
+ *          1  :  dest = Destination of copy.
+ *          2  :  src = Source for copy.
+ *
+ * Returns     :  N/A
+ *
+ *********************************************************************/
+jb_err copy_action (struct action_spec *dest,
+                    const struct action_spec *src)
+{
+   int i;
+   jb_err err = JB_ERR_OK;
+
+   free_action(dest);
+   memset(dest, '\0', sizeof(*dest));
+
+   dest->mask = src->mask;
+   dest->add  = src->add;
+
+   for (i = 0; i < ACTION_STRING_COUNT; i++)
+   {
+      char * str = src->string[i];
+      if (str)
+      {
+         str = strdup(str);
+         if (!str)
+         {
+            return JB_ERR_MEMORY;
+         }
+         dest->string[i] = str;
+      }
+   }
+
+   for (i = 0; i < ACTION_MULTI_COUNT; i++)
+   {
+      dest->multi_remove_all[i] = src->multi_remove_all[i];
+      err = list_duplicate(dest->multi_remove[i], src->multi_remove[i]);
+      if (err)
+      {
+         return err;
+      }
+      err = list_duplicate(dest->multi_add[i],    src->multi_add[i]);
+      if (err)
+      {
+         return err;
+      }
+   }
+   return err;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  free_action
+ *
+ * Description :  Destroy an action_spec.  Frees memory used by it,
+ *                except for the memory used by the struct action_spec
+ *                itself.
+ *
+ * Parameters  :
+ *          1  :  src = Source to free.
+ *
+ * Returns     :  N/A
+ *
+ *********************************************************************/
+void free_action (struct action_spec *src)
+{
+   int i;
+
+   if (src == NULL)
+   {
+      return;
+   }
+
+   for (i = 0; i < ACTION_STRING_COUNT; i++)
+   {
+      freez(src->string[i]);
+   }
+
+   for (i = 0; i < ACTION_MULTI_COUNT; i++)
+   {
+      destroy_list(src->multi_remove[i]);
+      destroy_list(src->multi_add[i]);
+   }
+
+   memset(src, '\0', sizeof(*src));
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  get_action_token
+ *
+ * Description :  Parses a line for the first action.
+ *                Modifies it's input array, doesn't allocate memory.
+ *                e.g. given:
+ *                *line="  +abc{def}  -ghi "
+ *                Returns:
+ *                *line="  -ghi "
+ *                *name="+abc"
+ *                *value="def"
+ *
+ * Parameters  :
+ *          1  :  line = [in] The line containing the action.
+ *                       [out] Start of next action on line, or
+ *                       NULL if we reached the end of line before
+ *                       we found an action.
+ *          2  :  name = [out] Start of action name, null
+ *                       terminated.  NULL on EOL
+ *          3  :  value = [out] Start of action value, null
+ *                        terminated.  NULL if none or EOL.
+ *
+ * Returns     :  JB_ERR_OK => Ok
+ *                JB_ERR_PARSE => Mismatched {} (line was trashed anyway)
+ *
+ *********************************************************************/
+jb_err get_action_token(char **line, char **name, char **value)
+{
+   char * str = *line;
+   char ch;
+
+   /* set default returns */
+   *line = NULL;
+   *name = NULL;
+   *value = NULL;
+
+   /* Eat any leading whitespace */
+   while ((*str == ' ') || (*str == '\t'))
+   {
+      str++;
+   }
+
+   if (*str == '\0')
+   {
+      return 0;
+   }
+
+   if (*str == '{')
+   {
+      /* null name, just value is prohibited */
+      return JB_ERR_PARSE;
+   }
+
+   *name = str;
+
+   /* parse option */
+   while (((ch = *str) != '\0') &&
+          (ch != ' ') && (ch != '\t') && (ch != '{'))
+   {
+      if (ch == '}')
+      {
+         /* error, '}' without '{' */
+         return JB_ERR_PARSE;
+      }
+      str++;
+   }
+   *str = '\0';
+
+   if (ch != '{')
+   {
+      /* no value */
+      if (ch == '\0')
+      {
+         /* EOL - be careful not to run off buffer */
+         *line = str;
+      }
+      else
+      {
+         /* More to parse next time. */
+         *line = str + 1;
+      }
+      return JB_ERR_OK;
+   }
+
+   str++;
+   *value = str;
+
+   str = strchr(str, '}');
+   if (str == NULL)
+   {
+      /* error */
+      *value = NULL;
+      return JB_ERR_PARSE;
+   }
+
+   /* got value */
+   *str = '\0';
+   *line = str + 1;
+
+   chomp(*value);
+
+   return JB_ERR_OK;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  get_actions
+ *
+ * Description :  Parses a list of actions.
+ *
+ * Parameters  :
+ *          1  :  line = The string containing the actions.
+ *                       Will be written to by this function.
+ *          2  :  alias_list = Custom alias list, or NULL for none.
+ *          3  :  cur_action = Where to store the action.  Caller
+ *                             allocates memory.
+ *
+ * Returns     :  JB_ERR_OK => Ok
+ *                JB_ERR_PARSE => Parse error (line was trashed anyway)
+ *                nonzero => Out of memory (line was trashed anyway)
+ *
+ *********************************************************************/
+jb_err get_actions(char *line,
+                   struct action_alias * alias_list,
+                   struct action_spec *cur_action)
+{
+   jb_err err;
+   init_action(cur_action);
+   cur_action->mask = ACTION_MASK_ALL;
+
+   while (line)
+   {
+      char * option = NULL;
+      char * value = NULL;
+
+      err = get_action_token(&line, &option, &value);
+      if (err)
+      {
+         return err;
+      }
+
+      if (option)
+      {
+         /* handle option in 'option' */
+
+         /* Check for standard action name */
+         const struct action_name * action = action_names;
+
+         while ( (action->name != NULL) && (0 != strcmpic(action->name, option)) )
+         {
+            action++;
+         }
+         if (action->name != NULL)
+         {
+            /* Found it */
+            cur_action->mask &= action->mask;
+            cur_action->add  &= action->mask;
+            cur_action->add  |= action->add;
+
+            switch (action->takes_value)
+            {
+            case AV_NONE:
+               /* ignore any option. */
+               break;
+            case AV_ADD_STRING:
+               {
+                  /* add single string. */
+
+                  if ((value == NULL) || (*value == '\0'))
+                  {
+                     return JB_ERR_PARSE;
+                  }
+                  /* FIXME: should validate option string here */
+                  freez (cur_action->string[action->index]);
+                  cur_action->string[action->index] = strdup(value);
+                  if (NULL == cur_action->string[action->index])
+                  {
+                     return JB_ERR_MEMORY;
+                  }
+                  break;
+               }
+            case AV_REM_STRING:
+               {
+                  /* remove single string. */
+
+                  freez (cur_action->string[action->index]);
+                  break;
+               }
+            case AV_ADD_MULTI:
+               {
+                  /* append multi string. */
+
+                  struct list * remove_p = cur_action->multi_remove[action->index];
+                  struct list * add_p    = cur_action->multi_add[action->index];
+
+                  if ((value == NULL) || (*value == '\0'))
+                  {
+                     return JB_ERR_PARSE;
+                  }
+
+                  list_remove_item(remove_p, value);
+                  err = enlist_unique(add_p, value, 0);
+                  if (err)
+                  {
+                     return err;
+                  }
+                  break;
+               }
+            case AV_REM_MULTI:
+               {
+                  /* remove multi string. */
+
+                  struct list * remove_p = cur_action->multi_remove[action->index];
+                  struct list * add_p    = cur_action->multi_add[action->index];
+
+                  if ( (value == NULL) || (*value == '\0')
+                     || ((*value == '*') && (value[1] == '\0')) )
+                  {
+                     /*
+                      * no option, or option == "*".
+                      *
+                      * Remove *ALL*.
+                      */
+                     list_remove_all(remove_p);
+                     list_remove_all(add_p);
+                     cur_action->multi_remove_all[action->index] = 1;
+                  }
+                  else
+                  {
+                     /* Valid option - remove only 1 option */
+
+                     if ( !cur_action->multi_remove_all[action->index] )
+                     {
+                        /* there isn't a catch-all in the remove list already */
+                        err = enlist_unique(remove_p, value, 0);
+                        if (err)
+                        {
+                           return err;
+                        }
+                     }
+                     list_remove_item(add_p, value);
+                  }
+                  break;
+               }
+            default:
+               /* Shouldn't get here unless there's memory corruption. */
+               assert(0);
+               return JB_ERR_PARSE;
+            }
+         }
+         else
+         {
+            /* try user aliases. */
+            const struct action_alias * alias = alias_list;
+
+            while ( (alias != NULL) && (0 != strcmpic(alias->name, option)) )
+            {
+               alias = alias->next;
+            }
+            if (alias != NULL)
+            {
+               /* Found it */
+               merge_actions(cur_action, alias->action);
+            }
+            else
+            {
+               /* Bad action name */
+               return JB_ERR_PARSE;
+            }
+         }
+      }
+   }
+
+   return JB_ERR_OK;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  init_current_action
+ *
+ * Description :  Zero out an action.
+ *
+ * Parameters  :
+ *          1  :  dest = An uninitialized current_action_spec.
+ *
+ * Returns     :  N/A
+ *
+ *********************************************************************/
+void init_current_action (struct current_action_spec *dest)
+{
+   memset(dest, '\0', sizeof(*dest));
+
+   dest->flags = ACTION_MOST_COMPATIBLE;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  init_action
+ *
+ * Description :  Zero out an action.
+ *
+ * Parameters  :
+ *          1  :  dest = An uninitialized action_spec.
+ *
+ * Returns     :  N/A
+ *
+ *********************************************************************/
+void init_action (struct action_spec *dest)
+{
+   memset(dest, '\0', sizeof(*dest));
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  merge_current_action
+ *
+ * Description :  Merge two actions together.
+ *                Similar to "dest += src".
+ *                Differences between this and merge_actions()
+ *                is that this one doesn't allocate memory for
+ *                strings (so "src" better be in memory for at least
+ *                as long as "dest" is, and you'd better free
+ *                "dest" using "free_current_action").
+ *                Also, there is no  mask or remove lists in dest.
+ *                (If we're applying it to a URL, we don't need them)
+ *
+ * Parameters  :
+ *          1  :  dest = Current actions, to modify.
+ *          2  :  src = Action to add.
+ *
+ * Returns  0  :  no error
+ *        !=0  :  error, probably JB_ERR_MEMORY.
+ *
+ *********************************************************************/
+jb_err merge_current_action (struct current_action_spec *dest,
+                             const struct action_spec *src)
+{
+   int i;
+   jb_err err = JB_ERR_OK;
+
+   dest->flags  &= src->mask;
+   dest->flags  |= src->add;
+
+   for (i = 0; i < ACTION_STRING_COUNT; i++)
+   {
+      char * str = src->string[i];
+      if (str)
+      {
+         str = strdup(str);
+         if (!str)
+         {
+            return JB_ERR_MEMORY;
+         }
+         freez(dest->string[i]);
+         dest->string[i] = str;
+      }
+   }
+
+   for (i = 0; i < ACTION_MULTI_COUNT; i++)
+   {
+      if (src->multi_remove_all[i])
+      {
+         /* Remove everything from dest, then add src->multi_add */
+         err = list_duplicate(dest->multi[i], src->multi_add[i]);
+         if (err)
+         {
+            return err;
+         }
+      }
+      else
+      {
+         list_remove_list(dest->multi[i], src->multi_remove[i]);
+         err = list_append_list_unique(dest->multi[i], src->multi_add[i]);
+         if (err)
+         {
+            return err;
+         }
+      }
+   }
+   return err;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  free_current_action
+ *
+ * Description :  Free memory used by a current_action_spec.
+ *                Does not free the current_action_spec itself.
+ *
+ * Parameters  :
+ *          1  :  src = Source to free.
+ *
+ * Returns     :  N/A
+ *
+ *********************************************************************/
+void free_current_action (struct current_action_spec *src)
+{
+   int i;
+
+   for (i = 0; i < ACTION_STRING_COUNT; i++)
+   {
+      freez(src->string[i]);
+   }
+
+   for (i = 0; i < ACTION_MULTI_COUNT; i++)
+   {
+      destroy_list(src->multi[i]);
+   }
+
+   memset(src, '\0', sizeof(*src));
+}
+
+
+static struct file_list *current_actions_file[MAX_ACTION_FILES]  = {
+   NULL, NULL, NULL, NULL, NULL,
+   NULL, NULL, NULL, NULL, NULL
+};
+
+
+#ifdef FEATURE_GRACEFUL_TERMINATION
+/*********************************************************************
+ *
+ * Function    :  unload_current_actions_file
+ *
+ * Description :  Unloads current actions file - reset to state at
+ *                beginning of program.
+ *
+ * Parameters  :  None
+ *
+ * Returns     :  N/A
+ *
+ *********************************************************************/
+void unload_current_actions_file(void)
+{
+   if (current_actions_file)
+   {
+      current_actions_file->unloader = unload_actions_file;
+      current_actions_file = NULL;
+   }
+}
+#endif /* FEATURE_GRACEFUL_TERMINATION */
+
+
+/*********************************************************************
+ *
+ * Function    :  unload_actions_file
+ *
+ * Description :  Unloads an actions module.
+ *
+ * Parameters  :
+ *          1  :  file_data = the data structure associated with the
+ *                            actions file.
+ *
+ * Returns     :  N/A
+ *
+ *********************************************************************/
+void unload_actions_file(void *file_data)
+{
+   struct url_actions * next;
+   struct url_actions * cur = (struct url_actions *)file_data;
+   while (cur != NULL)
+   {
+      next = cur->next;
+      free_url_spec(cur->url);
+      free_action(cur->action);
+      freez(cur);
+      cur = next;
+   }
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  free_alias_list
+ *
+ * Description :  Free memory used by a list of aliases.
+ *
+ * Parameters  :
+ *          1  :  alias_list = Linked list to free.
+ *
+ * Returns     :  N/A
+ *
+ *********************************************************************/
+void free_alias_list(struct action_alias *alias_list)
+{
+   while (alias_list != NULL)
+   {
+      struct action_alias * next = alias_list->next;
+      alias_list->next = NULL;
+      freez(alias_list->name);
+      free_action(alias_list->action);
+      free(alias_list);
+      alias_list = next;
+   }
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  load_actions_file
+ *
+ * Description :  Read and parse all the action files and add to files
+ *                list.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns     :  0 => Ok, everything else is an error.
+ *
+ *********************************************************************/
+int load_actions_file(struct client_state *csp)
+{
+   int i;
+   int result;
+
+   for (i = 0; i < MAX_ACTION_FILES; i++)
+   {
+      if (csp->config->actions_file[i])
+      {
+         result = load_one_actions_file(csp, i);
+         if (result)
+         {
+            return result;
+         }
+      }
+      else if (current_actions_file[i])
+      {
+         current_actions_file[i]->unloader = unload_actions_file;
+         current_actions_file[i] = NULL;
+      }
+   }
+
+   return 0;
+}
+
+/*********************************************************************
+ *
+ * Function    :  load_one_actions_file
+ *
+ * Description :  Read and parse a action file and add to files
+ *                list.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  fileid = File index to load.
+ *
+ * Returns     :  0 => Ok, everything else is an error.
+ *
+ *********************************************************************/
+static int load_one_actions_file(struct client_state *csp, int fileid)
+{
+
+   /*
+    * Parser mode.
+    * Note: Keep these in the order they occur in the file, they are
+    * sometimes tested with <=
+    */
+#define MODE_START_OF_FILE 1
+#define MODE_SETTINGS      2
+#define MODE_DESCRIPTION   3
+#define MODE_ALIAS         4
+#define MODE_ACTIONS       5
+
+   int mode = MODE_START_OF_FILE;
+
+   FILE *fp;
+   struct url_actions *last_perm;
+   struct url_actions *perm;
+   char  buf[BUFFER_SIZE];
+   struct file_list *fs;
+   struct action_spec * cur_action = NULL;
+   int cur_action_used = 0;
+   struct action_alias * alias_list = NULL;
+   unsigned long linenum = 0;
+
+   if (!check_file_changed(current_actions_file[fileid], csp->config->actions_file[fileid], &fs))
+   {
+      /* No need to load */
+      csp->actions_list[fileid] = current_actions_file[fileid];
+      return 0;
+   }
+   if (!fs)
+   {
+      log_error(LOG_LEVEL_FATAL, "can't load actions file '%s': error finding file: %E",
+                csp->config->actions_file[fileid]);
+      return 1; /* never get here */
+   }
+
+   fs->f = last_perm = (struct url_actions *)zalloc(sizeof(*last_perm));
+   if (last_perm == NULL)
+   {
+      log_error(LOG_LEVEL_FATAL, "can't load actions file '%s': out of memory!",
+                csp->config->actions_file[fileid]);
+      return 1; /* never get here */
+   }
+
+   if ((fp = fopen(csp->config->actions_file[fileid], "r")) == NULL)
+   {
+      log_error(LOG_LEVEL_FATAL, "can't load actions file '%s': error opening file: %E",
+                csp->config->actions_file[fileid]);
+      return 1; /* never get here */
+   }
+
+   while (read_config_line(buf, sizeof(buf), fp, &linenum) != NULL)
+   {
+      if (*buf == '{')
+      {
+         /* It's a header block */
+         if (buf[1] == '{')
+         {
+            /* It's {{settings}} or {{alias}} */
+            int len = strlen(buf);
+            char * start = buf + 2;
+            char * end = buf + len - 1;
+            if ((len < 5) || (*end-- != '}') || (*end-- != '}'))
+            {
+               /* too short */
+               fclose(fp);
+               log_error(LOG_LEVEL_FATAL,
+                  "can't load actions file '%s': invalid line (%lu): %s", 
+                  csp->config->actions_file[fileid], linenum, buf);
+               return 1; /* never get here */
+            }
+
+            /* Trim leading and trailing whitespace. */
+            end[1] = '\0';
+            chomp(start);
+
+            if (*start == '\0')
+            {
+               /* too short */
+               fclose(fp);
+               log_error(LOG_LEVEL_FATAL,
+                  "can't load actions file '%s': invalid line (%lu): {{ }}",
+                  csp->config->actions_file[fileid], linenum);
+               return 1; /* never get here */
+            }
+
+            /*
+             * An actionsfile can optionally contain the following blocks.
+             * They *MUST* be in this order, to simplify processing:
+             *
+             * {{settings}}
+             * name=value...
+             *
+             * {{description}}
+             * ...free text, format TBD, but no line may start with a '{'...
+             *
+             * {{alias}}
+             * name=actions...
+             *
+             * The actual actions must be *after* these special blocks.
+             * None of these special blocks may be repeated.
+             *
+             */
+            if (0 == strcmpic(start, "settings"))
+            {
+               /* it's a {{settings}} block */
+               if (mode >= MODE_SETTINGS)
+               {
+                  /* {{settings}} must be first thing in file and must only
+                   * appear once.
+                   */
+                  fclose(fp);
+                  log_error(LOG_LEVEL_FATAL,
+                     "can't load actions file '%s': line %lu: {{settings}} must only appear once, and it must be before anything else.",
+                     csp->config->actions_file[fileid], linenum);
+               }
+               mode = MODE_SETTINGS;
+            }
+            else if (0 == strcmpic(start, "description"))
+            {
+               /* it's a {{description}} block */
+               if (mode >= MODE_DESCRIPTION)
+               {
+                  /* {{description}} is a singleton and only {{settings}} may proceed it
+                   */
+                  fclose(fp);
+                  log_error(LOG_LEVEL_FATAL,
+                     "can't load actions file '%s': line %lu: {{description}} must only appear once, and only a {{settings}} block may be above it.",
+                     csp->config->actions_file[fileid], linenum);
+               }
+               mode = MODE_DESCRIPTION;
+            }
+            else if (0 == strcmpic(start, "alias"))
+            {
+               /* it's an {{alias}} block */
+               if (mode >= MODE_ALIAS)
+               {
+                  /* {{alias}} must be first thing in file, possibly after
+                   * {{settings}} and {{description}}
+                   *
+                   * {{alias}} must only appear once.
+                   *
+                   * Note that these are new restrictions introduced in
+                   * v2.9.10 in order to make actionsfile editing simpler.
+                   * (Otherwise, reordering actionsfile entries without
+                   * completely rewriting the file becomes non-trivial)
+                   */
+                  fclose(fp);
+                  log_error(LOG_LEVEL_FATAL,
+                     "can't load actions file '%s': line %lu: {{alias}} must only appear once, and it must be before all actions.",
+                     csp->config->actions_file[fileid], linenum);
+               }
+               mode = MODE_ALIAS;
+            }
+            else
+            {
+               /* invalid {{something}} block */
+               fclose(fp);
+               log_error(LOG_LEVEL_FATAL,
+                  "can't load actions file '%s': invalid line (%lu): {{%s}}",
+                  csp->config->actions_file[fileid], linenum, start);
+               return 1; /* never get here */
+            }
+         }
+         else
+         {
+            /* It's an actions block */
+
+            char  actions_buf[BUFFER_SIZE];
+            char * end;
+
+            /* set mode */
+            mode    = MODE_ACTIONS;
+
+            /* free old action */
+            if (cur_action)
+            {
+               if (!cur_action_used)
+               {
+                  free_action(cur_action);
+                  free(cur_action);
+               }
+               cur_action = NULL;
+            }
+            cur_action_used = 0;
+            cur_action = (struct action_spec *)zalloc(sizeof(*cur_action));
+            if (cur_action == NULL)
+            {
+               fclose(fp);
+               log_error(LOG_LEVEL_FATAL,
+                  "can't load actions file '%s': out of memory",
+                  csp->config->actions_file[fileid]);
+               return 1; /* never get here */
+            }
+            init_action(cur_action);
+
+            /* trim { */
+            strcpy(actions_buf, buf + 1);
+
+            /* check we have a trailing } and then trim it */
+            end = actions_buf + strlen(actions_buf) - 1;
+            if (*end != '}')
+            {
+               /* No closing } */
+               fclose(fp);
+               log_error(LOG_LEVEL_FATAL,
+                  "can't load actions file '%s': invalid line (%lu): %s",
+                  csp->config->actions_file[fileid], linenum, buf);
+               return 1; /* never get here */
+            }
+            *end = '\0';
+
+            /* trim any whitespace immediately inside {} */
+            chomp(actions_buf);
+
+            if (get_actions(actions_buf, alias_list, cur_action))
+            {
+               /* error */
+               fclose(fp);
+               log_error(LOG_LEVEL_FATAL,
+                  "can't load actions file '%s': invalid line (%lu): %s",
+                  csp->config->actions_file[fileid], linenum, buf);
+               return 1; /* never get here */
+            }
+         }
+      }
+      else if (mode == MODE_SETTINGS)
+      {
+         /*
+          * Part of the {{settings}} block.
+          * Ignore for now, but we may want to read & check permissions
+          * when we go multi-user.
+          */
+      }
+      else if (mode == MODE_DESCRIPTION)
+      {
+         /*
+          * Part of the {{description}} block.
+          * Ignore for now.
+          */
+      }
+      else if (mode == MODE_ALIAS)
+      {
+         /*
+          * define an alias
+          */
+         char  actions_buf[BUFFER_SIZE];
+         struct action_alias * new_alias;
+
+         char * start = strchr(buf, '=');
+         char * end = start;
+
+         if ((start == NULL) || (start == buf))
+         {
+            log_error(LOG_LEVEL_FATAL,
+               "can't load actions file '%s': invalid alias line (%lu): %s",
+               csp->config->actions_file[fileid], linenum, buf);
+            return 1; /* never get here */
+         }
+
+         if ((new_alias = zalloc(sizeof(*new_alias))) == NULL)
+         {
+            fclose(fp);
+            log_error(LOG_LEVEL_FATAL,
+               "can't load actions file '%s': out of memory!",
+               csp->config->actions_file[fileid]);
+            return 1; /* never get here */
+         }
+
+         /* Eat any the whitespace before the '=' */
+         end--;
+         while ((*end == ' ') || (*end == '\t'))
+         {
+            /*
+             * we already know we must have at least 1 non-ws char
+             * at start of buf - no need to check
+             */
+            end--;
+         }
+         end[1] = '\0';
+
+         /* Eat any the whitespace after the '=' */
+         start++;
+         while ((*start == ' ') || (*start == '\t'))
+         {
+            start++;
+         }
+         if (*start == '\0')
+         {
+            log_error(LOG_LEVEL_FATAL,
+               "can't load actions file '%s': invalid alias line (%lu): %s",
+               csp->config->actions_file[fileid], linenum, buf);
+            return 1; /* never get here */
+         }
+
+         new_alias->name = strdup(buf);
+
+         strcpy(actions_buf, start);
+
+         if (get_actions(actions_buf, alias_list, new_alias->action))
+         {
+            /* error */
+            fclose(fp);
+            log_error(LOG_LEVEL_FATAL,
+               "can't load actions file '%s': invalid alias line (%lu): %s = %s",
+               csp->config->actions_file[fileid], linenum, buf, start);
+            return 1; /* never get here */
+         }
+
+         /* add to list */
+         new_alias->next = alias_list;
+         alias_list = new_alias;
+      }
+      else if (mode == MODE_ACTIONS)
+      {
+         /* it's a URL pattern */
+
+         /* allocate a new node */
+         if ((perm = zalloc(sizeof(*perm))) == NULL)
+         {
+            fclose(fp);
+            log_error(LOG_LEVEL_FATAL,
+               "can't load actions file '%s': out of memory!",
+               csp->config->actions_file[fileid]);
+            return 1; /* never get here */
+         }
+
+         /* Save flags */
+         copy_action (perm->action, cur_action);
+
+         /* Save the URL pattern */
+         if (create_url_spec(perm->url, buf))
+         {
+            fclose(fp);
+            log_error(LOG_LEVEL_FATAL,
+               "can't load actions file '%s': line %lu: cannot create URL pattern from: %s",
+               csp->config->actions_file[fileid], linenum, buf);
+            return 1; /* never get here */
+         }
+
+         /* add it to the list */
+         last_perm->next = perm;
+         last_perm = perm;
+      }
+      else if (mode == MODE_START_OF_FILE)
+      {
+         /* oops - please have a {} line as 1st line in file. */
+         fclose(fp);
+         log_error(LOG_LEVEL_FATAL,
+            "can't load actions file '%s': first needed line (%lu) is invalid: %s",
+            csp->config->actions_file[fileid], linenum, buf);
+         return 1; /* never get here */
+      }
+      else
+      {
+         /* How did we get here? This is impossible! */
+         fclose(fp);
+         log_error(LOG_LEVEL_FATAL,
+            "can't load actions file '%s': INTERNAL ERROR - mode = %d",
+            csp->config->actions_file[fileid], mode);
+         return 1; /* never get here */
+      }
+   }
+
+   fclose(fp);
+
+   free_action(cur_action);
+
+   free_alias_list(alias_list);
+
+   /* the old one is now obsolete */
+   if (current_actions_file[fileid])
+   {
+      current_actions_file[fileid]->unloader = unload_actions_file;
+   }
+
+   fs->next    = files->next;
+   files->next = fs;
+   current_actions_file[fileid] = fs;
+
+   csp->actions_list[fileid] = fs;
+
+   return(0);
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  actions_to_text
+ *
+ * Description :  Converts a actionsfile entry from the internal
+ *                structurt into a text line.  The output is split
+ *                into one line for each action with line continuation. 
+ *
+ * Parameters  :
+ *          1  :  action = The action to format.
+ *
+ * Returns     :  A string.  Caller must free it.
+ *                NULL on out-of-memory error.
+ *
+ *********************************************************************/
+char * actions_to_text(struct action_spec *action)
+{
+   unsigned mask = action->mask;
+   unsigned add  = action->add;
+   char * result = strdup("");
+   struct list_entry * lst;
+
+   /* sanity - prevents "-feature +feature" */
+   mask |= add;
+
+
+#define DEFINE_ACTION_BOOL(__name, __bit)          \
+   if (!(mask & __bit))                            \
+   {                                               \
+      string_append(&result, " -" __name " \\\n"); \
+   }                                               \
+   else if (add & __bit)                           \
+   {                                               \
+      string_append(&result, " +" __name " \\\n"); \
+   }
+
+#define DEFINE_ACTION_STRING(__name, __bit, __index)   \
+   if (!(mask & __bit))                                \
+   {                                                   \
+      string_append(&result, " -" __name " \\\n");     \
+   }                                                   \
+   else if (add & __bit)                               \
+   {                                                   \
+      string_append(&result, " +" __name "{");         \
+      string_append(&result, action->string[__index]); \
+      string_append(&result, "} \\\n");                \
+   }
+
+#define DEFINE_ACTION_MULTI(__name, __index)         \
+   if (action->multi_remove_all[__index])            \
+   {                                                 \
+      string_append(&result, " -" __name " \\\n");   \
+   }                                                 \
+   else                                              \
+   {                                                 \
+      lst = action->multi_remove[__index]->first;    \
+      while (lst)                                    \
+      {                                              \
+         string_append(&result, " -" __name "{");    \
+         string_append(&result, lst->str);           \
+         string_append(&result, "} \\\n");           \
+         lst = lst->next;                            \
+      }                                              \
+   }                                                 \
+   lst = action->multi_add[__index]->first;          \
+   while (lst)                                       \
+   {                                                 \
+      string_append(&result, " +" __name "{");       \
+      string_append(&result, lst->str);              \
+      string_append(&result, "} \\\n");              \
+      lst = lst->next;                               \
+   }
+
+#define DEFINE_ACTION_ALIAS 0 /* No aliases for output */
+
+#include "actionlist.h"
+
+#undef DEFINE_ACTION_MULTI
+#undef DEFINE_ACTION_STRING
+#undef DEFINE_ACTION_BOOL
+#undef DEFINE_ACTION_ALIAS
+
+   return result;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  actions_to_html
+ *
+ * Description :  Converts a actionsfile entry from numeric form
+ *                ("mask" and "add") to a <br>-seperated HTML string
+ *                in which each action is linked to its chapter in
+ *                the user manual.
+ *
+ * Parameters  :
+ *          1  :  csp    = Client state (for config)
+ *          2  :  action = Action spec to be converted
+ *
+ * Returns     :  A string.  Caller must free it.
+ *                NULL on out-of-memory error.
+ *
+ *********************************************************************/
+char * actions_to_html(struct client_state *csp,
+                       struct action_spec *action)
+{
+   unsigned mask = action->mask;
+   unsigned add  = action->add;
+   char * result = strdup("");
+   struct list_entry * lst;
+
+   /* sanity - prevents "-feature +feature" */
+   mask |= add;
+
+
+#define DEFINE_ACTION_BOOL(__name, __bit)       \
+   if (!(mask & __bit))                         \
+   {                                            \
+      string_append(&result, "\n<br>-");        \
+      string_join(&result, add_help_link(__name, csp->config)); \
+   }                                            \
+   else if (add & __bit)                        \
+   {                                            \
+      string_append(&result, "\n<br>+");        \
+      string_join(&result, add_help_link(__name, csp->config)); \
+   }
+
+#define DEFINE_ACTION_STRING(__name, __bit, __index) \
+   if (!(mask & __bit))                              \
+   {                                                 \
+      string_append(&result, "\n<br>-");             \
+      string_join(&result, add_help_link(__name, csp->config)); \
+   }                                                 \
+   else if (add & __bit)                             \
+   {                                                 \
+      string_append(&result, "\n<br>+");             \
+      string_join(&result, add_help_link(__name, csp->config)); \
+      string_append(&result, "{");                   \
+      string_join(&result, html_encode(action->string[__index])); \
+      string_append(&result, "}");                   \
+   }
+
+#define DEFINE_ACTION_MULTI(__name, __index)          \
+   if (action->multi_remove_all[__index])             \
+   {                                                  \
+      string_append(&result, "\n<br>-");              \
+      string_join(&result, add_help_link(__name, csp->config)); \
+   }                                                  \
+   else                                               \
+   {                                                  \
+      lst = action->multi_remove[__index]->first;     \
+      while (lst)                                     \
+      {                                               \
+         string_append(&result, "\n<br>-");           \
+         string_join(&result, add_help_link(__name, csp->config)); \
+         string_append(&result, "{");                 \
+         string_join(&result, html_encode(lst->str)); \
+         string_append(&result, "}");                 \
+         lst = lst->next;                             \
+      }                                               \
+   }                                                  \
+   lst = action->multi_add[__index]->first;           \
+   while (lst)                                        \
+   {                                                  \
+      string_append(&result, "\n<br>+");              \
+      string_join(&result, add_help_link(__name, csp->config)); \
+      string_append(&result, "{");                    \
+      string_join(&result, html_encode(lst->str));    \
+      string_append(&result, "}");                    \
+      lst = lst->next;                                \
+   }
+
+#define DEFINE_ACTION_ALIAS 0 /* No aliases for output */
+
+#include "actionlist.h"
+
+#undef DEFINE_ACTION_MULTI
+#undef DEFINE_ACTION_STRING
+#undef DEFINE_ACTION_BOOL
+#undef DEFINE_ACTION_ALIAS
+
+   /* trim leading <br> */
+   if (result && *result)
+   {
+      char * s = result;
+      result = strdup(result + 5);
+      free(s);
+   }
+
+   return result;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  current_actions_to_html
+ *
+ * Description :  Converts a curren action spec to a <br> seperated HTML
+ *                text in which each action is linked to its chapter in
+ *                the user manual.
+ *
+ * Parameters  :
+ *          1  :  csp    = Client state (for config) 
+ *          2  :  action = Current action spec to be converted
+ *
+ * Returns     :  A string.  Caller must free it.
+ *                NULL on out-of-memory error.
+ *
+ *********************************************************************/
+char *current_action_to_html(struct client_state *csp,
+                             struct current_action_spec *action)
+{
+   unsigned long flags  = action->flags;
+   char * result = strdup("");
+   struct list_entry * lst;
+
+#define DEFINE_ACTION_BOOL(__name, __bit)  \
+   if (flags & __bit)                      \
+   {                                       \
+      string_append(&result, "\n<br>+");   \
+      string_join(&result, add_help_link(__name, csp->config)); \
+   }                                       \
+   else                                    \
+   {                                       \
+      string_append(&result, "\n<br>-");   \
+      string_join(&result, add_help_link(__name, csp->config)); \
+   }
+
+#define DEFINE_ACTION_STRING(__name, __bit, __index)   \
+   if (flags & __bit)                                  \
+   {                                                   \
+      string_append(&result, "\n<br>+");               \
+      string_join(&result, add_help_link(__name, csp->config)); \
+      string_append(&result, "{");                     \
+      string_join(&result, html_encode(action->string[__index])); \
+      string_append(&result, "}");                     \
+   }                                                   \
+   else                                                \
+   {                                                   \
+      string_append(&result, "\n<br>-");               \
+      string_join(&result, add_help_link(__name, csp->config)); \
+   }
+
+#define DEFINE_ACTION_MULTI(__name, __index)           \
+   lst = action->multi[__index]->first;                \
+   if (lst == NULL)                                    \
+   {                                                   \
+      string_append(&result, "\n<br>-");              \
+      string_join(&result, add_help_link(__name, csp->config)); \
+   }                                                   \
+   else                                                \
+   {                                                   \
+      while (lst)                                      \
+      {                                                \
+         string_append(&result, "\n<br>+");            \
+         string_join(&result, add_help_link(__name, csp->config)); \
+         string_append(&result, "{");                  \
+         string_join(&result, html_encode(lst->str));  \
+         string_append(&result, "}");                  \
+         lst = lst->next;                              \
+      }                                                \
+   }
+
+#define DEFINE_ACTION_ALIAS 0 /* No aliases for output */
+
+#include "actionlist.h"
+
+#undef DEFINE_ACTION_MULTI
+#undef DEFINE_ACTION_STRING
+#undef DEFINE_ACTION_BOOL
+#undef DEFINE_ACTION_ALIAS
+
+   return result;
+}
diff --git a/src/actions.h b/src/actions.h
new file mode 100644 (file)
index 0000000..07cf2ed
--- /dev/null
@@ -0,0 +1,161 @@
+#ifndef ACTIONS_H_INCLUDED
+#define ACTIONS_H_INCLUDED
+#define ACTIONS_H_VERSION "$Id: actions.h,v 1.12 2002/05/06 07:56:50 oes Exp $"
+/*********************************************************************
+ *
+ * File        :  $Source: /cvsroot/ijbswa/current/actions.h,v $
+ *
+ * Purpose     :  Declares functions to work with actions files
+ *                Functions declared include: FIXME
+ *
+ * Copyright   :  Written by and Copyright (C) 2001 the SourceForge
+ *                Privoxy team. http://www.privoxy.org/
+ *
+ *                Based on the Internet Junkbuster originally written
+ *                by and Copyright (C) 1997 Anonymous Coders and 
+ *                Junkbusters Corporation.  http://www.junkbusters.com
+ *
+ *                This program is free software; you can redistribute it 
+ *                and/or modify it under the terms of the GNU General
+ *                Public License as published by the Free Software
+ *                Foundation; either version 2 of the License, or (at
+ *                your option) any later version.
+ *
+ *                This program is distributed in the hope that it will
+ *                be useful, but WITHOUT ANY WARRANTY; without even the
+ *                implied warranty of MERCHANTABILITY or FITNESS FOR A
+ *                PARTICULAR PURPOSE.  See the GNU General Public
+ *                License for more details.
+ *
+ *                The GNU General Public License should be included with
+ *                this file.  If not, you can view it at
+ *                http://www.gnu.org/copyleft/gpl.html
+ *                or write to the Free Software Foundation, Inc., 59
+ *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * Revisions   :
+ *    $Log: actions.h,v $
+ *    Revision 1.12  2002/05/06 07:56:50  oes
+ *    Made actions_to_html independent of FEATURE_CGI_EDIT_ACTIONS
+ *
+ *    Revision 1.11  2002/04/30 11:14:52  oes
+ *    Made csp the first parameter in *action_to_html
+ *
+ *    Revision 1.10  2002/04/26 12:53:33  oes
+ *     -  actions_to_html signature change
+ *     -  current_action_to_text: renamed to current_action_to_html
+ *        and signature change
+ *
+ *    Revision 1.9  2002/03/26 22:29:54  swa
+ *    we have a new homepage!
+ *
+ *    Revision 1.8  2002/03/24 13:25:43  swa
+ *    name change related issues
+ *
+ *    Revision 1.7  2002/03/16 23:54:06  jongfoster
+ *    Adding graceful termination feature, to help look for memory leaks.
+ *    If you enable this (which, by design, has to be done by hand
+ *    editing config.h) and then go to http://i.j.b/die, then the program
+ *    will exit cleanly after the *next* request.  It should free all the
+ *    memory that was used.
+ *
+ *    Revision 1.6  2001/10/23 21:30:30  jongfoster
+ *    Adding error-checking to selected functions.
+ *
+ *    Revision 1.5  2001/10/14 21:58:22  jongfoster
+ *    Adding support for the CGI-based editor:
+ *    - Exported get_actions()
+ *    - Added new function free_alias_list()
+ *    - Added support for {{settings}} and {{description}} blocks
+ *      in the actions file.  They are currently ignored.
+ *    - Added restriction to only one {{alias}} block which must appear
+ *      first in the file, to simplify the editor's rewriting rules.
+ *    - Note that load_actions_file() is no longer used by the CGI-based
+ *      editor, but some of the other routines in this file are.
+ *
+ *    Revision 1.4  2001/09/16 15:47:37  jongfoster
+ *    First version of CGI-based edit interface.  This is very much a
+ *    work-in-progress, and you can't actually use it to edit anything
+ *    yet.  You must #define FEATURE_CGI_EDIT_ACTIONS for these changes
+ *    to have any effect.
+ *
+ *    Revision 1.3  2001/09/14 00:17:32  jongfoster
+ *    Tidying up memory allocation. New function init_action().
+ *
+ *    Revision 1.2  2001/07/29 19:01:11  jongfoster
+ *    Changed _FILENAME_H to FILENAME_H_INCLUDED.
+ *    Added forward declarations for needed structures.
+ *
+ *    Revision 1.1  2001/05/31 21:16:46  jongfoster
+ *    Moved functions to process the action list into this new file.
+ *
+ *
+ *********************************************************************/
+\f
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+struct action_spec;
+struct current_action_spec;
+struct client_state;
+
+
+
+/* This structure is used to hold user-defined aliases */
+struct action_alias
+{
+   const char * name;
+   struct action_spec action[1];
+   struct action_alias * next;
+};
+
+
+extern jb_err get_actions (char *line, 
+                           struct action_alias * alias_list,
+                           struct action_spec *cur_action);
+extern void free_alias_list(struct action_alias *alias_list);
+
+extern void init_action(struct action_spec *dest);
+extern void free_action(struct action_spec *src);
+extern jb_err merge_actions (struct action_spec *dest, 
+                             const struct action_spec *src);
+extern jb_err copy_action (struct action_spec *dest, 
+                           const struct action_spec *src);
+extern char * actions_to_text     (struct action_spec *action);
+extern char * actions_to_html     (struct client_state *csp,
+                                   struct action_spec *action);
+extern void init_current_action     (struct current_action_spec *dest);
+extern void free_current_action     (struct current_action_spec *src);
+extern jb_err merge_current_action  (struct current_action_spec *dest, 
+                                     const struct action_spec *src);
+extern char * current_action_to_html(struct client_state *csp,
+                                     struct current_action_spec *action);
+
+extern jb_err get_action_token(char **line, char **name, char **value);
+extern void unload_actions_file(void *file_data);
+extern int load_actions_file(struct client_state *csp);
+
+#ifdef FEATURE_GRACEFUL_TERMINATION
+void unload_current_actions_file(void);
+#endif
+
+
+/* Revision control strings from this header and associated .c file */
+extern const char actions_rcs[];
+extern const char actions_h_rcs[];
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* ndef ACTIONS_H_INCLUDED */
+
+/*
+  Local Variables:
+  tab-width: 3
+  end:
+*/
+
diff --git a/src/amiga.c b/src/amiga.c
new file mode 100644 (file)
index 0000000..6964eab
--- /dev/null
@@ -0,0 +1,286 @@
+const char amiga_rcs[] = "$Id: amiga.c,v 1.9 2002/03/26 22:29:54 swa Exp $";
+/*********************************************************************
+ *
+ * File        :  $Source: /cvsroot/ijbswa/current/amiga.c,v $
+ *
+ * Purpose     :  Amiga-specific declarations.
+ *
+ * Copyright   :  Written by and Copyright (C) 2001 the SourceForge
+ *                Privoxy team. http://www.privoxy.org/
+ *
+ *                This program is free software; you can redistribute it 
+ *                and/or modify it under the terms of the GNU General
+ *                Public License as published by the Free Software
+ *                Foundation; either version 2 of the License, or (at
+ *                your option) any later version.
+ *
+ *                This program is distributed in the hope that it will
+ *                be useful, but WITHOUT ANY WARRANTY; without even the
+ *                implied warranty of MERCHANTABILITY or FITNESS FOR A
+ *                PARTICULAR PURPOSE.  See the GNU General Public
+ *                License for more details.
+ *
+ *                The GNU General Public License should be included with
+ *                this file.  If not, you can view it at
+ *                http://www.gnu.org/copyleft/gpl.html
+ *                or write to the Free Software Foundation, Inc., 59
+ *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * Revisions   :
+ *    $Log: amiga.c,v $
+ *    Revision 1.9  2002/03/26 22:29:54  swa
+ *    we have a new homepage!
+ *
+ *    Revision 1.8  2002/03/25 19:32:15  joergs
+ *    Name in version string changed from junkbuster to Privoxy.
+ *
+ *    Revision 1.7  2002/03/24 13:25:43  swa
+ *    name change related issues
+ *
+ *    Revision 1.6  2002/03/09 20:03:52  jongfoster
+ *    - Making various functions return int rather than size_t.
+ *      (Undoing a recent change).  Since size_t is unsigned on
+ *      Windows, functions like read_socket that return -1 on
+ *      error cannot return a size_t.
+ *
+ *      THIS WAS A MAJOR BUG - it caused frequent, unpredictable
+ *      crashes, and also frequently caused JB to jump to 100%
+ *      CPU and stay there.  (Because it thought it had just
+ *      read ((unsigned)-1) == 4Gb of data...)
+ *
+ *    - The signature of write_socket has changed, it now simply
+ *      returns success=0/failure=nonzero.
+ *
+ *    - Trying to get rid of a few warnings --with-debug on
+ *      Windows, I've introduced a new type "jb_socket".  This is
+ *      used for the socket file descriptors.  On Windows, this
+ *      is SOCKET (a typedef for unsigned).  Everywhere else, it's
+ *      an int.  The error value can't be -1 any more, so it's
+ *      now JB_INVALID_SOCKET (which is -1 on UNIX, and in
+ *      Windows it maps to the #define INVALID_SOCKET.)
+ *
+ *    - The signature of bind_port has changed.
+ *
+ *    Revision 1.5  2002/03/03 09:18:03  joergs
+ *    Made jumbjuster work on AmigaOS again.
+ *
+ *    Revision 1.4  2001/10/07 15:35:13  oes
+ *    Replaced 6 boolean members of csp with one bitmap (csp->flags)
+ *
+ *    Revision 1.3  2001/09/12 22:54:51  joergs
+ *    Stacksize of main thread increased.
+ *
+ *    Revision 1.2  2001/05/23 00:13:58  joergs
+ *    AmigaOS support fixed.
+ *
+ *    Revision 1.1.1.1  2001/05/15 13:58:46  oes
+ *    Initial import of version 2.9.3 source tree
+ *
+ *
+ *********************************************************************/
+\f
+
+#include "config.h"
+
+#ifdef AMIGA
+
+#include <stdio.h>
+#include <signal.h>
+
+#include "project.h"
+
+const char amiga_h_rcs[] = AMIGA_H_VERSION;
+
+unsigned long __stack = 100*1024;
+static char ver[] = "$VER: Privoxy " __AMIGAVERSION__ " (" __AMIGADATE__ ")";
+struct Task *main_task = NULL;
+int childs = 0;
+
+void serve(struct client_state *csp);
+
+__saveds ULONG server_thread(void)
+{
+   struct client_state *local_csp;
+   struct UserData UserData;
+   struct Task *me=FindTask(NULL);
+
+   Wait(SIGF_SINGLE);
+   local_csp=(struct client_state *)(me->tc_UserData);
+   me->tc_UserData=&UserData;
+   SocketBase=(APTR)OpenLibrary("bsdsocket.library",3);
+   if(SocketBase)
+   {
+      SetErrnoPtr(&(UserData.eno),sizeof(int));
+      local_csp->cfd=ObtainSocket(local_csp->cfd, AF_INET, SOCK_STREAM, 0);
+      if(JB_INVALID_SOCKET!=local_csp->cfd)
+      {
+         Signal(main_task,SIGF_SINGLE);
+         serve((struct client_state *) local_csp);
+      } else {
+         local_csp->flags &= ~CSP_FLAG_ACTIVE;
+         Signal(main_task,SIGF_SINGLE);
+      }
+      CloseLibrary(SocketBase);
+   } else {
+      local_csp->flags &= ~CSP_FLAG_ACTIVE;
+      Signal(main_task,SIGF_SINGLE);
+   }
+   childs--;
+   return 0;
+}
+
+static BPTR olddir;
+
+void amiga_exit(void)
+{
+   if(SocketBase)
+   {
+      CloseLibrary(SocketBase);
+   }
+   CurrentDir(olddir);
+}
+
+static struct SignalSemaphore memsem;
+static struct SignalSemaphore *memsemptr = NULL;
+static struct UserData GlobalUserData;
+
+void InitAmiga(void)
+{
+   main_task = FindTask(NULL);
+   main_task->tc_UserData = &GlobalUserData;
+
+   if (((struct Library *)SysBase)->lib_Version < 39)
+   {
+      exit(RETURN_FAIL);
+   }
+
+   signal(SIGINT,SIG_IGN);
+   SocketBase = (APTR)OpenLibrary("bsdsocket.library",3);
+   if (!SocketBase)
+   {
+      fprintf(stderr, "Can't open bsdsocket.library V3+\n");
+      exit(RETURN_ERROR);
+   }
+   SetErrnoPtr(&(GlobalUserData.eno),sizeof(int));
+   InitSemaphore(&memsem);
+   memsemptr = &memsem;
+
+   olddir=CurrentDir(GetProgramDir());
+   atexit(amiga_exit);
+}
+
+#ifdef __GNUC__
+#ifdef libnix
+/* multithreadingsafe libnix replacements */
+static void *memPool=NULL;
+
+void *malloc (size_t s)
+{
+   ULONG *mem;
+   LONG size = s;
+
+   if (size<=0)
+   {
+      return NULL;
+   }
+   if (!memPool)
+   {
+      if (!(memPool=CreatePool(MEMF_ANY,32*1024,8*1024)))
+      {
+         return NULL;
+      }
+   }
+   size += sizeof(ULONG) + MEM_BLOCKMASK;
+   size &= ~MEM_BLOCKMASK;
+   if (memsemptr)
+   {
+      ObtainSemaphore(memsemptr);
+   }
+   if ((mem=AllocPooled(memPool,size)))
+   {
+      *mem++=size;
+   }
+   if (memsemptr)
+   {
+      ReleaseSemaphore(memsemptr);
+   }
+   return mem;
+}
+
+void free (void *m)
+{
+   ULONG *mem = m;
+
+   if(mem && memPool)
+   {
+      ULONG size=*--mem;
+
+      if (memsemptr)
+      {
+         ObtainSemaphore(memsemptr);
+      }
+      FreePooled(memPool,mem,size);
+      if (memsemptr)
+      {
+         ReleaseSemaphore(memsemptr);
+      }
+   }
+}
+
+void *realloc (void *old, size_t ns)
+{
+   void *new;
+   LONG osize, *o = old;
+   LONG nsize = ns;
+
+   if (!old)
+   {
+      return malloc(nsize);
+   }
+   osize = (*(o-1)) - sizeof(ULONG);
+   if (nsize <= osize)
+   {
+      return old;
+   }
+   if ((new = malloc(nsize)))
+   {
+      ULONG *n = new;
+
+      osize >>= 2;
+      while(osize--)
+      {
+         *n++ = *o++;
+      }
+      free(old);
+   }
+   return new;
+}
+
+void __memCleanUp (void)
+{
+   if (memsemptr)
+   {
+      ObtainSemaphore(memsemptr);
+   }
+   if (memPool)
+   {
+      DeletePool(memPool);
+   }
+   if (memsemptr)
+   {
+      ReleaseSemaphore(memsemptr);
+   }
+}
+
+#define ADD2LIST(a,b,c) asm(".stabs \"_" #b "\"," #c ",0,0,_" #a )
+#define ADD2EXIT(a,pri) ADD2LIST(a,__EXIT_LIST__,22); \
+                        asm(".stabs \"___EXIT_LIST__\",20,0,0," #pri "+128")
+ADD2EXIT(__memCleanUp,-50);
+#elif !defined(ixemul)
+#error No libnix and no ixemul!?
+#endif /* libnix */
+#else
+#error Only GCC is supported, multithreading safe malloc/free required.
+#endif /* __GNUC__ */
+
+#endif /* def AMIGA */
diff --git a/src/amiga.h b/src/amiga.h
new file mode 100644 (file)
index 0000000..b52a551
--- /dev/null
@@ -0,0 +1,126 @@
+#ifdef AMIGA
+#ifndef AMIGA_H_INCLUDED
+#define AMIGA_H_INCLUDED
+#define AMIGA_H_VERSION "$Id: amiga.h,v 1.9 2002/03/26 22:29:54 swa Exp $"
+/*********************************************************************
+ *
+ * File        :  $Source: /cvsroot/ijbswa/current/amiga.h,v $
+ *
+ * Purpose     :  Amiga-specific declarations.
+ *
+ * Copyright   :  Written by and Copyright (C) 2001 the SourceForge
+ *                Privoxy team. http://www.privoxy.org/
+ *
+ *                This program is free software; you can redistribute it 
+ *                and/or modify it under the terms of the GNU General
+ *                Public License as published by the Free Software
+ *                Foundation; either version 2 of the License, or (at
+ *                your option) any later version.
+ *
+ *                This program is distributed in the hope that it will
+ *                be useful, but WITHOUT ANY WARRANTY; without even the
+ *                implied warranty of MERCHANTABILITY or FITNESS FOR A
+ *                PARTICULAR PURPOSE.  See the GNU General Public
+ *                License for more details.
+ *
+ *                The GNU General Public License should be included with
+ *                this file.  If not, you can view it at
+ *                http://www.gnu.org/copyleft/gpl.html
+ *                or write to the Free Software Foundation, Inc., 59
+ *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * Revisions   :
+ *    $Log: amiga.h,v $
+ *    Revision 1.9  2002/03/26 22:29:54  swa
+ *    we have a new homepage!
+ *
+ *    Revision 1.8  2002/03/24 13:25:43  swa
+ *    name change related issues
+ *
+ *    Revision 1.7  2002/03/03 09:18:03  joergs
+ *    Made jumbjuster work on AmigaOS again.
+ *
+ *    Revision 1.6  2001/10/13 12:46:08  joergs
+ *    Added #undef EINTR to avoid warnings
+ *
+ *    Revision 1.5  2001/07/29 18:43:08  jongfoster
+ *    Changing #ifdef _FILENAME_H to FILENAME_H_INCLUDED, to conform to
+ *    ANSI C rules.
+ *
+ *    Revision 1.4  2001/05/29 20:05:06  joergs
+ *    Fixed exit() macro not exiting if called before InitAmiga()
+ *    (junkbuster --help and --version).
+ *
+ *    Revision 1.3  2001/05/25 21:53:27  jongfoster
+ *    Fixing indentation
+ *
+ *    Revision 1.2  2001/05/23 00:13:58  joergs
+ *    AmigaOS support fixed.
+ *
+ *    Revision 1.1.1.1  2001/05/15 13:58:46  oes
+ *    Initial import of version 2.9.3 source tree
+ *
+ *
+ *********************************************************************/
+\f
+
+#define _KERNEL
+#include <sys/socket.h>
+#undef _KERNEL
+
+#define __NOLIBBASE__
+#include <proto/socket.h>
+#undef __NOLIBBASE__
+
+#define __CONSTLIBBASEDECL__ const
+#define DEVICES_TIMER_H
+#include <proto/exec.h>
+#include <exec/tasks.h>
+#include <proto/dos.h>
+#include <dos/dostags.h>
+
+struct UserData
+{
+   struct Library *sb;
+   int eno;
+};
+
+#define SocketBase ((struct Library *)(((struct UserData *)(FindTask(NULL)->tc_UserData))->sb))
+#define errno (((struct UserData *)(FindTask(NULL)->tc_UserData))->eno)
+#define select(a,b,c,d,e) WaitSelect(a,b,c,d,e,NULL)
+#define inet_ntoa(x) Inet_NtoA(x.s_addr)
+
+extern int childs;
+extern struct Task *main_task;
+
+void InitAmiga(void);
+void amiga_exit(void);
+void __memCleanUp(void);
+__saveds ULONG server_thread(void);
+
+#define exit(x)                                             \
+{                                                           \
+   if(main_task)                                            \
+   {                                                        \
+      if(main_task == FindTask(NULL))                       \
+      {                                                     \
+         while(childs) Delay(10*TICKS_PER_SECOND); exit(x); \
+      }                                                     \
+      else                                                  \
+      {                                                     \
+         CloseLibrary(SocketBase);                          \
+         childs--;                                          \
+         RemTask(NULL);                                     \
+      }                                                     \
+   }                                                        \
+   else                                                     \
+   {                                                        \
+      exit(x);                                              \
+   }                                                        \
+}
+
+#undef EINTR
+#define EINTR 0
+
+#endif /* ndef AMIGA_H_INCLUDED */
+#endif /* def AMIGA */
diff --git a/src/cgi.c b/src/cgi.c
new file mode 100644 (file)
index 0000000..e20d8bf
--- /dev/null
+++ b/src/cgi.c
@@ -0,0 +1,2178 @@
+const char cgi_rcs[] = "$Id: cgi.c,v 1.70 2002/05/19 11:33:20 jongfoster Exp $";
+/*********************************************************************
+ *
+ * File        :  $Source: /cvsroot/ijbswa/current/cgi.c,v $
+ *
+ * Purpose     :  Declares functions to intercept request, generate
+ *                html or gif answers, and to compose HTTP resonses.
+ *                This only contains the framework functions, the
+ *                actual handler functions are declared elsewhere.
+ *                
+ *                Functions declared include:
+ * 
+ *
+ * Copyright   :  Written by and Copyright (C) 2001 the SourceForge
+ *                Privoxy team. http://www.privoxy.org/
+ *
+ *                Based on the Internet Junkbuster originally written
+ *                by and Copyright (C) 1997 Anonymous Coders and 
+ *                Junkbusters Corporation.  http://www.junkbusters.com
+ *
+ *                This program is free software; you can redistribute it 
+ *                and/or modify it under the terms of the GNU General
+ *                Public License as published by the Free Software
+ *                Foundation; either version 2 of the License, or (at
+ *                your option) any later version.
+ *
+ *                This program is distributed in the hope that it will
+ *                be useful, but WITHOUT ANY WARRANTY; without even the
+ *                implied warranty of MERCHANTABILITY or FITNESS FOR A
+ *                PARTICULAR PURPOSE.  See the GNU General Public
+ *                License for more details.
+ *
+ *                The GNU General Public License should be included with
+ *                this file.  If not, you can view it at
+ *                http://www.gnu.org/copyleft/gpl.html
+ *                or write to the Free Software Foundation, Inc., 59
+ *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * Revisions   :
+ *    $Log: cgi.c,v $
+ *    Revision 1.70  2002/05/19 11:33:20  jongfoster
+ *    If a CGI error was not handled, and propogated back to
+ *    dispatch_known_cgi(), then it was assumed to be "out of memory".
+ *    This gave a very misleading error message.
+ *
+ *    Now other errors will cause a simple message giving the error
+ *    number and asking the user to report a bug.
+ *
+ *    Bug report:
+ *    http://sourceforge.net/tracker/index.php?func=detail
+ *    &aid=557905&group_id=11118&atid=111118
+ *
+ *    Revision 1.69  2002/05/14 21:28:40  oes
+ *     - Fixed add_help_link to link to the (now split) actions
+ *       part of the config chapter
+ *     - Renamed helplink export to actions-help-prefix
+ *
+ *    Revision 1.68  2002/05/12 21:36:29  jongfoster
+ *    Correcting function comments
+ *
+ *    Revision 1.67  2002/04/30 12:02:07  oes
+ *    Nit: updated a comment
+ *
+ *    Revision 1.66  2002/04/26 18:32:57  jongfoster
+ *    Fixing a memory leak on error
+ *
+ *    Revision 1.65  2002/04/26 12:53:51  oes
+ *     - New function add_help_link
+ *     - default_exports now exports links to the user manual
+ *       and a prefix for links into the config chapter
+ *
+ *    Revision 1.64  2002/04/24 02:17:21  oes
+ *     - Better descriptions for CGIs
+ *     - Hide edit-actions, more shortcuts
+ *     - Moved get_char_param, get_string_param and get_number_param here
+ *       from cgiedit.c
+ *
+ *    Revision 1.63  2002/04/15 19:06:43  jongfoster
+ *    Typos
+ *
+ *    Revision 1.62  2002/04/10 19:59:46  jongfoster
+ *    Fixes to #include in templates:
+ *    - Didn't close main file if loading an included template fails.
+ *    - I'm paranoid and want to disallow "#include /etc/passwd".
+ *
+ *    Revision 1.61  2002/04/10 13:37:48  oes
+ *    Made templates modular: template_load now recursive with max depth 1
+ *
+ *    Revision 1.60  2002/04/08 20:50:25  swa
+ *    fixed JB spelling
+ *
+ *    Revision 1.59  2002/04/05 15:51:51  oes
+ *     - added send-stylesheet CGI
+ *     - bugfix: error-pages now get correct request protocol
+ *     - fixed
+ *     - kludged CGI descriptions and menu not to break JS syntax
+ *
+ *    Revision 1.58  2002/03/29 03:33:13  david__schmidt
+ *    Fix Mac OSX compiler warnings
+ *
+ *    Revision 1.57  2002/03/26 22:29:54  swa
+ *    we have a new homepage!
+ *
+ *    Revision 1.56  2002/03/24 17:50:46  jongfoster
+ *    Fixing compile error if actions file editor disabled
+ *
+ *    Revision 1.55  2002/03/24 16:55:06  oes
+ *    Making GIF checkerboard transparent
+ *
+ *    Revision 1.54  2002/03/24 16:18:15  jongfoster
+ *    Removing old logo
+ *
+ *    Revision 1.53  2002/03/24 16:06:00  oes
+ *    Correct transparency for checkerboard PNG. Thanks, Magnus!
+ *
+ *    Revision 1.52  2002/03/24 15:23:33  jongfoster
+ *    Name changes
+ *
+ *    Revision 1.51  2002/03/24 13:25:43  swa
+ *    name change related issues
+ *
+ *    Revision 1.50  2002/03/16 23:54:06  jongfoster
+ *    Adding graceful termination feature, to help look for memory leaks.
+ *    If you enable this (which, by design, has to be done by hand
+ *    editing config.h) and then go to http://i.j.b/die, then the program
+ *    will exit cleanly after the *next* request.  It should free all the
+ *    memory that was used.
+ *
+ *    Revision 1.49  2002/03/13 00:27:04  jongfoster
+ *    Killing warnings
+ *
+ *    Revision 1.48  2002/03/08 17:47:07  jongfoster
+ *    Adding comments
+ *
+ *    Revision 1.47  2002/03/08 16:41:33  oes
+ *    Added GIF images again
+ *
+ *    Revision 1.46  2002/03/07 03:48:38  oes
+ *     - Changed built-in images from GIF to PNG
+ *       (with regard to Unisys patent issue)
+ *     - Added a 4x4 pattern PNG which is less intrusive
+ *       than the logo but also clearly marks the deleted banners
+ *
+ *    Revision 1.45  2002/03/06 22:54:35  jongfoster
+ *    Automated function-comment nitpicking.
+ *
+ *    Revision 1.44  2002/03/05 22:43:45  david__schmidt
+ *    - Better error reporting on OS/2
+ *    - Fix double-slash comment (oops)
+ *
+ *    Revision 1.43  2002/03/05 21:33:45  david__schmidt
+ *    - Re-enable OS/2 building after new parms were added
+ *    - Fix false out of memory report when resolving CGI templates when no IP
+ *      address is available of failed attempt (a la no such domain)
+ *
+ *    Revision 1.42  2002/01/21 00:33:20  jongfoster
+ *    Replacing strsav() with the safer string_append() or string_join().
+ *    Adding map_block_keep() to save a few bytes in the edit-actions-list HTML.
+ *    Adding missing html_encode() to error message generators.
+ *    Adding edit-actions-section-swap and many "shortcuts" to the list of CGIs.
+ *
+ *    Revision 1.41  2002/01/17 20:56:22  jongfoster
+ *    Replacing hard references to the URL of the config interface
+ *    with #defines from project.h
+ *
+ *    Revision 1.40  2002/01/09 14:26:46  oes
+ *    Added support for thread-safe gmtime_r call.
+ *
+ *    Revision 1.39  2001/11/16 00:48:13  jongfoster
+ *    Fixing a compiler warning
+ *
+ *    Revision 1.38  2001/11/13 00:31:21  jongfoster
+ *    - Adding new CGIs for use by non-JavaScript browsers:
+ *        edit-actions-url-form
+ *        edit-actions-add-url-form
+ *        edit-actions-remove-url-form
+ *    - Fixing make_menu()'s HTML generation - it now quotes the href parameter.
+ *    - Fixing || bug.
+ *
+ *    Revision 1.37  2001/11/01 14:28:47  david__schmidt
+ *    Show enablement/disablement status in almost all templates.
+ *    There is a little trickiness here: apparent recursive resolution of
+ *    @if-enabled-then@ caused the toggle template to show status out-of-phase with
+ *    the actual enablement status.  So a similar construct,
+ *    @if-enabled-display-then@, is used to resolve the status display on non-'toggle'
+ *    templates.
+ *
+ *    Revision 1.36  2001/10/26 17:33:27  oes
+ *    marginal bugfix
+ *
+ *    Revision 1.35  2001/10/23 21:48:19  jongfoster
+ *    Cleaning up error handling in CGI functions - they now send back
+ *    a HTML error page and should never cause a FATAL error.  (Fixes one
+ *    potential source of "denial of service" attacks).
+ *
+ *    CGI actions file editor that works and is actually useful.
+ *
+ *    Ability to toggle Junkbuster remotely using a CGI call.
+ *
+ *    You can turn off both the above features in the main configuration
+ *    file, e.g. if you are running a multi-user proxy.
+ *
+ *    Revision 1.34  2001/10/18 22:22:09  david__schmidt
+ *    Only show "Local support" on templates conditionally:
+ *      - if either 'admin-address' or 'proxy-info-url' are uncommented in config
+ *      - if not, no Local support section appears
+ *
+ *    Revision 1.33  2001/10/14 22:28:41  jongfoster
+ *    Fixing stupid typo.
+ *
+ *    Revision 1.32  2001/10/14 22:20:18  jongfoster
+ *    - Changes to CGI dispatching method to match CGI names exactly,
+ *      rather than doing a prefix match.
+ *    - No longer need to count the length of the CGI handler names by hand.
+ *    - Adding new handler for 404 error when disptching a CGI, if none of
+ *      the handlers match.
+ *    - Adding new handlers for CGI actionsfile editor.
+ *
+ *    Revision 1.31  2001/10/10 10:56:39  oes
+ *    Failiure to load template now fatal. Before, the user got a hard-to-understand assertion failure from cgi.c
+ *
+ *    Revision 1.30  2001/10/02 15:30:57  oes
+ *    Introduced show-request cgi
+ *
+ *    Revision 1.29  2001/09/20 15:47:44  steudten
+ *
+ *    Fix BUG: Modify int size to size_t size in fill_template()
+ *     - removes big trouble on machines where sizeof(int) != sizeof(size_t).
+ *
+ *    Revision 1.28  2001/09/19 18:00:37  oes
+ *     - Deletef time() FIXME (Can't fail under Linux either, if
+ *       the argument is guaranteed to be in out address space,
+ *       which it is.)
+ *     - Fixed comments
+ *     - Pointer notation cosmetics
+ *     - Fixed a minor bug in template_fill(): Failiure of
+ *       pcrs_execute() now secure.
+ *
+ *    Revision 1.27  2001/09/16 17:08:54  jongfoster
+ *    Moving simple CGI functions from cgi.c to new file cgisimple.c
+ *
+ *    Revision 1.26  2001/09/16 15:47:37  jongfoster
+ *    First version of CGI-based edit interface.  This is very much a
+ *    work-in-progress, and you can't actually use it to edit anything
+ *    yet.  You must #define FEATURE_CGI_EDIT_ACTIONS for these changes
+ *    to have any effect.
+ *
+ *    Revision 1.25  2001/09/16 15:02:35  jongfoster
+ *    Adding i.j.b/robots.txt.
+ *    Inlining add_stats() since it's only ever called from one place.
+ *
+ *    Revision 1.24  2001/09/16 11:38:01  jongfoster
+ *    Splitting fill_template() into 2 functions:
+ *    template_load() loads the file
+ *    template_fill() performs the PCRS regexps.
+ *    This is because the CGI edit interface has a "table row"
+ *    template which is used many times in the page - this
+ *    change means it's only loaded from disk once.
+ *
+ *    Revision 1.23  2001/09/16 11:16:05  jongfoster
+ *    Better error handling in dispatch_cgi() and parse_cgi_parameters()
+ *
+ *    Revision 1.22  2001/09/16 11:00:10  jongfoster
+ *    New function alloc_http_response, for symmetry with free_http_response
+ *
+ *    Revision 1.21  2001/09/13 23:53:03  jongfoster
+ *    Support for both static and dynamically generated CGI pages.
+ *    Correctly setting Last-Modified: and Expires: HTTP headers.
+ *
+ *    Revision 1.20  2001/09/13 23:40:36  jongfoster
+ *    (Cosmetic only) Indentation correction
+ *
+ *    Revision 1.19  2001/09/13 23:31:25  jongfoster
+ *    Moving image data to cgi.c rather than cgi.h.
+ *
+ *    Revision 1.18  2001/08/05 16:06:20  jongfoster
+ *    Modifiying "struct map" so that there are now separate header and
+ *    "map_entry" structures.  This means that functions which modify a
+ *    map no longer need to return a pointer to the modified map.
+ *    Also, it no longer reverses the order of the entries (which may be
+ *    important with some advanced template substitutions).
+ *
+ *    Revision 1.17  2001/08/05 15:57:38  oes
+ *    Adapted finish_http_response to new list_to_text
+ *
+ *    Revision 1.16  2001/08/01 21:33:18  jongfoster
+ *    Changes to fill_template() that reduce memory usage without having
+ *    an impact on performance.  I also renamed some variables so as not
+ *    to clash with the C++ keywords "new" and "template".
+ *
+ *    Revision 1.15  2001/08/01 21:19:22  jongfoster
+ *    Moving file version information to a separate CGI page.
+ *
+ *    Revision 1.14  2001/08/01 00:19:03  jongfoster
+ *    New function: map_conditional() for an if-then-else syntax.
+ *    Changing to use new version of show_defines()
+ *
+ *    Revision 1.13  2001/07/30 22:08:36  jongfoster
+ *    Tidying up #defines:
+ *    - All feature #defines are now of the form FEATURE_xxx
+ *    - Permanently turned off WIN_GUI_EDIT
+ *    - Permanently turned on WEBDAV and SPLIT_PROXY_ARGS
+ *
+ *    Revision 1.12  2001/07/29 18:47:05  jongfoster
+ *    Adding missing #include "loadcfg.h"
+ *
+ *    Revision 1.11  2001/07/18 17:24:37  oes
+ *    Changed to conform to new pcrs interface
+ *
+ *    Revision 1.10  2001/07/13 13:53:13  oes
+ *    Removed all #ifdef PCRS and related code
+ *
+ *    Revision 1.9  2001/06/29 21:45:41  oes
+ *    Indentation, CRLF->LF, Tab-> Space
+ *
+ *    Revision 1.8  2001/06/29 13:21:46  oes
+ *    - Cosmetics: renamed and reordered functions, variables,
+ *      texts, improved comments  etc
+ *
+ *    - Removed ij_untrusted_url() The relevant
+ *      info is now part of the "untrusted" page,
+ *      which is generated by filters.c:trust_url()
+ *
+ *    - Generators of content now call finish_http_response()
+ *      themselves, making jcc.c:chat() a little less
+ *      cluttered
+ *
+ *    - Removed obsolete "Pragma: no-cache" from our headers
+ *
+ *    - http_responses now know their head length
+ *
+ *    - fill_template now uses the new interface to pcrs, so that
+ *       - long jobs (like whole files) no longer have to be assembled
+ *         in a fixed size buffer
+ *       - the new T (trivial) option is used, and the replacement may
+ *         contain Perl syntax backrefs without confusing pcrs
+ *
+ *    - Introduced default_exports() which generates a set of exports
+ *      common to all CGIs and other content generators
+ *
+ *    - Introduced convenience function map_block_killer()
+ *
+ *    - Introduced convenience function make_menu()
+ *
+ *    - Introduced CGI-like function error_response() which generates
+ *      the "No such domain" and "Connect failed" messages using the
+ *      CGI platform
+ *
+ *    - cgi_show_url_info:
+ *      - adapted to new CGI features
+ *      - form and answers now generated from same template
+ *      - http:// prefix in URL now OK
+ *
+ *    - cgi_show_status:
+ *      - adapted to new CGI features
+ *      - no longer uses csp->init_proxy_args
+ *
+ *    - cgi_default:
+ *      - moved menu generation to make_menu()
+ *
+ *    - add_stats now writes single export map entries instead
+ *      of a fixed string
+ *
+ *    - Moved redirect_url() to filters.c
+ *
+ *    - Fixed mem leak in free_http_response(), map_block_killer(),
+ *
+ *    - Removed logentry from cancelled commit
+ *
+ *    Revision 1.7  2001/06/09 10:51:58  jongfoster
+ *    Changing "show URL info" handler to new style.
+ *    Changing BUFSIZ ==> BUFFER_SIZE
+ *
+ *    Revision 1.6  2001/06/07 23:05:19  jongfoster
+ *    Removing code related to old forward and ACL files.
+ *
+ *    Revision 1.5  2001/06/05 19:59:16  jongfoster
+ *    Fixing multiline character string (a GCC-only "feature"), and snprintf (it's _snprintf under VC++).
+ *
+ *    Revision 1.4  2001/06/04 10:41:52  swa
+ *    show version string of cgi.h and cgi.c
+ *
+ *    Revision 1.3  2001/06/03 19:12:16  oes
+ *    introduced new cgi handling
+ *
+ *    No revisions before 1.3
+ *
+ **********************************************************************/
+\f
+
+#include "config.h"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+#include <limits.h>
+#include <assert.h>
+
+#ifdef _WIN32
+#define snprintf _snprintf
+#endif /* def _WIN32 */
+
+#include "project.h"
+#include "cgi.h"
+#include "list.h"
+#include "encode.h"
+#include "ssplit.h"
+#include "errlog.h"
+#include "miscutil.h"
+#include "cgisimple.h"
+#ifdef FEATURE_CGI_EDIT_ACTIONS
+#include "cgiedit.h"
+#endif /* def FEATURE_CGI_EDIT_ACTIONS */
+#include "loadcfg.h"
+/* loadcfg.h is for g_bToggleIJB only */
+
+const char cgi_h_rcs[] = CGI_H_VERSION;
+
+/*
+ * List of CGI functions: name, handler, description
+ * Note: Do NOT use single quotes in the description;
+ *       this will break the dynamic "blocked" template!
+ */
+static const struct cgi_dispatcher cgi_dispatchers[] = {
+   { "",
+         cgi_default,
+         "Privoxy main page" },
+#ifdef FEATURE_GRACEFUL_TERMINATION
+   { "die", 
+         cgi_die,  
+         "<b>Shut down</b> - <em class=\"warning\">Do not deploy this build in a production environment, "
+         "this is a one click Denial Of Service attack!!!</em>" }, 
+#endif
+   { "show-status", 
+         cgi_show_status,  
+         "View & change the current configuration" }, 
+   { "show-version", 
+         cgi_show_version,  
+         "View the source code version numbers" }, 
+   { "show-request", 
+         cgi_show_request,  
+         "View the request headers." }, 
+   { "show-url-info",
+         cgi_show_url_info, 
+         "Look up which actions apply to a URL and why"  },
+#ifdef FEATURE_CGI_EDIT_ACTIONS
+   { "toggle",
+         cgi_toggle, 
+         "Toggle Privoxy on or off" },
+
+   { "edit-actions", /* Edit the actions list */
+         cgi_edit_actions, 
+         NULL },
+   { "eaa", /* Shortcut for edit-actions-add-url-form */
+         cgi_edit_actions_add_url_form, 
+         NULL },
+   { "eau", /* Shortcut for edit-actions-url-form */
+         cgi_edit_actions_url_form, 
+         NULL },
+   { "ear", /* Shortcut for edit-actions-remove-url-form */
+         cgi_edit_actions_remove_url_form, 
+         NULL },
+   { "eafu", /* Shortcut for edit-actions-for-url */
+         cgi_edit_actions_for_url, 
+         NULL },
+   { "eas", /* Shortcut for edit-actions-submit */
+         cgi_edit_actions_submit, 
+         NULL },
+   { "easa", /* Shortcut for edit-actions-section-add */
+         cgi_edit_actions_section_add, 
+         NULL },
+   { "easr", /* Shortcut for edit-actions-section-remove */
+         cgi_edit_actions_section_remove, 
+         NULL },
+   { "eass", /* Shortcut for edit-actions-section-swap */
+         cgi_edit_actions_section_swap, 
+         NULL },
+   { "edit-actions-for-url",
+         cgi_edit_actions_for_url, 
+         NULL /* Edit the actions for (a) specified URL(s) */ },
+   { "edit-actions-list",
+         cgi_edit_actions_list, 
+         NULL /* Edit the actions list */ },
+   { "edit-actions-submit",
+         cgi_edit_actions_submit, 
+         NULL /* Change the actions for (a) specified URL(s) */ },
+   { "edit-actions-url",
+         cgi_edit_actions_url, 
+         NULL /* Change a URL pattern in the actionsfile */ },
+   { "edit-actions-url-form",
+         cgi_edit_actions_url_form, 
+         NULL /* Form to change a URL pattern in the actionsfile */ },
+   { "edit-actions-add-url",
+         cgi_edit_actions_add_url, 
+         NULL /* Add a URL pattern to the actionsfile */ },
+   { "edit-actions-add-url-form",
+         cgi_edit_actions_add_url_form, 
+         NULL /* Form to add a URL pattern to the actionsfile */ },
+   { "edit-actions-remove-url",
+         cgi_edit_actions_remove_url, 
+         NULL /* Remove a URL pattern from the actionsfile */ },
+   { "edit-actions-remove-url-form",
+         cgi_edit_actions_remove_url_form, 
+         NULL /* Form to remove a URL pattern from the actionsfile */ },
+   { "edit-actions-section-add",
+         cgi_edit_actions_section_add, 
+         NULL /* Remove a section from the actionsfile */ },
+   { "edit-actions-section-remove",
+         cgi_edit_actions_section_remove, 
+         NULL /* Remove a section from the actionsfile */ },
+   { "edit-actions-section-swap",
+         cgi_edit_actions_section_swap, 
+         NULL /* Swap two sections in the actionsfile */ },
+#endif /* def FEATURE_CGI_EDIT_ACTIONS */
+   { "robots.txt", 
+         cgi_robots_txt,  
+         NULL /* Sends a robots.txt file to tell robots to go away. */ }, 
+   { "send-banner",
+         cgi_send_banner, 
+         NULL /* Send a built-in image */ },
+   { "send-stylesheet",
+         cgi_send_stylesheet, 
+         NULL /* Send templates/cgi-style.css */ },
+   { "t",
+         cgi_transparent_image, 
+         NULL /* Send a transparent image (short name) */ },
+   { NULL, /* NULL Indicates end of list and default page */
+         cgi_error_404,
+         NULL /* Unknown CGI page */ }
+};
+
+
+/*
+ * Bulit-in images for ad replacement
+ *
+ * Hint: You can encode your own images like this:
+ * cat your-image | perl -e 'while (read STDIN, $c, 1) { printf("\\%.3o", unpack("C", $c)); }'
+ */
+
+#ifdef FEATURE_NO_GIFS
+
+/*
+ * Checkerboard pattern, as a PNG.
+ */
+const char image_pattern_data[] =
+   "\211\120\116\107\015\012\032\012\000\000\000\015\111\110\104"
+   "\122\000\000\000\004\000\000\000\004\010\002\000\000\000\046"
+   "\223\011\051\000\000\000\006\142\113\107\104\000\310\000\310"
+   "\000\310\052\045\225\037\000\000\000\032\111\104\101\124\170"
+   "\332\143\070\161\342\304\377\377\377\041\044\003\234\165\342"
+   "\304\011\006\234\062\000\125\200\052\251\125\174\360\223\000"
+   "\000\000\000\111\105\116\104\256\102\140\202";
+
+/*
+ * 1x1 transparant PNG.
+ */
+const char image_blank_data[] =
+ "\211\120\116\107\015\012\032\012\000\000\000\015\111\110\104\122"
+ "\000\000\000\004\000\000\000\004\010\006\000\000\000\251\361\236"
+ "\176\000\000\000\007\164\111\115\105\007\322\003\013\020\073\070"
+ "\013\025\036\203\000\000\000\011\160\110\131\163\000\000\013\022"
+ "\000\000\013\022\001\322\335\176\374\000\000\000\004\147\101\115"
+ "\101\000\000\261\217\013\374\141\005\000\000\000\033\111\104\101"
+ "\124\170\332\143\070\161\342\304\207\377\377\377\347\302\150\006"
+ "\144\016\210\146\040\250\002\000\042\305\065\221\270\027\131\110"
+ "\000\000\000\000\111\105\116\104\256\102\140\202";
+#else
+
+/*
+ * Checkerboard pattern, as a GIF.
+ */
+const char image_pattern_data[] =
+   "\107\111\106\070\071\141\004\000\004\000\200\000\000\310\310"
+   "\310\377\377\377\041\376\016\111\040\167\141\163\040\141\040"
+   "\142\141\156\156\145\162\000\041\371\004\001\012\000\001\000"
+   "\054\000\000\000\000\004\000\004\000\000\002\005\104\174\147"
+   "\270\005\000\073";
+
+/*
+ * 1x1 transparant GIF.
+ */
+const char image_blank_data[] =
+   "GIF89a\001\000\001\000\200\000\000\377\377\377\000\000"
+   "\000!\371\004\001\000\000\000\000,\000\000\000\000\001"
+   "\000\001\000\000\002\002D\001\000;";
+#endif
+
+const size_t image_pattern_length = sizeof(image_pattern_data) - 1;
+const size_t image_blank_length   = sizeof(image_blank_data) - 1;
+
+
+static struct http_response cgi_error_memory_response[1];
+
+static struct http_response *dispatch_known_cgi(struct client_state * csp,
+                                                const char * path);
+static struct map *parse_cgi_parameters(char *argstring);
+
+
+/*********************************************************************
+ * 
+ * Function    :  dispatch_cgi
+ *
+ * Description :  Checks if a request URL has either the magical
+ *                hostname CGI_SITE_1_HOST (usually http://p.p/) or
+ *                matches CGI_SITE_2_HOST CGI_SITE_2_PATH (usually
+ *                http://config.privoxy.org/). If so, it passes
+ *                the (rest of the) path onto dispatch_known_cgi, which
+ *                calls the relevant CGI handler function.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns     :  http_response if match, NULL if nonmatch or handler fail
+ *
+ *********************************************************************/
+struct http_response *dispatch_cgi(struct client_state *csp)
+{
+   const char *host = csp->http->host;
+   const char *path = csp->http->path;
+
+   /*
+    * Should we intercept ?
+    */
+
+   /* Note: "example.com" and "example.com." are equivalent hostnames. */
+
+   /* Either the host matches CGI_SITE_1_HOST ..*/
+   if (   ( (0 == strcmpic(host, CGI_SITE_1_HOST))
+         || (0 == strcmpic(host, CGI_SITE_1_HOST ".")))
+       && (path[0] == '/') )
+   {
+      /* ..then the path will all be for us.  Remove leading '/' */
+      path++;
+   }
+   /* Or it's the host part CGI_SITE_2_HOST, and the path CGI_SITE_2_PATH */
+   else if ( ( (0 == strcmpic(host, CGI_SITE_2_HOST ))
+            || (0 == strcmpic(host, CGI_SITE_2_HOST ".")) )
+          && (0 == strncmpic(path, CGI_SITE_2_PATH, strlen(CGI_SITE_2_PATH))) )
+   {
+      /* take everything following CGI_SITE_2_PATH */
+      path += strlen(CGI_SITE_2_PATH);
+      if (*path == '/')
+      {
+         /* skip the forward slash after CGI_SITE_2_PATH */
+         path++;
+      }
+      else if (*path != '\0')
+      {
+         /*
+          * wierdness: URL is /configXXX, where XXX is some string
+          * Do *NOT* intercept.
+          */
+         return NULL;
+      }
+   }
+   else
+   {
+      /* Not a CGI */
+      return NULL;
+   }
+
+   /* 
+    * This is a CGI call.
+    */
+
+   return dispatch_known_cgi(csp, path);
+}
+
+
+/*********************************************************************
+ * 
+ * Function    :  dispatch_known_cgi
+ *
+ * Description :  Processes a CGI once dispatch_cgi has determined that
+ *                it matches one of the magic prefixes. Parses the path
+ *                as a cgi name plus query string, prepares a map that
+ *                maps CGI parameter names to their values, initializes
+ *                the http_response struct, and calls the relevant CGI
+ *                handler function.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  path = Path of CGI, with the CGI prefix removed.
+ *                       Should not have a leading "/".
+ *
+ * Returns     :  http_response, or NULL on handler failure or out of
+ *                memory.
+ *
+ *********************************************************************/
+static struct http_response *dispatch_known_cgi(struct client_state * csp,
+                                                const char * path)
+{
+   const struct cgi_dispatcher *d;
+   struct map *param_list;
+   struct http_response *rsp;
+   char *query_args_start;
+   char *path_copy;
+   jb_err err;
+
+   if (NULL == (path_copy = strdup(path)))
+   {
+      return cgi_error_memory();
+   }
+
+   query_args_start = path_copy;
+   while (*query_args_start && *query_args_start != '?')
+   {
+      query_args_start++;
+   }
+   if (*query_args_start == '?')
+   {
+      *query_args_start++ = '\0';
+   }
+
+   if (NULL == (param_list = parse_cgi_parameters(query_args_start)))
+   {
+      free(path_copy);
+      return cgi_error_memory();
+   }
+
+
+   /*
+    * At this point:
+    * path_copy        = CGI call name
+    * param_list       = CGI params, as map
+    */
+
+   /* Get mem for response or fail*/
+   if (NULL == (rsp = alloc_http_response()))
+   {
+      free(path_copy);
+      free_map(param_list);
+      return cgi_error_memory();
+   }
+
+   log_error(LOG_LEVEL_GPC, "%s%s cgi call", csp->http->hostport, csp->http->path);
+   log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s\" 200 3", 
+                            csp->ip_addr_str, csp->http->cmd); 
+
+   /* Find and start the right CGI function*/
+   d = cgi_dispatchers;
+   for (;;)
+   {
+      if ((d->name == NULL) || (strcmp(path_copy, d->name) == 0))
+      {
+         err = (d->handler)(csp, rsp, param_list);
+         free(path_copy);
+         free_map(param_list);
+         if (err == JB_ERR_CGI_PARAMS)
+         {
+            err = cgi_error_bad_param(csp, rsp);
+         }
+         if (err && (err != JB_ERR_MEMORY))
+         {
+            /* Unexpected error! Shouldn't get here */
+            log_error(LOG_LEVEL_ERROR, "Unexpected CGI error %d in top-level handler.  Please file a bug report!", err);
+            err = cgi_error_unknown(csp, rsp, err);
+         }
+         if (!err)
+         {
+            /* It worked */
+            return finish_http_response(rsp);
+         }
+         else
+         {
+            /* Error in handler, probably out-of-memory */
+            free_http_response(rsp);
+            return cgi_error_memory();
+         }
+      }
+      d++;
+   }
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  parse_cgi_parameters
+ *
+ * Description :  Parse a URL-encoded argument string into name/value
+ *                pairs and store them in a struct map list.
+ *
+ * Parameters  :
+ *          1  :  argstring = string to be parsed.  Will be trashed.
+ *
+ * Returns     :  pointer to param list, or NULL if out of memory.
+ *
+ *********************************************************************/
+static struct map *parse_cgi_parameters(char *argstring)
+{
+   char *p;
+   char *vector[BUFFER_SIZE];
+   int pairs, i;
+   struct map *cgi_params;
+
+   if (NULL == (cgi_params = new_map()))
+   {
+      return NULL;
+   }
+
+   pairs = ssplit(argstring, "&", vector, SZ(vector), 1, 1);
+
+   for (i = 0; i < pairs; i++)
+   {
+      if ((NULL != (p = strchr(vector[i], '='))) && (*(p+1) != '\0'))
+      {
+         *p = '\0';
+         if (map(cgi_params, url_decode(vector[i]), 0, url_decode(++p), 0))
+         {
+            free_map(cgi_params);
+            return NULL;
+         }
+      }
+   }
+
+   return cgi_params;
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  get_char_param
+ *
+ * Description :  Get a single-character parameter passed to a CGI
+ *                function.
+ *
+ * Parameters  :
+ *          1  :  parameters = map of cgi parameters
+ *          2  :  param_name = The name of the parameter to read
+ *
+ * Returns     :  Uppercase character on success, '\0' on error.
+ *
+ *********************************************************************/
+char get_char_param(const struct map *parameters,
+                    const char *param_name)
+{
+   char ch;
+
+   assert(parameters);
+   assert(param_name);
+
+   ch = *(lookup(parameters, param_name));
+   if ((ch >= 'a') && (ch <= 'z'))
+   {
+      ch = ch - 'a' + 'A';
+   }
+
+   return ch;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  get_string_param
+ *
+ * Description :  Get a string paramater, to be used as an
+ *                ACTION_STRING or ACTION_MULTI paramater.
+ *                Validates the input to prevent stupid/malicious
+ *                users from corrupting their action file.
+ *
+ * Parameters  :
+ *          1  :  parameters = map of cgi parameters
+ *          2  :  param_name = The name of the parameter to read
+ *          3  :  pparam = destination for paramater.  Allocated as
+ *                part of the map "parameters", so don't free it.
+ *                Set to NULL if not specified.
+ *
+ * Returns     :  JB_ERR_OK         on success, or if the paramater
+ *                                  was not specified.
+ *                JB_ERR_MEMORY     on out-of-memory.
+ *                JB_ERR_CGI_PARAMS if the paramater is not valid.
+ *
+ *********************************************************************/
+jb_err get_string_param(const struct map *parameters,
+                        const char *param_name,
+                        const char **pparam)
+{
+   const char *param;
+   const char *s;
+   char ch;
+
+   assert(parameters);
+   assert(param_name);
+   assert(pparam);
+
+   *pparam = NULL;
+
+   param = lookup(parameters, param_name);
+   if (!*param)
+   {
+      return JB_ERR_OK;
+   }
+
+   if (strlen(param) >= CGI_PARAM_LEN_MAX)
+   {
+      /*
+       * Too long.
+       *
+       * Note that the length limit is arbitrary, it just seems
+       * sensible to limit it to *something*.  There's no
+       * technical reason for any limit at all.
+       */
+      return JB_ERR_CGI_PARAMS;
+   }
+
+   /* Check every character to see if it's legal */
+   s = param;
+   while ((ch = *s++) != '\0')
+   {
+      if ( ((unsigned char)ch < (unsigned char)' ')
+        || (ch == '}') )
+      {
+         /* Probable hack attempt, or user accidentally used '}'. */
+         return JB_ERR_CGI_PARAMS;
+      }
+   }
+
+   /* Success */
+   *pparam = param;
+
+   return JB_ERR_OK;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  get_number_param
+ *
+ * Description :  Get a non-negative integer from the parameters
+ *                passed to a CGI function.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  parameters = map of cgi parameters
+ *          3  :  name = Name of CGI parameter to read
+ *          4  :  pvalue = destination for value.
+ *                         Set to -1 on error.
+ *
+ * Returns     :  JB_ERR_OK         on success
+ *                JB_ERR_MEMORY     on out-of-memory
+ *                JB_ERR_CGI_PARAMS if the parameter was not specified
+ *                                  or is not valid.
+ *
+ *********************************************************************/
+jb_err get_number_param(struct client_state *csp,
+                        const struct map *parameters,
+                        char *name,
+                        unsigned *pvalue)
+{
+   const char *param;
+   char ch;
+   unsigned value;
+
+   assert(csp);
+   assert(parameters);
+   assert(name);
+   assert(pvalue);
+
+   *pvalue = 0; 
+
+   param = lookup(parameters, name);
+   if (!*param)
+   {
+      return JB_ERR_CGI_PARAMS;
+   }
+
+   /* We don't use atoi because I want to check this carefully... */
+
+   value = 0;
+   while ((ch = *param++) != '\0')
+   {
+      if ((ch < '0') || (ch > '9'))
+      {
+         return JB_ERR_CGI_PARAMS;
+      }
+
+      ch -= '0';
+
+      /* Note:
+       *
+       * <limits.h> defines UINT_MAX
+       *
+       * (UINT_MAX - ch) / 10 is the largest number that
+       *     can be safely multiplied by 10 then have ch added.
+       */
+      if (value > ((UINT_MAX - (unsigned)ch) / 10U))
+      {
+         return JB_ERR_CGI_PARAMS;
+      }
+
+      value = value * 10 + ch;
+   }
+
+   /* Success */
+   *pvalue = value;
+
+   return JB_ERR_OK;
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  error_response
+ *
+ * Description :  returns an http_response that explains the reason
+ *                why a request failed.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  templatename = Which template should be used for the answer
+ *          3  :  sys_err = system error number
+ *
+ * Returns     :  A http_response.  If we run out of memory, this
+ *                will be cgi_error_memory().
+ *
+ *********************************************************************/
+struct http_response *error_response(struct client_state *csp,
+                                     const char *templatename,
+                                     int sys_err)
+{
+   jb_err err;
+   struct http_response *rsp;
+   struct map * exports = default_exports(csp, NULL);
+   if (exports == NULL)
+   {
+      return cgi_error_memory();
+   }
+
+   if (NULL == (rsp = alloc_http_response()))
+   {
+      free_map(exports);
+      return cgi_error_memory();
+   }
+
+   err = map(exports, "host", 1, html_encode(csp->http->host), 0);
+   if (!err) err = map(exports, "hostport", 1, html_encode(csp->http->hostport), 0);
+   if (!err) err = map(exports, "path", 1, html_encode(csp->http->path), 0);
+   if (!err) err = map(exports, "error", 1, html_encode_and_free_original(safe_strerror(sys_err)), 0);
+   if (!err) err = map(exports, "protocol", 1, csp->http->ssl ? "https://" : "http://", 1); 
+   if (!err)
+   {
+     err = map(exports, "host-ip", 1, html_encode(csp->http->host_ip_addr_str), 0);
+     if (err)
+     {
+       /* Some failures, like "404 no such domain", don't have an IP address. */
+       err = map(exports, "host-ip", 1, html_encode(csp->http->host), 0);
+     }
+   }
+
+
+   if (err)
+   {
+      free_map(exports);
+      free_http_response(rsp);
+      return cgi_error_memory();
+   }
+
+   if (!strcmp(templatename, "no-such-domain"))
+   {
+      rsp->status = strdup("404 No such domain");
+      if (rsp->status == NULL)
+      {
+         free_map(exports);
+         free_http_response(rsp);
+         return cgi_error_memory();
+      }
+   }
+   else if (!strcmp(templatename, "connect-failed"))
+   {
+      rsp->status = strdup("503 Connect failed");
+      if (rsp->status == NULL)
+      {
+         free_map(exports);
+         free_http_response(rsp);
+         return cgi_error_memory();
+      }
+   }
+
+   err = template_fill_for_cgi(csp, templatename, exports, rsp);
+   if (err)
+   {
+      free_http_response(rsp);
+      return cgi_error_memory();
+   }
+
+   return finish_http_response(rsp);
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  cgi_init_error_messages
+ *
+ * Description :  Call at the start of the program to initialize
+ *                the error message used by cgi_error_memory().
+ *
+ * Parameters  :  N/A
+ *
+ * Returns     :  N/A
+ *
+ *********************************************************************/
+void cgi_init_error_messages(void)
+{
+   memset(cgi_error_memory_response, '\0', sizeof(*cgi_error_memory_response));
+   cgi_error_memory_response->head =
+      "HTTP/1.0 500 Internal Privoxy Error\r\n"
+      "Content-Type: text/html\r\n"
+      "\r\n";
+   cgi_error_memory_response->body =
+      "<html>\r\n"
+      "<head><title>500 Internal Privoxy Error</title></head>\r\n"
+      "<body>\r\n"
+      "<h1>500 Internal Privoxy Error</h1>\r\n"
+      "<p>Privoxy <b>ran out of memory</b> while processing your request.</p>\r\n"
+      "<p>Please contact your proxy administrator, or try again later</p>\r\n"
+      "</body>\r\n"
+      "</html>\r\n";
+
+   cgi_error_memory_response->head_length =
+      strlen(cgi_error_memory_response->head);
+   cgi_error_memory_response->content_length =
+      strlen(cgi_error_memory_response->body);
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  cgi_error_memory
+ *
+ * Description :  Called if a CGI function runs out of memory.
+ *                Returns a statically-allocated error response.
+ *
+ * Parameters  :  N/A
+ *
+ * Returns     :  http_response data structure for output.  This is
+ *                statically allocated, for obvious reasons.
+ *
+ *********************************************************************/
+struct http_response *cgi_error_memory(void)
+{
+   /* assert that it's been initialized. */
+   assert(cgi_error_memory_response->head);
+
+   return cgi_error_memory_response;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  cgi_error_no_template
+ *
+ * Description :  Almost-CGI function that is called if a template
+ *                cannot be loaded.  Note this is not a true CGI,
+ *                it takes a template name rather than a map of 
+ *                parameters.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  rsp = http_response data structure for output
+ *          3  :  template_name = Name of template that could not
+ *                                be loaded.
+ *
+ * Returns     :  JB_ERR_OK on success
+ *                JB_ERR_MEMORY on out-of-memory error.  
+ *
+ *********************************************************************/
+jb_err cgi_error_no_template(struct client_state *csp,
+                             struct http_response *rsp,
+                             const char *template_name)
+{
+   static const char status[] =
+      "500 Internal Privoxy Error";
+   static const char body_prefix[] =
+      "<html>\r\n"
+      "<head><title>500 Internal Privoxy Error</title></head>\r\n"
+      "<body>\r\n"
+      "<h1>500 Internal Privoxy Error</h1>\r\n"
+      "<p>Privoxy encountered an error while processing your request:</p>\r\n"
+      "<p><b>Could not load template file <code>";
+   static const char body_suffix[] =
+      "</code> or one of it's included components.</b></p>\r\n"
+      "<p>Please contact your proxy administrator.</p>\r\n"
+      "<p>If you are the proxy administrator, please put the required file(s)"
+      "in the <code><i>(confdir)</i>/templates</code> directory.  The "
+      "location of the <code><i>(confdir)</i></code> directory "
+      "is specified in the main Privoxy <code>config</code> "
+      "file.  (It's typically the Privoxy install directory"
+#ifndef _WIN32
+      ", or <code>/etc/privoxy/</code>"
+#endif /* ndef _WIN32 */
+      ").</p>\r\n"
+      "</body>\r\n"
+      "</html>\r\n";
+
+   assert(csp);
+   assert(rsp);
+   assert(template_name);
+
+   /* Reset rsp, if needed */
+   freez(rsp->status);
+   freez(rsp->head);
+   freez(rsp->body);
+   rsp->content_length = 0;
+   rsp->head_length = 0;
+   rsp->is_static = 0;
+
+   rsp->body = malloc(strlen(body_prefix) + strlen(template_name) + strlen(body_suffix) + 1);
+   if (rsp->body == NULL)
+   {
+      return JB_ERR_MEMORY;
+   }
+   strcpy(rsp->body, body_prefix);
+   strcat(rsp->body, template_name);
+   strcat(rsp->body, body_suffix);
+
+   rsp->status = strdup(status);
+   if (rsp->body == NULL)
+   {
+      return JB_ERR_MEMORY;
+   }
+
+   return JB_ERR_OK;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  cgi_error_unknown
+ *
+ * Description :  Almost-CGI function that is called if an unexpected
+ *                error occurs in the top-level CGI dispatcher.
+ *                In this context, "unexpected" means "anything other
+ *                than JB_ERR_MEMORY or JB_ERR_CGI_PARAMS" - CGIs are
+ *                expected to handle all other errors internally,
+ *                since they can give more relavent error messages
+ *                that way.
+ *
+ *                Note this is not a true CGI, it takes an error
+ *                code rather than a map of parameters.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  rsp = http_response data structure for output
+ *          3  :  error_to_report = Error code to report.
+ *
+ * Returns     :  JB_ERR_OK on success
+ *                JB_ERR_MEMORY on out-of-memory error.  
+ *
+ *********************************************************************/
+jb_err cgi_error_unknown(struct client_state *csp,
+                         struct http_response *rsp,
+                         jb_err error_to_report)
+{
+   static const char status[] =
+      "500 Internal Privoxy Error";
+   static const char body_prefix[] =
+      "<html>\r\n"
+      "<head><title>500 Internal Privoxy Error</title></head>\r\n"
+      "<body>\r\n"
+      "<h1>500 Internal Privoxy Error</h1>\r\n"
+      "<p>Privoxy encountered an error while processing your request:</p>\r\n"
+      "<p><b>Unexpected internal error: ";
+   static const char body_suffix[] =
+      "</b></p>\r\n"
+      "<p>Please "
+      "<a href=\"http://sourceforge.net/tracker/?group_id=11118&atid=111118\">"
+      "file a bug report</a>.</p>\r\n"
+      "</body>\r\n"
+      "</html>\r\n";
+   char errnumbuf[30];
+   assert(csp);
+   assert(rsp);
+
+   /* Reset rsp, if needed */
+   freez(rsp->status);
+   freez(rsp->head);
+   freez(rsp->body);
+   rsp->content_length = 0;
+   rsp->head_length = 0;
+   rsp->is_static = 0;
+
+   sprintf(errnumbuf, "%d", error_to_report);
+
+   rsp->body = malloc(strlen(body_prefix) + strlen(errnumbuf) + strlen(body_suffix) + 1);
+   if (rsp->body == NULL)
+   {
+      return JB_ERR_MEMORY;
+   }
+   strcpy(rsp->body, body_prefix);
+   strcat(rsp->body, errnumbuf);
+   strcat(rsp->body, body_suffix);
+
+   rsp->status = strdup(status);
+   if (rsp->body == NULL)
+   {
+      return JB_ERR_MEMORY;
+   }
+
+   return JB_ERR_OK;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  cgi_error_bad_param
+ *
+ * Description :  CGI function that is called if the parameters
+ *                (query string) for a CGI were wrong.
+ *               
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  rsp = http_response data structure for output
+ *
+ * CGI Parameters : none
+ *
+ * Returns     :  JB_ERR_OK on success
+ *                JB_ERR_MEMORY on out-of-memory error.  
+ *
+ *********************************************************************/
+jb_err cgi_error_bad_param(struct client_state *csp,
+                           struct http_response *rsp)
+{
+   struct map *exports;
+
+   assert(csp);
+   assert(rsp);
+
+   if (NULL == (exports = default_exports(csp, NULL)))
+   {
+      return JB_ERR_MEMORY;
+   }
+
+   return template_fill_for_cgi(csp, "cgi-error-bad-param", exports, rsp);
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  add_help_link
+ *
+ * Description :  Produce a copy of the string given as item,
+ *                embedded in an HTML link to its corresponding
+ *                section (item name in uppercase) in the actions
+ *                chapter of the user manual, (whose URL is given in
+ *                the config and defaults to our web site).
+ *
+ *                FIXME: I currently only work for actions, and would
+ *                       like to be generalized for other topics.
+ *
+ * Parameters  :  
+ *          1  :  item = item (will NOT be free()d.) 
+ *                       It is assumed to be HTML-safe.
+ *          2  :  config = The current configuration.
+ *
+ * Returns     :  String with item embedded in link, or NULL on
+ *                out-of-memory
+ *
+ *********************************************************************/
+char *add_help_link(const char *item,
+                    struct configuration_spec *config)
+{
+   char *result;
+
+   if (!item) return NULL;
+
+   result = strdup("<a href=\"");
+   string_append(&result, config->usermanual);
+   string_append(&result, ACTIONS_HELP_PREFIX);
+   string_join  (&result, string_toupper(item));
+   string_append(&result, "\">");
+   string_append(&result, item);
+   string_append(&result, "</a> ");
+
+   return result;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  get_http_time
+ *
+ * Description :  Get the time in a format suitable for use in a
+ *                HTTP header - e.g.:
+ *                "Sun, 06 Nov 1994 08:49:37 GMT"
+ *
+ * Parameters  :  
+ *          1  :  time_offset = Time returned will be current time
+ *                              plus this number of seconds.
+ *          2  :  buf = Destination for result.  Must be long enough
+ *                      to hold 29 characters plus a trailing zero.
+ *
+ * Returns     :  N/A
+ *
+ *********************************************************************/
+void get_http_time(int time_offset, char *buf)
+{
+   static const char day_names[7][4] =
+      { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
+   static const char month_names[12][4] =
+      { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+        "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
+
+   struct tm *t;
+   time_t current_time;
+
+   assert(buf);
+
+   time(&current_time); /* get current time */
+
+   current_time += time_offset;
+
+   /* get and save the gmt */
+   {
+#ifdef HAVE_GMTIME_R
+      struct tm dummy;
+      t = gmtime_r(&current_time, &dummy);
+#else
+      t = gmtime(&current_time);
+#endif
+   }
+
+   /* Format: "Sun, 06 Nov 1994 08:49:37 GMT" */
+   snprintf(buf, 30,
+      "%s, %02d %s %4d %02d:%02d:%02d GMT",
+      day_names[t->tm_wday],
+      t->tm_mday,
+      month_names[t->tm_mon],
+      t->tm_year + 1900,
+      t->tm_hour,
+      t->tm_min,
+      t->tm_sec
+      );
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  finish_http_response
+ *
+ * Description :  Fill in the missing headers in an http response,
+ *                and flatten the headers to an http head.
+ *
+ * Parameters  :
+ *          1  :  rsp = pointer to http_response to be processed
+ *
+ * Returns     :  A http_response, usually the rsp parameter.
+ *                On error, free()s rsp and returns cgi_error_memory()
+ *
+ *********************************************************************/
+struct http_response *finish_http_response(struct http_response *rsp)
+{
+   char buf[BUFFER_SIZE];
+   jb_err err;
+
+   /* Special case - do NOT change this statically allocated response,
+    * which is ready for output anyway.
+    */
+   if (rsp == cgi_error_memory_response)
+   {
+      return rsp;
+   }
+
+   /* 
+    * Fill in the HTTP Status
+    */
+   sprintf(buf, "HTTP/1.0 %s", rsp->status ? rsp->status : "200 OK");
+   err = enlist_first(rsp->headers, buf);
+
+   /* 
+    * Set the Content-Length
+    */
+   if (rsp->content_length == 0)
+   {
+      rsp->content_length = rsp->body ? strlen(rsp->body) : 0;
+   }
+   if (!err)
+   {
+      sprintf(buf, "Content-Length: %d", (int)rsp->content_length);
+      err = enlist(rsp->headers, buf);
+   }
+
+   /* 
+    * Fill in the default headers:
+    *
+    * Content-Type: default to text/html if not already specified.
+    * Date: set to current date/time.
+    * Last-Modified: set to date/time the page was last changed.
+    * Expires: set to date/time page next needs reloading.
+    * Cache-Control: set to "no-cache" if applicable.
+    * 
+    * See http://www.w3.org/Protocols/rfc2068/rfc2068
+    */
+   if (!err) err = enlist_unique(rsp->headers, "Content-Type: text/html", 13);
+
+   if (rsp->is_static)
+   {
+      /*
+       * Set Expires to about 10 min into the future so it'll get reloaded
+       * occasionally, e.g. if Privoxy gets upgraded.
+       */
+
+      if (!err)
+      {
+         get_http_time(0, buf);
+         err = enlist_unique_header(rsp->headers, "Date", buf);
+      }
+
+      /* Some date in the past. */
+      if (!err) err = enlist_unique_header(rsp->headers, "Last-Modified", "Sat, 17 Jun 2000 12:00:00 GMT");
+
+      if (!err)
+      {
+         get_http_time(10 * 60, buf); /* 10 * 60sec = 10 minutes */
+         err = enlist_unique_header(rsp->headers, "Expires", buf);
+      }
+   }
+   else
+   {
+      /*
+       * Compliant browsers should not cache this due to the "Cache-Control"
+       * setting.  However, to be certain, we also set both "Last-Modified"
+       * and "Expires" to the current time.
+       */
+      if (!err) err = enlist_unique_header(rsp->headers, "Cache-Control", "no-cache");
+
+      get_http_time(0, buf);
+      if (!err) err = enlist_unique_header(rsp->headers, "Date", buf);
+      if (!err) err = enlist_unique_header(rsp->headers, "Last-Modified", buf);
+      if (!err) err = enlist_unique_header(rsp->headers, "Expires", buf);
+   }
+
+
+   /* 
+    * Write the head
+    */
+   if (err || (NULL == (rsp->head = list_to_text(rsp->headers))))
+   {
+      free_http_response(rsp);
+      return cgi_error_memory();
+   }
+   rsp->head_length = strlen(rsp->head);
+
+   return rsp;
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  alloc_http_response
+ *
+ * Description :  Allocates a new http_response structure.
+ *
+ * Parameters  :  N/A
+ *
+ * Returns     :  pointer to a new http_response, or NULL.
+ *
+ *********************************************************************/
+struct http_response *alloc_http_response(void)
+{
+   return (struct http_response *) zalloc(sizeof(struct http_response));
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  free_http_response
+ *
+ * Description :  Free the memory occupied by an http_response
+ *                and its depandant structures.
+ *
+ * Parameters  :
+ *          1  :  rsp = pointer to http_response to be freed
+ *
+ * Returns     :  N/A
+ *
+ *********************************************************************/
+void free_http_response(struct http_response *rsp)
+{
+   /*
+    * Must special case cgi_error_memory_response, which is never freed.
+    */
+   if (rsp && (rsp != cgi_error_memory_response))
+   {
+      freez(rsp->status);
+      freez(rsp->head);
+      freez(rsp->body);
+      destroy_list(rsp->headers);
+      free(rsp);
+   }
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  template_load
+ *
+ * Description :  CGI support function that loads a given HTML
+ *                template from the confdir, ignoring comment
+ *                lines and following #include statements up to
+ *                a depth of 1.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  template_ptr = Destination for pointer to loaded
+ *                               template text.
+ *          3  :  templatename = name of the HTML template to be used
+ *          4  :  recursive = Flag set if this function calls itself
+ *                            following an #include statament
+ *
+ * Returns     :  JB_ERR_OK on success
+ *                JB_ERR_MEMORY on out-of-memory error.  
+ *                JB_ERR_FILE if the template file cannot be read
+ *
+ *********************************************************************/
+jb_err template_load(struct client_state *csp, char **template_ptr, 
+                     const char *templatename, int recursive)
+{
+   jb_err err;
+   char *templates_dir_path;
+   char *full_path;
+   char *file_buffer;
+   char *included_module;
+   const char *p;
+   FILE *fp;
+   char buf[BUFFER_SIZE];
+
+   assert(csp);
+   assert(template_ptr);
+   assert(templatename);
+
+   *template_ptr = NULL;
+
+   /* Validate template name.  Paranoia. */
+   for (p = templatename; *p != 0; p++)
+   {
+      if ( ((*p < 'a') || (*p > 'z'))
+        && ((*p < 'A') || (*p > 'Z'))
+        && ((*p < '0') || (*p > '9'))
+        && (*p != '-')
+        && (*p != '.'))
+      {
+         /* Illegal character */
+         return JB_ERR_FILE;
+      }
+   }
+
+   /* Generate full path */
+
+   templates_dir_path = make_path(csp->config->confdir, "templates");
+   if (templates_dir_path == NULL)
+   {
+      return JB_ERR_MEMORY;
+   }
+
+   full_path = make_path(templates_dir_path, templatename);
+   free(templates_dir_path);
+   if (full_path == NULL)
+   {
+      return JB_ERR_MEMORY;
+   }
+
+   /* Allocate buffer */
+
+   file_buffer = strdup("");
+   if (file_buffer == NULL)
+   {
+      free(full_path);
+      return JB_ERR_MEMORY;
+   }
+
+   /* Open template file */
+
+   if (NULL == (fp = fopen(full_path, "r")))
+   {
+      log_error(LOG_LEVEL_ERROR, "Cannot open template file %s: %E", full_path);
+      free(full_path);
+      free(file_buffer);
+      return JB_ERR_FILE;
+   }
+   free(full_path);
+
+   /* 
+    * Read the file, ignoring comments, and honoring #include
+    * statements, unless we're already called recursively.
+    *
+    * FIXME: The comment handling could break with lines >BUFFER_SIZE long.
+    *        This is unlikely in practise.
+    */
+   while (fgets(buf, BUFFER_SIZE, fp))
+   {
+      if (!recursive && !strncmp(buf, "#include ", 9))
+      {
+         if (JB_ERR_OK != (err = template_load(csp, &included_module, chomp(buf + 9), 1)))
+         {
+            free(file_buffer);
+            fclose(fp);
+            return err;
+         }
+
+         if (string_join(&file_buffer, included_module))
+         {
+            fclose(fp);
+            return JB_ERR_MEMORY;
+         }
+
+         continue;
+      }
+
+      /* skip lines starting with '#' */
+      if (*buf == '#')
+      {
+         continue;
+      }
+
+      if (string_append(&file_buffer, buf))
+      {
+         fclose(fp);
+         return JB_ERR_MEMORY;
+      }
+   }
+   fclose(fp);
+
+   *template_ptr = file_buffer;
+
+   return JB_ERR_OK;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  template_fill
+ *
+ * Description :  CGI support function that fills in a pre-loaded
+ *                HTML template by replacing @name@ with value using
+ *                pcrs, for each item in the output map.
+ *
+ *                Note that a leading '$' charachter in the export map's
+ *                values will be stripped and toggle on backreference
+ *                interpretation.
+ *
+ * Parameters  :
+ *          1  :  template_ptr = IN: Template to be filled out.
+ *                                   Will be free()d.
+ *                               OUT: Filled out template.
+ *                                    Caller must free().
+ *          2  :  exports = map with fill in symbol -> name pairs
+ *
+ * Returns     :  JB_ERR_OK on success
+ *                JB_ERR_MEMORY on out-of-memory error
+ *
+ *********************************************************************/
+jb_err template_fill(char **template_ptr, const struct map *exports)
+{
+   struct map_entry *m;
+   pcrs_job *job;
+   char buf[BUFFER_SIZE];
+   char *tmp_out_buffer;
+   char *file_buffer;
+   size_t  size;
+   int error;
+   const char *flags;
+
+   assert(template_ptr);
+   assert(*template_ptr);
+   assert(exports);
+
+   file_buffer = *template_ptr;
+   size = strlen(file_buffer) + 1;
+
+   /* 
+    * Assemble pcrs joblist from exports map
+    */
+   for (m = exports->first; m != NULL; m = m->next)
+   {
+      if (*m->name == '$')
+      {
+         /*
+          * First character of name is '$', so remove this flag
+          * character and allow backreferences ($1 etc) in the
+          * "replace with" text.
+          */
+         snprintf(buf, BUFFER_SIZE, "%s", m->name + 1);
+         flags = "sigU";
+      }
+      else
+      {
+         /*
+          * Treat the "replace with" text as a literal string - 
+          * no quoting needed, no backreferences allowed.
+          * ("Trivial" ['T'] flag).
+          */
+         flags = "sigTU";
+
+         /* Enclose name in @@ */
+         snprintf(buf, BUFFER_SIZE, "@%s@", m->name);
+      }
+
+
+      log_error(LOG_LEVEL_CGI, "Substituting: s/%s/%s/%s", buf, m->value, flags);
+
+      /* Make and run job. */
+      job = pcrs_compile(buf, m->value, flags,  &error);
+      if (job == NULL) 
+      {
+         if (error == PCRS_ERR_NOMEM)
+         {
+            free(file_buffer);
+            *template_ptr = NULL;
+            return JB_ERR_MEMORY;
+         }
+         else
+         {
+            log_error(LOG_LEVEL_ERROR, "Error compiling template fill job %s: %d", m->name, error);
+            /* Hope it wasn't important and silently ignore the invalid job */
+         }
+      }
+      else
+      {
+         pcrs_execute(job, file_buffer, size, &tmp_out_buffer, &size);
+         free(file_buffer);
+         pcrs_free_job(job);
+         if (NULL == tmp_out_buffer)
+         {
+            *template_ptr = NULL;
+            return JB_ERR_MEMORY;
+         }
+         file_buffer = tmp_out_buffer;
+      }
+   }
+
+   /*
+    * Return
+    */
+   *template_ptr = file_buffer;
+   return JB_ERR_OK;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  template_fill_for_cgi
+ *
+ * Description :  CGI support function that loads a HTML template
+ *                and fills it in.  Handles file-not-found errors
+ *                by sending a HTML error message.  For convenience,
+ *                this function also frees the passed "exports" map.
+ *
+ * Parameters  :
+ *          1  :  csp = Client state
+ *          2  :  templatename = name of the HTML template to be used
+ *          3  :  exports = map with fill in symbol -> name pairs.
+ *                          Will be freed by this function.
+ *          4  :  rsp = Response structure to fill in.
+ *
+ * Returns     :  JB_ERR_OK on success
+ *                JB_ERR_MEMORY on out-of-memory error
+ *
+ *********************************************************************/
+jb_err template_fill_for_cgi(struct client_state *csp,
+                             const char *templatename,
+                             struct map *exports,
+                             struct http_response *rsp)
+{
+   jb_err err;
+   
+   assert(csp);
+   assert(templatename);
+   assert(exports);
+   assert(rsp);
+
+   err = template_load(csp, &rsp->body, templatename, 0);
+   if (err == JB_ERR_FILE)
+   {
+      free_map(exports);
+      return cgi_error_no_template(csp, rsp, templatename);
+   }
+   else if (err)
+   {
+      free_map(exports);
+      return err; /* JB_ERR_MEMORY */
+   }
+   err = template_fill(&rsp->body, exports);
+   free_map(exports);
+   return err;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  default_exports
+ *
+ * Description :  returns a struct map list that contains exports
+ *                which are common to all CGI functions.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  caller = name of CGI who calls us and which should
+ *                         be excluded from the generated menu. May be
+ *                         NULL.
+ * Returns     :  NULL if no memory, else a new map.  Caller frees.
+ *
+ *********************************************************************/
+struct map *default_exports(const struct client_state *csp, const char *caller)
+{
+   char buf[20];
+   jb_err err;
+   struct map * exports;
+   int local_help_exists = 0;
+
+   assert(csp);
+
+   exports = new_map();
+   if (exports == NULL)
+   {
+      return NULL;
+   }
+
+   err = map(exports, "version", 1, html_encode(VERSION), 0);
+   if (!err) err = map(exports, "my-ip-address", 1, html_encode(csp->my_ip_addr_str ? csp->my_ip_addr_str : "unknown"), 0);
+   if (!err) err = map(exports, "my-hostname",   1, html_encode(csp->my_hostname ? csp->my_hostname : "unknown"), 0);
+   if (!err) err = map(exports, "homepage",      1, html_encode(HOME_PAGE_URL), 0);
+   if (!err) err = map(exports, "default-cgi",   1, html_encode(CGI_PREFIX), 0);
+   if (!err) err = map(exports, "menu",          1, make_menu(caller), 0);
+   if (!err) err = map(exports, "code-status",   1, CODE_STATUS, 1);
+   if (!err) err = map(exports, "user-manual",   1, csp->config->usermanual ,1);
+   if (!err) err = map(exports, "actions-help-prefix", 1, ACTIONS_HELP_PREFIX ,1);
+   if (!err) err = map_conditional(exports, "enabled-display", g_bToggleIJB);
+
+   snprintf(buf, 20, "%d", csp->config->hport);
+   if (!err) err = map(exports, "my-port", 1, buf, 1);
+
+   if(!strcmp(CODE_STATUS, "stable"))
+   {
+      if (!err) err = map_block_killer(exports, "unstable");
+   }
+
+   if (csp->config->admin_address != NULL)
+   {
+      if (!err) err = map(exports, "admin-address", 1, html_encode(csp->config->admin_address), 0);
+      local_help_exists = 1;
+   }
+   else
+   {
+      if (!err) err = map_block_killer(exports, "have-adminaddr-info");
+   }
+
+   if (csp->config->proxy_info_url != NULL)
+   {
+      if (!err) err = map(exports, "proxy-info-url", 1, html_encode(csp->config->proxy_info_url), 0);
+      local_help_exists = 1;
+   }
+   else
+   {
+      if (!err) err = map_block_killer(exports, "have-proxy-info");
+   }
+
+   if (local_help_exists == 0)
+   {
+      if (!err) err = map_block_killer(exports, "have-help-info");
+   }
+
+   if (err)
+   {
+      free_map(exports);
+      return NULL;
+   }
+
+   return exports;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  map_block_killer
+ *
+ * Description :  Convenience function.
+ *                Adds a "killer" for the conditional HTML-template
+ *                block <name>, i.e. a substitution of the regex
+ *                "if-<name>-start.*if-<name>-end" to the given
+ *                export list.
+ *
+ * Parameters  :  
+ *          1  :  exports = map to extend
+ *          2  :  name = name of conditional block
+ *
+ * Returns     :  JB_ERR_OK on success
+ *                JB_ERR_MEMORY on out-of-memory error.  
+ *
+ *********************************************************************/
+jb_err map_block_killer(struct map *exports, const char *name)
+{
+   char buf[1000]; /* Will do, since the names are hardwired */
+
+   assert(exports);
+   assert(name);
+   assert(strlen(name) < 490);
+
+   snprintf(buf, 1000, "if-%s-start.*if-%s-end", name, name);
+   return map(exports, buf, 1, "", 1);
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  map_block_keep
+ *
+ * Description :  Convenience function.  Removes the markers used
+ *                by map-block-killer, to save a few bytes.
+ *                i.e. removes "@if-<name>-start@" and "@if-<name>-end@"
+ *
+ * Parameters  :  
+ *          1  :  exports = map to extend
+ *          2  :  name = name of conditional block
+ *
+ * Returns     :  JB_ERR_OK on success
+ *                JB_ERR_MEMORY on out-of-memory error.  
+ *
+ *********************************************************************/
+jb_err map_block_keep(struct map *exports, const char *name)
+{
+   jb_err err;
+   char buf[500]; /* Will do, since the names are hardwired */
+
+   assert(exports);
+   assert(name);
+   assert(strlen(name) < 490);
+
+   snprintf(buf, 500, "if-%s-start", name);
+   err = map(exports, buf, 1, "", 1);
+
+   if (err)
+   {
+      return err;
+   }
+
+   snprintf(buf, 500, "if-%s-end", name);
+   return map(exports, buf, 1, "", 1);
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  map_conditional
+ *
+ * Description :  Convenience function.
+ *                Adds an "if-then-else" for the conditional HTML-template
+ *                block <name>, i.e. a substitution of the form:
+ *                @if-<name>-then@
+ *                   True text
+ *                @else-not-<name>@
+ *                   False text
+ *                @endif-<name>@
+ *
+ *                The control structure and one of the alternatives
+ *                will be hidden.
+ *
+ * Parameters  :  
+ *          1  :  exports = map to extend
+ *          2  :  name = name of conditional block
+ *          3  :  choose_first = nonzero for first, zero for second.
+ *
+ * Returns     :  JB_ERR_OK on success
+ *                JB_ERR_MEMORY on out-of-memory error.  
+ *
+ *********************************************************************/
+jb_err map_conditional(struct map *exports, const char *name, int choose_first)
+{
+   char buf[1000]; /* Will do, since the names are hardwired */
+   jb_err err;
+
+   assert(exports);
+   assert(name);
+   assert(strlen(name) < 480);
+
+   snprintf(buf, 1000, (choose_first
+      ? "else-not-%s@.*@endif-%s"
+      : "if-%s-then@.*@else-not-%s"),
+      name, name);
+
+   err = map(exports, buf, 1, "", 1);
+   if (err)
+   {
+      return err;
+   }
+
+   snprintf(buf, 1000, (choose_first ? "if-%s-then" : "endif-%s"), name);
+   return map(exports, buf, 1, "", 1);
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  make_menu
+ *
+ * Description :  Returns an HTML-formatted menu of the available 
+ *                unhidden CGIs, excluding the one given in <self>
+ *
+ * Parameters  :  self = name of CGI to leave out, can be NULL for
+ *                complete listing.
+ *
+ * Returns     :  menu string, or NULL on out-of-memory error.
+ *
+ *********************************************************************/
+char *make_menu(const char *self)
+{
+   const struct cgi_dispatcher *d;
+   char *result = strdup("");
+
+   if (self == NULL)
+   {
+      self = "NO-SUCH-CGI!";
+   }
+
+   /* List available unhidden CGI's and export as "other-cgis" */
+   for (d = cgi_dispatchers; d->name; d++)
+   {
+      if (d->description && strcmp(d->name, self))
+      {
+         string_append(&result, "<li><a href=\"" CGI_PREFIX);
+         string_append(&result, d->name);
+         string_append(&result, "\">");
+         string_append(&result, d->description);
+         string_append(&result, "</a></li>");
+      }
+   }
+
+   return result;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  dump_map
+ *
+ * Description :  HTML-dump a map for debugging (as table)
+ *
+ * Parameters  :
+ *          1  :  the_map = map to dump
+ *
+ * Returns     :  string with HTML
+ *
+ *********************************************************************/
+char *dump_map(const struct map *the_map)
+{
+   struct map_entry *cur_entry;
+   char *ret = strdup("");
+
+   string_append(&ret, "<table>\n");
+
+   for (cur_entry = the_map->first;
+        (cur_entry != NULL) && (ret != NULL);
+        cur_entry = cur_entry->next)
+   {
+      string_append(&ret, "<tr><td><b>");
+      string_join  (&ret, html_encode(cur_entry->name));
+      string_append(&ret, "</b></td><td>");
+      string_join  (&ret, html_encode(cur_entry->value));
+      string_append(&ret, "</td></tr>\n");
+   }
+
+   string_append(&ret, "</table>\n");
+   return ret;
+}
+
+
+/*
+  Local Variables:
+  tab-width: 3
+  end:
+*/
diff --git a/src/cgi.h b/src/cgi.h
new file mode 100644 (file)
index 0000000..813d1e8
--- /dev/null
+++ b/src/cgi.h
@@ -0,0 +1,257 @@
+#ifndef CGI_H_INCLUDED
+#define CGI_H_INCLUDED
+#define CGI_H_VERSION "$Id: cgi.h,v 1.29 2002/05/19 11:33:21 jongfoster Exp $"
+/*********************************************************************
+ *
+ * File        :  $Source: /cvsroot/ijbswa/current/cgi.h,v $
+ *
+ * Purpose     :  Declares functions to intercept request, generate
+ *                html or gif answers, and to compose HTTP resonses.
+ *                
+ *                Functions declared include:
+ * 
+ *
+ * Copyright   :  Written by and Copyright (C) 2001 the SourceForge
+ *                Privoxy team. http://www.privoxy.org/
+ *
+ *                Based on the Internet Junkbuster originally written
+ *                by and Copyright (C) 1997 Anonymous Coders and 
+ *                Junkbusters Corporation.  http://www.junkbusters.com
+ *
+ *                This program is free software; you can redistribute it 
+ *                and/or modify it under the terms of the GNU General
+ *                Public License as published by the Free Software
+ *                Foundation; either version 2 of the License, or (at
+ *                your option) any later version.
+ *
+ *                This program is distributed in the hope that it will
+ *                be useful, but WITHOUT ANY WARRANTY; without even the
+ *                implied warranty of MERCHANTABILITY or FITNESS FOR A
+ *                PARTICULAR PURPOSE.  See the GNU General Public
+ *                License for more details.
+ *
+ *                The GNU General Public License should be included with
+ *                this file.  If not, you can view it at
+ *                http://www.gnu.org/copyleft/gpl.html
+ *                or write to the Free Software Foundation, Inc., 59
+ *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * Revisions   :
+ *    $Log: cgi.h,v $
+ *    Revision 1.29  2002/05/19 11:33:21  jongfoster
+ *    If a CGI error was not handled, and propogated back to
+ *    dispatch_known_cgi(), then it was assumed to be "out of memory".
+ *    This gave a very misleading error message.
+ *
+ *    Now other errors will cause a simple message giving the error
+ *    number and asking the user to report a bug.
+ *
+ *    Bug report:
+ *    http://sourceforge.net/tracker/index.php?func=detail
+ *    &aid=557905&group_id=11118&atid=111118
+ *
+ *    Revision 1.28  2002/04/26 12:54:03  oes
+ *    New function add_help_link
+ *
+ *    Revision 1.27  2002/04/24 02:16:51  oes
+ *    Moved get_char_param, get_string_param and get_number_param here from cgiedit.c
+ *
+ *    Revision 1.26  2002/04/10 13:38:35  oes
+ *    load_template signature changed
+ *
+ *    Revision 1.25  2002/04/08 20:50:25  swa
+ *    fixed JB spelling
+ *
+ *    Revision 1.24  2002/03/26 22:29:54  swa
+ *    we have a new homepage!
+ *
+ *    Revision 1.23  2002/03/24 16:18:15  jongfoster
+ *    Removing old logo
+ *
+ *    Revision 1.22  2002/03/24 13:25:43  swa
+ *    name change related issues
+ *
+ *    Revision 1.21  2002/03/07 03:48:38  oes
+ *     - Changed built-in images from GIF to PNG
+ *       (with regard to Unisys patent issue)
+ *     - Added a 4x4 pattern PNG which is less intrusive
+ *       than the logo but also clearly marks the deleted banners
+ *
+ *    Revision 1.20  2002/03/04 17:53:22  oes
+ *    Fixed compiled warning
+ *
+ *    Revision 1.19  2002/01/21 00:33:52  jongfoster
+ *    Adding map_block_keep() to save a few bytes in the edit-actions-list HTML.
+ *
+ *    Revision 1.18  2001/11/16 00:46:31  jongfoster
+ *    Fixing compiler warnings
+ *
+ *    Revision 1.17  2001/10/23 21:48:19  jongfoster
+ *    Cleaning up error handling in CGI functions - they now send back
+ *    a HTML error page and should never cause a FATAL error.  (Fixes one
+ *    potential source of "denial of service" attacks).
+ *
+ *    CGI actions file editor that works and is actually useful.
+ *
+ *    Ability to toggle Junkbuster remotely using a CGI call.
+ *
+ *    You can turn off both the above features in the main configuration
+ *    file, e.g. if you are running a multi-user proxy.
+ *
+ *    Revision 1.16  2001/09/16 17:08:54  jongfoster
+ *    Moving simple CGI functions from cgi.c to new file cgisimple.c
+ *
+ *    Revision 1.15  2001/09/16 15:02:35  jongfoster
+ *    Adding i.j.b/robots.txt.
+ *    Inlining add_stats() since it's only ever called from one place.
+ *
+ *    Revision 1.14  2001/09/16 11:38:02  jongfoster
+ *    Splitting fill_template() into 2 functions:
+ *    template_load() loads the file
+ *    template_fill() performs the PCRS regexps.
+ *    This is because the CGI edit interface has a "table row"
+ *    template which is used many times in the page - this
+ *    change means it's only loaded from disk once.
+ *
+ *    Revision 1.13  2001/09/16 11:00:10  jongfoster
+ *    New function alloc_http_response, for symmetry with free_http_response
+ *
+ *    Revision 1.12  2001/09/13 23:31:25  jongfoster
+ *    Moving image data to cgi.c rather than cgi.h.
+ *
+ *    Revision 1.11  2001/08/05 16:06:20  jongfoster
+ *    Modifiying "struct map" so that there are now separate header and
+ *    "map_entry" structures.  This means that functions which modify a
+ *    map no longer need to return a pointer to the modified map.
+ *    Also, it no longer reverses the order of the entries (which may be
+ *    important with some advanced template substitutions).
+ *
+ *    Revision 1.10  2001/08/01 21:19:22  jongfoster
+ *    Moving file version information to a separate CGI page.
+ *
+ *    Revision 1.9  2001/08/01 00:17:54  jongfoster
+ *    Adding prototype for map_conditional
+ *
+ *    Revision 1.8  2001/07/30 22:08:36  jongfoster
+ *    Tidying up #defines:
+ *    - All feature #defines are now of the form FEATURE_xxx
+ *    - Permanently turned off WIN_GUI_EDIT
+ *    - Permanently turned on WEBDAV and SPLIT_PROXY_ARGS
+ *
+ *    Revision 1.7  2001/07/29 18:43:08  jongfoster
+ *    Changing #ifdef _FILENAME_H to FILENAME_H_INCLUDED, to conform to
+ *    ANSI C rules.
+ *
+ *    Revision 1.6  2001/06/29 21:45:41  oes
+ *    Indentation, CRLF->LF, Tab-> Space
+ *
+ *    Revision 1.5  2001/06/29 13:22:44  oes
+ *    - Cleaned up
+ *    - Added new functions: default_exports(), make_menu(),
+ *      error_response() etc, ranamed others and changed
+ *      param and return types.
+ *    - Removed HTTP/HTML snipplets
+ *    - Removed logentry from cancelled commit
+ *
+ *    Revision 1.4  2001/06/09 10:50:58  jongfoster
+ *    Changing "show URL info" handler to new style.
+ *    Adding "extern" to some function prototypes.
+ *
+ *    Revision 1.3  2001/06/03 19:12:16  oes
+ *    introduced new cgi handling
+ *
+ *    No revisions before 1.3
+ *
+ **********************************************************************/
+\f
+
+#include "project.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Main dispatch function
+ */
+extern struct http_response *dispatch_cgi(struct client_state *csp);
+
+/* Not exactly a CGI */
+extern struct http_response * error_response(struct client_state *csp,
+                                             const char *templatename,
+                                             int err);
+
+/*
+ * CGI support functions
+ */
+extern struct http_response * alloc_http_response(void);
+extern void free_http_response(struct http_response *rsp);
+
+extern struct http_response *finish_http_response(struct http_response *rsp);
+
+extern struct map * default_exports(const struct client_state *csp, const char *caller);
+
+extern jb_err map_block_killer (struct map *exports, const char *name);
+extern jb_err map_block_keep   (struct map *exports, const char *name);
+extern jb_err map_conditional  (struct map *exports, const char *name, int choose_first);
+
+extern jb_err template_load(struct client_state *csp, char ** template_ptr, 
+                            const char *templatename, int recursive);
+extern jb_err template_fill(char ** template_ptr, const struct map *exports);
+extern jb_err template_fill_for_cgi(struct client_state *csp,
+                                    const char *templatename,
+                                    struct map *exports,
+                                    struct http_response *rsp);
+
+extern void cgi_init_error_messages(void);
+extern struct http_response *cgi_error_memory(void);
+extern jb_err cgi_error_no_template(struct client_state *csp,
+                                    struct http_response *rsp,
+                                    const char *template_name);
+extern jb_err cgi_error_bad_param(struct client_state *csp,
+                                  struct http_response *rsp);
+jb_err cgi_error_unknown(struct client_state *csp,
+                         struct http_response *rsp,
+                         jb_err error_to_report);
+
+extern jb_err get_number_param(struct client_state *csp,
+                               const struct map *parameters,
+                               char *name,
+                               unsigned *pvalue);
+extern jb_err get_string_param(const struct map *parameters,
+                               const char *param_name,
+                               const char **pparam);
+extern char   get_char_param(const struct map *parameters,
+                             const char *param_name);
+
+/*
+ * Text generators
+ */
+extern void get_http_time(int time_offset, char * buf);
+extern char *add_help_link(const char *item, struct configuration_spec *config);
+extern char *make_menu(const char *self);
+extern char *dump_map(const struct map *the_map);
+
+/*
+ * Ad replacement images
+ */
+extern const char image_pattern_data[];
+extern const size_t  image_pattern_length;
+extern const char image_blank_data[];
+extern const size_t  image_blank_length;
+
+/* Revision control strings from this header and associated .c file */
+extern const char cgi_rcs[];
+extern const char cgi_h_rcs[];
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* ndef CGI_H_INCLUDED */
+
+/*
+  Local Variables:
+  tab-width: 3
+  end:
+*/
diff --git a/src/cgiedit.c b/src/cgiedit.c
new file mode 100644 (file)
index 0000000..8630263
--- /dev/null
@@ -0,0 +1,4715 @@
+const char cgiedit_rcs[] = "$Id: cgiedit.c,v 1.41 2002/05/21 19:09:45 oes Exp $";
+/*********************************************************************
+ *
+ * File        :  $Source: /cvsroot/ijbswa/current/cgiedit.c,v $
+ *
+ * Purpose     :  CGI-based actionsfile editor.
+ *
+ *                Functions declared include: cgi_edit_*
+ *
+ *                NOTE: The CGIs in this file use parameter names
+ *                such as "f" and "s" which are really *BAD* choices.
+ *                However, I'm trying to save bytes in the
+ *                edit-actions-list HTML page - the standard actions
+ *                file generated a 550kbyte page, which is ridiculous.
+ *
+ *                Stick to the short names in this file for consistency.
+ *
+ * Copyright   :  Written by and Copyright (C) 2001 the SourceForge
+ *                Privoxy team. http://www.privoxy.org/
+ *
+ *                Based on the Internet Junkbuster originally written
+ *                by and Copyright (C) 1997 Anonymous Coders and
+ *                Junkbusters Corporation.  http://www.junkbusters.com
+ *
+ *                This program is free software; you can redistribute it
+ *                and/or modify it under the terms of the GNU General
+ *                Public License as published by the Free Software
+ *                Foundation; either version 2 of the License, or (at
+ *                your option) any later version.
+ *
+ *                This program is distributed in the hope that it will
+ *                be useful, but WITHOUT ANY WARRANTY; without even the
+ *                implied warranty of MERCHANTABILITY or FITNESS FOR A
+ *                PARTICULAR PURPOSE.  See the GNU General Public
+ *                License for more details.
+ *
+ *                The GNU General Public License should be included with
+ *                this file.  If not, you can view it at
+ *                http://www.gnu.org/copyleft/gpl.html
+ *                or write to the Free Software Foundation, Inc., 59
+ *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * Revisions   :
+ *    $Log: cgiedit.c,v $
+ *    Revision 1.41  2002/05/21 19:09:45  oes
+ *     - Made Add/Edit/Remove URL Submit and Cancel
+ *       buttons jump back to relevant section in eal
+ *     - Bugfix: remove-url-form needs p export
+ *
+ *    Revision 1.40  2002/05/19 11:34:35  jongfoster
+ *    Handling read-only actions files better - report the actual
+ *    error, not "Out of memory"!
+ *
+ *    Bug report:
+ *    http://sourceforge.net/tracker/index.php?func=detail
+ *    &aid=557905&group_id=11118&atid=111118
+ *
+ *    Revision 1.39  2002/05/12 21:39:15  jongfoster
+ *    - Adding Doxygen-style comments to structures and #defines.
+ *    - Correcting function comments
+ *
+ *    Revision 1.38  2002/05/03 23:00:38  jongfoster
+ *    Support for templates for "standard actions" buttons.
+ *    See bug #549871
+ *
+ *    Revision 1.37  2002/04/30 11:14:52  oes
+ *    Made csp the first parameter in *action_to_html
+ *
+ *    Revision 1.36  2002/04/26 21:53:30  jongfoster
+ *    Fixing a memory leak.  (Near, but not caused by, my earlier commit).
+ *
+ *    Revision 1.35  2002/04/26 21:50:02  jongfoster
+ *    Honouring default exports in edit-actions-for-url-filter template.
+ *
+ *    Revision 1.34  2002/04/26 12:54:17  oes
+ *    Adaptions to changes in actions.c
+ *
+ *    Revision 1.33  2002/04/24 02:17:47  oes
+ *     - Moved get_char_param, get_string_param and get_number_param to cgi.c
+ *     - Comments
+ *     - Activated Jon's code for editing multiple AFs
+ *     - cgi_edit_list_actions now provides context-sensitive
+ *       help, looks up all action sets from standard.action and
+ *       makes buttons for them in the catchall section
+ *     - cgi_edit_action_submit now honors a p parameter, looks up
+ *       the corresponding action set, and sets the catchall pattern's
+ *       actions accordingly.
+ *
+ *    Revision 1.32  2002/04/19 16:55:31  jongfoster
+ *    Fixing newline problems.  If we do our own text file newline
+ *    mangling, we don't want the library to do any, so we need to
+ *    open the files in *binary* mode.
+ *
+ *    Revision 1.31  2002/04/18 19:21:08  jongfoster
+ *    Added code to detect "conventional" action files, that start
+ *    with a set of actions for all URLs (the pattern "/").
+ *    These are special-cased in the "edit-actions-list" CGI, so
+ *    that a special UI can be written for them.
+ *
+ *    Revision 1.30  2002/04/10 13:38:35  oes
+ *    load_template signature changed
+ *
+ *    Revision 1.29  2002/04/08 16:59:08  oes
+ *    Fixed comment
+ *
+ *    Revision 1.28  2002/03/27 12:30:29  oes
+ *    Deleted unsused variable
+ *
+ *    Revision 1.27  2002/03/26 23:06:04  jongfoster
+ *    Removing duplicate @ifs on the toggle page
+ *
+ *    Revision 1.26  2002/03/26 22:59:17  jongfoster
+ *    Fixing /toggle to display status consistently.
+ *
+ *    Revision 1.25  2002/03/26 22:29:54  swa
+ *    we have a new homepage!
+ *
+ *    Revision 1.24  2002/03/24 15:23:33  jongfoster
+ *    Name changes
+ *
+ *    Revision 1.23  2002/03/24 13:32:41  swa
+ *    name change related issues
+ *
+ *    Revision 1.22  2002/03/24 13:25:43  swa
+ *    name change related issues
+ *
+ *    Revision 1.21  2002/03/22 18:02:48  jongfoster
+ *    Fixing remote toggle
+ *
+ *    Revision 1.20  2002/03/16 20:28:34  oes
+ *    Added descriptions to the filters so users will know what they select in the cgi editor
+ *
+ *    Revision 1.19  2002/03/16 18:38:14  jongfoster
+ *    Stopping stupid or malicious users from breaking the actions
+ *    file using the web-based editor.
+ *
+ *    Revision 1.18  2002/03/16 14:57:44  jongfoster
+ *    Full support for enabling/disabling modular filters.
+ *
+ *    Revision 1.17  2002/03/16 14:26:42  jongfoster
+ *    First version of modular filters support - READ ONLY!
+ *    Fixing a double-free bug in the out-of-memory handling in map_radio().
+ *
+ *    Revision 1.16  2002/03/07 03:46:17  oes
+ *    Fixed compiler warnings
+ *
+ *    Revision 1.15  2002/03/06 22:54:35  jongfoster
+ *    Automated function-comment nitpicking.
+ *
+ *    Revision 1.14  2002/03/05 00:24:51  jongfoster
+ *    Patch to always edit the current actions file.
+ *
+ *    Revision 1.13  2002/03/04 02:07:59  david__schmidt
+ *    Enable web editing of actions file on OS/2 (it had been broken all this time!)
+ *
+ *    Revision 1.12  2002/03/03 09:18:03  joergs
+ *    Made jumbjuster work on AmigaOS again.
+ *
+ *    Revision 1.11  2002/01/23 01:03:31  jongfoster
+ *    Fixing gcc [CygWin] compiler warnings
+ *
+ *    Revision 1.10  2002/01/23 00:22:59  jongfoster
+ *    Adding new function cgi_edit_actions_section_swap(), to reorder
+ *    the actions file.
+ *
+ *    Adding get_url_spec_param() to get a validated URL pattern.
+ *
+ *    Moving edit_read_line() out of this file and into loaders.c.
+ *
+ *    Adding missing html_encode() to many CGI functions.
+ *
+ *    Moving the functions that #include actionlist.h to the end of the file,
+ *    because the Visual C++ 97 debugger gets extremely confused if you try
+ *    to debug any code that comes after them in the file.
+ *
+ *    Major optimizations in cgi_edit_actions_list() to reduce the size of
+ *    the generated HTML (down 40% from 550k to 304k), with major side-effects
+ *    throughout the editor and templates.  In particular, the length of the
+ *    URLs throughout the editor has been drastically reduced, by cutting
+ *    paramater names down to 1 character and CGI names down to 3-4
+ *    characters, by removing all non-essential CGI paramaters even at the
+ *    expense of having to re-read the actions file for the most trivial
+ *    page, and by using relative rather than absolute URLs.  This means
+ *    that this (typical example):
+ *
+ *    <a href="http://ijbswa.sourceforge.net/config/edit-actions-url-form?
+ *    filename=ijb&amp;ver=1011487572&amp;section=12&amp;pattern=13
+ *    &amp;oldval=www.oesterhelt.org%2Fdeanimate-demo">
+ *
+ *    is now this:
+ *
+ *    <a href="eau?f=ijb&amp;v=1011487572&amp;p=13">
+ *
+ *    Revision 1.9  2002/01/17 20:56:22  jongfoster
+ *    Replacing hard references to the URL of the config interface
+ *    with #defines from project.h
+ *
+ *    Revision 1.8  2001/11/30 23:35:51  jongfoster
+ *    Renaming actionsfile to ijb.action
+ *
+ *    Revision 1.7  2001/11/13 00:28:24  jongfoster
+ *    - Renaming parameters from edit-actions-for-url so that they only
+ *      contain legal JavaScript characters.  If we wanted to write
+ *      JavaScript that worked with Netscape 4, this is nessacery.
+ *      (Note that at the moment the JavaScript doesn't actually work
+ *      with Netscape 4, but now this is purely a template issue, not
+ *      one affecting code).
+ *    - Adding new CGIs for use by non-JavaScript browsers:
+ *        edit-actions-url-form
+ *        edit-actions-add-url-form
+ *        edit-actions-remove-url-form
+ *    - Fixing || bug.
+ *
+ *    Revision 1.6  2001/10/29 03:48:09  david__schmidt
+ *    OS/2 native needed a snprintf() routine.  Added one to miscutil, brackedted
+ *    by and __OS2__ ifdef.
+ *
+ *    Revision 1.5  2001/10/25 03:40:48  david__schmidt
+ *    Change in porting tactics: OS/2's EMX porting layer doesn't allow multiple
+ *    threads to call select() simultaneously.  So, it's time to do a real, live,
+ *    native OS/2 port.  See defines for __EMX__ (the porting layer) vs. __OS2__
+ *    (native). Both versions will work, but using __OS2__ offers multi-threading.
+ *
+ *    Revision 1.4  2001/10/23 21:48:19  jongfoster
+ *    Cleaning up error handling in CGI functions - they now send back
+ *    a HTML error page and should never cause a FATAL error.  (Fixes one
+ *    potential source of "denial of service" attacks).
+ *
+ *    CGI actions file editor that works and is actually useful.
+ *
+ *    Ability to toggle JunkBuster remotely using a CGI call.
+ *
+ *    You can turn off both the above features in the main configuration
+ *    file, e.g. if you are running a multi-user proxy.
+ *
+ *    Revision 1.3  2001/10/14 22:12:49  jongfoster
+ *    New version of CGI-based actionsfile editor.
+ *    Major changes, including:
+ *    - Completely new file parser and file output routines
+ *    - edit-actions CGI renamed edit-actions-for-url
+ *    - All CGIs now need a filename parameter, except for...
+ *    - New CGI edit-actions which doesn't need a filename,
+ *      to allow you to start the editor up.
+ *    - edit-actions-submit now works, and now automatically
+ *      redirects you back to the main edit-actions-list handler.
+ *
+ *    Revision 1.2  2001/09/16 17:05:14  jongfoster
+ *    Removing unused #include showarg.h
+ *
+ *    Revision 1.1  2001/09/16 15:47:37  jongfoster
+ *    First version of CGI-based edit interface.  This is very much a
+ *    work-in-progress, and you can't actually use it to edit anything
+ *    yet.  You must #define FEATURE_CGI_EDIT_ACTIONS for these changes
+ *    to have any effect.
+ *
+ *
+ **********************************************************************/
+\f
+
+#include "config.h"
+
+/*
+ * FIXME: Following includes copied from cgi.c - which are actually needed?
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <ctype.h>
+#include <string.h>
+#include <assert.h>
+#include <sys/stat.h>
+
+#ifdef _WIN32
+#define snprintf _snprintf
+#endif /* def _WIN32 */
+
+#include "project.h"
+#include "cgi.h"
+#include "cgiedit.h"
+#include "cgisimple.h"
+#include "list.h"
+#include "encode.h"
+#include "actions.h"
+#include "miscutil.h"
+#include "errlog.h"
+#include "loaders.h"
+#include "loadcfg.h"
+/* loadcfg.h is for g_bToggleIJB only */
+#include "urlmatch.h"
+
+const char cgiedit_h_rcs[] = CGIEDIT_H_VERSION;
+
+
+#ifdef FEATURE_CGI_EDIT_ACTIONS
+
+/**
+ * A line in an editable_file.
+ */
+struct file_line
+{
+   /** Next entry in the linked list */
+   struct file_line * next;
+   
+   /** The raw data, to write out if this line is unmodified. */
+   char * raw;
+   
+   /** Comments and/or whitespace to put before this line if it's modified
+       and then written out. */
+   char * prefix;
+
+   /** The actual data, as a string.  Line continuation and comment removal
+       are performed on the data read from file before it's stored here, so
+       it will be a single line of data.  */
+   char * unprocessed;
+   
+   /** The type of data on this line.  One of the FILE_LINE_xxx constants. */
+   int type;
+
+   /** The actual data, processed into some sensible data type. */
+   union
+   {
+
+      /** An action specification. */
+      struct action_spec action[1];
+
+      /** A name=value pair. */
+      struct
+      {
+
+         /** The name in the name=value pair. */
+         char * name;
+
+         /** The value in the name=value pair, as a string. */
+         char * svalue;
+
+         /** The value in the name=value pair, as an integer. */
+         int ivalue;
+
+      } setting;
+
+      /* Add more data types here... e.g.
+
+
+      struct url_spec url[1];
+
+      struct
+      {
+         struct action_spec action[1];
+         const char * name;
+      } alias;
+
+      */
+
+   } data;
+
+};
+
+/** This file_line has not been processed yet. */
+#define FILE_LINE_UNPROCESSED           1
+
+/** This file_line is blank. Can only appear at the end of a file, due to
+    the way the parser works. */
+#define FILE_LINE_BLANK                 2
+
+/** This file_line says {{alias}}. */
+#define FILE_LINE_ALIAS_HEADER          3
+
+/** This file_line defines an alias. */
+#define FILE_LINE_ALIAS_ENTRY           4
+
+/** This file_line defines an {action}. */
+#define FILE_LINE_ACTION                5
+
+/** This file_line specifies a URL pattern. */
+#define FILE_LINE_URL                   6
+
+/** This file_line says {{settings}}. */
+#define FILE_LINE_SETTINGS_HEADER       7
+
+/** This file_line is in a {{settings}} block. */
+#define FILE_LINE_SETTINGS_ENTRY        8
+
+/** This file_line says {{description}}. */
+#define FILE_LINE_DESCRIPTION_HEADER    9
+
+/** This file_line is in a {{description}} block. */
+#define FILE_LINE_DESCRIPTION_ENTRY    10
+
+
+/**
+ * A configuration file, in a format that can be edited and written back to
+ * disk.
+ */
+struct editable_file
+{
+   struct file_line * lines;  /**< The contents of the file.  A linked list of lines. */
+   const char * filename;     /**< Full pathname - e.g. "/etc/privoxy/wibble.action". */
+   const char * identifier;   /**< Filename stub - e.g. "wibble".  Use for CGI param. */
+                              /**< Pre-encoded with url_encode() for ease of use. */
+   const char * version_str;  /**< Last modification time, as a string.  For CGI param. */
+                              /**< Can be used in URL without using url_param(). */
+   unsigned version;          /**< Last modification time - prevents chaos with
+                                   the browser's "back" button.  Note that this is a
+                                   time_t cast to an unsigned.  When comparing, always
+                                   cast the time_t to an unsigned, and *NOT* vice-versa.
+                                   This may lose the top few bits, but they're not
+                                   significant anyway. */
+   int newline;               /**< Newline convention - one of the NEWLINE_xxx constants.
+                                   Note that changing this after the file has been
+                                   read in will cause a mess. */
+   struct file_line * parse_error; /**< On parse error, this is the offending line. */
+   const char * parse_error_text;  /**< On parse error, this is the problem.
+                                        (Statically allocated) */
+};
+
+/* FIXME: Following non-static functions should be prototyped in .h or made static */
+
+/* Functions to read and write arbitrary config files */
+jb_err edit_read_file(struct client_state *csp,
+                      const struct map *parameters,
+                      int require_version,
+                      const char *suffix,
+                      struct editable_file **pfile);
+jb_err edit_write_file(struct editable_file * file);
+void   edit_free_file(struct editable_file * file);
+
+/* Functions to read and write actions files */
+jb_err edit_parse_actions_file(struct editable_file * file);
+jb_err edit_read_actions_file(struct client_state *csp,
+                              struct http_response *rsp,
+                              const struct map *parameters,
+                              int require_version,
+                              struct editable_file **pfile);
+
+/* Error handlers */
+jb_err cgi_error_modified(struct client_state *csp,
+                          struct http_response *rsp,
+                          const char *filename);
+jb_err cgi_error_parse(struct client_state *csp,
+                       struct http_response *rsp,
+                       struct editable_file *file);
+jb_err cgi_error_file(struct client_state *csp,
+                      struct http_response *rsp,
+                      const char *filename);
+jb_err cgi_error_file_read_only(struct client_state *csp,
+                                struct http_response *rsp,
+                                const char *filename);
+jb_err cgi_error_disabled(struct client_state *csp,
+                          struct http_response *rsp);
+
+/* Internal arbitrary config file support functions */
+static jb_err edit_read_file_lines(FILE *fp, struct file_line ** pfile, int *newline);
+static void edit_free_file_lines(struct file_line * first_line);
+
+/* Internal actions file support functions */
+static int match_actions_file_header_line(const char * line, const char * name);
+static jb_err split_line_on_equals(const char * line, char ** pname, char ** pvalue);
+
+/* Internal parameter parsing functions */
+static jb_err get_file_name_param(struct client_state *csp,
+                                  const struct map *parameters,
+                                  const char *param_name,
+                                  const char *suffix,
+                                  char **pfilename,
+                                  const char **pparam);
+
+static jb_err get_url_spec_param(struct client_state *csp,
+                                 const struct map *parameters,
+                                 const char *name,
+                                 char **pvalue);
+
+
+/* Internal actionsfile <==> HTML conversion functions */
+static jb_err map_radio(struct map * exports,
+                        const char * optionname,
+                        const char * values,
+                        int value);
+static jb_err actions_to_radio(struct map * exports,
+                               const struct action_spec *action);
+static jb_err actions_from_radio(const struct map * parameters,
+                                 struct action_spec *action);
+
+
+static jb_err map_copy_parameter_html(struct map *out,
+                                      const struct map *in,
+                                      const char *name);
+#if 0 /* unused function */
+static jb_err map_copy_parameter_url(struct map *out,
+                                     const struct map *in,
+                                     const char *name);
+#endif /* unused function */
+
+/* Internal convenience functions */
+static char *section_target(const unsigned sectionid);
+
+/*********************************************************************
+ *
+ * Function    :  section_target
+ *
+ * Description :  Given an unsigned (section id) n, produce a dynamically
+ *                allocated string of the form #l<n>, for use in link
+ *                targets.
+ *
+ * Parameters  :
+ *          1  :  sectionid = start line number of section
+ *
+ * Returns     :  String with link target, or NULL if out of
+ *                memory
+ *
+ *********************************************************************/
+static char *section_target(const unsigned sectionid)
+{
+   char buf[30];
+
+   snprintf(buf, 30, "#l%d", sectionid);
+   return(strdup(buf));
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  map_copy_parameter_html
+ *
+ * Description :  Copy a CGI parameter from one map to another, HTML
+ *                encoding it.
+ *
+ * Parameters  :
+ *          1  :  out = target map
+ *          2  :  in = source map
+ *          3  :  name = name of cgi parameter to copy
+ *
+ * Returns     :  JB_ERR_OK on success
+ *                JB_ERR_MEMORY on out-of-memory
+ *                JB_ERR_CGI_PARAMS if the parameter doesn't exist
+ *                                  in the source map
+ *
+ *********************************************************************/
+static jb_err map_copy_parameter_html(struct map *out,
+                                      const struct map *in,
+                                      const char *name)
+{
+   const char * value;
+   jb_err err;
+
+   assert(out);
+   assert(in);
+   assert(name);
+
+   value = lookup(in, name);
+   err = map(out, name, 1, html_encode(value), 0);
+
+   if (err)
+   {
+      /* Out of memory */
+      return err;
+   }
+   else if (*value == '\0')
+   {
+      return JB_ERR_CGI_PARAMS;
+   }
+   else
+   {
+      return JB_ERR_OK;
+   }
+}
+
+
+#if 0 /* unused function */
+/*********************************************************************
+ *
+ * Function    :  map_copy_parameter_url
+ *
+ * Description :  Copy a CGI parameter from one map to another, URL
+ *                encoding it.
+ *
+ * Parameters  :
+ *          1  :  out = target map
+ *          2  :  in = source map
+ *          3  :  name = name of cgi parameter to copy
+ *
+ * Returns     :  JB_ERR_OK on success
+ *                JB_ERR_MEMORY on out-of-memory
+ *                JB_ERR_CGI_PARAMS if the parameter doesn't exist
+ *                                  in the source map
+ *
+ *********************************************************************/
+static jb_err map_copy_parameter_url(struct map *out,
+                                     const struct map *in,
+                                     const char *name)
+{
+   const char * value;
+   jb_err err;
+
+   assert(out);
+   assert(in);
+   assert(name);
+
+   value = lookup(in, name);
+   err = map(out, name, 1, url_encode(value), 0);
+
+   if (err)
+   {
+      /* Out of memory */
+      return err;
+   }
+   else if (*value == '\0')
+   {
+      return JB_ERR_CGI_PARAMS;
+   }
+   else
+   {
+      return JB_ERR_OK;
+   }
+}
+#endif /* 0 - unused function */
+
+/*********************************************************************
+ *
+ * Function    :  cgi_edit_actions_url_form
+ *
+ * Description :  CGI function that displays a form for
+ *                edit-actions-url
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  rsp = http_response data structure for output
+ *          3  :  parameters = map of cgi parameters
+ *
+ * CGI Parameters
+ *           f : (filename) Identifies the file to edit
+ *           v : (version) File's last-modified time
+ *           p : (pattern) Line number of pattern to edit
+ *
+ * Returns     :  JB_ERR_OK on success
+ *                JB_ERR_MEMORY on out-of-memory
+ *                JB_ERR_CGI_PARAMS if the CGI parameters are not
+ *                                  specified or not valid.
+ *
+ *********************************************************************/
+jb_err cgi_edit_actions_url_form(struct client_state *csp,
+                                 struct http_response *rsp,
+                                 const struct map *parameters)
+{
+   struct map * exports;
+   unsigned patternid;
+   struct editable_file * file;
+   struct file_line * cur_line;
+   unsigned line_number;
+   unsigned section_start_line_number = 0;
+   jb_err err;
+
+   assert(csp);
+   assert(rsp);
+   assert(parameters);
+
+   if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
+   {
+      return cgi_error_disabled(csp, rsp);
+   }
+
+   err = get_number_param(csp, parameters, "p", &patternid);
+   if (err)
+   {
+      return err;
+   }
+
+   err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
+   if (err)
+   {
+      /* No filename specified, can't read file, modified, or out of memory. */
+      return (err == JB_ERR_FILE ? JB_ERR_OK : err);
+   }
+
+   cur_line = file->lines;
+
+   for (line_number = 1; (cur_line != NULL) && (line_number < patternid); line_number++)
+   {
+      if (cur_line->type == FILE_LINE_ACTION)
+      {
+         section_start_line_number = line_number;
+      }
+      cur_line = cur_line->next;
+   }
+
+   if ( (cur_line == NULL)
+     || (line_number != patternid)
+     || (patternid < 1)
+     || (cur_line->type != FILE_LINE_URL))
+   {
+      /* Invalid "patternid" parameter */
+      edit_free_file(file);
+      return JB_ERR_CGI_PARAMS;
+   }
+
+   if (NULL == (exports = default_exports(csp, NULL)))
+   {
+      edit_free_file(file);
+      return JB_ERR_MEMORY;
+   }
+
+   err = map(exports, "f", 1, file->identifier, 1);
+   if (!err) err = map(exports, "v", 1, file->version_str, 1);
+   if (!err) err = map(exports, "p", 1, url_encode(lookup(parameters, "p")), 0);
+   if (!err) err = map(exports, "u", 1, html_encode(cur_line->unprocessed), 0);
+   if (!err) err = map(exports, "jumptarget", 1, section_target(section_start_line_number), 0);
+
+   edit_free_file(file);
+
+   if (err)
+   {
+      free_map(exports);
+      return err;
+   }
+
+   return template_fill_for_cgi(csp, "edit-actions-url-form", exports, rsp);
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  cgi_edit_actions_add_url_form
+ *
+ * Description :  CGI function that displays a form for
+ *                edit-actions-url
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  rsp = http_response data structure for output
+ *          3  :  parameters = map of cgi parameters
+ *
+ * CGI Parameters :
+ *           f : (filename) Identifies the file to edit
+ *           v : (version) File's last-modified time
+ *           s : (section) Line number of section to edit
+ *
+ * Returns     :  JB_ERR_OK on success
+ *                JB_ERR_MEMORY on out-of-memory
+ *                JB_ERR_CGI_PARAMS if the CGI parameters are not
+ *                                  specified or not valid.
+ *
+ *********************************************************************/
+jb_err cgi_edit_actions_add_url_form(struct client_state *csp,
+                                     struct http_response *rsp,
+                                     const struct map *parameters)
+{
+   struct map *exports;
+   jb_err err;
+
+   assert(csp);
+   assert(rsp);
+   assert(parameters);
+
+   if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
+   {
+      return cgi_error_disabled(csp, rsp);
+   }
+
+   if (NULL == (exports = default_exports(csp, NULL)))
+   {
+      return JB_ERR_MEMORY;
+   }
+
+   err = map_copy_parameter_html(exports, parameters, "f");
+   if (!err) err = map_copy_parameter_html(exports, parameters, "v");
+   if (!err) err = map_copy_parameter_html(exports, parameters, "s");
+
+   if (err)
+   {
+      free_map(exports);
+      return err;
+   }
+
+   return template_fill_for_cgi(csp, "edit-actions-add-url-form", exports, rsp);
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  cgi_edit_actions_remove_url_form
+ *
+ * Description :  CGI function that displays a form for
+ *                edit-actions-url
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  rsp = http_response data structure for output
+ *          3  :  parameters = map of cgi parameters
+ *
+ * CGI Parameters :
+ *           f : (filename) Identifies the file to edit
+ *           v : (version) File's last-modified time
+ *           p : (pattern) Line number of pattern to edit
+ *
+ * Returns     :  JB_ERR_OK on success
+ *                JB_ERR_MEMORY on out-of-memory
+ *                JB_ERR_CGI_PARAMS if the CGI parameters are not
+ *                                  specified or not valid.
+ *
+ *********************************************************************/
+jb_err cgi_edit_actions_remove_url_form(struct client_state *csp,
+                                     struct http_response *rsp,
+                                     const struct map *parameters)
+{
+   struct map * exports;
+   unsigned patternid;
+   struct editable_file * file;
+   struct file_line * cur_line;
+   unsigned line_number;
+   unsigned section_start_line_number = 0;
+   jb_err err;
+
+   assert(csp);
+   assert(rsp);
+   assert(parameters);
+
+   if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
+   {
+      return cgi_error_disabled(csp, rsp);
+   }
+
+   err = get_number_param(csp, parameters, "p", &patternid);
+   if (err)
+   {
+      return err;
+   }
+
+   err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
+   if (err)
+   {
+      /* No filename specified, can't read file, modified, or out of memory. */
+      return (err == JB_ERR_FILE ? JB_ERR_OK : err);
+   }
+
+   cur_line = file->lines;
+
+   for (line_number = 1; (cur_line != NULL) && (line_number < patternid); line_number++)
+   {
+      if (cur_line->type == FILE_LINE_ACTION)
+      {
+         section_start_line_number = line_number;
+      }      
+      cur_line = cur_line->next;
+   }
+
+   if ( (cur_line == NULL)
+     || (line_number != patternid)
+     || (patternid < 1)
+     || (cur_line->type != FILE_LINE_URL))
+   {
+      /* Invalid "patternid" parameter */
+      edit_free_file(file);
+      return JB_ERR_CGI_PARAMS;
+   }
+
+   if (NULL == (exports = default_exports(csp, NULL)))
+   {
+      edit_free_file(file);
+      return JB_ERR_MEMORY;
+   }
+
+   err = map(exports, "f", 1, file->identifier, 1);
+   if (!err) err = map(exports, "v", 1, file->version_str, 1);
+   if (!err) err = map(exports, "p", 1, url_encode(lookup(parameters, "p")), 0);
+   if (!err) err = map(exports, "u", 1, html_encode(cur_line->unprocessed), 0);
+   if (!err) err = map(exports, "jumptarget", 1, section_target(section_start_line_number), 0);
+
+   edit_free_file(file);
+
+   if (err)
+   {
+      free_map(exports);
+      return err;
+   }
+
+   return template_fill_for_cgi(csp, "edit-actions-remove-url-form", exports, rsp);
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  edit_write_file
+ *
+ * Description :  Write a complete file to disk.
+ *
+ * Parameters  :
+ *          1  :  file = File to write.
+ *
+ * Returns     :  JB_ERR_OK     on success
+ *                JB_ERR_FILE   on error writing to file.
+ *                JB_ERR_MEMORY on out of memory
+ *
+ *********************************************************************/
+jb_err edit_write_file(struct editable_file * file)
+{
+   FILE * fp;
+   struct file_line * cur_line;
+   struct stat statbuf[1];
+   char version_buf[22]; /* 22 = ceil(log10(2^64)) + 2 = max number of
+                            digits in time_t, assuming this is a 64-bit
+                            machine, plus null terminator, plus one
+                            for paranoia */
+
+   assert(file);
+   assert(file->filename);
+
+   if (NULL == (fp = fopen(file->filename, "wb")))
+   {
+      return JB_ERR_FILE;
+   }
+
+   cur_line = file->lines;
+   while (cur_line != NULL)
+   {
+      if (cur_line->raw)
+      {
+         if (fputs(cur_line->raw, fp) < 0)
+         {
+            fclose(fp);
+            return JB_ERR_FILE;
+         }
+      }
+      else
+      {
+         if (cur_line->prefix)
+         {
+            if (fputs(cur_line->prefix, fp) < 0)
+            {
+               fclose(fp);
+               return JB_ERR_FILE;
+            }
+         }
+         if (cur_line->unprocessed)
+         {
+
+            if (NULL != strchr(cur_line->unprocessed, '#'))
+            {
+               /* Must quote '#' characters */
+               int numhash = 0;
+               int len;
+               char * src;
+               char * dest;
+               char * str;
+
+               /* Count number of # characters, so we know length of output string */
+               src = cur_line->unprocessed;
+               while (NULL != (src = strchr(src, '#')))
+               {
+                  numhash++;
+                  src++;
+               }
+               assert(numhash > 0);
+
+               /* Allocate new memory for string */
+               len = strlen(cur_line->unprocessed);
+               if (NULL == (str = malloc((size_t) len + 1 + numhash)))
+               {
+                  /* Uh oh, just trashed file! */
+                  fclose(fp);
+                  return JB_ERR_MEMORY;
+               }
+
+               /* Loop through string from end */
+               src  = cur_line->unprocessed + len;
+               dest = str + len + numhash;
+               for ( ; len >= 0; len--)
+               {
+                  if ((*dest-- = *src--) == '#')
+                  {
+                     *dest-- = '\\';
+                     numhash--;
+                     assert(numhash >= 0);
+                  }
+               }
+               assert(numhash == 0);
+               assert(src  + 1 == cur_line->unprocessed);
+               assert(dest + 1 == str);
+
+               if (fputs(str, fp) < 0)
+               {
+                  free(str);
+                  fclose(fp);
+                  return JB_ERR_FILE;
+               }
+
+               free(str);
+            }
+            else
+            {
+               /* Can write without quoting '#' characters. */
+               if (fputs(cur_line->unprocessed, fp) < 0)
+               {
+                  fclose(fp);
+                  return JB_ERR_FILE;
+               }
+            }
+            if (fputs(NEWLINE(file->newline), fp) < 0)
+            {
+               fclose(fp);
+               return JB_ERR_FILE;
+            }
+         }
+         else
+         {
+            /* FIXME: Write data from file->data->whatever */
+            assert(0);
+         }
+      }
+      cur_line = cur_line->next;
+   }
+
+   fclose(fp);
+
+
+   /* Update the version stamp in the file structure, since we just
+    * wrote to the file & changed it's date.
+    */
+   if (stat(file->filename, statbuf) < 0)
+   {
+      /* Error, probably file not found. */
+      return JB_ERR_FILE;
+   }
+   file->version = (unsigned)statbuf->st_mtime;
+
+   /* Correct file->version_str */
+   freez(file->version_str);
+   snprintf(version_buf, 22, "%u", file->version);
+   version_buf[21] = '\0';
+   file->version_str = strdup(version_buf);
+   if (version_buf == NULL)
+   {
+      return JB_ERR_MEMORY;
+   }
+
+   return JB_ERR_OK;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  edit_free_file
+ *
+ * Description :  Free a complete file in memory.
+ *
+ * Parameters  :
+ *          1  :  file = Data structure to free.
+ *
+ * Returns     :  N/A
+ *
+ *********************************************************************/
+void edit_free_file(struct editable_file * file)
+{
+   if (!file)
+   {
+      /* Silently ignore NULL pointer */
+      return;
+   }
+
+   edit_free_file_lines(file->lines);
+   freez(file->filename);
+   freez(file->identifier);
+   freez(file->version_str);
+   file->version = 0;
+   file->parse_error_text = NULL; /* Statically allocated */
+   file->parse_error = NULL;
+
+   free(file);
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  edit_free_file_lines
+ *
+ * Description :  Free an entire linked list of file lines.
+ *
+ * Parameters  :
+ *          1  :  first_line = Data structure to free.
+ *
+ * Returns     :  N/A
+ *
+ *********************************************************************/
+static void edit_free_file_lines(struct file_line * first_line)
+{
+   struct file_line * next_line;
+
+   while (first_line != NULL)
+   {
+      next_line = first_line->next;
+      first_line->next = NULL;
+      freez(first_line->raw);
+      freez(first_line->prefix);
+      freez(first_line->unprocessed);
+      switch(first_line->type)
+      {
+         case 0: /* special case if memory zeroed */
+         case FILE_LINE_UNPROCESSED:
+         case FILE_LINE_BLANK:
+         case FILE_LINE_ALIAS_HEADER:
+         case FILE_LINE_SETTINGS_HEADER:
+         case FILE_LINE_DESCRIPTION_HEADER:
+         case FILE_LINE_DESCRIPTION_ENTRY:
+         case FILE_LINE_ALIAS_ENTRY:
+         case FILE_LINE_URL:
+            /* No data is stored for these */
+            break;
+
+         case FILE_LINE_ACTION:
+            free_action(first_line->data.action);
+            break;
+
+         case FILE_LINE_SETTINGS_ENTRY:
+            freez(first_line->data.setting.name);
+            freez(first_line->data.setting.svalue);
+            break;
+         default:
+            /* Should never happen */
+            assert(0);
+            break;
+      }
+      first_line->type = 0; /* paranoia */
+      free(first_line);
+      first_line = next_line;
+   }
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  match_actions_file_header_line
+ *
+ * Description :  Match an actions file {{header}} line
+ *
+ * Parameters  :
+ *          1  :  line = String from file
+ *          2  :  name = Header to match against
+ *
+ * Returns     :  0 iff they match.
+ *
+ *********************************************************************/
+static int match_actions_file_header_line(const char * line, const char * name)
+{
+   size_t len;
+
+   assert(line);
+   assert(name);
+
+   /* Look for "{{" */
+   if ((line[0] != '{') || (line[1] != '{'))
+   {
+      return 1;
+   }
+   line += 2;
+
+   /* Look for optional whitespace */
+   while ( (*line == ' ') || (*line == '\t') )
+   {
+      line++;
+   }
+
+   /* Look for the specified name (case-insensitive) */
+   len = strlen(name);
+   if (0 != strncmpic(line, name, len))
+   {
+      return 1;
+   }
+   line += len;
+
+   /* Look for optional whitespace */
+   while ( (*line == ' ') || (*line == '\t') )
+   {
+      line++;
+   }
+
+   /* Look for "}}" and end of string*/
+   if ((line[0] != '}') || (line[1] != '}') || (line[2] != '\0'))
+   {
+      return 1;
+   }
+
+   /* It matched!! */
+   return 0;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  match_actions_file_header_line
+ *
+ * Description :  Match an actions file {{header}} line
+ *
+ * Parameters  :
+ *          1  :  line = String from file.  Must not start with
+ *                       whitespace (else infinite loop!)
+ *          2  :  pname = Destination for name
+ *          2  :  pvalue = Destination for value
+ *
+ * Returns     :  JB_ERR_OK     on success
+ *                JB_ERR_MEMORY on out-of-memory
+ *                JB_ERR_PARSE  if there's no "=" sign, or if there's
+ *                              nothing before the "=" sign (but empty
+ *                              values *after* the "=" sign are legal).
+ *
+ *********************************************************************/
+static jb_err split_line_on_equals(const char * line, char ** pname, char ** pvalue)
+{
+   const char * name_end;
+   const char * value_start;
+   size_t name_len;
+
+   assert(line);
+   assert(pname);
+   assert(pvalue);
+   assert(*line != ' ');
+   assert(*line != '\t');
+
+   *pname = NULL;
+   *pvalue = NULL;
+
+   value_start = strchr(line, '=');
+   if ((value_start == NULL) || (value_start == line))
+   {
+      return JB_ERR_PARSE;
+   }
+
+   name_end = value_start - 1;
+
+   /* Eat any whitespace before the '=' */
+   while ((*name_end == ' ') || (*name_end == '\t'))
+   {
+      /*
+       * we already know we must have at least 1 non-ws char
+       * at start of buf - no need to check
+       */
+      name_end--;
+   }
+
+   name_len = name_end - line + 1; /* Length excluding \0 */
+   if (NULL == (*pname = (char *) malloc(name_len + 1)))
+   {
+      return JB_ERR_MEMORY;
+   }
+   strncpy(*pname, line, name_len);
+   (*pname)[name_len] = '\0';
+
+   /* Eat any the whitespace after the '=' */
+   value_start++;
+   while ((*value_start == ' ') || (*value_start == '\t'))
+   {
+      value_start++;
+   }
+
+   if (NULL == (*pvalue = strdup(value_start)))
+   {
+      free(*pname);
+      *pname = NULL;
+      return JB_ERR_MEMORY;
+   }
+
+   return JB_ERR_OK;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  edit_parse_actions_file
+ *
+ * Description :  Parse an actions file in memory.
+ *
+ *                Passed linked list must have the "data" member
+ *                zeroed, and must contain valid "next" and
+ *                "unprocessed" fields.  The "raw" and "prefix"
+ *                fields are ignored, and "type" is just overwritten.
+ *
+ *                Note that on error the file may have been
+ *                partially parsed.
+ *
+ * Parameters  :
+ *          1  :  file = Actions file to be parsed in-place.
+ *
+ * Returns     :  JB_ERR_OK     on success
+ *                JB_ERR_MEMORY on out-of-memory
+ *                JB_ERR_PARSE  on error
+ *
+ *********************************************************************/
+jb_err edit_parse_actions_file(struct editable_file * file)
+{
+   struct file_line * cur_line;
+   size_t len;
+   const char * text; /* Text from a line */
+   char * name;  /* For lines of the form name=value */
+   char * value; /* For lines of the form name=value */
+   struct action_alias * alias_list = NULL;
+   jb_err err = JB_ERR_OK;
+
+   /* alias_list contains the aliases defined in this file.
+    * It might be better to use the "file_line.data" fields
+    * in the relavent places instead.
+    */
+
+   cur_line = file->lines;
+
+   /* A note about blank line support: Blank lines should only
+    * ever occur as the last line in the file.  This function
+    * is more forgiving than that - FILE_LINE_BLANK can occur
+    * anywhere.
+    */
+
+   /* Skip leading blanks.  Should only happen if file is
+    * empty (which is valid, but pointless).
+    */
+   while ( (cur_line != NULL)
+        && (cur_line->unprocessed[0] == '\0') )
+   {
+      /* Blank line */
+      cur_line->type = FILE_LINE_BLANK;
+      cur_line = cur_line->next;
+   }
+
+   if ( (cur_line != NULL)
+     && (cur_line->unprocessed[0] != '{') )
+   {
+      /* File doesn't start with a header */
+      file->parse_error = cur_line;
+      file->parse_error_text = "First (non-comment) line of the file must contain a header.";
+      return JB_ERR_PARSE;
+   }
+
+   if ( (cur_line != NULL) && (0 ==
+      match_actions_file_header_line(cur_line->unprocessed, "settings") ) )
+   {
+      cur_line->type = FILE_LINE_SETTINGS_HEADER;
+
+      cur_line = cur_line->next;
+      while ((cur_line != NULL) && (cur_line->unprocessed[0] != '{'))
+      {
+         if (cur_line->unprocessed[0])
+         {
+            cur_line->type = FILE_LINE_SETTINGS_ENTRY;
+
+            err = split_line_on_equals(cur_line->unprocessed,
+                     &cur_line->data.setting.name,
+                     &cur_line->data.setting.svalue);
+            if (err == JB_ERR_MEMORY)
+            {
+               return err;
+            }
+            else if (err != JB_ERR_OK)
+            {
+               /* Line does not contain a name=value pair */
+               file->parse_error = cur_line;
+               file->parse_error_text = "Expected a name=value pair on this {{description}} line, but couldn't find one.";
+               return JB_ERR_PARSE;
+            }
+         }
+         else
+         {
+            cur_line->type = FILE_LINE_BLANK;
+         }
+         cur_line = cur_line->next;
+      }
+   }
+
+   if ( (cur_line != NULL) && (0 ==
+      match_actions_file_header_line(cur_line->unprocessed, "description") ) )
+   {
+      cur_line->type = FILE_LINE_DESCRIPTION_HEADER;
+
+      cur_line = cur_line->next;
+      while ((cur_line != NULL) && (cur_line->unprocessed[0] != '{'))
+      {
+         if (cur_line->unprocessed[0])
+         {
+            cur_line->type = FILE_LINE_DESCRIPTION_ENTRY;
+         }
+         else
+         {
+            cur_line->type = FILE_LINE_BLANK;
+         }
+         cur_line = cur_line->next;
+      }
+   }
+
+   if ( (cur_line != NULL) && (0 ==
+      match_actions_file_header_line(cur_line->unprocessed, "alias") ) )
+   {
+      cur_line->type = FILE_LINE_ALIAS_HEADER;
+
+      cur_line = cur_line->next;
+      while ((cur_line != NULL) && (cur_line->unprocessed[0] != '{'))
+      {
+         if (cur_line->unprocessed[0])
+         {
+            /* define an alias */
+            struct action_alias * new_alias;
+
+            cur_line->type = FILE_LINE_ALIAS_ENTRY;
+
+            err = split_line_on_equals(cur_line->unprocessed, &name, &value);
+            if (err == JB_ERR_MEMORY)
+            {
+               return err;
+            }
+            else if (err != JB_ERR_OK)
+            {
+               /* Line does not contain a name=value pair */
+               file->parse_error = cur_line;
+               file->parse_error_text = "Expected a name=value pair on this {{alias}} line, but couldn't find one.";
+               return JB_ERR_PARSE;
+            }
+
+            if ((new_alias = zalloc(sizeof(*new_alias))) == NULL)
+            {
+               /* Out of memory */
+               free(name);
+               free(value);
+               free_alias_list(alias_list);
+               return JB_ERR_MEMORY;
+            }
+
+            err = get_actions(value, alias_list, new_alias->action);
+            if (err)
+            {
+               /* Invalid action or out of memory */
+               free(name);
+               free(value);
+               free(new_alias);
+               free_alias_list(alias_list);
+               if (err == JB_ERR_MEMORY)
+               {
+                  return err;
+               }
+               else
+               {
+                  /* Line does not contain a name=value pair */
+                  file->parse_error = cur_line;
+                  file->parse_error_text = "This alias does not specify a valid set of actions.";
+                  return JB_ERR_PARSE;
+               }
+            }
+
+            free(value);
+
+            new_alias->name = name;
+
+            /* add to list */
+            new_alias->next = alias_list;
+            alias_list = new_alias;
+         }
+         else
+         {
+            cur_line->type = FILE_LINE_BLANK;
+         }
+         cur_line = cur_line->next;
+      }
+   }
+
+   /* Header done, process the main part of the file */
+   while (cur_line != NULL)
+   {
+      /* At this point, (cur_line->unprocessed[0] == '{') */
+      assert(cur_line->unprocessed[0] == '{');
+      text = cur_line->unprocessed + 1;
+      len = strlen(text) - 1;
+      if (text[len] != '}')
+      {
+         /* No closing } on header */
+         free_alias_list(alias_list);
+         file->parse_error = cur_line;
+         file->parse_error_text = "Headers starting with '{' must have a "
+            "closing bracket ('}').  Headers starting with two brackets ('{{') "
+            "must close with two brackets ('}}').";
+         return JB_ERR_PARSE;
+      }
+
+      if (text[0] == '{')
+      {
+         /* An invalid {{ header.  */
+         free_alias_list(alias_list);
+         file->parse_error = cur_line;
+         file->parse_error_text = "Unknown or unexpected two-bracket header.  "
+            "Please remember that the system (two-bracket) headers must "
+            "appear in the order {{settings}}, {{description}}, {{alias}}, "
+            "and must appear before any actions (one-bracket) headers.  "
+            "Also note that system headers may not be repeated.";
+         return JB_ERR_PARSE;
+      }
+
+      while ( (*text == ' ') || (*text == '\t') )
+      {
+         text++;
+         len--;
+      }
+      while ( (len > 0)
+           && ( (text[len - 1] == ' ')
+             || (text[len - 1] == '\t') ) )
+      {
+         len--;
+      }
+
+      cur_line->type = FILE_LINE_ACTION;
+
+      /* Remove {} and make copy */
+      if (NULL == (value = (char *) malloc(len + 1)))
+      {
+         /* Out of memory */
+         free_alias_list(alias_list);
+         return JB_ERR_MEMORY;
+      }
+      strncpy(value, text, len);
+      value[len] = '\0';
+
+      /* Get actions */
+      err = get_actions(value, alias_list, cur_line->data.action);
+      if (err)
+      {
+         /* Invalid action or out of memory */
+         free(value);
+         free_alias_list(alias_list);
+         if (err == JB_ERR_MEMORY)
+         {
+            return err;
+         }
+         else
+         {
+            /* Line does not contain a name=value pair */
+            file->parse_error = cur_line;
+            file->parse_error_text = "This header does not specify a valid set of actions.";
+            return JB_ERR_PARSE;
+         }
+      }
+
+      /* Done with string - it was clobbered anyway */
+      free(value);
+
+      /* Process next line */
+      cur_line = cur_line->next;
+
+      /* Loop processing URL patterns */
+      while ((cur_line != NULL) && (cur_line->unprocessed[0] != '{'))
+      {
+         if (cur_line->unprocessed[0])
+         {
+            /* Could parse URL here, but this isn't currently needed */
+
+            cur_line->type = FILE_LINE_URL;
+         }
+         else
+         {
+            cur_line->type = FILE_LINE_BLANK;
+         }
+         cur_line = cur_line->next;
+      }
+   } /* End main while(cur_line != NULL) loop */
+
+   free_alias_list(alias_list);
+
+   return JB_ERR_OK;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  edit_read_file_lines
+ *
+ * Description :  Read all the lines of a file into memory.
+ *                Handles whitespace, comments and line continuation.
+ *
+ * Parameters  :
+ *          1  :  fp = File to read from.  On return, this will be
+ *                     at EOF but it will not have been closed.
+ *          2  :  pfile = Destination for a linked list of file_lines.
+ *                        Will be set to NULL on error.
+ *          3  :  newline = How to handle newlines.
+ *
+ * Returns     :  JB_ERR_OK     on success
+ *                JB_ERR_MEMORY on out-of-memory
+ *
+ *********************************************************************/
+jb_err edit_read_file_lines(FILE *fp, struct file_line ** pfile, int *newline)
+{
+   struct file_line * first_line; /* Keep for return value or to free */
+   struct file_line * cur_line;   /* Current line */
+   struct file_line * prev_line;  /* Entry with prev_line->next = cur_line */
+   jb_err rval;
+
+   assert(fp);
+   assert(pfile);
+
+   *pfile = NULL;
+
+   cur_line = first_line = zalloc(sizeof(struct file_line));
+   if (cur_line == NULL)
+   {
+      return JB_ERR_MEMORY;
+   }
+
+   cur_line->type = FILE_LINE_UNPROCESSED;
+
+   rval = edit_read_line(fp, &cur_line->raw, &cur_line->prefix, &cur_line->unprocessed, newline, NULL);
+   if (rval)
+   {
+      /* Out of memory or empty file. */
+      /* Note that empty file is not an error we propogate up */
+      free(cur_line);
+      return ((rval == JB_ERR_FILE) ? JB_ERR_OK : rval);
+   }
+
+   do
+   {
+      prev_line = cur_line;
+      cur_line = prev_line->next = zalloc(sizeof(struct file_line));
+      if (cur_line == NULL)
+      {
+         /* Out of memory */
+         edit_free_file_lines(first_line);
+         return JB_ERR_MEMORY;
+      }
+
+      cur_line->type = FILE_LINE_UNPROCESSED;
+
+      rval = edit_read_line(fp, &cur_line->raw, &cur_line->prefix, &cur_line->unprocessed, newline, NULL);
+      if ((rval != JB_ERR_OK) && (rval != JB_ERR_FILE))
+      {
+         /* Out of memory */
+         edit_free_file_lines(first_line);
+         return JB_ERR_MEMORY;
+      }
+
+   }
+   while (rval != JB_ERR_FILE);
+
+   /* EOF */
+
+   /* We allocated one too many - free it */
+   prev_line->next = NULL;
+   free(cur_line);
+
+   *pfile = first_line;
+   return JB_ERR_OK;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  edit_read_file
+ *
+ * Description :  Read a complete file into memory.
+ *                Handles CGI parameter parsing.  If requested, also
+ *                checks the file's modification timestamp.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  parameters = map of cgi parameters.
+ *          3  :  require_version = true to check "ver" parameter.
+ *          4  :  suffix = File extension, e.g. ".action".
+ *          5  :  pfile = Destination for the file.  Will be set
+ *                        to NULL on error.
+ *
+ * CGI Parameters :
+ *    filename :  The name of the file to read, without the
+ *                path or ".action" extension.
+ *         ver :  (Only if require_version is nonzero)
+ *                Timestamp of the actions file.  If wrong, this
+ *                function fails with JB_ERR_MODIFIED.
+ *
+ * Returns     :  JB_ERR_OK     on success
+ *                JB_ERR_MEMORY on out-of-memory
+ *                JB_ERR_CGI_PARAMS if "filename" was not specified
+ *                                  or is not valid.
+ *                JB_ERR_FILE   if the file cannot be opened or
+ *                              contains no data
+ *                JB_ERR_MODIFIED if version checking was requested and
+ *                                failed - the file was modified outside
+ *                                of this CGI editor instance.
+ *
+ *********************************************************************/
+jb_err edit_read_file(struct client_state *csp,
+                      const struct map *parameters,
+                      int require_version,
+                      const char *suffix,
+                      struct editable_file **pfile)
+{
+   struct file_line * lines;
+   FILE * fp;
+   jb_err err;
+   char * filename;
+   const char * identifier;
+   struct editable_file * file;
+   unsigned version = 0;
+   struct stat statbuf[1];
+   char version_buf[22];
+   int newline = NEWLINE_UNKNOWN;
+
+   assert(csp);
+   assert(parameters);
+   assert(pfile);
+
+   *pfile = NULL;
+
+   err = get_file_name_param(csp, parameters, "f", suffix,
+                             &filename, &identifier);
+   if (err)
+   {
+      return err;
+   }
+
+   if (stat(filename, statbuf) < 0)
+   {
+      /* Error, probably file not found. */
+      free(filename);
+      return JB_ERR_FILE;
+   }
+   version = (unsigned) statbuf->st_mtime;
+
+   if (require_version)
+   {
+      unsigned specified_version;
+      err = get_number_param(csp, parameters, "v", &specified_version);
+      if (err)
+      {
+         free(filename);
+         return err;
+      }
+
+      if (version != specified_version)
+      {
+         return JB_ERR_MODIFIED;
+      }
+   }
+
+   if (NULL == (fp = fopen(filename,"rb")))
+   {
+      free(filename);
+      return JB_ERR_FILE;
+   }
+
+   err = edit_read_file_lines(fp, &lines, &newline);
+
+   fclose(fp);
+
+   if (err)
+   {
+      free(filename);
+      return err;
+   }
+
+   file = (struct editable_file *) zalloc(sizeof(*file));
+   if (err)
+   {
+      free(filename);
+      edit_free_file_lines(lines);
+      return err;
+   }
+
+   file->lines = lines;
+   file->newline = newline;
+   file->filename = filename;
+   file->version = version;
+   file->identifier = url_encode(identifier);
+
+   if (file->identifier == NULL)
+   {
+      edit_free_file(file);
+      return JB_ERR_MEMORY;
+   }
+
+   /* Correct file->version_str */
+   freez(file->version_str);
+   snprintf(version_buf, 22, "%u", file->version);
+   version_buf[21] = '\0';
+   file->version_str = strdup(version_buf);
+   if (version_buf == NULL)
+   {
+      edit_free_file(file);
+      return JB_ERR_MEMORY;
+   }
+
+   *pfile = file;
+   return JB_ERR_OK;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  edit_read_actions_file
+ *
+ * Description :  Read a complete actions file into memory.
+ *                Handles CGI parameter parsing.  If requested, also
+ *                checks the file's modification timestamp.
+ *
+ *                If this function detects an error in the categories
+ *                JB_ERR_FILE, JB_ERR_MODIFIED, or JB_ERR_PARSE,
+ *                then it handles it by filling in the specified
+ *                response structure and returning JB_ERR_FILE.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  rsp = HTTP response.  Only filled in on error.
+ *          2  :  parameters = map of cgi parameters.
+ *          3  :  require_version = true to check "ver" parameter.
+ *          4  :  pfile = Destination for the file.  Will be set
+ *                        to NULL on error.
+ *
+ * CGI Parameters :
+ *    filename :  The name of the actions file to read, without the
+ *                path or ".action" extension.
+ *         ver :  (Only if require_version is nonzero)
+ *                Timestamp of the actions file.  If wrong, this
+ *                function fails with JB_ERR_MODIFIED.
+ *
+ * Returns     :  JB_ERR_OK     on success
+ *                JB_ERR_MEMORY on out-of-memory
+ *                JB_ERR_CGI_PARAMS if "filename" was not specified
+ *                                  or is not valid.
+ *                JB_ERR_FILE  if the file does not contain valid data,
+ *                             or if file cannot be opened or
+ *                             contains no data, or if version
+ *                             checking was requested and failed.
+ *
+ *********************************************************************/
+jb_err edit_read_actions_file(struct client_state *csp,
+                              struct http_response *rsp,
+                              const struct map *parameters,
+                              int require_version,
+                              struct editable_file **pfile)
+{
+   jb_err err;
+   struct editable_file *file;
+
+   assert(csp);
+   assert(parameters);
+   assert(pfile);
+
+   *pfile = NULL;
+
+   err = edit_read_file(csp, parameters, require_version, ".action", &file);
+   if (err)
+   {
+      /* Try to handle if possible */
+      if (err == JB_ERR_FILE)
+      {
+         err = cgi_error_file(csp, rsp, lookup(parameters, "f"));
+      }
+      else if (err == JB_ERR_MODIFIED)
+      {
+         err = cgi_error_modified(csp, rsp, lookup(parameters, "f"));
+      }
+      if (err == JB_ERR_OK)
+      {
+         /*
+          * Signal to higher-level CGI code that there was a problem but we
+          * handled it, they should just return JB_ERR_OK.
+          */
+         err = JB_ERR_FILE;
+      }
+      return err;
+   }
+
+   err = edit_parse_actions_file(file);
+   if (err)
+   {
+      if (err == JB_ERR_PARSE)
+      {
+         err = cgi_error_parse(csp, rsp, file);
+         if (err == JB_ERR_OK)
+         {
+            /*
+             * Signal to higher-level CGI code that there was a problem but we
+             * handled it, they should just return JB_ERR_OK.
+             */
+            err = JB_ERR_FILE;
+         }
+      }
+      edit_free_file(file);
+      return err;
+   }
+
+   *pfile = file;
+   return JB_ERR_OK;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  get_file_name_param
+ *
+ * Description :  Get the name of the file to edit from the parameters
+ *                passed to a CGI function.  This function handles
+ *                security checks such as blocking urls containing
+ *                "/" or ".", prepending the config file directory,
+ *                and adding the specified suffix.
+ *
+ *                (This is an essential security check, otherwise
+ *                users may be able to pass "../../../etc/passwd"
+ *                and overwrite the password file [linux], "prn:"
+ *                and print random data [Windows], etc...)
+ *
+ *                This function only allows filenames contining the
+ *                characters '-', '_', 'A'-'Z', 'a'-'z', and '0'-'9'.
+ *                That's probably too restrictive but at least it's
+ *                secure.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  parameters = map of cgi parameters
+ *          3  :  param_name = The name of the parameter to read
+ *          4  :  suffix = File extension, e.g. ".actions"
+ *          5  :  pfilename = destination for full filename.  Caller
+ *                free()s.  Set to NULL on error.
+ *          6  :  pparam = destination for partial filename,
+ *                suitable for use in another URL.  Allocated as part
+ *                of the map "parameters", so don't free it.
+ *                Set to NULL if not specified.
+ *
+ * Returns     :  JB_ERR_OK         on success
+ *                JB_ERR_MEMORY     on out-of-memory
+ *                JB_ERR_CGI_PARAMS if "filename" was not specified
+ *                                  or is not valid.
+ *
+ *********************************************************************/
+static jb_err get_file_name_param(struct client_state *csp,
+                                  const struct map *parameters,
+                                  const char *param_name,
+                                  const char *suffix,
+                                  char **pfilename,
+                                  const char **pparam)
+{
+   const char *param;
+   const char *s;
+   char *name;
+   char *fullpath;
+   char ch;
+   int len;
+
+   assert(csp);
+   assert(parameters);
+   assert(suffix);
+   assert(pfilename);
+   assert(pparam);
+
+   *pfilename = NULL;
+   *pparam = NULL;
+
+   param = lookup(parameters, param_name);
+   if (!*param)
+   {
+      return JB_ERR_CGI_PARAMS;
+   }
+
+   *pparam = param;
+
+   len = strlen(param);
+   if (len >= FILENAME_MAX)
+   {
+      /* Too long. */
+      return JB_ERR_CGI_PARAMS;
+   }
+
+   /* Check every character to see if it's legal */
+   s = param;
+   while ((ch = *s++) != '\0')
+   {
+      if ( ((ch < 'A') || (ch > 'Z'))
+        && ((ch < 'a') || (ch > 'z'))
+        && ((ch < '0') || (ch > '9'))
+        && (ch != '-')
+        && (ch != '_') )
+      {
+         /* Probable hack attempt. */
+         return JB_ERR_CGI_PARAMS;
+      }
+   }
+
+   /* Append extension */
+   name = malloc(len + strlen(suffix) + 1);
+   if (name == NULL)
+   {
+      return JB_ERR_MEMORY;
+   }
+   strcpy(name, param);
+   strcpy(name + len, suffix);
+
+   /* Prepend path */
+   fullpath = make_path(csp->config->confdir, name);
+   free(name);
+
+   if (fullpath == NULL)
+   {
+      return JB_ERR_MEMORY;
+   }
+
+   /* Success */
+   *pfilename = fullpath;
+
+   return JB_ERR_OK;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  get_url_spec_param
+ *
+ * Description :  Get a URL pattern from the parameters
+ *                passed to a CGI function.  Removes leading/trailing
+ *                spaces and validates it.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  parameters = map of cgi parameters
+ *          3  :  name = Name of CGI parameter to read
+ *          4  :  pvalue = destination for value.  Will be malloc()'d.
+ *                         Set to NULL on error.
+ *
+ * Returns     :  JB_ERR_OK         on success
+ *                JB_ERR_MEMORY     on out-of-memory
+ *                JB_ERR_CGI_PARAMS if the parameter was not specified
+ *                                  or is not valid.
+ *
+ *********************************************************************/
+static jb_err get_url_spec_param(struct client_state *csp,
+                                 const struct map *parameters,
+                                 const char *name,
+                                 char **pvalue)
+{
+   const char *orig_param;
+   char *param;
+   char *s;
+   struct url_spec compiled[1];
+   jb_err err;
+
+   assert(csp);
+   assert(parameters);
+   assert(name);
+   assert(pvalue);
+
+   *pvalue = NULL;
+
+   orig_param = lookup(parameters, name);
+   if (!*orig_param)
+   {
+      return JB_ERR_CGI_PARAMS;
+   }
+
+   /* Copy and trim whitespace */
+   param = strdup(orig_param);
+   if (param == NULL)
+   {
+      return JB_ERR_MEMORY;
+   }
+   chomp(param);
+
+   /* Must be non-empty, and can't allow 1st character to be '{' */
+   if (param[0] == '\0' || param[0] == '{')
+   {
+      free(param);
+      return JB_ERR_CGI_PARAMS;
+   }
+
+   /* Check for embedded newlines */
+   for (s = param; *s != '\0'; s++)
+   {
+      if ((*s == '\r') || (*s == '\n'))
+      {
+         free(param);
+         return JB_ERR_CGI_PARAMS;
+      }
+   }
+
+   /* Check that regex is valid */
+   s = strdup(param);
+   if (s == NULL)
+   {
+      free(param);
+      return JB_ERR_MEMORY;
+   }
+   err = create_url_spec(compiled, s);
+   free(s);
+   if (err)
+   {
+      free(param);
+      return (err == JB_ERR_MEMORY) ? JB_ERR_MEMORY : JB_ERR_CGI_PARAMS;
+   }
+   free_url_spec(compiled);
+
+   if (param[strlen(param) - 1] == '\\')
+   {
+      /*
+       * Must protect trailing '\\' from becoming line continuation character.
+       * Two methods: 1) If it's a domain only, add a trailing '/'.
+       * 2) For path, add the do-nothing PCRE expression (?:) to the end
+       */
+      if (strchr(param, '/') == NULL)
+      {
+         err = string_append(&param, "/");
+      }
+      else
+      {
+         err = string_append(&param, "(?:)");
+      }
+      if (err)
+      {
+         return err;
+      }
+
+      /* Check that the modified regex is valid */
+      s = strdup(param);
+      if (s == NULL)
+      {
+         free(param);
+         return JB_ERR_MEMORY;
+      }
+      err = create_url_spec(compiled, s);
+      free(s);
+      if (err)
+      {
+         free(param);
+         return (err == JB_ERR_MEMORY) ? JB_ERR_MEMORY : JB_ERR_CGI_PARAMS;
+      }
+      free_url_spec(compiled);
+   }
+
+   *pvalue = param;
+   return JB_ERR_OK;
+}
+
+/*********************************************************************
+ *
+ * Function    :  map_radio
+ *
+ * Description :  Map a set of radio button values.  E.g. if you have
+ *                3 radio buttons, declare them as:
+ *                  <option type="radio" name="xyz" @xyz-a@>
+ *                  <option type="radio" name="xyz" @xyz-b@>
+ *                  <option type="radio" name="xyz" @xyz-c@>
+ *                Then map one of the @xyz-?@ variables to "checked"
+ *                and all the others to empty by calling:
+ *                map_radio(exports, "xyz", "abc", sel)
+ *                Where 'sel' is 'a', 'b', or 'c'.
+ *
+ * Parameters  :
+ *          1  :  exports = Exports map to modify.
+ *          2  :  optionname = name for map
+ *          3  :  values = null-terminated list of values;
+ *          4  :  value = Selected value.
+ *
+ * CGI Parameters : None
+ *
+ * Returns     :  JB_ERR_OK     on success
+ *                JB_ERR_MEMORY on out-of-memory
+ *
+ *********************************************************************/
+static jb_err map_radio(struct map * exports,
+                        const char * optionname,
+                        const char * values,
+                        int value)
+{
+   size_t len;
+   char * buf;
+   char * p;
+   char c;
+
+   assert(exports);
+   assert(optionname);
+   assert(values);
+
+   len = strlen(optionname);
+   buf = malloc(len + 3);
+   if (buf == NULL)
+   {
+      return JB_ERR_MEMORY;
+   }
+
+   strcpy(buf, optionname);
+   p = buf + len;
+   *p++ = '-';
+   p[1] = '\0';
+
+   while ((c = *values++) != '\0')
+   {
+      if (c != value)
+      {
+         *p = c;
+         if (map(exports, buf, 1, "", 1))
+         {
+            return JB_ERR_MEMORY;
+         }
+      }
+   }
+
+   *p = value;
+   return map(exports, buf, 0, "checked", 1);
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  cgi_error_modified
+ *
+ * Description :  CGI function that is called when a file is modified
+ *                outside the CGI editor.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  rsp = http_response data structure for output
+ *          3  :  filename = The file that was modified.
+ *
+ * CGI Parameters : none
+ *
+ * Returns     :  JB_ERR_OK on success
+ *                JB_ERR_MEMORY on out-of-memory error.
+ *
+ *********************************************************************/
+jb_err cgi_error_modified(struct client_state *csp,
+                          struct http_response *rsp,
+                          const char *filename)
+{
+   struct map *exports;
+   jb_err err;
+
+   assert(csp);
+   assert(rsp);
+   assert(filename);
+
+   if (NULL == (exports = default_exports(csp, NULL)))
+   {
+      return JB_ERR_MEMORY;
+   }
+
+   err = map(exports, "f", 1, html_encode(filename), 0);
+   if (err)
+   {
+      free_map(exports);
+      return err;
+   }
+
+   return template_fill_for_cgi(csp, "cgi-error-modified", exports, rsp);
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  cgi_error_parse
+ *
+ * Description :  CGI function that is called when a file cannot
+ *                be parsed by the CGI editor.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  rsp = http_response data structure for output
+ *          3  :  file = The file that was modified.
+ *
+ * CGI Parameters : none
+ *
+ * Returns     :  JB_ERR_OK on success
+ *                JB_ERR_MEMORY on out-of-memory error.
+ *
+ *********************************************************************/
+jb_err cgi_error_parse(struct client_state *csp,
+                       struct http_response *rsp,
+                       struct editable_file *file)
+{
+   struct map *exports;
+   jb_err err;
+   struct file_line *cur_line;
+
+   assert(csp);
+   assert(rsp);
+   assert(file);
+
+   if (NULL == (exports = default_exports(csp, NULL)))
+   {
+      return JB_ERR_MEMORY;
+   }
+
+   err = map(exports, "f", 1, file->identifier, 1);
+   if (!err) err = map(exports, "parse-error", 1, html_encode(file->parse_error_text), 0);
+
+   cur_line = file->parse_error;
+   assert(cur_line);
+
+   if (!err) err = map(exports, "line-raw", 1, html_encode(cur_line->raw), 0);
+   if (!err) err = map(exports, "line-data", 1, html_encode(cur_line->unprocessed), 0);
+
+   if (err)
+   {
+      free_map(exports);
+      return err;
+   }
+
+   return template_fill_for_cgi(csp, "cgi-error-parse", exports, rsp);
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  cgi_error_file
+ *
+ * Description :  CGI function that is called when a file cannot be
+ *                opened by the CGI editor.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  rsp = http_response data structure for output
+ *          3  :  filename = The file that was modified.
+ *
+ * CGI Parameters : none
+ *
+ * Returns     :  JB_ERR_OK on success
+ *                JB_ERR_MEMORY on out-of-memory error.
+ *
+ *********************************************************************/
+jb_err cgi_error_file(struct client_state *csp,
+                      struct http_response *rsp,
+                      const char *filename)
+{
+   struct map *exports;
+   jb_err err;
+
+   assert(csp);
+   assert(rsp);
+   assert(filename);
+
+   if (NULL == (exports = default_exports(csp, NULL)))
+   {
+      return JB_ERR_MEMORY;
+   }
+
+   err = map(exports, "f", 1, html_encode(filename), 0);
+   if (err)
+   {
+      free_map(exports);
+      return err;
+   }
+
+   return template_fill_for_cgi(csp, "cgi-error-file", exports, rsp);
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  cgi_error_file
+ *
+ * Description :  CGI function that is called when a file cannot be
+ *                opened for writing by the CGI editor.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  rsp = http_response data structure for output
+ *          3  :  filename = The file that we can't write to
+ *
+ * CGI Parameters : none
+ *
+ * Returns     :  JB_ERR_OK on success
+ *                JB_ERR_MEMORY on out-of-memory error.
+ *
+ *********************************************************************/
+jb_err cgi_error_file_read_only(struct client_state *csp,
+                                struct http_response *rsp,
+                                const char *filename)
+{
+   struct map *exports;
+   jb_err err;
+
+   assert(csp);
+   assert(rsp);
+   assert(filename);
+
+   if (NULL == (exports = default_exports(csp, NULL)))
+   {
+      return JB_ERR_MEMORY;
+   }
+
+   err = map(exports, "f", 1, html_encode(filename), 0);
+   if (err)
+   {
+      free_map(exports);
+      return err;
+   }
+
+   return template_fill_for_cgi(csp, "cgi-error-file-read-only", exports, rsp);
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  cgi_error_disabled
+ *
+ * Description :  CGI function that is called if the actions editor
+ *                is called although it's disabled in config
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  rsp = http_response data structure for output
+ *
+ * CGI Parameters : none
+ *
+ * Returns     :  JB_ERR_OK on success
+ *                JB_ERR_MEMORY on out-of-memory error.
+ *
+ *********************************************************************/
+jb_err cgi_error_disabled(struct client_state *csp,
+                          struct http_response *rsp)
+{
+   struct map *exports;
+
+   assert(csp);
+   assert(rsp);
+
+   if (NULL == (exports = default_exports(csp, NULL)))
+   {
+      return JB_ERR_MEMORY;
+   }
+
+   return template_fill_for_cgi(csp, "cgi-error-disabled", exports, rsp);
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  cgi_edit_actions
+ *
+ * Description :  CGI function that allows the user to choose which
+ *                actions file to edit.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  rsp = http_response data structure for output
+ *          3  :  parameters = map of cgi parameters
+ *
+ * CGI Parameters : None
+ *
+ * Returns     :  JB_ERR_OK on success
+ *                JB_ERR_MEMORY on out-of-memory error
+ *
+ *********************************************************************/
+jb_err cgi_edit_actions(struct client_state *csp,
+                        struct http_response *rsp,
+                        const struct map *parameters)
+{
+
+   if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
+   {
+      return cgi_error_disabled(csp, rsp);
+   }
+
+   /* FIXME: Incomplete */
+   rsp->status = strdup("302 Local Redirect from Privoxy");
+   if (rsp->status == NULL)
+   {
+      return JB_ERR_MEMORY;
+   }
+   if (enlist_unique_header(rsp->headers, "Location",
+      CGI_PREFIX "edit-actions-list?f=default"))
+   {
+      free(rsp->status);
+      rsp->status = NULL;
+      return JB_ERR_MEMORY;
+   }
+
+   return JB_ERR_OK;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  cgi_edit_actions_list
+ *
+ * Description :  CGI function that edits the actions list.
+ *                FIXME: This function shouldn't FATAL ever.
+ *                FIXME: This function doesn't check the retval of map()
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  rsp = http_response data structure for output
+ *          3  :  parameters = map of cgi parameters
+ *
+ * CGI Parameters : filename
+ *
+ * Returns     :  JB_ERR_OK     on success
+ *                JB_ERR_MEMORY on out-of-memory
+ *                JB_ERR_FILE   if the file cannot be opened or
+ *                              contains no data
+ *                JB_ERR_CGI_PARAMS if "filename" was not specified
+ *                                  or is not valid.
+ *
+ *********************************************************************/
+jb_err cgi_edit_actions_list(struct client_state *csp,
+                             struct http_response *rsp,
+                             const struct map *parameters)
+{
+   char * section_template;
+   char * url_template;
+   char * sections;
+   char * urls;
+   char buf[150];
+   char * s;
+   struct map * exports;
+   struct map * section_exports;
+   struct map * url_exports;
+   struct editable_file * file;
+   struct file_line * cur_line;
+   unsigned line_number = 0;
+   unsigned prev_section_line_number = ((unsigned) (-1));
+   int i, url_1_2;
+   struct file_list * fl;
+   struct url_actions * b;
+   char * buttons = NULL;
+   jb_err err;
+
+   if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
+   {
+      return cgi_error_disabled(csp, rsp);
+   }
+
+   if (NULL == (exports = default_exports(csp, NULL)))
+   {
+      edit_free_file(file);
+      return JB_ERR_MEMORY;
+   }
+
+   /* Load actions file */
+   err = edit_read_actions_file(csp, rsp, parameters, 0, &file);
+   if (err)
+   {
+      /* No filename specified, can't read file, or out of memory. */
+      return (err == JB_ERR_FILE ? JB_ERR_OK : err);
+   }
+
+   /* Find start of actions in file */
+   cur_line = file->lines;
+   line_number = 1;
+   while ((cur_line != NULL) && (cur_line->type != FILE_LINE_ACTION))
+   {
+      cur_line = cur_line->next;
+      line_number++;
+   }
+
+   /*
+    * Conventional actions files should have a match all block
+    * at the start:
+    * cur_line             = {...global actions...}
+    * cur_line->next       = /
+    * cur_line->next->next = {...actions...} or EOF
+    */
+   if ( (cur_line != NULL)
+     && (cur_line->type == FILE_LINE_ACTION)
+     && (cur_line->next != NULL)
+     && (cur_line->next->type == FILE_LINE_URL)
+     && (0 == strcmp(cur_line->next->unprocessed, "/"))
+     && ( (cur_line->next->next == NULL)
+       || (cur_line->next->next->type != FILE_LINE_URL)
+      ) )
+   {
+      /*
+       * Generate string with buttons to set actions for "/" to
+       * any predefined set of actions (named standard.*, probably
+       * residing in standard.action).
+       */
+
+      err = template_load(csp, &section_template, "edit-actions-list-button", 0);
+      if (err)
+      {
+         edit_free_file(file);
+         free_map(exports);
+         if (err == JB_ERR_FILE)
+         {
+            return cgi_error_no_template(csp, rsp, "edit-actions-list-button");
+         }
+         return err;
+      }
+
+      err = template_fill(&section_template, exports);
+      if (err)
+      {
+         edit_free_file(file);
+         free_map(exports);
+         return err;
+      }
+
+      buttons = strdup("");
+      for (i = 0; i < MAX_ACTION_FILES; i++)
+      {
+         if (((fl = csp->actions_list[i]) != NULL) && ((b = fl->f) != NULL))
+         {
+            for (b = b->next; NULL != b; b = b->next)
+            {
+               if (!strncmp(b->url->spec, "standard.", 9) && *(b->url->spec + 9) != '\0')
+               {
+                  if (err || (NULL == (section_exports = new_map())))
+                  {
+                     freez(buttons);
+                     free(section_template);
+                     edit_free_file(file);
+                     free_map(exports);
+                     return JB_ERR_MEMORY;
+                  }
+
+                  err = map(section_exports, "button-name", 1, b->url->spec + 9, 1);
+
+                  if (err || (NULL == (s = strdup(section_template))))
+                  {
+                     free_map(section_exports);
+                     freez(buttons);
+                     free(section_template);
+                     edit_free_file(file);
+                     free_map(exports);
+                     return JB_ERR_MEMORY;
+                  }
+
+                  if (!err) err = template_fill(&s, section_exports);
+                  free_map(section_exports);
+                  if (!err) err = string_join(&buttons, s);
+               }
+            }
+         }
+      }
+      freez(section_template);
+      if (!err) err = map(exports, "all-urls-buttons", 1, buttons, 0);
+
+      /*
+       * Conventional actions file, supply extra editing help.
+       * (e.g. don't allow them to make it an unconventional one).
+       */
+      if (!err) err = map_conditional(exports, "all-urls-present", 1);
+
+      snprintf(buf, 150, "%d", line_number);
+      if (!err) err = map(exports, "all-urls-s", 1, buf, 1);
+      snprintf(buf, 150, "%d", line_number + 2);
+      if (!err) err = map(exports, "all-urls-s-next", 1, buf, 1);
+      if (!err) err = map(exports, "all-urls-actions", 1,
+                          actions_to_html(csp, cur_line->data.action), 0);
+
+       /* Skip the 2 lines */
+      cur_line = cur_line->next->next;
+      line_number += 2;
+
+      /*
+       * Note that prev_section_line_number is NOT set here.
+       * This is deliberate and not a bug.  It stops a "Move up"
+       * option appearing on the next section.  Clicking "Move
+       * up" would make the actions file unconventional, which
+       * we don't want, so we hide this option.
+       */
+   }
+   else
+   {
+      /*
+       * Non-standard actions file - does not begin with
+       * the "All URLs" section.
+       */
+      if (!err) err = map_conditional(exports, "all-urls-present", 0);
+   }
+
+   /* Set up global exports */
+
+   if (!err) err = map(exports, "f", 1, file->identifier, 1);
+   if (!err) err = map(exports, "v", 1, file->version_str, 1);
+
+   /* Discourage private additions to default.action */
+
+   if (!err) err = map_conditional(exports, "default-action",
+                                   (strcmp("default", lookup(parameters, "f")) == 0));
+   if (err)
+   {
+      edit_free_file(file);
+      free_map(exports);
+      return err;
+   }
+
+   /* Should do all global exports above this point */
+
+   /* Load templates */
+
+   err = template_load(csp, &section_template, "edit-actions-list-section", 0);
+   if (err)
+   {
+      edit_free_file(file);
+      free_map(exports);
+      if (err == JB_ERR_FILE)
+      {
+         return cgi_error_no_template(csp, rsp, "edit-actions-list-section");
+      }
+      return err;
+   }
+
+   err = template_load(csp, &url_template, "edit-actions-list-url", 0);
+   if (err)
+   {
+      free(section_template);
+      edit_free_file(file);
+      free_map(exports);
+      if (err == JB_ERR_FILE)
+      {
+         return cgi_error_no_template(csp, rsp, "edit-actions-list-url");
+      }
+      return err;
+   }
+
+   err = template_fill(&section_template, exports);
+   if (err)
+   {
+      free(url_template);
+      edit_free_file(file);
+      free_map(exports);
+      free(url_template);
+      return err;
+   }
+
+   err = template_fill(&url_template, exports);
+   if (err)
+   {
+      free(section_template);
+      edit_free_file(file);
+      free_map(exports);
+      return err;
+   }
+
+   if (NULL == (sections = strdup("")))
+   {
+      free(section_template);
+      free(url_template);
+      edit_free_file(file);
+      free_map(exports);
+      return JB_ERR_MEMORY;
+   }
+
+   while ((cur_line != NULL) && (cur_line->type == FILE_LINE_ACTION))
+   {
+      if (NULL == (section_exports = new_map()))
+      {
+         free(sections);
+         free(section_template);
+         free(url_template);
+         edit_free_file(file);
+         free_map(exports);
+         return JB_ERR_MEMORY;
+      }
+
+      snprintf(buf, 150, "%d", line_number);
+      err = map(section_exports, "s", 1, buf, 1);
+      if (!err) err = map(section_exports, "actions", 1,
+                          actions_to_html(csp, cur_line->data.action), 0);
+
+      if ( (!err)
+        && (cur_line->next != NULL)
+        && (cur_line->next->type == FILE_LINE_URL))
+      {
+         /* This section contains at least one URL, don't allow delete */
+         err = map_block_killer(section_exports, "empty-section");
+      }
+      else
+      {
+         if (!err) err = map_block_keep(section_exports, "empty-section");
+      }
+
+      if (prev_section_line_number != ((unsigned)(-1)))
+      {
+         /* Not last section */
+         snprintf(buf, 150, "%d", prev_section_line_number);
+         if (!err) err = map(section_exports, "s-prev", 1, buf, 1);
+         if (!err) err = map_block_keep(section_exports, "s-prev-exists");
+      }
+      else
+      {
+         /* Last section */
+         if (!err) err = map_block_killer(section_exports, "s-prev-exists");
+      }
+      prev_section_line_number = line_number;
+
+      if (err)
+      {
+         free(sections);
+         free(section_template);
+         free(url_template);
+         edit_free_file(file);
+         free_map(exports);
+         free_map(section_exports);
+         return err;
+      }
+
+      /* Should do all section-specific exports above this point */
+
+      if (NULL == (urls = strdup("")))
+      {
+         free(sections);
+         free(section_template);
+         free(url_template);
+         edit_free_file(file);
+         free_map(exports);
+         free_map(section_exports);
+         return JB_ERR_MEMORY;
+      }
+
+      url_1_2 = 2;
+
+      cur_line = cur_line->next;
+      line_number++;
+
+      while ((cur_line != NULL) && (cur_line->type == FILE_LINE_URL))
+      {
+         if (NULL == (url_exports = new_map()))
+         {
+            free(urls);
+            free(sections);
+            free(section_template);
+            free(url_template);
+            edit_free_file(file);
+            free_map(exports);
+            free_map(section_exports);
+            return JB_ERR_MEMORY;
+         }
+
+         snprintf(buf, 150, "%d", line_number);
+         err = map(url_exports, "p", 1, buf, 1);
+
+         snprintf(buf, 150, "%d", url_1_2);
+         if (!err) err = map(url_exports, "url-1-2", 1, buf, 1);
+
+         if (!err) err = map(url_exports, "url-html", 1,
+                             html_encode(cur_line->unprocessed), 0);
+         if (!err) err = map(url_exports, "url", 1,
+                             url_encode(cur_line->unprocessed), 0);
+
+         if (err)
+         {
+            free(urls);
+            free(sections);
+            free(section_template);
+            free(url_template);
+            edit_free_file(file);
+            free_map(exports);
+            free_map(section_exports);
+            free_map(url_exports);
+            return err;
+         }
+
+         if (NULL == (s = strdup(url_template)))
+         {
+            free(urls);
+            free(sections);
+            free(section_template);
+            free(url_template);
+            edit_free_file(file);
+            free_map(exports);
+            free_map(section_exports);
+            free_map(url_exports);
+            return JB_ERR_MEMORY;
+         }
+
+         err = template_fill(&s, section_exports);
+         if (!err) err = template_fill(&s, url_exports);
+         if (!err) err = string_append(&urls, s);
+
+         free_map(url_exports);
+         freez(s);
+
+         if (err)
+         {
+            freez(urls);
+            free(sections);
+            free(section_template);
+            free(url_template);
+            edit_free_file(file);
+            free_map(exports);
+            free_map(section_exports);
+            return err;
+         }
+
+         url_1_2 = 3 - url_1_2;
+
+         cur_line = cur_line->next;
+         line_number++;
+      }
+
+      err = map(section_exports, "urls", 1, urls, 0);
+
+      /* Could also do section-specific exports here, but it wouldn't be as fast */
+
+      if ( (cur_line != NULL)
+        && (cur_line->type == FILE_LINE_ACTION))
+      {
+         /* Not last section */
+         snprintf(buf, 150, "%d", line_number);
+         if (!err) err = map(section_exports, "s-next", 1, buf, 1);
+         if (!err) err = map_block_keep(section_exports, "s-next-exists");
+      }
+      else
+      {
+         /* Last section */
+         if (!err) err = map_block_killer(section_exports, "s-next-exists");
+      }
+
+      if (err)
+      {
+         free(sections);
+         free(section_template);
+         free(url_template);
+         edit_free_file(file);
+         free_map(exports);
+         free_map(section_exports);
+         return err;
+      }
+
+      if (NULL == (s = strdup(section_template)))
+      {
+         free(sections);
+         free(section_template);
+         free(url_template);
+         edit_free_file(file);
+         free_map(exports);
+         free_map(section_exports);
+         return JB_ERR_MEMORY;
+      }
+
+      err = template_fill(&s, section_exports);
+      if (!err) err = string_append(&sections, s);
+
+      freez(s);
+      free_map(section_exports);
+
+      if (err)
+      {
+         freez(sections);
+         free(section_template);
+         free(url_template);
+         edit_free_file(file);
+         free_map(exports);
+         return err;
+      }
+   }
+
+   edit_free_file(file);
+   free(section_template);
+   free(url_template);
+
+   err = map(exports, "sections", 1, sections, 0);
+   if (err)
+   {
+      free_map(exports);
+      return err;
+   }
+
+   /* Could also do global exports here, but it wouldn't be as fast */
+
+   return template_fill_for_cgi(csp, "edit-actions-list", exports, rsp);
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  cgi_edit_actions_for_url
+ *
+ * Description :  CGI function that edits the Actions list.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  rsp = http_response data structure for output
+ *          3  :  parameters = map of cgi parameters
+ *
+ * CGI Parameters : None
+ *
+ * Returns     :  JB_ERR_OK     on success
+ *                JB_ERR_MEMORY on out-of-memory
+ *                JB_ERR_CGI_PARAMS if the CGI parameters are not
+ *                                  specified or not valid.
+ *
+ *********************************************************************/
+jb_err cgi_edit_actions_for_url(struct client_state *csp,
+                                struct http_response *rsp,
+                                const struct map *parameters)
+{
+   struct map * exports;
+   unsigned sectionid;
+   struct editable_file * file;
+   struct file_line * cur_line;
+   unsigned line_number;
+   jb_err err;
+   struct file_list *filter_file;
+   struct re_filterfile_spec *filter_group;
+
+   if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
+   {
+      return cgi_error_disabled(csp, rsp);
+   }
+
+   err = get_number_param(csp, parameters, "s", &sectionid);
+   if (err)
+   {
+      return err;
+   }
+
+   err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
+   if (err)
+   {
+      /* No filename specified, can't read file, modified, or out of memory. */
+      return (err == JB_ERR_FILE ? JB_ERR_OK : err);
+   }
+
+   cur_line = file->lines;
+
+   for (line_number = 1; (cur_line != NULL) && (line_number < sectionid); line_number++)
+   {
+      cur_line = cur_line->next;
+   }
+
+   if ( (cur_line == NULL)
+     || (line_number != sectionid)
+     || (sectionid < 1)
+     || (cur_line->type != FILE_LINE_ACTION))
+   {
+      /* Invalid "sectionid" parameter */
+      edit_free_file(file);
+      return JB_ERR_CGI_PARAMS;
+   }
+
+   if (NULL == (exports = default_exports(csp, NULL)))
+   {
+      edit_free_file(file);
+      return JB_ERR_MEMORY;
+   }
+
+   err = map(exports, "f", 1, file->identifier, 1);
+   if (!err) err = map(exports, "v", 1, file->version_str, 1);
+   if (!err) err = map(exports, "s", 1, url_encode(lookup(parameters, "s")), 0);
+
+   if (!err) err = actions_to_radio(exports, cur_line->data.action);
+
+   filter_file = csp->rlist;
+   filter_group = ((filter_file != NULL) ? filter_file->f : NULL);
+
+   if (!err) err = map_conditional(exports, "any-filters-defined", (filter_group != NULL));
+
+   if (err)
+   {
+      edit_free_file(file);
+      free_map(exports);
+      return err;
+   }
+
+   if (filter_group == NULL)
+   {
+      err = map(exports, "filter-params", 1, "", 1);
+   }
+   else
+   {
+      /* We have some entries in the filter list */
+      char * result;
+      int index = 0;
+      char * filter_template;
+
+      err = template_load(csp, &filter_template, "edit-actions-for-url-filter", 0);
+      if (err)
+      {
+         edit_free_file(file);
+         free_map(exports);
+         if (err == JB_ERR_FILE)
+         {
+            return cgi_error_no_template(csp, rsp, "edit-actions-for-url-filter");
+         }
+         return err;
+      }
+
+      err = template_fill(&filter_template, exports);
+
+      result = strdup("");
+
+      for (;(!err) && (filter_group != NULL); filter_group = filter_group->next)
+      {
+         char current_mode = 'x';
+         struct list_entry *filter_name;
+         char * this_line;
+         struct map *line_exports;
+         char number[20];
+
+         filter_name = cur_line->data.action->multi_add[ACTION_MULTI_FILTER]->first;
+         while ((filter_name != NULL)
+             && (0 != strcmp(filter_group->name, filter_name->str)))
+         {
+              filter_name = filter_name->next;
+         }
+
+         if (filter_name != NULL)
+         {
+            current_mode = 'y';
+         }
+         else
+         {
+            filter_name = cur_line->data.action->multi_remove[ACTION_MULTI_FILTER]->first;
+            while ((filter_name != NULL)
+                && (0 != strcmp(filter_group->name, filter_name->str)))
+            {
+                 filter_name = filter_name->next;
+            }
+            if (filter_name != NULL)
+            {
+               current_mode = 'n';
+            }
+         }
+
+         /* Generate a unique serial number */
+         snprintf(number, sizeof(number), "%x", index++);
+         number[sizeof(number) - 1] = '\0';
+
+         line_exports = new_map();
+         if (line_exports == NULL)
+         {
+            err = JB_ERR_MEMORY;
+            freez(result);
+         }
+         else
+         {
+            if (!err) err = map(line_exports, "index", 1, number, 1);
+            if (!err) err = map(line_exports, "name",  1, filter_group->name, 1);
+            if (!err) err = map(line_exports, "description",  1, filter_group->description, 1);
+            if (!err) err = map_radio(line_exports, "this-filter", "ynx", current_mode);
+
+            this_line = NULL;
+            if (!err)
+            {
+               this_line = strdup(filter_template);
+               if (this_line == NULL) err = JB_ERR_MEMORY;
+            }
+            if (!err) err = template_fill(&this_line, line_exports);
+            string_join(&result, this_line);
+
+            free_map(line_exports);
+         }
+      }
+
+      freez(filter_template);
+
+      if (!err)
+      {
+         err = map(exports, "filter-params", 1, result, 0);
+      }
+      else
+      {
+         freez(result);
+      }
+   }
+
+   if (!err) err = map_radio(exports, "filter-all", "nx",
+      (cur_line->data.action->multi_remove_all[ACTION_MULTI_FILTER] ? 'n' : 'x'));
+
+   edit_free_file(file);
+
+   if (err)
+   {
+      free_map(exports);
+      return err;
+   }
+
+   return template_fill_for_cgi(csp, "edit-actions-for-url", exports, rsp);
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  cgi_edit_actions_submit
+ *
+ * Description :  CGI function that actually edits the Actions list.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  rsp = http_response data structure for output
+ *          3  :  parameters = map of cgi parameters
+ *
+ * CGI Parameters : None
+ *
+ * Returns     :  JB_ERR_OK     on success
+ *                JB_ERR_MEMORY on out-of-memory
+ *                JB_ERR_CGI_PARAMS if the CGI parameters are not
+ *                                  specified or not valid.
+ *
+ *********************************************************************/
+jb_err cgi_edit_actions_submit(struct client_state *csp,
+                               struct http_response *rsp,
+                               const struct map *parameters)
+{
+   unsigned sectionid;
+   char * actiontext;
+   char * newtext;
+   size_t len;
+   struct editable_file * file;
+   struct file_line * cur_line;
+   unsigned line_number;
+   char * target;
+   jb_err err;
+   int index;
+   const char * action_set_name;
+   char ch;
+   struct file_list * fl;
+   struct url_actions * b;
+
+   if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
+   {
+      return cgi_error_disabled(csp, rsp);
+   }
+
+   err = get_number_param(csp, parameters, "s", &sectionid);
+   if (err)
+   {
+      return err;
+   }
+
+   err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
+   if (err)
+   {
+      /* No filename specified, can't read file, modified, or out of memory. */
+      return (err == JB_ERR_FILE ? JB_ERR_OK : err);
+   }
+
+   cur_line = file->lines;
+
+   for (line_number = 1; (cur_line != NULL) && (line_number < sectionid); line_number++)
+   {
+      cur_line = cur_line->next;
+   }
+
+   if ( (cur_line == NULL)
+     || (line_number != sectionid)
+     || (sectionid < 1)
+     || (cur_line->type != FILE_LINE_ACTION))
+   {
+      /* Invalid "sectionid" parameter */
+      edit_free_file(file);
+      return JB_ERR_CGI_PARAMS;
+   }
+
+   get_string_param(parameters, "p", &action_set_name);
+   if (action_set_name != NULL)
+   {
+      for (index = 0; index < MAX_ACTION_FILES; index++)
+      {
+         if (((fl = csp->actions_list[index]) != NULL) && ((b = fl->f) != NULL))
+         {
+            for (b = b->next; NULL != b; b = b->next)
+            {
+               if (!strncmp(b->url->spec, "standard.", 9) && !strcmp(b->url->spec + 9, action_set_name))
+               {
+                  copy_action(cur_line->data.action, b->action); 
+                  goto found;
+               }
+            }
+         }
+      }
+      edit_free_file(file);
+      return JB_ERR_CGI_PARAMS;
+
+      found: ;
+   }
+   else
+   {
+      err = actions_from_radio(parameters, cur_line->data.action);
+   }
+
+   if(err)
+   {
+      /* Out of memory */
+      edit_free_file(file);
+      return err;
+   }
+
+   ch = get_char_param(parameters, "filter_all");
+   if (ch == 'N')
+   {
+      list_remove_all(cur_line->data.action->multi_add[ACTION_MULTI_FILTER]);
+      list_remove_all(cur_line->data.action->multi_remove[ACTION_MULTI_FILTER]);
+      cur_line->data.action->multi_remove_all[ACTION_MULTI_FILTER] = 1;
+   }
+   else if (ch == 'X')
+   {
+      cur_line->data.action->multi_remove_all[ACTION_MULTI_FILTER] = 0;
+   }
+
+   for (index = 0; !err; index++)
+   {
+      char key_value[30];
+      char key_name[30];
+      const char *name;
+      char value;
+
+      /* Generate the keys */
+      snprintf(key_value, sizeof(key_value), "filter_r%x", index);
+      key_value[sizeof(key_value) - 1] = '\0';
+      snprintf(key_name, sizeof(key_name), "filter_n%x", index);
+      key_name[sizeof(key_name) - 1] = '\0';
+
+      err = get_string_param(parameters, key_name, &name);
+      if (err) break;
+
+      if (name == NULL)
+      {
+         /* End of list */
+         break;
+      }
+
+      value = get_char_param(parameters, key_value);
+      if (value == 'Y')
+      {
+         list_remove_item(cur_line->data.action->multi_add[ACTION_MULTI_FILTER], name);
+         if (!err) err = enlist(cur_line->data.action->multi_add[ACTION_MULTI_FILTER], name);
+         list_remove_item(cur_line->data.action->multi_remove[ACTION_MULTI_FILTER], name);
+      }
+      else if (value == 'N')
+      {
+         list_remove_item(cur_line->data.action->multi_add[ACTION_MULTI_FILTER], name);
+         if (!cur_line->data.action->multi_remove_all[ACTION_MULTI_FILTER])
+         {
+            list_remove_item(cur_line->data.action->multi_remove[ACTION_MULTI_FILTER], name);
+            if (!err) err = enlist(cur_line->data.action->multi_remove[ACTION_MULTI_FILTER], name);
+         }
+      }
+      else if (value == 'X')
+      {
+         list_remove_item(cur_line->data.action->multi_add[ACTION_MULTI_FILTER], name);
+         list_remove_item(cur_line->data.action->multi_remove[ACTION_MULTI_FILTER], name);
+      }
+   }
+
+   if(err)
+   {
+      /* Out of memory */
+      edit_free_file(file);
+      return err;
+   }
+
+   if (NULL == (actiontext = actions_to_text(cur_line->data.action)))
+   {
+      /* Out of memory */
+      edit_free_file(file);
+      return JB_ERR_MEMORY;
+   }
+
+   len = strlen(actiontext);
+   if (len == 0)
+   {
+      /*
+       * Empty action - must special-case this.
+       * Simply setting len to 1 is sufficient...
+       */
+      len = 1;
+   }
+
+   if (NULL == (newtext = malloc(len + 2)))
+   {
+      /* Out of memory */
+      free(actiontext);
+      edit_free_file(file);
+      return JB_ERR_MEMORY;
+   }
+   strcpy(newtext, actiontext);
+   free(actiontext);
+   newtext[0]       = '{';
+   newtext[len]     = '}';
+   newtext[len + 1] = '\0';
+
+   freez(cur_line->raw);
+   freez(cur_line->unprocessed);
+   cur_line->unprocessed = newtext;
+
+   err = edit_write_file(file);
+   if (err)
+   {
+      /* Error writing file */
+      if (err == JB_ERR_FILE)
+      {
+         /* Read-only file. */
+         err = cgi_error_file_read_only(csp, rsp, file->identifier);
+      }
+      edit_free_file(file);
+      return err;
+   }
+
+   target = strdup(CGI_PREFIX "edit-actions-list?f=");
+   string_append(&target, file->identifier);
+   string_join(&target, section_target(sectionid));
+
+
+   edit_free_file(file);
+
+   if (target == NULL)
+   {
+      /* Out of memory */
+      return JB_ERR_MEMORY;
+   }
+
+   rsp->status = strdup("302 Local Redirect from Privoxy");
+   if (rsp->status == NULL)
+   {
+      free(target);
+      return JB_ERR_MEMORY;
+   }
+   err = enlist_unique_header(rsp->headers, "Location", target);
+   free(target);
+
+   return err;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  cgi_edit_actions_url
+ *
+ * Description :  CGI function that actually edits a URL pattern in
+ *                an actions file.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  rsp = http_response data structure for output
+ *          3  :  parameters = map of cgi parameters
+ *
+ * CGI Parameters :
+ *    filename : Identifies the file to edit
+ *         ver : File's last-modified time
+ *     section : Line number of section to edit
+ *     pattern : Line number of pattern to edit
+ *      newval : New value for pattern
+ *
+ * Returns     :  JB_ERR_OK     on success
+ *                JB_ERR_MEMORY on out-of-memory
+ *                JB_ERR_CGI_PARAMS if the CGI parameters are not
+ *                                  specified or not valid.
+ *
+ *********************************************************************/
+jb_err cgi_edit_actions_url(struct client_state *csp,
+                            struct http_response *rsp,
+                            const struct map *parameters)
+{
+   unsigned patternid;
+   char * new_pattern;
+   struct editable_file * file;
+   struct file_line * cur_line;
+   unsigned line_number;
+   unsigned section_start_line_number = 0;
+   char * target;
+   jb_err err;
+
+   if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
+   {
+      return cgi_error_disabled(csp, rsp);
+   }
+
+   err = get_number_param(csp, parameters, "p", &patternid);
+   if (err)
+   {
+      return err;
+   }
+   if (patternid < 1U)
+   {
+      return JB_ERR_CGI_PARAMS;
+   }
+
+   err = get_url_spec_param(csp, parameters, "u", &new_pattern);
+   if (err)
+   {
+      return err;
+   }
+
+   err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
+   if (err)
+   {
+      /* No filename specified, can't read file, modified, or out of memory. */
+      free(new_pattern);
+      return (err == JB_ERR_FILE ? JB_ERR_OK : err);
+   }
+
+   line_number = 1;
+   cur_line = file->lines;
+
+   while ((cur_line != NULL) && (line_number < patternid))
+   {
+      if (cur_line->type == FILE_LINE_ACTION)
+      {
+         section_start_line_number = line_number;
+      }      
+      cur_line = cur_line->next;
+      line_number++;
+   }
+
+   if ( (cur_line == NULL)
+     || (cur_line->type != FILE_LINE_URL))
+   {
+      /* Invalid "patternid" parameter */
+      free(new_pattern);
+      edit_free_file(file);
+      return JB_ERR_CGI_PARAMS;
+   }
+
+   /* At this point, the line to edit is in cur_line */
+
+   freez(cur_line->raw);
+   freez(cur_line->unprocessed);
+   cur_line->unprocessed = new_pattern;
+
+   err = edit_write_file(file);
+   if (err)
+   {
+      /* Error writing file */
+      if (err == JB_ERR_FILE)
+      {
+         /* Read-only file. */
+         err = cgi_error_file_read_only(csp, rsp, file->identifier);
+      }
+      edit_free_file(file);
+      return err;
+   }
+
+   target = strdup(CGI_PREFIX "edit-actions-list?f=");
+   string_append(&target, file->identifier);
+   string_join(&target, section_target(section_start_line_number));
+
+   edit_free_file(file);
+
+   if (target == NULL)
+   {
+      /* Out of memory */
+      return JB_ERR_MEMORY;
+   }
+
+   rsp->status = strdup("302 Local Redirect from Privoxy");
+   if (rsp->status == NULL)
+   {
+      free(target);
+      return JB_ERR_MEMORY;
+   }
+   err = enlist_unique_header(rsp->headers, "Location", target);
+   free(target);
+
+   return err;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  cgi_edit_actions_add_url
+ *
+ * Description :  CGI function that actually adds a URL pattern to
+ *                an actions file.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  rsp = http_response data structure for output
+ *          3  :  parameters = map of cgi parameters
+ *
+ * CGI Parameters :
+ *    filename : Identifies the file to edit
+ *         ver : File's last-modified time
+ *     section : Line number of section to edit
+ *      newval : New pattern
+ *
+ * Returns     :  JB_ERR_OK     on success
+ *                JB_ERR_MEMORY on out-of-memory
+ *                JB_ERR_CGI_PARAMS if the CGI parameters are not
+ *                                  specified or not valid.
+ *
+ *********************************************************************/
+jb_err cgi_edit_actions_add_url(struct client_state *csp,
+                                struct http_response *rsp,
+                                const struct map *parameters)
+{
+   unsigned sectionid;
+   char * new_pattern;
+   struct file_line * new_line;
+   struct editable_file * file;
+   struct file_line * cur_line;
+   unsigned line_number;
+   char * target;
+   jb_err err;
+
+   if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
+   {
+      return cgi_error_disabled(csp, rsp);
+   }
+
+   err = get_number_param(csp, parameters, "s", &sectionid);
+   if (err)
+   {
+      return err;
+   }
+   if (sectionid < 1U)
+   {
+      return JB_ERR_CGI_PARAMS;
+   }
+
+   err = get_url_spec_param(csp, parameters, "u", &new_pattern);
+   if (err)
+   {
+      return err;
+   }
+
+   err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
+   if (err)
+   {
+      /* No filename specified, can't read file, modified, or out of memory. */
+      free(new_pattern);
+      return (err == JB_ERR_FILE ? JB_ERR_OK : err);
+   }
+
+   line_number = 1;
+   cur_line = file->lines;
+
+   while ((cur_line != NULL) && (line_number < sectionid))
+   {
+      cur_line = cur_line->next;
+      line_number++;
+   }
+
+   if ( (cur_line == NULL)
+     || (cur_line->type != FILE_LINE_ACTION))
+   {
+      /* Invalid "sectionid" parameter */
+      free(new_pattern);
+      edit_free_file(file);
+      return JB_ERR_CGI_PARAMS;
+   }
+
+   /* At this point, the section header is in cur_line - add after this. */
+
+   /* Allocate the new line */
+   new_line = (struct file_line *)zalloc(sizeof(*new_line));
+   if (new_line == NULL)
+   {
+      free(new_pattern);
+      edit_free_file(file);
+      return JB_ERR_MEMORY;
+   }
+
+   /* Fill in the data members of the new line */
+   new_line->raw = NULL;
+   new_line->prefix = NULL;
+   new_line->unprocessed = new_pattern;
+   new_line->type = FILE_LINE_URL;
+
+   /* Link new_line into the list, after cur_line */
+   new_line->next = cur_line->next;
+   cur_line->next = new_line;
+
+   /* Done making changes, now commit */
+
+   err = edit_write_file(file);
+   if (err)
+   {
+      /* Error writing file */
+      if (err == JB_ERR_FILE)
+      {
+         /* Read-only file. */
+         err = cgi_error_file_read_only(csp, rsp, file->identifier);
+      }
+      edit_free_file(file);
+      return err;
+   }
+
+   target = strdup(CGI_PREFIX "edit-actions-list?f=");
+   string_append(&target, file->identifier);
+   string_join(&target, section_target(sectionid));
+
+   edit_free_file(file);
+
+   if (target == NULL)
+   {
+      /* Out of memory */
+      return JB_ERR_MEMORY;
+   }
+
+   rsp->status = strdup("302 Local Redirect from Privoxy");
+   if (rsp->status == NULL)
+   {
+      free(target);
+      return JB_ERR_MEMORY;
+   }
+   err = enlist_unique_header(rsp->headers, "Location", target);
+   free(target);
+
+   return err;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  cgi_edit_actions_remove_url
+ *
+ * Description :  CGI function that actually removes a URL pattern from
+ *                the actions file.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  rsp = http_response data structure for output
+ *          3  :  parameters = map of cgi parameters
+ *
+ * CGI Parameters :
+ *           f : (filename) Identifies the file to edit
+ *           v : (version) File's last-modified time
+ *           p : (pattern) Line number of pattern to remove
+ *
+ * Returns     :  JB_ERR_OK     on success
+ *                JB_ERR_MEMORY on out-of-memory
+ *                JB_ERR_CGI_PARAMS if the CGI parameters are not
+ *                                  specified or not valid.
+ *
+ *********************************************************************/
+jb_err cgi_edit_actions_remove_url(struct client_state *csp,
+                                   struct http_response *rsp,
+                                   const struct map *parameters)
+{
+   unsigned patternid;
+   struct editable_file * file;
+   struct file_line * cur_line;
+   struct file_line * prev_line;
+   unsigned line_number;
+   unsigned section_start_line_number = 0;
+   char * target;
+   jb_err err;
+
+   if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
+   {
+      return cgi_error_disabled(csp, rsp);
+   }
+
+   err = get_number_param(csp, parameters, "p", &patternid);
+   if (err)
+   {
+      return err;
+   }
+
+   err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
+   if (err)
+   {
+      /* No filename specified, can't read file, modified, or out of memory. */
+      return (err == JB_ERR_FILE ? JB_ERR_OK : err);
+   }
+
+   line_number = 1;
+   prev_line = NULL;
+   cur_line = file->lines;
+
+   while ((cur_line != NULL) && (line_number < patternid))
+   {
+      if (cur_line->type == FILE_LINE_ACTION)
+      {
+         section_start_line_number = line_number;
+      }
+      prev_line = cur_line;
+      cur_line = cur_line->next;
+      line_number++;
+   }
+
+   if ( (cur_line == NULL)
+     || (prev_line == NULL)
+     || (cur_line->type != FILE_LINE_URL))
+   {
+      /* Invalid "patternid" parameter */
+      edit_free_file(file);
+      return JB_ERR_CGI_PARAMS;
+   }
+
+   /* At this point, the line to remove is in cur_line, and the previous
+    * one is in prev_line
+    */
+
+   /* Unlink cur_line */
+   prev_line->next = cur_line->next;
+   cur_line->next = NULL;
+
+   /* Free cur_line */
+   edit_free_file_lines(cur_line);
+
+   err = edit_write_file(file);
+   if (err)
+   {
+      /* Error writing file */
+      if (err == JB_ERR_FILE)
+      {
+         /* Read-only file. */
+         err = cgi_error_file_read_only(csp, rsp, file->identifier);
+      }
+      edit_free_file(file);
+      return err;
+   }
+
+   target = strdup(CGI_PREFIX "edit-actions-list?f=");
+   string_append(&target, file->identifier);
+   string_join(&target, section_target(section_start_line_number));
+
+   edit_free_file(file);
+
+   if (target == NULL)
+   {
+      /* Out of memory */
+      return JB_ERR_MEMORY;
+   }
+
+   rsp->status = strdup("302 Local Redirect from Privoxy");
+   if (rsp->status == NULL)
+   {
+      free(target);
+      return JB_ERR_MEMORY;
+   }
+   err = enlist_unique_header(rsp->headers, "Location", target);
+   free(target);
+
+   return err;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  cgi_edit_actions_section_remove
+ *
+ * Description :  CGI function that actually removes a whole section from
+ *                the actions file.  The section must be empty first
+ *                (else JB_ERR_CGI_PARAMS).
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  rsp = http_response data structure for output
+ *          3  :  parameters = map of cgi parameters
+ *
+ * CGI Parameters :
+ *           f : (filename) Identifies the file to edit
+ *           v : (version) File's last-modified time
+ *           s : (section) Line number of section to edit
+ *
+ * Returns     :  JB_ERR_OK     on success
+ *                JB_ERR_MEMORY on out-of-memory
+ *                JB_ERR_CGI_PARAMS if the CGI parameters are not
+ *                                  specified or not valid.
+ *
+ *********************************************************************/
+jb_err cgi_edit_actions_section_remove(struct client_state *csp,
+                                       struct http_response *rsp,
+                                       const struct map *parameters)
+{
+   unsigned sectionid;
+   struct editable_file * file;
+   struct file_line * cur_line;
+   struct file_line * prev_line;
+   unsigned line_number;
+   char * target;
+   jb_err err;
+
+   if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
+   {
+      return cgi_error_disabled(csp, rsp);
+   }
+
+   err = get_number_param(csp, parameters, "s", &sectionid);
+   if (err)
+   {
+      return err;
+   }
+
+   err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
+   if (err)
+   {
+      /* No filename specified, can't read file, modified, or out of memory. */
+      return (err == JB_ERR_FILE ? JB_ERR_OK : err);
+   }
+
+   line_number = 1;
+   cur_line = file->lines;
+
+   prev_line = NULL;
+   while ((cur_line != NULL) && (line_number < sectionid))
+   {
+      prev_line = cur_line;
+      cur_line = cur_line->next;
+      line_number++;
+   }
+
+   if ( (cur_line == NULL)
+     || (cur_line->type != FILE_LINE_ACTION) )
+   {
+      /* Invalid "sectionid" parameter */
+      edit_free_file(file);
+      return JB_ERR_CGI_PARAMS;
+   }
+
+   if ( (cur_line->next != NULL)
+     && (cur_line->next->type == FILE_LINE_URL) )
+   {
+      /* Section not empty. */
+      edit_free_file(file);
+      return JB_ERR_CGI_PARAMS;
+   }
+
+   /* At this point, the line to remove is in cur_line, and the previous
+    * one is in prev_line
+    */
+
+   /* Unlink cur_line */
+   if (prev_line == NULL)
+   {
+      /* Removing the first line from the file */
+      file->lines = cur_line->next;
+   }
+   else
+   {
+      prev_line->next = cur_line->next;
+   }
+   cur_line->next = NULL;
+
+   /* Free cur_line */
+   edit_free_file_lines(cur_line);
+
+   err = edit_write_file(file);
+   if (err)
+   {
+      /* Error writing file */
+      if (err == JB_ERR_FILE)
+      {
+         /* Read-only file. */
+         err = cgi_error_file_read_only(csp, rsp, file->identifier);
+      }
+      edit_free_file(file);
+      return err;
+   }
+
+   target = strdup(CGI_PREFIX "edit-actions-list?f=");
+   string_append(&target, file->identifier);
+
+   edit_free_file(file);
+
+   if (target == NULL)
+   {
+      /* Out of memory */
+      return JB_ERR_MEMORY;
+   }
+
+   rsp->status = strdup("302 Local Redirect from Privoxy");
+   if (rsp->status == NULL)
+   {
+      free(target);
+      return JB_ERR_MEMORY;
+   }
+   err = enlist_unique_header(rsp->headers, "Location", target);
+   free(target);
+
+   return err;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  cgi_edit_actions_section_add
+ *
+ * Description :  CGI function that adds a new empty section to
+ *                an actions file.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  rsp = http_response data structure for output
+ *          3  :  parameters = map of cgi parameters
+ *
+ * CGI Parameters :
+ *           f : (filename) Identifies the file to edit
+ *           v : (version) File's last-modified time
+ *           s : (section) Line number of section to add after, 0 for
+ *               start of file.
+ *
+ * Returns     :  JB_ERR_OK     on success
+ *                JB_ERR_MEMORY on out-of-memory
+ *                JB_ERR_CGI_PARAMS if the CGI parameters are not
+ *                                  specified or not valid.
+ *
+ *********************************************************************/
+jb_err cgi_edit_actions_section_add(struct client_state *csp,
+                                    struct http_response *rsp,
+                                    const struct map *parameters)
+{
+   unsigned sectionid;
+   struct file_line * new_line;
+   char * new_text;
+   struct editable_file * file;
+   struct file_line * cur_line;
+   unsigned line_number;
+   char * target;
+   jb_err err;
+
+   if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
+   {
+      return cgi_error_disabled(csp, rsp);
+   }
+
+   err = get_number_param(csp, parameters, "s", &sectionid);
+   if (err)
+   {
+      return err;
+   }
+
+   err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
+   if (err)
+   {
+      /* No filename specified, can't read file, modified, or out of memory. */
+      return (err == JB_ERR_FILE ? JB_ERR_OK : err);
+   }
+
+   line_number = 1;
+   cur_line = file->lines;
+
+   if (sectionid < 1U)
+   {
+      /* Add to start of file */
+      if (cur_line != NULL)
+      {
+         /* There's something in the file, find the line before the first
+          * action.
+          */
+         while ( (cur_line->next != NULL)
+              && (cur_line->next->type != FILE_LINE_ACTION) )
+         {
+            cur_line = cur_line->next;
+            line_number++;
+         }
+      }
+   }
+   else
+   {
+      /* Add after stated section. */
+      while ((cur_line != NULL) && (line_number < sectionid))
+      {
+         cur_line = cur_line->next;
+         line_number++;
+      }
+
+      if ( (cur_line == NULL)
+        || (cur_line->type != FILE_LINE_ACTION))
+      {
+         /* Invalid "sectionid" parameter */
+         edit_free_file(file);
+         return JB_ERR_CGI_PARAMS;
+      }
+
+      /* Skip through the section to find the last line in it. */
+      while ( (cur_line->next != NULL)
+           && (cur_line->next->type != FILE_LINE_ACTION) )
+      {
+         cur_line = cur_line->next;
+         line_number++;
+      }
+   }
+
+   /* At this point, the last line in the previous section is in cur_line
+    * - add after this.  (Or if we need to add as the first line, cur_line
+    * will be NULL).
+    */
+
+   new_text = strdup("{}");
+   if (NULL == new_text)
+   {
+      edit_free_file(file);
+      return JB_ERR_MEMORY;
+   }
+
+   /* Allocate the new line */
+   new_line = (struct file_line *)zalloc(sizeof(*new_line));
+   if (new_line == NULL)
+   {
+      free(new_text);
+      edit_free_file(file);
+      return JB_ERR_MEMORY;
+   }
+
+   /* Fill in the data members of the new line */
+   new_line->raw = NULL;
+   new_line->prefix = NULL;
+   new_line->unprocessed = new_text;
+   new_line->type = FILE_LINE_ACTION;
+
+   if (cur_line != NULL)
+   {
+      /* Link new_line into the list, after cur_line */
+      new_line->next = cur_line->next;
+      cur_line->next = new_line;
+   }
+   else
+   {
+      /* Link new_line into the list, as first line */
+      new_line->next = file->lines;
+      file->lines = new_line;
+   }
+
+   /* Done making changes, now commit */
+
+   err = edit_write_file(file);
+   if (err)
+   {
+      /* Error writing file */
+      if (err == JB_ERR_FILE)
+      {
+         /* Read-only file. */
+         err = cgi_error_file_read_only(csp, rsp, file->identifier);
+      }
+      edit_free_file(file);
+      return err;
+   }
+
+   target = strdup(CGI_PREFIX "edit-actions-list?f=");
+   string_append(&target, file->identifier);
+
+   edit_free_file(file);
+
+   if (target == NULL)
+   {
+      /* Out of memory */
+      return JB_ERR_MEMORY;
+   }
+
+   rsp->status = strdup("302 Local Redirect from Privoxy");
+   if (rsp->status == NULL)
+   {
+      free(target);
+      return JB_ERR_MEMORY;
+   }
+   err = enlist_unique_header(rsp->headers, "Location", target);
+   free(target);
+
+   return err;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  cgi_edit_actions_section_swap
+ *
+ * Description :  CGI function that swaps the order of two sections
+ *                in the actions file.  Note that this CGI can actually
+ *                swap any two arbitrary sections, but the GUI interface
+ *                currently only allows consecutive sections to be
+ *                specified.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  rsp = http_response data structure for output
+ *          3  :  parameters = map of cgi parameters
+ *
+ * CGI Parameters :
+ *           f : (filename) Identifies the file to edit
+ *           v : (version) File's last-modified time
+ *          s1 : (section1) Line number of first section to swap
+ *          s2 : (section2) Line number of second section to swap
+ *
+ * Returns     :  JB_ERR_OK     on success
+ *                JB_ERR_MEMORY on out-of-memory
+ *                JB_ERR_CGI_PARAMS if the CGI parameters are not
+ *                                  specified or not valid.
+ *
+ *********************************************************************/
+jb_err cgi_edit_actions_section_swap(struct client_state *csp,
+                                     struct http_response *rsp,
+                                     const struct map *parameters)
+{
+   unsigned section1;
+   unsigned section2;
+   struct editable_file * file;
+   struct file_line * cur_line;
+   struct file_line * prev_line;
+   struct file_line * line_before_section1;
+   struct file_line * line_start_section1;
+   struct file_line * line_end_section1;
+   struct file_line * line_after_section1;
+   struct file_line * line_before_section2;
+   struct file_line * line_start_section2;
+   struct file_line * line_end_section2;
+   struct file_line * line_after_section2;
+   unsigned line_number;
+   char * target;
+   jb_err err;
+
+   if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_EDIT_ACTIONS))
+   {
+      return cgi_error_disabled(csp, rsp);
+   }
+
+   err = get_number_param(csp, parameters, "s1", &section1);
+   if (!err) err = get_number_param(csp, parameters, "s2", &section2);
+   if (err)
+   {
+      return err;
+   }
+
+   if (section1 > section2)
+   {
+      unsigned temp = section2;
+      section2 = section1;
+      section1 = temp;
+   }
+
+   err = edit_read_actions_file(csp, rsp, parameters, 1, &file);
+   if (err)
+   {
+      /* No filename specified, can't read file, modified, or out of memory. */
+      return (err == JB_ERR_FILE ? JB_ERR_OK : err);
+   }
+
+   /* Start at the beginning... */
+   line_number = 1;
+   cur_line = file->lines;
+   prev_line = NULL;
+
+   /* ... find section1 ... */
+   while ((cur_line != NULL) && (line_number < section1))
+   {
+      prev_line = cur_line;
+      cur_line = cur_line->next;
+      line_number++;
+   }
+
+   if ( (cur_line == NULL)
+     || (cur_line->type != FILE_LINE_ACTION) )
+   {
+      /* Invalid "section1" parameter */
+      edit_free_file(file);
+      return JB_ERR_CGI_PARAMS;
+   }
+
+   /* If no-op, we've validated params and can skip the rest. */
+   if (section1 != section2)
+   {
+      /* ... find the end of section1 ... */
+      line_before_section1 = prev_line;
+      line_start_section1 = cur_line;
+      do
+      {
+         prev_line = cur_line;
+         cur_line = cur_line->next;
+         line_number++;
+      }
+      while ((cur_line != NULL) && (cur_line->type == FILE_LINE_URL));
+      line_end_section1 = prev_line;
+      line_after_section1 = cur_line;
+
+      /* ... find section2 ... */
+      while ((cur_line != NULL) && (line_number < section2))
+      {
+         prev_line = cur_line;
+         cur_line = cur_line->next;
+         line_number++;
+      }
+
+      if ( (cur_line == NULL)
+        || (cur_line->type != FILE_LINE_ACTION) )
+      {
+         /* Invalid "section2" parameter */
+         edit_free_file(file);
+         return JB_ERR_CGI_PARAMS;
+      }
+
+      /* ... find the end of section2 ... */
+      line_before_section2 = prev_line;
+      line_start_section2 = cur_line;
+      do
+      {
+         prev_line = cur_line;
+         cur_line = cur_line->next;
+         line_number++;
+      }
+      while ((cur_line != NULL) && (cur_line->type == FILE_LINE_URL));
+      line_end_section2 = prev_line;
+      line_after_section2 = cur_line;
+
+      /* Now have all the pointers we need. Do the swap. */
+
+      /* Change the pointer to section1 to point to section2 instead */
+      if (line_before_section1 == NULL)
+      {
+         file->lines = line_start_section2;
+      }
+      else
+      {
+         line_before_section1->next = line_start_section2;
+      }
+
+      if (line_before_section2 == line_end_section1)
+      {
+         /* Consecutive sections */
+         line_end_section2->next = line_start_section1;
+      }
+      else
+      {
+         line_end_section2->next = line_after_section1;
+         line_before_section2->next = line_start_section1;
+      }
+
+      /* Set the pointer from the end of section1 to the rest of the file */
+      line_end_section1->next = line_after_section2;
+
+      err = edit_write_file(file);
+      if (err)
+      {
+         /* Error writing file */
+         if (err == JB_ERR_FILE)
+         {
+            /* Read-only file. */
+            err = cgi_error_file_read_only(csp, rsp, file->identifier);
+         }
+         edit_free_file(file);
+         return err;
+      }
+   } /* END if (section1 != section2) */
+
+   target = strdup(CGI_PREFIX "edit-actions-list?f=");
+   string_append(&target, file->identifier);
+
+   edit_free_file(file);
+
+   if (target == NULL)
+   {
+      /* Out of memory */
+      return JB_ERR_MEMORY;
+   }
+
+   rsp->status = strdup("302 Local Redirect from Privoxy");
+   if (rsp->status == NULL)
+   {
+      free(target);
+      return JB_ERR_MEMORY;
+   }
+   err = enlist_unique_header(rsp->headers, "Location", target);
+   free(target);
+
+   return err;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  cgi_toggle
+ *
+ * Description :  CGI function that adds a new empty section to
+ *                an actions file.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  rsp = http_response data structure for output
+ *          3  :  parameters = map of cgi parameters
+ *
+ * CGI Parameters :
+ *         set : If present, how to change toggle setting:
+ *               "enable", "disable", "toggle", or none (default).
+ *        mini : If present, use mini reply template.
+ *
+ * Returns     :  JB_ERR_OK     on success
+ *                JB_ERR_MEMORY on out-of-memory
+ *
+ *********************************************************************/
+jb_err cgi_toggle(struct client_state *csp,
+                  struct http_response *rsp,
+                  const struct map *parameters)
+{
+   struct map *exports;
+   char mode;
+   const char *template_name;
+
+   assert(csp);
+   assert(rsp);
+   assert(parameters);
+
+   if (0 == (csp->config->feature_flags & RUNTIME_FEATURE_CGI_TOGGLE))
+   {
+      return cgi_error_disabled(csp, rsp);
+   }
+
+   mode = get_char_param(parameters, "set");
+
+   if (mode == 'E')
+   {
+      /* Enable */
+      g_bToggleIJB = 1;
+   }
+   else if (mode == 'D')
+   {
+      /* Disable */
+      g_bToggleIJB = 0;
+   }
+   else if (mode == 'T')
+   {
+      /* Toggle */
+      g_bToggleIJB = !g_bToggleIJB;
+   }
+
+   if (NULL == (exports = default_exports(csp, "toggle")))
+   {
+      return JB_ERR_MEMORY;
+   }
+
+   template_name = (get_char_param(parameters, "mini")
+                 ? "toggle-mini"
+                 : "toggle");
+
+   return template_fill_for_cgi(csp, template_name, exports, rsp);
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  javascriptify
+ *
+ * Description :  Converts a string into a form JavaScript will like.
+ *
+ *                Netscape 4's JavaScript sucks - it doesn't use 
+ *                "id" parameters, so you have to set the "name"
+ *                used to submit a form element to something JavaScript
+ *                will like.  (Or access the elements by index in an
+ *                array.  That array contains >60 elements and will
+ *                be changed whenever we add a new action to the
+ *                editor, so I'm NOT going to use indexes that have
+ *                to be figured out by hand.)
+ *
+ *                Currently the only thing we have to worry about
+ *                is "-" ==> "_" conversion.
+ *
+ *                This is a length-preserving operation so it is
+ *                carried out in-place, no memory is allocated
+ *                or freed.
+ *
+ * Parameters  :
+ *          1  :  identifier = String to make JavaScript-friendly.
+ *
+ * Returns     :  N/A
+ *
+ *********************************************************************/
+static void javascriptify(char * identifier)
+{
+   char * p = identifier;
+   while (NULL != (p = strchr(p, '-')))
+   {
+      *p++ = '_';
+   }
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  actions_to_radio
+ *
+ * Description :  Converts a actionsfile entry into settings for
+ *                radio buttons and edit boxes on a HTML form.
+ *
+ * Parameters  :
+ *          1  :  exports = List of substitutions to add to.
+ *          2  :  action  = Action to read
+ *
+ * Returns     :  JB_ERR_OK     on success
+ *                JB_ERR_MEMORY on out-of-memory
+ *
+ *********************************************************************/
+static jb_err actions_to_radio(struct map * exports,
+                               const struct action_spec *action)
+{
+   unsigned mask = action->mask;
+   unsigned add  = action->add;
+   int mapped_param;
+   int checked;
+   char current_mode;
+
+   assert(exports);
+   assert(action);
+
+   mask = action->mask;
+   add  = action->add;
+
+   /* sanity - prevents "-feature +feature" */
+   mask |= add;
+
+
+#define DEFINE_ACTION_BOOL(name, bit)                 \
+   if (!(mask & bit))                                 \
+   {                                                  \
+      current_mode = 'n';                             \
+   }                                                  \
+   else if (add & bit)                                \
+   {                                                  \
+      current_mode = 'y';                             \
+   }                                                  \
+   else                                               \
+   {                                                  \
+      current_mode = 'x';                             \
+   }                                                  \
+   if (map_radio(exports, name, "ynx", current_mode)) \
+   {                                                  \
+      return JB_ERR_MEMORY;                           \
+   }
+
+#define DEFINE_ACTION_STRING(name, bit, index)        \
+   DEFINE_ACTION_BOOL(name, bit);                     \
+   mapped_param = 0;
+
+#define DEFINE_CGI_PARAM_RADIO(name, bit, index, value, is_default)  \
+   if (add & bit)                                                    \
+   {                                                                 \
+      checked = !strcmp(action->string[index], value);               \
+   }                                                                 \
+   else                                                              \
+   {                                                                 \
+      checked = is_default;                                          \
+   }                                                                 \
+   mapped_param |= checked;                                          \
+   if (map(exports, name "-param-" value, 1, (checked ? "checked" : ""), 1)) \
+   {                                                                 \
+      return JB_ERR_MEMORY;                                          \
+   }
+
+#define DEFINE_CGI_PARAM_CUSTOM(name, bit, index, default_val)       \
+   if (map(exports, name "-param-custom", 1,                         \
+           ((!mapped_param) ? "checked" : ""), 1))                   \
+   {                                                                 \
+      return JB_ERR_MEMORY;                                          \
+   }                                                                 \
+   if (map(exports, name "-param", 1,                                \
+           (((add & bit) && !mapped_param) ?                         \
+           action->string[index] : default_val), 1))                 \
+   {                                                                 \
+      return JB_ERR_MEMORY;                                          \
+   }
+
+#define DEFINE_CGI_PARAM_NO_RADIO(name, bit, index, default_val)     \
+   if (map(exports, name "-param", 1,                                \
+           ((add & bit) ? action->string[index] : default_val), 1))  \
+   {                                                                 \
+      return JB_ERR_MEMORY;                                          \
+   }
+
+#define DEFINE_ACTION_MULTI(name, index)              \
+   if (action->multi_add[index]->first)               \
+   {                                                  \
+      current_mode = 'y';                             \
+   }                                                  \
+   else if (action->multi_remove_all[index])          \
+   {                                                  \
+      current_mode = 'n';                             \
+   }                                                  \
+   else if (action->multi_remove[index]->first)       \
+   {                                                  \
+      current_mode = 'y';                             \
+   }                                                  \
+   else                                               \
+   {                                                  \
+      current_mode = 'x';                             \
+   }                                                  \
+   if (map_radio(exports, name, "ynx", current_mode)) \
+   {                                                  \
+      return JB_ERR_MEMORY;                           \
+   }
+
+#define DEFINE_ACTION_ALIAS 0 /* No aliases for output */
+
+#include "actionlist.h"
+
+#undef DEFINE_ACTION_MULTI
+#undef DEFINE_ACTION_STRING
+#undef DEFINE_ACTION_BOOL
+#undef DEFINE_ACTION_ALIAS
+#undef DEFINE_CGI_PARAM_CUSTOM
+#undef DEFINE_CGI_PARAM_RADIO
+#undef DEFINE_CGI_PARAM_NO_RADIO
+
+   return JB_ERR_OK;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  actions_from_radio
+ *
+ * Description :  Converts a map of parameters passed to a CGI function
+ *                into an actionsfile entry.
+ *
+ * Parameters  :
+ *          1  :  parameters = parameters to the CGI call
+ *          2  :  action  = Action to change.  Must be valid before
+ *                          the call, actions not specified will be
+ *                          left unchanged.
+ *
+ * Returns     :  JB_ERR_OK     on success
+ *                JB_ERR_MEMORY on out-of-memory
+ *
+ *********************************************************************/
+static jb_err actions_from_radio(const struct map * parameters,
+                                 struct action_spec *action)
+{
+   static int first_time = 1;
+   const char * param;
+   char * param_dup;
+   char ch;
+   const char * js_name;
+   jb_err err = JB_ERR_OK;
+
+   assert(parameters);
+   assert(action);
+
+   /* Statics are generally a potential race condition,
+    * but in this case we're safe and don't need semaphores.
+    * Be careful if you modify this function.
+    * - Jon
+    */
+
+#define JAVASCRIPTIFY(dest_var, string)               \
+   {                                                  \
+      static char js_name_arr[] = string;             \
+      if (first_time)                                 \
+      {                                               \
+         javascriptify(js_name_arr);                  \
+      }                                               \
+      dest_var = js_name_arr;                         \
+   }                                                  \
+
+#define DEFINE_ACTION_BOOL(name, bit)                 \
+   JAVASCRIPTIFY(js_name, name);                      \
+   ch = get_char_param(parameters, js_name);          \
+   if (ch == 'Y')                                     \
+   {                                                  \
+      action->add  |= bit;                            \
+      action->mask |= bit;                            \
+   }                                                  \
+   else if (ch == 'N')                                \
+   {                                                  \
+      action->add  &= ~bit;                           \
+      action->mask &= ~bit;                           \
+   }                                                  \
+   else if (ch == 'X')                                \
+   {                                                  \
+      action->add  &= ~bit;                           \
+      action->mask |= bit;                            \
+   }                                                  \
+
+#define DEFINE_ACTION_STRING(name, bit, index)                 \
+   JAVASCRIPTIFY(js_name, name);                               \
+   ch = get_char_param(parameters, js_name);                   \
+   if (ch == 'Y')                                              \
+   {                                                           \
+      param = NULL;                                            \
+      JAVASCRIPTIFY(js_name, name "-mode");                    \
+      if (!err) err = get_string_param(parameters, js_name, &param);    \
+      if ((param == NULL) || (0 == strcmp(param, "CUSTOM")))            \
+      {                                                                 \
+         JAVASCRIPTIFY(js_name, name "-param");                         \
+         if (!err) err = get_string_param(parameters, js_name, &param); \
+      }                                                        \
+      if (param != NULL)                                       \
+      {                                                        \
+         if (NULL == (param_dup = strdup(param)))              \
+         {                                                     \
+            return JB_ERR_MEMORY;                              \
+         }                                                     \
+         freez(action->string[index]);                         \
+         action->add  |= bit;                                  \
+         action->mask |= bit;                                  \
+         action->string[index] = param_dup;                    \
+      }                                                        \
+   }                                                           \
+   else if (ch == 'N')                                         \
+   {                                                           \
+      if (action->add & bit)                                   \
+      {                                                        \
+         freez(action->string[index]);                         \
+      }                                                        \
+      action->add  &= ~bit;                                    \
+      action->mask &= ~bit;                                    \
+   }                                                           \
+   else if (ch == 'X')                                         \
+   {                                                           \
+      if (action->add & bit)                                   \
+      {                                                        \
+         freez(action->string[index]);                         \
+      }                                                        \
+      action->add  &= ~bit;                                    \
+      action->mask |= bit;                                     \
+   }                                                           \
+
+#define DEFINE_ACTION_MULTI(name, index)                       \
+   JAVASCRIPTIFY(js_name, name);                               \
+   ch = get_char_param(parameters, js_name);                   \
+   if (ch == 'Y')                                              \
+   {                                                           \
+      /* FIXME */                                              \
+   }                                                           \
+   else if (ch == 'N')                                         \
+   {                                                           \
+      list_remove_all(action->multi_add[index]);               \
+      list_remove_all(action->multi_remove[index]);            \
+      action->multi_remove_all[index] = 1;                     \
+   }                                                           \
+   else if (ch == 'X')                                         \
+   {                                                           \
+      list_remove_all(action->multi_add[index]);               \
+      list_remove_all(action->multi_remove[index]);            \
+      action->multi_remove_all[index] = 0;                     \
+   }                                                           \
+
+#define DEFINE_ACTION_ALIAS 0 /* No aliases for URL parsing */
+
+#include "actionlist.h"
+
+#undef DEFINE_ACTION_MULTI
+#undef DEFINE_ACTION_STRING
+#undef DEFINE_ACTION_BOOL
+#undef DEFINE_ACTION_ALIAS
+#undef JAVASCRIPTIFY
+
+   first_time = 0;
+
+   return err;
+}
+
+
+#endif /* def FEATURE_CGI_EDIT_ACTIONS */
+
+
+/*
+  Local Variables:
+  tab-width: 3
+  end:
+*/
diff --git a/src/cgiedit.h b/src/cgiedit.h
new file mode 100644 (file)
index 0000000..821b528
--- /dev/null
@@ -0,0 +1,157 @@
+#ifndef CGIEDIT_H_INCLUDED
+#define CGIEDIT_H_INCLUDED
+#define CGIEDIT_H_VERSION "$Id: cgiedit.h,v 1.7 2002/03/26 22:29:54 swa Exp $"
+/*********************************************************************
+ *
+ * File        :  $Source: /cvsroot/ijbswa/current/cgiedit.h,v $
+ *
+ * Purpose     :  CGI-based actionsfile editor.
+ *                
+ *                Functions declared include:
+ * 
+ *
+ * Copyright   :  Written by and Copyright (C) 2001 the SourceForge
+ *                Privoxy team. http://www.privoxy.org/
+ *
+ *                Based on the Internet Junkbuster originally written
+ *                by and Copyright (C) 1997 Anonymous Coders and 
+ *                Junkbusters Corporation.  http://www.junkbusters.com
+ *
+ *                This program is free software; you can redistribute it 
+ *                and/or modify it under the terms of the GNU General
+ *                Public License as published by the Free Software
+ *                Foundation; either version 2 of the License, or (at
+ *                your option) any later version.
+ *
+ *                This program is distributed in the hope that it will
+ *                be useful, but WITHOUT ANY WARRANTY; without even the
+ *                implied warranty of MERCHANTABILITY or FITNESS FOR A
+ *                PARTICULAR PURPOSE.  See the GNU General Public
+ *                License for more details.
+ *
+ *                The GNU General Public License should be included with
+ *                this file.  If not, you can view it at
+ *                http://www.gnu.org/copyleft/gpl.html
+ *                or write to the Free Software Foundation, Inc., 59
+ *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * Revisions   :
+ *    $Log: cgiedit.h,v $
+ *    Revision 1.7  2002/03/26 22:29:54  swa
+ *    we have a new homepage!
+ *
+ *    Revision 1.6  2002/03/24 13:25:43  swa
+ *    name change related issues
+ *
+ *    Revision 1.5  2002/01/22 23:24:48  jongfoster
+ *    Adding edit-actions-section-swap
+ *
+ *    Revision 1.4  2001/11/13 00:28:51  jongfoster
+ *    Adding new CGIs for use by non-JavaScript browsers:
+ *      edit-actions-url-form
+ *      edit-actions-add-url-form
+ *      edit-actions-remove-url-form
+ *
+ *    Revision 1.3  2001/10/23 21:48:19  jongfoster
+ *    Cleaning up error handling in CGI functions - they now send back
+ *    a HTML error page and should never cause a FATAL error.  (Fixes one
+ *    potential source of "denial of service" attacks).
+ *
+ *    CGI actions file editor that works and is actually useful.
+ *
+ *    Ability to toggle JunkBuster remotely using a CGI call.
+ *
+ *    You can turn off both the above features in the main configuration
+ *    file, e.g. if you are running a multi-user proxy.
+ *
+ *    Revision 1.2  2001/10/14 22:12:49  jongfoster
+ *    New version of CGI-based actionsfile editor.
+ *    Major changes, including:
+ *    - Completely new file parser and file output routines
+ *    - edit-actions CGI renamed edit-actions-for-url
+ *    - All CGIs now need a filename parameter, except for...
+ *    - New CGI edit-actions which doesn't need a filename,
+ *      to allow you to start the editor up.
+ *    - edit-actions-submit now works, and now automatically
+ *      redirects you back to the main edit-actions-list handler.
+ *
+ *    Revision 1.1  2001/09/16 15:47:37  jongfoster
+ *    First version of CGI-based edit interface.  This is very much a
+ *    work-in-progress, and you can't actually use it to edit anything
+ *    yet.  You must #define FEATURE_CGI_EDIT_ACTIONS for these changes
+ *    to have any effect.
+ *
+ *
+ **********************************************************************/
+\f
+
+#include "project.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * CGI functions
+ */
+#ifdef FEATURE_CGI_EDIT_ACTIONS
+extern jb_err cgi_edit_actions        (struct client_state *csp,
+                                       struct http_response *rsp,
+                                       const struct map *parameters);
+extern jb_err cgi_edit_actions_for_url(struct client_state *csp,
+                                       struct http_response *rsp,
+                                       const struct map *parameters);
+extern jb_err cgi_edit_actions_list   (struct client_state *csp,
+                                       struct http_response *rsp,
+                                       const struct map *parameters);
+extern jb_err cgi_edit_actions_submit (struct client_state *csp,
+                                       struct http_response *rsp,
+                                       const struct map *parameters);
+extern jb_err cgi_edit_actions_url    (struct client_state *csp,
+                                       struct http_response *rsp,
+                                       const struct map *parameters);
+extern jb_err cgi_edit_actions_url_form(struct client_state *csp,
+                                        struct http_response *rsp,
+                                        const struct map *parameters);
+extern jb_err cgi_edit_actions_add_url(struct client_state *csp,
+                                       struct http_response *rsp,
+                                       const struct map *parameters);
+extern jb_err cgi_edit_actions_add_url_form(struct client_state *csp,
+                                            struct http_response *rsp,
+                                            const struct map *parameters);
+extern jb_err cgi_edit_actions_remove_url    (struct client_state *csp,
+                                              struct http_response *rsp,
+                                              const struct map *parameters);
+extern jb_err cgi_edit_actions_remove_url_form(struct client_state *csp,
+                                            struct http_response *rsp,
+                                            const struct map *parameters);
+extern jb_err cgi_edit_actions_section_remove(struct client_state *csp,
+                                              struct http_response *rsp,
+                                              const struct map *parameters);
+extern jb_err cgi_edit_actions_section_add   (struct client_state *csp,
+                                              struct http_response *rsp,
+                                              const struct map *parameters);
+extern jb_err cgi_edit_actions_section_swap  (struct client_state *csp,
+                                              struct http_response *rsp,
+                                              const struct map *parameters);
+extern jb_err cgi_toggle(struct client_state *csp,
+                         struct http_response *rsp,
+                         const struct map *parameters);
+#endif /* def FEATURE_CGI_EDIT_ACTIONS */
+
+
+/* Revision control strings from this header and associated .c file */
+extern const char cgiedit_rcs[];
+extern const char cgiedit_h_rcs[];
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* ndef CGI_H_INCLUDED */
+
+/*
+  Local Variables:
+  tab-width: 3
+  end:
+*/
diff --git a/src/cgisimple.c b/src/cgisimple.c
new file mode 100644 (file)
index 0000000..cebe576
--- /dev/null
@@ -0,0 +1,1419 @@
+const char cgisimple_rcs[] = "$Id: cgisimple.c,v 1.35 2002/05/12 21:44:44 jongfoster Exp $";
+/*********************************************************************
+ *
+ * File        :  $Source: /cvsroot/ijbswa/current/cgisimple.c,v $
+ *
+ * Purpose     :  Simple CGIs to get information about Privoxy's
+ *                status.
+ *                
+ *                Functions declared include:
+ * 
+ *
+ * Copyright   :  Written by and Copyright (C) 2001 the SourceForge
+ *                Privoxy team. http://www.privoxy.org/
+ *
+ *                Based on the Internet Junkbuster originally written
+ *                by and Copyright (C) 1997 Anonymous Coders and 
+ *                Junkbusters Corporation.  http://www.junkbusters.com
+ *
+ *                This program is free software; you can redistribute it 
+ *                and/or modify it under the terms of the GNU General
+ *                Public License as published by the Free Software
+ *                Foundation; either version 2 of the License, or (at
+ *                your option) any later version.
+ *
+ *                This program is distributed in the hope that it will
+ *                be useful, but WITHOUT ANY WARRANTY; without even the
+ *                implied warranty of MERCHANTABILITY or FITNESS FOR A
+ *                PARTICULAR PURPOSE.  See the GNU General Public
+ *                License for more details.
+ *
+ *                The GNU General Public License should be included with
+ *                this file.  If not, you can view it at
+ *                http://www.gnu.org/copyleft/gpl.html
+ *                or write to the Free Software Foundation, Inc., 59
+ *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * Revisions   :
+ *    $Log: cgisimple.c,v $
+ *    Revision 1.35  2002/05/12 21:44:44  jongfoster
+ *    Adding amiga.[ch] revision information, if on an amiga.
+ *
+ *    Revision 1.34  2002/04/30 12:06:12  oes
+ *    Deleted unused code from default_cgi
+ *
+ *    Revision 1.33  2002/04/30 11:14:52  oes
+ *    Made csp the first parameter in *action_to_html
+ *
+ *    Revision 1.32  2002/04/26 18:29:13  jongfoster
+ *    Fixing this Visual C++ warning:
+ *    cgisimple.c(775) : warning C4018: '<' : signed/unsigned mismatch
+ *
+ *    Revision 1.31  2002/04/26 12:54:36  oes
+ *     - Kill obsolete REDIRECT_URL code
+ *     - Error handling fixes
+ *     - Style sheet related HTML snipplet changes
+ *     - cgi_show_url_info:
+ *       - Matches now in table, actions on single lines,
+ *         linked to help
+ *       - standard.action suppressed
+ *       - Buttons to View and Edit AFs
+ *
+ *    Revision 1.30  2002/04/24 02:18:08  oes
+ *     - show-status is now the starting point for editing
+ *       the actions files, generate list of all AFs with buttons
+ *       for viewing and editing, new look for file list (Jon:
+ *       buttons now aligned ;-P ), view mode now supports multiple
+ *       AFs, name changes, no view links for unspecified files,
+ *       no edit link for standard.action.
+ *
+ *     - Jon's multiple AF patch: cgi_show_url_info now uses all
+ *       AFs and marks the output accordingly
+ *
+ *    Revision 1.29  2002/04/10 13:38:35  oes
+ *    load_template signature changed
+ *
+ *    Revision 1.28  2002/04/07 15:42:12  jongfoster
+ *    Fixing send-banner?type=auto when the image-blocker is
+ *    a redirect to send-banner
+ *
+ *    Revision 1.27  2002/04/05 15:50:48  oes
+ *    added send-stylesheet CGI
+ *
+ *    Revision 1.26  2002/04/04 00:36:36  gliptak
+ *    always use pcre for matching
+ *
+ *    Revision 1.25  2002/04/03 22:28:03  gliptak
+ *    Removed references to gnu_regex
+ *
+ *    Revision 1.24  2002/04/02 16:12:47  oes
+ *    Fix: moving misplaced lines into #ifdef FEATURE_FORCE
+ *
+ *    Revision 1.23  2002/03/26 22:29:54  swa
+ *    we have a new homepage!
+ *
+ *    Revision 1.22  2002/03/24 16:18:15  jongfoster
+ *    Removing old logo
+ *
+ *    Revision 1.21  2002/03/24 15:23:33  jongfoster
+ *    Name changes
+ *
+ *    Revision 1.20  2002/03/24 13:25:43  swa
+ *    name change related issues
+ *
+ *    Revision 1.19  2002/03/16 23:54:06  jongfoster
+ *    Adding graceful termination feature, to help look for memory leaks.
+ *    If you enable this (which, by design, has to be done by hand
+ *    editing config.h) and then go to http://i.j.b/die, then the program
+ *    will exit cleanly after the *next* request.  It should free all the
+ *    memory that was used.
+ *
+ *    Revision 1.18  2002/03/12 01:44:49  oes
+ *    Changed default for "blocked" image from jb logo to checkboard pattern
+ *
+ *    Revision 1.17  2002/03/08 16:43:18  oes
+ *    Added choice beween GIF and PNG built-in images
+ *
+ *    Revision 1.16  2002/03/07 03:48:38  oes
+ *     - Changed built-in images from GIF to PNG
+ *       (with regard to Unisys patent issue)
+ *     - Added a 4x4 pattern PNG which is less intrusive
+ *       than the logo but also clearly marks the deleted banners
+ *
+ *    Revision 1.15  2002/03/06 22:54:35  jongfoster
+ *    Automated function-comment nitpicking.
+ *
+ *    Revision 1.14  2002/03/02 04:14:50  david__schmidt
+ *    Clean up a little CRLF unpleasantness that suddenly appeared
+ *
+ *    Revision 1.13  2002/02/21 00:10:37  jongfoster
+ *    Adding send-banner?type=auto option
+ *
+ *    Revision 1.12  2002/01/23 01:03:32  jongfoster
+ *    Fixing gcc [CygWin] compiler warnings
+ *
+ *    Revision 1.11  2002/01/23 00:01:04  jongfoster
+ *    Adding cgi_transparent_gif() for http://i.j.b/t
+ *    Adding missing html_encode() to many CGI functions.
+ *    Adding urlmatch.[ch] to http://i.j.b/show-version
+ *
+ *    Revision 1.10  2002/01/17 21:10:37  jongfoster
+ *    Changes to cgi_show_url_info to use new matching code from urlmatch.c.
+ *    Also fixing a problem in the same function with improperly quoted URLs
+ *    in output HTML, and adding code to handle https:// URLs correctly.
+ *
+ *    Revision 1.9  2001/11/30 23:09:15  jongfoster
+ *    Now reports on FEATURE_CGI_EDIT_ACTIONS
+ *    Removing FEATURE_DENY_GZIP from template
+ *
+ *    Revision 1.8  2001/11/13 00:14:07  jongfoster
+ *    Fixing stupid bug now I've figured out what || means.
+ *    (It always returns 0 or 1, not one of it's paramaters.)
+ *
+ *    Revision 1.7  2001/10/23 21:48:19  jongfoster
+ *    Cleaning up error handling in CGI functions - they now send back
+ *    a HTML error page and should never cause a FATAL error.  (Fixes one
+ *    potential source of "denial of service" attacks).
+ *
+ *    CGI actions file editor that works and is actually useful.
+ *
+ *    Ability to toggle JunkBuster remotely using a CGI call.
+ *
+ *    You can turn off both the above features in the main configuration
+ *    file, e.g. if you are running a multi-user proxy.
+ *
+ *    Revision 1.6  2001/10/14 22:00:32  jongfoster
+ *    Adding support for a 404 error when an invalid CGI page is requested.
+ *
+ *    Revision 1.5  2001/10/07 15:30:41  oes
+ *    Removed FEATURE_DENY_GZIP
+ *
+ *    Revision 1.4  2001/10/02 15:31:12  oes
+ *    Introduced show-request cgi
+ *
+ *    Revision 1.3  2001/09/22 16:34:44  jongfoster
+ *    Removing unneeded #includes
+ *
+ *    Revision 1.2  2001/09/19 18:01:11  oes
+ *    Fixed comments; cosmetics
+ *
+ *    Revision 1.1  2001/09/16 17:08:54  jongfoster
+ *    Moving simple CGI functions from cgi.c to new file cgisimple.c
+ *
+ *
+ **********************************************************************/
+\f
+
+#include "config.h"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+#include <assert.h>
+
+#ifdef _WIN32
+#define snprintf _snprintf
+#endif /* def _WIN32 */
+
+#include "project.h"
+#include "cgi.h"
+#include "cgisimple.h"
+#include "list.h"
+#include "encode.h"
+#include "jcc.h"
+#include "filters.h"
+#include "actions.h"
+#include "miscutil.h"
+#include "loadcfg.h"
+#include "parsers.h"
+#include "urlmatch.h"
+#include "errlog.h"
+
+const char cgisimple_h_rcs[] = CGISIMPLE_H_VERSION;
+
+
+static char *show_rcs(void);
+static jb_err show_defines(struct map *exports);
+
+
+/*********************************************************************
+ *
+ * Function    :  cgi_default
+ *
+ * Description :  CGI function that is called for the CGI_SITE_1_HOST
+ *                and CGI_SITE_2_HOST/CGI_SITE_2_PATH base URLs.
+ *                Boring - only exports the default exports.
+ *               
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  rsp = http_response data structure for output
+ *          3  :  parameters = map of cgi parameters
+ *
+ * CGI Parameters : none
+ *
+ * Returns     :  JB_ERR_OK on success
+ *                JB_ERR_MEMORY on out-of-memory
+ *
+ *********************************************************************/
+jb_err cgi_default(struct client_state *csp,
+                   struct http_response *rsp,
+                   const struct map *parameters)
+{
+   struct map *exports;
+
+   assert(csp);
+   assert(rsp);
+
+   if (NULL == (exports = default_exports(csp, "")))
+   {
+      return JB_ERR_MEMORY;
+   }
+
+   return template_fill_for_cgi(csp, "default", exports, rsp);
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  cgi_error_404
+ *
+ * Description :  CGI function that is called if an unknown action was
+ *                given.
+ *               
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  rsp = http_response data structure for output
+ *          3  :  parameters = map of cgi parameters
+ *
+ * CGI Parameters : none
+ *
+ * Returns     :  JB_ERR_OK on success
+ *                JB_ERR_MEMORY on out-of-memory error.  
+ *
+ *********************************************************************/
+jb_err cgi_error_404(struct client_state *csp,
+                     struct http_response *rsp,
+                     const struct map *parameters)
+{
+   struct map *exports;
+
+   assert(csp);
+   assert(rsp);
+   assert(parameters);
+
+   if (NULL == (exports = default_exports(csp, NULL)))
+   {
+      return JB_ERR_MEMORY;
+   }
+
+   rsp->status = strdup("404 Privoxy configuration page not found");
+   if (rsp->status == NULL)
+   {
+      free_map(exports);
+      return JB_ERR_MEMORY;
+   }
+
+   return template_fill_for_cgi(csp, "cgi-error-404", exports, rsp);
+}
+
+
+#ifdef FEATURE_GRACEFUL_TERMINATION
+/*********************************************************************
+ *
+ * Function    :  cgi_die
+ *
+ * Description :  CGI function to shut down Privoxy.
+ *                NOTE: Turning this on in a production build
+ *                would be a BAD idea.  An EXTREMELY BAD idea.
+ *                In short, don't do it.
+ *               
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  rsp = http_response data structure for output
+ *          3  :  parameters = map of cgi parameters
+ *
+ * CGI Parameters : none
+ *
+ * Returns     :  JB_ERR_OK on success
+ *                JB_ERR_MEMORY on out-of-memory error.  
+ *
+ *********************************************************************/
+jb_err cgi_die (struct client_state *csp,
+                struct http_response *rsp,
+                const struct map *parameters)
+{
+   assert(csp);
+   assert(rsp);
+   assert(parameters);
+
+   /* quit */
+   g_terminate = 1;
+
+   /*
+    * I don't really care what gets sent back to the browser.
+    * Take the easy option - "out of memory" page.
+    */
+
+   return JB_ERR_MEMORY;
+}
+#endif /* def FEATURE_GRACEFUL_TERMINATION */
+
+
+/*********************************************************************
+ *
+ * Function    :  cgi_show_request
+ *
+ * Description :  Show the client's request and what sed() would have
+ *                made of it.
+ *               
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  rsp = http_response data structure for output
+ *          3  :  parameters = map of cgi parameters
+ *
+ * CGI Parameters : none
+ *
+ * Returns     :  JB_ERR_OK on success
+ *                JB_ERR_MEMORY on out-of-memory error.  
+ *
+ *********************************************************************/
+jb_err cgi_show_request(struct client_state *csp,
+                        struct http_response *rsp,
+                        const struct map *parameters)
+{
+   char *p;
+   struct map *exports;
+
+   assert(csp);
+   assert(rsp);
+   assert(parameters);
+
+   if (NULL == (exports = default_exports(csp, "show-request")))
+   {
+      return JB_ERR_MEMORY;
+   }
+   
+   /*
+    * Repair the damage done to the IOB by get_header()
+    */
+   for (p = csp->iob->buf; p < csp->iob->eod; p++)
+   {
+      if (*p == '\0') *p = '\n';
+   }
+
+   /*
+    * Export the original client's request and the one we would
+    * be sending to the server if this wasn't a CGI call
+    */
+
+   if (map(exports, "client-request", 1, html_encode(csp->iob->buf), 0))
+   {
+      free_map(exports);
+      return JB_ERR_MEMORY;
+   }
+
+   if (map(exports, "processed-request", 1, html_encode_and_free_original(
+      sed(client_patterns, add_client_headers, csp)), 0))
+   {
+      free_map(exports);
+      return JB_ERR_MEMORY;
+   }
+
+   return template_fill_for_cgi(csp, "show-request", exports, rsp);
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  cgi_send_banner
+ *
+ * Description :  CGI function that returns a banner. 
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  rsp = http_response data structure for output
+ *          3  :  parameters = map of cgi parameters
+ *
+ * CGI Parameters :
+ *           type : Selects the type of banner between "trans", "logo",
+ *                  and "auto". Defaults to "logo" if absent or invalid.
+ *                  "auto" means to select as if we were image-blocking.
+ *                  (Only the first character really counts).
+ *
+ * Returns     :  JB_ERR_OK on success
+ *                JB_ERR_MEMORY on out-of-memory error.  
+ *
+ *********************************************************************/
+jb_err cgi_send_banner(struct client_state *csp,
+                       struct http_response *rsp,
+                       const struct map *parameters)
+{
+   char imagetype = lookup(parameters, "type")[0];
+
+   if (imagetype == 'a') /* auto */
+   {
+      /* Default to pattern */
+      imagetype = 'p';
+#ifdef FEATURE_IMAGE_BLOCKING
+      if ((csp->action->flags & ACTION_IMAGE_BLOCKER) != 0)
+      {
+         static const char prefix1[] = CGI_PREFIX "send-banner?type=";
+         static const char prefix2[] = "http://" CGI_SITE_1_HOST "/send-banner?type=";
+
+         /* determine HOW images should be blocked */
+         const char * p = csp->action->string[ACTION_STRING_IMAGE_BLOCKER];
+
+         /* and handle accordingly: */
+         if (p == NULL)
+         {
+            /* Use default - nothing to do here. */
+         }
+         else if (0 == strcmpic(p, "blank"))
+         {
+            imagetype = 'b';
+         }
+         else if (0 == strcmpic(p, "pattern"))
+         {
+            imagetype = 'p';
+         }
+         else if (0 == strncmpic(p, prefix1, sizeof(prefix1) - 1))
+         {
+            imagetype = p[sizeof(prefix1) - 1];
+         }
+         else if (0 == strncmpic(p, prefix2, sizeof(prefix2) - 1))
+         {
+            imagetype = p[sizeof(prefix2) - 1];
+         }
+      }
+#endif /* def FEATURE_IMAGE_BLOCKING */
+   }
+      
+   if ((imagetype == 'b') || (imagetype == 't')) /* blank / transparent */
+   {
+      rsp->body = bindup(image_blank_data, image_blank_length);
+      rsp->content_length = image_blank_length;
+
+   }
+   else /* pattern */
+   {
+      rsp->body = bindup(image_pattern_data, image_pattern_length);
+      rsp->content_length = image_pattern_length;
+   }   
+
+   if (rsp->body == NULL)
+   {
+      return JB_ERR_MEMORY;
+   }
+
+   if (enlist(rsp->headers, "Content-Type: " BUILTIN_IMAGE_MIMETYPE))
+   {
+      return JB_ERR_MEMORY;
+   }
+
+   rsp->is_static = 1;
+
+   return JB_ERR_OK;
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  cgi_transparent_image
+ *
+ * Description :  CGI function that sends a 1x1 transparent image.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  rsp = http_response data structure for output
+ *          3  :  parameters = map of cgi parameters
+ *
+ * CGI Parameters : None
+ *
+ * Returns     :  JB_ERR_OK on success
+ *                JB_ERR_MEMORY on out-of-memory error.  
+ *
+ *********************************************************************/
+jb_err cgi_transparent_image(struct client_state *csp,
+                             struct http_response *rsp,
+                             const struct map *parameters)
+{
+   rsp->body = bindup(image_blank_data, image_blank_length);
+   rsp->content_length = image_blank_length;
+
+   if (rsp->body == NULL)
+   {
+      return JB_ERR_MEMORY;
+   }
+
+   if (enlist(rsp->headers, "Content-Type: " BUILTIN_IMAGE_MIMETYPE))
+   {
+      return JB_ERR_MEMORY;
+   }
+
+   rsp->is_static = 1;
+
+   return JB_ERR_OK;
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  cgi_send_stylesheet
+ *
+ * Description :  CGI function that sends a css stylesheet found
+ *                in the cgi-style.css template
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  rsp = http_response data structure for output
+ *          3  :  parameters = map of cgi parameters
+ *
+ * CGI Parameters : None
+ *
+ * Returns     :  JB_ERR_OK on success
+ *                JB_ERR_MEMORY on out-of-memory error.  
+ *
+ *********************************************************************/
+jb_err cgi_send_stylesheet(struct client_state *csp,
+                           struct http_response *rsp,
+                           const struct map *parameters)
+{
+   jb_err err;
+   
+   assert(csp);
+   assert(rsp);
+
+   err = template_load(csp, &rsp->body, "cgi-style.css", 0);
+
+   if (err == JB_ERR_FILE)
+   {
+      /*
+       * No way to tell user; send empty stylesheet
+       */
+      log_error(LOG_LEVEL_ERROR, "Could not find cgi-style.css template");
+   }
+   else if (err)
+   {
+      return err; /* JB_ERR_MEMORY */
+   }
+
+   if (enlist(rsp->headers, "Content-Type: text/css"))
+   {
+      return JB_ERR_MEMORY;
+   }
+
+   return JB_ERR_OK;
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  cgi_show_version
+ *
+ * Description :  CGI function that returns a a web page describing the
+ *                file versions of Privoxy.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  rsp = http_response data structure for output
+ *          3  :  parameters = map of cgi parameters
+ *
+ * CGI Parameters : none
+ *
+ * Returns     :  JB_ERR_OK on success
+ *                JB_ERR_MEMORY on out-of-memory error.  
+ *
+ *********************************************************************/
+jb_err cgi_show_version(struct client_state *csp,
+                        struct http_response *rsp,
+                        const struct map *parameters)
+{
+   struct map *exports;
+
+   assert(csp);
+   assert(rsp);
+   assert(parameters);
+
+   if (NULL == (exports = default_exports(csp, "show-version")))
+   {
+      return JB_ERR_MEMORY;
+   }
+
+   if (map(exports, "sourceversions", 1, show_rcs(), 0))
+   {
+      free_map(exports);
+      return JB_ERR_MEMORY;
+   }
+
+   return template_fill_for_cgi(csp, "show-version", exports, rsp);
+}
+
+/*********************************************************************
+ *
+ * Function    :  cgi_show_status
+ *
+ * Description :  CGI function that returns a a web page describing the
+ *                current status of Privoxy.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  rsp = http_response data structure for output
+ *          3  :  parameters = map of cgi parameters
+ *
+ * CGI Parameters :
+ *        file :  Which file to show.  Only first letter is checked,
+ *                valid values are:
+ *                - "p"ermissions (actions) file
+ *                - "r"egex
+ *                - "t"rust
+ *                Default is to show menu and other information.
+ *
+ * Returns     :  JB_ERR_OK on success
+ *                JB_ERR_MEMORY on out-of-memory error.  
+ *
+ *********************************************************************/
+jb_err cgi_show_status(struct client_state *csp,
+                       struct http_response *rsp,
+                       const struct map *parameters)
+{
+   char *s = NULL;
+   unsigned i;
+   int j;
+
+   FILE * fp;
+   char buf[BUFFER_SIZE];
+   const char * filename = NULL;
+   char * file_description = NULL;
+#ifdef FEATURE_STATISTICS
+   float perc_rej;   /* Percentage of http requests rejected */
+   int local_urls_read;
+   int local_urls_rejected;
+#endif /* ndef FEATURE_STATISTICS */
+   struct file_list * fl;
+   struct url_actions * b;
+   jb_err err = JB_ERR_OK;
+
+   struct map *exports;
+
+   assert(csp);
+   assert(rsp);
+   assert(parameters);
+
+   if (NULL == (exports = default_exports(csp, "show-status")))
+   {
+      return JB_ERR_MEMORY;
+   }
+
+   switch (*(lookup(parameters, "file")))
+   {
+   case 'a':
+      if (!get_number_param(csp, parameters, "index", &i) && i < MAX_ACTION_FILES && csp->actions_list[i])
+      {
+         filename = csp->actions_list[i]->filename;
+         file_description = "Actions File";
+      }
+      break;
+
+   case 'f':
+      if (csp->rlist)
+      {
+         filename = csp->rlist->filename;
+         file_description = "Filter File";
+      }
+      break;
+
+#ifdef FEATURE_TRUST
+   case 't':
+      if (csp->tlist)
+      {
+         filename = csp->tlist->filename;
+         file_description = "Trust File";
+      }
+      break;
+#endif /* def FEATURE_TRUST */
+   }
+
+   if (NULL != filename)
+   {
+      if ( map(exports, "file-description", 1, file_description, 1)
+        || map(exports, "filepath", 1, html_encode(filename), 0) )
+      {
+         free_map(exports);
+         return JB_ERR_MEMORY;
+      }
+
+      if ((fp = fopen(filename, "r")) == NULL)
+      {
+         if (map(exports, "content", 1, "<h1>ERROR OPENING FILE!</h1>", 1))
+         {
+            free_map(exports);
+            return JB_ERR_MEMORY;
+         }
+      }
+      else
+      {
+         s = strdup("");
+         while ((s != NULL) && fgets(buf, sizeof(buf), fp))
+         {
+            string_join  (&s, html_encode(buf));
+         }
+         fclose(fp);
+
+         if (map(exports, "contents", 1, s, 0))
+         {
+            free_map(exports);
+            return JB_ERR_MEMORY;
+         }
+      }
+
+      return template_fill_for_cgi(csp, "show-status-file", exports, rsp);
+   }
+
+   s = strdup("");
+   for (j = 0; (s != NULL) && (j < Argc); j++)
+   {
+      if (!err) err = string_join  (&s, html_encode(Argv[j]));
+      if (!err) err = string_append(&s, " ");
+   }
+   if (!err) err = map(exports, "invocation", 1, s, 0);
+
+   if (!err) err = map(exports, "options", 1, csp->config->proxy_args, 1);
+   if (!err) err = show_defines(exports);
+
+   if (err) 
+   {
+      free_map(exports);
+      return JB_ERR_MEMORY;
+   }
+
+#ifdef FEATURE_STATISTICS
+   local_urls_read     = urls_read;
+   local_urls_rejected = urls_rejected;
+
+   /*
+    * Need to alter the stats not to include the fetch of this
+    * page.
+    *
+    * Can't do following thread safely! doh!
+    *
+    * urls_read--;
+    * urls_rejected--; * This will be incremented subsequently *
+    */
+
+   if (local_urls_read == 0)
+   {
+      if (!err) err = map_block_killer(exports, "have-stats");
+   }
+   else
+   {
+      if (!err) err = map_block_killer(exports, "have-no-stats");
+
+      perc_rej = (float)local_urls_rejected * 100.0F /
+            (float)local_urls_read;
+
+      sprintf(buf, "%d", local_urls_read);
+      if (!err) err = map(exports, "requests-received", 1, buf, 1);
+
+      sprintf(buf, "%d", local_urls_rejected);
+      if (!err) err = map(exports, "requests-blocked", 1, buf, 1);
+
+      sprintf(buf, "%6.2f", perc_rej);
+      if (!err) err = map(exports, "percent-blocked", 1, buf, 1);
+   }
+
+#else /* ndef FEATURE_STATISTICS */
+   err = err || map_block_killer(exports, "statistics");
+#endif /* ndef FEATURE_STATISTICS */
+   
+   /* 
+    * List all action files in use, together with view and edit links,
+    * except for standard.action, which should only be viewable. (Not
+    * enforced in the editor itself)
+    * FIXME: Shouldn't include hardwired HTML here, use line template instead!
+    */
+   s = strdup("");
+   for (i = 0; i < MAX_ACTION_FILES; i++)
+   {
+      if (((fl = csp->actions_list[i]) != NULL) && ((b = fl->f) != NULL))
+      {
+         if (!err) err = string_append(&s, "<tr><td>");
+         if (!err) err = string_join(&s, html_encode(csp->actions_list[i]->filename));
+         snprintf(buf, 100, "</td><td class=\"buttons\"><a href=\"/show-status?file=actions&index=%d\">View</a> ", i);
+         if (!err) err = string_append(&s, buf);
+
+         if (NULL == strstr(csp->actions_list[i]->filename, "standard.action") && NULL != csp->config->actions_file_short[i])
+         {
+            snprintf(buf, 100, "<a href=\"/edit-actions-list?f=%s\">Edit</a>", csp->config->actions_file_short[i]);
+            if (!err) err = string_append(&s, buf);
+         }
+
+         if (!err) err = string_append(&s, "</td></tr>\n");
+      }
+   }
+   if (*s != '\0')   
+   {
+      if (!err) err = map(exports, "actions-filenames", 1, s, 0);
+   }
+   else
+   {
+      if (!err) err = map(exports, "actions-filenames", 1, "<tr><td>None specified</td></tr>", 1);
+   }
+
+   if (csp->rlist)
+   {
+      if (!err) err = map(exports, "re-filter-filename", 1, html_encode(csp->rlist->filename), 0);
+   }
+   else
+   {
+      if (!err) err = map(exports, "re-filter-filename", 1, "None specified", 1);
+      if (!err) err = map_block_killer(exports, "have-filterfile");
+   }
+
+#ifdef FEATURE_TRUST
+   if (csp->tlist)
+   {
+      if (!err) err = map(exports, "trust-filename", 1, html_encode(csp->tlist->filename), 0);
+   }
+   else
+   {
+      if (!err) err = map(exports, "trust-filename", 1, "None specified", 1);
+      if (!err) err = map_block_killer(exports, "have-trustfile");
+   }
+#else
+   if (!err) err = map_block_killer(exports, "trust-support");
+#endif /* ndef FEATURE_TRUST */
+
+   if (err)
+   {
+      free_map(exports);
+      return JB_ERR_MEMORY;
+   }
+
+   return template_fill_for_cgi(csp, "show-status", exports, rsp);
+}
+
+/*********************************************************************
+ *
+ * Function    :  cgi_show_url_info
+ *
+ * Description :  CGI function that determines and shows which actions
+ *                Privoxy will perform for a given url, and which
+ *                matches starting from the defaults have lead to that.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  rsp = http_response data structure for output
+ *          3  :  parameters = map of cgi parameters
+ *
+ * CGI Parameters :
+ *            url : The url whose actions are to be determined.
+ *                  If url is unset, the url-given conditional will be
+ *                  set, so that all but the form can be suppressed in
+ *                  the template.
+ *
+ * Returns     :  JB_ERR_OK on success
+ *                JB_ERR_MEMORY on out-of-memory error.  
+ *
+ *********************************************************************/
+jb_err cgi_show_url_info(struct client_state *csp,
+                         struct http_response *rsp,
+                         const struct map *parameters)
+{
+   char *url_param;
+   struct map *exports;
+   char buf[150];
+
+   assert(csp);
+   assert(rsp);
+   assert(parameters);
+
+   if (NULL == (exports = default_exports(csp, "show-url-info")))
+   {
+      return JB_ERR_MEMORY;
+   }
+
+   /*
+    * Get the url= parameter (if present) and remove any leading/trailing spaces.
+    */
+   url_param = strdup(lookup(parameters, "url"));
+   if (url_param == NULL)
+   {
+      free_map(exports);
+      return JB_ERR_MEMORY;
+   }
+   chomp(url_param);
+
+   /*
+    * Handle prefixes.  4 possibilities:
+    * 1) "http://" or "https://" prefix present and followed by URL - OK
+    * 2) Only the "http://" or "https://" part is present, no URL - change
+    *    to empty string so it will be detected later as "no URL".
+    * 3) Parameter specified but doesn't contain "http(s?)://" - add a
+    *    "http://" prefix.
+    * 4) Parameter not specified or is empty string - let this fall through
+    *    for now, next block of code will handle it.
+    */
+   if (0 == strncmp(url_param, "http://", 7))
+   {
+      if (url_param[7] == '\0')
+      {
+         /*
+          * Empty URL (just prefix).
+          * Make it totally empty so it's caught by the next if()
+          */
+         url_param[0] = '\0';
+      }
+   }
+   else if (0 == strncmp(url_param, "https://", 8))
+   {
+      if (url_param[8] == '\0')
+      {
+         /*
+          * Empty URL (just prefix).
+          * Make it totally empty so it's caught by the next if()
+          */
+         url_param[0] = '\0';
+      }
+   }
+   else if (url_param[0] != '\0')
+   {
+      /*
+       * Unknown prefix - assume http://
+       */
+      char * url_param_prefixed = malloc(7 + 1 + strlen(url_param));
+      if (NULL == url_param_prefixed)
+      {
+         free(url_param);
+         free_map(exports);
+         return JB_ERR_MEMORY;
+      }
+      strcpy(url_param_prefixed, "http://");
+      strcpy(url_param_prefixed + 7, url_param);
+      free(url_param);
+      url_param = url_param_prefixed;
+   }
+
+
+   if (url_param[0] == '\0')
+   {
+      /* URL paramater not specified, display query form only. */
+      free(url_param);
+      if (map_block_killer(exports, "url-given")
+        || map(exports, "url", 1, "", 1))
+      {
+         free_map(exports);
+         return JB_ERR_MEMORY;
+      }
+   }
+   else
+   {
+      /* Given a URL, so query it. */
+      jb_err err;
+      char *matches;
+      char *s;
+      int hits = 0;
+      struct file_list *fl;
+      struct url_actions *b;
+      struct http_request url_to_query[1];
+      struct current_action_spec action[1];
+      int i;
+      
+      if (map(exports, "url", 1, html_encode(url_param), 0))
+      {
+         free(url_param);
+         free_map(exports);
+         return JB_ERR_MEMORY;
+      }
+
+      init_current_action(action);
+
+      if (map(exports, "default", 1, current_action_to_html(csp, action), 0))
+      {
+         free_current_action(action);
+         free(url_param);
+         free_map(exports);
+         return JB_ERR_MEMORY;
+      }
+
+      err = parse_http_url(url_param, url_to_query, csp);
+
+      free(url_param);
+
+      if (err == JB_ERR_MEMORY)
+      {
+         free_current_action(action);
+         free_map(exports);
+         return JB_ERR_MEMORY;
+      }
+      else if (err)
+      {
+         /* Invalid URL */
+
+         err = map(exports, "matches", 1, "<b>[Invalid URL specified!]</b>" , 1);
+         if (!err) err = map(exports, "final", 1, lookup(exports, "default"), 1);
+
+         free_current_action(action);
+
+         if (err)
+         {
+            free_map(exports);
+            return JB_ERR_MEMORY;
+         }
+
+         return template_fill_for_cgi(csp, "show-url-info", exports, rsp);
+      }
+
+      /*
+       * We have a warning about SSL paths.  Hide it for insecure sites.
+       */
+      if (!url_to_query->ssl)
+      {
+         if (map_block_killer(exports, "https"))
+         {
+            free_current_action(action);
+            free_map(exports);
+            return JB_ERR_MEMORY;
+         }
+      }
+
+      matches = strdup("<table class=\"transparent\">");
+
+      for (i = 0; i < MAX_ACTION_FILES; i++)
+      {
+         if (NULL == csp->config->actions_file_short[i]
+             || !strcmp(csp->config->actions_file_short[i], "standard")) continue;
+
+         b = NULL;
+         hits = 1;
+         if ((fl = csp->actions_list[i]) != NULL)
+         {
+            if ((b = fl->f) != NULL)
+            {
+               /* FIXME: Hardcoded HTML! */
+               string_append(&matches, "<tr><th>In file: ");
+               string_join  (&matches, html_encode(csp->config->actions_file_short[i]));
+               snprintf(buf, 150, ".action <a class=\"cmd\" href=\"/show-status?file=actions&index=%d\">", i);
+               string_append(&matches, buf);
+               string_append(&matches, "View</a> <a class=\"cmd\" href=\"/edit-actions-list?f=");
+               string_join  (&matches, html_encode(csp->config->actions_file_short[i]));
+               string_append(&matches, "\">Edit</a></th></tr>\n");
+
+               hits = 0;
+               b = b->next;
+            }
+         }
+
+         for (; (b != NULL) && (matches != NULL); b = b->next)
+         {
+            if (url_match(b->url, url_to_query))
+            {
+               string_append(&matches, "<tr><td>{");
+               string_join  (&matches, actions_to_html(csp, b->action));
+               string_append(&matches, " }</b><br>\n<code>");
+               string_join  (&matches, html_encode(b->url->spec));
+               string_append(&matches, "</code></td></tr>\n");
+
+               if (merge_current_action(action, b->action))
+               {
+                  freez(matches);
+                  free_http_request(url_to_query);
+                  free_current_action(action);
+                  free_map(exports);
+                  return JB_ERR_MEMORY;
+               }
+               hits++;
+            }
+         }
+
+         if (!hits)
+         {
+            string_append(&matches, "<tr><td>(no matches in this file)</td></tr>\n");
+         }
+      }
+      string_append(&matches, "</table>\n");
+
+      free_http_request(url_to_query);
+
+      if (matches == NULL)
+      {
+         free_current_action(action);
+         free_map(exports);
+         return JB_ERR_MEMORY;
+      }
+
+      if (map(exports, "matches", 1, matches , 0))
+      {
+         free_current_action(action);
+         free_map(exports);
+         return JB_ERR_MEMORY;
+      }
+
+      s = current_action_to_html(csp, action);
+
+      free_current_action(action);
+
+      if (map(exports, "final", 1, s, 0))
+      {
+         free_map(exports);
+         return JB_ERR_MEMORY;
+      }
+   }
+
+   return template_fill_for_cgi(csp, "show-url-info", exports, rsp);
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  cgi_robots_txt
+ *
+ * Description :  CGI function to return "/robots.txt".
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  rsp = http_response data structure for output
+ *          3  :  parameters = map of cgi parameters
+ *
+ * CGI Parameters : None
+ *
+ * Returns     :  JB_ERR_OK on success
+ *                JB_ERR_MEMORY on out-of-memory error.  
+ *
+ *********************************************************************/
+jb_err cgi_robots_txt(struct client_state *csp,
+                      struct http_response *rsp,
+                      const struct map *parameters)
+{
+   char buf[100];
+   jb_err err;
+
+   rsp->body = strdup(
+      "# This is the Privoxy control interface.\n"
+      "# It isn't very useful to index it, and you're likely to break stuff.\n"
+      "# So go away!\n"
+      "\n"
+      "User-agent: *\n"
+      "Disallow: /\n"
+      "\n");
+   if (rsp->body == NULL)
+   {
+      return JB_ERR_MEMORY;
+   }
+
+   err = enlist_unique(rsp->headers, "Content-Type: text/plain", 13);
+
+   rsp->is_static = 1;
+
+   get_http_time(7 * 24 * 60 * 60, buf); /* 7 days into future */
+   if (!err) err = enlist_unique_header(rsp->headers, "Expires", buf);
+
+   return (err ? JB_ERR_MEMORY : JB_ERR_OK);
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  show_defines
+ *
+ * Description :  Add to a map the state od all conditional #defines
+ *                used when building
+ *
+ * Parameters  :
+ *          1  :  exports = map to extend
+ *
+ * Returns     :  JB_ERR_OK on success
+ *                JB_ERR_MEMORY on out-of-memory error.  
+ *
+ *********************************************************************/
+static jb_err show_defines(struct map *exports)
+{
+   jb_err err = JB_ERR_OK;
+
+#ifdef FEATURE_ACL
+   if (!err) err = map_conditional(exports, "FEATURE_ACL", 1);
+#else /* ifndef FEATURE_ACL */
+   if (!err) err = map_conditional(exports, "FEATURE_ACL", 0);
+#endif /* ndef FEATURE_ACL */
+
+#ifdef FEATURE_CGI_EDIT_ACTIONS
+   if (!err) err = map_conditional(exports, "FEATURE_CGI_EDIT_ACTIONS", 1);
+#else /* ifndef FEATURE_COOKIE_JAR */
+   if (!err) err = map_conditional(exports, "FEATURE_CGI_EDIT_ACTIONS", 0);
+#endif /* ndef FEATURE_COOKIE_JAR */
+
+#ifdef FEATURE_COOKIE_JAR
+   if (!err) err = map_conditional(exports, "FEATURE_COOKIE_JAR", 1);
+#else /* ifndef FEATURE_COOKIE_JAR */
+   if (!err) err = map_conditional(exports, "FEATURE_COOKIE_JAR", 0);
+#endif /* ndef FEATURE_COOKIE_JAR */
+
+#ifdef FEATURE_FAST_REDIRECTS
+   if (!err) err = map_conditional(exports, "FEATURE_FAST_REDIRECTS", 1);
+#else /* ifndef FEATURE_FAST_REDIRECTS */
+   if (!err) err = map_conditional(exports, "FEATURE_FAST_REDIRECTS", 0);
+#endif /* ndef FEATURE_FAST_REDIRECTS */
+
+#ifdef FEATURE_FORCE_LOAD
+   if (!err) err = map_conditional(exports, "FEATURE_FORCE_LOAD", 1);
+   if (!err) err = map(exports, "FORCE_PREFIX", 1, FORCE_PREFIX, 1);
+#else /* ifndef FEATURE_FORCE_LOAD */
+   if (!err) err = map_conditional(exports, "FEATURE_FORCE_LOAD", 0);
+   if (!err) err = map(exports, "FORCE_PREFIX", 1, "(none - disabled)", 1);
+#endif /* ndef FEATURE_FORCE_LOAD */
+
+#ifdef FEATURE_IMAGE_BLOCKING
+   if (!err) err = map_conditional(exports, "FEATURE_IMAGE_BLOCKING", 1);
+#else /* ifndef FEATURE_IMAGE_BLOCKING */
+   if (!err) err = map_conditional(exports, "FEATURE_IMAGE_BLOCKING", 0);
+#endif /* ndef FEATURE_IMAGE_BLOCKING */
+
+#ifdef FEATURE_IMAGE_DETECT_MSIE
+   if (!err) err = map_conditional(exports, "FEATURE_IMAGE_DETECT_MSIE", 1);
+#else /* ifndef FEATURE_IMAGE_DETECT_MSIE */
+   if (!err) err = map_conditional(exports, "FEATURE_IMAGE_DETECT_MSIE", 0);
+#endif /* ndef FEATURE_IMAGE_DETECT_MSIE */
+
+#ifdef FEATURE_KILL_POPUPS
+   if (!err) err = map_conditional(exports, "FEATURE_KILL_POPUPS", 1);
+#else /* ifndef FEATURE_KILL_POPUPS */
+   if (!err) err = map_conditional(exports, "FEATURE_KILL_POPUPS", 0);
+#endif /* ndef FEATURE_KILL_POPUPS */
+
+#ifdef FEATURE_NO_GIFS
+   if (!err) err = map_conditional(exports, "FEATURE_NO_GIFS", 1);
+#else /* ifndef FEATURE_NO_GIFS */
+   if (!err) err = map_conditional(exports, "FEATURE_NO_GIFS", 0);
+#endif /* ndef FEATURE_NO_GIFS */
+
+#ifdef FEATURE_PTHREAD
+   if (!err) err = map_conditional(exports, "FEATURE_PTHREAD", 1);
+#else /* ifndef FEATURE_PTHREAD */
+   if (!err) err = map_conditional(exports, "FEATURE_PTHREAD", 0);
+#endif /* ndef FEATURE_PTHREAD */
+
+#ifdef FEATURE_STATISTICS
+   if (!err) err = map_conditional(exports, "FEATURE_STATISTICS", 1);
+#else /* ifndef FEATURE_STATISTICS */
+   if (!err) err = map_conditional(exports, "FEATURE_STATISTICS", 0);
+#endif /* ndef FEATURE_STATISTICS */
+
+#ifdef FEATURE_TOGGLE
+   if (!err) err = map_conditional(exports, "FEATURE_TOGGLE", 1);
+#else /* ifndef FEATURE_TOGGLE */
+   if (!err) err = map_conditional(exports, "FEATURE_TOGGLE", 0);
+#endif /* ndef FEATURE_TOGGLE */
+
+#ifdef FEATURE_TRUST
+   if (!err) err = map_conditional(exports, "FEATURE_TRUST", 1);
+#else /* ifndef FEATURE_TRUST */
+   if (!err) err = map_conditional(exports, "FEATURE_TRUST", 0);
+#endif /* ndef FEATURE_TRUST */
+
+#ifdef STATIC_PCRE
+   if (!err) err = map_conditional(exports, "STATIC_PCRE", 1);
+#else /* ifndef STATIC_PCRE */
+   if (!err) err = map_conditional(exports, "STATIC_PCRE", 0);
+#endif /* ndef STATIC_PCRE */
+
+#ifdef STATIC_PCRS
+   if (!err) err = map_conditional(exports, "STATIC_PCRS", 1);
+#else /* ifndef STATIC_PCRS */
+   if (!err) err = map_conditional(exports, "STATIC_PCRS", 0);
+#endif /* ndef STATIC_PCRS */
+
+   return err;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  show_rcs
+ *
+ * Description :  Create a string with the rcs info for all sourcefiles
+ *
+ * Parameters  :  None
+ *
+ * Returns     :  A string, or NULL on out-of-memory.
+ *
+ *********************************************************************/
+static char *show_rcs(void)
+{
+   char *result = strdup("");
+   char buf[BUFFER_SIZE];
+
+   /* Instead of including *all* dot h's in the project (thus creating a
+    * tremendous amount of dependencies), I will concede to declaring them
+    * as extern's.  This forces the developer to add to this list, but oh well.
+    */
+
+#define SHOW_RCS(__x)              \
+   {                               \
+      extern const char __x[];     \
+      sprintf(buf, "%s\n", __x);   \
+      string_append(&result, buf); \
+   }
+
+   /* In alphabetical order */
+   SHOW_RCS(actions_h_rcs)
+   SHOW_RCS(actions_rcs)
+#ifdef AMIGA
+   SHOW_RCS(amiga_h_rcs)
+   SHOW_RCS(amiga_rcs)
+#endif /* def AMIGA */
+   SHOW_RCS(cgi_h_rcs)
+   SHOW_RCS(cgi_rcs)
+#ifdef FEATURE_CGI_EDIT_ACTIONS
+   SHOW_RCS(cgiedit_h_rcs)
+   SHOW_RCS(cgiedit_rcs)
+#endif /* def FEATURE_CGI_EDIT_ACTIONS */
+   SHOW_RCS(cgisimple_h_rcs)
+   SHOW_RCS(cgisimple_rcs)
+#ifdef __MINGW32__
+   SHOW_RCS(cygwin_h_rcs)
+#endif
+   SHOW_RCS(deanimate_h_rcs)
+   SHOW_RCS(deanimate_rcs)
+   SHOW_RCS(encode_h_rcs)
+   SHOW_RCS(encode_rcs)
+   SHOW_RCS(errlog_h_rcs)
+   SHOW_RCS(errlog_rcs)
+   SHOW_RCS(filters_h_rcs)
+   SHOW_RCS(filters_rcs)
+   SHOW_RCS(gateway_h_rcs)
+   SHOW_RCS(gateway_rcs)
+   SHOW_RCS(jbsockets_h_rcs)
+   SHOW_RCS(jbsockets_rcs)
+   SHOW_RCS(jcc_h_rcs)
+   SHOW_RCS(jcc_rcs)
+#ifdef FEATURE_KILL_POPUPS
+   SHOW_RCS(killpopup_h_rcs)
+   SHOW_RCS(killpopup_rcs)
+#endif /* def FEATURE_KILL_POPUPS */
+   SHOW_RCS(list_h_rcs)
+   SHOW_RCS(list_rcs)
+   SHOW_RCS(loadcfg_h_rcs)
+   SHOW_RCS(loadcfg_rcs)
+   SHOW_RCS(loaders_h_rcs)
+   SHOW_RCS(loaders_rcs)
+   SHOW_RCS(miscutil_h_rcs)
+   SHOW_RCS(miscutil_rcs)
+   SHOW_RCS(parsers_h_rcs)
+   SHOW_RCS(parsers_rcs)
+   SHOW_RCS(pcrs_rcs)
+   SHOW_RCS(pcrs_h_rcs)
+   SHOW_RCS(project_h_rcs)
+   SHOW_RCS(ssplit_h_rcs)
+   SHOW_RCS(ssplit_rcs)
+   SHOW_RCS(urlmatch_h_rcs)
+   SHOW_RCS(urlmatch_rcs)
+#ifdef _WIN32
+#ifndef _WIN_CONSOLE
+   SHOW_RCS(w32log_h_rcs)
+   SHOW_RCS(w32log_rcs)
+   SHOW_RCS(w32res_h_rcs)
+   SHOW_RCS(w32taskbar_h_rcs)
+   SHOW_RCS(w32taskbar_rcs)
+#endif /* ndef _WIN_CONSOLE */
+   SHOW_RCS(win32_h_rcs)
+   SHOW_RCS(win32_rcs)
+#endif /* def _WIN32 */
+
+#undef SHOW_RCS
+
+   return result;
+
+}
+
+
+/*
+  Local Variables:
+  tab-width: 3
+  end:
+*/
diff --git a/src/cgisimple.h b/src/cgisimple.h
new file mode 100644 (file)
index 0000000..8f36749
--- /dev/null
@@ -0,0 +1,152 @@
+#ifndef CGISIMPLE_H_INCLUDED
+#define CGISIMPLE_H_INCLUDED
+#define CGISIMPLE_H_VERSION "$Id: cgisimple.h,v 1.11 2002/04/05 15:50:53 oes Exp $"
+/*********************************************************************
+ *
+ * File        :  $Source: /cvsroot/ijbswa/current/cgisimple.h,v $
+ *
+ * Purpose     :  Declares functions to intercept request, generate
+ *                html or gif answers, and to compose HTTP resonses.
+ *                
+ *                Functions declared include:
+ * 
+ *
+ * Copyright   :  Written by and Copyright (C) 2001 the SourceForge
+ *                Privoxy team. http://www.privoxy.org/
+ *
+ *                Based on the Internet Junkbuster originally written
+ *                by and Copyright (C) 1997 Anonymous Coders and 
+ *                Junkbusters Corporation.  http://www.junkbusters.com
+ *
+ *                This program is free software; you can redistribute it 
+ *                and/or modify it under the terms of the GNU General
+ *                Public License as published by the Free Software
+ *                Foundation; either version 2 of the License, or (at
+ *                your option) any later version.
+ *
+ *                This program is distributed in the hope that it will
+ *                be useful, but WITHOUT ANY WARRANTY; without even the
+ *                implied warranty of MERCHANTABILITY or FITNESS FOR A
+ *                PARTICULAR PURPOSE.  See the GNU General Public
+ *                License for more details.
+ *
+ *                The GNU General Public License should be included with
+ *                this file.  If not, you can view it at
+ *                http://www.gnu.org/copyleft/gpl.html
+ *                or write to the Free Software Foundation, Inc., 59
+ *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * Revisions   :
+ *    $Log: cgisimple.h,v $
+ *    Revision 1.11  2002/04/05 15:50:53  oes
+ *    added send-stylesheet CGI
+ *
+ *    Revision 1.10  2002/03/26 22:29:54  swa
+ *    we have a new homepage!
+ *
+ *    Revision 1.9  2002/03/24 13:25:43  swa
+ *    name change related issues
+ *
+ *    Revision 1.8  2002/03/16 23:54:06  jongfoster
+ *    Adding graceful termination feature, to help look for memory leaks.
+ *    If you enable this (which, by design, has to be done by hand
+ *    editing config.h) and then go to http://i.j.b/die, then the program
+ *    will exit cleanly after the *next* request.  It should free all the
+ *    memory that was used.
+ *
+ *    Revision 1.7  2002/03/08 16:43:59  oes
+ *    Renamed cgi_transparent_png to cgi_transparent_image
+ *
+ *    Revision 1.6  2002/03/07 03:48:59  oes
+ *     - Changed built-in images from GIF to PNG
+ *       (with regard to Unisys patent issue)
+ *
+ *    Revision 1.5  2002/01/22 23:26:03  jongfoster
+ *    Adding cgi_transparent_gif() for http://i.j.b/t
+ *
+ *    Revision 1.4  2001/10/23 21:48:19  jongfoster
+ *    Cleaning up error handling in CGI functions - they now send back
+ *    a HTML error page and should never cause a FATAL error.  (Fixes one
+ *    potential source of "denial of service" attacks).
+ *
+ *    CGI actions file editor that works and is actually useful.
+ *
+ *    Ability to toggle JunkBuster remotely using a CGI call.
+ *
+ *    You can turn off both the above features in the main configuration
+ *    file, e.g. if you are running a multi-user proxy.
+ *
+ *    Revision 1.3  2001/10/14 22:00:32  jongfoster
+ *    Adding support for a 404 error when an invalid CGI page is requested.
+ *
+ *    Revision 1.2  2001/10/02 15:31:20  oes
+ *    Introduced show-request cgi
+ *
+ *    Revision 1.1  2001/09/16 17:08:54  jongfoster
+ *    Moving simple CGI functions from cgi.c to new file cgisimple.c
+ *
+ *
+ **********************************************************************/
+\f
+
+#include "project.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * CGI functions
+ */
+extern jb_err cgi_default      (struct client_state *csp,
+                                struct http_response *rsp,
+                                const struct map *parameters);
+extern jb_err cgi_error_404    (struct client_state *csp,
+                                struct http_response *rsp,
+                                const struct map *parameters);
+extern jb_err cgi_robots_txt   (struct client_state *csp,
+                                struct http_response *rsp,
+                                const struct map *parameters);
+extern jb_err cgi_send_banner  (struct client_state *csp,
+                                struct http_response *rsp,
+                                const struct map *parameters);
+extern jb_err cgi_show_status  (struct client_state *csp,
+                                struct http_response *rsp,
+                                const struct map *parameters);
+extern jb_err cgi_show_url_info(struct client_state *csp,
+                                struct http_response *rsp,
+                                const struct map *parameters);
+extern jb_err cgi_show_version (struct client_state *csp,
+                                struct http_response *rsp,
+                                const struct map *parameters);
+extern jb_err cgi_show_request (struct client_state *csp,
+                                struct http_response *rsp,
+                                const struct map *parameters);
+extern jb_err cgi_transparent_image (struct client_state *csp,
+                                     struct http_response *rsp,
+                                     const struct map *parameters);
+extern jb_err cgi_send_stylesheet(struct client_state *csp,
+                                  struct http_response *rsp,
+                                  const struct map *parameters);
+
+#ifdef FEATURE_GRACEFUL_TERMINATION
+extern jb_err cgi_die (struct client_state *csp,
+                       struct http_response *rsp,
+                       const struct map *parameters);
+#endif
+
+/* Revision control strings from this header and associated .c file */
+extern const char cgisimple_rcs[];
+extern const char cgisimple_h_rcs[];
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* ndef CGISIMPLE_H_INCLUDED */
+
+/*
+  Local Variables:
+  tab-width: 3
+  end:
+*/
diff --git a/src/cygwin.h b/src/cygwin.h
new file mode 100644 (file)
index 0000000..1ed69f6
--- /dev/null
@@ -0,0 +1,76 @@
+#ifndef CYGWIN_H_INCLUDED
+#define CYGWIN_H_INCLUDED
+#define CYGWIN_H_VERSION "$Id: cygwin.h,v 1.4 2002/03/26 22:29:54 swa Exp $"
+/*********************************************************************
+ *
+ * File        :  $Source: /cvsroot/ijbswa/current/cygwin.h,v $
+ *
+ * Purpose     :  The windows.h file seems to be a *tad* different, so I
+ *                will bridge the gaps here.  Perhaps I should convert the
+ *                latest SDK too?  Shudder, I think not.
+ *
+ * Copyright   :  Written by and Copyright (C) 2001 the SourceForge
+ *                Privoxy team. http://www.privoxy.org/
+ *
+ *                Based on the Internet Junkbuster originally written
+ *                by and Copyright (C) 1997 Anonymous Coders and 
+ *                Junkbusters Corporation.  http://www.junkbusters.com
+ *
+ *                This program is free software; you can redistribute it 
+ *                and/or modify it under the terms of the GNU General
+ *                Public License as published by the Free Software
+ *                Foundation; either version 2 of the License, or (at
+ *                your option) any later version.
+ *
+ *                This program is distributed in the hope that it will
+ *                be useful, but WITHOUT ANY WARRANTY; without even the
+ *                implied warranty of MERCHANTABILITY or FITNESS FOR A
+ *                PARTICULAR PURPOSE.  See the GNU General Public
+ *                License for more details.
+ *
+ *                The GNU General Public License should be included with
+ *                this file.  If not, you can view it at
+ *                http://www.gnu.org/copyleft/gpl.html
+ *                or write to the Free Software Foundation, Inc., 59
+ *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * Revisions   :
+ *    $Log: cygwin.h,v $
+ *    Revision 1.4  2002/03/26 22:29:54  swa
+ *    we have a new homepage!
+ *
+ *    Revision 1.3  2002/03/24 13:25:43  swa
+ *    name change related issues
+ *
+ *    Revision 1.2  2001/07/29 18:43:08  jongfoster
+ *    Changing #ifdef _FILENAME_H to FILENAME_H_INCLUDED, to conform to
+ *    ANSI C rules.
+ *
+ *    Revision 1.1.1.1  2001/05/15 13:58:51  oes
+ *    Initial import of version 2.9.3 source tree
+ *
+ *
+ *********************************************************************/
+
+/* Conditionally include this whole file. */
+#ifdef __MINGW32__
+
+/* Hmmm, seems to be overlooked. */
+#define _RICHEDIT_VER 0x0300
+
+/*
+ * Named slightly different ... but not in Cygwin v1.3.1 ...
+ *
+ * #define LVITEM   LV_ITEM
+ * #define LVCOLUMN LV_COLUMN
+ */
+
+#endif /* def __MINGW32__ */
+#endif /* ndef CYGWIN_H_INCLUDED */
+
+
+/*
+  Local Variables:
+  tab-width: 3
+  end:
+*/
diff --git a/src/deanimate.c b/src/deanimate.c
new file mode 100644 (file)
index 0000000..8c33bc5
--- /dev/null
@@ -0,0 +1,507 @@
+const char deanimate_rcs[] = "$Id: deanimate.c,v 1.12 2002/05/12 21:36:29 jongfoster Exp $";
+/*********************************************************************
+ *
+ * File        :  $Source: /cvsroot/ijbswa/current/deanimate.c,v $
+ *
+ * Purpose     :  Declares functions to deanimate GIF images on the fly.
+ *                
+ *                Functions declared include: gif_deanimate, buf_free,
+ *                buf_copy,  buf_getbyte, gif_skip_data_block, and
+ *                gif_extract_image
+ *
+ * Copyright   :  Written by and Copyright (C) 2001 by the the SourceForge
+ *                Privoxy team. http://www.privoxy.org/
+ *
+ *                Based on the GIF file format specification (see
+ *                http://tronche.com/computer-graphics/gif/gif89a.html)
+ *                and ideas from the Image::DeAnim Perl module by
+ *                Ken MacFarlane, <ksm+cpan@universal.dca.net>
+ *
+ *                This program is free software; you can redistribute it 
+ *                and/or modify it under the terms of the GNU General
+ *                Public License as published by the Free Software
+ *                Foundation; either version 2 of the License, or (at
+ *                your option) any later version.
+ *
+ *                This program is distributed in the hope that it will
+ *                be useful, but WITHOUT ANY WARRANTY; without even the
+ *                implied warranty of MERCHANTABILITY or FITNESS FOR A
+ *                PARTICULAR PURPOSE.  See the GNU General Public
+ *                License for more details.
+ *
+ *                The GNU General Public License should be included with
+ *                this file.  If not, you can view it at
+ *                http://www.gnu.org/copyleft/gpl.html
+ *                or write to the Free Software Foundation, Inc., 59
+ *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * Revisions   :
+ *    $Log: deanimate.c,v $
+ *    Revision 1.12  2002/05/12 21:36:29  jongfoster
+ *    Correcting function comments
+ *
+ *    Revision 1.11  2002/03/26 22:29:54  swa
+ *    we have a new homepage!
+ *
+ *    Revision 1.10  2002/03/24 13:25:43  swa
+ *    name change related issues
+ *
+ *    Revision 1.9  2002/03/13 00:27:04  jongfoster
+ *    Killing warnings
+ *
+ *    Revision 1.8  2002/03/09 19:42:47  jongfoster
+ *    Fixing more warnings
+ *
+ *    Revision 1.7  2002/03/08 17:46:04  jongfoster
+ *    Fixing int/size_t warnings
+ *
+ *    Revision 1.6  2002/03/07 03:46:17  oes
+ *    Fixed compiler warnings
+ *
+ *    Revision 1.5  2001/09/10 10:16:06  oes
+ *    Silenced compiler warnings
+ *
+ *    Revision 1.4  2001/07/18 12:28:49  oes
+ *    - Added feature for extracting the first frame
+ *      to gif_deanimate
+ *    - Separated image buffer extension into buf_extend
+ *    - Extended gif deanimation to GIF87a (untested!)
+ *    - Cosmetics
+ *
+ *    Revision 1.3  2001/07/15 13:57:50  jongfoster
+ *    Adding #includes string.h and miscutil.h
+ *
+ *    Revision 1.2  2001/07/13 13:46:20  oes
+ *    Introduced GIF deanimation feature
+ *
+ *
+ **********************************************************************/
+\f
+
+#include "config.h"
+
+#include <string.h>
+#include <fcntl.h>
+
+#include "project.h"
+#include "deanimate.h"
+#include "miscutil.h"
+
+const char deanimate_h_rcs[] = DEANIMATE_H_VERSION;
+
+/*********************************************************************
+ * 
+ * Function    :  buf_free
+ *
+ * Description :  Safely frees a struct binbuffer
+ *
+ * Parameters  :
+ *          1  :  buf = Pointer to the binbuffer to be freed
+ *
+ * Returns     :  N/A
+ *
+ *********************************************************************/
+void buf_free(struct binbuffer *buf)
+{
+   if (buf == NULL) return;
+
+   if (buf->buffer != NULL)
+   {
+      free(buf->buffer);
+   }
+
+   free(buf);
+
+}
+
+
+/*********************************************************************
+ * 
+ * Function    :  buf_extend
+ *
+ * Description :  Ensure that a given binbuffer can hold a given amount
+ *                of bytes, by reallocating its buffer if necessary.
+ *                Allocate new mem in chunks of 1024 bytes, so we don't
+ *                have to realloc() too often.
+ *
+ * Parameters  :
+ *          1  :  buf = Pointer to the binbuffer
+ *          2  :  length = Desired minimum size
+ *                
+ *
+ * Returns     :  0 on success, 1 on failiure.
+ *
+ *********************************************************************/
+int buf_extend(struct binbuffer *buf, size_t length)
+{
+   char *newbuf;
+
+   if (buf->offset + length > buf->size)
+   {
+      buf->size = ((buf->size + length + (size_t)1023) & ~(size_t)1023);
+      newbuf = (char *)realloc(buf->buffer, buf->size);
+
+      if (newbuf == NULL)
+      {
+         freez(buf->buffer);
+         return 1;
+      }
+      else
+      {
+         buf->buffer = newbuf;
+         return 0;
+      }
+   }
+   return 0;
+
+}
+
+
+/*********************************************************************
+ * 
+ * Function    :  buf_copy
+ *
+ * Description :  Safely copies a given amount of bytes from one
+ *                struct binbuffer to another, advancing the
+ *                offsets appropriately.
+ *
+ * Parameters  :
+ *          1  :  src = Pointer to the source binbuffer
+ *          2  :  dst = Pointer to the destination binbuffer
+ *          3  :  length = Number of bytes to be copied
+ *
+ * Returns     :  0 on success, 1 on failiure.
+ *
+ *********************************************************************/
+int buf_copy(struct binbuffer *src, struct binbuffer *dst, size_t length)
+{
+
+   /*
+    * Sanity check: Can't copy more data than we have
+    */
+   if (src->offset + length > src->size) 
+   {
+      return 1;
+   }
+
+   /*
+    * Ensure that dst can hold the new data
+    */
+   if (buf_extend(dst, length)) 
+   {
+      return 1;
+   }
+
+   /*
+    * Now that it's safe, memcpy() the desired amount of
+    * data from src to dst and adjust the offsets
+    */
+   memcpy(dst->buffer + dst->offset, src->buffer + src->offset, length);
+   src->offset += length;
+   dst->offset += length;
+
+   return 0;
+
+}
+
+
+/*********************************************************************
+ * 
+ * Function    :  buf_getbyte
+ *
+ * Description :  Safely gets a byte from a given binbuffer at a
+ *                given offset
+ *
+ * Parameters  :
+ *          1  :  src = Pointer to the source binbuffer
+ *          2  :  offset = Offset to the desired byte
+ *
+ * Returns     :  The byte on success, or 0 on failiure
+ *
+ *********************************************************************/
+unsigned char buf_getbyte(struct binbuffer *src, size_t offset)
+{
+   if (src->offset + offset < src->size)
+   {
+      return (unsigned char)*(src->buffer + src->offset + offset);
+   }
+   else
+   {
+      return '\0';
+   }
+
+}
+
+
+/*********************************************************************
+ * 
+ * Function    :  gif_skip_data_block
+ *
+ * Description :  Safely advances the offset of a given struct binbuffer
+ *                that contains a GIF image and whose offset is
+ *                positioned at the start of a data block, behind
+ *                that block.
+ *
+ * Parameters  :
+ *          1  :  buf = Pointer to the binbuffer
+ *
+ * Returns     :  0 on success, or 1 on failiure
+ *
+ *********************************************************************/
+int gif_skip_data_block(struct binbuffer *buf)
+{
+   unsigned char c;
+
+   /* 
+    * Data blocks are sequences of chunks, which are headed
+    * by a one-byte length field, with the last chunk having
+    * zero length.
+    */
+   while((c = buf_getbyte(buf, 0)) != '\0')
+   {
+      if ((buf->offset += c + 1) >= buf->size - 1)
+      {
+         return 1;
+      }
+   }
+   buf->offset++;
+
+   return 0;
+
+}
+
+
+/*********************************************************************
+ * 
+ * Function    :  gif_extract_image
+ *
+ * Description :  Safely extracts an image data block from a given
+ *                struct binbuffer that contains a GIF image and whose
+ *                offset is positioned at the start of a data block 
+ *                into a given destination binbuffer.
+ *
+ * Parameters  :
+ *          1  :  src = Pointer to the source binbuffer
+ *          2  :  dst = Pointer to the destination binbuffer
+ *
+ * Returns     :  0 on success, or 1 on failiure
+ *
+ *********************************************************************/
+int gif_extract_image(struct binbuffer *src, struct binbuffer *dst)
+{
+   unsigned char c;
+   
+   /*
+    * Remember the colormap flag and copy the image head
+    */
+   c = buf_getbyte(src, 9);
+   if (buf_copy(src, dst, 10))
+   {
+      return 1;
+   }
+
+   /*
+    * If the image has a local colormap, copy it.
+    */
+   if (c & 0x80)
+   {
+      if (buf_copy(src, dst, (size_t) 3 * (1 << ((c & 0x07) + 1))))
+      {
+         return 1;
+      }           
+   }
+   if (buf_copy(src, dst, 1)) return 1;
+
+   /*
+    * Copy the image chunk by chunk.
+    */
+   while((c = buf_getbyte(src, 0)) != '\0')
+   {
+      if (buf_copy(src, dst, 1 + (size_t) c)) return 1;
+   }
+   if (buf_copy(src, dst, 1)) return 1;
+
+   /*
+    * Trim and rewind the dst buffer
+    */
+   if (NULL == (dst->buffer = (char *)realloc(dst->buffer, dst->offset))) return 1;
+   dst->size = dst->offset;
+   dst->offset = 0;
+
+   return(0);
+
+}
+
+/*********************************************************************
+ * 
+ * Function    :  gif_deanimate
+ *
+ * Description :  Deanimate a given GIF image, i.e. given a GIF with
+ *                an (optional) image block and an arbitrary number
+ *                of image extension blocks, produce an output GIF with
+ *                only one image block that contains the last image
+ *                (extenstion) block of the original.
+ *                Also strip Comments, Application extenstions, etc.
+ *
+ * Parameters  :
+ *          1  :  src = Pointer to the source binbuffer
+ *          2  :  dst = Pointer to the destination binbuffer
+ *          3  :  get_first_image = Flag: If set, get the first image
+ *                                        If unset (default), get the last
+ *
+ * Returns     :  0 on success, or 1 on failiure
+ *
+ *********************************************************************/
+int gif_deanimate(struct binbuffer *src, struct binbuffer *dst, int get_first_image)
+{
+   unsigned char c;
+   struct binbuffer *image;
+
+   if (NULL == src || NULL == dst)
+   {
+      return 1;
+   }
+
+   c = buf_getbyte(src, 10);
+
+   /*
+    * Check & copy GIF header 
+    */
+   if (strncmp(src->buffer, "GIF89a", 6) && strncmp(src->buffer, "GIF87a", 6)) 
+   {
+      return 1;
+   }
+   else
+   {
+      if (buf_copy(src, dst, 13))
+      {
+         return 1;
+      }
+   }
+
+   /*
+    * Look for global colormap and  copy if found.
+    */
+   if(c & 0x80)
+   {
+      if (buf_copy(src, dst, (size_t) 3 * (1 << ((c & 0x07) + 1))))
+      {
+         return 1;
+      }
+   }
+
+   /*
+    * Reserve a buffer for the current image block
+    */
+   if (NULL == (image = (struct binbuffer *)zalloc(sizeof(*image))))
+   {
+      return 1;
+   }
+
+   /*
+    * Parse the GIF block by block and copy the relevant
+    * parts to dst
+    */
+   while(src->offset < src->size)
+   {
+      switch(buf_getbyte(src, 0))
+      {
+         /*
+          *  End-of-GIF Marker: Append current image and return
+          */
+      case 0x3b:
+         goto write;
+
+         /* 
+          * Image block: Extract to current image buffer.
+          */
+      case 0x2c:
+         image->offset = 0;
+         if (gif_extract_image(src, image)) goto failed;
+         if (get_first_image) goto write;
+         continue;
+
+         /*
+          * Extension block: Look at next byte and decide
+          */
+      case 0x21:
+         switch (buf_getbyte(src, 1))
+         {
+            /*
+             * Image extension: Copy extension  header and image
+             *                  to the current image buffer
+             */
+         case 0xf9:
+            image->offset = 0;
+            if (buf_copy(src, image, 8) || buf_getbyte(src, 0) != 0x2c) goto failed;
+            if (gif_extract_image(src, image)) goto failed;
+            if (get_first_image) goto write;
+            continue;
+
+            /*
+             * Application extension: Skip
+             */
+         case 0xff:
+            if ((src->offset += 14) >= src->size || gif_skip_data_block(src)) goto failed;
+            continue;
+
+            /*
+             * Comment extension: Skip
+             */
+         case 0xfe:
+            if ((src->offset += 2) >= src->size || gif_skip_data_block(src)) goto failed;
+            continue;
+
+            /*
+             * Plain text extension: Skip
+             */
+         case 0x01:
+            if ((src->offset += 15) >= src->size || gif_skip_data_block(src)) goto failed;
+            continue;
+
+            /*
+             * Ooops, what type of extension is that?
+             */
+         default:
+            goto failed;
+
+         }
+
+         /*
+          * Ooops, what type of block is that?
+          */
+      default:
+         goto failed;
+         
+      }
+   } /* -END- while src */
+
+   /*
+    * Either we got here by goto, or because the GIF is
+    * bogus and EOF was reached before an end-of-gif marker 
+    * was found.
+    */
+
+failed:
+   buf_free(image);
+   return 1;
+
+   /*
+    * Append the current image to dst and return
+    */
+
+write:
+   if (buf_copy(image, dst, image->size)) goto failed;
+   if (buf_extend(dst, 1)) goto failed;
+   *(dst->buffer + dst->offset++) = 0x3b;
+   buf_free(image);
+   return 0;
+
+}
+
+
+/*
+  Local Variables:
+  tab-width: 3
+  end:
+*/
diff --git a/src/deanimate.h b/src/deanimate.h
new file mode 100644 (file)
index 0000000..1e8d60f
--- /dev/null
@@ -0,0 +1,105 @@
+#ifndef DEANIMATE_H_INCLUDED
+#define DEANIMATE_H_INCLUDED
+#define DEANIMATE_H_VERSION "$Id: deanimate.h,v 1.8 2002/03/26 22:29:54 swa Exp $"
+/*********************************************************************
+ *
+ * File        :  $Source: /cvsroot/ijbswa/current/deanimate.h,v $
+ *
+ * Purpose     :  Declares functions to deanimate GIF images on the fly.
+ *                
+ *                Functions declared include: gif_deanimate, buf_free
+ *                
+ *
+ * Copyright   :  Written by and Copyright (C) 2001 Andreas S. Oesterhelt
+ *                for the Privoxy team. http://www.privoxy.org/
+ *
+ *                Based on ideas from the Image::DeAnim Perl module by
+ *                Ken MacFarlane, <ksm+cpan@universal.dca.net>
+ *
+ *                Based on the Internet Junkbuster originally written
+ *                by and Copyright (C) 1997 Anonymous Coders and 
+ *                Junkbusters Corporation.  http://www.junkbusters.com
+ *
+ *                This program is free software; you can redistribute it 
+ *                and/or modify it under the terms of the GNU General
+ *                Public License as published by the Free Software
+ *                Foundation; either version 2 of the License, or (at
+ *                your option) any later version.
+ *
+ *                This program is distributed in the hope that it will
+ *                be useful, but WITHOUT ANY WARRANTY; without even the
+ *                implied warranty of MERCHANTABILITY or FITNESS FOR A
+ *                PARTICULAR PURPOSE.  See the GNU General Public
+ *                License for more details.
+ *
+ *                The GNU General Public License should be included with
+ *                this file.  If not, you can view it at
+ *                http://www.gnu.org/copyleft/gpl.html
+ *                or write to the Free Software Foundation, Inc., 59
+ *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * Revisions   :
+ *    $Log: deanimate.h,v $
+ *    Revision 1.8  2002/03/26 22:29:54  swa
+ *    we have a new homepage!
+ *
+ *    Revision 1.7  2002/03/24 13:25:43  swa
+ *    name change related issues
+ *
+ *    Revision 1.6  2002/03/08 17:46:04  jongfoster
+ *    Fixing int/size_t warnings
+ *
+ *    Revision 1.5  2002/03/07 03:46:17  oes
+ *    Fixed compiler warnings
+ *
+ *    Revision 1.4  2001/07/29 18:50:04  jongfoster
+ *    Fixing "extern C" block, and renaming #define _DEANIMATE_H
+ *
+ *    Revision 1.3  2001/07/18 12:29:05  oes
+ *    Updated prototype for gif_deanimate
+ *
+ *    Revision 1.2  2001/07/13 13:46:20  oes
+ *    Introduced GIF deanimation feature
+ *
+ *
+ *********************************************************************/
+\f
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * A struct that holds a buffer, a read/write offset,
+ * and the buffer's capacity.
+ */
+struct binbuffer
+{
+   char *buffer; 
+   size_t offset;   
+   size_t size;    
+};
+
+/*
+ * Function prototypes
+ */
+extern int gif_deanimate(struct binbuffer *src, struct binbuffer *dst, int get_first_image);
+extern void buf_free(struct binbuffer *buf);
+
+/* 
+ * Revision control strings from this header and associated .c file
+ */
+extern const char deanimate_rcs[];
+extern const char deanimate_h_rcs[];
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* ndef DEANIMATE_H_INCLUDED */
+
+/*
+  Local Variables:
+  tab-width: 3
+  end:
+*/
diff --git a/src/encode.c b/src/encode.c
new file mode 100644 (file)
index 0000000..c1e38d8
--- /dev/null
@@ -0,0 +1,486 @@
+const char encode_rcs[] = "$Id: encode.c,v 1.8 2002/03/26 22:29:54 swa Exp $";
+/*********************************************************************
+ *
+ * File        :  $Source: /cvsroot/ijbswa/current/encode.c,v $
+ *
+ * Purpose     :  Functions to encode and decode URLs, and also to
+ *                encode cookies and HTML text.
+ *
+ * Copyright   :  Written by and Copyright (C) 2001 the SourceForge
+ *                Privoxy team. http://www.privoxy.org/
+ *
+ *                Based on the Internet Junkbuster originally written
+ *                by and Copyright (C) 1997 Anonymous Coders and 
+ *                Junkbusters Corporation.  http://www.junkbusters.com
+ *
+ *                This program is free software; you can redistribute it 
+ *                and/or modify it under the terms of the GNU General
+ *                Public License as published by the Free Software
+ *                Foundation; either version 2 of the License, or (at
+ *                your option) any later version.
+ *
+ *                This program is distributed in the hope that it will
+ *                be useful, but WITHOUT ANY WARRANTY; without even the
+ *                implied warranty of MERCHANTABILITY or FITNESS FOR A
+ *                PARTICULAR PURPOSE.  See the GNU General Public
+ *                License for more details.
+ *
+ *                The GNU General Public License should be included with
+ *                this file.  If not, you can view it at
+ *                http://www.gnu.org/copyleft/gpl.html
+ *                or write to the Free Software Foundation, Inc., 59
+ *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * Revisions   :
+ *    $Log: encode.c,v $
+ *    Revision 1.8  2002/03/26 22:29:54  swa
+ *    we have a new homepage!
+ *
+ *    Revision 1.7  2002/03/24 13:25:43  swa
+ *    name change related issues
+ *
+ *    Revision 1.6  2002/03/13 00:27:04  jongfoster
+ *    Killing warnings
+ *
+ *    Revision 1.5  2002/03/07 03:46:53  oes
+ *    Fixed compiler warnings etc
+ *
+ *    Revision 1.4  2002/01/22 23:28:07  jongfoster
+ *    Adding convenience function html_encode_and_free_original()
+ *    Making all functions accept NULL paramaters - in this case, they
+ *    simply return NULL.  This allows error-checking to be deferred.
+ *
+ *    Revision 1.3  2001/11/13 00:16:40  jongfoster
+ *    Replacing references to malloc.h with the standard stdlib.h
+ *    (See ANSI or K&R 2nd Ed)
+ *
+ *    Revision 1.2  2001/05/17 22:52:35  oes
+ *     - Cleaned CRLF's from the sources and related files
+ *
+ *    Revision 1.1.1.1  2001/05/15 13:58:51  oes
+ *    Initial import of version 2.9.3 source tree
+ *
+ *
+ *********************************************************************/
+\f
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "encode.h"
+
+const char encode_h_rcs[] = ENCODE_H_VERSION;
+
+/* Maps special characters in a URL to their equivalent % codes. */
+static const char * const url_code_map[256] = {
+   NULL, "%01", "%02", "%03", "%04", "%05", "%06", "%07", "%08", "%09",
+   "%0A", "%0B", "%0C", "%0D", "%0E", "%0F", "%10", "%11", "%12", "%13",
+   "%14", "%15", "%16", "%17", "%18", "%19", "%1A", "%1B", "%1C", "%1D",
+   "%1E", "%1F", "+",   "%21", "%22", "%23", "%24", "%25", "%26", "%27",
+   "%28", "%29", NULL,  "%2B", "%2C", NULL,  NULL,  "%2F", NULL,  NULL,
+   NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  "%3A", "%3B",
+   "%3C", "%3D", "%3E", "%3F", NULL,  NULL,  NULL,  NULL,  NULL,  NULL,
+   NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,
+   NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,
+   NULL,  "%5B", "%5C", "%5D", "%5E", NULL,  "%60", NULL,  NULL,  NULL,
+   NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,
+   NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,  NULL,
+   NULL,  NULL,  NULL,  "%7B", "%7C", "%7D", "%7E", "%7F", "%80", "%81",
+   "%82", "%83", "%84", "%85", "%86", "%87", "%88", "%89", "%8A", "%8B",
+   "%8C", "%8D", "%8E", "%8F", "%90", "%91", "%92", "%93", "%94", "%95",
+   "%96", "%97", "%98", "%99", "%9A", "%9B", "%9C", "%9D", "%9E", "%9F",
+   "%A0", "%A1", "%A2", "%A3", "%A4", "%A5", "%A6", "%A7", "%A8", "%A9",
+   "%AA", "%AB", "%AC", "%AD", "%AE", "%AF", "%B0", "%B1", "%B2", "%B3",
+   "%B4", "%B5", "%B6", "%B7", "%B8", "%B9", "%BA", "%BB", "%BC", "%BD",
+   "%BE", "%BF", "%C0", "%C1", "%C2", "%C3", "%C4", "%C5", "%C6", "%C7",
+   "%C8", "%C9", "%CA", "%CB", "%CC", "%CD", "%CE", "%CF", "%D0", "%D1",
+   "%D2", "%D3", "%D4", "%D5", "%D6", "%D7", "%D8", "%D9", "%DA", "%DB",
+   "%DC", "%DD", "%DE", "%DF", "%E0", "%E1", "%E2", "%E3", "%E4", "%E5",
+   "%E6", "%E7", "%E8", "%E9", "%EA", "%EB", "%EC", "%ED", "%EE", "%EF",
+   "%F0", "%F1", "%F2", "%F3", "%F4", "%F5", "%F6", "%F7", "%F8", "%F9",
+   "%FA", "%FB", "%FC", "%FD", "%FE", "%FF"
+};
+
+/* Maps special characters in HTML to their equivalent entites. */
+static const char * const html_code_map[256] = {
+   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+   NULL, NULL, NULL, NULL,"&quot;",NULL,NULL,NULL,"&amp;",NULL,
+   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+   "&lt;",NULL,"&gt;",NULL,NULL, NULL, NULL, NULL, NULL, NULL,
+   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+   NULL, NULL, NULL, NULL, NULL, NULL
+};
+
+/* Maps special characters in a cookie to their equivalent % codes. */
+static const char * const cookie_code_map[256] = {
+   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+   NULL, NULL, "+",  NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+   NULL, NULL, NULL, NULL, "%2C",NULL, NULL, NULL, NULL, NULL,
+   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, "%3B",
+   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+   NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+   NULL, NULL, NULL, NULL, NULL, NULL
+};
+
+
+/*********************************************************************
+ *
+ * Function    :  html_encode
+ *
+ * Description :  Encodes a string so it's not interpreted as
+ *                containing HTML tags or entities.
+ *                Replaces <, >, &, and " with the appropriate HTML
+ *                entities.
+ *
+ * Parameters  :
+ *          1  :  s = String to encode.  Null-terminated.
+ *
+ * Returns     :  Encoded string, newly allocated on the heap. 
+ *                Caller is responsible for freeing it with free().
+ *                If s is NULL, or on out-of memory, returns NULL.
+ *
+ *********************************************************************/
+char * html_encode(const char *s)
+{
+   char * buf;
+   
+   if (s == NULL)
+   {
+      return NULL;
+   }
+
+   /* each input char can expand to at most 6 chars */
+   buf = (char *) malloc((strlen(s) * 6) + 1);
+
+   if (buf)
+   {
+      char c;
+      char * p = buf;
+      while ( (c = *s++) != '\0')
+      {
+         const char * replace_with = html_code_map[(unsigned char) c];
+         if(replace_with != NULL)
+         {
+            strcpy(p, replace_with);
+            p += strlen(replace_with);
+         }
+         else
+         {
+            *p++ = c;
+         }
+      }
+
+      *p = '\0';
+   }
+
+   return(buf);
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  html_encode_and_free_original
+ *
+ * Description :  Encodes a string so it's not interpreted as
+ *                containing HTML tags or entities.
+ *                Replaces <, >, &, and " with the appropriate HTML
+ *                entities.  Free()s original string.
+ *                If original string is NULL, simply returns NULL.
+ *
+ * Parameters  :
+ *          1  :  s = String to encode.  Null-terminated.
+ *
+ * Returns     :  Encoded string, newly allocated on the heap. 
+ *                Caller is responsible for freeing it with free().
+ *                If s is NULL, or on out-of memory, returns NULL.
+ *
+ *********************************************************************/
+char * html_encode_and_free_original(char *s)
+{
+   char * result;
+   
+   if (s == NULL)
+   {
+      return NULL;
+   }
+
+   result = html_encode(s);
+   free(s);
+
+   return result;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  cookie_encode
+ *
+ * Description :  Encodes a string so it can be used in a cookie.
+ *                Replaces " ", ",", and ";" with the appropriate
+ *                codes.
+ *
+ * Parameters  :
+ *          1  :  s = String to encode.  Null-terminated.
+ *
+ * Returns     :  Encoded string, newly allocated on the heap. 
+ *                Caller is responsible for freeing it with free().
+ *                If s is NULL, or on out-of memory, returns NULL.
+ *
+ *********************************************************************/
+char * cookie_encode(const char *s)
+{
+   char * buf;
+
+   if (s == NULL)
+   {
+      return NULL;
+   }
+
+   /* each input char can expand to at most 3 chars */
+   buf = (char *) malloc((strlen(s) * 3) + 1);
+
+   if (buf)
+   {
+      char c;
+      char * p = buf;
+      while ( (c = *s++) != '\0')
+      {
+         const char * replace_with = cookie_code_map[(unsigned char) c];
+         if (replace_with != NULL)
+         {
+            strcpy(p, replace_with);
+            p += strlen(replace_with);
+         }
+         else
+         {
+            *p++ = c;
+         }
+      }
+
+      *p = '\0';
+   }
+
+   return(buf);
+}
+
+/*********************************************************************
+ *
+ * Function    :  url_encode
+ *
+ * Description :  Encodes a string so it can be used in a URL
+ *                query string.  Replaces special characters with
+ *                the appropriate %xx codes.
+ *
+ * Parameters  :
+ *          1  :  s = String to encode.  Null-terminated.
+ *
+ * Returns     :  Encoded string, newly allocated on the heap. 
+ *                Caller is responsible for freeing it with free().
+ *                If s is NULL, or on out-of memory, returns NULL.
+ *
+ *********************************************************************/
+char * url_encode(const char *s)
+{
+   char * buf;
+
+   if (s == NULL)
+   {
+      return NULL;
+   }
+
+   /* each input char can expand to at most 3 chars */
+   buf = (char *) malloc((strlen(s) * 3) + 1);
+
+   if (buf)
+   {
+      char c;
+      char * p = buf;
+      while( (c = *s++) != '\0')
+      {
+         const char * replace_with = url_code_map[(unsigned char) c];
+         if (replace_with != NULL)
+         {
+            strcpy(p, replace_with);
+            p += strlen(replace_with);
+         }
+         else
+         {
+            *p++ = c;
+         }
+      }
+
+      *p = '\0';
+
+   }
+
+   return(buf);
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  xdtoi
+ *
+ * Description :  Converts a single hex digit to an integer.
+ *
+ * Parameters  :
+ *          1  :  d = in the range of ['0'..'9', 'A'..'F', 'a'..'f']
+ *
+ * Returns     :  The integer value, or -1 for non-hex characters.
+ *
+ *********************************************************************/
+static int xdtoi(const int d)
+{
+   if ((d >= '0') && (d <= '9'))
+   {
+      return(d - '0');
+   }
+   else if ((d >= 'a') && (d <= 'f')) 
+   {
+      return(d - 'a' + 10);
+   }
+   else if ((d >= 'A') && (d <= 'F'))
+   {
+      return(d - 'A' + 10);
+   }
+   else
+   {
+      return(-1);
+   }
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  xtoi
+ *
+ * Description :  Hex string to integer conversion.
+ *
+ * Parameters  :
+ *          1  :  s = a 2 digit hex string (e.g. "1f").  Only the
+ *                    first two characters will be looked at.
+ *
+ * Returns     :  The integer value, or 0 for non-hex strings.
+ *
+ *********************************************************************/
+static int xtoi(const char *s)
+{
+   int d1, d2;
+
+   d1 = xdtoi(*s);
+   if(d1 >= 0)
+   {
+      d2 = xdtoi(*(s+1));
+      if(d2 >= 0)
+      {
+         return (d1 << 4) + d2;
+      }
+   }
+
+   return 0;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  url_decode
+ *
+ * Description :  Decodes a URL query string, replacing %xx codes
+ *                with their decoded form.
+ *
+ * Parameters  :
+ *          1  :  s = String to decode.  Null-terminated.
+ *
+ * Returns     :  Decoded string, newly allocated on the heap. 
+ *                Caller is responsible for freeing it with free().
+ *
+ *********************************************************************/
+char *url_decode(const char * s)
+{
+   char *buf = malloc(strlen(s) + 1);
+   char *q = buf;
+
+   if (buf)
+   {
+      while (*s)
+      {
+         switch (*s)
+         {
+            case '+':
+               s++;
+               *q++ = ' ';
+               break;
+
+            case '%':
+               if ((*q = xtoi(s + 1)) != '\0')
+               {
+                  s += 3;
+                  q++;
+               }
+               else
+               {
+                  /* malformed, just use it */
+                  *q++ = *s++;
+               }
+               break;
+
+            default:
+               *q++ = *s++;
+               break;
+         }
+      }
+      *q = '\0';
+   }
+
+   return(buf);
+
+}
+
+
+/*
+  Local Variables:
+  tab-width: 3
+  end:
+*/
diff --git a/src/encode.h b/src/encode.h
new file mode 100644 (file)
index 0000000..b810e42
--- /dev/null
@@ -0,0 +1,85 @@
+#ifndef ENCODE_H_INCLUDED
+#define ENCODE_H_INCLUDED
+#define ENCODE_H_VERSION "$Id: encode.h,v 1.5 2002/03/26 22:29:54 swa Exp $"
+/*********************************************************************
+ *
+ * File        :  $Source: /cvsroot/ijbswa/current/encode.h,v $
+ *
+ * Purpose     :  Functions to encode and decode URLs, and also to
+ *                encode cookies and HTML text.
+ *
+ * Copyright   :  Written by and Copyright (C) 2001 the SourceForge
+ *                Privoxy team. http://www.privoxy.org/
+ *
+ *                Based on the Internet Junkbuster originally written
+ *                by and Copyright (C) 1997 Anonymous Coders and 
+ *                Junkbusters Corporation.  http://www.junkbusters.com
+ *
+ *                This program is free software; you can redistribute it 
+ *                and/or modify it under the terms of the GNU General
+ *                Public License as published by the Free Software
+ *                Foundation; either version 2 of the License, or (at
+ *                your option) any later version.
+ *
+ *                This program is distributed in the hope that it will
+ *                be useful, but WITHOUT ANY WARRANTY; without even the
+ *                implied warranty of MERCHANTABILITY or FITNESS FOR A
+ *                PARTICULAR PURPOSE.  See the GNU General Public
+ *                License for more details.
+ *
+ *                The GNU General Public License should be included with
+ *                this file.  If not, you can view it at
+ *                http://www.gnu.org/copyleft/gpl.html
+ *                or write to the Free Software Foundation, Inc., 59
+ *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * Revisions   :
+ *    $Log: encode.h,v $
+ *    Revision 1.5  2002/03/26 22:29:54  swa
+ *    we have a new homepage!
+ *
+ *    Revision 1.4  2002/03/24 13:25:43  swa
+ *    name change related issues
+ *
+ *    Revision 1.3  2002/01/22 23:28:07  jongfoster
+ *    Adding convenience function html_encode_and_free_original()
+ *    Making all functions accept NULL paramaters - in this case, they
+ *    simply return NULL.  This allows error-checking to be deferred.
+ *
+ *    Revision 1.2  2001/07/29 18:43:08  jongfoster
+ *    Changing #ifdef _FILENAME_H to FILENAME_H_INCLUDED, to conform to
+ *    ANSI C rules.
+ *
+ *    Revision 1.1.1.1  2001/05/15 13:58:51  oes
+ *    Initial import of version 2.9.3 source tree
+ *
+ *
+ *********************************************************************/
+\f
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern char * html_encode(const char *s);
+extern char * cookie_encode(const char *s);
+extern char * url_encode(const char *s);
+extern char * url_decode(const char *str);
+
+extern char * html_encode_and_free_original(char *s);
+
+/* Revision control strings from this header and associated .c file */
+extern const char encode_rcs[];
+extern const char encode_h_rcs[];
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* ndef ENCODE_H_INCLUDED */
+
+/*
+  Local Variables:
+  tab-width: 3
+  end:
+*/
diff --git a/src/errlog.c b/src/errlog.c
new file mode 100644 (file)
index 0000000..c65aec9
--- /dev/null
@@ -0,0 +1,973 @@
+const char errlog_rcs[] = "$Id: errlog.c,v 1.40 2002/05/22 01:27:27 david__schmidt Exp $";
+/*********************************************************************
+ *
+ * File        :  $Source: /cvsroot/ijbswa/current/errlog.c,v $
+ *
+ * Purpose     :  Log errors to a designated destination in an elegant,
+ *                printf-like fashion.
+ *
+ * Copyright   :  Written by and Copyright (C) 2001 the SourceForge
+ *                Privoxy team. http://www.privoxy.org/
+ *
+ *                Based on the Internet Junkbuster originally written
+ *                by and Copyright (C) 1997 Anonymous Coders and 
+ *                Junkbusters Corporation.  http://www.junkbusters.com
+ *
+ *                This program is free software; you can redistribute it 
+ *                and/or modify it under the terms of the GNU General
+ *                Public License as published by the Free Software
+ *                Foundation; either version 2 of the License, or (at
+ *                your option) any later version.
+ *
+ *                This program is distributed in the hope that it will
+ *                be useful, but WITHOUT ANY WARRANTY; without even the
+ *                implied warranty of MERCHANTABILITY or FITNESS FOR A
+ *                PARTICULAR PURPOSE.  See the GNU General Public
+ *                License for more details.
+ *
+ *                The GNU General Public License should be included with
+ *                this file.  If not, you can view it at
+ *                http://www.gnu.org/copyleft/gpl.html
+ *                or write to the Free Software Foundation, Inc., 59
+ *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * Revisions   :
+ *    $Log: errlog.c,v $
+ *    Revision 1.40  2002/05/22 01:27:27  david__schmidt
+ *
+ *    Add os2_socket_strerr mirroring w32_socket_strerr.
+ *
+ *    Revision 1.39  2002/04/03 17:15:27  gliptak
+ *    zero padding thread ids in log
+ *
+ *    Revision 1.38  2002/03/31 17:18:59  jongfoster
+ *    Win32 only: Enabling STRICT to fix a VC++ compile warning.
+ *
+ *    Revision 1.37  2002/03/27 14:32:43  david__schmidt
+ *    More compiler warning message maintenance
+ *
+ *    Revision 1.36  2002/03/26 22:29:54  swa
+ *    we have a new homepage!
+ *
+ *    Revision 1.35  2002/03/24 15:23:33  jongfoster
+ *    Name changes
+ *
+ *    Revision 1.34  2002/03/24 13:25:43  swa
+ *    name change related issues
+ *
+ *    Revision 1.33  2002/03/13 00:27:04  jongfoster
+ *    Killing warnings
+ *
+ *    Revision 1.32  2002/03/07 03:46:17  oes
+ *    Fixed compiler warnings
+ *
+ *    Revision 1.31  2002/03/06 23:02:57  jongfoster
+ *    Removing tabs
+ *
+ *    Revision 1.30  2002/03/05 22:43:45  david__schmidt
+ *    - Better error reporting on OS/2
+ *    - Fix double-slash comment (oops)
+ *
+ *    Revision 1.29  2002/03/04 23:45:13  jongfoster
+ *    Printing thread ID if using Win32 native threads
+ *
+ *    Revision 1.28  2002/03/04 17:59:59  oes
+ *    Deleted deletePidFile(), cosmetics
+ *
+ *    Revision 1.27  2002/03/04 02:08:01  david__schmidt
+ *    Enable web editing of actions file on OS/2 (it had been broken all this time!)
+ *
+ *    Revision 1.26  2002/01/09 19:05:45  steudten
+ *    Fix big memory leak.
+ *
+ *    Revision 1.25  2002/01/09 14:32:08  oes
+ *    Added support for gmtime_r and localtime_r.
+ *
+ *    Revision 1.24  2001/12/30 14:07:32  steudten
+ *    - Add signal handling (unix)
+ *    - Add SIGHUP handler (unix)
+ *    - Add creation of pidfile (unix)
+ *    - Add action 'top' in rc file (RH)
+ *    - Add entry 'SIGNALS' to manpage
+ *    - Add exit message to logfile (unix)
+ *
+ *    Revision 1.23  2001/11/07 00:02:13  steudten
+ *    Add line number in error output for lineparsing for
+ *    actionsfile and configfile.
+ *    Special handling for CLF added.
+ *
+ *    Revision 1.22  2001/11/05 23:43:05  steudten
+ *    Add time+date to log files.
+ *
+ *    Revision 1.21  2001/10/25 03:40:47  david__schmidt
+ *    Change in porting tactics: OS/2's EMX porting layer doesn't allow multiple
+ *    threads to call select() simultaneously.  So, it's time to do a real, live,
+ *    native OS/2 port.  See defines for __EMX__ (the porting layer) vs. __OS2__
+ *    (native). Both versions will work, but using __OS2__ offers multi-threading.
+ *
+ *    Revision 1.20  2001/09/16 23:04:34  jongfoster
+ *    Fixing a warning
+ *
+ *    Revision 1.19  2001/09/13 20:08:06  jongfoster
+ *    Adding support for LOG_LEVEL_CGI
+ *
+ *    Revision 1.18  2001/09/10 11:27:24  oes
+ *    Declaration of w32_socket_strerr now conditional
+ *
+ *    Revision 1.17  2001/09/10 10:17:13  oes
+ *    Removed unused variable; Fixed sprintf format
+ *
+ *    Revision 1.16  2001/07/30 22:08:36  jongfoster
+ *    Tidying up #defines:
+ *    - All feature #defines are now of the form FEATURE_xxx
+ *    - Permanently turned off WIN_GUI_EDIT
+ *    - Permanently turned on WEBDAV and SPLIT_PROXY_ARGS
+ *
+ *    Revision 1.15  2001/07/29 17:41:10  jongfoster
+ *    Now prints thread ID for each message (pthreads only)
+ *
+ *    Revision 1.14  2001/07/19 19:03:48  haroon
+ *    - Added case for LOG_LEVEL_POPUPS
+ *
+ *    Revision 1.13  2001/07/13 13:58:58  oes
+ *     - Added case for LOG_LEVEL_DEANIMATE
+ *     - Removed all #ifdef PCRS
+ *
+ *    Revision 1.12  2001/06/09 10:55:28  jongfoster
+ *    Changing BUFSIZ ==> BUFFER_SIZE
+ *
+ *    Revision 1.11  2001/06/01 18:14:49  jongfoster
+ *    Changing the calls to strerr() to check HAVE_STRERR (which is defined
+ *    in config.h if appropriate) rather than the NO_STRERR macro.
+ *
+ *    Revision 1.10  2001/05/29 11:52:21  oes
+ *    Conditional compilation of w32_socket_error
+ *
+ *    Revision 1.9  2001/05/28 16:15:17  jongfoster
+ *    Improved reporting of errors under Win32.
+ *
+ *    Revision 1.8  2001/05/26 17:25:14  jongfoster
+ *    Added support for CLF (Common Log Format) and fixed LOG_LEVEL_LOG
+ *
+ *    Revision 1.7  2001/05/26 15:21:28  jongfoster
+ *    Activity animation in Win32 GUI now works even if debug==0
+ *
+ *    Revision 1.6  2001/05/25 21:55:08  jongfoster
+ *    Now cleans up properly on FATAL (removes taskbar icon etc)
+ *
+ *    Revision 1.5  2001/05/22 18:46:04  oes
+ *
+ *    - Enabled filtering banners by size rather than URL
+ *      by adding patterns that replace all standard banner
+ *      sizes with the "Junkbuster" gif to the re_filterfile
+ *
+ *    - Enabled filtering WebBugs by providing a pattern
+ *      which kills all 1x1 images
+ *
+ *    - Added support for PCRE_UNGREEDY behaviour to pcrs,
+ *      which is selected by the (nonstandard and therefore
+ *      capital) letter 'U' in the option string.
+ *      It causes the quantifiers to be ungreedy by default.
+ *      Appending a ? turns back to greedy (!).
+ *
+ *    - Added a new interceptor ijb-send-banner, which
+ *      sends back the "Junkbuster" gif. Without imagelist or
+ *      MSIE detection support, or if tinygif = 1, or the
+ *      URL isn't recognized as an imageurl, a lame HTML
+ *      explanation is sent instead.
+ *
+ *    - Added new feature, which permits blocking remote
+ *      script redirects and firing back a local redirect
+ *      to the browser.
+ *      The feature is conditionally compiled, i.e. it
+ *      can be disabled with --disable-fast-redirects,
+ *      plus it must be activated by a "fast-redirects"
+ *      line in the config file, has its own log level
+ *      and of course wants to be displayed by show-proxy-args
+ *      Note: Boy, all the #ifdefs in 1001 locations and
+ *      all the fumbling with configure.in and acconfig.h
+ *      were *way* more work than the feature itself :-(
+ *
+ *    - Because a generic redirect template was needed for
+ *      this, tinygif = 3 now uses the same.
+ *
+ *    - Moved GIFs, and other static HTTP response templates
+ *      to project.h
+ *
+ *    - Some minor fixes
+ *
+ *    - Removed some >400 CRs again (Jon, you really worked
+ *      a lot! ;-)
+ *
+ *    Revision 1.4  2001/05/21 19:32:54  jongfoster
+ *    Added another #ifdef _WIN_CONSOLE
+ *
+ *    Revision 1.3  2001/05/20 01:11:40  jongfoster
+ *    Added support for LOG_LEVEL_FATAL
+ *    Renamed LOG_LEVEL_FRC to LOG_LEVEL_FORCE,
+ *    and LOG_LEVEL_REF to LOG_LEVEL_RE_FILTER
+ *
+ *    Revision 1.2  2001/05/17 22:42:01  oes
+ *     - Cleaned CRLF's from the sources and related files
+ *     - Repaired logging for REF and FRC
+ *
+ *    Revision 1.1.1.1  2001/05/15 13:58:51  oes
+ *    Initial import of version 2.9.3 source tree
+ *
+ *
+ *********************************************************************/
+\f
+
+#include "config.h"
+#include "miscutil.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <string.h>
+
+#if !defined(_WIN32) && !defined(__OS2__)
+#include <unistd.h>
+#endif /* !defined(_WIN32) && !defined(__OS2__) */
+
+#include <errno.h>
+#include <assert.h>
+#ifdef FEATURE_PTHREAD
+#include <pthread.h>
+#endif /* def FEATURE_PTHREAD */
+
+#ifdef _WIN32
+#ifndef STRICT
+#define STRICT
+#endif
+#include <windows.h>
+#ifndef _WIN_CONSOLE
+#include "w32log.h"
+#endif /* ndef _WIN_CONSOLE */
+#endif /* def _WIN32 */
+
+#ifdef __OS2__
+#include <sys/socket.h> /* For sock_errno */
+#define INCL_DOS
+#include <os2.h>
+#endif
+
+#include "errlog.h"
+#include "project.h"
+#include "jcc.h"
+
+const char errlog_h_rcs[] = ERRLOG_H_VERSION;
+
+
+/*
+ * LOG_LEVEL_FATAL cannot be turned off.  (There are
+ * some exceptional situations where we need to get a
+ * message to the user).
+ */
+#define LOG_LEVEL_MINIMUM  LOG_LEVEL_FATAL
+
+/* where to log (default: stderr) */
+static FILE *logfp = NULL;
+
+/* logging detail level.  */
+static int debug = (LOG_LEVEL_FATAL | LOG_LEVEL_ERROR | LOG_LEVEL_INFO);  
+
+/* static functions */
+static void fatal_error(const char * error_message);
+#ifdef _WIN32
+static char *w32_socket_strerr(int errcode, char *tmp_buf);
+#endif
+#ifdef __OS2__
+static char *os2_socket_strerr(int errcode, char *tmp_buf);
+#endif
+
+/*********************************************************************
+ *
+ * Function    :  fatal_error
+ *
+ * Description :  Displays a fatal error to standard error (or, on 
+ *                a WIN32 GUI, to a dialog box), and exits
+ *                JunkBuster with status code 1.
+ *
+ * Parameters  :
+ *          1  :  error_message = The error message to display.
+ *
+ * Returns     :  Does not return.
+ *
+ *********************************************************************/
+static void fatal_error(const char * error_message)
+{
+#if defined(_WIN32) && !defined(_WIN_CONSOLE)
+   MessageBox(g_hwndLogFrame, error_message, "Privoxy Error", 
+      MB_OK | MB_ICONERROR | MB_TASKMODAL | MB_SETFOREGROUND | MB_TOPMOST);  
+
+   /* Cleanup - remove taskbar icon etc. */
+   TermLogWindow();
+
+#else /* if !defined(_WIN32) || defined(_WIN_CONSOLE) */
+   fputs(error_message, stderr);
+#endif /* defined(_WIN32) && !defined(_WIN_CONSOLE) */
+
+#if defined(unix)
+   unlink(pidfile);
+#endif /* unix */
+
+   exit(1);
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  init_error_log
+ *
+ * Description :  Initializes the logging module.  Must call before
+ *                calling log_error.
+ *
+ * Parameters  :
+ *          1  :  prog_name  = The program name.
+ *          2  :  logfname   = The logfile name, or NULL for stderr.
+ *          3  :  debuglevel = The debugging level.
+ *
+ * Returns     :  N/A
+ *
+ *********************************************************************/
+void init_error_log(const char *prog_name, const char *logfname, int debuglevel)
+{
+   FILE *fp;
+
+   /* FIXME RACE HAZARD: should start critical section error_log_use here */
+
+   /* set the logging detail level */
+   debug = debuglevel | LOG_LEVEL_MINIMUM;
+
+   if ((logfp != NULL) && (logfp != stderr))
+   {
+      log_error(LOG_LEVEL_INFO, "(Re-)Open logfile %s", logfname ? logfname : "none");
+      fclose(logfp);
+   }
+   logfp = stderr;
+
+   /* set the designated log file */
+   if( logfname )
+   {
+      if( NULL == (fp = fopen(logfname, "a")) )
+      {
+         log_error(LOG_LEVEL_FATAL, "init_error_log(): can't open logfile: %s", logfname);
+      }
+
+      /* set logging to be completely unbuffered */
+      setbuf(fp, NULL);
+
+      logfp = fp;
+   }
+
+   log_error(LOG_LEVEL_INFO, "Privoxy version " VERSION);
+   if (prog_name != NULL)
+   {
+      log_error(LOG_LEVEL_INFO, "Program name: %s", prog_name);
+   }
+
+   /* FIXME RACE HAZARD: should end critical section error_log_use here */
+
+} /* init_error_log */
+
+
+/*********************************************************************
+ *
+ * Function    :  log_error
+ *
+ * Description :  This is the error-reporting and logging function.
+ *
+ * Parameters  :
+ *          1  :  loglevel  = the type of message to be logged
+ *          2  :  fmt       = the main string we want logged, printf-like
+ *          3  :  ...       = arguments to be inserted in fmt (printf-like).
+ *
+ * Returns     :  N/A
+ *
+ *********************************************************************/
+void log_error(int loglevel, char *fmt, ...)
+{
+   va_list ap;
+   char *outbuf= NULL;
+   static char *outbuf_save = NULL;
+   char * src = fmt;
+   int outc = 0;
+   long this_thread = 1;  /* was: pthread_t this_thread;*/
+#ifdef __OS2__
+   PTIB     ptib;
+   APIRET   ulrc;
+#endif /* __OS2__ */
+
+#if defined(_WIN32) && !defined(_WIN_CONSOLE)
+   /*
+    * Irrespective of debug setting, a GET/POST/CONNECT makes
+    * the taskbar icon animate.  (There is an option to disable
+    * this but checking that is handled inside LogShowActivity()).
+    */
+   if (loglevel == LOG_LEVEL_GPC)
+   {
+      LogShowActivity();
+   }
+#endif /* defined(_WIN32) && !defined(_WIN_CONSOLE) */
+
+   /* verify if loglevel applies to current settings and bail out if negative */
+   if ((loglevel & debug) == 0)
+   {
+      return;
+   }
+
+   /* FIXME get current thread id */
+#ifdef FEATURE_PTHREAD
+   this_thread = (long)pthread_self();
+#elif defined(_WIN32)
+   this_thread = GetCurrentThreadId();
+#elif defined(__OS2__)
+   ulrc = DosGetInfoBlocks(&ptib, NULL);
+   if (ulrc == 0)
+     this_thread = ptib -> tib_ptib2 -> tib2_ultid;
+#endif /* def FEATURE_PTHREAD */
+
+   if ( !outbuf_save ) 
+   {
+      outbuf_save = outbuf = (char*)malloc(BUFFER_SIZE);
+      assert(outbuf);
+   }
+   outbuf = outbuf_save;
+
+    {
+       /*
+        * Write timestamp into tempbuf.
+        *
+        * Complex because not all OSs have tm_gmtoff or
+        * the %z field in strftime()
+        */
+       time_t now; 
+       struct tm tm_now; 
+       time (&now);
+#ifdef HAVE_LOCALTIME_R
+       tm_now = *localtime_r(&now, &tm_now);
+#else
+       tm_now = *localtime (&now); 
+#endif
+       strftime(outbuf, BUFFER_SIZE-6, "%b %d %H:%M:%S ", &tm_now); 
+       outbuf += strlen( outbuf );
+    }
+   switch (loglevel)
+   {
+      case LOG_LEVEL_ERROR:
+         outc = sprintf(outbuf, "Privoxy(%05ld) Error: ", this_thread);
+         break;
+      case LOG_LEVEL_FATAL:
+         outc = sprintf(outbuf, "Privoxy(%05ld) Fatal error: ", this_thread);
+         break;
+      case LOG_LEVEL_GPC:
+         outc = sprintf(outbuf, "Privoxy(%05ld) Request: ", this_thread);
+         break;
+      case LOG_LEVEL_CONNECT:
+         outc = sprintf(outbuf, "Privoxy(%05ld) Connect: ", this_thread);
+         break;
+      case LOG_LEVEL_LOG:
+         outc = sprintf(outbuf, "Privoxy(%05ld) Writing: ", this_thread);
+         break;
+      case LOG_LEVEL_HEADER:
+         outc = sprintf(outbuf, "Privoxy(%05ld) Header: ", this_thread);
+         break;
+      case LOG_LEVEL_INFO:
+         outc = sprintf(outbuf, "Privoxy(%05ld) Info: ", this_thread);
+         break;
+      case LOG_LEVEL_RE_FILTER:
+         outc = sprintf(outbuf, "Privoxy(%05ld) Re-Filter: ", this_thread);
+         break;
+#ifdef FEATURE_FORCE_LOAD
+      case LOG_LEVEL_FORCE:
+         outc = sprintf(outbuf, "Privoxy(%05ld) Force: ", this_thread);
+         break;
+#endif /* def FEATURE_FORCE_LOAD */
+#ifdef FEATURE_FAST_REDIRECTS
+      case LOG_LEVEL_REDIRECTS:
+         outc = sprintf(outbuf, "Privoxy(%05ld) Redirect: ", this_thread);
+         break;
+#endif /* def FEATURE_FAST_REDIRECTS */
+      case LOG_LEVEL_DEANIMATE:
+         outc = sprintf(outbuf, "Privoxy(%05ld) Gif-Deanimate: ", this_thread);
+         break;
+      case LOG_LEVEL_CLF:
+         outbuf = outbuf_save;
+         outc = 0;
+         outbuf[0] = '\0';
+         break;
+#ifdef FEATURE_KILL_POPUPS
+      case LOG_LEVEL_POPUPS:
+         outc = sprintf(outbuf, "Privoxy(%05ld) Kill-Popups: ", this_thread);
+         break;
+#endif /* def FEATURE_KILL_POPUPS */
+      case LOG_LEVEL_CGI:
+         outc = sprintf(outbuf, "Privoxy(%05ld) CGI: ", this_thread);
+         break;
+      default:
+         outc = sprintf(outbuf, "Privoxy(%05ld) UNKNOWN LOG TYPE(%d): ", this_thread, loglevel);
+         break;
+   }
+   
+   /* get ready to scan var. args. */
+   va_start( ap, fmt );
+
+   /* build formatted message from fmt and var-args */
+   while ((*src) && (outc < BUFFER_SIZE-2))
+   {
+      char tempbuf[BUFFER_SIZE];
+      char *sval = NULL;
+      int ival;
+      unsigned uval;
+      long lval;
+      unsigned long ulval;
+      int oldoutc;
+      char ch;
+      
+      ch = *src++;
+      if( ch != '%' )
+      {
+         outbuf[outc++] = ch;
+         continue;
+      }
+
+      ch = *src++;
+      switch (ch) {
+         case '%':
+            outbuf[outc++] = '%';
+            break;
+         case 'd':
+            ival = va_arg( ap, int );
+            oldoutc = outc;
+            outc += sprintf(tempbuf, "%d", ival);
+            if (outc < BUFFER_SIZE-1) 
+            {
+               strcpy(outbuf + oldoutc, tempbuf);
+            }
+            else
+            {
+               outbuf[oldoutc] = '\0';
+            }
+            break;
+         case 'u':
+            uval = va_arg( ap, unsigned );
+            oldoutc = outc;
+            outc += sprintf(tempbuf, "%u", uval);
+            if (outc < BUFFER_SIZE-1) 
+            {
+               strcpy(outbuf + oldoutc, tempbuf);
+            }
+            else
+            {
+               outbuf[oldoutc] = '\0';
+            }
+            break;
+         case 'l':
+            /* this is a modifier that must be followed by u or d */
+            ch = *src++;
+            if (ch == 'd')
+            {
+               lval = va_arg( ap, long );
+               oldoutc = outc;
+               outc += sprintf(tempbuf, "%ld", lval);
+            }
+            else if (ch == 'u')
+            {
+               ulval = va_arg( ap, unsigned long );
+               oldoutc = outc;
+               outc += sprintf(tempbuf, "%lu", ulval);
+            }
+            else
+            {
+               /* Error */
+               sprintf(outbuf, "Privoxy(%ld) Error: log_error(): Bad format string:\n"
+                               "Format = \"%s\"\n"
+                               "Exiting.", this_thread, fmt);
+               /* FIXME RACE HAZARD: should start critical section error_log_use here */
+               if( !logfp )
+               {
+                  logfp = stderr;
+               }
+               fputs(outbuf, logfp);
+               /* FIXME RACE HAZARD: should end critical section error_log_use here */
+               fatal_error(outbuf);
+               /* Never get here */
+               break;
+            }
+            if (outc < BUFFER_SIZE-1) 
+            {
+               strcpy(outbuf + oldoutc, tempbuf);
+            }
+            else
+            {
+               outbuf[oldoutc] = '\0';
+            }
+            break;
+         case 'c':
+            /*
+             * Note that char paramaters are converted to int, so we need to
+             * pass "int" to va_arg.  (See K&R, 2nd ed, section A7.3.2, page 202)
+             */
+            outbuf[outc++] = (char) va_arg( ap, int );
+            break;
+         case 's':
+            sval = va_arg( ap, char * );
+            oldoutc = outc;
+            outc += strlen(sval);
+            if (outc < BUFFER_SIZE-1) 
+            {
+               strcpy(outbuf + oldoutc, sval);
+            }
+            else
+            {
+               outbuf[oldoutc] = '\0';
+            }
+            break;
+         case 'N':
+            /* Non-standard: Print a counted string.  Takes 2 parameters:
+             * int length, const char * string
+             */
+            ival = va_arg( ap, int );
+            sval = va_arg( ap, char * );
+            if (ival < 0)
+            {
+               ival = 0;
+            }
+            oldoutc = outc;
+            outc += ival;
+            if (outc < BUFFER_SIZE-1)
+            {
+               memcpy(outbuf + oldoutc, sval, (size_t) ival);
+            }
+            else
+            {
+               outbuf[oldoutc] = '\0';
+            }
+            break;
+         case 'E':
+            /* Non-standard: Print error code from errno */
+#ifdef _WIN32
+            ival = WSAGetLastError();
+            sval = w32_socket_strerr(ival, tempbuf);
+#elif __OS2__
+            ival = sock_errno();
+            if (ival != 0)
+              sval = os2_socket_strerr(ival, tempbuf);
+            else
+            {
+              ival = errno;
+              sval = strerror(ival);
+            }
+#else /* ifndef _WIN32 */
+            ival = errno; 
+#ifdef HAVE_STRERROR
+            sval = strerror(ival);
+#else /* ifndef HAVE_STRERROR */
+            sval = NULL;
+#endif /* ndef HAVE_STRERROR */
+            if (sval == NULL)
+            {
+               sprintf(tempbuf, "(errno = %d)", ival);
+               sval = tempbuf;
+            }
+#endif /* ndef _WIN32 */
+            oldoutc = outc;
+            outc += strlen(sval);
+            if (outc < BUFFER_SIZE-1) 
+            {
+               strcpy(outbuf + oldoutc, sval);
+            }
+            else
+            {
+               outbuf[oldoutc] = '\0';
+            }
+            break;
+         case 'T':
+            /* Non-standard: Print a Common Log File timestamp */
+            {
+               /*
+                * Write timestamp into tempbuf.
+                *
+                * Complex because not all OSs have tm_gmtoff or
+                * the %z field in strftime()
+                */
+               time_t now; 
+               struct tm *tm_now; 
+               struct tm gmt;
+#ifdef HAVE_LOCALTIME_R
+               struct tm dummy;
+#endif
+               int days, hrs, mins; 
+               time (&now); 
+#ifdef HAVE_GMTIME_R
+               gmt = *gmtime_r(&now, &gmt);
+#else
+               gmt = *gmtime(&now);
+#endif
+#ifdef HAVE_LOCALTIME_R
+               tm_now = localtime_r(&now, &dummy);
+#else
+               tm_now = localtime (&now); 
+#endif
+               days = tm_now->tm_yday - gmt.tm_yday; 
+               hrs = ((days < -1 ? 24 : 1 < days ? -24 : days * 24) + tm_now->tm_hour - gmt.tm_hour); 
+               mins = hrs * 60 + tm_now->tm_min - gmt.tm_min; 
+               strftime (tempbuf, BUFFER_SIZE-6, "%d/%b/%Y:%H:%M:%S ", tm_now); 
+               sprintf (tempbuf + strlen(tempbuf), "%+03d%02d", mins / 60, abs(mins) % 60); 
+            }
+            oldoutc = outc;
+            outc += strlen(tempbuf);
+            if (outc < BUFFER_SIZE-1) 
+            {
+               strcpy(outbuf + oldoutc, tempbuf);
+            }
+            else
+            {
+               outbuf[oldoutc] = '\0';
+            }
+            break;
+         default:
+            sprintf(outbuf, "Privoxy(%ld) Error: log_error(): Bad format string:\n"
+                            "Format = \"%s\"\n"
+                            "Exiting.", this_thread, fmt);
+            /* FIXME RACE HAZARD: should start critical section error_log_use here */
+            if( !logfp )
+            {
+               logfp = stderr;
+            }
+            fputs(outbuf_save, logfp);
+            /* FIXME RACE HAZARD: should end critical section error_log_use here */
+            fatal_error(outbuf_save);
+            /* Never get here */
+            break;
+
+      } /* switch( p ) */
+
+   } /* for( p ... ) */
+   
+   /* done with var. args */
+   va_end( ap );
+   
+   if (outc >= BUFFER_SIZE-2)
+   {
+      /* insufficient room for newline and trailing null. */
+
+      static const char warning[] = "... [too long, truncated]\n";
+
+      if (outc < BUFFER_SIZE)
+      {
+         /* Need to add terminating null in this case. */
+         outbuf[outc] = '\0';
+      }
+
+      /* Truncate output */
+      outbuf[BUFFER_SIZE - sizeof(warning)] = '\0';
+
+      /* Append warning */
+      strcat(outbuf, warning);
+   }
+   else
+   {
+      /* Add terminating newline and null */
+      outbuf[outc++] = '\n';
+      outbuf[outc] = '\0';
+   }
+
+   /* FIXME RACE HAZARD: should start critical section error_log_use here */
+
+   /* deal with glibc stupidity - it won't let you initialize logfp */
+   if( !logfp )
+   {
+      logfp = stderr;
+   }
+
+   fputs(outbuf_save, logfp);
+
+   if (loglevel == LOG_LEVEL_FATAL)
+   {
+      fatal_error(outbuf_save);
+      /* Never get here */
+   }
+
+   /* FIXME RACE HAZARD: should end critical section error_log_use here */
+
+#if defined(_WIN32) && !defined(_WIN_CONSOLE)
+   /* Write to display */
+   LogPutString(outbuf_save);
+#endif /* defined(_WIN32) && !defined(_WIN_CONSOLE) */
+
+}
+
+
+#ifdef _WIN32
+/*********************************************************************
+ *
+ * Function    :  w32_socket_strerr
+ *
+ * Description :  Translate the return value from WSAGetLastError()
+ *                into a string.
+ *
+ * Parameters  :
+ *          1  :  errcode = The return value from WSAGetLastError().
+ *          2  :  tmp_buf = A temporary buffer that might be used to
+ *                          store the string.
+ *
+ * Returns     :  String representing the error code.  This may be
+ *                a global string constant or a string stored in
+ *                tmp_buf.
+ *
+ *********************************************************************/
+static char *w32_socket_strerr(int errcode, char *tmp_buf)
+{
+#define TEXT_FOR_ERROR(code,text) \
+   if (errcode == code)           \
+   {                              \
+      return #code " - " text;    \
+   }
+
+   TEXT_FOR_ERROR(WSAEACCES, "Permission denied")
+   TEXT_FOR_ERROR(WSAEADDRINUSE, "Address already in use.")
+   TEXT_FOR_ERROR(WSAEADDRNOTAVAIL, "Cannot assign requested address.");
+   TEXT_FOR_ERROR(WSAEAFNOSUPPORT, "Address family not supported by protocol family.");
+   TEXT_FOR_ERROR(WSAEALREADY, "Operation already in progress.");
+   TEXT_FOR_ERROR(WSAECONNABORTED, "Software caused connection abort.");
+   TEXT_FOR_ERROR(WSAECONNREFUSED, "Connection refused.");
+   TEXT_FOR_ERROR(WSAECONNRESET, "Connection reset by peer.");
+   TEXT_FOR_ERROR(WSAEDESTADDRREQ, "Destination address required.");
+   TEXT_FOR_ERROR(WSAEFAULT, "Bad address.");
+   TEXT_FOR_ERROR(WSAEHOSTDOWN, "Host is down.");
+   TEXT_FOR_ERROR(WSAEHOSTUNREACH, "No route to host.");
+   TEXT_FOR_ERROR(WSAEINPROGRESS, "Operation now in progress.");
+   TEXT_FOR_ERROR(WSAEINTR, "Interrupted function call.");
+   TEXT_FOR_ERROR(WSAEINVAL, "Invalid argument.");
+   TEXT_FOR_ERROR(WSAEISCONN, "Socket is already connected.");
+   TEXT_FOR_ERROR(WSAEMFILE, "Too many open sockets.");
+   TEXT_FOR_ERROR(WSAEMSGSIZE, "Message too long.");
+   TEXT_FOR_ERROR(WSAENETDOWN, "Network is down.");
+   TEXT_FOR_ERROR(WSAENETRESET, "Network dropped connection on reset.");
+   TEXT_FOR_ERROR(WSAENETUNREACH, "Network is unreachable.");
+   TEXT_FOR_ERROR(WSAENOBUFS, "No buffer space available.");
+   TEXT_FOR_ERROR(WSAENOPROTOOPT, "Bad protocol option.");
+   TEXT_FOR_ERROR(WSAENOTCONN, "Socket is not connected.");
+   TEXT_FOR_ERROR(WSAENOTSOCK, "Socket operation on non-socket.");
+   TEXT_FOR_ERROR(WSAEOPNOTSUPP, "Operation not supported.");
+   TEXT_FOR_ERROR(WSAEPFNOSUPPORT, "Protocol family not supported.");
+   TEXT_FOR_ERROR(WSAEPROCLIM, "Too many processes.");
+   TEXT_FOR_ERROR(WSAEPROTONOSUPPORT, "Protocol not supported.");
+   TEXT_FOR_ERROR(WSAEPROTOTYPE, "Protocol wrong type for socket.");
+   TEXT_FOR_ERROR(WSAESHUTDOWN, "Cannot send after socket shutdown.");
+   TEXT_FOR_ERROR(WSAESOCKTNOSUPPORT, "Socket type not supported.");
+   TEXT_FOR_ERROR(WSAETIMEDOUT, "Connection timed out.");
+   TEXT_FOR_ERROR(WSAEWOULDBLOCK, "Resource temporarily unavailable.");
+   TEXT_FOR_ERROR(WSAHOST_NOT_FOUND, "Host not found.");
+   TEXT_FOR_ERROR(WSANOTINITIALISED, "Successful WSAStartup not yet performed.");
+   TEXT_FOR_ERROR(WSANO_DATA, "Valid name, no data record of requested type.");
+   TEXT_FOR_ERROR(WSANO_RECOVERY, "This is a non-recoverable error.");
+   TEXT_FOR_ERROR(WSASYSNOTREADY, "Network subsystem is unavailable.");
+   TEXT_FOR_ERROR(WSATRY_AGAIN, "Non-authoritative host not found.");
+   TEXT_FOR_ERROR(WSAVERNOTSUPPORTED, "WINSOCK.DLL version out of range.");
+   TEXT_FOR_ERROR(WSAEDISCON, "Graceful shutdown in progress.");
+   /*
+    * The following error codes are documented in the Microsoft WinSock
+    * reference guide, but don't actually exist.
+    *
+    * TEXT_FOR_ERROR(WSA_INVALID_HANDLE, "Specified event object handle is invalid.");
+    * TEXT_FOR_ERROR(WSA_INVALID_PARAMETER, "One or more parameters are invalid.");
+    * TEXT_FOR_ERROR(WSAINVALIDPROCTABLE, "Invalid procedure table from service provider.");
+    * TEXT_FOR_ERROR(WSAINVALIDPROVIDER, "Invalid service provider version number.");
+    * TEXT_FOR_ERROR(WSA_IO_PENDING, "Overlapped operations will complete later.");
+    * TEXT_FOR_ERROR(WSA_IO_INCOMPLETE, "Overlapped I/O event object not in signaled state.");
+    * TEXT_FOR_ERROR(WSA_NOT_ENOUGH_MEMORY, "Insufficient memory available.");
+    * TEXT_FOR_ERROR(WSAPROVIDERFAILEDINIT, "Unable to initialize a service provider.");
+    * TEXT_FOR_ERROR(WSASYSCALLFAILURE, "System call failure.");
+    * TEXT_FOR_ERROR(WSA_OPERATION_ABORTED, "Overlapped operation aborted.");
+    */
+
+   sprintf(tmp_buf, "(error number %d)", errcode);
+   return tmp_buf;
+}
+#endif /* def _WIN32 */
+
+
+#ifdef __OS2__
+/*********************************************************************
+ *
+ * Function    :  os2_socket_strerr
+ *
+ * Description :  Translate the return value from sock_errno()
+ *                into a string.
+ *
+ * Parameters  :
+ *          1  :  errcode = The return value from sock_errno().
+ *          2  :  tmp_buf = A temporary buffer that might be used to
+ *                          store the string.
+ *
+ * Returns     :  String representing the error code.  This may be
+ *                a global string constant or a string stored in
+ *                tmp_buf.
+ *
+ *********************************************************************/
+static char *os2_socket_strerr(int errcode, char *tmp_buf)
+{
+#define TEXT_FOR_ERROR(code,text) \
+   if (errcode == code)           \
+   {                              \
+      return #code " - " text;    \
+   }
+
+   TEXT_FOR_ERROR(SOCEPERM          , "Not owner.")
+   TEXT_FOR_ERROR(SOCESRCH          , "No such process.")
+   TEXT_FOR_ERROR(SOCEINTR          , "Interrupted system call.")
+   TEXT_FOR_ERROR(SOCENXIO          , "No such device or address.")
+   TEXT_FOR_ERROR(SOCEBADF          , "Bad file number.")
+   TEXT_FOR_ERROR(SOCEACCES         , "Permission denied.")
+   TEXT_FOR_ERROR(SOCEFAULT         , "Bad address.")
+   TEXT_FOR_ERROR(SOCEINVAL         , "Invalid argument.")
+   TEXT_FOR_ERROR(SOCEMFILE         , "Too many open files.")
+   TEXT_FOR_ERROR(SOCEPIPE          , "Broken pipe.")
+   TEXT_FOR_ERROR(SOCEWOULDBLOCK    , "Operation would block.")
+   TEXT_FOR_ERROR(SOCEINPROGRESS    , "Operation now in progress.")
+   TEXT_FOR_ERROR(SOCEALREADY       , "Operation already in progress.")
+   TEXT_FOR_ERROR(SOCENOTSOCK       , "Socket operation on non-socket.")
+   TEXT_FOR_ERROR(SOCEDESTADDRREQ   , "Destination address required.")
+   TEXT_FOR_ERROR(SOCEMSGSIZE       , "Message too long.")
+   TEXT_FOR_ERROR(SOCEPROTOTYPE     , "Protocol wrong type for socket.")
+   TEXT_FOR_ERROR(SOCENOPROTOOPT    , "Protocol not available.")
+   TEXT_FOR_ERROR(SOCEPROTONOSUPPORT, "Protocol not supported.")
+   TEXT_FOR_ERROR(SOCESOCKTNOSUPPORT, "Socket type not supported.")
+   TEXT_FOR_ERROR(SOCEOPNOTSUPP     , "Operation not supported.")
+   TEXT_FOR_ERROR(SOCEPFNOSUPPORT   , "Protocol family not supported.")
+   TEXT_FOR_ERROR(SOCEAFNOSUPPORT   , "Address family not supported by protocol family.")
+   TEXT_FOR_ERROR(SOCEADDRINUSE     , "Address already in use.")
+   TEXT_FOR_ERROR(SOCEADDRNOTAVAIL  , "Can't assign requested address.")
+   TEXT_FOR_ERROR(SOCENETDOWN       , "Network is down.")
+   TEXT_FOR_ERROR(SOCENETUNREACH    , "Network is unreachable.")
+   TEXT_FOR_ERROR(SOCENETRESET      , "Network dropped connection on reset.")
+   TEXT_FOR_ERROR(SOCECONNABORTED   , "Software caused connection abort.")
+   TEXT_FOR_ERROR(SOCECONNRESET     , "Connection reset by peer.")
+   TEXT_FOR_ERROR(SOCENOBUFS        , "No buffer space available.")
+   TEXT_FOR_ERROR(SOCEISCONN        , "Socket is already connected.")
+   TEXT_FOR_ERROR(SOCENOTCONN       , "Socket is not connected.")
+   TEXT_FOR_ERROR(SOCESHUTDOWN      , "Can't send after socket shutdown.")
+   TEXT_FOR_ERROR(SOCETOOMANYREFS   , "Too many references: can't splice.")
+   TEXT_FOR_ERROR(SOCETIMEDOUT      , "Operation timed out.")
+   TEXT_FOR_ERROR(SOCECONNREFUSED   , "Connection refused.")
+   TEXT_FOR_ERROR(SOCELOOP          , "Too many levels of symbolic links.")
+   TEXT_FOR_ERROR(SOCENAMETOOLONG   , "File name too long.")
+   TEXT_FOR_ERROR(SOCEHOSTDOWN      , "Host is down.")
+   TEXT_FOR_ERROR(SOCEHOSTUNREACH   , "No route to host.")
+   TEXT_FOR_ERROR(SOCENOTEMPTY      , "Directory not empty.")
+   TEXT_FOR_ERROR(SOCEOS2ERR        , "OS/2 Error.")
+
+   sprintf(tmp_buf, "(error number %d)", errcode);
+   return tmp_buf;
+}
+#endif /* def __OS2__ */
+
+
+/*
+  Local Variables:
+  tab-width: 3
+  end:
+*/
diff --git a/src/errlog.h b/src/errlog.h
new file mode 100644 (file)
index 0000000..2f02493
--- /dev/null
@@ -0,0 +1,180 @@
+#ifndef ERRLOG_H_INCLUDED
+#define ERRLOG_H_INCLUDED
+#define ERRLOG_H_VERSION "$Id: errlog.h,v 1.13 2002/03/26 22:29:54 swa Exp $"
+/*********************************************************************
+ *
+ * File        :  $Source: /cvsroot/ijbswa/current/errlog.h,v $
+ *
+ * Purpose     :  Log errors to a designated destination in an elegant,
+ *                printf-like fashion.
+ *
+ * Copyright   :  Written by and Copyright (C) 2001 the SourceForge
+ *                Privoxy team. http://www.privoxy.org/
+ *
+ *                Based on the Internet Junkbuster originally written
+ *                by and Copyright (C) 1997 Anonymous Coders and 
+ *                Junkbusters Corporation.  http://www.junkbusters.com
+ *
+ *                This program is free software; you can redistribute it 
+ *                and/or modify it under the terms of the GNU General
+ *                Public License as published by the Free Software
+ *                Foundation; either version 2 of the License, or (at
+ *                your option) any later version.
+ *
+ *                This program is distributed in the hope that it will
+ *                be useful, but WITHOUT ANY WARRANTY; without even the
+ *                implied warranty of MERCHANTABILITY or FITNESS FOR A
+ *                PARTICULAR PURPOSE.  See the GNU General Public
+ *                License for more details.
+ *
+ *                The GNU General Public License should be included with
+ *                this file.  If not, you can view it at
+ *                http://www.gnu.org/copyleft/gpl.html
+ *                or write to the Free Software Foundation, Inc., 59
+ *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * Revisions   :
+ *    $Log: errlog.h,v $
+ *    Revision 1.13  2002/03/26 22:29:54  swa
+ *    we have a new homepage!
+ *
+ *    Revision 1.12  2002/03/24 13:25:43  swa
+ *    name change related issues
+ *
+ *    Revision 1.11  2002/03/06 23:02:57  jongfoster
+ *    Removing tabs
+ *
+ *    Revision 1.10  2001/09/13 20:08:06  jongfoster
+ *    Adding support for LOG_LEVEL_CGI
+ *
+ *    Revision 1.9  2001/07/30 22:08:36  jongfoster
+ *    Tidying up #defines:
+ *    - All feature #defines are now of the form FEATURE_xxx
+ *    - Permanently turned off WIN_GUI_EDIT
+ *    - Permanently turned on WEBDAV and SPLIT_PROXY_ARGS
+ *
+ *    Revision 1.8  2001/07/29 18:43:08  jongfoster
+ *    Changing #ifdef _FILENAME_H to FILENAME_H_INCLUDED, to conform to
+ *    ANSI C rules.
+ *
+ *    Revision 1.7  2001/07/19 19:02:53  haroon
+ *    Added define for LOG_LEVEL_POPUPS
+ *
+ *    Revision 1.6  2001/07/13 13:59:22  oes
+ *     - Added LOG_LEVEL_DEANIMATE
+ *     - Changed LOG_LEVEL_CLF
+ *     - Removed all #ifdef PCRS
+ *
+ *    Revision 1.5  2001/05/26 17:25:14  jongfoster
+ *    Added support for CLF (Common Log Format) and fixed LOG_LEVEL_LOG
+ *
+ *    Revision 1.4  2001/05/25 21:56:06  jongfoster
+ *    Added FIXME comment to (broken) LOG_LEVEL_LOG
+ *
+ *    Revision 1.3  2001/05/22 18:46:04  oes
+ *
+ *    - Enabled filtering banners by size rather than URL
+ *      by adding patterns that replace all standard banner
+ *      sizes with the "Junkbuster" gif to the re_filterfile
+ *
+ *    - Enabled filtering WebBugs by providing a pattern
+ *      which kills all 1x1 images
+ *
+ *    - Added support for PCRE_UNGREEDY behaviour to pcrs,
+ *      which is selected by the (nonstandard and therefore
+ *      capital) letter 'U' in the option string.
+ *      It causes the quantifiers to be ungreedy by default.
+ *      Appending a ? turns back to greedy (!).
+ *
+ *    - Added a new interceptor ijb-send-banner, which
+ *      sends back the "Junkbuster" gif. Without imagelist or
+ *      MSIE detection support, or if tinygif = 1, or the
+ *      URL isn't recognized as an imageurl, a lame HTML
+ *      explanation is sent instead.
+ *
+ *    - Added new feature, which permits blocking remote
+ *      script redirects and firing back a local redirect
+ *      to the browser.
+ *      The feature is conditionally compiled, i.e. it
+ *      can be disabled with --disable-fast-redirects,
+ *      plus it must be activated by a "fast-redirects"
+ *      line in the config file, has its own log level
+ *      and of course wants to be displayed by show-proxy-args
+ *      Note: Boy, all the #ifdefs in 1001 locations and
+ *      all the fumbling with configure.in and acconfig.h
+ *      were *way* more work than the feature itself :-(
+ *
+ *    - Because a generic redirect template was needed for
+ *      this, tinygif = 3 now uses the same.
+ *
+ *    - Moved GIFs, and other static HTTP response templates
+ *      to project.h
+ *
+ *    - Some minor fixes
+ *
+ *    - Removed some >400 CRs again (Jon, you really worked
+ *      a lot! ;-)
+ *
+ *    Revision 1.2  2001/05/20 01:11:40  jongfoster
+ *    Added support for LOG_LEVEL_FATAL
+ *    Renamed LOG_LEVEL_FRC to LOG_LEVEL_FORCE,
+ *    and LOG_LEVEL_REF to LOG_LEVEL_RE_FILTER
+ *
+ *    Revision 1.1.1.1  2001/05/15 13:58:51  oes
+ *    Initial import of version 2.9.3 source tree
+ *
+ *
+ *********************************************************************/
+\f
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Debug level for errors */
+
+#define LOG_LEVEL_GPC        0x0001
+#define LOG_LEVEL_CONNECT    0x0002
+#define LOG_LEVEL_IO         0x0004
+#define LOG_LEVEL_HEADER     0x0008
+#define LOG_LEVEL_LOG        0x0010
+#ifdef FEATURE_FORCE_LOAD
+#define LOG_LEVEL_FORCE      0x0020
+#endif /* def FEATURE_FORCE_LOAD */
+#define LOG_LEVEL_RE_FILTER  0x0040
+#ifdef FEATURE_FAST_REDIRECTS
+#define LOG_LEVEL_REDIRECTS  0x0080
+#endif /* def FEATURE_FAST_REDIRECTS */
+#define LOG_LEVEL_DEANIMATE  0x0100
+
+#define LOG_LEVEL_CLF        0x0200 /* Common Log File format */
+#ifdef FEATURE_KILL_POPUPS
+#define LOG_LEVEL_POPUPS     0x0400 /* Kill Popups */
+#endif /* def FEATURE_KILL_POPUPS */
+
+#define LOG_LEVEL_CGI   0x0800 /* CGI / templates */
+
+/* Following are always on: */
+#define LOG_LEVEL_INFO    0x1000
+#define LOG_LEVEL_ERROR   0x2000
+#define LOG_LEVEL_FATAL   0x4000 /* Exits after writing log */
+
+extern void init_error_log(const char *prog_name, const char *logfname, int debuglevel);
+extern void log_error(int loglevel, char *fmt, ...);
+
+/* Revision control strings from this header and associated .c file */
+extern const char errlog_rcs[];
+extern const char errlog_h_rcs[];
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* ndef ERRLOG_H_INCLUDED */
+
+/*
+  Local Variables:
+  tab-width: 3
+  end:
+*/
+
diff --git a/src/filters.c b/src/filters.c
new file mode 100644 (file)
index 0000000..60f47e9
--- /dev/null
@@ -0,0 +1,1601 @@
+const char filters_rcs[] = "$Id: filters.c,v 1.58 2002/04/24 02:11:17 oes Exp $";
+/*********************************************************************
+ *
+ * File        :  $Source: /cvsroot/ijbswa/current/filters.c,v $
+ *
+ * Purpose     :  Declares functions to parse/crunch headers and pages.
+ *                Functions declared include:
+ *                   `acl_addr', `add_stats', `block_acl', `block_imageurl',
+ *                   `block_url', `url_actions', `domain_split',
+ *                   `filter_popups', `forward_url', 'redirect_url',
+ *                   `ij_untrusted_url', `intercept_url', `pcrs_filter_respose',
+ *                   'ijb_send_banner', and `trust_url'
+ *
+ * Copyright   :  Written by and Copyright (C) 2001 the SourceForge
+ *                Privoxy team. http://www.privoxy.org/
+ *
+ *                Based on the Internet Junkbuster originally written
+ *                by and Copyright (C) 1997 Anonymous Coders and
+ *                Junkbusters Corporation.  http://www.junkbusters.com
+ *
+ *                This program is free software; you can redistribute it
+ *                and/or modify it under the terms of the GNU General
+ *                Public License as published by the Free Software
+ *                Foundation; either version 2 of the License, or (at
+ *                your option) any later version.
+ *
+ *                This program is distributed in the hope that it will
+ *                be useful, but WITHOUT ANY WARRANTY; without even the
+ *                implied warranty of MERCHANTABILITY or FITNESS FOR A
+ *                PARTICULAR PURPOSE.  See the GNU General Public
+ *                License for more details.
+ *
+ *                The GNU General Public License should be included with
+ *                this file.  If not, you can view it at
+ *                http://www.gnu.org/copyleft/gpl.html
+ *                or write to the Free Software Foundation, Inc., 59
+ *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * Revisions   :
+ *    $Log: filters.c,v $
+ *    Revision 1.58  2002/04/24 02:11:17  oes
+ *    Jon's multiple AF patch: url_actions now evaluates rules
+ *    from all AFs.
+ *
+ *    Revision 1.57  2002/04/08 20:38:34  swa
+ *    fixed JB spelling
+ *
+ *    Revision 1.56  2002/04/05 15:51:24  oes
+ *     - bugfix: error-pages now get correct request protocol
+ *     - fix for invalid HTML in trust info
+ *
+ *    Revision 1.55  2002/04/02 16:13:51  oes
+ *    Fix: No "Go there anyway" for SSL
+ *
+ *    Revision 1.54  2002/04/02 14:55:56  oes
+ *    Bugfix: is_untrusted_url() now depends on FEATURE_TRUST, not FEATURE_COOKIE_JAR
+ *
+ *    Revision 1.53  2002/03/26 22:29:54  swa
+ *    we have a new homepage!
+ *
+ *    Revision 1.52  2002/03/24 16:35:57  jongfoster
+ *    Removing logo
+ *
+ *    Revision 1.51  2002/03/24 15:23:33  jongfoster
+ *    Name changes
+ *
+ *    Revision 1.50  2002/03/24 13:25:43  swa
+ *    name change related issues
+ *
+ *    Revision 1.49  2002/03/16 20:29:14  oes
+ *    Cosmetics
+ *
+ *    Revision 1.48  2002/03/13 20:25:34  oes
+ *    Better logging for content filters
+ *
+ *    Revision 1.47  2002/03/13 00:30:52  jongfoster
+ *    Killing warnings
+ *    Added option of always sending redirect for imageblock,
+ *    currently disabled with #if 0.
+ *
+ *    Revision 1.46  2002/03/12 01:42:49  oes
+ *    Introduced modular filters
+ *
+ *    Revision 1.45  2002/03/08 16:47:50  oes
+ *    Added choice beween GIF and PNG built-in images
+ *
+ *    Revision 1.44  2002/03/07 03:49:31  oes
+ *     - Fixed compiler warnings etc
+ *     - Changed built-in images from GIF to PNG
+ *       (with regard to Unisys patent issue)
+ *     - Added a 4x4 pattern PNG which is less intrusive
+ *       than the logo but also clearly marks the deleted banners
+ *
+ *    Revision 1.43  2002/01/22 23:51:59  jongfoster
+ *    Replacing strsav() with the safer string_append().
+ *
+ *    Adding missing html_encode() to error message generators.  Where encoded
+ *    and unencoded versions of a string were provided, removing the unencoded
+ *    one.
+ *
+ *    Revision 1.42  2002/01/17 21:00:32  jongfoster
+ *    Moving all our URL and URL pattern parsing code to urlmatch.c.
+ *
+ *    Using a single, simple url_match(pattern,url) function - rather than
+ *    the 3-line match routine which was repeated all over the place.
+ *
+ *    Renaming free_url to free_url_spec, since it frees a struct url_spec.
+ *
+ *    Using parse_http_url() to parse URLs without faking a HTTP
+ *    request line for parse_http_request().
+ *
+ *    Revision 1.41  2001/11/13 00:14:07  jongfoster
+ *    Fixing stupid bug now I've figured out what || means.
+ *    (It always returns 0 or 1, not one of it's paramaters.)
+ *
+ *    Revision 1.40  2001/10/26 17:37:55  oes
+ *    - Re-enabled Netscape 200/404 bug workaround in block_url():
+ *      - Removed OS/2 special case
+ *      - Made block_url() independant from sed() having been run
+ *    - Made trust_url independant from sed() having been run
+ *    - Made is_imageurl independant from sed() having been run.
+ *      It now checks User-Agent: and Accept: by itself.
+ *
+ *
+ *    Revision 1.39  2001/10/25 03:40:48  david__schmidt
+ *    Change in porting tactics: OS/2's EMX porting layer doesn't allow multiple
+ *    threads to call select() simultaneously.  So, it's time to do a real, live,
+ *    native OS/2 port.  See defines for __EMX__ (the porting layer) vs. __OS2__
+ *    (native). Both versions will work, but using __OS2__ offers multi-threading.
+ *
+ *    Revision 1.38  2001/10/23 21:32:33  jongfoster
+ *    Adding error-checking to selected functions
+ *
+ *    Revision 1.37  2001/10/22 15:33:56  david__schmidt
+ *    Special-cased OS/2 out of the Netscape-abort-on-404-in-js problem in
+ *    filters.c.  Added a FIXME in front of the offending code.  I'll gladly
+ *    put in a better/more robust fix for all parties if one is presented...
+ *    It seems that just returning 200 instead of 404 would pretty much fix
+ *    it for everyone, but I don't know all the history of the problem.
+ *
+ *    Revision 1.36  2001/10/10 16:44:16  oes
+ *    Added match_portlist function
+ *
+ *    Revision 1.35  2001/10/07 15:41:23  oes
+ *    Replaced 6 boolean members of csp with one bitmap (csp->flags)
+ *
+ *    New function remove_chunked_transfer_coding that strips chunked
+ *      transfer coding to plain and is called by pcrs_filter_response
+ *      and gif_deanimate_response if neccessary
+ *
+ *    Improved handling of zero-change re_filter runs
+ *
+ *    pcrs_filter_response and gif_deanimate_response now remove
+ *      chunked transfer codeing before processing the body.
+ *
+ *    Revision 1.34  2001/09/20 15:49:36  steudten
+ *
+ *    Fix BUG: Change int size to size_t size in pcrs_filter_response().
+ *    See cgi.c fill_template().
+ *
+ *    Revision 1.33  2001/09/16 17:05:14  jongfoster
+ *    Removing unused #include showarg.h
+ *
+ *    Revision 1.32  2001/09/16 13:21:27  jongfoster
+ *    Changes to use new list functions.
+ *
+ *    Revision 1.31  2001/09/16 11:38:02  jongfoster
+ *    Splitting fill_template() into 2 functions:
+ *    template_load() loads the file
+ *    template_fill() performs the PCRS regexps.
+ *    This is because the CGI edit interface has a "table row"
+ *    template which is used many times in the page - this
+ *    change means it's only loaded from disk once.
+ *
+ *    Revision 1.30  2001/09/16 11:00:10  jongfoster
+ *    New function alloc_http_response, for symmetry with free_http_response
+ *
+ *    Revision 1.29  2001/09/13 23:32:40  jongfoster
+ *    Moving image data to cgi.c rather than cgi.h
+ *    Fixing a GPF under Win32 (and any other OS that protects global
+ *    constants from being written to).
+ *
+ *    Revision 1.28  2001/09/10 10:18:51  oes
+ *    Silenced compiler warnings
+ *
+ *    Revision 1.27  2001/08/05 16:06:20  jongfoster
+ *    Modifiying "struct map" so that there are now separate header and
+ *    "map_entry" structures.  This means that functions which modify a
+ *    map no longer need to return a pointer to the modified map.
+ *    Also, it no longer reverses the order of the entries (which may be
+ *    important with some advanced template substitutions).
+ *
+ *    Revision 1.26  2001/07/30 22:08:36  jongfoster
+ *    Tidying up #defines:
+ *    - All feature #defines are now of the form FEATURE_xxx
+ *    - Permanently turned off WIN_GUI_EDIT
+ *    - Permanently turned on WEBDAV and SPLIT_PROXY_ARGS
+ *
+ *    Revision 1.25  2001/07/26 10:09:46  oes
+ *    Made browser detection a little less naive
+ *
+ *    Revision 1.24  2001/07/25 17:22:51  oes
+ *    Added workaround for Netscape bug that prevents display of page when loading a component fails.
+ *
+ *    Revision 1.23  2001/07/23 13:40:12  oes
+ *    Fixed bug that caused document body to be dropped when pcrs joblist was empty.
+ *
+ *    Revision 1.22  2001/07/18 12:29:34  oes
+ *    - Made gif_deanimate_response respect
+ *      csp->action->string[ACTION_STRING_DEANIMATE]
+ *    - Logging cosmetics
+ *
+ *    Revision 1.21  2001/07/13 13:59:53  oes
+ *     - Introduced gif_deanimate_response which shares the
+ *       generic content modification interface of pcrs_filter_response
+ *       and acts as a wrapper to deanimate.c:gif_deanimate()
+ *     - Renamed re_process_buffer to pcrs_filter_response
+ *     - pcrs_filter_response now returns NULL on failiure
+ *     - Removed all #ifdef PCRS
+ *
+ *    Revision 1.20  2001/07/01 17:01:04  oes
+ *    Added comments and missing return statement in is_untrusted_url()
+ *
+ *    Revision 1.19  2001/06/29 21:45:41  oes
+ *    Indentation, CRLF->LF, Tab-> Space
+ *
+ *    Revision 1.18  2001/06/29 13:27:38  oes
+ *    - Cleaned up, renamed and reorderd functions
+ *      and improved comments
+ *
+ *    - block_url:
+ *      - Ported to CGI platform. Now delivers
+ *        http_response or NULL
+ *      - Unified HTML and GIF generation (moved image detection
+ *        and GIF generation here from jcc.c:chat())
+ *      - Fixed HTTP status to:
+ *       -  403 (Forbidden) for the "blocked" HTML message
+ *       -  200 (OK) for GIF answers
+ *       -  302 (Redirect) for redirect to GIF
+ *
+ *    - trust_url:
+ *      - Ported to CGI platform. Now delivers
+ *        http_response or NULL
+ *      - Separated detection of untrusted URL into
+ *        (bool)is_untrusted_url
+ *      - Added enforcement of untrusted requests
+ *
+ *    - Moved redirect_url() from cgi.c to here
+ *      and ported it to the CGI platform
+ *
+ *    - Removed logentry from cancelled commit
+ *
+ *    Revision 1.17  2001/06/09 10:55:28  jongfoster
+ *    Changing BUFSIZ ==> BUFFER_SIZE
+ *
+ *    Revision 1.16  2001/06/07 23:10:26  jongfoster
+ *    Allowing unanchored domain patterns to back off and retry
+ *    if they partially match.  Optimized right-anchored patterns.
+ *    Moving ACL and forward files into config file.
+ *    Replacing struct gateway with struct forward_spec
+ *
+ *    Revision 1.15  2001/06/03 19:12:00  oes
+ *    extracted-CGI relevant stuff
+ *
+ *    Revision 1.14  2001/06/01 10:30:55  oes
+ *    Added optional left-anchoring to domaincmp
+ *
+ *    Revision 1.13  2001/05/31 21:21:30  jongfoster
+ *    Permissionsfile / actions file changes:
+ *    - Changed "permission" to "action" throughout
+ *    - changes to file format to allow string parameters
+ *    - Moved helper functions to actions.c
+ *
+ *    Revision 1.12  2001/05/31 17:35:20  oes
+ *
+ *     - Enhanced domain part globbing with infix and prefix asterisk
+ *       matching and optional unanchored operation
+ *
+ *    Revision 1.11  2001/05/29 11:53:23  oes
+ *    "See why" link added to "blocked" page
+ *
+ *    Revision 1.10  2001/05/29 09:50:24  jongfoster
+ *    Unified blocklist/imagelist/permissionslist.
+ *    File format is still under discussion, but the internal changes
+ *    are (mostly) done.
+ *
+ *    Also modified interceptor behaviour:
+ *    - We now intercept all URLs beginning with one of the following
+ *      prefixes (and *only* these prefixes):
+ *        * http://i.j.b/
+ *        * http://ijbswa.sf.net/config/
+ *        * http://ijbswa.sourceforge.net/config/
+ *    - New interceptors "home page" - go to http://i.j.b/ to see it.
+ *    - Internal changes so that intercepted and fast redirect pages
+ *      are not replaced with an image.
+ *    - Interceptors now have the option to send a binary page direct
+ *      to the client. (i.e. ijb-send-banner uses this)
+ *    - Implemented show-url-info interceptor.  (Which is why I needed
+ *      the above interceptors changes - a typical URL is
+ *      "http://i.j.b/show-url-info?url=www.somesite.com/banner.gif".
+ *      The previous mechanism would not have intercepted that, and
+ *      if it had been intercepted then it then it would have replaced
+ *      it with an image.)
+ *
+ *    Revision 1.9  2001/05/27 22:17:04  oes
+ *
+ *    - re_process_buffer no longer writes the modified buffer
+ *      to the client, which was very ugly. It now returns the
+ *      buffer, which it is then written by chat.
+ *
+ *    - content_length now adjusts the Content-Length: header
+ *      for modified documents rather than crunch()ing it.
+ *      (Length info in csp->content_length, which is 0 for
+ *      unmodified documents)
+ *
+ *    - For this to work, sed() is called twice when filtering.
+ *
+ *    Revision 1.8  2001/05/26 17:13:28  jongfoster
+ *    Filled in a function comment.
+ *
+ *    Revision 1.7  2001/05/26 15:26:15  jongfoster
+ *    ACL feature now provides more security by immediately dropping
+ *    connections from untrusted hosts.
+ *
+ *    Revision 1.6  2001/05/26 00:28:36  jongfoster
+ *    Automatic reloading of config file.
+ *    Removed obsolete SIGHUP support (Unix) and Reload menu option (Win32).
+ *    Most of the global variables have been moved to a new
+ *    struct configuration_spec, accessed through csp->config->globalname
+ *    Most of the globals remaining are used by the Win32 GUI.
+ *
+ *    Revision 1.5  2001/05/25 22:34:30  jongfoster
+ *    Hard tabs->Spaces
+ *
+ *    Revision 1.4  2001/05/22 18:46:04  oes
+ *
+ *    - Enabled filtering banners by size rather than URL
+ *      by adding patterns that replace all standard banner
+ *      sizes with the "Junkbuster" gif to the re_filterfile
+ *
+ *    - Enabled filtering WebBugs by providing a pattern
+ *      which kills all 1x1 images
+ *
+ *    - Added support for PCRE_UNGREEDY behaviour to pcrs,
+ *      which is selected by the (nonstandard and therefore
+ *      capital) letter 'U' in the option string.
+ *      It causes the quantifiers to be ungreedy by default.
+ *      Appending a ? turns back to greedy (!).
+ *
+ *    - Added a new interceptor ijb-send-banner, which
+ *      sends back the "Junkbuster" gif. Without imagelist or
+ *      MSIE detection support, or if tinygif = 1, or the
+ *      URL isn't recognized as an imageurl, a lame HTML
+ *      explanation is sent instead.
+ *
+ *    - Added new feature, which permits blocking remote
+ *      script redirects and firing back a local redirect
+ *      to the browser.
+ *      The feature is conditionally compiled, i.e. it
+ *      can be disabled with --disable-fast-redirects,
+ *      plus it must be activated by a "fast-redirects"
+ *      line in the config file, has its own log level
+ *      and of course wants to be displayed by show-proxy-args
+ *      Note: Boy, all the #ifdefs in 1001 locations and
+ *      all the fumbling with configure.in and acconfig.h
+ *      were *way* more work than the feature itself :-(
+ *
+ *    - Because a generic redirect template was needed for
+ *      this, tinygif = 3 now uses the same.
+ *
+ *    - Moved GIFs, and other static HTTP response templates
+ *      to project.h
+ *
+ *    - Some minor fixes
+ *
+ *    - Removed some >400 CRs again (Jon, you really worked
+ *      a lot! ;-)
+ *
+ *    Revision 1.3  2001/05/20 16:44:47  jongfoster
+ *    Removing last hardcoded Junkbusters.com URLs.
+ *
+ *    Revision 1.2  2001/05/20 01:21:20  jongfoster
+ *    Version 2.9.4 checkin.
+ *    - Merged popupfile and cookiefile, and added control over PCRS
+ *      filtering, in new "permissionsfile".
+ *    - Implemented LOG_LEVEL_FATAL, so that if there is a configuration
+ *      file error you now get a message box (in the Win32 GUI) rather
+ *      than the program exiting with no explanation.
+ *    - Made killpopup use the PCRS MIME-type checking and HTTP-header
+ *      skipping.
+ *    - Removed tabs from "config"
+ *    - Moved duplicated url parsing code in "loaders.c" to a new funcition.
+ *    - Bumped up version number.
+ *
+ *    Revision 1.1.1.1  2001/05/15 13:58:52  oes
+ *    Initial import of version 2.9.3 source tree
+ *
+ *
+ *********************************************************************/
+\f
+
+#include "config.h"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+#include <assert.h>
+
+#ifndef _WIN32
+#ifndef __OS2__
+#include <unistd.h>
+#endif /* ndef __OS2__ */
+#include <netinet/in.h>
+#else
+#include <winsock2.h>
+#endif /* ndef _WIN32 */
+
+#ifdef __OS2__
+#include <utils.h>
+#endif /* def __OS2__ */
+
+#include "project.h"
+#include "filters.h"
+#include "encode.h"
+#include "parsers.h"
+#include "ssplit.h"
+#include "errlog.h"
+#include "jbsockets.h"
+#include "miscutil.h"
+#include "actions.h"
+#include "cgi.h"
+#include "list.h"
+#include "deanimate.h"
+#include "urlmatch.h"
+
+#ifdef _WIN32
+#include "win32.h"
+#endif
+
+const char filters_h_rcs[] = FILTERS_H_VERSION;
+
+/* Fix a problem with Solaris.  There should be no effect on other
+ * platforms.
+ * Solaris's isspace() is a macro which uses it's argument directly
+ * as an array index.  Therefore we need to make sure that high-bit
+ * characters generate +ve values, and ideally we also want to make
+ * the argument match the declared parameter type of "int".
+ */
+#define ijb_isdigit(__X) isdigit((int)(unsigned char)(__X))
+
+
+#ifdef FEATURE_ACL
+/*********************************************************************
+ *
+ * Function    :  block_acl
+ *
+ * Description :  Block this request?
+ *                Decide yes or no based on ACL file.
+ *
+ * Parameters  :
+ *          1  :  dst = The proxy or gateway address this is going to.
+ *                      Or NULL to check all possible targets.
+ *          2  :  csp = Current client state (buffers, headers, etc...)
+ *                      Also includes the client IP address.
+ *
+ * Returns     : 0 = FALSE (don't block) and 1 = TRUE (do block)
+ *
+ *********************************************************************/
+int block_acl(struct access_control_addr *dst, struct client_state *csp)
+{
+   struct access_control_list *acl = csp->config->acl;
+
+   /* if not using an access control list, then permit the connection */
+   if (acl == NULL)
+   {
+      return(0);
+   }
+
+   /* search the list */
+   while (acl != NULL)
+   {
+      if ((csp->ip_addr_long & acl->src->mask) == acl->src->addr)
+      {
+         if (dst == NULL)
+         {
+            /* Just want to check if they have any access */
+            if (acl->action == ACL_PERMIT)
+            {
+               return(0);
+            }
+         }
+         else if ( ((dst->addr & acl->dst->mask) == acl->dst->addr)
+           && ((dst->port == acl->dst->port) || (acl->dst->port == 0)))
+         {
+            if (acl->action == ACL_PERMIT)
+            {
+               return(0);
+            }
+            else
+            {
+               return(1);
+            }
+         }
+      }
+      acl = acl->next;
+   }
+
+   return(1);
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  acl_addr
+ *
+ * Description :  Called from `load_config' to parse an ACL address.
+ *
+ * Parameters  :
+ *          1  :  aspec = String specifying ACL address.
+ *          2  :  aca = struct access_control_addr to fill in.
+ *
+ * Returns     :  0 => Ok, everything else is an error.
+ *
+ *********************************************************************/
+int acl_addr(char *aspec, struct access_control_addr *aca)
+{
+   int i, masklength, port;
+   char *p;
+
+   masklength = 32;
+   port       =  0;
+
+   if ((p = strchr(aspec, '/')) != NULL)
+   {
+      *p++ = '\0';
+
+      if (ijb_isdigit(*p) == 0)
+      {
+         return(-1);
+      }
+      masklength = atoi(p);
+   }
+
+   if ((masklength < 0) || (masklength > 32))
+   {
+      return(-1);
+   }
+
+   if ((p = strchr(aspec, ':')) != NULL)
+   {
+      *p++ = '\0';
+
+      if (ijb_isdigit(*p) == 0)
+      {
+         return(-1);
+      }
+      port = atoi(p);
+   }
+
+   aca->port = port;
+
+   aca->addr = ntohl(resolve_hostname_to_ip(aspec));
+
+   if (aca->addr == INADDR_NONE)
+   {
+      return(-1);
+   }
+
+   /* build the netmask */
+   aca->mask = 0;
+   for (i=1; i <= masklength ; i++)
+   {
+      aca->mask |= (1 << (32 - i));
+   }
+
+   /* now mask off the host portion of the ip address
+    * (i.e. save on the network portion of the address).
+    */
+   aca->addr = aca->addr & aca->mask;
+
+   return(0);
+
+}
+#endif /* def FEATURE_ACL */
+
+
+/*********************************************************************
+ *
+ * Function    :  match_portlist
+ *
+ * Description :  Check if a given number is covered by a comma
+ *                separated list of numbers and ranges (a,b-c,d,..)
+ *
+ * Parameters  :
+ *          1  :  portlist = String with list
+ *          2  :  port = port to check
+ *
+ * Returns     :  0 => no match
+ *                1 => match
+ *
+ *********************************************************************/
+int match_portlist(const char *portlist, int port)
+{
+   char *min, *max, *next, *portlist_copy;
+
+   min = next = portlist_copy = strdup(portlist);
+
+   /*
+    * Zero-terminate first item and remember offset for next
+    */
+   if (NULL != (next = strchr(portlist_copy, (int) ',')))
+   {
+      *next++ = '\0';
+   }
+
+   /*
+    * Loop through all items, checking for match
+    */
+   while(min)
+   {
+      if (NULL == (max = strchr(min, (int) '-')))
+      {
+         /*
+          * No dash, check for equality
+          */
+         if (port == atoi(min))
+         {
+            free(portlist_copy);
+            return(1);
+         }
+      }
+      else
+      {
+         /*
+          * This is a range, so check if between min and max,
+          * or, if max was omitted, between min and 65K
+          */
+         *max++ = '\0';
+         if(port >= atoi(min) && port <= (atoi(max) ? atoi(max) : 65535))
+         {
+            free(portlist_copy);
+            return(1);
+         }
+
+      }
+
+      /*
+       * Jump to next item
+       */
+      min = next;
+
+      /*
+       * Zero-terminate next item and remember offset for n+1
+       */
+      if ((NULL != next) && (NULL != (next = strchr(next, (int) ','))))
+      {
+         *next++ = '\0';
+      }
+   }
+
+   free(portlist_copy);
+   return 0;
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  block_url
+ *
+ * Description :  Called from `chat'.  Check to see if we need to block this.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns     :  NULL => unblocked, else HTTP block response
+ *
+ *********************************************************************/
+struct http_response *block_url(struct client_state *csp)
+{
+#ifdef FEATURE_IMAGE_BLOCKING
+   char *p;
+#endif /* def FEATURE_IMAGE_BLOCKING */
+   struct http_response *rsp;
+
+   /*
+    * If it's not blocked, don't block it ;-)
+    */
+   if ((csp->action->flags & ACTION_BLOCK) == 0)
+   {
+      return NULL;
+   }
+
+   /*
+    * Else, prepare a response
+    */
+   if (NULL == (rsp = alloc_http_response()))
+   {
+      return cgi_error_memory();
+   }
+
+   /*
+    * If it's an image-url, send back an image or redirect
+    * as specified by the relevant +image action
+    */
+#ifdef FEATURE_IMAGE_BLOCKING
+   if (((csp->action->flags & ACTION_IMAGE_BLOCKER) != 0)
+        && is_imageurl(csp))
+   {
+      /* determine HOW images should be blocked */
+      p = csp->action->string[ACTION_STRING_IMAGE_BLOCKER];
+
+#if 1 /* Two alternative strategies, use this one for now: */
+
+      /* and handle accordingly: */
+      if ((p == NULL) || (0 == strcmpic(p, "pattern")))
+      {
+         rsp->body = bindup(image_pattern_data, image_pattern_length);
+         if (rsp->body == NULL)
+         {
+            free_http_response(rsp);
+            return cgi_error_memory();
+         }
+         rsp->content_length = image_pattern_length;
+
+         if (enlist_unique_header(rsp->headers, "Content-Type", BUILTIN_IMAGE_MIMETYPE))
+         {
+            free_http_response(rsp);
+            return cgi_error_memory();
+         }
+      }
+
+      else if (0 == strcmpic(p, "blank"))
+      {
+         rsp->body = bindup(image_blank_data, image_blank_length);
+         if (rsp->body == NULL)
+         {
+            free_http_response(rsp);
+            return cgi_error_memory();
+         }
+         rsp->content_length = image_blank_length;
+
+         if (enlist_unique_header(rsp->headers, "Content-Type", BUILTIN_IMAGE_MIMETYPE))
+         {
+            free_http_response(rsp);
+            return cgi_error_memory();
+         }
+      }
+
+      else
+      {
+         rsp->status = strdup("302 Local Redirect from Privoxy");
+         if (rsp->status == NULL)
+         {
+            free_http_response(rsp);
+            return cgi_error_memory();
+         }
+
+         if (enlist_unique_header(rsp->headers, "Location", p))
+         {
+            free_http_response(rsp);
+            return cgi_error_memory();
+         }
+      }
+
+#else /* Following code is disabled for now */
+
+      /* and handle accordingly: */
+      if ((p == NULL) || (0 == strcmpic(p, "pattern")))
+      {
+         p = CGI_PREFIX "send-banner?type=pattern";
+      }
+      else if (0 == strcmpic(p, "blank"))
+      {
+         p = CGI_PREFIX "send-banner?type=blank";
+      }
+      rsp->status = strdup("302 Local Redirect from Privoxy");
+      if (rsp->status == NULL)
+      {
+         free_http_response(rsp);
+         return cgi_error_memory();
+      }
+
+      if (enlist_unique_header(rsp->headers, "Location", p))
+      {
+         free_http_response(rsp);
+         return cgi_error_memory();
+      }
+#endif /* Preceeding code is disabled for now */
+   }
+   else
+#endif /* def FEATURE_IMAGE_BLOCKING */
+
+   /*
+    * Else, generate an HTML "blocked" message:
+    */
+   {
+      jb_err err;
+      struct map * exports;
+
+      /*
+       * Workaround for stupid Netscape bug which prevents
+       * pages from being displayed if loading a referenced
+       * JavaScript or style sheet fails. So make it appear
+       * as if it succeeded.
+       */
+      if ( NULL != (p = get_header_value(csp->headers, "User-Agent:"))
+           && !strncmpic(p, "mozilla", 7) /* Catch Netscape but */
+           && !strstr(p, "Gecko")         /* save Mozilla, */
+           && !strstr(p, "compatible")    /* MSIE */
+           && !strstr(p, "Opera"))        /* and Opera. */
+      {
+         rsp->status = strdup("200 Request for blocked URL");
+      }
+      else
+      {
+         rsp->status = strdup("404 Request for blocked URL");
+      }
+
+      if (rsp->status == NULL)
+      {
+         free_http_response(rsp);
+         return cgi_error_memory();
+      }
+
+      exports = default_exports(csp, NULL);
+      if (exports == NULL)
+      {
+         free_http_response(rsp);
+         return cgi_error_memory();
+      }
+
+#ifdef FEATURE_FORCE_LOAD
+      err = map(exports, "force-prefix", 1, FORCE_PREFIX, 1);
+      if (csp->http->ssl != 0)
+#endif /* ndef FEATURE_FORCE_LOAD */
+      {
+         err = map_block_killer(exports, "force-support");
+      }
+
+      if (!err) err = map(exports, "protocol", 1, csp->http->ssl ? "https://" : "http://", 1);
+      if (!err) err = map(exports, "hostport", 1, html_encode(csp->http->hostport), 0);
+      if (!err) err = map(exports, "path", 1, html_encode(csp->http->path), 0);
+
+      if (err)
+      {
+         free_map(exports);
+         free_http_response(rsp);
+         return cgi_error_memory();
+      }
+
+      err = template_fill_for_cgi(csp, "blocked", exports, rsp);
+      if (err)
+      {
+         free_http_response(rsp);
+         return cgi_error_memory();
+      }
+   }
+
+   return finish_http_response(rsp);
+
+}
+
+
+#ifdef FEATURE_TRUST
+/*********************************************************************
+ *
+ * Function    :  trust_url FIXME: I should be called distrust_url
+ *
+ * Description :  Calls is_untrusted_url to determine if the URL is trusted
+ *                and if not, returns a HTTP 304 response with a reject message.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns     :  NULL => trusted, else http_response.
+ *
+ *********************************************************************/
+struct http_response *trust_url(struct client_state *csp)
+{
+   struct http_response *rsp;
+   struct map * exports;
+   char buf[BUFFER_SIZE];
+   char *p;
+   struct url_spec **tl;
+   struct url_spec *t;
+   jb_err err;
+
+   /*
+    * Don't bother to work on trusted URLs
+    */
+   if (!is_untrusted_url(csp))
+   {
+      return NULL;
+   }
+
+   /*
+    * Else, prepare a response:
+    */
+   if (NULL == (rsp = alloc_http_response()))
+   {
+      return cgi_error_memory();
+   }
+
+   exports = default_exports(csp, NULL);
+   if (exports == NULL)
+   {
+      free_http_response(rsp);
+      return cgi_error_memory();
+   }
+
+   /*
+    * Export the protocol, host, port, and referrer information
+    */
+   err = map(exports, "hostport", 1, csp->http->hostport, 1);
+   if (!err) err = map(exports, "protocol", 1, csp->http->ssl ? "https://" : "http://", 1); 
+   if (!err) err = map(exports, "path", 1, csp->http->path, 1);
+
+   if (NULL != (p = get_header_value(csp->headers, "Referer:")))
+   {
+      if (!err) err = map(exports, "referrer", 1, html_encode(p), 0);
+   }
+   else
+   {
+      if (!err) err = map(exports, "referrer", 1, "unknown", 1);
+   }
+
+   if (err)
+   {
+      free_map(exports);
+      free_http_response(rsp);
+      return cgi_error_memory();
+   }
+
+   /*
+    * Export the trust list
+    */
+   p = strdup("");
+   for (tl = csp->config->trust_list; (t = *tl) != NULL ; tl++)
+   {
+      sprintf(buf, "<li>%s</li>\n", t->spec);
+      string_append(&p, buf);
+   }
+   err = map(exports, "trusted-referrers", 1, p, 0);
+
+   if (err)
+   {
+      free_map(exports);
+      free_http_response(rsp);
+      return cgi_error_memory();
+   }
+
+   /*
+    * Export the trust info, if available
+    */
+   if (csp->config->trust_info->first)
+   {
+      struct list_entry *l;
+
+      p = strdup("");
+      for (l = csp->config->trust_info->first; l ; l = l->next)
+      {
+         sprintf(buf, "<li> <a href=\"%s\">%s</a><br>\n",l->str, l->str);
+         string_append(&p, buf);
+      }
+      err = map(exports, "trust-info", 1, p, 0);
+   }
+   else
+   {
+      err = map_block_killer(exports, "have-trust-info");
+   }
+
+   if (err)
+   {
+      free_map(exports);
+      free_http_response(rsp);
+      return cgi_error_memory();
+   }
+
+   /*
+    * Export the force prefix or the force conditional block killer
+    */
+#ifdef FEATURE_FORCE_LOAD
+   err = map(exports, "force-prefix", 1, FORCE_PREFIX, 1);
+#else /* ifndef FEATURE_FORCE_LOAD */
+   err = map_block_killer(exports, "force-support");
+#endif /* ndef FEATURE_FORCE_LOAD */
+
+   if (err)
+   {
+      free_map(exports);
+      free_http_response(rsp);
+      return cgi_error_memory();
+   }
+
+   /*
+    * Build the response
+    */
+   err = template_fill_for_cgi(csp, "untrusted", exports, rsp);
+   if (err)
+   {
+      free_http_response(rsp);
+      return cgi_error_memory();
+   }
+
+   return finish_http_response(rsp);
+}
+#endif /* def FEATURE_TRUST */
+
+
+#ifdef FEATURE_FAST_REDIRECTS
+/*********************************************************************
+ *
+ * Function    :  redirect_url
+ *
+ * Description :  Checks for redirection URLs and returns a HTTP redirect
+ *                to the destination URL, if necessary
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns     :  NULL if URL was clean, HTTP redirect otherwise.
+ *
+ *********************************************************************/
+struct http_response *redirect_url(struct client_state *csp)
+{
+   char *p, *q;
+   struct http_response *rsp;
+
+   p = q = csp->http->path;
+   log_error(LOG_LEVEL_REDIRECTS, "checking path for redirects: %s", p);
+
+   /*
+    * find the last URL encoded in the request
+    */
+   while ((p = strstr(p, "http://")) != NULL)
+   {
+      q = p++;
+   }
+
+   /*
+    * if there was any, generate and return a HTTP redirect
+    */
+   if (q != csp->http->path)
+   {
+      log_error(LOG_LEVEL_REDIRECTS, "redirecting to: %s", q);
+
+      if (NULL == (rsp = alloc_http_response()))
+      {
+         return cgi_error_memory();
+      }
+
+      if ( enlist_unique_header(rsp->headers, "Location", q)
+        || (NULL == (rsp->status = strdup("302 Local Redirect from Privoxy"))) )
+      {
+         free_http_response(rsp);
+         return cgi_error_memory();
+      }
+
+      return finish_http_response(rsp);
+   }
+   else
+   {
+      return NULL;
+   }
+
+}
+#endif /* def FEATURE_FAST_REDIRECTS */
+
+
+#ifdef FEATURE_IMAGE_BLOCKING
+/*********************************************************************
+ *
+ * Function    :  is_imageurl
+ *
+ * Description :  Given a URL, decide whether it is an image or not,
+ *                using either the info from a previous +image action
+ *                or, #ifdef FEATURE_IMAGE_DETECT_MSIE, the info from
+ *                the browser's accept header.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns     :  True (nonzero) if URL is an image, false (0)
+ *                otherwise
+ *
+ *********************************************************************/
+int is_imageurl(struct client_state *csp)
+{
+#ifdef FEATURE_IMAGE_DETECT_MSIE
+   char *tmp;
+
+   tmp = get_header_value(csp->headers, "User-Agent:");
+   if (tmp && strstr(tmp, "MSIE"))
+   {
+      tmp = get_header_value(csp->headers, "Accept:");
+      if (tmp && strstr(tmp, "image/gif"))
+      {
+         /* Client will accept HTML.  If this seems counterintuitive,
+          * blame Microsoft.
+          */
+         return(0);
+      }
+      else
+      {
+         return(1);
+      }
+   }
+#endif /* def FEATURE_IMAGE_DETECT_MSIE */
+
+   return ((csp->action->flags & ACTION_IMAGE) != 0);
+
+}
+#endif /* def FEATURE_IMAGE_BLOCKING */
+
+
+#ifdef FEATURE_TRUST
+/*********************************************************************
+ *
+ * Function    :  is_untrusted_url
+ *
+ * Description :  Should we "distrust" this URL (and block it)?
+ *
+ *                Yes if it matches a line in the trustfile, or if the
+ *                    referrer matches a line starting with "+" in the
+ *                    trustfile.
+ *                No  otherwise.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns     :  0 => trusted, 1 => untrusted
+ *
+ *********************************************************************/
+int is_untrusted_url(struct client_state *csp)
+{
+   struct file_list *fl;
+   struct block_spec *b;
+   struct url_spec **trusted_url;
+   struct http_request rhttp[1];
+   const char * referer;
+   jb_err err;
+
+   /*
+    * If we don't have a trustlist, we trust everybody
+    */
+   if (((fl = csp->tlist) == NULL) || ((b  = fl->f) == NULL))
+   {
+      return 0;
+   }
+
+   memset(rhttp, '\0', sizeof(*rhttp));
+
+   /*
+    * Do we trust the request URL itself?
+    */
+   for (b = b->next; b ; b = b->next)
+   {
+      if (url_match(b->url, csp->http))
+      {
+         return b->reject;
+      }
+   }
+
+   if (NULL == (referer = get_header_value(csp->headers, "Referer:")))
+   {
+      /* no referrer was supplied */
+      return 1;
+   }
+
+
+   /*
+    * If not, do we maybe trust its referrer?
+    */
+   err = parse_http_url(referer, rhttp, csp);
+   if (err)
+   {
+      return 1;
+   }
+
+   for (trusted_url = csp->config->trust_list; *trusted_url != NULL; trusted_url++)
+   {
+      if (url_match(*trusted_url, rhttp))
+      {
+         /* if the URL's referrer is from a trusted referrer, then
+          * add the target spec to the trustfile as an unblocked
+          * domain and return NULL (which means it's OK).
+          */
+
+         FILE *fp;
+
+         if (NULL != (fp = fopen(csp->config->trustfile, "a")))
+         {
+            char * path;
+            char * path_end;
+            char * new_entry = strdup("~");
+
+            string_append(&new_entry, csp->http->hostport);
+
+            path = csp->http->path;
+            if ( (path[0] == '/')
+              && (path[1] == '~')
+              && ((path_end = strchr(path + 2, '/')) != NULL))
+            {
+               /* since this path points into a user's home space
+                * be sure to include this spec in the trustfile.
+                */
+               int path_len = path_end - path; /* save offset */
+               path = strdup(path); /* Copy string */
+               if (path != NULL)
+               {
+                  path_end = path + path_len; /* regenerate ptr to new buffer */
+                  *(path_end + 1) = '\0'; /* Truncate path after '/' */
+               }
+               string_join(&new_entry, path);
+            }
+
+            if (new_entry != NULL)
+            {
+               fprintf(fp, "%s\n", new_entry);
+               free(new_entry);
+            }
+            else
+            {
+               /* FIXME: No way to handle out-of memory, so mostly ignoring it */
+               log_error(LOG_LEVEL_ERROR, "Out of memory adding pattern to trust file");
+            }
+
+            fclose(fp);
+         }
+         return 0;
+      }
+   }
+   return 1;
+}
+#endif /* def FEATURE_TRUST */
+
+
+/*********************************************************************
+ *
+ * Function    :  pcrs_filter_response
+ *
+ * Description :  Ecexute all text substitutions from all applying
+ *                +filter actions on the text buffer that's been accumulated
+ *                in csp->iob->buf. If this changes the contents, set
+ *                csp->content_length to the modified size and raise the
+ *                CSP_FLAG_MODIFIED flag.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns     :  a pointer to the (newly allocated) modified buffer.
+ *                or NULL if there were no hits or something went wrong
+ *
+ *********************************************************************/
+char *pcrs_filter_response(struct client_state *csp)
+{
+   int hits=0;
+   size_t size;
+
+   char *old = csp->iob->cur, *new = NULL;
+   pcrs_job *job;
+
+   struct file_list *fl;
+   struct re_filterfile_spec *b;
+   struct list_entry *filtername;
+
+   /* 
+    * Sanity first
+    */
+   if (csp->iob->cur >= csp->iob->eod)
+   {
+      return(NULL);
+   }
+   size = csp->iob->eod - csp->iob->cur;
+
+   if ( ( NULL == (fl = csp->rlist) ) || ( NULL == fl->f) )
+   {
+      log_error(LOG_LEVEL_ERROR, "Unable to get current state of regexp filtering.");
+      return(NULL);
+   }
+
+   /*
+    * If the body has a "chunked" transfer-encoding,
+    * get rid of it first, adjusting size and iob->eod
+    */
+   if (csp->flags & CSP_FLAG_CHUNKED)
+   {
+      log_error(LOG_LEVEL_RE_FILTER, "Need to de-chunk first");
+      if (0 == (size = remove_chunked_transfer_coding(csp->iob->cur, size)))
+      {
+         return(NULL);
+      }
+      csp->iob->eod = csp->iob->cur + size;
+      csp->flags |= CSP_FLAG_MODIFIED;
+   }
+
+   /*
+    * For all applying +filter actions, look if a filter by that
+    * name exists and if yes, execute it's pcrs_joblist on the
+    * buffer.
+    */
+   for (b = fl->f; b; b = b->next)
+   {
+      for (filtername = csp->action->multi[ACTION_MULTI_FILTER]->first;
+           filtername ; filtername = filtername->next)
+      {
+         if (strcmp(b->name, filtername->str) == 0)
+         {
+            int current_hits = 0;
+
+            if ( NULL == b->joblist )
+            {
+               log_error(LOG_LEVEL_RE_FILTER, "Filter %s has empty joblist. Nothing to do.", b->name);
+               return(NULL);
+            }
+
+            log_error(LOG_LEVEL_RE_FILTER, "re_filtering %s%s (size %d) with filter %s...",
+                      csp->http->hostport, csp->http->path, size, b->name);
+
+            /* Apply all jobs from the joblist */
+            for (job = b->joblist; NULL != job; job = job->next)
+            {
+               current_hits += pcrs_execute(job, old, size, &new, &size);
+               if (old != csp->iob->cur) free(old);
+               old=new;
+            }
+
+            log_error(LOG_LEVEL_RE_FILTER, " ...produced %d hits (new size %d).", current_hits, size);
+            hits += current_hits;
+         }
+      }
+   }
+
+   /*
+    * If there were no hits, destroy our copy and let
+    * chat() use the original in csp->iob
+    */
+   if (!hits)
+   {
+      free(new);
+      return(NULL);
+   }
+
+   csp->flags |= CSP_FLAG_MODIFIED;
+   csp->content_length = size;
+   IOB_RESET(csp);
+
+   return(new);
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  gif_deanimate_response
+ *
+ * Description :  Deanimate the GIF image that has been accumulated in
+ *                csp->iob->buf, set csp->content_length to the modified
+ *                size and raise the CSP_FLAG_MODIFIED flag.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns     :  a pointer to the (newly allocated) modified buffer.
+ *                or NULL in case something went wrong.
+ *
+ *********************************************************************/
+char *gif_deanimate_response(struct client_state *csp)
+{
+   struct binbuffer *in, *out;
+   char *p;
+   size_t size = csp->iob->eod - csp->iob->cur;
+
+   /*
+    * If the body has a "chunked" transfer-encoding,
+    * get rid of it first, adjusting size and iob->eod
+    */
+   if (csp->flags & CSP_FLAG_CHUNKED)
+   {
+      log_error(LOG_LEVEL_DEANIMATE, "Need to de-chunk first");
+      if (0 == (size = remove_chunked_transfer_coding(csp->iob->cur, size)))
+      {
+         return(NULL);
+      }
+      csp->iob->eod = csp->iob->cur + size;
+      csp->flags |= CSP_FLAG_MODIFIED;
+   }
+
+   if (  (NULL == (in =  (struct binbuffer *)zalloc(sizeof *in )))
+      || (NULL == (out = (struct binbuffer *)zalloc(sizeof *out))) )
+   {
+      log_error(LOG_LEVEL_DEANIMATE, "failed! (no mem)");
+      return NULL;
+   }
+
+   in->buffer = csp->iob->cur;
+   in->size = size;
+
+   if (gif_deanimate(in, out, strncmp("last", csp->action->string[ACTION_STRING_DEANIMATE], 4)))
+   {
+      log_error(LOG_LEVEL_DEANIMATE, "failed! (gif parsing)");
+      free(in);
+      buf_free(out);
+      return(NULL);
+   }
+   else
+   {
+      if ((int)size == out->offset)
+      {
+         log_error(LOG_LEVEL_DEANIMATE, "GIF not changed.");
+      }
+      else
+      {
+         log_error(LOG_LEVEL_DEANIMATE, "Success! GIF shrunk from %d bytes to %d.", size, out->offset);
+      }
+      csp->content_length = out->offset;
+      csp->flags |= CSP_FLAG_MODIFIED;
+      p = out->buffer;
+      free(in);
+      free(out);
+      return(p);
+   }
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  remove_chunked_transfer_coding
+ *
+ * Description :  In-situ remove the "chunked" transfer coding as defined
+ *                in rfc2616 from a buffer.
+ *
+ * Parameters  :
+ *          1  :  buffer = Pointer to the text buffer
+ *          2  :  size = Number of bytes to be processed
+ *
+ * Returns     :  The new size, i.e. the number of bytes from buffer which
+ *                are occupied by the stripped body, or 0 in case something
+ *                went wrong
+ *
+ *********************************************************************/
+int remove_chunked_transfer_coding(char *buffer, const size_t size)
+{
+   size_t newsize = 0;
+   unsigned int chunksize = 0;
+   char *from_p, *to_p;
+
+   assert(buffer);
+   from_p = to_p = buffer;
+
+   if (sscanf(buffer, "%x", &chunksize) != 1)
+   {
+      log_error(LOG_LEVEL_ERROR, "Invalid first chunksize while stripping \"chunked\" transfer coding");
+      return(0);
+   }
+
+   while (chunksize > 0)
+   {
+      if (NULL == (from_p = strstr(from_p, "\r\n")))
+      {
+         log_error(LOG_LEVEL_ERROR, "Parse error while stripping \"chunked\" transfer coding");
+         return(0);
+      }
+      newsize += chunksize;
+      from_p += 2;
+
+      memmove(to_p, from_p, (size_t) chunksize);
+      to_p = buffer + newsize;
+      from_p += chunksize + 2;
+
+      if (sscanf(from_p, "%x", &chunksize) != 1)
+      {
+         log_error(LOG_LEVEL_ERROR, "Parse error while stripping \"chunked\" transfer coding");
+         return(0);
+      }
+   }
+
+   /* FIXME: Should this get its own loglevel? */
+   log_error(LOG_LEVEL_RE_FILTER, "De-chunking successful. Shrunk from %d to %d\n", size, newsize);
+   return(newsize);
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  url_actions
+ *
+ * Description :  Gets the actions for this URL.
+ *
+ * Parameters  :
+ *          1  :  http = http_request request for blocked URLs
+ *          2  :  csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns     :  N/A
+ *
+ *********************************************************************/
+void url_actions(struct http_request *http,
+                 struct client_state *csp)
+{
+   struct file_list *fl;
+   struct url_actions *b;
+   int i;
+
+   init_current_action(csp->action);
+
+   for (i = 0; i < MAX_ACTION_FILES; i++)
+   {
+      if (((fl = csp->actions_list[i]) == NULL) || ((b = fl->f) == NULL))
+      {
+         return;
+      }
+
+      apply_url_actions(csp->action, http, b);
+   }
+
+   return;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  apply_url_actions
+ *
+ * Description :  Applies a list of URL actions.
+ *
+ * Parameters  :
+ *          1  :  action = Destination.
+ *          2  :  http = Current URL
+ *          3  :  b = list of URL actions to apply
+ *
+ * Returns     :  N/A
+ *
+ *********************************************************************/
+void apply_url_actions(struct current_action_spec *action,
+                       struct http_request *http,
+                       struct url_actions *b)
+{
+   if (b == NULL)
+   {
+      /* Should never happen */
+      return;
+   }
+
+   for (b = b->next; NULL != b; b = b->next)
+   {
+      if (url_match(b->url, http))
+      {
+         merge_current_action(action, b->action);
+      }
+   }
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  forward_url
+ *
+ * Description :  Should we forward this to another proxy?
+ *
+ * Parameters  :
+ *          1  :  http = http_request request for current URL
+ *          2  :  csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns     :  Pointer to forwarding information.
+ *
+ *********************************************************************/
+const struct forward_spec * forward_url(struct http_request *http,
+                                        struct client_state *csp)
+{
+   static const struct forward_spec fwd_default[1] = { FORWARD_SPEC_INITIALIZER };
+   struct forward_spec *fwd = csp->config->forward;
+
+   if (fwd == NULL)
+   {
+      return fwd_default;
+   }
+
+   while (fwd != NULL)
+   {
+      if (url_match(fwd->url, http))
+      {
+         return fwd;
+      }
+      fwd = fwd->next;
+   }
+
+   return fwd_default;
+}
+
+
+/*
+  Local Variables:
+  tab-width: 3
+  end:
+*/
diff --git a/src/filters.h b/src/filters.h
new file mode 100644 (file)
index 0000000..33f4e49
--- /dev/null
@@ -0,0 +1,285 @@
+#ifndef FILTERS_H_INCLUDED
+#define FILTERS_H_INCLUDED
+#define FILTERS_H_VERSION "$Id: filters.h,v 1.20 2002/04/02 14:56:16 oes Exp $"
+/*********************************************************************
+ *
+ * File        :  $Source: /cvsroot/ijbswa/current/filters.h,v $
+ *
+ * Purpose     :  Declares functions to parse/crunch headers and pages.
+ *                Functions declared include:
+ *                   `acl_addr', `add_stats', `block_acl', `block_imageurl',
+ *                   `block_url', `url_actions', `filter_popups', `forward_url'
+ *                   `ij_untrusted_url', `intercept_url', `re_process_buffer',
+ *                   `show_proxy_args', and `trust_url'
+ *
+ * Copyright   :  Written by and Copyright (C) 2001 the SourceForge
+ *                Privoxy team. http://www.privoxy.org/
+ *
+ *                Based on the Internet Junkbuster originally written
+ *                by and Copyright (C) 1997 Anonymous Coders and 
+ *                Junkbusters Corporation.  http://www.junkbusters.com
+ *
+ *                This program is free software; you can redistribute it 
+ *                and/or modify it under the terms of the GNU General
+ *                Public License as published by the Free Software
+ *                Foundation; either version 2 of the License, or (at
+ *                your option) any later version.
+ *
+ *                This program is distributed in the hope that it will
+ *                be useful, but WITHOUT ANY WARRANTY; without even the
+ *                implied warranty of MERCHANTABILITY or FITNESS FOR A
+ *                PARTICULAR PURPOSE.  See the GNU General Public
+ *                License for more details.
+ *
+ *                The GNU General Public License should be included with
+ *                this file.  If not, you can view it at
+ *                http://www.gnu.org/copyleft/gpl.html
+ *                or write to the Free Software Foundation, Inc., 59
+ *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * Revisions   :
+ *    $Log: filters.h,v $
+ *    Revision 1.20  2002/04/02 14:56:16  oes
+ *    Bugfix: is_untrusted_url() and trust_url() now depend on FEATURE_TRUST, not FEATURE_COOKIE_JAR
+ *
+ *    Revision 1.19  2002/03/26 22:29:54  swa
+ *    we have a new homepage!
+ *
+ *    Revision 1.18  2002/03/25 22:12:45  oes
+ *    Added fix for undefined INADDR_NONE on Solaris by Bart Schelstraete
+ *
+ *    Revision 1.17  2002/03/24 13:25:43  swa
+ *    name change related issues
+ *
+ *    Revision 1.16  2002/01/17 21:01:02  jongfoster
+ *    Moving all our URL and URL pattern parsing code to urlmatch.c.
+ *
+ *    Revision 1.15  2001/10/10 16:44:16  oes
+ *    Added match_portlist function
+ *
+ *    Revision 1.14  2001/10/07 15:41:40  oes
+ *    Added prototype for remove_chunked_transfer_coding
+ *
+ *    Revision 1.13  2001/07/30 22:08:36  jongfoster
+ *    Tidying up #defines:
+ *    - All feature #defines are now of the form FEATURE_xxx
+ *    - Permanently turned off WIN_GUI_EDIT
+ *    - Permanently turned on WEBDAV and SPLIT_PROXY_ARGS
+ *
+ *    Revision 1.12  2001/07/29 19:01:11  jongfoster
+ *    Changed _FILENAME_H to FILENAME_H_INCLUDED.
+ *    Added forward declarations for needed structures.
+ *
+ *    Revision 1.11  2001/07/13 14:00:18  oes
+ *     - Introduced gif_deanimate_response
+ *     - Renamed re_process_buffer to pcrs_filter_response
+ *     - Removed all #ifdef PCRS
+ *
+ *    Revision 1.10  2001/06/29 13:29:01  oes
+ *    Cleaned up and updated to reflect the changesin
+ *    filters.c
+ *
+ *    Revision 1.9  2001/06/07 23:10:53  jongfoster
+ *    Replacing struct gateway with struct forward_spec
+ *
+ *    Revision 1.8  2001/06/03 19:12:00  oes
+ *    extracted-CGI relevant stuff
+ *
+ *    Revision 1.7  2001/05/31 21:21:30  jongfoster
+ *    Permissionsfile / actions file changes:
+ *    - Changed "permission" to "action" throughout
+ *    - changes to file format to allow string parameters
+ *    - Moved helper functions to actions.c
+ *
+ *    Revision 1.6  2001/05/29 09:50:24  jongfoster
+ *    Unified blocklist/imagelist/permissionslist.
+ *    File format is still under discussion, but the internal changes
+ *    are (mostly) done.
+ *
+ *    Also modified interceptor behaviour:
+ *    - We now intercept all URLs beginning with one of the following
+ *      prefixes (and *only* these prefixes):
+ *        * http://i.j.b/
+ *        * http://ijbswa.sf.net/config/
+ *        * http://ijbswa.sourceforge.net/config/
+ *    - New interceptors "home page" - go to http://i.j.b/ to see it.
+ *    - Internal changes so that intercepted and fast redirect pages
+ *      are not replaced with an image.
+ *    - Interceptors now have the option to send a binary page direct
+ *      to the client. (i.e. ijb-send-banner uses this)
+ *    - Implemented show-url-info interceptor.  (Which is why I needed
+ *      the above interceptors changes - a typical URL is
+ *      "http://i.j.b/show-url-info?url=www.somesite.com/banner.gif".
+ *      The previous mechanism would not have intercepted that, and
+ *      if it had been intercepted then it then it would have replaced
+ *      it with an image.)
+ *
+ *    Revision 1.5  2001/05/27 22:17:04  oes
+ *
+ *    - re_process_buffer no longer writes the modified buffer
+ *      to the client, which was very ugly. It now returns the
+ *      buffer, which it is then written by chat.
+ *
+ *    - content_length now adjusts the Content-Length: header
+ *      for modified documents rather than crunch()ing it.
+ *      (Length info in csp->content_length, which is 0 for
+ *      unmodified documents)
+ *
+ *    - For this to work, sed() is called twice when filtering.
+ *
+ *    Revision 1.4  2001/05/26 15:26:15  jongfoster
+ *    ACL feature now provides more security by immediately dropping
+ *    connections from untrusted hosts.
+ *
+ *    Revision 1.3  2001/05/22 18:46:04  oes
+ *
+ *    - Enabled filtering banners by size rather than URL
+ *      by adding patterns that replace all standard banner
+ *      sizes with the "Junkbuster" gif to the re_filterfile
+ *
+ *    - Enabled filtering WebBugs by providing a pattern
+ *      which kills all 1x1 images
+ *
+ *    - Added support for PCRE_UNGREEDY behaviour to pcrs,
+ *      which is selected by the (nonstandard and therefore
+ *      capital) letter 'U' in the option string.
+ *      It causes the quantifiers to be ungreedy by default.
+ *      Appending a ? turns back to greedy (!).
+ *
+ *    - Added a new interceptor ijb-send-banner, which
+ *      sends back the "Junkbuster" gif. Without imagelist or
+ *      MSIE detection support, or if tinygif = 1, or the
+ *      URL isn't recognized as an imageurl, a lame HTML
+ *      explanation is sent instead.
+ *
+ *    - Added new feature, which permits blocking remote
+ *      script redirects and firing back a local redirect
+ *      to the browser.
+ *      The feature is conditionally compiled, i.e. it
+ *      can be disabled with --disable-fast-redirects,
+ *      plus it must be activated by a "fast-redirects"
+ *      line in the config file, has its own log level
+ *      and of course wants to be displayed by show-proxy-args
+ *      Note: Boy, all the #ifdefs in 1001 locations and
+ *      all the fumbling with configure.in and acconfig.h
+ *      were *way* more work than the feature itself :-(
+ *
+ *    - Because a generic redirect template was needed for
+ *      this, tinygif = 3 now uses the same.
+ *
+ *    - Moved GIFs, and other static HTTP response templates
+ *      to project.h
+ *
+ *    - Some minor fixes
+ *
+ *    - Removed some >400 CRs again (Jon, you really worked
+ *      a lot! ;-)
+ *
+ *    Revision 1.2  2001/05/20 01:21:20  jongfoster
+ *    Version 2.9.4 checkin.
+ *    - Merged popupfile and cookiefile, and added control over PCRS
+ *      filtering, in new "permissionsfile".
+ *    - Implemented LOG_LEVEL_FATAL, so that if there is a configuration
+ *      file error you now get a message box (in the Win32 GUI) rather
+ *      than the program exiting with no explanation.
+ *    - Made killpopup use the PCRS MIME-type checking and HTTP-header
+ *      skipping.
+ *    - Removed tabs from "config"
+ *    - Moved duplicated url parsing code in "loaders.c" to a new funcition.
+ *    - Bumped up version number.
+ *
+ *    Revision 1.1.1.1  2001/05/15 13:58:52  oes
+ *    Initial import of version 2.9.3 source tree
+ *
+ *
+ *********************************************************************/
+\f
+
+#include "project.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+struct access_control_addr;
+struct client_state;
+struct http_request;
+struct http_response;
+struct current_action_spec;
+struct url_actions;
+struct url_spec;
+
+
+/*
+ * ACL checking
+ */
+#ifdef FEATURE_ACL
+extern int block_acl(struct access_control_addr *dst, struct client_state *csp);
+extern int acl_addr(char *aspec, struct access_control_addr *aca);
+#endif /* def FEATURE_ACL */
+extern int match_portlist(const char *portlist, int port);
+
+/*
+ * Interceptors
+ */
+extern struct http_response *block_url(struct client_state *csp);
+extern struct http_response *redirect_url(struct client_state *csp);
+#ifdef FEATURE_TRUST
+extern struct http_response *trust_url(struct client_state *csp);
+#endif /* def FEATURE_TRUST */
+
+/*
+ * Request inspectors
+ */
+#ifdef FEATURE_TRUST
+extern int is_untrusted_url(struct client_state *csp);
+#endif /* def FEATURE_TRUST */
+#ifdef FEATURE_IMAGE_BLOCKING
+extern int is_imageurl(struct client_state *csp);
+#endif /* def FEATURE_IMAGE_BLOCKING */
+
+/*
+ * Determining applicable actions
+ */
+extern void url_actions(struct http_request *http, 
+                        struct client_state *csp);
+extern void apply_url_actions(struct current_action_spec *action, 
+                              struct http_request *http, 
+                              struct url_actions *b);
+/*
+ * Determining parent proxies
+ */
+extern const struct forward_spec *forward_url(struct http_request *http, struct client_state *csp);
+
+/*
+ * Content modification
+ */
+extern char *pcrs_filter_response(struct client_state *csp);
+extern char *gif_deanimate_response(struct client_state *csp);
+extern int remove_chunked_transfer_coding(char *buffer, const size_t size);
+
+/*
+ * Solaris fix:
+ */
+#ifndef INADDR_NONE
+#define INADDR_NONE -1
+#endif     
+
+/* 
+ * Revision control strings from this header and associated .c file
+ */
+extern const char filters_rcs[];
+extern const char filters_h_rcs[];
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* ndef FILTERS_H_INCLUDED */
+
+/*
+  Local Variables:
+  tab-width: 3
+  end:
+*/
diff --git a/src/gateway.c b/src/gateway.c
new file mode 100644 (file)
index 0000000..6ff762a
--- /dev/null
@@ -0,0 +1,399 @@
+const char gateway_rcs[] = "$Id: gateway.c,v 1.16 2002/05/12 21:36:29 jongfoster Exp $";
+/*********************************************************************
+ *
+ * File        :  $Source: /cvsroot/ijbswa/current/gateway.c,v $
+ *
+ * Purpose     :  Contains functions to connect to a server, possibly
+ *                using a "forwarder" (i.e. HTTP proxy and/or a SOCKS4
+ *                proxy).
+ *
+ * Copyright   :  Written by and Copyright (C) 2001 the SourceForge
+ *                Privoxy team. http://www.privoxy.org/
+ *
+ *                Based on the Internet Junkbuster originally written
+ *                by and Copyright (C) 1997 Anonymous Coders and
+ *                Junkbusters Corporation.  http://www.junkbusters.com
+ *
+ *                This program is free software; you can redistribute it
+ *                and/or modify it under the terms of the GNU General
+ *                Public License as published by the Free Software
+ *                Foundation; either version 2 of the License, or (at
+ *                your option) any later version.
+ *
+ *                This program is distributed in the hope that it will
+ *                be useful, but WITHOUT ANY WARRANTY; without even the
+ *                implied warranty of MERCHANTABILITY or FITNESS FOR A
+ *                PARTICULAR PURPOSE.  See the GNU General Public
+ *                License for more details.
+ *
+ *                The GNU General Public License should be included with
+ *                this file.  If not, you can view it at
+ *                http://www.gnu.org/copyleft/gpl.html
+ *                or write to the Free Software Foundation, Inc., 59
+ *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * Revisions   :
+ *    $Log: gateway.c,v $
+ *    Revision 1.16  2002/05/12 21:36:29  jongfoster
+ *    Correcting function comments
+ *
+ *    Revision 1.15  2002/03/26 22:29:54  swa
+ *    we have a new homepage!
+ *
+ *    Revision 1.14  2002/03/24 13:25:43  swa
+ *    name change related issues
+ *
+ *    Revision 1.13  2002/03/13 00:29:59  jongfoster
+ *    Killing warnings
+ *
+ *    Revision 1.12  2002/03/09 20:03:52  jongfoster
+ *    - Making various functions return int rather than size_t.
+ *      (Undoing a recent change).  Since size_t is unsigned on
+ *      Windows, functions like read_socket that return -1 on
+ *      error cannot return a size_t.
+ *
+ *      THIS WAS A MAJOR BUG - it caused frequent, unpredictable
+ *      crashes, and also frequently caused JB to jump to 100%
+ *      CPU and stay there.  (Because it thought it had just
+ *      read ((unsigned)-1) == 4Gb of data...)
+ *
+ *    - The signature of write_socket has changed, it now simply
+ *      returns success=0/failure=nonzero.
+ *
+ *    - Trying to get rid of a few warnings --with-debug on
+ *      Windows, I've introduced a new type "jb_socket".  This is
+ *      used for the socket file descriptors.  On Windows, this
+ *      is SOCKET (a typedef for unsigned).  Everywhere else, it's
+ *      an int.  The error value can't be -1 any more, so it's
+ *      now JB_INVALID_SOCKET (which is -1 on UNIX, and in
+ *      Windows it maps to the #define INVALID_SOCKET.)
+ *
+ *    - The signature of bind_port has changed.
+ *
+ *    Revision 1.11  2002/03/08 17:46:04  jongfoster
+ *    Fixing int/size_t warnings
+ *
+ *    Revision 1.10  2002/03/07 03:50:19  oes
+ *     - Improved handling of failed DNS lookups
+ *     - Fixed compiler warnings
+ *
+ *    Revision 1.9  2001/10/25 03:40:48  david__schmidt
+ *    Change in porting tactics: OS/2's EMX porting layer doesn't allow multiple
+ *    threads to call select() simultaneously.  So, it's time to do a real, live,
+ *    native OS/2 port.  See defines for __EMX__ (the porting layer) vs. __OS2__
+ *    (native). Both versions will work, but using __OS2__ offers multi-threading.
+ *
+ *    Revision 1.8  2001/09/13 20:10:12  jongfoster
+ *    Fixing missing #include under Windows
+ *
+ *    Revision 1.7  2001/09/12 17:58:26  steudten
+ *
+ *    add #include <string.h>
+ *
+ *    Revision 1.6  2001/09/10 10:41:16  oes
+ *    Added #include in.h
+ *
+ *    Revision 1.5  2001/07/29 18:47:57  jongfoster
+ *    Adding missing #include project.h
+ *
+ *    Revision 1.4  2001/07/24 12:47:06  oes
+ *    Applied BeOS support update by Eugenia
+ *
+ *    Revision 1.3  2001/06/09 10:55:28  jongfoster
+ *    Changing BUFSIZ ==> BUFFER_SIZE
+ *
+ *    Revision 1.2  2001/06/07 23:11:38  jongfoster
+ *    Removing gateways[] list - no longer used.
+ *    Replacing function pointer in struct gateway with a directly
+ *    called function forwarded_connect(), which can do the common
+ *    task of deciding whether to connect to the web server or HTTP
+ *    proxy.
+ *    Replacing struct gateway with struct forward_spec
+ *    Fixing bug with SOCKS4A and HTTP proxy server in combination.
+ *    It was a bug which led to the connection being made to the web
+ *    server rather than the HTTP proxy, and also a buffer overrun.
+ *
+ *    Revision 1.1.1.1  2001/05/15 13:58:54  oes
+ *    Initial import of version 2.9.3 source tree
+ *
+ *
+ *********************************************************************/
+\f
+
+#include "config.h"
+
+#include <stdio.h>
+#include <sys/types.h>
+
+#ifndef _WIN32
+#include <netinet/in.h>
+#endif
+
+#include <errno.h>
+#include <string.h>
+
+#ifdef _WIN32
+#include <winsock2.h>
+#endif /* def _WIN32 */
+
+#ifdef __BEOS__
+#include <netdb.h>
+#endif /* def __BEOS__ */
+
+#ifdef __OS2__
+#include <utils.h>
+#endif /* def __OS2__ */
+
+#include "project.h"
+#include "jcc.h"
+#include "errlog.h"
+#include "jbsockets.h"
+#include "gateway.h"
+
+const char gateway_h_rcs[] = GATEWAY_H_VERSION;
+
+static jb_socket socks4_connect(const struct forward_spec * fwd,
+                                const char * target_host,
+                                int target_port,
+                                struct client_state *csp);
+
+
+#define SOCKS_REQUEST_GRANTED          90
+#define SOCKS_REQUEST_REJECT           91
+#define SOCKS_REQUEST_IDENT_FAILED     92
+#define SOCKS_REQUEST_IDENT_CONFLICT   93
+
+/* structure of a socks client operation */
+struct socks_op {
+   unsigned char vn;          /* socks version number */
+   unsigned char cd;          /* command code */
+   unsigned char dstport[2];  /* destination port */
+   unsigned char dstip[4];    /* destination address */
+   unsigned char userid;      /* first byte of userid */
+   /* more bytes of the userid follow, terminated by a NULL */
+};
+
+/* structure of a socks server reply */
+struct socks_reply {
+   unsigned char vn;          /* socks version number */
+   unsigned char cd;          /* command code */
+   unsigned char dstport[2];  /* destination port */
+   unsigned char dstip[4];    /* destination address */
+};
+
+static const char socks_userid[] = "anonymous";
+
+
+/*********************************************************************
+ *
+ * Function    :  forwarded_connect
+ *
+ * Description :  Connect to a specified web server, possibly via
+ *                a HTTP proxy and/or a SOCKS proxy.
+ *
+ * Parameters  :
+ *          1  :  fwd = the proxies to use when connecting.
+ *          2  :  http = the http request and apropos headers
+ *          3  :  csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns     :  JB_INVALID_SOCKET => failure, else it is the socket file descriptor.
+ *
+ *********************************************************************/
+jb_socket forwarded_connect(const struct forward_spec * fwd,
+                            struct http_request *http,
+                            struct client_state *csp)
+{
+   const char * dest_host;
+   int dest_port;
+
+   /* Figure out if we need to connect to the web server or a HTTP proxy. */
+   if (fwd->forward_host)
+   {
+      /* HTTP proxy */
+      dest_host = fwd->forward_host;
+      dest_port = fwd->forward_port;
+   }
+   else
+   {
+      /* Web server */
+      dest_host = http->host;
+      dest_port = http->port;
+   }
+
+   /* Connect, maybe using a SOCKS proxy */
+   switch (fwd->type)
+   {
+      case SOCKS_NONE:
+         return (connect_to(dest_host, dest_port, csp));
+
+      case SOCKS_4:
+      case SOCKS_4A:
+         return (socks4_connect(fwd, dest_host, dest_port, csp));
+
+      default:
+         /* Should never get here */
+         log_error(LOG_LEVEL_FATAL, "SOCKS4 impossible internal error - bad SOCKS type.");
+         errno = EINVAL;
+         return(JB_INVALID_SOCKET);
+   }
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  socks4_connect
+ *
+ * Description :  Connect to the SOCKS server, and connect through
+ *                it to the specified server.   This handles
+ *                all the SOCKS negotiation, and returns a file
+ *                descriptor for a socket which can be treated as a
+ *                normal (non-SOCKS) socket.
+ *
+ * Parameters  :
+ *          1  :  fwd = Specifies the SOCKS proxy to use.
+ *          2  :  target_host = The final server to connect to.
+ *          3  :  target_port = The final port to connect to.
+ *          4  :  csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns     :  JB_INVALID_SOCKET => failure, else a socket file descriptor.
+ *
+ *********************************************************************/
+static jb_socket socks4_connect(const struct forward_spec * fwd,
+                                const char * target_host,
+                                int target_port,
+                                struct client_state *csp)
+{
+   int web_server_addr;
+   char cbuf[BUFFER_SIZE];
+   char sbuf[BUFFER_SIZE];
+   struct socks_op    *c = (struct socks_op    *)cbuf;
+   struct socks_reply *s = (struct socks_reply *)sbuf;
+   size_t n;
+   size_t csiz;
+   jb_socket sfd;
+   int err = 0;
+   char *errstr;
+
+   if ((fwd->gateway_host == NULL) || (*fwd->gateway_host == '\0'))
+   {
+      log_error(LOG_LEVEL_CONNECT, "socks4_connect: NULL gateway host specified");
+      err = 1;
+   }
+
+   if (fwd->gateway_port <= 0)
+   {
+      log_error(LOG_LEVEL_CONNECT, "socks4_connect: invalid gateway port specified");
+      err = 1;
+   }
+
+   if (err)
+   {
+      errno = EINVAL;
+      return(JB_INVALID_SOCKET);
+   }
+
+   /* build a socks request for connection to the web server */
+
+   strcpy((char *)&(c->userid), socks_userid);
+
+   csiz = sizeof(*c) + sizeof(socks_userid) - 1;
+
+   switch (fwd->type)
+   {
+      case SOCKS_4:
+         web_server_addr = htonl(resolve_hostname_to_ip(target_host));
+         if (web_server_addr == INADDR_NONE)
+         {
+            log_error(LOG_LEVEL_CONNECT, "socks4_connect: could not resolve target host %s", target_host);
+            return(JB_INVALID_SOCKET);
+         }
+         break;
+      case SOCKS_4A:
+         web_server_addr = 0x00000001;
+         n = csiz + strlen(target_host) + 1;
+         if (n > sizeof(cbuf))
+         {
+            errno = EINVAL;
+            return(JB_INVALID_SOCKET);
+         }
+         strcpy(cbuf + csiz, target_host);
+         csiz = n;
+         break;
+      default:
+         /* Should never get here */
+         log_error(LOG_LEVEL_FATAL, "SOCKS4 impossible internal error - bad SOCKS type.");
+         errno = EINVAL;
+         return(JB_INVALID_SOCKET);
+   }
+
+   c->vn          = 4;
+   c->cd          = 1;
+   c->dstport[0]  = (target_port       >> 8  ) & 0xff;
+   c->dstport[1]  = (target_port             ) & 0xff;
+   c->dstip[0]    = (web_server_addr   >> 24 ) & 0xff;
+   c->dstip[1]    = (web_server_addr   >> 16 ) & 0xff;
+   c->dstip[2]    = (web_server_addr   >>  8 ) & 0xff;
+   c->dstip[3]    = (web_server_addr         ) & 0xff;
+
+   /* pass the request to the socks server */
+   sfd = connect_to(fwd->gateway_host, fwd->gateway_port, csp);
+
+   if (sfd == JB_INVALID_SOCKET)
+   {
+      return(JB_INVALID_SOCKET);
+   }
+
+   if (write_socket(sfd, (char *)c, csiz))
+   {
+      log_error(LOG_LEVEL_CONNECT, "SOCKS4 negotiation write failed...");
+      close_socket(sfd);
+      return(JB_INVALID_SOCKET);
+   }
+
+   if (read_socket(sfd, sbuf, sizeof(sbuf)) != sizeof(*s))
+   {
+      log_error(LOG_LEVEL_CONNECT, "SOCKS4 negotiation read failed...");
+      close_socket(sfd);
+      return(JB_INVALID_SOCKET);
+   }
+
+   switch (s->cd)
+   {
+      case SOCKS_REQUEST_GRANTED:
+         return(sfd);
+         break;
+      case SOCKS_REQUEST_REJECT:
+         errstr = "SOCKS request rejected or failed";
+         errno = EINVAL;
+         break;
+      case SOCKS_REQUEST_IDENT_FAILED:
+         errstr = "SOCKS request rejected because "
+            "SOCKS server cannot connect to identd on the client";
+         errno = EACCES;
+         break;
+      case SOCKS_REQUEST_IDENT_CONFLICT:
+         errstr = "SOCKS request rejected because "
+            "the client program and identd report "
+            "different user-ids";
+         errno = EACCES;
+         break;
+      default:
+         errstr = cbuf;
+         errno = ENOENT;
+         sprintf(errstr,
+                 "SOCKS request rejected for reason code %d\n", s->cd);
+   }
+
+   log_error(LOG_LEVEL_CONNECT, "socks4_connect: %s ...", errstr);
+
+   close_socket(sfd);
+   return(JB_INVALID_SOCKET);
+
+}
+
+
+/*
+  Local Variables:
+  tab-width: 3
+  end:
+*/
diff --git a/src/gateway.h b/src/gateway.h
new file mode 100644 (file)
index 0000000..8b39690
--- /dev/null
@@ -0,0 +1,126 @@
+#ifndef GATEWAY_H_INCLUDED
+#define GATEWAY_H_INCLUDED
+#define GATEWAY_H_VERSION "$Id: gateway.h,v 1.7 2002/03/26 22:29:54 swa Exp $"
+/*********************************************************************
+ *
+ * File        :  $Source: /cvsroot/ijbswa/current/gateway.h,v $
+ *
+ * Purpose     :  Contains functions to connect to a server, possibly
+ *                using a "gateway" (i.e. HTTP proxy and/or SOCKS4
+ *                proxy).  Also contains the list of gateway types.
+ *
+ * Copyright   :  Written by and Copyright (C) 2001 the SourceForge
+ *                Privoxy team. http://www.privoxy.org/
+ *
+ *                Based on the Internet Junkbuster originally written
+ *                by and Copyright (C) 1997 Anonymous Coders and 
+ *                Junkbusters Corporation.  http://www.junkbusters.com
+ *
+ *                This program is free software; you can redistribute it 
+ *                and/or modify it under the terms of the GNU General
+ *                Public License as published by the Free Software
+ *                Foundation; either version 2 of the License, or (at
+ *                your option) any later version.
+ *
+ *                This program is distributed in the hope that it will
+ *                be useful, but WITHOUT ANY WARRANTY; without even the
+ *                implied warranty of MERCHANTABILITY or FITNESS FOR A
+ *                PARTICULAR PURPOSE.  See the GNU General Public
+ *                License for more details.
+ *
+ *                The GNU General Public License should be included with
+ *                this file.  If not, you can view it at
+ *                http://www.gnu.org/copyleft/gpl.html
+ *                or write to the Free Software Foundation, Inc., 59
+ *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * Revisions   :
+ *    $Log: gateway.h,v $
+ *    Revision 1.7  2002/03/26 22:29:54  swa
+ *    we have a new homepage!
+ *
+ *    Revision 1.6  2002/03/25 22:12:45  oes
+ *    Added fix for undefined INADDR_NONE on Solaris by Bart Schelstraete
+ *
+ *    Revision 1.5  2002/03/24 13:25:43  swa
+ *    name change related issues
+ *
+ *    Revision 1.4  2002/03/09 20:03:52  jongfoster
+ *    - Making various functions return int rather than size_t.
+ *      (Undoing a recent change).  Since size_t is unsigned on
+ *      Windows, functions like read_socket that return -1 on
+ *      error cannot return a size_t.
+ *
+ *      THIS WAS A MAJOR BUG - it caused frequent, unpredictable
+ *      crashes, and also frequently caused JB to jump to 100%
+ *      CPU and stay there.  (Because it thought it had just
+ *      read ((unsigned)-1) == 4Gb of data...)
+ *
+ *    - The signature of write_socket has changed, it now simply
+ *      returns success=0/failure=nonzero.
+ *
+ *    - Trying to get rid of a few warnings --with-debug on
+ *      Windows, I've introduced a new type "jb_socket".  This is
+ *      used for the socket file descriptors.  On Windows, this
+ *      is SOCKET (a typedef for unsigned).  Everywhere else, it's
+ *      an int.  The error value can't be -1 any more, so it's
+ *      now JB_INVALID_SOCKET (which is -1 on UNIX, and in
+ *      Windows it maps to the #define INVALID_SOCKET.)
+ *
+ *    - The signature of bind_port has changed.
+ *
+ *    Revision 1.3  2001/07/29 18:58:15  jongfoster
+ *    Removing nested #includes, adding forward declarations for needed
+ *    structures, and changing the #define _FILENAME_H to FILENAME_H_INCLUDED.
+ *
+ *    Revision 1.2  2001/06/07 23:12:14  jongfoster
+ *    Removing gateways[] list - no longer used.
+ *    Replacing function pointer in struct gateway with a directly
+ *    called function forwarded_connect(), which can do the common
+ *    task of deciding whether to connect to the web server or HTTP
+ *    proxy.
+ *    Replacing struct gateway with struct forward_spec
+ *
+ *    Revision 1.1.1.1  2001/05/15 13:58:54  oes
+ *    Initial import of version 2.9.3 source tree
+ *
+ *
+ *********************************************************************/
+\f
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct forward_spec;
+struct http_request;
+struct client_state;
+
+extern jb_socket forwarded_connect(const struct forward_spec * fwd, 
+                                   struct http_request *http, 
+                                   struct client_state *csp);
+
+/*
+ * Solaris fix
+ */
+#ifndef INADDR_NONE
+#define INADDR_NONE -1
+#endif
+
+/*
+ * Revision control strings from this header and associated .c file
+ */
+extern const char gateway_rcs[];
+extern const char gateway_h_rcs[];
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* ndef GATEWAY_H_INCLUDED */
+
+/*
+  Local Variables:
+  tab-width: 3
+  end:
+*/
diff --git a/src/jbsockets.c b/src/jbsockets.c
new file mode 100644 (file)
index 0000000..bfc025c
--- /dev/null
@@ -0,0 +1,820 @@
+const char jbsockets_rcs[] = "$Id: jbsockets.c,v 1.36 2002/05/26 23:23:10 joergs Exp $";
+/*********************************************************************
+ *
+ * File        :  $Source: /cvsroot/ijbswa/current/jbsockets.c,v $
+ *
+ * Purpose     :  Contains wrappers for system-specific sockets code,
+ *                so that the rest of Junkbuster can be more
+ *                OS-independent.  Contains #ifdefs to make this work
+ *                on many platforms.
+ *
+ * Copyright   :  Written by and Copyright (C) 2001 the SourceForge
+ *                Privoxy team. http://www.privoxy.org/
+ *
+ *                Based on the Internet Junkbuster originally written
+ *                by and Copyright (C) 1997 Anonymous Coders and 
+ *                Junkbusters Corporation.  http://www.junkbusters.com
+ *
+ *                This program is free software; you can redistribute it 
+ *                and/or modify it under the terms of the GNU General
+ *                Public License as published by the Free Software
+ *                Foundation; either version 2 of the License, or (at
+ *                your option) any later version.
+ *
+ *                This program is distributed in the hope that it will
+ *                be useful, but WITHOUT ANY WARRANTY; without even the
+ *                implied warranty of MERCHANTABILITY or FITNESS FOR A
+ *                PARTICULAR PURPOSE.  See the GNU General Public
+ *                License for more details.
+ *
+ *                The GNU General Public License should be included with
+ *                this file.  If not, you can view it at
+ *                http://www.gnu.org/copyleft/gpl.html
+ *                or write to the Free Software Foundation, Inc., 59
+ *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * Revisions   :
+ *    $Log: jbsockets.c,v $
+ *    Revision 1.36  2002/05/26 23:23:10  joergs
+ *    AmigaOS: Fixed wrong type for len in write_socket()
+ *
+ *    Revision 1.35  2002/04/26 15:50:04  joergs
+ *    AmigaOS: No socklen_t, added AMIGA to the systems using int instead.
+ *
+ *    Revision 1.34  2002/04/08 20:31:41  swa
+ *    fixed JB spelling
+ *
+ *    Revision 1.33  2002/04/03 16:02:18  gliptak
+ *    Correcting compile warning with older gcc
+ *
+ *    Revision 1.32  2002/03/31 17:18:59  jongfoster
+ *    Win32 only: Enabling STRICT to fix a VC++ compile warning.
+ *
+ *    Revision 1.31  2002/03/29 03:33:13  david__schmidt
+ *    Fix Mac OSX compiler warnings
+ *
+ *    Revision 1.30  2002/03/27 14:32:43  david__schmidt
+ *    More compiler warning message maintenance
+ *
+ *    Revision 1.29  2002/03/26 22:29:54  swa
+ *    we have a new homepage!
+ *
+ *    Revision 1.28  2002/03/24 13:25:43  swa
+ *    name change related issues
+ *
+ *    Revision 1.27  2002/03/13 00:27:05  jongfoster
+ *    Killing warnings
+ *
+ *    Revision 1.26  2002/03/11 22:07:02  david__schmidt
+ *    OS/2 port maintenance:
+ *    - Fixed EMX build - it had decayed a little
+ *    - Fixed inexplicable crash during FD_ZERO - must be due to a bad macro.
+ *      substituted a memset for now.
+ *
+ *    Revision 1.25  2002/03/09 20:03:52  jongfoster
+ *    - Making various functions return int rather than size_t.
+ *      (Undoing a recent change).  Since size_t is unsigned on
+ *      Windows, functions like read_socket that return -1 on
+ *      error cannot return a size_t.
+ *
+ *      THIS WAS A MAJOR BUG - it caused frequent, unpredictable
+ *      crashes, and also frequently caused JB to jump to 100%
+ *      CPU and stay there.  (Because it thought it had just
+ *      read ((unsigned)-1) == 4Gb of data...)
+ *
+ *    - The signature of write_socket has changed, it now simply
+ *      returns success=0/failure=nonzero.
+ *
+ *    - Trying to get rid of a few warnings --with-debug on
+ *      Windows, I've introduced a new type "jb_socket".  This is
+ *      used for the socket file descriptors.  On Windows, this
+ *      is SOCKET (a typedef for unsigned).  Everywhere else, it's
+ *      an int.  The error value can't be -1 any more, so it's
+ *      now JB_INVALID_SOCKET (which is -1 on UNIX, and in
+ *      Windows it maps to the #define INVALID_SOCKET.)
+ *
+ *    - The signature of bind_port has changed.
+ *
+ *    Revision 1.24  2002/03/07 03:51:36  oes
+ *     - Improved handling of failed DNS lookups
+ *     - Fixed compiler warnings etc
+ *
+ *    Revision 1.23  2002/03/05 00:36:01  jongfoster
+ *    Fixing bug 514988 - unable to restart Junkbuster
+ *
+ *    Revision 1.22  2002/03/04 02:08:02  david__schmidt
+ *    Enable web editing of actions file on OS/2 (it had been broken all this time!)
+ *
+ *    Revision 1.21  2002/01/09 14:32:33  oes
+ *    Added support for gethostbyname_r and gethostbyaddr_r.
+ *
+ *    Revision 1.20  2001/11/16 00:48:48  jongfoster
+ *    Enabling duplicate-socket detection for all platforms, not
+ *    just Win32.
+ *
+ *    Revision 1.19  2001/10/25 03:40:47  david__schmidt
+ *    Change in porting tactics: OS/2's EMX porting layer doesn't allow multiple
+ *    threads to call select() simultaneously.  So, it's time to do a real, live,
+ *    native OS/2 port.  See defines for __EMX__ (the porting layer) vs. __OS2__
+ *    (native). Both versions will work, but using __OS2__ offers multi-threading.
+ *
+ *    Revision 1.18  2001/09/21 23:02:02  david__schmidt
+ *    Cleaning up 2 compiler warnings on OS/2.
+ *
+ *    Revision 1.17  2001/09/13 20:11:46  jongfoster
+ *    Fixing 2 compiler warnings under Win32
+ *
+ *    Revision 1.16  2001/07/30 22:08:36  jongfoster
+ *    Tidying up #defines:
+ *    - All feature #defines are now of the form FEATURE_xxx
+ *    - Permanently turned off WIN_GUI_EDIT
+ *    - Permanently turned on WEBDAV and SPLIT_PROXY_ARGS
+ *
+ *    Revision 1.15  2001/07/29 17:40:43  jongfoster
+ *    Fixed compiler warning by adding a cast
+ *
+ *    Revision 1.14  2001/07/18 13:47:59  oes
+ *    Eliminated dirty hack for getsockbyname()
+ *
+ *    Revision 1.13  2001/07/15 13:56:57  jongfoster
+ *    Removing unused local variable.
+ *
+ *    Revision 1.12  2001/07/01 17:04:11  oes
+ *    Bugfix: accept_connection no longer uses the obsolete hstrerror() function
+ *
+ *    Revision 1.11  2001/06/29 21:45:41  oes
+ *    Indentation, CRLF->LF, Tab-> Space
+ *
+ *    Revision 1.10  2001/06/29 13:29:15  oes
+ *    - Added remote (server) host IP to csp->http->host_ip_addr_str
+ *    - Added detection of local socket IP and fqdn
+ *    - Removed logentry from cancelled commit
+ *
+ *    Revision 1.9  2001/06/07 23:06:09  jongfoster
+ *    The host parameter to connect_to() is now const.
+ *
+ *    Revision 1.8  2001/06/03 19:12:07  oes
+ *    filled comment
+ *
+ *    Revision 1.7  2001/05/28 16:14:00  jongfoster
+ *    Fixing bug in LOG_LEVEL_LOG
+ *
+ *    Revision 1.6  2001/05/26 17:28:32  jongfoster
+ *    Fixed LOG_LEVEL_LOG
+ *
+ *    Revision 1.5  2001/05/26 15:26:15  jongfoster
+ *    ACL feature now provides more security by immediately dropping
+ *    connections from untrusted hosts.
+ *
+ *    Revision 1.4  2001/05/26 00:37:42  jongfoster
+ *    Cosmetic indentation correction.
+ *
+ *    Revision 1.3  2001/05/25 21:57:54  jongfoster
+ *    Now gives a warning under Windows if you try to bind
+ *    it to a port that's already in use.
+ *
+ *    Revision 1.2  2001/05/17 23:01:01  oes
+ *     - Cleaned CRLF's from the sources and related files
+ *
+ *    Revision 1.1.1.1  2001/05/15 13:58:54  oes
+ *    Initial import of version 2.9.3 source tree
+ *
+ *
+ *********************************************************************/
+\f
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/types.h>
+
+#ifdef _WIN32
+
+#ifndef STRICT
+#define STRICT
+#endif
+#include <windows.h>
+#include <sys/timeb.h>
+#include <io.h>
+
+#else
+
+#ifndef __OS2__
+#include <unistd.h>
+#endif
+#include <sys/time.h>
+#include <netinet/in.h>
+#include <sys/ioctl.h>
+#include <netdb.h>
+#include <sys/socket.h>
+
+#ifndef __BEOS__
+#include <netinet/tcp.h>
+#ifndef __OS2__
+#include <arpa/inet.h>
+#endif
+#else
+#include <socket.h>
+#endif
+
+#if defined(__EMX__) || defined (__OS2__)
+#include <sys/select.h>  /* OS/2/EMX needs a little help with select */
+#ifdef __OS2__
+#include <nerrno.h>
+#endif
+#endif
+
+#endif
+
+#include "project.h"
+#include "jbsockets.h"
+#include "filters.h"
+#include "errlog.h"
+
+const char jbsockets_h_rcs[] = JBSOCKETS_H_VERSION;
+
+
+/*********************************************************************
+ *
+ * Function    :  connect_to
+ *
+ * Description :  Open a socket and connect to it.  Will check
+ *                that this is allowed according to ACL.
+ *
+ * Parameters  :
+ *          1  :  host = hostname to connect to
+ *          2  :  portnum = port to connent on
+ *          3  :  csp = Current client state (buffers, headers, etc...)
+ *                      Not modified, only used for source IP and ACL.
+ *
+ * Returns     :  JB_INVALID_SOCKET => failure, else it is the socket
+ *                file descriptor.
+ *
+ *********************************************************************/
+jb_socket connect_to(const char *host, int portnum, struct client_state *csp)
+{
+   struct sockaddr_in inaddr;
+   jb_socket fd;
+   int addr;
+   fd_set wfds;
+   struct timeval tv[1];
+#if !defined(_WIN32) && !defined(__BEOS__) && !defined(AMIGA)
+   int   flags;
+#endif /* !defined(_WIN32) && !defined(__BEOS__) && !defined(AMIGA) */
+
+#ifdef FEATURE_ACL
+   struct access_control_addr dst[1];
+#endif /* def FEATURE_ACL */
+
+   memset((char *)&inaddr, 0, sizeof inaddr);
+
+   if ((addr = resolve_hostname_to_ip(host)) == INADDR_NONE)
+   {
+      csp->http->host_ip_addr_str = strdup("unknown");
+      return(JB_INVALID_SOCKET);
+   }
+
+#ifdef FEATURE_ACL
+   dst->addr = ntohl((unsigned long) addr);
+   dst->port = portnum;
+
+   if (block_acl(dst, csp))
+   {
+#ifdef __OS2__
+      errno = SOCEPERM;
+#else
+      errno = EPERM;
+#endif
+      return(JB_INVALID_SOCKET);
+   }
+#endif /* def FEATURE_ACL */
+
+   inaddr.sin_addr.s_addr = addr;
+   inaddr.sin_family      = AF_INET;
+   csp->http->host_ip_addr_str = strdup(inet_ntoa(inaddr.sin_addr));
+
+#ifndef _WIN32
+   if (sizeof(inaddr.sin_port) == sizeof(short))
+#endif /* ndef _WIN32 */
+   {
+      inaddr.sin_port = htons((unsigned short) portnum);
+   }
+#ifndef _WIN32
+   else
+   {
+      inaddr.sin_port = htonl((unsigned long)portnum);
+   }
+#endif /* ndef _WIN32 */
+
+#ifdef _WIN32
+   if ((fd = socket(inaddr.sin_family, SOCK_STREAM, 0)) == JB_INVALID_SOCKET)
+#else
+   if ((fd = socket(inaddr.sin_family, SOCK_STREAM, 0)) < 0)
+#endif
+   {
+      return(JB_INVALID_SOCKET);
+   }
+
+#ifdef TCP_NODELAY
+   {  /* turn off TCP coalescence */
+      int mi = 1;
+      setsockopt (fd, IPPROTO_TCP, TCP_NODELAY, (char *) &mi, sizeof (int));
+   }
+#endif /* def TCP_NODELAY */
+
+#if !defined(_WIN32) && !defined(__BEOS__) && !defined(AMIGA) && !defined(__OS2__)
+   if ((flags = fcntl(fd, F_GETFL, 0)) != -1)
+   {
+      flags |= O_NDELAY;
+      fcntl(fd, F_SETFL, flags);
+   }
+#endif /* !defined(_WIN32) && !defined(__BEOS__) && !defined(AMIGA) && !defined(__OS2__) */
+
+   while (connect(fd, (struct sockaddr *) & inaddr, sizeof inaddr) == JB_INVALID_SOCKET)
+   {
+#ifdef _WIN32
+      if (errno == WSAEINPROGRESS)
+#elif __OS2__ 
+      if (sock_errno() == EINPROGRESS)
+#else /* ifndef _WIN32 */
+      if (errno == EINPROGRESS)
+#endif /* ndef _WIN32 || __OS2__ */
+      {
+         break;
+      }
+
+#ifdef __OS2__ 
+      if (sock_errno() != EINTR)
+#else
+      if (errno != EINTR)
+#endif /* __OS2__ */
+      {
+         close_socket(fd);
+         return(JB_INVALID_SOCKET);
+      }
+   }
+
+#if !defined(_WIN32) && !defined(__BEOS__) && !defined(AMIGA) && !defined(__OS2__)
+   if (flags != -1)
+   {
+      flags &= ~O_NDELAY;
+      fcntl(fd, F_SETFL, flags);
+   }
+#endif /* !defined(_WIN32) && !defined(__BEOS__) && !defined(AMIGA) && !defined(__OS2__) */
+
+   /* wait for connection to complete */
+   FD_ZERO(&wfds);
+   FD_SET(fd, &wfds);
+
+   tv->tv_sec  = 30;
+   tv->tv_usec = 0;
+
+   /* MS Windows uses int, not SOCKET, for the 1st arg of select(). Wierd! */
+   if (select((int)fd + 1, NULL, &wfds, NULL, tv) <= 0)
+   {
+      close_socket(fd);
+      return(JB_INVALID_SOCKET);
+   }
+   return(fd);
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  write_socket
+ *
+ * Description :  Write the contents of buf (for n bytes) to socket fd.
+ *
+ * Parameters  :
+ *          1  :  fd = file descriptor (aka. handle) of socket to write to.
+ *          2  :  buf = pointer to data to be written.
+ *          3  :  len = length of data to be written to the socket "fd".
+ *
+ * Returns     :  0 on success (entire buffer sent).
+ *                nonzero on error.
+ *
+ *********************************************************************/
+#ifdef AMIGA
+int write_socket(jb_socket fd, const char *buf, ssize_t len)
+#else
+int write_socket(jb_socket fd, const char *buf, size_t len)
+#endif
+{
+   if (len == 0)
+   {
+      return 0;
+   }
+
+   if (len < 0)
+   {
+      return 1;
+   }
+
+   log_error(LOG_LEVEL_LOG, "%N", len, buf);
+
+#if defined(_WIN32)
+   return (send(fd, buf, (int)len, 0) != (int)len);
+#elif defined(__BEOS__) || defined(AMIGA)
+   return (send(fd, buf, len, 0) != len);
+#elif defined(__OS2__)
+   /*
+    * Break the data up into SOCKET_SEND_MAX chunks for sending...
+    * OS/2 seemed to complain when the chunks were too large.
+    */
+#define SOCKET_SEND_MAX 65000
+   {
+      int write_len = 0, send_len, send_rc = 0, i = 0;
+      while ((i < len) && (send_rc != -1))
+      {
+         if ((i + SOCKET_SEND_MAX) > len)
+            send_len = len - i;
+         else
+            send_len = SOCKET_SEND_MAX;
+         send_rc = send(fd,(char*)buf + i, send_len, 0);
+         if (send_rc == -1)
+            return 1;
+         i = i + send_len;
+      }
+      return 0;
+   }
+#else
+   return (write(fd, buf, len) != len);
+#endif
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  read_socket
+ *
+ * Description :  Read from a TCP/IP socket in a platform independent way.
+ *
+ * Parameters  :
+ *          1  :  fd = file descriptor of the socket to read
+ *          2  :  buf = pointer to buffer where data will be written
+ *                Must be >= len bytes long.
+ *          3  :  len = maximum number of bytes to read
+ *
+ * Returns     :  On success, the number of bytes read is returned (zero
+ *                indicates end of file), and the file position is advanced
+ *                by this number.  It is not an error if this number is
+ *                smaller than the number of bytes requested; this may hap-
+ *                pen for example because fewer bytes are actually available
+ *                right now (maybe because we were close to end-of-file, or
+ *                because we are reading from a pipe, or from a terminal,
+ *                or because read() was interrupted by a signal).  On error,
+ *                -1 is returned, and errno is set appropriately.  In this
+ *                case it is left unspecified whether the file position (if
+ *                any) changes.
+ *
+ *********************************************************************/
+int read_socket(jb_socket fd, char *buf, int len)
+{
+   if (len <= 0)
+   {
+      return(0);
+   }
+
+#if defined(_WIN32)
+   return(recv(fd, buf, len, 0));
+#elif defined(__BEOS__) || defined(AMIGA) || defined(__OS2__)
+   return(recv(fd, buf, (size_t)len, 0));
+#else
+   return(read(fd, buf, (size_t)len));
+#endif
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  close_socket
+ *
+ * Description :  Closes a TCP/IP socket
+ *
+ * Parameters  :
+ *          1  :  fd = file descriptor of socket to be closed
+ *
+ * Returns     :  void
+ *
+ *********************************************************************/
+void close_socket(jb_socket fd)
+{
+#if defined(_WIN32) || defined(__BEOS__)
+   closesocket(fd);
+#elif defined(AMIGA)
+   CloseSocket(fd); 
+#elif defined(__OS2__)
+   soclose(fd);
+#else
+   close(fd);
+#endif
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  bind_port
+ *
+ * Description :  Call socket, set socket options, and listen.
+ *                Called by listen_loop to "boot up" our proxy address.
+ *
+ * Parameters  :
+ *          1  :  hostnam = TCP/IP address to bind/listen to
+ *          2  :  portnum = port to listen on
+ *          3  :  pfd = pointer used to return file descriptor.
+ *
+ * Returns     :  if success, returns 0 and sets *pfd.
+ *                if failure, returns -3 if address is in use,
+ *                                    -2 if address unresolvable,
+ *                                    -1 otherwise
+ *********************************************************************/
+int bind_port(const char *hostnam, int portnum, jb_socket *pfd)
+{
+   struct sockaddr_in inaddr;
+   jb_socket fd;
+#ifndef _WIN32
+   int one = 1;
+#endif /* ndef _WIN32 */
+
+   *pfd = JB_INVALID_SOCKET;
+
+   memset((char *)&inaddr, '\0', sizeof inaddr);
+
+   inaddr.sin_family      = AF_INET;
+   inaddr.sin_addr.s_addr = resolve_hostname_to_ip(hostnam);
+
+   if (inaddr.sin_addr.s_addr == INADDR_NONE)
+   {
+      return(-2);
+   }
+
+#ifndef _WIN32
+   if (sizeof(inaddr.sin_port) == sizeof(short))
+#endif /* ndef _WIN32 */
+   {
+      inaddr.sin_port = htons((unsigned short) portnum);
+   }
+#ifndef _WIN32
+   else
+   {
+      inaddr.sin_port = htonl((unsigned long) portnum);
+   }
+#endif /* ndef _WIN32 */
+
+   fd = socket(AF_INET, SOCK_STREAM, 0);
+
+#ifdef _WIN32
+   if (fd == JB_INVALID_SOCKET)
+#else
+   if (fd < 0)
+#endif
+   {
+      return(-1);
+   }
+
+#ifndef _WIN32
+   /*
+    * This is not needed for Win32 - in fact, it stops
+    * duplicate instances of Junkbuster from being caught.
+    *
+    * On UNIX, we assume the user is sensible enough not
+    * to start Junkbuster multiple times on the same IP.
+    * Without this, stopping and restarting Junkbuster
+    * from a script fails.
+    * Note: SO_REUSEADDR is meant to only take over
+    * sockets which are *not* in listen state in Linux,
+    * e.g. sockets in TIME_WAIT. YMMV.
+    */
+   setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *)&one, sizeof(one));
+#endif /* ndef _WIN32 */
+
+   if (bind (fd, (struct sockaddr *)&inaddr, sizeof(inaddr)) < 0)
+   {
+      close_socket (fd);
+#ifdef _WIN32
+      if (errno == WSAEADDRINUSE)
+#else
+      if (errno == EADDRINUSE)
+#endif
+      {
+         return(-3);
+      }
+      else
+      {
+         return(-1);
+      }
+   }
+
+   while (listen(fd, 5) == -1)
+   {
+      if (errno != EINTR)
+      {
+         return(-1);
+      }
+   }
+
+   *pfd = fd;
+   return 0;
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  accept_connection
+ *
+ * Description :  Accepts a connection on a socket.  Socket must have
+ *                been created using bind_port().
+ *
+ * Parameters  :
+ *          1  :  csp = Client state, cfd, ip_addr_str, and 
+ *                ip_addr_long will be set by this routine.
+ *          2  :  fd  = file descriptor returned from bind_port
+ *
+ * Returns     :  when a connection is accepted, it returns 1 (TRUE).
+ *                On an error it returns 0 (FALSE).
+ *
+ *********************************************************************/
+int accept_connection(struct client_state * csp, jb_socket fd)
+{
+   struct sockaddr_in client, server;
+   struct hostent *host = NULL;
+   jb_socket afd;
+#if defined(_WIN32) || defined(__OS2__) || defined(__APPLE_CC__) || defined(AMIGA)
+   /* Wierdness - fix a warning. */
+   int c_length, s_length;
+#else
+   socklen_t c_length, s_length;
+#endif
+#if defined(HAVE_GETHOSTBYADDR_R_8_ARGS) ||  defined(HAVE_GETHOSTBYADDR_R_7_ARGS) || defined(HAVE_GETHOSTBYADDR_R_5_ARGS)
+   struct hostent result;
+#if defined(HAVE_GETHOSTBYADDR_R_5_ARGS)
+   struct hostent_data hdata;
+#else
+   char hbuf[HOSTENT_BUFFER_SIZE];
+   int thd_err;
+#endif /* def HAVE_GETHOSTBYADDR_R_5_ARGS */
+#endif /* def HAVE_GETHOSTBYADDR_R_(8|7|5)_ARGS */
+
+   c_length = s_length = sizeof(client);
+
+#ifdef _WIN32
+   afd = accept (fd, (struct sockaddr *) &client, &c_length);
+   if (afd == JB_INVALID_SOCKET)
+   {
+      return 0;
+   }
+#else
+   do
+   {
+      afd = accept (fd, (struct sockaddr *) &client, &c_length);
+   } while (afd < 1 && errno == EINTR);
+   if (afd < 0)
+   {
+      return 0;
+   }
+#endif
+
+   /* 
+    * Determine the IP-Adress that the client used to reach us
+    * and the hostname associated with that address
+    */
+   if (!getsockname(afd, (struct sockaddr *) &server, &s_length))
+   {
+      csp->my_ip_addr_str = strdup(inet_ntoa(server.sin_addr));
+#if defined(HAVE_GETHOSTBYADDR_R_8_ARGS)
+      gethostbyaddr_r((const char *)&server.sin_addr,
+                      sizeof(server.sin_addr), AF_INET,
+                      &result, hbuf, HOSTENT_BUFFER_SIZE,
+                      &host, &thd_err);
+#elif defined(HAVE_GETHOSTBYADDR_R_7_ARGS)
+      host = gethostbyaddr_r((const char *)&server.sin_addr,
+                      sizeof(server.sin_addr), AF_INET,
+                      &result, hbuf, HOSTENT_BUFFER_SIZE, &thd_err);
+#elif defined(HAVE_GETHOSTBYADDR_R_5_ARGS)
+      if (0 == gethostbyaddr_r((const char *)&server.sin_addr,
+                               sizeof(server.sin_addr), AF_INET,
+                               &result, &hdata))
+      {
+         host = &result;
+      }
+      else
+      {
+         host = NULL;
+      }
+#else
+      host = gethostbyaddr((const char *)&server.sin_addr, 
+                           sizeof(server.sin_addr), AF_INET);
+#endif
+      if (host == NULL)
+      {
+         log_error(LOG_LEVEL_ERROR, "Unable to get my own hostname: %E\n");
+      }
+      else
+      {
+         csp->my_hostname = strdup(host->h_name);
+      }
+   }
+
+   csp->cfd    = afd;
+   csp->ip_addr_str  = strdup(inet_ntoa(client.sin_addr));
+   csp->ip_addr_long = ntohl(client.sin_addr.s_addr);
+
+   return 1;
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  resolve_hostname_to_ip
+ *
+ * Description :  Resolve a hostname to an internet tcp/ip address.
+ *                NULL or an empty string resolve to INADDR_ANY.
+ *
+ * Parameters  :
+ *          1  :  host = hostname to resolve
+ *
+ * Returns     :  INADDR_NONE => failure, INADDR_ANY or tcp/ip address if succesful.
+ *
+ *********************************************************************/
+unsigned long resolve_hostname_to_ip(const char *host)
+{
+   struct sockaddr_in inaddr;
+   struct hostent *hostp;
+#if defined(HAVE_GETHOSTBYNAME_R_6_ARGS) || defined(HAVE_GETHOSTBYNAME_R_5_ARGS) || defined(HAVE_GETHOSTBYNAME_R_3_ARGS)
+   struct hostent result;
+#if defined(HAVE_GETHOSTBYNAME_R_6_ARGS) || defined(HAVE_GETHOSTBYNAME_R_5_ARGS)
+   char hbuf[HOSTENT_BUFFER_SIZE];
+   int thd_err;
+#else /* defined(HAVE_GETHOSTBYNAME_R_3_ARGS) */
+   struct hostent_data hdata;
+#endif /* def HAVE_GETHOSTBYNAME_R_(6|5)_ARGS */
+#endif /* def HAVE_GETHOSTBYNAME_R_(6|5|3)_ARGS */
+
+   if ((host == NULL) || (*host == '\0'))
+   {
+      return(INADDR_ANY);
+   }
+
+   memset((char *) &inaddr, 0, sizeof inaddr);
+
+   if ((inaddr.sin_addr.s_addr = inet_addr(host)) == -1)
+   {
+#if defined(HAVE_GETHOSTBYNAME_R_6_ARGS)
+      gethostbyname_r(host, &result, hbuf,
+                      HOSTENT_BUFFER_SIZE, &hostp, &thd_err);
+#elif defined(HAVE_GETHOSTBYNAME_R_5_ARGS)
+      hostp = gethostbyname_r(host, &result, hbuf,
+                      HOSTENT_BUFFER_SIZE, &thd_err);
+#elif defined(HAVE_GETHOSTBYNAME_R_3_ARGS)
+      if (0 == gethostbyname_r(host, &result, &hdata))
+      {
+         hostp = &result;
+      }
+      else
+      {
+         hostp = NULL;
+      }
+#else
+      hostp = gethostbyname(host);
+#endif /* def HAVE_GETHOSTBYNAME_R_(6|5|3)_ARGS */
+      if (hostp == NULL)
+      {
+         errno = EINVAL;
+         log_error(LOG_LEVEL_ERROR, "could not resolve hostname %s", host);
+         return(INADDR_NONE);
+      }
+      if (hostp->h_addrtype != AF_INET)
+      {
+#ifdef _WIN32
+         errno = WSAEPROTOTYPE;
+#else
+         errno = EPROTOTYPE;
+#endif 
+         log_error(LOG_LEVEL_ERROR, "hostname %s resolves to unknown address type.", host);
+         return(INADDR_NONE);
+      }
+      memcpy(
+         (char *) &inaddr.sin_addr,
+         (char *) hostp->h_addr,
+         sizeof(inaddr.sin_addr)
+      );
+   }
+   return(inaddr.sin_addr.s_addr);
+
+}
+
+
+/*
+  Local Variables:
+  tab-width: 3
+  end:
+*/
diff --git a/src/jbsockets.h b/src/jbsockets.h
new file mode 100644 (file)
index 0000000..8a95bb2
--- /dev/null
@@ -0,0 +1,133 @@
+#ifndef JBSOCKETS_H_INCLUDED
+#define JBSOCKETS_H_INCLUDED
+#define JBSOCKETS_H_VERSION "$Id: jbsockets.h,v 1.10 2002/05/26 23:23:10 joergs Exp $"
+/*********************************************************************
+ *
+ * File        :  $Source: /cvsroot/ijbswa/current/jbsockets.h,v $
+ *
+ * Purpose     :  Contains wrappers for system-specific sockets code,
+ *                so that the rest of Junkbuster can be more
+ *                OS-independent.  Contains #ifdefs to make this work
+ *                on many platforms.
+ *
+ * Copyright   :  Written by and Copyright (C) 2001 the SourceForge
+ *                Privoxy team. http://www.privoxy.org/
+ *
+ *                Based on the Internet Junkbuster originally written
+ *                by and Copyright (C) 1997 Anonymous Coders and 
+ *                Junkbusters Corporation.  http://www.junkbusters.com
+ *
+ *                This program is free software; you can redistribute it 
+ *                and/or modify it under the terms of the GNU General
+ *                Public License as published by the Free Software
+ *                Foundation; either version 2 of the License, or (at
+ *                your option) any later version.
+ *
+ *                This program is distributed in the hope that it will
+ *                be useful, but WITHOUT ANY WARRANTY; without even the
+ *                implied warranty of MERCHANTABILITY or FITNESS FOR A
+ *                PARTICULAR PURPOSE.  See the GNU General Public
+ *                License for more details.
+ *
+ *                The GNU General Public License should be included with
+ *                this file.  If not, you can view it at
+ *                http://www.gnu.org/copyleft/gpl.html
+ *                or write to the Free Software Foundation, Inc., 59
+ *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * Revisions   :
+ *    $Log: jbsockets.h,v $
+ *    Revision 1.10  2002/05/26 23:23:10  joergs
+ *    AmigaOS: Fixed wrong type for len in write_socket()
+ *
+ *    Revision 1.9  2002/04/08 20:31:41  swa
+ *    fixed JB spelling
+ *
+ *    Revision 1.8  2002/03/26 22:29:54  swa
+ *    we have a new homepage!
+ *
+ *    Revision 1.7  2002/03/24 13:25:43  swa
+ *    name change related issues
+ *
+ *    Revision 1.6  2002/03/13 00:27:05  jongfoster
+ *    Killing warnings
+ *
+ *    Revision 1.5  2002/03/09 20:03:52  jongfoster
+ *    - Making various functions return int rather than size_t.
+ *      (Undoing a recent change).  Since size_t is unsigned on
+ *      Windows, functions like read_socket that return -1 on
+ *      error cannot return a size_t.
+ *
+ *      THIS WAS A MAJOR BUG - it caused frequent, unpredictable
+ *      crashes, and also frequently caused JB to jump to 100%
+ *      CPU and stay there.  (Because it thought it had just
+ *      read ((unsigned)-1) == 4Gb of data...)
+ *
+ *    - The signature of write_socket has changed, it now simply
+ *      returns success=0/failure=nonzero.
+ *
+ *    - Trying to get rid of a few warnings --with-debug on
+ *      Windows, I've introduced a new type "jb_socket".  This is
+ *      used for the socket file descriptors.  On Windows, this
+ *      is SOCKET (a typedef for unsigned).  Everywhere else, it's
+ *      an int.  The error value can't be -1 any more, so it's
+ *      now JB_INVALID_SOCKET (which is -1 on UNIX, and in
+ *      Windows it maps to the #define INVALID_SOCKET.)
+ *
+ *    - The signature of bind_port has changed.
+ *
+ *    Revision 1.4  2002/03/07 03:51:36  oes
+ *     - Improved handling of failed DNS lookups
+ *     - Fixed compiler warnings etc
+ *
+ *    Revision 1.3  2001/07/29 19:01:11  jongfoster
+ *    Changed _FILENAME_H to FILENAME_H_INCLUDED.
+ *    Added forward declarations for needed structures.
+ *
+ *    Revision 1.2  2001/06/07 23:06:09  jongfoster
+ *    The host parameter to connect_to() is now const.
+ *
+ *    Revision 1.1.1.1  2001/05/15 13:58:54  oes
+ *    Initial import of version 2.9.3 source tree
+ *
+ *
+ *********************************************************************/
+\f
+
+#include "project.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct client_state;
+
+extern jb_socket connect_to(const char *host, int portnum, struct client_state *csp);
+#ifdef AMIGA
+extern int write_socket(jb_socket fd, const char *buf, ssize_t n);
+#else
+extern int write_socket(jb_socket fd, const char *buf, size_t n);
+#endif
+extern int read_socket(jb_socket fd, char *buf, int n);
+extern void close_socket(jb_socket fd);
+
+extern int bind_port(const char *hostnam, int portnum, jb_socket *pfd);
+extern int accept_connection(struct client_state * csp, jb_socket fd);
+
+extern unsigned long resolve_hostname_to_ip(const char *host);
+
+/* Revision control strings from this header and associated .c file */
+extern const char jbsockets_rcs[];
+extern const char jbsockets_h_rcs[];
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* ndef JBSOCKETS_H_INCLUDED */
+
+/*
+  Local Variables:
+  tab-width: 3
+  end:
+*/
diff --git a/src/jcc.c b/src/jcc.c
new file mode 100644 (file)
index 0000000..5dd10e0
--- /dev/null
+++ b/src/jcc.c
@@ -0,0 +1,2364 @@
+const char jcc_rcs[] = "$Id: jcc.c,v 1.92 2002/05/08 16:00:46 oes Exp $";
+/*********************************************************************
+ *
+ * File        :  $Source: /cvsroot/ijbswa/current/jcc.c,v $
+ *
+ * Purpose     :  Main file.  Contains main() method, main loop, and
+ *                the main connection-handling function.
+ *
+ * Copyright   :  Written by and Copyright (C) 2001 the SourceForge
+ *                Privoxy team. http://www.privoxy.org/
+ *
+ *                Based on the Internet Junkbuster originally written
+ *                by and Copyright (C) 1997 Anonymous Coders and
+ *                Junkbusters Corporation.  http://www.junkbusters.com
+ *
+ *                This program is free software; you can redistribute it
+ *                and/or modify it under the terms of the GNU General
+ *                Public License as published by the Free Software
+ *                Foundation; either version 2 of the License, or (at
+ *                your option) any later version.
+ *
+ *                This program is distributed in the hope that it will
+ *                be useful, but WITHOUT ANY WARRANTY; without even the
+ *                implied warranty of MERCHANTABILITY or FITNESS FOR A
+ *                PARTICULAR PURPOSE.  See the GNU General Public
+ *                License for more details.
+ *
+ *                The GNU General Public License should be included with
+ *                this file.  If not, you can view it at
+ *                http://www.gnu.org/copyleft/gpl.html
+ *                or write to the Free Software Foundation, Inc., 59
+ *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * Revisions   :
+ *    $Log: jcc.c,v $
+ *    Revision 1.92  2002/05/08 16:00:46  oes
+ *    Chat's buffer handling:
+ *     - Fixed bug with unchecked out-of-mem conditions
+ *       while reading client request & server headers
+ *     - No longer predict if the buffer limit will be exceeded
+ *       in the next read -- check add_to_iob's new
+ *       return code. If buffer couldn't be extended
+ *       (policy or out-of-mem) while
+ *       - reading from client: abort
+ *       - reading server headers: send error page
+ *       - buffering server body for filter: flush,
+ *         and if that fails: send error page
+ *
+ *    Revision 1.91  2002/04/08 20:35:58  swa
+ *    fixed JB spelling
+ *
+ *    Revision 1.90  2002/04/02 14:57:28  oes
+ *    Made sending wafers independent of FEATURE_COOKIE_JAR
+ *
+ *    Revision 1.89  2002/03/31 17:18:59  jongfoster
+ *    Win32 only: Enabling STRICT to fix a VC++ compile warning.
+ *
+ *    Revision 1.88  2002/03/27 14:32:43  david__schmidt
+ *    More compiler warning message maintenance
+ *
+ *    Revision 1.87  2002/03/26 22:29:54  swa
+ *    we have a new homepage!
+ *
+ *    Revision 1.86  2002/03/25 17:04:55  david__schmidt
+ *    Workaround for closing the jarfile before load_config() comes around again
+ *
+ *    Revision 1.85  2002/03/24 15:23:33  jongfoster
+ *    Name changes
+ *
+ *    Revision 1.84  2002/03/24 13:25:43  swa
+ *    name change related issues
+ *
+ *    Revision 1.83  2002/03/16 23:54:06  jongfoster
+ *    Adding graceful termination feature, to help look for memory leaks.
+ *    If you enable this (which, by design, has to be done by hand
+ *    editing config.h) and then go to http://i.j.b/die, then the program
+ *    will exit cleanly after the *next* request.  It should free all the
+ *    memory that was used.
+ *
+ *    Revision 1.82  2002/03/13 00:27:05  jongfoster
+ *    Killing warnings
+ *
+ *    Revision 1.81  2002/03/12 01:42:50  oes
+ *    Introduced modular filters
+ *
+ *    Revision 1.80  2002/03/11 22:07:05  david__schmidt
+ *    OS/2 port maintenance:
+ *    - Fixed EMX build - it had decayed a little
+ *    - Fixed inexplicable crash during FD_ZERO - must be due to a bad macro.
+ *      substituted a memset for now.
+ *
+ *    Revision 1.79  2002/03/09 20:03:52  jongfoster
+ *    - Making various functions return int rather than size_t.
+ *      (Undoing a recent change).  Since size_t is unsigned on
+ *      Windows, functions like read_socket that return -1 on
+ *      error cannot return a size_t.
+ *
+ *      THIS WAS A MAJOR BUG - it caused frequent, unpredictable
+ *      crashes, and also frequently caused JB to jump to 100%
+ *      CPU and stay there.  (Because it thought it had just
+ *      read ((unsigned)-1) == 4Gb of data...)
+ *
+ *    - The signature of write_socket has changed, it now simply
+ *      returns success=0/failure=nonzero.
+ *
+ *    - Trying to get rid of a few warnings --with-debug on
+ *      Windows, I've introduced a new type "jb_socket".  This is
+ *      used for the socket file descriptors.  On Windows, this
+ *      is SOCKET (a typedef for unsigned).  Everywhere else, it's
+ *      an int.  The error value can't be -1 any more, so it's
+ *      now JB_INVALID_SOCKET (which is -1 on UNIX, and in
+ *      Windows it maps to the #define INVALID_SOCKET.)
+ *
+ *    - The signature of bind_port has changed.
+ *
+ *    Revision 1.78  2002/03/08 21:35:04  oes
+ *    Added optional group supplement to --user option. Will now use default group of user if no group given
+ *
+ *    Revision 1.77  2002/03/07 03:52:06  oes
+ *     - Fixed compiler warnings etc
+ *     - Improved handling of failed DNS lookups
+ *
+ *    Revision 1.76  2002/03/06 22:54:35  jongfoster
+ *    Automated function-comment nitpicking.
+ *
+ *    Revision 1.75  2002/03/06 10:02:19  oes
+ *    Fixed stupid bug when --user was not given
+ *
+ *    Revision 1.74  2002/03/06 00:49:31  jongfoster
+ *    Fixing warning on Windows
+ *    Making #ifdefs that refer to the same variable consistently
+ *    use #ifdef unix rather than mixing #ifdef unix & #ifndef OS2
+ *
+ *    Revision 1.73  2002/03/05 23:57:30  hal9
+ *    Stray character 's' on line 1618 was breaking build.
+ *
+ *    Revision 1.72  2002/03/05 21:33:45  david__schmidt
+ *    - Re-enable OS/2 building after new parms were added
+ *    - Fix false out of memory report when resolving CGI templates when no IP
+ *      address is available of failed attempt (a la no such domain)
+ *
+ *    Revision 1.71  2002/03/05 18:13:56  oes
+ *    Added --user option
+ *
+ *    Revision 1.70  2002/03/05 04:52:42  oes
+ *    Deleted non-errlog debugging code
+ *
+ *    Revision 1.69  2002/03/04 23:50:00  jongfoster
+ *    Splitting off bind_port() call into bind_port_helper(), with
+ *    improved logging.
+ *
+ *    Revision 1.68  2002/03/04 20:17:32  oes
+ *    Fixed usage info
+ *
+ *    Revision 1.67  2002/03/04 18:18:57  oes
+ *    - Removed _DEBUG mode
+ *    - Cleand up cmdline parsing
+ *    - Introduced --no-daemon, --pidfile options
+ *    - Cleaned up signal handling:
+ *      - Terminate cleanly on INT, TERM and ABRT
+ *      - Schedule logfile for re-opening on HUP
+ *      - Ignore CHLD and PIPE
+ *      - Leave the rest with their default handlers
+ *      - Uniform handler registration
+ *    - Added usage() function
+ *    - Played styleguide police
+ *
+ *    Revision 1.66  2002/03/03 15:06:55  oes
+ *    Re-enabled automatic config reloading
+ *
+ *    Revision 1.65  2002/03/03 14:49:11  oes
+ *    Fixed CLF logging: Now uses client's original HTTP request
+ *
+ *    Revision 1.64  2002/03/03 09:18:03  joergs
+ *    Made jumbjuster work on AmigaOS again.
+ *
+ *    Revision 1.63  2002/03/02 04:14:50  david__schmidt
+ *    Clean up a little CRLF unpleasantness that suddenly appeared
+ *
+ *    Revision 1.62  2002/02/20 23:17:23  jongfoster
+ *    Detecting some out-of memory conditions and exiting with a log message.
+ *
+ *    Revision 1.61  2002/01/17 21:01:52  jongfoster
+ *    Moving all our URL and URL pattern parsing code to urlmatch.c.
+ *
+ *    Revision 1.60  2001/12/30 14:07:32  steudten
+ *    - Add signal handling (unix)
+ *    - Add SIGHUP handler (unix)
+ *    - Add creation of pidfile (unix)
+ *    - Add action 'top' in rc file (RH)
+ *    - Add entry 'SIGNALS' to manpage
+ *    - Add exit message to logfile (unix)
+ *
+ *    Revision 1.59  2001/12/13 14:07:18  oes
+ *    Fixed Bug: 503 error page now sent OK
+ *
+ *    Revision 1.58  2001/11/30 23:37:24  jongfoster
+ *    Renaming the Win32 config file to config.txt - this is almost the
+ *    same as the corresponding UNIX name "config"
+ *
+ *    Revision 1.57  2001/11/16 00:47:43  jongfoster
+ *    Changing the tty-disconnection code to use setsid().
+ *
+ *    Revision 1.56  2001/11/13 20:20:54  jongfoster
+ *    Tabs->spaces, fixing a bug with missing {} around an if()
+ *
+ *    Revision 1.55  2001/11/13 20:14:53  jongfoster
+ *    Patch for FreeBSD setpgrp() as suggested by Alexander Lazic
+ *
+ *    Revision 1.54  2001/11/07 00:03:14  steudten
+ *    Give reliable return value if an error
+ *    occurs not just 0 with new daemon mode.
+ *
+ *    Revision 1.53  2001/11/05 21:41:43  steudten
+ *    Add changes to be a real daemon just for unix os.
+ *    (change cwd to /, detach from controlling tty, set
+ *    process group and session leader to the own process.
+ *    Add DBG() Macro.
+ *    Add some fatal-error log message for failed malloc().
+ *    Add '-d' if compiled with 'configure --with-debug' to
+ *    enable debug output.
+ *
+ *    Revision 1.52  2001/10/26 20:11:20  jongfoster
+ *    Fixing type mismatch
+ *
+ *    Revision 1.51  2001/10/26 17:38:28  oes
+ *    Cosmetics
+ *
+ *    Revision 1.50  2001/10/25 03:40:48  david__schmidt
+ *    Change in porting tactics: OS/2's EMX porting layer doesn't allow multiple
+ *    threads to call select() simultaneously.  So, it's time to do a real, live,
+ *    native OS/2 port.  See defines for __EMX__ (the porting layer) vs. __OS2__
+ *    (native). Both versions will work, but using __OS2__ offers multi-threading.
+ *
+ *    Revision 1.49  2001/10/23 21:41:35  jongfoster
+ *    Added call to initialize the (statically-allocated of course)
+ *    "out of memory" CGI response.
+ *
+ *    Revision 1.48  2001/10/10 19:56:46  jongfoster
+ *    Moving some code that wasn't cookie-related out of an #ifdef
+ *    FEATURE_COOKIE_JAR
+ *
+ *    Revision 1.47  2001/10/10 16:44:36  oes
+ *    Added CONNECT destination port limitation check
+ *
+ *    Revision 1.46  2001/10/08 15:17:41  oes
+ *    Re-enabled SSL forwarding
+ *
+ *    Revision 1.45  2001/10/07 15:42:11  oes
+ *    Replaced 6 boolean members of csp with one bitmap (csp->flags)
+ *
+ *    Moved downgrading of the HTTP version from parse_http_request to
+ *      chat(), since we can't decide if it is necessary before we have
+ *      determined the actions for the URL. The HTTP command is now
+ *      *always* re-built so the repairs need no longer be special-cased.
+ *
+ *    filter_popups now gets a csp pointer so it can raise the new
+ *      CSP_FLAG_MODIFIED flag.
+ *
+ *    Bugfix
+ *
+ *    Added configurable size limit for the IOB. If the IOB grows so
+ *      large that the next read would exceed the limit, the header
+ *      is generated, and the header & unfiltered buffer are flushed
+ *      to the client. Chat then continues in non-buffering,
+ *      non-filtering body mode.
+ *
+ *    Revision 1.44  2001/10/02 18:13:57  oes
+ *    Ooops
+ *
+ *    Revision 1.43  2001/10/02 15:32:13  oes
+ *    Moved generation of hdr
+ *
+ *    Revision 1.42  2001/09/21 23:02:02  david__schmidt
+ *    Cleaning up 2 compiler warnings on OS/2.
+ *
+ *    Revision 1.41  2001/09/16 17:05:14  jongfoster
+ *    Removing unused #include showarg.h
+ *
+ *    Revision 1.40  2001/09/16 15:41:45  jongfoster
+ *    Fixing signed/unsigned comparison warning.
+ *
+ *    Revision 1.39  2001/09/16 13:21:27  jongfoster
+ *    Changes to use new list functions.
+ *
+ *    Revision 1.38  2001/09/16 13:01:46  jongfoster
+ *    Removing redundant function call that zeroed zalloc()'d memory.
+ *
+ *    Revision 1.37  2001/09/10 11:12:24  oes
+ *    Deleted unused variable
+ *
+ *    Revision 1.36  2001/09/10 10:56:15  oes
+ *    Silenced compiler warnings
+ *
+ *    Revision 1.35  2001/07/31 14:44:22  oes
+ *    Deleted unused size parameter from filter_popups()
+ *
+ *    Revision 1.34  2001/07/30 22:08:36  jongfoster
+ *    Tidying up #defines:
+ *    - All feature #defines are now of the form FEATURE_xxx
+ *    - Permanently turned off WIN_GUI_EDIT
+ *    - Permanently turned on WEBDAV and SPLIT_PROXY_ARGS
+ *
+ *    Revision 1.33  2001/07/29 19:32:00  jongfoster
+ *    Renaming _main() [mingw32 only] to real_main(), for ANSI compliance.
+ *
+ *    Revision 1.32  2001/07/29 18:47:05  jongfoster
+ *    Adding missing #include "loadcfg.h"
+ *
+ *    Revision 1.31  2001/07/29 12:17:48  oes
+ *    Applied pthread fix by Paul Lieverse
+ *
+ *    Revision 1.30  2001/07/25 22:57:13  jongfoster
+ *    __BEOS__ no longer overrides FEATURE_PTHREAD.
+ *    This is because FEATURE_PTHREAD will soon be widely used, so I
+ *    want to keep it simple.
+ *
+ *    Revision 1.29  2001/07/24 12:47:06  oes
+ *    Applied BeOS support update by Eugenia
+ *
+ *    Revision 1.28  2001/07/23 13:26:12  oes
+ *    Fixed bug in popup-killing for the first read that caused binary garbage to be sent between headers and body
+ *
+ *    Revision 1.27  2001/07/19 19:09:47  haroon
+ *    - Added code to take care of the situation where while processing the first
+ *      server response (which includes the server header), after finding the end
+ *      of the headers we were not looking past the end of the headers for
+ *      content modification. I enabled it for filter_popups.
+ *      Someone else should look to see if other similar operations should be
+ *      done to the discarded portion of the buffer.
+ *
+ *      Note 2001/07/20: No, the other content modification mechanisms will process
+ *                       the whole iob later anyway. --oes
+ *
+ *    Revision 1.26  2001/07/18 12:31:36  oes
+ *    cosmetics
+ *
+ *    Revision 1.25  2001/07/15 19:43:49  jongfoster
+ *    Supports POSIX threads.
+ *    Also removed some unused #includes.
+ *
+ *    Revision 1.24  2001/07/13 14:00:40  oes
+ *     - Generic content modification scheme:
+ *       Each feature has its own applicability flag that is set
+ *       from csp->action->flags.
+ *       Replaced the "filtering" int flag , by a function pointer
+ *       "content_filter" to the function that will do the content
+ *       modification. If it is != NULL, the document will be buffered
+ *       and processed through *content_filter, which must set
+ *       csp->content_length and return a modified copy of the body
+ *       or return NULL (on failiure).
+ *     - Changed csp->is_text to the more generic bitmap csp->content_type
+ *       which can currently take the valued CT_TEXT or CT_GIF
+ *     - Reformatting etc
+ *     - Removed all #ifdef PCRS
+ *
+ *    Revision 1.23  2001/07/02 02:28:25  iwanttokeepanon
+ *    Added "#ifdef ACL_FILES" conditional compilation to line 1291 to exclude
+ *    the `block_acl' call.  This prevents a compilation error when the user
+ *    does not wish to use the "ACL" feature.
+ *
+ *    Revision 1.22  2001/06/29 21:45:41  oes
+ *    Indentation, CRLF->LF, Tab-> Space
+ *
+ *    Revision 1.21  2001/06/29 13:29:36  oes
+ *    - Cleaned up, improved comments
+ *    - Unified all possible interceptors (CGI,
+ *      block, trust, fast_redirect) in one
+ *      place, with one (CGI) answer generation
+ *      mechansim. Much clearer now.
+ *    - Removed the GIF image generation, which
+ *      is now done in filters.c:block_url()
+ *    - Made error conditions like domain lookup
+ *      failiure or (various) problems while talking
+ *      to the server use cgi.c:error_response()
+ *      instead of generating HTML/HTTP in chat() (yuck!)
+ *    - Removed logentry from cancelled commit
+ *
+ *    Revision 1.20  2001/06/09 10:55:28  jongfoster
+ *    Changing BUFSIZ ==> BUFFER_SIZE
+ *
+ *    Revision 1.19  2001/06/07 23:12:52  jongfoster
+ *    Replacing function pointer in struct gateway with a directly
+ *    called function forwarded_connect().
+ *    Replacing struct gateway with struct forward_spec
+ *
+ *    Revision 1.18  2001/06/03 19:12:16  oes
+ *    introduced new cgi handling
+ *
+ *    Revision 1.17  2001/06/01 20:07:23  jongfoster
+ *    Now uses action +image-blocker{} rather than config->tinygif
+ *
+ *    Revision 1.16  2001/06/01 18:49:17  jongfoster
+ *    Replaced "list_share" with "list" - the tiny memory gain was not
+ *    worth the extra complexity.
+ *
+ *    Revision 1.15  2001/05/31 21:24:47  jongfoster
+ *    Changed "permission" to "action" throughout.
+ *    Removed DEFAULT_USER_AGENT - it must now be specified manually.
+ *    Moved vanilla wafer check into chat(), since we must now
+ *    decide whether or not to add it based on the URL.
+ *
+ *    Revision 1.14  2001/05/29 20:14:01  joergs
+ *    AmigaOS bugfix: PCRS needs a lot of stack, stacksize for child threads
+ *    increased.
+ *
+ *    Revision 1.13  2001/05/29 09:50:24  jongfoster
+ *    Unified blocklist/imagelist/permissionslist.
+ *    File format is still under discussion, but the internal changes
+ *    are (mostly) done.
+ *
+ *    Also modified interceptor behaviour:
+ *    - We now intercept all URLs beginning with one of the following
+ *      prefixes (and *only* these prefixes):
+ *        * http://i.j.b/
+ *        * http://ijbswa.sf.net/config/
+ *        * http://ijbswa.sourceforge.net/config/
+ *    - New interceptors "home page" - go to http://i.j.b/ to see it.
+ *    - Internal changes so that intercepted and fast redirect pages
+ *      are not replaced with an image.
+ *    - Interceptors now have the option to send a binary page direct
+ *      to the client. (i.e. ijb-send-banner uses this)
+ *    - Implemented show-url-info interceptor.  (Which is why I needed
+ *      the above interceptors changes - a typical URL is
+ *      "http://i.j.b/show-url-info?url=www.somesite.com/banner.gif".
+ *      The previous mechanism would not have intercepted that, and
+ *      if it had been intercepted then it then it would have replaced
+ *      it with an image.)
+ *
+ *    Revision 1.12  2001/05/27 22:17:04  oes
+ *
+ *    - re_process_buffer no longer writes the modified buffer
+ *      to the client, which was very ugly. It now returns the
+ *      buffer, which it is then written by chat.
+ *
+ *    - content_length now adjusts the Content-Length: header
+ *      for modified documents rather than crunch()ing it.
+ *      (Length info in csp->content_length, which is 0 for
+ *      unmodified documents)
+ *
+ *    - For this to work, sed() is called twice when filtering.
+ *
+ *    Revision 1.11  2001/05/26 17:27:53  jongfoster
+ *    Added support for CLF and fixed LOG_LEVEL_LOG.
+ *    Also did CRLF->LF fix of my previous patch.
+ *
+ *    Revision 1.10  2001/05/26 15:26:15  jongfoster
+ *    ACL feature now provides more security by immediately dropping
+ *    connections from untrusted hosts.
+ *
+ *    Revision 1.9  2001/05/26 00:28:36  jongfoster
+ *    Automatic reloading of config file.
+ *    Removed obsolete SIGHUP support (Unix) and Reload menu option (Win32).
+ *    Most of the global variables have been moved to a new
+ *    struct configuration_spec, accessed through csp->config->globalname
+ *    Most of the globals remaining are used by the Win32 GUI.
+ *
+ *    Revision 1.8  2001/05/25 22:43:18  jongfoster
+ *    Fixing minor memory leak and buffer overflow.
+ *
+ *    Revision 1.7  2001/05/25 22:34:30  jongfoster
+ *    Hard tabs->Spaces
+ *
+ *    Revision 1.6  2001/05/23 00:13:58  joergs
+ *    AmigaOS support fixed.
+ *
+ *    Revision 1.5  2001/05/22 18:46:04  oes
+ *
+ *    - Enabled filtering banners by size rather than URL
+ *      by adding patterns that replace all standard banner
+ *      sizes with the "Junkbuster" gif to the re_filterfile
+ *
+ *    - Enabled filtering WebBugs by providing a pattern
+ *      which kills all 1x1 images
+ *
+ *    - Added support for PCRE_UNGREEDY behaviour to pcrs,
+ *      which is selected by the (nonstandard and therefore
+ *      capital) letter 'U' in the option string.
+ *      It causes the quantifiers to be ungreedy by default.
+ *      Appending a ? turns back to greedy (!).
+ *
+ *    - Added a new interceptor ijb-send-banner, which
+ *      sends back the "Junkbuster" gif. Without imagelist or
+ *      MSIE detection support, or if tinygif = 1, or the
+ *      URL isn't recognized as an imageurl, a lame HTML
+ *      explanation is sent instead.
+ *
+ *    - Added new feature, which permits blocking remote
+ *      script redirects and firing back a local redirect
+ *      to the browser.
+ *      The feature is conditionally compiled, i.e. it
+ *      can be disabled with --disable-fast-redirects,
+ *      plus it must be activated by a "fast-redirects"
+ *      line in the config file, has its own log level
+ *      and of course wants to be displayed by show-proxy-args
+ *      Note: Boy, all the #ifdefs in 1001 locations and
+ *      all the fumbling with configure.in and acconfig.h
+ *      were *way* more work than the feature itself :-(
+ *
+ *    - Because a generic redirect template was needed for
+ *      this, tinygif = 3 now uses the same.
+ *
+ *    - Moved GIFs, and other static HTTP response templates
+ *      to project.h
+ *
+ *    - Some minor fixes
+ *
+ *    - Removed some >400 CRs again (Jon, you really worked
+ *      a lot! ;-)
+ *
+ *    Revision 1.4  2001/05/21 19:34:01  jongfoster
+ *    Made failure to bind() a fatal error.
+ *
+ *    Revision 1.3  2001/05/20 01:21:20  jongfoster
+ *    Version 2.9.4 checkin.
+ *    - Merged popupfile and cookiefile, and added control over PCRS
+ *      filtering, in new "permissionsfile".
+ *    - Implemented LOG_LEVEL_FATAL, so that if there is a configuration
+ *      file error you now get a message box (in the Win32 GUI) rather
+ *      than the program exiting with no explanation.
+ *    - Made killpopup use the PCRS MIME-type checking and HTTP-header
+ *      skipping.
+ *    - Removed tabs from "config"
+ *    - Moved duplicated url parsing code in "loaders.c" to a new funcition.
+ *    - Bumped up version number.
+ *
+ *    Revision 1.2  2001/05/17 22:34:44  oes
+ *     - Added hint on GIF char array generation to jcc.c
+ *     - Cleaned CRLF's from the sources and related files
+ *     - Repaired logging for REF and FRC
+ *
+ *    Revision 1.1.1.1  2001/05/15 13:58:56  oes
+ *    Initial import of version 2.9.3 source tree
+ *
+ *
+ *********************************************************************/
+\f
+
+#include "config.h"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#ifdef FEATURE_PTHREAD
+#include <pthread.h>
+#endif /* def FEATURE_PTHREAD */
+
+#ifdef _WIN32
+# ifndef FEATURE_PTHREAD
+#  ifndef STRICT
+#   define STRICT
+#  endif
+#  include <windows.h>
+#  include <process.h>
+# endif /* ndef FEATURE_PTHREAD */
+
+# include "win32.h"
+# ifndef _WIN_CONSOLE
+#  include "w32log.h"
+# endif /* ndef _WIN_CONSOLE */
+
+#else /* ifndef _WIN32 */
+
+# if !defined (__OS2__)
+# include <unistd.h>
+# include <sys/wait.h>
+# endif /* ndef __OS2__ */
+# include <sys/time.h>
+# include <sys/stat.h>
+# include <sys/ioctl.h>
+
+#ifdef sun
+#include <sys/termios.h>
+#endif /* sun */
+
+#ifdef unix
+#include <pwd.h>
+#include <grp.h>
+#endif
+
+# include <signal.h>
+
+# ifdef __BEOS__
+#  include <socket.h>  /* BeOS has select() for sockets only. */
+#  include <OS.h>      /* declarations for threads and stuff. */
+# endif
+
+# if defined(__EMX__) || defined(__OS2__)
+#  include <sys/select.h>  /* OS/2/EMX needs a little help with select */
+# endif
+# ifdef __OS2__
+#define INCL_DOS
+# include <os2.h>
+#define bzero(B,N) memset(B,0x00,n)
+# endif
+
+# ifndef FD_ZERO
+#  include <select.h>
+# endif
+
+#endif
+
+#include "project.h"
+#include "list.h"
+#include "jcc.h"
+#include "filters.h"
+#include "loaders.h"
+#include "parsers.h"
+#include "killpopup.h"
+#include "miscutil.h"
+#include "errlog.h"
+#include "jbsockets.h"
+#include "gateway.h"
+#include "actions.h"
+#include "cgi.h"
+#include "loadcfg.h"
+#include "urlmatch.h"
+
+const char jcc_h_rcs[] = JCC_H_VERSION;
+const char project_h_rcs[] = PROJECT_H_VERSION;
+
+int no_daemon = 0;
+struct client_state  clients[1];
+struct file_list     files[1];
+
+#ifdef FEATURE_STATISTICS
+int urls_read     = 0;     /* total nr of urls read inc rejected */
+int urls_rejected = 0;     /* total nr of urls rejected */
+#endif /* def FEATURE_STATISTICS */
+
+#ifdef FEATURE_GRACEFUL_TERMINATION
+int g_terminate = 0;
+#endif
+
+static void listen_loop(void);
+static void chat(struct client_state *csp);
+#ifdef AMIGA
+void serve(struct client_state *csp);
+#else /* ifndef AMIGA */
+static void serve(struct client_state *csp);
+#endif /* def AMIGA */
+
+#ifdef __BEOS__
+static int32 server_thread(void *data);
+#endif /* def __BEOS__ */
+
+#ifdef _WIN32
+#define sleep(N)  Sleep(((N) * 1000))
+#endif
+
+#ifdef __OS2__
+#define sleep(N)  DosSleep(((N) * 100))
+#endif
+
+#if defined(unix) || defined(__EMX__)
+const char *basedir;
+const char *pidfile = NULL;
+int received_hup_signal = 0;
+#endif /* defined unix */
+
+/* The vanilla wafer. */
+static const char VANILLA_WAFER[] =
+   "NOTICE=TO_WHOM_IT_MAY_CONCERN_"
+   "Do_not_send_me_any_copyrighted_information_other_than_the_"
+   "document_that_I_am_requesting_or_any_of_its_necessary_components._"
+   "In_particular_do_not_send_me_any_cookies_that_"
+   "are_subject_to_a_claim_of_copyright_by_anybody._"
+   "Take_notice_that_I_refuse_to_be_bound_by_any_license_condition_"
+   "(copyright_or_otherwise)_applying_to_any_cookie._";
+
+
+#if !defined(_WIN32) && !defined(__OS2__) && !defined(AMIGA)
+/*********************************************************************
+ *
+ * Function    :  sig_handler 
+ *
+ * Description :  Signal handler for different signals.
+ *                Exit gracefully on ABRT, TERM and  INT
+ *                or set a flag that will cause the errlog
+ *                to be reopened by the main thread on HUP.
+ *
+ * Parameters  :
+ *          1  :  the_signal = the signal cause this function to call
+ *
+ * Returns     :  - 
+ *
+ *********************************************************************/
+static void sig_handler(int the_signal)
+{
+   switch(the_signal)
+   {
+      case SIGABRT:
+      case SIGTERM:
+      case SIGINT:
+         log_error(LOG_LEVEL_INFO, "exiting by signal %d .. bye", the_signal);
+#if defined(unix)
+         unlink(pidfile);
+#endif /* unix */
+         exit(the_signal);
+         break;
+
+      case SIGHUP:
+         received_hup_signal = 1;
+         break;         
+
+      default:
+         /* 
+          * We shouldn't be here, unless we catch signals
+          * in main() that we can't handle here!
+          */
+         log_error(LOG_LEVEL_FATAL, "sig_handler: exiting on unexpected signal %d", the_signal);
+   }
+   return;
+
+}
+#endif
+
+
+/*********************************************************************
+ *
+ * Function    :  chat
+ *
+ * Description :  Once a connection to the client has been accepted,
+ *                this function is called (via serve()) to handle the
+ *                main business of the communication.  When this
+ *                function returns, the caller must close the client
+ *                socket handle.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns     :  On success, the number of bytes written are returned (zero
+ *                indicates nothing was written).  On error, -1 is returned,
+ *                and errno is set appropriately.  If count is zero and the
+ *                file descriptor refers to a regular file, 0 will be
+ *                returned without causing any other effect.  For a special
+ *                file, the results are not portable.
+ *
+ *********************************************************************/
+static void chat(struct client_state *csp)
+{
+/*
+ * This next lines are a little ugly, but they simplifies the if statements
+ * below.  Basically if TOGGLE, then we want the if to test if the
+ * CSP_FLAG_TOGGLED_ON flag ist set, else we don't.  And if FEATURE_FORCE_LOAD,
+ * then we want the if to test for CSP_FLAG_FORCED , else we don't
+ */
+#ifdef FEATURE_TOGGLE
+#   define IS_TOGGLED_ON_AND (csp->flags & CSP_FLAG_TOGGLED_ON) &&
+#else /* ifndef FEATURE_TOGGLE */
+#   define IS_TOGGLED_ON_AND
+#endif /* ndef FEATURE_TOGGLE */
+#ifdef FEATURE_FORCE_LOAD
+#   define IS_NOT_FORCED_AND !(csp->flags & CSP_FLAG_FORCED) &&
+#else /* ifndef FEATURE_FORCE_LOAD */
+#   define IS_NOT_FORCED_AND
+#endif /* def FEATURE_FORCE_LOAD */
+
+#define IS_ENABLED_AND   IS_TOGGLED_ON_AND IS_NOT_FORCED_AND
+
+   char buf[BUFFER_SIZE];
+   char *hdr;
+   char *p;
+   char *req;
+   fd_set rfds;
+   int n;
+   jb_socket maxfd;
+   int server_body;
+   int ms_iis5_hack = 0;
+   int byte_count = 0;
+   const struct forward_spec * fwd;
+   struct http_request *http;
+   int len; /* for buffer sizes */
+#ifdef FEATURE_KILL_POPUPS
+   int block_popups;         /* bool, 1==will block popups */
+   int block_popups_now = 0; /* bool, 1==currently blocking popups */
+#endif /* def FEATURE_KILL_POPUPS */
+
+   int pcrs_filter;        /* bool, 1==will filter through pcrs */
+   int gif_deanimate;      /* bool, 1==will deanimate gifs */
+
+   /* Function that does the content filtering for the current request */
+   char *(*content_filter)() = NULL;
+
+   /* Skeleton for HTTP response, if we should intercept the request */
+   struct http_response *rsp;
+
+   http = csp->http;
+
+   /*
+    * Read the client's request.  Note that since we're not using select() we
+    * could get blocked here if a client connected, then didn't say anything!
+    */
+
+   for (;;)
+   {
+      len = read_socket(csp->cfd, buf, sizeof(buf));
+
+      if (len <= 0) break;      /* error! */
+      
+      /*
+       * If there is no memory left for buffering the
+       * request, there is nothing we can do but hang up
+       */
+      if (add_to_iob(csp, buf, len))
+      {
+         return;
+      }
+
+      req = get_header(csp);
+
+      if (req == NULL)
+      {
+         break;    /* no HTTP request! */
+      }
+
+      if (*req == '\0')
+      {
+         continue;   /* more to come! */
+      }
+
+#ifdef FEATURE_FORCE_LOAD
+      /* If this request contains the FORCE_PREFIX,
+       * better get rid of it now and set the force flag --oes
+       */
+
+      if (strstr(req, FORCE_PREFIX))
+      {
+         strclean(req, FORCE_PREFIX);
+         log_error(LOG_LEVEL_FORCE, "Enforcing request \"%s\".\n", req);
+         csp->flags |= CSP_FLAG_FORCED;
+      }
+
+#endif /* def FEATURE_FORCE_LOAD */
+
+      parse_http_request(req, http, csp);
+      freez(req);
+      break;
+   }
+
+   if (http->cmd == NULL)
+   {
+      strcpy(buf, CHEADER);
+      write_socket(csp->cfd, buf, strlen(buf));
+
+      log_error(LOG_LEVEL_CLF, "%s - - [%T] \" \" 400 0", csp->ip_addr_str);
+
+      return;
+   }
+
+   /* decide how to route the HTTP request */
+
+   if ((fwd = forward_url(http, csp)) == NULL)
+   {
+      log_error(LOG_LEVEL_FATAL, "gateway spec is NULL!?!?  This can't happen!");
+      /* Never get here - LOG_LEVEL_FATAL causes program exit */
+   }
+
+   /* build the http request to send to the server
+    * we have to do one of the following:
+    *
+    * create = use the original HTTP request to create a new
+    *          HTTP request that has either the path component
+    *          without the http://domainspec (w/path) or the
+    *          full orininal URL (w/url)
+    *          Note that the path and/or the HTTP version may
+    *          have been altered by now.
+    *
+    * connect = Open a socket to the host:port of the server
+    *           and short-circuit server and client socket.
+    *
+    * pass =  Pass the request unchanged if forwarding a CONNECT
+    *         request to a parent proxy. Note that we'll be sending
+    *         the CFAIL message ourselves if connecting to the parent
+    *         fails, but we won't send a CSUCCEED message if it works,
+    *         since that would result in a double message (ours and the
+    *         parent's). After sending the request to the parent, we simply
+    *         tunnel.
+    *
+    * here's the matrix:
+    *                        SSL
+    *                    0        1
+    *                +--------+--------+
+    *                |        |        |
+    *             0  | create | connect|
+    *                | w/path |        |
+    *  Forwarding    +--------+--------+
+    *                |        |        |
+    *             1  | create | pass   |
+    *                | w/url  |        |
+    *                +--------+--------+
+    *
+    */
+
+   /*
+    * Determine the actions for this URL
+    */
+#ifdef FEATURE_TOGGLE
+   if (!(csp->flags & CSP_FLAG_TOGGLED_ON))
+   {
+      /* Most compatible set of actions (i.e. none) */
+      init_current_action(csp->action);
+   }
+   else
+#endif /* ndef FEATURE_TOGGLE */
+   {
+      url_actions(http, csp);
+   }
+
+
+   /*
+    * Check if a CONNECT request is allowable:
+    * In the absence of a +limit-connect action, allow only port 443.
+    * If there is an action, allow whatever matches the specificaton.
+    */
+   if(http->ssl)
+   {
+      if(  ( !(csp->action->flags & ACTION_LIMIT_CONNECT) && csp->http->port != 443)
+           || (csp->action->flags & ACTION_LIMIT_CONNECT
+              && !match_portlist(csp->action->string[ACTION_STRING_LIMIT_CONNECT], csp->http->port)) )
+      {
+         strcpy(buf, CFORBIDDEN);
+         write_socket(csp->cfd, buf, strlen(buf));
+
+         log_error(LOG_LEVEL_CONNECT, "Denying suspicious CONNECT request from %s", csp->ip_addr_str);
+         log_error(LOG_LEVEL_CLF, "%s - - [%T] \" \" 403 0", csp->ip_addr_str);
+
+         return;
+      }
+   }
+
+
+   /*
+    * Downgrade http version from 1.1 to 1.0 if +downgrade
+    * action applies
+    */
+   if ( (http->ssl == 0)
+     && (!strcmpic(http->ver, "HTTP/1.1"))
+     && (csp->action->flags & ACTION_DOWNGRADE))
+   {
+      freez(http->ver);
+      http->ver = strdup("HTTP/1.0");
+
+      if (http->ver == NULL)
+      {
+         log_error(LOG_LEVEL_FATAL, "Out of memory downgrading HTTP version");
+      }
+   }
+
+   /* 
+    * Save a copy of the original request for logging
+    */
+   http->ocmd = strdup(http->cmd);
+
+   if (http->ocmd == NULL)
+   {
+      log_error(LOG_LEVEL_FATAL, "Out of memory copying HTTP request line");
+   }
+
+   /*
+    * (Re)build the HTTP request for non-SSL requests.
+    * If forwarding, use the whole URL, else, use only the path.
+    */
+   if (http->ssl == 0)
+   {
+      freez(http->cmd);
+
+      http->cmd = strdup(http->gpc);
+      string_append(&http->cmd, " ");
+
+      if (fwd->forward_host)
+      {
+         string_append(&http->cmd, http->url);
+      }
+      else
+      {
+         string_append(&http->cmd, http->path);
+      }
+
+      string_append(&http->cmd, " ");
+      string_append(&http->cmd, http->ver);
+
+      if (http->cmd == NULL)
+      {
+         log_error(LOG_LEVEL_FATAL, "Out of memory rewiting SSL command");
+      }
+   }
+   enlist(csp->headers, http->cmd);
+
+
+   /*
+    * If the user has not supplied any wafers, and the user has not
+    * told us to suppress the vanilla wafer, then send the vanilla wafer.
+    */
+   if (list_is_empty(csp->action->multi[ACTION_MULTI_WAFER])
+       && ((csp->action->flags & ACTION_VANILLA_WAFER) != 0))
+   {
+      enlist(csp->action->multi[ACTION_MULTI_WAFER], VANILLA_WAFER);
+   }
+
+
+#ifdef FEATURE_KILL_POPUPS
+   block_popups               = ((csp->action->flags & ACTION_NO_POPUPS) != 0);
+#endif /* def FEATURE_KILL_POPUPS */
+
+   pcrs_filter                = (csp->rlist != NULL) &&  /* There are expressions to be used */
+                                (!list_is_empty(csp->action->multi[ACTION_MULTI_FILTER]));
+
+   gif_deanimate              = ((csp->action->flags & ACTION_DEANIMATE) != 0);
+
+   /* grab the rest of the client's headers */
+
+   for (;;)
+   {
+      if ( ( ( p = get_header(csp) ) != NULL) && ( *p == '\0' ) )
+      {
+         len = read_socket(csp->cfd, buf, sizeof(buf));
+         if (len <= 0)
+         {
+            log_error(LOG_LEVEL_ERROR, "read from client failed: %E");
+            return;
+         }
+         
+         /*
+          * If there is no memory left for buffering the
+          * request, there is nothing we can do but hang up
+          */
+         if (add_to_iob(csp, buf, len))
+         {
+            return;
+         }
+         continue;
+      }
+
+      if (p == NULL) break;
+
+      enlist(csp->headers, p);
+      freez(p);
+   }
+   /*
+    * We have a request. Now, check to see if we need to
+    * intercept it, i.e. If ..
+    */
+
+   if (
+       /* a CGI call was detected and answered */
+       (NULL != (rsp = dispatch_cgi(csp)))
+
+       /* or we are enabled and... */
+       || (IS_ENABLED_AND (
+
+            /* ..the request was blocked */
+          ( NULL != (rsp = block_url(csp)))
+
+          /* ..or untrusted */
+#ifdef FEATURE_TRUST
+          || ( NULL != (rsp = trust_url(csp)))
+#endif /* def FEATURE_TRUST */
+
+          /* ..or a fast redirect kicked in */
+#ifdef FEATURE_FAST_REDIRECTS
+          || (((csp->action->flags & ACTION_FAST_REDIRECTS) != 0) &&
+                (NULL != (rsp = redirect_url(csp))))
+#endif /* def FEATURE_FAST_REDIRECTS */
+          ))
+      )
+   {
+      /* Write the answer to the client */
+      if (write_socket(csp->cfd, rsp->head, rsp->head_length)
+       || write_socket(csp->cfd, rsp->body, rsp->content_length))
+      {
+         log_error(LOG_LEVEL_ERROR, "write to: %s failed: %E", http->host);
+      }
+
+#ifdef FEATURE_STATISTICS
+      /* Count as a rejected request */
+      csp->flags |= CSP_FLAG_REJECTED;
+#endif /* def FEATURE_STATISTICS */
+
+      /* Log (FIXME: All intercept reasons apprear as "crunch" with Status 200) */
+      log_error(LOG_LEVEL_GPC, "%s%s crunch!", http->hostport, http->path);
+      log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s\" 200 3", csp->ip_addr_str, http->ocmd);
+
+      /* Clean up and return */
+      free_http_response(rsp);
+      return;
+   }
+
+   log_error(LOG_LEVEL_GPC, "%s%s", http->hostport, http->path);
+
+   if (fwd->forward_host)
+   {
+      log_error(LOG_LEVEL_CONNECT, "via %s:%d to: %s",
+               fwd->forward_host, fwd->forward_port, http->hostport);
+   }
+   else
+   {
+      log_error(LOG_LEVEL_CONNECT, "to %s", http->hostport);
+   }
+
+   /* here we connect to the server, gateway, or the forwarder */
+
+   csp->sfd = forwarded_connect(fwd, http, csp);
+
+   if (csp->sfd == JB_INVALID_SOCKET)
+   {
+      log_error(LOG_LEVEL_CONNECT, "connect to: %s failed: %E",
+                http->hostport);
+
+      if (errno == EINVAL)
+      {
+         rsp = error_response(csp, "no-such-domain", errno);
+
+         log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s\" 404 0",
+                   csp->ip_addr_str, http->ocmd);
+      }
+      else
+      {
+         rsp = error_response(csp, "connect-failed", errno);
+
+         log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s\" 503 0",
+                   csp->ip_addr_str, http->ocmd);
+      }
+
+
+      /* Write the answer to the client */
+      if(rsp)
+      {
+         if (write_socket(csp->cfd, rsp->head, rsp->head_length)
+          || write_socket(csp->cfd, rsp->body, rsp->content_length))
+         {
+            log_error(LOG_LEVEL_ERROR, "write to: %s failed: %E", http->host);
+         }
+      }
+
+      free_http_response(rsp);
+      return;
+   }
+
+   log_error(LOG_LEVEL_CONNECT, "OK");
+
+   hdr = sed(client_patterns, add_client_headers, csp);
+   if (hdr == NULL)
+   {
+      /* FIXME Should handle error properly */
+      log_error(LOG_LEVEL_FATAL, "Out of memory parsing client header");
+   }
+
+   list_remove_all(csp->headers);
+
+   if (fwd->forward_host || (http->ssl == 0))
+   {
+      /* write the client's (modified) header to the server
+       * (along with anything else that may be in the buffer)
+       */
+
+      if (write_socket(csp->sfd, hdr, strlen(hdr))
+       || (flush_socket(csp->sfd, csp) <  0))
+      {
+         log_error(LOG_LEVEL_CONNECT, "write header to: %s failed: %E",
+                    http->hostport);
+
+         log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s\" 503 0",
+                   csp->ip_addr_str, http->ocmd);
+
+         rsp = error_response(csp, "connect-failed", errno);
+
+         if(rsp)
+         {
+            if (write_socket(csp->cfd, rsp->head, rsp->head_length)
+             || write_socket(csp->cfd, rsp->body, rsp->content_length))
+            {
+               log_error(LOG_LEVEL_ERROR, "write to: %s failed: %E", http->host);
+            }
+         }
+
+         free_http_response(rsp);
+         freez(hdr);
+         return;
+      }
+   }
+   else
+   {
+      /*
+       * We're running an SSL tunnel and we're not forwarding,
+       * so just send the "connect succeeded" message to the
+       * client, flush the rest, and get out of the way.
+       */
+      log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s\" 200 2\n",
+                csp->ip_addr_str, http->ocmd);
+
+      if (write_socket(csp->cfd, CSUCCEED, sizeof(CSUCCEED)-1))
+      {
+         freez(hdr);
+         return;
+      }
+      IOB_RESET(csp);
+   }
+
+   /* we're finished with the client's header */
+   freez(hdr);
+
+   maxfd = ( csp->cfd > csp->sfd ) ? csp->cfd : csp->sfd;
+
+   /* pass data between the client and server
+    * until one or the other shuts down the connection.
+    */
+
+   server_body = 0;
+
+   for (;;)
+   {
+#ifdef __OS2__
+      /*
+       * FD_ZERO here seems to point to an errant macro which crashes.
+       * So do this by hand for now...
+       */
+      memset(&rfds,0x00,sizeof(fd_set));
+#else
+      FD_ZERO(&rfds);
+#endif
+      FD_SET(csp->cfd, &rfds);
+      FD_SET(csp->sfd, &rfds);
+
+      n = select((int)maxfd+1, &rfds, NULL, NULL, NULL);
+
+      if (n < 0)
+      {
+         log_error(LOG_LEVEL_ERROR, "select() failed!: %E");
+         return;
+      }
+
+      /* this is the body of the browser's request
+       * just read it and write it.
+       */
+
+      if (FD_ISSET(csp->cfd, &rfds))
+      {
+         len = read_socket(csp->cfd, buf, sizeof(buf));
+
+         if (len <= 0)
+         {
+            break; /* "game over, man" */
+         }
+
+         if (write_socket(csp->sfd, buf, (size_t)len))
+         {
+            log_error(LOG_LEVEL_ERROR, "write to: %s failed: %E", http->host);
+            return;
+         }
+         continue;
+      }
+
+      /*
+       * The server wants to talk.  It could be the header or the body.
+       * If `hdr' is null, then it's the header otherwise it's the body.
+       * FIXME: Does `hdr' really mean `host'? No.
+       */
+
+
+      if (FD_ISSET(csp->sfd, &rfds))
+      {
+         fflush( 0 );
+         len = read_socket(csp->sfd, buf, sizeof(buf) - 1);
+
+         if (len < 0)
+         {
+            log_error(LOG_LEVEL_ERROR, "read from: %s failed: %E", http->host);
+
+            log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s\" 503 0",
+                      csp->ip_addr_str, http->ocmd);
+
+            rsp = error_response(csp, "connect-failed", errno);
+
+            if(rsp)
+            {
+               if (write_socket(csp->cfd, rsp->head, rsp->head_length)
+                || write_socket(csp->cfd, rsp->body, rsp->content_length))
+               {
+                  log_error(LOG_LEVEL_ERROR, "write to: %s failed: %E", http->host);
+               }
+            }
+
+            free_http_response(rsp);
+            return;
+         }
+
+         /* Add a trailing zero.  This lets filter_popups
+          * use string operations.
+          */
+         buf[len] = '\0';
+
+#ifdef FEATURE_KILL_POPUPS
+         /* Filter the popups on this read. */
+         if (block_popups_now)
+         {
+            filter_popups(buf, csp);
+         }
+#endif /* def FEATURE_KILL_POPUPS */
+
+         /* Normally, this would indicate that we've read
+          * as much as the server has sent us and we can
+          * close the client connection.  However, Microsoft
+          * in its wisdom has released IIS/5 with a bug that
+          * prevents it from sending the trailing \r\n in
+          * a 302 redirect header (and possibly other headers).
+          * To work around this if we've haven't parsed
+          * a full header we'll append a trailing \r\n
+          * and see if this now generates a valid one.
+          *
+          * This hack shouldn't have any impacts.  If we've
+          * already transmitted the header or if this is a
+          * SSL connection, then we won't bother with this
+          * hack.  So we only work on partially received
+          * headers.  If we append a \r\n and this still
+          * doesn't generate a valid header, then we won't
+          * transmit anything to the client.
+          */
+         if (len == 0)
+         {
+
+            if (server_body || http->ssl)
+            {
+               /*
+                * If we have been buffering up the document,
+                * now is the time to apply content modification
+                * and send the result to the client.
+                */
+               if (content_filter)
+               {
+                  /*
+                   * If the content filter fails, use the original
+                   * buffer and length.
+                   * (see p != NULL ? p : csp->iob->cur below)
+                   */
+                  if (NULL == (p = (*content_filter)(csp)))
+                  {
+                     csp->content_length = csp->iob->eod - csp->iob->cur;
+                  }
+
+                  hdr = sed(server_patterns, add_server_headers, csp);
+                  if (hdr == NULL)
+                  {
+                     /* FIXME Should handle error properly */
+                     log_error(LOG_LEVEL_FATAL, "Out of memory parsing server header");
+                  }
+
+                  if (write_socket(csp->cfd, hdr, strlen(hdr))
+                   || write_socket(csp->cfd, p != NULL ? p : csp->iob->cur, csp->content_length))
+                  {
+                     log_error(LOG_LEVEL_ERROR, "write modified content to client failed: %E");
+                     return;
+                  }
+
+                  freez(hdr);
+                  if (NULL != p) {
+                     freez(p);
+                  }
+               }
+
+               break; /* "game over, man" */
+            }
+
+            /*
+             * This is NOT the body, so
+             * Let's pretend the server just sent us a blank line.
+             */
+            len = sprintf(buf, "\r\n");
+
+            /*
+             * Now, let the normal header parsing algorithm below do its
+             * job.  If it fails, we'll exit instead of continuing.
+             */
+
+            ms_iis5_hack = 1;
+         }
+
+         /*
+          * If this is an SSL connection or we're in the body
+          * of the server document, just write it to the client,
+          * unless we need to buffer the body for later content-filtering
+          */
+
+         if (server_body || http->ssl)
+         {
+            if (content_filter)
+            {
+               /*
+                * If there is no memory left for buffering the content, or the buffer limit
+                * has been reached, switch to non-filtering mode, i.e. make & write the
+                * header, flush the iob and buf, and get out of the way.
+                */
+               if (add_to_iob(csp, buf, len))
+               {
+                  size_t hdrlen;
+                  int flushed;
+
+                  log_error(LOG_LEVEL_ERROR, "Flushing header and buffers. Stepping back from filtering.");
+
+                  hdr = sed(server_patterns, add_server_headers, csp);
+                  if (hdr == NULL)
+                  {
+                     /* 
+                      * Memory is too tight to even generate the header.
+                      * Send our static "Out-of-memory" page.
+                      */
+                     log_error(LOG_LEVEL_ERROR, "Out of memory while trying to flush.");
+                     rsp = cgi_error_memory();
+
+                     if (write_socket(csp->cfd, rsp->head, rsp->head_length)
+                         || write_socket(csp->cfd, rsp->body, rsp->content_length))
+                     {
+                        log_error(LOG_LEVEL_ERROR, "write to: %s failed: %E", http->host);
+                     }
+                     return;
+                  }
+
+                  hdrlen = strlen(hdr);
+
+                  if (write_socket(csp->cfd, hdr, hdrlen)
+                   || ((flushed = flush_socket(csp->cfd, csp)) < 0)
+                   || (write_socket(csp->cfd, buf, len)))
+                  {
+                     log_error(LOG_LEVEL_CONNECT, "Flush header and buffers to client failed: %E");
+
+                     freez(hdr);
+                     return;
+                  }
+
+                  byte_count += hdrlen + flushed + len;
+                  freez(hdr);
+                  content_filter = NULL;
+                  server_body = 1;
+
+               }
+            }
+            else
+            {
+               if (write_socket(csp->cfd, buf, (size_t)len))
+               {
+                  log_error(LOG_LEVEL_ERROR, "write to client failed: %E");
+                  return;
+               }
+            }
+            byte_count += len;
+            continue;
+         }
+         else
+         {
+            /* we're still looking for the end of the
+             * server's header ... (does that make header
+             * parsing an "out of body experience" ?
+             */
+
+            /* 
+             * buffer up the data we just read.  If that fails, 
+             * there's little we can do but send our static
+             * out-of-memory page.
+             */
+            if (add_to_iob(csp, buf, len))
+            {
+               log_error(LOG_LEVEL_ERROR, "Out of memory while looking for end of server headers.");
+               rsp = cgi_error_memory();
+               
+               if (write_socket(csp->cfd, rsp->head, rsp->head_length)
+                   || write_socket(csp->cfd, rsp->body, rsp->content_length))
+               {
+                  log_error(LOG_LEVEL_ERROR, "write to: %s failed: %E", http->host);
+               }
+               return;
+            }
+
+            /* get header lines from the iob */
+
+            while ((p = get_header(csp)) != NULL)
+            {
+               if (*p == '\0')
+               {
+                  /* see following note */
+                  break;
+               }
+               enlist(csp->headers, p);
+               freez(p);
+            }
+
+            /* NOTE: there are no "empty" headers so
+             * if the pointer `p' is not NULL we must
+             * assume that we reached the end of the
+             * buffer before we hit the end of the header.
+             */
+
+            if (p)
+            {
+               if (ms_iis5_hack)
+               {
+                  /* Well, we tried our MS IIS/5
+                   * hack and it didn't work.
+                   * The header is incomplete
+                   * and there isn't anything
+                   * we can do about it.
+                   */
+                  break;
+               }
+               else
+               {
+                  /* Since we have to wait for
+                   * more from the server before
+                   * we can parse the headers
+                   * we just continue here.
+                   */
+                  continue;
+               }
+            }
+
+            /* we have now received the entire header.
+             * filter it and send the result to the client
+             */
+
+            hdr = sed(server_patterns, add_server_headers, csp);
+            if (hdr == NULL)
+            {
+               /* FIXME Should handle error properly */
+               log_error(LOG_LEVEL_FATAL, "Out of memory parsing server header");
+            }
+
+#ifdef FEATURE_KILL_POPUPS
+            /* Start blocking popups if appropriate. */
+
+            if ((csp->content_type & CT_TEXT) &&  /* It's a text / * MIME-Type */
+                !http->ssl    &&                  /* We talk plaintext */
+                block_popups)                     /* Policy allows */
+            {
+               block_popups_now = 1;
+               /*
+                * Filter the part of the body that came in the same read
+                * as the last headers:
+                */
+               filter_popups(csp->iob->cur, csp);
+            }
+
+#endif /* def FEATURE_KILL_POPUPS */
+
+            /* Buffer and pcrs filter this if appropriate. */
+
+            if ((csp->content_type & CT_TEXT) &&  /* It's a text / * MIME-Type */
+                !http->ssl    &&                  /* We talk plaintext */
+                pcrs_filter)                      /* Policy allows */
+            {
+               content_filter = pcrs_filter_response;
+            }
+
+            /* Buffer and gif_deanimate this if appropriate. */
+
+            if ((csp->content_type & CT_GIF)  &&  /* It's a image/gif MIME-Type */
+                !http->ssl    &&                  /* We talk plaintext */
+                gif_deanimate)                    /* Policy allows */
+            {
+               content_filter = gif_deanimate_response;
+            }
+
+            /*
+             * Only write if we're not buffering for content modification
+             */
+            if (!content_filter)
+            {
+               /* write the server's (modified) header to
+                * the client (along with anything else that
+                * may be in the buffer)
+                */
+
+               if (write_socket(csp->cfd, hdr, strlen(hdr))
+                || ((len = flush_socket(csp->cfd, csp)) < 0))
+               {
+                  log_error(LOG_LEVEL_CONNECT, "write header to client failed: %E");
+
+                  /* the write failed, so don't bother
+                   * mentioning it to the client...
+                   * it probably can't hear us anyway.
+                   */
+                  freez(hdr);
+                  return;
+               }
+
+               byte_count += len;
+            }
+
+            /* we're finished with the server's header */
+
+            freez(hdr);
+            server_body = 1;
+
+            /* If this was a MS IIS/5 hack then it means
+             * the server has already closed the
+             * connection.  Nothing more to read.  Time
+             * to bail.
+             */
+            if (ms_iis5_hack)
+            {
+               break;
+            }
+         }
+         continue;
+      }
+
+      return; /* huh? we should never get here */
+   }
+
+   log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s\" 200 %d",
+             csp->ip_addr_str, http->ocmd, byte_count);
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  serve
+ *
+ * Description :  This is little more than chat.  We only "serve" to
+ *                to close any socket that chat may have opened.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns     :  N/A
+ *
+ *********************************************************************/
+#ifdef AMIGA
+void serve(struct client_state *csp)
+#else /* ifndef AMIGA */
+static void serve(struct client_state *csp)
+#endif /* def AMIGA */
+{
+   chat(csp);
+   close_socket(csp->cfd);
+
+   if (csp->sfd != JB_INVALID_SOCKET)
+   {
+      close_socket(csp->sfd);
+   }
+
+   csp->flags &= ~CSP_FLAG_ACTIVE;
+
+}
+
+
+#ifdef __BEOS__
+/*********************************************************************
+ *
+ * Function    :  server_thread
+ *
+ * Description :  We only exist to call `serve' in a threaded environment.
+ *
+ * Parameters  :
+ *          1  :  data = Current client state (buffers, headers, etc...)
+ *
+ * Returns     :  Always 0.
+ *
+ *********************************************************************/
+static int32 server_thread(void *data)
+{
+   serve((struct client_state *) data);
+   return 0;
+
+}
+#endif
+
+
+/*********************************************************************
+ *
+ * Function    :  usage
+ *
+ * Description :  Print usage info & exit.
+ *
+ * Parameters  :  Pointer to argv[0] for identifying ourselves
+ *
+ * Returns     :  No. ,-)
+ *
+ *********************************************************************/
+void usage(const char *myname)
+{
+   printf("Privoxy version " VERSION " (" HOME_PAGE_URL ")\n"
+           "Usage: %s [--help] [--version] [--no-daemon] [--pidfile pidfile] [--user user[.group]] [configfile]\n"
+           "Aborting.\n", myname);
+   exit(2);
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  main
+ *
+ * Description :  Load the config file and start the listen loop.
+ *                This function is a lot more *sane* with the `load_config'
+ *                and `listen_loop' functions; although it stills does
+ *                a *little* too much for my taste.
+ *
+ * Parameters  :
+ *          1  :  argc = Number of parameters (including $0).
+ *          2  :  argv = Array of (char *)'s to the parameters.
+ *
+ * Returns     :  1 if : can't open config file, unrecognized directive,
+ *                stats requested in multi-thread mode, can't open the
+ *                log file, can't open the jar file, listen port is invalid,
+ *                any load fails, and can't bind port.
+ *
+ *                Else main never returns, the process must be signaled
+ *                to terminate execution.  Or, on Windows, use the
+ *                "File", "Exit" menu option.
+ *
+ *********************************************************************/
+#ifdef __MINGW32__
+int real_main(int argc, const char *argv[])
+#else
+int main(int argc, const char *argv[])
+#endif
+{
+   int argc_pos = 0;
+#ifdef unix
+   struct passwd *pw = NULL;
+   struct group *grp = NULL;
+   char *p;
+#endif
+
+   Argc = argc;
+   Argv = argv;
+
+   configfile =
+#if !defined(_WIN32)
+   "config"
+#else
+   "config.txt"
+#endif
+      ;
+
+   /*
+    * Parse the command line arguments
+    */
+   while (++argc_pos < argc)
+   {
+#if !defined(_WIN32) || defined(_WIN_CONSOLE)
+
+      if (strcmp(argv[argc_pos], "--help") == 0)
+      {
+         usage(argv[0]);
+      }
+
+      else if(strcmp(argv[argc_pos], "--version") == 0)
+      {
+         printf("Privoxy version " VERSION " (" HOME_PAGE_URL ")\n");
+         exit(0);
+      }
+
+      else if (strcmp(argv[argc_pos], "--no-daemon" ) == 0)
+      {
+         no_daemon = 1;
+      }
+#if defined(unix)
+      else if (strcmp(argv[argc_pos], "--pidfile" ) == 0)
+      {
+         if (++argc_pos == argc) usage(argv[0]);
+         pidfile = strdup(argv[argc_pos]);
+      }
+
+      else if (strcmp(argv[argc_pos], "--user" ) == 0)
+      {
+         if (++argc_pos == argc) usage(argv[argc_pos]);
+
+         if ((NULL != (p = strchr(argv[argc_pos], '.'))) && *(p + 1) != '0')
+         {
+            *p++ = '\0';
+            if (NULL == (grp = getgrnam(p)))
+            {
+               log_error(LOG_LEVEL_FATAL, "Group %s not found.", p);
+            }
+         }
+
+         if (NULL == (pw = getpwnam(argv[argc_pos])))
+         {
+            log_error(LOG_LEVEL_FATAL, "User %s not found.", argv[argc_pos]);
+         }
+
+         if (p != NULL) *--p = '\0';
+      }
+#endif /* defined(unix) */
+      else
+#endif /* defined(_WIN32) && !defined(_WIN_CONSOLE) */
+      {
+         configfile = argv[argc_pos];
+      }
+
+   } /* -END- while (more arguments) */
+
+#if defined(unix)
+   if ( *configfile != '/' )
+   {
+      char *abs_file;
+
+      /* make config-filename absolute here */
+      if ( !(basedir = getcwd( NULL, 1024 )))
+      {
+         perror("get working dir failed");
+         exit( 1 );
+      }
+
+      if ( !(abs_file = malloc( strlen( basedir ) + strlen( configfile ) + 5 )))
+      {
+         perror("malloc failed");
+         exit( 1 );
+      }
+      strcpy( abs_file, basedir );
+      strcat( abs_file, "/" );
+      strcat( abs_file, configfile );
+      configfile = abs_file;
+   }
+#endif /* defined unix */
+
+
+   files->next = NULL;
+
+#ifdef AMIGA
+   InitAmiga();
+#elif defined(_WIN32)
+   InitWin32();
+#endif
+
+   /*
+    * Unix signal handling
+    *
+    * Catch the abort, interrupt and terminate signals for a graceful exit
+    * Catch the hangup signal so the errlog can be reopened.
+    * Ignore the broken pipe and child signals
+    *  FIXME: Isn't ignoring the default for SIGCHLD anyway and why ignore SIGPIPE? 
+    */
+#if !defined(_WIN32) && !defined(__OS2__) && !defined(AMIGA)
+{
+   int idx;
+   const int catched_signals[] = { SIGABRT, SIGTERM, SIGINT, SIGHUP, 0 };
+   const int ignored_signals[] = { SIGPIPE, SIGCHLD, 0 };
+
+   for (idx = 0; catched_signals[idx] != 0; idx++)
+   {
+      if (signal(catched_signals[idx], sig_handler) == SIG_ERR)
+      {
+         log_error(LOG_LEVEL_FATAL, "Can't set signal-handler for signal %d: %E", catched_signals[idx]);
+      }
+   }
+
+   for (idx = 0; ignored_signals[idx] != 0; idx++)
+   {
+      if (signal(ignored_signals[idx], SIG_IGN) == SIG_ERR)
+      {
+         log_error(LOG_LEVEL_FATAL, "Can't set ignore-handler for signal %d: %E", ignored_signals[idx]);
+      }
+   }
+
+}
+#else /* ifdef _WIN32 */
+# ifdef _WIN_CONSOLE
+   /*
+    * We *are* in a windows console app.
+    * Print a verbose messages about FAQ's and such
+    */
+   printf(win32_blurb);
+# endif /* def _WIN_CONSOLE */
+#endif /* def _WIN32 */
+
+
+   /* Initialize the CGI subsystem */
+   cgi_init_error_messages();
+
+   /*
+    * If runnig on unix and without the --nodaemon
+    * option, become a daemon. I.e. fork, detach
+    * from tty and get process group leadership
+    */
+#if defined(unix)
+{
+   pid_t pid = 0;
+#if 0
+   int   fd;
+#endif
+
+   if (!no_daemon)
+   {
+      pid  = fork();
+
+      if ( pid < 0 ) /* error */
+      {
+         perror("fork");
+         exit( 3 );
+      }
+      else if ( pid != 0 ) /* parent */
+      {
+         int status;
+         pid_t wpid;
+         /*
+          * must check for errors
+          * child died due to missing files aso
+          */
+         sleep( 1 );
+         wpid = waitpid( pid, &status, WNOHANG );
+         if ( wpid != 0 )
+         {
+            exit( 1 );
+         }
+         exit( 0 );
+      }
+      /* child */
+#if 1
+      /* Should be more portable, but not as well tested */
+      setsid();
+#else /* !1 */
+#ifdef __FreeBSD__
+      setpgrp(0,0);
+#else /* ndef __FreeBSD__ */
+      setpgrp();
+#endif /* ndef __FreeBSD__ */
+      fd = open("/dev/tty", O_RDONLY);
+      if ( fd )
+      {
+         /* no error check here */
+         ioctl( fd, TIOCNOTTY,0 );
+         close ( fd );
+      }
+#endif /* 1 */
+      /* FIXME: should close stderr (fd 2) here too, but the test
+       * for existence
+       * and load config file is done in listen_loop() and puts
+       * some messages on stderr there.
+       */
+
+      close( 0 );
+      close( 1 );
+      chdir("/");
+
+   } /* -END- if (!no_daemon) */
+
+   /*
+    * As soon as we have written the PID file, we can switch
+    * to the user and group ID indicated by the --user option
+    */
+   write_pid_file();
+   
+   if (NULL != pw)
+   {
+      if (((NULL != grp) && setgid(grp->gr_gid)) || (setgid(pw->pw_gid)))
+      {
+         log_error(LOG_LEVEL_FATAL, "Cannot setgid(): Insufficient permissions.");
+      }
+      if (setuid(pw->pw_uid))
+      {
+         log_error(LOG_LEVEL_FATAL, "Cannot setuid(): Insufficient permissions.");
+      }
+   }
+}
+#endif /* defined unix */
+
+   listen_loop();
+
+   /* NOTREACHED */
+   return(-1);
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  bind_port_helper
+ *
+ * Description :  Bind the listen port.  Handles logging, and aborts
+ *                on failure.
+ *
+ * Parameters  :
+ *          1  :  config = Privoxy configuration.  Specifies port
+ *                         to bind to.
+ *
+ * Returns     :  Port that was opened.
+ *
+ *********************************************************************/
+static jb_socket bind_port_helper(struct configuration_spec * config)
+{
+   int result;
+   jb_socket bfd;
+
+   if ( (config->haddr != NULL)
+     && (config->haddr[0] == '1')
+     && (config->haddr[1] == '2')
+     && (config->haddr[2] == '7')
+     && (config->haddr[3] == '.') )
+   {
+      log_error(LOG_LEVEL_INFO, "Listening on port %d for local connections only",
+                config->hport);
+   }
+   else if (config->haddr == NULL)
+   {
+      log_error(LOG_LEVEL_INFO, "Listening on port %d on all IP addresses",
+                config->hport);
+   }
+   else
+   {
+      log_error(LOG_LEVEL_INFO, "Listening on port %d on IP address %s",
+                config->hport, config->haddr);
+   }
+
+   result = bind_port(config->haddr, config->hport, &bfd);
+
+   if (result < 0)
+   {
+      switch(result)
+      {
+         case -3 :
+            log_error(LOG_LEVEL_FATAL, "can't bind to %s:%d: "
+               "There may be another Privoxy or some other "
+               "proxy running on port %d",
+               (NULL != config->haddr) ? config->haddr : "INADDR_ANY",
+                      config->hport, config->hport);
+
+         case -2 :
+            log_error(LOG_LEVEL_FATAL, "can't bind to %s:%d: " 
+               "The hostname is not resolvable",
+               (NULL != config->haddr) ? config->haddr : "INADDR_ANY", config->hport);
+
+         default :
+            log_error(LOG_LEVEL_FATAL, "can't bind to %s:%d: because %E",
+               (NULL != config->haddr) ? config->haddr : "INADDR_ANY", config->hport);
+      }
+
+      /* shouldn't get here */
+      return JB_INVALID_SOCKET;
+   }
+
+   config->need_bind = 0;
+
+   return bfd;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  listen_loop
+ *
+ * Description :  bind the listen port and enter a "FOREVER" listening loop.
+ *
+ * Parameters  :  N/A
+ *
+ * Returns     :  Never.
+ *
+ *********************************************************************/
+static void listen_loop(void)
+{
+   struct client_state *csp = NULL;
+   jb_socket bfd;
+   struct configuration_spec * config;
+
+   config = load_config();
+
+   bfd = bind_port_helper(config);
+
+#ifdef FEATURE_GRACEFUL_TERMINATION
+   while (!g_terminate)
+#else
+   for (;;)
+#endif
+   {
+#if !defined(FEATURE_PTHREAD) && !defined(_WIN32) && !defined(__BEOS__) && !defined(AMIGA) && !defined(__OS2__)
+      while (waitpid(-1, NULL, WNOHANG) > 0)
+      {
+         /* zombie children */
+      }
+#endif /* !defined(FEATURE_PTHREAD) && !defined(_WIN32) && !defined(__BEOS__) && !defined(AMIGA) */
+
+      /*
+       * Free data that was used by died threads
+       */
+      sweep();
+
+#if defined(unix)
+      /*
+       * Re-open the errlog after HUP signal
+       */
+      if (received_hup_signal)
+      {
+         init_error_log(Argv[0], config->logfile, config->debug);
+         received_hup_signal = 0;
+      }
+#endif
+
+#ifdef __OS2__
+#ifdef FEATURE_COOKIE_JAR
+      /*
+       * Need a workaround here: we have to fclose() the jarfile, or we die because it's
+       * already open.  I think unload_configfile() is not being run, which should do
+       * this work.  Until that can get resolved, we'll use this workaround.
+       */
+       if (csp)
+         if(csp->config)
+           if (csp->config->jar)
+           {
+             fclose(csp->config->jar);
+             csp->config->jar = NULL;
+           }
+#endif /* FEATURE_COOKIE_JAR */
+#endif /* __OS2__ */
+
+      if ( NULL == (csp = (struct client_state *) zalloc(sizeof(*csp))) )
+      {
+         log_error(LOG_LEVEL_FATAL, "malloc(%d) for csp failed: %E", sizeof(*csp));
+         continue;
+      }
+
+      csp->flags |= CSP_FLAG_ACTIVE;
+      csp->sfd    = JB_INVALID_SOCKET;
+
+      csp->config = config = load_config();
+
+      if ( config->need_bind )
+      {
+         /*
+          * Since we were listening to the "old port", we will not see
+          * a "listen" param change until the next IJB request.  So, at
+          * least 1 more request must be made for us to find the new
+          * setting.  I am simply closing the old socket and binding the
+          * new one.
+          *
+          * Which-ever is correct, we will serve 1 more page via the
+          * old settings.  This should probably be a "show-proxy-args"
+          * request.  This should not be a so common of an operation
+          * that this will hurt people's feelings.
+          */
+
+         close_socket(bfd);
+
+         bfd = bind_port_helper(config);
+      }
+
+      log_error(LOG_LEVEL_CONNECT, "accept connection ... ");
+
+      if (!accept_connection(csp, bfd))
+      {
+         log_error(LOG_LEVEL_CONNECT, "accept failed: %E");
+
+#ifdef AMIGA
+         if(!childs)
+         {
+            exit(1);
+         }
+#endif
+         freez(csp);
+         continue;
+      }
+      else
+      {
+         log_error(LOG_LEVEL_CONNECT, "OK");
+      }
+
+#ifdef FEATURE_TOGGLE
+      if (g_bToggleIJB)
+      {
+         csp->flags |= CSP_FLAG_TOGGLED_ON;
+      }
+#endif /* def FEATURE_TOGGLE */
+
+      if (run_loader(csp))
+      {
+         log_error(LOG_LEVEL_FATAL, "a loader failed - must exit");
+         /* Never get here - LOG_LEVEL_FATAL causes program exit */
+      }
+
+#ifdef FEATURE_ACL
+      if (block_acl(NULL,csp))
+      {
+         log_error(LOG_LEVEL_CONNECT, "Connection dropped due to ACL");
+         close_socket(csp->cfd);
+         freez(csp);
+         continue;
+      }
+#endif /* def FEATURE_ACL */
+
+      /* add it to the list of clients */
+      csp->next = clients->next;
+      clients->next = csp;
+
+      if (config->multi_threaded)
+      {
+         int child_id;
+
+/* this is a switch () statment in the C preprocessor - ugh */
+#undef SELECTED_ONE_OPTION
+
+/* Use Pthreads in preference to native code */
+#if defined(FEATURE_PTHREAD) && !defined(SELECTED_ONE_OPTION)
+#define SELECTED_ONE_OPTION
+         {
+            pthread_t the_thread;
+            pthread_attr_t attrs;
+
+            pthread_attr_init(&attrs);
+            pthread_attr_setdetachstate(&attrs, PTHREAD_CREATE_DETACHED);
+            child_id = (pthread_create(&the_thread, &attrs,
+               (void*)serve, csp) ? -1 : 0);
+            pthread_attr_destroy(&attrs);
+         }
+#endif
+
+#if defined(_WIN32) && !defined(_CYGWIN) && !defined(SELECTED_ONE_OPTION)
+#define SELECTED_ONE_OPTION
+         child_id = _beginthread(
+            (void (*)(void *))serve,
+            64 * 1024,
+            csp);
+#endif
+
+#if defined(__OS2__) && !defined(SELECTED_ONE_OPTION)
+#define SELECTED_ONE_OPTION
+         child_id = _beginthread(
+            (void(* _Optlink)(void*))serve,
+            NULL,
+            64 * 1024,
+            csp);
+#endif
+
+#if defined(__BEOS__) && !defined(SELECTED_ONE_OPTION)
+#define SELECTED_ONE_OPTION
+         {
+            thread_id tid = spawn_thread
+               (server_thread, "server", B_NORMAL_PRIORITY, csp);
+
+            if ((tid >= 0) && (resume_thread(tid) == B_OK))
+            {
+               child_id = (int) tid;
+            }
+            else
+            {
+               child_id = -1;
+            }
+         }
+#endif
+
+#if defined(AMIGA) && !defined(SELECTED_ONE_OPTION)
+#define SELECTED_ONE_OPTION
+         csp->cfd = ReleaseSocket(csp->cfd, -1);
+         if((child_id = (int)CreateNewProcTags(
+            NP_Entry, (ULONG)server_thread,
+            NP_Output, Output(),
+            NP_CloseOutput, FALSE,
+            NP_Name, (ULONG)"privoxy child",
+            NP_StackSize, 200*1024,
+            TAG_DONE)))
+         {
+            childs++;
+            ((struct Task *)child_id)->tc_UserData = csp;
+            Signal((struct Task *)child_id, SIGF_SINGLE);
+            Wait(SIGF_SINGLE);
+         }
+#endif
+
+#if !defined(SELECTED_ONE_OPTION)
+         child_id = fork();
+
+         /* This block is only needed when using fork().
+          * When using threads, the server thread was
+          * created and run by the call to _beginthread().
+          */
+         if (child_id == 0)   /* child */
+         {
+            serve(csp);
+            _exit(0);
+
+         }
+         else if (child_id > 0) /* parent */
+         {
+            /* in a fork()'d environment, the parent's
+             * copy of the client socket and the CSP
+             * are not used.
+             */
+
+#if !defined(_WIN32) && defined(__CYGWIN__)
+            wait( NULL );
+#endif /* !defined(_WIN32) && defined(__CYGWIN__) */
+            close_socket(csp->cfd);
+            csp->flags &= ~CSP_FLAG_ACTIVE;
+         }
+#endif
+
+#undef SELECTED_ONE_OPTION
+/* end of cpp switch () */
+
+         if (child_id < 0) /* failed */
+         {
+            char buf[BUFFER_SIZE];
+
+            log_error(LOG_LEVEL_ERROR, "can't fork: %E");
+
+            sprintf(buf , "Privoxy: can't fork: errno = %d", errno);
+
+            write_socket(csp->cfd, buf, strlen(buf));
+            close_socket(csp->cfd);
+            csp->flags &= ~CSP_FLAG_ACTIVE;
+            sleep(5);
+            continue;
+         }
+      }
+      else
+      {
+         serve(csp);
+      }
+   }
+
+   /* NOTREACHED unless FEATURE_GRACEFUL_TERMINATION is defined */
+
+   /* Clean up.  Aim: free all memory (no leaks) */
+#ifdef FEATURE_GRACEFUL_TERMINATION
+
+   log_error(LOG_LEVEL_ERROR, "Graceful termination requested");
+
+   unload_current_config_file();
+   unload_current_actions_file();
+   unload_current_re_filterfile();
+#ifdef FEATURE_TRUST
+   unload_current_trust_file();
+#endif
+
+   if (config->multi_threaded)
+   {
+      int i = 60;
+      do
+      {
+         sleep(1);
+         sweep();
+      } while ((clients->next != NULL) && (--i > 0));
+
+      if (i <= 0)
+      {
+         log_error(LOG_LEVEL_ERROR, "Graceful termination failed - still some live clients after 1 minute wait.");
+      }
+   }
+   sweep();
+   sweep();
+
+#if defined(unix)
+   free(basedir);
+#endif
+#if defined(_WIN32) && !defined(_WIN_CONSOLE)
+   /* Cleanup - remove taskbar icon etc. */
+   TermLogWindow();
+#endif
+
+   exit(0);
+#endif /* FEATURE_GRACEFUL_TERMINATION */
+
+}
+
+
+/*
+  Local Variables:
+  tab-width: 3
+  end:
+*/
diff --git a/src/jcc.h b/src/jcc.h
new file mode 100644 (file)
index 0000000..9cdab39
--- /dev/null
+++ b/src/jcc.h
@@ -0,0 +1,143 @@
+#ifndef JCC_H_INCLUDED
+#define JCC_H_INCLUDED
+#define JCC_H_VERSION "$Id: jcc.h,v 1.12 2002/03/26 22:29:55 swa Exp $"
+/*********************************************************************
+ *
+ * File        :  $Source: /cvsroot/ijbswa/current/jcc.h,v $
+ *
+ * Purpose     :  Main file.  Contains main() method, main loop, and 
+ *                the main connection-handling function.
+ *
+ * Copyright   :  Written by and Copyright (C) 2001 the SourceForge
+ *                Privoxy team. http://www.privoxy.org/
+ *
+ *                Based on the Internet Junkbuster originally written
+ *                by and Copyright (C) 1997 Anonymous Coders and 
+ *                Junkbusters Corporation.  http://www.junkbusters.com
+ *
+ *                This program is free software; you can redistribute it 
+ *                and/or modify it under the terms of the GNU General
+ *                Public License as published by the Free Software
+ *                Foundation; either version 2 of the License, or (at
+ *                your option) any later version.
+ *
+ *                This program is distributed in the hope that it will
+ *                be useful, but WITHOUT ANY WARRANTY; without even the
+ *                implied warranty of MERCHANTABILITY or FITNESS FOR A
+ *                PARTICULAR PURPOSE.  See the GNU General Public
+ *                License for more details.
+ *
+ *                The GNU General Public License should be included with
+ *                this file.  If not, you can view it at
+ *                http://www.gnu.org/copyleft/gpl.html
+ *                or write to the Free Software Foundation, Inc., 59
+ *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * Revisions   :
+ *    $Log: jcc.h,v $
+ *    Revision 1.12  2002/03/26 22:29:55  swa
+ *    we have a new homepage!
+ *
+ *    Revision 1.11  2002/03/24 13:25:43  swa
+ *    name change related issues
+ *
+ *    Revision 1.10  2002/03/16 23:54:06  jongfoster
+ *    Adding graceful termination feature, to help look for memory leaks.
+ *    If you enable this (which, by design, has to be done by hand
+ *    editing config.h) and then go to http://i.j.b/die, then the program
+ *    will exit cleanly after the *next* request.  It should free all the
+ *    memory that was used.
+ *
+ *    Revision 1.9  2002/03/07 03:52:44  oes
+ *    Set logging to tty for --no-daemon mode
+ *
+ *    Revision 1.8  2002/03/04 18:19:49  oes
+ *    Added extern const char *pidfile
+ *
+ *    Revision 1.7  2001/11/05 21:41:43  steudten
+ *    Add changes to be a real daemon just for unix os.
+ *    (change cwd to /, detach from controlling tty, set
+ *    process group and session leader to the own process.
+ *    Add DBG() Macro.
+ *    Add some fatal-error log message for failed malloc().
+ *    Add '-d' if compiled with 'configure --with-debug' to
+ *    enable debug output.
+ *
+ *    Revision 1.6  2001/07/30 22:08:36  jongfoster
+ *    Tidying up #defines:
+ *    - All feature #defines are now of the form FEATURE_xxx
+ *    - Permanently turned off WIN_GUI_EDIT
+ *    - Permanently turned on WEBDAV and SPLIT_PROXY_ARGS
+ *
+ *    Revision 1.5  2001/07/29 19:32:00  jongfoster
+ *    Renaming _main() [mingw32 only] to real_main(), for ANSI compliance.
+ *
+ *    Revision 1.4  2001/07/29 18:58:15  jongfoster
+ *    Removing nested #includes, adding forward declarations for needed
+ *    structures, and changing the #define _FILENAME_H to FILENAME_H_INCLUDED.
+ *
+ *    Revision 1.3  2001/07/18 12:31:58  oes
+ *    moved #define freez from jcc.h to project.h
+ *
+ *    Revision 1.2  2001/05/31 21:24:47  jongfoster
+ *    Changed "permission" to "action" throughout.
+ *    Removed DEFAULT_USER_AGENT - it must now be specified manually.
+ *    Moved vanilla wafer check into chat(), since we must now
+ *    decide whether or not to add it based on the URL.
+ *
+ *    Revision 1.1.1.1  2001/05/15 13:58:56  oes
+ *    Initial import of version 2.9.3 source tree
+ *
+ *
+ *********************************************************************/
+\f
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct client_state;
+struct file_list;
+
+/* Global variables */
+
+#ifdef FEATURE_STATISTICS
+extern int urls_read;
+extern int urls_rejected;
+#endif /*def FEATURE_STATISTICS*/
+
+extern struct client_state clients[];
+extern struct file_list    files[];
+
+#ifdef unix
+extern const char *pidfile;
+#endif
+extern int no_daemon;
+
+#ifdef FEATURE_GRACEFUL_TERMINATION
+extern int g_terminate;
+#endif
+
+/* Functions */
+
+#ifdef __MINGW32__
+int real_main(int argc, const char *argv[]);
+#else
+int main(int argc, const char *argv[]);
+#endif
+
+/* Revision control strings from this header and associated .c file */
+extern const char jcc_rcs[];
+extern const char jcc_h_rcs[];
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* ndef JCC_H_INCLUDED */
+
+/*
+  Local Variables:
+  tab-width: 3
+  end:
+*/
diff --git a/src/killpopup.c b/src/killpopup.c
new file mode 100644 (file)
index 0000000..bc239a4
--- /dev/null
@@ -0,0 +1,216 @@
+const char killpopup_rcs[] = "$Id: killpopup.c,v 1.16 2002/03/26 22:29:55 swa Exp $";
+/*********************************************************************
+ *
+ * File        :  $Source: /cvsroot/ijbswa/current/killpopup.c,v $
+ *
+ * Purpose     :  Handles the filtering of popups.
+ *
+ * Copyright   :  Written by and Copyright (C) 2001 the SourceForge
+ *                Privoxy team. http://www.privoxy.org/
+ *
+ *                Based on the Internet Junkbuster originally written
+ *                by and Copyright (C) 1997 Anonymous Coders and
+ *                Junkbusters Corporation.  http://www.junkbusters.com
+ *
+ *                This program is free software; you can redistribute it
+ *                and/or modify it under the terms of the GNU General
+ *                Public License as published by the Free Software
+ *                Foundation; either version 2 of the License, or (at
+ *                your option) any later version.
+ *
+ *                This program is distributed in the hope that it will
+ *                be useful, but WITHOUT ANY WARRANTY; without even the
+ *                implied warranty of MERCHANTABILITY or FITNESS FOR A
+ *                PARTICULAR PURPOSE.  See the GNU General Public
+ *                License for more details.
+ *
+ *                The GNU General Public License should be included with
+ *                this file.  If not, you can view it at
+ *                http://www.gnu.org/copyleft/gpl.html
+ *                or write to the Free Software Foundation, Inc., 59
+ *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * Revisions   :
+ *    $Log: killpopup.c,v $
+ *    Revision 1.16  2002/03/26 22:29:55  swa
+ *    we have a new homepage!
+ *
+ *    Revision 1.15  2002/03/24 13:25:43  swa
+ *    name change related issues
+ *
+ *    Revision 1.14  2002/03/07 03:46:53  oes
+ *    Fixed compiler warnings etc
+ *
+ *    Revision 1.13  2001/11/13 00:16:40  jongfoster
+ *    Replacing references to malloc.h with the standard stdlib.h
+ *    (See ANSI or K&R 2nd Ed)
+ *
+ *    Revision 1.12  2001/10/25 03:40:48  david__schmidt
+ *    Change in porting tactics: OS/2's EMX porting layer doesn't allow multiple
+ *    threads to call select() simultaneously.  So, it's time to do a real, live,
+ *    native OS/2 port.  See defines for __EMX__ (the porting layer) vs. __OS2__
+ *    (native). Both versions will work, but using __OS2__ offers multi-threading.
+ *
+ *    Revision 1.11  2001/10/07 15:42:41  oes
+ *    filter_popups now gets a csp pointer so it can raise the new
+ *      CSP_FLAG_MODIFIED flag.
+ *
+ *    Revision 1.10  2001/09/22 16:34:44  jongfoster
+ *    Removing unneeded #includes
+ *
+ *    Revision 1.9  2001/07/31 14:44:22  oes
+ *    Deleted unused size parameter from filter_popups()
+ *
+ *    Revision 1.8  2001/07/30 22:08:36  jongfoster
+ *    Tidying up #defines:
+ *    - All feature #defines are now of the form FEATURE_xxx
+ *    - Permanently turned off WIN_GUI_EDIT
+ *    - Permanently turned on WEBDAV and SPLIT_PROXY_ARGS
+ *
+ *    Revision 1.7  2001/07/20 19:29:25  haroon
+ *    - In v1.5 forgot to add that I implemented LOG_LEVEL_POPUPS in errlog.c,
+ *      errlog.h and killpopup.c. In that case, it is superfluous to have define for
+ *      POPUP_VERBOSE, so I removed the defines and logging is now done
+ *      via log_error(LOG_LEVEL_POPUPS, ....)
+ *
+ *    Revision 1.6  2001/07/19 19:11:35  haroon
+ *    - Implemented Guy's idea of replacing window.open( with 1;''.concat(
+ *    - Implemented Guy's idea of replacing .resizeTo( with .scrollTo(
+ *
+ *    Revision 1.5  2001/07/18 15:02:52  haroon
+ *    improved nuking of window.open
+ *
+ *    Revision 1.4  2001/06/29 13:29:55  oes
+ *    Added FIXMEs (and didn't repair, hehe)
+ *
+ *    Revision 1.3  2001/05/22 18:56:28  oes
+ *    CRLF -> LF
+ *
+ *    Revision 1.2  2001/05/20 01:21:20  jongfoster
+ *    Version 2.9.4 checkin.
+ *    - Merged popupfile and cookiefile, and added control over PCRS
+ *      filtering, in new "permissionsfile".
+ *    - Implemented LOG_LEVEL_FATAL, so that if there is a configuration
+ *      file error you now get a message box (in the Win32 GUI) rather
+ *      than the program exiting with no explanation.
+ *    - Made killpopup use the PCRS MIME-type checking and HTTP-header
+ *      skipping.
+ *    - Removed tabs from "config"
+ *    - Moved duplicated url parsing code in "loaders.c" to a new funcition.
+ *    - Bumped up version number.
+ *
+ *    Revision 1.1.1.1  2001/05/15 13:58:58  oes
+ *    Initial import of version 2.9.3 source tree
+ *
+ *
+ *********************************************************************/
+\f
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <ctype.h>
+
+#if !defined(_WIN32) && !defined(__OS2__)
+#include <unistd.h>
+#endif
+
+#include "project.h"
+#include "killpopup.h"
+#include "errlog.h"
+
+const char killpopup_h_rcs[] = KILLPOPUP_H_VERSION;
+
+#ifdef FEATURE_KILL_POPUPS
+
+/*********************************************************************
+ *
+ * Function    :  filter_popups
+ *
+ * Description :  Filter the block of data that's been read from the server
+ *                for javascript popup code and replace by syntactically
+ *                neutral code of the same size.
+ *                Raise the CSP_FLAG_MODIFIED flag on success.
+ *
+ * Parameters  :
+ *          1  :  buff = Buffer to scan and modify.  Null terminated.
+ *          2  :  csp = Client state pointer
+ *
+ * Returns     :  void
+ *
+ *********************************************************************/
+void filter_popups(char *buff, struct client_state *csp)
+{
+   char *start_p = NULL;
+   char *close_p = NULL;
+   char *p     = NULL;
+
+   /*
+    * replace the window.open( with a harmless JavaScript replacement
+    * (notice the two single quotes)
+    */
+   while ((start_p = strstr(buff, "window.open(")) != NULL)
+   {
+      if (start_p)
+      {
+         strncpy(start_p, "1;''.concat(", 12);
+         log_error(LOG_LEVEL_POPUPS, "Blocked popup window open");
+         csp->flags |= CSP_FLAG_MODIFIED;
+      }
+   }
+
+   /*
+    * replace the .resizeTo( with a harmless JavaScript replacement
+    */
+   while ((start_p = strstr(buff, ".resizeTo(")) != NULL)
+   {
+      if (start_p)
+      {
+         strncpy(start_p, ".scrollTo(", 10);
+         log_error(LOG_LEVEL_POPUPS, "Blocked popup window resize");
+         csp->flags |= CSP_FLAG_MODIFIED;
+      }
+   }
+
+   /* 
+    * Filter onUnload and onExit
+    */
+   start_p = strstr(buff, "<body");
+   if (!start_p) start_p = strstr(buff, "<BODY");
+   if (!start_p) start_p = strstr(buff, "<Body");
+   if (!start_p) start_p = strstr(buff, "<BOdy");
+   if (start_p)
+   {
+      close_p = strchr(start_p, '>');
+      if (close_p)
+      {
+         /* we are now between <body and the ending > */
+         p = strstr(start_p, "onUnload");
+         if (p)
+         {
+            strncpy(p, "_nU_", 4);
+            csp->flags |= CSP_FLAG_MODIFIED;
+         }
+         p = strstr(start_p, "onExit");
+         if (p)
+         {
+            strncpy(p, "_nE_", 4);
+            csp->flags |= CSP_FLAG_MODIFIED;
+         }
+      }
+   }
+
+}
+
+#endif /* def FEATURE_KILL_POPUPS */
+
+/*
+  Local Variables:
+  tab-width: 3
+  end:
+*/
diff --git a/src/killpopup.h b/src/killpopup.h
new file mode 100644 (file)
index 0000000..76f62c4
--- /dev/null
@@ -0,0 +1,106 @@
+#ifndef KILLPOPUP_H_INCLUDED
+#define KILLPOPUP_H_INCLUDED
+#define KILLPOPUP_H_VERSION "$Id: killpopup.h,v 1.8 2002/03/26 22:29:55 swa Exp $"
+/*********************************************************************
+ *
+ * File        :  $Source: /cvsroot/ijbswa/current/killpopup.h,v $
+ *
+ * Purpose     :  Handles the filtering of popups.
+ *
+ * Copyright   :  Written by and Copyright (C) 2001 the SourceForge
+ *                Privoxy team. http://www.privoxy.org/
+ *
+ *                Based on the Internet Junkbuster originally written
+ *                by and Copyright (C) 1997 Anonymous Coders and 
+ *                Junkbusters Corporation.  http://www.junkbusters.com
+ *
+ *                This program is free software; you can redistribute it 
+ *                and/or modify it under the terms of the GNU General
+ *                Public License as published by the Free Software
+ *                Foundation; either version 2 of the License, or (at
+ *                your option) any later version.
+ *
+ *                This program is distributed in the hope that it will
+ *                be useful, but WITHOUT ANY WARRANTY; without even the
+ *                implied warranty of MERCHANTABILITY or FITNESS FOR A
+ *                PARTICULAR PURPOSE.  See the GNU General Public
+ *                License for more details.
+ *
+ *                The GNU General Public License should be included with
+ *                this file.  If not, you can view it at
+ *                http://www.gnu.org/copyleft/gpl.html
+ *                or write to the Free Software Foundation, Inc., 59
+ *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * Revisions   :
+ *    $Log: killpopup.h,v $
+ *    Revision 1.8  2002/03/26 22:29:55  swa
+ *    we have a new homepage!
+ *
+ *    Revision 1.7  2002/03/24 13:25:43  swa
+ *    name change related issues
+ *
+ *    Revision 1.6  2001/10/07 15:42:41  oes
+ *    filter_popups now gets a csp pointer so it can raise the new
+ *      CSP_FLAG_MODIFIED flag.
+ *
+ *    Revision 1.5  2001/07/31 14:44:22  oes
+ *    Deleted unused size parameter from filter_popups()
+ *
+ *    Revision 1.4  2001/07/30 22:08:36  jongfoster
+ *    Tidying up #defines:
+ *    - All feature #defines are now of the form FEATURE_xxx
+ *    - Permanently turned off WIN_GUI_EDIT
+ *    - Permanently turned on WEBDAV and SPLIT_PROXY_ARGS
+ *
+ *    Revision 1.3  2001/07/29 18:59:21  jongfoster
+ *    - Changing #define _KILLPOPUP_H to KILLPOPUP_H_INCLUDED
+ *    - Adding extern "C" {}
+ *
+ *    Revision 1.2  2001/05/20 01:21:20  jongfoster
+ *    Version 2.9.4 checkin.
+ *    - Merged popupfile and cookiefile, and added control over PCRS
+ *      filtering, in new "permissionsfile".
+ *    - Implemented LOG_LEVEL_FATAL, so that if there is a configuration
+ *      file error you now get a message box (in the Win32 GUI) rather
+ *      than the program exiting with no explanation.
+ *    - Made killpopup use the PCRS MIME-type checking and HTTP-header
+ *      skipping.
+ *    - Removed tabs from "config"
+ *    - Moved duplicated url parsing code in "loaders.c" to a new funcition.
+ *    - Bumped up version number.
+ *
+ *    Revision 1.1.1.1  2001/05/15 13:58:58  oes
+ *    Initial import of version 2.9.3 source tree
+ *
+ *
+ *********************************************************************/
+\f
+
+#include "project.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef FEATURE_KILL_POPUPS
+
+extern void filter_popups(char *buff, struct client_state *csp);
+
+#endif /* def FEATURE_KILL_POPUPS */
+
+/* Revision control strings from this header and associated .c file */
+extern const char killpopup_rcs[];
+extern const char killpopup_h_rcs[];
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* ndef KILLPOPUP_H_INCLUDED */
+
+/*
+  Local Variables:
+  tab-width: 3
+  end:
+*/
diff --git a/src/list.c b/src/list.c
new file mode 100644 (file)
index 0000000..c8556d4
--- /dev/null
@@ -0,0 +1,1067 @@
+const char list_rcs[] = "$Id: list.c,v 1.15 2002/03/26 22:29:55 swa Exp $";
+/*********************************************************************
+ *
+ * File        :  $Source: /cvsroot/ijbswa/current/list.c,v $
+ *
+ * Purpose     :  Declares functions to handle lists.
+ *                Functions declared include:
+ *                   `destroy_list', `enlist' and `list_to_text'
+ *
+ * Copyright   :  Written by and Copyright (C) 2001 the SourceForge
+ *                Privoxy team. http://www.privoxy.org/
+ *
+ *                Based on the Internet Junkbuster originally written
+ *                by and Copyright (C) 1997 Anonymous Coders and
+ *                Junkbusters Corporation.  http://www.junkbusters.com
+ *
+ *                This program is free software; you can redistribute it
+ *                and/or modify it under the terms of the GNU General
+ *                Public License as published by the Free Software
+ *                Foundation; either version 2 of the License, or (at
+ *                your option) any later version.
+ *
+ *                This program is distributed in the hope that it will
+ *                be useful, but WITHOUT ANY WARRANTY; without even the
+ *                implied warranty of MERCHANTABILITY or FITNESS FOR A
+ *                PARTICULAR PURPOSE.  See the GNU General Public
+ *                License for more details.
+ *
+ *                The GNU General Public License should be included with
+ *                this file.  If not, you can view it at
+ *                http://www.gnu.org/copyleft/gpl.html
+ *                or write to the Free Software Foundation, Inc., 59
+ *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * Revisions   :
+ *    $Log: list.c,v $
+ *    Revision 1.15  2002/03/26 22:29:55  swa
+ *    we have a new homepage!
+ *
+ *    Revision 1.14  2002/03/24 13:25:43  swa
+ *    name change related issues
+ *
+ *    Revision 1.13  2002/03/07 03:46:17  oes
+ *    Fixed compiler warnings
+ *
+ *    Revision 1.12  2001/10/25 03:40:48  david__schmidt
+ *    Change in porting tactics: OS/2's EMX porting layer doesn't allow multiple
+ *    threads to call select() simultaneously.  So, it's time to do a real, live,
+ *    native OS/2 port.  See defines for __EMX__ (the porting layer) vs. __OS2__
+ *    (native). Both versions will work, but using __OS2__ offers multi-threading.
+ *
+ *    Revision 1.11  2001/10/23 21:21:03  jongfoster
+ *    New error handling - error codes are now jb_errs, not ints.
+ *    Changed the way map() handles out-of-memory, to dramatically
+ *    reduce the amount of error-checking clutter needed.
+ *
+ *    Revision 1.10  2001/09/16 17:30:24  jongfoster
+ *    Fixing a compiler warning.
+ *
+ *    Revision 1.9  2001/09/16 13:20:29  jongfoster
+ *    Rewrite of list library.  Now has seperate header and list_entry
+ *    structures.  Also added a large sprinking of assert()s to the list
+ *    code.
+ *
+ *    Revision 1.8  2001/08/07 14:00:20  oes
+ *    Fixed comment
+ *
+ *    Revision 1.7  2001/08/05 16:06:20  jongfoster
+ *    Modifiying "struct map" so that there are now separate header and
+ *    "map_entry" structures.  This means that functions which modify a
+ *    map no longer need to return a pointer to the modified map.
+ *    Also, it no longer reverses the order of the entries (which may be
+ *    important with some advanced template substitutions).
+ *
+ *    Revision 1.6  2001/07/31 14:44:51  oes
+ *    list_to_text() now appends empty line at end
+ *
+ *    Revision 1.5  2001/06/29 21:45:41  oes
+ *    Indentation, CRLF->LF, Tab-> Space
+ *
+ *    Revision 1.4  2001/06/29 13:30:22  oes
+ *    - Added Convenience function enlist_unique_header(),
+ *      which takes the Header name and value as separate
+ *      arguments and thus saves the pain of sprintf()ing
+ *      and determining the Header name length to enlist_unique
+ *    - Improved comments
+ *    - Removed logentry from cancelled commit
+ *
+ *    Revision 1.3  2001/06/03 19:12:24  oes
+ *    functions for new struct map, extended enlist_unique
+ *
+ *    Revision 1.2  2001/06/01 18:49:17  jongfoster
+ *    Replaced "list_share" with "list" - the tiny memory gain was not
+ *    worth the extra complexity.
+ *
+ *    Revision 1.1  2001/05/31 21:11:53  jongfoster
+ *    - Moved linked list support to new "list.c" file.
+ *      Structure definitions are still in project.h,
+ *      function prototypes are now in "list.h".
+ *    - Added support for "struct list_share", which is identical
+ *      to "struct list" except it saves memory by not duplicating
+ *      the strings.  Obviously, this only works if there is some
+ *      other way of managing the memory used by the strings.
+ *      (These list_share lists are used for lists which last
+ *      for only 1 request, and where all the list entries are
+ *      just coming directly from entries in the actionsfile.)
+ *      Note that you still need to destroy list_share lists
+ *      properly to free the nodes - it's only the strings
+ *      which are shared.
+ *
+ *
+ *********************************************************************/
+\f
+
+#include "config.h"
+
+#ifndef _WIN32
+/* FIXME: The following headers are not needed for Win32.  Are they
+ * needed on other platforms?
+ */
+#include <stdio.h>
+#include <sys/types.h>
+#include <stdlib.h>
+#include <ctype.h>
+#endif
+#include <string.h>
+
+#if !defined(_WIN32) && !defined(__OS2__)
+#include <unistd.h>
+#endif
+
+#include <assert.h>
+
+#include "project.h"
+#include "list.h"
+#include "miscutil.h"
+
+const char list_h_rcs[] = LIST_H_VERSION;
+
+
+static int list_is_valid (const struct list *the_list);
+
+
+/*********************************************************************
+ *
+ * Function    :  list_init
+ *
+ * Description :  Create a new, empty list in user-allocated memory.
+ *                Caller should allocate a "struct list" variable,
+ *                then pass it to this function.
+ *                (Implementation note:  Rather than calling this
+ *                function, you can also just memset the memory to
+ *                zero, e.g. if you have a larger structure you
+ *                want to initialize quickly.  However, that isn't
+ *                really good design.)
+ *
+ * Parameters  :
+ *          1  :  the_list = pointer to list
+ *
+ * Returns     :  N/A
+ *
+ *********************************************************************/
+void init_list(struct list *the_list)
+{
+   memset(the_list, '\0', sizeof(*the_list));
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  destroy_list
+ *
+ * Description :  Destroy a string list (opposite of list_init).
+ *                On return, the memory used by the list entries has
+ *                been freed, but not the memory used by the_list
+ *                itself.  You should not re-use the_list without
+ *                calling list_init().
+ *
+ *                (Implementation note:  You *can* reuse the_list
+ *                without calling list_init(), but please don't.
+ *                If you want to remove all entries from a list
+ *                and still have a usable list, then use
+ *                list_remove_all().)
+ *
+ * Parameters  :
+ *          1  :  the_list = pointer to list
+ *
+ * Returns     :  N/A
+ *
+ *********************************************************************/
+void destroy_list (struct list *the_list)
+{
+   struct list_entry *cur_entry, *next_entry;
+
+   assert(the_list);
+
+   for (cur_entry = the_list->first; cur_entry ; cur_entry = next_entry)
+   {
+      next_entry = cur_entry->next;
+      freez(cur_entry->str);
+      free(cur_entry);
+   }
+
+   the_list->first = NULL;
+   the_list->last = NULL;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  list_is_valid
+ *
+ * Description :  Check that a string list is valid.  The intended
+ *                usage is "assert(list_is_valid(the_list))".
+ *                Currently this checks that "the_list->last"
+ *                is correct, and that the list dosn't contain
+ *                circular references.  It is likely to crash if
+ *                it's passed complete garbage.
+ *
+ * Parameters  :
+ *          1  :  the_list = pointer to list.  Must be non-null.
+ *
+ * Returns     :  1 if list is valid, 0 otherwise.
+ *
+ *********************************************************************/
+static int list_is_valid (const struct list *the_list)
+{
+   /*
+    * If you don't want this check, just change the line below
+    * from "#if 1" to "#if 0".
+    */
+#if 1
+   const struct list_entry *cur_entry;
+   const struct list_entry *last_entry = NULL;
+   int length = 0;
+
+   assert(the_list);
+
+   for (cur_entry = the_list->first; cur_entry ; cur_entry = cur_entry->next)
+   {
+      last_entry = cur_entry;
+
+      if (cur_entry->str)
+      {
+         /*
+          * Just check that this string can be accessed - i.e. it's a valid
+          * pointer.
+          */
+         strlen(cur_entry->str);
+      }
+
+      /*
+       * Check for looping back to first
+       */
+      if ((length != 0) && (cur_entry == the_list->first))
+      {
+         return 0;
+      }
+
+      /*
+       * Arbitrarily limit length to prevent infinite loops.
+       */
+      if (++length > 1000)
+      {
+         return 0;
+      }
+
+      /*
+       * Check this isn't marked as the last entry, unless of course it's
+       * *really* the last entry.
+       */
+      if ((the_list->last == cur_entry) && (cur_entry->next != NULL))
+      {
+         /* This is the last entry, but there's data after it !!?? */
+         return 0;
+      }
+   }
+
+   return (the_list->last == last_entry);
+#else
+   return 1;
+#endif
+}
+
+/*********************************************************************
+ *
+ * Function    :  enlist
+ *
+ * Description :  Append a string into a specified string list.
+ *
+ * Parameters  :
+ *          1  :  the_list = pointer to list
+ *          2  :  str = string to add to the list (maybe NULL)
+ *
+ * Returns     :  JB_ERR_OK on success
+ *                JB_ERR_MEMORY on out-of-memory error.
+ *                On error, the_list will be unchanged.
+ *
+ *********************************************************************/
+jb_err enlist(struct list *the_list, const char *str)
+{
+   struct list_entry *cur;
+
+   assert(the_list);
+   assert(list_is_valid(the_list));
+
+   if (NULL == (cur = (struct list_entry *)zalloc(sizeof(*cur))))
+   {
+      return JB_ERR_MEMORY;
+   }
+
+   if (str)
+   {
+      if (NULL == (cur->str = strdup(str)))
+      {
+         free(cur);
+         return JB_ERR_MEMORY;
+      }
+   }
+   /* else { cur->str = NULL; }  - implied by zalloc */
+
+   /* cur->next = NULL;  - implied by zalloc */
+
+   if (the_list->last)
+   {
+      the_list->last->next = cur;
+      the_list->last = cur;
+   }
+   else
+   {
+      the_list->first = cur;
+      the_list->last = cur;
+   }
+
+   assert(list_is_valid(the_list));
+   return JB_ERR_OK;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  enlist_first
+ *
+ * Description :  Append a string as first element into a specified
+ *                string list.
+ *
+ * Parameters  :
+ *          1  :  the_list = pointer to list
+ *          2  :  str = string to add to the list (maybe NULL)
+ *
+ * Returns     :  JB_ERR_OK on success
+ *                JB_ERR_MEMORY on out-of-memory error.
+ *                On error, the_list will be unchanged.
+ *
+ *********************************************************************/
+jb_err enlist_first(struct list *the_list, const char *str)
+{
+   struct list_entry *cur;
+
+   assert(the_list);
+   assert(list_is_valid(the_list));
+
+   if (NULL == (cur = (struct list_entry *)zalloc(sizeof(*cur))))
+   {
+      return JB_ERR_MEMORY;
+   }
+
+   if (str)
+   {
+      if (NULL == (cur->str = strdup(str)))
+      {
+         free(cur);
+         return JB_ERR_MEMORY;
+      }
+   }
+   /* else { cur->str = NULL; }  - implied by zalloc */
+
+   cur->next = the_list->first;
+
+   the_list->first = cur;
+   if (the_list->last == NULL)
+   {
+      the_list->last = cur;
+   }
+
+   assert(list_is_valid(the_list));
+   return JB_ERR_OK;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  enlist_unique
+ *
+ * Description :  Append a string into a specified string list,
+ *                if & only if it's not there already.
+ *                If the num_significant_chars argument is nonzero,
+ *                only compare up to the nth character.
+ *
+ * Parameters  :
+ *          1  :  the_list = pointer to list
+ *          2  :  str = string to add to the list
+ *          3  :  num_significant_chars = number of chars to use
+ *                for uniqueness test, or 0 to require an exact match.
+ *
+ * Returns     :  JB_ERR_OK on success
+ *                JB_ERR_MEMORY on out-of-memory error.
+ *                On error, the_list will be unchanged.
+ *                "Success" does not indicate whether or not the
+ *                item was already in the list.
+ *
+ *********************************************************************/
+jb_err enlist_unique(struct list *the_list, const char *str,
+                     size_t num_significant_chars)
+{
+   struct list_entry *cur_entry;
+
+   assert(the_list);
+   assert(list_is_valid(the_list));
+   assert(str);
+   assert(num_significant_chars >= 0);
+   assert(num_significant_chars <= strlen(str));
+
+   if (num_significant_chars > 0)
+   {
+      for (cur_entry = the_list->first; cur_entry != NULL; cur_entry = cur_entry->next)
+      {
+         if ( (cur_entry->str != NULL)
+           && (0 == strncmp(str, cur_entry->str, num_significant_chars)))
+         {
+            /* Already there */
+            return JB_ERR_OK;
+         }
+      }
+   }
+   else
+   {
+      /* Test whole string */
+      for (cur_entry = the_list->first; cur_entry != NULL; cur_entry = cur_entry->next)
+      {
+         if ( (cur_entry->str != NULL) && (0 == strcmp(str, cur_entry->str)))
+         {
+            /* Already there */
+            return JB_ERR_OK;
+         }
+      }
+   }
+
+   return enlist(the_list, str);
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  enlist_unique_header
+ *
+ * Description :  Make a HTTP header from the two strings name and value,
+ *                and append the result into a specified string list,
+ *                if & only if there isn't already a header with that name.
+ *
+ * Parameters  :
+ *          1  :  the_list = pointer to list
+ *          2  :  name = HTTP header name (e.g. "Content-type")
+ *          3  :  value = HTTP header value (e.g. "text/html")
+ *
+ * Returns     :  JB_ERR_OK on success
+ *                JB_ERR_MEMORY on out-of-memory error.
+ *                On error, the_list will be unchanged.
+ *                "Success" does not indicate whether or not the
+ *                header was already in the list.
+ *
+ *********************************************************************/
+jb_err enlist_unique_header(struct list *the_list, const char *name,
+                            const char *value)
+{
+   size_t length;
+   jb_err result;
+   char *str;
+
+   assert(the_list);
+   assert(list_is_valid(the_list));
+   assert(name);
+   assert(value);
+
+   length = strlen(name) + 2;
+   if (NULL == (str = (char *)malloc(length + strlen(value) + 1)))
+   {
+      return JB_ERR_MEMORY;
+   }
+   strcpy(str, name);
+   str[length - 2] = ':';
+   str[length - 1] = ' ';
+   strcpy(str + length, value);
+
+   result = enlist_unique(the_list, str, length);
+
+   free(str);
+
+   assert(list_is_valid(the_list));
+
+   return result;
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  list_remove_all
+ *
+ * Description :  Remove all entries from a list.  On return, the_list
+ *                is a valid, empty list.  Note that this is similar
+ *                to destroy_list(), but the difference is that this
+ *                function guarantees that the list structure is still
+ *                valid after the call.
+ *
+ * Parameters  :
+ *          1  :  the_list = pointer to list
+ *
+ * Returns     :  N/A
+ *
+ *********************************************************************/
+void list_remove_all(struct list *the_list)
+{
+   struct list_entry *cur_entry;
+   struct list_entry *next_entry;
+
+   assert(the_list);
+   assert(list_is_valid(the_list));
+
+   for (cur_entry = the_list->first; cur_entry ; cur_entry = next_entry)
+   {
+      next_entry = cur_entry->next;
+      freez(cur_entry->str);
+      free(cur_entry);
+   }
+
+   the_list->first = the_list->last = NULL;
+
+   assert(list_is_valid(the_list));
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  list_to_text
+ *
+ * Description :  "Flatten" a string list into 1 long \r\n delimited string,
+ *                adding an empty line at the end.  NULL entries are ignored.
+ *                This function does not change the_list.
+ *
+ * Parameters  :
+ *          1  :  the_list = pointer to list
+ *
+ * Returns     :  NULL on malloc error, else new long string.
+ *                Caller must free() it.
+ *
+ *********************************************************************/
+char *list_to_text(const struct list *the_list)
+{
+   struct list_entry *cur_entry;
+   char *ret = NULL;
+   char *s;
+   size_t size = 2;
+
+   assert(the_list);
+   assert(list_is_valid(the_list));
+
+   for (cur_entry = the_list->first; cur_entry ; cur_entry = cur_entry->next)
+   {
+      if (cur_entry->str)
+      {
+         size += strlen(cur_entry->str) + 2;
+      }
+   }
+
+   if ((ret = (char *)malloc(size + 1)) == NULL)
+   {
+      return NULL;
+   }
+
+   ret[size] = '\0';
+
+   s = ret;
+
+   for (cur_entry = the_list->first; cur_entry ; cur_entry = cur_entry->next)
+   {
+      if (cur_entry->str)
+      {
+         strcpy(s, cur_entry->str);
+         s += strlen(s);
+         *s++ = '\r'; *s++ = '\n';
+      }
+   }
+   *s++ = '\r'; *s++ = '\n';
+
+   return ret;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  list_remove_item
+ *
+ * Description :  Remove a string from a specified string list.
+ *
+ * Parameters  :
+ *          1  :  the_list = pointer to list
+ *          2  :  str = string to remove from the list - non-NULL
+ *
+ * Returns     :  Number of times it was removed.
+ *
+ *********************************************************************/
+int list_remove_item(struct list *the_list, const char *str)
+{
+   struct list_entry *prev = NULL;
+   struct list_entry *cur;
+   struct list_entry *next;
+   int count = 0;
+
+   assert(the_list);
+   assert(list_is_valid(the_list));
+   assert(str);
+
+   cur = the_list->first;
+
+   while (cur != NULL)
+   {
+      next = cur->next;
+
+      if ((cur->str != NULL) && (0 == strcmp(str, cur->str)))
+      {
+         count++;
+
+         if (prev != NULL)
+         {
+            prev->next = next;
+         }
+         else
+         {
+            the_list->first = next;
+         }
+         free((char *)cur->str);
+         free(cur);
+      }
+      else
+      {
+         prev = cur;
+      }
+      cur = next;
+   }
+
+   the_list->last = prev;
+
+   assert(list_is_valid(the_list));
+
+   return count;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  list_remove_list
+ *
+ * Description :  Remove all strings in one list from another list.
+ *                This is currently a brute-force algorithm
+ *                (it compares every pair of strings).
+ *
+ * Parameters  :
+ *          1  :  dest = list to change
+ *          2  :  src = list of strings to remove
+ *
+ * Returns     :  Total number of strings removed.
+ *
+ *********************************************************************/
+int list_remove_list(struct list *dest, const struct list *src)
+{
+   struct list_entry *cur;
+   int count = 0;
+
+   assert(src);
+   assert(dest);
+   assert(list_is_valid(src));
+   assert(list_is_valid(dest));
+
+   for (cur = src->first; cur != NULL; cur = cur->next)
+   {
+      if (cur->str != NULL)
+      {
+         count += list_remove_item(dest, cur->str);
+      }
+   }
+
+   assert(list_is_valid(src));
+   assert(list_is_valid(dest));
+
+   return count;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  list_duplicate
+ *
+ * Description :  Copy a string list
+ *
+ * Parameters  :
+ *          1  :  dest = Destination list.  Must be a valid list.
+ *                       All existing entries will be removed.
+ *          1  :  src = pointer to source list for copy.
+ *
+ * Returns     :  JB_ERR_OK on success
+ *                JB_ERR_MEMORY on out-of-memory error.
+ *                On error, dest will be empty.
+ *
+ *********************************************************************/
+jb_err list_duplicate(struct list *dest, const struct list *src)
+{
+   struct list_entry * cur_src;
+   struct list_entry * cur_dest;
+
+   assert(src);
+   assert(dest);
+   assert(list_is_valid(src));
+   assert(list_is_valid(dest));
+
+   list_remove_all(dest);
+
+   /* Need to process first entry specially so we can set dest->first */
+   cur_src = src->first;
+   if (cur_src)
+   {
+      cur_dest = dest->first = (struct list_entry *)zalloc(sizeof(*cur_dest));
+      if (cur_dest == NULL)
+      {
+         destroy_list(dest);
+
+         assert(list_is_valid(src));
+         assert(list_is_valid(dest));
+
+         return JB_ERR_MEMORY;
+      }
+
+      if (cur_src->str)
+      {
+         cur_dest->str = strdup(cur_src->str);
+         if (cur_dest->str == NULL)
+         {
+            destroy_list(dest);
+
+            assert(list_is_valid(src));
+            assert(list_is_valid(dest));
+
+            return JB_ERR_MEMORY;
+         }
+      }
+      /* else { cur_dest->str = NULL; }  - implied by zalloc */
+
+      /* Now process the rest */
+      for (cur_src = cur_src->next; cur_src; cur_src = cur_src->next)
+      {
+         cur_dest = cur_dest->next = (struct list_entry *)zalloc(sizeof(*cur_dest));
+         if (cur_dest == NULL)
+         {
+            destroy_list(dest);
+
+            assert(list_is_valid(src));
+            assert(list_is_valid(dest));
+
+            return JB_ERR_MEMORY;
+         }
+         if (cur_src->str)
+         {
+            cur_dest->str = strdup(cur_src->str);
+            if (cur_dest->str == NULL)
+            {
+               destroy_list(dest);
+
+               assert(list_is_valid(src));
+               assert(list_is_valid(dest));
+
+               return JB_ERR_MEMORY;
+            }
+         }
+         /* else { cur_dest->str = NULL; }  - implied by zalloc */
+      }
+
+      dest->last = cur_dest;
+   }
+
+   assert(list_is_valid(src));
+   assert(list_is_valid(dest));
+
+   return JB_ERR_OK;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  list_append_list_unique
+ *
+ * Description :  Append a string list to another list.
+ *                Duplicate items are not added.
+ *
+ * Parameters  :
+ *          1  :  dest = pointer to destination list for merge.
+ *          2  :  src = pointer to source for merge.
+ *
+ * Returns     :  JB_ERR_OK on success
+ *                JB_ERR_MEMORY on out-of-memory error.
+ *                On error, some (but not all) of src might have
+ *                been copied into dest.
+ *
+ *********************************************************************/
+jb_err list_append_list_unique(struct list *dest, const struct list *src)
+{
+   struct list_entry * cur;
+
+   assert(src);
+   assert(dest);
+   assert(list_is_valid(src));
+   assert(list_is_valid(dest));
+
+   for (cur = src->first; cur; cur = cur->next)
+   {
+      if (cur->str)
+      {
+         if (enlist_unique(dest, cur->str, 0))
+         {
+            assert(list_is_valid(src));
+            assert(list_is_valid(dest));
+
+            return JB_ERR_MEMORY;
+         }
+      }
+   }
+
+   assert(list_is_valid(src));
+   assert(list_is_valid(dest));
+
+   return JB_ERR_OK;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  list_is_empty
+ *
+ * Description :  Test whether a list is empty.  Does not change the list.
+ *
+ * Parameters  :
+ *          1  :  the_list = pointer to list to test.
+ *
+ * Returns     :  Nonzero iff the list contains no entries.
+ *
+ *********************************************************************/
+int list_is_empty(const struct list *the_list)
+{
+   assert(the_list);
+   assert(list_is_valid(the_list));
+
+   return (the_list->first == NULL);
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  new_map
+ *
+ * Description :  Create a new, empty map.
+ *
+ * Parameters  :  N/A
+ *
+ * Returns     :  A new, empty map, or NULL if out of memory.
+ *
+ *********************************************************************/
+struct map *new_map(void)
+{
+   return (struct map *) zalloc(sizeof(struct map));
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  free_map
+ *
+ * Description :  Free the memory occupied by a map and its
+ *                depandant strings
+ *
+ * Parameters  :
+ *          1  :  the_map = map to be freed.  May be NULL.
+ *
+ * Returns     :  N/A
+ *
+ *********************************************************************/
+void free_map(struct map *the_map)
+{
+   struct map_entry *cur_entry;
+   struct map_entry *next_entry;
+
+   if (the_map == NULL)
+   {
+      return;
+   }
+
+   for (cur_entry = the_map->first; cur_entry != NULL; cur_entry = next_entry)
+   {
+      freez(cur_entry->name);
+      freez(cur_entry->value);
+
+      next_entry = cur_entry->next;
+      free(cur_entry);
+   }
+
+   the_map->first = the_map->last = NULL;
+
+   free(the_map);
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  map
+ *
+ * Description :  Add a mapping from given name to given value to a
+ *                given map.
+ *
+ *                Note: Since all strings will be free()d in free_map()
+ *                      later, set the copy flags for constants or
+ *                      strings that will be independantly free()d.
+ *
+ *                Note2: This function allows NULL parameters - it
+ *                       returns JB_ERR_MEMORY in that case.
+ *
+ *                Note3: If this function returns JB_ERR_MEMORY,
+ *                       it will free(name) unless you specify
+ *                       name_needs_copying, and similarly it will
+ *                       free(value) unless you specify
+ *                       value_needs_copying.
+ *
+ *                Due to Note2 and Note3 above, the following code
+ *                is legal, and will never crash or leak memory even
+ *                if the system runs out of memory:
+ *
+ *                    err = map(mymap, "xyz", 1, html_encode(somestring), 0);
+ *
+ *                err will be set to JB_ERR_MEMORY if either call runs
+ *                out-of-memory.  Without these features, you would
+ *                need to check the return value of html_encode in the
+ *                above example for NULL, which (at least) doubles the
+ *                amount of error-checking code needed.
+ *
+ * Parameters  :
+ *          1  :  the_map = map to add to
+ *          2  :  name = name to add
+ *          3  :  name_needs_copying = flag set if a copy of name should be used
+ *          4  :  value = value to add
+ *          5  :  value_needs_copying = flag set if a copy of value should be used
+ *
+ * Returns     :  JB_ERR_OK on success
+ *                JB_ERR_MEMORY on out-of-memory error.
+ *
+ *********************************************************************/
+jb_err map(struct map *the_map,
+           const char *name, int name_needs_copying,
+           const char *value, int value_needs_copying)
+{
+   struct map_entry *new_entry;
+
+   assert(the_map);
+
+   if ( (NULL == value)
+     || (NULL == name)
+     || (NULL == (new_entry = zalloc(sizeof(*new_entry)))) )
+   {
+      if ((name != NULL) && (!name_needs_copying))
+      {
+          free((char *)name);
+      }
+      if ((value != NULL) && (!value_needs_copying))
+      {
+          free((char *)value);
+      }
+      return JB_ERR_MEMORY;
+   }
+
+   if (name_needs_copying)
+   {
+      if (NULL == (name = strdup(name)))
+      {
+         free(new_entry);
+         if (!value_needs_copying)
+         {
+             free((char *)value);
+         }
+         return JB_ERR_MEMORY;
+      }
+   }
+
+   if (value_needs_copying)
+   {
+      if (NULL == (value = strdup(value)))
+      {
+         free((char *)name);
+         free(new_entry);
+         return JB_ERR_MEMORY;
+      }
+   }
+
+   new_entry->name = name;
+   new_entry->value = value;
+   /* new_entry->next = NULL;  - implied by zalloc */
+
+   if (the_map->last)
+   {
+      the_map->last->next = new_entry;
+      the_map->last = new_entry;
+   }
+   else
+   {
+      the_map->first = new_entry;
+      the_map->last = new_entry;
+   }
+
+   return JB_ERR_OK;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  lookup
+ *
+ * Description :  Look up an item with a given name in a map, and
+ *                return its value
+ *
+ * Parameters  :
+ *          1  :  the_map = map to look in
+ *          2  :  name = name parameter to look for
+ *
+ * Returns     :  the value if found, else the empty string.
+ *                Return value is alloced as part of the map, so
+ *                it is freed when the map is destroyed.  Caller
+ *                must not free or modify it.
+ *
+ *********************************************************************/
+const char *lookup(const struct map *the_map, const char *name)
+{
+   const struct map_entry *cur_entry;
+
+   assert(the_map);
+   assert(name);
+
+   for (cur_entry = the_map->first; cur_entry != NULL; cur_entry = cur_entry->next)
+   {
+      if (!strcmp(name, cur_entry->name))
+      {
+         return cur_entry->value;
+      }
+   }
+   return "";
+}
+
+
+/*
+  Local Variables:
+  tab-width: 3
+  end:
+*/
diff --git a/src/list.h b/src/list.h
new file mode 100644 (file)
index 0000000..8dd1835
--- /dev/null
@@ -0,0 +1,167 @@
+#ifndef LIST_H_INCLUDED
+#define LIST_H_INCLUDED
+#define LIST_H_VERSION "$Id: list.h,v 1.12 2002/03/26 22:29:55 swa Exp $"
+/*********************************************************************
+ *
+ * File        :  $Source: /cvsroot/ijbswa/current/list.h,v $
+ *
+ * Purpose     :  Declares functions to handle lists.
+ *                Functions declared include:
+ *                   `destroy_list', `enlist' and `list_to_text'
+ *
+ * Copyright   :  Written by and Copyright (C) 2001 the SourceForge
+ *                Privoxy team. http://www.privoxy.org/
+ *
+ *                Based on the Internet Junkbuster originally written
+ *                by and Copyright (C) 1997 Anonymous Coders and 
+ *                Junkbusters Corporation.  http://www.junkbusters.com
+ *
+ *                This program is free software; you can redistribute it 
+ *                and/or modify it under the terms of the GNU General
+ *                Public License as published by the Free Software
+ *                Foundation; either version 2 of the License, or (at
+ *                your option) any later version.
+ *
+ *                This program is distributed in the hope that it will
+ *                be useful, but WITHOUT ANY WARRANTY; without even the
+ *                implied warranty of MERCHANTABILITY or FITNESS FOR A
+ *                PARTICULAR PURPOSE.  See the GNU General Public
+ *                License for more details.
+ *
+ *                The GNU General Public License should be included with
+ *                this file.  If not, you can view it at
+ *                http://www.gnu.org/copyleft/gpl.html
+ *                or write to the Free Software Foundation, Inc., 59
+ *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * Revisions   :
+ *    $Log: list.h,v $
+ *    Revision 1.12  2002/03/26 22:29:55  swa
+ *    we have a new homepage!
+ *
+ *    Revision 1.11  2002/03/24 13:25:43  swa
+ *    name change related issues
+ *
+ *    Revision 1.10  2002/03/07 03:46:17  oes
+ *    Fixed compiler warnings
+ *
+ *    Revision 1.9  2001/10/23 21:21:03  jongfoster
+ *    New error handling - error codes are now jb_errs, not ints.
+ *    Changed the way map() handles out-of-memory, to dramatically
+ *    reduce the amount of error-checking clutter needed.
+ *
+ *    Revision 1.8  2001/09/16 17:30:24  jongfoster
+ *    Fixing a compiler warning.
+ *
+ *    Revision 1.7  2001/09/16 13:20:29  jongfoster
+ *    Rewrite of list library.  Now has seperate header and list_entry
+ *    structures.  Also added a large sprinking of assert()s to the list
+ *    code.
+ *
+ *    Revision 1.6  2001/08/05 16:06:20  jongfoster
+ *    Modifiying "struct map" so that there are now separate header and
+ *    "map_entry" structures.  This means that functions which modify a
+ *    map no longer need to return a pointer to the modified map.
+ *    Also, it no longer reverses the order of the entries (which may be
+ *    important with some advanced template substitutions).
+ *
+ *    Revision 1.5  2001/07/29 18:43:08  jongfoster
+ *    Changing #ifdef _FILENAME_H to FILENAME_H_INCLUDED, to conform to
+ *    ANSI C rules.
+ *
+ *    Revision 1.4  2001/06/29 13:30:37  oes
+ *    - Introduced enlist_unique_header()
+ *    - Removed logentry from cancelled commit
+ *
+ *    Revision 1.3  2001/06/03 11:03:48  oes
+ *    introduced functions for new list type "map": map(), lookup(),
+ *    free_map(), and extended enlist_unique
+ *
+ *    Revision 1.2  2001/06/01 18:49:17  jongfoster
+ *    Replaced "list_share" with "list" - the tiny memory gain was not
+ *    worth the extra complexity.
+ *
+ *    Revision 1.1  2001/05/31 21:11:53  jongfoster
+ *    - Moved linked list support to new "list.c" file.
+ *      Structure definitions are still in project.h,
+ *      function prototypes are now in "list.h".
+ *    - Added support for "struct list_share", which is identical
+ *      to "struct list" except it saves memory by not duplicating
+ *      the strings.  Obviously, this only works if there is some
+ *      other way of managing the memory used by the strings.
+ *      (These list_share lists are used for lists which last
+ *      for only 1 request, and where all the list entries are
+ *      just coming directly from entries in the actionsfile.)
+ *      Note that you still need to destroy list_share lists
+ *      properly to free the nodes - it's only the strings
+ *      which are shared.
+ *
+ *
+ *********************************************************************/
+\f
+
+#include "project.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/*
+ * struct list
+ *
+ * A linked list class.
+ */
+
+extern void init_list    (struct list *the_list);
+extern void destroy_list (struct list *the_list);
+
+extern jb_err enlist                 (struct list *the_list, const char *str);
+extern jb_err enlist_unique          (struct list *the_list, const char *str, size_t num_significant_chars);
+extern jb_err enlist_unique_header   (struct list *the_list, const char *name, const char *value);
+extern jb_err enlist_first           (struct list *the_list, const char *str);
+extern jb_err list_append_list_unique(struct list *dest,     const struct list *src);
+extern jb_err list_duplicate         (struct list *dest,     const struct list *src);
+
+extern int    list_remove_item(struct list *the_list, const char *str);
+extern int    list_remove_list(struct list *dest,     const struct list *src);
+extern void   list_remove_all (struct list *the_list);
+
+extern int    list_is_empty(const struct list *the_list);
+
+extern char * list_to_text(const struct list *the_list);
+
+
+/*
+ * struct map
+ *
+ * A class which maps names to values.
+ *
+ * Note: You must allocate this through new_map() and free it
+ * through free_map().
+ */
+
+extern struct map * new_map  (void);
+extern void         free_map (struct map * the_map);
+
+extern int          map      (struct map * the_map,
+                              const char * name, int name_needs_copying,
+                              const char * value, int value_needs_copying);
+extern const char * lookup   (const struct map * the_map, const char * name);
+
+
+/* Revision control strings from this header and associated .c file */
+extern const char list_rcs[];
+extern const char list_h_rcs[];
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* ndef LIST_H_INCLUDED */
+
+/*
+  Local Variables:
+  tab-width: 3
+  end:
+*/
diff --git a/src/loadcfg.c b/src/loadcfg.c
new file mode 100644 (file)
index 0000000..30ca467
--- /dev/null
@@ -0,0 +1,1598 @@
+const char loadcfg_rcs[] = "$Id: loadcfg.c,v 1.48 2002/05/14 21:30:38 oes Exp $";
+/*********************************************************************
+ *
+ * File        :  $Source: /cvsroot/ijbswa/current/loadcfg.c,v $
+ *
+ * Purpose     :  Loads settings from the configuration file into
+ *                global variables.  This file contains both the
+ *                routine to load the configuration and the global
+ *                variables it writes to.
+ *
+ * Copyright   :  Written by and Copyright (C) 2001 the SourceForge
+ *                Privoxy team. http://www.privoxy.org/
+ *
+ *                Based on the Internet Junkbuster originally written
+ *                by and Copyright (C) 1997 Anonymous Coders and
+ *                Junkbusters Corporation.  http://www.junkbusters.com
+ *
+ *                This program is free software; you can redistribute it
+ *                and/or modify it under the terms of the GNU General
+ *                Public License as published by the Free Software
+ *                Foundation; either version 2 of the License, or (at
+ *                your option) any later version.
+ *
+ *                This program is distributed in the hope that it will
+ *                be useful, but WITHOUT ANY WARRANTY; without even the
+ *                implied warranty of MERCHANTABILITY or FITNESS FOR A
+ *                PARTICULAR PURPOSE.  See the GNU General Public
+ *                License for more details.
+ *
+ *                The GNU General Public License should be included with
+ *                this file.  If not, you can view it at
+ *                http://www.gnu.org/copyleft/gpl.html
+ *                or write to the Free Software Foundation, Inc., 59
+ *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * Revisions   :
+ *    $Log: loadcfg.c,v $
+ *    Revision 1.48  2002/05/14 21:30:38  oes
+ *    savearg now uses own linking code instead of (now special-cased) add_help_link
+ *
+ *    Revision 1.47  2002/05/12 21:36:29  jongfoster
+ *    Correcting function comments
+ *
+ *    Revision 1.46  2002/04/26 12:55:14  oes
+ *     - New option "user-manual", defaults to our site
+ *       via project.h #define
+ *     - savearg now embeds option names in help links
+ *
+ *    Revision 1.45  2002/04/24 02:11:54  oes
+ *    Jon's multiple AF patch: Allow up to MAX_ACTION_FILES actionsfile options
+ *
+ *    Revision 1.44  2002/04/08 20:37:13  swa
+ *    fixed JB spelling
+ *
+ *    Revision 1.43  2002/04/08 20:36:50  swa
+ *    fixed JB spelling
+ *
+ *    Revision 1.42  2002/04/05 15:50:15  oes
+ *    fix for invalid HTML proxy_args
+ *
+ *    Revision 1.41  2002/03/31 17:19:00  jongfoster
+ *    Win32 only: Enabling STRICT to fix a VC++ compile warning.
+ *
+ *    Revision 1.40  2002/03/26 22:29:55  swa
+ *    we have a new homepage!
+ *
+ *    Revision 1.39  2002/03/24 13:25:43  swa
+ *    name change related issues
+ *
+ *    Revision 1.38  2002/03/24 13:05:48  jongfoster
+ *    Renaming re_filterfile to filterfile
+ *
+ *    Revision 1.37  2002/03/16 23:54:06  jongfoster
+ *    Adding graceful termination feature, to help look for memory leaks.
+ *    If you enable this (which, by design, has to be done by hand
+ *    editing config.h) and then go to http://i.j.b/die, then the program
+ *    will exit cleanly after the *next* request.  It should free all the
+ *    memory that was used.
+ *
+ *    Revision 1.36  2002/03/13 00:27:05  jongfoster
+ *    Killing warnings
+ *
+ *    Revision 1.35  2002/03/07 03:52:44  oes
+ *    Set logging to tty for --no-daemon mode
+ *
+ *    Revision 1.34  2002/03/06 23:14:35  jongfoster
+ *    Trivial cosmetic changes to make function comments easier to find.
+ *
+ *    Revision 1.33  2002/03/05 04:52:42  oes
+ *    Deleted non-errlog debugging code
+ *
+ *    Revision 1.32  2002/03/04 18:24:53  oes
+ *    Re-enabled output of unknown config directive hash
+ *
+ *    Revision 1.31  2002/03/03 15:07:20  oes
+ *    Re-enabled automatic config reloading
+ *
+ *    Revision 1.30  2002/01/22 23:31:43  jongfoster
+ *    Replacing strsav() with string_append()
+ *
+ *    Revision 1.29  2002/01/17 21:02:30  jongfoster
+ *    Moving all our URL and URL pattern parsing code to urlmatch.c.
+ *
+ *    Renaming free_url to free_url_spec, since it frees a struct url_spec.
+ *
+ *    Revision 1.28  2001/12/30 14:07:32  steudten
+ *    - Add signal handling (unix)
+ *    - Add SIGHUP handler (unix)
+ *    - Add creation of pidfile (unix)
+ *    - Add action 'top' in rc file (RH)
+ *    - Add entry 'SIGNALS' to manpage
+ *    - Add exit message to logfile (unix)
+ *
+ *    Revision 1.27  2001/11/07 00:02:13  steudten
+ *    Add line number in error output for lineparsing for
+ *    actionsfile and configfile.
+ *    Special handling for CLF added.
+ *
+ *    Revision 1.26  2001/11/05 21:41:43  steudten
+ *    Add changes to be a real daemon just for unix os.
+ *    (change cwd to /, detach from controlling tty, set
+ *    process group and session leader to the own process.
+ *    Add DBG() Macro.
+ *    Add some fatal-error log message for failed malloc().
+ *    Add '-d' if compiled with 'configure --with-debug' to
+ *    enable debug output.
+ *
+ *    Revision 1.25  2001/10/25 03:40:48  david__schmidt
+ *    Change in porting tactics: OS/2's EMX porting layer doesn't allow multiple
+ *    threads to call select() simultaneously.  So, it's time to do a real, live,
+ *    native OS/2 port.  See defines for __EMX__ (the porting layer) vs. __OS2__
+ *    (native). Both versions will work, but using __OS2__ offers multi-threading.
+ *
+ *    Revision 1.24  2001/10/23 21:40:30  jongfoster
+ *    Added support for enable-edit-actions and enable-remote-toggle config
+ *    file options.
+ *
+ *    Revision 1.23  2001/10/07 15:36:00  oes
+ *    Introduced new config option "buffer-limit"
+ *
+ *    Revision 1.22  2001/09/22 16:36:59  jongfoster
+ *    Removing unused parameter fs from read_config_line()
+ *
+ *    Revision 1.21  2001/09/16 17:10:43  jongfoster
+ *    Moving function savearg() here, since it was the only thing left in
+ *    showargs.c.
+ *
+ *    Revision 1.20  2001/07/30 22:08:36  jongfoster
+ *    Tidying up #defines:
+ *    - All feature #defines are now of the form FEATURE_xxx
+ *    - Permanently turned off WIN_GUI_EDIT
+ *    - Permanently turned on WEBDAV and SPLIT_PROXY_ARGS
+ *
+ *    Revision 1.19  2001/07/15 17:45:16  jongfoster
+ *    Removing some unused #includes
+ *
+ *    Revision 1.18  2001/07/13 14:01:14  oes
+ *     - Removed all #ifdef PCRS
+ *     - Removed vim-settings
+ *
+ *    Revision 1.17  2001/06/29 13:31:03  oes
+ *    - Improved comments
+ *    - Fixed (actionsfile) and sorted hashes
+ *    - Introduced admin_address and proxy-info-url
+ *      as config parameters
+ *    - Renamed config->proxy_args_invocation (which didn't have
+ *      the invocation but the options!) to config->proxy_args
+ *    - Various adaptions
+ *    - Removed logentry from cancelled commit
+ *
+ *    Revision 1.16  2001/06/09 10:55:28  jongfoster
+ *    Changing BUFSIZ ==> BUFFER_SIZE
+ *
+ *    Revision 1.15  2001/06/07 23:13:40  jongfoster
+ *    Merging ACL and forward files into config file.
+ *    Cosmetic: Sorting config file options alphabetically.
+ *    Cosmetic: Adding brief syntax comments to config file options.
+ *
+ *    Revision 1.14  2001/06/07 14:46:25  joergs
+ *    Missing make_path() added for re_filterfile.
+ *
+ *    Revision 1.13  2001/06/05 22:33:54  jongfoster
+ *
+ *    Fixed minor memory leak.
+ *    Also now uses make_path to prepend the pathnames.
+ *
+ *    Revision 1.12  2001/06/05 20:04:09  jongfoster
+ *    Now uses _snprintf() in place of snprintf() under Win32.
+ *
+ *    Revision 1.11  2001/06/04 18:31:58  swa
+ *    files are now prefixed with either `confdir' or `logdir'.
+ *    `make redhat-dist' replaces both entries confdir and logdir
+ *    with redhat values
+ *
+ *    Revision 1.10  2001/06/03 19:11:54  oes
+ *    introduced confdir option
+ *
+ *    Revision 1.9  2001/06/01 20:06:24  jongfoster
+ *    Removed support for "tinygif" option - moved to actions file.
+ *
+ *    Revision 1.8  2001/05/31 21:27:13  jongfoster
+ *    Removed many options from the config file and into the
+ *    "actions" file: add_forwarded, suppress_vanilla_wafer,
+ *    wafer, add_header, user_agent, referer, from
+ *    Also globally replaced "permission" with "action".
+ *
+ *    Revision 1.7  2001/05/29 09:50:24  jongfoster
+ *    Unified blocklist/imagelist/permissionslist.
+ *    File format is still under discussion, but the internal changes
+ *    are (mostly) done.
+ *
+ *    Also modified interceptor behaviour:
+ *    - We now intercept all URLs beginning with one of the following
+ *      prefixes (and *only* these prefixes):
+ *        * http://i.j.b/
+ *        * http://ijbswa.sf.net/config/
+ *        * http://ijbswa.sourceforge.net/config/
+ *    - New interceptors "home page" - go to http://i.j.b/ to see it.
+ *    - Internal changes so that intercepted and fast redirect pages
+ *      are not replaced with an image.
+ *    - Interceptors now have the option to send a binary page direct
+ *      to the client. (i.e. ijb-send-banner uses this)
+ *    - Implemented show-url-info interceptor.  (Which is why I needed
+ *      the above interceptors changes - a typical URL is
+ *      "http://i.j.b/show-url-info?url=www.somesite.com/banner.gif".
+ *      The previous mechanism would not have intercepted that, and
+ *      if it had been intercepted then it then it would have replaced
+ *      it with an image.)
+ *
+ *    Revision 1.6  2001/05/26 00:28:36  jongfoster
+ *    Automatic reloading of config file.
+ *    Removed obsolete SIGHUP support (Unix) and Reload menu option (Win32).
+ *    Most of the global variables have been moved to a new
+ *    struct configuration_spec, accessed through csp->config->globalname
+ *    Most of the globals remaining are used by the Win32 GUI.
+ *
+ *    Revision 1.5  2001/05/25 22:34:30  jongfoster
+ *    Hard tabs->Spaces
+ *
+ *    Revision 1.4  2001/05/22 18:46:04  oes
+ *
+ *    - Enabled filtering banners by size rather than URL
+ *      by adding patterns that replace all standard banner
+ *      sizes with the "Junkbuster" gif to the re_filterfile
+ *
+ *    - Enabled filtering WebBugs by providing a pattern
+ *      which kills all 1x1 images
+ *
+ *    - Added support for PCRE_UNGREEDY behaviour to pcrs,
+ *      which is selected by the (nonstandard and therefore
+ *      capital) letter 'U' in the option string.
+ *      It causes the quantifiers to be ungreedy by default.
+ *      Appending a ? turns back to greedy (!).
+ *
+ *    - Added a new interceptor ijb-send-banner, which
+ *      sends back the "Junkbuster" gif. Without imagelist or
+ *      MSIE detection support, or if tinygif = 1, or the
+ *      URL isn't recognized as an imageurl, a lame HTML
+ *      explanation is sent instead.
+ *
+ *    - Added new feature, which permits blocking remote
+ *      script redirects and firing back a local redirect
+ *      to the browser.
+ *      The feature is conditionally compiled, i.e. it
+ *      can be disabled with --disable-fast-redirects,
+ *      plus it must be activated by a "fast-redirects"
+ *      line in the config file, has its own log level
+ *      and of course wants to be displayed by show-proxy-args
+ *      Note: Boy, all the #ifdefs in 1001 locations and
+ *      all the fumbling with configure.in and acconfig.h
+ *      were *way* more work than the feature itself :-(
+ *
+ *    - Because a generic redirect template was needed for
+ *      this, tinygif = 3 now uses the same.
+ *
+ *    - Moved GIFs, and other static HTTP response templates
+ *      to project.h
+ *
+ *    - Some minor fixes
+ *
+ *    - Removed some >400 CRs again (Jon, you really worked
+ *      a lot! ;-)
+ *
+ *    Revision 1.3  2001/05/20 01:21:20  jongfoster
+ *    Version 2.9.4 checkin.
+ *    - Merged popupfile and cookiefile, and added control over PCRS
+ *      filtering, in new "permissionsfile".
+ *    - Implemented LOG_LEVEL_FATAL, so that if there is a configuration
+ *      file error you now get a message box (in the Win32 GUI) rather
+ *      than the program exiting with no explanation.
+ *    - Made killpopup use the PCRS MIME-type checking and HTTP-header
+ *      skipping.
+ *    - Removed tabs from "config"
+ *    - Moved duplicated url parsing code in "loaders.c" to a new funcition.
+ *    - Bumped up version number.
+ *
+ *    Revision 1.2  2001/05/17 23:01:01  oes
+ *     - Cleaned CRLF's from the sources and related files
+ *
+ *    Revision 1.1.1.1  2001/05/15 13:58:58  oes
+ *    Initial import of version 2.9.3 source tree
+ *
+ *
+ *********************************************************************/
+\f
+
+#include "config.h"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <stdlib.h>
+#include <string.h>
+#include <signal.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <ctype.h>
+#include <assert.h>
+
+#ifdef _WIN32
+
+# ifndef STRICT
+#  define STRICT
+# endif
+# include <windows.h>
+
+# include "win32.h"
+# ifndef _WIN_CONSOLE
+#  include "w32log.h"
+# endif /* ndef _WIN_CONSOLE */
+
+/* VC++ has "_snprintf", not "snprintf" */
+#define snprintf _snprintf
+
+#else /* ifndef _WIN32 */
+
+#ifndef __OS2__
+# include <unistd.h>
+# include <sys/wait.h>
+#endif
+# include <sys/time.h>
+# include <sys/stat.h>
+# include <signal.h>
+
+#endif
+
+#include "loadcfg.h"
+#include "list.h"
+#include "jcc.h"
+#include "filters.h"
+#include "loaders.h"
+#include "miscutil.h"
+#include "errlog.h"
+#include "ssplit.h"
+#include "encode.h"
+#include "urlmatch.h"
+#include "cgi.h"
+
+const char loadcfg_h_rcs[] = LOADCFG_H_VERSION;
+
+/*
+ * Fix a problem with Solaris.  There should be no effect on other
+ * platforms.
+ * Solaris's isspace() is a macro which uses it's argument directly
+ * as an array index.  Therefore we need to make sure that high-bit
+ * characters generate +ve values, and ideally we also want to make
+ * the argument match the declared parameter type of "int".
+ */
+#define ijb_isupper(__X) isupper((int)(unsigned char)(__X))
+#define ijb_tolower(__X) tolower((int)(unsigned char)(__X))
+
+#ifdef FEATURE_TOGGLE
+/* by haroon - indicates if ijb is enabled */
+int g_bToggleIJB        = 1;   /* Privoxy is enabled by default. */
+#endif /* def FEATURE_TOGGLE */
+
+/* The filename of the configfile */
+const char *configfile  = NULL;
+
+/*
+ * CGI functions will later need access to the invocation args,
+ * so we will make argc and argv global.
+ */
+int Argc = 0;
+const char **Argv = NULL;
+
+static struct file_list *current_configfile = NULL;
+
+
+/*
+ * This takes the "cryptic" hash of each keyword and aliases them to
+ * something a little more readable.  This also makes changing the
+ * hash values easier if they should change or the hash algorthm changes.
+ * Use the included "hash" program to find out what the hash will be
+ * for any string supplied on the command line.  (Or just put it in the
+ * config file and read the number from the error message in the log).
+ *
+ * Please keep this list sorted alphabetically (but with the Windows
+ * console and GUI specific options last).
+ */
+
+#define hash_actions_file              1196306641ul /* "actionsfile" */
+#define hash_admin_address             4112573064ul /* "admin-address" */
+#define hash_buffer_limit              1881726070ul /* "buffer-limit */
+#define hash_confdir                      1978389ul /* "confdir" */
+#define hash_debug                          78263ul /* "debug" */
+#define hash_deny_access               1227333715ul /* "deny-access" */
+#define hash_enable_edit_actions       2517097536ul /* "enable-edit-actions" */
+#define hash_enable_remote_toggle      2979744683ul /* "enable-remote-toggle" */
+#define hash_filterfile                 250887266ul /* "filterfile" */
+#define hash_forward                      2029845ul /* "forward" */
+#define hash_forward_socks4            3963965521ul /* "forward-socks4" */
+#define hash_forward_socks4a           2639958518ul /* "forward-socks4a" */
+#define hash_jarfile                      2046641ul /* "jarfile" */
+#define hash_listen_address            1255650842ul /* "listen-address" */
+#define hash_logdir                        422889ul /* "logdir" */
+#define hash_logfile                      2114766ul /* "logfile" */
+#define hash_permit_access             3587953268ul /* "permit-access" */
+#define hash_proxy_info_url            3903079059ul /* "proxy-info-url" */
+#define hash_single_threaded           4250084780ul /* "single-threaded" */
+#define hash_suppress_blocklists       1948693308ul /* "suppress-blocklists" */
+#define hash_toggle                        447966ul /* "toggle" */
+#define hash_trust_info_url             430331967ul /* "trust-info-url" */
+#define hash_trustfile                   56494766ul /* "trustfile" */
+#define hash_usermanual                1416668518ul /* "user-manual" */
+#define hash_activity_animation        1817904738ul /* "activity-animation" */
+#define hash_close_button_minimizes    3651284693ul /* "close-button-minimizes" */
+#define hash_hide_console              2048809870ul /* "hide-console" */
+#define hash_log_buffer_size           2918070425ul /* "log-buffer-size" */
+#define hash_log_font_name             2866730124ul /* "log-font-name" */
+#define hash_log_font_size             2866731014ul /* "log-font-size" */
+#define hash_log_highlight_messages    4032101240ul /* "log-highlight-messages" */
+#define hash_log_max_lines             2868344173ul /* "log-max-lines" */
+#define hash_log_messages              2291744899ul /* "log-messages" */
+#define hash_show_on_task_bar           215410365ul /* "show-on-task-bar" */
+
+
+static void savearg(char *command, char *argument, struct configuration_spec * config);
+
+/*********************************************************************
+ *
+ * Function    :  unload_configfile
+ *
+ * Description :  Free the config structure and all components.
+ *
+ * Parameters  :
+ *          1  :  data: struct configuration_spec to unload
+ *
+ * Returns     :  N/A
+ *
+ *********************************************************************/
+void unload_configfile (void * data)
+{
+   struct configuration_spec * config = (struct configuration_spec *)data;
+   struct forward_spec *cur_fwd = config->forward;
+#ifdef FEATURE_ACL
+   struct access_control_list *cur_acl = config->acl;
+   int i;
+
+   while (cur_acl != NULL)
+   {
+      struct access_control_list * next_acl = cur_acl->next;
+      free(cur_acl);
+      cur_acl = next_acl;
+   }
+   config->acl = NULL;
+#endif /* def FEATURE_ACL */
+
+   while (cur_fwd != NULL)
+   {
+      struct forward_spec * next_fwd = cur_fwd->next;
+      free_url_spec(cur_fwd->url);
+
+      freez(cur_fwd->gateway_host);
+      freez(cur_fwd->forward_host);
+      free(cur_fwd);
+      cur_fwd = next_fwd;
+   }
+   config->forward = NULL;
+
+#ifdef FEATURE_COOKIE_JAR
+   if ( NULL != config->jar )
+   {
+      fclose( config->jar );
+      config->jar = NULL;
+   }
+#endif /* def FEATURE_COOKIE_JAR */
+
+   freez(config->confdir);
+   freez(config->logdir);
+
+   freez(config->haddr);
+   freez(config->logfile);
+
+   for (i = 0; i < MAX_ACTION_FILES; i++)
+   {
+      freez(config->actions_file_short[i]);
+      freez(config->actions_file[i]);
+   }
+
+   freez(config->admin_address);
+   freez(config->proxy_info_url);
+   freez(config->proxy_args);
+   freez(config->usermanual);
+
+#ifdef FEATURE_COOKIE_JAR
+   freez(config->jarfile);
+#endif /* def FEATURE_COOKIE_JAR */
+
+   freez(config->re_filterfile);
+
+}
+
+
+#ifdef FEATURE_GRACEFUL_TERMINATION
+/*********************************************************************
+ *
+ * Function    :  unload_current_config_file
+ *
+ * Description :  Unloads current config file - reset to state at
+ *                beginning of program.
+ *
+ * Parameters  :  None
+ *
+ * Returns     :  N/A
+ *
+ *********************************************************************/
+void unload_current_config_file(void)
+{
+   if (current_configfile)
+   {
+      current_configfile->unloader = unload_configfile;
+      current_configfile = NULL;
+   }
+}
+#endif
+
+
+/*********************************************************************
+ *
+ * Function    :  load_config
+ *
+ * Description :  Load the config file and all parameters.
+ *
+ * Parameters  :  None
+ *
+ * Returns     :  The configuration_spec, or NULL on error.
+ *
+ *********************************************************************/
+struct configuration_spec * load_config(void)
+{
+   char buf[BUFFER_SIZE];
+   char *p, *q;
+   FILE *configfp = NULL;
+   struct configuration_spec * config = NULL;
+   struct client_state * fake_csp;
+   struct file_list *fs;
+   unsigned long linenum = 0;
+   int i;
+
+   if ( !check_file_changed(current_configfile, configfile, &fs))
+   {
+      /* No need to load */
+      return ((struct configuration_spec *)current_configfile->f);
+   }
+   if (!fs)
+   {
+      log_error(LOG_LEVEL_FATAL, "can't check configuration file '%s':  %E",
+                configfile);
+   }
+
+   log_error(LOG_LEVEL_INFO, "loading configuration file '%s':", configfile);
+
+#ifdef FEATURE_TOGGLE
+   g_bToggleIJB      = 1;
+#endif /* def FEATURE_TOGGLE */
+
+   fs->f = config = (struct configuration_spec *)zalloc(sizeof(*config));
+
+   if (config==NULL)
+   {
+      freez(fs->filename);
+      freez(fs);
+      log_error(LOG_LEVEL_FATAL, "can't allocate memory for configuration");
+      /* Never get here - LOG_LEVEL_FATAL causes program exit */
+   }
+
+   /*
+    * This is backwards from how it's usually done.
+    * Following the usual pattern, "fs" would be stored in a member
+    * variable in "csp", and then we'd access "config" from "fs->f",
+    * using a cast.  However, "config" is used so often that a
+    * cast each time would be very ugly, and the extra indirection
+    * would waste CPU cycles.  Therefore we store "config" in
+    * "csp->config", and "fs" in "csp->config->config_file_list".
+    */
+   config->config_file_list = fs;
+
+   /*
+    * Set to defaults
+    */
+   config->multi_threaded    = 1;
+   config->hport             = HADDR_PORT;
+   config->buffer_limit      = 4096 * 1024;
+   config->usermanual        = strdup(USER_MANUAL_URL);
+   config->proxy_args        = strdup("");
+
+   if ((configfp = fopen(configfile, "r")) == NULL)
+   {
+      log_error(LOG_LEVEL_FATAL, "can't open configuration file '%s':  %E",
+              configfile);
+      /* Never get here - LOG_LEVEL_FATAL causes program exit */
+   }
+
+   while (read_config_line(buf, sizeof(buf), configfp, &linenum) != NULL)
+   {
+      char cmd[BUFFER_SIZE];
+      char arg[BUFFER_SIZE];
+      char tmp[BUFFER_SIZE];
+#ifdef FEATURE_ACL
+      struct access_control_list *cur_acl;
+#endif /* def FEATURE_ACL */
+      struct forward_spec *cur_fwd;
+      int vec_count;
+      char *vec[3];
+
+      strcpy(tmp, buf);
+
+      /* Copy command (i.e. up to space or tab) into cmd */
+      p = buf;
+      q = cmd;
+      while (*p && (*p != ' ') && (*p != '\t'))
+      {
+         *q++ = *p++;
+      }
+      *q = '\0';
+
+      /* Skip over the whitespace in buf */
+      while (*p && ((*p == ' ') || (*p == '\t')))
+      {
+         p++;
+      }
+
+      /* Copy the argument into arg */
+      strcpy(arg, p);
+
+      /* Should never happen, but check this anyway */
+      if (*cmd == '\0')
+      {
+         continue;
+      }
+
+      /* Make sure the command field is lower case */
+      for (p=cmd; *p; p++)
+      {
+         if (ijb_isupper(*p))
+         {
+            *p = ijb_tolower(*p);
+         }
+      }
+
+      /* Save the argument for show-proxy-args */
+      savearg(cmd, arg, config);
+
+
+      switch( hash_string( cmd ) )
+      {
+/* *************************************************************************
+ * actionsfile actions-file-name
+ * In confdir by default
+ * *************************************************************************/
+         case hash_actions_file :
+            i = 0;
+            while ((i < MAX_ACTION_FILES) && (NULL != config->actions_file[i]))
+            {
+               i++;
+            }
+
+            if (i >= MAX_ACTION_FILES)
+            {
+               log_error(LOG_LEVEL_FATAL, "Too many 'actionsfile' directives in config file - limit is %d.\n"
+                  "(You can increase this limit by changing MAX_ACTION_FILES in project.h and recompiling).",
+                  MAX_ACTION_FILES);
+            }
+            config->actions_file_short[i] = strdup(arg);
+            p = malloc(strlen(arg) + sizeof(".action"));
+            if (p == NULL)
+            {
+               log_error(LOG_LEVEL_FATAL, "Out of memory");
+            }
+            strcpy(p, arg);
+            strcat(p, ".action");
+            config->actions_file[i] = make_path(config->confdir, p);
+            free(p);
+            continue;
+
+/* *************************************************************************
+ * admin-address email-address
+ * *************************************************************************/
+         case hash_admin_address :
+            freez(config->admin_address);
+            config->admin_address = strdup(arg);
+            continue;
+
+/* *************************************************************************
+ * buffer-limit n
+ * *************************************************************************/
+         case hash_buffer_limit :
+            config->buffer_limit = (size_t) 1024 * atoi(arg);
+            continue;
+
+/* *************************************************************************
+ * confdir directory-name
+ * *************************************************************************/
+         case hash_confdir :
+            freez(config->confdir);
+            config->confdir = make_path( NULL, arg);
+            continue;
+
+/* *************************************************************************
+ * debug n
+ * Specifies debug level, multiple values are ORed together.
+ * *************************************************************************/
+         case hash_debug :
+            config->debug |= atoi(arg);
+            continue;
+
+/* *************************************************************************
+ * deny-access source-ip[/significant-bits] [dest-ip[/significant-bits]]
+ * *************************************************************************/
+#ifdef FEATURE_ACL
+         case hash_deny_access:
+            vec_count = ssplit(arg, " \t", vec, SZ(vec), 1, 1);
+
+            if ((vec_count != 1) && (vec_count != 2))
+            {
+               log_error(LOG_LEVEL_ERROR, "Wrong number of parameters for "
+                     "deny-access directive in configuration file.");
+               string_append(&config->proxy_args,
+                  "<br>\nWARNING: Wrong number of parameters for "
+                  "deny-access directive in configuration file.<br><br>\n");
+               continue;
+            }
+
+            /* allocate a new node */
+            cur_acl = (struct access_control_list *) zalloc(sizeof(*cur_acl));
+
+            if (cur_acl == NULL)
+            {
+               log_error(LOG_LEVEL_FATAL, "can't allocate memory for configuration");
+               /* Never get here - LOG_LEVEL_FATAL causes program exit */
+               continue;
+            }
+            cur_acl->action = ACL_DENY;
+
+            if (acl_addr(vec[0], cur_acl->src) < 0)
+            {
+               log_error(LOG_LEVEL_ERROR, "Invalid source IP for deny-access "
+                     "directive in configuration file: \"%s\"", vec[0]);
+               string_append(&config->proxy_args,
+                  "<br>\nWARNING: Invalid source IP for deny-access directive"
+                  " in configuration file: \"");
+               string_append(&config->proxy_args,
+                  vec[0]);
+               string_append(&config->proxy_args,
+                  "\"<br><br>\n");
+               freez(cur_acl);
+               continue;
+            }
+            if (vec_count == 2)
+            {
+               if (acl_addr(vec[1], cur_acl->dst) < 0)
+               {
+                  log_error(LOG_LEVEL_ERROR, "Invalid destination IP for deny-access "
+                        "directive in configuration file: \"%s\"", vec[0]);
+                  string_append(&config->proxy_args,
+                     "<br>\nWARNING: Invalid destination IP for deny-access directive"
+                     " in configuration file: \"");
+                  string_append(&config->proxy_args,
+                     vec[0]);
+                  string_append(&config->proxy_args,
+                     "\"<br><br>\n");
+                  freez(cur_acl);
+                  continue;
+               }
+            }
+
+            /*
+             * Add it to the list.  Note we reverse the list to get the
+             * behaviour the user expects.  With both the ACL and
+             * actions file, the last match wins.  However, the internal
+             * implementations are different:  The actions file is stored
+             * in the same order as the file, and scanned completely.
+             * With the ACL, we reverse the order as we load it, then
+             * when we scan it we stop as soon as we get a match.
+             */
+            cur_acl->next  = config->acl;
+            config->acl = cur_acl;
+
+            continue;
+#endif /* def FEATURE_ACL */
+
+/* *************************************************************************
+ * enable-edit-actions 0|1
+ * *************************************************************************/
+#ifdef FEATURE_CGI_EDIT_ACTIONS
+         case hash_enable_edit_actions:
+            if ((*arg != '\0') && (0 != atoi(arg)))
+            {
+               config->feature_flags |= RUNTIME_FEATURE_CGI_EDIT_ACTIONS;
+            }
+            else
+            {
+               config->feature_flags &= ~RUNTIME_FEATURE_CGI_EDIT_ACTIONS;
+            }
+            continue;
+#endif /* def FEATURE_CGI_EDIT_ACTIONS */
+
+/* *************************************************************************
+ * enable-remote-toggle 0|1
+ * *************************************************************************/
+#ifdef FEATURE_CGI_EDIT_ACTIONS
+         case hash_enable_remote_toggle:
+            if ((*arg != '\0') && (0 != atoi(arg)))
+            {
+               config->feature_flags |= RUNTIME_FEATURE_CGI_TOGGLE;
+            }
+            else
+            {
+               config->feature_flags &= ~RUNTIME_FEATURE_CGI_TOGGLE;
+            }
+            continue;
+#endif /* def FEATURE_CGI_EDIT_ACTIONS */
+
+/* *************************************************************************
+ * forward url-pattern (.|http-proxy-host[:port])
+ * *************************************************************************/
+         case hash_forward:
+            vec_count = ssplit(arg, " \t", vec, SZ(vec), 1, 1);
+
+            if (vec_count != 2)
+            {
+               log_error(LOG_LEVEL_ERROR, "Wrong number of parameters for forward "
+                     "directive in configuration file.");
+               string_append(&config->proxy_args,
+                  "<br>\nWARNING: Wrong number of parameters for "
+                  "forward directive in configuration file.");
+               continue;
+            }
+
+            /* allocate a new node */
+            cur_fwd = zalloc(sizeof(*cur_fwd));
+            if (cur_fwd == NULL)
+            {
+               log_error(LOG_LEVEL_FATAL, "can't allocate memory for configuration");
+               /* Never get here - LOG_LEVEL_FATAL causes program exit */
+               continue;
+            }
+
+            cur_fwd->type = SOCKS_NONE;
+
+            /* Save the URL pattern */
+            if (create_url_spec(cur_fwd->url, vec[0]))
+            {
+               log_error(LOG_LEVEL_ERROR, "Bad URL specifier for forward "
+                     "directive in configuration file.");
+               string_append(&config->proxy_args,
+                  "<br>\nWARNING: Bad URL specifier for "
+                  "forward directive in configuration file.");
+               continue;
+            }
+
+            /* Parse the parent HTTP proxy host:port */
+            p = vec[1];
+
+            if (strcmp(p, ".") != 0)
+            {
+               cur_fwd->forward_host = strdup(p);
+
+               if (NULL != (p = strchr(cur_fwd->forward_host, ':')))
+               {
+                  *p++ = '\0';
+                  cur_fwd->forward_port = atoi(p);
+               }
+
+               if (cur_fwd->forward_port <= 0)
+               {
+                  cur_fwd->forward_port = 8000;
+               }
+            }
+
+            /* Add to list. */
+            cur_fwd->next = config->forward;
+            config->forward = cur_fwd;
+
+            continue;
+
+/* *************************************************************************
+ * forward-socks4 url-pattern socks-proxy[:port] (.|http-proxy[:port])
+ * *************************************************************************/
+         case hash_forward_socks4:
+            vec_count = ssplit(arg, " \t", vec, SZ(vec), 1, 1);
+
+            if (vec_count != 3)
+            {
+               log_error(LOG_LEVEL_ERROR, "Wrong number of parameters for "
+                     "forward-socks4 directive in configuration file.");
+               string_append(&config->proxy_args,
+                  "<br>\nWARNING: Wrong number of parameters for "
+                  "forward-socks4 directive in configuration file.");
+               continue;
+            }
+
+            /* allocate a new node */
+            cur_fwd = zalloc(sizeof(*cur_fwd));
+            if (cur_fwd == NULL)
+            {
+               log_error(LOG_LEVEL_FATAL, "can't allocate memory for configuration");
+               /* Never get here - LOG_LEVEL_FATAL causes program exit */
+               continue;
+            }
+
+            cur_fwd->type = SOCKS_4;
+
+            /* Save the URL pattern */
+            if (create_url_spec(cur_fwd->url, vec[0]))
+            {
+               log_error(LOG_LEVEL_ERROR, "Bad URL specifier for forward-socks4 "
+                     "directive in configuration file.");
+               string_append(&config->proxy_args,
+                  "<br>\nWARNING: Bad URL specifier for "
+                  "forward-socks4 directive in configuration file.");
+               continue;
+            }
+
+            /* Parse the SOCKS proxy host[:port] */
+            p = vec[1];
+
+            if (strcmp(p, ".") != 0)
+            {
+               cur_fwd->gateway_host = strdup(p);
+
+               if (NULL != (p = strchr(cur_fwd->gateway_host, ':')))
+               {
+                  *p++ = '\0';
+                  cur_fwd->gateway_port = atoi(p);
+               }
+               if (cur_fwd->gateway_port <= 0)
+               {
+                  cur_fwd->gateway_port = 1080;
+               }
+            }
+
+            /* Parse the parent HTTP proxy host[:port] */
+            p = vec[2];
+
+            if (strcmp(p, ".") != 0)
+            {
+               cur_fwd->forward_host = strdup(p);
+
+               if (NULL != (p = strchr(cur_fwd->forward_host, ':')))
+               {
+                  *p++ = '\0';
+                  cur_fwd->forward_port = atoi(p);
+               }
+
+               if (cur_fwd->forward_port <= 0)
+               {
+                  cur_fwd->forward_port = 8000;
+               }
+            }
+
+            /* Add to list. */
+            cur_fwd->next = config->forward;
+            config->forward = cur_fwd;
+
+            continue;
+
+/* *************************************************************************
+ * forward-socks4a url-pattern socks-proxy[:port] (.|http-proxy[:port])
+ * *************************************************************************/
+         case hash_forward_socks4a:
+            vec_count = ssplit(arg, " \t", vec, SZ(vec), 1, 1);
+
+            if (vec_count != 3)
+            {
+               log_error(LOG_LEVEL_ERROR, "Wrong number of parameters for "
+                     "forward-socks4a directive in configuration file.");
+               string_append(&config->proxy_args,
+                  "<br>\nWARNING: Wrong number of parameters for "
+                  "forward-socks4a directive in configuration file.");
+               continue;
+            }
+
+            /* allocate a new node */
+            cur_fwd = zalloc(sizeof(*cur_fwd));
+            if (cur_fwd == NULL)
+            {
+               log_error(LOG_LEVEL_FATAL, "can't allocate memory for configuration");
+               /* Never get here - LOG_LEVEL_FATAL causes program exit */
+               continue;
+            }
+
+            cur_fwd->type = SOCKS_4A;
+
+            /* Save the URL pattern */
+            if (create_url_spec(cur_fwd->url, vec[0]))
+            {
+               log_error(LOG_LEVEL_ERROR, "Bad URL specifier for forward-socks4a "
+                     "directive in configuration file.");
+               string_append(&config->proxy_args,
+                  "<br>\nWARNING: Bad URL specifier for "
+                  "forward-socks4a directive in configuration file.");
+               continue;
+            }
+
+            /* Parse the SOCKS proxy host[:port] */
+            p = vec[1];
+
+            cur_fwd->gateway_host = strdup(p);
+
+            if (NULL != (p = strchr(cur_fwd->gateway_host, ':')))
+            {
+               *p++ = '\0';
+               cur_fwd->gateway_port = atoi(p);
+            }
+            if (cur_fwd->gateway_port <= 0)
+            {
+               cur_fwd->gateway_port = 1080;
+            }
+
+            /* Parse the parent HTTP proxy host[:port] */
+            p = vec[2];
+
+            if (strcmp(p, ".") != 0)
+            {
+               cur_fwd->forward_host = strdup(p);
+
+               if (NULL != (p = strchr(cur_fwd->forward_host, ':')))
+               {
+                  *p++ = '\0';
+                  cur_fwd->forward_port = atoi(p);
+               }
+
+               if (cur_fwd->forward_port <= 0)
+               {
+                  cur_fwd->forward_port = 8000;
+               }
+            }
+
+            /* Add to list. */
+            cur_fwd->next = config->forward;
+            config->forward = cur_fwd;
+
+            continue;
+
+/* *************************************************************************
+ * jarfile jar-file-name
+ * In logdir by default
+ * *************************************************************************/
+#ifdef FEATURE_COOKIE_JAR
+         case hash_jarfile :
+            freez(config->jarfile);
+            config->jarfile = make_path(config->logdir, arg);
+            continue;
+#endif /* def FEATURE_COOKIE_JAR */
+
+/* *************************************************************************
+ * listen-address [ip][:port]
+ * *************************************************************************/
+         case hash_listen_address :
+            freez(config->haddr);
+            config->haddr = strdup(arg);
+            continue;
+
+/* *************************************************************************
+ * logdir directory-name
+ * *************************************************************************/
+         case hash_logdir :
+            freez(config->logdir);
+            config->logdir = make_path(NULL, arg);
+            continue;
+
+/* *************************************************************************
+ * logfile log-file-name
+ * In logdir by default
+ * *************************************************************************/
+         case hash_logfile :
+            freez(config->logfile);
+            config->logfile = no_daemon ? NULL : make_path(config->logdir, arg);
+            continue;
+
+/* *************************************************************************
+ * permit-access source-ip[/significant-bits] [dest-ip[/significant-bits]]
+ * *************************************************************************/
+#ifdef FEATURE_ACL
+         case hash_permit_access:
+            vec_count = ssplit(arg, " \t", vec, SZ(vec), 1, 1);
+
+            if ((vec_count != 1) && (vec_count != 2))
+            {
+               log_error(LOG_LEVEL_ERROR, "Wrong number of parameters for "
+                     "permit-access directive in configuration file.");
+               string_append(&config->proxy_args,
+                  "<br>\nWARNING: Wrong number of parameters for "
+                  "permit-access directive in configuration file.<br><br>\n");
+
+               continue;
+            }
+
+            /* allocate a new node */
+            cur_acl = (struct access_control_list *) zalloc(sizeof(*cur_acl));
+
+            if (cur_acl == NULL)
+            {
+               log_error(LOG_LEVEL_FATAL, "can't allocate memory for configuration");
+               /* Never get here - LOG_LEVEL_FATAL causes program exit */
+               continue;
+            }
+            cur_acl->action = ACL_PERMIT;
+
+            if (acl_addr(vec[0], cur_acl->src) < 0)
+            {
+               log_error(LOG_LEVEL_ERROR, "Invalid source IP for permit-access "
+                     "directive in configuration file: \"%s\"", vec[0]);
+               string_append(&config->proxy_args,
+                  "<br>\nWARNING: Invalid source IP for permit-access directive"
+                  " in configuration file: \"");
+               string_append(&config->proxy_args,
+                  vec[0]);
+               string_append(&config->proxy_args,
+                  "\"<br><br>\n");
+               freez(cur_acl);
+               continue;
+            }
+            if (vec_count == 2)
+            {
+               if (acl_addr(vec[1], cur_acl->dst) < 0)
+               {
+                  log_error(LOG_LEVEL_ERROR, "Invalid destination IP for "
+                        "permit-access directive in configuration file: \"%s\"",
+                        vec[0]);
+                  string_append(&config->proxy_args,
+                     "<br>\nWARNING: Invalid destination IP for permit-access directive"
+                     " in configuration file: \"");
+                  string_append(&config->proxy_args,
+                     vec[0]);
+                  string_append(&config->proxy_args,
+                     "\"<br><br>\n");
+                  freez(cur_acl);
+                  continue;
+               }
+            }
+
+            /*
+             * Add it to the list.  Note we reverse the list to get the
+             * behaviour the user expects.  With both the ACL and
+             * actions file, the last match wins.  However, the internal
+             * implementations are different:  The actions file is stored
+             * in the same order as the file, and scanned completely.
+             * With the ACL, we reverse the order as we load it, then
+             * when we scan it we stop as soon as we get a match.
+             */
+            cur_acl->next  = config->acl;
+            config->acl = cur_acl;
+
+            continue;
+#endif /* def FEATURE_ACL */
+
+/* *************************************************************************
+ * proxy-info-url url
+ * *************************************************************************/
+         case hash_proxy_info_url :
+            freez(config->proxy_info_url);
+            config->proxy_info_url = strdup(arg);
+            continue;
+
+/* *************************************************************************
+ * re_filterfile file-name
+ * In confdir by default.
+ * *************************************************************************/
+         case hash_filterfile :
+            freez(config->re_filterfile);
+            config->re_filterfile = make_path(config->confdir, arg);
+            continue;
+
+/* *************************************************************************
+ * single-threaded
+ * *************************************************************************/
+         case hash_single_threaded :
+            config->multi_threaded = 0;
+            continue;
+
+/* *************************************************************************
+ * toggle (0|1)
+ * *************************************************************************/
+#ifdef FEATURE_TOGGLE
+         case hash_toggle :
+            g_bToggleIJB = atoi(arg);
+            continue;
+#endif /* def FEATURE_TOGGLE */
+
+/* *************************************************************************
+ * trust-info-url url
+ * *************************************************************************/
+#ifdef FEATURE_TRUST
+         case hash_trust_info_url :
+            enlist(config->trust_info, arg);
+            continue;
+#endif /* def FEATURE_TRUST */
+
+/* *************************************************************************
+ * trustfile filename
+ * (In confdir by default.)
+ * *************************************************************************/
+#ifdef FEATURE_TRUST
+         case hash_trustfile :
+            freez(config->trustfile);
+            config->trustfile = make_path(config->confdir, arg);
+            continue;
+#endif /* def FEATURE_TRUST */
+
+/* *************************************************************************
+ * usermanual url
+ * *************************************************************************/
+         case hash_usermanual :
+            freez(config->usermanual);
+            config->usermanual = strdup(arg);
+            continue;
+
+/* *************************************************************************
+ * Win32 Console options:
+ * *************************************************************************/
+
+/* *************************************************************************
+ * hide-console
+ * *************************************************************************/
+#ifdef _WIN_CONSOLE
+         case hash_hide_console :
+            hideConsole = 1;
+            continue;
+#endif /*def _WIN_CONSOLE*/
+
+
+/* *************************************************************************
+ * Win32 GUI options:
+ * *************************************************************************/
+
+#if defined(_WIN32) && ! defined(_WIN_CONSOLE)
+/* *************************************************************************
+ * activity-animation (0|1)
+ * *************************************************************************/
+         case hash_activity_animation :
+            g_bShowActivityAnimation = atoi(arg);
+            continue;
+
+/* *************************************************************************
+ *  close-button-minimizes (0|1)
+ * *************************************************************************/
+         case hash_close_button_minimizes :
+            g_bCloseHidesWindow = atoi(arg);
+            continue;
+
+/* *************************************************************************
+ * log-buffer-size (0|1)
+ * *************************************************************************/
+         case hash_log_buffer_size :
+            g_bLimitBufferSize = atoi(arg);
+            continue;
+
+/* *************************************************************************
+ * log-font-name fontnane
+ * *************************************************************************/
+         case hash_log_font_name :
+            strcpy( g_szFontFaceName, arg );
+            continue;
+
+/* *************************************************************************
+ * log-font-size n
+ * *************************************************************************/
+         case hash_log_font_size :
+            g_nFontSize = atoi(arg);
+            continue;
+
+/* *************************************************************************
+ * log-highlight-messages (0|1)
+ * *************************************************************************/
+         case hash_log_highlight_messages :
+            g_bHighlightMessages = atoi(arg);
+            continue;
+
+/* *************************************************************************
+ * log-max-lines n
+ * *************************************************************************/
+         case hash_log_max_lines :
+            g_nMaxBufferLines = atoi(arg);
+            continue;
+
+/* *************************************************************************
+ * log-messages (0|1)
+ * *************************************************************************/
+         case hash_log_messages :
+            g_bLogMessages = atoi(arg);
+            continue;
+
+/* *************************************************************************
+ * show-on-task-bar (0|1)
+ * *************************************************************************/
+         case hash_show_on_task_bar :
+            g_bShowOnTaskBar = atoi(arg);
+            continue;
+
+#endif /* defined(_WIN32) && ! defined(_WIN_CONSOLE) */
+
+
+/* *************************************************************************
+ * Warnings about unsupported features
+ * *************************************************************************/
+#ifndef FEATURE_ACL
+         case hash_deny_access:
+#endif /* ndef FEATURE_ACL */
+#ifndef FEATURE_CGI_EDIT_ACTIONS
+         case hash_enable_edit_actions:
+         case hash_enable_remote_toggle:
+#endif /* def FEATURE_CGI_EDIT_ACTIONS */
+#ifndef FEATURE_COOKIE_JAR
+         case hash_jarfile :
+#endif /* ndef FEATURE_COOKIE_JAR */
+#ifndef FEATURE_ACL
+         case hash_permit_access:
+#endif /* ndef FEATURE_ACL */
+#ifndef FEATURE_TOGGLE
+         case hash_toggle :
+#endif /* ndef FEATURE_TOGGLE */
+#ifndef FEATURE_TRUST
+         case hash_trustfile :
+         case hash_trust_info_url :
+#endif /* ndef FEATURE_TRUST */
+
+#ifndef _WIN_CONSOLE
+         case hash_hide_console :
+#endif /* ndef _WIN_CONSOLE */
+
+#if defined(_WIN_CONSOLE) || ! defined(_WIN32)
+         case hash_activity_animation :
+         case hash_close_button_minimizes :
+         case hash_log_buffer_size :
+         case hash_log_font_name :
+         case hash_log_font_size :
+         case hash_log_highlight_messages :
+         case hash_log_max_lines :
+         case hash_log_messages :
+         case hash_show_on_task_bar :
+#endif /* defined(_WIN_CONSOLE) || ! defined(_WIN32) */
+            /* These warnings are annoying - so hide them. -- Jon */
+            /* log_error(LOG_LEVEL_INFO, "Unsupported directive \"%s\" ignored.", cmd); */
+            continue;
+
+/* *************************************************************************/
+         default :
+/* *************************************************************************/
+            /*
+             * I decided that I liked this better as a warning than an
+             * error.  To change back to an error, just change log level
+             * to LOG_LEVEL_FATAL.
+             */
+            log_error(LOG_LEVEL_ERROR, "Unrecognized directive '%s' (%luul) in line %lu in "
+                  "configuration file (%s).",  buf, hash_string(cmd), linenum, configfile);
+            string_append(&config->proxy_args, "<br>\nWARNING: unrecognized directive : ");
+            string_append(&config->proxy_args, buf);
+            string_append(&config->proxy_args, "<br><br>\n");
+            continue;
+
+/* *************************************************************************/
+      } /* end switch( hash_string(cmd) ) */
+   } /* end while ( read_config_line(...) ) */
+
+   fclose(configfp);
+
+   if (NULL == config->proxy_args)
+   {
+      log_error(LOG_LEVEL_FATAL, "Out of memory loading config - insufficient memory for config->proxy_args");
+   }
+
+   init_error_log(Argv[0], config->logfile, config->debug);
+
+   if (config->actions_file[0])
+   {
+      add_loader(load_actions_file, config);
+   }
+
+   if (config->re_filterfile)
+   {
+      add_loader(load_re_filterfile, config);
+   }
+
+#ifdef FEATURE_TRUST
+   if (config->trustfile)
+   {
+      add_loader(load_trustfile, config);
+   }
+#endif /* def FEATURE_TRUST */
+
+#ifdef FEATURE_COOKIE_JAR
+   if ( NULL != config->jarfile )
+   {
+      if ( NULL == (config->jar = fopen(config->jarfile, "a")) )
+      {
+         log_error(LOG_LEVEL_FATAL, "can't open jarfile '%s': %E", config->jarfile);
+         /* Never get here - LOG_LEVEL_FATAL causes program exit */
+      }
+      setbuf(config->jar, NULL);
+   }
+#endif /* def FEATURE_COOKIE_JAR */
+
+   if ( NULL == config->haddr )
+   {
+      config->haddr = strdup( HADDR_DEFAULT );
+   }
+
+   if ( NULL != config->haddr )
+   {
+      if (NULL != (p = strchr(config->haddr, ':')))
+      {
+         *p++ = '\0';
+         if (*p)
+         {
+            config->hport = atoi(p);
+         }
+      }
+
+      if (config->hport <= 0)
+      {
+         *--p = ':';
+         log_error(LOG_LEVEL_FATAL, "invalid bind port spec %s", config->haddr);
+         /* Never get here - LOG_LEVEL_FATAL causes program exit */
+      }
+      if (*config->haddr == '\0')
+      {
+         config->haddr = NULL;
+      }
+   }
+
+   /*
+    * Want to run all the loaders once now.
+    *
+    * Need to set up a fake csp, so they can get to the config.
+    */
+   fake_csp = (struct client_state *) zalloc (sizeof(*fake_csp));
+   fake_csp->config = config;
+
+   if (run_loader(fake_csp))
+   {
+      freez(fake_csp);
+      log_error(LOG_LEVEL_FATAL, "A loader failed while loading config file. Exiting.");
+      /* Never get here - LOG_LEVEL_FATAL causes program exit */
+   }
+   freez(fake_csp);
+
+/* FIXME: this is a kludge for win32 */
+#if defined(_WIN32) && !defined (_WIN_CONSOLE)
+
+   g_actions_file     = config->actions_file[0]; /* FIXME only works for first action file */
+   g_re_filterfile    = config->re_filterfile;
+
+#ifdef FEATURE_TRUST
+   g_trustfile        = config->trustfile;
+#endif /* def FEATURE_TRUST */
+
+
+#endif /* defined(_WIN32) && !defined (_WIN_CONSOLE) */
+/* FIXME: end kludge */
+
+
+   config->need_bind = 1;
+
+   if (current_configfile)
+   {
+      struct configuration_spec * oldcfg = (struct configuration_spec *)
+                                           current_configfile->f;
+      /*
+       * Check if config->haddr,hport == oldcfg->haddr,hport
+       *
+       * The following could be written more compactly as a single,
+       * (unreadably long) if statement.
+       */
+      config->need_bind = 0;
+      if (config->hport != oldcfg->hport)
+      {
+         config->need_bind = 1;
+      }
+      else if (config->haddr == NULL)
+      {
+         if (oldcfg->haddr != NULL)
+         {
+            config->need_bind = 1;
+         }
+      }
+      else if (oldcfg->haddr == NULL)
+      {
+         config->need_bind = 1;
+      }
+      else if (0 != strcmp(config->haddr, oldcfg->haddr))
+      {
+         config->need_bind = 1;
+      }
+
+      current_configfile->unloader = unload_configfile;
+   }
+
+   fs->next = files->next;
+   files->next = fs;
+
+   current_configfile = fs;
+
+   return (config);
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  savearg
+ *
+ * Description :  Called from `load_config'.  It saves each non-empty
+ *                and non-comment line from config into
+ *                config->proxy_args.  This is used to create the
+ *                show-proxy-args page.  On error, frees
+ *                config->proxy_args and sets it to NULL
+ *
+ * Parameters  :
+ *          1  :  command = config setting that was found
+ *          2  :  argument = the setting's argument (if any)
+ *          3  :  config = Configuration to save into.
+ *
+ * Returns     :  N/A
+ *
+ *********************************************************************/
+static void savearg(char *command, char *argument, struct configuration_spec * config)
+{
+   char * buf;
+   char * s;
+
+   assert(command);
+   assert(*command);
+   assert(argument);
+
+   /*
+    * Add config option name embedded in
+    * link to it's section in the user-manual
+    */
+   buf = strdup("<a href=\"");
+   string_append(&buf, config->usermanual);
+   string_append(&buf, CONFIG_HELP_PREFIX);
+   string_join  (&buf, string_toupper(command));
+   string_append(&buf, "\">");
+   string_append(&buf, command);
+   string_append(&buf, "</a> ");
+
+   if (NULL == buf)
+   {
+      freez(config->proxy_args);
+      return;
+   }
+
+   if ( (NULL != argument) && ('\0' != *argument) )
+   {
+      s = html_encode(argument);
+      if (NULL == s)
+      {
+         freez(buf);
+         freez(config->proxy_args);
+         return;
+      }
+
+      if (strncmpic(argument, "http://", 7) == 0)
+      {
+         string_append(&buf, "<a href=\"");
+         string_append(&buf, s);
+         string_append(&buf, "\">");
+         string_join  (&buf, s);
+         string_append(&buf, "</a>");
+      }
+      else
+      {
+         string_join  (&buf, s);
+      }
+   }
+
+   string_append(&buf, "<br>\n");
+
+   string_join(&config->proxy_args, buf);
+}
+
+
+/*
+  Local Variables:
+  tab-width: 3
+  end:
+*/
diff --git a/src/loadcfg.h b/src/loadcfg.h
new file mode 100644 (file)
index 0000000..6216a90
--- /dev/null
@@ -0,0 +1,192 @@
+#ifndef LOADCFG_H_INCLUDED
+#define LOADCFG_H_INCLUDED
+#define LOADCFG_H_VERSION "$Id: loadcfg.h,v 1.11 2002/03/26 22:29:55 swa Exp $"
+/*********************************************************************
+ *
+ * File        :  $Source: /cvsroot/ijbswa/current/loadcfg.h,v $
+ *
+ * Purpose     :  Loads settings from the configuration file into
+ *                global variables.  This file contains both the 
+ *                routine to load the configuration and the global
+ *                variables it writes to.
+ *
+ * Copyright   :  Written by and Copyright (C) 2001 the SourceForge
+ *                Privoxy team. http://www.privoxy.org/
+ *
+ *                Based on the Internet Junkbuster originally written
+ *                by and Copyright (C) 1997 Anonymous Coders and 
+ *                Junkbusters Corporation.  http://www.junkbusters.com
+ *
+ *                This program is free software; you can redistribute it 
+ *                and/or modify it under the terms of the GNU General
+ *                Public License as published by the Free Software
+ *                Foundation; either version 2 of the License, or (at
+ *                your option) any later version.
+ *
+ *                This program is distributed in the hope that it will
+ *                be useful, but WITHOUT ANY WARRANTY; without even the
+ *                implied warranty of MERCHANTABILITY or FITNESS FOR A
+ *                PARTICULAR PURPOSE.  See the GNU General Public
+ *                License for more details.
+ *
+ *                The GNU General Public License should be included with
+ *                this file.  If not, you can view it at
+ *                http://www.gnu.org/copyleft/gpl.html
+ *                or write to the Free Software Foundation, Inc., 59
+ *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * Revisions   :
+ *    $Log: loadcfg.h,v $
+ *    Revision 1.11  2002/03/26 22:29:55  swa
+ *    we have a new homepage!
+ *
+ *    Revision 1.10  2002/03/24 13:25:43  swa
+ *    name change related issues
+ *
+ *    Revision 1.9  2002/03/16 23:54:06  jongfoster
+ *    Adding graceful termination feature, to help look for memory leaks.
+ *    If you enable this (which, by design, has to be done by hand
+ *    editing config.h) and then go to http://i.j.b/die, then the program
+ *    will exit cleanly after the *next* request.  It should free all the
+ *    memory that was used.
+ *
+ *    Revision 1.8  2001/12/30 14:07:32  steudten
+ *    - Add signal handling (unix)
+ *    - Add SIGHUP handler (unix)
+ *    - Add creation of pidfile (unix)
+ *    - Add action 'top' in rc file (RH)
+ *    - Add entry 'SIGNALS' to manpage
+ *    - Add exit message to logfile (unix)
+ *
+ *    Revision 1.7  2001/07/30 22:08:36  jongfoster
+ *    Tidying up #defines:
+ *    - All feature #defines are now of the form FEATURE_xxx
+ *    - Permanently turned off WIN_GUI_EDIT
+ *    - Permanently turned on WEBDAV and SPLIT_PROXY_ARGS
+ *
+ *    Revision 1.6  2001/07/29 18:58:15  jongfoster
+ *    Removing nested #includes, adding forward declarations for needed
+ *    structures, and changing the #define _FILENAME_H to FILENAME_H_INCLUDED.
+ *
+ *    Revision 1.5  2001/05/26 00:28:36  jongfoster
+ *    Automatic reloading of config file.
+ *    Removed obsolete SIGHUP support (Unix) and Reload menu option (Win32).
+ *    Most of the global variables have been moved to a new
+ *    struct configuration_spec, accessed through csp->config->globalname
+ *    Most of the globals remaining are used by the Win32 GUI.
+ *
+ *    Revision 1.4  2001/05/22 18:46:04  oes
+ *
+ *    - Enabled filtering banners by size rather than URL
+ *      by adding patterns that replace all standard banner
+ *      sizes with the "Junkbuster" gif to the re_filterfile
+ *
+ *    - Enabled filtering WebBugs by providing a pattern
+ *      which kills all 1x1 images
+ *
+ *    - Added support for PCRE_UNGREEDY behaviour to pcrs,
+ *      which is selected by the (nonstandard and therefore
+ *      capital) letter 'U' in the option string.
+ *      It causes the quantifiers to be ungreedy by default.
+ *      Appending a ? turns back to greedy (!).
+ *
+ *    - Added a new interceptor ijb-send-banner, which
+ *      sends back the "Junkbuster" gif. Without imagelist or
+ *      MSIE detection support, or if tinygif = 1, or the
+ *      URL isn't recognized as an imageurl, a lame HTML
+ *      explanation is sent instead.
+ *
+ *    - Added new feature, which permits blocking remote
+ *      script redirects and firing back a local redirect
+ *      to the browser.
+ *      The feature is conditionally compiled, i.e. it
+ *      can be disabled with --disable-fast-redirects,
+ *      plus it must be activated by a "fast-redirects"
+ *      line in the config file, has its own log level
+ *      and of course wants to be displayed by show-proxy-args
+ *      Note: Boy, all the #ifdefs in 1001 locations and
+ *      all the fumbling with configure.in and acconfig.h
+ *      were *way* more work than the feature itself :-(
+ *
+ *    - Because a generic redirect template was needed for
+ *      this, tinygif = 3 now uses the same.
+ *
+ *    - Moved GIFs, and other static HTTP response templates
+ *      to project.h
+ *
+ *    - Some minor fixes
+ *
+ *    - Removed some >400 CRs again (Jon, you really worked
+ *      a lot! ;-)
+ *
+ *    Revision 1.3  2001/05/20 01:21:20  jongfoster
+ *    Version 2.9.4 checkin.
+ *    - Merged popupfile and cookiefile, and added control over PCRS
+ *      filtering, in new "permissionsfile".
+ *    - Implemented LOG_LEVEL_FATAL, so that if there is a configuration
+ *      file error you now get a message box (in the Win32 GUI) rather
+ *      than the program exiting with no explanation.
+ *    - Made killpopup use the PCRS MIME-type checking and HTTP-header
+ *      skipping.
+ *    - Removed tabs from "config"
+ *    - Moved duplicated url parsing code in "loaders.c" to a new funcition.
+ *    - Bumped up version number.
+ *
+ *    Revision 1.2  2001/05/17 23:01:01  oes
+ *     - Cleaned CRLF's from the sources and related files
+ *
+ *    Revision 1.1.1.1  2001/05/15 13:58:58  oes
+ *    Initial import of version 2.9.3 source tree
+ *
+ *
+ *********************************************************************/
+\f
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Don't need project.h, only this: */
+struct configuration_spec;
+
+/* Global variables */
+
+#ifdef FEATURE_TOGGLE
+/* indicates if ijb is enabled */
+extern int g_bToggleIJB;
+#endif /* def FEATURE_TOGGLE */
+
+extern const char *configfile;
+
+
+/* The load_config function is now going to call:
+ * init_proxy_args, so it will need argc and argv.
+ * Since load_config will also be a signal handler,
+ * we need to have these globally available.
+ */
+extern int Argc;
+extern const char **Argv;
+extern short int MustReload;
+
+
+extern struct configuration_spec * load_config(void);
+
+#ifdef FEATURE_GRACEFUL_TERMINATION
+void unload_current_config_file(void);
+#endif
+
+/* Revision control strings from this header and associated .c file */
+extern const char loadcfg_rcs[];
+extern const char loadcfg_h_rcs[];
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* ndef LOADCFG_H_INCLUDED */
+
+/*
+  Local Variables:
+  tab-width: 3
+  end:
+*/
diff --git a/src/loaders.c b/src/loaders.c
new file mode 100644 (file)
index 0000000..3d7ca63
--- /dev/null
@@ -0,0 +1,1449 @@
+const char loaders_rcs[] = "$Id: loaders.c,v 1.50 2002/04/24 02:12:16 oes Exp $";
+/*********************************************************************
+ *
+ * File        :  $Source: /cvsroot/ijbswa/current/loaders.c,v $
+ *
+ * Purpose     :  Functions to load and unload the various
+ *                configuration files.  Also contains code to manage
+ *                the list of active loaders, and to automatically
+ *                unload files that are no longer in use.
+ *
+ * Copyright   :  Written by and Copyright (C) 2001 the SourceForge
+ *                Privoxy team. http://www.privoxy.org/
+ *
+ *                Based on the Internet Junkbuster originally written
+ *                by and Copyright (C) 1997 Anonymous Coders and
+ *                Junkbusters Corporation.  http://www.junkbusters.com
+ *
+ *                This program is free software; you can redistribute it
+ *                and/or modify it under the terms of the GNU General
+ *                Public License as published by the Free Software
+ *                Foundation; either version 2 of the License, or (at
+ *                your option) any later version.
+ *
+ *                This program is distributed in the hope that it will
+ *                be useful, but WITHOUT ANY WARRANTY; without even the
+ *                implied warranty of MERCHANTABILITY or FITNESS FOR A
+ *                PARTICULAR PURPOSE.  See the GNU General Public
+ *                License for more details.
+ *
+ *                The GNU General Public License should be included with
+ *                this file.  If not, you can view it at
+ *                http://www.gnu.org/copyleft/gpl.html
+ *                or write to the Free Software Foundation, Inc., 59
+ *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * Revisions   :
+ *    $Log: loaders.c,v $
+ *    Revision 1.50  2002/04/24 02:12:16  oes
+ *    Jon's multiple AF patch: Sweep now takes care of all AFs
+ *
+ *    Revision 1.49  2002/04/19 16:53:25  jongfoster
+ *    Optimize away a function call by using an equivalent macro
+ *
+ *    Revision 1.48  2002/04/05 00:56:09  gliptak
+ *    Correcting typo to clean up on realloc failure
+ *
+ *    Revision 1.47  2002/03/26 22:29:55  swa
+ *    we have a new homepage!
+ *
+ *    Revision 1.46  2002/03/24 13:25:43  swa
+ *    name change related issues
+ *
+ *    Revision 1.45  2002/03/16 23:54:06  jongfoster
+ *    Adding graceful termination feature, to help look for memory leaks.
+ *    If you enable this (which, by design, has to be done by hand
+ *    editing config.h) and then go to http://i.j.b/die, then the program
+ *    will exit cleanly after the *next* request.  It should free all the
+ *    memory that was used.
+ *
+ *    Revision 1.44  2002/03/16 21:51:00  jongfoster
+ *    Fixing free(NULL).
+ *
+ *    Revision 1.43  2002/03/16 20:28:34  oes
+ *    Added descriptions to the filters so users will know what they select in the cgi editor
+ *
+ *    Revision 1.42  2002/03/13 00:27:05  jongfoster
+ *    Killing warnings
+ *
+ *    Revision 1.41  2002/03/12 01:42:50  oes
+ *    Introduced modular filters
+ *
+ *    Revision 1.40  2002/03/08 17:46:04  jongfoster
+ *    Fixing int/size_t warnings
+ *
+ *    Revision 1.39  2002/03/07 03:46:17  oes
+ *    Fixed compiler warnings
+ *
+ *    Revision 1.38  2002/03/06 22:54:35  jongfoster
+ *    Automated function-comment nitpicking.
+ *
+ *    Revision 1.37  2002/03/03 15:07:49  oes
+ *    Re-enabled automatic config reloading
+ *
+ *    Revision 1.36  2002/01/22 23:46:18  jongfoster
+ *    Moving edit_read_line() and simple_read_line() to loaders.c, and
+ *    extending them to support reading MS-DOS, Mac and UNIX style files
+ *    on all platforms.
+ *
+ *    Modifying read_config_line() (without changing it's prototype) to
+ *    be a trivial wrapper for edit_read_line().  This means that we have
+ *    one function to read a line and handle comments, which is common
+ *    between the initialization code and the edit interface.
+ *
+ *    Revision 1.35  2002/01/17 21:03:08  jongfoster
+ *    Moving all our URL and URL pattern parsing code to urlmatch.c.
+ *
+ *    Renaming free_url to free_url_spec, since it frees a struct url_spec.
+ *
+ *    Revision 1.34  2001/12/30 14:07:32  steudten
+ *    - Add signal handling (unix)
+ *    - Add SIGHUP handler (unix)
+ *    - Add creation of pidfile (unix)
+ *    - Add action 'top' in rc file (RH)
+ *    - Add entry 'SIGNALS' to manpage
+ *    - Add exit message to logfile (unix)
+ *
+ *    Revision 1.33  2001/11/13 00:16:38  jongfoster
+ *    Replacing references to malloc.h with the standard stdlib.h
+ *    (See ANSI or K&R 2nd Ed)
+ *
+ *    Revision 1.32  2001/11/07 00:02:13  steudten
+ *    Add line number in error output for lineparsing for
+ *    actionsfile and configfile.
+ *    Special handling for CLF added.
+ *
+ *    Revision 1.31  2001/10/26 17:39:01  oes
+ *    Removed csp->referrer
+ *    Moved ijb_isspace and ijb_tolower to project.h
+ *
+ *    Revision 1.30  2001/10/25 03:40:48  david__schmidt
+ *    Change in porting tactics: OS/2's EMX porting layer doesn't allow multiple
+ *    threads to call select() simultaneously.  So, it's time to do a real, live,
+ *    native OS/2 port.  See defines for __EMX__ (the porting layer) vs. __OS2__
+ *    (native). Both versions will work, but using __OS2__ offers multi-threading.
+ *
+ *    Revision 1.29  2001/10/23 21:38:53  jongfoster
+ *    Adding error-checking to create_url_spec()
+ *
+ *    Revision 1.28  2001/10/07 15:40:39  oes
+ *    Replaced 6 boolean members of csp with one bitmap (csp->flags)
+ *
+ *    Revision 1.27  2001/09/22 16:36:59  jongfoster
+ *    Removing unused parameter fs from read_config_line()
+ *
+ *    Revision 1.26  2001/09/22 14:05:22  jongfoster
+ *    Bugfix: Multiple escaped "#" characters in a configuration
+ *    file are now permitted.
+ *    Also removing 3 unused headers.
+ *
+ *    Revision 1.25  2001/09/13 22:44:03  jongfoster
+ *    Adding {} to an if statement
+ *
+ *    Revision 1.24  2001/07/30 22:08:36  jongfoster
+ *    Tidying up #defines:
+ *    - All feature #defines are now of the form FEATURE_xxx
+ *    - Permanently turned off WIN_GUI_EDIT
+ *    - Permanently turned on WEBDAV and SPLIT_PROXY_ARGS
+ *
+ *    Revision 1.23  2001/07/20 15:51:54  oes
+ *    Fixed indentation of prepocessor commands
+ *
+ *    Revision 1.22  2001/07/20 15:16:17  haroon
+ *    - per Guy's suggestion, added a while loop in sweep() to catch not just
+ *      the last inactive CSP but all other consecutive inactive CSPs after that
+ *      as well
+ *
+ *    Revision 1.21  2001/07/18 17:26:24  oes
+ *    Changed to conform to new pcrs interface
+ *
+ *    Revision 1.20  2001/07/17 13:07:01  oes
+ *    Fixed segv when last line in config files
+ *     lacked a terminating (\r)\n
+ *
+ *    Revision 1.19  2001/07/13 14:01:54  oes
+ *    Removed all #ifdef PCRS
+ *
+ *    Revision 1.18  2001/06/29 21:45:41  oes
+ *    Indentation, CRLF->LF, Tab-> Space
+ *
+ *    Revision 1.17  2001/06/29 13:31:51  oes
+ *    Various adaptions
+ *
+ *    Revision 1.16  2001/06/09 10:55:28  jongfoster
+ *    Changing BUFSIZ ==> BUFFER_SIZE
+ *
+ *    Revision 1.15  2001/06/07 23:14:14  jongfoster
+ *    Removing ACL and forward file loaders - these
+ *    files have been merged into the config file.
+ *    Cosmetic: Moving unloader funcs next to their
+ *    respective loader funcs
+ *
+ *    Revision 1.14  2001/06/01 03:27:04  oes
+ *    Fixed line continuation problem
+ *
+ *    Revision 1.13  2001/05/31 21:28:49  jongfoster
+ *    Removed all permissionsfile code - it's now called the actions
+ *    file, and (almost) all the code is in actions.c
+ *
+ *    Revision 1.12  2001/05/31 17:32:31  oes
+ *
+ *     - Enhanced domain part globbing with infix and prefix asterisk
+ *       matching and optional unanchored operation
+ *
+ *    Revision 1.11  2001/05/29 23:25:24  oes
+ *
+ *     - load_config_line() and load_permissions_file() now use chomp()
+ *
+ *    Revision 1.10  2001/05/29 09:50:24  jongfoster
+ *    Unified blocklist/imagelist/permissionslist.
+ *    File format is still under discussion, but the internal changes
+ *    are (mostly) done.
+ *
+ *    Also modified interceptor behaviour:
+ *    - We now intercept all URLs beginning with one of the following
+ *      prefixes (and *only* these prefixes):
+ *        * http://i.j.b/
+ *        * http://ijbswa.sf.net/config/
+ *        * http://ijbswa.sourceforge.net/config/
+ *    - New interceptors "home page" - go to http://i.j.b/ to see it.
+ *    - Internal changes so that intercepted and fast redirect pages
+ *      are not replaced with an image.
+ *    - Interceptors now have the option to send a binary page direct
+ *      to the client. (i.e. ijb-send-banner uses this)
+ *    - Implemented show-url-info interceptor.  (Which is why I needed
+ *      the above interceptors changes - a typical URL is
+ *      "http://i.j.b/show-url-info?url=www.somesite.com/banner.gif".
+ *      The previous mechanism would not have intercepted that, and
+ *      if it had been intercepted then it then it would have replaced
+ *      it with an image.)
+ *
+ *    Revision 1.9  2001/05/26 17:12:07  jongfoster
+ *    Fatal errors loading configuration files now give better error messages.
+ *
+ *    Revision 1.8  2001/05/26 00:55:20  jongfoster
+ *    Removing duplicated code.  load_forwardfile() now uses create_url_spec()
+ *
+ *    Revision 1.7  2001/05/26 00:28:36  jongfoster
+ *    Automatic reloading of config file.
+ *    Removed obsolete SIGHUP support (Unix) and Reload menu option (Win32).
+ *    Most of the global variables have been moved to a new
+ *    struct configuration_spec, accessed through csp->config->globalname
+ *    Most of the globals remaining are used by the Win32 GUI.
+ *
+ *    Revision 1.6  2001/05/23 12:27:33  oes
+ *
+ *    Fixed ugly indentation of my last changes
+ *
+ *    Revision 1.5  2001/05/23 10:39:05  oes
+ *    - Added support for escaping the comment character
+ *      in config files by a backslash
+ *    - Added support for line continuation in config
+ *      files
+ *    - Fixed a buffer overflow bug with long config lines
+ *
+ *    Revision 1.4  2001/05/22 18:56:28  oes
+ *    CRLF -> LF
+ *
+ *    Revision 1.3  2001/05/20 01:21:20  jongfoster
+ *    Version 2.9.4 checkin.
+ *    - Merged popupfile and cookiefile, and added control over PCRS
+ *      filtering, in new "permissionsfile".
+ *    - Implemented LOG_LEVEL_FATAL, so that if there is a configuration
+ *      file error you now get a message box (in the Win32 GUI) rather
+ *      than the program exiting with no explanation.
+ *    - Made killpopup use the PCRS MIME-type checking and HTTP-header
+ *      skipping.
+ *    - Removed tabs from "config"
+ *    - Moved duplicated url parsing code in "loaders.c" to a new funcition.
+ *    - Bumped up version number.
+ *
+ *    Revision 1.2  2001/05/17 23:01:01  oes
+ *     - Cleaned CRLF's from the sources and related files
+ *
+ *    Revision 1.1.1.1  2001/05/15 13:58:59  oes
+ *    Initial import of version 2.9.3 source tree
+ *
+ *
+ *********************************************************************/
+\f
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/stat.h>
+#include <ctype.h>
+#include <assert.h>
+
+#if !defined(_WIN32) && !defined(__OS2__)
+#include <unistd.h>
+#endif
+
+#include "project.h"
+#include "list.h"
+#include "loaders.h"
+#include "filters.h"
+#include "parsers.h"
+#include "jcc.h"
+#include "miscutil.h"
+#include "errlog.h"
+#include "actions.h"
+#include "urlmatch.h"
+
+const char loaders_h_rcs[] = LOADERS_H_VERSION;
+
+/*
+ * Currently active files.
+ * These are also entered in the main linked list of files.
+ */
+
+#ifdef FEATURE_TRUST
+static struct file_list *current_trustfile      = NULL;
+#endif /* def FEATURE_TRUST */
+
+static struct file_list *current_re_filterfile  = NULL;
+
+
+
+/*********************************************************************
+ *
+ * Function    :  sweep
+ *
+ * Description :  Basically a mark and sweep garbage collector, it is run
+ *                (by the parent thread) every once in a while to reclaim memory.
+ *
+ * It uses a mark and sweep strategy:
+ *   1) mark all files as inactive
+ *
+ *   2) check with each client:
+ *       if it is active,   mark its files as active
+ *       if it is inactive, free its resources
+ *
+ *   3) free the resources of all of the files that
+ *      are still marked as inactive (and are obsolete).
+ *
+ *   N.B. files that are not obsolete don't have an unloader defined.
+ *
+ * Parameters  :  None
+ *
+ * Returns     :  N/A
+ *
+ *********************************************************************/
+void sweep(void)
+{
+   struct file_list *fl, *nfl;
+   struct client_state *csp, *ncsp;
+   int i;
+
+   /* clear all of the file's active flags */
+   for ( fl = files->next; NULL != fl; fl = fl->next )
+   {
+      fl->active = 0;
+   }
+
+   for (csp = clients; csp && (NULL != (ncsp = csp->next)) ; csp = csp->next)
+   {
+      if (ncsp->flags & CSP_FLAG_ACTIVE)
+      {
+         /* mark this client's files as active */
+
+         /*
+          * Always have a configuration file.
+          * (Also note the slightly non-standard extra
+          * indirection here.)
+          */
+         ncsp->config->config_file_list->active = 1;
+
+         for (i = 0; i < MAX_ACTION_FILES; i++)
+         {
+            if (ncsp->actions_list[i])     /* actions files */
+            {
+               ncsp->actions_list[i]->active = 1;
+            }
+         }
+
+         if (ncsp->rlist)     /* pcrsjob files */
+         {
+            ncsp->rlist->active = 1;
+         }
+
+#ifdef FEATURE_TRUST
+         if (ncsp->tlist)     /* trust files */
+         {
+            ncsp->tlist->active = 1;
+         }
+#endif /* def FEATURE_TRUST */
+
+      }
+      else
+      /*
+       * this client is not active, release its resources
+       * and the ones of all inactive clients that might
+       * follow it
+       */
+      {
+         while (!(ncsp->flags & CSP_FLAG_ACTIVE))
+         {
+            csp->next = ncsp->next;
+
+            freez(ncsp->ip_addr_str);
+            freez(ncsp->my_ip_addr_str);
+            freez(ncsp->my_hostname);
+            freez(ncsp->x_forwarded);
+            freez(ncsp->iob->buf);
+
+            free_http_request(ncsp->http);
+
+            destroy_list(ncsp->headers);
+            destroy_list(ncsp->cookie_list);
+
+            free_current_action(ncsp->action);
+
+#ifdef FEATURE_STATISTICS
+            urls_read++;
+            if (ncsp->flags & CSP_FLAG_REJECTED)
+            {
+               urls_rejected++;
+            }
+#endif /* def FEATURE_STATISTICS */
+
+            freez(ncsp);
+
+            /* are there any more in sequence after it? */
+            if( (ncsp = csp->next) == NULL)
+               break;
+         }
+      }
+   }
+
+   for (fl = files; fl && ((nfl = fl->next) != NULL) ; fl = fl->next)
+   {
+      if ( ( 0 == nfl->active ) && ( NULL != nfl->unloader ) )
+      {
+         fl->next = nfl->next;
+
+         (nfl->unloader)(nfl->f);
+
+         freez(nfl->filename);
+
+         freez(nfl);
+      }
+   }
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  check_file_changed
+ *
+ * Description :  Helper function to check if a file needs reloading.
+ *                If "current" is still current, return it.  Otherwise
+ *                allocates a new (zeroed) "struct file_list", fills
+ *                in the disk file name and timestamp, and returns it.
+ *
+ * Parameters  :
+ *          1  :  current = The file_list currently being used - will
+ *                          be checked to see if it is out of date.
+ *                          May be NULL (which is treated as out of
+ *                          date).
+ *          2  :  filename = Name of file to check.
+ *          3  :  newfl    = New file list. [Output only]
+ *                           This will be set to NULL, OR a struct
+ *                           file_list newly allocated on the
+ *                           heap, with the filename and lastmodified
+ *                           fields filled, and all others zeroed.
+ *
+ * Returns     :  If file unchanged: 0 (and sets newfl == NULL)
+ *                If file changed: 1 and sets newfl != NULL
+ *                On error: 1 and sets newfl == NULL
+ *
+ *********************************************************************/
+int check_file_changed(const struct file_list * current,
+                       const char * filename,
+                       struct file_list ** newfl)
+{
+   struct file_list *fs;
+   struct stat statbuf[1];
+
+   *newfl = NULL;
+
+   if (stat(filename, statbuf) < 0)
+   {
+      /* Error, probably file not found. */
+      return 1;
+   }
+
+   if (current
+       && (current->lastmodified == statbuf->st_mtime)
+       && (0 == strcmp(current->filename, filename)))
+   {
+      return 0;
+   }
+
+   fs = (struct file_list *)zalloc(sizeof(struct file_list));
+   if (fs == NULL)
+   {
+      /* Out of memory error */
+      return 1;
+   }
+
+   fs->filename = strdup(filename);
+   fs->lastmodified = statbuf->st_mtime;
+
+   if (fs->filename == NULL)
+   {
+      /* Out of memory error */
+      freez (fs);
+      return 1;
+   }
+   *newfl = fs;
+   return 1;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  simple_read_line
+ *
+ * Description :  Read a single line from a file and return it.
+ *                This is basically a version of fgets() that malloc()s
+ *                it's own line buffer.  Note that the buffer will
+ *                always be a multiple of BUFFER_SIZE bytes long.
+ *                Therefore if you are going to keep the string for
+ *                an extended period of time, you should probably
+ *                strdup() it and free() the original, to save memory.
+ *
+ *
+ * Parameters  :
+ *          1  :  dest = destination for newly malloc'd pointer to
+ *                line data.  Will be set to NULL on error.
+ *          2  :  fp = File to read from
+ *          3  :  newline = Standard for newlines in the file.
+ *                Will be unchanged if it's value on input is not
+ *                NEWLINE_UNKNOWN.
+ *                On output, may be changed from NEWLINE_UNKNOWN to
+ *                actual convention in file.
+ *
+ * Returns     :  JB_ERR_OK     on success
+ *                JB_ERR_MEMORY on out-of-memory
+ *                JB_ERR_FILE   on EOF.
+ *
+ *********************************************************************/
+jb_err simple_read_line(FILE *fp, char **dest, int *newline)
+{
+   size_t len = 0;
+   size_t buflen = BUFFER_SIZE;
+   char * buf;
+   char * p;
+   int ch;
+   int realnewline = NEWLINE_UNKNOWN;
+
+   if (NULL == (buf = malloc(buflen)))
+   {
+      return JB_ERR_MEMORY;
+   }
+
+   p = buf;
+
+/*
+ * Character codes.  If you have a wierd compiler and the following are
+ * incorrect, you also need to fix NEWLINE() in loaders.h
+ */
+#define CHAR_CR '\r' /* ASCII 13 */
+#define CHAR_LF '\n' /* ASCII 10 */
+
+   for (;;)
+   {
+      ch = getc(fp);
+      if (ch == EOF)
+      {
+         if (len > 0)
+         {
+            *p = '\0';
+            *dest = buf;
+            return JB_ERR_OK;
+         }
+         else
+         {
+            free(buf);
+            *dest = NULL;
+            return JB_ERR_FILE;
+         }
+      }
+      else if (ch == CHAR_CR)
+      {
+         ch = getc(fp);
+         if (ch == CHAR_LF)
+         {
+            if (*newline == NEWLINE_UNKNOWN)
+            {
+               *newline = NEWLINE_DOS;
+            }
+         }
+         else
+         {
+            if (ch != EOF)
+            {
+               ungetc(ch, fp);
+            }
+            if (*newline == NEWLINE_UNKNOWN)
+            {
+               *newline = NEWLINE_MAC;
+            }
+         }
+         *p = '\0';
+         *dest = buf;
+         if (*newline == NEWLINE_UNKNOWN)
+         {
+            *newline = realnewline;
+         }
+         return JB_ERR_OK;
+      }
+      else if (ch == CHAR_LF)
+      {
+         *p = '\0';
+         *dest = buf;
+         if (*newline == NEWLINE_UNKNOWN)
+         {
+            *newline = NEWLINE_UNIX;
+         }
+         return JB_ERR_OK;
+      }
+      else if (ch == 0)
+      {
+         *p = '\0';
+         *dest = buf;
+         return JB_ERR_OK;
+      }
+
+      *p++ = ch;
+
+      if (++len >= buflen)
+      {
+         buflen += BUFFER_SIZE;
+         if (NULL == (p = realloc(buf, buflen)))
+         {
+            free(buf);
+            return JB_ERR_MEMORY;
+         }
+         buf = p;
+         p = buf + len;
+      }
+   }
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  edit_read_line
+ *
+ * Description :  Read a single non-empty line from a file and return
+ *                it.  Trims comments, leading and trailing whitespace
+ *                and respects escaping of newline and comment char.
+ *                Provides the line in 2 alternative forms: raw and
+ *                preprocessed.
+ *                - raw is the raw data read from the file.  If the
+ *                  line is not modified, then this should be written
+ *                  to the new file.
+ *                - prefix is any comments and blank lines that were
+ *                  read from the file.  If the line is modified, then
+ *                  this should be written out to the file followed
+ *                  by the modified data.  (If this string is non-empty
+ *                  then it will have a newline at the end).
+ *                - data is the actual data that will be parsed
+ *                  further by appropriate routines.
+ *                On EOF, the 3 strings will all be set to NULL and
+ *                0 will be returned.
+ *
+ * Parameters  :
+ *          1  :  fp = File to read from
+ *          2  :  raw_out = destination for newly malloc'd pointer to
+ *                raw line data.  May be NULL if you don't want it.
+ *          3  :  prefix_out = destination for newly malloc'd pointer to
+ *                comments.  May be NULL if you don't want it.
+ *          4  :  data_out = destination for newly malloc'd pointer to
+ *                line data with comments and leading/trailing spaces
+ *                removed, and line continuation performed.  May be
+ *                NULL if you don't want it.
+ *          5  :  newline = Standard for newlines in the file.
+ *                On input, set to value to use or NEWLINE_UNKNOWN.
+ *                On output, may be changed from NEWLINE_UNKNOWN to
+ *                actual convention in file.  May be NULL if you
+ *                don't want it.
+ *          6  :  line_number = Line number in file.  In "lines" as
+ *                reported by a text editor, not lines containing data.
+ *
+ * Returns     :  JB_ERR_OK     on success
+ *                JB_ERR_MEMORY on out-of-memory
+ *                JB_ERR_FILE   on EOF.
+ *
+ *********************************************************************/
+jb_err edit_read_line(FILE *fp,
+                      char **raw_out,
+                      char **prefix_out,
+                      char **data_out,
+                      int *newline,
+                      unsigned long *line_number)
+{
+   char *p;          /* Temporary pointer   */
+   char *linebuf;    /* Line read from file */
+   char *linestart;  /* Start of linebuf, usually first non-whitespace char */
+   int contflag = 0; /* Nonzero for line continuation - i.e. line ends '\' */
+   int is_empty = 1; /* Flag if not got any data yet */
+   char *raw    = NULL; /* String to be stored in raw_out    */
+   char *prefix = NULL; /* String to be stored in prefix_out */
+   char *data   = NULL; /* String to be stored in data_out   */
+   int scrapnewline;    /* Used for (*newline) if newline==NULL */
+   jb_err rval = JB_ERR_OK;
+
+   assert(fp);
+   assert(raw_out || data_out);
+   assert(newline == NULL
+       || *newline == NEWLINE_UNKNOWN
+       || *newline == NEWLINE_UNIX
+       || *newline == NEWLINE_DOS
+       || *newline == NEWLINE_MAC);
+
+   if (newline == NULL)
+   {
+      scrapnewline = NEWLINE_UNKNOWN;
+      newline = &scrapnewline;
+   }
+
+   /* Set output parameters to NULL */
+   if (raw_out)
+   {
+      *raw_out    = NULL;
+   }
+   if (prefix_out)
+   {
+      *prefix_out = NULL;
+   }
+   if (data_out)
+   {
+      *data_out   = NULL;
+   }
+
+   /* Set string variables to new, empty strings. */
+
+   if (raw_out)
+   {
+      if ((raw = malloc(1)) == NULL)
+      {
+         return JB_ERR_MEMORY;
+      }
+      *raw = '\0';
+   }
+   if (prefix_out)
+   {
+      if ((prefix = malloc(1)) == NULL)
+      {
+         freez(raw);
+         return JB_ERR_MEMORY;
+      }
+      *prefix = '\0';
+   }
+   if (data_out)
+   {
+      if ((data = malloc(1)) == NULL)
+      {
+         freez(raw);
+         freez(prefix);
+         return JB_ERR_MEMORY;
+      }
+      *data = '\0';
+   }
+
+   /* Main loop.  Loop while we need more data & it's not EOF. */
+
+   while ( (contflag || is_empty)
+        && (JB_ERR_OK == (rval = simple_read_line(fp, &linebuf, newline))))
+   {
+      if (line_number)
+      {
+         (*line_number)++;
+      }
+      if (raw)
+      {
+         string_append(&raw,linebuf);
+         if (string_append(&raw,NEWLINE(*newline)))
+         {
+            freez(prefix);
+            freez(data);
+            free(linebuf);
+            return JB_ERR_MEMORY;
+         }
+      }
+
+      /* Line continuation? Trim escape and set flag. */
+      p = linebuf + strlen(linebuf) - 1;
+      contflag = ((*linebuf != '\0') && (*p == '\\'));
+      if (contflag)
+      {
+         *p = '\0';
+      }
+
+      /* Trim leading spaces if we're at the start of the line */
+      linestart = linebuf;
+      if (*data == '\0')
+      {
+         /* Trim leading spaces */
+         while (*linestart && isspace((int)(unsigned char)*linestart))
+         {
+            linestart++;
+         }
+      }
+
+      /* Handle comment characters. */
+      p = linestart;
+      while ((p = strchr(p, '#')) != NULL)
+      {
+         /* Found a comment char.. */
+         if ((p != linebuf) && (*(p-1) == '\\'))
+         {
+            /* ..and it's escaped, left-shift the line over the escape. */
+            char *q = p - 1;
+            while ((*q = *(q + 1)) != '\0')
+            {
+               q++;
+            }
+            /* Now scan from just after the "#". */
+         }
+         else
+         {
+            /* Real comment.  Save it... */
+            if (p == linestart)
+            {
+               /* Special case:  Line only contains a comment, so all the
+                * previous whitespace is considered part of the comment.
+                * Undo the whitespace skipping, if any.
+                */
+               linestart = linebuf;
+               p = linestart;
+            }
+            if (prefix)
+            {
+               string_append(&prefix,p);
+               if (string_append(&prefix, NEWLINE(*newline)))
+               {
+                  freez(raw);
+                  freez(data);
+                  free(linebuf);
+                  return JB_ERR_MEMORY;
+               }
+            }
+
+            /* ... and chop off the rest of the line */
+            *p = '\0';
+         }
+      } /* END while (there's a # character) */
+
+      /* Write to the buffer */
+      if (*linestart)
+      {
+         is_empty = 0;
+         if (data)
+         {
+            if (string_append(&data, linestart))
+            {
+               freez(raw);
+               freez(prefix);
+               free(linebuf);
+               return JB_ERR_MEMORY;
+            }
+         }
+      }
+
+      free(linebuf);
+   } /* END while(we need more data) */
+
+   /* Handle simple_read_line() errors - ignore EOF */
+   if ((rval != JB_ERR_OK) && (rval != JB_ERR_FILE))
+   {
+      freez(raw);
+      freez(prefix);
+      freez(data);
+      return rval;
+   }
+
+   if (raw ? (*raw == '\0') : is_empty)
+   {
+      /* EOF and no data there.  (Definition of "data" depends on whether
+       * the caller cares about "raw" or just "data").
+       */
+
+      freez(raw);
+      freez(prefix);
+      freez(data);
+
+      return JB_ERR_FILE;
+   }
+   else
+   {
+      /* Got at least some data */
+
+      /* Remove trailing whitespace */
+      chomp(data);
+
+      if (raw_out)
+      {
+         *raw_out    = raw;
+      }
+      else
+      {
+         freez(raw);
+      }
+      if (prefix_out)
+      {
+         *prefix_out = prefix;
+      }
+      else
+      {
+         freez(prefix);
+      }
+      if (data_out)
+      {
+         *data_out   = data;
+      }
+      else
+      {
+         freez(data);
+      }
+      return JB_ERR_OK;
+   }
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  read_config_line
+ *
+ * Description :  Read a single non-empty line from a file and return
+ *                it.  Trims comments, leading and trailing whitespace
+ *                and respects escaping of newline and comment char.
+ *
+ * Parameters  :
+ *          1  :  buf = Buffer to use.
+ *          2  :  buflen = Size of buffer in bytes.
+ *          3  :  fp = File to read from
+ *          4  :  linenum = linenumber in file
+ *
+ * Returns     :  NULL on EOF or error
+ *                Otherwise, returns buf.
+ *
+ *********************************************************************/
+char *read_config_line(char *buf, size_t buflen, FILE *fp, unsigned long *linenum)
+{
+   jb_err err;
+   char *buf2 = NULL;
+   err = edit_read_line(fp, NULL, NULL, &buf2, NULL, linenum);
+   if (err)
+   {
+      if (err == JB_ERR_MEMORY)
+      {
+         log_error(LOG_LEVEL_FATAL, "Out of memory loading a config file");
+      }
+      return NULL;
+   }
+   else
+   {
+      assert(buf2);
+      assert(strlen(buf2) + 1U < buflen);
+      strncpy(buf, buf2, buflen - 1);
+      free(buf2);
+      buf[buflen - 1] = '\0';
+      return buf;
+   }
+}
+
+
+#ifdef FEATURE_TRUST
+/*********************************************************************
+ *
+ * Function    :  unload_trustfile
+ *
+ * Description :  Unloads a trustfile.
+ *
+ * Parameters  :
+ *          1  :  f = the data structure associated with the trustfile.
+ *
+ * Returns     :  N/A
+ *
+ *********************************************************************/
+static void unload_trustfile(void *f)
+{
+   struct block_spec *cur = (struct block_spec *)f;
+   struct block_spec *next;
+
+   while (cur != NULL)
+   {
+      next = cur->next;
+
+      free_url_spec(cur->url);
+      free(cur);
+
+      cur = next;
+   }
+
+}
+
+
+#ifdef FEATURE_GRACEFUL_TERMINATION
+/*********************************************************************
+ *
+ * Function    :  unload_current_trust_file
+ *
+ * Description :  Unloads current trust file - reset to state at
+ *                beginning of program.
+ *
+ * Parameters  :  None
+ *
+ * Returns     :  N/A
+ *
+ *********************************************************************/
+void unload_current_trust_file(void)
+{
+   if (current_trustfile)
+   {
+      current_trustfile->unloader = unload_trustfile;
+      current_trustfile = NULL;
+   }
+}
+#endif /* FEATURE_GRACEFUL_TERMINATION */
+
+
+/*********************************************************************
+ *
+ * Function    :  load_trustfile
+ *
+ * Description :  Read and parse a trustfile and add to files list.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns     :  0 => Ok, everything else is an error.
+ *
+ *********************************************************************/
+int load_trustfile(struct client_state *csp)
+{
+   FILE *fp;
+
+   struct block_spec *b, *bl;
+   struct url_spec **tl;
+
+   char  buf[BUFFER_SIZE], *p, *q;
+   int reject, trusted;
+   struct file_list *fs;
+   unsigned long linenum = 0;
+
+   if (!check_file_changed(current_trustfile, csp->config->trustfile, &fs))
+   {
+      /* No need to load */
+      if (csp)
+      {
+         csp->tlist = current_trustfile;
+      }
+      return(0);
+   }
+   if (!fs)
+   {
+      goto load_trustfile_error;
+   }
+
+   fs->f = bl = (struct block_spec *)zalloc(sizeof(*bl));
+   if (bl == NULL)
+   {
+      goto load_trustfile_error;
+   }
+
+   if ((fp = fopen(csp->config->trustfile, "r")) == NULL)
+   {
+      goto load_trustfile_error;
+   }
+
+   tl = csp->config->trust_list;
+
+   while (read_config_line(buf, sizeof(buf), fp, &linenum) != NULL)
+   {
+      trusted = 0;
+      reject  = 1;
+
+      if (*buf == '+')
+      {
+         trusted = 1;
+         *buf = '~';
+      }
+
+      if (*buf == '~')
+      {
+         reject = 0;
+         p = buf;
+         q = p+1;
+         while ((*p++ = *q++) != '\0')
+         {
+            /* nop */
+         }
+      }
+
+      /* skip blank lines */
+      if (*buf == '\0')
+      {
+         continue;
+      }
+
+      /* allocate a new node */
+      if ((b = zalloc(sizeof(*b))) == NULL)
+      {
+         fclose(fp);
+         goto load_trustfile_error;
+      }
+
+      /* add it to the list */
+      b->next  = bl->next;
+      bl->next = b;
+
+      b->reject = reject;
+
+      /* Save the URL pattern */
+      if (create_url_spec(b->url, buf))
+      {
+         fclose(fp);
+         goto load_trustfile_error;
+      }
+
+      /*
+       * save a pointer to URL's spec in the list of trusted URL's, too
+       */
+      if (trusted)
+      {
+         *tl++ = b->url;
+         /* FIXME BUFFER OVERFLOW if >=64 entries */
+      }
+   }
+
+   *tl = NULL;
+
+   fclose(fp);
+
+   /* the old one is now obsolete */
+   if (current_trustfile)
+   {
+      current_trustfile->unloader = unload_trustfile;
+   }
+
+   fs->next    = files->next;
+   files->next = fs;
+   current_trustfile = fs;
+
+   if (csp)
+   {
+      csp->tlist = fs;
+   }
+
+   return(0);
+
+load_trustfile_error:
+   log_error(LOG_LEVEL_FATAL, "can't load trustfile '%s': %E",
+             csp->config->trustfile);
+   return(-1);
+
+}
+#endif /* def FEATURE_TRUST */
+
+
+/*********************************************************************
+ *
+ * Function    :  unload_re_filterfile
+ *
+ * Description :  Unload the re_filter list by freeing all chained
+ *                re_filterfile specs and their data.
+ *
+ * Parameters  :
+ *          1  :  f = the data structure associated with the filterfile.
+ *
+ * Returns     :  N/A
+ *
+ *********************************************************************/
+static void unload_re_filterfile(void *f)
+{
+   struct re_filterfile_spec *a, *b = (struct re_filterfile_spec *)f;
+
+   while (b != NULL)
+   {
+      a = b->next;
+
+      destroy_list(b->patterns);
+      pcrs_free_joblist(b->joblist);
+      freez(b->name);
+      freez(b->description);
+      freez(b);
+
+      b = a;
+   }
+
+   return;
+}
+
+
+#ifdef FEATURE_GRACEFUL_TERMINATION
+/*********************************************************************
+ *
+ * Function    :  unload_current_re_filterfile
+ *
+ * Description :  Unloads current re_filter file - reset to state at
+ *                beginning of program.
+ *
+ * Parameters  :  None
+ *
+ * Returns     :  N/A
+ *
+ *********************************************************************/
+void unload_current_re_filterfile(void)
+{
+   if (current_re_filterfile)
+   {
+      current_re_filterfile->unloader = unload_re_filterfile;
+      current_re_filterfile = NULL;
+   }
+}
+#endif
+
+
+/*********************************************************************
+ *
+ * Function    :  load_re_filterfile
+ *
+ * Description :  Load the re_filterfile. 
+ *                Generate a chained list of re_filterfile_spec's from
+ *                the "FILTER: " blocks, compiling all their substitutions
+ *                into chained lists of pcrs_job structs.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns     :  0 => Ok, everything else is an error.
+ *
+ *********************************************************************/
+int load_re_filterfile(struct client_state *csp)
+{
+   FILE *fp;
+
+   struct re_filterfile_spec *new_bl, *bl = NULL;
+   struct file_list *fs;
+
+   char  buf[BUFFER_SIZE];
+   int error;
+   unsigned long linenum = 0;
+   pcrs_job *dummy;
+
+   /*
+    * No need to reload if unchanged
+    */
+   if (!check_file_changed(current_re_filterfile, csp->config->re_filterfile, &fs))
+   {
+      if (csp)
+      {
+         csp->rlist = current_re_filterfile;
+      }
+      return(0);
+   }
+   if (!fs)
+   {
+      goto load_re_filterfile_error;
+   }
+
+   /* 
+    * Open the file or fail
+    */
+   if ((fp = fopen(csp->config->re_filterfile, "r")) == NULL)
+   {
+      goto load_re_filterfile_error;
+   }
+
+   /* 
+    * Read line by line
+    */
+   while (read_config_line(buf, sizeof(buf), fp, &linenum) != NULL)
+   {
+      /*
+       * If this is the head of a new filter block, make it a
+       * re_filterfile spec of its own and chain it to the list:
+       */
+      if (strncmp(buf, "FILTER:", 7) == 0)
+      {
+         new_bl = (struct re_filterfile_spec  *)zalloc(sizeof(*bl));
+         if (new_bl == NULL)
+         {
+            goto load_re_filterfile_error;
+         }
+
+         new_bl->name = chomp(buf + 7);
+
+         if (NULL != (new_bl->description = strchr(new_bl->name, ' ')))
+         {
+            *new_bl->description++ = '\0';
+            new_bl->description = strdup(chomp(new_bl->description));
+         }
+         else
+         {
+            new_bl->description = strdup("No description available for this filter");
+         }
+
+         new_bl->name = strdup(chomp(new_bl->name));
+         
+         /*
+          * If this is the first filter block, chain it
+          * to the file_list rather than its (nonexistant)
+          * predecessor
+          */
+         if (fs->f == NULL)
+         {
+            fs->f = new_bl;
+         }
+         else
+         {
+            bl->next = new_bl;
+         }
+         bl = new_bl;
+
+         log_error(LOG_LEVEL_RE_FILTER, "Reading in filter \"%s\" (\"%s\")", bl->name, bl->description);
+
+         continue;
+      }
+
+      /* 
+       * Else, save the expression, make it a pcrs_job
+       * and chain it into the current filter's joblist 
+       */
+      if (bl != NULL)
+      {
+         enlist(bl->patterns, buf);
+
+         if ((dummy = pcrs_compile_command(buf, &error)) == NULL)
+         {
+            log_error(LOG_LEVEL_RE_FILTER,
+                      "Adding re_filter job %s to filter %s failed with error %d.", buf, bl->name, error);
+            continue;
+         }
+         else
+         {
+            dummy->next = bl->joblist;
+            bl->joblist = dummy;
+            log_error(LOG_LEVEL_RE_FILTER, "Adding re_filter job %s to filter %s succeeded.", buf, bl->name);
+         }
+      }
+      else
+      {
+         log_error(LOG_LEVEL_ERROR, "Ignoring job %s outside filter block in %s, line %d", buf, csp->config->re_filterfile, linenum);
+      }
+   }
+
+   fclose(fp);
+
+   /* 
+    * Schedule the now-obsolete old data for unloading
+    */
+   if ( NULL != current_re_filterfile )
+   {
+      current_re_filterfile->unloader = unload_re_filterfile;
+   }
+
+   /*
+    * Chain this file into the global list of loaded files
+    */
+   fs->next    = files->next;
+   files->next = fs;
+   current_re_filterfile = fs;
+
+   if (csp)
+   {
+      csp->rlist = fs;
+   }
+
+   return( 0 );
+
+load_re_filterfile_error:
+   log_error(LOG_LEVEL_FATAL, "can't load re_filterfile '%s': %E",
+             csp->config->re_filterfile);
+   return(-1);
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  add_loader
+ *
+ * Description :  Called from `load_config'.  Called once for each input
+ *                file found in config.
+ *
+ * Parameters  :
+ *          1  :  loader = pointer to a function that can parse and load
+ *                the appropriate config file.
+ *          2  :  config = The configuration_spec to add the loader to.
+ *
+ * Returns     :  N/A
+ *
+ *********************************************************************/
+void add_loader(int (*loader)(struct client_state *),
+                struct configuration_spec * config)
+{
+   int i;
+
+   for (i=0; i < NLOADERS; i++)
+   {
+      if (config->loaders[i] == NULL)
+      {
+         config->loaders[i] = loader;
+         break;
+      }
+   }
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  run_loader
+ *
+ * Description :  Called from `load_config' and `listen_loop'.  This
+ *                function keeps the "csp" current with any file mods
+ *                since the last loop.  If a file is unchanged, the
+ *                loader functions do NOT reload the file.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *                      Must be non-null.  Reads: "csp->config"
+ *                      Writes: various data members.
+ *
+ * Returns     :  0 => Ok, everything else is an error.
+ *
+ *********************************************************************/
+int run_loader(struct client_state *csp)
+{
+   int ret = 0;
+   int i;
+
+   for (i=0; i < NLOADERS; i++)
+   {
+      if (csp->config->loaders[i] == NULL)
+      {
+         break;
+      }
+      ret |= (csp->config->loaders[i])(csp);
+   }
+   return(ret);
+
+}
+
+
+/*
+  Local Variables:
+  tab-width: 3
+  end:
+*/
diff --git a/src/loaders.h b/src/loaders.h
new file mode 100644 (file)
index 0000000..0b179e9
--- /dev/null
@@ -0,0 +1,238 @@
+#ifndef LOADERS_H_INCLUDED
+#define LOADERS_H_INCLUDED
+#define LOADERS_H_VERSION "$Id: loaders.h,v 1.19 2002/03/26 22:29:55 swa Exp $"
+/*********************************************************************
+ *
+ * File        :  $Source: /cvsroot/ijbswa/current/loaders.h,v $
+ *
+ * Purpose     :  Functions to load and unload the various
+ *                configuration files.  Also contains code to manage
+ *                the list of active loaders, and to automatically 
+ *                unload files that are no longer in use.
+ *
+ * Copyright   :  Written by and Copyright (C) 2001 the SourceForge
+ *                Privoxy team. http://www.privoxy.org/
+ *
+ *                Based on the Internet Junkbuster originally written
+ *                by and Copyright (C) 1997 Anonymous Coders and 
+ *                Junkbusters Corporation.  http://www.junkbusters.com
+ *
+ *                This program is free software; you can redistribute it 
+ *                and/or modify it under the terms of the GNU General
+ *                Public License as published by the Free Software
+ *                Foundation; either version 2 of the License, or (at
+ *                your option) any later version.
+ *
+ *                This program is distributed in the hope that it will
+ *                be useful, but WITHOUT ANY WARRANTY; without even the
+ *                implied warranty of MERCHANTABILITY or FITNESS FOR A
+ *                PARTICULAR PURPOSE.  See the GNU General Public
+ *                License for more details.
+ *
+ *                The GNU General Public License should be included with
+ *                this file.  If not, you can view it at
+ *                http://www.gnu.org/copyleft/gpl.html
+ *                or write to the Free Software Foundation, Inc., 59
+ *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * Revisions   :
+ *    $Log: loaders.h,v $
+ *    Revision 1.19  2002/03/26 22:29:55  swa
+ *    we have a new homepage!
+ *
+ *    Revision 1.18  2002/03/24 13:25:43  swa
+ *    name change related issues
+ *
+ *    Revision 1.17  2002/03/16 23:54:06  jongfoster
+ *    Adding graceful termination feature, to help look for memory leaks.
+ *    If you enable this (which, by design, has to be done by hand
+ *    editing config.h) and then go to http://i.j.b/die, then the program
+ *    will exit cleanly after the *next* request.  It should free all the
+ *    memory that was used.
+ *
+ *    Revision 1.16  2002/03/07 03:46:17  oes
+ *    Fixed compiler warnings
+ *
+ *    Revision 1.15  2002/01/22 23:46:18  jongfoster
+ *    Moving edit_read_line() and simple_read_line() to loaders.c, and
+ *    extending them to support reading MS-DOS, Mac and UNIX style files
+ *    on all platforms.
+ *
+ *    Modifying read_config_line() (without changing it's prototype) to
+ *    be a trivial wrapper for edit_read_line().  This means that we have
+ *    one function to read a line and handle comments, which is common
+ *    between the initialization code and the edit interface.
+ *
+ *    Revision 1.14  2002/01/17 21:03:08  jongfoster
+ *    Moving all our URL and URL pattern parsing code to urlmatch.c.
+ *
+ *    Renaming free_url to free_url_spec, since it frees a struct url_spec.
+ *
+ *    Revision 1.13  2001/12/30 14:07:32  steudten
+ *    - Add signal handling (unix)
+ *    - Add SIGHUP handler (unix)
+ *    - Add creation of pidfile (unix)
+ *    - Add action 'top' in rc file (RH)
+ *    - Add entry 'SIGNALS' to manpage
+ *    - Add exit message to logfile (unix)
+ *
+ *    Revision 1.12  2001/11/07 00:02:13  steudten
+ *    Add line number in error output for lineparsing for
+ *    actionsfile and configfile.
+ *    Special handling for CLF added.
+ *
+ *    Revision 1.11  2001/10/23 21:38:53  jongfoster
+ *    Adding error-checking to create_url_spec()
+ *
+ *    Revision 1.10  2001/09/22 16:36:59  jongfoster
+ *    Removing unused parameter fs from read_config_line()
+ *
+ *    Revision 1.9  2001/07/30 22:08:36  jongfoster
+ *    Tidying up #defines:
+ *    - All feature #defines are now of the form FEATURE_xxx
+ *    - Permanently turned off WIN_GUI_EDIT
+ *    - Permanently turned on WEBDAV and SPLIT_PROXY_ARGS
+ *
+ *    Revision 1.8  2001/07/29 18:58:15  jongfoster
+ *    Removing nested #includes, adding forward declarations for needed
+ *    structures, and changing the #define _FILENAME_H to FILENAME_H_INCLUDED.
+ *
+ *    Revision 1.7  2001/07/13 14:01:54  oes
+ *    Removed all #ifdef PCRS
+ *
+ *    Revision 1.6  2001/06/07 23:14:38  jongfoster
+ *    Removing ACL and forward file loaders - these files have
+ *    been merged into the config file.
+ *
+ *    Revision 1.5  2001/05/31 21:28:49  jongfoster
+ *    Removed all permissionsfile code - it's now called the actions
+ *    file, and (almost) all the code is in actions.c
+ *
+ *    Revision 1.4  2001/05/29 09:50:24  jongfoster
+ *    Unified blocklist/imagelist/permissionslist.
+ *    File format is still under discussion, but the internal changes
+ *    are (mostly) done.
+ *
+ *    Also modified interceptor behaviour:
+ *    - We now intercept all URLs beginning with one of the following
+ *      prefixes (and *only* these prefixes):
+ *        * http://i.j.b/
+ *        * http://ijbswa.sf.net/config/
+ *        * http://ijbswa.sourceforge.net/config/
+ *    - New interceptors "home page" - go to http://i.j.b/ to see it.
+ *    - Internal changes so that intercepted and fast redirect pages
+ *      are not replaced with an image.
+ *    - Interceptors now have the option to send a binary page direct
+ *      to the client. (i.e. ijb-send-banner uses this)
+ *    - Implemented show-url-info interceptor.  (Which is why I needed
+ *      the above interceptors changes - a typical URL is
+ *      "http://i.j.b/show-url-info?url=www.somesite.com/banner.gif".
+ *      The previous mechanism would not have intercepted that, and
+ *      if it had been intercepted then it then it would have replaced
+ *      it with an image.)
+ *
+ *    Revision 1.3  2001/05/26 00:28:36  jongfoster
+ *    Automatic reloading of config file.
+ *    Removed obsolete SIGHUP support (Unix) and Reload menu option (Win32).
+ *    Most of the global variables have been moved to a new
+ *    struct configuration_spec, accessed through csp->config->globalname
+ *    Most of the globals remaining are used by the Win32 GUI.
+ *
+ *    Revision 1.2  2001/05/20 01:21:20  jongfoster
+ *    Version 2.9.4 checkin.
+ *    - Merged popupfile and cookiefile, and added control over PCRS
+ *      filtering, in new "permissionsfile".
+ *    - Implemented LOG_LEVEL_FATAL, so that if there is a configuration
+ *      file error you now get a message box (in the Win32 GUI) rather
+ *      than the program exiting with no explanation.
+ *    - Made killpopup use the PCRS MIME-type checking and HTTP-header
+ *      skipping.
+ *    - Removed tabs from "config"
+ *    - Moved duplicated url parsing code in "loaders.c" to a new funcition.
+ *    - Bumped up version number.
+ *
+ *    Revision 1.1.1.1  2001/05/15 13:59:00  oes
+ *    Initial import of version 2.9.3 source tree
+ *
+ *
+ *********************************************************************/
+\f
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Structures taken from project.h */
+struct client_state;
+struct file_list;
+struct configuration_spec;
+struct url_spec;
+
+extern void sweep(void);
+extern char *read_config_line(char *buf, size_t buflen, FILE *fp, unsigned long *linenum);
+extern int check_file_changed(const struct file_list * current,
+                              const char * filename,
+                              struct file_list ** newfl);
+
+extern jb_err edit_read_line(FILE *fp,
+                             char **raw_out,
+                             char **prefix_out,
+                             char **data_out,
+                             int *newline,
+                             unsigned long *line_number);
+
+extern jb_err simple_read_line(FILE *fp, char **dest, int *newline);
+
+/*
+ * Various types of newlines that a file may contain.
+ */
+#define NEWLINE_UNKNOWN 0  /* Newline convention in file is unknown */
+#define NEWLINE_UNIX    1  /* Newline convention in file is '\n'   (ASCII 10) */
+#define NEWLINE_DOS     2  /* Newline convention in file is '\r\n' (ASCII 13,10) */
+#define NEWLINE_MAC     3  /* Newline convention in file is '\r'   (ASCII 13) */
+
+/*
+ * Types of newlines that a file may contain, as strings.  If you have an
+ * extremely wierd compiler that does not have '\r' == CR == ASCII 13 and
+ * '\n' == LF == ASCII 10), then fix CHAR_CR and CHAR_LF in loaders.c as
+ * well as these definitions.
+ */
+#define NEWLINE(style) ((style)==NEWLINE_DOS ? "\r\n" : \
+                        ((style)==NEWLINE_MAC ? "\r" : "\n"))
+
+
+extern short int MustReload;
+extern int load_actions_file(struct client_state *csp);
+extern int load_re_filterfile(struct client_state *csp);
+
+#ifdef FEATURE_TRUST
+extern int load_trustfile(struct client_state *csp);
+#endif /* def FEATURE_TRUST */
+
+#ifdef FEATURE_GRACEFUL_TERMINATION
+#ifdef FEATURE_TRUST
+void unload_current_trust_file(void);
+#endif
+void unload_current_re_filterfile(void);
+#endif /* FEATURE_GRACEFUL_TERMINATION */
+
+
+extern void add_loader(int (*loader)(struct client_state *), 
+                       struct configuration_spec * config);
+extern int run_loader(struct client_state *csp);
+
+/* Revision control strings from this header and associated .c file */
+extern const char loaders_rcs[];
+extern const char loaders_h_rcs[];
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* ndef LOADERS_H_INCLUDED */
+
+/*
+  Local Variables:
+  tab-width: 3
+  end:
+*/
diff --git a/src/miscutil.c b/src/miscutil.c
new file mode 100644 (file)
index 0000000..53b3b4d
--- /dev/null
@@ -0,0 +1,1763 @@
+const char miscutil_rcs[] = "$Id: miscutil.c,v 1.37 2002/04/26 18:29:43 jongfoster Exp $";
+/*********************************************************************
+ *
+ * File        :  $Source: /cvsroot/ijbswa/current/miscutil.c,v $
+ *
+ * Purpose     :  zalloc, hash_string, safe_strerror, strcmpic,
+ *                strncmpic, chomp, and MinGW32 strdup
+ *                functions. 
+ *                These are each too small to deserve their own file
+ *                but don't really fit in any other file.
+ *
+ * Copyright   :  Written by and Copyright (C) 2001 the SourceForge
+ *                Privoxy team. http://www.privoxy.org/
+ *
+ *                Based on the Internet Junkbuster originally written
+ *                by and Copyright (C) 1997 Anonymous Coders and 
+ *                Junkbusters Corporation.  http://www.junkbusters.com
+ *
+ *                This program is free software; you can redistribute it 
+ *                and/or modify it under the terms of the GNU General
+ *                Public License as published by the Free Software
+ *                Foundation; either version 2 of the License, or (at
+ *                your option) any later version.
+ *
+ *                This program is distributed in the hope that it will
+ *                be useful, but WITHOUT ANY WARRANTY; without even the
+ *                implied warranty of MERCHANTABILITY or FITNESS FOR A
+ *                PARTICULAR PURPOSE.  See the GNU General Public
+ *                License for more details.
+ *
+ *                The GNU General Public License should be included with
+ *                this file.  If not, you can view it at
+ *                http://www.gnu.org/copyleft/gpl.html
+ *                or write to the Free Software Foundation, Inc., 59
+ *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * Revisions   :
+ *    $Log: miscutil.c,v $
+ *    Revision 1.37  2002/04/26 18:29:43  jongfoster
+ *    Fixing this Visual C++ warning:
+ *    miscutil.c(710) : warning C4090: '=' : different 'const' qualifiers
+ *
+ *    Revision 1.36  2002/04/26 12:55:38  oes
+ *    New function string_toupper
+ *
+ *    Revision 1.35  2002/03/26 22:29:55  swa
+ *    we have a new homepage!
+ *
+ *    Revision 1.34  2002/03/24 13:25:43  swa
+ *    name change related issues
+ *
+ *    Revision 1.33  2002/03/07 03:46:53  oes
+ *    Fixed compiler warnings etc
+ *
+ *    Revision 1.32  2002/03/06 23:02:57  jongfoster
+ *    Removing tabs
+ *
+ *    Revision 1.31  2002/03/05 04:52:42  oes
+ *    Deleted non-errlog debugging code
+ *
+ *    Revision 1.30  2002/03/04 18:27:42  oes
+ *    - Deleted deletePidFile
+ *    - Made write_pid_file use the --pidfile option value
+ *      (or no PID file, if the option was absent)
+ *    - Played styleguide police
+ *
+ *    Revision 1.29  2002/03/04 02:08:02  david__schmidt
+ *    Enable web editing of actions file on OS/2 (it had been broken all this time!)
+ *
+ *    Revision 1.28  2002/03/03 09:18:03  joergs
+ *    Made jumbjuster work on AmigaOS again.
+ *
+ *    Revision 1.27  2002/01/21 00:52:32  jongfoster
+ *    Adding string_join()
+ *
+ *    Revision 1.26  2001/12/30 14:07:32  steudten
+ *    - Add signal handling (unix)
+ *    - Add SIGHUP handler (unix)
+ *    - Add creation of pidfile (unix)
+ *    - Add action 'top' in rc file (RH)
+ *    - Add entry 'SIGNALS' to manpage
+ *    - Add exit message to logfile (unix)
+ *
+ *    Revision 1.25  2001/11/13 00:16:38  jongfoster
+ *    Replacing references to malloc.h with the standard stdlib.h
+ *    (See ANSI or K&R 2nd Ed)
+ *
+ *    Revision 1.24  2001/11/05 21:41:43  steudten
+ *    Add changes to be a real daemon just for unix os.
+ *    (change cwd to /, detach from controlling tty, set
+ *    process group and session leader to the own process.
+ *    Add DBG() Macro.
+ *    Add some fatal-error log message for failed malloc().
+ *    Add '-d' if compiled with 'configure --with-debug' to
+ *    enable debug output.
+ *
+ *    Revision 1.23  2001/10/29 03:48:10  david__schmidt
+ *    OS/2 native needed a snprintf() routine.  Added one to miscutil, brackedted
+ *    by and __OS2__ ifdef.
+ *
+ *    Revision 1.22  2001/10/26 17:39:38  oes
+ *    Moved ijb_isspace and ijb_tolower to project.h
+ *
+ *    Revision 1.21  2001/10/23 21:27:50  jongfoster
+ *    Standardising error codes in string_append
+ *    make_path() no longer adds '\\' if the dir already ends in '\\' (this
+ *    is just copying a UNIX-specific fix to the Windows-specific part)
+ *
+ *    Revision 1.20  2001/10/22 15:33:56  david__schmidt
+ *    Special-cased OS/2 out of the Netscape-abort-on-404-in-js problem in
+ *    filters.c.  Added a FIXME in front of the offending code.  I'll gladly
+ *    put in a better/more robust fix for all parties if one is presented...
+ *    It seems that just returning 200 instead of 404 would pretty much fix
+ *    it for everyone, but I don't know all the history of the problem.
+ *
+ *    Revision 1.19  2001/10/14 22:02:57  jongfoster
+ *    New function string_append() which is like strsav(), but running
+ *    out of memory isn't automatically FATAL.
+ *
+ *    Revision 1.18  2001/09/20 13:33:43  steudten
+ *
+ *    change long to int as return value in hash_string(). Remember the wraparound
+ *    for int = long = sizeof(4) - thats maybe not what we want.
+ *
+ *    Revision 1.17  2001/09/13 20:51:29  jongfoster
+ *    Fixing potential problems with characters >=128 in simplematch()
+ *    This was also a compiler warning.
+ *
+ *    Revision 1.16  2001/09/10 10:56:59  oes
+ *    Silenced compiler warnings
+ *
+ *    Revision 1.15  2001/07/13 14:02:24  oes
+ *    Removed vim-settings
+ *
+ *    Revision 1.14  2001/06/29 21:45:41  oes
+ *    Indentation, CRLF->LF, Tab-> Space
+ *
+ *    Revision 1.13  2001/06/29 13:32:14  oes
+ *    Removed logentry from cancelled commit
+ *
+ *    Revision 1.12  2001/06/09 10:55:28  jongfoster
+ *    Changing BUFSIZ ==> BUFFER_SIZE
+ *
+ *    Revision 1.11  2001/06/07 23:09:19  jongfoster
+ *    Cosmetic indentation changes.
+ *
+ *    Revision 1.10  2001/06/07 14:51:38  joergs
+ *    make_path() no longer adds '/' if the dir already ends in '/'.
+ *
+ *    Revision 1.9  2001/06/07 14:43:17  swa
+ *    slight mistake in make_path, unix path style is /.
+ *
+ *    Revision 1.8  2001/06/05 22:32:01  jongfoster
+ *    New function make_path() to splice directory and file names together.
+ *
+ *    Revision 1.7  2001/06/03 19:12:30  oes
+ *    introduced bindup()
+ *
+ *    Revision 1.6  2001/06/01 18:14:49  jongfoster
+ *    Changing the calls to strerr() to check HAVE_STRERR (which is defined
+ *    in config.h if appropriate) rather than the NO_STRERR macro.
+ *
+ *    Revision 1.5  2001/06/01 10:31:51  oes
+ *    Added character class matching to trivimatch; renamed to simplematch
+ *
+ *    Revision 1.4  2001/05/31 17:32:31  oes
+ *
+ *     - Enhanced domain part globbing with infix and prefix asterisk
+ *       matching and optional unanchored operation
+ *
+ *    Revision 1.3  2001/05/29 23:10:09  oes
+ *
+ *
+ *     - Introduced chomp()
+ *     - Moved strsav() from showargs to miscutil
+ *
+ *    Revision 1.2  2001/05/29 09:50:24  jongfoster
+ *    Unified blocklist/imagelist/permissionslist.
+ *    File format is still under discussion, but the internal changes
+ *    are (mostly) done.
+ *
+ *    Also modified interceptor behaviour:
+ *    - We now intercept all URLs beginning with one of the following
+ *      prefixes (and *only* these prefixes):
+ *        * http://i.j.b/
+ *        * http://ijbswa.sf.net/config/
+ *        * http://ijbswa.sourceforge.net/config/
+ *    - New interceptors "home page" - go to http://i.j.b/ to see it.
+ *    - Internal changes so that intercepted and fast redirect pages
+ *      are not replaced with an image.
+ *    - Interceptors now have the option to send a binary page direct
+ *      to the client. (i.e. ijb-send-banner uses this)
+ *    - Implemented show-url-info interceptor.  (Which is why I needed
+ *      the above interceptors changes - a typical URL is
+ *      "http://i.j.b/show-url-info?url=www.somesite.com/banner.gif".
+ *      The previous mechanism would not have intercepted that, and
+ *      if it had been intercepted then it then it would have replaced
+ *      it with an image.)
+ *
+ *    Revision 1.1.1.1  2001/05/15 13:59:00  oes
+ *    Initial import of version 2.9.3 source tree
+ *
+ *
+ *********************************************************************/
+\f
+
+#include "config.h"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <stdlib.h>
+#if !defined(_WIN32) && !defined(__OS2__)
+#include <unistd.h>
+#endif /* #if !defined(_WIN32) && !defined(__OS2__) */
+#include <string.h>
+#include <ctype.h>
+#include <assert.h>
+
+#include "project.h"
+#include "miscutil.h"
+#include "errlog.h"
+#include "jcc.h"
+
+const char miscutil_h_rcs[] = MISCUTIL_H_VERSION;
+
+/*********************************************************************
+ *
+ * Function    :  zalloc
+ *
+ * Description :  Malloc some memory and set it to '\0'.
+ *                The way calloc() ought to be -acjc
+ *
+ * Parameters  :
+ *          1  :  size = Size of memory chunk to return.
+ *
+ * Returns     :  Pointer to newly malloc'd memory chunk.
+ *
+ *********************************************************************/
+void *zalloc(size_t size)
+{
+   void * ret;
+
+   if ((ret = (void *)malloc(size)) != NULL)
+   {
+      memset(ret, 0, size);
+   }
+
+   return(ret);
+
+}
+
+
+#if defined(unix)
+/*********************************************************************
+ *
+ * Function    :  write_pid_file 
+ *
+ * Description :  Writes a pid file with the pid of the main process 
+ *
+ * Parameters  :  None
+ *
+ * Returns     :  N/A 
+ *
+ *********************************************************************/
+void write_pid_file(void)
+{
+   FILE   *fp;
+   
+   /*
+    * If no --pidfile option was given,
+    * we can live without one.
+    */
+   if (pidfile == NULL) return;
+
+   if ((fp = fopen(pidfile, "w")) == NULL)
+   {
+      log_error(LOG_LEVEL_INFO, "can't open pidfile '%s': %E", pidfile);
+   }
+   else
+   {
+      fprintf(fp, "%u\n", (unsigned int) getpid());
+      fclose (fp);
+   }
+   return;
+
+}
+#endif /* def unix */
+
+
+/*********************************************************************
+ *
+ * Function    :  hash_string
+ *
+ * Description :  Take a string and compute a (hopefuly) unique numeric
+ *                integer value.  This has several uses, but being able
+ *                to "switch" a string the one of my favorites.
+ *
+ * Parameters  :
+ *          1  :  s : string to be hashed.
+ *
+ * Returns     :  an unsigned long variable with the hashed value.
+ *
+ *********************************************************************/
+unsigned int hash_string( const char* s )
+{
+   unsigned int h = 0; 
+
+   for ( ; *s; ++s )
+   {
+      h = 5 * h + *s;
+   }
+
+   return (h);
+
+}
+
+
+#ifdef __MINGW32__
+/*********************************************************************
+ *
+ * Function    :  strdup
+ *
+ * Description :  For some reason (which is beyond me), gcc and WIN32
+ *                don't like strdup.  When a "free" is executed on a
+ *                strdup'd ptr, it can at times freez up!  So I just
+ *                replaced it and problem was solved.
+ *
+ * Parameters  :
+ *          1  :  s = string to duplicate
+ *
+ * Returns     :  Pointer to newly malloc'ed copy of the string.
+ *
+ *********************************************************************/
+char *strdup( const char *s )
+{
+   char * result = (char *)malloc( strlen(s)+1 );
+
+   if (result != NULL)
+   {
+      strcpy( result, s );
+   }
+
+   return( result );
+}
+
+#endif /* def __MINGW32__ */
+
+
+
+/*********************************************************************
+ *
+ * Function    :  safe_strerror
+ *
+ * Description :  Variant of the library routine strerror() which will
+ *                work on systems without the library routine, and
+ *                which should never return NULL.
+ *
+ * Parameters  :
+ *          1  :  err = the `errno' of the last operation.
+ *
+ * Returns     :  An "English" string of the last `errno'.  Allocated
+ *                with strdup(), so caller frees.  May be NULL if the
+ *                system is out of memory.
+ *
+ *********************************************************************/
+char *safe_strerror(int err)
+{
+   char *s = NULL;
+   char buf[BUFFER_SIZE];
+
+
+#ifdef HAVE_STRERROR
+   s = strerror(err);
+#endif /* HAVE_STRERROR */
+
+   if (s == NULL)
+   {
+      sprintf(buf, "(errno = %d)", err);
+      s = buf;
+   }
+
+   return(strdup(s));
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  strcmpic
+ *
+ * Description :  Case insensitive string comparison
+ *
+ * Parameters  :
+ *          1  :  s1 = string 1 to compare
+ *          2  :  s2 = string 2 to compare
+ *
+ * Returns     :  0 if s1==s2, Negative if s1<s2, Positive if s1>s2
+ *
+ *********************************************************************/
+int strcmpic(const char *s1, const char *s2)
+{
+   while (*s1 && *s2)
+   {
+      if ( ( *s1 != *s2 ) && ( ijb_tolower(*s1) != ijb_tolower(*s2) ) )
+      {
+         break;
+      }
+      s1++, s2++;
+   }
+   return(ijb_tolower(*s1) - ijb_tolower(*s2));
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  strncmpic
+ *
+ * Description :  Case insensitive string comparison (upto n characters)
+ *
+ * Parameters  :
+ *          1  :  s1 = string 1 to compare
+ *          2  :  s2 = string 2 to compare
+ *          3  :  n = maximum characters to compare
+ *
+ * Returns     :  0 if s1==s2, Negative if s1<s2, Positive if s1>s2
+ *
+ *********************************************************************/
+int strncmpic(const char *s1, const char *s2, size_t n)
+{
+   if (n <= 0) return(0);
+
+   while (*s1 && *s2)
+   {
+      if ( ( *s1 != *s2 ) && ( ijb_tolower(*s1) != ijb_tolower(*s2) ) )
+      {
+         break;
+      }
+
+      if (--n <= 0) break;
+
+      s1++, s2++;
+   }
+   return(ijb_tolower(*s1) - ijb_tolower(*s2));
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  chomp
+ *
+ * Description :  In-situ-eliminate all leading and trailing whitespace
+ *                from a string.
+ *
+ * Parameters  :
+ *          1  :  s : string to be chomped.
+ *
+ * Returns     :  chomped string
+ *
+ *********************************************************************/
+char *chomp(char *string)
+{
+   char *p, *q, *r;
+
+   /* 
+    * strip trailing whitespace
+    */
+   p = string + strlen(string);
+   while (p > string && ijb_isspace(*(p-1)))
+   {
+      p--;
+   }
+   *p = '\0';
+
+   /* 
+    * find end of leading whitespace 
+    */
+   q = r = string;
+   while (*q && ijb_isspace(*q))
+   {
+      q++;
+   }
+
+   /*
+    * if there was any, move the rest forwards
+    */
+   if (q != string)
+   {
+      while (q <= p)
+      {
+         *r++ = *q++;
+      }
+   }
+
+   return(string);
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  strsav
+ *
+ * Description :  Reallocate "old" and append text to it.  This makes
+ *                it easier to append to malloc'd strings.
+ *                Running out of memory is a FATAL error.
+ *
+ * Parameters  :
+ *          1  :  old = Old text that is to be extended.  Will be
+ *                free()d by this routine.  May be NULL.
+ *          2  :  text_to_append = Text to be appended to old.
+ *                May be NULL.
+ *
+ * Returns     :  Pointer to newly malloc'ed appended string.
+ *                If there is no text to append, return old.  Caller
+ *                must free().
+ *
+ *********************************************************************/
+char *strsav(char *old, const char *text_to_append)
+{
+   size_t old_len, new_len = 0;
+   char *p;
+
+   if ((text_to_append == NULL) || (*text_to_append == '\0'))
+   {
+      return(old);
+   }
+
+   if (NULL == old)
+   {
+      if ((p = strdup(text_to_append)) == NULL)
+      {
+         log_error(LOG_LEVEL_FATAL, "strdup() failed!");
+         /* Never get here - LOG_LEVEL_FATAL causes program exit */
+      }
+      return p;
+   }
+
+   old_len = strlen(old);
+   new_len = old_len + strlen(text_to_append) + 1;
+
+   if ((p = realloc(old, new_len)) == NULL)
+   {
+      log_error(LOG_LEVEL_FATAL, "realloc(%d) bytes failed!", new_len);
+      /* Never get here - LOG_LEVEL_FATAL causes program exit */
+   }
+
+   strcpy(p + old_len, text_to_append);
+   return(p);
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  string_append
+ *
+ * Description :  Reallocate target_string and append text to it.  
+ *                This makes it easier to append to malloc'd strings.
+ *                This is similar to the (removed) strsav(), but
+ *                running out of memory isn't catastrophic.
+ *
+ *                Programming style:
+ *
+ *                The following style provides sufficient error
+ *                checking for this routine, with minimal clutter
+ *                in the source code.  It is recommended if you
+ *                have many calls to this function:
+ *
+ *                char * s = strdup(...); // don't check for error
+ *                string_append(&s, ...);  // don't check for error
+ *                string_append(&s, ...);  // don't check for error
+ *                string_append(&s, ...);  // don't check for error
+ *                if (NULL == s) { ... handle error ... }
+ *
+ *                OR, equivalently:
+ *
+ *                char * s = strdup(...); // don't check for error
+ *                string_append(&s, ...);  // don't check for error
+ *                string_append(&s, ...);  // don't check for error
+ *                if (string_append(&s, ...)) {... handle error ...}
+ *
+ * Parameters  :
+ *          1  :  target_string = Pointer to old text that is to be
+ *                extended.  *target_string will be free()d by this
+ *                routine.  target_string must be non-NULL.
+ *                If *target_string is NULL, this routine will
+ *                do nothing and return with an error - this allows
+ *                you to make many calls to this routine and only
+ *                check for errors after the last one.
+ *          2  :  text_to_append = Text to be appended to old.
+ *                Must not be NULL.
+ *
+ * Returns     :  JB_ERR_OK on success, and sets *target_string
+ *                   to newly malloc'ed appended string.  Caller
+ *                   must free(*target_string).
+ *                JB_ERR_MEMORY on out-of-memory.  (And free()s
+ *                   *target_string and sets it to NULL).
+ *                JB_ERR_MEMORY if *target_string is NULL.
+ *
+ *********************************************************************/
+jb_err string_append(char **target_string, const char *text_to_append)
+{
+   size_t old_len;
+   char *new_string;
+
+   assert(target_string);
+   assert(text_to_append);
+
+   if (*target_string == NULL)
+   {
+      return JB_ERR_MEMORY;
+   }
+
+   if (*text_to_append == '\0')
+   {
+      return JB_ERR_OK;
+   }
+
+   old_len = strlen(*target_string);
+
+   if (NULL == (new_string = realloc(*target_string,
+          strlen(text_to_append) + old_len + 1)))
+   {
+      free(*target_string);
+
+      *target_string = NULL;
+      return JB_ERR_MEMORY;
+   }
+
+   strcpy(new_string + old_len, text_to_append);
+
+   *target_string = new_string;
+   return JB_ERR_OK;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  string_join
+ *
+ * Description :  Join two strings together.  Frees BOTH the original
+ *                strings.  If either or both input strings are NULL,
+ *                fails as if it had run out of memory.
+ *
+ *                For comparison, string_append requires that the
+ *                second string is non-NULL, and doesn't free it.
+ *
+ *                Rationale: Too often, we want to do
+ *                string_append(s, html_encode(s2)).  That assert()s
+ *                if s2 is NULL or if html_encode() runs out of memory.
+ *                It also leaks memory.  Proper checking is cumbersome.
+ *                The solution: string_join(s, html_encode(s2)) is safe,
+ *                and will free the memory allocated by html_encode().
+ *
+ * Parameters  :
+ *          1  :  target_string = Pointer to old text that is to be
+ *                extended.  *target_string will be free()d by this
+ *                routine.  target_string must be non-NULL.
+ *          2  :  text_to_append = Text to be appended to old.
+ *
+ * Returns     :  JB_ERR_OK on success, and sets *target_string
+ *                   to newly malloc'ed appended string.  Caller
+ *                   must free(*target_string).
+ *                JB_ERR_MEMORY on out-of-memory, or if
+ *                   *target_string or text_to_append is NULL.  (In
+ *                   this case, frees *target_string and text_to_append,
+ *                   sets *target_string to NULL).
+ *
+ *********************************************************************/
+jb_err string_join(char **target_string, char *text_to_append)
+{
+   jb_err err;
+
+   assert(target_string);
+
+   if (text_to_append == NULL)
+   {
+      freez(*target_string);
+      return JB_ERR_MEMORY;
+   }
+
+   err = string_append(target_string, text_to_append);
+
+   free(text_to_append);
+
+   return err;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  string_toupper
+ *
+ * Description :  Produce a copy of string with all convertible
+ *                characters converted to uppercase.
+ *
+ * Parameters  :
+ *          1  :  string = string to convert
+ *
+ * Returns     :  Uppercase copy of string if possible, 
+ *                NULL on out-of-memory or if string was NULL.
+ *
+ *********************************************************************/
+char *string_toupper(const char *string)
+{
+   char *result, *p;
+   const char *q;
+
+   if (!string || ((result = (char *) zalloc(strlen(string) + 1)) == NULL))
+   {
+      return NULL;
+   }
+   
+   q = string;
+   p = result;
+
+   while (*q != '\0')
+   {
+      *p++ = toupper(*q++);
+   }
+
+   return result;
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  simplematch
+ *
+ * Description :  String matching, with a (greedy) '*' wildcard that
+ *                stands for zero or more arbitrary characters and
+ *                character classes in [], which take both enumerations
+ *                and ranges.
+ *
+ * Parameters  :
+ *          1  :  pattern = pattern for matching
+ *          2  :  text    = text to be matched
+ *
+ * Returns     :  0 if match, else nonzero
+ *
+ *********************************************************************/
+int simplematch(char *pattern, char *text)
+{
+   unsigned char *pat = (unsigned char *) pattern;
+   unsigned char *txt = (unsigned char *) text;
+   unsigned char *fallback = pat; 
+   int wildcard = 0;
+  
+   unsigned char lastchar = 'a';
+   unsigned i;
+   unsigned char charmap[32];
+  
+  
+   while (*txt)
+   {
+
+      /* EOF pattern but !EOF text? */
+      if (*pat == '\0')
+      {
+         return 1;
+      }
+
+      /* '*' in the pattern?  */
+      if (*pat == '*') 
+      {
+     
+         /* The pattern ends afterwards? Speed up the return. */
+         if (*++pat == '\0')
+         {
+            return 0;
+         }
+     
+         /* Else, set wildcard mode and remember position after '*' */
+         wildcard = 1;
+         fallback = pat;
+      }
+
+      /* Character range specification? */
+      if (*pat == '[')
+      {
+         memset(charmap, '\0', sizeof(charmap));
+
+         while (*++pat != ']')
+         {
+            if (!*pat)
+            { 
+               return 1;
+            }
+            else if (*pat == '-')
+            {
+               if ((*++pat == ']') || *pat == '\0')
+               {
+                  return(1);
+               }
+               for(i = lastchar; i <= *pat; i++)
+               {
+                  charmap[i / 8] |= (1 << (i % 8));
+               } 
+            }
+            else
+            {
+               charmap[*pat / 8] |= (1 << (*pat % 8));
+               lastchar = *pat;
+            }
+         }
+      } /* -END- if Character range specification */
+
+
+      /* Compare: Char match, or char range match*/
+      if ((*pat == *txt)  
+      || ((*pat == ']') && (charmap[*txt / 8] & (1 << (*txt % 8)))) )
+      {
+         /* Sucess, go ahead */
+         pat++;
+      }
+      else
+      {
+         /* In wildcard mode, just try again after failiure */
+         if(wildcard)
+         {
+            pat = fallback;
+         }
+
+         /* Else, bad luck */
+         else
+         {
+            return 1;
+         }
+      }
+      txt++;
+   }
+
+   /* Cut off extra '*'s */
+   if(*pat == '*')  pat++;
+
+   /* If this is the pattern's end, fine! */
+   return(*pat);
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  bindup
+ *
+ * Description :  Duplicate the first n characters of a string that may
+ *                contain '\0' characters.
+ *
+ * Parameters  :
+ *          1  :  string = string to be duplicated
+ *          2  :  len = number of bytes to duplicate
+ *
+ * Returns     :  pointer to copy, or NULL if failiure
+ *
+ *********************************************************************/
+char *bindup(const char *string, size_t len)
+{
+   char *duplicate;
+
+   if (NULL == (duplicate = (char *)malloc(len)))
+   {
+      return NULL;
+   }
+   else
+   {
+     memcpy(duplicate, string, len);
+   }
+
+   return duplicate;
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  make_path
+ *
+ * Description :  Takes a directory name and a file name, returns 
+ *                the complete path.  Handles windows/unix differences.
+ *                If the file name is already an absolute path, or if
+ *                the directory name is NULL or empty, it returns 
+ *                the filename. 
+ *
+ * Parameters  :
+ *          1  :  dir: Name of directory or NULL for none.
+ *          2  :  file: Name of file.  Should not be NULL or empty.
+ *
+ * Returns     :  "dir/file" (Or on windows, "dir\file").
+ *                It allocates the string on the heap.  Caller frees.
+ *                Returns NULL in error (i.e. NULL file or out of
+ *                memory) 
+ *
+ *********************************************************************/
+char * make_path(const char * dir, const char * file)
+{
+#ifdef AMIGA
+   char path[512];
+
+   if(dir)
+   {
+      if(dir[0] == '.')
+      {
+         if(dir[1] == '/')
+         {
+            strncpy(path,dir+2,512);
+         }
+         else
+         {
+            strncpy(path,dir+1,512);
+         }
+      }
+      else
+      {
+         strncpy(path,dir,512);
+      }
+      path[511]=0;
+   } else {
+      path[0]=0;
+   }
+   if(AddPart(path,file,512))
+   {
+      return strdup(path);
+   } else {
+      return NULL;
+   }
+#else /* ndef AMIGA */
+
+   if ((file == NULL) || (*file == '\0'))
+   {
+      return NULL; /* Error */
+   }
+
+   if ((dir == NULL) || (*dir == '\0') /* No directory specified */
+#if defined(_WIN32) || defined(__OS2__)
+      || (*file == '\\') || (file[1] == ':') /* Absolute path (DOS) */
+#else /* ifndef _WIN32 || __OS2__ */
+      || (*file == '/') /* Absolute path (U*ix) */
+#endif /* ifndef _WIN32 || __OS2__  */
+      )
+   {
+      return strdup(file);
+   }
+   else
+   {
+      char * path;
+
+#if defined(unix)
+      if ( *dir != '/' && basedir && *basedir )
+      {
+         path = malloc( strlen( basedir ) + strlen(dir) + strlen(file) + 3);
+         if (!path ) log_error(LOG_LEVEL_FATAL, "malloc failed!");
+         strcpy(path, basedir);
+         strcat(path, "/");
+         strcat(path, dir);
+      }
+      else
+      {
+         path = malloc(strlen(dir) + strlen(file) + 2);
+         if (!path ) log_error(LOG_LEVEL_FATAL, "malloc failed!");
+         strcpy(path, dir);
+      }
+#else
+
+      path = malloc(strlen(dir) + strlen(file) + 2);
+      if (!path ) log_error(LOG_LEVEL_FATAL, "malloc failed!");
+      strcpy(path, dir);
+
+#endif /* defined unix */
+
+#if defined(_WIN32) || defined(__OS2__)
+      if(path[strlen(path)-1] != '\\')
+      {
+         strcat(path, "\\");
+      }
+#else /* ifndef _WIN32 || __OS2__ */
+      if(path[strlen(path)-1] != '/')
+      {
+         strcat(path, "/");
+      }
+#endif /* ifndef _WIN32 || __OS2__ */
+      strcat(path, file);
+
+      return path;
+   }
+#endif /* ndef AMIGA */
+}
+
+
+/*
+ * What follows is a portable snprintf routine, written by Mark Martinec.
+ * See: http://www.ijs.si/software/snprintf/
+ * Anyone who needs it can add a define for themselves... so far, only 
+ * OS/2 (native) lacks snprintf.
+
+                                  snprintf.c
+                   - a portable implementation of snprintf,
+       including vsnprintf.c, asnprintf, vasnprintf, asprintf, vasprintf
+                                       
+   snprintf is a routine to convert numeric and string arguments to
+   formatted strings. It is similar to sprintf(3) provided in a system's
+   C library, yet it requires an additional argument - the buffer size -
+   and it guarantees never to store anything beyond the given buffer,
+   regardless of the format or arguments to be formatted. Some newer
+   operating systems do provide snprintf in their C library, but many do
+   not or do provide an inadequate (slow or idiosyncratic) version, which
+   calls for a portable implementation of this routine.
+
+Author
+
+   Mark Martinec <mark.martinec@ijs.si>, April 1999, June 2000
+   Copyright Â© 1999, Mark Martinec
+
+ */
+
+#ifdef __OS2__
+
+#define PORTABLE_SNPRINTF_VERSION_MAJOR 2
+#define PORTABLE_SNPRINTF_VERSION_MINOR 2
+
+#if defined(NEED_ASPRINTF) || defined(NEED_ASNPRINTF) || defined(NEED_VASPRINTF) || defined(NEED_VASNPRINTF)
+# if defined(NEED_SNPRINTF_ONLY)
+# undef NEED_SNPRINTF_ONLY
+# endif
+# if !defined(PREFER_PORTABLE_SNPRINTF)
+# define PREFER_PORTABLE_SNPRINTF
+# endif
+#endif
+
+#if defined(SOLARIS_BUG_COMPATIBLE) && !defined(SOLARIS_COMPATIBLE)
+#define SOLARIS_COMPATIBLE
+#endif
+
+#if defined(HPUX_BUG_COMPATIBLE) && !defined(HPUX_COMPATIBLE)
+#define HPUX_COMPATIBLE
+#endif
+
+#if defined(DIGITAL_UNIX_BUG_COMPATIBLE) && !defined(DIGITAL_UNIX_COMPATIBLE)
+#define DIGITAL_UNIX_COMPATIBLE
+#endif
+
+#if defined(PERL_BUG_COMPATIBLE) && !defined(PERL_COMPATIBLE)
+#define PERL_COMPATIBLE
+#endif
+
+#if defined(LINUX_BUG_COMPATIBLE) && !defined(LINUX_COMPATIBLE)
+#define LINUX_COMPATIBLE
+#endif
+
+#include <sys/types.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdarg.h>
+#include <assert.h>
+#include <errno.h>
+
+#ifdef isdigit
+#undef isdigit
+#endif
+#define isdigit(c) ((c) >= '0' && (c) <= '9')
+
+/* For copying strings longer or equal to 'breakeven_point'
+ * it is more efficient to call memcpy() than to do it inline.
+ * The value depends mostly on the processor architecture,
+ * but also on the compiler and its optimization capabilities.
+ * The value is not critical, some small value greater than zero
+ * will be just fine if you don't care to squeeze every drop
+ * of performance out of the code.
+ *
+ * Small values favor memcpy, large values favor inline code.
+ */
+#if defined(__alpha__) || defined(__alpha)
+#  define breakeven_point   2    /* AXP (DEC Alpha)     - gcc or cc or egcs */
+#endif
+#if defined(__i386__)  || defined(__i386)
+#  define breakeven_point  12    /* Intel Pentium/Linux - gcc 2.96 */
+#endif
+#if defined(__hppa)
+#  define breakeven_point  10    /* HP-PA               - gcc */
+#endif
+#if defined(__sparc__) || defined(__sparc)
+#  define breakeven_point  33    /* Sun Sparc 5         - gcc 2.8.1 */
+#endif
+
+/* some other values of possible interest: */
+/* #define breakeven_point  8 */ /* VAX 4000          - vaxc */
+/* #define breakeven_point 19 */ /* VAX 4000          - gcc 2.7.0 */
+
+#ifndef breakeven_point
+#  define breakeven_point   6    /* some reasonable one-size-fits-all value */
+#endif
+
+#define fast_memcpy(d,s,n) \
+  { register size_t nn = (size_t)(n); \
+    if (nn >= breakeven_point) memcpy((d), (s), nn); \
+    else if (nn > 0) { /* proc call overhead is worth only for large strings*/\
+      register char *dd; register const char *ss; \
+      for (ss=(s), dd=(d); nn>0; nn--) *dd++ = *ss++; } }
+
+#define fast_memset(d,c,n) \
+  { register size_t nn = (size_t)(n); \
+    if (nn >= breakeven_point) memset((d), (int)(c), nn); \
+    else if (nn > 0) { /* proc call overhead is worth only for large strings*/\
+      register char *dd; register const int cc=(int)(c); \
+      for (dd=(d); nn>0; nn--) *dd++ = cc; } }
+
+/* prototypes */
+
+#if defined(NEED_ASPRINTF)
+int asprintf   (char **ptr, const char *fmt, /*args*/ ...);
+#endif
+#if defined(NEED_VASPRINTF)
+int vasprintf  (char **ptr, const char *fmt, va_list ap);
+#endif
+#if defined(NEED_ASNPRINTF)
+int asnprintf  (char **ptr, size_t str_m, const char *fmt, /*args*/ ...);
+#endif
+#if defined(NEED_VASNPRINTF)
+int vasnprintf (char **ptr, size_t str_m, const char *fmt, va_list ap);
+#endif
+
+#if defined(HAVE_SNPRINTF)
+/* declare our portable snprintf  routine under name portable_snprintf  */
+/* declare our portable vsnprintf routine under name portable_vsnprintf */
+#else
+/* declare our portable routines under names snprintf and vsnprintf */
+#define portable_snprintf snprintf
+#if !defined(NEED_SNPRINTF_ONLY)
+#define portable_vsnprintf vsnprintf
+#endif
+#endif
+
+#if !defined(HAVE_SNPRINTF) || defined(PREFER_PORTABLE_SNPRINTF)
+int portable_snprintf(char *str, size_t str_m, const char *fmt, /*args*/ ...);
+#if !defined(NEED_SNPRINTF_ONLY)
+int portable_vsnprintf(char *str, size_t str_m, const char *fmt, va_list ap);
+#endif
+#endif
+
+/* declarations */
+
+static char credits[] = "\n\
+@(#)snprintf.c, v2.2: Mark Martinec, <mark.martinec@ijs.si>\n\
+@(#)snprintf.c, v2.2: Copyright 1999, Mark Martinec. Frontier Artistic License applies.\n\
+@(#)snprintf.c, v2.2: http://www.ijs.si/software/snprintf/\n";
+
+#if defined(NEED_ASPRINTF)
+int asprintf(char **ptr, const char *fmt, /*args*/ ...) {
+  va_list ap;
+  size_t str_m;
+  int str_l;
+
+  *ptr = NULL;
+  va_start(ap, fmt);                            /* measure the required size */
+  str_l = portable_vsnprintf(NULL, (size_t)0, fmt, ap);
+  va_end(ap);
+  assert(str_l >= 0);        /* possible integer overflow if str_m > INT_MAX */
+  *ptr = (char *) malloc(str_m = (size_t)str_l + 1);
+  if (*ptr == NULL) { errno = ENOMEM; str_l = -1; }
+  else {
+    int str_l2;
+    va_start(ap, fmt);
+    str_l2 = portable_vsnprintf(*ptr, str_m, fmt, ap);
+    va_end(ap);
+    assert(str_l2 == str_l);
+  }
+  return str_l;
+}
+#endif
+
+#if defined(NEED_VASPRINTF)
+int vasprintf(char **ptr, const char *fmt, va_list ap) {
+  size_t str_m;
+  int str_l;
+
+  *ptr = NULL;
+  { va_list ap2;
+    va_copy(ap2, ap);  /* don't consume the original ap, we'll need it again */
+    str_l = portable_vsnprintf(NULL, (size_t)0, fmt, ap2);/*get required size*/
+    va_end(ap2);
+  }
+  assert(str_l >= 0);        /* possible integer overflow if str_m > INT_MAX */
+  *ptr = (char *) malloc(str_m = (size_t)str_l + 1);
+  if (*ptr == NULL) { errno = ENOMEM; str_l = -1; }
+  else {
+    int str_l2 = portable_vsnprintf(*ptr, str_m, fmt, ap);
+    assert(str_l2 == str_l);
+  }
+  return str_l;
+}
+#endif
+
+#if defined(NEED_ASNPRINTF)
+int asnprintf (char **ptr, size_t str_m, const char *fmt, /*args*/ ...) {
+  va_list ap;
+  int str_l;
+
+  *ptr = NULL;
+  va_start(ap, fmt);                            /* measure the required size */
+  str_l = portable_vsnprintf(NULL, (size_t)0, fmt, ap);
+  va_end(ap);
+  assert(str_l >= 0);        /* possible integer overflow if str_m > INT_MAX */
+  if ((size_t)str_l + 1 < str_m) str_m = (size_t)str_l + 1;      /* truncate */
+  /* if str_m is 0, no buffer is allocated, just set *ptr to NULL */
+  if (str_m == 0) {  /* not interested in resulting string, just return size */
+  } else {
+    *ptr = (char *) malloc(str_m);
+    if (*ptr == NULL) { errno = ENOMEM; str_l = -1; }
+    else {
+      int str_l2;
+      va_start(ap, fmt);
+      str_l2 = portable_vsnprintf(*ptr, str_m, fmt, ap);
+      va_end(ap);
+      assert(str_l2 == str_l);
+    }
+  }
+  return str_l;
+}
+#endif
+
+#if defined(NEED_VASNPRINTF)
+int vasnprintf (char **ptr, size_t str_m, const char *fmt, va_list ap) {
+  int str_l;
+
+  *ptr = NULL;
+  { va_list ap2;
+    va_copy(ap2, ap);  /* don't consume the original ap, we'll need it again */
+    str_l = portable_vsnprintf(NULL, (size_t)0, fmt, ap2);/*get required size*/
+    va_end(ap2);
+  }
+  assert(str_l >= 0);        /* possible integer overflow if str_m > INT_MAX */
+  if ((size_t)str_l + 1 < str_m) str_m = (size_t)str_l + 1;      /* truncate */
+  /* if str_m is 0, no buffer is allocated, just set *ptr to NULL */
+  if (str_m == 0) {  /* not interested in resulting string, just return size */
+  } else {
+    *ptr = (char *) malloc(str_m);
+    if (*ptr == NULL) { errno = ENOMEM; str_l = -1; }
+    else {
+      int str_l2 = portable_vsnprintf(*ptr, str_m, fmt, ap);
+      assert(str_l2 == str_l);
+    }
+  }
+  return str_l;
+}
+#endif
+
+/*
+ * If the system does have snprintf and the portable routine is not
+ * specifically required, this module produces no code for snprintf/vsnprintf.
+ */
+#if !defined(HAVE_SNPRINTF) || defined(PREFER_PORTABLE_SNPRINTF)
+
+#if !defined(NEED_SNPRINTF_ONLY)
+int portable_snprintf(char *str, size_t str_m, const char *fmt, /*args*/ ...) {
+  va_list ap;
+  int str_l;
+
+  va_start(ap, fmt);
+  str_l = portable_vsnprintf(str, str_m, fmt, ap);
+  va_end(ap);
+  return str_l;
+}
+#endif
+
+#if defined(NEED_SNPRINTF_ONLY)
+int portable_snprintf(char *str, size_t str_m, const char *fmt, /*args*/ ...) {
+#else
+int portable_vsnprintf(char *str, size_t str_m, const char *fmt, va_list ap) {
+#endif
+
+#if defined(NEED_SNPRINTF_ONLY)
+  va_list ap;
+#endif
+  size_t str_l = 0;
+  const char *p = fmt;
+
+/* In contrast with POSIX, the ISO C99 now says
+ * that str can be NULL and str_m can be 0.
+ * This is more useful than the old:  if (str_m < 1) return -1; */
+
+#if defined(NEED_SNPRINTF_ONLY)
+  va_start(ap, fmt);
+#endif
+  if (!p) p = "";
+  while (*p) {
+    if (*p != '%') {
+   /* if (str_l < str_m) str[str_l++] = *p++;    -- this would be sufficient */
+   /* but the following code achieves better performance for cases
+    * where format string is long and contains few conversions */
+      const char *q = strchr(p+1,'%');
+      size_t n = !q ? strlen(p) : (q-p);
+      if (str_l < str_m) {
+        size_t avail = str_m-str_l;
+        fast_memcpy(str+str_l, p, (n>avail?avail:n));
+      }
+      p += n; str_l += n;
+    } else {
+      const char *starting_p;
+      size_t min_field_width = 0, precision = 0;
+      int zero_padding = 0, precision_specified = 0, justify_left = 0;
+      int alternate_form = 0, force_sign = 0;
+      int space_for_positive = 1; /* If both the ' ' and '+' flags appear,
+                                     the ' ' flag should be ignored. */
+      char length_modifier = '\0';            /* allowed values: \0, h, l, L */
+      char tmp[32];/* temporary buffer for simple numeric->string conversion */
+
+      const char *str_arg;      /* string address in case of string argument */
+      size_t str_arg_l;         /* natural field width of arg without padding
+                                   and sign */
+      unsigned char uchar_arg;
+        /* unsigned char argument value - only defined for c conversion.
+           N.B. standard explicitly states the char argument for
+           the c conversion is unsigned */
+
+      size_t number_of_zeros_to_pad = 0;
+        /* number of zeros to be inserted for numeric conversions
+           as required by the precision or minimal field width */
+
+      size_t zero_padding_insertion_ind = 0;
+        /* index into tmp where zero padding is to be inserted */
+
+      char fmt_spec = '\0';
+        /* current conversion specifier character */
+
+      str_arg = credits;/* just to make compiler happy (defined but not used)*/
+      str_arg = NULL;
+      starting_p = p; p++;  /* skip '%' */
+   /* parse flags */
+      while (*p == '0' || *p == '-' || *p == '+' ||
+             *p == ' ' || *p == '#' || *p == '\'') {
+        switch (*p) {
+        case '0': zero_padding = 1; break;
+        case '-': justify_left = 1; break;
+        case '+': force_sign = 1; space_for_positive = 0; break;
+        case ' ': force_sign = 1;
+     /* If both the ' ' and '+' flags appear, the ' ' flag should be ignored */
+#ifdef PERL_COMPATIBLE
+     /* ... but in Perl the last of ' ' and '+' applies */
+                  space_for_positive = 1;
+#endif
+                  break;
+        case '#': alternate_form = 1; break;
+        case '\'': break;
+        }
+        p++;
+      }
+   /* If the '0' and '-' flags both appear, the '0' flag should be ignored. */
+
+   /* parse field width */
+      if (*p == '*') {
+        int j;
+        p++; j = va_arg(ap, int);
+        if (j >= 0) min_field_width = j;
+        else { min_field_width = -j; justify_left = 1; }
+      } else if (isdigit((int)(*p))) {
+        /* size_t could be wider than unsigned int;
+           make sure we treat argument like common implementations do */
+        unsigned int uj = *p++ - '0';
+        while (isdigit((int)(*p))) uj = 10*uj + (unsigned int)(*p++ - '0');
+        min_field_width = uj;
+      }
+   /* parse precision */
+      if (*p == '.') {
+        p++; precision_specified = 1;
+        if (*p == '*') {
+          int j = va_arg(ap, int);
+          p++;
+          if (j >= 0) precision = j;
+          else {
+            precision_specified = 0; precision = 0;
+         /* NOTE:
+          *   Solaris 2.6 man page claims that in this case the precision
+          *   should be set to 0.  Digital Unix 4.0, HPUX 10 and BSD man page
+          *   claim that this case should be treated as unspecified precision,
+          *   which is what we do here.
+          */
+          }
+        } else if (isdigit((int)(*p))) {
+          /* size_t could be wider than unsigned int;
+             make sure we treat argument like common implementations do */
+          unsigned int uj = *p++ - '0';
+          while (isdigit((int)(*p))) uj = 10*uj + (unsigned int)(*p++ - '0');
+          precision = uj;
+        }
+      }
+   /* parse 'h', 'l' and 'll' length modifiers */
+      if (*p == 'h' || *p == 'l') {
+        length_modifier = *p; p++;
+        if (length_modifier == 'l' && *p == 'l') {   /* double l = long long */
+#ifdef SNPRINTF_LONGLONG_SUPPORT
+          length_modifier = '2';                  /* double l encoded as '2' */
+#else
+          length_modifier = 'l';                 /* treat it as a single 'l' */
+#endif
+          p++;
+        }
+      }
+      fmt_spec = *p;
+   /* common synonyms: */
+      switch (fmt_spec) {
+      case 'i': fmt_spec = 'd'; break;
+      case 'D': fmt_spec = 'd'; length_modifier = 'l'; break;
+      case 'U': fmt_spec = 'u'; length_modifier = 'l'; break;
+      case 'O': fmt_spec = 'o'; length_modifier = 'l'; break;
+      default: break;
+      }
+   /* get parameter value, do initial processing */
+      switch (fmt_spec) {
+      case '%': /* % behaves similar to 's' regarding flags and field widths */
+      case 'c': /* c behaves similar to 's' regarding flags and field widths */
+      case 's':
+        length_modifier = '\0';          /* wint_t and wchar_t not supported */
+     /* the result of zero padding flag with non-numeric conversion specifier*/
+     /* is undefined. Solaris and HPUX 10 does zero padding in this case,    */
+     /* Digital Unix and Linux does not. */
+#if !defined(SOLARIS_COMPATIBLE) && !defined(HPUX_COMPATIBLE)
+        zero_padding = 0;    /* turn zero padding off for string conversions */
+#endif
+        str_arg_l = 1;
+        switch (fmt_spec) {
+        case '%':
+          str_arg = p; break;
+        case 'c': {
+          int j = va_arg(ap, int);
+          uchar_arg = (unsigned char) j;   /* standard demands unsigned char */
+          str_arg = (const char *) &uchar_arg;
+          break;
+        }
+        case 's':
+          str_arg = va_arg(ap, const char *);
+          if (!str_arg) str_arg_l = 0;
+       /* make sure not to address string beyond the specified precision !!! */
+          else if (!precision_specified) str_arg_l = strlen(str_arg);
+       /* truncate string if necessary as requested by precision */
+          else if (precision == 0) str_arg_l = 0;
+          else {
+       /* memchr on HP does not like n > 2^31  !!! */
+            const char *q = memchr(str_arg, '\0',
+                             precision <= 0x7fffffff ? precision : 0x7fffffff);
+            str_arg_l = !q ? precision : (q-str_arg);
+          }
+          break;
+        default: break;
+        }
+        break;
+      case 'd': case 'u': case 'o': case 'x': case 'X': case 'p': {
+        /* NOTE: the u, o, x, X and p conversion specifiers imply
+                 the value is unsigned;  d implies a signed value */
+
+        int arg_sign = 0;
+          /* 0 if numeric argument is zero (or if pointer is NULL for 'p'),
+            +1 if greater than zero (or nonzero for unsigned arguments),
+            -1 if negative (unsigned argument is never negative) */
+
+        int int_arg = 0;  unsigned int uint_arg = 0;
+          /* only defined for length modifier h, or for no length modifiers */
+
+        long int long_arg = 0;  unsigned long int ulong_arg = 0;
+          /* only defined for length modifier l */
+
+        void *ptr_arg = NULL;
+          /* pointer argument value -only defined for p conversion */
+
+#ifdef SNPRINTF_LONGLONG_SUPPORT
+        long long int long_long_arg = 0;
+        unsigned long long int ulong_long_arg = 0;
+          /* only defined for length modifier ll */
+#endif
+        if (fmt_spec == 'p') {
+        /* HPUX 10: An l, h, ll or L before any other conversion character
+         *   (other than d, i, u, o, x, or X) is ignored.
+         * Digital Unix:
+         *   not specified, but seems to behave as HPUX does.
+         * Solaris: If an h, l, or L appears before any other conversion
+         *   specifier (other than d, i, u, o, x, or X), the behavior
+         *   is undefined. (Actually %hp converts only 16-bits of address
+         *   and %llp treats address as 64-bit data which is incompatible
+         *   with (void *) argument on a 32-bit system).
+         */
+#ifdef SOLARIS_COMPATIBLE
+#  ifdef SOLARIS_BUG_COMPATIBLE
+          /* keep length modifiers even if it represents 'll' */
+#  else
+          if (length_modifier == '2') length_modifier = '\0';
+#  endif
+#else
+          length_modifier = '\0';
+#endif
+          ptr_arg = va_arg(ap, void *);
+          if (ptr_arg != NULL) arg_sign = 1;
+        } else if (fmt_spec == 'd') {  /* signed */
+          switch (length_modifier) {
+          case '\0':
+          case 'h':
+         /* It is non-portable to specify a second argument of char or short
+          * to va_arg, because arguments seen by the called function
+          * are not char or short.  C converts char and short arguments
+          * to int before passing them to a function.
+          */
+            int_arg = va_arg(ap, int);
+            if      (int_arg > 0) arg_sign =  1;
+            else if (int_arg < 0) arg_sign = -1;
+            break;
+          case 'l':
+            long_arg = va_arg(ap, long int);
+            if      (long_arg > 0) arg_sign =  1;
+            else if (long_arg < 0) arg_sign = -1;
+            break;
+#ifdef SNPRINTF_LONGLONG_SUPPORT
+          case '2':
+            long_long_arg = va_arg(ap, long long int);
+            if      (long_long_arg > 0) arg_sign =  1;
+            else if (long_long_arg < 0) arg_sign = -1;
+            break;
+#endif
+          }
+        } else {  /* unsigned */
+          switch (length_modifier) {
+          case '\0':
+          case 'h':
+            uint_arg = va_arg(ap, unsigned int);
+            if (uint_arg) arg_sign = 1;
+            break;
+          case 'l':
+            ulong_arg = va_arg(ap, unsigned long int);
+            if (ulong_arg) arg_sign = 1;
+            break;
+#ifdef SNPRINTF_LONGLONG_SUPPORT
+          case '2':
+            ulong_long_arg = va_arg(ap, unsigned long long int);
+            if (ulong_long_arg) arg_sign = 1;
+            break;
+#endif
+          }
+        }
+        str_arg = tmp; str_arg_l = 0;
+     /* NOTE:
+      *   For d, i, u, o, x, and X conversions, if precision is specified,
+      *   the '0' flag should be ignored. This is so with Solaris 2.6,
+      *   Digital UNIX 4.0, HPUX 10, Linux, FreeBSD, NetBSD; but not with Perl.
+      */
+#ifndef PERL_COMPATIBLE
+        if (precision_specified) zero_padding = 0;
+#endif
+        if (fmt_spec == 'd') {
+          if (force_sign && arg_sign >= 0)
+            tmp[str_arg_l++] = space_for_positive ? ' ' : '+';
+         /* leave negative numbers for sprintf to handle,
+            to avoid handling tricky cases like (short int)(-32768) */
+#ifdef LINUX_COMPATIBLE
+        } else if (fmt_spec == 'p' && force_sign && arg_sign > 0) {
+          tmp[str_arg_l++] = space_for_positive ? ' ' : '+';
+#endif
+        } else if (alternate_form) {
+          if (arg_sign != 0 && (fmt_spec == 'x' || fmt_spec == 'X') )
+            { tmp[str_arg_l++] = '0'; tmp[str_arg_l++] = fmt_spec; }
+         /* alternate form should have no effect for p conversion, but ... */
+#ifdef HPUX_COMPATIBLE
+          else if (fmt_spec == 'p'
+         /* HPUX 10: for an alternate form of p conversion,
+          *          a nonzero result is prefixed by 0x. */
+#ifndef HPUX_BUG_COMPATIBLE
+         /* Actually it uses 0x prefix even for a zero value. */
+                   && arg_sign != 0
+#endif
+                  ) { tmp[str_arg_l++] = '0'; tmp[str_arg_l++] = 'x'; }
+#endif
+        }
+        zero_padding_insertion_ind = str_arg_l;
+        if (!precision_specified) precision = 1;   /* default precision is 1 */
+        if (precision == 0 && arg_sign == 0
+#if defined(HPUX_BUG_COMPATIBLE) || defined(LINUX_COMPATIBLE)
+            && fmt_spec != 'p'
+         /* HPUX 10 man page claims: With conversion character p the result of
+          * converting a zero value with a precision of zero is a null string.
+          * Actually HP returns all zeroes, and Linux returns "(nil)". */
+#endif
+        ) {
+         /* converted to null string */
+         /* When zero value is formatted with an explicit precision 0,
+            the resulting formatted string is empty (d, i, u, o, x, X, p).   */
+        } else {
+          char f[5]; int f_l = 0;
+          f[f_l++] = '%';    /* construct a simple format string for sprintf */
+          if (!length_modifier) { }
+          else if (length_modifier=='2') { f[f_l++] = 'l'; f[f_l++] = 'l'; }
+          else f[f_l++] = length_modifier;
+          f[f_l++] = fmt_spec; f[f_l++] = '\0';
+          if (fmt_spec == 'p') str_arg_l += sprintf(tmp+str_arg_l, f, ptr_arg);
+          else if (fmt_spec == 'd') {  /* signed */
+            switch (length_modifier) {
+            case '\0':
+            case 'h': str_arg_l+=sprintf(tmp+str_arg_l, f, int_arg);  break;
+            case 'l': str_arg_l+=sprintf(tmp+str_arg_l, f, long_arg); break;
+#ifdef SNPRINTF_LONGLONG_SUPPORT
+            case '2': str_arg_l+=sprintf(tmp+str_arg_l,f,long_long_arg); break;
+#endif
+            }
+          } else {  /* unsigned */
+            switch (length_modifier) {
+            case '\0':
+            case 'h': str_arg_l+=sprintf(tmp+str_arg_l, f, uint_arg);  break;
+            case 'l': str_arg_l+=sprintf(tmp+str_arg_l, f, ulong_arg); break;
+#ifdef SNPRINTF_LONGLONG_SUPPORT
+            case '2': str_arg_l+=sprintf(tmp+str_arg_l,f,ulong_long_arg);break;
+#endif
+            }
+          }
+         /* include the optional minus sign and possible "0x"
+            in the region before the zero padding insertion point */
+          if (zero_padding_insertion_ind < str_arg_l &&
+              tmp[zero_padding_insertion_ind] == '-') {
+            zero_padding_insertion_ind++;
+          }
+          if (zero_padding_insertion_ind+1 < str_arg_l &&
+              tmp[zero_padding_insertion_ind]   == '0' &&
+             (tmp[zero_padding_insertion_ind+1] == 'x' ||
+              tmp[zero_padding_insertion_ind+1] == 'X') ) {
+            zero_padding_insertion_ind += 2;
+          }
+        }
+        { size_t num_of_digits = str_arg_l - zero_padding_insertion_ind;
+          if (alternate_form && fmt_spec == 'o'
+#ifdef HPUX_COMPATIBLE                                  /* ("%#.o",0) -> ""  */
+              && (str_arg_l > 0)
+#endif
+#ifdef DIGITAL_UNIX_BUG_COMPATIBLE                      /* ("%#o",0) -> "00" */
+#else
+              /* unless zero is already the first character */
+              && !(zero_padding_insertion_ind < str_arg_l
+                   && tmp[zero_padding_insertion_ind] == '0')
+#endif
+          ) {        /* assure leading zero for alternate-form octal numbers */
+            if (!precision_specified || precision < num_of_digits+1) {
+             /* precision is increased to force the first character to be zero,
+                except if a zero value is formatted with an explicit precision
+                of zero */
+              precision = num_of_digits+1; precision_specified = 1;
+            }
+          }
+       /* zero padding to specified precision? */
+          if (num_of_digits < precision) 
+            number_of_zeros_to_pad = precision - num_of_digits;
+        }
+     /* zero padding to specified minimal field width? */
+        if (!justify_left && zero_padding) {
+          int n = min_field_width - (str_arg_l+number_of_zeros_to_pad);
+          if (n > 0) number_of_zeros_to_pad += n;
+        }
+        break;
+      }
+      default: /* unrecognized conversion specifier, keep format string as-is*/
+        zero_padding = 0;  /* turn zero padding off for non-numeric convers. */
+#ifndef DIGITAL_UNIX_COMPATIBLE
+        justify_left = 1; min_field_width = 0;                /* reset flags */
+#endif
+#if defined(PERL_COMPATIBLE) || defined(LINUX_COMPATIBLE)
+     /* keep the entire format string unchanged */
+        str_arg = starting_p; str_arg_l = p - starting_p;
+     /* well, not exactly so for Linux, which does something inbetween,
+      * and I don't feel an urge to imitate it: "%+++++hy" -> "%+y"  */
+#else
+     /* discard the unrecognized conversion, just keep *
+      * the unrecognized conversion character          */
+        str_arg = p; str_arg_l = 0;
+#endif
+        if (*p) str_arg_l++;  /* include invalid conversion specifier unchanged
+                                 if not at end-of-string */
+        break;
+      }
+      if (*p) p++;      /* step over the just processed conversion specifier */
+   /* insert padding to the left as requested by min_field_width;
+      this does not include the zero padding in case of numerical conversions*/
+      if (!justify_left) {                /* left padding with blank or zero */
+        int n = min_field_width - (str_arg_l+number_of_zeros_to_pad);
+        if (n > 0) {
+          if (str_l < str_m) {
+            size_t avail = str_m-str_l;
+            fast_memset(str+str_l, (zero_padding?'0':' '), (n>avail?avail:n));
+          }
+          str_l += n;
+        }
+      }
+   /* zero padding as requested by the precision or by the minimal field width
+    * for numeric conversions required? */
+      if (number_of_zeros_to_pad <= 0) {
+     /* will not copy first part of numeric right now, *
+      * force it to be copied later in its entirety    */
+        zero_padding_insertion_ind = 0;
+      } else {
+     /* insert first part of numerics (sign or '0x') before zero padding */
+        int n = zero_padding_insertion_ind;
+        if (n > 0) {
+          if (str_l < str_m) {
+            size_t avail = str_m-str_l;
+            fast_memcpy(str+str_l, str_arg, (n>avail?avail:n));
+          }
+          str_l += n;
+        }
+     /* insert zero padding as requested by the precision or min field width */
+        n = number_of_zeros_to_pad;
+        if (n > 0) {
+          if (str_l < str_m) {
+            size_t avail = str_m-str_l;
+            fast_memset(str+str_l, '0', (n>avail?avail:n));
+          }
+          str_l += n;
+        }
+      }
+   /* insert formatted string
+    * (or as-is conversion specifier for unknown conversions) */
+      { int n = str_arg_l - zero_padding_insertion_ind;
+        if (n > 0) {
+          if (str_l < str_m) {
+            size_t avail = str_m-str_l;
+            fast_memcpy(str+str_l, str_arg+zero_padding_insertion_ind,
+                        (n>avail?avail:n));
+          }
+          str_l += n;
+        }
+      }
+   /* insert right padding */
+      if (justify_left) {          /* right blank padding to the field width */
+        int n = min_field_width - (str_arg_l+number_of_zeros_to_pad);
+        if (n > 0) {
+          if (str_l < str_m) {
+            size_t avail = str_m-str_l;
+            fast_memset(str+str_l, ' ', (n>avail?avail:n));
+          }
+          str_l += n;
+        }
+      }
+    }
+  }
+#if defined(NEED_SNPRINTF_ONLY)
+  va_end(ap);
+#endif
+  if (str_m > 0) { /* make sure the string is null-terminated
+                      even at the expense of overwriting the last character
+                      (shouldn't happen, but just in case) */
+    str[str_l <= str_m-1 ? str_l : str_m-1] = '\0';
+  }
+  /* Return the number of characters formatted (excluding trailing null
+   * character), that is, the number of characters that would have been
+   * written to the buffer if it were large enough.
+   *
+   * The value of str_l should be returned, but str_l is of unsigned type
+   * size_t, and snprintf is int, possibly leading to an undetected
+   * integer overflow, resulting in a negative return value, which is illegal.
+   * Both XSH5 and ISO C99 (at least the draft) are silent on this issue.
+   * Should errno be set to EOVERFLOW and EOF returned in this case???
+   */
+  return (int) str_l;
+}
+#endif
+#endif /* __OS2__ */
+/*
+  Local Variables:
+  tab-width: 3
+  end:
+*/
diff --git a/src/miscutil.h b/src/miscutil.h
new file mode 100644 (file)
index 0000000..77503cd
--- /dev/null
@@ -0,0 +1,197 @@
+#ifndef MISCUTIL_H_INCLUDED
+#define MISCUTIL_H_INCLUDED
+#define MISCUTIL_H_VERSION "$Id: miscutil.h,v 1.21 2002/04/26 12:55:38 oes Exp $"
+/*********************************************************************
+ *
+ * File        :  $Source: /cvsroot/ijbswa/current/miscutil.h,v $
+ *
+ * Purpose     :  zalloc, hash_string, safe_strerror, strcmpic,
+ *                strncmpic, and MinGW32 strdup functions.  These are
+ *                each too small to deserve their own file but don't 
+ *                really fit in any other file.
+ *
+ * Copyright   :  Written by and Copyright (C) 2001 the SourceForge
+ *                Privoxy team. http://www.privoxy.org/
+ *
+ *                Based on the Internet Junkbuster originally written
+ *                by and Copyright (C) 1997 Anonymous Coders and 
+ *                Junkbusters Corporation.  http://www.junkbusters.com
+ *
+ *                This program is free software; you can redistribute it 
+ *                and/or modify it under the terms of the GNU General
+ *                Public License as published by the Free Software
+ *                Foundation; either version 2 of the License, or (at
+ *                your option) any later version.
+ *
+ *                This program is distributed in the hope that it will
+ *                be useful, but WITHOUT ANY WARRANTY; without even the
+ *                implied warranty of MERCHANTABILITY or FITNESS FOR A
+ *                PARTICULAR PURPOSE.  See the GNU General Public
+ *                License for more details.
+ *
+ *                The GNU General Public License should be included with
+ *                this file.  If not, you can view it at
+ *                http://www.gnu.org/copyleft/gpl.html
+ *                or write to the Free Software Foundation, Inc., 59
+ *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * Revisions   :
+ *    $Log: miscutil.h,v $
+ *    Revision 1.21  2002/04/26 12:55:38  oes
+ *    New function string_toupper
+ *
+ *    Revision 1.20  2002/03/26 22:29:55  swa
+ *    we have a new homepage!
+ *
+ *    Revision 1.19  2002/03/24 13:25:43  swa
+ *    name change related issues
+ *
+ *    Revision 1.18  2002/03/07 03:46:17  oes
+ *    Fixed compiler warnings
+ *
+ *    Revision 1.17  2002/03/04 18:28:32  oes
+ *    Deleted deletePidFile, played syleguide police
+ *
+ *    Revision 1.16  2002/01/21 00:53:36  jongfoster
+ *    Adding string_join()
+ *
+ *    Revision 1.15  2001/12/30 14:07:32  steudten
+ *    - Add signal handling (unix)
+ *    - Add SIGHUP handler (unix)
+ *    - Add creation of pidfile (unix)
+ *    - Add action 'top' in rc file (RH)
+ *    - Add entry 'SIGNALS' to manpage
+ *    - Add exit message to logfile (unix)
+ *
+ *    Revision 1.14  2001/11/05 21:43:48  steudten
+ *    Add global var 'basedir' for unix os.
+ *
+ *    Revision 1.13  2001/10/29 03:48:10  david__schmidt
+ *    OS/2 native needed a snprintf() routine.  Added one to miscutil, brackedted
+ *    by and __OS2__ ifdef.
+ *
+ *    Revision 1.12  2001/10/23 21:27:50  jongfoster
+ *    Standardising error codes in string_append
+ *    make_path() no longer adds '\\' if the dir already ends in '\\' (this
+ *    is just copying a UNIX-specific fix to the Windows-specific part)
+ *
+ *    Revision 1.11  2001/10/14 22:02:57  jongfoster
+ *    New function string_append() which is like strsav(), but running
+ *    out of memory isn't automatically FATAL.
+ *
+ *    Revision 1.10  2001/09/20 13:34:09  steudten
+ *
+ *    change long to int for prototype hash_string()
+ *
+ *    Revision 1.9  2001/07/29 18:43:08  jongfoster
+ *    Changing #ifdef _FILENAME_H to FILENAME_H_INCLUDED, to conform to
+ *    ANSI C rules.
+ *
+ *    Revision 1.8  2001/06/29 13:32:14  oes
+ *    Removed logentry from cancelled commit
+ *
+ *    Revision 1.7  2001/06/05 22:32:01  jongfoster
+ *    New function make_path() to splice directory and file names together.
+ *
+ *    Revision 1.6  2001/06/03 19:12:30  oes
+ *    introduced bindup()
+ *
+ *    Revision 1.5  2001/06/01 10:31:51  oes
+ *    Added character class matching to trivimatch; renamed to simplematch
+ *
+ *    Revision 1.4  2001/05/31 17:32:31  oes
+ *
+ *     - Enhanced domain part globbing with infix and prefix asterisk
+ *       matching and optional unanchored operation
+ *
+ *    Revision 1.3  2001/05/29 23:10:09  oes
+ *
+ *
+ *     - Introduced chomp()
+ *     - Moved strsav() from showargs to miscutil
+ *
+ *    Revision 1.2  2001/05/29 09:50:24  jongfoster
+ *    Unified blocklist/imagelist/permissionslist.
+ *    File format is still under discussion, but the internal changes
+ *    are (mostly) done.
+ *
+ *    Also modified interceptor behaviour:
+ *    - We now intercept all URLs beginning with one of the following
+ *      prefixes (and *only* these prefixes):
+ *        * http://i.j.b/
+ *        * http://ijbswa.sf.net/config/
+ *        * http://ijbswa.sourceforge.net/config/
+ *    - New interceptors "home page" - go to http://i.j.b/ to see it.
+ *    - Internal changes so that intercepted and fast redirect pages
+ *      are not replaced with an image.
+ *    - Interceptors now have the option to send a binary page direct
+ *      to the client. (i.e. ijb-send-banner uses this)
+ *    - Implemented show-url-info interceptor.  (Which is why I needed
+ *      the above interceptors changes - a typical URL is
+ *      "http://i.j.b/show-url-info?url=www.somesite.com/banner.gif".
+ *      The previous mechanism would not have intercepted that, and
+ *      if it had been intercepted then it then it would have replaced
+ *      it with an image.)
+ *
+ *    Revision 1.1.1.1  2001/05/15 13:59:00  oes
+ *    Initial import of version 2.9.3 source tree
+ *
+ *
+ *********************************************************************/
+\f
+
+#include "project.h"
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+extern const char *basedir;
+extern void *zalloc(size_t size);
+
+#if defined(unix)
+extern void write_pid_file(void);
+#endif /* unix */
+
+extern unsigned int hash_string(const char* s);
+
+extern char *safe_strerror(int err);
+
+extern int strcmpic(const char *s1, const char *s2);
+extern int strncmpic(const char *s1, const char *s2, size_t n);
+
+extern char *strsav(char *old, const char *text_to_append);
+extern jb_err string_append(char **target_string, const char *text_to_append);
+extern jb_err string_join  (char **target_string,       char *text_to_append);
+
+extern char *string_toupper(const char *string);
+extern char *chomp(char *string);
+extern int simplematch(char *pattern, char *text);
+
+extern char *bindup(const char *string, size_t len);
+
+extern char *make_path(const char * dir, const char * file);
+
+#ifdef __MINGW32__
+extern char *strdup(const char *s);
+#endif /* def __MINGW32__ */
+
+#ifdef __OS2__
+extern int snprintf(char *, size_t, const char *, /*args*/ ...);
+#endif /* def __OS2__ */
+
+/* Revision control strings from this header and associated .c file */
+extern const char miscutil_rcs[];
+extern const char miscutil_h_rcs[];
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* ndef MISCUTIL_H_INCLUDED */
+
+/*
+  Local Variables:
+  tab-width: 3
+  end:
+*/
diff --git a/src/parsers.c b/src/parsers.c
new file mode 100644 (file)
index 0000000..317d0e3
--- /dev/null
@@ -0,0 +1,1794 @@
+const char parsers_rcs[] = "$Id: parsers.c,v 1.56 2002/05/12 15:34:22 jongfoster Exp $";
+/*********************************************************************
+ *
+ * File        :  $Source: /cvsroot/ijbswa/current/parsers.c,v $
+ *
+ * Purpose     :  Declares functions to parse/crunch headers and pages.
+ *                Functions declared include:
+ *                   `add_to_iob', `client_cookie_adder', `client_from',
+ *                   `client_referrer', `client_send_cookie', `client_ua',
+ *                   `client_uagent', `client_x_forwarded',
+ *                   `client_x_forwarded_adder', `client_xtra_adder',
+ *                   `content_type', `crumble', `destroy_list', `enlist',
+ *                   `flush_socket', ``get_header', `sed',
+ *                   and `server_set_cookie'.
+ *
+ * Copyright   :  Written by and Copyright (C) 2001 the SourceForge
+ *                Privoxy team. http://www.privoxy.org/
+ *
+ *                Based on the Internet Junkbuster originally written
+ *                by and Copyright (C) 1997 Anonymous Coders and
+ *                Junkbusters Corporation.  http://www.junkbusters.com
+ *
+ *                This program is free software; you can redistribute it
+ *                and/or modify it under the terms of the GNU General
+ *                Public License as published by the Free Software
+ *                Foundation; either version 2 of the License, or (at
+ *                your option) any later version.
+ *
+ *                This program is distributed in the hope that it will
+ *                be useful, but WITHOUT ANY WARRANTY; without even the
+ *                implied warranty of MERCHANTABILITY or FITNESS FOR A
+ *                PARTICULAR PURPOSE.  See the GNU General Public
+ *                License for more details.
+ *
+ *                The GNU General Public License should be included with
+ *                this file.  If not, you can view it at
+ *                http://www.gnu.org/copyleft/gpl.html
+ *                or write to the Free Software Foundation, Inc., 59
+ *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * Revisions   :
+ *    $Log: parsers.c,v $
+ *    Revision 1.56  2002/05/12 15:34:22  jongfoster
+ *    Fixing typo in a comment
+ *
+ *    Revision 1.55  2002/05/08 16:01:07  oes
+ *    Optimized add_to_iob:
+ *     - Use realloc instead of malloc(), memcpy(), free()
+ *     - Expand to powers of two if possible, to get
+ *       O(log n) reallocs instead of O(n).
+ *     - Moved check for buffer limit here from chat
+ *     - Report failure via returncode
+ *
+ *    Revision 1.54  2002/04/02 15:03:16  oes
+ *    Tiny code cosmetics
+ *
+ *    Revision 1.53  2002/03/26 22:29:55  swa
+ *    we have a new homepage!
+ *
+ *    Revision 1.52  2002/03/24 13:25:43  swa
+ *    name change related issues
+ *
+ *    Revision 1.51  2002/03/13 00:27:05  jongfoster
+ *    Killing warnings
+ *
+ *    Revision 1.50  2002/03/12 01:45:35  oes
+ *    More verbose logging
+ *
+ *    Revision 1.49  2002/03/09 20:03:52  jongfoster
+ *    - Making various functions return int rather than size_t.
+ *      (Undoing a recent change).  Since size_t is unsigned on
+ *      Windows, functions like read_socket that return -1 on
+ *      error cannot return a size_t.
+ *
+ *      THIS WAS A MAJOR BUG - it caused frequent, unpredictable
+ *      crashes, and also frequently caused JB to jump to 100%
+ *      CPU and stay there.  (Because it thought it had just
+ *      read ((unsigned)-1) == 4Gb of data...)
+ *
+ *    - The signature of write_socket has changed, it now simply
+ *      returns success=0/failure=nonzero.
+ *
+ *    - Trying to get rid of a few warnings --with-debug on
+ *      Windows, I've introduced a new type "jb_socket".  This is
+ *      used for the socket file descriptors.  On Windows, this
+ *      is SOCKET (a typedef for unsigned).  Everywhere else, it's
+ *      an int.  The error value can't be -1 any more, so it's
+ *      now JB_INVALID_SOCKET (which is -1 on UNIX, and in
+ *      Windows it maps to the #define INVALID_SOCKET.)
+ *
+ *    - The signature of bind_port has changed.
+ *
+ *    Revision 1.48  2002/03/07 03:46:53  oes
+ *    Fixed compiler warnings etc
+ *
+ *    Revision 1.47  2002/02/20 23:15:13  jongfoster
+ *    Parsing functions now handle out-of-memory gracefully by returning
+ *    an error code.
+ *
+ *    Revision 1.46  2002/01/17 21:03:47  jongfoster
+ *    Moving all our URL and URL pattern parsing code to urlmatch.c.
+ *
+ *    Revision 1.45  2002/01/09 14:33:03  oes
+ *    Added support for localtime_r.
+ *
+ *    Revision 1.44  2001/12/14 01:22:54  steudten
+ *    Remove 'user:pass@' from 'proto://user:pass@host' for the
+ *    new added header 'Host: ..'. (See Req ID 491818)
+ *
+ *    Revision 1.43  2001/11/23 00:26:38  jongfoster
+ *    Fixing two really stupid errors in my previous commit
+ *
+ *    Revision 1.42  2001/11/22 21:59:30  jongfoster
+ *    Adding code to handle +no-cookies-keep
+ *
+ *    Revision 1.41  2001/11/05 23:43:05  steudten
+ *    Add time+date to log files.
+ *
+ *    Revision 1.40  2001/10/26 20:13:09  jongfoster
+ *    ctype.h is needed in Windows, too.
+ *
+ *    Revision 1.39  2001/10/26 17:40:04  oes
+ *    Introduced get_header_value()
+ *    Removed http->user_agent, csp->referrer and csp->accept_types
+ *    Removed client_accept()
+ *
+ *    Revision 1.38  2001/10/25 03:40:48  david__schmidt
+ *    Change in porting tactics: OS/2's EMX porting layer doesn't allow multiple
+ *    threads to call select() simultaneously.  So, it's time to do a real, live,
+ *    native OS/2 port.  See defines for __EMX__ (the porting layer) vs. __OS2__
+ *    (native). Both versions will work, but using __OS2__ offers multi-threading.
+ *
+ *    Revision 1.37  2001/10/23 21:36:02  jongfoster
+ *    Documenting sed()'s error behaviou (doc change only)
+ *
+ *    Revision 1.36  2001/10/13 12:51:51  joergs
+ *    Removed client_host, (was only required for the old 2.0.2-11 http://noijb.
+ *    force-load), instead crumble Host: and add it (again) in client_host_adder
+ *    (in case we get a HTTP/1.0 request without Host: header and forward it to
+ *    a HTTP/1.1 server/proxy).
+ *
+ *    Revision 1.35  2001/10/09 22:39:21  jongfoster
+ *    assert.h is also required under Win32, so moving out of #ifndef _WIN32
+ *    block.
+ *
+ *    Revision 1.34  2001/10/07 18:50:55  oes
+ *    Added server_content_encoding, renamed server_transfer_encoding
+ *
+ *    Revision 1.33  2001/10/07 18:04:49  oes
+ *    Changed server_http11 to server_http and its pattern to "HTTP".
+ *      Additional functionality: it now saves the HTTP status into
+ *      csp->http->status and sets CT_TABOO for Status 206 (partial range)
+ *
+ *    Revision 1.32  2001/10/07 15:43:28  oes
+ *    Removed FEATURE_DENY_GZIP and replaced it with client_accept_encoding,
+ *       client_te and client_accept_encoding_adder, triggered by the new
+ *       +no-compression action. For HTTP/1.1 the Accept-Encoding header is
+ *       changed to allow only identity and chunked, and the TE header is
+ *       crunched. For HTTP/1.0, Accept-Encoding is crunched.
+ *
+ *    parse_http_request no longer does anything than parsing. The rewriting
+ *      of http->cmd and version mangling are gone. It now also recognizes
+ *      the put and delete methods and saves the url in http->url. Removed
+ *      unused variable.
+ *
+ *    renamed content_type and content_length to have the server_ prefix
+ *
+ *    server_content_type now only works if csp->content_type != CT_TABOO
+ *
+ *    added server_transfer_encoding, which
+ *      - Sets CT_TABOO to prohibit filtering if encoding compresses
+ *      - Raises the CSP_FLAG_CHUNKED flag if Encoding is "chunked"
+ *      - Change from "chunked" to "identity" if body was chunked
+ *        but has been de-chunked for filtering.
+ *
+ *    added server_content_md5 which crunches any Content-MD5 headers
+ *      if the body was modified.
+ *
+ *    made server_http11 conditional on +downgrade action
+ *
+ *    Replaced 6 boolean members of csp with one bitmap (csp->flags)
+ *
+ *    Revision 1.31  2001/10/05 14:25:02  oes
+ *    Crumble Keep-Alive from Server
+ *
+ *    Revision 1.30  2001/09/29 12:56:03  joergs
+ *    IJB now changes HTTP/1.1 to HTTP/1.0 in requests and answers.
+ *
+ *    Revision 1.29  2001/09/24 21:09:24  jongfoster
+ *    Fixing 2 memory leaks that Guy spotted, where the paramater to
+ *    enlist() was not being free()d.
+ *
+ *    Revision 1.28  2001/09/22 16:32:28  jongfoster
+ *    Removing unused #includes.
+ *
+ *    Revision 1.27  2001/09/20 15:45:25  steudten
+ *
+ *    add casting from size_t to int for printf()
+ *    remove local variable shadow s2
+ *
+ *    Revision 1.26  2001/09/16 17:05:14  jongfoster
+ *    Removing unused #include showarg.h
+ *
+ *    Revision 1.25  2001/09/16 13:21:27  jongfoster
+ *    Changes to use new list functions.
+ *
+ *    Revision 1.24  2001/09/13 23:05:50  jongfoster
+ *    Changing the string paramater to the header parsers a "const".
+ *
+ *    Revision 1.23  2001/09/12 18:08:19  steudten
+ *
+ *    In parse_http_request() header rewriting miss the host value, so
+ *    from http://www.mydomain.com the result was just " / " not
+ *    http://www.mydomain.com/ in case we forward.
+ *
+ *    Revision 1.22  2001/09/10 10:58:53  oes
+ *    Silenced compiler warnings
+ *
+ *    Revision 1.21  2001/07/31 14:46:00  oes
+ *     - Persistant connections now suppressed
+ *     - sed() no longer appends empty header to csp->headers
+ *
+ *    Revision 1.20  2001/07/30 22:08:36  jongfoster
+ *    Tidying up #defines:
+ *    - All feature #defines are now of the form FEATURE_xxx
+ *    - Permanently turned off WIN_GUI_EDIT
+ *    - Permanently turned on WEBDAV and SPLIT_PROXY_ARGS
+ *
+ *    Revision 1.19  2001/07/25 17:21:54  oes
+ *    client_uagent now saves copy of User-Agent: header value
+ *
+ *    Revision 1.18  2001/07/13 14:02:46  oes
+ *     - Included fix to repair broken HTTP requests that
+ *       don't contain a path, not even '/'.
+ *     - Removed all #ifdef PCRS
+ *     - content_type now always inspected and classified as
+ *       text, gif or other.
+ *     - formatting / comments
+ *
+ *    Revision 1.17  2001/06/29 21:45:41  oes
+ *    Indentation, CRLF->LF, Tab-> Space
+ *
+ *    Revision 1.16  2001/06/29 13:32:42  oes
+ *    - Fixed a comment
+ *    - Adapted free_http_request
+ *    - Removed logentry from cancelled commit
+ *
+ *    Revision 1.15  2001/06/03 19:12:38  oes
+ *    deleted const struct interceptors
+ *
+ *    Revision 1.14  2001/06/01 18:49:17  jongfoster
+ *    Replaced "list_share" with "list" - the tiny memory gain was not
+ *    worth the extra complexity.
+ *
+ *    Revision 1.13  2001/05/31 21:30:33  jongfoster
+ *    Removed list code - it's now in list.[ch]
+ *    Renamed "permission" to "action", and changed many features
+ *    to use the actions file rather than the global config.
+ *
+ *    Revision 1.12  2001/05/31 17:33:13  oes
+ *
+ *    CRLF -> LF
+ *
+ *    Revision 1.11  2001/05/29 20:11:19  joergs
+ *    '/ * inside comment' warning removed.
+ *
+ *    Revision 1.10  2001/05/29 09:50:24  jongfoster
+ *    Unified blocklist/imagelist/permissionslist.
+ *    File format is still under discussion, but the internal changes
+ *    are (mostly) done.
+ *
+ *    Also modified interceptor behaviour:
+ *    - We now intercept all URLs beginning with one of the following
+ *      prefixes (and *only* these prefixes):
+ *        * http://i.j.b/
+ *        * http://ijbswa.sf.net/config/
+ *        * http://ijbswa.sourceforge.net/config/
+ *    - New interceptors "home page" - go to http://i.j.b/ to see it.
+ *    - Internal changes so that intercepted and fast redirect pages
+ *      are not replaced with an image.
+ *    - Interceptors now have the option to send a binary page direct
+ *      to the client. (i.e. ijb-send-banner uses this)
+ *    - Implemented show-url-info interceptor.  (Which is why I needed
+ *      the above interceptors changes - a typical URL is
+ *      "http://i.j.b/show-url-info?url=www.somesite.com/banner.gif".
+ *      The previous mechanism would not have intercepted that, and
+ *      if it had been intercepted then it then it would have replaced
+ *      it with an image.)
+ *
+ *    Revision 1.9  2001/05/28 17:26:33  jongfoster
+ *    Fixing segfault if last header was crunched.
+ *    Fixing Windows build (snprintf() is _snprintf() under Win32, but we
+ *    can use the cross-platform sprintf() instead.)
+ *
+ *    Revision 1.8  2001/05/27 22:17:04  oes
+ *
+ *    - re_process_buffer no longer writes the modified buffer
+ *      to the client, which was very ugly. It now returns the
+ *      buffer, which it is then written by chat.
+ *
+ *    - content_length now adjusts the Content-Length: header
+ *      for modified documents rather than crunch()ing it.
+ *      (Length info in csp->content_length, which is 0 for
+ *      unmodified documents)
+ *
+ *    - For this to work, sed() is called twice when filtering.
+ *
+ *    Revision 1.7  2001/05/27 13:19:06  oes
+ *    Patched Joergs solution for the content-length in.
+ *
+ *    Revision 1.6  2001/05/26 13:39:32  jongfoster
+ *    Only crunches Content-Length header if applying RE filtering.
+ *    Without this fix, Microsoft Windows Update wouldn't work.
+ *
+ *    Revision 1.5  2001/05/26 00:28:36  jongfoster
+ *    Automatic reloading of config file.
+ *    Removed obsolete SIGHUP support (Unix) and Reload menu option (Win32).
+ *    Most of the global variables have been moved to a new
+ *    struct configuration_spec, accessed through csp->config->globalname
+ *    Most of the globals remaining are used by the Win32 GUI.
+ *
+ *    Revision 1.4  2001/05/22 18:46:04  oes
+ *
+ *    - Enabled filtering banners by size rather than URL
+ *      by adding patterns that replace all standard banner
+ *      sizes with the "Junkbuster" gif to the re_filterfile
+ *
+ *    - Enabled filtering WebBugs by providing a pattern
+ *      which kills all 1x1 images
+ *
+ *    - Added support for PCRE_UNGREEDY behaviour to pcrs,
+ *      which is selected by the (nonstandard and therefore
+ *      capital) letter 'U' in the option string.
+ *      It causes the quantifiers to be ungreedy by default.
+ *      Appending a ? turns back to greedy (!).
+ *
+ *    - Added a new interceptor ijb-send-banner, which
+ *      sends back the "Junkbuster" gif. Without imagelist or
+ *      MSIE detection support, or if tinygif = 1, or the
+ *      URL isn't recognized as an imageurl, a lame HTML
+ *      explanation is sent instead.
+ *
+ *    - Added new feature, which permits blocking remote
+ *      script redirects and firing back a local redirect
+ *      to the browser.
+ *      The feature is conditionally compiled, i.e. it
+ *      can be disabled with --disable-fast-redirects,
+ *      plus it must be activated by a "fast-redirects"
+ *      line in the config file, has its own log level
+ *      and of course wants to be displayed by show-proxy-args
+ *      Note: Boy, all the #ifdefs in 1001 locations and
+ *      all the fumbling with configure.in and acconfig.h
+ *      were *way* more work than the feature itself :-(
+ *
+ *    - Because a generic redirect template was needed for
+ *      this, tinygif = 3 now uses the same.
+ *
+ *    - Moved GIFs, and other static HTTP response templates
+ *      to project.h
+ *
+ *    - Some minor fixes
+ *
+ *    - Removed some >400 CRs again (Jon, you really worked
+ *      a lot! ;-)
+ *
+ *    Revision 1.3  2001/05/20 01:21:20  jongfoster
+ *    Version 2.9.4 checkin.
+ *    - Merged popupfile and cookiefile, and added control over PCRS
+ *      filtering, in new "permissionsfile".
+ *    - Implemented LOG_LEVEL_FATAL, so that if there is a configuration
+ *      file error you now get a message box (in the Win32 GUI) rather
+ *      than the program exiting with no explanation.
+ *    - Made killpopup use the PCRS MIME-type checking and HTTP-header
+ *      skipping.
+ *    - Removed tabs from "config"
+ *    - Moved duplicated url parsing code in "loaders.c" to a new funcition.
+ *    - Bumped up version number.
+ *
+ *    Revision 1.2  2001/05/17 23:02:36  oes
+ *     - Made referrer option accept 'L' as a substitute for '§'
+ *
+ *    Revision 1.1.1.1  2001/05/15 13:59:01  oes
+ *    Initial import of version 2.9.3 source tree
+ *
+ *
+ *********************************************************************/
+\f
+
+#include "config.h"
+
+#ifndef _WIN32
+#include <stdio.h>
+#include <sys/types.h>
+#endif
+
+#include <stdlib.h>
+#include <ctype.h>
+#include <assert.h>
+#include <string.h>
+
+#if !defined(_WIN32) && !defined(__OS2__)
+#include <unistd.h>
+#endif
+
+#include "project.h"
+#include "list.h"
+#include "parsers.h"
+#include "encode.h"
+#include "ssplit.h"
+#include "errlog.h"
+#include "jbsockets.h"
+#include "miscutil.h"
+#include "list.h"
+
+const char parsers_h_rcs[] = PARSERS_H_VERSION;
+
+/* Fix a problem with Solaris.  There should be no effect on other
+ * platforms.
+ * Solaris's isspace() is a macro which uses it's argument directly
+ * as an array index.  Therefore we need to make sure that high-bit
+ * characters generate +ve values, and ideally we also want to make
+ * the argument match the declared parameter type of "int".
+ *
+ * Why did they write a character function that can't take a simple
+ * "char" argument?  Doh!
+ */
+#define ijb_isupper(__X) isupper((int)(unsigned char)(__X))
+#define ijb_tolower(__X) tolower((int)(unsigned char)(__X))
+
+
+const struct parsers client_patterns[] = {
+   { "referer:",                 8,    client_referrer },
+   { "user-agent:",              11,   client_uagent },
+   { "ua-",                      3,    client_ua },
+   { "from:",                    5,    client_from },
+   { "cookie:",                  7,    client_send_cookie },
+   { "x-forwarded-for:",         16,   client_x_forwarded },
+   { "Accept-Encoding:",         16,   client_accept_encoding },
+   { "TE:",                      3,    client_te },
+   { "Host:",                     5,   crumble },
+/* { "if-modified-since:",       18,   crumble }, */
+   { "Keep-Alive:",              11,   crumble },
+   { "connection:",              11,   crumble },
+   { "proxy-connection:",        17,   crumble },
+   { NULL,                       0,    NULL }
+};
+
+
+const struct parsers server_patterns[] = {
+   { "HTTP",                4, server_http },
+   { "set-cookie:",        11, server_set_cookie },
+   { "connection:",        11, crumble },
+   { "Content-Type:",      13, server_content_type },
+   { "Content-Length:",    15, server_content_length },
+   { "Content-MD5:",       12, server_content_md5 },
+   { "Content-Encoding:",  17, server_content_encoding },
+   { "Transfer-Encoding:", 18, server_transfer_coding },
+   { "Keep-Alive:",        11, crumble },
+   { NULL, 0, NULL }
+};
+
+
+const add_header_func_ptr add_client_headers[] = {
+   client_host_adder,
+   client_cookie_adder,
+   client_x_forwarded_adder,
+   client_xtra_adder,
+   client_accept_encoding_adder,
+   connection_close_adder,
+   NULL
+};
+
+
+const add_header_func_ptr add_server_headers[] = {
+   connection_close_adder,
+   NULL
+};
+
+
+/*********************************************************************
+ *
+ * Function    :  flush_socket
+ *
+ * Description :  Write any pending "buffered" content.
+ *
+ * Parameters  :
+ *          1  :  fd = file descriptor of the socket to read
+ *          2  :  csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns     :  On success, the number of bytes written are returned (zero
+ *                indicates nothing was written).  On error, -1 is returned,
+ *                and errno is set appropriately.  If count is zero and the
+ *                file descriptor refers to a regular file, 0 will be
+ *                returned without causing any other effect.  For a special
+ *                file, the results are not portable.
+ *
+ *********************************************************************/
+int flush_socket(jb_socket fd, struct client_state *csp)
+{
+   struct iob *iob = csp->iob;
+   int len = iob->eod - iob->cur;
+
+   if (len <= 0)
+   {
+      return(0);
+   }
+
+   if (write_socket(fd, iob->cur, (size_t)len))
+   {
+      return(-1);
+   }
+   iob->eod = iob->cur = iob->buf;
+   return(len);
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  add_to_iob
+ *
+ * Description :  Add content to the buffered page, expanding the
+ *                buffer if necessary.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  buf = holds the content to be added to the page
+ *          3  :  n = number of bytes to be added
+ *
+ * Returns     :  JB_ERR_OK on success, JB_ERR_MEMORY if out-of-memory
+ *                or buffer limit reached.
+ *
+ *********************************************************************/
+jb_err add_to_iob(struct client_state *csp, char *buf, int n)
+{
+   struct iob *iob = csp->iob;
+   size_t used, offset, need, want;
+   char *p;
+
+   if (n <= 0) return JB_ERR_OK;
+
+   used   = iob->eod - iob->buf;
+   offset = iob->cur - iob->buf;
+   need   = used + n + 1;
+
+   /*
+    * If the buffer can't hold the new data, extend it first.
+    * Use the next power of two if possible, else use the actual need.
+    */
+   if (need > csp->config->buffer_limit)
+   {
+      log_error(LOG_LEVEL_ERROR, "Buffer limit reached while extending the buffer (iob)");
+      return JB_ERR_MEMORY;
+   }
+
+   if (need > iob->size)
+   {
+      for (want = csp->iob->size ? csp->iob->size : 512; want <= need;) want *= 2;
+      
+      if (want <= csp->config->buffer_limit && NULL != (p = (char *)realloc(iob->buf, want)))
+      {
+         iob->size = want;
+      }
+      else if (NULL != (p = (char *)realloc(iob->buf, need)))
+      {
+         iob->size = need;
+      }
+      else
+      {
+         log_error(LOG_LEVEL_ERROR, "Extending the buffer (iob) failed: %E");
+         return JB_ERR_MEMORY;
+      }
+
+      /* Update the iob pointers */
+      iob->cur = p + offset;
+      iob->eod = p + used;
+      iob->buf = p;
+   }
+
+   /* copy the new data into the iob buffer */
+   memcpy(iob->eod, buf, (size_t)n);
+
+   /* point to the end of the data */
+   iob->eod += n;
+
+   /* null terminate == cheap insurance */
+   *iob->eod = '\0';
+
+   return JB_ERR_OK;
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  get_header
+ *
+ * Description :  This (odd) routine will parse the csp->iob
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns     :  Any one of the following:
+ *
+ * 1) a pointer to a dynamically allocated string that contains a header line
+ * 2) NULL  indicating that the end of the header was reached
+ * 3) ""    indicating that the end of the iob was reached before finding
+ *          a complete header line.
+ *
+ *********************************************************************/
+char *get_header(struct client_state *csp)
+{
+   struct iob *iob;
+   char *p, *q, *ret;
+   iob = csp->iob;
+
+   if ((iob->cur == NULL)
+      || ((p = strchr(iob->cur, '\n')) == NULL))
+   {
+      return(""); /* couldn't find a complete header */
+   }
+
+   *p = '\0';
+
+   ret = strdup(iob->cur);
+   if (ret == NULL)
+   {
+      /* FIXME No way to handle error properly */
+      log_error(LOG_LEVEL_FATAL, "Out of memory in get_header()");
+   }
+
+   iob->cur = p+1;
+
+   if ((q = strchr(ret, '\r')) != NULL) *q = '\0';
+
+   /* is this a blank line (i.e. the end of the header) ? */
+   if (*ret == '\0')
+   {
+      freez(ret);
+      return(NULL);
+   }
+
+   return(ret);
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  get_header_value
+ *
+ * Description :  Get the value of a given header from a chained list
+ *                of header lines or return NULL if no such header is
+ *                present in the list.
+ *
+ * Parameters  :
+ *          1  :  header_list = pointer to list
+ *          2  :  header_name = string with name of header to look for.
+ *                              Trailing colon required, capitalization
+ *                              doesn't matter.
+ *
+ * Returns     :  NULL if not found, else value of header
+ *
+ *********************************************************************/
+char *get_header_value(const struct list *header_list, const char *header_name)
+{
+   struct list_entry *cur_entry;
+   char *ret = NULL;
+   size_t length = 0;
+
+   assert(header_list);
+   assert(header_name);
+   length = strlen(header_name);
+
+   for (cur_entry = header_list->first; cur_entry ; cur_entry = cur_entry->next)
+   {
+      if (cur_entry->str)
+      {
+         if (!strncmpic(cur_entry->str, header_name, length))
+         {
+            /*
+             * Found: return pointer to start of value
+             */
+            ret = (char *) (cur_entry->str + length);
+            while (*ret && ijb_isspace(*ret)) ret++;
+            return(ret);
+         }
+      }
+   }
+
+   /* 
+    * Not found
+    */
+   return NULL;
+
+}
+
+/*********************************************************************
+ *
+ * Function    :  sed
+ *
+ * Description :  add, delete or modify lines in the HTTP header streams.
+ *                On entry, it receives a linked list of headers space
+ *                that was allocated dynamically (both the list nodes
+ *                and the header contents).
+ *
+ *                As a side effect it frees the space used by the original
+ *                header lines.
+ *
+ * Parameters  :
+ *          1  :  pats = list of patterns to match against headers
+ *          2  :  more_headers = list of functions to add more
+ *                headers (client or server)
+ *          3  :  csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns     :  Single pointer to a fully formed header, or NULL
+ *                on out-of-memory error.
+ *
+ *********************************************************************/
+char *sed(const struct parsers pats[],
+          const add_header_func_ptr more_headers[],
+          struct client_state *csp)
+{
+   struct list_entry *p;
+   const struct parsers *v;
+   const add_header_func_ptr *f;
+   jb_err err = JB_ERR_OK;
+
+   for (v = pats; (err == JB_ERR_OK) && (v->str != NULL) ; v++)
+   {
+      for (p = csp->headers->first; (err == JB_ERR_OK) && (p != NULL) ; p = p->next)
+      {
+         /* Header crunch()ed in previous run? -> ignore */
+         if (p->str == NULL) continue;
+
+         if (v == pats) log_error(LOG_LEVEL_HEADER, "scan: %s", p->str);
+
+         if (strncmpic(p->str, v->str, v->len) == 0)
+         {
+            err = v->parser(csp, (char **)&(p->str));
+         }
+      }
+   }
+
+   /* place any additional headers on the csp->headers list */
+   for (f = more_headers; (err == JB_ERR_OK) && (*f) ; f++)
+   {
+      err = (*f)(csp);
+   }
+
+   if (err != JB_ERR_OK)
+   {
+      return NULL;
+   }
+
+   return list_to_text(csp->headers);
+}
+
+
+/* here begins the family of parser functions that reformat header lines */
+
+
+/*********************************************************************
+ *
+ * Function    :  crumble
+ *
+ * Description :  This is called if a header matches a pattern to "crunch"
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  header = On input, pointer to header to modify.
+ *                On output, pointer to the modified header, or NULL
+ *                to remove the header.  This function frees the
+ *                original string if necessary.
+ *
+ * Returns     :  JB_ERR_OK on success, or
+ *                JB_ERR_MEMORY on out-of-memory error.
+ *
+ *********************************************************************/
+jb_err crumble(struct client_state *csp, char **header)
+{
+   log_error(LOG_LEVEL_HEADER, "crunch!");
+   freez(*header);
+   return JB_ERR_OK;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  server_content_type
+ *
+ * Description :  Set the content-type for filterable types (text/.*,
+ *                javascript and image/gif) unless filtering has been
+ *                forbidden (CT_TABOO) while parsing earlier headers.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  header = On input, pointer to header to modify.
+ *                On output, pointer to the modified header, or NULL
+ *                to remove the header.  This function frees the
+ *                original string if necessary.
+ *
+ * Returns     :  JB_ERR_OK on success, or
+ *                JB_ERR_MEMORY on out-of-memory error.
+ *
+ *********************************************************************/
+jb_err server_content_type(struct client_state *csp, char **header)
+{
+   if (csp->content_type != CT_TABOO)
+   {
+      if (strstr(*header, " text/")
+       || strstr(*header, "application/x-javascript"))
+         csp->content_type = CT_TEXT;
+      else if (strstr(*header, " image/gif"))
+         csp->content_type = CT_GIF;
+      else
+         csp->content_type = 0;
+   }
+
+   return JB_ERR_OK;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  server_transfer_coding
+ *
+ * Description :  - Prohibit filtering (CT_TABOO) if transfer coding compresses
+ *                - Raise the CSP_FLAG_CHUNKED flag if coding is "chunked"
+ *                - Change from "chunked" to "identity" if body was chunked
+ *                  but has been de-chunked for filtering.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  header = On input, pointer to header to modify.
+ *                On output, pointer to the modified header, or NULL
+ *                to remove the header.  This function frees the
+ *                original string if necessary.
+ *
+ * Returns     :  JB_ERR_OK on success, or
+ *                JB_ERR_MEMORY on out-of-memory error.
+ *
+ *********************************************************************/
+jb_err server_transfer_coding(struct client_state *csp, char **header)
+{
+   /*
+    * Turn off pcrs and gif filtering if body compressed
+    */
+   if (strstr(*header, "gzip") || strstr(*header, "compress") || strstr(*header, "deflate"))
+   {
+      csp->content_type = CT_TABOO;
+   }
+
+   /*
+    * Raise flag if body chunked
+    */
+   if (strstr(*header, "chunked"))
+   {
+      csp->flags |= CSP_FLAG_CHUNKED;
+
+      /*
+       * If the body was modified, it has been
+       * de-chunked first, so adjust the header:
+       */
+      if (csp->flags & CSP_FLAG_MODIFIED)
+      {
+         freez(*header);
+         *header = strdup("Transfer-Encoding: identity");
+         return (header == NULL) ? JB_ERR_MEMORY : JB_ERR_OK;
+      }
+   }
+
+   return JB_ERR_OK;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  server_content_encoding
+ *
+ * Description :  Prohibit filtering (CT_TABOO) if content encoding compresses
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  header = On input, pointer to header to modify.
+ *                On output, pointer to the modified header, or NULL
+ *                to remove the header.  This function frees the
+ *                original string if necessary.
+ *
+ * Returns     :  JB_ERR_OK on success, or
+ *                JB_ERR_MEMORY on out-of-memory error.
+ *
+ *********************************************************************/
+jb_err server_content_encoding(struct client_state *csp, char **header)
+{
+   /*
+    * Turn off pcrs and gif filtering if body compressed
+    */
+   if (strstr(*header, "gzip") || strstr(*header, "compress") || strstr(*header, "deflate"))
+   {
+      csp->content_type = CT_TABOO;
+   }
+
+   return JB_ERR_OK;
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  server_content_length
+ *
+ * Description :  Adjust Content-Length header if we modified
+ *                the body.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  header = On input, pointer to header to modify.
+ *                On output, pointer to the modified header, or NULL
+ *                to remove the header.  This function frees the
+ *                original string if necessary.
+ *
+ * Returns     :  JB_ERR_OK on success, or
+ *                JB_ERR_MEMORY on out-of-memory error.
+ *
+ *********************************************************************/
+jb_err server_content_length(struct client_state *csp, char **header)
+{
+   if (csp->content_length != 0) /* Content length has been modified */
+   {
+      freez(*header);
+      *header = (char *) zalloc(100);
+      if (*header == NULL)
+      {
+         return JB_ERR_MEMORY;
+      }
+
+      sprintf(*header, "Content-Length: %d", (int) csp->content_length);
+
+      log_error(LOG_LEVEL_HEADER, "Adjust Content-Length to %d", (int) csp->content_length);
+   }
+
+   return JB_ERR_OK;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  server_content_md5
+ *
+ * Description :  Crumble any Content-MD5 headers if the document was
+ *                modified. FIXME: Should we re-compute instead?
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  header = On input, pointer to header to modify.
+ *                On output, pointer to the modified header, or NULL
+ *                to remove the header.  This function frees the
+ *                original string if necessary.
+ *
+ * Returns     :  JB_ERR_OK on success, or
+ *                JB_ERR_MEMORY on out-of-memory error.
+ *
+ *********************************************************************/
+jb_err server_content_md5(struct client_state *csp, char **header)
+{
+   if (csp->flags & CSP_FLAG_MODIFIED)
+   {
+      log_error(LOG_LEVEL_HEADER, "Crunching Content-MD5");
+      freez(*header);
+   }
+
+   return JB_ERR_OK;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  client_accept_encoding
+ *
+ * Description :  Rewrite the client's Accept-Encoding header so that
+ *                if doesn't allow compression, if the action applies.
+ *                Note: For HTTP/1.0 the absence of the header is enough.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  header = On input, pointer to header to modify.
+ *                On output, pointer to the modified header, or NULL
+ *                to remove the header.  This function frees the
+ *                original string if necessary.
+ *
+ * Returns     :  JB_ERR_OK on success, or
+ *                JB_ERR_MEMORY on out-of-memory error.
+ *
+ *********************************************************************/
+jb_err client_accept_encoding(struct client_state *csp, char **header)
+{
+   if ((csp->action->flags & ACTION_NO_COMPRESSION) != 0)
+   {
+      log_error(LOG_LEVEL_HEADER, "Supressed offer to compress content");
+
+      freez(*header);
+      if (!strcmpic(csp->http->ver, "HTTP/1.1"))
+      {
+         *header = strdup("Accept-Encoding: identity;q=1.0, *;q=0");
+         if (*header == NULL)
+         {
+            return JB_ERR_MEMORY;
+         }
+      }
+   }
+
+   return JB_ERR_OK;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  client_te
+ *
+ * Description :  Rewrite the client's TE header so that
+ *                if doesn't allow compression, if the action applies.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  header = On input, pointer to header to modify.
+ *                On output, pointer to the modified header, or NULL
+ *                to remove the header.  This function frees the
+ *                original string if necessary.
+ *
+ * Returns     :  JB_ERR_OK on success, or
+ *                JB_ERR_MEMORY on out-of-memory error.
+ *
+ *********************************************************************/
+jb_err client_te(struct client_state *csp, char **header)
+{
+   if ((csp->action->flags & ACTION_NO_COMPRESSION) != 0)
+   {
+      freez(*header);
+      log_error(LOG_LEVEL_HEADER, "Supressed offer to compress transfer");
+   }
+
+   return JB_ERR_OK;
+}
+
+/*********************************************************************
+ *
+ * Function    :  client_referrer
+ *
+ * Description :  Handle the "referer" config setting properly.
+ *                Called from `sed'.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  header = On input, pointer to header to modify.
+ *                On output, pointer to the modified header, or NULL
+ *                to remove the header.  This function frees the
+ *                original string if necessary.
+ *
+ * Returns     :  JB_ERR_OK on success, or
+ *                JB_ERR_MEMORY on out-of-memory error.
+ *
+ *********************************************************************/
+jb_err client_referrer(struct client_state *csp, char **header)
+{
+   const char *newval;
+
+#ifdef FEATURE_FORCE_LOAD
+   /* Since the referrer can include the prefix even
+    * even if the request itself is non-forced, we must
+    * clean it unconditionally
+    */
+   strclean(*header, FORCE_PREFIX);
+#endif /* def FEATURE_FORCE_LOAD */
+
+   /*
+    * Are we sending referer?
+    */
+   if ((csp->action->flags & ACTION_HIDE_REFERER) == 0)
+   {
+      return JB_ERR_OK;
+   }
+
+   freez(*header);
+
+   newval = csp->action->string[ACTION_STRING_REFERER];
+
+   if ((newval == NULL) || (0 == strcmpic(newval, "block")) )
+   {
+      /*
+       * Blocking referer
+       */
+      log_error(LOG_LEVEL_HEADER, "crunch!");
+      return JB_ERR_OK;
+   }
+   else if (0 == strncmpic(newval, "http://", 7))
+   {
+      /*
+       * We have a specific (fixed) referer we want to send.
+       */
+      log_error(LOG_LEVEL_HEADER, "modified");
+
+      *header = strdup("Referer: ");
+      string_append(header, newval);
+
+      return (*header == NULL) ? JB_ERR_MEMORY : JB_ERR_OK;
+   }
+   else
+   {
+      /*
+       * Forge a referer as http://[hostname:port of REQUEST]/
+       * to fool stupid checks for in-site links
+       */
+      if (0 != strcmpic(newval, "forge"))
+      {
+         /*
+          * Invalid choice - but forge is probably the best default.
+          */
+         log_error(LOG_LEVEL_ERROR, "Bad parameter: +referer{%s}", newval);
+      }
+
+      *header = strdup("Referer: http://");
+      string_append(header, csp->http->hostport);
+      string_append(header, "/");
+      log_error(LOG_LEVEL_HEADER, "crunch+forge to %s", *header);
+      
+      return (*header == NULL) ? JB_ERR_MEMORY : JB_ERR_OK;
+   }
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  client_uagent
+ *
+ * Description :  Handle the "user-agent" config setting properly
+ *                and remember its original value to enable browser
+ *                bug workarounds. Called from `sed'.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  header = On input, pointer to header to modify.
+ *                On output, pointer to the modified header, or NULL
+ *                to remove the header.  This function frees the
+ *                original string if necessary.
+ *
+ * Returns     :  JB_ERR_OK on success, or
+ *                JB_ERR_MEMORY on out-of-memory error.
+ *
+ *********************************************************************/
+jb_err client_uagent(struct client_state *csp, char **header)
+{
+   const char *newval;
+
+   if ((csp->action->flags & ACTION_HIDE_USER_AGENT) == 0)
+   {
+      return JB_ERR_OK;
+   }
+
+   newval = csp->action->string[ACTION_STRING_USER_AGENT];
+   if (newval == NULL)
+   {
+      return JB_ERR_OK;
+   }
+
+   log_error(LOG_LEVEL_HEADER, "modified");
+
+   freez(*header);
+   *header = strdup("User-Agent: ");
+   string_append(header, newval);
+
+   return (*header == NULL) ? JB_ERR_MEMORY : JB_ERR_OK;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  client_ua
+ *
+ * Description :  Handle "ua-" headers properly.  Called from `sed'.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  header = On input, pointer to header to modify.
+ *                On output, pointer to the modified header, or NULL
+ *                to remove the header.  This function frees the
+ *                original string if necessary.
+ *
+ * Returns     :  JB_ERR_OK on success, or
+ *                JB_ERR_MEMORY on out-of-memory error.
+ *
+ *********************************************************************/
+jb_err client_ua(struct client_state *csp, char **header)
+{
+   if ((csp->action->flags & ACTION_HIDE_USER_AGENT) != 0)
+   {
+      log_error(LOG_LEVEL_HEADER, "crunch!");
+      freez(*header);
+   }
+
+   return JB_ERR_OK;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  client_from
+ *
+ * Description :  Handle the "from" config setting properly.
+ *                Called from `sed'.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  header = On input, pointer to header to modify.
+ *                On output, pointer to the modified header, or NULL
+ *                to remove the header.  This function frees the
+ *                original string if necessary.
+ *
+ * Returns     :  JB_ERR_OK on success, or
+ *                JB_ERR_MEMORY on out-of-memory error.
+ *
+ *********************************************************************/
+jb_err client_from(struct client_state *csp, char **header)
+{
+   const char *newval;
+
+   if ((csp->action->flags & ACTION_HIDE_FROM) == 0)
+   {
+      return JB_ERR_OK;
+   }
+
+   freez(*header);
+
+   newval = csp->action->string[ACTION_STRING_FROM];
+
+   /*
+    * Are we blocking the e-mail address?
+    */
+   if ((newval == NULL) || (0 == strcmpic(newval, "block")) )
+   {
+      log_error(LOG_LEVEL_HEADER, "crunch!");
+      return JB_ERR_OK;
+   }
+
+   log_error(LOG_LEVEL_HEADER, " modified");
+
+   *header = strdup("From: ");
+   string_append(header, newval);
+
+   return (*header == NULL) ? JB_ERR_MEMORY : JB_ERR_OK;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  client_send_cookie
+ *
+ * Description :  Handle the "cookie" header properly.  Called from `sed'.
+ *                If cookie is accepted, add it to the cookie_list,
+ *                else we crunch it.  Mmmmmmmmmmm ... cookie ......
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  header = On input, pointer to header to modify.
+ *                On output, pointer to the modified header, or NULL
+ *                to remove the header.  This function frees the
+ *                original string if necessary.
+ *
+ * Returns     :  JB_ERR_OK on success, or
+ *                JB_ERR_MEMORY on out-of-memory error.
+ *
+ *********************************************************************/
+jb_err client_send_cookie(struct client_state *csp, char **header)
+{
+   jb_err result = JB_ERR_OK;
+
+   if ((csp->action->flags & ACTION_NO_COOKIE_READ) == 0)
+   {
+      /* strlen("cookie: ") == 8 */
+      result = enlist(csp->cookie_list, *header + 8);
+   }
+   else
+   {
+      log_error(LOG_LEVEL_HEADER, " crunch!");
+   }
+
+   /*
+    * Always remove the cookie here.  The cookie header
+    * will be sent at the end of the header.
+    */
+   freez(*header);
+
+   return result;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  client_x_forwarded
+ *
+ * Description :  Handle the "x-forwarded-for" config setting properly,
+ *                also used in the add_client_headers list.  Called from `sed'.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  header = On input, pointer to header to modify.
+ *                On output, pointer to the modified header, or NULL
+ *                to remove the header.  This function frees the
+ *                original string if necessary.
+ *
+ * Returns     :  JB_ERR_OK on success, or
+ *                JB_ERR_MEMORY on out-of-memory error.
+ *
+ *********************************************************************/
+jb_err client_x_forwarded(struct client_state *csp, char **header)
+{
+   if ((csp->action->flags & ACTION_HIDE_FORWARDED) == 0)
+   {
+      /* Save it so we can re-add it later */
+      freez(csp->x_forwarded);
+      csp->x_forwarded = *header;
+
+      /*
+       * Always set *header = NULL, since this information
+       * will be sent at the end of the header.
+       */
+      *header = NULL;
+   }
+   else
+   {
+      freez(*header);
+      log_error(LOG_LEVEL_HEADER, " crunch!");
+   }
+
+   return JB_ERR_OK;
+}
+
+/* the following functions add headers directly to the header list */
+
+/*********************************************************************
+ *
+ * Function    :  client_host_adder
+ *
+ * Description :  (re)adds the host header. Called from `sed'.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns     :  JB_ERR_OK on success, or
+ *                JB_ERR_MEMORY on out-of-memory error.
+ *
+ *********************************************************************/
+jb_err client_host_adder(struct client_state *csp)
+{
+   char *p;
+   char *pos;
+   jb_err err;
+
+   if ( !csp->http->hostport || !*(csp->http->hostport))
+   {
+      return JB_ERR_OK;
+   }
+
+   p = strdup("Host: ");
+   /*
+   ** remove 'user:pass@' from 'proto://user:pass@host'
+   */
+   if ( (pos = strchr( csp->http->hostport, '@')) != NULL )
+   {
+       string_append(&p, pos+1);
+   }
+   else
+   {
+      string_append(&p, csp->http->hostport);
+   }
+
+   if (p == NULL)
+   {
+      return JB_ERR_MEMORY;
+   }
+
+   log_error(LOG_LEVEL_HEADER, "addh: %s", p);
+
+   err = enlist(csp->headers, p);
+
+   freez(p);
+
+   return err;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  client_cookie_adder
+ *
+ * Description :  Used in the add_client_headers list.  Called from `sed'.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns     :  JB_ERR_OK on success, or
+ *                JB_ERR_MEMORY on out-of-memory error.
+ *
+ *********************************************************************/
+jb_err client_cookie_adder(struct client_state *csp)
+{
+   struct list_entry *lst;
+   char *tmp;
+   struct list_entry *list1 = csp->cookie_list->first;
+   struct list_entry *list2 = csp->action->multi[ACTION_MULTI_WAFER]->first;
+   int first_cookie = 1;
+   jb_err err;
+
+   if ((list1 == NULL) && (list2 == NULL))
+   {
+      /* Nothing to do */
+      return JB_ERR_OK;
+   }
+
+   tmp = strdup("Cookie: ");
+
+   for (lst = list1; lst ; lst = lst->next)
+   {
+      if (first_cookie)
+      {
+         first_cookie = 0;
+      }
+      else
+      {
+         string_append(&tmp, "; ");
+      }
+      string_append(&tmp, lst->str);
+   }
+
+   for (lst = list2;  lst ; lst = lst->next)
+   {
+      if (first_cookie)
+      {
+         first_cookie = 0;
+      }
+      else
+      {
+         string_append(&tmp, "; ");
+      }
+      string_join(&tmp, cookie_encode(lst->str));
+   }
+
+   if (tmp == NULL)
+   {
+      return JB_ERR_MEMORY;
+   }
+
+   log_error(LOG_LEVEL_HEADER, "addh: %s", tmp);
+   err = enlist(csp->headers, tmp);
+   free(tmp);
+   return err;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  client_accept_encoding_adder
+ *
+ * Description :  Add an Accept-Encoding header to the client's request
+ *                that disables compression if the action applies, and
+ *                the header is not already there. Called from `sed'.
+ *                Note: For HTTP/1.0, the absence of the header is enough.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns     :  JB_ERR_OK on success, or
+ *                JB_ERR_MEMORY on out-of-memory error.
+ *
+ *********************************************************************/
+jb_err client_accept_encoding_adder(struct client_state *csp)
+{
+   if (   ((csp->action->flags & ACTION_NO_COMPRESSION) != 0)
+       && (!strcmpic(csp->http->ver, "HTTP/1.1")) )
+   {
+      return enlist_unique(csp->headers, "Accept-Encoding: identity;q=1.0, *;q=0", 16);
+   }
+
+   return JB_ERR_OK;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  client_xtra_adder
+ *
+ * Description :  Used in the add_client_headers list.  Called from `sed'.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns     :  JB_ERR_OK on success, or
+ *                JB_ERR_MEMORY on out-of-memory error.
+ *
+ *********************************************************************/
+jb_err client_xtra_adder(struct client_state *csp)
+{
+   struct list_entry *lst;
+   jb_err err;
+
+   for (lst = csp->action->multi[ACTION_MULTI_ADD_HEADER]->first;
+        lst ; lst = lst->next)
+   {
+      log_error(LOG_LEVEL_HEADER, "addh: %s", lst->str);
+      err = enlist(csp->headers, lst->str);
+      if (err)
+      {
+         return err;
+      }
+
+   }
+
+   return JB_ERR_OK;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  client_x_forwarded_adder
+ *
+ * Description :  Used in the add_client_headers list.  Called from `sed'.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns     :  JB_ERR_OK on success, or
+ *                JB_ERR_MEMORY on out-of-memory error.
+ *
+ *********************************************************************/
+jb_err client_x_forwarded_adder(struct client_state *csp)
+{
+   char *p = NULL;
+   jb_err err;
+
+   if ((csp->action->flags & ACTION_HIDE_FORWARDED) != 0)
+   {
+      return JB_ERR_OK;
+   }
+
+   if (csp->x_forwarded)
+   {
+      p = strdup(csp->x_forwarded);
+      string_append(&p, ", ");
+   }
+   else
+   {
+      p = strdup("X-Forwarded-For: ");
+   }
+   string_append(&p, csp->ip_addr_str);
+
+   if (p == NULL)
+   {
+      return JB_ERR_MEMORY;
+   }
+
+   log_error(LOG_LEVEL_HEADER, "addh: %s", p);
+   err = enlist(csp->headers, p);
+   free(p);
+
+   return err;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  connection_close_adder
+ *
+ * Description :  Adds a "Connection: close" header to csp->headers
+ *                as a temporary fix for the needed but missing HTTP/1.1
+ *                support. Called from `sed'.
+ *                FIXME: This whole function shouldn't be neccessary!
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns     :  JB_ERR_OK on success, or
+ *                JB_ERR_MEMORY on out-of-memory error.
+ *
+ *********************************************************************/
+jb_err connection_close_adder(struct client_state *csp)
+{
+   return enlist(csp->headers, "Connection: close");
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  server_http
+ *
+ * Description :  - Save the HTTP Status into csp->http->status
+ *                - Set CT_TABOO to prevent filtering if the answer
+ *                  is a partial range (HTTP status 206)
+ *                - Rewrite HTTP/1.1 answers to HTTP/1.0 if +downgrade
+ *                  action applies.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  header = On input, pointer to header to modify.
+ *                On output, pointer to the modified header, or NULL
+ *                to remove the header.  This function frees the
+ *                original string if necessary.
+ *
+ * Returns     :  JB_ERR_OK on success, or
+ *                JB_ERR_MEMORY on out-of-memory error.
+ *
+ *********************************************************************/
+jb_err server_http(struct client_state *csp, char **header)
+{
+   sscanf(*header, "HTTP/%*d.%*d %d", &(csp->http->status));
+   if (csp->http->status == 206)
+   {
+      csp->content_type = CT_TABOO;
+   }
+
+   if ((csp->action->flags & ACTION_DOWNGRADE) != 0)
+   {
+      (*header)[7] = '0';
+      log_error(LOG_LEVEL_HEADER, "Downgraded answer to HTTP/1.0");
+   }
+
+   return JB_ERR_OK;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  server_set_cookie
+ *
+ * Description :  Handle the server "cookie" header properly.
+ *                Log cookie to the jar file.  Then "crunch" it,
+ *                or accept it.  Called from `sed'.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  header = On input, pointer to header to modify.
+ *                On output, pointer to the modified header, or NULL
+ *                to remove the header.  This function frees the
+ *                original string if necessary.
+ *
+ * Returns     :  JB_ERR_OK on success, or
+ *                JB_ERR_MEMORY on out-of-memory error.
+ *
+ *********************************************************************/
+jb_err server_set_cookie(struct client_state *csp, char **header)
+{
+#ifdef FEATURE_COOKIE_JAR
+   if (csp->config->jar)
+   {
+      /*
+       * Write timestamp into outbuf.
+       *
+       * Complex because not all OSs have tm_gmtoff or
+       * the %z field in strftime()
+       */
+      char tempbuf[ BUFFER_SIZE ];
+      time_t now; 
+      struct tm tm_now; 
+      time (&now); 
+#ifdef HAVE_LOCALTIME_R
+      tm_now = *localtime_r(&now, &tm_now);
+#else
+      tm_now = *localtime (&now); 
+#endif
+      strftime(tempbuf, BUFFER_SIZE-6, "%b %d %H:%M:%S ", &tm_now); 
+
+      /* strlen("set-cookie: ") = 12 */
+      fprintf(csp->config->jar, "%s %s\t%s\n", tempbuf, csp->http->host, *header + 12);
+   }
+#endif /* def FEATURE_COOKIE_JAR */
+
+   if ((csp->action->flags & ACTION_NO_COOKIE_SET) != 0)
+   {
+      return crumble(csp, header);
+   }
+   else if ((csp->action->flags & ACTION_NO_COOKIE_KEEP) != 0)
+   {
+      /* Flag whether or not to log a message */
+      int changed = 0;
+
+      /* A variable to store the tag we're working on */
+      char *cur_tag;
+
+      /* Skip "Set-Cookie:" (11 characters) in header */
+      cur_tag = *header + 11;
+
+      /* skip whitespace between "Set-Cookie:" and value */
+      while (*cur_tag && ijb_isspace(*cur_tag))
+      {
+         cur_tag++;
+      }
+
+      /* Loop through each tag in the cookie */
+      while (*cur_tag)
+      {
+         /* Find next tag */
+         char *next_tag = strchr(cur_tag, ';');
+         if (next_tag != NULL)
+         {
+            /* Skip the ';' character itself */
+            next_tag++;
+
+            /* skip whitespace ";" and start of tag */
+            while (*next_tag && ijb_isspace(*next_tag))
+            {
+               next_tag++;
+            }
+         }
+         else
+         {
+            /* "Next tag" is the end of the string */
+            next_tag = cur_tag + strlen(cur_tag);
+         }
+
+         /* Is this the "Expires" tag? */
+         if (strncmpic(cur_tag, "expires=", 8) == 0)
+         {
+            /* Delete the tag by copying the rest of the string over it.
+             * (Note that we cannot just use "strcpy(cur_tag, next_tag)",
+             * since the behaviour of strcpy is undefined for overlapping
+             * strings.)
+             */
+            memmove(cur_tag, next_tag, strlen(next_tag) + 1);
+
+            /* That changed the header, need to issue a log message */
+            changed = 1;
+
+            /* Note that the next tag has now been moved to *cur_tag,
+             * so we do not need to update the cur_tag pointer.
+             */
+         }
+         else
+         {
+            /* Move on to next cookie tag */
+            cur_tag = next_tag;
+         }
+      }
+
+      if (changed)
+      {
+         log_error(LOG_LEVEL_HEADER, "Changed cookie to a temporary one.");
+      }
+   }
+
+   return JB_ERR_OK;
+}
+
+
+#ifdef FEATURE_FORCE_LOAD
+/*********************************************************************
+ *
+ * Function    :  strclean
+ *
+ * Description :  In-Situ-Eliminate all occurances of substring in
+ *                string
+ *
+ * Parameters  :
+ *          1  :  string = string to clean
+ *          2  :  substring = substring to eliminate
+ *
+ * Returns     :  Number of eliminations
+ *
+ *********************************************************************/
+int strclean(const char *string, const char *substring)
+{
+   int hits = 0, len = strlen(substring);
+   char *pos, *p;
+
+   while((pos = strstr(string, substring)) != NULL)
+   {
+      p = pos + len;
+      do
+      {
+         *(p - len) = *p;
+      }
+      while (*p++ != '\0');
+
+      hits++;
+   }
+
+   return(hits);
+}
+#endif /* def FEATURE_FORCE_LOAD */
+
+
+/*
+  Local Variables:
+  tab-width: 3
+  end:
+*/
diff --git a/src/parsers.h b/src/parsers.h
new file mode 100644 (file)
index 0000000..5d22442
--- /dev/null
@@ -0,0 +1,234 @@
+#ifndef PARSERS_H_INCLUDED
+#define PARSERS_H_INCLUDED
+#define PARSERS_H_VERSION "$Id: parsers.h,v 1.26 2002/05/08 15:59:53 oes Exp $"
+/*********************************************************************
+ *
+ * File        :  $Source: /cvsroot/ijbswa/current/parsers.h,v $
+ *
+ * Purpose     :  Declares functions to parse/crunch headers and pages.
+ *                Functions declared include:
+ *                   `add_to_iob', `client_cookie_adder', `client_from',
+ *                   `client_referrer', `client_send_cookie', `client_ua',
+ *                   `client_uagent', `client_x_forwarded',
+ *                   `client_x_forwarded_adder', `client_xtra_adder',
+ *                   `content_type', `crumble', `destroy_list', `enlist',
+ *                   `flush_socket', `free_http_request', `get_header',
+ *                   `list_to_text', `parse_http_request', `sed',
+ *                   and `server_set_cookie'.
+ *
+ * Copyright   :  Written by and Copyright (C) 2001 the SourceForge
+ *                Privoxy team. http://www.privoxy.org/
+ *
+ *                Based on the Internet Junkbuster originally written
+ *                by and Copyright (C) 1997 Anonymous Coders and 
+ *                Junkbusters Corporation.  http://www.junkbusters.com
+ *
+ *                This program is free software; you can redistribute it 
+ *                and/or modify it under the terms of the GNU General
+ *                Public License as published by the Free Software
+ *                Foundation; either version 2 of the License, or (at
+ *                your option) any later version.
+ *
+ *                This program is distributed in the hope that it will
+ *                be useful, but WITHOUT ANY WARRANTY; without even the
+ *                implied warranty of MERCHANTABILITY or FITNESS FOR A
+ *                PARTICULAR PURPOSE.  See the GNU General Public
+ *                License for more details.
+ *
+ *                The GNU General Public License should be included with
+ *                this file.  If not, you can view it at
+ *                http://www.gnu.org/copyleft/gpl.html
+ *                or write to the Free Software Foundation, Inc., 59
+ *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * Revisions   :
+ *    $Log: parsers.h,v $
+ *    Revision 1.26  2002/05/08 15:59:53  oes
+ *    Changed add_to_iob signature (now returns jb_err)
+ *
+ *    Revision 1.25  2002/03/26 22:29:55  swa
+ *    we have a new homepage!
+ *
+ *    Revision 1.24  2002/03/24 13:25:43  swa
+ *    name change related issues
+ *
+ *    Revision 1.23  2002/03/13 00:27:05  jongfoster
+ *    Killing warnings
+ *
+ *    Revision 1.22  2002/03/09 20:03:52  jongfoster
+ *    - Making various functions return int rather than size_t.
+ *      (Undoing a recent change).  Since size_t is unsigned on
+ *      Windows, functions like read_socket that return -1 on
+ *      error cannot return a size_t.
+ *
+ *      THIS WAS A MAJOR BUG - it caused frequent, unpredictable
+ *      crashes, and also frequently caused JB to jump to 100%
+ *      CPU and stay there.  (Because it thought it had just
+ *      read ((unsigned)-1) == 4Gb of data...)
+ *
+ *    - The signature of write_socket has changed, it now simply
+ *      returns success=0/failure=nonzero.
+ *
+ *    - Trying to get rid of a few warnings --with-debug on
+ *      Windows, I've introduced a new type "jb_socket".  This is
+ *      used for the socket file descriptors.  On Windows, this
+ *      is SOCKET (a typedef for unsigned).  Everywhere else, it's
+ *      an int.  The error value can't be -1 any more, so it's
+ *      now JB_INVALID_SOCKET (which is -1 on UNIX, and in
+ *      Windows it maps to the #define INVALID_SOCKET.)
+ *
+ *    - The signature of bind_port has changed.
+ *
+ *    Revision 1.21  2002/03/07 03:46:17  oes
+ *    Fixed compiler warnings
+ *
+ *    Revision 1.20  2002/02/20 23:15:13  jongfoster
+ *    Parsing functions now handle out-of-memory gracefully by returning
+ *    an error code.
+ *
+ *    Revision 1.19  2002/01/17 21:03:47  jongfoster
+ *    Moving all our URL and URL pattern parsing code to urlmatch.c.
+ *
+ *    Revision 1.18  2001/10/26 17:40:23  oes
+ *    Introduced get_header_value()
+ *    Removed client_accept()
+ *
+ *    Revision 1.17  2001/10/13 12:47:32  joergs
+ *    Removed client_host, added client_host_adder
+ *
+ *    Revision 1.16  2001/10/07 18:50:16  oes
+ *    Added server_content_encoding, renamed server_transfer_encoding
+ *
+ *    Revision 1.15  2001/10/07 18:01:55  oes
+ *    Changed server_http11 to server_http
+ *
+ *    Revision 1.14  2001/10/07 15:45:48  oes
+ *    added client_accept_encoding, client_te, client_accept_encoding_adder
+ *
+ *    renamed content_type and content_length
+ *
+ *    fixed client_host and strclean prototypes
+ *
+ *    Revision 1.13  2001/09/29 12:56:03  joergs
+ *    IJB now changes HTTP/1.1 to HTTP/1.0 in requests and answers.
+ *
+ *    Revision 1.12  2001/09/13 23:05:50  jongfoster
+ *    Changing the string paramater to the header parsers a "const".
+ *
+ *    Revision 1.11  2001/07/31 14:46:53  oes
+ *    Added prototype for connection_close_adder
+ *
+ *    Revision 1.10  2001/07/30 22:08:36  jongfoster
+ *    Tidying up #defines:
+ *    - All feature #defines are now of the form FEATURE_xxx
+ *    - Permanently turned off WIN_GUI_EDIT
+ *    - Permanently turned on WEBDAV and SPLIT_PROXY_ARGS
+ *
+ *    Revision 1.9  2001/07/29 18:43:08  jongfoster
+ *    Changing #ifdef _FILENAME_H to FILENAME_H_INCLUDED, to conform to
+ *    ANSI C rules.
+ *
+ *    Revision 1.8  2001/07/13 14:01:54  oes
+ *    Removed all #ifdef PCRS
+ *
+ *    Revision 1.7  2001/06/29 13:32:14  oes
+ *    Removed logentry from cancelled commit
+ *
+ *    Revision 1.6  2001/06/03 19:12:38  oes
+ *    deleted const struct interceptors
+ *
+ *    Revision 1.5  2001/05/31 21:30:33  jongfoster
+ *    Removed list code - it's now in list.[ch]
+ *    Renamed "permission" to "action", and changed many features
+ *    to use the actions file rather than the global config.
+ *
+ *    Revision 1.4  2001/05/27 13:19:06  oes
+ *    Patched Joergs solution for the content-length in.
+ *
+ *    Revision 1.3  2001/05/26 13:39:32  jongfoster
+ *    Only crunches Content-Length header if applying RE filtering.
+ *    Without this fix, Microsoft Windows Update wouldn't work.
+ *
+ *    Revision 1.2  2001/05/20 01:21:20  jongfoster
+ *    Version 2.9.4 checkin.
+ *    - Merged popupfile and cookiefile, and added control over PCRS
+ *      filtering, in new "permissionsfile".
+ *    - Implemented LOG_LEVEL_FATAL, so that if there is a configuration
+ *      file error you now get a message box (in the Win32 GUI) rather
+ *      than the program exiting with no explanation.
+ *    - Made killpopup use the PCRS MIME-type checking and HTTP-header
+ *      skipping.
+ *    - Removed tabs from "config"
+ *    - Moved duplicated url parsing code in "loaders.c" to a new funcition.
+ *    - Bumped up version number.
+ *
+ *    Revision 1.1.1.1  2001/05/15 13:59:01  oes
+ *    Initial import of version 2.9.3 source tree
+ *
+ *
+ *********************************************************************/
+\f
+
+#include "project.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern const struct parsers client_patterns[];
+extern const struct parsers server_patterns[];
+
+extern const add_header_func_ptr add_client_headers[];
+extern const add_header_func_ptr add_server_headers[];
+
+extern int flush_socket(jb_socket fd, struct client_state *csp);
+extern jb_err add_to_iob(struct client_state *csp, char *buf, int n);
+extern char *get_header(struct client_state *csp);
+extern char *get_header_value(const struct list *header_list, const char *header_name);
+extern char *sed(const struct parsers pats[], const add_header_func_ptr more_headers[], struct client_state *csp);
+
+extern jb_err crumble                (struct client_state *csp, char **header);
+extern jb_err client_referrer        (struct client_state *csp, char **header);
+extern jb_err client_uagent          (struct client_state *csp, char **header);
+extern jb_err client_ua              (struct client_state *csp, char **header);
+extern jb_err client_from            (struct client_state *csp, char **header);
+extern jb_err client_send_cookie     (struct client_state *csp, char **header);
+extern jb_err client_x_forwarded     (struct client_state *csp, char **header);
+extern jb_err client_accept_encoding (struct client_state *csp, char **header);
+extern jb_err client_te              (struct client_state *csp, char **header);
+
+extern jb_err client_host_adder           (struct client_state *csp);
+extern jb_err client_cookie_adder         (struct client_state *csp);
+extern jb_err client_xtra_adder           (struct client_state *csp);
+extern jb_err client_accept_encoding_adder(struct client_state *csp);
+extern jb_err client_x_forwarded_adder    (struct client_state *csp);
+
+extern jb_err connection_close_adder      (struct client_state *csp); 
+
+extern jb_err server_set_cookie      (struct client_state *csp, char **header);
+extern jb_err server_content_type    (struct client_state *csp, char **header);
+extern jb_err server_content_length  (struct client_state *csp, char **header);
+extern jb_err server_content_md5     (struct client_state *csp, char **header);
+extern jb_err server_content_encoding(struct client_state *csp, char **header);
+extern jb_err server_transfer_coding (struct client_state *csp, char **header);
+extern jb_err server_http            (struct client_state *csp, char **header);
+
+#ifdef FEATURE_FORCE_LOAD
+extern int strclean(const char *string, const char *substring);
+#endif /* def FEATURE_FORCE_LOAD */
+
+/* Revision control strings from this header and associated .c file */
+extern const char parsers_rcs[];
+extern const char parsers_h_rcs[];
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* ndef PARSERS_H_INCLUDED */
+
+/*
+  Local Variables:
+  tab-width: 3
+  end:
+*/
diff --git a/src/pcrs.c b/src/pcrs.c
new file mode 100644 (file)
index 0000000..eb3b677
--- /dev/null
@@ -0,0 +1,942 @@
+const char pcrs_rcs[] = "$Id: pcrs.c,v 1.19 2002/03/08 14:47:48 oes Exp $";
+
+/*********************************************************************
+ *
+ * File        :  $Source: /cvsroot/ijbswa/current/pcrs.c,v $
+ *
+ * Purpose     :  pcrs is a supplement to the pcre library by Philip Hazel
+ *                <ph10@cam.ac.uk> and adds Perl-style substitution. That
+ *                is, it mimics Perl's 's' operator. See pcrs(3) for details.
+ *
+ *
+ * Copyright   :  Written and Copyright (C) 2000, 2001 by Andreas S. Oesterhelt
+ *                <andreas@oesterhelt.org>
+ *
+ *                This program is free software; you can redistribute it 
+ *                and/or modify it under the terms of the GNU Lesser
+ *                General Public License (LGPL), version 2.1, which  should
+ *                be included in this distribution (see LICENSE.txt), with
+ *                the exception that the permission to replace that license
+ *                with the GNU General Public License (GPL) given in section
+ *                3 is restricted to version 2 of the GPL.
+ *
+ *                This program is distributed in the hope that it will
+ *                be useful, but WITHOUT ANY WARRANTY; without even the
+ *                implied warranty of MERCHANTABILITY or FITNESS FOR A
+ *                PARTICULAR PURPOSE.  See the license for more details.
+ *
+ *                The GNU Lesser General Public License should be included
+ *                with this file.  If not, you can view it at
+ *                http://www.gnu.org/licenses/lgpl.html
+ *                or write to the Free Software Foundation, Inc., 59
+ *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * Revisions   :
+ *    $Log: pcrs.c,v $
+ *    Revision 1.19  2002/03/08 14:47:48  oes
+ *    Cosmetics
+ *
+ *    Revision 1.18  2002/03/08 14:17:14  oes
+ *    Fixing -Wconversion warnings
+ *
+ *    Revision 1.17  2002/03/08 13:45:48  oes
+ *    Hiding internal functions
+ *
+ *    Revision 1.16  2001/11/30 21:32:14  jongfoster
+ *    Fixing signed/unsigned comparison (Andreas please check this!)
+ *    One tab->space
+ *
+ *    Revision 1.15  2001/09/20 16:11:06  steudten
+ *
+ *    Add casting for some string functions.
+ *
+ *    Revision 1.14  2001/09/09 21:41:57  oes
+ *    Fixing yet another silly bug
+ *
+ *    Revision 1.13  2001/09/06 14:05:59  oes
+ *    Fixed silly bug
+ *
+ *    Revision 1.12  2001/08/18 11:35:00  oes
+ *    - Introduced pcrs_strerror()
+ *    - made some NULL arguments non-fatal
+ *    - added support for \n \r \e \b \t \f \a \0 in substitute
+ *    - made quoting adhere to standard rules
+ *    - added warning for bad backrefs
+ *    - added pcrs_execute_list()
+ *    - fixed comments
+ *    - bugfix & cosmetics
+ *
+ *    Revision 1.11  2001/08/15 15:32:03  oes
+ *     - Added support for Perl's special variables $+, $' and $`
+ *     - Improved the substitute parser
+ *     - Replaced the hard limit for the maximum number of matches
+ *       by dynamic reallocation
+ *
+ *    Revision 1.10  2001/08/05 13:13:11  jongfoster
+ *    Making parameters "const" where possible.
+ *
+ *    Revision 1.9  2001/07/18 17:27:00  oes
+ *    Changed interface; Cosmetics
+ *
+ *    Revision 1.8  2001/06/29 21:45:41  oes
+ *    Indentation, CRLF->LF, Tab-> Space
+ *
+ *    Revision 1.7  2001/06/29 13:33:04  oes
+ *    - Cleaned up, renamed and reordered functions,
+ *      improved comments
+ *    - Removed my_strsep
+ *    - Replaced globalflag with a general flags int
+ *      that holds PCRS_GLOBAL, PCRS_SUCCESS, and PCRS_TRIVIAL
+ *    - Introduced trivial option that will prevent pcrs
+ *      from honouring backreferences in the substitute,
+ *      which is useful for large substitutes that are
+ *      red in from somewhere and saves the pain of escaping
+ *      the backrefs
+ *    - Introduced convenience function pcrs_free_joblist()
+ *    - Split pcrs_make_job() into pcrs_compile(), which still
+ *      takes a complete s/// comand as argument and parses it,
+ *      and a new function pcrs_make_job, which takes the
+ *      three separate components. This should make for a
+ *      much friendlier frontend.
+ *    - Removed create_pcrs_job() which was useless
+ *    - Fixed a bug in pcrs_execute
+ *    - Success flag is now handled by pcrs instead of user
+ *
+ *    Revision 1.6  2001/06/03 19:12:45  oes
+ *    added FIXME
+ *
+ *    Revision 1.5  2001/05/29 09:50:24  jongfoster
+ *    (Fixed one int -> size_t)
+ *
+ *    Revision 1.4  2001/05/25 14:12:40  oes
+ *    Fixed bug: Empty substitutes now detected
+ *
+ *    Revision 1.3  2001/05/25 11:03:55  oes
+ *    Added sanity check for NULL jobs to pcrs_exec_substitution
+ *
+ *    Revision 1.2  2001/05/22 18:46:04  oes
+ *
+ *      Added support for PCRE_UNGREEDY behaviour to pcrs,
+ *      which is selected by the (nonstandard and therefore
+ *      capital) letter 'U' in the option string.
+ *      It causes the quantifiers to be ungreedy by default.
+ *      Appending a ? turns back to greedy (!).
+ *
+ *    Revision 1.1.1.1  2001/05/15 13:59:02  oes
+ *    Initial import of version 2.9.3 source tree
+ *
+ *
+ *********************************************************************/
+\f
+
+#include <pcre.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "pcrs.h"
+
+const char pcrs_h_rcs[] = PCRS_H_VERSION;
+
+/*
+ * Internal prototypes
+ */
+
+static int              pcrs_parse_perl_options(const char *optstring, int *flags);
+static pcrs_substitute *pcrs_compile_replacement(const char *replacement, int trivialflag,
+                        int capturecount, int *errptr);
+
+/*********************************************************************
+ *
+ * Function    :  pcrs_strerror
+ *
+ * Description :  Return a string describing a given error code.
+ *             
+ * Parameters  :
+ *          1  :  error = the error code
+ *
+ * Returns     :  char * to the descriptive string
+ *
+ *********************************************************************/
+const char *pcrs_strerror(const int error)
+{
+   if (error < 0)
+   {
+      switch (error)
+      {
+         /* Passed-through PCRE error: */
+         case PCRE_ERROR_NOMEMORY:     return "(pcre:) No memory";
+
+         /* Shouldn't happen unless PCRE or PCRS bug, or user messed with compiled job: */
+         case PCRE_ERROR_NULL:         return "(pcre:) NULL code or subject or ovector";
+         case PCRE_ERROR_BADOPTION:    return "(pcre:) Unrecognized option bit";
+         case PCRE_ERROR_BADMAGIC:     return "(pcre:) Bad magic number in code";
+         case PCRE_ERROR_UNKNOWN_NODE: return "(pcre:) Bad node in pattern";
+
+         /* Can't happen / not passed: */
+         case PCRE_ERROR_NOSUBSTRING:  return "(pcre:) Fire in power supply"; 
+         case PCRE_ERROR_NOMATCH:      return "(pcre:) Water in power supply";
+
+         /* PCRS errors: */
+         case PCRS_ERR_NOMEM:          return "(pcrs:) No memory";
+         case PCRS_ERR_CMDSYNTAX:      return "(pcrs:) Syntax error while parsing command";
+         case PCRS_ERR_STUDY:          return "(pcrs:) PCRE error while studying the pattern";
+         case PCRS_ERR_BADJOB:         return "(pcrs:) Bad job - NULL job, pattern or substitute";
+         case PCRS_WARN_BADREF:        return "(pcrs:) Backreference out of range";
+
+         /* What's that? */
+         default:  return "Unknown error";
+      }
+   }
+   /* error >= 0: No error */
+   return "(pcrs:) Everything's just fine. Thanks for asking.";
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  pcrs_parse_perl_options
+ *
+ * Description :  This function parses a string containing the options to
+ *                Perl's s/// operator. It returns an integer that is the
+ *                pcre equivalent of the symbolic optstring.
+ *                Since pcre doesn't know about Perl's 'g' (global) or pcrs',
+ *                'T' (trivial) options but pcrs needs them, the corresponding
+ *                flags are set if 'g'or 'T' is encountered.
+ *                Note: The 'T' and 'U' options do not conform to Perl.
+ *             
+ * Parameters  :
+ *          1  :  optstring = string with options in perl syntax
+ *          2  :  flags = see description
+ *
+ * Returns     :  option integer suitable for pcre 
+ *
+ *********************************************************************/
+static int pcrs_parse_perl_options(const char *optstring, int *flags)
+{
+   size_t i;
+   int rc = 0;
+   *flags = 0;
+
+   if (NULL == optstring) return 0;
+
+   for (i = 0; i < strlen(optstring); i++)
+   {
+      switch(optstring[i])
+      {
+         case 'e': break; /* ToDo ;-) */
+         case 'g': *flags |= PCRS_GLOBAL; break;
+         case 'i': rc |= PCRE_CASELESS; break;
+         case 'm': rc |= PCRE_MULTILINE; break;
+         case 'o': break;
+         case 's': rc |= PCRE_DOTALL; break;
+         case 'x': rc |= PCRE_EXTENDED; break;
+         case 'U': rc |= PCRE_UNGREEDY; break;
+         case 'T': *flags |= PCRS_TRIVIAL; break;
+         default: break;
+      }
+   }
+   return rc;
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  pcrs_compile_replacement
+ *
+ * Description :  This function takes a Perl-style replacement (2nd argument
+ *                to the s/// operator and returns a compiled pcrs_substitute,
+ *                or NULL if memory allocation for the substitute structure
+ *                fails.
+ *
+ * Parameters  :
+ *          1  :  replacement = replacement part of s/// operator
+ *                              in perl syntax
+ *          2  :  trivialflag = Flag that causes backreferences to be
+ *                              ignored.
+ *          3  :  capturecount = Number of capturing subpatterns in
+ *                               the pattern. Needed for $+ handling.
+ *          4  :  errptr = pointer to an integer in which error
+ *                         conditions can be returned.
+ *
+ * Returns     :  pcrs_substitute data structure, or NULL if an
+ *                error is encountered. In that case, *errptr has
+ *                the reason.
+ *
+ *********************************************************************/
+static pcrs_substitute *pcrs_compile_replacement(const char *replacement, int trivialflag, int capturecount, int *errptr)
+{
+   int i, k, l, quoted;
+   size_t length;
+   char *text;
+   pcrs_substitute *r;
+
+   i = k = l = quoted = 0;
+
+   /*
+    * Sanity check
+    */
+   if (NULL == replacement)
+   {
+      replacement = "";
+   }
+
+   /*
+    * Get memory or fail
+    */
+   if (NULL == (r = (pcrs_substitute *)malloc(sizeof(pcrs_substitute))))
+   {
+      *errptr = PCRS_ERR_NOMEM;
+      return NULL;
+   }
+   memset(r, '\0', sizeof(pcrs_substitute));
+
+   length = strlen(replacement);
+
+   if (NULL == (text = (char *)malloc(length + 1)))
+   {
+      free(r);
+      *errptr = PCRS_ERR_NOMEM;
+      return NULL;
+   }
+   memset(text, '\0', length + 1);
+   
+
+   /*
+    * In trivial mode, just copy the substitute text
+    */
+   if (trivialflag)
+   {
+      text = strncpy(text, replacement, length + 1);
+      k = length;
+   }
+
+   /*
+    * Else, parse, cut out and record all backreferences
+    */
+   else
+   {
+      while (i < (int)length)
+      {
+         /* Quoting */
+         if (replacement[i] == '\\')
+         {
+            if (quoted)
+            {
+               text[k++] = replacement[i++];
+               quoted = 0;
+            }
+            else
+            {
+               if (replacement[i+1] && strchr("tnrfae0", replacement[i+1]))
+               {
+                  switch (replacement[++i])
+                  {
+                  case 't':
+                     text[k++] = '\t';
+                     break;
+                  case 'n':
+                     text[k++] = '\n';
+                     break;
+                  case 'r':
+                     text[k++] = '\r';
+                     break;
+                  case 'f':
+                     text[k++] = '\f';
+                     break;
+                  case 'a':
+                     text[k++] = 7;
+                     break;
+                  case 'e':
+                     text[k++] = 27;
+                     break;
+                  case '0':
+                     text[k++] = '\0';
+                     break;
+                  }
+                  i++;
+               }
+               else
+               {
+                  quoted = 1;
+                  i++;
+               }
+            }
+            continue;
+         }
+
+         /* Backreferences */
+         if (replacement[i] == '$' && !quoted && i < (int)(length - 1))
+         {
+            char *symbol, symbols[] = "'`+&";
+            r->block_length[l] = k - r->block_offset[l];
+
+            /* Numerical backreferences */
+            if (isdigit((int)replacement[i + 1]))
+            {
+               while (i < (int)length && isdigit((int)replacement[++i]))
+               {
+                  r->backref[l] = r->backref[l] * 10 + replacement[i] - 48;
+               }
+               if (r->backref[l] > capturecount)
+               {
+                  *errptr = PCRS_WARN_BADREF;
+               }
+            }
+
+            /* Symbolic backreferences: */
+            else if (NULL != (symbol = strchr(symbols, replacement[i + 1])))
+            {
+               
+               if (symbol - symbols == 2) /* $+ */
+               {
+                  r->backref[l] = capturecount;
+               }
+               else if (symbol - symbols == 3) /* $& */
+               {
+                  r->backref[l] = 0;
+               }
+               else /* $' or $` */
+               {
+                  r->backref[l] = PCRS_MAX_SUBMATCHES + 1 - (symbol - symbols);
+               }
+               i += 2;
+            }
+
+            /* Invalid backref -> plain '$' */
+            else
+            {
+               goto plainchar;
+            }
+
+            /* Valid and in range? -> record */
+            if (r->backref[l] < PCRS_MAX_SUBMATCHES + 2)
+            {
+               r->backref_count[r->backref[l]] += 1;
+               r->block_offset[++l] = k;
+            }
+            else
+            {
+               *errptr = PCRS_WARN_BADREF;
+            }   
+            continue;
+         }
+         
+plainchar:
+         /* Plain chars are copied */
+         text[k++] = replacement[i++];
+         quoted = 0;
+      }
+   } /* -END- if (!trivialflag) */
+
+   /*
+    * Finish & return
+    */
+   r->text = text;
+   r->backrefs = l;
+   r->block_length[l] = k - r->block_offset[l];
+
+   return r;
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  pcrs_free_job
+ *
+ * Description :  Frees the memory used by a pcrs_job struct and its
+ *                dependant structures.
+ *
+ * Parameters  :
+ *          1  :  job = pointer to the pcrs_job structure to be freed
+ *
+ * Returns     :  a pointer to the next job, if there was any, or
+ *                NULL otherwise. 
+ *
+ *********************************************************************/
+pcrs_job *pcrs_free_job(pcrs_job *job)
+{
+   pcrs_job *next;
+
+   if (job == NULL)
+   {
+      return NULL;
+   }
+   else
+   {
+      next = job->next;
+      if (job->pattern != NULL) free(job->pattern);
+      if (job->hints != NULL) free(job->hints);
+      if (job->substitute != NULL)
+      {
+         if (job->substitute->text != NULL) free(job->substitute->text);
+         free(job->substitute);
+      }
+      free(job);
+   }
+   return next;
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  pcrs_free_joblist
+ *
+ * Description :  Iterates through a chained list of pcrs_job's and
+ *                frees them using pcrs_free_job.
+ *
+ * Parameters  :
+ *          1  :  joblist = pointer to the first pcrs_job structure to
+ *                be freed
+ *
+ * Returns     :  N/A
+ *
+ *********************************************************************/
+void pcrs_free_joblist(pcrs_job *joblist)
+{
+   while ( NULL != (joblist = pcrs_free_job(joblist)) ) {};
+
+   return;
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  pcrs_compile_command
+ *
+ * Description :  Parses a string with a Perl-style s/// command, 
+ *                calls pcrs_compile, and returns a corresponding
+ *                pcrs_job, or NULL if parsing or compiling the job
+ *                fails.
+ *
+ * Parameters  :
+ *          1  :  command = string with perl-style s/// command
+ *          2  :  errptr = pointer to an integer in which error
+ *                         conditions can be returned.
+ *
+ * Returns     :  a corresponding pcrs_job data structure, or NULL
+ *                if an error was encountered. In that case, *errptr
+ *                has the reason.
+ *
+ *********************************************************************/
+pcrs_job *pcrs_compile_command(const char *command, int *errptr)
+{
+   int i, k, l, quoted = FALSE;
+   size_t limit;
+   char delimiter;
+   char *tokens[4];   
+   pcrs_job *newjob;
+   
+   i = k = l = 0;
+   
+   /*
+    * Tokenize the perl command
+    */
+   limit = strlen(command);
+   if (limit < 4)
+   {
+      *errptr = PCRS_ERR_CMDSYNTAX;
+      return NULL;
+   }
+   else
+   {
+      delimiter = command[1];
+   }
+
+   tokens[l] = (char *) malloc(limit + 1);
+
+   for (i = 0; i <= (int)limit; i++)
+   {
+      
+      if (command[i] == delimiter && !quoted)
+      {
+         if (l == 3)
+         {
+            l = -1;
+            break;
+         }
+         tokens[0][k++] = '\0';
+         tokens[++l] = tokens[0] + k;
+         continue;
+      }
+      
+      else if (command[i] == '\\' && !quoted)
+      {
+         quoted = TRUE;
+         if (command[i+1] == delimiter) continue;
+      }
+      else
+      {
+         quoted = FALSE;
+      }
+      tokens[0][k++] = command[i];
+   }
+
+   /*
+    * Syntax error ?
+    */
+   if (l != 3)
+   {
+      *errptr = PCRS_ERR_CMDSYNTAX;
+      free(tokens[0]);
+      return NULL;
+   }
+   
+   newjob = pcrs_compile(tokens[1], tokens[2], tokens[3], errptr);
+   free(tokens[0]);
+   return newjob;
+   
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  pcrs_compile
+ *
+ * Description :  Takes the three arguments to a perl s/// command
+ *                and compiles a pcrs_job structure from them.
+ *
+ * Parameters  :
+ *          1  :  pattern = string with perl-style pattern
+ *          2  :  substitute = string with perl-style substitute
+ *          3  :  options = string with perl-style options
+ *          4  :  errptr = pointer to an integer in which error
+ *                         conditions can be returned.
+ *
+ * Returns     :  a corresponding pcrs_job data structure, or NULL
+ *                if an error was encountered. In that case, *errptr
+ *                has the reason.
+ *
+ *********************************************************************/
+pcrs_job *pcrs_compile(const char *pattern, const char *substitute, const char *options, int *errptr)
+{
+   pcrs_job *newjob;
+   int flags;
+   int capturecount;
+   const char *error;
+
+   *errptr = 0;
+
+   /* 
+    * Handle NULL arguments
+    */
+   if (pattern == NULL) pattern = "";
+   if (substitute == NULL) substitute = "";
+
+
+   /* 
+    * Get and init memory
+    */
+   if (NULL == (newjob = (pcrs_job *)malloc(sizeof(pcrs_job))))
+   {
+      *errptr = PCRS_ERR_NOMEM;
+      return NULL;
+   }
+   memset(newjob, '\0', sizeof(pcrs_job));
+
+
+   /*
+    * Evaluate the options
+    */
+   newjob->options = pcrs_parse_perl_options(options, &flags);
+   newjob->flags = flags;
+
+
+   /*
+    * Compile the pattern
+    */
+   newjob->pattern = pcre_compile(pattern, newjob->options, &error, errptr, NULL);
+   if (newjob->pattern == NULL)
+   {
+      pcrs_free_job(newjob);
+      return NULL;
+   }
+
+
+   /*
+    * Generate hints. This has little overhead, since the
+    * hints will be NULL for a boring pattern anyway.
+    */
+   newjob->hints = pcre_study(newjob->pattern, 0, &error);
+   if (error != NULL)
+   {
+      *errptr = PCRS_ERR_STUDY;
+      pcrs_free_job(newjob);
+      return NULL;
+   }
+
+   /* 
+    * Determine the number of capturing subpatterns. 
+    * This is needed for handling $+ in the substitute.
+    */
+   if (0 > (*errptr = pcre_fullinfo(newjob->pattern, newjob->hints, PCRE_INFO_CAPTURECOUNT, &capturecount)))
+   {
+      pcrs_free_job(newjob);
+      return NULL;
+   }
+
+   /*
+    * Compile the substitute
+    */
+   if (NULL == (newjob->substitute = pcrs_compile_replacement(substitute, newjob->flags & PCRS_TRIVIAL, capturecount, errptr)))
+   {
+      pcrs_free_job(newjob);
+      return NULL;
+   }
+   return newjob;
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  pcrs_execute_list
+ *
+ * Description :  This is a multiple job wrapper for pcrs_execute().
+ *                Apply the regular substitutions defined by the jobs in
+ *                the joblist to the subject.
+ *                The subject itself is left untouched, memory for the result
+ *                is malloc()ed and it is the caller's responsibility to free
+ *                the result when it's no longer needed.
+ *
+ * Parameters  :
+ *          1  :  joblist = the chained list of pcrs_jobs to be executed
+ *          2  :  subject = the subject string
+ *          3  :  subject_length = the subject's length 
+ *                INCLUDING the terminating zero, if string!
+ *          4  :  result = char** for returning  the result 
+ *          5  :  result_length = size_t* for returning the result's length
+ *
+ * Returns     :  On success, the number of substitutions that were made.
+ *                 May be > 1 if job->flags contained PCRS_GLOBAL
+ *                On failiure, the (negative) pcre error code describing the
+ *                 failiure, which may be translated to text using pcrs_strerror().
+ *
+ *********************************************************************/
+int pcrs_execute_list(pcrs_job *joblist, char *subject, size_t subject_length, char **result, size_t *result_length)
+{
+   pcrs_job *job;
+   char *old, *new;
+   int hits, total_hits;
+   old = subject;
+   *result_length = subject_length;
+   hits = total_hits = 0;
+
+   for (job = joblist; job != NULL; job = job->next)
+   {
+      hits = pcrs_execute(job, old, *result_length, &new, result_length);
+
+      if (old != subject) free(old);
+
+      if (hits < 0)
+      {
+         return(hits);
+      }
+      else
+      {
+         total_hits += hits;
+         old = new;
+      }
+   }
+
+   *result = new;
+   return(total_hits);
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  pcrs_execute
+ *
+ * Description :  Apply the regular substitution defined by the job to the
+ *                subject.
+ *                The subject itself is left untouched, memory for the result
+ *                is malloc()ed and it is the caller's responsibility to free
+ *                the result when it's no longer needed.
+ *
+ * Parameters  :
+ *          1  :  job = the pcrs_job to be executed
+ *          2  :  subject = the subject (== original) string
+ *          3  :  subject_length = the subject's length 
+ *                INCLUDING the terminating zero, if string!
+ *          4  :  result = char** for returning  the result 
+ *          5  :  result_length = size_t* for returning the result's length
+ *
+ * Returns     :  On success, the number of substitutions that were made.
+ *                 May be > 1 if job->flags contained PCRS_GLOBAL
+ *                On failiure, the (negative) pcre error code describing the
+ *                 failiure, which may be translated to text using pcrs_strerror().
+ *
+ *********************************************************************/
+int pcrs_execute(pcrs_job *job, char *subject, size_t subject_length, char **result, size_t *result_length)
+{
+   int offsets[3 * PCRS_MAX_SUBMATCHES],
+       offset,
+       i, k,
+       matches_found,
+       submatches,
+       max_matches = PCRS_MAX_MATCH_INIT;
+   size_t newsize;
+   pcrs_match *matches, *dummy;
+   char *result_offset;
+
+   offset = i = k = 0;
+
+   /* 
+    * Sanity check & memory allocation
+    */
+   if (job == NULL || job->pattern == NULL || job->substitute == NULL)
+   {
+      *result = NULL;
+      return(PCRS_ERR_BADJOB);
+   }
+
+   if (NULL == (matches = (pcrs_match *)malloc(max_matches * sizeof(pcrs_match))))
+   {
+      *result = NULL;
+      return(PCRS_ERR_NOMEM);
+   }
+   memset(matches, '\0', max_matches * sizeof(pcrs_match));
+
+
+   /*
+    * Find the pattern and calculate the space
+    * requirements for the result
+    */
+   newsize = subject_length;
+
+   while ((submatches = pcre_exec(job->pattern, job->hints, subject, (int)subject_length, offset, 0, offsets, 3 * PCRS_MAX_SUBMATCHES)) > 0)
+   {
+      job->flags |= PCRS_SUCCESS;
+      matches[i].submatches = submatches;
+
+      for (k = 0; k < submatches; k++)
+      {
+         matches[i].submatch_offset[k] = offsets[2 * k];
+
+         /* Note: Non-found optional submatches have length -1-(-1)==0 */
+         matches[i].submatch_length[k] = offsets[2 * k + 1] - offsets[2 * k]; 
+
+         /* reserve mem for each submatch as often as it is ref'd */
+         newsize += matches[i].submatch_length[k] * job->substitute->backref_count[k]; 
+      }
+      /* plus replacement text size minus match text size */
+      newsize += strlen(job->substitute->text) - matches[i].submatch_length[0]; 
+
+      /* chunk before match */
+      matches[i].submatch_offset[PCRS_MAX_SUBMATCHES] = 0;
+      matches[i].submatch_length[PCRS_MAX_SUBMATCHES] = offsets[0];
+      newsize += offsets[0] * job->substitute->backref_count[PCRS_MAX_SUBMATCHES];
+
+      /* chunk after match */
+      matches[i].submatch_offset[PCRS_MAX_SUBMATCHES + 1] = offsets[1];
+      matches[i].submatch_length[PCRS_MAX_SUBMATCHES + 1] = subject_length - offsets[1] - 1;
+      newsize += (subject_length - offsets[1]) * job->substitute->backref_count[PCRS_MAX_SUBMATCHES + 1];
+
+      /* Storage for matches exhausted? -> Extend! */
+      if (++i >= max_matches)
+      {
+         max_matches = (int)(max_matches * PCRS_MAX_MATCH_GROW);
+         if (NULL == (dummy = (pcrs_match *)realloc(matches, max_matches * sizeof(pcrs_match))))
+         {
+            free(matches);
+            *result = NULL;
+            return(PCRS_ERR_NOMEM);
+         }
+         matches = dummy;
+      }
+
+      /* Non-global search or limit reached? */
+      if (!(job->flags & PCRS_GLOBAL)) break;
+
+      /* Don't loop on empty matches */
+      if (offsets[1] == offset)
+         if ((size_t)offset < subject_length)
+            offset++;
+         else
+            break;
+      /* Go find the next one */
+      else
+         offset = offsets[1];
+   }
+   /* Pass pcre error through if (bad) failiure */
+   if (submatches < PCRE_ERROR_NOMATCH)
+   {
+      free(matches);
+      return submatches;   
+   }
+   matches_found = i;
+
+
+   /* 
+    * Get memory for the result
+    */
+   if ((*result = (char *)malloc(newsize)) == NULL)   /* must be free()d by caller */
+   {
+      free(matches);
+      return PCRS_ERR_NOMEM;
+   }
+
+
+   /* 
+    * Replace
+    */
+   offset = 0;
+   result_offset = *result;
+
+   for (i = 0; i < matches_found; i++)
+   {
+      /* copy the chunk preceding the match */
+      memcpy(result_offset, subject + offset, (size_t)matches[i].submatch_offset[0] - offset); 
+      result_offset += matches[i].submatch_offset[0] - offset;
+
+      /* For every segment of the substitute.. */
+      for (k = 0; k <= job->substitute->backrefs; k++)
+      {
+         /* ...copy its text.. */
+         memcpy(result_offset, job->substitute->text + job->substitute->block_offset[k], job->substitute->block_length[k]);
+         result_offset += job->substitute->block_length[k];
+
+         /* ..plus, if it's not the last chunk, i.e.: There *is* a backref.. */
+         if (k != job->substitute->backrefs
+             /* ..in legal range.. */
+             && job->substitute->backref[k] < PCRS_MAX_SUBMATCHES + 2
+             /* ..and referencing a nonempty match.. */
+             && matches[i].submatch_length[job->substitute->backref[k]] > 0)
+         {
+            /* ..copy the submatch that is ref'd. */
+            memcpy(
+               result_offset,
+               subject + matches[i].submatch_offset[job->substitute->backref[k]],
+               matches[i].submatch_length[job->substitute->backref[k]]
+            );
+            result_offset += matches[i].submatch_length[job->substitute->backref[k]];
+         }
+      }
+      offset =  matches[i].submatch_offset[0] + matches[i].submatch_length[0];
+   }
+
+   /* Copy the rest. */
+   memcpy(result_offset, subject + offset, subject_length - offset);
+
+   *result_length = newsize;
+   free(matches);
+   return matches_found;
+
+}
+
+
+/*
+  Local Variables:
+  tab-width: 3
+  end:
+*/
diff --git a/src/pcrs.h b/src/pcrs.h
new file mode 100644 (file)
index 0000000..b756067
--- /dev/null
@@ -0,0 +1,171 @@
+#ifndef PCRS_H_INCLUDED
+#define PCRS_H_INCLUDED
+
+/*********************************************************************
+ *
+ * File        :  $Source: /cvsroot/ijbswa/current/pcrs.h,v $
+ *
+ * Purpose     :  Header file for pcrs.c
+ *
+ * Copyright   :  see pcrs.c
+ *
+ * Revisions   :
+ *    $Log: pcrs.h,v $
+ *    Revision 1.11  2002/03/08 14:18:23  oes
+ *    Fixing -Wconversion warnings
+ *
+ *    Revision 1.10  2002/03/08 13:44:48  oes
+ *    Hiding internal functions, preventing double inclusion of pcre.h
+ *
+ *    Revision 1.9  2001/08/18 11:35:29  oes
+ *    - Introduced pcrs_strerror()
+ *    - added pcrs_execute_list()
+ *
+ *    Revision 1.8  2001/08/15 15:32:50  oes
+ *    Replaced the hard limit for the maximum number of matches
+ *    by dynamic reallocation
+ *
+ *    Revision 1.7  2001/08/05 13:13:11  jongfoster
+ *    Making parameters "const" where possible.
+ *
+ *    Revision 1.6  2001/07/29 18:52:06  jongfoster
+ *    Renaming _PCRS_H, and adding "extern C {}"
+ *
+ *    Revision 1.5  2001/07/18 17:27:00  oes
+ *    Changed interface; Cosmetics
+ *
+ *    Revision 1.4  2001/06/29 13:33:19  oes
+ *    - Cleaned up, commented and adapted to reflect the
+ *      changes in pcrs.c
+ *    - Introduced the PCRS_* flags
+ *
+ *    Revision 1.3  2001/06/09 10:58:57  jongfoster
+ *    Removing a single unused #define which referenced BUFSIZ
+ *
+ *    Revision 1.2  2001/05/25 11:03:55  oes
+ *    Added sanity check for NULL jobs to pcrs_exec_substitution
+ *
+ *    Revision 1.1.1.1  2001/05/15 13:59:02  oes
+ *    Initial import of version 2.9.3 source tree
+ *
+ *    Revision 1.4  2001/05/11 01:57:02  rodney
+ *    Added new file header standard w/RCS control tags.
+ *
+ *    revision 1.3  2001/05/08 02:38:13  rodney
+ *    Changed C++ "//" style comment to C style comments.
+ *
+ *    revision 1.2  2001/04/30 02:39:24  rodney
+ *    Made this pcrs.h file conditionally included.
+ *
+ *    revision 1.1  2001/04/16 21:10:38  rodney
+ *    Initial checkin
+ *
+ *********************************************************************/
+
+#define PCRS_H_VERSION "$Id: pcrs.h,v 1.11 2002/03/08 14:18:23 oes Exp $"
+\f
+
+#ifndef _PCRE_H
+#include <pcre.h>
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * Constants:
+ */
+
+#define FALSE 0
+#define TRUE 1
+
+/* Capacity */
+#define PCRS_MAX_SUBMATCHES  33     /* Maximum number of capturing subpatterns allowed. MUST be <= 99! FIXME: Should be dynamic */
+#define PCRS_MAX_MATCH_INIT  40     /* Initial amount of matches that can be stored in global searches */
+#define PCRS_MAX_MATCH_GROW  1.6    /* Factor by which storage for matches is extended if exhausted */
+
+/* Error codes */
+#define PCRS_ERR_NOMEM     -10      /* Failed to acquire memory. */
+#define PCRS_ERR_CMDSYNTAX -11      /* Syntax of s///-command */
+#define PCRS_ERR_STUDY     -12      /* pcre error while studying the pattern */
+#define PCRS_ERR_BADJOB    -13      /* NULL job pointer, pattern or substitute */
+#define PCRS_WARN_BADREF   -14      /* Backreference out of range */
+
+/* Flags */
+#define PCRS_GLOBAL          1      /* Job should be applied globally, as with perl's g option */
+#define PCRS_TRIVIAL         2      /* Backreferences in the substitute are ignored */
+#define PCRS_SUCCESS         4      /* Job did previously match */
+
+
+/*
+ * Data types:
+ */
+
+/* A compiled substitute */
+
+typedef struct {
+  char  *text;                                   /* The plaintext part of the substitute, with all backreferences stripped */
+  int    backrefs;                               /* The number of backreferences */
+  int    block_offset[PCRS_MAX_SUBMATCHES];      /* Array with the offsets of all plaintext blocks in text */
+  size_t block_length[PCRS_MAX_SUBMATCHES];      /* Array with the lengths of all plaintext blocks in text */
+  int    backref[PCRS_MAX_SUBMATCHES];           /* Array with the backref number for all plaintext block borders */
+  int    backref_count[PCRS_MAX_SUBMATCHES + 2]; /* Array with the number of references to each backref index */
+} pcrs_substitute;
+
+
+/*
+ * A match, including all captured subpatterns (submatches)
+ * Note: The zeroth is the whole match, the PCRS_MAX_SUBMATCHES + 0th
+ * is the range before the match, the PCRS_MAX_SUBMATCHES + 1th is the
+ * range after the match.
+ */
+
+typedef struct {
+  int    submatches;                               /* Number of captured subpatterns */
+  int    submatch_offset[PCRS_MAX_SUBMATCHES + 2]; /* Offset for each submatch in the subject */
+  size_t submatch_length[PCRS_MAX_SUBMATCHES + 2]; /* Length of each submatch in the subject */
+} pcrs_match;
+
+
+/* A PCRS job */
+
+typedef struct PCRS_JOB {
+  pcre *pattern;                            /* The compiled pcre pattern */
+  pcre_extra *hints;                        /* The pcre hints for the pattern */
+  int options;                              /* The pcre options (numeric) */
+  int flags;                                /* The pcrs and user flags (see "Flags" above) */
+  pcrs_substitute *substitute;              /* The compiled pcrs substitute */
+  struct PCRS_JOB *next;                    /* Pointer for chaining jobs to joblists */
+} pcrs_job;
+
+
+/*
+ * Prototypes:
+ */
+
+/* Main usage */
+extern pcrs_job        *pcrs_compile_command(const char *command, int *errptr);
+extern pcrs_job        *pcrs_compile(const char *pattern, const char *substitute, const char *options, int *errptr);
+extern int              pcrs_execute(pcrs_job *job, char *subject, size_t subject_length, char **result, size_t *result_length);
+extern int              pcrs_execute_list(pcrs_job *joblist, char *subject, size_t subject_length, char **result, size_t *result_length);
+
+/* Freeing jobs */
+extern pcrs_job        *pcrs_free_job(pcrs_job *job);
+extern void             pcrs_free_joblist(pcrs_job *joblist);
+
+/* Info on errors: */
+extern const char *pcrs_strerror(const int error);
+
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* ndef PCRS_H_INCLUDED */
+
+/*
+  Local Variables:
+  tab-width: 3
+  end:
+*/
diff --git a/src/project.h b/src/project.h
new file mode 100644 (file)
index 0000000..c63c94d
--- /dev/null
@@ -0,0 +1,1437 @@
+#ifndef PROJECT_H_INCLUDED
+#define PROJECT_H_INCLUDED
+/** Version string. */
+#define PROJECT_H_VERSION "$Id: project.h,v 1.72 2002/05/14 21:35:49 oes Exp $"
+/*********************************************************************
+ *
+ * File        :  $Source: /cvsroot/ijbswa/current/project.h,v $
+ *
+ * Purpose     :  Defines data structures which are widely used in the
+ *                project.  Does not define any variables or functions
+ *                (though it does declare some macros).
+ *
+ * Copyright   :  Written by and Copyright (C) 2001 the SourceForge
+ *                Privoxy team. http://www.privoxy.org/
+ *
+ *                Based on the Internet Junkbuster originally written
+ *                by and Copyright (C) 1997 Anonymous Coders and
+ *                Junkbusters Corporation.  http://www.junkbusters.com
+ *
+ *                This program is free software; you can redistribute it
+ *                and/or modify it under the terms of the GNU General
+ *                Public License as published by the Free Software
+ *                Foundation; either version 2 of the License, or (at
+ *                your option) any later version.
+ *
+ *                This program is distributed in the hope that it will
+ *                be useful, but WITHOUT ANY WARRANTY; without even the
+ *                implied warranty of MERCHANTABILITY or FITNESS FOR A
+ *                PARTICULAR PURPOSE.  See the GNU General Public
+ *                License for more details.
+ *
+ *                The GNU General Public License should be included with
+ *                this file.  If not, you can view it at
+ *                http://www.gnu.org/copyleft/gpl.html
+ *                or write to the Free Software Foundation, Inc., 59
+ *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * Revisions   :
+ *    $Log: project.h,v $
+ *    Revision 1.72  2002/05/14 21:35:49  oes
+ *    Split HELP_LINK_PREFIX into ACTIONS_HELP_PREFIX and CONFIG_HELP_PREFIX
+ *    because of split in user-manual
+ *
+ *    Revision 1.71  2002/05/12 21:39:36  jongfoster
+ *    - Adding Doxygen-style comments to structures and #defines.
+ *
+ *    Revision 1.70  2002/05/12 16:05:50  jongfoster
+ *    Fixing ACTION_MASK_ALL to be unsigned long rather than
+ *    just unsigned int.  I don't know if anyone is porting
+ *    Privoxy to 16-bit platforms, but if so, +limit-connect
+ *    wouldn't have worked because of this bug.
+ *
+ *    Revision 1.69  2002/05/08 16:00:16  oes
+ *    Added size member to struct iob, so it can
+ *    be alloced larger than needed.
+ *
+ *    Revision 1.68  2002/04/26 12:56:00  oes
+ *    Killed REDIRECT_URL, added USER_MANUAL_URL and HELP_LINK_PREFIX
+ *
+ *    Revision 1.67  2002/04/24 02:12:43  oes
+ *     - Jon's multiple AF patch:
+ *       - Make csp->actions_list an array
+ *       - #define MAX_ACTION_FILES
+ *     - Moved CGI_PARAM_LEN_MAX (500) here
+ *
+ *    Revision 1.66  2002/04/15 19:06:43  jongfoster
+ *    Typos
+ *
+ *    Revision 1.65  2002/04/04 00:36:36  gliptak
+ *    always use pcre for matching
+ *
+ *    Revision 1.64  2002/04/03 22:28:03  gliptak
+ *    Removed references to gnu_regex
+ *
+ *    Revision 1.63  2002/03/31 17:19:00  jongfoster
+ *    Win32 only: Enabling STRICT to fix a VC++ compile warning.
+ *
+ *    Revision 1.62  2002/03/26 22:48:49  swa
+ *    new homepage url
+ *
+ *    Revision 1.61  2002/03/26 22:29:55  swa
+ *    we have a new homepage!
+ *
+ *    Revision 1.60  2002/03/24 15:52:17  jongfoster
+ *    Changing CGI URL prefixes for new name
+ *
+ *    Revision 1.59  2002/03/24 15:23:33  jongfoster
+ *    Name changes
+ *
+ *    Revision 1.58  2002/03/24 13:25:43  swa
+ *    name change related issues
+ *
+ *    Revision 1.57  2002/03/16 20:28:34  oes
+ *    Added descriptions to the filters so users will know what they select in the cgi editor
+ *
+ *    Revision 1.56  2002/03/13 20:27:30  oes
+ *    Fixing bug with CT_TABOO
+ *
+ *    Revision 1.55  2002/03/12 01:42:50  oes
+ *    Introduced modular filters
+ *
+ *    Revision 1.54  2002/03/09 20:03:52  jongfoster
+ *    - Making various functions return int rather than size_t.
+ *      (Undoing a recent change).  Since size_t is unsigned on
+ *      Windows, functions like read_socket that return -1 on
+ *      error cannot return a size_t.
+ *
+ *      THIS WAS A MAJOR BUG - it caused frequent, unpredictable
+ *      crashes, and also frequently caused JB to jump to 100%
+ *      CPU and stay there.  (Because it thought it had just
+ *      read ((unsigned)-1) == 4Gb of data...)
+ *
+ *    - The signature of write_socket has changed, it now simply
+ *      returns success=0/failure=nonzero.
+ *
+ *    - Trying to get rid of a few warnings --with-debug on
+ *      Windows, I've introduced a new type "jb_socket".  This is
+ *      used for the socket file descriptors.  On Windows, this
+ *      is SOCKET (a typedef for unsigned).  Everywhere else, it's
+ *      an int.  The error value can't be -1 any more, so it's
+ *      now JB_INVALID_SOCKET (which is -1 on UNIX, and in
+ *      Windows it maps to the #define INVALID_SOCKET.)
+ *
+ *    - The signature of bind_port has changed.
+ *
+ *    Revision 1.53  2002/03/08 16:48:55  oes
+ *    Added FEATURE_NO_GIFS and BUILTIN_IMAGE_MIMETYPE
+ *
+ *    Revision 1.52  2002/03/07 03:46:17  oes
+ *    Fixed compiler warnings
+ *
+ *    Revision 1.51  2002/03/05 04:52:42  oes
+ *    Deleted non-errlog debugging code
+ *
+ *    Revision 1.50  2002/03/04 19:32:07  oes
+ *    Changed default port to 8118
+ *
+ *    Revision 1.49  2002/03/04 18:28:55  oes
+ *    Deleted PID_FILE_NAME
+ *
+ *    Revision 1.48  2002/03/03 14:50:40  oes
+ *    Fixed CLF logging: Added ocmd member for client's request to struct http_request
+ *
+ *    Revision 1.47  2002/02/20 23:15:13  jongfoster
+ *    Parsing functions now handle out-of-memory gracefully by returning
+ *    an error code.
+ *
+ *    Revision 1.46  2002/01/17 21:06:09  jongfoster
+ *    Now #defining the URLs of the config interface
+ *
+ *    Minor changes to struct http_request and struct url_spec due to
+ *    standardizing that struct http_request is used to represent a URL, and
+ *    struct url_spec is used to represent a URL pattern.  (Before, URLs were
+ *    represented as seperate variables and a partially-filled-in url_spec).
+ *
+ *    Revision 1.45  2002/01/09 14:33:27  oes
+ *    Added HOSTENT_BUFFER_SIZE
+ *
+ *    Revision 1.44  2001/12/30 14:07:32  steudten
+ *    - Add signal handling (unix)
+ *    - Add SIGHUP handler (unix)
+ *    - Add creation of pidfile (unix)
+ *    - Add action 'top' in rc file (RH)
+ *    - Add entry 'SIGNALS' to manpage
+ *    - Add exit message to logfile (unix)
+ *
+ *    Revision 1.43  2001/11/22 21:57:51  jongfoster
+ *    Making action_spec->flags into an unsigned long rather than just an
+ *    unsigned int.
+ *    Adding ACTION_NO_COOKIE_KEEP
+ *
+ *    Revision 1.42  2001/11/05 21:42:41  steudten
+ *    Include DBG() macro.
+ *
+ *    Revision 1.41  2001/10/28 19:12:06  jongfoster
+ *    Adding ijb_toupper()
+ *
+ *    Revision 1.40  2001/10/26 17:40:47  oes
+ *    Moved ijb_isspace and ijb_tolower to project.h
+ *    Removed http->user_agent, csp->referrer and csp->accept_types
+ *
+ *    Revision 1.39  2001/10/25 03:45:02  david__schmidt
+ *    Adding a (void*) cast to freez() because Visual Age C++ won't expand the
+ *    macro when called with a cast; so moving the cast to the macro def'n
+ *    seems to both eliminate compiler warnings (on darwin and OS/2, anyway) and
+ *    doesn't make macro expansion complain.  Hope this works for everyone else
+ *    too...
+ *
+ *    Revision 1.38  2001/10/23 21:19:04  jongfoster
+ *    New error-handling support: jb_err type and JB_ERR_xxx constants
+ *    CGI functions now return a jb_err, and their parameters map is const.
+ *    Support for RUNTIME_FEATUREs to enable/disable config editor
+ *    Adding a few comments
+ *
+ *    Revision 1.37  2001/10/14 22:14:01  jongfoster
+ *    Removing name_length field from struct cgi_dispatcher, as this is
+ *    now calculated at runtime from the "name" field.
+ *
+ *    Revision 1.36  2001/10/10 16:45:15  oes
+ *    Added LIMIT_CONNECT action and string
+ *    Fixed HTTP message line termination
+ *    Added CFORBIDDEN HTTP message
+ *
+ *    Revision 1.35  2001/10/07 18:06:43  oes
+ *    Added status member to struct http_request
+ *
+ *    Revision 1.34  2001/10/07 15:45:25  oes
+ *    Added url member to struct http_request and commented all
+ *      members
+ *
+ *    Added CT_TABOO
+ *
+ *    Added ACTION_DOWNGRADE and ACTION_NO_COMPRESSION
+ *
+ *    Replaced struct client_state members rejected,
+ *      force, active and toggled_on with "flags" bitmap.
+ *
+ *    Added CSP_FLAG_MODIFIED and CSP_FLAG_CHUNKED
+ *
+ *    Added buffer_limit to struct configuration_spec
+ *
+ *    Revision 1.33  2001/09/20 13:30:08  steudten
+ *
+ *    Make freez() more secure in case of: if (exp) { free(z) ; a=*z }
+ *    Last case will set z to NULL in free(z) and thats bad..
+ *
+ *    Revision 1.32  2001/09/16 23:02:51  jongfoster
+ *    Fixing warning
+ *
+ *    Revision 1.31  2001/09/16 13:20:29  jongfoster
+ *    Rewrite of list library.  Now has seperate header and list_entry
+ *    structures.  Also added a large sprinking of assert()s to the list
+ *    code.
+ *
+ *    Revision 1.30  2001/09/13 23:52:00  jongfoster
+ *    Support for both static and dynamically generated CGI pages
+ *
+ *    Revision 1.29  2001/09/13 23:29:43  jongfoster
+ *    Defining FORWARD_SPEC_INITIALIZER
+ *
+ *    Revision 1.28  2001/09/13 23:05:50  jongfoster
+ *    Changing the string paramater to the header parsers a "const".
+ *
+ *    Revision 1.27  2001/08/05 16:06:20  jongfoster
+ *    Modifiying "struct map" so that there are now separate header and
+ *    "map_entry" structures.  This means that functions which modify a
+ *    map no longer need to return a pointer to the modified map.
+ *    Also, it no longer reverses the order of the entries (which may be
+ *    important with some advanced template substitutions).
+ *
+ *    Revision 1.26  2001/07/30 22:08:36  jongfoster
+ *    Tidying up #defines:
+ *    - All feature #defines are now of the form FEATURE_xxx
+ *    - Permanently turned off WIN_GUI_EDIT
+ *    - Permanently turned on WEBDAV and SPLIT_PROXY_ARGS
+ *
+ *    Revision 1.25  2001/07/29 18:43:08  jongfoster
+ *    Changing #ifdef _FILENAME_H to FILENAME_H_INCLUDED, to conform to
+ *    ANSI C rules.
+ *
+ *    Revision 1.24  2001/07/25 17:20:27  oes
+ *    Introduced http->user_agent
+ *
+ *    Revision 1.23  2001/07/18 12:32:23  oes
+ *    - Added ACTION_STRING_DEANIMATE
+ *    - moved #define freez from jcc.h to project.h
+ *
+ *    Revision 1.22  2001/07/15 17:51:41  jongfoster
+ *    Renaming #define STATIC to STATIC_PCRE
+ *
+ *    Revision 1.21  2001/07/13 14:03:19  oes
+ *     - Reorganized regex header inclusion and #defines to
+ *       comply to the scheme in configure.in
+ *     - Added csp->content_type and its CT_* keys
+ *     - Added ACTION_DEANIMATE
+ *     - Removed all #ifdef PCRS
+ *
+ *    Revision 1.20  2001/06/29 21:45:41  oes
+ *    Indentation, CRLF->LF, Tab-> Space
+ *
+ *    Revision 1.19  2001/06/29 13:33:36  oes
+ *    - Improved comments
+ *    - Introduced http_request.host_ip_addr_str
+ *    - Introduced http_response.head_length
+ *    - Introduced config.my_ip_addr_str, config.my_hostname,
+ *      config.admin_address and config.proxy_info_url
+ *    - Removed config.proxy_args_header and config.proxy_args_trailer,
+ *      renamed config.proxy_args_invocation to config.proxy_args
+ *    - Removed HTML snipplets and GIFs
+ *    - Removed logentry from cancelled commit
+ *
+ *    Revision 1.18  2001/06/09 10:57:39  jongfoster
+ *    Adding definition of BUFFER_SIZE.
+ *    Changing struct cgi_dispatcher to use "const" strings.
+ *
+ *    Revision 1.17  2001/06/07 23:15:09  jongfoster
+ *    Merging ACL and forward files into config file.
+ *    Moving struct gateway members into struct forward_spec
+ *    Removing config->proxy_args_gateways
+ *    Cosmetic: Adding a few comments
+ *
+ *    Revision 1.16  2001/06/04 18:31:58  swa
+ *    files are now prefixed with either `confdir' or `logdir'.
+ *    `make redhat-dist' replaces both entries confdir and logdir
+ *    with redhat values
+ *
+ *    Revision 1.15  2001/06/04 11:28:53  swa
+ *    redirect did not work due to missing /
+ *
+ *    Revision 1.14  2001/06/03 11:03:48  oes
+ *    Added struct map,
+ *    added struct http_response,
+ *    changed struct interceptors to struct cgi_dispatcher,
+ *    moved HTML stuff to cgi.h
+ *
+ *    Revision 1.13  2001/06/01 20:05:36  jongfoster
+ *    Support for +image-blocker{}: added ACTION_IMAGE_BLOCKER
+ *    constant, and removed csp->tinygif.
+ *
+ *    Revision 1.12  2001/06/01 18:49:17  jongfoster
+ *    Replaced "list_share" with "list" - the tiny memory gain was not
+ *    worth the extra complexity.
+ *
+ *    Revision 1.11  2001/06/01 10:32:47  oes
+ *    Added constants for anchoring selection bitmap
+ *
+ *    Revision 1.10  2001/05/31 21:33:53  jongfoster
+ *    Changes for new actions file, replacing permissionsfile
+ *    and parts of the config file.  Also added support for
+ *    list_shared.
+ *
+ *    Revision 1.9  2001/05/31 17:32:31  oes
+ *
+ *     - Enhanced domain part globbing with infix and prefix asterisk
+ *       matching and optional unanchored operation
+ *
+ *    Revision 1.8  2001/05/29 20:09:15  joergs
+ *    HTTP_REDIRECT_TEMPLATE fixed.
+ *
+ *    Revision 1.7  2001/05/29 09:50:24  jongfoster
+ *    Unified blocklist/imagelist/actionslist.
+ *    File format is still under discussion, but the internal changes
+ *    are (mostly) done.
+ *
+ *    Also modified interceptor behaviour:
+ *    - We now intercept all URLs beginning with one of the following
+ *      prefixes (and *only* these prefixes):
+ *        * http://i.j.b/
+ *        * http://ijbswa.sf.net/config/
+ *        * http://ijbswa.sourceforge.net/config/
+ *    - New interceptors "home page" - go to http://i.j.b/ to see it.
+ *    - Internal changes so that intercepted and fast redirect pages
+ *      are not replaced with an image.
+ *    - Interceptors now have the option to send a binary page direct
+ *      to the client. (i.e. ijb-send-banner uses this)
+ *    - Implemented show-url-info interceptor.  (Which is why I needed
+ *      the above interceptors changes - a typical URL is
+ *      "http://i.j.b/show-url-info?url=www.somesite.com/banner.gif".
+ *      The previous mechanism would not have intercepted that, and
+ *      if it had been intercepted then it then it would have replaced
+ *      it with an image.)
+ *
+ *    Revision 1.6  2001/05/27 22:17:04  oes
+ *
+ *    - re_process_buffer no longer writes the modified buffer
+ *      to the client, which was very ugly. It now returns the
+ *      buffer, which it is then written by chat.
+ *
+ *    - content_length now adjusts the Content-Length: header
+ *      for modified documents rather than crunch()ing it.
+ *      (Length info in csp->content_length, which is 0 for
+ *      unmodified documents)
+ *
+ *    - For this to work, sed() is called twice when filtering.
+ *
+ *    Revision 1.5  2001/05/26 00:28:36  jongfoster
+ *    Automatic reloading of config file.
+ *    Removed obsolete SIGHUP support (Unix) and Reload menu option (Win32).
+ *    Most of the global variables have been moved to a new
+ *    struct configuration_spec, accessed through csp->config->globalname
+ *    Most of the globals remaining are used by the Win32 GUI.
+ *
+ *    Revision 1.4  2001/05/22 18:46:04  oes
+ *
+ *    - Enabled filtering banners by size rather than URL
+ *      by adding patterns that replace all standard banner
+ *      sizes with the "Junkbuster" gif to the re_filterfile
+ *
+ *    - Enabled filtering WebBugs by providing a pattern
+ *      which kills all 1x1 images
+ *
+ *    - Added support for PCRE_UNGREEDY behaviour to pcrs,
+ *      which is selected by the (nonstandard and therefore
+ *      capital) letter 'U' in the option string.
+ *      It causes the quantifiers to be ungreedy by default.
+ *      Appending a ? turns back to greedy (!).
+ *
+ *    - Added a new interceptor ijb-send-banner, which
+ *      sends back the "Junkbuster" gif. Without imagelist or
+ *      MSIE detection support, or if tinygif = 1, or the
+ *      URL isn't recognized as an imageurl, a lame HTML
+ *      explanation is sent instead.
+ *
+ *    - Added new feature, which permits blocking remote
+ *      script redirects and firing back a local redirect
+ *      to the browser.
+ *      The feature is conditionally compiled, i.e. it
+ *      can be disabled with --disable-fast-redirects,
+ *      plus it must be activated by a "fast-redirects"
+ *      line in the config file, has its own log level
+ *      and of course wants to be displayed by show-proxy-args
+ *      Note: Boy, all the #ifdefs in 1001 locations and
+ *      all the fumbling with configure.in and acconfig.h
+ *      were *way* more work than the feature itself :-(
+ *
+ *    - Because a generic redirect template was needed for
+ *      this, tinygif = 3 now uses the same.
+ *
+ *    - Moved GIFs, and other static HTTP response templates
+ *      to project.h
+ *
+ *    - Some minor fixes
+ *
+ *    - Removed some >400 CRs again (Jon, you really worked
+ *      a lot! ;-)
+ *
+ *    Revision 1.3  2001/05/20 01:21:20  jongfoster
+ *    Version 2.9.4 checkin.
+ *    - Merged popupfile and cookiefile, and added control over PCRS
+ *      filtering, in new "actionsfile".
+ *    - Implemented LOG_LEVEL_FATAL, so that if there is a configuration
+ *      file error you now get a message box (in the Win32 GUI) rather
+ *      than the program exiting with no explanation.
+ *    - Made killpopup use the PCRS MIME-type checking and HTTP-header
+ *      skipping.
+ *    - Removed tabs from "config"
+ *    - Moved duplicated url parsing code in "loaders.c" to a new funcition.
+ *    - Bumped up version number.
+ *
+ *    Revision 1.2  2001/05/17 23:01:01  oes
+ *     - Cleaned CRLF's from the sources and related files
+ *
+ *    Revision 1.1.1.1  2001/05/15 13:59:03  oes
+ *    Initial import of version 2.9.3 source tree
+ *
+ *
+ *********************************************************************/
+\f
+
+/* Declare struct FILE for vars and funcs. */
+#include <stdio.h>
+
+/* Need time_t for file_list */
+#include <time.h>
+
+/*
+ * Include appropriate regular expression libraries.
+ * Note that pcrs and pcre (native) are needed for cgi
+ * and are included anyway.
+ */
+
+#ifdef STATIC_PCRE
+#  include "pcre.h"
+#else
+#  include <pcre.h>
+#endif
+
+#ifdef STATIC_PCRS
+#  include "pcrs.h"
+#else
+#  include <pcrs.h>
+#endif
+
+#ifdef STATIC_PCRE
+#  include "pcreposix.h"
+#else
+#  include <pcreposix.h>
+#endif
+
+#ifdef AMIGA
+#include "amiga.h"
+#endif /* def AMIGA */
+
+#ifdef _WIN32
+/*
+ * I don't want to have to #include all this just for the declaration
+ * of SOCKET.  However, it looks like we have to...
+ */
+#ifndef STRICT
+#define STRICT
+#endif
+#include <windows.h>
+#endif
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef _WIN32
+
+typedef SOCKET jb_socket;
+
+#define JB_INVALID_SOCKET INVALID_SOCKET
+
+#else /* ndef _WIN32 */
+
+/**
+ * The type used by sockets.  On UNIX it's an int.  Microsoft decided to
+ * make it an unsigned.
+ */
+typedef int jb_socket;
+
+/**
+ * The error value used for variables of type jb_socket.  On UNIX this
+ * is -1, however Microsoft decided to make socket handles unsigned, so
+ * they use a different value.
+ */
+
+#define JB_INVALID_SOCKET (-1)
+
+#endif /* ndef _WIN32 */
+
+
+/**
+ * A standard error code.  This should be JB_ERR_OK or one of the JB_ERR_xxx
+ * series of errors.
+ */
+typedef int jb_err;
+
+#define JB_ERR_OK         0 /**< Success, no error                        */
+#define JB_ERR_MEMORY     1 /**< Out of memory                            */
+#define JB_ERR_CGI_PARAMS 2 /**< Missing or corrupt CGI parameters        */
+#define JB_ERR_FILE       3 /**< Error opening, reading or writing a file */
+#define JB_ERR_PARSE      4 /**< Error parsing file                       */
+#define JB_ERR_MODIFIED   5 /**< File has been modified outside of the  
+                                 CGI actions editor.                      */
+
+
+/**
+ * This macro is used to free a pointer that may be NULL.
+ * It also sets the variable to NULL after it's been freed.
+ * The paramater should be a simple variable without side effects.
+ */
+#define freez(X)  { if(X) { free((void*)X); X = NULL ; } }
+
+
+/**
+ * Fix a problem with Solaris.  There should be no effect on other
+ * platforms.
+ *
+ * Solaris's isspace() is a macro which uses it's argument directly
+ * as an array index.  Therefore we need to make sure that high-bit
+ * characters generate +ve values, and ideally we also want to make
+ * the argument match the declared parameter type of "int".
+ *
+ * Note: Remember to #include <ctype.h> if you use these macros.
+ */
+#define ijb_toupper(__X) toupper((int)(unsigned char)(__X))
+#define ijb_tolower(__X) tolower((int)(unsigned char)(__X))
+#define ijb_isspace(__X) isspace((int)(unsigned char)(__X))  
+
+/**
+ * Use for statically allocated buffers if you have no other choice.
+ * Remember to check the length of what you write into the buffer
+ * - we don't want any buffer overflows!
+ */
+#define BUFFER_SIZE 5000
+
+/**
+ * Max length of CGI parameters (arbitrary limit).
+ */
+#define CGI_PARAM_LEN_MAX 500
+
+/**
+ * Buffer size for capturing struct hostent data in the
+ * gethostby(name|addr)_r library calls. Since we don't
+ * loop over gethostbyname_r, the buffer must be sufficient
+ * to accomodate multiple IN A RRs, as used in DNS round robin
+ * load balancing. W3C's wwwlib uses 1K, so that should be
+ * good enough for us, too.
+ */
+#define HOSTENT_BUFFER_SIZE 1024
+
+/**
+ * Do not use.  Originally this was so that you can
+ * say "while (FOREVER) { ...do something... }".
+ * However, this gives a warning with some compilers (e.g. VC++).
+ * Instead, use "for (;;) { ...do something... }".
+ */
+#define FOREVER 1
+
+/**
+ * Default IP address to listen on, as a string.
+ * Set to "127.0.0.1".
+ */
+#define HADDR_DEFAULT   "127.0.0.1"
+
+/**
+ * Default port to listen on, as a number.
+ * Set to 8118.
+ */
+#define HADDR_PORT      8118
+
+
+/* Forward def for struct client_state */
+struct configuration_spec;
+
+
+/**
+ * Entry in a linked list of strings.
+ */
+struct list_entry
+{
+   /**
+    * The string.  The "const" is only to discourage modification,
+    * you can actually change it if you *really* want to.
+    * You can even freez() it and replace it with another
+    * malloc()d string.  If you replace it with NULL, the list
+    * functions will work, just be careful next time you iterate
+    * through the list in your own code.
+    *
+    * FIXME: Should we remove the "const"?
+    */
+   const char *str;
+   
+   /** Next entry in the linked list, or NULL if no more. */
+   struct list_entry *next;
+};
+
+/**
+ * A header for a linked list of strings.
+ */
+struct list
+{
+   /** First entry in the list, or NULL if the list is empty. */
+   struct list_entry *first;
+
+   /** Last entry in the list, or NULL if the list is empty. */
+   struct list_entry *last;
+};
+
+
+/**
+ * An entry in a map.  This is a name=value pair.
+ */
+struct map_entry
+{
+   /** The key for the map. */
+   const char *name;
+   /** The value associated with that key. */
+   const char *value;
+   /** The next map entry, or NULL if none. */
+   struct map_entry *next;
+};
+
+/**
+ * A map from a string to another string.
+ * This is used for the paramaters passed in a HTTP GET request, and
+ * to store the exports when the CGI interface is filling in a template.
+ */
+struct map
+{
+   /** The first map entry, or NULL if the map is empty. */
+   struct map_entry *first;
+   /** The last map entry, or NULL if the map is empty. */
+   struct map_entry *last;
+};
+
+
+/**
+ * A HTTP request.  This includes the method (GET, POST) and
+ * the parsed URL.
+ *
+ * This is also used whenever we want to match a URL against a
+ * URL pattern.  This always contains the URL to match, and never
+ * a URL pattern.  (See struct url_spec).
+ */
+struct http_request
+{
+   char *cmd;      /**< Whole command line: method, URL, Version */
+   char *ocmd;     /**< Backup of original cmd for CLF logging */
+   char *gpc;      /**< HTTP method: GET, POST, ... */
+   char *url;      /**< The URL */
+   char *ver;      /**< Protocol version */
+   int status;     /**< HTTP Status */
+
+   char *host;     /**< Host part of URL */
+   int   port;     /**< Port of URL or 80 (default) */
+   char *path;     /**< Path of URL */
+   char *hostport; /**< host[:port] */
+   int   ssl;      /**< Flag if protocol is https */
+
+   char *host_ip_addr_str; /**< String with dotted decimal representation
+                                of host's IP. NULL before connect_to() */
+
+   char  *dbuffer; /**< Buffer with '\0'-delimited domain name.           */
+   char **dvec;    /**< List of pointers to the strings in dbuffer.       */
+   int    dcount;  /**< How many parts to this domain? (length of dvec)   */
+};
+
+
+/**
+ * Response generated by CGI, blocker, or error handler
+ */
+struct http_response
+{
+  char  *status;          /**< HTTP status (string). */
+  struct list headers[1]; /**< List of header lines. */
+  char  *head;            /**< Formatted http response head. */
+  size_t head_length;     /**< Length of http response head. */
+  char  *body;            /**< HTTP document body. */
+  size_t content_length;  /**< Length of body, REQUIRED if binary body. */
+  int    is_static;       /**< Nonzero if the content will never change and
+                               should be cached by the browser (e.g. images). */
+};
+
+/**
+ * A URL pattern.
+ */
+struct url_spec
+{
+   /** The string which was parsed to produce this url_spec.
+       Used for debugging or display only.  */
+   char  *spec;
+
+   char  *dbuffer;     /**< Buffer with '\0'-delimited domain name, or NULL to match all hosts. */
+   char **dvec;        /**< List of pointers to the strings in dbuffer.       */
+   int    dcount;      /**< How many parts to this domain? (length of dvec)   */
+   int    unanchored;  /**< Bitmap - flags are ANCHOR_LEFT and ANCHOR_RIGHT.  */
+
+   int   port;         /**< The port number, or 0 to match all ports.         */
+
+   char *path;         /**< The source for the regex.                         */
+   int   pathlen;      /**< ==strlen(path).  Needed for prefix matching.  FIXME: Now obsolete?     */
+   regex_t *preg;      /**< Regex for matching path part                      */
+};
+
+/**
+ * If you declare a static url_spec, this is the value to initialize it to zero.
+ */
+#define URL_SPEC_INITIALIZER { NULL, NULL, NULL, 0, 0, 0, NULL, 0, NULL }
+
+/**
+ * Constant for host part matching in URLs.  If set, indicates that the start of
+ * the pattern must match the start of the URL.  E.g. this is not set for the
+ * pattern ".example.com", so that it will match both "example.com" and
+ * "www.example.com".  It is set for the pattern "example.com", which makes it
+ * match "example.com" only, not "www.example.com".
+ */
+#define ANCHOR_LEFT  1
+
+/**
+ * Constant for host part matching in URLs.  If set, indicates that the end of
+ * the pattern must match the end of the URL.  E.g. this is not set for the
+ * pattern "ad.", so that it will match any host called "ad", irrespective
+ * of how many subdomains are in the fully-qualified domain name.
+ */
+#define ANCHOR_RIGHT 2
+
+
+/**
+ * An I/O buffer.  Holds a string which can be appended to, and can have data
+ * removed from the beginning.
+ */
+struct iob
+{
+   char *buf;    /**< Start of buffer        */
+   char *cur;    /**< Start of relevant data */
+   char *eod;    /**< End of relevant data   */
+   size_t size;  /**< Size as malloc()ed     */
+};
+
+
+/**
+ * Return the number of bytes in the I/O buffer associated with the passed
+ * client_state pointer.
+ * May be zero.
+ */
+#define IOB_PEEK(CSP) ((CSP->iob->cur > CSP->iob->eod) ? (CSP->iob->eod - CSP->iob->cur) : 0)
+
+
+/**
+ * Remove any data in the I/O buffer associated with the passed
+ * client_state pointer.
+ */
+#define IOB_RESET(CSP) if(CSP->iob->buf) free(CSP->iob->buf); memset(CSP->iob, '\0', sizeof(CSP->iob));
+
+/* Bits for csp->content_type */
+#define CT_TEXT   1 /**< csp->content_type bitmask:
+                         Suitable for pcrs filtering. */
+#define CT_GIF    2 /**< csp->content_type bitmask:
+                         Suitable for GIF filtering.  */
+#define CT_TABOO  4 /**< csp->content_type bitmask:
+                         DO NOT filter, irrespective of other flags. */
+
+/**
+ * The mask which includes all actions.
+ */
+#define ACTION_MASK_ALL        (~0UL)
+
+/**
+ * The most compatible set of actions - i.e. none.
+ */
+#define ACTION_MOST_COMPATIBLE 0x00000000UL
+
+/** Action bitmap: Block the request. */
+#define ACTION_BLOCK           0x00000001UL
+/** Action bitmap: Deanimate if it's a GIF. */
+#define ACTION_DEANIMATE       0x00000002UL
+/** Action bitmap: Downgrade HTTP/1.1 to 1.0. */
+#define ACTION_DOWNGRADE       0x00000004UL
+/** Action bitmap: Fast redirects. */
+#define ACTION_FAST_REDIRECTS  0x00000008UL
+/** Action bitmap: Remove existing "Forwarded" header, and do not add another. */
+#define ACTION_HIDE_FORWARDED  0x00000010UL
+/** Action bitmap: Hide "From" header. */
+#define ACTION_HIDE_FROM       0x00000020UL
+/** Action bitmap: Hide "Referer" header.  (sic - follow HTTP, not English). */
+#define ACTION_HIDE_REFERER    0x00000040UL
+/** Action bitmap: Hide "User-Agent" and similar headers. */
+#define ACTION_HIDE_USER_AGENT 0x00000080UL
+/** Action bitmap: This is an image. */
+#define ACTION_IMAGE           0x00000100UL
+/** Action bitmap: Sets the image blocker. */
+#define ACTION_IMAGE_BLOCKER   0x00000200UL
+/** Action bitmap: Prevent compression. */
+#define ACTION_NO_COMPRESSION  0x00000400UL
+/** Action bitmap: Change cookies to session only cookies. */
+#define ACTION_NO_COOKIE_KEEP  0x00000800UL
+/** Action bitmap: Block rending cookies. */
+#define ACTION_NO_COOKIE_READ  0x00001000UL
+/** Action bitmap: Block setting cookies. */
+#define ACTION_NO_COOKIE_SET   0x00002000UL
+/** Action bitmap: Filter out popups. */
+#define ACTION_NO_POPUPS       0x00004000UL
+/** Action bitmap: Send a vanilla wafer. */
+#define ACTION_VANILLA_WAFER   0x00008000UL
+/** Action bitmap: Limit CONNECT requests to safe ports. */
+#define ACTION_LIMIT_CONNECT   0x00010000UL
+
+/** Action string index: How to deanimate GIFs */
+#define ACTION_STRING_DEANIMATE     0
+/** Action string index: Replacement for "From:" header */
+#define ACTION_STRING_FROM          1
+/** Action string index: How to block images */
+#define ACTION_STRING_IMAGE_BLOCKER 2
+/** Action string index: Replacement for "Referer:" header */
+#define ACTION_STRING_REFERER       3
+/** Action string index: Replacement for "User-Agent:" header */
+#define ACTION_STRING_USER_AGENT    4
+/** Action string index: Legal CONNECT ports. */
+#define ACTION_STRING_LIMIT_CONNECT 5
+/** Number of string actions. */
+#define ACTION_STRING_COUNT         6
+
+/** Index into current_action_spec::multi[] for headers to add. */
+#define ACTION_MULTI_ADD_HEADER     0
+/** Index into current_action_spec::multi[] for headers to add. */
+#define ACTION_MULTI_WAFER          1
+/** Index into current_action_spec::multi[] for filters to apply. */
+#define ACTION_MULTI_FILTER         2
+/** Number of multi-string actions. */
+#define ACTION_MULTI_COUNT          3
+
+
+/**
+ * This structure contains a list of actions to apply to a URL.
+ * It only contains positive instructions - no "-" options.
+ * It is not used to store the actions list itself, only for
+ * url_actions() to return the current values.
+ */
+struct current_action_spec
+{
+   /** Actions to apply.  A bit set to "1" means perform the action. */
+   unsigned long flags;
+
+   /**
+    * Paramaters for those actions that require them.
+    * Each entry is valid if & only if the corresponding entry in "flags" is
+    * set.
+    */
+   char * string[ACTION_STRING_COUNT];
+
+   /** Lists of strings for multi-string actions. */
+   struct list multi[ACTION_MULTI_COUNT][1];
+};
+
+
+/**
+ * This structure contains a set of changes to actions.
+ * It can contain both positive and negative instructions.
+ * It is used to store an entry in the actions list.
+ */
+struct action_spec
+{
+   unsigned long mask; /**< Actions to keep. A bit set to "0" means remove action. */
+   unsigned long add;  /**< Actions to add.  A bit set to "1" means add action.    */
+
+   /**
+    * Paramaters for those actions that require them.
+    * Each entry is valid if & only if the corresponding entry in "flags" is
+    * set.
+    */
+   char * string[ACTION_STRING_COUNT];
+
+   /** Lists of strings to remove, for multi-string actions. */
+   struct list multi_remove[ACTION_MULTI_COUNT][1];
+
+   /** If nonzero, remove *all* strings from the multi-string action. */
+   int         multi_remove_all[ACTION_MULTI_COUNT];
+
+   /** Lists of strings to add, for multi-string actions. */
+   struct list multi_add[ACTION_MULTI_COUNT][1];
+};
+
+
+/**
+ * This structure is used to store the actions list.
+ *
+ * It contains a URL pattern, and the chages to the actions.
+ * It is a linked list.
+ */
+struct url_actions
+{
+   struct url_spec url[1];        /**< URL pattern. */
+
+   struct action_spec action[1];  /**< Actions. */
+
+   struct url_actions * next;     /**< Next action in file, or NULL. */
+};
+
+
+/*
+ * Flags for use in csp->flags
+ */
+/**
+ * Flag for csp->flags: Set if this client is processing data.
+ * Cleared when the thread associated with this structure dies.
+ */
+#define CSP_FLAG_ACTIVE     0x01
+
+/**
+ * Flag for csp->flags: Set if the server's reply is in "chunked"
+ * transfer encoding
+ */
+#define CSP_FLAG_CHUNKED    0x02
+
+/**
+ * Flag for csp->flags: Set if this request was enforced, although it would
+ * normally have been blocked.
+ */
+#define CSP_FLAG_FORCED     0x04
+
+/**
+ * Flag for csp->flags: Set if any modification to the body was done.
+ */
+#define CSP_FLAG_MODIFIED   0x08
+
+/**
+ * Flag for csp->flags: Set if request was blocked.
+ */
+#define CSP_FLAG_REJECTED   0x10
+
+/**
+ * Flag for csp->flags: Set if we are toggled on (FEATURE_TOGGLE).
+ */
+#define CSP_FLAG_TOGGLED_ON 0x20
+
+
+/**
+ * Maximum number of actions files.  This limit is arbitrary - it's just used
+ * to size an array.
+ */
+#define MAX_ACTION_FILES 10
+
+/**
+ * The state of a Privoxy processing thread.
+ */
+struct client_state
+{
+   /** The proxy's configuration */
+   struct configuration_spec * config;
+
+   /** The actions to perform on the current request */
+   struct current_action_spec  action[1];
+
+   /** socket to talk to client (web browser) */
+   jb_socket cfd;
+
+   /** socket to talk to server (web server or proxy) */
+   jb_socket sfd;
+
+   /** Multi-purpose flag container, see CSP_FLAG_* above */
+   unsigned short int flags;
+
+   /** Client PC's IP address, as reported by the accept() function.
+       As a string. */
+   char *ip_addr_str;
+   /** Client PC's IP address, as reported by the accept() function.
+       As a number. */
+   long  ip_addr_long;
+
+   /** Our IP address. I.e. the IP address that the client used to reach us,
+       as a string. */
+   char *my_ip_addr_str;
+
+   /** Our hostname. I.e. the reverse DNS of the IP address that the client
+       used to reach us, as a string. */
+   char *my_hostname;
+
+   /** The URL that was requested */
+   struct http_request http[1];
+
+   /** An I/O buffer used for buffering data read from the network */
+   struct iob iob[1];
+
+   /** List of all headers for this request */
+   struct list headers[1];
+
+   /** List of all cookies for this request */
+   struct list cookie_list[1];
+
+   /** MIME-Type key, see CT_* above */
+   unsigned short int content_type;
+
+   /** The "X-Forwarded-For:" header sent by the client */
+   char   *x_forwarded;
+
+   /** Actions files associated with this client */
+   struct file_list *actions_list[MAX_ACTION_FILES];
+
+   /** pcrs job file. */
+   struct file_list *rlist;
+
+   /** Length after content modification. */
+   size_t content_length;
+
+#ifdef FEATURE_TRUST
+
+   /** Trust file. */
+   struct file_list *tlist;
+
+#endif /* def FEATURE_TRUST */
+
+   /** Next thread in linked list. Only read or modify from the main thread! */
+   struct client_state *next;
+};
+
+
+/**
+ * A function to add a header
+ */
+typedef jb_err (*add_header_func_ptr)(struct client_state *);
+
+/**
+ * A function to process a header
+ */
+typedef jb_err (*parser_func_ptr    )(struct client_state *, char **);
+
+
+/**
+ * List of functions to run on a list of headers
+ */
+struct parsers
+{
+   /** The header prefix to match */
+   char   *str;
+   
+   /** The length of the prefix to match */
+   size_t len;
+   
+   /** The function to apply to this line */
+   parser_func_ptr parser;
+};
+
+
+/**
+ * List of available CGI functions.
+ */
+struct cgi_dispatcher
+{
+   /** The URL of the CGI, relative to the CGI root. */
+   const char * const name;
+
+   /** The handler function for the CGI */
+   jb_err    (* const handler)(struct client_state *csp, struct http_response *rsp, const struct map *parameters);
+
+   /** The description of the CGI, to appear on the main menu, or NULL to hide it. */
+   const char * const description;
+};
+
+
+/**
+ * A data file used by Privoxy.  Kept in a linked list.
+ */
+struct file_list
+{
+   /**
+    * This is a pointer to the data structures associated with the file.
+    * Read-only once the structure has been created.
+    */
+   void *f;
+
+   /**
+    * The unloader function.
+    * Normally NULL.  When we are finished with file (i.e. when we have
+    * loaded a new one), set to a pointer to an unloader function.
+    * Unloader will be called by sweep() (called from main loop) when
+    * all clients using this file are done.  This prevents threading
+    * problems.
+    */
+   void (*unloader)(void *);
+
+   /**
+    * Used internally by sweep().  Do not access from elsewhere.
+    */
+   int active;
+
+   /**
+    * File last-modified time, so we can check if file has been changed.
+    * Read-only once the structure has been created.
+    */
+   time_t lastmodified;
+   
+   /**
+    * The full filename.
+    */
+   char * filename;
+
+   /**
+    * Pointer to next entry in the linked list of all "file_list"s.
+    * This linked list is so that sweep() can navigate it.
+    * Since sweep() can remove items from the list, we must be careful
+    * to only access this value from main thread (when we know sweep
+    * won't be running).
+    */
+   struct file_list *next;
+};
+
+
+#ifdef FEATURE_TRUST
+
+/**
+ * The format of a trust file when loaded into memory.
+ */
+struct block_spec
+{
+   struct url_spec url[1];   /**< The URL pattern              */
+   int    reject;            /**< FIXME: Please document this! */
+   struct block_spec *next;  /**< Next entry in linked list    */
+};
+
+#endif /* def FEATURE_TRUST */
+
+
+#define SOCKS_NONE    0    /**< Don't use a SOCKS server               */
+#define SOCKS_4      40    /**< original SOCKS 4 protocol              */
+#define SOCKS_4A     41    /**< as modified for hosts w/o external DNS */
+
+
+/**
+ * How to forward a connection to a parent proxy.
+ */
+struct forward_spec
+{
+   /** URL pattern that this forward_spec is for. */
+   struct url_spec url[1];
+
+   /** Connection type.  Must be SOCKS_NONE, SOCKS_4, or SOCKS_4A. */
+   int   type;
+
+   /** SOCKS server hostname.  Only valid if "type" is SOCKS_4 or SOCKS_4A. */
+   char *gateway_host;
+
+   /** SOCKS server port. */
+   int   gateway_port;
+
+   /** Parent HTTP proxy hostname, or NULL for none. */
+   char *forward_host;
+
+   /** Parent HTTP proxy port. */
+   int   forward_port;
+
+   /** Next entry in the linked list. */
+   struct forward_spec *next;
+};
+
+
+/**
+ * Initializer for a static struct forward_spec.
+ */
+#define FORWARD_SPEC_INITIALIZER { { URL_SPEC_INITIALIZER }, 0, NULL, 0, NULL, 0, NULL }
+
+
+/**
+ * This struct represents one filter (one block) from
+ * the re_filterfile. If there is more than one filter
+ * in the file, the file will be represented by a
+ * chained list of re_filterfile specs.
+ */
+struct re_filterfile_spec
+{
+   char *name;                      /**< Name from FILTER: statement in re_filterfile. */
+   char *description;               /**< Description from FILTER: statement in re_filterfile. */
+   struct list patterns[1];         /**< The patterns from the re_filterfile. */
+   pcrs_job *joblist;               /**< The resulting compiled pcrs_jobs. */
+   struct re_filterfile_spec *next; /**< The pointer for chaining. */
+};
+
+
+#ifdef FEATURE_ACL
+
+#define ACL_PERMIT   1  /**< Accept connection request */
+#define ACL_DENY     2  /**< Reject connection request */
+
+/**
+ * An IP address pattern.  Used to specify networks in the ACL.
+ */
+struct access_control_addr
+{
+   unsigned long addr;  /**< The IP address as an integer. */
+   unsigned long mask;  /**< The network mask as an integer. */
+   unsigned long port;  /**< The port number. */
+};
+
+/**
+ * An access control list (ACL) entry.
+ *
+ * This is a linked list.
+ */
+struct access_control_list
+{
+   struct access_control_addr src[1];  /**< Client IP address */
+   struct access_control_addr dst[1];  /**< Website or parent proxy IP address */
+
+   short action;                       /**< ACL_PERMIT or ACL_DENY */
+   struct access_control_list *next;   /**< The next entry in the ACL. */
+};
+
+#endif /* def FEATURE_ACL */
+
+
+/** Maximum number of loaders (actions, re_filter, ...) */
+#define NLOADERS 8
+
+
+/** configuration_spec::feature_flags: CGI actions editor. */
+#define RUNTIME_FEATURE_CGI_EDIT_ACTIONS  1
+
+/** configuration_spec::feature_flags: Web-based toggle. */
+#define RUNTIME_FEATURE_CGI_TOGGLE        2
+
+
+/**
+ * Data loaded from the configuration file.
+ *
+ * (Anomaly: toggle is still handled through a global, not this structure)
+ */
+struct configuration_spec
+{
+   /** What to log */
+   int debug;
+   
+   /** Nonzero to enable multithreading. */
+   int multi_threaded;
+
+   /**
+    * Bitmask of features that can be enabled/disabled through the config
+    * file.  Currently defined bits:
+    *
+    * - RUNTIME_FEATURE_CGI_EDIT_ACTIONS
+    * - RUNTIME_FEATURE_CGI_TOGGLE
+    */
+   unsigned feature_flags;
+
+   /** The log file name. */
+   const char *logfile;
+
+   /** The config file directory. */
+   const char *confdir;
+
+   /** The log file directory. */
+   const char *logdir;
+
+   /** The full paths to the actions files. */
+   const char *actions_file[MAX_ACTION_FILES];
+
+   /** The short names of the actions files. */
+   const char *actions_file_short[MAX_ACTION_FILES];
+
+   /** The administrator's email address */
+   char *admin_address;
+
+   /** A URL with info on this proxy */
+   char *proxy_info_url;
+
+   /** URL to the user manual (on our website or local copy) */
+   char *usermanual;
+
+   /** The file name of the pcre filter file */
+   const char *re_filterfile;
+
+#ifdef FEATURE_COOKIE_JAR
+
+   /** The file name of the cookie jar file */
+   const char * jarfile;
+
+   /** The handle to the cookie jar file */
+   FILE * jar;
+
+#endif /* def FEATURE_COOKIE_JAR */
+
+   /** IP address to bind to.  Defaults to HADDR_DEFAULT == 127.0.0.1. */
+   const char *haddr;
+
+   /** Port to bind to.  Defaults to HADDR_PORT == 8118. */
+   int         hport;
+
+   /** Size limit for IOB */
+   size_t buffer_limit;
+
+#ifdef FEATURE_TRUST
+
+   /** The file name of the trust file. */
+   const char * trustfile;
+
+   /** FIXME: DOCME: Document this. */
+   struct list trust_info[1];
+
+   /** FIXME: DOCME: Document this. */
+   struct url_spec *trust_list[64];
+
+#endif /* def FEATURE_TRUST */
+
+#ifdef FEATURE_ACL
+
+   /** The access control list (ACL). */
+   struct access_control_list *acl;
+
+#endif /* def FEATURE_ACL */
+
+   /** Information about parent proxies (forwarding). */
+   struct forward_spec *forward;
+
+   /** All options from the config file, HTML-formatted. */
+   char *proxy_args;
+
+   /** The configuration file object. */
+   struct file_list *config_file_list;
+
+   /** List of loaders */
+   int (*loaders[NLOADERS])(struct client_state *);
+
+   /** Nonzero if we need to bind() to the new port. */
+   int need_bind;
+};
+
+/** Calculates the number of elements in an array, using sizeof. */
+#define SZ(X)  (sizeof(X) / sizeof(*X))
+
+#ifdef FEATURE_FORCE_LOAD
+/** The force load URL prefix. */
+#define FORCE_PREFIX "/PRIVOXY-FORCE"
+#endif /* def FEATURE_FORCE_LOAD */
+
+#ifdef FEATURE_NO_GIFS
+/** The MIME type for images ("image/png" or "image/gif"). */
+#define BUILTIN_IMAGE_MIMETYPE "image/png"
+#else
+#define BUILTIN_IMAGE_MIMETYPE "image/gif"
+#endif /* def FEATURE_NO_GIFS */
+
+
+/* 
+ * Hardwired URLs
+ */
+
+/** URL for the Privoxy home page. */
+#define HOME_PAGE_URL     "http://www.privoxy.org/"
+
+/** URL for the Privoxy user manual. */
+#define USER_MANUAL_URL   HOME_PAGE_URL "/" VERSION "/user-manual/"
+
+/** Prefix for actions help links  (append to USER_MANUAL_URL). */
+#define ACTIONS_HELP_PREFIX "actions-file.html#"
+
+/** Prefix for config option help links (append to USER_MANUAL_URL). */
+#define CONFIG_HELP_PREFIX  "config.html#"
+
+/*
+ * The "hosts" to intercept and display CGI pages.
+ * First one is a hostname only, second one can specify host and path.
+ *
+ * Notes:
+ * 1) Do not specify the http: prefix
+ * 2) CGI_SITE_2_PATH must not end with /, one will be added automatically.
+ * 3) CGI_SITE_2_PATH must start with /, unless it is the empty string.
+ */
+#define CGI_SITE_1_HOST "p.p"
+#define CGI_SITE_2_HOST "config.privoxy.org"
+#define CGI_SITE_2_PATH ""
+
+/**
+ * The prefix for CGI pages.  Written out in generated HTML.
+ * INCLUDES the trailing slash.
+ */
+#define CGI_PREFIX  "http://" CGI_SITE_2_HOST CGI_SITE_2_PATH "/"
+
+
+/* HTTP snipplets.
+ *
+ * FIXME: This is very inefficient.  There could be one copy of these strings
+ * for each .c file!!  They should be "extern", not "static".
+ */
+static const char CSUCCEED[] =
+   "HTTP/1.0 200 Connection established\n"
+   "Proxy-Agent: Privoxy/" VERSION "\r\n\r\n";
+
+static const char CHEADER[] =
+   "HTTP/1.0 400 Invalid header received from browser\r\n\r\n";
+
+static const char CFORBIDDEN[] =
+   "HTTP/1.0 403 Connection not allowable\r\nX-Hint: If you read this message interactively, then you know why this happens ,-)\r\n\r\n";
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* ndef PROJECT_H_INCLUDED */
+
+/*
+  Local Variables:
+  tab-width: 3
+  end:
+*/
diff --git a/src/ssplit.c b/src/ssplit.c
new file mode 100644 (file)
index 0000000..1c3a2f5
--- /dev/null
@@ -0,0 +1,211 @@
+const char ssplit_rcs[] = "$Id: ssplit.c,v 1.6 2002/03/26 22:29:55 swa Exp $";
+/*********************************************************************
+ *
+ * File        :  $Source: /cvsroot/ijbswa/current/ssplit.c,v $
+ *
+ * Purpose     :  A function to split a string at specified deliminters.
+ *
+ * Copyright   :  Written by and Copyright (C) 2001 the SourceForge
+ *                Privoxy team. http://www.privoxy.org/
+ *
+ *                Based on the Internet Junkbuster originally written
+ *                by and Copyright (C) 1997 Anonymous Coders and 
+ *                Junkbusters Corporation.  http://www.junkbusters.com
+ *
+ *                This program is free software; you can redistribute it 
+ *                and/or modify it under the terms of the GNU General
+ *                Public License as published by the Free Software
+ *                Foundation; either version 2 of the License, or (at
+ *                your option) any later version.
+ *
+ *                This program is distributed in the hope that it will
+ *                be useful, but WITHOUT ANY WARRANTY; without even the
+ *                implied warranty of MERCHANTABILITY or FITNESS FOR A
+ *                PARTICULAR PURPOSE.  See the GNU General Public
+ *                License for more details.
+ *
+ *                The GNU General Public License should be included with
+ *                this file.  If not, you can view it at
+ *                http://www.gnu.org/copyleft/gpl.html
+ *                or write to the Free Software Foundation, Inc., 59
+ *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * Revisions   :
+ *    $Log: ssplit.c,v $
+ *    Revision 1.6  2002/03/26 22:29:55  swa
+ *    we have a new homepage!
+ *
+ *    Revision 1.5  2002/03/24 13:25:43  swa
+ *    name change related issues
+ *
+ *    Revision 1.4  2001/11/13 00:16:38  jongfoster
+ *    Replacing references to malloc.h with the standard stdlib.h
+ *    (See ANSI or K&R 2nd Ed)
+ *
+ *    Revision 1.3  2001/05/29 08:54:25  jongfoster
+ *    Rewrote the innards of ssplit() to be easier to understand,
+ *    faster, and to use less memory.  Didn't change the interface
+ *    except to give the parameters meaningful names.
+ *
+ *    Revision 1.2  2001/05/17 23:01:01  oes
+ *     - Cleaned CRLF's from the sources and related files
+ *
+ *    Revision 1.1.1.1  2001/05/15 13:59:04  oes
+ *    Initial import of version 2.9.3 source tree
+ *
+ *
+ *********************************************************************/
+\f
+
+#include "config.h"
+
+#include <string.h>
+#include <stdlib.h>
+
+#include "ssplit.h"
+#include "miscutil.h"
+
+const char ssplit_h_rcs[] = SSPLIT_H_VERSION;
+
+/* Define this for lots of debugging information to stdout */
+#undef SSPLIT_VERBOSE
+/* #define SSPLIT_VERBOSE 1 */
+
+
+/*********************************************************************
+ *
+ * Function    :  ssplit
+ *
+ * Description :  Split a string using delimiters in `delim'.  Results
+ *                go into `vec'.
+ *
+ * Parameters  :
+ *          1  :  str = string to split.  Will be split in place
+ *                (i.e. do not free until you've finished with vec,
+ *                previous contents will be trashed by the call).
+ *          2  :  delim = array of delimiters (if NULL, uses " \t").
+ *          3  :  vec[] = results vector (aka. array) [out]
+ *          4  :  vec_len = number of usable slots in the vector (aka. array size)
+ *          5  :  dont_save_empty_fields = zero if consecutive delimiters
+ *                give a null output field(s), nonzero if they are just 
+ *                to be considered as single delimeter
+ *          6  :  ignore_leading = nonzero to ignore leading field
+ *                separators.
+ *
+ * Returns     :  -1 => Error: vec_len is too small to hold all the 
+ *                      data, or str == NULL.
+ *                >=0 => the number of fields put in `vec'.
+ *                On error, vec and str may still have been overwritten.
+ *
+ *********************************************************************/
+int ssplit(char *str, const char *delim, char *vec[], int vec_len, 
+           int dont_save_empty_fields, int ignore_leading)
+{
+   unsigned char is_delim[256];
+   unsigned char char_type;
+   int vec_count = 0;
+
+   if (!str)
+   {
+      return(-1);
+   }
+
+
+   /* Build is_delim array */
+
+   memset(is_delim, '\0', sizeof(is_delim));
+
+   if (!delim)
+   {
+      delim = " \t";  /* default field separators */
+   }
+
+   while (*delim)
+   {
+      is_delim[(unsigned)(unsigned char)*delim++] = 1;   /* separator  */
+   }
+
+   is_delim[(unsigned)(unsigned char)'\0'] = 2;   /* terminator */
+   is_delim[(unsigned)(unsigned char)'\n'] = 2;   /* terminator */
+
+
+   /* Parse string */
+
+   if (ignore_leading)
+   {
+      /* skip leading separators */
+      while (is_delim[(unsigned)(unsigned char)*str] == 1)
+      {
+         str++;
+      }
+   }
+
+   /* first pointer is the beginning of string */
+   /* Check if we want to save this field */
+   if ( (!dont_save_empty_fields)
+     || (is_delim[(unsigned)(unsigned char)*str] == 0) )
+      {
+      /*
+       * We want empty fields, or the first character in this 
+       * field is not a delimiter or the end of string.
+       * So save it.
+       */
+      if (vec_count >= vec_len)
+      {
+         return(-1); /* overflow */
+      }
+      vec[vec_count++] = (char *) str;   
+   }
+
+   while ((char_type = is_delim[(unsigned)(unsigned char)*str]) != 2)
+   {
+      if (char_type == 1)    
+      {
+         /* the char is a separator */
+
+         /* null terminate the substring */
+         *str++ = '\0';      
+
+         /* Check if we want to save this field */
+         if ( (!dont_save_empty_fields)
+           || (is_delim[(unsigned)(unsigned char)*str] == 0) )
+            {
+            /*
+             * We want empty fields, or the first character in this 
+             * field is not a delimiter or the end of string.
+             * So save it.
+             */
+            if (vec_count >= vec_len)
+            {
+               return(-1); /* overflow */
+            }
+            vec[vec_count++] = (char *) str;   
+         }
+      }
+      else
+      {
+         str++;
+      }
+   }
+   *str = '\0';     /* null terminate the substring */
+
+#ifdef SSPLIT_VERBOSE
+   {
+      int i;
+      printf("dump %d strings\n", vec_count);
+      for (i = 0; i < vec_count; i++)
+      {
+         printf("%d '%s'\n", i, vec[i]);
+      }
+   }
+#endif /* def SSPLIT_VERBOSE */
+
+   return(vec_count);
+}
+
+
+/*
+  Local Variables:
+  tab-width: 3
+  end:
+*/
diff --git a/src/ssplit.h b/src/ssplit.h
new file mode 100644 (file)
index 0000000..9e1e8ed
--- /dev/null
@@ -0,0 +1,80 @@
+#ifndef SSPLIT_H_INCLUDED
+#define SSPLIT_H_INCLUDED
+#define SSPLIT_H_VERSION "$Id: ssplit.h,v 1.5 2002/03/26 22:29:55 swa Exp $"
+/*********************************************************************
+ *
+ * File        :  $Source: /cvsroot/ijbswa/current/ssplit.h,v $
+ *
+ * Purpose     :  A function to split a string at specified deliminters.
+ *
+ * Copyright   :  Written by and Copyright (C) 2001 the SourceForge
+ *                Privoxy team. http://www.privoxy.org/
+ *
+ *                Based on the Internet Junkbuster originally written
+ *                by and Copyright (C) 1997 Anonymous Coders and 
+ *                Junkbusters Corporation.  http://www.junkbusters.com
+ *
+ *                This program is free software; you can redistribute it 
+ *                and/or modify it under the terms of the GNU General
+ *                Public License as published by the Free Software
+ *                Foundation; either version 2 of the License, or (at
+ *                your option) any later version.
+ *
+ *                This program is distributed in the hope that it will
+ *                be useful, but WITHOUT ANY WARRANTY; without even the
+ *                implied warranty of MERCHANTABILITY or FITNESS FOR A
+ *                PARTICULAR PURPOSE.  See the GNU General Public
+ *                License for more details.
+ *
+ *                The GNU General Public License should be included with
+ *                this file.  If not, you can view it at
+ *                http://www.gnu.org/copyleft/gpl.html
+ *                or write to the Free Software Foundation, Inc., 59
+ *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * Revisions   :
+ *    $Log: ssplit.h,v $
+ *    Revision 1.5  2002/03/26 22:29:55  swa
+ *    we have a new homepage!
+ *
+ *    Revision 1.4  2002/03/24 13:25:43  swa
+ *    name change related issues
+ *
+ *    Revision 1.3  2001/07/29 18:43:08  jongfoster
+ *    Changing #ifdef _FILENAME_H to FILENAME_H_INCLUDED, to conform to
+ *    ANSI C rules.
+ *
+ *    Revision 1.2  2001/05/29 08:54:25  jongfoster
+ *    Rewrote the innards of ssplit() to be easier to understand,
+ *    faster, and to use less memory.  Didn't change the interface
+ *    except to give the parameters meaningful names.
+ *
+ *    Revision 1.1.1.1  2001/05/15 13:59:04  oes
+ *    Initial import of version 2.9.3 source tree
+ *
+ *
+ *********************************************************************/
+\f
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern int ssplit(char *str, const char *delim, char *vec[], int vec_len, 
+                  int dont_save_empty_fields, int ignore_leading);
+
+/* Revision control strings from this header and associated .c file */
+extern const char ssplit_rcs[];
+extern const char ssplit_h_rcs[];
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* ndef SSPLIT_H_INCLUDED */
+
+/*
+  Local Variables:
+  tab-width: 3
+  end:
+*/
diff --git a/src/urlmatch.c b/src/urlmatch.c
new file mode 100644 (file)
index 0000000..4bad343
--- /dev/null
@@ -0,0 +1,812 @@
+const char urlmatch_rcs[] = "$Id: urlmatch.c,v 1.10 2002/05/12 21:40:37 jongfoster Exp $";
+/*********************************************************************
+ *
+ * File        :  $Source: /cvsroot/ijbswa/current/urlmatch.c,v $
+ *
+ * Purpose     :  Declares functions to match URLs against URL
+ *                patterns.
+ *
+ * Copyright   :  Written by and Copyright (C) 2001 the SourceForge
+ *                Privoxy team. http://www.privoxy.org/
+ *
+ *                Based on the Internet Junkbuster originally written
+ *                by and Copyright (C) 1997 Anonymous Coders and
+ *                Junkbusters Corporation.  http://www.junkbusters.com
+ *
+ *                This program is free software; you can redistribute it
+ *                and/or modify it under the terms of the GNU General
+ *                Public License as published by the Free Software
+ *                Foundation; either version 2 of the License, or (at
+ *                your option) any later version.
+ *
+ *                This program is distributed in the hope that it will
+ *                be useful, but WITHOUT ANY WARRANTY; without even the
+ *                implied warranty of MERCHANTABILITY or FITNESS FOR A
+ *                PARTICULAR PURPOSE.  See the GNU General Public
+ *                License for more details.
+ *
+ *                The GNU General Public License should be included with
+ *                this file.  If not, you can view it at
+ *                http://www.gnu.org/copyleft/gpl.html
+ *                or write to the Free Software Foundation, Inc., 59
+ *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * Revisions   :
+ *    $Log: urlmatch.c,v $
+ *    Revision 1.10  2002/05/12 21:40:37  jongfoster
+ *    - Removing some unused code
+ *
+ *    Revision 1.9  2002/04/04 00:36:36  gliptak
+ *    always use pcre for matching
+ *
+ *    Revision 1.8  2002/04/03 23:32:47  jongfoster
+ *    Fixing memory leak on error
+ *
+ *    Revision 1.7  2002/03/26 22:29:55  swa
+ *    we have a new homepage!
+ *
+ *    Revision 1.6  2002/03/24 13:25:43  swa
+ *    name change related issues
+ *
+ *    Revision 1.5  2002/03/13 00:27:05  jongfoster
+ *    Killing warnings
+ *
+ *    Revision 1.4  2002/03/07 03:46:17  oes
+ *    Fixed compiler warnings
+ *
+ *    Revision 1.3  2002/03/03 14:51:11  oes
+ *    Fixed CLF logging: Added ocmd member for client's request to struct http_request
+ *
+ *    Revision 1.2  2002/01/21 00:14:09  jongfoster
+ *    Correcting comment style
+ *    Fixing an uninitialized memory bug in create_url_spec()
+ *
+ *    Revision 1.1  2002/01/17 20:53:46  jongfoster
+ *    Moving all our URL and URL pattern parsing code to the same file - it
+ *    was scattered around in filters.c, loaders.c and parsers.c.
+ *
+ *    Providing a single, simple url_match(pattern,url) function - rather than
+ *    the 3-line match routine which was repeated all over the place.
+ *
+ *    Renaming free_url to free_url_spec, since it frees a struct url_spec.
+ *
+ *    Providing parse_http_url() so that URLs can be parsed without faking a
+ *    HTTP request line for parse_http_request() or repeating the parsing
+ *    code (both of which were techniques that were actually in use).
+ *
+ *    Standardizing that struct http_request is used to represent a URL, and
+ *    struct url_spec is used to represent a URL pattern.  (Before, URLs were
+ *    represented as seperate variables and a partially-filled-in url_spec).
+ *
+ *
+ *********************************************************************/
+\f
+
+#include "config.h"
+
+#ifndef _WIN32
+#include <stdio.h>
+#include <sys/types.h>
+#endif
+
+#include <stdlib.h>
+#include <ctype.h>
+#include <assert.h>
+#include <string.h>
+
+#if !defined(_WIN32) && !defined(__OS2__)
+#include <unistd.h>
+#endif
+
+#include "project.h"
+#include "urlmatch.h"
+#include "ssplit.h"
+#include "miscutil.h"
+#include "errlog.h"
+
+const char urlmatch_h_rcs[] = URLMATCH_H_VERSION;
+
+
+/*********************************************************************
+ *
+ * Function    :  free_http_request
+ *
+ * Description :  Freez a http_request structure
+ *
+ * Parameters  :
+ *          1  :  http = points to a http_request structure to free
+ *
+ * Returns     :  N/A
+ *
+ *********************************************************************/
+void free_http_request(struct http_request *http)
+{
+   assert(http);
+
+   freez(http->cmd);
+   freez(http->ocmd);
+   freez(http->gpc);
+   freez(http->host);
+   freez(http->url);
+   freez(http->hostport);
+   freez(http->path);
+   freez(http->ver);
+   freez(http->host_ip_addr_str);
+   freez(http->dbuffer);
+   freez(http->dvec);
+   http->dcount = 0;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  parse_http_url
+ *
+ * Description :  Parse out the host and port from the URL.  Find the
+ *                hostname & path, port (if ':'), and/or password (if '@')
+ *
+ * Parameters  :
+ *          1  :  url = URL (or is it URI?) to break down
+ *          2  :  http = pointer to the http structure to hold elements.
+ *                       Will be zeroed before use.  Note that this
+ *                       function sets the http->gpc and http->ver
+ *                       members to NULL.
+ *          3  :  csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns     :  JB_ERR_OK on success
+ *                JB_ERR_MEMORY on out of memory
+ *                JB_ERR_CGI_PARAMS on malformed command/URL
+ *                                  or >100 domains deep.
+ *
+ *********************************************************************/
+jb_err parse_http_url(const char * url,
+                      struct http_request *http,
+                      struct client_state *csp)
+{
+   /*
+    * Zero out the results structure
+    */
+   memset(http, '\0', sizeof(*http));
+
+
+   /*
+    * Save our initial URL
+    */
+   http->url = strdup(url);
+   if (http->url == NULL)
+   {
+      return JB_ERR_MEMORY;
+   }
+
+
+   /*
+    * Split URL into protocol,hostport,path.
+    */
+   {
+      char *buf;
+      char *url_noproto;
+      char *url_path;
+
+      buf = strdup(url);
+      if (buf == NULL)
+      {
+         return JB_ERR_MEMORY;
+      }
+
+      /* Find the start of the URL in our scratch space */
+      url_noproto = buf;
+      if (strncmpic(url_noproto, "http://",  7) == 0)
+      {
+         url_noproto += 7;
+         http->ssl = 0;
+      }
+      else if (strncmpic(url_noproto, "https://", 8) == 0)
+      {
+         url_noproto += 8;
+         http->ssl = 1;
+      }
+      else
+      {
+         http->ssl = 0;
+      }
+
+      url_path = strchr(url_noproto, '/');
+      if (url_path != NULL)
+      {
+         /*
+          * Got a path.
+          *
+          * NOTE: The following line ignores the path for HTTPS URLS.
+          * This means that you get consistent behaviour if you type a
+          * https URL in and it's parsed by the function.  (When the
+          * URL is actually retrieved, SSL hides the path part).
+          */
+         http->path = strdup(http->ssl ? "/" : url_path);
+         *url_path = '\0';
+         http->hostport = strdup(url_noproto);
+      }
+      else
+      {
+         /*
+          * Repair broken HTTP requests that don't contain a path,
+          * or CONNECT requests
+          */
+         http->path = strdup("/");
+         http->hostport = strdup(url_noproto);
+      }
+
+      free(buf);
+
+      if ( (http->path == NULL)
+        || (http->hostport == NULL))
+      {
+         free(buf);
+         free_http_request(http);
+         return JB_ERR_MEMORY;
+      }
+   }
+
+
+   /*
+    * Split hostport into user/password (ignored), host, port.
+    */
+   {
+      char *buf;
+      char *host;
+      char *port;
+
+      buf = strdup(http->hostport);
+      if (buf == NULL)
+      {
+         free_http_request(http);
+         return JB_ERR_MEMORY;
+      }
+
+      /* check if url contains username and/or password */
+      host = strchr(buf, '@');
+      if (host != NULL)
+      {
+         /* Contains username/password, skip it and the @ sign. */
+         host++;
+      }
+      else
+      {
+         /* No username or password. */
+         host = buf;
+      }
+
+      /* check if url contains port */
+      port = strchr(host, ':');
+      if (port != NULL)
+      {
+         /* Contains port */
+         /* Terminate hostname and point to start of port string */
+         *port++ = '\0';
+         http->port = atoi(port);
+      }
+      else
+      {
+         /* No port specified. */
+         http->port = (http->ssl ? 143 : 80);
+      }
+
+      http->host = strdup(host);
+
+      free(buf);
+
+      if (http->host == NULL)
+      {
+         free_http_request(http);
+         return JB_ERR_MEMORY;
+      }
+   }
+
+
+   /*
+    * Split domain name so we can compare it against wildcards
+    */
+   {
+      char *vec[BUFFER_SIZE];
+      size_t size;
+      char *p;
+
+      http->dbuffer = strdup(http->host);
+      if (NULL == http->dbuffer)
+      {
+         free_http_request(http);
+         return JB_ERR_MEMORY;
+      }
+
+      /* map to lower case */
+      for (p = http->dbuffer; *p ; p++)
+      {
+         *p = tolower((int)(unsigned char)*p);
+      }
+
+      /* split the domain name into components */
+      http->dcount = ssplit(http->dbuffer, ".", vec, SZ(vec), 1, 1);
+
+      if (http->dcount <= 0)
+      {
+         /*
+          * Error: More than SZ(vec) components in domain
+          *    or: no components in domain
+          */
+         free_http_request(http);
+         return JB_ERR_PARSE;
+      }
+
+      /* save a copy of the pointers in dvec */
+      size = http->dcount * sizeof(*http->dvec);
+
+      http->dvec = (char **)malloc(size);
+      if (NULL == http->dvec)
+      {
+         free_http_request(http);
+         return JB_ERR_MEMORY;
+      }
+
+      memcpy(http->dvec, vec, size);
+   }
+
+
+   return JB_ERR_OK;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  parse_http_request
+ *
+ * Description :  Parse out the host and port from the URL.  Find the
+ *                hostname & path, port (if ':'), and/or password (if '@')
+ *
+ * Parameters  :
+ *          1  :  req = HTTP request line to break down
+ *          2  :  http = pointer to the http structure to hold elements
+ *          3  :  csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns     :  JB_ERR_OK on success
+ *                JB_ERR_MEMORY on out of memory
+ *                JB_ERR_CGI_PARAMS on malformed command/URL
+ *                                  or >100 domains deep.
+ *
+ *********************************************************************/
+jb_err parse_http_request(const char *req,
+                          struct http_request *http,
+                          struct client_state *csp)
+{
+   char *buf;
+   char *v[10];
+   int n;
+   jb_err err;
+   int is_connect = 0;
+
+   memset(http, '\0', sizeof(*http));
+
+   buf = strdup(req);
+   if (buf == NULL)
+   {
+      return JB_ERR_MEMORY;
+   }
+
+   n = ssplit(buf, " \r\n", v, SZ(v), 1, 1);
+   if (n != 3)
+   {
+      free(buf);
+      return JB_ERR_PARSE;
+   }
+
+   /* this could be a CONNECT request */
+   if (strcmpic(v[0], "connect") == 0)
+   {
+      /* Secure */
+      is_connect = 1;
+   }
+   /* or it could be any other basic HTTP request type */
+   else if ((0 == strcmpic(v[0], "get"))
+         || (0 == strcmpic(v[0], "head"))
+         || (0 == strcmpic(v[0], "post"))
+         || (0 == strcmpic(v[0], "put"))
+         || (0 == strcmpic(v[0], "delete"))
+         /* or a webDAV extension (RFC2518) */
+         || (0 == strcmpic(v[0], "propfind"))
+         || (0 == strcmpic(v[0], "proppatch"))
+         || (0 == strcmpic(v[0], "move"))
+         || (0 == strcmpic(v[0], "copy"))
+         || (0 == strcmpic(v[0], "mkcol"))
+         || (0 == strcmpic(v[0], "lock"))
+         || (0 == strcmpic(v[0], "unlock"))
+         )
+   {
+      /* Normal */
+      is_connect = 0;
+   }
+   else
+   {
+      /* Unknown HTTP method */
+      free(buf);
+      return JB_ERR_PARSE;
+   }
+
+   err = parse_http_url(v[1], http, csp);
+   if (err)
+   {
+      free(buf);
+      return err;
+   }
+
+   /*
+    * Copy the details into the structure
+    */
+   http->ssl = is_connect;
+   http->cmd = strdup(req);
+   http->gpc = strdup(v[0]);
+   http->ver = strdup(v[2]);
+
+   if ( (http->cmd == NULL)
+     || (http->gpc == NULL)
+     || (http->ver == NULL) )
+   {
+      free(buf);
+      free_http_request(http);
+      return JB_ERR_MEMORY;
+   }
+
+   return JB_ERR_OK;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  simple_domaincmp
+ *
+ * Description :  Domain-wise Compare fqdn's.  The comparison is
+ *                both left- and right-anchored.  The individual
+ *                domain names are compared with simplematch().
+ *                This is only used by domain_match.
+ *
+ * Parameters  :
+ *          1  :  pv = array of patterns to compare
+ *          2  :  fv = array of domain components to compare
+ *          3  :  len = length of the arrays (both arrays are the
+ *                      same length - if they weren't, it couldn't
+ *                      possibly be a match).
+ *
+ * Returns     :  0 => domains are equivalent, else no match.
+ *
+ *********************************************************************/
+static int simple_domaincmp(char **pv, char **fv, int len)
+{
+   int n;
+
+   for (n = 0; n < len; n++)
+   {
+      if (simplematch(pv[n], fv[n]))
+      {
+         return 1;
+      }
+   }
+
+   return 0;
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  domain_match
+ *
+ * Description :  Domain-wise Compare fqdn's. Governed by the bimap in
+ *                pattern->unachored, the comparison is un-, left-,
+ *                right-anchored, or both.
+ *                The individual domain names are compared with
+ *                simplematch().
+ *
+ * Parameters  :
+ *          1  :  pattern = a domain that may contain a '*' as a wildcard.
+ *          2  :  fqdn = domain name against which the patterns are compared.
+ *
+ * Returns     :  0 => domains are equivalent, else no match.
+ *
+ *********************************************************************/
+static int domain_match(const struct url_spec *pattern, const struct http_request *fqdn)
+{
+   char **pv, **fv;  /* vectors  */
+   int    plen, flen;
+   int unanchored = pattern->unanchored & (ANCHOR_RIGHT | ANCHOR_LEFT);
+
+   plen = pattern->dcount;
+   flen = fqdn->dcount;
+
+   if (flen < plen)
+   {
+      /* fqdn is too short to match this pattern */
+      return 1;
+   }
+
+   pv   = pattern->dvec;
+   fv   = fqdn->dvec;
+
+   if (unanchored == ANCHOR_LEFT)
+   {
+      /*
+       * Right anchored.
+       *
+       * Convert this into a fully anchored pattern with
+       * the fqdn and pattern the same length
+       */
+      fv += (flen - plen); /* flen - plen >= 0 due to check above */
+      return simple_domaincmp(pv, fv, plen);
+   }
+   else if (unanchored == 0)
+   {
+      /* Fully anchored, check length */
+      if (flen != plen)
+      {
+         return 1;
+      }
+      return simple_domaincmp(pv, fv, plen);
+   }
+   else if (unanchored == ANCHOR_RIGHT)
+   {
+      /* Left anchored, ignore all extra in fqdn */
+      return simple_domaincmp(pv, fv, plen);
+   }
+   else
+   {
+      /* Unanchored */
+      int n;
+      int maxn = flen - plen;
+      for (n = 0; n <= maxn; n++)
+      {
+         if (!simple_domaincmp(pv, fv, plen))
+         {
+            return 0;
+         }
+         /*
+          * Doesn't match from start of fqdn
+          * Try skipping first part of fqdn
+          */
+         fv++;
+      }
+      return 1;
+   }
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  create_url_spec
+ *
+ * Description :  Creates a "url_spec" structure from a string.
+ *                When finished, free with unload_url().
+ *
+ * Parameters  :
+ *          1  :  url = Target url_spec to be filled in.  Will be
+ *                      zeroed before use.
+ *          2  :  buf = Source pattern, null terminated.  NOTE: The
+ *                      contents of this buffer are destroyed by this
+ *                      function.  If this function succeeds, the
+ *                      buffer is copied to url->spec.  If this
+ *                      function fails, the contents of the buffer
+ *                      are lost forever.
+ *
+ * Returns     :  JB_ERR_OK - Success
+ *                JB_ERR_MEMORY - Out of memory
+ *                JB_ERR_PARSE - Cannot parse regex (Detailed message
+ *                               written to system log)
+ *
+ *********************************************************************/
+jb_err create_url_spec(struct url_spec * url, const char * buf)
+{
+   char *p;
+
+   assert(url);
+   assert(buf);
+
+   /* Zero memory */
+   memset(url, '\0', sizeof(*url));
+
+   /* save a copy of the orignal specification */
+   if ((url->spec = strdup(buf)) == NULL)
+   {
+      return JB_ERR_MEMORY;
+   }
+
+   if ((p = strchr(buf, '/')) != NULL)
+   {
+      if (NULL == (url->path = strdup(p)))
+      {
+         freez(url->spec);
+         return JB_ERR_MEMORY;
+      }
+      url->pathlen = strlen(url->path);
+      *p = '\0';
+   }
+   else
+   {
+      url->path    = NULL;
+      url->pathlen = 0;
+   }
+   if (url->path)
+   {
+      int errcode;
+      char rebuf[BUFFER_SIZE];
+
+      if (NULL == (url->preg = zalloc(sizeof(*url->preg))))
+      {
+         freez(url->spec);
+         freez(url->path);
+         return JB_ERR_MEMORY;
+      }
+
+      sprintf(rebuf, "^(%s)", url->path);
+
+      errcode = regcomp(url->preg, rebuf,
+            (REG_EXTENDED|REG_NOSUB|REG_ICASE));
+      if (errcode)
+      {
+         size_t errlen = regerror(errcode,
+            url->preg, rebuf, sizeof(rebuf));
+
+         if (errlen > (sizeof(rebuf) - (size_t)1))
+         {
+            errlen = sizeof(rebuf) - (size_t)1;
+         }
+         rebuf[errlen] = '\0';
+
+         log_error(LOG_LEVEL_ERROR, "error compiling %s: %s",
+            url->spec, rebuf);
+
+         freez(url->spec);
+         freez(url->path);
+         regfree(url->preg);
+         freez(url->preg);
+
+         return JB_ERR_PARSE;
+      }
+   }
+   if ((p = strchr(buf, ':')) == NULL)
+   {
+      url->port = 0;
+   }
+   else
+   {
+      *p++ = '\0';
+      url->port = atoi(p);
+   }
+
+   if (buf[0] != '\0')
+   {
+      char *v[150];
+      size_t size;
+
+      /* Parse domain part */
+      if (buf[strlen(buf) - 1] == '.')
+      {
+         url->unanchored |= ANCHOR_RIGHT;
+      }
+      if (buf[0] == '.')
+      {
+         url->unanchored |= ANCHOR_LEFT;
+      }
+
+      /* split domain into components */
+
+      url->dbuffer = strdup(buf);
+      if (NULL == url->dbuffer)
+      {
+         freez(url->spec);
+         freez(url->path);
+         regfree(url->preg);
+         freez(url->preg);
+         return JB_ERR_MEMORY;
+      }
+
+      /* map to lower case */
+      for (p = url->dbuffer; *p ; p++)
+      {
+         *p = tolower((int)(unsigned char)*p);
+      }
+
+      /* split the domain name into components */
+      url->dcount = ssplit(url->dbuffer, ".", v, SZ(v), 1, 1);
+
+      if (url->dcount < 0)
+      {
+         freez(url->spec);
+         freez(url->path);
+         regfree(url->preg);
+         freez(url->preg);
+         freez(url->dbuffer);
+         url->dcount = 0;
+         return JB_ERR_MEMORY;
+      }
+      else if (url->dcount != 0)
+      {
+
+         /* save a copy of the pointers in dvec */
+         size = url->dcount * sizeof(*url->dvec);
+
+         url->dvec = (char **)malloc(size);
+         if (NULL == url->dvec)
+         {
+            freez(url->spec);
+            freez(url->path);
+            regfree(url->preg);
+            freez(url->preg);
+            freez(url->dbuffer);
+            url->dcount = 0;
+            return JB_ERR_MEMORY;
+         }
+
+         memcpy(url->dvec, v, size);
+      }
+   }
+
+   return JB_ERR_OK;
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  free_url_spec
+ *
+ * Description :  Called from the "unloaders".  Freez the url
+ *                structure elements.
+ *
+ * Parameters  :
+ *          1  :  url = pointer to a url_spec structure.
+ *
+ * Returns     :  N/A
+ *
+ *********************************************************************/
+void free_url_spec(struct url_spec *url)
+{
+   if (url == NULL) return;
+
+   freez(url->spec);
+   freez(url->dbuffer);
+   freez(url->dvec);
+   freez(url->path);
+   if (url->preg)
+   {
+      regfree(url->preg);
+      freez(url->preg);
+   }
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  url_match
+ *
+ * Description :  Compare a URL against a URL pattern.
+ *
+ * Parameters  :
+ *          1  :  pattern = a URL pattern
+ *          2  :  url = URL to match
+ *
+ * Returns     :  0 iff the URL matches the pattern, else nonzero.
+ *
+ *********************************************************************/
+int url_match(const struct url_spec *pattern,
+              const struct http_request *url)
+{
+   return ((pattern->port == 0) || (pattern->port == url->port))
+       && ((pattern->dbuffer == NULL) || (domain_match(pattern, url) == 0))
+       && ((pattern->path == NULL) ||
+            (regexec(pattern->preg, url->path, 0, NULL, 0) == 0)
+      );
+}
+
+
+/*
+  Local Variables:
+  tab-width: 3
+  end:
+*/
diff --git a/src/urlmatch.h b/src/urlmatch.h
new file mode 100644 (file)
index 0000000..fb4b451
--- /dev/null
@@ -0,0 +1,100 @@
+#ifndef URLMATCH_H_INCLUDED
+#define URLMATCH_H_INCLUDED
+#define URLMATCH_H_VERSION "$Id: urlmatch.h,v 1.3 2002/03/26 22:29:55 swa Exp $"
+/*********************************************************************
+ *
+ * File        :  $Source: /cvsroot/ijbswa/current/urlmatch.h,v $
+ *
+ * Purpose     :  Declares functions to match URLs against URL
+ *                patterns.
+ *
+ * Copyright   :  Written by and Copyright (C) 2001 the SourceForge
+ *                Privoxy team. http://www.privoxy.org/
+ *
+ *                Based on the Internet Junkbuster originally written
+ *                by and Copyright (C) 1997 Anonymous Coders and 
+ *                Junkbusters Corporation.  http://www.junkbusters.com
+ *
+ *                This program is free software; you can redistribute it 
+ *                and/or modify it under the terms of the GNU General
+ *                Public License as published by the Free Software
+ *                Foundation; either version 2 of the License, or (at
+ *                your option) any later version.
+ *
+ *                This program is distributed in the hope that it will
+ *                be useful, but WITHOUT ANY WARRANTY; without even the
+ *                implied warranty of MERCHANTABILITY or FITNESS FOR A
+ *                PARTICULAR PURPOSE.  See the GNU General Public
+ *                License for more details.
+ *
+ *                The GNU General Public License should be included with
+ *                this file.  If not, you can view it at
+ *                http://www.gnu.org/copyleft/gpl.html
+ *                or write to the Free Software Foundation, Inc., 59
+ *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * Revisions   :
+ *    $Log: urlmatch.h,v $
+ *    Revision 1.3  2002/03/26 22:29:55  swa
+ *    we have a new homepage!
+ *
+ *    Revision 1.2  2002/03/24 13:25:43  swa
+ *    name change related issues
+ *
+ *    Revision 1.1  2002/01/17 20:53:46  jongfoster
+ *    Moving all our URL and URL pattern parsing code to the same file - it
+ *    was scattered around in filters.c, loaders.c and parsers.c.
+ *
+ *    Providing a single, simple url_match(pattern,url) function - rather than
+ *    the 3-line match routine which was repeated all over the place.
+ *
+ *    Renaming free_url to free_url_spec, since it frees a struct url_spec.
+ *
+ *    Providing parse_http_url() so that URLs can be parsed without faking a
+ *    HTTP request line for parse_http_request() or repeating the parsing
+ *    code (both of which were techniques that were actually in use).
+ *
+ *    Standardizing that struct http_request is used to represent a URL, and
+ *    struct url_spec is used to represent a URL pattern.  (Before, URLs were
+ *    represented as seperate variables and a partially-filled-in url_spec).
+ *
+ *
+ *********************************************************************/
+\f
+
+#include "project.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern void free_http_request(struct http_request *http);
+extern jb_err parse_http_request(const char *req,
+                                 struct http_request *http,
+                                 struct client_state *csp);
+extern jb_err parse_http_url(const char * url,
+                             struct http_request *http,
+                             struct client_state *csp);
+
+extern int url_match(const struct url_spec *pattern,
+                     const struct http_request *url);
+
+extern jb_err create_url_spec(struct url_spec * url, const char * buf);
+extern void free_url_spec(struct url_spec *url);
+
+
+/* Revision control strings from this header and associated .c file */
+extern const char urlmatch_rcs[];
+extern const char urlmatch_h_rcs[];
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* ndef URLMATCH_H_INCLUDED */
+
+/*
+  Local Variables:
+  tab-width: 3
+  end:
+*/
diff --git a/src/w32log.c b/src/w32log.c
new file mode 100644 (file)
index 0000000..5559a38
--- /dev/null
@@ -0,0 +1,1347 @@
+const char w32log_rcs[] = "$Id: w32log.c,v 1.25 2002/04/04 00:36:36 gliptak Exp $";
+/*********************************************************************
+ *
+ * File        :  $Source: /cvsroot/ijbswa/current/w32log.c,v $
+ *
+ * Purpose     :  Functions for creating and destroying the log window,
+ *                ouputting strings, processing messages and so on.
+ *
+ * Copyright   :  Written by and Copyright (C) 2001-2002 members of
+ *                the Privoxy team.  http://www.privoxy.org/
+ *
+ *                Written by and Copyright (C) 1999 Adam Lock
+ *                <locka@iol.ie>
+ *
+ *                This program is free software; you can redistribute it 
+ *                and/or modify it under the terms of the GNU General
+ *                Public License as published by the Free Software
+ *                Foundation; either version 2 of the License, or (at
+ *                your option) any later version.
+ *
+ *                This program is distributed in the hope that it will
+ *                be useful, but WITHOUT ANY WARRANTY; without even the
+ *                implied warranty of MERCHANTABILITY or FITNESS FOR A
+ *                PARTICULAR PURPOSE.  See the GNU General Public
+ *                License for more details.
+ *
+ *                The GNU General Public License should be included with
+ *                this file.  If not, you can view it at
+ *                http://www.gnu.org/copyleft/gpl.html
+ *                or write to the Free Software Foundation, Inc., 59
+ *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * Revisions   :
+ *    $Log: w32log.c,v $
+ *    Revision 1.25  2002/04/04 00:36:36  gliptak
+ *    always use pcre for matching
+ *
+ *    Revision 1.24  2002/03/31 17:19:00  jongfoster
+ *    Win32 only: Enabling STRICT to fix a VC++ compile warning.
+ *
+ *    Revision 1.23  2002/03/26 22:57:10  jongfoster
+ *    Web server name should begin www.
+ *
+ *    Revision 1.22  2002/03/24 12:48:23  jongfoster
+ *    Fixing doc links
+ *
+ *    Revision 1.21  2002/03/24 12:07:35  jongfoster
+ *    Consistern name for filters file
+ *
+ *    Revision 1.20  2002/03/24 12:03:47  jongfoster
+ *    Name change
+ *
+ *    Revision 1.19  2002/01/17 21:04:17  jongfoster
+ *    Replacing hard references to the URL of the config interface
+ *    with #defines from project.h
+ *
+ *    Revision 1.18  2001/11/30 23:37:24  jongfoster
+ *    Renaming the Win32 config file to config.txt - this is almost the
+ *    same as the corresponding UNIX name "config"
+ *
+ *    Revision 1.17  2001/11/16 00:46:31  jongfoster
+ *    Fixing compiler warnings
+ *
+ *    Revision 1.16  2001/08/01 19:58:12  jongfoster
+ *    Fixing documentation filenames in help menu, and making status
+ *    option work without needing the "Junkbuster Status.URL" file.
+ *
+ *    Revision 1.15  2001/07/30 22:08:36  jongfoster
+ *    Tidying up #defines:
+ *    - All feature #defines are now of the form FEATURE_xxx
+ *    - Permanently turned off WIN_GUI_EDIT
+ *    - Permanently turned on WEBDAV and SPLIT_PROXY_ARGS
+ *
+ *    Revision 1.14  2001/07/29 18:47:05  jongfoster
+ *    Adding missing #include "loadcfg.h"
+ *
+ *    Revision 1.13  2001/07/19 19:15:14  haroon
+ *    - Added a FIXME for EditFile but didn't fix :-)
+ *
+ *    Revision 1.12  2001/07/13 14:04:59  oes
+ *    Removed all #ifdef PCRS
+ *
+ *    Revision 1.11  2001/06/07 23:08:12  jongfoster
+ *    Forward and ACL edit options removed.
+ *
+ *    Revision 1.10  2001/05/31 21:37:11  jongfoster
+ *    GUI changes to rename "permissions file" to "actions file".
+ *
+ *    Revision 1.9  2001/05/31 17:33:13  oes
+ *
+ *    CRLF -> LF
+ *
+ *    Revision 1.8  2001/05/29 09:50:24  jongfoster
+ *    Unified blocklist/imagelist/permissionslist.
+ *    File format is still under discussion, but the internal changes
+ *    are (mostly) done.
+ *
+ *    Also modified interceptor behaviour:
+ *    - We now intercept all URLs beginning with one of the following
+ *      prefixes (and *only* these prefixes):
+ *        * http://i.j.b/
+ *        * http://ijbswa.sf.net/config/
+ *        * http://ijbswa.sourceforge.net/config/
+ *    - New interceptors "home page" - go to http://i.j.b/ to see it.
+ *    - Internal changes so that intercepted and fast redirect pages
+ *      are not replaced with an image.
+ *    - Interceptors now have the option to send a binary page direct
+ *      to the client. (i.e. ijb-send-banner uses this)
+ *    - Implemented show-url-info interceptor.  (Which is why I needed
+ *      the above interceptors changes - a typical URL is
+ *      "http://i.j.b/show-url-info?url=www.somesite.com/banner.gif".
+ *      The previous mechanism would not have intercepted that, and
+ *      if it had been intercepted then it then it would have replaced
+ *      it with an image.)
+ *
+ *    Revision 1.7  2001/05/26 01:26:34  jongfoster
+ *    New #define, WIN_GUI_EDIT, enables the (embryonic) Win32 GUI editor.
+ *    This #define cannot be set from ./configure - there's no point, it
+ *    doesn't work yet.  See feature request # 425722
+ *
+ *    Revision 1.6  2001/05/26 00:31:30  jongfoster
+ *    Fixing compiler warning about comparing signed/unsigned.
+ *
+ *    Revision 1.5  2001/05/26 00:28:36  jongfoster
+ *    Automatic reloading of config file.
+ *    Removed obsolete SIGHUP support (Unix) and Reload menu option (Win32).
+ *    Most of the global variables have been moved to a new
+ *    struct configuration_spec, accessed through csp->config->globalname
+ *    Most of the globals remaining are used by the Win32 GUI.
+ *
+ *    Revision 1.4  2001/05/22 18:56:28  oes
+ *    CRLF -> LF
+ *
+ *    Revision 1.3  2001/05/20 15:07:54  jongfoster
+ *    File is now ignored if _WIN_CONSOLE is defined.
+ *
+ *    Revision 1.2  2001/05/20 01:21:20  jongfoster
+ *    Version 2.9.4 checkin.
+ *    - Merged popupfile and cookiefile, and added control over PCRS
+ *      filtering, in new "permissionsfile".
+ *    - Implemented LOG_LEVEL_FATAL, so that if there is a configuration
+ *      file error you now get a message box (in the Win32 GUI) rather
+ *      than the program exiting with no explanation.
+ *    - Made killpopup use the PCRS MIME-type checking and HTTP-header
+ *      skipping.
+ *    - Removed tabs from "config"
+ *    - Moved duplicated url parsing code in "loaders.c" to a new funcition.
+ *    - Bumped up version number.
+ *
+ *    Revision 1.1.1.1  2001/05/15 13:59:07  oes
+ *    Initial import of version 2.9.3 source tree
+ *
+ *
+ *********************************************************************/
+\f
+
+#include "config.h"
+
+#include <assert.h>
+#include <stdio.h>
+
+#ifndef STRICT
+#define STRICT
+#endif
+#include <windows.h>
+#include <richedit.h>
+
+#include "project.h"
+#include "w32log.h"
+#include "w32taskbar.h"
+#include "win32.h"
+#include "w32res.h"
+#include "jcc.h"
+#include "miscutil.h"
+#include "errlog.h"
+#include "loadcfg.h"
+
+const char w32res_h_rcs[] = W32RES_H_VERSION;
+
+#ifdef __MINGW32__
+#include "cygwin.h"
+const char cygwin_h_rcs[] = CYGWIN_H_VERSION;
+#endif
+
+const char w32log_h_rcs[] = W32LOG_H_VERSION;
+
+#ifndef _WIN_CONSOLE /* entire file */
+
+/*
+ * Timers and the various durations
+ */
+#define TIMER_ANIM_ID               1
+#define TIMER_ANIM_TIME             100
+#define TIMER_ANIMSTOP_ID           2
+#define TIMER_ANIMSTOP_TIME         1000
+#define TIMER_CLIPBUFFER_ID         3
+#define TIMER_CLIPBUFFER_TIME       1000
+#define TIMER_CLIPBUFFER_FORCE_ID   4
+#define TIMER_CLIPBUFFER_FORCE_TIME 5000
+
+/*
+ * Styles of text that can be output
+ */
+#define STYLE_NONE      0
+#define STYLE_HIGHLIGHT 1
+#define STYLE_LINK      2
+#define STYLE_HEADER    3
+
+/*
+ * Number of frames of animation in tray activity sequence
+ */
+#define ANIM_FRAMES 8
+
+#define DEFAULT_MAX_BUFFER_LINES    200
+#define DEFAULT_LOG_FONT_NAME       "MS Sans Serif"
+#define DEFAULT_LOG_FONT_SIZE       8
+
+/*
+ * These values affect the way the log window behaves, they should be read
+ * from a file but for the moment, they are hardcoded here. Some options are
+ * configurable through the UI.
+ */
+
+/* Indicates whether task bar shows activity animation */
+BOOL g_bShowActivityAnimation = 1;
+
+/* Indicates if the log window appears on the task bar */
+BOOL g_bShowOnTaskBar = 0;
+
+/* Indicates whether closing the log window really just hides it */
+BOOL g_bCloseHidesWindow = 1;
+
+/* Indicates if messages are logged at all */
+BOOL g_bLogMessages = 1;
+
+/* Indicates whether log messages are highlighted */
+BOOL g_bHighlightMessages = 1;
+
+/* Indicates if buffer is limited in size */
+BOOL g_bLimitBufferSize = 1;
+
+/* Maximum number of lines allowed in buffer when limited */
+int g_nMaxBufferLines = DEFAULT_MAX_BUFFER_LINES;
+
+/* Font to use */
+char g_szFontFaceName[255] = DEFAULT_LOG_FONT_NAME;
+
+/* Size of font to use */
+int g_nFontSize = DEFAULT_LOG_FONT_SIZE;
+
+
+/* FIXME: this is a kludge */
+
+const char * g_actions_file = NULL;
+const char * g_re_filterfile = NULL;
+#ifdef FEATURE_TRUST
+const char * g_trustfile = NULL;
+#endif /* def FEATURE_TRUST */
+
+/* FIXME: end kludge */
+
+/* Regular expression for detected URLs */
+#define RE_URL "http:[^ \n\r]*"
+
+/*
+ * Regular expressions that are used to perform highlight in the log window
+ */
+static struct _Pattern
+{
+   const char *str;
+   int style;
+   regex_t buffer;
+} patterns_to_highlight[] =
+{
+   /* url headers */
+   { RE_URL,                STYLE_LINK },
+/* { "[a-zA-Z0-9]+\\.[a-zA-Z0-9]+\\.[a-zA-Z0-9]+\\.[^ \n\r]*", STYLE_LINK }, */
+   /* interesting text to highlight */
+   { "crunch!",           STYLE_HIGHLIGHT },
+   /* http headers */
+   { "referer:",            STYLE_HEADER },
+   { "proxy-connection:",   STYLE_HEADER },
+   { "proxy-agent:",        STYLE_HEADER },
+   { "user-agent:",         STYLE_HEADER },
+   { "host:",               STYLE_HEADER },
+   { "accept:",             STYLE_HEADER },
+   { "accept-encoding:",    STYLE_HEADER },
+   { "accept-language:",    STYLE_HEADER },
+   { "accept-charset:",     STYLE_HEADER },
+   { "accept-ranges:",      STYLE_HEADER },
+   { "date:",               STYLE_HEADER },
+   { "cache-control:",      STYLE_HEADER },
+   { "cache-last-checked:", STYLE_HEADER },
+   { "connection:",         STYLE_HEADER },
+   { "content-type",        STYLE_HEADER },
+   { "content-length",      STYLE_HEADER },
+   { "cookie",              STYLE_HEADER },
+   { "last-modified:",      STYLE_HEADER },
+   { "pragma:",             STYLE_HEADER },
+   { "server:",             STYLE_HEADER },
+   { "etag:",               STYLE_HEADER },
+   { "expires:",            STYLE_HEADER },
+   { "warning:",            STYLE_HEADER },
+   /* this is the terminator statement - do not delete! */
+   { NULL,                  STYLE_NONE }
+};
+
+/*
+ * Public variables
+ */
+HWND g_hwndLogFrame;
+
+/*
+ * Private variables
+ */
+static CRITICAL_SECTION g_criticalsection;
+static HWND g_hwndTray;
+static HWND g_hwndLogBox;
+static WNDPROC g_fnLogBox;
+static HICON g_hiconAnim[ANIM_FRAMES];
+static HICON g_hiconIdle;
+static HICON g_hiconApp;
+static int g_nAnimFrame;
+static BOOL g_bClipPending = FALSE;
+static int g_nRichEditVersion = 0;
+
+/*
+ * Private functions
+ */
+static HWND CreateLogWindow(HINSTANCE hInstance, int nCmdShow);
+static HWND CreateHiddenLogOwnerWindow(HINSTANCE hInstance);
+static LRESULT CALLBACK LogWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+static LRESULT CALLBACK LogOwnerWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+static LRESULT CALLBACK LogRichEditProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
+static BOOL InitRichEdit(void);
+static void LogClipBuffer(void);
+static void LogCreatePatternMatchingBuffers(void);
+static void LogDestroyPatternMatchingBuffers(void);
+static int LogPutStringNoMatch(const char *pszText, int style);
+
+
+/*********************************************************************
+ *
+ * Function    :  InitLogWindow
+ *
+ * Description :  Initialise the log window.
+ *
+ * Parameters  :  None
+ *
+ * Returns     :  Always TRUE (there should be error checking on the resources).
+ *
+ *********************************************************************/
+BOOL InitLogWindow(void)
+{
+   int i;
+
+   /* Load the icons */
+   g_hiconIdle = LoadIcon(g_hInstance, MAKEINTRESOURCE(IDI_IDLE));
+   for (i = 0; i < ANIM_FRAMES; i++)
+   {
+      g_hiconAnim[i] = LoadIcon(g_hInstance, MAKEINTRESOURCE(IDI_ANIMATED1 + i));
+   }
+   g_hiconApp = LoadIcon(g_hInstance, MAKEINTRESOURCE(IDI_MAINICON));
+
+   /* Create the user interface */
+   g_hwndLogFrame = CreateLogWindow(g_hInstance, g_nCmdShow);
+   g_hwndTray = CreateTrayWindow(g_hInstance);
+   TrayAddIcon(g_hwndTray, 1, g_hiconApp, "Privoxy");
+
+   /* Create pattern matching buffers (for highlighting */
+   LogCreatePatternMatchingBuffers();
+
+   /* Create a critical section to protect multi-threaded access to certain things */
+   InitializeCriticalSection(&g_criticalsection);
+
+   return TRUE;
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  TermLogWindow
+ *
+ * Description :  Cleanup the logwindow.
+ *
+ * Parameters  :  None
+ *
+ * Returns     :  N/A
+ *
+ *********************************************************************/
+void TermLogWindow(void)
+{
+   int i;
+
+   LogDestroyPatternMatchingBuffers();
+
+   TrayDeleteIcon(g_hwndTray, 1);
+   DeleteObject(g_hiconApp);
+   DeleteObject(g_hiconIdle);
+   for (i = 0; i < ANIM_FRAMES; i++)
+   {
+      DeleteObject(g_hiconAnim[i]);
+   }
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  LogCreatePatternMatchingBuffers
+ *
+ * Description :  Compile the pattern matching buffers.
+ *
+ * Parameters  :  None
+ *
+ * Returns     :  N/A
+ *
+ *********************************************************************/
+void LogCreatePatternMatchingBuffers(void)
+{
+   int i;
+   for (i = 0; patterns_to_highlight[i].str != NULL; i++)
+   {
+      regcomp(&patterns_to_highlight[i].buffer, patterns_to_highlight[i].str, REG_ICASE);
+   }
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  LogDestroyPatternMatchingBuffers
+ *
+ * Description :  Free up the pattern matching buffers.
+ *
+ * Parameters  :  None
+ *
+ * Returns     :  N/A
+ *
+ *********************************************************************/
+void LogDestroyPatternMatchingBuffers(void)
+{
+   int i;
+   for (i = 0; patterns_to_highlight[i].str != NULL; i++)
+   {
+      regfree(&patterns_to_highlight[i].buffer);
+   }
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  LogGetURLUnderCursor
+ *
+ * Description :  Returns the URL from under the cursor (remember to free it!).
+ *
+ * Parameters  :  None
+ *
+ * Returns     :  NULL or a pointer to an URL string.
+ *
+ *********************************************************************/
+char *LogGetURLUnderCursor(void)
+{
+   char *szResult = NULL;
+   regex_t re;
+   POINT ptCursor;
+   POINTL ptl;
+   DWORD nPos;
+   DWORD nWordStart = 0;
+   DWORD nWordEnd = 0;
+
+   regcomp(&re, RE_URL, REG_ICASE);
+
+   /* Get the position of the cursor over the text window */
+   GetCursorPos(&ptCursor);
+   ScreenToClient(g_hwndLogBox, &ptCursor);
+   ptl.x = ptCursor.x;
+   ptl.y = ptCursor.y;
+
+   /* Search backwards and fowards to obtain the word that is highlighted */
+   nPos = LOWORD(SendMessage(g_hwndLogBox, EM_CHARFROMPOS, 0, (LPARAM) &ptl));
+   nWordStart = SendMessage(g_hwndLogBox, EM_FINDWORDBREAK, WB_LEFT, nPos);
+   nWordEnd = SendMessage(g_hwndLogBox, EM_FINDWORDBREAK, WB_RIGHTBREAK, nPos);
+
+   /* Compare the string to the pattern */
+   if (nWordEnd > nWordStart)
+   {
+      TEXTRANGE range;
+      regmatch_t match;
+
+      range.chrg.cpMin = nWordStart;
+      range.chrg.cpMax = nWordEnd;
+      range.lpstrText = (LPSTR)zalloc(nWordEnd - nWordStart + 1);
+      SendMessage(g_hwndLogBox, EM_GETTEXTRANGE, 0, (LPARAM) &range);
+
+      if (regexec(&re, range.lpstrText, 1, &match, 0) == 0)
+      {
+         szResult = range.lpstrText;
+      }
+      else
+      {
+         free(range.lpstrText);
+      }
+
+      regfree(&re);
+   }
+   return szResult;
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  LogPutString
+ *
+ * Description :  Inserts text into the logging window.  This is really
+ *                a regexp aware wrapper function to `LogPutStringNoMatch'.
+ *
+ * Parameters  :
+ *          1  :  pszText = pointer to string going to the log window
+ *
+ * Returns     :  1 => success, else the return code from `LogPutStringNoMatch'.
+ *                FIXME: this is backwards to the rest of IJB and to common
+ *                programming practice.  Please use 0 => success instead.
+ *
+ *********************************************************************/
+int LogPutString(const char *pszText)
+{
+   int i;
+   int result = 0;
+
+   if (pszText == NULL || strlen(pszText) == 0)
+   {
+      return 1;
+   }
+
+   if (!g_bLogMessages)
+   {
+      return 1;
+   }
+
+   /* Critical section stops multiple threads doing nasty interactions that
+    * foul up the highlighting and output.
+    */
+   EnterCriticalSection(&g_criticalsection);
+
+   if (g_bHighlightMessages)
+   {
+      regmatch_t match;
+
+      /* First things first, regexp scan for various things that we would like highlighted */
+      for (i = 0; patterns_to_highlight[i].str != NULL; i++)
+      {
+         if (regexec(&patterns_to_highlight[i].buffer, pszText, 1, &match, 0) == 0)
+         {
+            char *pszBefore = NULL;
+            char *pszMatch = NULL;
+            char *pszAfter = NULL;
+            int nMatchSize;
+
+            /* Split the string up into pieces representing the strings, before
+               at and after the matching pattern
+             */
+            if (match.rm_so > 0)
+            {
+               pszBefore = (char *)malloc((match.rm_so + 1) * sizeof(char));
+               memset(pszBefore, 0, (match.rm_so + 1) * sizeof(char));
+               strncpy(pszBefore, pszText, match.rm_so);
+            }
+            if (match.rm_eo < (regoff_t)strlen(pszText))
+            {
+               pszAfter = strdup(&pszText[match.rm_eo]);
+            }
+            nMatchSize = match.rm_eo - match.rm_so;
+            pszMatch = (char *)malloc(nMatchSize + 1);
+            strncpy(pszMatch, &pszText[match.rm_so], nMatchSize);
+            pszMatch[nMatchSize] = '\0';
+
+            /* Recursively call LogPutString */
+            if (pszBefore)
+            {
+               LogPutString(pszBefore);
+               free(pszBefore);
+            }
+            if (pszMatch)
+            {
+               LogPutStringNoMatch(pszMatch, patterns_to_highlight[i].style);
+               free(pszMatch);
+            }
+            if (pszAfter)
+            {
+               LogPutString(pszAfter);
+               free(pszAfter);
+            }
+
+            result = 1;
+            goto end;
+         }
+      }
+   }
+
+   result = LogPutStringNoMatch(pszText, STYLE_NONE);
+
+end:
+   LeaveCriticalSection(&g_criticalsection);
+
+   return result;
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  LogPutStringNoMatch
+ *
+ * Description :  Puts a string into the logging window.
+ *
+ * Parameters  :
+ *          1  :  pszText = pointer to string going to the log window
+ *          2  :  style = STYLE_NONE, STYLE_HEADER, STYLE_HIGHLIGHT, or STYLE_LINK
+ *
+ * Returns     :  Always 1 => success.
+ *                FIXME: this is backwards to the rest of IJB and to common
+ *                programming practice.  Please use 0 => success instead.
+ *
+ *********************************************************************/
+int LogPutStringNoMatch(const char *pszText, int style)
+{
+   CHARRANGE range;
+   CHARFORMAT format;
+   int nTextLength;
+
+   assert(g_hwndLogBox);
+   if (g_hwndLogBox == NULL)
+   {
+      return 1;
+   }
+
+   /* TODO preserve existing selection */
+
+   /* Go to the end of the text */
+   nTextLength = GetWindowTextLength(g_hwndLogBox);
+   range.cpMin = nTextLength;
+   range.cpMax = nTextLength;
+   SendMessage(g_hwndLogBox, EM_EXSETSEL, 0, (LPARAM) &range);
+
+   /* Apply a formatting style */
+   memset(&format, 0, sizeof(format));
+   format.cbSize = sizeof(format);
+   format.dwMask = CFM_BOLD | CFM_UNDERLINE | CFM_STRIKEOUT | CFM_ITALIC | CFM_COLOR | CFM_FACE | CFM_SIZE;
+   format.yHeight = (g_nFontSize * 1440) / 72;
+   strcpy(format.szFaceName, g_szFontFaceName);
+   if (style == STYLE_NONE)
+   {
+      /* DO NOTHING */
+      format.dwEffects |= CFE_AUTOCOLOR;
+   }
+   else if (style == STYLE_HEADER)
+   {
+      format.dwEffects |= CFE_AUTOCOLOR | CFE_ITALIC;
+   }
+   else if (style == STYLE_HIGHLIGHT)
+   {
+      format.dwEffects |= CFE_AUTOCOLOR | CFE_BOLD;
+   }
+   else if (style == STYLE_LINK)
+   {
+      format.dwEffects |= CFE_UNDERLINE;
+      format.crTextColor = RGB(0, 0, 255);
+   }
+   SendMessage(g_hwndLogBox, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM) &format);
+
+   /* Append text to the end */
+   SendMessage(g_hwndLogBox, EM_REPLACESEL, FALSE, (LPARAM) pszText);
+
+   /* TODO Restore the old selection */
+
+   /* Purge buffer */
+   if (strchr(pszText, '\n') != NULL)
+   {
+      SetTimer(g_hwndLogFrame, TIMER_CLIPBUFFER_ID, TIMER_CLIPBUFFER_TIME, NULL);
+      if (!g_bClipPending)
+      {
+         /* Set the force clip timer going. This timer ensures clipping is done
+            intermittently even when there is a sustained burst of logging
+         */
+         SetTimer(g_hwndLogFrame, TIMER_CLIPBUFFER_FORCE_ID, TIMER_CLIPBUFFER_FORCE_TIME, NULL);
+      }
+      g_bClipPending = TRUE;
+   }
+
+   return 1;
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  LogShowActivity
+ *
+ * Description :  Start the spinner.
+ *
+ * Parameters  :  None
+ *
+ * Returns     :  N/A
+ *
+ *********************************************************************/
+void LogShowActivity(void)
+{
+   /* Start some activity timers */
+   if (g_bShowActivityAnimation)
+   {
+      SetTimer(g_hwndLogFrame, TIMER_ANIM_ID, TIMER_ANIM_TIME, NULL);
+      SetTimer(g_hwndLogFrame, TIMER_ANIMSTOP_ID, TIMER_ANIMSTOP_TIME, NULL);
+   }
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  LogClipBuffer
+ *
+ * Description :  Prunes old lines from the log.
+ *
+ * Parameters  :  None
+ *
+ * Returns     :  N/A
+ *
+ *********************************************************************/
+void LogClipBuffer(void)
+{
+   int nLines = SendMessage(g_hwndLogBox, EM_GETLINECOUNT, 0, 0);
+   if (g_bLimitBufferSize && nLines > g_nMaxBufferLines)
+   {
+      /* Compute the range representing the lines to be deleted */
+      LONG nLastLineToDelete = nLines - g_nMaxBufferLines;
+      LONG nLastChar = SendMessage(g_hwndLogBox, EM_LINEINDEX, nLastLineToDelete, 0);
+      CHARRANGE range;
+      range.cpMin = 0;
+      range.cpMax = nLastChar;
+
+      /* TODO get current selection */
+
+      /* TODO adjust and clip old selection against range to be deleted */
+
+      /* Select range and erase it (turning off autoscroll to prevent
+         nasty scrolling) */
+      SendMessage(g_hwndLogBox, EM_SETOPTIONS, ECOOP_XOR, ECO_AUTOVSCROLL);
+      SendMessage(g_hwndLogBox, EM_EXSETSEL, 0, (LPARAM) &range);
+      SendMessage(g_hwndLogBox, EM_REPLACESEL, FALSE, (LPARAM) "");
+      SendMessage(g_hwndLogBox, EM_SETOPTIONS, ECOOP_XOR, ECO_AUTOVSCROLL);
+
+      /* Restore old selection */
+   }
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  CreateHiddenLogOwnerWindow
+ *
+ * Description :  Creates a hidden owner window that stops the log
+ *                window appearing in the task bar.
+ *
+ * Parameters  :
+ *          1  :  hInstance = application's instance handle
+ *
+ * Returns     :  Handle to newly created window.
+ *
+ *********************************************************************/
+HWND CreateHiddenLogOwnerWindow(HINSTANCE hInstance)
+{
+   static const char *szWndName = "PrivoxyLogOwner";
+   WNDCLASS wc;
+   HWND hwnd;
+
+   wc.style          = 0;
+   wc.lpfnWndProc    = LogOwnerWindowProc;
+   wc.cbClsExtra     = 0;
+   wc.cbWndExtra     = 0;
+   wc.hInstance      = hInstance;
+   wc.hIcon          = 0;
+   wc.hCursor        = 0;
+   wc.hbrBackground  = 0;
+   wc.lpszMenuName   = 0;
+   wc.lpszClassName  = szWndName;
+
+   RegisterClass(&wc);
+
+   hwnd = CreateWindow(szWndName, szWndName,
+      WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
+      CW_USEDEFAULT, NULL, NULL, hInstance, NULL );
+
+   return hwnd;
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  LogOwnerWindowProc
+ *
+ * Description :  Dummy procedure that does nothing special.
+ *
+ * Parameters  :
+ *          1  :  hwnd = window handle
+ *          2  :  uMsg = message number
+ *          3  :  wParam = first param for this message
+ *          4  :  lParam = next param for this message
+ *
+ * Returns     :  Same as `DefWindowProc'.
+ *
+ *********************************************************************/
+LRESULT CALLBACK LogOwnerWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+   return DefWindowProc(hwnd, uMsg, wParam, lParam);
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  CreateLogWindow
+ *
+ * Description :  Create the logging window.
+ *
+ * Parameters  :
+ *          1  :  hInstance = application's instance handle
+ *          2  :  nCmdShow = window show value (MIN, MAX, NORMAL, etc...)
+ *
+ * Returns     :  Handle to newly created window.
+ *
+ *********************************************************************/
+HWND CreateLogWindow(HINSTANCE hInstance, int nCmdShow)
+{
+   static const char *szWndName = "PrivoxyLogWindow";
+   static const char *szWndTitle = "Privoxy";
+
+   HWND hwnd = NULL;
+   HWND hwndOwner = (g_bShowOnTaskBar) ? NULL : CreateHiddenLogOwnerWindow(hInstance);
+   RECT rcClient;
+   WNDCLASSEX wc;
+
+   memset(&wc, 0, sizeof(wc));
+   wc.cbSize         = sizeof(wc);
+   wc.style          = CS_DBLCLKS;
+   wc.lpfnWndProc    = LogWindowProc;
+   wc.cbClsExtra     = 0;
+   wc.cbWndExtra     = 0;
+   wc.hInstance      = hInstance;
+   wc.hIcon          = g_hiconApp;
+   wc.hCursor        = 0;
+   wc.hbrBackground  = 0;
+   wc.lpszMenuName   = MAKEINTRESOURCE(IDR_LOGVIEW);
+   wc.lpszClassName  = szWndName;
+   wc.hbrBackground  = GetStockObject(WHITE_BRUSH);
+   RegisterClassEx(&wc);
+
+   hwnd = CreateWindowEx(WS_EX_APPWINDOW, szWndName, szWndTitle,
+      WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
+      CW_USEDEFAULT, hwndOwner, NULL, hInstance, NULL);
+
+   /* Now create a child list box */
+   GetClientRect(hwnd, &rcClient);
+
+   /* Create a rich edit control */
+   InitRichEdit();
+   g_hwndLogBox = CreateWindowEx(0, (g_nRichEditVersion == 0x0100) ? "RichEdit" : RICHEDIT_CLASS, "",
+      ES_AUTOVSCROLL | ES_MULTILINE | ES_READONLY | ES_NOHIDESEL | WS_CHILD | WS_VSCROLL | WS_HSCROLL | WS_VISIBLE,
+      rcClient.left, rcClient.top, rcClient.right, rcClient.bottom,
+      hwnd, NULL, hInstance, NULL);
+/* SendMessage(g_hwndLogBox, EM_SETWORDWRAPMODE, 0, 0); */
+
+   /* Subclass the control to catch certain messages */
+   g_fnLogBox = (WNDPROC) GetWindowLong(g_hwndLogBox, GWL_WNDPROC);
+   SetWindowLong(g_hwndLogBox, GWL_WNDPROC, (LONG) LogRichEditProc);
+
+   /* Minimizing looks stupid when the log window is not on the task bar, so hide instead */
+   if (!g_bShowOnTaskBar &&
+         (nCmdShow == SW_SHOWMINIMIZED ||
+          nCmdShow == SW_MINIMIZE ||
+          nCmdShow == SW_SHOWMINNOACTIVE))
+   {
+      nCmdShow = SW_HIDE;
+   }
+
+   ShowWindow(hwnd, nCmdShow);
+   UpdateWindow(hwnd);
+
+   GetClientRect(g_hwndLogFrame, &rcClient);
+   SetWindowPos(g_hwndLogBox, NULL, rcClient.left, rcClient.top, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top, SWP_NOZORDER);
+
+   return hwnd;
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  InitRichEdit
+ *
+ * Description :  Initialise the rich edit control library.
+ *
+ * Parameters  :  None
+ *
+ * Returns     :  TRUE => success, FALSE => failure.
+ *                FIXME: this is backwards to the rest of IJB and to common
+ *                programming practice.  Please use 0 => success instead.
+ *
+ *********************************************************************/
+BOOL InitRichEdit(void)
+{
+   static HINSTANCE hInstRichEdit;
+   if (hInstRichEdit == NULL)
+   {
+      g_nRichEditVersion = 0;
+      hInstRichEdit = LoadLibraryA("RICHED20.DLL");
+      if (hInstRichEdit)
+      {
+         g_nRichEditVersion = _RICHEDIT_VER;
+      }
+      else
+      {
+         hInstRichEdit = LoadLibraryA("RICHED32.DLL");
+         if (hInstRichEdit)
+         {
+            g_nRichEditVersion = 0x0100;
+         }
+      }
+   }
+   return (hInstRichEdit != NULL) ? TRUE : FALSE;
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  ShowLogWindow
+ *
+ * Description :  Shows or hides the log window.  We will also raise the
+ *                window on a show command in case it is buried.
+ *
+ * Parameters  :
+ *          1  :  bShow = TRUE to show, FALSE to mimize/hide
+ *
+ * Returns     :  N/A
+ *
+ *********************************************************************/
+void ShowLogWindow(BOOL bShow)
+{
+   if (bShow)
+   {
+      SetForegroundWindow(g_hwndLogFrame);
+      SetWindowPos(g_hwndLogFrame, HWND_TOP, 0, 0, 0, 0, SWP_SHOWWINDOW | SWP_NOSIZE | SWP_NOMOVE);
+   }
+   else if (g_bShowOnTaskBar)
+   {
+      ShowWindow(g_hwndLogFrame, SW_MINIMIZE);
+   }
+   else
+   {
+      ShowWindow(g_hwndLogFrame, SW_HIDE);
+   }
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  EditFile
+ *
+ * Description :  Opens the specified setting file for editing.
+ * FIXME: What if the file has no associated application. Check for return values
+*        from ShellExecute??
+ *
+ * Parameters  :
+ *          1  :  filename = filename from the config (aka config.txt) file.
+ *
+ * Returns     :  N/A
+ *
+ *********************************************************************/
+void EditFile(const char *filename)
+{
+   if (filename)
+   {
+      ShellExecute(g_hwndLogFrame, "open", filename, NULL, NULL, SW_SHOWNORMAL);
+   }
+
+}
+
+
+/*--------------------------------------------------------------------------*/
+/* Windows message handlers                                                 */
+/*--------------------------------------------------------------------------*/
+
+
+/*********************************************************************
+ *
+ * Function    :  OnLogRButtonUp
+ *
+ * Description :  Handler for WM_RBUTTONUP messages.
+ *
+ * Parameters  :
+ *          1  :  nModifier = wParam from mouse message (unused)
+ *          2  :  x = x coordinate of the mouse event
+ *          3  :  y = y coordinate of the mouse event
+ *
+ * Returns     :  N/A
+ *
+ *********************************************************************/
+void OnLogRButtonUp(int nModifier, int x, int y)
+{
+   HMENU hMenu = LoadMenu(g_hInstance, MAKEINTRESOURCE(IDR_POPUP_SELECTION));
+   if (hMenu != NULL)
+   {
+      HMENU hMenuPopup = GetSubMenu(hMenu, 0);
+
+      /* Check if there is a selection */
+      CHARRANGE range;
+      SendMessage(g_hwndLogBox, EM_EXGETSEL, 0, (LPARAM) &range);
+      if (range.cpMin == range.cpMax)
+      {
+         EnableMenuItem(hMenuPopup, ID_EDIT_COPY, MF_BYCOMMAND | MF_GRAYED);
+      }
+      else
+      {
+         EnableMenuItem(hMenuPopup, ID_EDIT_COPY, MF_BYCOMMAND | MF_ENABLED);
+      }
+
+      /* Display the popup */
+      TrackPopupMenu(hMenuPopup, TPM_LEFTALIGN | TPM_TOPALIGN | TPM_RIGHTBUTTON, x, y, 0, g_hwndLogFrame, NULL);
+      DestroyMenu(hMenu);
+   }
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  OnLogCommand
+ *
+ * Description :  Handler for WM_COMMAND messages.
+ *
+ * Parameters  :
+ *          1  :  nCommand = the command portion of the menu selection event
+ *
+ * Returns     :  N/A
+ *
+ *********************************************************************/
+void OnLogCommand(int nCommand)
+{
+   switch (nCommand)
+   {
+      case ID_SHOWWINDOW:
+         ShowLogWindow(TRUE);
+         break;
+
+      case ID_FILE_EXIT:
+         PostMessage(g_hwndLogFrame, WM_CLOSE, 0, 0);
+         break;
+
+      case ID_EDIT_COPY:
+         SendMessage(g_hwndLogBox, WM_COPY, 0, 0);
+         break;
+
+      case ID_VIEW_CLEARLOG:
+         SendMessage(g_hwndLogBox, WM_SETTEXT, 0, (LPARAM) "");
+         break;
+
+      case ID_VIEW_LOGMESSAGES:
+         g_bLogMessages = !g_bLogMessages;
+         /* SaveLogSettings(); */
+         break;
+
+      case ID_VIEW_MESSAGEHIGHLIGHTING:
+         g_bHighlightMessages = !g_bHighlightMessages;
+         /* SaveLogSettings(); */
+         break;
+
+      case ID_VIEW_LIMITBUFFERSIZE:
+         g_bLimitBufferSize = !g_bLimitBufferSize;
+         /* SaveLogSettings(); */
+         break;
+
+      case ID_VIEW_ACTIVITYANIMATION:
+         g_bShowActivityAnimation = !g_bShowActivityAnimation;
+         /* SaveLogSettings(); */
+         break;
+
+#ifdef FEATURE_TOGGLE
+      /* by haroon - change toggle to its opposite value */
+      case ID_TOGGLE_ENABLED:
+         g_bToggleIJB = !g_bToggleIJB;
+         if (g_bToggleIJB)
+         {
+            log_error(LOG_LEVEL_INFO, "Now toggled ON.");
+         }
+         else
+         {
+            log_error(LOG_LEVEL_INFO, "Now toggled OFF.");
+         }
+         break;
+#endif /* def FEATURE_TOGGLE */
+
+      case ID_TOOLS_EDITCONFIG:
+         EditFile(configfile);
+         break;
+
+      case ID_TOOLS_EDITACTIONS:
+         EditFile(g_actions_file);
+         break;
+
+      case ID_TOOLS_EDITFILTERS:
+         EditFile(g_re_filterfile);
+         break;
+
+#ifdef FEATURE_TRUST
+      case ID_TOOLS_EDITTRUST:
+         EditFile(g_trustfile);
+         break;
+#endif /* def FEATURE_TRUST */
+
+      case ID_HELP_GPL:
+         ShellExecute(g_hwndLogFrame, "open", "LICENSE.txt", NULL, NULL, SW_SHOWNORMAL);
+         break;
+
+      case ID_HELP_FAQ:
+         ShellExecute(g_hwndLogFrame, "open", "doc\\faq\\index.html", NULL, NULL, SW_SHOWNORMAL);
+         break;
+
+      case ID_HELP_MANUAL:
+         ShellExecute(g_hwndLogFrame, "open", "doc\\user-manual\\index.html", NULL, NULL, SW_SHOWNORMAL);
+         break;
+
+      case ID_HELP_STATUS:
+         ShellExecute(g_hwndLogFrame, "open", CGI_PREFIX "show-status", NULL, NULL, SW_SHOWNORMAL);
+         break;
+
+      case ID_HELP_ABOUT:
+         MessageBox(g_hwndLogFrame, win32_blurb, "About Privoxy", MB_OK);
+         break;
+
+      default:
+         /* DO NOTHING */
+         break;
+   }
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  OnLogInitMenu
+ *
+ * Description :  Handler for WM_INITMENU messages.  Enable, disable,
+ *                check, and/or uncheck menu options as apropos.
+ *
+ * Parameters  :
+ *          1  :  hmenu = handle to menu to "make current"
+ *
+ * Returns     :  N/A
+ *
+ *********************************************************************/
+void OnLogInitMenu(HMENU hmenu)
+{
+   /* Only enable editors if there is a file to edit */
+   EnableMenuItem(hmenu, ID_TOOLS_EDITACTIONS, MF_BYCOMMAND | (g_actions_file ? MF_ENABLED : MF_GRAYED));
+   EnableMenuItem(hmenu, ID_TOOLS_EDITFILTERS, MF_BYCOMMAND | (g_re_filterfile ? MF_ENABLED : MF_GRAYED));
+#ifdef FEATURE_TRUST
+   EnableMenuItem(hmenu, ID_TOOLS_EDITTRUST, MF_BYCOMMAND | (g_trustfile ? MF_ENABLED : MF_GRAYED));
+#endif /* def FEATURE_TRUST */
+
+   /* Check/uncheck options */
+   CheckMenuItem(hmenu, ID_VIEW_LOGMESSAGES, MF_BYCOMMAND | (g_bLogMessages ? MF_CHECKED : MF_UNCHECKED));
+   CheckMenuItem(hmenu, ID_VIEW_MESSAGEHIGHLIGHTING, MF_BYCOMMAND | (g_bHighlightMessages ? MF_CHECKED : MF_UNCHECKED));
+   CheckMenuItem(hmenu, ID_VIEW_LIMITBUFFERSIZE, MF_BYCOMMAND | (g_bLimitBufferSize ? MF_CHECKED : MF_UNCHECKED));
+   CheckMenuItem(hmenu, ID_VIEW_ACTIVITYANIMATION, MF_BYCOMMAND | (g_bShowActivityAnimation ? MF_CHECKED : MF_UNCHECKED));
+#ifdef FEATURE_TOGGLE
+   /* by haroon - menu item for Enable toggle on/off */
+   CheckMenuItem(hmenu, ID_TOGGLE_ENABLED, MF_BYCOMMAND | (g_bToggleIJB ? MF_CHECKED : MF_UNCHECKED));
+#endif /* def FEATURE_TOGGLE */
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  OnLogTimer
+ *
+ * Description :  Handler for WM_TIMER messages.
+ *
+ * Parameters  :
+ *          1  :  nTimer = timer id (animation start/stop or clip buffer)
+ *
+ * Returns     :  N/A
+ *
+ *********************************************************************/
+void OnLogTimer(int nTimer)
+{
+   switch (nTimer)
+   {
+      case TIMER_ANIM_ID:
+         TraySetIcon(g_hwndTray, 1, g_hiconAnim[g_nAnimFrame++ % ANIM_FRAMES]);
+         break;
+
+      case TIMER_ANIMSTOP_ID:
+         g_nAnimFrame = 0;
+         TraySetIcon(g_hwndTray, 1, g_hiconIdle);
+         KillTimer(g_hwndLogFrame, TIMER_ANIM_ID);
+         KillTimer(g_hwndLogFrame, TIMER_ANIMSTOP_ID);
+         break;
+
+      case TIMER_CLIPBUFFER_ID:
+      case TIMER_CLIPBUFFER_FORCE_ID:
+         LogClipBuffer();
+         g_bClipPending = FALSE;
+         KillTimer(g_hwndLogFrame, TIMER_CLIPBUFFER_ID);
+         KillTimer(g_hwndLogFrame, TIMER_CLIPBUFFER_FORCE_ID);
+         break;
+
+      default:
+         /* DO NOTHING */
+         break;
+   }
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  LogRichEditProc
+ *
+ * Description :  Window subclass routine handles some events for the rich edit control.
+ *
+ * Parameters  :
+ *          1  :  hwnd = window handle of the rich edit control
+ *          2  :  uMsg = message number
+ *          3  :  wParam = first param for this message
+ *          4  :  lParam = next param for this message
+ *
+ * Returns     :  Appropriate M$ window message handler codes.
+ *
+ *********************************************************************/
+LRESULT CALLBACK LogRichEditProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+   switch (uMsg)
+   {
+      case WM_RBUTTONUP:
+      {
+         POINT pt;
+         pt.x = LOWORD(lParam);
+         pt.y = HIWORD(lParam);
+         ClientToScreen(hwnd, &pt);
+         OnLogRButtonUp(wParam, pt.x, pt.y);
+      }
+      return 0;
+   }
+   return CallWindowProc(g_fnLogBox, hwnd, uMsg, wParam, lParam);
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  LogWindowProc
+ *
+ * Description :  Windows call back routine handles events on the log window.
+ *
+ * Parameters  :
+ *          1  :  hwnd = handle of the logging window
+ *          2  :  uMsg = message number
+ *          3  :  wParam = first param for this message
+ *          4  :  lParam = next param for this message
+ *
+ * Returns     :  Appropriate M$ window message handler codes.
+ *
+ *********************************************************************/
+LRESULT CALLBACK LogWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+   switch (uMsg)
+   {
+      case WM_CREATE:
+         return 0;
+
+      case WM_CLOSE:
+         /* This is the end - beautiful friend - the end */
+         DestroyWindow(g_hwndLogBox);
+         DestroyWindow(g_hwndLogFrame);
+         return 0;
+
+      case WM_DESTROY:
+         PostQuitMessage(0);
+         return 0;
+
+      case WM_SHOWWINDOW:
+      case WM_SIZE:
+         /* Resize the logging window to fit the new frame */
+         if (g_hwndLogBox)
+         {
+            RECT rc;
+            GetClientRect(g_hwndLogFrame, &rc);
+            SetWindowPos(g_hwndLogBox, NULL, rc.left, rc.top, rc.right - rc.left, rc.bottom - rc.top, SWP_NOZORDER);
+         }
+         return 0;
+
+      case WM_INITMENU:
+         OnLogInitMenu((HMENU) wParam);
+         return 0;
+
+      case WM_TIMER:
+         OnLogTimer(wParam);
+         return 0;
+
+      case WM_COMMAND:
+         OnLogCommand(LOWORD(wParam));
+         return 0;
+
+      case WM_SYSCOMMAND:
+         switch (wParam)
+         {
+            case SC_CLOSE:
+               if (g_bCloseHidesWindow)
+               {
+                  ShowLogWindow(FALSE);
+                  return 0;
+               }
+               break;
+            case SC_MINIMIZE:
+               ShowLogWindow(FALSE);
+               return 0;
+         }
+         break;
+   }
+
+   return DefWindowProc(hwnd, uMsg, wParam, lParam);
+
+}
+
+#endif /* ndef _WIN_CONSOLE - entire file */
+
+/*
+  Local Variables:
+  tab-width: 3
+  end:
+*/
diff --git a/src/w32log.h b/src/w32log.h
new file mode 100644 (file)
index 0000000..dd7bc41
--- /dev/null
@@ -0,0 +1,164 @@
+#ifndef W32LOG_H_INCLUDED
+#define W32LOG_H_INCLUDED
+#define W32LOG_H_VERSION "$Id: w32log.h,v 1.10 2002/03/26 22:57:10 jongfoster Exp $"
+/*********************************************************************
+ *
+ * File        :  $Source: /cvsroot/ijbswa/current/w32log.h,v $
+ *
+ * Purpose     :  Functions for creating and destroying the log window,
+ *                ouputting strings, processing messages and so on.
+ *
+ * Copyright   :  Written by and Copyright (C) 2001-2002 members of
+ *                the Privoxy team.  http://www.privoxy.org/
+ *
+ *                Written by and Copyright (C) 1999 Adam Lock
+ *                <locka@iol.ie>
+ *
+ *                This program is free software; you can redistribute it 
+ *                and/or modify it under the terms of the GNU General
+ *                Public License as published by the Free Software
+ *                Foundation; either version 2 of the License, or (at
+ *                your option) any later version.
+ *
+ *                This program is distributed in the hope that it will
+ *                be useful, but WITHOUT ANY WARRANTY; without even the
+ *                implied warranty of MERCHANTABILITY or FITNESS FOR A
+ *                PARTICULAR PURPOSE.  See the GNU General Public
+ *                License for more details.
+ *
+ *                The GNU General Public License should be included with
+ *                this file.  If not, you can view it at
+ *                http://www.gnu.org/copyleft/gpl.html
+ *                or write to the Free Software Foundation, Inc., 59
+ *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * Revisions   :
+ *    $Log: w32log.h,v $
+ *    Revision 1.10  2002/03/26 22:57:10  jongfoster
+ *    Web server name should begin www.
+ *
+ *    Revision 1.9  2002/03/24 12:03:47  jongfoster
+ *    Name change
+ *
+ *    Revision 1.8  2001/07/30 22:08:36  jongfoster
+ *    Tidying up #defines:
+ *    - All feature #defines are now of the form FEATURE_xxx
+ *    - Permanently turned off WIN_GUI_EDIT
+ *    - Permanently turned on WEBDAV and SPLIT_PROXY_ARGS
+ *
+ *    Revision 1.7  2001/07/29 18:43:08  jongfoster
+ *    Changing #ifdef _FILENAME_H to FILENAME_H_INCLUDED, to conform to
+ *    ANSI C rules.
+ *
+ *    Revision 1.6  2001/07/13 14:04:59  oes
+ *    Removed all #ifdef PCRS
+ *
+ *    Revision 1.5  2001/06/07 23:08:12  jongfoster
+ *    Forward and ACL edit options removed.
+ *
+ *    Revision 1.4  2001/05/31 21:37:11  jongfoster
+ *    GUI changes to rename "permissions file" to "actions file".
+ *
+ *    Revision 1.3  2001/05/29 09:50:24  jongfoster
+ *    Unified blocklist/imagelist/permissionslist.
+ *    File format is still under discussion, but the internal changes
+ *    are (mostly) done.
+ *
+ *    Also modified interceptor behaviour:
+ *    - We now intercept all URLs beginning with one of the following
+ *      prefixes (and *only* these prefixes):
+ *        * http://i.j.b/
+ *        * http://ijbswa.sf.net/config/
+ *        * http://ijbswa.sourceforge.net/config/
+ *    - New interceptors "home page" - go to http://i.j.b/ to see it.
+ *    - Internal changes so that intercepted and fast redirect pages
+ *      are not replaced with an image.
+ *    - Interceptors now have the option to send a binary page direct
+ *      to the client. (i.e. ijb-send-banner uses this)
+ *    - Implemented show-url-info interceptor.  (Which is why I needed
+ *      the above interceptors changes - a typical URL is
+ *      "http://i.j.b/show-url-info?url=www.somesite.com/banner.gif".
+ *      The previous mechanism would not have intercepted that, and
+ *      if it had been intercepted then it then it would have replaced
+ *      it with an image.)
+ *
+ *    Revision 1.2  2001/05/26 00:28:36  jongfoster
+ *    Automatic reloading of config file.
+ *    Removed obsolete SIGHUP support (Unix) and Reload menu option (Win32).
+ *    Most of the global variables have been moved to a new
+ *    struct configuration_spec, accessed through csp->config->globalname
+ *    Most of the globals remaining are used by the Win32 GUI.
+ *
+ *    Revision 1.1.1.1  2001/05/15 13:59:07  oes
+ *    Initial import of version 2.9.3 source tree
+ *
+ *
+ *********************************************************************/
+\f
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern HWND g_hwndLogFrame;
+
+/* Indicates whether task bar shows activity animation */
+extern BOOL g_bShowActivityAnimation;
+
+/* Indicates if the log window appears on the task bar */
+extern BOOL g_bShowOnTaskBar;
+
+/* Indicates whether closing the log window really just hides it */
+extern BOOL g_bCloseHidesWindow;
+
+/* Indicates if messages are logged at all */
+extern BOOL g_bLogMessages;
+
+/* Indicates whether log messages are highlighted */
+extern BOOL g_bHighlightMessages;
+
+/* Indicates if buffer is limited in size */
+extern BOOL g_bLimitBufferSize;
+
+/* Maximum number of lines allowed in buffer when limited */
+extern int g_nMaxBufferLines;
+
+/* Font to use */
+extern char g_szFontFaceName[255];
+
+/* Size of font to use */
+extern int g_nFontSize;
+
+
+/* FIXME: this is a kludge */
+
+extern const char * g_actions_file;
+extern const char * g_re_filterfile;
+#ifdef FEATURE_TRUST
+extern const char * g_trustfile;
+#endif /* def FEATURE_TRUST */
+
+/* FIXME: end kludge */
+
+extern int LogPutString(const char *pszText);
+extern BOOL InitLogWindow(void);
+extern void TermLogWindow(void);
+extern void ShowLogWindow(BOOL bShow);
+extern void LogShowActivity(void);
+
+/* Revision control strings from this header and associated .c file */
+extern const char w32log_rcs[];
+extern const char w32log_h_rcs[];
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* ndef W32LOG_H_INCLUDED */
+
+
+/*
+  Local Variables:
+  tab-width: 3
+  end:
+*/
diff --git a/src/w32res.h b/src/w32res.h
new file mode 100644 (file)
index 0000000..5302580
--- /dev/null
@@ -0,0 +1,177 @@
+#ifndef W32RES_H_INCLUDED
+#define W32RES_H_INCLUDED
+#define W32RES_H_VERSION "$Id: w32res.h,v 1.13 2002/03/26 22:57:10 jongfoster Exp $"
+/*********************************************************************
+ *
+ * File        :  $Source: /cvsroot/ijbswa/current/w32res.h,v $
+ *
+ * Purpose     :  Identifiers for Windows GUI resources.
+ *
+ * Copyright   :  Written by and Copyright (C) 2001-2002 members of
+ *                the Privoxy team.  http://www.privoxy.org/
+ *
+ *                Based on the Internet Junkbuster originally written
+ *                by and Copyright (C) 1997 Anonymous Coders and 
+ *                Junkbusters Corporation.  http://www.junkbusters.com
+ *
+ *                This program is free software; you can redistribute it 
+ *                and/or modify it under the terms of the GNU General
+ *                Public License as published by the Free Software
+ *                Foundation; either version 2 of the License, or (at
+ *                your option) any later version.
+ *
+ *                This program is distributed in the hope that it will
+ *                be useful, but WITHOUT ANY WARRANTY; without even the
+ *                implied warranty of MERCHANTABILITY or FITNESS FOR A
+ *                PARTICULAR PURPOSE.  See the GNU General Public
+ *                License for more details.
+ *
+ *                The GNU General Public License should be included with
+ *                this file.  If not, you can view it at
+ *                http://www.gnu.org/copyleft/gpl.html
+ *                or write to the Free Software Foundation, Inc., 59
+ *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * Revisions   :
+ *    $Log: w32res.h,v $
+ *    Revision 1.13  2002/03/26 22:57:10  jongfoster
+ *    Web server name should begin www.
+ *
+ *    Revision 1.12  2002/03/24 12:07:36  jongfoster
+ *    Consistern name for filters file
+ *
+ *    Revision 1.11  2002/03/24 12:03:47  jongfoster
+ *    Name change
+ *
+ *    Revision 1.10  2001/07/30 22:08:36  jongfoster
+ *    Tidying up #defines:
+ *    - All feature #defines are now of the form FEATURE_xxx
+ *    - Permanently turned off WIN_GUI_EDIT
+ *    - Permanently turned on WEBDAV and SPLIT_PROXY_ARGS
+ *
+ *    Revision 1.9  2001/07/29 18:43:08  jongfoster
+ *    Changing #ifdef _FILENAME_H to FILENAME_H_INCLUDED, to conform to
+ *    ANSI C rules.
+ *
+ *    Revision 1.8  2001/07/13 14:04:59  oes
+ *    Removed all #ifdef PCRS
+ *
+ *    Revision 1.7  2001/06/07 23:08:12  jongfoster
+ *    Forward and ACL edit options removed.
+ *
+ *    Revision 1.6  2001/05/31 21:37:11  jongfoster
+ *    GUI changes to rename "permissions file" to "actions file".
+ *
+ *    Revision 1.5  2001/05/29 09:50:24  jongfoster
+ *    Unified blocklist/imagelist/permissionslist.
+ *    File format is still under discussion, but the internal changes
+ *    are (mostly) done.
+ *
+ *    Also modified interceptor behaviour:
+ *    - We now intercept all URLs beginning with one of the following
+ *      prefixes (and *only* these prefixes):
+ *        * http://i.j.b/
+ *        * http://ijbswa.sf.net/config/
+ *        * http://ijbswa.sourceforge.net/config/
+ *    - New interceptors "home page" - go to http://i.j.b/ to see it.
+ *    - Internal changes so that intercepted and fast redirect pages
+ *      are not replaced with an image.
+ *    - Interceptors now have the option to send a binary page direct
+ *      to the client. (i.e. ijb-send-banner uses this)
+ *    - Implemented show-url-info interceptor.  (Which is why I needed
+ *      the above interceptors changes - a typical URL is
+ *      "http://i.j.b/show-url-info?url=www.somesite.com/banner.gif".
+ *      The previous mechanism would not have intercepted that, and
+ *      if it had been intercepted then it then it would have replaced
+ *      it with an image.)
+ *
+ *    Revision 1.4  2001/05/26 01:26:34  jongfoster
+ *    New #define, WIN_GUI_EDIT, enables the (embryonic) Win32 GUI editor.
+ *    This #define cannot be set from ./configure - there's no point, it
+ *    doesn't work yet.  See feature request # 425722
+ *
+ *    Revision 1.3  2001/05/26 00:28:36  jongfoster
+ *    Automatic reloading of config file.
+ *    Removed obsolete SIGHUP support (Unix) and Reload menu option (Win32).
+ *    Most of the global variables have been moved to a new
+ *    struct configuration_spec, accessed through csp->config->globalname
+ *    Most of the globals remaining are used by the Win32 GUI.
+ *
+ *    Revision 1.2  2001/05/20 01:21:20  jongfoster
+ *    Version 2.9.4 checkin.
+ *    - Merged popupfile and cookiefile, and added control over PCRS
+ *      filtering, in new "permissionsfile".
+ *    - Implemented LOG_LEVEL_FATAL, so that if there is a configuration
+ *      file error you now get a message box (in the Win32 GUI) rather
+ *      than the program exiting with no explanation.
+ *    - Made killpopup use the PCRS MIME-type checking and HTTP-header
+ *      skipping.
+ *    - Removed tabs from "config"
+ *    - Moved duplicated url parsing code in "loaders.c" to a new funcition.
+ *    - Bumped up version number.
+ *
+ *    Revision 1.1.1.1  2001/05/15 13:59:08  oes
+ *    Initial import of version 2.9.3 source tree
+ *
+ *
+ *********************************************************************/
+
+#define IDR_TRAYMENU                      101
+#define IDI_IDLE                          102
+#define IDR_LOGVIEW                       103
+#define IDR_ACCELERATOR                   104
+#define IDR_POPUP_SELECTION               105
+
+
+#define IDI_MAINICON                      200
+#define IDI_ANIMATED1                     201
+#define IDI_ANIMATED2                     202
+#define IDI_ANIMATED3                     203
+#define IDI_ANIMATED4                     204
+#define IDI_ANIMATED5                     205
+#define IDI_ANIMATED6                     206
+#define IDI_ANIMATED7                     207
+#define IDI_ANIMATED8                     208
+
+#define ID_SHOWWINDOW                     4000
+#define ID_HELP_ABOUT                     4001
+#define ID_FILE_EXIT                      4002
+#define ID_VIEW_CLEARLOG                  4003
+#define ID_VIEW_LOGMESSAGES               4004
+#define ID_VIEW_MESSAGEHIGHLIGHTING       4005
+#define ID_VIEW_LIMITBUFFERSIZE           4006
+#define ID_VIEW_ACTIVITYANIMATION         4007
+#define ID_HELP_FAQ                       4008
+#define ID_HELP_MANUAL                    4009
+#define ID_HELP_GPL                       4010
+#define ID_HELP_STATUS                    4011
+#ifdef FEATURE_TOGGLE
+#define ID_TOGGLE_ENABLED                 4012
+#endif /* def FEATURE_TOGGLE */
+
+/* Break these out so they are easier to extend, but keep consecutive */
+#define ID_TOOLS_EDITCONFIG               5000
+#define ID_TOOLS_EDITACTIONS              5001
+#define ID_TOOLS_EDITFILTERS              5002
+
+#ifdef FEATURE_TRUST
+#define ID_TOOLS_EDITTRUST                5003
+#endif /* def FEATURE_TRUST */
+
+/*
+ * The following symbols are declared in <afxres.h> in VC++.
+ * However, mingw32 doesn't have that header.  Let's 
+ * always declare them here, for consistency.
+ * These are the VC++ values.
+ */
+#define IDC_STATIC      (-1)
+#define ID_EDIT_COPY  30000
+
+
+#endif /* ndef W32RES_H_INCLUDED */
+
+/*
+  Local Variables:
+  tab-width: 3
+  end:
+*/
diff --git a/src/w32taskbar.c b/src/w32taskbar.c
new file mode 100644 (file)
index 0000000..2d45209
--- /dev/null
@@ -0,0 +1,291 @@
+const char w32taskbar_rcs[] = "$Id: w32taskbar.c,v 1.7 2002/03/31 17:19:00 jongfoster Exp $";
+/*********************************************************************
+ *
+ * File        :  $Source: /cvsroot/ijbswa/current/w32taskbar.c,v $
+ *
+ * Purpose     :  Functions for creating, setting and destroying the
+ *                workspace tray icon
+ *
+ * Copyright   :  Written by and Copyright (C) 2001-2002 members of
+ *                the Privoxy team.  http://www.privoxy.org/
+ *
+ *                Written by and Copyright (C) 1999 Adam Lock
+ *                <locka@iol.ie>
+ *
+ *                This program is free software; you can redistribute it 
+ *                and/or modify it under the terms of the GNU General
+ *                Public License as published by the Free Software
+ *                Foundation; either version 2 of the License, or (at
+ *                your option) any later version.
+ *
+ *                This program is distributed in the hope that it will
+ *                be useful, but WITHOUT ANY WARRANTY; without even the
+ *                implied warranty of MERCHANTABILITY or FITNESS FOR A
+ *                PARTICULAR PURPOSE.  See the GNU General Public
+ *                License for more details.
+ *
+ *                The GNU General Public License should be included with
+ *                this file.  If not, you can view it at
+ *                http://www.gnu.org/copyleft/gpl.html
+ *                or write to the Free Software Foundation, Inc., 59
+ *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * Revisions   :
+ *    $Log: w32taskbar.c,v $
+ *    Revision 1.7  2002/03/31 17:19:00  jongfoster
+ *    Win32 only: Enabling STRICT to fix a VC++ compile warning.
+ *
+ *    Revision 1.6  2002/03/26 22:57:10  jongfoster
+ *    Web server name should begin www.
+ *
+ *    Revision 1.5  2002/03/24 12:03:47  jongfoster
+ *    Name change
+ *
+ *    Revision 1.4  2001/11/16 00:46:31  jongfoster
+ *    Fixing compiler warnings
+ *
+ *    Revision 1.3  2001/05/22 18:56:28  oes
+ *    CRLF -> LF
+ *
+ *    Revision 1.2  2001/05/20 15:07:54  jongfoster
+ *    File is now ignored if _WIN_CONSOLE is defined.
+ *
+ *    Revision 1.1.1.1  2001/05/15 13:59:08  oes
+ *    Initial import of version 2.9.3 source tree
+ *
+ *
+ *********************************************************************/
+\f
+
+#include "config.h"
+
+#include <stdio.h>
+
+#ifndef STRICT
+#define STRICT
+#endif
+#include <windows.h>
+
+#include "w32taskbar.h"
+#include "w32res.h"
+#include "w32log.h"
+
+const char w32taskbar_h_rcs[] = W32TASKBAR_H_VERSION;
+
+#ifndef _WIN_CONSOLE /* entire file */
+
+#define WM_TRAYMSG WM_USER+1
+
+static HMENU g_hmenuTray;
+static HWND g_hwndTrayX;
+
+static LRESULT CALLBACK TrayProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
+
+
+/*********************************************************************
+ *
+ * Function    :  CreateTrayWindow
+ *
+ * Description :  Creates and returns the invisible window responsible
+ *                for processing tray messages.
+ *
+ * Parameters  :
+ *          1  :  hInstance = instance handle of this application
+ *
+ * Returns     :  Handle of the systray window.
+ *
+ *********************************************************************/
+HWND CreateTrayWindow(HINSTANCE hInstance)
+{
+   WNDCLASS wc;
+   static const char *szWndName = "PrivoxyTrayWindow";
+
+   wc.style          = 0;
+   wc.lpfnWndProc    = TrayProc;
+   wc.cbClsExtra     = 0;
+   wc.cbWndExtra     = 0;
+   wc.hInstance      = hInstance;
+   wc.hIcon          = 0;
+   wc.hCursor        = 0;
+   wc.hbrBackground  = 0;
+   wc.lpszMenuName   = 0;
+   wc.lpszClassName  = szWndName;
+
+   RegisterClass(&wc);
+
+   g_hwndTrayX = CreateWindow(szWndName, szWndName,
+      WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
+      CW_USEDEFAULT, NULL, NULL, hInstance, NULL );
+
+   ShowWindow(g_hwndTrayX, SW_HIDE);
+   UpdateWindow(g_hwndTrayX);
+
+   g_hmenuTray = LoadMenu(hInstance, MAKEINTRESOURCE(IDR_TRAYMENU));
+
+   return g_hwndTrayX;
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  TraySetIcon
+ *
+ * Description :  Sets the tray icon to the specified shape.
+ *
+ * Parameters  :
+ *          1  :  hwnd = handle of the systray window
+ *          2  :  uID = user message number to notify systray window
+ *          3  :  hicon = set the current icon to this handle
+ *
+ * Returns     :  Same value as `Shell_NotifyIcon'.
+ *
+ *********************************************************************/
+BOOL TraySetIcon(HWND hwnd, UINT uID, HICON hicon)
+{
+   NOTIFYICONDATA nid;
+
+   memset(&nid, 0, sizeof(nid));
+
+   nid.cbSize = sizeof(nid);
+   nid.hWnd = hwnd;
+   nid.uID = uID;
+   nid.uFlags = NIF_ICON;
+   nid.uCallbackMessage = 0;
+   nid.hIcon = hicon;
+
+   return( Shell_NotifyIcon(NIM_MODIFY, &nid) );
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  TrayAddIcon
+ *
+ * Description :  Adds a tray icon.
+ *
+ * Parameters  :
+ *          1  :  hwnd = handle of the systray window
+ *          2  :  uID = user message number to notify systray window
+ *          3  :  hicon = handle of icon to add to systray window
+ *          4  :  pszToolTip = tool tip when mouse hovers over systray window
+ *
+ * Returns     :  Same as `Shell_NotifyIcon'.
+ *
+ *********************************************************************/
+BOOL TrayAddIcon(HWND hwnd, UINT uID, HICON hicon, const char *pszToolTip)
+{
+   NOTIFYICONDATA nid;
+
+   memset(&nid, 0, sizeof(nid));
+
+   nid.cbSize = sizeof(nid);
+   nid.hWnd = hwnd;
+   nid.uID = uID;
+   nid.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP;
+   nid.uCallbackMessage = WM_TRAYMSG;
+   nid.hIcon = hicon;
+
+   if (pszToolTip)
+   {
+      strcpy(nid.szTip, pszToolTip);
+   }
+
+   return( Shell_NotifyIcon(NIM_ADD, &nid) );
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  TrayDeleteIcon
+ *
+ * Description :  Deletes a tray icon.
+ *
+ * Parameters  :
+ *          1  :  hwnd = handle of the systray window
+ *          2  :  uID = user message number to notify systray window
+ *
+ * Returns     :  Same as `Shell_NotifyIcon'.
+ *
+ *********************************************************************/
+BOOL TrayDeleteIcon(HWND hwnd, UINT uID)
+{
+   NOTIFYICONDATA nid;
+
+   memset(&nid, 0, sizeof(nid));
+
+   nid.cbSize = sizeof(nid);
+   nid.hWnd = hwnd;
+   nid.uID = uID;
+
+   return( Shell_NotifyIcon(NIM_DELETE, &nid) );
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  TrayProc
+ *
+ * Description :  Call back procedure processes tray messages.
+ *
+ * Parameters  :
+ *          1  :  hwnd = handle of the systray window
+ *          2  :  msg = message number
+ *          3  :  wParam = first param for this message
+ *          4  :  lParam = next param for this message
+ *
+ * Returns     :  Appropriate M$ window message handler codes.
+ *
+ *********************************************************************/
+LRESULT CALLBACK TrayProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+   switch (msg)
+   {
+      case WM_CREATE:
+         return 0;
+
+      case WM_CLOSE:
+         PostQuitMessage(0);
+         return 0;
+
+      case WM_TRAYMSG:
+      {
+         /* UINT uID = (UINT) wParam; */
+         UINT uMouseMsg = (UINT) lParam;
+
+         if (uMouseMsg == WM_RBUTTONDOWN)
+         {
+            POINT pt;
+            HMENU hmenu = GetSubMenu(g_hmenuTray,0);
+            GetCursorPos(&pt);
+            SetForegroundWindow(g_hwndLogFrame);
+            TrackPopupMenu(hmenu, TPM_LEFTALIGN | TPM_TOPALIGN, pt.x, pt.y, 0, g_hwndLogFrame, NULL);
+            PostMessage(g_hwndLogFrame, WM_NULL, 0, 0 ) ;
+         }
+         else if (uMouseMsg == WM_LBUTTONDBLCLK)
+         {
+            ShowLogWindow(TRUE);
+         }
+      }
+      return 0;
+
+      default:
+         /* DO NOTHING */
+         break;
+   }
+
+   return DefWindowProc(hwnd, msg, wParam, lParam);
+
+}
+
+
+#endif /* ndef _WIN_CONSOLE - entire file */
+
+/*
+  Local Variables:
+  tab-width: 3
+  end:
+*/
diff --git a/src/w32taskbar.h b/src/w32taskbar.h
new file mode 100644 (file)
index 0000000..1ccc75c
--- /dev/null
@@ -0,0 +1,78 @@
+#ifndef W32TASKBAR_H_INCLUDED
+#define W32TASKBAR_H_INCLUDED
+#define W32TASKBAR_H_VERSION "$Id: w32taskbar.h,v 1.4 2002/03/26 22:57:10 jongfoster Exp $"
+/*********************************************************************
+ *
+ * File        :  $Source: /cvsroot/ijbswa/current/w32taskbar.h,v $
+ *
+ * Purpose     :  Functions for creating, setting and destroying the
+ *                workspace tray icon
+ *
+ * Copyright   :  Written by and Copyright (C) 2001-2002 members of
+ *                the Privoxy team.  http://www.privoxy.org/
+ *
+ *                Written by and Copyright (C) 1999 Adam Lock
+ *                <locka@iol.ie>
+ *
+ *                This program is free software; you can redistribute it 
+ *                and/or modify it under the terms of the GNU General
+ *                Public License as published by the Free Software
+ *                Foundation; either version 2 of the License, or (at
+ *                your option) any later version.
+ *
+ *                This program is distributed in the hope that it will
+ *                be useful, but WITHOUT ANY WARRANTY; without even the
+ *                implied warranty of MERCHANTABILITY or FITNESS FOR A
+ *                PARTICULAR PURPOSE.  See the GNU General Public
+ *                License for more details.
+ *
+ *                The GNU General Public License should be included with
+ *                this file.  If not, you can view it at
+ *                http://www.gnu.org/copyleft/gpl.html
+ *                or write to the Free Software Foundation, Inc., 59
+ *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * Revisions   :
+ *    $Log: w32taskbar.h,v $
+ *    Revision 1.4  2002/03/26 22:57:10  jongfoster
+ *    Web server name should begin www.
+ *
+ *    Revision 1.3  2002/03/24 12:03:47  jongfoster
+ *    Name change
+ *
+ *    Revision 1.2  2001/07/29 18:43:08  jongfoster
+ *    Changing #ifdef _FILENAME_H to FILENAME_H_INCLUDED, to conform to
+ *    ANSI C rules.
+ *
+ *    Revision 1.1.1.1  2001/05/15 13:59:08  oes
+ *    Initial import of version 2.9.3 source tree
+ *
+ *
+ *********************************************************************/
+\f
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern HWND CreateTrayWindow(HINSTANCE hInstance);
+extern BOOL TrayAddIcon(HWND hwnd, UINT uID, HICON hicon, const char *pszToolTip);
+extern BOOL TraySetIcon(HWND hwnd, UINT uID, HICON hicon);
+extern BOOL TrayDeleteIcon(HWND hwnd, UINT uID);
+
+/* Revision control strings from this header and associated .c file */
+extern const char w32taskbar_rcs[];
+extern const char w32taskbar_h_rcs[];
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* ndef W32TASKBAR_H_INCLUDED */
+
+
+/*
+  Local Variables:
+  tab-width: 3
+  end:
+*/
diff --git a/src/win32.c b/src/win32.c
new file mode 100644 (file)
index 0000000..41dea70
--- /dev/null
@@ -0,0 +1,314 @@
+const char win32_rcs[] = "$Id: win32.c,v 1.9 2002/03/31 17:19:00 jongfoster Exp $";
+/*********************************************************************
+ *
+ * File        :  $Source: /cvsroot/ijbswa/current/win32.c,v $
+ *
+ * Purpose     :  Win32 User Interface initialization and message loop
+ *
+ * Copyright   :  Written by and Copyright (C) 2001-2002 members of
+ *                the Privoxy team.  http://www.privoxy.org/
+ *
+ *                Written by and Copyright (C) 1999 Adam Lock
+ *                <locka@iol.ie>
+ *
+ *                This program is free software; you can redistribute it 
+ *                and/or modify it under the terms of the GNU General
+ *                Public License as published by the Free Software
+ *                Foundation; either version 2 of the License, or (at
+ *                your option) any later version.
+ *
+ *                This program is distributed in the hope that it will
+ *                be useful, but WITHOUT ANY WARRANTY; without even the
+ *                implied warranty of MERCHANTABILITY or FITNESS FOR A
+ *                PARTICULAR PURPOSE.  See the GNU General Public
+ *                License for more details.
+ *
+ *                The GNU General Public License should be included with
+ *                this file.  If not, you can view it at
+ *                http://www.gnu.org/copyleft/gpl.html
+ *                or write to the Free Software Foundation, Inc., 59
+ *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * Revisions   :
+ *    $Log: win32.c,v $
+ *    Revision 1.9  2002/03/31 17:19:00  jongfoster
+ *    Win32 only: Enabling STRICT to fix a VC++ compile warning.
+ *
+ *    Revision 1.8  2002/03/26 22:57:10  jongfoster
+ *    Web server name should begin www.
+ *
+ *    Revision 1.7  2002/03/24 12:03:47  jongfoster
+ *    Name change
+ *
+ *    Revision 1.6  2002/03/16 21:53:28  jongfoster
+ *    VC++ Heap debug option
+ *
+ *    Revision 1.5  2002/03/04 23:47:30  jongfoster
+ *    - Rewritten, simpler command-line pre-parser
+ *    - not using raise(SIGINT) any more
+ *
+ *    Revision 1.4  2001/11/30 21:29:33  jongfoster
+ *    Fixing a warning
+ *
+ *    Revision 1.3  2001/11/16 00:46:31  jongfoster
+ *    Fixing compiler warnings
+ *
+ *    Revision 1.2  2001/07/29 19:32:00  jongfoster
+ *    Renaming _main() [mingw32 only] to real_main(), for ANSI compliance.
+ *
+ *    Revision 1.1.1.1  2001/05/15 13:59:08  oes
+ *    Initial import of version 2.9.3 source tree
+ *
+ *
+ *********************************************************************/
+\f
+
+#include "config.h"
+
+#ifdef _WIN32
+
+#include <stdio.h>
+
+#include "project.h"
+#include "jcc.h"
+#include "miscutil.h"
+
+/* Uncomment this if you want to build Win32 as a console app */
+/* #define _WIN_CONSOLE */
+
+#ifndef STRICT
+#define STRICT
+#endif
+#include <windows.h>
+
+#include <stdarg.h>
+#include <process.h>
+
+#if defined(_WIN32) && defined(_MSC_VER) && defined(_DEBUG)
+/* Visual C++ Heap debugging */
+#include <crtdbg.h>
+#endif /* defined(_WIN32) && defined(_MSC_VER) && defined(_DEBUG) */
+
+#include "win32.h"
+
+const char win32_h_rcs[] = WIN32_H_VERSION;
+
+const char win32_blurb[] =
+"Privoxy version " VERSION " for Windows\n"
+"Copyright (C) 2000-2002 by members of the Privoxy Team\n"
+"Copyright (C) 1997-8 by Junkbusters Corp.\n"
+"This is free software; it may be used and copied under the\n"
+"GNU General Public License: http://www.gnu.org/copyleft/gpl.html .\n"
+"This program comes with ABSOLUTELY NO WARRANTY OF ANY KIND.\n"
+"\n"
+"For information about how to to configure the proxy and your browser, see\n"
+"        " HOME_PAGE_URL "\n"
+"\n";
+
+#ifdef _WIN_CONSOLE
+
+int hideConsole     = 0;
+
+#else
+
+HINSTANCE g_hInstance;
+int g_nCmdShow;
+
+static void  __cdecl UserInterfaceThread(void *);
+
+#endif
+
+
+/*********************************************************************
+ *
+ * Function    :  WinMain
+ *
+ * Description :  M$ Windows "main" routine:
+ *                parse the `lpCmdLine' param into main's argc and argv variables,
+ *                start the user interface thread (for the systray window), and
+ *                call main (i.e. patch execution into normal startup).
+ *
+ * Parameters  :
+ *          1  :  hInstance = instance handle of this execution
+ *          2  :  hPrevInstance = instance handle of previous execution
+ *          3  :  lpCmdLine = command line string which started us
+ *          4  :  nCmdShow = window show value (MIN, MAX, NORMAL, etc...)
+ *
+ * Returns     :  `main' never returns, so WinMain will also never return.
+ *
+ *********************************************************************/
+int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
+{
+   int i;
+   int res;
+   int argc = 1;
+   const char *argv[3];
+   char szModule[MAX_PATH+1];
+#ifndef _WIN_CONSOLE
+   HANDLE hInitCompleteEvent = NULL;
+#endif
+
+
+#if defined(_WIN32) && defined(_MSC_VER) && defined(_DEBUG)
+#if 0
+   /* Visual C++ Heap debugging */
+
+   /* Get current flag*/
+   int tmpFlag = _CrtSetDbgFlag( _CRTDBG_REPORT_FLAG );
+
+   /* Turn on leak-checking bit */
+   tmpFlag |= _CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF | _CRTDBG_CHECK_ALWAYS_DF;
+
+   /* Turn off CRT block checking bit */
+   tmpFlag &= ~(_CRTDBG_CHECK_CRT_DF | _CRTDBG_DELAY_FREE_MEM_DF);
+
+   /* Set flag to the new value */
+   _CrtSetDbgFlag( tmpFlag );
+#endif
+#endif /* defined(_WIN32) && defined(_MSC_VER) && defined(_DEBUG) */
+
+   /*
+    * Cheat in parsing the command line.  We only ever have at most one
+    * paramater, which may optionally be specified inside double quotes.
+    */
+
+   if (lpCmdLine != NULL)
+   {
+      /* Make writable copy */
+      lpCmdLine = strdup(lpCmdLine);
+   }
+   if (lpCmdLine != NULL)
+   {
+      chomp(lpCmdLine);
+      i = strlen(lpCmdLine);
+      if ((i >= 2) && (lpCmdLine[0] == '\"') && (lpCmdLine[i - 1] == '\"'))
+      {
+         lpCmdLine[i - 1] = '\0';
+         lpCmdLine++;
+      }
+      if (lpCmdLine[0] == '\0')
+      {
+         lpCmdLine = NULL;
+      }
+   }
+
+   GetModuleFileName(hInstance, szModule, MAX_PATH);
+   argv[0] = szModule;
+   argv[1] = lpCmdLine;
+   argv[2] = NULL;
+   argc = ((lpCmdLine != NULL) ? 2 : 1);
+
+#ifndef _WIN_CONSOLE
+   /* Create a user-interface thread and wait for it to initialise */
+   hInitCompleteEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
+   g_hInstance = hInstance;
+   g_nCmdShow = nCmdShow;
+   _beginthread(UserInterfaceThread, 0, &hInitCompleteEvent);
+   WaitForSingleObject(hInitCompleteEvent, INFINITE);
+   DeleteObject(hInitCompleteEvent);
+#endif
+
+#ifdef __MINGW32__
+   res = real_main( argc, argv );
+#else
+   res = main( argc, argv );
+#endif
+
+   return res;
+
+}
+
+#endif
+
+/*********************************************************************
+ *
+ * Function    :  InitWin32
+ *
+ * Description :  Initialise windows, setting up the console or windows as appropriate.
+ *
+ * Parameters  :  None
+ *
+ * Returns     :  N/A
+ *
+ *********************************************************************/
+void InitWin32(void)
+{
+   WORD wVersionRequested;
+   WSADATA wsaData;
+
+#ifdef _WIN_CONSOLE
+   SetProcessShutdownParameters(0x100, SHUTDOWN_NORETRY);
+   if (hideConsole)
+   {
+      FreeConsole();
+   }
+#endif
+   wVersionRequested = MAKEWORD(2, 0);
+   if (WSAStartup(wVersionRequested, &wsaData) != 0)
+   {
+#ifndef _WIN_CONSOLE
+      MessageBox(NULL, "Cannot initialize WinSock library", "Privoxy Error", 
+         MB_OK | MB_ICONERROR | MB_TASKMODAL | MB_SETFOREGROUND | MB_TOPMOST);  
+#endif
+      exit(1);
+   }
+
+}
+
+
+#ifndef _WIN_CONSOLE
+#include <signal.h>
+#include <assert.h>
+
+#include "win32.h"
+#include "w32log.h"
+
+
+/*********************************************************************
+ *
+ * Function    :  UserInterfaceThread
+ *
+ * Description :  User interface thread.  WinMain will wait for us to set
+ *                the hInitCompleteEvent before patching over to `main'.
+ *                This ensures the systray window is active before beginning
+ *                operations.
+ *
+ * Parameters  :
+ *          1  :  pData = pointer to `hInitCompleteEvent'.
+ *
+ * Returns     :  N/A
+ *
+ *********************************************************************/
+static void __cdecl UserInterfaceThread(void *pData)
+{
+   MSG msg;
+   HANDLE hInitCompleteEvent = *((HANDLE *) pData);
+
+   /* Initialise */
+   InitLogWindow();
+   SetEvent(hInitCompleteEvent);
+
+   /* Enter a message processing loop */
+   while (GetMessage(&msg, (HWND) NULL, 0, 0))
+   {
+      TranslateMessage(&msg);
+      DispatchMessage(&msg);
+   }
+
+   /* Cleanup */
+   TermLogWindow();
+
+   /* Time to die... */
+   exit(0);
+
+}
+
+
+#endif
+
+
+/*
+  Local Variables:
+  tab-width: 3
+  end:
+*/
diff --git a/src/win32.h b/src/win32.h
new file mode 100644 (file)
index 0000000..7e82c81
--- /dev/null
@@ -0,0 +1,90 @@
+#ifndef WIN32_H_INCLUDED
+#define WIN32_H_INCLUDED
+#define WIN32_H_VERSION "$Id: win32.h,v 1.5 2002/03/26 22:57:10 jongfoster Exp $"
+/*********************************************************************
+ *
+ * File        :  $Source: /cvsroot/ijbswa/current/win32.h,v $
+ *
+ * Purpose     :  Win32 User Interface initialization and message loop
+ *
+ * Copyright   :  Written by and Copyright (C) 2001-2002 members of
+ *                the Privoxy team.  http://www.privoxy.org/
+ *
+ *                Written by and Copyright (C) 1999 Adam Lock
+ *                <locka@iol.ie>
+ *
+ *                This program is free software; you can redistribute it 
+ *                and/or modify it under the terms of the GNU General
+ *                Public License as published by the Free Software
+ *                Foundation; either version 2 of the License, or (at
+ *                your option) any later version.
+ *
+ *                This program is distributed in the hope that it will
+ *                be useful, but WITHOUT ANY WARRANTY; without even the
+ *                implied warranty of MERCHANTABILITY or FITNESS FOR A
+ *                PARTICULAR PURPOSE.  See the GNU General Public
+ *                License for more details.
+ *
+ *                The GNU General Public License should be included with
+ *                this file.  If not, you can view it at
+ *                http://www.gnu.org/copyleft/gpl.html
+ *                or write to the Free Software Foundation, Inc., 59
+ *                Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ *
+ * Revisions   :
+ *    $Log: win32.h,v $
+ *    Revision 1.5  2002/03/26 22:57:10  jongfoster
+ *    Web server name should begin www.
+ *
+ *    Revision 1.4  2002/03/24 12:03:47  jongfoster
+ *    Name change
+ *
+ *    Revision 1.3  2001/07/30 22:08:36  jongfoster
+ *    Tidying up #defines:
+ *    - All feature #defines are now of the form FEATURE_xxx
+ *    - Permanently turned off WIN_GUI_EDIT
+ *    - Permanently turned on WEBDAV and SPLIT_PROXY_ARGS
+ *
+ *    Revision 1.2  2001/07/29 18:43:08  jongfoster
+ *    Changing #ifdef _FILENAME_H to FILENAME_H_INCLUDED, to conform to
+ *    ANSI C rules.
+ *
+ *    Revision 1.1.1.1  2001/05/15 13:59:08  oes
+ *    Initial import of version 2.9.3 source tree
+ *
+ *
+ *********************************************************************/
+\f
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern const char win32_blurb[];
+
+extern void InitWin32(void);
+
+#ifdef _WIN_CONSOLE
+extern int hideConsole;
+#endif /*def _WIN_CONSOLE */
+
+extern HINSTANCE g_hInstance;
+extern int g_nCmdShow;
+
+extern int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow);
+
+/* Revision control strings from this header and associated .c file */
+extern const char win32_rcs[];
+extern const char win32_h_rcs[];
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* ndef WIN32_H_INCLUDED */
+  
+/*
+  Local Variables:
+  tab-width: 3
+  end:
+*/