[squid-dev] [PATCH] external_acl_type logformat tokens

Amos Jeffries squid3 at treenet.co.nz
Sat Oct 3 08:35:07 UTC 2015


Update the external_acl_type helper interface to use libformat and thus
make any logformat token valid in its format parameter field.

As a result much of the logic surrounding format code parsing, display
and helper query generation has been completely dropped. What remains is
a basic parse loop handling backward compatibility for the unusual
%CERT_* token syntax, space delimiter and field default encodings.


Extensions to logformat resulting from the merger:

* adds \-escape encoding of output fields

* allows {arg} field to be placed before or after the format code.

* extended to accept the old external_acl_type %macros. But not
documented, these are deprecated and only for backward compatibility.

* extended to support outputting formats without a format-name prefix.


The major side effect of this change is that these ACLs now require
AccessLogEntry to be filled out with state data, rather than just the
ACLChecklist object members.

The requires*() mechanism of ACLChecklist has been extended to catch
some cases resulting from missing the ALE entirely. But it cannot catch
the more subtle problem of data members inside the ALE being unset.


I have given it some basic testing and the commonly used %macros work
fine for the common use cases. So I believe it is suitable for inclusion
into trunk. But this definitely needs wider testing to catch uncommon
and rare edge cases where the ALE object is not filled out early enough,
or not presented by the checklist.

Amos
-------------- next part --------------
=== modified file 'src/AccessLogEntry.h'
--- src/AccessLogEntry.h	2015-09-14 16:25:05 +0000
+++ src/AccessLogEntry.h	2015-10-02 10:25:46 +0000
@@ -186,43 +186,50 @@
     /** \brief This subclass holds general adaptation log info.
      * \todo Inner class declarations should be moved outside.
      */
     class AdaptationDetails
     {
 
     public:
         AdaptationDetails(): last_meta(NULL) {}
 
         /// image of the last ICAP response header or eCAP meta received
         char *last_meta;
     } adapt;
 #endif
 
     // Why is this a sub-class and not a set of real "private:" fields?
     // TODO: shuffle this to the relevant ICP/HTCP protocol section
     class Private
     {
 
     public:
-        Private() : method_str(NULL) {}
+        Private() : method_str(NULL), lastAclName(NULL), lastAclData(NULL) {}
+        ~Private() {
+            safe_free(lastAclName);
+            safe_free(lastAclData);
+        }
 
         const char *method_str;
+        const char *lastAclName; ///< string for external_acl_type %ACL format code
+        const char *lastAclData; ///< string for external_acl_type %DATA format code
+
     } _private;
     HierarchyLogEntry hier;
     HttpReply *reply;
     HttpRequest *request; //< virgin HTTP request
     HttpRequest *adapted_request; //< HTTP request after adaptation and redirection
 
     /// key:value pairs set by squid.conf note directive and
     /// key=value pairs returned from URL rewrite/redirect helper
     NotePairs::Pointer notes;
 
 #if ICAP_CLIENT
     /** \brief This subclass holds log info for ICAP part of request
      *  \todo Inner class declarations should be moved outside
      */
     class IcapLogEntry
     {
     public:
         IcapLogEntry() : reqMethod(Adaptation::methodNone), bytesSent(0), bytesRead(0),
             bodyBytesRead(-1), request(NULL), reply(NULL),
             outcome(Adaptation::Icap::xoUnknown), resStatus(Http::scNone)

=== modified file 'src/ExternalACL.h'
--- src/ExternalACL.h	2015-02-04 16:45:30 +0000
+++ src/ExternalACL.h	2015-05-29 10:33:44 +0000
@@ -34,40 +34,41 @@
 
 #include "acl/Acl.h"
 
 class ACLExternal : public ACL
 {
     MEMPROXY_CLASS(ACLExternal);
 
 public:
     static void ExternalAclLookup(ACLChecklist * ch, ACLExternal *);
 
     ACLExternal(char const *);
     ACLExternal(ACLExternal const &);
     ~ACLExternal();
     ACLExternal&operator=(ACLExternal const &);
 
     virtual ACL *clone()const;
     virtual char const *typeString() const;
     virtual void parse();
     virtual int match(ACLChecklist *checklist);
     /* This really should be dynamic based on the external class defn */
+    virtual bool requiresAleXXX() const {return true;}
     virtual bool requiresRequest() const {return true;}
 
     /* when requiresRequest is made dynamic, review this too */
     //    virtual bool requiresReply() const {return true;}
     virtual bool isProxyAuth() const;
     virtual SBufList dump() const;
     virtual bool valid () const;
     virtual bool empty () const;
 
 protected:
     static Prototype RegistryProtoype;
     static ACLExternal RegistryEntry_;
     external_acl_data *data;
     char const *class_;
 };
 
 void parse_externalAclHelper(external_acl **);
 void dump_externalAclHelper(StoreEntry * sentry, const char *name, const external_acl *);
 void free_externalAclHelper(external_acl **);
 typedef void EAH(void *data, const ExternalACLEntryPointer &result);

=== modified file 'src/Makefile.am'
--- src/Makefile.am	2015-09-25 10:12:56 +0000
+++ src/Makefile.am	2015-09-28 00:41:05 +0000
@@ -3445,40 +3445,41 @@
 	HttpHeader.h \
 	HttpHeader.cc \
 	HttpHeaderFieldInfo.h \
 	HttpHeaderTools.h \
 	HttpHeaderTools.cc \
 	HttpMsg.cc \
 	HttpReply.cc \
 	PeerPoolMgr.h \
 	PeerPoolMgr.cc \
 	RequestFlags.h \
 	RequestFlags.cc \
 	HttpRequest.cc \
 	icp_v2.cc \
 	icp_v3.cc \
 	$(IPC_SOURCE) \
 	ipcache.cc \
 	int.h \
 	int.cc \
 	internal.h \
 	internal.cc \
+	tests/stub_libeui.cc \
 	LogTags.cc \
 	SquidList.h \
 	SquidList.cc \
 	MasterXaction.cc \
 	MasterXaction.h \
 	multicast.h \
 	multicast.cc \
 	mem_node.cc \
 	MemBuf.cc \
 	MemObject.cc \
 	mime.h \
 	mime.cc \
 	mime_header.h \
 	mime_header.cc \
 	neighbors.h \
 	neighbors.cc \
 	Notes.h \
 	Notes.cc \
 	Parsing.cc \
 	pconn.cc \

=== modified file 'src/acl/Acl.cc'
--- src/acl/Acl.cc	2015-08-24 14:20:07 +0000
+++ src/acl/Acl.cc	2015-09-30 09:36:05 +0000
@@ -136,41 +136,44 @@
     *name = 0;
 }
 
 bool ACL::valid () const
 {
     return true;
 }
 
 bool
 ACL::matches(ACLChecklist *checklist) const
 {
     PROF_start(ACL_matches);
     debugs(28, 5, "checking " << name);
 
     // XXX: AclMatchedName does not contain a matched ACL name when the acl
     // does not match. It contains the last (usually leaf) ACL name checked
     // (or is NULL if no ACLs were checked).
     AclMatchedName = name;
 
     int result = 0;
-    if (!checklist->hasRequest() && requiresRequest()) {
+    if (!checklist->hasAleXXX() && requiresAleXXX()) {
+        debugs(28, DBG_IMPORTANT, "WARNING: " << name << " ACL is used in " <<
+               "context without an ALE state. Assuming mismatch.");
+    } else if (!checklist->hasRequest() && requiresRequest()) {
         debugs(28, DBG_IMPORTANT, "WARNING: " << name << " ACL is used in " <<
                "context without an HTTP request. Assuming mismatch.");
     } else if (!checklist->hasReply() && requiresReply()) {
         debugs(28, DBG_IMPORTANT, "WARNING: " << name << " ACL is used in " <<
                "context without an HTTP response. Assuming mismatch.");
     } else {
         // have to cast because old match() API is missing const
         result = const_cast<ACL*>(this)->match(checklist);
     }
 
     const char *extra = checklist->asyncInProgress() ? " async" : "";
     debugs(28, 3, "checked: " << name << " = " << result << extra);
     PROF_stop(ACL_matches);
     return result == 1; // true for match; false for everything else
 }
 
 void
 ACL::context(const char *aName, const char *aCfgLine)
 {
     name[0] = '\0';
@@ -351,40 +354,46 @@
 
 void
 aclCacheMatchFlush(dlink_list * cache)
 {
     acl_proxy_auth_match_cache *auth_match;
     dlink_node *link, *tmplink;
     link = cache->head;
 
     debugs(28, 8, "aclCacheMatchFlush called for cache " << cache);
 
     while (link) {
         auth_match = (acl_proxy_auth_match_cache *)link->data;
         tmplink = link;
         link = link->next;
         dlinkDelete(tmplink, cache);
         delete auth_match;
     }
 }
 
 bool
+ACL::requiresAleXXX() const
+{
+    return false;
+}
+
+bool
 ACL::requiresReply() const
 {
     return false;
 }
 
 bool
 ACL::requiresRequest() const
 {
     return false;
 }
 
 /*********************/
 /* Destroy functions */
 /*********************/
 
 ACL::~ACL()
 {
     debugs(28, 3, "freeing ACL " << name);
     safe_free(cfgline);
     AclMatchedName = NULL; // in case it was pointing to our name

=== modified file 'src/acl/Acl.h'
--- src/acl/Acl.h	2015-08-24 14:20:07 +0000
+++ src/acl/Acl.h	2015-09-28 00:41:05 +0000
@@ -124,40 +124,42 @@
         ~Prototype();
         static bool Registered(char const *);
         static ACL *Factory(char const *);
 
     private:
         ACL const *prototype;
         char const *typeString;
 
     private:
         static std::vector<Prototype const *> * Registry;
         static void *Initialized;
         typedef std::vector<Prototype const*>::iterator iterator;
         typedef std::vector<Prototype const*>::const_iterator const_iterator;
         void registerMe();
     };
 
 private:
     /// Matches the actual data in checklist against this ACL.
     virtual int match(ACLChecklist *checklist) = 0; // XXX: missing const
 
+    /// whether our (i.e. shallow) match() requires checklist to have a AccessLogEntry
+    virtual bool requiresAleXXX() const;
     /// whether our (i.e. shallow) match() requires checklist to have a request
     virtual bool requiresRequest() const;
     /// whether our (i.e. shallow) match() requires checklist to have a reply
     virtual bool requiresReply() const;
 };
 
 /// \ingroup ACLAPI
 typedef enum {
     // Authorization ACL result states
     ACCESS_DENIED,
     ACCESS_ALLOWED,
     ACCESS_DUNNO,
 
     // Authentication ACL result states
     ACCESS_AUTH_REQUIRED,    // Missing Credentials
 } aclMatchCode;
 
 /// \ingroup ACLAPI
 /// ACL check answer; TODO: Rename to Acl::Answer
 class allow_t

=== modified file 'src/acl/Checklist.h'
--- src/acl/Checklist.h	2015-08-30 20:36:48 +0000
+++ src/acl/Checklist.h	2015-09-28 00:41:05 +0000
@@ -146,40 +146,41 @@
     /// whether markFinished() was called
     bool finished() const { return finished_; }
     /// async call has been started and has not finished (or failed) yet
     bool asyncInProgress() const { return asyncStage_ != asyncNone; }
     /// called when no more ACLs should be checked; sets the final answer and
     /// prints a debugging message explaining the reason for that answer
     void markFinished(const allow_t &newAnswer, const char *reason);
 
     const allow_t &currentAnswer() const { return allow_; }
 
     /// whether the action is banned or not
     bool bannedAction(const allow_t &action) const;
     /// add action to the list of banned actions
     void banAction(const allow_t &action);
 
     // XXX: ACLs that need request or reply have to use ACLFilledChecklist and
     // should do their own checks so that we do not have to povide these two
     // for ACL::checklistMatches to use
     virtual bool hasRequest() const = 0;
     virtual bool hasReply() const = 0;
+    virtual bool hasAleXXX() const = 0;
 
     /// change the current ACL list
     /// \return a pointer to the old list value (may be nullptr)
     const Acl::Tree *changeAcl(const Acl::Tree *t) {
         const Acl::Tree *old = accessList;
         if (t != accessList) {
             cbdataReferenceDone(accessList);
             accessList = cbdataReference(t);
         }
         return old;
     }
 
 private:
     /// Calls non-blocking check callback with the answer and destroys self.
     void checkCallback(allow_t answer);
 
     void matchAndFinish();
 
     void changeState(AsyncState *);
     AsyncState *asyncState() const;

=== modified file 'src/acl/FilledChecklist.h'
--- src/acl/FilledChecklist.h	2015-09-14 16:25:05 +0000
+++ src/acl/FilledChecklist.h	2015-09-28 00:41:05 +0000
@@ -45,67 +45,68 @@
     ConnStateData * conn() const;
 
     /// The client side fd. It uses conn() if available
     int fd() const;
 
     /// set either conn
     void conn(ConnStateData *);
     /// set the client side FD
     void fd(int aDescriptor);
 
     //int authenticated();
 
     bool destinationDomainChecked() const;
     void markDestinationDomainChecked();
     bool sourceDomainChecked() const;
     void markSourceDomainChecked();
 
     // ACLChecklist API
     virtual bool hasRequest() const { return request != NULL; }
     virtual bool hasReply() const { return reply != NULL; }
+    virtual bool hasAleXXX() const { return al != NULL; }
 
 public:
     Ip::Address src_addr;
     Ip::Address dst_addr;
     Ip::Address my_addr;
     SBuf dst_peer_name;
     char *dst_rdns;
 
     HttpRequest *request;
     HttpReply *reply;
 
     char rfc931[USER_IDENT_SZ];
 #if USE_AUTH
     Auth::UserRequest::Pointer auth_user_request;
 #endif
 #if SQUID_SNMP
     char *snmp_community;
 #endif
 
 #if USE_OPENSSL
     /// SSL [certificate validation] errors, in undefined order
     Ssl::CertErrors *sslErrors;
     /// The peer certificate
     Security::CertPointer serverCert;
 #endif
 
-    AccessLogEntry::Pointer al; ///< info for the future access.log entry
+    AccessLogEntry::Pointer al; ///< info for the future access.log, and external ACL
 
     ExternalACLEntryPointer extacl_entry;
 
     err_type requestErrorType;
 
 private:
     ConnStateData * conn_;          /**< hack for ident and NTLM */
     int fd_;                        /**< may be available when conn_ is not */
     bool destinationDomainChecked_;
     bool sourceDomainChecked_;
     /// not implemented; will cause link failures if used
     ACLFilledChecklist(const ACLFilledChecklist &);
     /// not implemented; will cause link failures if used
     ACLFilledChecklist &operator=(const ACLFilledChecklist &);
 };
 
 /// convenience and safety wrapper for dynamic_cast<ACLFilledChecklist*>
 inline
 ACLFilledChecklist *Filled(ACLChecklist *checklist)
 {

=== modified file 'src/cf.data.pre'
--- src/cf.data.pre	2015-08-30 01:07:47 +0000
+++ src/cf.data.pre	2015-10-02 10:15:34 +0000
@@ -700,141 +700,111 @@
 DOC_END
 
 COMMENT_START
  ACCESS CONTROLS
  -----------------------------------------------------------------------------
 COMMENT_END
 
 NAME: external_acl_type
 TYPE: externalAclHelper
 LOC: Config.externalAclHelperList
 DEFAULT: none
 DOC_START
 	This option defines external acl classes using a helper program
 	to look up the status
 
 	  external_acl_type name [options] FORMAT.. /path/to/helper [helper arguments..]
 
 	Options:
 
 	  ttl=n		TTL in seconds for cached results (defaults to 3600
-	  		for 1 hour)
+			for 1 hour)
 
 	  negative_ttl=n
-	  		TTL for cached negative lookups (default same
-	  		as ttl)
+			TTL for cached negative lookups (default same
+			as ttl)
 
 	  grace=n	Percentage remaining of TTL where a refresh of a
 			cached entry should be initiated without needing to
 			wait for a new reply. (default is for no grace period)
 
 	  cache=n	Limit the result cache size, default is 262144.
 			The expanded FORMAT value is used as the cache key, so
 			if the details in FORMAT are highly variable a larger
 			cache may be needed to produce reduction in helper load.
 
 	  children-max=n
 			Maximum number of acl helper processes spawned to service
 			external acl lookups of this type. (default 20)
 
 	  children-startup=n
 			Minimum number of acl helper processes to spawn during
 			startup and reconfigure to service external acl lookups
 			of this type. (default 0)
 
 	  children-idle=n
 			Number of acl helper processes to keep ahead of traffic
 			loads. Squid will spawn this many at once whenever load
 			rises above the capabilities of existing processes.
 			Up to the value of children-max. (default 1)
 
 	  concurrency=n	concurrency level per process. Only used with helpers
 			capable of processing more than one query at a time.
 
 	  queue-size=N  The queue-size= option sets the maximum number of queued
 			requests. If the queued requests exceed queue size 
 			the acl is ignored.
 			The default value is set to 2*children-max.
 
 	  protocol=2.5	Compatibility mode for Squid-2.5 external acl helpers.
 
 	  ipv4 / ipv6	IP protocol used to communicate with this helper.
 			The default is to auto-detect IPv6 and use it when available.
 
 
-	FORMAT specifications
+	FORMAT is a series of %macro codes. See logformat directive for a full list
+	of the accepted codes. Although note that at the time of any external ACL
+	being tested data may not be available and thus some %macro expand to '-'.
 
-	  %LOGIN	Authenticated user login name
-	  %un		A user name. Expands to the first available name
-	  		from the following list of information sources:
-			- authenticated user name, like %ul or %LOGIN
-			- user name sent by an external ACL, like %EXT_USER
-			- SSL client name, like %us in logformat
-			- ident user name, like %ui in logformat
-	  %EXT_USER	Username from previous external acl
-	  %EXT_LOG	Log details from previous external acl
-	  %EXT_TAG	Tag from previous external acl
-	  %IDENT	Ident user name
-	  %SRC		Client IP
-	  %SRCPORT	Client source port
-	  %URI		Requested URI
-	  %DST		Requested host
-	  %PROTO	Requested URL scheme
-	  %PORT		Requested port
-	  %PATH		Requested URL path
-	  %METHOD	Request method
-	  %MYADDR	Squid interface address
-	  %MYPORT	Squid http_port number
-	  %PATH		Requested URL-path (including query-string if any)
-	  %USER_CERT	SSL User certificate in PEM format
-	  %USER_CERTCHAIN SSL User certificate chain in PEM format
-	  %USER_CERT_xx	SSL User certificate subject attribute xx
-	  %USER_CA_CERT_xx SSL User certificate issuer attribute xx
-	  %ssl::>sni	SSL client SNI sent to Squid
-	  %ssl::<cert_subject SSL server certificate DN
-	  %ssl::<cert_issuer SSL server certificate issuer DN
-
-	  %>{Header}	HTTP request header "Header"
-	  %>{Hdr:member}
-	  		HTTP request header "Hdr" list member "member"
-	  %>{Hdr:;member}
-	  		HTTP request header list member using ; as
-	  		list separator. ; can be any non-alphanumeric
-			character.
-
-	  %<{Header}	HTTP reply header "Header"
-	  %<{Hdr:member}
-	  		HTTP reply header "Hdr" list member "member"
-	  %<{Hdr:;member}
-	  		HTTP reply header list member using ; as
-	  		list separator. ; can be any non-alphanumeric
-			character.
+	In addition to the logformat codes; when processing external ACLs these
+	additional macros are made available:
 
 	  %ACL		The name of the ACL being tested.
-	  %DATA		The ACL arguments. If not used then any arguments
-			is automatically added at the end of the line
-			sent to the helper.
-			NOTE: this will encode the arguments as one token,
-			whereas the default will pass each separately.
 
-	  %%		The percent sign. Useful for helpers which need
-			an unchanging input format.
+	  %DATA		The ACL arguments. If a logformat encoding modifier
+			is used it will encode the whole set of arguments
+			as a single token.
+
+			If not used; then any arguments are automatically
+			added at the end of the line sent to the helper
+			as separately URL-encoded fields.
+
+	If SSL is enabled, the following formating codes become available:
+
+	  %USER_CERT		SSL User certificate in PEM format
+	  %USER_CERTCHAIN	SSL User certificate chain in PEM format
+	  %USER_CERT_xx		SSL User certificate subject attribute xx
+	  %USER_CA_CERT_xx	SSL User certificate issuer attribute xx
+
+
+	NOTE: all other format codes accepted by older Squid versions
+		are deprecated.
 
 
 	General request syntax:
 
 	  [channel-ID] FORMAT-values [acl-values ...]
 
 
 	FORMAT-values consists of transaction details expanded with
 	whitespace separation per the config file FORMAT specification
 	using the FORMAT macros listed above.
 
 	acl-values consists of any string specified in the referencing
 	config 'acl ... external' line. see the "acl external" directive.
 
 	Request values sent to the helper are URL escaped to protect
 	each value in requests against whitespaces.
 
 	If using protocol=2.5 then the request sent to the helper is not
 	URL escaped to protect against whitespace.
 
@@ -868,43 +838,43 @@
 		An internal error occurred in the helper, preventing
 		a result being identified.
 
 	The meaning of 'a match' is determined by your squid.conf
 	access control configuration. See the Squid wiki for details.
 
 	Defined keywords:
 
 	  user=		The users name (login)
 
 	  password=	The users password (for login= cache_peer option)
 
 	  message=	Message describing the reason for this response.
 			Available as %o in error pages.
 			Useful on (ERR and BH results).
 
 	  tag=		Apply a tag to a request. Only sets a tag once,
 			does not alter existing tags.
 
 	  log=		String to be logged in access.log. Available as
-	  		%ea in logformat specifications.
+			%ea in logformat specifications.
 
-  	  clt_conn_tag= Associates a TAG with the client TCP connection.
+	  clt_conn_tag= Associates a TAG with the client TCP connection.
 			Please see url_rewrite_program related documentation
 			for this kv-pair.
 
 	Any keywords may be sent on any response whether OK, ERR or BH.
 
 	All response keyword values need to be a single token with URL
 	escaping, or enclosed in double quotes (") and escaped using \ on
 	any double quotes or \ characters within the value. The wrapping
 	double quotes are removed before the value is interpreted by Squid.
 	\r and \n are also replace by CR and LF.
 
 	Some example key values:
 
 		user=John%20Smith
 		user="John Smith"
 		user="J. \"Bob\" Smith"
 DOC_END
 
 NAME: acl
 TYPE: acl
@@ -4064,55 +4034,57 @@
 NAME: logformat
 TYPE: logformat
 LOC: Log::TheConfig
 DEFAULT: none
 DEFAULT_DOC: The format definitions squid, common, combined, referrer, useragent are built in.
 DOC_START
 	Usage:
 
 	logformat <name> <format specification>
 
 	Defines an access log format.
 
 	The <format specification> is a string with embedded % format codes
 
 	% format codes all follow the same basic structure where all but
 	the formatcode is optional. Output strings are automatically escaped
 	as required according to their context and the output format
 	modifiers are usually not needed, but can be specified if an explicit
 	output format is desired.
 
-		% ["|[|'|#] [-] [[0]width] [{argument}] formatcode
+		% ["|[|'|#|/] [-] [[0]width] [{arg}] formatcode [{arg}]
 
 		"	output in quoted string format
 		[	output in squid text log format as used by log_mime_hdrs
 		#	output in URL quoted format
+		/	output in shell \-escaped format
 		'	output as-is
 
 		-	left aligned
 
 		width	minimum and/or maximum field width:
 			    [width_min][.width_max]
 			When minimum starts with 0, the field is zero-padded.
 			String values exceeding maximum width are truncated.
 
-		{arg}	argument such as header name etc
+		{arg}	argument such as header name etc. This field may be
+			placed before or after the token, but not both at once.
 
 	Format codes:
 
 		%	a literal % character
 		sn	Unique sequence number per log line entry
 		err_code    The ID of an error response served by Squid or
 				a similar internal error identifier.
 		err_detail  Additional err_code-dependent error information.
 		note	The annotation specified by the argument. Also
 			logs the adaptation meta headers set by the
 			adaptation_meta configuration parameter.
 			If no argument given all annotations logged.
 			The argument may include a separator to use with
 			annotation values:
                             name[:separator]
 			By default, multiple note values are separated with ","
 			and multiple notes are separated with "\r\n".
 			When logging named notes with %{name}note, the
 			explicitly configured separator is used between note
 			values. When logging all notes with %note, the

=== modified file 'src/external_acl.cc'
--- src/external_acl.cc	2015-09-25 05:07:55 +0000
+++ src/external_acl.cc	2015-10-02 10:13:22 +0000
@@ -1,41 +1,42 @@
 /*
  * Copyright (C) 1996-2015 The Squid Software Foundation and contributors
  *
  * Squid software is distributed under GPLv2+ license and includes
  * contributions from numerous individuals and organizations.
  * Please see the COPYING and CONTRIBUTORS files for details.
  */
 
 /* DEBUG: section 82    External ACL */
 
 #include "squid.h"
 #include "acl/Acl.h"
 #include "acl/FilledChecklist.h"
 #include "cache_cf.h"
 #include "client_side.h"
+#include "client_side_request.h"
 #include "comm/Connection.h"
 #include "ConfigParser.h"
 #include "ExternalACL.h"
 #include "ExternalACLEntry.h"
 #include "fde.h"
-#include "format/ByteCode.h"
+#include "format/Token.h"
 #include "helper.h"
 #include "helper/Reply.h"
 #include "HttpHeaderTools.h"
 #include "HttpReply.h"
 #include "HttpRequest.h"
 #include "ip/tools.h"
 #include "MemBuf.h"
 #include "mgr/Registration.h"
 #include "rfc1738.h"
 #include "SquidConfig.h"
 #include "SquidString.h"
 #include "SquidTime.h"
 #include "Store.h"
 #include "tools.h"
 #include "URL.h"
 #include "wordlist.h"
 #if USE_OPENSSL
 #include "ssl/ServerBump.h"
 #include "ssl/support.h"
 #endif
@@ -49,427 +50,301 @@
 #endif
 
 #ifndef DEFAULT_EXTERNAL_ACL_TTL
 #define DEFAULT_EXTERNAL_ACL_TTL 1 * 60 * 60
 #endif
 #ifndef DEFAULT_EXTERNAL_ACL_CHILDREN
 #define DEFAULT_EXTERNAL_ACL_CHILDREN 5
 #endif
 
 static char *makeExternalAclKey(ACLFilledChecklist * ch, external_acl_data * acl_data);
 static void external_acl_cache_delete(external_acl * def, const ExternalACLEntryPointer &entry);
 static int external_acl_entry_expired(external_acl * def, const ExternalACLEntryPointer &entry);
 static int external_acl_grace_expired(external_acl * def, const ExternalACLEntryPointer &entry);
 static void external_acl_cache_touch(external_acl * def, const ExternalACLEntryPointer &entry);
 static ExternalACLEntryPointer external_acl_cache_add(external_acl * def, const char *key, ExternalACLEntryData const &data);
 
 /******************************************************************
  * external_acl directive
  */
 
-class external_acl_format : public RefCountable
-{
-    MEMPROXY_CLASS(external_acl_format);
-
-public:
-    typedef RefCount<external_acl_format> Pointer;
-
-    external_acl_format() :
-        type(Format::LFT_NONE),
-        header(nullptr),
-        member(nullptr),
-        separator(' '),
-        header_id(Http::HdrType::BAD_HDR)
-    {}
-    ~external_acl_format() {
-        xfree(header);
-        xfree(member);
-    }
-
-    Format::ByteCode_t type;
-    external_acl_format::Pointer next;
-    char *header;
-    char *member;
-    char separator;
-    Http::HdrType header_id;
-};
-
 class external_acl
 {
     /* FIXME: These are not really cbdata, but it is an easy way
      * to get them pooled, refcounted, accounted and freed properly...
      */
     CBDATA_CLASS(external_acl);
 
 public:
     external_acl();
     ~external_acl();
 
     external_acl *next;
 
     void add(const ExternalACLEntryPointer &);
 
     void trimCache();
 
     int ttl;
 
     int negative_ttl;
 
     int grace;
 
     char *name;
 
-    external_acl_format::Pointer format;
+    Format::Format format;
 
     wordlist *cmdline;
 
     Helper::ChildConfig children;
 
     helper *theHelper;
 
     hash_table *cache;
 
     dlink_list lru_list;
 
     int cache_size;
 
     int cache_entries;
 
     dlink_list queue;
 
 #if USE_AUTH
     /**
      * Configuration flag. May only be altered by the configuration parser.
      *
      * Indicates that all uses of this external_acl_type helper require authentication
      * details to be processed. If none are available its a fail match.
      */
     bool require_auth;
 #endif
 
-    enum {
-        QUOTE_METHOD_SHELL = 1,
-        QUOTE_METHOD_URL
-    } quote;
+    Format::Quoting quote; // default quoting to use, set by protocol= parameter
 
     Ip::Address local_addr;
 };
 
 CBDATA_CLASS_INIT(external_acl);
 
 external_acl::external_acl() :
     next(NULL),
     ttl(DEFAULT_EXTERNAL_ACL_TTL),
     negative_ttl(-1),
     grace(1),
     name(NULL),
+    format("external_acl_type"),
     cmdline(NULL),
     children(DEFAULT_EXTERNAL_ACL_CHILDREN),
     theHelper(NULL),
     cache(NULL),
     cache_size(256*1024),
     cache_entries(0),
 #if USE_AUTH
     require_auth(0),
 #endif
-    quote(external_acl::QUOTE_METHOD_URL)
+    quote(Format::LOG_QUOTE_URL)
 {
     local_addr.setLocalhost();
 }
 
 external_acl::~external_acl()
 {
     xfree(name);
-    format = NULL;
     wordlistDestroy(&cmdline);
 
     if (theHelper) {
         helperShutdown(theHelper);
         delete theHelper;
         theHelper = NULL;
     }
 
     while (lru_list.tail) {
         ExternalACLEntryPointer e(static_cast<ExternalACLEntry *>(lru_list.tail->data));
         external_acl_cache_delete(this, e);
     }
     if (cache)
         hashFreeMemory(cache);
 
     while (next) {
         external_acl *node = next;
         next = node->next;
         node->next = NULL; // prevent recursion
         delete node;
     }
 }
 
-/**
- * Parse the External ACL format %<{.*} and %>{.*} token(s) to pass a specific
- * request or reply header to external helper.
- *
- \param header   - the token being parsed (without the identifying prefix)
- \param type     - format enum identifier for this element, pulled from identifying prefix
- \param format   - structure to contain all the info about this format element.
- */
-void
-parse_header_token(external_acl_format::Pointer format, char *header, const Format::ByteCode_t type)
-{
-    /* header format */
-    char *member, *end;
-
-    /** Cut away the closing brace */
-    end = strchr(header, '}');
-    if (end && strlen(end) == 1)
-        *end = '\0';
-    else
-        self_destruct();
-
-    member = strchr(header, ':');
-
-    if (member) {
-        /* Split in header and member */
-        *member = '\0';
-        ++member;
-
-        if (!xisalnum(*member)) {
-            format->separator = *member;
-            ++member;
-        } else {
-            format->separator = ',';
-        }
-
-        format->member = xstrdup(member);
-
-        if (type == Format::LFT_ADAPTED_REQUEST_HEADER)
-            format->type = Format::LFT_ADAPTED_REQUEST_HEADER_ELEM;
-        else
-            format->type = Format::LFT_REPLY_HEADER_ELEM;
-
-    } else {
-        format->type = type;
-    }
-
-    format->header = xstrdup(header);
-    format->header_id = Http::HeaderLookupTable.lookup(SBuf(header)).id;
-}
-
 void
 parse_externalAclHelper(external_acl ** list)
 {
     external_acl *a = new external_acl;
     char *token = ConfigParser::NextToken();
 
     if (!token)
         self_destruct();
 
     a->name = xstrdup(token);
 
     // Allow supported %macros inside quoted tokens
     ConfigParser::EnableMacros();
     token = ConfigParser::NextToken();
 
     /* Parse options */
     while (token) {
         if (strncmp(token, "ttl=", 4) == 0) {
             a->ttl = atoi(token + 4);
         } else if (strncmp(token, "negative_ttl=", 13) == 0) {
             a->negative_ttl = atoi(token + 13);
         } else if (strncmp(token, "children=", 9) == 0) {
             a->children.n_max = atoi(token + 9);
             debugs(0, DBG_CRITICAL, "WARNING: external_acl_type option children=N has been deprecated in favor of children-max=N and children-startup=N");
         } else if (strncmp(token, "children-max=", 13) == 0) {
             a->children.n_max = atoi(token + 13);
         } else if (strncmp(token, "children-startup=", 17) == 0) {
             a->children.n_startup = atoi(token + 17);
         } else if (strncmp(token, "children-idle=", 14) == 0) {
             a->children.n_idle = atoi(token + 14);
         } else if (strncmp(token, "concurrency=", 12) == 0) {
             a->children.concurrency = atoi(token + 12);
         } else if (strncmp(token, "queue-size=", 11) == 0) {
             a->children.queue_size = atoi(token + 11);
             a->children.defaultQueueSize = false;
         } else if (strncmp(token, "cache=", 6) == 0) {
             a->cache_size = atoi(token + 6);
         } else if (strncmp(token, "grace=", 6) == 0) {
             a->grace = atoi(token + 6);
         } else if (strcmp(token, "protocol=2.5") == 0) {
-            a->quote = external_acl::QUOTE_METHOD_SHELL;
+            a->quote = Format::LOG_QUOTE_SHELL;
         } else if (strcmp(token, "protocol=3.0") == 0) {
             debugs(3, DBG_PARSE_NOTE(2), "WARNING: external_acl_type option protocol=3.0 is deprecated. Remove this from your config.");
-            a->quote = external_acl::QUOTE_METHOD_URL;
+            a->quote = Format::LOG_QUOTE_URL;
         } else if (strcmp(token, "quote=url") == 0) {
             debugs(3, DBG_PARSE_NOTE(2), "WARNING: external_acl_type option quote=url is deprecated. Remove this from your config.");
-            a->quote = external_acl::QUOTE_METHOD_URL;
+            a->quote = Format::LOG_QUOTE_URL;
         } else if (strcmp(token, "quote=shell") == 0) {
             debugs(3, DBG_PARSE_NOTE(2), "WARNING: external_acl_type option quote=shell is deprecated. Use protocol=2.5 if still needed.");
-            a->quote = external_acl::QUOTE_METHOD_SHELL;
+            a->quote = Format::LOG_QUOTE_SHELL;
 
             /* INET6: allow admin to configure some helpers explicitly to
                       bind to IPv4/v6 localhost port. */
         } else if (strcmp(token, "ipv4") == 0) {
             if ( !a->local_addr.setIPv4() ) {
                 debugs(3, DBG_CRITICAL, "WARNING: Error converting " << a->local_addr << " to IPv4 in " << a->name );
             }
         } else if (strcmp(token, "ipv6") == 0) {
             if (!Ip::EnableIpv6)
                 debugs(3, DBG_CRITICAL, "WARNING: --enable-ipv6 required for external ACL helpers to use IPv6: " << a->name );
             // else nothing to do.
         } else {
             break;
         }
 
         token = ConfigParser::NextToken();
     }
     ConfigParser::DisableMacros();
 
     /* check that child startup value is sane. */
     if (a->children.n_startup > a->children.n_max)
         a->children.n_startup = a->children.n_max;
 
     /* check that child idle value is sane. */
     if (a->children.n_idle > a->children.n_max)
         a->children.n_idle = a->children.n_max;
     if (a->children.n_idle < 1)
         a->children.n_idle = 1;
 
     if (a->negative_ttl == -1)
         a->negative_ttl = a->ttl;
 
     if (a->children.defaultQueueSize)
         a->children.queue_size = 2 * a->children.n_max;
 
-    /* Parse format */
-    external_acl_format::Pointer *p = &a->format;
-
+    /* Legacy external_acl_type format parser.
+     * Handles a series of %... tokens where any non-% means
+     * the start of another parameter field (ie the path to binary).
+     */
+    enum Format::Quoting quote = Format::LOG_QUOTE_NONE;
+    Format::Token **fmt = &a->format.format;
+    bool data_used = false;
     while (token) {
-        /* stop on first non-format token found */
-
+        /* stop on first non-% token found */
         if (*token != '%')
             break;
 
-        external_acl_format::Pointer format = new external_acl_format;
-
-        if (strncmp(token, "%{", 2) == 0) {
-            // deprecated. but assume the old configs all referred to request headers.
-            debugs(82, DBG_PARSE_NOTE(DBG_IMPORTANT), "WARNING: external_acl_type format %{...} is being replaced by %>ha{...} for : " << token);
-            parse_header_token(format, (token+2), Format::LFT_ADAPTED_REQUEST_HEADER);
-        } else if (strncmp(token, "%>{", 3) == 0) {
-            debugs(82, DBG_PARSE_NOTE(DBG_IMPORTANT), "WARNING: external_acl_type format %>{...} is being replaced by %>ha{...} for : " << token);
-            parse_header_token(format, (token+3), Format::LFT_ADAPTED_REQUEST_HEADER);
-        } else if (strncmp(token, "%>ha{", 5) == 0) {
-            parse_header_token(format, (token+5), Format::LFT_ADAPTED_REQUEST_HEADER);
-        } else if (strncmp(token, "%<{", 3) == 0) {
-            debugs(82, DBG_PARSE_NOTE(DBG_IMPORTANT), "WARNING: external_acl_type format %<{...} is being replaced by %<h{...} for : " << token);
-            parse_header_token(format, (token+3), Format::LFT_REPLY_HEADER);
-        } else if (strncmp(token, "%<h{", 4) == 0) {
-            parse_header_token(format, (token+4), Format::LFT_REPLY_HEADER);
-#if USE_AUTH
-        } else if (strcmp(token, "%LOGIN") == 0 || strcmp(token, "%ul") == 0) {
-            format->type = Format::LFT_USER_LOGIN;
-            a->require_auth = true;
-#endif
-        }
-#if USE_IDENT
-        else if (strcmp(token, "%IDENT") == 0 || strcmp(token, "%ui") == 0)
-            format->type = Format::LFT_USER_IDENT;
-#endif
-        else if (strcmp(token, "%SRC") == 0 || strcmp(token, "%>a") == 0)
-            format->type = Format::LFT_CLIENT_IP_ADDRESS;
-        else if (strcmp(token, "%SRCPORT") == 0 || strcmp(token, "%>p") == 0)
-            format->type = Format::LFT_CLIENT_PORT;
-#if USE_SQUID_EUI
-        else if (strcmp(token, "%SRCEUI48") == 0)
-            format->type = Format::LFT_EXT_ACL_CLIENT_EUI48;
-        else if (strcmp(token, "%SRCEUI64") == 0)
-            format->type = Format::LFT_EXT_ACL_CLIENT_EUI64;
-#endif
-        else if (strcmp(token, "%MYADDR") == 0 || strcmp(token, "%la") == 0)
-            format->type = Format::LFT_LOCAL_LISTENING_IP;
-        else if (strcmp(token, "%MYPORT") == 0 || strcmp(token, "%lp") == 0)
-            format->type = Format::LFT_LOCAL_LISTENING_PORT;
-        else if (strcmp(token, "%URI") == 0 || strcmp(token, "%>ru") == 0)
-            format->type = Format::LFT_CLIENT_REQ_URI;
-        else if (strcmp(token, "%DST") == 0 || strcmp(token, "%>rd") == 0)
-            format->type = Format::LFT_CLIENT_REQ_URLDOMAIN;
-        else if (strcmp(token, "%PROTO") == 0 || strcmp(token, "%>rs") == 0)
-            format->type = Format::LFT_CLIENT_REQ_URLSCHEME;
-        else if (strcmp(token, "%PORT") == 0) // XXX: add a logformat token
-            format->type = Format::LFT_CLIENT_REQ_URLPORT;
-        else if (strcmp(token, "%PATH") == 0 || strcmp(token, "%>rp") == 0)
-            format->type = Format::LFT_CLIENT_REQ_URLPATH;
-        else if (strcmp(token, "%METHOD") == 0 || strcmp(token, "%>rm") == 0)
-            format->type = Format::LFT_CLIENT_REQ_METHOD;
-#if USE_OPENSSL
-        else if (strcmp(token, "%USER_CERT") == 0)
-            format->type = Format::LFT_EXT_ACL_USER_CERT_RAW;
-        else if (strcmp(token, "%USER_CERTCHAIN") == 0)
-            format->type = Format::LFT_EXT_ACL_USER_CERTCHAIN_RAW;
-        else if (strncmp(token, "%USER_CERT_", 11) == 0) {
-            format->type = Format::LFT_EXT_ACL_USER_CERT;
-            format->header = xstrdup(token + 11);
+        *fmt = new Format::Token;
+        // these tokens are whitespace delimited
+        (*fmt)->space = true;
+
+	// set the default encoding to match the protocol= config
+        // this will be overridden by explicit %macro attributes
+        (*fmt)->quote = a->quote;
+
+        // compatibility for old tokens incompatible with Format::Token syntax
+#if USE_OPENSSL // dont bother if we dont have to.
+        if (strncmp(token, "%USER_CERT_", 11) == 0) {
+            (*fmt)->type = Format::LFT_EXT_ACL_USER_CERT;
+            (*fmt)->data.string = xstrdup(token + 11);
+            (*fmt)->data.header.header = (*fmt)->data.string;
         } else if (strncmp(token, "%USER_CA_CERT_", 14) == 0) {
-            format->type = Format::LFT_EXT_ACL_USER_CA_CERT;
-            format->header = xstrdup(token + 14);
+            (*fmt)->type = Format::LFT_EXT_ACL_USER_CA_CERT;
+            (*fmt)->data.string = xstrdup(token + 14);
+            (*fmt)->data.header.header = (*fmt)->data.string;
         } else if (strncmp(token, "%CA_CERT_", 9) == 0) {
             debugs(82, DBG_PARSE_NOTE(DBG_IMPORTANT), "WARNING: external_acl_type %CA_CERT_* code is obsolete. Use %USER_CA_CERT_* instead");
-            format->type = Format::LFT_EXT_ACL_USER_CA_CERT;
-            format->header = xstrdup(token + 9);
-        } else if (strcmp(token, "%ssl::>sni") == 0)
-            format->type = Format::LFT_SSL_CLIENT_SNI;
-        else if (strcmp(token, "%ssl::<cert_subject") == 0)
-            format->type = Format::LFT_SSL_SERVER_CERT_SUBJECT;
-        else if (strcmp(token, "%ssl::<cert_issuer") == 0)
-            format->type = Format::LFT_SSL_SERVER_CERT_ISSUER;
-#endif
+            (*fmt)->type = Format::LFT_EXT_ACL_USER_CA_CERT;
+            (*fmt)->data.string = xstrdup(token + 9);
+            (*fmt)->data.header.header = (*fmt)->data.string;
+        } else
+#endif
+        {
+            // we can use the Format::Token::parse() method since it
+            // only pulls off one token. Since we already checked
+            // for '%' prefix above this is guaranteed to be a token.
+            const size_t len = (*fmt)->parse(token, &quote);
+            assert(len == strlen(token));
+        }
+
+        // process special token-specific actions (only if necessary)
 #if USE_AUTH
-        else if (strcmp(token, "%EXT_USER") == 0 || strcmp(token, "%ue") == 0)
-            format->type = Format::LFT_USER_EXTERNAL;
-#endif
-#if USE_AUTH || defined(USE_OPENSSL) || defined(USE_IDENT)
-        else if (strcmp(token, "%un") == 0)
-            format->type = Format::LFT_USER_NAME;
+        if ((*fmt)->type == Format::LFT_USER_LOGIN)
+            a->require_auth = true;
 #endif
-        else if (strcmp(token, "%EXT_LOG") == 0 || strcmp(token, "%ea") == 0)
-            format->type = Format::LFT_EXT_LOG;
-        else if (strcmp(token, "%TAG") == 0  || strcmp(token, "%et") == 0)
-            format->type = Format::LFT_TAG;
-        else if (strcmp(token, "%ACL") == 0)
-            format->type = Format::LFT_EXT_ACL_NAME;
-        else if (strcmp(token, "%DATA") == 0)
-            format->type = Format::LFT_EXT_ACL_DATA;
-        else if (strcmp(token, "%%") == 0)
-            format->type = Format::LFT_PERCENT;
-        else {
-            debugs(0, DBG_CRITICAL, "ERROR: Unknown Format token " << token);
-            self_destruct();
-        }
 
-        *p = format;
-        p = &format->next;
+        if ((*fmt)->type == Format::LFT_EXT_ACL_DATA)
+            data_used = true;
+
+        fmt = &((*fmt)->next);
         token = ConfigParser::NextToken();
     }
 
     /* There must be at least one format token */
-    if (!a->format)
+    if (!a->format.format)
         self_destruct();
 
+    // format has implicit %DATA on the end if not used explicitly
+    if (!data_used) {
+        *fmt = new Format::Token;
+        (*fmt)->type = Format::LFT_EXT_ACL_DATA;
+        (*fmt)->quote = Format::LOG_QUOTE_URL;
+    }
+
     /* helper */
     if (!token)
         self_destruct();
 
     wordlistAdd(&a->cmdline, token);
 
     /* arguments */
     parse_wordlist(&a->cmdline);
 
     while (*list)
         list = &(*list)->next;
 
     *list = a;
 }
 
 void
 dump_externalAclHelper(StoreEntry * sentry, const char *name, const external_acl * list)
 {
     const external_acl *node;
     const wordlist *word;
@@ -489,110 +364,44 @@
             storeAppendPrintf(sentry, " negative_ttl=%d", node->negative_ttl);
 
         if (node->grace)
             storeAppendPrintf(sentry, " grace=%d", node->grace);
 
         if (node->children.n_max != DEFAULT_EXTERNAL_ACL_CHILDREN)
             storeAppendPrintf(sentry, " children-max=%d", node->children.n_max);
 
         if (node->children.n_startup != 1)
             storeAppendPrintf(sentry, " children-startup=%d", node->children.n_startup);
 
         if (node->children.n_idle != (node->children.n_max + node->children.n_startup) )
             storeAppendPrintf(sentry, " children-idle=%d", node->children.n_idle);
 
         if (node->children.concurrency)
             storeAppendPrintf(sentry, " concurrency=%d", node->children.concurrency);
 
         if (node->cache)
             storeAppendPrintf(sentry, " cache=%d", node->cache_size);
 
-        if (node->quote == external_acl::QUOTE_METHOD_SHELL)
+        if (node->quote == Format::LOG_QUOTE_SHELL)
             storeAppendPrintf(sentry, " protocol=2.5");
 
-        for (external_acl_format::Pointer format = node->format; format!= NULL; format = format->next) {
-            switch (format->type) {
-
-            case Format::LFT_ADAPTED_REQUEST_HEADER:
-                storeAppendPrintf(sentry, " %%>ha{%s}", format->header);
-                break;
-
-            case Format::LFT_ADAPTED_REQUEST_HEADER_ELEM:
-                storeAppendPrintf(sentry, " %%>ha{%s:%s}", format->header, format->member);
-                break;
-
-            case Format::LFT_REPLY_HEADER:
-                storeAppendPrintf(sentry, " %%<h{%s}", format->header);
-                break;
-
-            case Format::LFT_REPLY_HEADER_ELEM:
-                storeAppendPrintf(sentry, " %%<h{%s:%s}", format->header, format->member);
-                break;
-
-#define DUMP_EXT_ACL_TYPE_FMT(a, fmt, ...) \
-            case Format::LFT_##a: \
-                storeAppendPrintf(sentry, fmt, ##__VA_ARGS__); \
-                break
-#if USE_AUTH
-                DUMP_EXT_ACL_TYPE_FMT(USER_LOGIN," %%ul");
-                DUMP_EXT_ACL_TYPE_FMT(USER_NAME," %%un");
-#endif
-#if USE_IDENT
-
-                DUMP_EXT_ACL_TYPE_FMT(USER_IDENT," %%ui");
-#endif
-                DUMP_EXT_ACL_TYPE_FMT(CLIENT_IP_ADDRESS," %%>a");
-                DUMP_EXT_ACL_TYPE_FMT(CLIENT_PORT," %%>p");
-#if USE_SQUID_EUI
-                DUMP_EXT_ACL_TYPE_FMT(EXT_ACL_CLIENT_EUI48," %%SRCEUI48");
-                DUMP_EXT_ACL_TYPE_FMT(EXT_ACL_CLIENT_EUI64," %%SRCEUI64");
-#endif
-                DUMP_EXT_ACL_TYPE_FMT(LOCAL_LISTENING_IP," %%>la");
-                DUMP_EXT_ACL_TYPE_FMT(LOCAL_LISTENING_PORT," %%>lp");
-                DUMP_EXT_ACL_TYPE_FMT(CLIENT_REQ_URI," %%>ru");
-                DUMP_EXT_ACL_TYPE_FMT(CLIENT_REQ_URLDOMAIN," %%>rd");
-                DUMP_EXT_ACL_TYPE_FMT(CLIENT_REQ_URLSCHEME," %%>rs");
-                DUMP_EXT_ACL_TYPE_FMT(CLIENT_REQ_URLPORT," %%>rP");
-                DUMP_EXT_ACL_TYPE_FMT(CLIENT_REQ_URLPATH," %%>rp");
-                DUMP_EXT_ACL_TYPE_FMT(CLIENT_REQ_METHOD," %%>rm");
-#if USE_OPENSSL
-                DUMP_EXT_ACL_TYPE_FMT(EXT_ACL_USER_CERT_RAW, " %%USER_CERT_RAW");
-                DUMP_EXT_ACL_TYPE_FMT(EXT_ACL_USER_CERTCHAIN_RAW, " %%USER_CERTCHAIN_RAW");
-                DUMP_EXT_ACL_TYPE_FMT(EXT_ACL_USER_CERT, " %%USER_CERT_%s", format->header);
-                DUMP_EXT_ACL_TYPE_FMT(EXT_ACL_USER_CA_CERT, " %%USER_CA_CERT_%s", format->header);
-                DUMP_EXT_ACL_TYPE_FMT(SSL_CLIENT_SNI, "%%ssl::>sni");
-                DUMP_EXT_ACL_TYPE_FMT(SSL_SERVER_CERT_SUBJECT, "%%ssl::<cert_subject");
-                DUMP_EXT_ACL_TYPE_FMT(SSL_SERVER_CERT_ISSUER, "%%ssl::<cert_issuer");
-#endif
-#if USE_AUTH
-                DUMP_EXT_ACL_TYPE_FMT(USER_EXTERNAL," %%ue");
-#endif
-                DUMP_EXT_ACL_TYPE_FMT(EXT_LOG," %%ea");
-                DUMP_EXT_ACL_TYPE_FMT(TAG," %%et");
-                DUMP_EXT_ACL_TYPE_FMT(EXT_ACL_NAME," %%ACL");
-                DUMP_EXT_ACL_TYPE_FMT(EXT_ACL_DATA," %%DATA");
-                DUMP_EXT_ACL_TYPE_FMT(PERCENT, " %%%%");
-            default:
-                fatal("unknown external_acl format error");
-                break;
-            }
-        }
+        node->format.dump(sentry, NULL, false);
 
         for (word = node->cmdline; word; word = word->next)
             storeAppendPrintf(sentry, " %s", word->key);
 
         storeAppendPrintf(sentry, "\n");
     }
 }
 
 void
 free_externalAclHelper(external_acl ** list)
 {
     delete *list;
     *list = NULL;
 }
 
 static external_acl *
 find_externalAclHelper(const char *name)
 {
     external_acl *node;
 
@@ -881,361 +690,92 @@
 
     return rv;
 }
 
 /******************************************************************
  * external_acl cache
  */
 
 static void
 external_acl_cache_touch(external_acl * def, const ExternalACLEntryPointer &entry)
 {
     // this must not be done when nothing is being cached.
     if (def->cache_size <= 0 || (def->ttl <= 0 && entry->result == 1) || (def->negative_ttl <= 0 && entry->result != 1))
         return;
 
     dlinkDelete(&entry->lru, &def->lru_list);
     ExternalACLEntry *e = const_cast<ExternalACLEntry *>(entry.getRaw()); // XXX: make hash a std::map of Pointer.
     dlinkAdd(e, &entry->lru, &def->lru_list);
 }
 
-#if USE_OPENSSL
-static const char *
-external_acl_ssl_get_user_attribute(const ACLFilledChecklist &ch, const char *attr)
-{
-    if (ch.conn() != NULL && Comm::IsConnOpen(ch.conn()->clientConnection)) {
-        if (SSL *ssl = fd_table[ch.conn()->clientConnection->fd].ssl)
-            return sslGetUserAttribute(ssl, attr);
-    }
-    return NULL;
-}
-#endif
-
 static char *
 makeExternalAclKey(ACLFilledChecklist * ch, external_acl_data * acl_data)
 {
     static MemBuf mb;
-    char buf[256];
-    int first = 1;
-    wordlist *arg;
-    HttpRequest *request = ch->request;
-    HttpReply *reply = ch->reply;
     mb.reset();
-    bool data_used = false;
-
-    for (external_acl_format::Pointer format = acl_data->def->format; format != NULL; format = format->next) {
-        const char *str = NULL;
-        String sb;
-
-        switch (format->type) {
-#if USE_AUTH
-        case Format::LFT_USER_LOGIN:
-            // if this ACL line was the cause of credentials fetch
-            // they may not already be in the checklist
-            if (ch->auth_user_request == NULL && ch->request)
-                ch->auth_user_request = ch->request->auth_user_request;
 
-            if (ch->auth_user_request != NULL)
-                str = ch->auth_user_request->username();
-            break;
-#endif
-#if USE_IDENT
-        case Format::LFT_USER_IDENT:
-            str = ch->rfc931;
+    // check for special case tokens in the format
+    for (Format::Token *t = acl_data->def->format.format; t ; t = t->next) {
 
-            if (!str || !*str) {
-                // if we fail to go async, we still return NULL and the caller
-                // will detect the failure in ACLExternal::match().
-                (void)ch->goAsync(IdentLookup::Instance());
-                return NULL;
-            }
-
-            break;
-#endif
-
-        case Format::LFT_CLIENT_IP_ADDRESS:
-            str = ch->src_addr.toStr(buf,sizeof(buf));
-            break;
-
-        case Format::LFT_CLIENT_PORT:
-            snprintf(buf, sizeof(buf), "%d", request->client_addr.port());
-            str = buf;
-            break;
-
-#if USE_SQUID_EUI
-        case Format::LFT_EXT_ACL_CLIENT_EUI48:
-            if (request->clientConnectionManager.valid() && request->clientConnectionManager->clientConnection != NULL &&
-                    request->clientConnectionManager->clientConnection->remoteEui48.encode(buf, sizeof(buf)))
-                str = buf;
-            break;
-
-        case Format::LFT_EXT_ACL_CLIENT_EUI64:
-            if (request->clientConnectionManager.valid() && request->clientConnectionManager->clientConnection != NULL &&
-                    request->clientConnectionManager->clientConnection->remoteEui64.encode(buf, sizeof(buf)))
-                str = buf;
-            break;
-#endif
-
-        case Format::LFT_LOCAL_LISTENING_IP:
-            str = request->my_addr.toStr(buf, sizeof(buf));
-            break;
-
-        case Format::LFT_LOCAL_LISTENING_PORT:
-            snprintf(buf, sizeof(buf), "%d", request->my_addr.port());
-            str = buf;
-            break;
-
-        case Format::LFT_CLIENT_REQ_URI:
-            snprintf(buf, sizeof(buf), SQUIDSBUFPH, SQUIDSBUFPRINT(request->effectiveRequestUri()));
-            str = buf;
-            break;
-
-        case Format::LFT_CLIENT_REQ_URLDOMAIN:
-            str = request->url.host();
-            break;
-
-        case Format::LFT_CLIENT_REQ_URLSCHEME:
-            str = request->url.getScheme().c_str();
-            break;
-
-        case Format::LFT_CLIENT_REQ_URLPORT:
-            snprintf(buf, sizeof(buf), "%u", request->url.port());
-            str = buf;
-            break;
-
-        case Format::LFT_CLIENT_REQ_URLPATH: {
-            SBuf tmp = request->url.path();
-            str = tmp.c_str();
-        }
-        break;
-
-        case Format::LFT_CLIENT_REQ_METHOD: {
-            const SBuf &s = request->method.image();
-            sb.append(s.rawContent(), s.length());
+        if (t->type == Format::LFT_EXT_ACL_NAME) {
+            // setup for %ACL
+            safe_free(ch->al->_private.lastAclName);
+            ch->al->_private.lastAclName = xstrdup(acl_data->name);
         }
-        str = sb.termedBuf();
-        break;
-
-        case Format::LFT_ADAPTED_REQUEST_HEADER:
-            if (format->header_id == Http::HdrType::BAD_HDR)
-                sb = request->header.getByName(format->header);
-            else
-                sb = request->header.getStrOrList(format->header_id);
-            str = sb.termedBuf();
-            break;
-
-        case Format::LFT_ADAPTED_REQUEST_HEADER_ELEM:
-            if (format->header_id == Http::HdrType::BAD_HDR)
-                sb = request->header.getByNameListMember(format->header, format->member, format->separator);
-            else
-                sb = request->header.getListMember(format->header_id, format->member, format->separator);
-            str = sb.termedBuf();
-            break;
-
-        case Format::LFT_REPLY_HEADER:
-            if (reply) {
-                if (format->header_id == Http::HdrType::BAD_HDR)
-                    sb = reply->header.getByName(format->header);
-                else
-                    sb = reply->header.getStrOrList(format->header_id);
-                str = sb.termedBuf();
-            }
-            break;
-
-        case Format::LFT_REPLY_HEADER_ELEM:
-            if (reply) {
-                if (format->header_id == Http::HdrType::BAD_HDR)
-                    sb = reply->header.getByNameListMember(format->header, format->member, format->separator);
-                else
-                    sb = reply->header.getListMember(format->header_id, format->member, format->separator);
-                str = sb.termedBuf();
-            }
-            break;
-
-#if USE_OPENSSL
-
-        case Format::LFT_EXT_ACL_USER_CERT_RAW:
-
-            if (ch->conn() != NULL && Comm::IsConnOpen(ch->conn()->clientConnection)) {
-                if (auto ssl = fd_table[ch->conn()->clientConnection->fd].ssl)
-                    str = sslGetUserCertificatePEM(ssl);
-            }
-
-            break;
-
-        case Format::LFT_EXT_ACL_USER_CERTCHAIN_RAW:
-
-            if (ch->conn() != NULL && Comm::IsConnOpen(ch->conn()->clientConnection)) {
-                if (auto ssl = fd_table[ch->conn()->clientConnection->fd].ssl)
-                    str = sslGetUserCertificateChainPEM(ssl);
-            }
-
-            break;
-
-        case Format::LFT_EXT_ACL_USER_CERT:
-
-            str = external_acl_ssl_get_user_attribute(*ch, format->header);
-            break;
-
-        case Format::LFT_EXT_ACL_USER_CA_CERT:
-
-            if (ch->conn() != NULL && Comm::IsConnOpen(ch->conn()->clientConnection)) {
-                if (auto ssl = fd_table[ch->conn()->clientConnection->fd].ssl)
-                    str = sslGetCAAttribute(ssl, format->header);
-            }
 
-            break;
-
-        case Format::LFT_SSL_CLIENT_SNI:
-            if (ch->conn() != NULL) {
-                if (Ssl::ServerBump * srvBump = ch->conn()->serverBump()) {
-                    if (!srvBump->clientSni.isEmpty())
-                        str = srvBump->clientSni.c_str();
-                }
-            }
-            break;
-
-        case Format::LFT_SSL_SERVER_CERT_SUBJECT:
-        case Format::LFT_SSL_SERVER_CERT_ISSUER: {
-            X509 *serverCert = NULL;
-            if (ch->serverCert.get())
-                serverCert = ch->serverCert.get();
-            else if (ch->conn() && ch->conn()->serverBump())
-                serverCert = ch->conn()->serverBump()->serverCert.get();
-
-            if (serverCert) {
-                if (format->type == Format::LFT_SSL_SERVER_CERT_SUBJECT)
-                    str = Ssl::GetX509UserAttribute(serverCert, "DN");
-                else
-                    str = Ssl::GetX509CAAttribute(serverCert, "DN");
-            }
-            break;
-        }
-
-#endif
-#if USE_AUTH
-        case Format::LFT_USER_EXTERNAL:
-            str = request->extacl_user.termedBuf();
-            break;
-#endif
-        case Format::LFT_USER_NAME:
-            /* find the first available name from various sources */
-#if USE_AUTH
-            // if this ACL line was the cause of credentials fetch
-            // they may not already be in the checklist
-            if (!ch->auth_user_request && ch->request)
-                ch->auth_user_request = ch->request->auth_user_request;
-
-            if (ch->auth_user_request != NULL)
-                str = ch->auth_user_request->username();
-
-            if ((!str || !*str) &&
-                    (request->extacl_user.size() > 0 && request->extacl_user[0] != '-'))
-                str = request->extacl_user.termedBuf();
-#endif
-#if USE_OPENSSL
-            if (!str || !*str)
-                str = external_acl_ssl_get_user_attribute(*ch, "CN");
-#endif
-#if USE_IDENT
-            if (!str || !*str)
-                str = ch->rfc931;
-#endif
-            break;
-        case Format::LFT_EXT_LOG:
-            str = request->extacl_log.termedBuf();
-            break;
-        case Format::LFT_TAG:
-            str = request->tag.termedBuf();
-            break;
-        case Format::LFT_EXT_ACL_NAME:
-            str = acl_data->name;
-            break;
-        case Format::LFT_EXT_ACL_DATA:
-            data_used = true;
-            for (arg = acl_data->arguments; arg; arg = arg->next) {
-                if (!first)
+        if (t->type == Format::LFT_EXT_ACL_DATA) {
+            // setup string for %DATA
+            SBuf sb;
+            for (auto arg = acl_data->arguments; arg; arg = arg->next) {
+                if (sb.length())
                     sb.append(" ", 1);
 
-                if (acl_data->def->quote == external_acl::QUOTE_METHOD_URL) {
+                if (acl_data->def->quote == Format::LOG_QUOTE_URL) {
                     const char *quoted = rfc1738_escape(arg->key);
                     sb.append(quoted, strlen(quoted));
                 } else {
                     static MemBuf mb2;
                     mb2.init();
                     strwordquote(&mb2, arg->key);
                     sb.append(mb2.buf, mb2.size);
                     mb2.clean();
                 }
-
-                first = 0;
             }
-            break;
-        case Format::LFT_PERCENT:
-            str = "%";
-            break;
 
-        default:
-            // TODO: replace this function with Format::assemble()
-            // For now die on unsupported logformat codes.
-            fatalf("ERROR: unknown external_acl_type format %u", (uint8_t)format->type);
-            break;
+            ch->al->_private.lastAclData = sb.c_str();
         }
 
-        if (str)
-            if (!*str)
-                str = NULL;
-
-        if (!str)
-            str = "-";
-
-        if (!first)
-            mb.append(" ", 1);
-
-        if (acl_data->def->quote == external_acl::QUOTE_METHOD_URL) {
-            const char *quoted = rfc1738_escape(str);
-            mb.append(quoted, strlen(quoted));
-        } else {
-            strwordquote(&mb, str);
-        }
-
-        sb.clean();
-
-        first = 0;
-    }
-
-    if (!data_used) {
-        for (arg = acl_data->arguments; arg; arg = arg->next) {
-            if (!first)
-                mb.append(" ", 1);
-
-            if (acl_data->def->quote == external_acl::QUOTE_METHOD_URL) {
-                const char *quoted = rfc1738_escape(arg->key);
-                mb.append(quoted, strlen(quoted));
-            } else {
-                strwordquote(&mb, arg->key);
+#if USE_IDENT
+        if (t->type == Format::LFT_USER_IDENT) {
+            if (!*ch->rfc931) {
+                // if we fail to go async, we still return NULL and the caller
+                // will detect the failure in ACLExternal::match().
+                (void)ch->goAsync(IdentLookup::Instance());
+                return NULL;
             }
-
-            first = 0;
         }
+#endif
     }
 
+    // assemble the full helper lookup string
+    acl_data->def->format.assemble(mb, ch->al, 0);
+
     return mb.buf;
 }
 
 static int
 external_acl_entry_expired(external_acl * def, const ExternalACLEntryPointer &entry)
 {
     if (def->cache_size <= 0)
         return 1;
 
     if (entry->date + (entry->result == 1 ? def->ttl : def->negative_ttl) < squid_curtime)
         return 1;
     else
         return 0;
 }
 
 static int
 external_acl_grace_expired(external_acl * def, const ExternalACLEntryPointer &entry)
 {
     if (def->cache_size <= 0)
         return 1;

=== modified file 'src/format/ByteCode.h'
--- src/format/ByteCode.h	2015-01-13 07:25:36 +0000
+++ src/format/ByteCode.h	2015-10-02 10:00:59 +0000
@@ -206,44 +206,47 @@
     LFT_ICAP_TR_RESPONSE_TIME,
     LFT_ICAP_IO_TIME,
     LFT_ICAP_OUTCOME,
     LFT_ICAP_STATUS_CODE,
 #endif
     LFT_CREDENTIALS,
 
 #if USE_OPENSSL
     LFT_SSL_BUMP_MODE,
     LFT_SSL_USER_CERT_SUBJECT,
     LFT_SSL_USER_CERT_ISSUER,
     LFT_SSL_CLIENT_SNI,
     LFT_SSL_SERVER_CERT_SUBJECT,
     LFT_SSL_SERVER_CERT_ISSUER,
 #endif
 
     LFT_NOTE,
     LFT_PERCENT,            /* special string cases for escaped chars */
 
     // TODO assign better bytecode names and Token strings for these
+#if USE_OPENSSL
     LFT_EXT_ACL_USER_CERT_RAW,
     LFT_EXT_ACL_USER_CERTCHAIN_RAW,
     LFT_EXT_ACL_USER_CERT,
     LFT_EXT_ACL_USER_CA_CERT,
+#endif
     LFT_EXT_ACL_CLIENT_EUI48,
     LFT_EXT_ACL_CLIENT_EUI64,
     LFT_EXT_ACL_NAME,
     LFT_EXT_ACL_DATA
 
 } ByteCode_t;
 
 /// Quoting style for a format output.
 enum Quoting {
     LOG_QUOTE_NONE = 0,
     LOG_QUOTE_QUOTES,
     LOG_QUOTE_MIMEBLOB,
     LOG_QUOTE_URL,
+    LOG_QUOTE_SHELL,
     LOG_QUOTE_RAW
 };
 
 } // namespace Format
 
 #endif /* _SQUID_FMT_BYTECODE_H */
 

=== modified file 'src/format/Format.cc'
--- src/format/Format.cc	2015-07-19 13:23:01 +0000
+++ src/format/Format.cc	2015-10-02 10:47:21 +0000
@@ -5,40 +5,41 @@
  * contributions from numerous individuals and organizations.
  * Please see the COPYING and CONTRIBUTORS files for details.
  */
 
 #include "squid.h"
 #include "AccessLogEntry.h"
 #include "client_side.h"
 #include "comm/Connection.h"
 #include "err_detail_type.h"
 #include "errorpage.h"
 #include "fde.h"
 #include "format/Format.h"
 #include "format/Quoting.h"
 #include "format/Token.h"
 #include "fqdncache.h"
 #include "HttpRequest.h"
 #include "MemBuf.h"
 #include "rfc1738.h"
 #include "SquidTime.h"
 #include "Store.h"
+#include "tools.h"
 #include "URL.h"
 #if USE_OPENSSL
 #include "ssl/ErrorDetail.h"
 #include "ssl/ServerBump.h"
 #endif
 
 /// Convert a string to NULL pointer if it is ""
 #define strOrNull(s) ((s)==NULL||(s)[0]=='\0'?NULL:(s))
 
 Format::Format::Format(const char *n) :
     format(NULL),
     next(NULL)
 {
     name = xstrdup(n);
 }
 
 Format::Format::~Format()
 {
     // erase the list without consuming stack space
     while (next) {
@@ -71,48 +72,49 @@
     /* very inefficent parser, but who cares, this needs to be simple */
     /* First off, let's tokenize, we'll optimize in a second pass.
      * A token can either be a %-prefixed sequence (usually a dynamic
      * token but it can be an escaped sequence), or a string. */
     cur = def;
     eos = def + strlen(def);
     format = new_lt = last_lt = new Token;
     cur += new_lt->parse(cur, &quote);
 
     while (cur < eos) {
         new_lt = new Token;
         last_lt->next = new_lt;
         last_lt = new_lt;
         cur += new_lt->parse(cur, &quote);
     }
 
     return true;
 }
 
 void
-Format::Format::dump(StoreEntry * entry, const char *directiveName)
+Format::Format::dump(StoreEntry * entry, const char *directiveName, bool eol) const
 {
     debugs(46, 4, HERE);
 
     // loop rather than recursing to conserve stack space.
-    for (Format *fmt = this; fmt; fmt = fmt->next) {
+    for (const Format *fmt = this; fmt; fmt = fmt->next) {
         debugs(46, 3, HERE << "Dumping format definition for " << fmt->name);
-        storeAppendPrintf(entry, "%s %s ", directiveName, fmt->name);
+        if (directiveName)
+            storeAppendPrintf(entry, "%s %s ", directiveName, fmt->name);
 
         for (Token *t = fmt->format; t; t = t->next) {
             if (t->type == LFT_STRING)
                 storeAppendPrintf(entry, "%s", t->data.string);
             else {
                 char argbuf[256];
                 char *arg = NULL;
                 ByteCode_t type = t->type;
 
                 switch (type) {
                 /* special cases */
 
                 case LFT_STRING:
                     break;
 #if USE_ADAPTATION
                 case LFT_ADAPTATION_LAST_HEADER_ELEM:
 #endif
 #if ICAP_CLIENT
                 case LFT_ICAP_REQ_HEADER_ELEM:
                 case LFT_ICAP_REP_HEADER_ELEM:
@@ -208,67 +210,72 @@
                 entry->append("%", 1);
 
                 switch (t->quote) {
 
                 case LOG_QUOTE_QUOTES:
                     entry->append("\"", 1);
                     break;
 
                 case LOG_QUOTE_MIMEBLOB:
                     entry->append("[", 1);
                     break;
 
                 case LOG_QUOTE_URL:
                     entry->append("#", 1);
                     break;
 
                 case LOG_QUOTE_RAW:
                     entry->append("'", 1);
                     break;
 
+                case LOG_QUOTE_SHELL:
+                    entry->append("/", 1);
+                    break;
+
                 case LOG_QUOTE_NONE:
                     break;
                 }
 
                 if (t->left)
                     entry->append("-", 1);
 
                 if (t->zero)
                     entry->append("0", 1);
 
                 if (t->widthMin >= 0)
                     storeAppendPrintf(entry, "%d", t->widthMin);
 
                 if (t->widthMax >= 0)
                     storeAppendPrintf(entry, ".%d", t->widthMax);
 
                 if (arg)
                     storeAppendPrintf(entry, "{%s}", arg);
 
                 storeAppendPrintf(entry, "%s", t->label);
 
                 if (t->space)
                     entry->append(" ", 1);
             }
         }
 
-        entry->append("\n", 1);
+        if (eol)
+            entry->append("\n", 1);
     }
 
 }
 
 static void
 log_quoted_string(const char *str, char *out)
 {
     char *p = out;
 
     while (*str) {
         int l = strcspn(str, "\"\\\r\n\t");
         memcpy(p, str, l);
         str += l;
         p += l;
 
         switch (*str) {
 
         case '\0':
             break;
 
@@ -353,42 +360,62 @@
 
             break;
 
         case LFT_CLIENT_PORT:
             if (al->request) {
                 outint = al->request->client_addr.port();
                 doint = 1;
             }
             break;
 
         case LFT_CLIENT_EUI:
 #if USE_SQUID_EUI
             // TODO make the ACL checklist have a direct link to any TCP details.
             if (al->request && al->request->clientConnectionManager.valid() && al->request->clientConnectionManager->clientConnection != NULL) {
                 if (al->request->clientConnectionManager->clientConnection->remote.isIPv4())
                     al->request->clientConnectionManager->clientConnection->remoteEui48.encode(tmp, 1024);
                 else
                     al->request->clientConnectionManager->clientConnection->remoteEui64.encode(tmp, 1024);
                 out = tmp;
             }
-#else
-            out = "-";
+#endif
+            break;
+
+        case LFT_EXT_ACL_CLIENT_EUI48:
+#if USE_SQUID_EUI
+            if (al->request && al->request->clientConnectionManager.valid() &&
+                    al->request->clientConnectionManager->clientConnection != NULL &&
+                    al->request->clientConnectionManager->clientConnection->remote.isIPv4()) {
+                al->request->clientConnectionManager->clientConnection->remoteEui48.encode(tmp, 1024);
+                out = tmp;
+            }
+#endif
+            break;
+
+        case LFT_EXT_ACL_CLIENT_EUI64:
+#if USE_SQUID_EUI
+            if (al->request && al->request->clientConnectionManager.valid() &&
+                    al->request->clientConnectionManager->clientConnection != NULL &&
+                    !al->request->clientConnectionManager->clientConnection->remote.isIPv4()) {
+                al->request->clientConnectionManager->clientConnection->remoteEui64.encode(tmp, 1024);
+                out = tmp;
+            }
 #endif
             break;
 
         case LFT_SERVER_IP_ADDRESS:
             if (al->hier.tcpServer != NULL) {
                 out = al->hier.tcpServer->remote.toStr(tmp,sizeof(tmp));
             }
             break;
 
         case LFT_SERVER_FQDN_OR_PEER_NAME:
             out = al->hier.host;
             break;
 
         case LFT_SERVER_PORT:
             if (al->hier.tcpServer != NULL) {
                 outint = al->hier.tcpServer->remote.port();
                 doint = 1;
             }
             break;
 
@@ -820,41 +847,45 @@
 #if USE_OPENSSL
             if (!out)
                 out = strOrNull(al->cache.ssluser);
 #endif
             if (!out)
                 out = strOrNull(al->cache.rfc931);
             break;
 
         case LFT_USER_LOGIN:
 #if USE_AUTH
             if (al->request && al->request->auth_user_request != NULL)
                 out = strOrNull(al->request->auth_user_request->username());
 #endif
             break;
 
         case LFT_USER_IDENT:
             out = strOrNull(al->cache.rfc931);
             break;
 
         case LFT_USER_EXTERNAL:
-            out = strOrNull(al->cache.extuser);
+            if (al->request && al->request->extacl_user.size())
+                out = al->request->extacl_user.termedBuf();
+
+            if (!out)
+                out = strOrNull(al->cache.extuser);
             break;
 
         /* case LFT_USER_REALM: */
         /* case LFT_USER_SCHEME: */
 
         // the fmt->type can not be LFT_HTTP_SENT_STATUS_CODE_OLD_30
         // but compiler complains if ommited
         case LFT_HTTP_SENT_STATUS_CODE_OLD_30:
         case LFT_HTTP_SENT_STATUS_CODE:
             outint = al->http.code;
 
             doint = 1;
 
             break;
 
         case LFT_HTTP_RECEIVED_STATUS_CODE:
             if (al->hier.peer_reply_status == Http::scNone) {
                 out = "-";
             } else {
                 outint = al->hier.peer_reply_status;
@@ -878,42 +909,41 @@
 
         case LFT_SQUID_STATUS:
             out = al->cache.code.c_str();
             break;
 
         case LFT_SQUID_ERROR:
             if (al->request && al->request->errType != ERR_NONE)
                 out = errorPageName(al->request->errType);
             break;
 
         case LFT_SQUID_ERROR_DETAIL:
 #if USE_OPENSSL
             if (al->request && al->request->errType == ERR_SECURE_CONNECT_FAIL) {
                 if (! (out = Ssl::GetErrorName(al->request->errDetail))) {
                     snprintf(tmp, sizeof(tmp), "SSL_ERR=%d", al->request->errDetail);
                     out = tmp;
                 }
             } else
 #endif
                 if (al->request && al->request->errDetail != ERR_DETAIL_NONE) {
-                    if (al->request->errDetail > ERR_DETAIL_START  &&
-                            al->request->errDetail < ERR_DETAIL_MAX)
+                    if (al->request->errDetail > ERR_DETAIL_START && al->request->errDetail < ERR_DETAIL_MAX)
                         out = errorDetailName(al->request->errDetail);
                     else {
                         if (al->request->errDetail >= ERR_DETAIL_EXCEPTION_START)
                             snprintf(tmp, sizeof(tmp), "%s=0x%X",
                                      errorDetailName(al->request->errDetail), (uint32_t) al->request->errDetail);
                         else
                             snprintf(tmp, sizeof(tmp), "%s=%d",
                                      errorDetailName(al->request->errDetail), al->request->errDetail);
                         out = tmp;
                     }
                 }
             break;
 
         case LFT_SQUID_HIERARCHY:
             if (al->hier.ping.timedout)
                 mb.append("TIMEOUT_", 8);
 
             out = hier_code_str[al->hier.code];
 
             break;
@@ -1112,60 +1142,101 @@
             break;
 
         case LFT_EXT_LOG:
             if (al->request)
                 out = al->request->extacl_log.termedBuf();
 
             quote = 1;
 
             break;
 
         case LFT_SEQUENCE_NUMBER:
             outoff = logSequenceNumber;
             dooff = 1;
             break;
 
 #if USE_OPENSSL
         case LFT_SSL_BUMP_MODE: {
             const Ssl::BumpMode mode = static_cast<Ssl::BumpMode>(al->ssl.bumpMode);
             // for Ssl::bumpEnd, Ssl::bumpMode() returns NULL and we log '-'
             out = Ssl::bumpMode(mode);
-            break;
         }
+            break;
+
+        case LFT_EXT_ACL_USER_CERT_RAW:
+            if (al->request) {
+                ConnStateData *conn = al->request->clientConnectionManager.get();
+                if (conn != NULL && Comm::IsConnOpen(conn->clientConnection)) {
+                    if (SSL *ssl = fd_table[conn->clientConnection->fd].ssl)
+                        out = sslGetUserCertificatePEM(ssl);
+                }
+            }
+            break;
+
+        case LFT_EXT_ACL_USER_CERTCHAIN_RAW:
+            if (al->request) {
+                ConnStateData *conn = al->request->clientConnectionManager.get();
+                if (conn != NULL && Comm::IsConnOpen(conn->clientConnection)) {
+                    if (SSL *ssl = fd_table[conn->clientConnection->fd].ssl)
+                        out = sslGetUserCertificatePEM(ssl);
+                }
+            }
+            break;
+
+        case LFT_EXT_ACL_USER_CERT:
+            if (al->request) {
+                ConnStateData *conn = al->request->clientConnectionManager.get();
+                if (conn != NULL && Comm::IsConnOpen(conn->clientConnection)) {
+                    if (SSL *ssl = fd_table[conn->clientConnection->fd].ssl)
+                        out = sslGetUserAttribute(ssl, format->data.header.header);
+                }
+            }
+            break;
+
+        case LFT_EXT_ACL_USER_CA_CERT:
+            if (al->request) {
+                ConnStateData *conn = al->request->clientConnectionManager.get();
+                if (conn != NULL && Comm::IsConnOpen(conn->clientConnection)) {
+                    if (SSL *ssl = fd_table[conn->clientConnection->fd].ssl)
+                       out = sslGetCAAttribute(ssl, format->data.header.header);
+                }
+            }
+            break;
 
         case LFT_SSL_USER_CERT_SUBJECT:
             if (X509 *cert = al->cache.sslClientCert.get()) {
                 if (X509_NAME *subject = X509_get_subject_name(cert)) {
                     X509_NAME_oneline(subject, tmp, sizeof(tmp));
                     out = tmp;
                 }
             }
             break;
 
         case LFT_SSL_USER_CERT_ISSUER:
             if (X509 *cert = al->cache.sslClientCert.get()) {
                 if (X509_NAME *issuer = X509_get_issuer_name(cert)) {
                     X509_NAME_oneline(issuer, tmp, sizeof(tmp));
                     out = tmp;
                 }
             }
             break;
+
         case LFT_SSL_CLIENT_SNI:
             if (al->request && al->request->clientConnectionManager.valid()) {
                 if (Ssl::ServerBump * srvBump = al->request->clientConnectionManager->serverBump()) {
                     if (!srvBump->clientSni.isEmpty())
                         out = srvBump->clientSni.c_str();
                 }
             }
             break;
 
         case LFT_SSL_SERVER_CERT_ISSUER:
         case LFT_SSL_SERVER_CERT_SUBJECT:
             // Not implemented
             break;
 #endif
 
         case LFT_REQUEST_URLGROUP_OLD_2X:
             assert(LFT_REQUEST_URLGROUP_OLD_2X == 0); // should never happen.
 
         case LFT_NOTE:
             tmp[0] = fmt->data.header.separator;
@@ -1199,52 +1270,46 @@
                 if (al->notes != NULL && !al->notes->empty())
                     sb.append(al->notes->toString(separator));
 
                 out = sb.termedBuf();
                 quote = 1;
             }
             break;
 
         case LFT_CREDENTIALS:
 #if USE_AUTH
             if (al->request && al->request->auth_user_request != NULL)
                 out = strOrNull(al->request->auth_user_request->credentialsStr());
 #endif
 
             break;
 
         case LFT_PERCENT:
             out = "%";
             break;
 
-        // XXX: external_acl_type format tokens which are not output by logformat.
-        // They are listed here because the switch requires
-        // every ByteCode_t to be explicitly enumerated.
-        // But do not output due to lack of access to the values.
-        case LFT_EXT_ACL_USER_CERT_RAW:
-        case LFT_EXT_ACL_USER_CERTCHAIN_RAW:
-        case LFT_EXT_ACL_USER_CERT:
-        case LFT_EXT_ACL_USER_CA_CERT:
-        case LFT_EXT_ACL_CLIENT_EUI48:
-        case LFT_EXT_ACL_CLIENT_EUI64:
         case LFT_EXT_ACL_NAME:
+            out = al->_private.lastAclName;
+            break;
+
         case LFT_EXT_ACL_DATA:
+            out = al->_private.lastAclData;
             break;
         }
 
         if (dooff) {
             snprintf(tmp, sizeof(tmp), "%0*" PRId64, fmt->zero && fmt->widthMin >= 0 ? fmt->widthMin : 0, outoff);
             out = tmp;
 
         } else if (doint) {
             snprintf(tmp, sizeof(tmp), "%0*ld", fmt->zero && fmt->widthMin >= 0 ? fmt->widthMin : 0, outint);
             out = tmp;
         } else if (doMsec) {
             if (fmt->widthMax < 0) {
                 snprintf(tmp, sizeof(tmp), "%0*ld", fmt->widthMin , tvToMsec(outtv));
             } else {
                 int precision = fmt->widthMax;
                 snprintf(tmp, sizeof(tmp), "%0*" PRId64 ".%0*" PRId64 "", fmt->zero && (fmt->widthMin - precision - 1 >= 0) ? fmt->widthMin - precision - 1 : 0, static_cast<int64_t>(outtv.tv_sec * 1000 + outtv.tv_usec / 1000), precision, static_cast<int64_t>((outtv.tv_usec % 1000 )* (1000 / fmt->divisor)));
             }
             out = tmp;
         } else if (doSec) {
             int precision = fmt->widthMax >=0 ? fmt->widthMax :3;
@@ -1266,40 +1331,49 @@
                 case LOG_QUOTE_QUOTES: {
                     size_t out_len = static_cast<size_t>(strlen(out)) * 2 + 1;
                     if (out_len >= sizeof(tmp)) {
                         newout = (char *)xmalloc(out_len);
                         newfree = 1;
                     } else
                         newout = tmp;
                     log_quoted_string(out, newout);
                 }
                 break;
 
                 case LOG_QUOTE_MIMEBLOB:
                     newout = QuoteMimeBlob(out);
                     newfree = 1;
                     break;
 
                 case LOG_QUOTE_URL:
                     newout = rfc1738_escape(out);
                     break;
 
+                case LOG_QUOTE_SHELL: {
+                    MemBuf mbq;
+                    mbq.init();
+                    strwordquote(&mbq, out);
+                    newout = mbq.content();
+                    mbq.stolen = 1;
+                    newfree = 1;
+                    } break;
+
                 case LOG_QUOTE_RAW:
                     break;
                 }
 
                 if (newout) {
                     if (dofree)
                         safe_free(out);
 
                     out = newout;
 
                     dofree = newfree;
                 }
             }
 
             // enforce width limits if configured
             const bool haveMaxWidth = fmt->widthMax >=0 && !doint && !dooff && !doMsec && !doSec;
             if (haveMaxWidth || fmt->widthMin) {
                 const int minWidth = fmt->widthMin >= 0 ?
                                      fmt->widthMin :0;
                 const int maxWidth = haveMaxWidth ?

=== modified file 'src/format/Format.h'
--- src/format/Format.h	2015-01-13 07:25:36 +0000
+++ src/format/Format.h	2015-05-22 05:54:39 +0000
@@ -34,31 +34,31 @@
 
 class Token;
 
 // XXX: inherit from linked list
 class Format
 {
 public:
     Format(const char *name);
     virtual ~Format();
 
     /* very inefficent parser, but who cares, this needs to be simple */
     /* First off, let's tokenize, we'll optimize in a second pass.
      * A token can either be a %-prefixed sequence (usually a dynamic
      * token but it can be an escaped sequence), or a string. */
     bool parse(const char *def);
 
     /// assemble the state information into a formatted line.
     void assemble(MemBuf &mb, const AccessLogEntryPointer &al, int logSequenceNumber) const;
 
     /// dump this whole list of formats into the provided StoreEntry
-    void dump(StoreEntry * entry, const char *directiveName);
+    void dump(StoreEntry * entry, const char *directiveName, bool eol = true) const;
 
     char *name;
     Token *format;
     Format *next;
 };
 
 } // namespace Format
 
 #endif /* _SQUID_FORMAT_FORMAT_H */
 

=== modified file 'src/format/Token.cc'
--- src/format/Token.cc	2015-01-13 07:25:36 +0000
+++ src/format/Token.cc	2015-08-09 11:23:34 +0000
@@ -128,40 +128,66 @@
     /*TokenTableEntry("stP", LFT_SERVER_IO_SIZE_TOTAL),*/
 
     TokenTableEntry("et", LFT_TAG),
     TokenTableEntry("ea", LFT_EXT_LOG),
     TokenTableEntry("sn", LFT_SEQUENCE_NUMBER),
 
     TokenTableEntry(NULL, LFT_NONE)        /* this must be last */
 };
 
 /// Miscellaneous >2 byte tokens
 static TokenTableEntry TokenTableMisc[] = {
     TokenTableEntry(">eui", LFT_CLIENT_EUI),
     TokenTableEntry(">qos", LFT_CLIENT_LOCAL_TOS),
     TokenTableEntry("<qos", LFT_SERVER_LOCAL_TOS),
     TokenTableEntry(">nfmark", LFT_CLIENT_LOCAL_NFMARK),
     TokenTableEntry("<nfmark", LFT_SERVER_LOCAL_NFMARK),
     TokenTableEntry("err_code", LFT_SQUID_ERROR ),
     TokenTableEntry("err_detail", LFT_SQUID_ERROR_DETAIL ),
     TokenTableEntry("note", LFT_NOTE ),
     TokenTableEntry("credentials", LFT_CREDENTIALS),
+    /*
+     * Legacy external_acl_type format tokens
+     */
+    TokenTableEntry("ACL", LFT_EXT_ACL_NAME),
+    TokenTableEntry("DATA", LFT_EXT_ACL_DATA),
+    TokenTableEntry("DST", LFT_CLIENT_REQ_URLDOMAIN),
+    TokenTableEntry("EXT_LOG", LFT_EXT_LOG),
+    TokenTableEntry("EXT_USER", LFT_USER_EXTERNAL),
+    TokenTableEntry("IDENT", LFT_USER_IDENT),
+    TokenTableEntry("LOGIN", LFT_USER_LOGIN),
+    TokenTableEntry("METHOD", LFT_CLIENT_REQ_METHOD),
+    TokenTableEntry("MYADDR", LFT_LOCAL_LISTENING_IP),
+    TokenTableEntry("MYPORT", LFT_LOCAL_LISTENING_PORT),
+    TokenTableEntry("PATH", LFT_CLIENT_REQ_URLPATH),
+    TokenTableEntry("PORT", LFT_CLIENT_REQ_URLPORT),
+    TokenTableEntry("PROTO", LFT_CLIENT_REQ_URLSCHEME),
+    TokenTableEntry("SRCEUI48", LFT_EXT_ACL_CLIENT_EUI48),
+    TokenTableEntry("SRCEUI64", LFT_EXT_ACL_CLIENT_EUI64),
+    TokenTableEntry("SRCPORT", LFT_CLIENT_PORT),
+    TokenTableEntry("SRC", LFT_CLIENT_IP_ADDRESS), // keep after longer SRC* tokens
+    TokenTableEntry("TAG", LFT_TAG),
+    TokenTableEntry("URI", LFT_CLIENT_REQ_URI),
+#if USE_OPENSSL
+    TokenTableEntry("USER_CERTCHAIN", LFT_EXT_ACL_USER_CERTCHAIN_RAW),
+    TokenTableEntry("USER_CERT", LFT_EXT_ACL_USER_CERT_RAW),
+#endif
     TokenTableEntry(NULL, LFT_NONE)        /* this must be last */
 };
 
 #if USE_ADAPTATION
 static TokenTableEntry TokenTableAdapt[] = {
     TokenTableEntry("all_trs", LFT_ADAPTATION_ALL_XACT_TIMES),
     TokenTableEntry("sum_trs", LFT_ADAPTATION_SUM_XACT_TIMES),
     TokenTableEntry("<last_h", LFT_ADAPTATION_LAST_HEADER),
     TokenTableEntry(NULL, LFT_NONE)           /* this must be last */
 };
 #endif
 
 #if ICAP_CLIENT
 /// ICAP (icap::) tokens
 static TokenTableEntry TokenTableIcap[] = {
     TokenTableEntry("tt", LFT_ICAP_TOTAL_TIME),
     TokenTableEntry("<last_h", LFT_ADAPTATION_LAST_HEADER), // deprecated
 
     TokenTableEntry("<A",  LFT_ICAP_ADDR),
     TokenTableEntry("<service_name",  LFT_ICAP_SERV_NAME),
@@ -185,41 +211,40 @@
 
 #if USE_OPENSSL
 // SSL (ssl::) tokens
 static TokenTableEntry TokenTableSsl[] = {
     TokenTableEntry("bump_mode", LFT_SSL_BUMP_MODE),
     TokenTableEntry(">cert_subject", LFT_SSL_USER_CERT_SUBJECT),
     TokenTableEntry(">cert_issuer", LFT_SSL_USER_CERT_ISSUER),
     TokenTableEntry(">sni", LFT_SSL_CLIENT_SNI),
     /*TokenTableEntry("<cert_subject", LFT_SSL_SERVER_CERT_SUBJECT), */
     /*TokenTableEntry("<cert_issuer", LFT_SSL_SERVER_CERT_ISSUER), */
     TokenTableEntry(NULL, LFT_NONE)
 };
 #endif
 } // namespace Format
 
 /// Register all components custom format tokens
 void
 Format::Token::Init()
 {
     // TODO standard log tokens
-    // TODO external ACL fmt tokens
 
 #if USE_ADAPTATION
     TheConfig.registerTokens(String("adapt"),::Format::TokenTableAdapt);
 #endif
 #if ICAP_CLIENT
     TheConfig.registerTokens(String("icap"),::Format::TokenTableIcap);
 #endif
 #if USE_OPENSSL
     TheConfig.registerTokens(String("ssl"),::Format::TokenTableSsl);
 #endif
 }
 
 /// Scans a token table to see if the next token exists there
 /// returns a pointer to next unparsed byte and updates type member if found
 const char *
 Format::Token::scanForToken(TokenTableEntry const table[], const char *cur)
 {
     for (TokenTableEntry const *lte = table; lte->configTag != NULL; ++lte) {
         debugs(46, 8, HERE << "compare tokens '" << lte->configTag << "' with '" << cur << "'");
         if (strncmp(lte->configTag, cur, strlen(lte->configTag)) == 0) {
@@ -318,40 +343,41 @@
             left = true;
             ++cur;
         }
 
         if (*cur == '0') {
             zero = true;
             ++cur;
         }
 
         char *endp;
         if (xisdigit(*cur)) {
             widthMin = strtol(cur, &endp, 10);
             cur = endp;
         }
 
         if (*cur == '.' && xisdigit(*(++cur))) {
             widthMax = strtol(cur, &endp, 10);
             cur = endp;
         }
 
+        // when {arg} field is before the token (old logformat syntax)
         if (*cur == '{') {
             char *cp;
             ++cur;
             l = strcspn(cur, "}");
             cp = (char *)xmalloc(l + 1);
             xstrncpy(cp, cur, l + 1);
             data.string = cp;
             cur += l;
 
             if (*cur == '}')
                 ++cur;
         }
 
         type = LFT_NONE;
 
         // Scan each registered token namespace
         debugs(46, 9, HERE << "check for token in " << TheConfig.tokens.size() << " namespaces.");
         for (std::list<TokenNamespace>::const_iterator itr = TheConfig.tokens.begin(); itr != TheConfig.tokens.end(); ++itr) {
             debugs(46, 7, HERE << "check for possible " << itr->prefix << ":: token");
             const size_t len = itr->prefix.size();
@@ -378,40 +404,55 @@
 
             // Scan for various long tokens
             debugs(46, 5, HERE << "scan for possible Misc token");
             cur = scanForToken(TokenTableMisc, cur);
             // scan for 2-char tokens
             if (type == LFT_NONE) {
                 debugs(46, 5, HERE << "scan for possible 2C token");
                 cur = scanForToken(TokenTable2C, cur);
             }
             // finally scan for 1-char tokens.
             if (type == LFT_NONE) {
                 debugs(46, 5, HERE << "scan for possible 1C token");
                 cur = scanForToken(TokenTable1C, cur);
             }
         }
 
         if (type == LFT_NONE) {
             fatalf("Can't parse configuration token: '%s'\n", def);
         }
 
+        // when {arg} field is after the token (old external_acl_type token syntax)
+        // but accept only if there was none before the token
+        if (*cur == '{' && !data.string) {
+            char *cp;
+            ++cur;
+            l = strcspn(cur, "}");
+            cp = (char *)xmalloc(l + 1);
+            xstrncpy(cp, cur, l + 1);
+            data.string = cp;
+            cur += l;
+
+            if (*cur == '}')
+                ++cur;
+        }
+
         if (*cur == ' ') {
             space = true;
             ++cur;
         }
     }
 
     switch (type) {
 
 #if USE_ADAPTATION
     case LFT_ADAPTATION_LAST_HEADER:
 #endif
 
 #if ICAP_CLIENT
     case LFT_ICAP_REQ_HEADER:
 
     case LFT_ICAP_REP_HEADER:
 #endif
 
     case LFT_ADAPTED_REQUEST_HEADER:
 



More information about the squid-dev mailing list