]> git.ipfire.org Git - thirdparty/squid.git/commitdiff
Support logformat %codes in error page templates (#365) M-staged-PR365
authorChristos Tsantilas <christos@chtsanti.net>
Mon, 4 Mar 2019 12:43:27 +0000 (12:43 +0000)
committerSquid Anubis <squid-anubis@squid-cache.org>
Tue, 5 Mar 2019 14:43:48 +0000 (14:43 +0000)
... using `@Squid{%code}` syntax which allows Squid to distinguish newly
supported logformat %codes like `%http::<rm` from classic single-letter
error page %codes like `%M` (that are never enclosed in `@Squid{}`).

This improvement gives error pages access to more information about the
failed transaction and paves the way towards unifying the two partially
duplicated %code interfaces (and underlying code).

Some single-letter error page %codes have similar logformat %codes. For
example, error page `%i` will often be replaced with the same characters
as `@Squid{%>a}`. However, admins should not rely on expansion
equivalence because most codes may be rendered differently under some
conditions, and the two rendering algorithms are not kept in sync. For
example, error page `%i` does not account for log_uses_indirect_client.

Only a few letters remain unused by the error page %codes. Most
(possibly all) new codes should be added to the logformat instead,
within an appropriate error_page:: namespace if needed.

Side effects of updating error page parsing and assembly code include:

* Leave bad %codes in deny_info URLs instead of removing them. This
  helps in triage and is consistent with how bad %codes are already
  handled in error pages.

* Report bad deny_info %codes at startup and reconfiguration. Startup
  errors should eventually become fatal, just like logformat errors are.

* Report bad error page %codes at startup and reconfiguration (except
  for `%;` sequences found in all existing error pages templates).

* Fixed parsing of non-default error-details.txt that did not end with
  an empty line.

* Fixed unterminated c-string printing when reporting error-details.txt
  parsing problems.

* Upgraded MsgBody to use SBuf.

This is a Measurement Factory project.

40 files changed:
src/CacheManager.h
src/ConfigParser.cc
src/ConfigParser.h
src/FwdState.cc
src/HttpBody.cc
src/HttpBody.h
src/acl/AclDenyInfoList.h
src/acl/Gadgets.cc
src/cache_manager.cc
src/client_side.cc
src/client_side_reply.cc
src/client_side_request.cc
src/clients/Client.cc
src/clients/FtpClient.cc
src/clients/FtpGateway.cc
src/errorpage.cc
src/errorpage.h
src/esi/Esi.cc
src/format/Format.cc
src/format/Format.h
src/format/Token.cc
src/gopher.cc
src/http.cc
src/internal.cc
src/internal.h
src/log/Makefile.am
src/log/forward.h [new file with mode: 0644]
src/mgr/Forwarder.cc
src/mgr/Forwarder.h
src/mgr/Inquirer.cc
src/peer_select.cc
src/security/PeerConnector.cc
src/ssl/ErrorDetail.cc
src/ssl/ErrorDetailManager.cc
src/tests/stub_cache_manager.cc
src/tests/stub_errorpage.cc
src/tunnel.cc
src/urn.cc
src/urn.h
src/whois.cc

index 74089ab6be7d2c425da2217075e4ac2fc228e439..de5069384fdf2eacc1429aadc04c6574d494c133 100644 (file)
@@ -10,6 +10,7 @@
 #define SQUID_CACHEMANAGER_H
 
 #include "comm/forward.h"
+#include "log/forward.h"
 #include "mgr/Action.h"
 #include "mgr/ActionProfile.h"
 #include "mgr/Command.h"
@@ -42,7 +43,7 @@ public:
     Mgr::Action::Pointer createRequestedAction(const Mgr::ActionParams &);
     const Menu& menu() const { return menu_; }
 
-    void Start(const Comm::ConnectionPointer &client, HttpRequest * request, StoreEntry * entry);
+    void start(const Comm::ConnectionPointer &client, HttpRequest *request, StoreEntry *entry, const AccessLogEntryPointer &ale);
 
     static CacheManager* GetInstance();
     const char *ActionProtection(const Mgr::ActionProfilePointer &profile);
index 80d2b759c9d2bfd2498bbae6e3670a2bb4bc4cb3..ef96b01c114b333e99b7d6b9f7e5cb695afb94f2 100644 (file)
@@ -7,11 +7,13 @@
  */
 
 #include "squid.h"
+#include "base/Here.h"
 #include "cache_cf.h"
 #include "ConfigParser.h"
 #include "Debug.h"
 #include "fatal.h"
 #include "globals.h"
+#include "sbuf/Stream.h"
 
 bool ConfigParser::RecognizeQuotedValues = true;
 bool ConfigParser::StrictMode = true;
@@ -242,6 +244,12 @@ ConfigParser::SetCfgLine(char *line)
     }
 }
 
+SBuf
+ConfigParser::CurrentLocation()
+{
+    return ToSBuf(SourceLocation(cfg_directive, cfg_filename, config_lineno));
+}
+
 char *
 ConfigParser::TokenParse(const char * &nextToken, ConfigParser::TokenType &type)
 {
index e12b9694cfa3bedea463f515eceeebe76dfd05f4..1212bce5f299740a939adcdf706cdd9ca0f7f135 100644 (file)
 #define SQUID_CONFIGPARSER_H
 
 #include "SquidString.h"
+#include "sbuf/forward.h"
 
 #include <queue>
 #include <stack>
 #include <string>
 
 class wordlist;
+
 /**
  * Limit to how long any given config line may be.
  * This affects squid.conf and all included files.
@@ -125,6 +127,8 @@ public:
     /// Do not allow %macros inside quoted strings
     static void DisableMacros() {AllowMacros_ = false;}
 
+    static SBuf CurrentLocation();
+
     /// configuration_includes_quoted_values in squid.conf
     static bool RecognizeQuotedValues;
 
index 3c9cedada5908bd60c819df881840b1c6ee3a276..6c7a0147c47dacf91b4dd283cae21310411bb825 100644 (file)
@@ -248,7 +248,7 @@ FwdState::completed()
     if (entry->store_status == STORE_PENDING) {
         if (entry->isEmpty()) {
             if (!err) // we quit (e.g., fd closed) before an error or content
-                fail(new ErrorState(ERR_READ_ERROR, Http::scBadGateway, request));
+                fail(new ErrorState(ERR_READ_ERROR, Http::scBadGateway, request, al));
             assert(err);
             errorAppendEntry(entry, err);
             err = NULL;
@@ -333,7 +333,7 @@ FwdState::Start(const Comm::ConnectionPointer &clientConn, StoreEntry *entry, Ht
             if (page_id == ERR_NONE)
                 page_id = ERR_FORWARDING_DENIED;
 
-            ErrorState *anErr = new ErrorState(page_id, Http::scForbidden, request);
+            const auto anErr = new ErrorState(page_id, Http::scForbidden, request, al);
             errorAppendEntry(entry, anErr); // frees anErr
             return;
         }
@@ -352,14 +352,14 @@ FwdState::Start(const Comm::ConnectionPointer &clientConn, StoreEntry *entry, Ht
 
     if (shutting_down) {
         /* more yuck */
-        ErrorState *anErr = new ErrorState(ERR_SHUTTING_DOWN, Http::scServiceUnavailable, request);
+        const auto anErr = new ErrorState(ERR_SHUTTING_DOWN, Http::scServiceUnavailable, request, al);
         errorAppendEntry(entry, anErr); // frees anErr
         return;
     }
 
     if (request->flags.internal) {
         debugs(17, 2, "calling internalStart() due to request flag");
-        internalStart(clientConn, request, entry);
+        internalStart(clientConn, request, entry, al);
         return;
     }
 
@@ -367,7 +367,7 @@ FwdState::Start(const Comm::ConnectionPointer &clientConn, StoreEntry *entry, Ht
 
     case AnyP::PROTO_CACHE_OBJECT:
         debugs(17, 2, "calling CacheManager due to request scheme " << request->url.getScheme());
-        CacheManager::GetInstance()->Start(clientConn, request, entry);
+        CacheManager::GetInstance()->start(clientConn, request, entry, al);
         return;
 
     case AnyP::PROTO_URN:
@@ -430,7 +430,7 @@ FwdState::useDestinations()
 
         debugs(17, 3, HERE << "Connection failed: " << entry->url());
         if (!err) {
-            ErrorState *anErr = new ErrorState(ERR_CANNOT_FORWARD, Http::scInternalServerError, request);
+            const auto anErr = new ErrorState(ERR_CANNOT_FORWARD, Http::scInternalServerError, request, al);
             fail(anErr);
         } // else use actual error from last connection attempt
 
@@ -683,7 +683,7 @@ FwdState::retryOrBail()
     request->hier.stopPeerClock(false);
 
     if (self != NULL && !err && shutting_down && entry->isEmpty()) {
-        ErrorState *anErr = new ErrorState(ERR_SHUTTING_DOWN, Http::scServiceUnavailable, request);
+        const auto anErr = new ErrorState(ERR_SHUTTING_DOWN, Http::scServiceUnavailable, request, al);
         errorAppendEntry(entry, anErr);
     }
 
@@ -809,7 +809,7 @@ FwdState::connectTimeout(int fd)
     assert(fd == serverDestinations[0]->fd);
 
     if (entry->isEmpty()) {
-        ErrorState *anErr = new ErrorState(ERR_CONNECT_FAIL, Http::scGatewayTimeout, request);
+        const auto anErr = new ErrorState(ERR_CONNECT_FAIL, Http::scGatewayTimeout, request, al);
         anErr->xerrno = ETIMEDOUT;
         fail(anErr);
 
@@ -881,7 +881,7 @@ FwdState::connectStart()
     // origin server
     if (serverDestinations[0]->getPeer() && !serverDestinations[0]->getPeer()->options.originserver && request->flags.sslBumped) {
         debugs(50, 4, "fwdConnectStart: Ssl bumped connections through parent proxy are not allowed");
-        ErrorState *anErr = new ErrorState(ERR_CANNOT_FORWARD, Http::scServiceUnavailable, request);
+        const auto anErr = new ErrorState(ERR_CANNOT_FORWARD, Http::scServiceUnavailable, request, al);
         fail(anErr);
         stopAndDestroy("SslBump misconfiguration");
         return;
@@ -957,7 +957,7 @@ FwdState::usePinned()
     if (!Comm::IsConnOpen(temp)) {
         syncHierNote(temp, connManager ? connManager->pinning.host : request->url.host());
         serverConn = nullptr;
-        const auto anErr = new ErrorState(ERR_ZERO_SIZE_OBJECT, Http::scServiceUnavailable, request);
+        const auto anErr = new ErrorState(ERR_ZERO_SIZE_OBJECT, Http::scServiceUnavailable, request, al);
         fail(anErr);
         // Connection managers monitor their idle pinned to-server
         // connections and close from-client connections upon seeing
@@ -1088,7 +1088,7 @@ FwdState::dispatch()
 
         default:
             debugs(17, DBG_IMPORTANT, "WARNING: Cannot retrieve '" << entry->url() << "'.");
-            ErrorState *anErr = new ErrorState(ERR_UNSUP_REQ, Http::scBadRequest, request);
+            const auto anErr = new ErrorState(ERR_UNSUP_REQ, Http::scBadRequest, request, al);
             fail(anErr);
             // Set the dont_retry flag because this is not a transient (network) error.
             flags.dont_retry = true;
@@ -1164,7 +1164,7 @@ ErrorState *
 FwdState::makeConnectingError(const err_type type) const
 {
     return new ErrorState(type, request->flags.needValidation ?
-                          Http::scGatewayTimeout : Http::scServiceUnavailable, request);
+                          Http::scGatewayTimeout : Http::scServiceUnavailable, request, al);
 }
 
 static void
index 5591d62f2b6221f1276b603f40929704b2121b02..af5e49e8d4414534cb566405bae306068e15dcc7 100644 (file)
@@ -9,41 +9,14 @@
 /* DEBUG: section 56    HTTP Message Body */
 
 #include "squid.h"
+#include "base/Packable.h"
 #include "HttpBody.h"
-#include "MemBuf.h"
-
-HttpBody::HttpBody() : mb(new MemBuf)
-{}
-
-HttpBody::~HttpBody()
-{
-    delete mb;
-}
-
-void
-HttpBody::clear()
-{
-    mb->clean();
-}
-
-/* set body by absorbing mb */
-void
-HttpBody::setMb(MemBuf * mb_)
-{
-    delete mb;
-    /* note: protection against assign-to-self is not needed
-     * as MemBuf doesn't have a copy-constructor. If such a constructor
-     * is ever added, add such protection here.
-     */
-    mb = mb_;       /* absorb */
-}
 
 void
 HttpBody::packInto(Packable * p) const
 {
     assert(p);
-
-    if (mb->contentSize())
-        p->append(mb->content(), mb->contentSize());
+    if (const auto size = contentSize())
+        p->append(content(), size);
 }
 
index 5e27ed52c5de72e14f357c2c3b2d509a7a7bae37..32329af376e72e2373d8633cb954efcfe9e11e43 100644 (file)
@@ -9,7 +9,9 @@
 #ifndef HTTPBODY_H_
 #define HTTPBODY_H_
 
-#include "MemBuf.h"
+#include "sbuf/SBuf.h"
+
+class Packable; // TODO: Add and use base/forward.h.
 
 /** Representation of a short predetermined message
  *
 class HttpBody
 {
 public:
-    HttpBody();
-    ~HttpBody();
-    /** absorb the MemBuf, discarding anything currently stored
-     *
-     * After this call the lifetime of the passed MemBuf is managed
-     * by the HttpBody.
-     */
-    void setMb(MemBuf *);
+    HttpBody() {}
+
+    void set(const SBuf &newContent) { raw_ = newContent; }
 
     /** output the HttpBody contents into the supplied container
      *
@@ -35,20 +32,22 @@ public:
     void packInto(Packable *) const;
 
     /// clear the HttpBody content
-    void clear();
+    void clear() { raw_.clear(); }
 
     /// \return true if there is any content in the HttpBody
-    bool hasContent() const { return (mb->contentSize()>0); }
+    bool hasContent() const { return raw_.length() > 0; }
 
     /// \return size of the HttpBody's message content
-    mb_size_t contentSize() const { return mb->contentSize(); }
+    size_t contentSize() const { return raw_.length(); }
+
+    /// \return body bytes (possibly not nil-terminated)
+    const char *content() const { return raw_.rawContent(); }
 
-    /// \return pointer to the storage of the HttpBody
-    char *content() const { return mb->content(); }
 private:
     HttpBody& operator=(const HttpBody&); //not implemented
     HttpBody(const HttpBody&); // not implemented
-    MemBuf *mb;
+
+    SBuf raw_; // body bytes
 };
 
 #endif /* HTTPBODY_H_ */
index c231a9478b4532ba7ac9d8064907d6d45b3a660e..8ead25155fa4ea7ef321f11a9a8dc86e9db969a8 100644 (file)
@@ -13,6 +13,7 @@
 #include "err_type.h"
 #include "errorpage.h"
 #include "mem/forward.h"
+#include "sbuf/forward.h"
 
 /// deny_info representation. Currently a POD.
 class AclDenyInfoList
@@ -20,9 +21,9 @@ class AclDenyInfoList
     MEMPROXY_CLASS(AclDenyInfoList);
 
 public:
-    AclDenyInfoList(const char *t) {
+    AclDenyInfoList(const char *t, const SBuf &aCfgLocation) {
         err_page_name = xstrdup(t);
-        err_page_id = errorReservePageId(t);
+        err_page_id = errorReservePageId(t, aCfgLocation);
     }
     ~AclDenyInfoList() {
         xfree(err_page_name);
index 42fb1d2c485c91eaf071ee870e726d70b17af96b..d04055f19e630ad1a03a2ef7988b57aafe2aa7b5 100644 (file)
@@ -27,6 +27,7 @@
 #include "errorpage.h"
 #include "globals.h"
 #include "HttpRequest.h"
+#include "src/sbuf/Stream.h"
 
 #include <set>
 #include <algorithm>
@@ -112,7 +113,7 @@ aclParseDenyInfoLine(AclDenyInfoList ** head)
         return;
     }
 
-    AclDenyInfoList *A = new AclDenyInfoList(t);
+    const auto A = new AclDenyInfoList(t, ConfigParser::CurrentLocation());
 
     /* next expect a list of ACL names */
     while ((t = ConfigParser::NextToken())) {
index f88cd1c46b6b65e0f5e311302bbb8a25aa034911..21db8798154d4d1bb0131dbb99beb7258c22cd24 100644 (file)
@@ -9,6 +9,7 @@
 /* DEBUG: section 16    Cache Manager Objects */
 
 #include "squid.h"
+#include "AccessLogEntry.h"
 #include "base/TextException.h"
 #include "CacheManager.h"
 #include "comm/Connection.h"
@@ -302,13 +303,13 @@ CacheManager::CheckPassword(const Mgr::Command &cmd)
  * all needed internal work and renders the response.
  */
 void
-CacheManager::Start(const Comm::ConnectionPointer &client, HttpRequest * request, StoreEntry * entry)
+CacheManager::start(const Comm::ConnectionPointer &client, HttpRequest *request, StoreEntry *entry, const AccessLogEntry::Pointer &ale)
 {
     debugs(16, 3, "CacheManager::Start: '" << entry->url() << "'" );
 
     Mgr::Command::Pointer cmd = ParseUrl(entry->url());
     if (!cmd) {
-        ErrorState *err = new ErrorState(ERR_INVALID_URL, Http::scNotFound, request);
+        const auto err = new ErrorState(ERR_INVALID_URL, Http::scNotFound, request, ale);
         err->url = xstrdup(entry->url());
         errorAppendEntry(entry, err);
         entry->expires = squid_curtime;
@@ -331,7 +332,7 @@ CacheManager::Start(const Comm::ConnectionPointer &client, HttpRequest * request
 
     if (CheckPassword(*cmd) != 0) {
         /* build error message */
-        ErrorState errState(ERR_CACHE_MGR_ACCESS_DENIED, Http::scUnauthorized, request);
+        ErrorState errState(ERR_CACHE_MGR_ACCESS_DENIED, Http::scUnauthorized, request, ale);
         /* warn if user specified incorrect password */
 
         if (cmd->params.password.size()) {
@@ -385,7 +386,7 @@ CacheManager::Start(const Comm::ConnectionPointer &client, HttpRequest * request
 
     // special case: /squid-internal-mgr/ index page
     if (!strcmp(cmd->profile->name, "index")) {
-        ErrorState err(MGR_INDEX, Http::scOkay, request);
+        ErrorState err(MGR_INDEX, Http::scOkay, request, ale);
         err.url = xstrdup(entry->url());
         HttpReply *rep = err.BuildHttpReply();
         if (strncmp(rep->body.content(),"Internal Error:", 15) == 0)
@@ -405,7 +406,7 @@ CacheManager::Start(const Comm::ConnectionPointer &client, HttpRequest * request
 
     if (UsingSmp() && IamWorkerProcess()) {
         // is client the right connection to pass here?
-        AsyncJob::Start(new Mgr::Forwarder(client, cmd->params, request, entry));
+        AsyncJob::Start(new Mgr::Forwarder(client, cmd->params, request, entry, ale));
         return;
     }
 
index 2ee7518c13c2a5fa77a99ec5e8c4ce358e5c15de..5610bc4c0862c9caf2bc0516e12300b3d17de698 100644 (file)
@@ -1531,7 +1531,7 @@ bool ConnStateData::serveDelayedError(Http::Stream *context)
                 request->hier = sslServerBump->request->hier;
 
                 // Create an error object and fill it
-                ErrorState *err = new ErrorState(ERR_SECURE_CONNECT_FAIL, Http::scServiceUnavailable, request);
+                const auto err = new ErrorState(ERR_SECURE_CONNECT_FAIL, Http::scServiceUnavailable, request, http->al);
                 err->src_addr = clientConnection->remote;
                 Ssl::ErrorDetail *errDetail = new Ssl::ErrorDetail(
                     SQUID_X509_V_ERR_DOMAIN_MISMATCH,
index 755f73a1cdd2324a445e1ce51ff4fce0e0d5a2a5..eb8fd5e1699af93d9b976e3db77932a12b72cf18 100644 (file)
@@ -53,7 +53,7 @@ CBDATA_CLASS_INIT(clientReplyContext);
 
 /* Local functions */
 extern "C" CSS clientReplyStatus;
-ErrorState *clientBuildError(err_type, Http::StatusCode, char const *, Ip::Address &, HttpRequest *);
+ErrorState *clientBuildError(err_type, Http::StatusCode, char const *, Ip::Address &, HttpRequest *, const AccessLogEntry::Pointer &);
 
 /* privates */
 
@@ -111,7 +111,7 @@ clientReplyContext::setReplyToError(
 #endif
 )
 {
-    ErrorState *errstate = clientBuildError(err, status, uri, addr, failedrequest);
+    auto errstate = clientBuildError(err, status, uri, addr, failedrequest, http->al);
 
     if (unparsedrequest)
         errstate->request_hdrs = xstrdup(unparsedrequest);
@@ -767,7 +767,7 @@ clientReplyContext::processMiss()
         http->al->http.code = Http::scForbidden;
         Ip::Address tmp_noaddr;
         tmp_noaddr.setNoAddr();
-        err = clientBuildError(ERR_ACCESS_DENIED, Http::scForbidden, nullptr, conn ? conn->remote : tmp_noaddr, http->request);
+        err = clientBuildError(ERR_ACCESS_DENIED, Http::scForbidden, nullptr, conn ? conn->remote : tmp_noaddr, http->request, http->al);
         createStoreEntry(r->method, RequestFlags());
         errorAppendEntry(http->storeEntry(), err);
         triggerInitialStoreRead();
@@ -809,7 +809,7 @@ clientReplyContext::processOnlyIfCachedMiss()
     tmp_noaddr.setNoAddr();
     ErrorState *err = clientBuildError(ERR_ONLY_IF_CACHED_MISS, Http::scGatewayTimeout, NULL,
                                        http->getConn() ? http->getConn()->clientConnection->remote : tmp_noaddr,
-                                       http->request);
+                                       http->request, http->al);
     removeClientStoreReference(&sc, http);
     startError(err);
 }
@@ -986,7 +986,7 @@ clientReplyContext::purgeFoundObject(StoreEntry *entry)
         tmp_noaddr.setNoAddr(); // TODO: make a global const
         ErrorState *err = clientBuildError(ERR_ACCESS_DENIED, Http::scForbidden, NULL,
                                            http->getConn() ? http->getConn()->clientConnection->remote : tmp_noaddr,
-                                           http->request);
+                                           http->request, http->al);
         startError(err);
         return; // XXX: leaking unused entry if some store does not keep it
     }
@@ -1026,7 +1026,7 @@ clientReplyContext::purgeRequest()
         Ip::Address tmp_noaddr;
         tmp_noaddr.setNoAddr();
         ErrorState *err = clientBuildError(ERR_ACCESS_DENIED, Http::scForbidden, NULL,
-                                           http->getConn() ? http->getConn()->clientConnection->remote : tmp_noaddr, http->request);
+                                           http->getConn() ? http->getConn()->clientConnection->remote : tmp_noaddr, http->request, http->al);
         startError(err);
         return;
     }
@@ -1965,7 +1965,7 @@ clientReplyContext::sendBodyTooLargeError()
     http->logType.update(LOG_TCP_DENIED_REPLY);
     ErrorState *err = clientBuildError(ERR_TOO_BIG, Http::scForbidden, NULL,
                                        http->getConn() != NULL ? http->getConn()->clientConnection->remote : tmp_noaddr,
-                                       http->request);
+                                       http->request, http->al);
     removeClientStoreReference(&(sc), http);
     HTTPMSGUNLOCK(reply);
     startError(err);
@@ -1981,7 +1981,7 @@ clientReplyContext::sendPreconditionFailedError()
     tmp_noaddr.setNoAddr();
     ErrorState *const err =
         clientBuildError(ERR_PRECONDITION_FAILED, Http::scPreconditionFailed,
-                         NULL, http->getConn() ? http->getConn()->clientConnection->remote : tmp_noaddr, http->request);
+                         NULL, http->getConn() ? http->getConn()->clientConnection->remote : tmp_noaddr, http->request, http->al);
     removeClientStoreReference(&sc, http);
     HTTPMSGUNLOCK(reply);
     startError(err);
@@ -2094,7 +2094,7 @@ clientReplyContext::processReplyAccessResult(const allow_t &accessAllowed)
         tmp_noaddr.setNoAddr();
         err = clientBuildError(page_id, Http::scForbidden, NULL,
                                http->getConn() != NULL ? http->getConn()->clientConnection->remote : tmp_noaddr,
-                               http->request);
+                               http->request, http->al);
 
         removeClientStoreReference(&sc, http);
 
@@ -2337,9 +2337,9 @@ clientReplyContext::createStoreEntry(const HttpRequestMethod& m, RequestFlags re
 
 ErrorState *
 clientBuildError(err_type page_id, Http::StatusCode status, char const *url,
-                 Ip::Address &src_addr, HttpRequest * request)
+                 Ip::Address &src_addr, HttpRequest * request, const AccessLogEntry::Pointer &al)
 {
-    ErrorState *err = new ErrorState(page_id, status, request);
+    const auto err = new ErrorState(page_id, status, request, al);
     err->src_addr = src_addr;
 
     if (url)
index 07f0db61d83b5ce8029b7ae1915c7a10141a4ae7..0cc0a0412580eda370e356f296af434944f2dc3a 100644 (file)
@@ -84,7 +84,7 @@ static const char *const crlf = "\r\n";
 static void clientFollowXForwardedForCheck(allow_t answer, void *data);
 #endif /* FOLLOW_X_FORWARDED_FOR */
 
-ErrorState *clientBuildError(err_type, Http::StatusCode, char const *url, Ip::Address &, HttpRequest *);
+ErrorState *clientBuildError(err_type, Http::StatusCode, char const *url, Ip::Address &, HttpRequest *, const AccessLogEntry::Pointer &);
 
 CBDATA_CLASS_INIT(ClientRequestContext);
 
@@ -808,7 +808,7 @@ ClientRequestContext::clientAccessCheckDone(const allow_t &answer)
         error = clientBuildError(page_id, status,
                                  NULL,
                                  http->getConn() != NULL ? http->getConn()->clientConnection->remote : tmpnoaddr,
-                                 http->request
+                                 http->request, http->al
                                 );
 
 #if USE_AUTH
@@ -2171,7 +2171,8 @@ ClientHttpRequest::calloutsError(const err_type error, const int errDetail)
         calloutContext->error = clientBuildError(error, Http::scInternalServerError,
                                 NULL,
                                 c != NULL ? c->clientConnection->remote : noAddr,
-                                request
+                                request,
+                                al
                                                 );
 #if USE_AUTH
         calloutContext->error->auth_user_request =
index 98ab0f22d58c0e33220f090e7a82af5c5b5c68af..776fa3f3d31b3649d2f9b243c154d3239d660e25 100644 (file)
@@ -364,7 +364,7 @@ Client::sentRequestBody(const CommIoCbParams &io)
     if (io.flag) {
         debugs(11, DBG_IMPORTANT, "sentRequestBody error: FD " << io.fd << ": " << xstrerr(io.xerrno));
         ErrorState *err;
-        err = new ErrorState(ERR_WRITE_ERROR, Http::scBadGateway, fwd->request);
+        err = new ErrorState(ERR_WRITE_ERROR, Http::scBadGateway, fwd->request, fwd->al);
         err->xerrno = io.xerrno;
         fwd->fail(err);
         abortOnData("I/O error while sending request body");
@@ -856,7 +856,7 @@ Client::handledEarlyAdaptationAbort()
 {
     if (entry->isEmpty()) {
         debugs(11,8, "adaptation failure with an empty entry: " << *entry);
-        ErrorState *err = new ErrorState(ERR_ICAP_FAILURE, Http::scInternalServerError, request.getRaw());
+        const auto err = new ErrorState(ERR_ICAP_FAILURE, Http::scInternalServerError, request.getRaw(), fwd->al);
         err->detailError(ERR_DETAIL_ICAP_RESPMOD_EARLY);
         fwd->fail(err);
         fwd->dontRetry(true);
@@ -893,7 +893,7 @@ Client::handleAdaptationBlocked(const Adaptation::Answer &answer)
     if (page_id == ERR_NONE)
         page_id = ERR_ACCESS_DENIED;
 
-    ErrorState *err = new ErrorState(page_id, Http::scForbidden, request.getRaw());
+    const auto err = new ErrorState(page_id, Http::scForbidden, request.getRaw(), fwd->al);
     err->detailError(ERR_DETAIL_RESPMOD_BLOCK_EARLY);
     fwd->fail(err);
     fwd->dontRetry(true);
@@ -932,7 +932,7 @@ Client::noteAdaptationAclCheckDone(Adaptation::ServiceGroupPointer group)
 void
 Client::sendBodyIsTooLargeError()
 {
-    ErrorState *err = new ErrorState(ERR_TOO_BIG, Http::scForbidden, request.getRaw());
+    const auto err = new ErrorState(ERR_TOO_BIG, Http::scForbidden, request.getRaw(), fwd->al);
     fwd->fail(err);
     fwd->dontRetry(true);
     abortOnData("Virgin body too large.");
index f111fdb6eeabd0361032934509d1708121bf5850..34a6f4488015eefef5a3aac697051512959322d7 100644 (file)
@@ -257,7 +257,7 @@ Ftp::Client::failed(err_type error, int xerrno, ErrorState *err)
         ftperr = err;
     } else {
         Http::StatusCode httpStatus = failedHttpStatus(error);
-        ftperr = new ErrorState(error, httpStatus, fwd->request);
+        ftperr = new ErrorState(error, httpStatus, fwd->request, fwd->al);
     }
 
     ftperr->xerrno = xerrno;
index f06d128d869e4d1fe5a1e0e4f4338fe4b8f51cbe..b28007f26ad6a7868b17e50e270e9e71ea2767f8 100644 (file)
@@ -153,7 +153,7 @@ public:
     virtual void timeout(const CommTimeoutCbParams &io);
     void ftpAcceptDataConnection(const CommAcceptCbParams &io);
 
-    static HttpReply *ftpAuthRequired(HttpRequest * request, SBuf &realm);
+    static HttpReply *ftpAuthRequired(HttpRequest * request, SBuf &realm, AccessLogEntry::Pointer &);
     SBuf ftpRealm();
     void loginFailed(void);
 
@@ -1150,7 +1150,7 @@ Ftp::Gateway::start()
     if (!checkAuth(&request->header)) {
         /* create appropriate reply */
         SBuf realm(ftpRealm()); // local copy so SBuf will not disappear too early
-        HttpReply *reply = ftpAuthRequired(request.getRaw(), realm);
+        const auto reply = ftpAuthRequired(request.getRaw(), realm, fwd->al);
         entry->replaceHttpReply(reply);
         serverComplete();
         return;
@@ -1225,16 +1225,16 @@ Ftp::Gateway::loginFailed()
     if ((state == SENT_USER || state == SENT_PASS) && ctrl.replycode >= 400) {
         if (ctrl.replycode == 421 || ctrl.replycode == 426) {
             // 421/426 - Service Overload - retry permitted.
-            err = new ErrorState(ERR_FTP_UNAVAILABLE, Http::scServiceUnavailable, fwd->request);
+            err = new ErrorState(ERR_FTP_UNAVAILABLE, Http::scServiceUnavailable, fwd->request, fwd->al);
         } else if (ctrl.replycode >= 430 && ctrl.replycode <= 439) {
             // 43x - Invalid or Credential Error - retry challenge required.
-            err = new ErrorState(ERR_FTP_FORBIDDEN, Http::scUnauthorized, fwd->request);
+            err = new ErrorState(ERR_FTP_FORBIDDEN, Http::scUnauthorized, fwd->request, fwd->al);
         } else if (ctrl.replycode >= 530 && ctrl.replycode <= 539) {
             // 53x - Credentials Missing - retry challenge required
             if (password_url) // but they were in the URI! major fail.
-                err = new ErrorState(ERR_FTP_FORBIDDEN, Http::scForbidden, fwd->request);
+                err = new ErrorState(ERR_FTP_FORBIDDEN, Http::scForbidden, fwd->request, fwd->al);
             else
-                err = new ErrorState(ERR_FTP_FORBIDDEN, Http::scUnauthorized, fwd->request);
+                err = new ErrorState(ERR_FTP_FORBIDDEN, Http::scUnauthorized, fwd->request, fwd->al);
         }
     }
 
@@ -2263,7 +2263,7 @@ Ftp::Gateway::completedListing()
 {
     assert(entry);
     entry->lock("Ftp::Gateway");
-    ErrorState ferr(ERR_DIR_LISTING, Http::scOkay, request.getRaw());
+    ErrorState ferr(ERR_DIR_LISTING, Http::scOkay, request.getRaw(), fwd->al);
     ferr.ftp.listing = &listing;
     ferr.ftp.cwd_msg = xstrdup(cwd_message.size()? cwd_message.termedBuf() : "");
     ferr.ftp.server_msg = ctrl.message;
@@ -2437,7 +2437,7 @@ ftpFail(Ftp::Gateway *ftpState)
     }
 
     Http::StatusCode sc = ftpState->failedHttpStatus(error_code);
-    ErrorState *ftperr = new ErrorState(error_code, sc, ftpState->fwd->request);
+    const auto ftperr = new ErrorState(error_code, sc, ftpState->fwd->request, ftpState->fwd->al);
     ftpState->failed(error_code, code, ftperr);
     ftperr->detailError(code);
     HttpReply *newrep = ftperr->BuildHttpReply();
@@ -2505,7 +2505,7 @@ ftpSendReply(Ftp::Gateway * ftpState)
         http_code = Http::scInternalServerError;
     }
 
-    ErrorState err(err_code, http_code, ftpState->request.getRaw());
+    ErrorState err(err_code, http_code, ftpState->request.getRaw(), ftpState->fwd->al);
 
     if (ftpState->old_request)
         err.ftp.request = xstrdup(ftpState->old_request);
@@ -2626,9 +2626,9 @@ Ftp::Gateway::haveParsedReplyHeaders()
 }
 
 HttpReply *
-Ftp::Gateway::ftpAuthRequired(HttpRequest * request, SBuf &realm)
+Ftp::Gateway::ftpAuthRequired(HttpRequest * request, SBuf &realm, AccessLogEntry::Pointer &ale)
 {
-    ErrorState err(ERR_CACHE_ACCESS_DENIED, Http::scUnauthorized, request);
+    ErrorState err(ERR_CACHE_ACCESS_DENIED, Http::scUnauthorized, request, ale);
     HttpReply *newrep = err.BuildHttpReply();
 #if HAVE_AUTH_MODULE_BASIC
     /* add Authenticate header */
index 1535f6387f8c17634805c8488478842e370fe0c9..7e867e877fe3ef3ed02ca6bcda9530d0e8fc4eea 100644 (file)
@@ -9,6 +9,7 @@
 /* DEBUG: section 04    Error Generation */
 
 #include "squid.h"
+#include "AccessLogEntry.h"
 #include "cache_cf.h"
 #include "clients/forward.h"
 #include "comm/Connection.h"
@@ -16,6 +17,7 @@
 #include "err_detail_type.h"
 #include "errorpage.h"
 #include "fde.h"
+#include "format/Format.h"
 #include "fs_io.h"
 #include "html_quote.h"
 #include "HttpHeaderTools.h"
 #include "MemBuf.h"
 #include "MemObject.h"
 #include "rfc1738.h"
+#include "sbuf/Stream.h"
 #include "SquidConfig.h"
+#include "SquidTime.h"
 #include "Store.h"
 #include "tools.h"
 #include "wordlist.h"
 #if USE_AUTH
 #include "auth/UserRequest.h"
 #endif
-#include "SquidTime.h"
 #if USE_OPENSSL
 #include "ssl/ErrorDetailManager.h"
 #endif
 /// \ingroup ErrorPageInternal
 CBDATA_CLASS_INIT(ErrorState);
 
+const SBuf ErrorState::LogformatMagic("@Squid{");
+
 /* local types */
 
-/// \ingroup ErrorPageInternal
-typedef struct {
+/// an error page created from admin-configurable metadata (e.g. deny_info)
+class ErrorDynamicPageInfo {
+public:
+    ErrorDynamicPageInfo(const int anId, const char *aName, const SBuf &aCfgLocation);
+    ~ErrorDynamicPageInfo() { xfree(page_name); }
+
+    /// error_text[] index for response body (unused in redirection responses)
     int id;
+
+    /// Primary deny_info parameter:
+    /// * May start with an HTTP status code.
+    /// * Either a well-known error page name, a filename, or a redirect URL.
     char *page_name;
+
+    /// admin-configured HTTP Location header value for redirection responses
+    const char *uri;
+
+    /// admin-configured name for the error page template (custom or standard)
+    const char *filename;
+
+    /// deny_info directive position in squid.conf (for reporting)
+    SBuf cfgLocation;
+
+    // XXX: Misnamed. Not just for redirects.
+    /// admin-configured HTTP status code
     Http::StatusCode page_redirect;
-} ErrorDynamicPageInfo;
+
+private:
+    // no copying of any kind
+    ErrorDynamicPageInfo(ErrorDynamicPageInfo &&) = delete;
+};
+
+namespace ErrorPage {
+
+/// state and parameters shared by several ErrorState::compile*() methods
+class Build
+{
+public:
+    SBuf output; ///< compilation result
+    const char *input = nullptr; ///< template bytes that need to be compiled
+    bool building_deny_info_url = false; ///< whether we compile deny_info URI
+    bool allowRecursion = false; ///< whether top-level compile() calls are OK
+};
+
+/// pretty-prints error page/deny_info building error
+class BuildErrorPrinter
+{
+public:
+    BuildErrorPrinter(const SBuf &anInputLocation, int aPage, const char *aMsg, const char *aNear): inputLocation(anInputLocation), page_id(aPage), msg(aMsg), near(aNear) {}
+
+    /// reports error details (for admin-visible exceptions and debugging)
+    std::ostream &print(std::ostream &) const;
+
+    /// print() helper to report where the error was found
+    std::ostream &printLocation(std::ostream &os) const;
+
+    /* saved constructor parameters */
+    const SBuf &inputLocation;
+    const int page_id;
+    const char *msg;
+    const char *near;
+};
+
+static inline std::ostream &
+operator <<(std::ostream &os, const BuildErrorPrinter &context)
+{
+    return context.print(os);
+}
+
+static const char *IsDenyInfoUri(const int page_id);
+
+static void ImportStaticErrorText(const int page_id, const char *text, const SBuf &inputLocation);
+static void ValidateStaticError(const int page_id, const SBuf &inputLocation);
+
+} // namespace ErrorPage
 
 /* local constant and vars */
 
@@ -93,6 +167,18 @@ error_hard_text[] = {
     {
         TCP_RESET,
         "reset"
+    },
+    {
+        ERR_SECURE_ACCEPT_FAIL,
+        "secure accept fail"
+    },
+    {
+        ERR_REQUEST_START_TIMEOUT,
+        "request start timedout"
+    },
+    {
+        MGR_INDEX,
+        "mgr_index"
     }
 };
 
@@ -114,8 +200,6 @@ static int error_page_count = 0;
 static MemBuf error_stylesheet;
 
 static const char *errorFindHardText(err_type type);
-static ErrorDynamicPageInfo *errorDynamicPageInfoCreate(int id, const char *page_name);
-static void errorDynamicPageInfoDestroy(ErrorDynamicPageInfo * info);
 static IOCB errorSendComplete;
 
 /// \ingroup ErrorPageInternal
@@ -123,20 +207,16 @@ static IOCB errorSendComplete;
 class ErrorPageFile: public TemplateFile
 {
 public:
-    ErrorPageFile(const char *name, const err_type code) : TemplateFile(name,code) {textBuf.init();}
+    ErrorPageFile(const char *name, const err_type code) : TemplateFile(name, code) {}
 
     /// The template text data read from disk
-    const char *text() { return textBuf.content(); }
+    const char *text() { return template_.c_str(); }
 
-private:
-    /// stores the data read from disk to a local buffer
-    virtual bool parse(const char *buf, int len, bool) {
-        if (len)
-            textBuf.append(buf, len);
-        return true;
+protected:
+    virtual void setDefault() override {
+        template_ = "Internal Error: Missing Template ";
+        template_.append(templateName.termedBuf());
     }
-
-    MemBuf textBuf; ///< A buffer to store the error page
 };
 
 /// \ingroup ErrorPageInternal
@@ -153,9 +233,21 @@ int operator - (err_type const &anErr, err_type const &anErr2)
     return (int)anErr - (int)anErr2;
 }
 
+/// \return deny_info URL if the given page is a deny_info page with a URL
+/// \return nullptr otherwise
+static const char *
+ErrorPage::IsDenyInfoUri(const int page_id)
+{
+    if (ERR_MAX <= page_id && page_id < error_page_count)
+        return ErrorDynamicPages.at(page_id - ERR_MAX)->uri; // may be nil
+    return nullptr;
+}
+
 void
 errorInitialize(void)
 {
+    using ErrorPage::ImportStaticErrorText;
+
     err_type i;
     const char *text;
     error_page_count = ERR_MAX + ErrorDynamicPages.size();
@@ -168,7 +260,8 @@ errorInitialize(void)
             /**\par
              * Index any hard-coded error text into defaults.
              */
-            error_text[i] = xstrdup(text);
+            static const SBuf builtIn("built-in");
+            ImportStaticErrorText(i, text, builtIn);
 
         } else if (i < ERR_MAX) {
             /**\par
@@ -177,7 +270,8 @@ errorInitialize(void)
              *  (b) admin specified custom directory (error_directory)
              */
             ErrorPageFile errTmpl(err_type_str[i], i);
-            error_text[i] = errTmpl.loadDefault() ? xstrdup(errTmpl.text()) : NULL;
+            errTmpl.loadDefault();
+            ImportStaticErrorText(i, errTmpl.text(), errTmpl.filename);
         } else {
             /** \par
              * Index any unknown file names used by deny_info.
@@ -185,14 +279,14 @@ errorInitialize(void)
             ErrorDynamicPageInfo *info = ErrorDynamicPages.at(i - ERR_MAX);
             assert(info && info->id == i && info->page_name);
 
-            const char *pg = info->page_name;
-            if (info->page_redirect != Http::scNone)
-                pg = info->page_name +4;
-
-            if (strchr(pg, ':') == NULL) {
+            if (info->filename) {
                 /** But only if they are not redirection URL. */
-                ErrorPageFile errTmpl(pg, ERR_MAX);
-                error_text[i] = errTmpl.loadDefault() ? xstrdup(errTmpl.text()) : NULL;
+                ErrorPageFile errTmpl(info->filename, ERR_MAX);
+                errTmpl.loadDefault();
+                ImportStaticErrorText(i, errTmpl.text(), errTmpl.filename);
+            } else {
+                assert(info->uri);
+                ErrorPage::ValidateStaticError(i, info->cfgLocation);
             }
         }
     }
@@ -224,7 +318,7 @@ errorClean(void)
     }
 
     while (!ErrorDynamicPages.empty()) {
-        errorDynamicPageInfoDestroy(ErrorDynamicPages.back());
+        delete ErrorDynamicPages.back();
         ErrorDynamicPages.pop_back();
     }
 
@@ -253,11 +347,11 @@ TemplateFile::TemplateFile(const char *name, const err_type code): silent(false)
     assert(name);
 }
 
-bool
+void
 TemplateFile::loadDefault()
 {
     if (loaded()) // already loaded?
-        return true;
+        return;
 
     /** test error_directory configured location */
     if (Config.errorDirectory) {
@@ -283,11 +377,10 @@ TemplateFile::loadDefault()
     /* giving up if failed */
     if (!loaded()) {
         debugs(1, (templateCode < TCP_RESET ? DBG_CRITICAL : 3), "WARNING: failed to find or read error text file " << templateName);
-        parse("Internal Error: Missing Template ", 33, '\0');
-        parse(templateName.termedBuf(), templateName.size(), '\0');
+        template_.clear();
+        setDefault();
+        wasLoaded = true;
     }
-
-    return true;
 }
 
 bool
@@ -337,22 +430,29 @@ TemplateFile::loadFromFile(const char *path)
         return wasLoaded;
     }
 
+    template_.clear();
     while ((len = FD_READ_METHOD(fd, buf, sizeof(buf))) > 0) {
-        if (!parse(buf, len, false)) {
-            debugs(4, DBG_CRITICAL, "ERROR: parsing error in template file: " << path);
-            wasLoaded = false;
-            return wasLoaded;
-        }
+        template_.append(buf, len);
     }
-    parse(buf, 0, true);
 
     if (len < 0) {
         int xerrno = errno;
+        file_close(fd);
         debugs(4, DBG_CRITICAL, MYNAME << "ERROR: failed to fully read: '" << path << "': " << xstrerr(xerrno));
+        wasLoaded = false;
+        return false;
     }
 
     file_close(fd);
 
+    filename = SBuf(path);
+
+    if (!parse()) {
+        debugs(4, DBG_CRITICAL, "ERROR: parsing error in template file: " << path);
+        wasLoaded = false;
+        return false;
+    }
+
     wasLoaded = true;
     return wasLoaded;
 }
@@ -450,14 +550,41 @@ TemplateFile::loadFor(const HttpRequest *request)
     return loaded();
 }
 
-/// \ingroup ErrorPageInternal
-static ErrorDynamicPageInfo *
-errorDynamicPageInfoCreate(int id, const char *page_name)
+ErrorDynamicPageInfo::ErrorDynamicPageInfo(const int anId, const char *aName, const SBuf &aCfgLocation):
+    id(anId),
+    page_name(xstrdup(aName)),
+    uri(nullptr),
+    filename(nullptr),
+    cfgLocation(aCfgLocation),
+    page_redirect(static_cast<Http::StatusCode>(atoi(page_name)))
 {
-    ErrorDynamicPageInfo *info = new ErrorDynamicPageInfo;
-    info->id = id;
-    info->page_name = xstrdup(page_name);
-    info->page_redirect = static_cast<Http::StatusCode>(atoi(page_name));
+    const char *filenameOrUri = nullptr;
+    if (xisdigit(*page_name)) {
+        if (const char *statusCodeEnd = strchr(page_name, ':'))
+            filenameOrUri = statusCodeEnd + 1;
+    } else {
+        assert(!page_redirect);
+        filenameOrUri = page_name;
+    }
+
+    // Guessed uri, filename, or both values may be nil or malformed.
+    // They are validated later.
+    if (!page_redirect) {
+        if (filenameOrUri && strchr(filenameOrUri, ':')) // looks like a URL
+            uri = filenameOrUri;
+        else
+            filename = filenameOrUri;
+    }
+    else if (page_redirect/100 == 3) {
+        // redirects imply a URL
+        uri = filenameOrUri;
+    } else {
+        // non-redirects imply an error page name
+        filename = filenameOrUri;
+    }
+
+    const auto info = this; // source code change reduction hack
+    // TODO: Move and refactor to avoid self_destruct()s in reconfigure.
 
     /* WARNING on redirection status:
      * 2xx are permitted, but not documented officially.
@@ -490,17 +617,6 @@ errorDynamicPageInfoCreate(int id, const char *page_name)
         self_destruct();
     }
     // else okay.
-
-    return info;
-}
-
-/// \ingroup ErrorPageInternal
-static void
-errorDynamicPageInfoDestroy(ErrorDynamicPageInfo * info)
-{
-    assert(info);
-    safe_free(info->page_name);
-    delete info;
 }
 
 /// \ingroup ErrorPageInternal
@@ -521,15 +637,14 @@ errorPageId(const char *page_name)
 }
 
 err_type
-errorReservePageId(const char *page_name)
+errorReservePageId(const char *page_name, const SBuf &cfgLocation)
 {
-    ErrorDynamicPageInfo *info;
     int id = errorPageId(page_name);
 
     if (id == ERR_NONE) {
-        info = errorDynamicPageInfoCreate(ERR_MAX + ErrorDynamicPages.size(), page_name);
+        id = ERR_MAX + ErrorDynamicPages.size();
+        const auto info = new ErrorDynamicPageInfo(id, page_name, cfgLocation);
         ErrorDynamicPages.push_back(info);
-        id = info->id;
     }
 
     return (err_type)id;
@@ -549,18 +664,19 @@ errorPageName(int pageId)
 }
 
 ErrorState *
-ErrorState::NewForwarding(err_type type, HttpRequestPointer &request)
+ErrorState::NewForwarding(err_type type, HttpRequestPointer &request, const AccessLogEntry::Pointer &ale)
 {
     const Http::StatusCode status = (request && request->flags.needValidation) ?
                                     Http::scGatewayTimeout : Http::scServiceUnavailable;
-    return new ErrorState(type, status, request.getRaw());
+    return new ErrorState(type, status, request.getRaw(), ale);
 }
 
-ErrorState::ErrorState(err_type t, Http::StatusCode status, HttpRequest * req) :
+ErrorState::ErrorState(err_type t, Http::StatusCode status, HttpRequest * req, const AccessLogEntry::Pointer &anAle) :
     type(t),
     page_id(t),
     httpStatus(status),
-    callback(nullptr)
+    callback(nullptr),
+    ale(anAle)
 {
     if (page_id >= ERR_MAX && ErrorDynamicPages[page_id - ERR_MAX]->page_redirect != Http::scNone)
         httpStatus = ErrorDynamicPages[page_id - ERR_MAX]->page_redirect;
@@ -735,8 +851,44 @@ ErrorState::Dump(MemBuf * mb)
 /// \ingroup ErrorPageInternal
 #define CVT_BUF_SZ 512
 
-const char *
-ErrorState::Convert(char token, bool building_deny_info_url, bool allowRecursion)
+void
+ErrorState::compileLogformatCode(Build &build)
+{
+    assert(LogformatMagic.cmp(build.input, LogformatMagic.length()) == 0);
+
+    try {
+        const auto logformat = build.input + LogformatMagic.length();
+
+        // Logformat supports undocumented "external" encoding specifications
+        // like [%>h] or "%<a". To preserve the possibility of extending
+        // @Squid{} syntax to non-logformat sequences, we require logformat
+        // sequences to start with '%'. This restriction does not limit
+        // logformat quoting abilities. TODO: Deprecate "external" encoding?
+        if (*logformat != '%')
+            throw TexcHere("logformat expressions that do not start with % are not supported");
+
+        static MemBuf result;
+        result.reset();
+        const auto logformatLen = Format::AssembleOne(logformat, result, ale);
+        assert(logformatLen > 0);
+        const auto closure = logformat + logformatLen;
+        if (*closure != '}')
+            throw TexcHere("Missing closing brace (})");
+        build.output.append(result.content(), result.contentSize());
+        build.input = closure + 1;
+        return;
+    } catch (...) {
+        noteBuildError("Bad @Squid{logformat} sequence", build.input);
+    }
+
+    // we cannot recover reliably so stop interpreting the rest of input
+    const auto remainingSize = strlen(build.input);
+    build.output.append(build.input, remainingSize);
+    build.input += remainingSize;
+}
+
+void
+ErrorState::compileLegacyCode(Build &build)
 {
     static MemBuf mb;
     const char *p = NULL;   /* takes priority over mb if set */
@@ -746,7 +898,11 @@ ErrorState::Convert(char token, bool building_deny_info_url, bool allowRecursion
 
     mb.reset();
 
-    switch (token) {
+    const auto &building_deny_info_url = build.building_deny_info_url; // a change reduction hack
+
+    const auto letter = build.input[1];
+
+    switch (letter) {
 
     case 'a':
 #if USE_AUTH
@@ -784,7 +940,7 @@ ErrorState::Convert(char token, bool building_deny_info_url, bool allowRecursion
         break;
 
     case 'D':
-        if (!allowRecursion)
+        if (!build.allowRecursion)
             p = "%D";  // if recursion is not allowed, do not convert
 #if USE_OPENSSL
         // currently only SSL error details implemented
@@ -792,9 +948,8 @@ ErrorState::Convert(char token, bool building_deny_info_url, bool allowRecursion
             detail->useRequest(request.getRaw());
             const String &errDetail = detail->toString();
             if (errDetail.size() > 0) {
-                MemBuf *detail_mb  = ConvertText(errDetail.termedBuf(), false);
-                mb.append(detail_mb->content(), detail_mb->contentSize());
-                delete detail_mb;
+                const auto compiledDetail = compileBody(errDetail.termedBuf(), false);
+                mb.append(compiledDetail.rawContent(), compiledDetail.length());
                 do_quote = 0;
             }
         }
@@ -975,10 +1130,8 @@ ErrorState::Convert(char token, bool building_deny_info_url, bool allowRecursion
         if (page_id != ERR_SQUID_SIGNATURE) {
             const int saved_id = page_id;
             page_id = ERR_SQUID_SIGNATURE;
-            MemBuf *sign_mb = BuildContent();
-            mb.append(sign_mb->content(), sign_mb->contentSize());
-            sign_mb->clean();
-            delete sign_mb;
+            const auto signature = buildBody();
+            mb.append(signature.rawContent(), signature.length());
             page_id = saved_id;
             do_quote = 0;
         } else {
@@ -1063,7 +1216,13 @@ ErrorState::Convert(char token, bool building_deny_info_url, bool allowRecursion
         break;
 
     default:
-        mb.appendf("%%%c", token);
+        if (building_deny_info_url)
+            bypassBuildErrorXXX("Unsupported deny_info %code", build.input);
+        else if (letter != ';')
+            bypassBuildErrorXXX("Unsupported error page %code", build.input);
+        // else too many "font-size: 100%;" template errors to report
+
+        mb.append(build.input, 2);
         do_quote = 0;
         break;
     }
@@ -1073,7 +1232,7 @@ ErrorState::Convert(char token, bool building_deny_info_url, bool allowRecursion
 
     assert(p);
 
-    debugs(4, 3, "errorConvert: %%" << token << " --> '" << p << "'" );
+    debugs(4, 3, "%" << letter << " --> '" << p << "'" );
 
     if (do_quote)
         p = html_quote(p);
@@ -1081,30 +1240,21 @@ ErrorState::Convert(char token, bool building_deny_info_url, bool allowRecursion
     if (building_deny_info_url && !no_urlescape)
         p = rfc1738_escape_part(p);
 
-    return p;
+    // TODO: Optimize by replacing mb with direct build.output usage.
+    build.output.append(p, strlen(p));
+    build.input += 2;
 }
 
 void
-ErrorState::DenyInfoLocation(const char *name, HttpRequest *, MemBuf &result)
+ErrorState::validate()
 {
-    char const *m = name;
-    char const *p = m;
-    char const *t;
-
-    if (m[0] == '3')
-        m += 4; // skip "3xx:"
-
-    while ((p = strchr(m, '%'))) {
-        result.append(m, p - m);       /* copy */
-        t = Convert(*++p, true, true);       /* convert */
-        result.appendf("%s", t);        /* copy */
-        m = p + 1;                     /* advance */
+    if (const auto urlTemplate = ErrorPage::IsDenyInfoUri(page_id)) {
+        (void)compile(urlTemplate, true, true);
+    } else {
+        assert(page_id > ERR_NONE);
+        assert(page_id < error_page_count);
+        (void)compileBody(error_text[page_id], true);
     }
-
-    if (*m)
-        result.appendf("%s", m);        /* copy tail */
-
-    assert((size_t)result.contentSize() == strlen(result.content()));
 }
 
 HttpReply *
@@ -1114,7 +1264,7 @@ ErrorState::BuildHttpReply()
     const char *name = errorPageName(page_id);
     /* no LMT for error pages; error pages expire immediately */
 
-    if (name[0] == '3' || (name[0] != '2' && name[0] != '4' && name[0] != '5' && strchr(name, ':'))) {
+    if (const auto urlTemplate = ErrorPage::IsDenyInfoUri(page_id)) {
         /* Redirection */
         Http::StatusCode status = Http::scFound;
         // Use configured 3xx reply status if set.
@@ -1129,16 +1279,14 @@ ErrorState::BuildHttpReply()
         rep->setHeaders(status, NULL, "text/html;charset=utf-8", 0, 0, -1);
 
         if (request) {
-            MemBuf redirect_location;
-            redirect_location.init();
-            DenyInfoLocation(name, request.getRaw(), redirect_location);
-            httpHeaderPutStrf(&rep->header, Http::HdrType::LOCATION, "%s", redirect_location.content() );
+            auto location = compile(urlTemplate, true, true);
+            rep->header.putStr(Http::HdrType::LOCATION, location.c_str());
         }
 
         httpHeaderPutStrf(&rep->header, Http::HdrType::X_SQUID_ERROR, "%d %s", httpStatus, "Access Denied");
     } else {
-        MemBuf *content = BuildContent();
-        rep->setHeaders(httpStatus, NULL, "text/html;charset=utf-8", content->contentSize(), 0, -1);
+        const auto body = buildBody();
+        rep->setHeaders(httpStatus, NULL, "text/html;charset=utf-8", body.length(), 0, -1);
         /*
          * include some information for downstream caches. Implicit
          * replaceable content. This isn't quite sufficient. xerrno is not
@@ -1174,8 +1322,7 @@ ErrorState::BuildHttpReply()
                 rep->header.putStr(Http::HdrType::CONTENT_LANGUAGE, "en");
         }
 
-        rep->body.setMb(content);
-        /* do not memBufClean() or delete the content, it was absorbed by httpBody */
+        rep->body.set(body);
     }
 
     // Make sure error codes get back to the client side for logging and
@@ -1197,16 +1344,12 @@ ErrorState::BuildHttpReply()
     return rep;
 }
 
-MemBuf *
-ErrorState::BuildContent()
+SBuf
+ErrorState::buildBody()
 {
-    const char *m = NULL;
-
     assert(page_id > ERR_NONE && page_id < error_page_count);
 
 #if USE_ERR_LOCALES
-    ErrorPageFile *localeTmpl = NULL;
-
     /** error_directory option in squid.conf overrides translations.
      * Custom errors are always found either in error_directory or the templates directory.
      * Otherwise locate the Accept-Language header
@@ -1215,11 +1358,12 @@ ErrorState::BuildContent()
         if (err_language && err_language != Config.errorDefaultLanguage)
             safe_free(err_language);
 
-        localeTmpl = new ErrorPageFile(err_type_str[page_id], static_cast<err_type>(page_id));
-        if (localeTmpl->loadFor(request.getRaw())) {
-            m = localeTmpl->text();
-            assert(localeTmpl->language());
-            err_language = xstrdup(localeTmpl->language());
+        ErrorPageFile localeTmpl(err_type_str[page_id], static_cast<err_type>(page_id));
+        if (localeTmpl.loadFor(request.getRaw())) {
+            inputLocation = localeTmpl.filename;
+            assert(localeTmpl.language());
+            err_language = xstrdup(localeTmpl.language());
+            return compileBody(localeTmpl.text(), true);
         }
     }
 #endif /* USE_ERR_LOCALES */
@@ -1228,45 +1372,142 @@ ErrorState::BuildContent()
      * If client-specific error templates are not enabled or available.
      * fall back to the old style squid.conf settings.
      */
-    if (!m) {
-        m = error_text[page_id];
 #if USE_ERR_LOCALES
-        if (!Config.errorDirectory)
-            err_language = Config.errorDefaultLanguage;
+    if (!Config.errorDirectory)
+        err_language = Config.errorDefaultLanguage;
 #endif
-        debugs(4, 2, HERE << "No existing error page language negotiated for " << errorPageName(page_id) << ". Using default error file.");
-    }
+    debugs(4, 2, "No existing error page language negotiated for " << errorPageName(page_id) << ". Using default error file.");
+    return compileBody(error_text[page_id], true);
+}
 
-    MemBuf *result = ConvertText(m, true);
-#if USE_ERR_LOCALES
-    if (localeTmpl)
-        delete localeTmpl;
-#endif
-    return result;
+SBuf
+ErrorState::compileBody(const char *input, bool allowRecursion)
+{
+    return compile(input, false, allowRecursion);
 }
 
-MemBuf *ErrorState::ConvertText(const char *text, bool allowRecursion)
+SBuf
+ErrorState::compile(const char *input, bool building_deny_info_url, bool allowRecursion)
 {
-    MemBuf *content = new MemBuf;
-    const char *p;
-    const char *m = text;
-    assert(m);
-    content->init();
-
-    while ((p = strchr(m, '%'))) {
-        content->append(m, p - m);  /* copy */
-        const char *t = Convert(*++p, false, allowRecursion);   /* convert */
-        content->appendf("%s", t);   /* copy */
-        m = p + 1;          /* advance */
+    assert(input);
+
+    Build build;
+    build.building_deny_info_url = building_deny_info_url;
+    build.allowRecursion = allowRecursion;
+    build.input = input;
+
+    auto blockStart = build.input;
+    while (const auto letter = *build.input) {
+        if (letter == '%') {
+            build.output.append(blockStart, build.input - blockStart);
+            compileLegacyCode(build);
+            blockStart = build.input;
+        }
+        else if (letter == '@' && LogformatMagic.cmp(build.input, LogformatMagic.length()) == 0) {
+            build.output.append(blockStart, build.input - blockStart);
+            compileLogformatCode(build);
+            blockStart = build.input;
+        } else {
+            ++build.input;
+        }
+    }
+    build.output.append(blockStart, build.input - blockStart);
+    return build.output;
+}
+
+/// react to a compile() error
+/// \param msg  description of what went wrong
+/// \param near  approximate start of the problematic input
+/// \param  forceBypass whether detection of this error was introduced late,
+/// after old configurations containing this error could have been
+/// successfully validated and deployed (i.e. the admin may not be
+/// able to fix this newly detected but old problem quickly)
+void
+ErrorState::noteBuildError_(const char *msg, const char *near, const bool forceBypass)
+{
+    using ErrorPage::BuildErrorPrinter;
+    const auto runtime = !starting_up;
+    if (runtime || forceBypass) {
+        // swallow this problem because the admin may not be (and/or the page
+        // building code is not) ready to handle throwing consequences
+
+        static unsigned int seenErrors = 0;
+        ++seenErrors;
+
+        const auto debugLevel =
+            (seenErrors > 100) ? DBG_DATA:
+            (starting_up || reconfiguring) ? DBG_CRITICAL:
+            3; // most other errors have been reported as configuration errors
+
+        // Error fatality depends on the error context: Reconfiguration errors
+        // are, like startup ones, DBG_CRITICAL but will never become FATAL.
+        if (starting_up && seenErrors <= 10)
+            debugs(4, debugLevel, "WARNING: The following configuration error will be fatal in future Squid versions");
+
+        debugs(4, debugLevel, "ERROR: " << BuildErrorPrinter(inputLocation, page_id, msg, near));
+    } else {
+        throw TexcHere(ToSBuf(BuildErrorPrinter(inputLocation, page_id, msg, near)));
+    }
+}
+
+/* ErrorPage::BuildErrorPrinter */
+
+std::ostream &
+ErrorPage::BuildErrorPrinter::printLocation(std::ostream &os) const {
+    if (!inputLocation.isEmpty())
+        return os << inputLocation;
+
+    if (page_id < ERR_NONE || page_id >= error_page_count)
+        return os << "[error page " << page_id << "]"; // should not happen
+
+    if (page_id < ERR_MAX)
+        return os << err_type_str[page_id];
+
+    return os << "deny_info " << ErrorDynamicPages.at(page_id - ERR_MAX)->page_name;
+}
+
+std::ostream &
+ErrorPage::BuildErrorPrinter::print(std::ostream &os) const {
+    printLocation(os) << ": " << msg << " near ";
+
+    // TODO: Add support for prefix printing to Raw
+    const size_t maxContextLength = 15; // plus "..."
+    if (strlen(near) > maxContextLength) {
+        os.write(near, maxContextLength);
+        os << "...";
+    } else {
+        os << near;
     }
 
-    if (*m)
-        content->appendf("%s", m);   /* copy tail */
+    // XXX: We should not be converting (inner) exception to text if we are
+    // going to throw again. See "add arbitrary (re)thrower-supplied details"
+    // TODO in TextException.h for a long-term in-catcher solution.
+    if (std::current_exception())
+        os << "\n    additional info: " << CurrentException;
 
-    content->terminate();
+    return os;
+}
 
-    assert((size_t)content->contentSize() == strlen(content->content()));
+/// add error page template to the global index
+static void
+ErrorPage::ImportStaticErrorText(const int page_id, const char *text, const SBuf &inputLocation)
+{
+    assert(!error_text[page_id]);
+    error_text[page_id] = xstrdup(text);
+    ValidateStaticError(page_id, inputLocation);
+}
 
-    return content;
+/// validate static error page
+static void
+ErrorPage::ValidateStaticError(const int page_id, const SBuf &inputLocation)
+{
+    // Supplying nil ALE pointer limits validation to logformat %code
+    // recognition by Format::Token::parse(). This is probably desirable
+    // because actual %code assembly is slow and should not affect validation
+    // when our ALE cannot have any real data (this code is not associated
+    // with any real transaction).
+    ErrorState anErr(err_type(page_id), Http::scNone, nullptr, nullptr);
+    anErr.inputLocation = inputLocation;
+    anErr.validate();
 }
 
index 15bc3fc4b9f82dc684ca4ae73a156d62e423468c..002db460d65822a8375ccaa3fb3c61b8e5e62d2e 100644 (file)
@@ -18,6 +18,8 @@
 #include "http/forward.h"
 #include "http/StatusCode.h"
 #include "ip/Address.h"
+#include "log/forward.h"
+#include "sbuf/SBuf.h"
 #include "SquidString.h"
 /* auth/UserRequest.h is empty unless USE_AUTH is defined */
 #include "auth/UserRequest.h"
@@ -69,24 +71,32 @@ typedef void ERCB(int fd, void *, size_t);
    z - dns server error message                 x
    Z - Preformatted error message               x
  \endverbatim
+ *
+ * Plus logformat %codes embedded using @Squid{%logformat_code} syntax.
  */
 
 class MemBuf;
 class StoreEntry;
 class wordlist;
 
+namespace ErrorPage {
+
+class Build;
+
+} // namespace ErrorPage
+
 /// \ingroup ErrorPageAPI
 class ErrorState
 {
     CBDATA_CLASS(ErrorState);
 
 public:
-    ErrorState(err_type type, Http::StatusCode, HttpRequest * request);
+    ErrorState(err_type type, Http::StatusCode, HttpRequest * request, const AccessLogEntryPointer &al);
     ErrorState() = delete; // not implemented.
     ~ErrorState();
 
     /// Creates a general request forwarding error with the right http_status.
-    static ErrorState *NewForwarding(err_type, HttpRequestPointer &);
+    static ErrorState *NewForwarding(err_type, HttpRequestPointer &, const AccessLogEntryPointer &);
 
     /**
      * Allocates and initializes an error response
@@ -96,38 +106,51 @@ public:
     /// set error type-specific detail code
     void detailError(int dCode) {detailCode = dCode;}
 
-private:
-    /**
-     * Locates error page template to be used for this error
-     * and constructs the HTML page content from it.
-     */
-    MemBuf *BuildContent(void);
+    /// ensures that a future BuildHttpReply() is likely to succeed
+    void validate();
 
-    /**
-     * Convert the given template string into textual output
-     *
-     * \param text            The string to be converted
-     * \param allowRecursion  Whether to convert codes which output may contain codes
-     */
-    MemBuf *ConvertText(const char *text, bool allowRecursion);
-
-    /**
-     * Generates the Location: header value for a deny_info error page
-     * to be used for this error.
-     */
-    void DenyInfoLocation(const char *name, HttpRequest *request, MemBuf &result);
+    /// the source of the error template (for reporting purposes)
+    SBuf inputLocation;
 
-    /**
-     * Map the Error page and deny_info template % codes into textual output.
-     *
-     * Several of the codes produce blocks of non-URL compatible results.
-     * When processing the deny_info location URL they will be skipped.
-     *
-     * \param token                    The token following % which need to be converted
-     * \param building_deny_info_url   Perform special deny_info actions, such as URL-encoding and token skipping.
-     * \ allowRecursion   True if the codes which do recursions should converted
-     */
-    const char *Convert(char token, bool building_deny_info_url, bool allowRecursion);
+private:
+    typedef ErrorPage::Build Build;
+
+    /// locates the right error page template for this error and compiles it
+    SBuf buildBody();
+
+    /// compiles error page or error detail template (i.e. anything but deny_url)
+    /// \param input  the template text to be compiled
+    /// \param allowRecursion  whether to compile %codes which produce %codes
+    SBuf compileBody(const char *text, bool allowRecursion);
+
+    /// compile a single-letter %code like %D
+    void compileLegacyCode(Build &build);
+
+    /// compile @Squid{%code} sequence containing a single logformat %code
+    void compileLogformatCode(Build &build);
+
+    /// replaces all legacy and logformat %codes in the given input
+    /// \param input  the template text to be converted
+    /// \param building_deny_info_url  whether input is a deny_info URL parameter
+    /// \param allowRecursion  whether to compile %codes which produce %codes
+    /// \returns the given input with all %codes substituted
+    SBuf compile(const char *input, bool building_deny_info_url, bool allowRecursion);
+
+    /// React to a compile() error, throwing if buildContext allows.
+    /// \param msg description of what went wrong
+    /// \param near approximate start of the problematic input
+    void noteBuildError(const char *msg, const char *near) {
+        noteBuildError_(msg, near, false);
+    }
+
+    /// Note a compile() error but do not throw for backwards
+    /// compatibility with older configurations that may have such errors.
+    /// Should eventually be replaced with noteBuildError().
+    /// \param msg description of what went wrong
+    /// \param near approximate start of the problematic input
+    void bypassBuildErrorXXX(const char *msg, const char *near) {
+        noteBuildError_(msg, near, true);
+    }
 
     /**
      * CacheManager / Debug dump of the ErrorState object.
@@ -167,12 +190,19 @@ public:
     char *request_hdrs = nullptr;
     char *err_msg = nullptr; /* Preformatted error message from the cache */
 
+    AccessLogEntryPointer ale; ///< transaction details (or nil)
+
 #if USE_OPENSSL
     Ssl::ErrorDetail *detail = nullptr;
 #endif
     /// type-specific detail about the transaction error;
     /// overwrites xerrno; overwritten by detail, if any.
     int detailCode = ERR_DETAIL_NONE;
+
+private:
+    void noteBuildError_(const char *msg, const char *near, const bool forceBypass);
+
+    static const SBuf LogformatMagic; ///< marks each embedded logformat entry
 };
 
 /**
@@ -229,8 +259,8 @@ void errorSend(const Comm::ConnectionPointer &conn, ErrorState *err);
  */
 void errorAppendEntry(StoreEntry *entry, ErrorState *err);
 
-/// \ingroup ErrorPageAPI
-err_type errorReservePageId(const char *page_name);
+/// allocates a new slot for the error page
+err_type errorReservePageId(const char *page_name, const SBuf &cfgLocation);
 
 const char *errorPageName(int pageId); ///< error ID to string
 
@@ -254,8 +284,9 @@ public:
      *  (a) admin specified custom directory (error_directory)
      *  (b) default language translation directory (error_default_language)
      *  (c) English sub-directory where errors should ALWAYS exist
+     * If all of the above fail, setDefault() is called.
      */
-    bool loadDefault();
+    void loadDefault();
 
     /**
      * Load an error template for a given HTTP request. This function examines the
@@ -274,11 +305,16 @@ public:
     /// The language used for the template
     const char *language() {return errLanguage.termedBuf();}
 
+    SBuf filename; ///< where the template was loaded from
+
     bool silent; ///< Whether to print error messages on cache.log file or not. It is user defined.
 
 protected:
-    /// Used to parse (if parsing required) the template data .
-    virtual bool parse(const char *buf, int len, bool eof) = 0;
+    /// post-process the loaded template
+    virtual bool parse() { return true; }
+
+    /// recover from loadDefault() failure to load or parse() a template
+    virtual void setDefault() {}
 
     /**
      * Try to load the "page_name" template for a given language "lang"
@@ -287,6 +323,7 @@ protected:
      */
     bool tryLoadTemplate(const char *lang);
 
+    SBuf template_; ///< raw template contents
     bool wasLoaded; ///< True if the template data read from disk without any problem
     String errLanguage; ///< The error language of the template.
     String templateName; ///< The name of the template
index ebc1ecdf68c3b7cefb1233514a4421e08b287e35..c7c6a786225e3e006f7fb9c000b90f71b1b5f50a 100644 (file)
@@ -1402,7 +1402,7 @@ ESIContext::freeResources ()
     /* don't touch incoming, it's a pointer into buffered anyway */
 }
 
-ErrorState *clientBuildError (err_type, Http::StatusCode, char const *, Ip::Address &, HttpRequest *);
+ErrorState *clientBuildError(err_type, Http::StatusCode, char const *, Ip::Address &, HttpRequest *, const AccessLogEntry::Pointer &);
 
 /* This can ONLY be used before we have sent *any* data to the client */
 void
@@ -1420,10 +1420,11 @@ ESIContext::fail ()
     flags.error = 1;
     /* create an error object */
     // XXX: with the in-direction on remote IP. does the http->getConn()->clientConnection exist?
-    ErrorState * err = clientBuildError(errorpage, errorstatus, NULL, http->getConn()->clientConnection->remote, http->request);
+    const auto err = clientBuildError(errorpage, errorstatus, nullptr, http->getConn()->clientConnection->remote, http->request, http->al);
     err->err_msg = errormessage;
     errormessage = NULL;
     rep = err->BuildHttpReply();
+    // XXX: Leaking err!
     assert (rep->body.hasContent());
     size_t errorprogress = rep->body.contentSize();
     /* Tell esiSend where to start sending from */
index b0e79639c7f336fc50625bd81901ceef2167258e..062d199e28b4de64a64f370a8a795529e890c459 100644 (file)
@@ -95,6 +95,23 @@ Format::Format::parse(const char *def)
     return true;
 }
 
+size_t
+Format::AssembleOne(const char *token, MemBuf &mb, const AccessLogEntryPointer &ale)
+{
+    Token tkn;
+    enum Quoting quote = LOG_QUOTE_NONE;
+    const auto tokenSize = tkn.parse(token, &quote);
+    assert(tokenSize > 0);
+    if (ale != nullptr) {
+        Format fmt("SimpleToken");
+        fmt.format = &tkn;
+        fmt.assemble(mb, ale, 0);
+        fmt.format = nullptr;
+    } else
+        mb.append("-", 1);
+    return static_cast<size_t>(tokenSize);
+}
+
 void
 Format::Format::dump(StoreEntry * entry, const char *directiveName, bool eol) const
 {
index 3887d7d65f137adc5ad2deb5f0ef66b45545e6c7..afa220f544356eb6fc8b905503ac42ba4ecaa4da 100644 (file)
@@ -61,6 +61,12 @@ public:
     Format *next;
 };
 
+/// Compiles a single logformat %code expression into the given buffer.
+/// Ignores any input characters after the expression.
+/// \param start  where the logformat expression begins
+/// \return the length of the parsed %code expression
+size_t AssembleOne(const char *start, MemBuf &buf, const AccessLogEntryPointer &ale);
+
 } // namespace Format
 
 #endif /* _SQUID_FORMAT_FORMAT_H */
index e731b46948c8d595fd9afd4861b892b6e6cbe22d..edb4ce51754f041c9501bcc4a05c14647fcd689f 100644 (file)
@@ -442,9 +442,8 @@ Format::Token::parse(const char *def, Quoting *quoting)
             }
         }
 
-        if (type == LFT_NONE) {
-            fatalf("Can't parse configuration token: '%s'\n", def);
-        }
+        if (type == LFT_NONE)
+            throw TexcHere(ToSBuf("Unsupported %code: '", def, "'"));
 
         // when {arg} field is after the token (old external_acl_type token syntax)
         // but accept only if there was none before the token
index a50ad5f3e4441b7a469fc62ce1c597cf02bdf338..93a2bb914e50c2e4a1c20a8ab50b20192b19bf42 100644 (file)
@@ -710,7 +710,7 @@ gopherTimeout(const CommTimeoutCbParams &io)
     GopherStateData *gopherState = static_cast<GopherStateData *>(io.data);
     debugs(10, 4, HERE << io.conn << ": '" << gopherState->entry->url() << "'" );
 
-    gopherState->fwd->fail(new ErrorState(ERR_READ_TIMEOUT, Http::scGatewayTimeout, gopherState->fwd->request));
+    gopherState->fwd->fail(new ErrorState(ERR_READ_TIMEOUT, Http::scGatewayTimeout, gopherState->fwd->request, gopherState->fwd->al));
 
     if (Comm::IsConnOpen(io.conn))
         io.conn->close();
@@ -790,13 +790,13 @@ gopherReadReply(const Comm::ConnectionPointer &conn, char *buf, size_t len, Comm
                                                  CommIoCbPtrFun(gopherReadReply, gopherState));
             comm_read(conn, buf, read_sz, call);
         } else {
-            ErrorState *err = new ErrorState(ERR_READ_ERROR, Http::scInternalServerError, gopherState->fwd->request);
+            const auto err = new ErrorState(ERR_READ_ERROR, Http::scInternalServerError, gopherState->fwd->request, gopherState->fwd->al);
             err->xerrno = xerrno;
             gopherState->fwd->fail(err);
             gopherState->serverConn->close();
         }
     } else if (len == 0 && entry->isEmpty()) {
-        gopherState->fwd->fail(new ErrorState(ERR_ZERO_SIZE_OBJECT, Http::scServiceUnavailable, gopherState->fwd->request));
+        gopherState->fwd->fail(new ErrorState(ERR_ZERO_SIZE_OBJECT, Http::scServiceUnavailable, gopherState->fwd->request, gopherState->fwd->al));
         gopherState->serverConn->close();
     } else if (len == 0) {
         /* Connection closed; retrieval done. */
@@ -838,8 +838,7 @@ gopherSendComplete(const Comm::ConnectionPointer &conn, char *, size_t size, Com
     }
 
     if (errflag) {
-        ErrorState *err;
-        err = new ErrorState(ERR_WRITE_ERROR, Http::scServiceUnavailable, gopherState->fwd->request);
+        const auto err = new ErrorState(ERR_WRITE_ERROR, Http::scServiceUnavailable, gopherState->fwd->request, gopherState->fwd->al);
         err->xerrno = xerrno;
         err->port = gopherState->fwd->request->url.port();
         err->url = xstrdup(entry->url());
index fecdaf2d69a9fb17f1d0b5c7705a782d329d75f5..0a96bd950e7238607367974768bb03b8ef709d0b 100644 (file)
@@ -156,7 +156,7 @@ HttpStateData::httpTimeout(const CommTimeoutCbParams &)
     debugs(11, 4, serverConnection << ": '" << entry->url() << "'");
 
     if (entry->store_status == STORE_PENDING) {
-        fwd->fail(new ErrorState(ERR_READ_TIMEOUT, Http::scGatewayTimeout, fwd->request));
+        fwd->fail(new ErrorState(ERR_READ_TIMEOUT, Http::scGatewayTimeout, fwd->request, fwd->al));
     }
 
     closeServer();
@@ -1211,7 +1211,7 @@ HttpStateData::readReply(const CommIoCbParams &io)
     // case Comm::COMM_ERROR:
     default: // no other flags should ever occur
         debugs(11, 2, io.conn << ": read failure: " << xstrerr(rd.xerrno));
-        ErrorState *err = new ErrorState(ERR_READ_ERROR, Http::scBadGateway, fwd->request);
+        const auto err = new ErrorState(ERR_READ_ERROR, Http::scBadGateway, fwd->request, fwd->al);
         err->xerrno = rd.xerrno;
         fwd->fail(err);
         flags.do_next_read = false;
@@ -1314,7 +1314,7 @@ HttpStateData::continueAfterParsingHeader()
 
     assert(error != ERR_NONE);
     entry->reset();
-    fwd->fail(new ErrorState(error, Http::scBadGateway, fwd->request));
+    fwd->fail(new ErrorState(error, Http::scBadGateway, fwd->request, fwd->al));
     flags.do_next_read = false;
     closeServer();
     mustStop("HttpStateData::continueAfterParsingHeader");
@@ -1598,7 +1598,7 @@ HttpStateData::wroteLast(const CommIoCbParams &io)
     request->hier.notePeerWrite();
 
     if (io.flag) {
-        ErrorState *err = new ErrorState(ERR_WRITE_ERROR, Http::scBadGateway, fwd->request);
+        const auto err = new ErrorState(ERR_WRITE_ERROR, Http::scBadGateway, fwd->request, fwd->al);
         err->xerrno = io.xerrno;
         fwd->fail(err);
         closeServer();
@@ -2435,7 +2435,7 @@ HttpStateData::handleRequestBodyProducerAborted()
         // We might also get here if client-side aborts, but then our response
         // should not matter because either client-side will provide its own or
         // there will be no response at all (e.g., if the the client has left).
-        ErrorState *err = new ErrorState(ERR_ICAP_FAILURE, Http::scInternalServerError, fwd->request);
+        const auto err = new ErrorState(ERR_ICAP_FAILURE, Http::scInternalServerError, fwd->request, fwd->al);
         err->detailError(ERR_DETAIL_SRV_REQMOD_REQ_BODY);
         fwd->fail(err);
     }
index 11fae9bd47faaacf906b4867a0d792687ef08c66..5e18ab5995cd90220f9a9a0b5feff79eedaad5cf 100644 (file)
@@ -9,6 +9,7 @@
 /* DEBUG: section 76    Internal Squid Object handling */
 
 #include "squid.h"
+#include "AccessLogEntry.h"
 #include "CacheManager.h"
 #include "comm/Connection.h"
 #include "errorpage.h"
@@ -28,7 +29,7 @@
  * return Http::scNotFound for others
  */
 void
-internalStart(const Comm::ConnectionPointer &clientConn, HttpRequest * request, StoreEntry * entry)
+internalStart(const Comm::ConnectionPointer &clientConn, HttpRequest * request, StoreEntry * entry, const AccessLogEntry::Pointer &ale)
 {
     ErrorState *err;
     const SBuf upath = request->url.path();
@@ -55,11 +56,11 @@ internalStart(const Comm::ConnectionPointer &clientConn, HttpRequest * request,
         entry->complete();
     } else if (upath.startsWith(mgrPfx)) {
         debugs(17, 2, "calling CacheManager due to URL-path " << mgrPfx);
-        CacheManager::GetInstance()->Start(clientConn, request, entry);
+        CacheManager::GetInstance()->start(clientConn, request, entry, ale);
     } else {
         debugObj(76, 1, "internalStart: unknown request:\n",
                  request, (ObjPackMethod) & httpRequestPack);
-        err = new ErrorState(ERR_INVALID_REQ, Http::scNotFound, request);
+        err = new ErrorState(ERR_INVALID_REQ, Http::scNotFound, request, ale);
         errorAppendEntry(entry, err);
     }
 }
index 267c0b84c2df3ded2abbbabbcc136827bba0487f..158f787a1eb1921ccb58db01970f334ffc21e1c5 100644 (file)
 #define SQUID_INTERNAL_H_
 
 #include "comm/forward.h"
+#include "log/forward.h"
 #include "sbuf/forward.h"
 
 class HttpRequest;
 class StoreEntry;
 
-void internalStart(const Comm::ConnectionPointer &clientConn, HttpRequest *, StoreEntry *);
+void internalStart(const Comm::ConnectionPointer &clientConn, HttpRequest *, StoreEntry *, const AccessLogEntryPointer &);
 bool internalCheck(const SBuf &urlPath);
 bool internalStaticCheck(const SBuf &urlPath);
 char *internalLocalUri(const char *dir, const SBuf &name);
index 00763a4ddfcd5d6562fbf16fa743456cdcbf4ab7..ade3545b3dba12809c9545958e4826052758e42b 100644 (file)
@@ -29,6 +29,7 @@ liblog_la_SOURCES = \
        FormatSquidNative.cc \
        FormatSquidReferer.cc \
        FormatSquidUseragent.cc \
+       forward.h \
        ModDaemon.cc \
        ModDaemon.h \
        ModStdio.cc \
diff --git a/src/log/forward.h b/src/log/forward.h
new file mode 100644 (file)
index 0000000..9c19ba0
--- /dev/null
@@ -0,0 +1,18 @@
+/*
+ * Copyright (C) 1996-2017 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.
+ */
+
+#ifndef SQUID_FORMAT_FORWARD_H
+#define SQUID_FORMAT_FORWARD_H
+
+#include "base/RefCount.h"
+
+class AccessLogEntry;
+typedef RefCount<AccessLogEntry> AccessLogEntryPointer;
+
+#endif /* SQUID_FORMAT_FORWARD_H */
+
index fd282b319971db9ccedd3e4bfa8893e96eee70eb..2fb8120ad48fc722235e8e0239c5f8bc0590e6bb 100644 (file)
@@ -9,6 +9,7 @@
 /* DEBUG: section 16    Cache Manager API */
 
 #include "squid.h"
+#include "AccessLogEntry.h"
 #include "base/AsyncJobCalls.h"
 #include "base/TextException.h"
 #include "comm/Connection.h"
@@ -26,9 +27,9 @@
 CBDATA_NAMESPACED_CLASS_INIT(Mgr, Forwarder);
 
 Mgr::Forwarder::Forwarder(const Comm::ConnectionPointer &aConn, const ActionParams &aParams,
-                          HttpRequest* aRequest, StoreEntry* anEntry):
+                          HttpRequest* aRequest, StoreEntry* anEntry, const AccessLogEntryPointer &anAle):
     Ipc::Forwarder(new Request(KidIdentifier, 0, aConn, aParams), 10),
-    httpRequest(aRequest), entry(anEntry), conn(aConn)
+    httpRequest(aRequest), entry(anEntry), conn(aConn), ale(anAle)
 {
     debugs(16, 5, HERE << conn);
     Must(Comm::IsConnOpen(conn));
@@ -72,14 +73,14 @@ void
 Mgr::Forwarder::handleError()
 {
     debugs(16, DBG_CRITICAL, "ERROR: uri " << entry->url() << " exceeds buffer size");
-    sendError(new ErrorState(ERR_INVALID_URL, Http::scUriTooLong, httpRequest));
+    sendError(new ErrorState(ERR_INVALID_URL, Http::scUriTooLong, httpRequest, ale));
     mustStop("long URI");
 }
 
 void
 Mgr::Forwarder::handleTimeout()
 {
-    sendError(new ErrorState(ERR_LIFETIME_EXP, Http::scRequestTimeout, httpRequest));
+    sendError(new ErrorState(ERR_LIFETIME_EXP, Http::scRequestTimeout, httpRequest, ale));
     Ipc::Forwarder::handleTimeout();
 }
 
@@ -87,7 +88,7 @@ void
 Mgr::Forwarder::handleException(const std::exception &e)
 {
     if (entry != NULL && httpRequest != NULL && Comm::IsConnOpen(conn))
-        sendError(new ErrorState(ERR_INVALID_RESP, Http::scInternalServerError, httpRequest));
+        sendError(new ErrorState(ERR_INVALID_RESP, Http::scInternalServerError, httpRequest, ale));
     Ipc::Forwarder::handleException(e);
 }
 
index 761e0a9779bc17f21ebd7c973b2bf687673df4ff..d1bb79be6e60019500aac9700415b46b229fa6b0 100644 (file)
@@ -13,6 +13,7 @@
 
 #include "comm/forward.h"
 #include "ipc/Forwarder.h"
+#include "log/forward.h"
 #include "mgr/ActionParams.h"
 
 class CommCloseCbParams;
@@ -33,7 +34,7 @@ class Forwarder: public Ipc::Forwarder
 
 public:
     Forwarder(const Comm::ConnectionPointer &aConn, const ActionParams &aParams, HttpRequest* aRequest,
-              StoreEntry* anEntry);
+              StoreEntry* anEntry, const AccessLogEntryPointer &anAle);
     virtual ~Forwarder();
 
 protected:
@@ -52,6 +53,7 @@ private:
     StoreEntry* entry; ///< Store entry expecting the response
     Comm::ConnectionPointer conn; ///< HTTP client connection descriptor
     AsyncCall::Pointer closer; ///< comm_close handler for the HTTP connection
+    AccessLogEntryPointer ale; ///< more transaction details
 };
 
 } // namespace Mgr
index 71706a4b79cd45587f1e7a118da3872a7987697c..34f7b9fac2298f74678bcc30bb5b24cb3af886b7 100644 (file)
@@ -9,6 +9,7 @@
 /* DEBUG: section 16    Cache Manager API */
 
 #include "squid.h"
+#include "AccessLogEntry.h"
 #include "base/TextException.h"
 #include "comm.h"
 #include "comm/Connection.h"
@@ -77,7 +78,7 @@ Mgr::Inquirer::start()
         const char *url = aggrAction->command().params.httpUri.termedBuf();
         const MasterXaction::Pointer mx = new MasterXaction(XactionInitiator::initIpc);
         HttpRequest *req = HttpRequest::FromUrl(url, mx);
-        ErrorState err(ERR_INVALID_URL, Http::scNotFound, req);
+        ErrorState err(ERR_INVALID_URL, Http::scNotFound, req, nullptr);
         std::unique_ptr<HttpReply> reply(err.BuildHttpReply());
         replyBuf.reset(reply->pack());
     } else {
index 298c785c6d6ffaa6fe10496faff0825cfe072e91..2a265003dec8808e852c65d115b68fb09da812a3 100644 (file)
@@ -406,7 +406,7 @@ PeerSelector::noteIps(const Dns::CachedIps *ia, const Dns::LookupDetails &detail
         delete lastError;
         lastError = NULL;
         if (fs->code == HIER_DIRECT) {
-            lastError = new ErrorState(ERR_DNS_FAIL, Http::scServiceUnavailable, request);
+            lastError = new ErrorState(ERR_DNS_FAIL, Http::scServiceUnavailable, request, al);
             lastError->dnsError = details.error;
         }
     }
index da2e0ac44e94f8d94f1a30daa073105d466861d5..050c8a747d780b881baaa07ba149adc599ec2ccc 100644 (file)
@@ -111,7 +111,7 @@ Security::PeerConnector::initialize(Security::SessionPointer &serverSession)
         if (!ctx) {
             debugs(83, DBG_IMPORTANT, "Error initializing TLS connection: No security context.");
         } // else CreateClientSession() did the appropriate debugs() already
-        ErrorState *anErr = new ErrorState(ERR_SOCKET_FAILURE, Http::scInternalServerError, request.getRaw());
+        const auto anErr = new ErrorState(ERR_SOCKET_FAILURE, Http::scInternalServerError, request.getRaw(), al);
         anErr->xerrno = xerrno;
         noteNegotiationDone(anErr);
         bail(anErr);
@@ -250,7 +250,7 @@ Security::PeerConnector::sslFinalized()
                    " certificate: " << e.what() << "; will now block to " <<
                    "validate that certificate.");
             // fall through to do blocking in-process generation.
-            ErrorState *anErr = new ErrorState(ERR_GATEWAY_FAILURE, Http::scInternalServerError, request.getRaw());
+            const auto anErr = new ErrorState(ERR_GATEWAY_FAILURE, Http::scInternalServerError, request.getRaw(), al);
 
             noteNegotiationDone(anErr);
             bail(anErr);
@@ -300,9 +300,9 @@ Security::PeerConnector::sslCrtvdHandleReply(Ssl::CertValidationResponse::Pointe
 
     ErrorState *anErr = NULL;
     if (validatorFailed) {
-        anErr = new ErrorState(ERR_GATEWAY_FAILURE, Http::scInternalServerError, request.getRaw());
+        anErr = new ErrorState(ERR_GATEWAY_FAILURE, Http::scInternalServerError, request.getRaw(), al);
     }  else {
-        anErr =  new ErrorState(ERR_SECURE_CONNECT_FAIL, Http::scServiceUnavailable, request.getRaw());
+        anErr =  new ErrorState(ERR_SECURE_CONNECT_FAIL, Http::scServiceUnavailable, request.getRaw(), al);
         anErr->detail = errDetails;
         /*anErr->xerrno= Should preserved*/
     }
@@ -516,7 +516,7 @@ Security::PeerConnector::noteNegotiationError(const int ret, const int ssl_error
            ": " << Security::ErrorString(ssl_lib_error) << " (" <<
            ssl_error << "/" << ret << "/" << xerr << ")");
 
-    ErrorState *anErr = ErrorState::NewForwarding(ERR_SECURE_CONNECT_FAIL, request);
+    const auto anErr = ErrorState::NewForwarding(ERR_SECURE_CONNECT_FAIL, request, al);
     anErr->xerrno = sysErrNo;
 
 #if USE_OPENSSL
@@ -586,7 +586,7 @@ Security::PeerConnector::swanSong()
     AsyncJob::swanSong();
     if (callback != NULL) { // paranoid: we have left the caller waiting
         debugs(83, DBG_IMPORTANT, "BUG: Unexpected state while connecting to a cache_peer or origin server");
-        ErrorState *anErr = new ErrorState(ERR_GATEWAY_FAILURE, Http::scInternalServerError, request.getRaw());
+        const auto anErr = new ErrorState(ERR_GATEWAY_FAILURE, Http::scInternalServerError, request.getRaw(), al);
         bail(anErr);
         assert(!callback);
         return;
index a1be1969100d3c825f5f6ea46476fc2b51170ada..3ab5476e7ff4b7fdc1c161e229318f9d4811ec6e 100644 (file)
@@ -596,6 +596,7 @@ int Ssl::ErrorDetail::convert(const char *code, const char **value) const
             return len;
         }
     }
+    // TODO: Support logformat %codes.
     return 0;
 }
 
index f2b94bd0783997defd431a7bf30625bea9cac74d..f2cffb1103eaa27464bba13e31a88f977555f231 100644 (file)
@@ -31,14 +31,12 @@ class ErrorDetailFile : public TemplateFile
 {
 public:
     explicit ErrorDetailFile(ErrorDetailsList::Pointer const details): TemplateFile("error-details.txt", ERR_NONE) {
-        buf.init();
         theDetails = details;
     }
 
 private:
-    MemBuf buf;
     ErrorDetailsList::Pointer  theDetails;
-    virtual bool parse(const char *buf, int len, bool eof);
+    virtual bool parse() override;
 };
 }// namespace Ssl
 
@@ -188,24 +186,20 @@ public:
 inline size_t detailEntryEnd(const char *s, size_t len) {return headersEnd(s, len);}
 
 bool
-Ssl::ErrorDetailFile::parse(const char *buffer, int len, bool eof)
+Ssl::ErrorDetailFile::parse()
 {
     if (!theDetails)
         return false;
 
-    if (len) {
-        buf.append(buffer, len);
-    }
-
-    if (eof)
-        buf.append("\n\n", 1);
+    auto buf = template_;
+    buf.append("\n\n"); // ensure detailEntryEnd() finds the last entry
 
-    while (size_t size = detailEntryEnd(buf.content(), buf.contentSize())) {
-        const char *e = buf.content() + size;
+    while (const auto size = detailEntryEnd(buf.rawContent(), buf.length())) {
+        auto *s = buf.c_str();
+        const auto e = s + size;
 
         //ignore spaces, new lines and comment lines (starting with #) at the beggining
-        const char *s;
-        for (s = buf.content(); (*s == '\n' || *s == ' '  || *s == '\t' || *s == '#')  && s < e; ++s) {
+        for (; (*s == '\n' || *s == ' '  || *s == '\t' || *s == '#')  && s < e; ++s) {
             if (*s == '#')
                 while (s<e &&  *s != '\n')
                     ++s; // skip untill the end of line
@@ -244,6 +238,7 @@ Ssl::ErrorDetailFile::parse(const char *buffer, int len, bool eof)
                 const int detailsParseOk = httpHeaderParseQuotedString(tmp.termedBuf(), tmp.size(), &entry.detail);
                 tmp = parser.getByName("descr");
                 const int descrParseOk = httpHeaderParseQuotedString(tmp.termedBuf(), tmp.size(), &entry.descr);
+                // TODO: Validate "descr" and "detail" field values.
 
                 if (!detailsParseOk || !descrParseOk) {
                     debugs(83, DBG_IMPORTANT, HERE <<
@@ -261,7 +256,7 @@ Ssl::ErrorDetailFile::parse(const char *buffer, int len, bool eof)
 
         buf.consume(size);
     }
-    debugs(83, 9, HERE << " Remain size: " << buf.contentSize() << " Content: " << buf.content());
+    debugs(83, 9, Raw("unparsed data", buf.rawContent(), buf.length()));
     return true;
 }
 
index e8fbb202d3b2a24ba91253d0c9b6bb7cc1717242..f5023abdd3b1927543ec8bed9fb13211b75f66e8 100644 (file)
@@ -15,7 +15,7 @@
 #include "tests/STUB.h"
 
 Mgr::Action::Pointer CacheManager::createNamedAction(char const* action) STUB_RETVAL(NULL)
-void CacheManager::Start(const Comm::ConnectionPointer &conn, HttpRequest * request, StoreEntry * entry)
+void CacheManager::start(const Comm::ConnectionPointer &, HttpRequest *, StoreEntry *, const AccessLogEntryPointer &)
 {
     std::cerr << HERE << "\n";
     STUB
index 970f80c9063c279b789d42f559fcd3f0be06a8e7..696dc4c251d99d7fc58d89e0748431c7e0814270 100644 (file)
 #define STUB_API "errorpage.cc"
 #include "tests/STUB.h"
 
-err_type errorReservePageId(const char *page_name) STUB_RETVAL(err_type())
+err_type errorReservePageId(const char *, const SBuf &) STUB_RETVAL(err_type(0))
 void errorAppendEntry(StoreEntry * entry, ErrorState * err) STUB
 bool strHdrAcptLangGetItem(const String &hdr, char *lang, int langLen, size_t &pos) STUB_RETVAL(false)
-bool TemplateFile::loadDefault() STUB_RETVAL(false)
+void TemplateFile::loadDefault() STUB
 TemplateFile::TemplateFile(char const*, err_type) STUB
 bool TemplateFile::loadFor(const HttpRequest *) STUB_RETVAL(false)
 
index 5af4a5665d53d29bb756c0da2d8a3fe49c76c8c8..826bc32b961fffd01ad17358a5a895d3c16f2249 100644 (file)
@@ -465,7 +465,7 @@ TunnelStateData::informUserOfPeerError(const char *errMsg, const size_t sz)
 
     // if we have no reply suitable to relay, use 502 Bad Gateway
     if (!sz || sz > static_cast<size_t>(connectRespBuf->contentSize()))
-        return sendError(new ErrorState(ERR_CONNECT_FAIL, Http::scBadGateway, request.getRaw()),
+        return sendError(new ErrorState(ERR_CONNECT_FAIL, Http::scBadGateway, request.getRaw(), al),
                          "peer error without reply");
 
     // if we need to send back the server response. write its headers to the client
@@ -1038,7 +1038,7 @@ tunnelConnectDone(const Comm::ConnectionPointer &conn, Comm::Flag status, int xe
     TunnelStateData *tunnelState = (TunnelStateData *)data;
 
     if (status != Comm::OK) {
-        ErrorState *err = new ErrorState(ERR_CONNECT_FAIL, Http::scServiceUnavailable, tunnelState->request.getRaw());
+        const auto err = new ErrorState(ERR_CONNECT_FAIL, Http::scServiceUnavailable, tunnelState->request.getRaw(), tunnelState->al);
         err->xerrno = xerrno;
         // on timeout is this still:    err->xerrno = ETIMEDOUT;
         err->port = conn->remote.port();
@@ -1113,7 +1113,7 @@ tunnelStart(ClientHttpRequest * http)
         ch.syncAle(request, http->log_uri);
         if (ch.fastCheck().denied()) {
             debugs(26, 4, HERE << "MISS access forbidden.");
-            err = new ErrorState(ERR_FORWARDING_DENIED, Http::scForbidden, request);
+            err = new ErrorState(ERR_FORWARDING_DENIED, Http::scForbidden, request, http->al);
             http->al->http.code = Http::scForbidden;
             errorSend(http->getConn()->clientConnection, err);
             return;
@@ -1253,7 +1253,7 @@ TunnelStateData::noteDestinationsEnd(ErrorState *selectionError)
         if (savedError)
             return sendError(savedError, "all found paths have failed");
 
-        return sendError(new ErrorState(ERR_CANNOT_FORWARD, Http::scServiceUnavailable, request.getRaw()),
+        return sendError(new ErrorState(ERR_CANNOT_FORWARD, Http::scServiceUnavailable, request.getRaw(), al),
                          "path selection found no paths");
     }
     // else continue to use one of the previously noted destinations;
@@ -1325,7 +1325,7 @@ TunnelStateData::usePinned()
     debugs(26,7, "pinned peer connection: " << serverConn);
     if (!Comm::IsConnOpen(serverConn)) {
         // a PINNED path failure is fatal; do not wait for more paths
-        sendError(new ErrorState(ERR_CANNOT_FORWARD, Http::scServiceUnavailable, request.getRaw()),
+        sendError(new ErrorState(ERR_CANNOT_FORWARD, Http::scServiceUnavailable, request.getRaw(), al),
                   "pinned path failure");
         return;
     }
index 32982f7324065275c62437d515c13378e3823ef2..398bc4d5c7b0e990f7a8c9504bba191c9989f2ad 100644 (file)
@@ -48,6 +48,7 @@ public:
     StoreEntry *urlres_e = nullptr;
     HttpRequest::Pointer request;
     HttpRequest::Pointer urlres_r;
+    AccessLogEntry::Pointer ale; ///< details of the requesting transaction
 
     struct {
         bool force_menu = false;
@@ -61,7 +62,6 @@ private:
     virtual void fillChecklist(ACLFilledChecklist &) const;
 
     char *urlres = nullptr;
-    AccessLogEntry::Pointer ale; ///< master transaction summary
 };
 
 typedef struct {
@@ -163,7 +163,7 @@ UrnState::setUriResFromRequest(HttpRequest *r)
 
     if (!urlres_r) {
         debugs(52, 3, "Bad uri-res URL " << local_urlres);
-        ErrorState *err = new ErrorState(ERR_URN_RESOLVE, Http::scNotFound, r);
+        const auto err = new ErrorState(ERR_URN_RESOLVE, Http::scNotFound, r, ale);
         err->url = xstrdup(local_urlres);
         errorAppendEntry(entry, err);
         return;
@@ -267,7 +267,6 @@ urnHandleReply(void *data, StoreIOBuffer result)
     url_entry *urls;
     url_entry *u;
     url_entry *min_u;
-    MemBuf *mb = NULL;
     ErrorState *err;
     int i;
     int urlcnt = 0;
@@ -320,7 +319,7 @@ urnHandleReply(void *data, StoreIOBuffer result)
 
     if (rep->sline.status() != Http::scOkay) {
         debugs(52, 3, "urnHandleReply: failed.");
-        err = new ErrorState(ERR_URN_RESOLVE, Http::scNotFound, urnState->request.getRaw());
+        err = new ErrorState(ERR_URN_RESOLVE, Http::scNotFound, urnState->request.getRaw(), urnState->ale);
         err->url = xstrdup(e->url());
         errorAppendEntry(e, err);
         delete rep;
@@ -337,7 +336,7 @@ urnHandleReply(void *data, StoreIOBuffer result)
 
     if (!urls) {     /* unknown URN error */
         debugs(52, 3, "urnTranslateDone: unknown URN " << e->url());
-        err = new ErrorState(ERR_URN_RESOLVE, Http::scNotFound, urnState->request.getRaw());
+        err = new ErrorState(ERR_URN_RESOLVE, Http::scNotFound, urnState->request.getRaw(), urnState->ale);
         err->url = xstrdup(e->url());
         errorAppendEntry(e, err);
         urnHandleReplyError(urnState, urlres_e);
@@ -352,8 +351,8 @@ urnHandleReply(void *data, StoreIOBuffer result)
     min_u = urnFindMinRtt(urls, urnState->request->method, NULL);
     qsort(urls, urlcnt, sizeof(*urls), url_entry_sort);
     e->buffer();
-    mb = new MemBuf;
-    mb->init();
+    SBuf body;
+    SBuf *mb = &body; // diff reduction hack; TODO: Remove
     mb->appendf( "<TITLE>Select URL for %s</TITLE>\n"
                  "<STYLE type=\"text/css\"><!--BODY{background-color:#ffffff;font-family:verdana,sans-serif}--></STYLE>\n"
                  "<H2>Select URL for %s</H2>\n"
@@ -382,7 +381,7 @@ urnHandleReply(void *data, StoreIOBuffer result)
         "</ADDRESS>\n",
         APP_FULLNAME, getMyHostname());
     rep = new HttpReply;
-    rep->setHeaders(Http::scFound, NULL, "text/html", mb->contentSize(), 0, squid_curtime);
+    rep->setHeaders(Http::scFound, NULL, "text/html", mb->length(), 0, squid_curtime);
 
     if (urnState->flags.force_menu) {
         debugs(51, 3, "urnHandleReply: forcing menu");
@@ -390,8 +389,7 @@ urnHandleReply(void *data, StoreIOBuffer result)
         rep->header.putStr(Http::HdrType::LOCATION, min_u->url);
     }
 
-    rep->body.setMb(mb);
-    /* don't clean or delete mb; rep->body owns it now */
+    rep->body.set(body);
     e->replaceHttpReply(rep);
     e->complete();
 
@@ -401,7 +399,6 @@ urnHandleReply(void *data, StoreIOBuffer result)
     }
 
     safe_free(urls);
-    /* mb was absorbed in httpBodySet call, so we must not clean it */
     storeUnregister(urnState->sc, urlres_e, urnState);
 
     urnHandleReplyError(urnState, urlres_e);
index 46518d55a57901e0a610da5589da66c35114f812..60a411daf51c358c592c4cd87c977cb7c4ce2ac6 100644 (file)
--- a/src/urn.h
+++ b/src/urn.h
 #ifndef SQUID_URN_H_
 #define SQUID_URN_H_
 
-class AccessLogEntry;
+#include "log/forward.h"
+
 class HttpRequest;
 class StoreEntry;
 
-template <class C> class RefCount;
-typedef RefCount<AccessLogEntry> AccessLogEntryPointer;
-
 void urnStart(HttpRequest *, StoreEntry *, const AccessLogEntryPointer &ale);
 
 #endif /* SQUID_URN_H_ */
index decc506daedbc199b3fb084dcaf1834c58ba6d45..448d7238b34e6f3f635fb84ee206abb2318b75e6 100644 (file)
@@ -128,7 +128,7 @@ WhoisState::readReply(const Comm::ConnectionPointer &conn, char *aBuffer, size_t
                                                  CommIoCbPtrFun(whoisReadReply, this));
             comm_read(conn, aBuffer, BUFSIZ, call);
         } else {
-            ErrorState *err = new ErrorState(ERR_READ_ERROR, Http::scInternalServerError, fwd->request);
+            const auto err = new ErrorState(ERR_READ_ERROR, Http::scInternalServerError, fwd->request, fwd->al);
             err->xerrno = xerrno;
             fwd->fail(err);
             conn->close();