Bug 3349: Bad support for adaptation service URIs with '=' 

Currently using URIs  which  include "=" is not supported by 
ecap_service/icap_service configuration parameters. Also the squid.conf
documentation about ecap_service is outdated.

This patch 
- Fixes the [e|i]cap_service line parser to allow URIs with "="
- Changes the [e|i]cap_service configuration parameter to use the following syntax:
   ecap_service id vectoring_point uri [name=value ...]
- Check for duplicated options
- Fixes the related documentation 
- Also the older [e|i]cap_service syntax forms are supported:
   ecap_service id vectoring_point [1|0] uri 
   ecap_service id vectoring_point [name=value ...] uri 
- The "uri" options is not documented but supported.

This is a Measurement Factory project.
=== modified file 'src/adaptation/ServiceConfig.cc'
--- src/adaptation/ServiceConfig.cc	2011-05-24 22:26:21 +0000
+++ src/adaptation/ServiceConfig.cc	2011-10-05 08:30:22 +0000
@@ -6,6 +6,7 @@
 #include "ConfigParser.h"
 #include "adaptation/ServiceConfig.h"
 #include "ip/tools.h"
+#include <set>
 
 Adaptation::ServiceConfig::ServiceConfig():
         port(-1), method(methodNone), point(pointNone),
@@ -69,29 +70,39 @@
     bypass = routing = false;
 
     // handle optional service name=value parameters
-    const char *lastOption = NULL;
     bool grokkedUri = false;
     bool onOverloadSet = false;
+    std::set<std::string> options;
+    
     while (char *option = strtok(NULL, w_space)) {
+        const char *name = option;
+        const char *value = "";
         if (strcmp(option, "0") == 0) { // backward compatibility
-            bypass = false;
-            continue;
-        }
-        if (strcmp(option, "1") == 0) { // backward compatibility
-            bypass = true;
-            continue;
-        }
-
-        const char *name = option;
-        char *value = strstr(option, "=");
-        if (!value) {
-            lastOption = option;
-            break;
-        }
-        *value = '\0'; // terminate option name
-        ++value; // skip '='
-
-        // TODO: warn if option is set twice?
+            name = "bypass";
+            value = "off";
+        }  else if (strcmp(option, "1") == 0) { // backward compatibility
+            name = "bypass";
+            value = "on";
+        } else {
+            char *eq = strstr(option, "=");
+            const char *sffx = strstr(option, "://");
+            if (!eq || (sffx && sffx < eq)) { //no "=" or has the form "icap://host?arg=val"
+                name = "uri";
+                value = option;
+            }  else { // a normal name=value option
+                *eq = '\0'; // terminate option name
+                value = eq + 1; // skip '='
+            }
+        }
+
+        // Check if option is set twice
+        if (options.find(name) != options.end()) {
+            debugs(3, DBG_CRITICAL, cfg_filename << ':' << config_lineno << ": " << 
+                   "Douplicate option \"" << name << "\" in ataptation service definition");
+            return false;
+        }
+        options.insert(name);
+
         bool grokked = false;
         if (strcmp(name, "bypass") == 0) {
             grokked = grokBool(bypass, name, value);
@@ -119,14 +130,10 @@
     if (!onOverloadSet)
         onOverload = bypass ? srvBypass : srvWait;
 
-    // what is left must be the service URI
-    if (!grokkedUri && !grokUri(lastOption))
-        return false;
-
-    // there should be nothing else left
-    if (const char *tail = strtok(NULL, w_space)) {
-        debugs(3, 0, cfg_filename << ':' << config_lineno << ": " <<
-               "garbage after adaptation service URI: " << tail);
+    // is the service URI set?
+    if (!grokkedUri) {
+        debugs(3, DBG_CRITICAL, cfg_filename << ':' << config_lineno << ": " << 
+               "No \"uri\" option in adaptation service definition");
         return false;
     }
 

=== modified file 'src/cf.data.pre'
--- src/cf.data.pre	2011-09-22 00:46:26 +0000
+++ src/cf.data.pre	2011-10-05 08:29:23 +0000
@@ -6596,17 +6596,19 @@
 DOC_START
 	Defines a single ICAP service using the following format:
 
-	icap_service service_name vectoring_point [options] service_url
+	icap_service id vectoring_point uri [option ...]
 
-	service_name: ID
-		an opaque identifier which must be unique in squid.conf
+	id: ID
+		an opaque identifier or name which is used to direct traffic to
+		this specific service. Must be unique among all adaptation
+		services in squid.conf.
 
 	vectoring_point: reqmod_precache|reqmod_postcache|respmod_precache|respmod_postcache
 		This specifies at which point of transaction processing the
 		ICAP service should be activated. *_postcache vectoring points
 		are not yet supported.
 
-	service_url: icap://servername:port/servicepath
+	uri: icap://servername:port/servicepath
 		ICAP server and service location.
 
 	ICAP does not allow a single service to handle both REQMOD and RESPMOD
@@ -6675,8 +6677,8 @@
 	deprecated but supported for backward compatibility.
 
 Example:
-icap_service svcBlocker reqmod_precache bypass=0 icap://icap1.mydomain.net:1344/reqmod
-icap_service svcLogger reqmod_precache routing=on icap://icap2.mydomain.net:1344/respmod
+icap_service svcBlocker reqmod_precache icap://icap1.mydomain.net:1344/reqmod bypass=0
+icap_service svcLogger reqmod_precache icap://icap2.mydomain.net:1344/respmod routing=on
 DOC_END
 
 NAME: icap_class
@@ -6728,25 +6730,56 @@
 DOC_START
 	Defines a single eCAP service
 
-	ecap_service servicename vectoring_point bypass service_url
-
-	vectoring_point = reqmod_precache|reqmod_postcache|respmod_precache|respmod_postcache
+	ecap_service id vectoring_point uri [option ...]
+
+        id: ID
+		an opaque identifier or name which is used to direct traffic to
+		this specific service. Must be unique among all adaptation
+		services in squid.conf.
+
+	vectoring_point: reqmod_precache|reqmod_postcache|respmod_precache|respmod_postcache
 		This specifies at which point of transaction processing the
 		eCAP service should be activated. *_postcache vectoring points
 		are not yet supported.
-	bypass = 1|0
-		If set to 1, the eCAP service is treated as optional. If the
-		service cannot be reached or malfunctions, Squid will try to
-		ignore any errors and process the message as if the service
+
+	uri: ecap://vendor/service_name?custom&cgi=style&parameters=optional
+		Squid uses the eCAP service URI to match this configuration
+		line with one of the dynamically loaded services. Each loaded
+		eCAP service must have a unique URI. Obtain the right URI from
+		the service provider.
+
+
+	Service options are separated by white space. eCAP services support
+	the following name=value options:
+
+	bypass=on|off|1|0
+		If set to 'on' or '1', the eCAP service is treated as optional.
+		If the service cannot be reached or malfunctions, Squid will try
+		to ignore any errors and process the message as if the service
 		was not enabled. No all eCAP errors can be bypassed.
-		If set to 0, the eCAP service is treated as essential and all
-		eCAP errors will result in an error page returned to the
+		If set to 'off' or '0', the eCAP service is treated as essential
+		and all eCAP errors will result in an error page returned to the
 		HTTP client.
-	service_url = ecap://vendor/service_name?custom&cgi=style&parameters=optional
+
+                Bypass is off by default: services are treated as essential.
+
+	routing=on|off|1|0
+		If set to 'on' or '1', the eCAP service is allowed to
+		dynamically change the current message adaptation plan by
+		returning a chain of services to be used next.
+
+		Dynamic adaptation plan may cross or cover multiple supported
+		vectoring points in their natural processing order.
+
+		Routing is not allowed by default.
+
+	Older ecap_service format without optional named parameters is
+	deprecated but supported for backward compatibility.
+
 
 Example:
-ecap_service service_1 reqmod_precache 0 ecap://filters-R-us/leakDetector?on_error=block
-ecap_service service_2 respmod_precache 1 icap://filters-R-us/virusFilter?config=/etc/vf.cfg
+ecap_service s1 reqmod_precache ecap://filters.R.us/leakDetector?on_error=block bypass=off
+ecap_service s2 respmod_precache ecap://filters.R.us/virusFilter config=/etc/vf.cfg bypass=on
 DOC_END
 
 NAME: loadable_modules