Implement client-specific tags
authorFabian Keil <fk@fabiankeil.de>
Thu, 17 Mar 2016 10:42:27 +0000 (10:42 +0000)
committerFabian Keil <fk@fabiankeil.de>
Thu, 17 Mar 2016 10:42:27 +0000 (10:42 +0000)
... which allow Privoxy admins to pre-define tags that
are set for all requests from clients that previously
opted-in through the CGI interface.

They are useful in multi-user setups where admins may
want to allow users to disable certain actions and filters
for themselves without affecting others.

In single-user setups they are useful to allow more
fine-grained toggling. For example to disable request
blocking while still crunching cookies, or to disable
experimental filters only.

This is an experimental feature, to enable it configure
with --enable-client-tags. The syntax and behaviour may
change in future versions.

Implements TODO list item #144 and #145.
Funded by a donation from Robert Klemme.

16 files changed:
GNUmakefile.in
cgi.c
cgisimple.c
cgisimple.h
client-tags.c [new file with mode: 0644]
client-tags.h [new file with mode: 0644]
configure.in
filters.c
filters.h
jcc.c
jcc.h
loadcfg.c
project.h
templates/show-client-tags [new file with mode: 0644]
templates/show-status
urlmatch.c

index aabeedd..9da003a 100644 (file)
@@ -1,6 +1,6 @@
 # Note:  Makefile is built automatically from Makefile.in
 #
-# $Id: GNUmakefile.in,v 1.234 2016/02/02 13:08:03 fabiankeil Exp $
+# $Id: GNUmakefile.in,v 1.235 2016/02/13 11:18:02 fabiankeil Exp $
 #
 # Written by and Copyright (C) 2001-2014 members of the
 # Privoxy team. http://www.privoxy.org/
@@ -190,6 +190,9 @@ C_SRC  = actions.c cgi.c cgiedit.c cgisimple.c deanimate.c encode.c \
 C_OBJS = $(C_SRC:.c=.@OBJEXT@)
 C_HDRS = $(C_SRC:.c=.h) project.h actionlist.h
 
+CLIENT_TAG_SRC = @FEATURE_CLIENT_TAGS_ONLY@client-tags.c
+CLIENT_TAG_OBJS = @FEATURE_CLIENT_TAGS_ONLY@client-tags.@OBJEXT@
+
 W32_SRC   = @WIN_ONLY@w32log.c w32taskbar.c win32.c w32svrapi.c
 W32_FILES = @WIN_ONLY@w32.res
 W32_OBJS  = @WIN_ONLY@$(W32_SRC:.c=.@OBJEXT@) $(W32_FILES)
@@ -221,8 +224,8 @@ SOCKET_LIB   = @SOCKET_LIB@
 # PThreads library, if needed.
 PTHREAD_LIB  = @PTHREAD_ONLY@@PTHREAD_LIB@
 
-SRCS         = $(C_SRC)  $(W32_SRC)  $(PCRS_SRC)  $(PCRE_SRC)  $(REGEX_SRC)
-OBJS         = $(C_OBJS) $(W32_OBJS) $(PCRS_OBJS) $(PCRE_OBJS) $(REGEX_OBJS)
+SRCS         = $(C_SRC) $(CLIENT_TAG_SRC) $(W32_SRC)  $(PCRS_SRC)  $(PCRE_SRC)  $(REGEX_SRC)
+OBJS         = $(C_OBJS) $(CLIENT_TAG_OBJS) $(W32_OBJS) $(PCRS_OBJS) $(PCRE_OBJS) $(REGEX_OBJS)
 HDRS         = $(C_HDRS) $(W32_HDRS) $(PCRS_HDRS) $(PCRE_OBJS) $(REGEX_HDRS)
 LIBS         = @LIBS@ $(W32_LIB) $(SOCKET_LIB) $(PTHREAD_LIB)
 
diff --git a/cgi.c b/cgi.c
index 114eae8..14abf8b 100644 (file)
--- a/cgi.c
+++ b/cgi.c
@@ -1,4 +1,4 @@
-const char cgi_rcs[] = "$Id: cgi.c,v 1.160 2014/10/18 11:31:52 fabiankeil Exp $";
+const char cgi_rcs[] = "$Id: cgi.c,v 1.161 2016/02/26 12:32:26 fabiankeil Exp $";
 /*********************************************************************
  *
  * File        :  $Source: /cvsroot/ijbswa/current/cgi.c,v $
@@ -100,6 +100,12 @@ static const struct cgi_dispatcher cgi_dispatchers[] = {
          cgi_show_version,
          "View the source code version numbers",
           TRUE },
+#ifdef FEATURE_CLIENT_TAGS
+   { "show-client-tags",
+         cgi_show_client_tags,
+         "Show the tags that can be set based on the client's address (opt-in)",
+          FALSE },
+#endif
    { "show-request",
          cgi_show_request,
          "View the request headers",
index ad44966..faa70da 100644 (file)
@@ -1,4 +1,4 @@
-const char cgisimple_rcs[] = "$Id: cgisimple.c,v 1.134 2016/02/26 12:32:09 fabiankeil Exp $";
+const char cgisimple_rcs[] = "$Id: cgisimple.c,v 1.135 2016/03/04 13:22:22 fabiankeil Exp $";
 /*********************************************************************
  *
  * File        :  $Source: /cvsroot/ijbswa/current/cgisimple.c,v $
@@ -60,6 +60,9 @@ const char cgisimple_rcs[] = "$Id: cgisimple.c,v 1.134 2016/02/26 12:32:09 fabia
 #include "parsers.h"
 #include "urlmatch.h"
 #include "errlog.h"
+#ifdef FEATURE_CLIENT_TAGS
+#include "client-tags.h"
+#endif
 
 const char cgisimple_h_rcs[] = CGISIMPLE_H_VERSION;
 
@@ -271,6 +274,136 @@ jb_err cgi_show_request(struct client_state *csp,
 }
 
 
+#ifdef FEATURE_CLIENT_TAGS
+/*********************************************************************
+ *
+ * Function    :  cgi_show_client_tags
+ *
+ * Description :  Shows the tags that can be set based on the client
+ *                address (opt-in).
+ *
+ * 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_client_tags(struct client_state *csp,
+                        struct http_response *rsp,
+                        const struct map *parameters)
+{
+   struct map *exports;
+   struct client_tag_spec *this_tag;
+   jb_err err = JB_ERR_OK;
+   const char *toggled_tag;
+   const char *toggle_state;
+   const char *tag_expires;
+   time_t time_to_live;
+   char *client_tags = strdup_or_die("");
+   char buf[1000];
+
+   assert(csp);
+   assert(rsp);
+   assert(parameters);
+
+   if (NULL == (exports = default_exports(csp, "show-client-tags")))
+   {
+      return JB_ERR_MEMORY;
+   }
+
+   toggled_tag = lookup(parameters, "tag");
+   if (*toggled_tag != '\0')
+   {
+      tag_expires = lookup(parameters, "expires");
+      if (*tag_expires == '0')
+      {
+         time_to_live = 0;
+      }
+      else
+      {
+         time_to_live = csp->config->client_tag_lifetime;
+      }
+      toggle_state = lookup(parameters, "toggle-state");
+      if (*toggle_state == '1')
+      {
+         enable_client_specific_tag(csp, toggled_tag, time_to_live);
+      }
+      else
+      {
+         disable_client_specific_tag(csp, toggled_tag);
+      }
+   }
+
+   this_tag = csp->config->client_tags;
+   if (this_tag->name == NULL)
+   {
+      if (!err) err = string_append(&client_tags, "<p>No tags available.</p>\n");
+   }
+   else
+   {
+      if (!err)
+      {
+         err = string_append(&client_tags, "<table border=\"1\">\n"
+            "<tr><th>Tag name</th>\n"
+            "<th>Current state</th><th>Change state</th><th>Description</th></tr>\n");
+      }
+      while ((this_tag != NULL) && (this_tag->name != NULL))
+      {
+         int tag_state;
+
+         privoxy_mutex_lock(&client_tags_mutex);
+         tag_state = client_has_requested_tag(csp->ip_addr_str, this_tag->name);
+         privoxy_mutex_unlock(&client_tags_mutex);
+         if (!err) err = string_append(&client_tags, "<tr><td>");
+         if (!err) err = string_append(&client_tags, this_tag->name);
+         if (!err) err = string_append(&client_tags, "</td><td>");
+         if (!err) err = string_append(&client_tags, tag_state == 1 ? "Enabled" : "Disabled");
+         snprintf(buf, sizeof(buf),
+            "</td><td><a href=\"/show-client-tags?tag=%s&toggle-state=%d&amp;expires=0\">%s</a>",
+            this_tag->name, !tag_state, tag_state == 1 ? "Disable" : "Enable");
+         if (!err) err = string_append(&client_tags, buf);
+         if (tag_state == 0)
+         {
+            snprintf(buf, sizeof(buf), ". <a href=\"/show-client-tags?"
+               "tag=%s&amp;toggle-state=1&amp;expires=1\">Enable temporarily</a>",
+               this_tag->name);
+            if (!err) err = string_append(&client_tags, buf);
+         }
+         if (!err) err = string_append(&client_tags, "</td><td>");
+         if (!err) err = string_append(&client_tags, this_tag->description);
+         if (!err) err = string_append(&client_tags, "</td></tr>\n");
+         if (err)
+         {
+            free_map(exports);
+            return JB_ERR_MEMORY;
+         }
+         this_tag = this_tag->next;
+      }
+      if (!err) err = string_append(&client_tags, "</table>\n");
+   }
+
+   if (map(exports, "client-tags", 1, client_tags, 0))
+   {
+      free_map(exports);
+      return JB_ERR_MEMORY;
+   }
+
+   if (map(exports, "client-ip-addr", 1, csp->ip_addr_str, 1))
+   {
+      free_map(exports);
+      return JB_ERR_MEMORY;
+   }
+
+   return template_fill_for_cgi(csp, "show-client-tags", exports, rsp);
+}
+#endif /* def FEATURE_CLIENT_TAGS */
+
+
 /*********************************************************************
  *
  * Function    :  cgi_send_banner
@@ -1564,6 +1697,14 @@ static jb_err show_defines(struct map *exports)
          1,
 #else
          0,
+#endif
+      },
+      {
+         "FEATURE_CLIENT_TAGS",
+#ifdef FEATURE_CLIENT_TAGS
+         1,
+#else
+         0,
 #endif
       },
       {
index 55cafb6..3b66fc7 100644 (file)
@@ -1,6 +1,6 @@
 #ifndef CGISIMPLE_H_INCLUDED
 #define CGISIMPLE_H_INCLUDED
-#define CGISIMPLE_H_VERSION "$Id: cgisimple.h,v 1.18 2011/09/04 11:10:56 fabiankeil Exp $"
+#define CGISIMPLE_H_VERSION "$Id: cgisimple.h,v 1.19 2013/11/24 14:23:28 fabiankeil Exp $"
 /*********************************************************************
  *
  * File        :  $Source: /cvsroot/ijbswa/current/cgisimple.h,v $
@@ -68,6 +68,11 @@ extern jb_err cgi_show_version (struct client_state *csp,
 extern jb_err cgi_show_request (struct client_state *csp,
                                 struct http_response *rsp,
                                 const struct map *parameters);
+#ifdef FEATURE_CLIENT_TAGS
+extern jb_err cgi_show_client_tags(struct client_state *csp,
+                                   struct http_response *rsp,
+                                   const struct map *parameters);
+#endif
 extern jb_err cgi_transparent_image (struct client_state *csp,
                                      struct http_response *rsp,
                                      const struct map *parameters);
diff --git a/client-tags.c b/client-tags.c
new file mode 100644 (file)
index 0000000..e04345c
--- /dev/null
@@ -0,0 +1,587 @@
+/*********************************************************************
+ *
+ * File        :  $Source: /cvsroot/ijbswa/current/client-tags.c,v $
+ *
+ * Purpose     :  Functions related to client-specific tags.
+ *
+ * Copyright   :  Copyright (C) 2016 Fabian Keil <fk@fabiankeil.de>
+ *
+ *                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.
+ *
+ **********************************************************************/
+
+#include "config.h"
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+#include <assert.h>
+
+#include "project.h"
+#include "list.h"
+#include "jcc.h"
+#include "miscutil.h"
+#include "errlog.h"
+
+struct client_specific_tag
+{
+   char *name;
+
+   time_t end_of_life;
+
+   struct client_specific_tag *next;
+   struct client_specific_tag *prev;
+};
+
+/**
+ * This struct represents tags that have been requested by clients
+ */
+struct requested_tags
+{
+   char *client; /**< The IP address of the client that requested the tag */
+
+   /**< List of tags the client requested .... */
+   struct client_specific_tag *tags;
+
+   struct requested_tags *next;
+   struct requested_tags *prev;
+};
+
+struct requested_tags *requested_tags;
+static void remove_tag_for_client(const char *client_address, const char *tag);
+
+/*********************************************************************
+ *
+ * Function    :  validate_tag_list
+ *
+ * Description :  Validates the given tag list
+ *
+ * Parameters  :
+ *          1  :  enabled_tags = The tags to validate
+ *
+ * Returns     :  void
+ *
+ *********************************************************************/
+static void validate_tag_list(struct client_specific_tag *enabled_tags)
+{
+   while (enabled_tags != NULL)
+   {
+      if (enabled_tags->name == NULL)
+      {
+         assert(enabled_tags->name != NULL);
+         log_error(LOG_LEVEL_FATAL, "validate_tag_list(): Tag without name detected");
+      }
+      if (enabled_tags->next != NULL)
+      {
+         if (enabled_tags->next->prev != enabled_tags)
+         {
+            assert(enabled_tags->next->prev == enabled_tags);
+            log_error(LOG_LEVEL_FATAL, "validate_tag_list(): Invalid backlink detected");
+         }
+      }
+      enabled_tags = enabled_tags->next;
+   }
+}
+
+/*********************************************************************
+ *
+ * Function    :  validate_requested_tags
+ *
+ * Description :  Validates the requested_tags list
+ *
+ * Parameters  : N/A
+ *
+ * Returns     :  void
+ *
+ *********************************************************************/
+static jb_err validate_requested_tags()
+{
+   struct requested_tags *requested_tag;
+
+   for (requested_tag = requested_tags; requested_tag != NULL;
+        requested_tag = requested_tag->next)
+   {
+      if (requested_tag->client == NULL)
+      {
+         assert(requested_tag->client != NULL);
+         log_error(LOG_LEVEL_FATAL, "validate_tag_list(): Client not registered");
+      }
+      validate_tag_list(requested_tag->tags);
+      if (requested_tag->next != NULL)
+      {
+         if (requested_tag->next->prev != requested_tag)
+         {
+            assert(requested_tag->next->prev == requested_tag);
+            log_error(LOG_LEVEL_FATAL, "validate_requested_tags(): Invalid backlink detected");
+         }
+      }
+   }
+
+   return TRUE;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  get_client_specific_tag
+ *
+ * Description :  Returns the data for a client-specific-tag specified
+ *                by name.
+ *
+ * Parameters  :
+ *          1  :  tag_list = The tag list to check
+ *          2  :  name =     The tag name to look up
+ *
+ * Returns     :  Pointer to tag structure or NULL on error.
+ *
+ *********************************************************************/
+static struct client_tag_spec *get_client_specific_tag(
+   struct client_tag_spec *tag_list, const char *name)
+{
+   struct client_tag_spec *tag;
+
+   for (tag = tag_list; tag != NULL; tag = tag->next)
+   {
+      if (tag->name != NULL && !strcmp(tag->name, name))
+      {
+         return tag;
+      }
+   }
+
+   log_error(LOG_LEVEL_ERROR, "No such tag: '%s'", name);
+
+   return NULL;
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  get_tags_for_client
+ *
+ * Description :  Returns the list of tags the client opted-in.
+ *
+ * Parameters  :
+ *          1  :  client_address = Address of the client
+ *
+ * Returns     :  Pointer to tag structure or NULL on error.
+ *
+ *********************************************************************/
+static struct client_specific_tag *get_tags_for_client(const char *client_address)
+{
+   struct requested_tags *requested_tag;
+
+   for (requested_tag = requested_tags; requested_tag != NULL;
+        requested_tag = requested_tag->next)
+   {
+      if (!strcmp(requested_tag->client, client_address))
+      {
+         return requested_tag->tags;
+      }
+   }
+
+   return NULL;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  get_tag_list_for_client
+ *
+ * Description :  Provides a list of tag names the client opted-in.
+ *                Other tag attributes are not part of the list.
+ *
+ * Parameters  :
+ *          1  :  tag_list = The list to fill in.
+ *          2  :  client_address = Address of the client
+ *
+ * Returns     :  Pointer to tag list.
+ *
+ *********************************************************************/
+void get_tag_list_for_client(struct list *tag_list,
+                             const char *client_address)
+{
+   struct client_specific_tag *enabled_tags;
+   const time_t now = time(NULL);
+
+   privoxy_mutex_lock(&client_tags_mutex);
+
+   enabled_tags = get_tags_for_client(client_address);
+   while (enabled_tags != NULL)
+   {
+      if (enabled_tags->end_of_life && (enabled_tags->end_of_life < now))
+      {
+         struct client_specific_tag *next_tag = enabled_tags->next;
+         log_error(LOG_LEVEL_INFO,
+            "Tag '%s' for client %s expired %u seconds ago. Deleting it.",
+            enabled_tags->name, client_address,
+            (now - enabled_tags->end_of_life));
+         remove_tag_for_client(client_address, enabled_tags->name);
+         enabled_tags = next_tag;
+         continue;
+      }
+      else
+      {
+         enlist(tag_list, enabled_tags->name);
+      }
+      enabled_tags = enabled_tags->next;
+   }
+
+   privoxy_mutex_unlock(&client_tags_mutex);
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  add_tag_for_client
+ *
+ * Description :  Adds the tag for the client.
+ *
+ * Parameters  :
+ *          1  :  client_address = Address of the client
+ *          2  :  tag = The tag to add.
+ *          3  :  time_to_live = 0, or the number of seconds
+ *                               the tag remains activated.
+ *
+ * Returns     :  void
+ *
+ *********************************************************************/
+static void add_tag_for_client(const char *client_address,
+   const char *tag, const time_t time_to_live)
+{
+   struct requested_tags *clients_with_tags;
+   struct client_specific_tag *enabled_tags;
+
+   validate_requested_tags();
+
+   if (requested_tags == NULL)
+   {
+      /* XXX: Code duplication. */
+      requested_tags = zalloc_or_die(sizeof(struct requested_tags));
+      requested_tags->client = strdup_or_die(client_address);
+      requested_tags->tags = zalloc_or_die(sizeof(struct client_specific_tag));
+      requested_tags->tags->name = strdup_or_die(tag);
+      requested_tags->tags->end_of_life = time_to_live ?
+         (time(NULL) + time_to_live) : 0;
+
+      validate_requested_tags();
+      return;
+   }
+   else
+   {
+      clients_with_tags = requested_tags;
+      while (clients_with_tags->next != NULL)
+      {
+         if (!strcmp(clients_with_tags->client, client_address))
+         {
+            break;
+         }
+         clients_with_tags = clients_with_tags->next;
+      }
+      if (strcmp(clients_with_tags->client, client_address))
+      {
+         /* Client does not have tags yet, add new structure */
+         clients_with_tags->next = zalloc_or_die(sizeof(struct requested_tags));
+         clients_with_tags->next->prev = clients_with_tags;
+         clients_with_tags = clients_with_tags->next;
+         clients_with_tags->client = strdup_or_die(client_address);
+         clients_with_tags->tags = zalloc_or_die(sizeof(struct client_specific_tag));
+         clients_with_tags->tags->name = strdup_or_die(tag);
+         clients_with_tags->tags->end_of_life = time_to_live ?
+            (time(NULL) + time_to_live) : 0;
+
+         validate_requested_tags();
+
+         return;
+      }
+   }
+
+   enabled_tags = clients_with_tags->tags;
+   while (enabled_tags != NULL)
+   {
+      if (enabled_tags->next == NULL)
+      {
+         enabled_tags->next = zalloc_or_die(sizeof(struct client_specific_tag));
+         enabled_tags->next->name = strdup_or_die(tag);
+         clients_with_tags->tags->end_of_life = time_to_live ?
+            (time(NULL) + time_to_live) : 0;
+         enabled_tags->next->prev = enabled_tags;
+         break;
+      }
+      enabled_tags = enabled_tags->next;
+   }
+
+   validate_requested_tags();
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  remove_tag_for_client
+ *
+ * Description :  Removes the tag for the client.
+ *
+ * Parameters  :
+ *          1  :  client_address = Address of the client
+ *          2  :  tag = The tag to remove.
+ *
+ * Returns     :  void
+ *
+ *********************************************************************/
+static void remove_tag_for_client(const char *client_address, const char *tag)
+{
+   struct requested_tags *clients_with_tags;
+   struct client_specific_tag *enabled_tags;
+
+   validate_requested_tags();
+
+   clients_with_tags = requested_tags;
+   while (clients_with_tags != NULL && clients_with_tags->client != NULL)
+   {
+      if (!strcmp(clients_with_tags->client, client_address))
+      {
+         break;
+      }
+      clients_with_tags = clients_with_tags->next;
+   }
+
+   enabled_tags = clients_with_tags->tags;
+   while (enabled_tags != NULL)
+   {
+      if (!strcmp(enabled_tags->name, tag))
+      {
+         if (enabled_tags->next != NULL)
+         {
+            enabled_tags->next->prev = enabled_tags->prev;
+            if (enabled_tags == clients_with_tags->tags)
+            {
+               /* Tag is first in line */
+               clients_with_tags->tags = enabled_tags->next;
+            }
+         }
+         if (enabled_tags->prev != NULL)
+         {
+            /* Tag has preceding tag */
+            enabled_tags->prev->next = enabled_tags->next;
+         }
+         if (enabled_tags->prev == NULL && enabled_tags->next == NULL)
+         {
+            /* Tag is the only one */
+            if (clients_with_tags->next != NULL)
+            {
+               /* Client has following client */
+               clients_with_tags->next->prev = clients_with_tags->prev;
+            }
+            if (clients_with_tags->prev != NULL)
+            {
+               /* Client has preceding client */
+               clients_with_tags->prev->next = clients_with_tags->next;
+            }
+            freez(clients_with_tags->client);
+            if (clients_with_tags == requested_tags)
+            {
+               /* Removing last tag */
+               freez(requested_tags);
+               clients_with_tags = requested_tags;
+            }
+            else
+            {
+               freez(clients_with_tags);
+            }
+         }
+         freez(enabled_tags->name);
+         freez(enabled_tags);
+         break;
+      }
+
+      enabled_tags = enabled_tags->next;
+   }
+
+   validate_requested_tags();
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  client_has_requested_tag
+ *
+ * Description :  Checks whether or not the given client requested
+ *                the tag.
+ *
+ * Parameters  :
+ *          1  :  client_address = Address of the client
+ *          2  :  tag = Tag to check.
+ *
+ * Returns     :  TRUE or FALSE.
+ *
+ *********************************************************************/
+int client_has_requested_tag(const char *client_address, const char *tag)
+{
+   struct client_specific_tag *enabled_tags;
+
+   enabled_tags = get_tags_for_client(client_address);
+
+   while (enabled_tags != NULL)
+   {
+      if (!strcmp(enabled_tags->name, tag))
+      {
+         return TRUE;
+      }
+      enabled_tags = enabled_tags->next;
+   }
+
+   return FALSE;
+
+}
+
+/*********************************************************************
+ *
+ * Function    :  enable_client_specific_tag
+ *
+ * Description :  Enables a client-specific-tag for the client
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  tag_name = The name of the tag to enable
+ *          3  :  time_to_live = If not 0, the number of seconds the
+ *                               tag should stay enabled.
+ *
+ * Returns     :  JB_ERR_OK on success, JB_ERR_MEMORY or JB_ERR_PARSE.
+ *
+ *********************************************************************/
+jb_err enable_client_specific_tag(struct client_state *csp,
+   const char *tag_name, const time_t time_to_live)
+{
+   struct client_tag_spec *tag;
+
+   privoxy_mutex_lock(&client_tags_mutex);
+
+   tag = get_client_specific_tag(csp->config->client_tags, tag_name);
+   if (tag == NULL)
+   {
+      privoxy_mutex_unlock(&client_tags_mutex);
+      return JB_ERR_PARSE;
+   }
+
+   if (client_has_requested_tag(csp->ip_addr_str, tag_name))
+   {
+      log_error(LOG_LEVEL_ERROR,
+         "Tag '%s' already enabled for client '%s'", tag->name, csp->ip_addr_str);
+   }
+   else
+   {
+      add_tag_for_client(csp->ip_addr_str, tag_name, time_to_live);
+      log_error(LOG_LEVEL_INFO,
+         "Tag '%s' enabled for client '%s'", tag->name, csp->ip_addr_str);
+   }
+
+   privoxy_mutex_unlock(&client_tags_mutex);
+
+   return JB_ERR_OK;
+
+}
+
+/*********************************************************************
+ *
+ * Function    :  disable_client_specific_tag
+ *
+ * Description :  Disables a client-specific-tag for the client
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  tag_name = The name of the tag to disable
+ *
+ * Returns     :  JB_ERR_OK on success, JB_ERR_MEMORY or JB_ERR_PARSE.
+ *
+ *********************************************************************/
+jb_err disable_client_specific_tag(struct client_state *csp, const char *tag_name)
+{
+   struct client_tag_spec *tag;
+
+   privoxy_mutex_lock(&client_tags_mutex);
+
+   tag = get_client_specific_tag(csp->config->client_tags, tag_name);
+   if (tag == NULL)
+   {
+      privoxy_mutex_unlock(&client_tags_mutex);
+      return JB_ERR_PARSE;
+   }
+
+   if (client_has_requested_tag(csp->ip_addr_str, tag_name))
+   {
+      remove_tag_for_client(csp->ip_addr_str, tag_name);
+      log_error(LOG_LEVEL_INFO,
+         "Tag '%s' disabled for client '%s'", tag->name, csp->ip_addr_str);
+   }
+   else
+   {
+      log_error(LOG_LEVEL_ERROR,
+         "Tag '%s' currently not set for client '%s'",
+         tag->name, csp->ip_addr_str);
+   }
+
+   privoxy_mutex_unlock(&client_tags_mutex);
+   return JB_ERR_OK;
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  client_tag_match
+ *
+ * Description :  Compare a client tag against a client tag pattern.
+ *
+ * Parameters  :
+ *          1  :  pattern = a TAG pattern
+ *          2  :  tag = Client tag to match
+ *
+ * Returns     :  Nonzero if the tag matches the pattern, else 0.
+ *
+ *********************************************************************/
+int client_tag_match(const struct pattern_spec *pattern,
+                     const struct list *tags)
+{
+   struct list_entry *tag;
+
+   if (!(pattern->flags & PATTERN_SPEC_CLIENT_TAG_PATTERN))
+   {
+      /*
+       * It's not a client pattern and thus shouldn't
+       * be matched against client tags.
+       */
+      return 0;
+   }
+
+   assert(tags);
+
+   for (tag = tags->first; tag != NULL; tag = tag->next)
+   {
+      if (0 == regexec(pattern->pattern.tag_regex, tag->str, 0, NULL, 0))
+      {
+         return 1;
+      }
+   }
+
+   return 0;
+
+}
diff --git a/client-tags.h b/client-tags.h
new file mode 100644 (file)
index 0000000..ac6ef0d
--- /dev/null
@@ -0,0 +1,43 @@
+#ifndef CLIENT_TAGS_H_INCLUDED
+#define CLIENT_TAGS_H_INCLUDED
+#define CLIENT_TAGS_H_VERSION "$Id:$"
+/*********************************************************************
+ *
+ * File        :  $Source: $
+ *
+ * Purpose     :  Declares functions for client-specific tags.
+ *
+ * Copyright   :  Copyright (C) 2016 Fabian Keil <fk@fabiankeil.de>
+ *
+ *                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.
+ *
+ *********************************************************************/
+
+extern int client_tag_match(const struct pattern_spec *pattern,
+                            const struct list *tags);
+extern void get_tag_list_for_client(struct list *tag_list,
+                                    const char *client_address);
+extern jb_err disable_client_specific_tag(struct client_state *csp,
+                                          const char *tag_name);
+extern jb_err enable_client_specific_tag(struct client_state *csp,
+                                         const char *tag_name,
+                                         const time_t time_to_live);
+extern int client_has_requested_tag(const char *client_address,
+                                    const char *tag);
+#endif
index 61152d5..ec670d0 100644 (file)
@@ -1,6 +1,6 @@
 dnl Process this file with autoconf to produce a configure script.
 dnl
-dnl $Id: configure.in,v 1.190 2016/01/16 12:33:16 fabiankeil Exp $
+dnl $Id: configure.in,v 1.191 2016/02/02 13:08:17 fabiankeil Exp $
 dnl
 dnl Written by and Copyright (C) 2001-2014 the
 dnl Privoxy team. http://www.privoxy.org/
@@ -32,7 +32,7 @@ dnl =================================================================
 dnl AutoConf Initialization
 dnl =================================================================
 
-AC_REVISION($Revision: 1.190 $)
+AC_REVISION($Revision: 1.191 $)
 AC_INIT(jcc.c)
 
 if test ! -f config.h.in; then
@@ -982,6 +982,16 @@ AC_ARG_ENABLE(strptime-sanity-checks,
   AC_DEFINE(FEATURE_STRPTIME_SANITY_CHECKS)
 fi])
 
+AC_ARG_ENABLE(client-tags,
+[  --enable-client-tags                Enable client-specific tags],
+[if test $enableval = yes; then
+  FEATURE_CLIENT_TAGS_ONLY=""
+  AC_DEFINE(FEATURE_CLIENT_TAGS,1,[Define to enable client-specific tags.])
+ else
+  FEATURE_CLIENT_TAGS_ONLY="#"
+fi])
+AC_SUBST(FEATURE_CLIENT_TAGS_ONLY)
+
 dnl pcre/pcrs is needed for CGI anyway, so
 dnl the choice is only between static and
 dnl dynamic:
index 8a83bbc..c3aa3fd 100644 (file)
--- a/filters.c
+++ b/filters.c
@@ -1,4 +1,4 @@
-const char filters_rcs[] = "$Id: filters.c,v 1.199 2016/01/16 12:33:35 fabiankeil Exp $";
+const char filters_rcs[] = "$Id: filters.c,v 1.200 2016/02/26 12:29:38 fabiankeil Exp $";
 /*********************************************************************
  *
  * File        :  $Source: /cvsroot/ijbswa/current/filters.c,v $
@@ -70,6 +70,9 @@ const char filters_rcs[] = "$Id: filters.c,v 1.199 2016/01/16 12:33:35 fabiankei
 #include "deanimate.h"
 #include "urlmatch.h"
 #include "loaders.h"
+#ifdef FEATURE_CLIENT_TAGS
+#include "client-tags.h"
+#endif
 
 #ifdef _WIN32
 #include "win32.h"
@@ -81,6 +84,12 @@ typedef char *(*filter_function_ptr)();
 static filter_function_ptr get_filter_function(const struct client_state *csp);
 static jb_err remove_chunked_transfer_coding(char *buffer, size_t *size);
 static jb_err prepare_for_filtering(struct client_state *csp);
+static void apply_url_actions(struct current_action_spec *action,
+                              struct http_request *http,
+#ifdef FEATURE_CLIENT_TAGS
+                              const struct list *client_tags,
+#endif
+                              struct url_actions *b);
 
 #ifdef FEATURE_ACL
 #ifdef HAVE_RFC2553
@@ -2322,13 +2331,16 @@ void get_url_actions(struct client_state *csp, struct http_request *http)
          return;
       }
 
+#ifdef FEATURE_CLIENT_TAGS
+      apply_url_actions(csp->action, http, csp->client_tags, b);
+#else
       apply_url_actions(csp->action, http, b);
+#endif
    }
 
    return;
 }
 
-
 /*********************************************************************
  *
  * Function    :  apply_url_actions
@@ -2338,14 +2350,18 @@ void get_url_actions(struct client_state *csp, struct http_request *http)
  * Parameters  :
  *          1  :  action = Destination.
  *          2  :  http = Current URL
- *          3  :  b = list of URL actions to apply
+ *          3  :  client_tags = list of client tags
+ *          4  :  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)
+static void apply_url_actions(struct current_action_spec *action,
+                              struct http_request *http,
+#ifdef FEATURE_CLIENT_TAGS
+                              const struct list *client_tags,
+#endif
+                              struct url_actions *b)
 {
    if (b == NULL)
    {
@@ -2359,6 +2375,12 @@ void apply_url_actions(struct current_action_spec *action,
       {
          merge_current_action(action, b->action);
       }
+#ifdef FEATURE_CLIENT_TAGS
+      if (client_tag_match(b->url, client_tags))
+      {
+         merge_current_action(action, b->action);
+      }
+#endif
    }
 }
 
index ec1663f..802a3e1 100644 (file)
--- a/filters.h
+++ b/filters.h
@@ -1,6 +1,6 @@
 #ifndef FILTERS_H_INCLUDED
 #define FILTERS_H_INCLUDED
-#define FILTERS_H_VERSION "$Id: filters.h,v 1.45 2013/11/24 14:23:28 fabiankeil Exp $"
+#define FILTERS_H_VERSION "$Id: filters.h,v 1.46 2013/12/24 13:32:51 fabiankeil Exp $"
 /*********************************************************************
  *
  * File        :  $Source: /cvsroot/ijbswa/current/filters.h,v $
@@ -75,9 +75,6 @@ extern int connect_port_is_forbidden(const struct client_state *csp);
  */
 extern void get_url_actions(struct client_state *csp,
                             struct http_request *http);
-extern void apply_url_actions(struct current_action_spec *action,
-                              struct http_request *http,
-                              struct url_actions *b);
 
 extern struct re_filterfile_spec *get_filter(const struct client_state *csp,
                                              const char *requested_name,
diff --git a/jcc.c b/jcc.c
index e0ca39b..8ab7f87 100644 (file)
--- a/jcc.c
+++ b/jcc.c
@@ -1,4 +1,4 @@
-const char jcc_rcs[] = "$Id: jcc.c,v 1.440 2016/01/16 12:33:36 fabiankeil Exp $";
+const char jcc_rcs[] = "$Id: jcc.c,v 1.441 2016/02/26 12:29:38 fabiankeil Exp $";
 /*********************************************************************
  *
  * File        :  $Source: /cvsroot/ijbswa/current/jcc.c,v $
@@ -115,6 +115,9 @@ const char jcc_rcs[] = "$Id: jcc.c,v 1.440 2016/01/16 12:33:36 fabiankeil Exp $"
 #include "cgi.h"
 #include "loadcfg.h"
 #include "urlmatch.h"
+#ifdef FEATURE_CLIENT_TAGS
+#include "client-tags.h"
+#endif
 
 const char jcc_h_rcs[] = JCC_H_VERSION;
 const char project_h_rcs[] = PROJECT_H_VERSION;
@@ -185,6 +188,9 @@ privoxy_mutex_t connection_reuse_mutex;
 #ifdef FEATURE_EXTERNAL_FILTERS
 privoxy_mutex_t external_filter_mutex;
 #endif
+#ifdef FEATURE_CLIENT_TAGS
+privoxy_mutex_t client_tags_mutex;
+#endif
 
 #if !defined(HAVE_GETHOSTBYADDR_R) || !defined(HAVE_GETHOSTBYNAME_R)
 privoxy_mutex_t resolver_mutex;
@@ -1842,6 +1848,9 @@ static void chat(struct client_state *csp)
 
    http = csp->http;
 
+#if FEATURE_CLIENT_TAGS
+   get_tag_list_for_client(csp->client_tags, csp->ip_addr_str);
+#endif
    if (receive_client_request(csp) != JB_ERR_OK)
    {
       return;
@@ -2813,6 +2822,9 @@ static void prepare_csp_for_next_request(struct client_state *csp)
    free_http_request(csp->http);
    destroy_list(csp->headers);
    destroy_list(csp->tags);
+#ifdef FEATURE_CLIENT_TAGS
+   destroy_list(csp->client_tags);
+#endif
    free_current_action(csp->action);
    if (NULL != csp->fwd)
    {
@@ -3246,6 +3258,9 @@ static void initialize_mutexes(void)
 #ifdef FEATURE_EXTERNAL_FILTERS
    privoxy_mutex_init(&external_filter_mutex);
 #endif
+#ifdef FEATURE_CLIENT_TAGS
+   privoxy_mutex_init(&client_tags_mutex);
+#endif
 
    /*
     * XXX: The assumptions below are a bit naive
diff --git a/jcc.h b/jcc.h
index 57e68f7..43b765f 100644 (file)
--- a/jcc.h
+++ b/jcc.h
@@ -1,6 +1,6 @@
 #ifndef JCC_H_INCLUDED
 #define JCC_H_INCLUDED
-#define JCC_H_VERSION "$Id: jcc.h,v 1.34 2014/06/02 06:19:06 fabiankeil Exp $"
+#define JCC_H_VERSION "$Id: jcc.h,v 1.35 2014/06/02 06:22:21 fabiankeil Exp $"
 /*********************************************************************
  *
  * File        :  $Source: /cvsroot/ijbswa/current/jcc.h,v $
@@ -83,6 +83,10 @@ extern privoxy_mutex_t connection_reuse_mutex;
 extern privoxy_mutex_t external_filter_mutex;
 #endif
 
+#ifdef FEATURE_CLIENT_TAGS
+extern privoxy_mutex_t client_tags_mutex;
+#endif
+
 #ifndef HAVE_GMTIME_R
 extern privoxy_mutex_t gmtime_mutex;
 #endif /* ndef HAVE_GMTIME_R */
index fa79ba1..c73928d 100644 (file)
--- a/loadcfg.c
+++ b/loadcfg.c
@@ -1,4 +1,4 @@
-const char loadcfg_rcs[] = "$Id: loadcfg.c,v 1.146 2016/02/26 12:29:38 fabiankeil Exp $";
+const char loadcfg_rcs[] = "$Id: loadcfg.c,v 1.147 2016/02/26 12:30:59 fabiankeil Exp $";
 /*********************************************************************
  *
  * File        :  $Source: /cvsroot/ijbswa/current/loadcfg.c,v $
@@ -124,6 +124,8 @@ static struct file_list *current_configfile = NULL;
 #define hash_allow_cgi_request_crunching  258915987U /* "allow-cgi-request-crunching" */
 #define hash_buffer_limit                1881726070U /* "buffer-limit */
 #define hash_client_header_order         2701453514U /* "client-header-order" */
+#define hash_client_specific_tag         3353703383U /* "client-specific-tag" */
+#define hash_client_tag_lifetime          647957580U /* "client-tag-lifetime" */
 #define hash_compression_level           2464423563U /* "compression-level" */
 #define hash_confdir                        1978389U /* "confdir" */
 #define hash_connection_sharing          1348841265U /* "connection-sharing" */
@@ -176,6 +178,9 @@ static struct file_list *current_configfile = NULL;
 
 
 static void savearg(char *command, char *argument, struct configuration_spec * config);
+#ifdef FEATURE_CLIENT_TAGS
+static void free_client_specific_tags(struct client_tag_spec *tag_list);
+#endif
 
 /*********************************************************************
  *
@@ -253,6 +258,10 @@ static void unload_configfile (void * data)
    list_remove_all(config->trust_info);
 #endif /* def FEATURE_TRUST */
 
+#ifdef FEATURE_CLIENT_TAGS
+   free_client_specific_tags(config->client_tags);
+#endif
+
    freez(config);
 }
 
@@ -281,6 +290,85 @@ void unload_current_config_file(void)
 #endif
 
 
+#ifdef FEATURE_CLIENT_TAGS
+/*********************************************************************
+ *
+ * Function    :  register_tag
+ *
+ * Description :  Registers a client-specific-tag and its description
+ *
+ * Parameters  :
+ *          1  :  config: The tag list
+ *          2  :  name:  The name of the client-specific-tag
+ *          3  :  description: The human-readable description for the tag
+ *
+ * Returns     :  N/A
+ *
+ *********************************************************************/
+static void register_tag(struct client_tag_spec *tag_list,
+   const char *name, const char *description)
+{
+   struct client_tag_spec *new_tag;
+   struct client_tag_spec *last_tag;
+
+   last_tag = tag_list;
+   while (last_tag->next != NULL)
+   {
+      last_tag = last_tag->next;
+   }
+   if (last_tag->name == NULL)
+   {
+      /* First entry */
+      new_tag = last_tag;
+   }
+   else
+   {
+      new_tag = zalloc_or_die(sizeof(struct client_tag_spec));
+   }
+   new_tag->name = strdup_or_die(name);
+   new_tag->description = strdup_or_die(description);
+   if (new_tag != last_tag)
+   {
+      last_tag->next = new_tag;
+   }
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  free_client_specific_tags
+ *
+ * Description :  Frees client-specific tags and their descriptions
+ *
+ * Parameters  :
+ *          1  :  tag_list: The tag list to free
+ *
+ * Returns     :  N/A
+ *
+ *********************************************************************/
+static void free_client_specific_tags(struct client_tag_spec *tag_list)
+{
+   struct client_tag_spec *this_tag;
+   struct client_tag_spec *next_tag;
+
+   next_tag = tag_list;
+   do
+   {
+      this_tag = next_tag;
+      next_tag = next_tag->next;
+
+      freez(this_tag->name);
+      freez(this_tag->description);
+
+      if (this_tag != tag_list)
+      {
+         freez(this_tag);
+      }
+   } while (next_tag != NULL);
+}
+#endif /* def FEATURE_CLIENT_TAGS */
+
+
 /*********************************************************************
  *
  * Function    :  parse_numeric_value
@@ -667,6 +755,49 @@ struct configuration_spec * load_config(void)
             parse_client_header_order(config->ordered_client_headers, arg);
             break;
 
+/* *************************************************************************
+ * client-specific-tag tag-name description
+ * *************************************************************************/
+#ifdef FEATURE_CLIENT_TAGS
+         case hash_client_specific_tag:
+            {
+               char *name;
+               char *description;
+
+               name = arg;
+               description = strstr(arg, " ");
+               if (description == NULL)
+               {
+                  log_error(LOG_LEVEL_FATAL,
+                     "client-specific-tag '%s' lacks a description.", name);
+               }
+               *description = '\0';
+               description++;
+               register_tag(config->client_tags, name, description);
+            }
+            break;
+#endif /* def FEATURE_CLIENT_TAGS */
+
+/* *************************************************************************
+ * client-tag-lifetime ttl
+ * *************************************************************************/
+#ifdef FEATURE_CLIENT_TAGS
+         case hash_client_tag_lifetime:
+         {
+            int ttl = parse_numeric_value(cmd, arg);
+            if (0 <= ttl)
+            {
+               config->client_tag_lifetime = (unsigned)ttl;
+            }
+            else
+            {
+               log_error(LOG_LEVEL_FATAL,
+                  "client-tag-lifetime can't be negative.");
+            }
+            break;
+         }
+#endif /* def FEATURE_CLIENT_TAGS */
+
 /* *************************************************************************
  * confdir directory-name
  * *************************************************************************/
index 9b54879..f3aa09c 100644 (file)
--- a/project.h
+++ b/project.h
@@ -1,7 +1,7 @@
 #ifndef PROJECT_H_INCLUDED
 #define PROJECT_H_INCLUDED
 /** Version string. */
-#define PROJECT_H_VERSION "$Id: project.h,v 1.211 2016/01/16 12:30:28 fabiankeil Exp $"
+#define PROJECT_H_VERSION "$Id: project.h,v 1.212 2016/01/16 12:30:43 fabiankeil Exp $"
 /*********************************************************************
  *
  * File        :  $Source: /cvsroot/ijbswa/current/project.h,v $
@@ -400,6 +400,9 @@ struct pattern_spec
 /** Pattern spec bitmap: It's a NO-RESPONSE-TAG pattern. */
 #define PATTERN_SPEC_NO_RESPONSE_TAG_PATTERN 0x00000008UL
 
+/** Pattern spec bitmap: It's a CLIENT-TAG pattern. */
+#define PATTERN_SPEC_CLIENT_TAG_PATTERN      0x00000010UL
+
 /**
  * An I/O buffer.  Holds a string which can be appended to, and can have data
  * removed from the beginning.
@@ -947,6 +950,11 @@ struct client_state
    /** List of all tags that apply to this request */
    struct list tags[1];
 
+#ifdef FEATURE_CLIENT_TAGS
+   /** List of all tags that apply to this client (assigned based on address) */
+   struct list client_tags[1];
+#endif
+
    /** MIME-Type key, see CT_* above */
    unsigned int content_type;
 
@@ -1202,6 +1210,15 @@ struct access_control_list
 /** Maximum number of loaders (actions, re_filter, ...) */
 #define NLOADERS 8
 
+/**
+ * This struct represents a client-spcific-tag and it's description
+ */
+struct client_tag_spec
+{
+   char *name;        /**< Name from "client-specific-tag bla" directive */
+   char *description; /**< Description from "client-specific-tag-description " directive */
+   struct client_tag_spec *next; /**< The pointer for chaining. */
+};
 
 /** configuration_spec::feature_flags: CGI actions editor. */
 #define RUNTIME_FEATURE_CGI_EDIT_ACTIONS             1U
@@ -1324,6 +1341,13 @@ struct configuration_spec
 
 #endif /* def FEATURE_TRUST */
 
+#ifdef FEATURE_CLIENT_TAGS
+   struct client_tag_spec client_tags[1];
+
+   /* Maximum number of seconds a temporarily enabled tag stays enabled. */
+   unsigned int client_tag_lifetime;
+#endif /* def FEATURE_CLIENT_TAGS */
+
 #ifdef FEATURE_ACL
 
    /** The access control list (ACL). */
diff --git a/templates/show-client-tags b/templates/show-client-tags
new file mode 100644 (file)
index 0000000..d448218
--- /dev/null
@@ -0,0 +1,149 @@
+##########################################################
+#
+# Show-Request-CGI Output template for Privoxy.
+#
+#
+# USING HTML TEMPLATES:
+# ---------------------
+#
+# Template files are written win plain HTML, with a few
+# additions:
+#
+# - Lines that start with a '#' character like this one
+#   are ignored
+#
+# - Each item in the below list of exported symbols will
+#   be replaced by dynamically generated text, if they
+#   are enclosed in '@'-characters. E.g. The string @version@
+#   will be replaced by the version number of Privoxy.
+#
+# - One special application of this is to make whole blocks
+#   of the HTML template disappear if the condition <name>
+#   is not given. Simply enclose the block between the two
+#   strings @if-<name>start and if-<name>-end@. The strings
+#   should be placed in HTML comments (<!-- -->), so the
+#   html structure won't be messed when the magic happens.
+#
+# USABLE SYMBOLS IN THIS TEMPLATE:
+# --------------------------------
+#
+#  my-ip-addr:
+#    The IP-address that the client used to reach this proxy
+#  my-hostname:
+#    The hostname associated with my-ip-addr
+#  admin-address:
+#    The email address of the pxoxy's administrator, as configured
+#    in the config file
+#  default-cgi:
+#    The URL for the "main menu" builtin CGI of this proxy
+#  menu:
+#    List of <li> elements linking to the other available CGIs
+#  version:
+#    The version number of the proxy software
+#  code-status:
+#    The development status of the proxy software: "alpha", "beta",
+#    or "stable".
+#  homepage:
+#    The URL of the SourceForge ijbswa project, who maintains this
+#    software.
+#  client-request:
+#    The request and headers that the client sent.
+#  processed-request:
+#    What we would have rewritten this request to, if this had not
+#    been intercepted.
+#
+# CONDITIONAL SYMBOLS FOR THIS TEMPLATE AND THEIR DEPANDANT SYMBOLS:
+# ------------------------------------------------------------------
+#
+#  unstable:
+#    this is an alpha or beta release of the proxy software
+#  have-adminaddr-info:
+#    An e-mail address for the local Privoxy administrator has
+#    been specified and is available through the "admin-address"
+#    symbol
+#  have-proxy-info:
+#    A URL for online documentation about this proxy has been
+#    specified and is available through the "proxy-info-url"
+#    symbol
+#  have-help-info:
+#    If either have-proxy-info is true or have-adminaddr-info is
+#    true, have-help-info is true.  Used to conditionally include
+#    a grey box for any and all help info.
+#
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html>
+
+<head>
+  <title>Privoxy@@my-hostname@</title>
+  <meta http-equiv="Content-Style-Type" content="text/css">
+  <meta http-equiv="Content-Script-Type" content="text/javascript">
+  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+  <meta name="robots" content="noindex,nofollow">
+  <link rel="stylesheet" type="text/css" href="@default-cgi@send-stylesheet">
+  <link rel="shortcut icon" href="@default-cgi@favicon.ico" type="image/x-icon">
+</head>
+
+<body>
+
+  <table cellpadding="20" cellspacing="10" border="0" width="100%">
+    <tr>
+      <td class="title">
+
+#include mod-title
+
+      </td>
+    </tr>
+
+<!-- @if-unstable-start -->
+# This will only appear if CODE_STATUS is "alpha" or "beta". See configure.in
+    <tr>
+      <td class="warning">
+
+#include mod-unstable-warning
+
+      </td>
+    </tr>
+<!-- if-unstable-end@ -->
+
+    <tr>
+      <td class="box">
+       <h2>Show client tags</h2>
+          <p>
+            Here you see the client tags that are or can be optionally set based for
+            client with address @client-ip-addr@:
+          </p>
+
+        @client-tags@
+
+      </td>
+    </tr>
+
+     <tr>
+      <td class="box">
+        <h2>More Privoxy:</h2>
+        <ul>@menu@<li><a href="@user-manual@">Documentation</a></li></ul>
+      </td>
+    </tr>
+
+    <tr>
+      <td class="info">
+
+#include mod-support-and-service
+
+      </td>
+    </tr>
+
+<!-- @if-have-help-info-start -->
+    <tr>
+      <td class="info">
+
+#include mod-local-help
+
+      </td>
+    </tr>
+<!-- if-have-help-info-end@ -->
+
+  </table>
+
+</body>
+</html>
index e0024ed..78349ae 100644 (file)
                  @endif-FEATURE_CGI_EDIT_ACTIONS@web-based actions file
                  editor@if-FEATURE_CGI_EDIT_ACTIONS-then@</a>@else-not-FEATURE_CGI_EDIT_ACTIONS@@endif-FEATURE_CGI_EDIT_ACTIONS@.</td>
             </tr>
+            <tr>
+              <td><code>FEATURE_CLIENT_TAGS</code></td>
+              <td>@if-FEATURE_CLIENT_TAGS-then@ Yes @else-not-FEATURE_CLIENT_TAGS@ No @endif-FEATURE_CLIENT_TAGS@</td>
+              <td>
+               Allows clients to request to be tagged.
+              </td>
+            </tr>
             <tr>
               <td><code>FEATURE_COMPRESSION</code></td>
               <td>@if-FEATURE_COMPRESSION-then@ Yes @else-not-FEATURE_COMPRESSION@ No @endif-FEATURE_COMPRESSION@</td>
index 418a949..4839c2b 100644 (file)
@@ -1,4 +1,4 @@
-const char urlmatch_rcs[] = "$Id: urlmatch.c,v 1.86 2015/12/27 12:47:17 fabiankeil Exp $";
+const char urlmatch_rcs[] = "$Id: urlmatch.c,v 1.87 2016/02/26 12:29:39 fabiankeil Exp $";
 /*********************************************************************
  *
  * File        :  $Source: /cvsroot/ijbswa/current/urlmatch.c,v $
@@ -1161,6 +1161,9 @@ jb_err create_pattern_spec(struct pattern_spec *pattern, char *buf)
       const unsigned flag;
    } tag_pattern[] = {
       { "TAG:",              4, PATTERN_SPEC_TAG_PATTERN},
+ #ifdef FEATURE_CLIENT_TAGS
+      { "CLIENT-TAG:",      11, PATTERN_SPEC_CLIENT_TAG_PATTERN},
+ #endif
       { "NO-REQUEST-TAG:",  15, PATTERN_SPEC_NO_REQUEST_TAG_PATTERN},
       { "NO-RESPONSE-TAG:", 16, PATTERN_SPEC_NO_RESPONSE_TAG_PATTERN}
    };