From: Christos Tsantilas Date: Mon, 4 Mar 2019 12:43:27 +0000 (+0000) Subject: Support logformat %codes in error page templates (#365) X-Git-Tag: M-staged-PR365 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=7e6eabbc6976f939079e2abbbc52c2a98d65bd89;p=thirdparty%2Fsquid.git Support logformat %codes in error page templates (#365) ... using `@Squid{%code}` syntax which allows Squid to distinguish newly supported logformat %codes like `%http::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. --- diff --git a/src/CacheManager.h b/src/CacheManager.h index 74089ab6be..de5069384f 100644 --- a/src/CacheManager.h +++ b/src/CacheManager.h @@ -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); diff --git a/src/ConfigParser.cc b/src/ConfigParser.cc index 80d2b759c9..ef96b01c11 100644 --- a/src/ConfigParser.cc +++ b/src/ConfigParser.cc @@ -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) { diff --git a/src/ConfigParser.h b/src/ConfigParser.h index e12b9694cf..1212bce5f2 100644 --- a/src/ConfigParser.h +++ b/src/ConfigParser.h @@ -10,12 +10,14 @@ #define SQUID_CONFIGPARSER_H #include "SquidString.h" +#include "sbuf/forward.h" #include #include #include 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; diff --git a/src/FwdState.cc b/src/FwdState.cc index 3c9cedada5..6c7a0147c4 100644 --- a/src/FwdState.cc +++ b/src/FwdState.cc @@ -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 diff --git a/src/HttpBody.cc b/src/HttpBody.cc index 5591d62f2b..af5e49e8d4 100644 --- a/src/HttpBody.cc +++ b/src/HttpBody.cc @@ -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); } diff --git a/src/HttpBody.h b/src/HttpBody.h index 5e27ed52c5..32329af376 100644 --- a/src/HttpBody.h +++ b/src/HttpBody.h @@ -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 * @@ -19,14 +21,9 @@ 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_ */ diff --git a/src/acl/AclDenyInfoList.h b/src/acl/AclDenyInfoList.h index c231a9478b..8ead25155f 100644 --- a/src/acl/AclDenyInfoList.h +++ b/src/acl/AclDenyInfoList.h @@ -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); diff --git a/src/acl/Gadgets.cc b/src/acl/Gadgets.cc index 42fb1d2c48..d04055f19e 100644 --- a/src/acl/Gadgets.cc +++ b/src/acl/Gadgets.cc @@ -27,6 +27,7 @@ #include "errorpage.h" #include "globals.h" #include "HttpRequest.h" +#include "src/sbuf/Stream.h" #include #include @@ -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())) { diff --git a/src/cache_manager.cc b/src/cache_manager.cc index f88cd1c46b..21db879815 100644 --- a/src/cache_manager.cc +++ b/src/cache_manager.cc @@ -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; } diff --git a/src/client_side.cc b/src/client_side.cc index 2ee7518c13..5610bc4c08 100644 --- a/src/client_side.cc +++ b/src/client_side.cc @@ -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, diff --git a/src/client_side_reply.cc b/src/client_side_reply.cc index 755f73a1cd..eb8fd5e169 100644 --- a/src/client_side_reply.cc +++ b/src/client_side_reply.cc @@ -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) diff --git a/src/client_side_request.cc b/src/client_side_request.cc index 07f0db61d8..0cc0a04125 100644 --- a/src/client_side_request.cc +++ b/src/client_side_request.cc @@ -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 = diff --git a/src/clients/Client.cc b/src/clients/Client.cc index 98ab0f22d5..776fa3f3d3 100644 --- a/src/clients/Client.cc +++ b/src/clients/Client.cc @@ -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."); diff --git a/src/clients/FtpClient.cc b/src/clients/FtpClient.cc index f111fdb6ee..34a6f44880 100644 --- a/src/clients/FtpClient.cc +++ b/src/clients/FtpClient.cc @@ -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; diff --git a/src/clients/FtpGateway.cc b/src/clients/FtpGateway.cc index f06d128d86..b28007f26a 100644 --- a/src/clients/FtpGateway.cc +++ b/src/clients/FtpGateway.cc @@ -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 */ diff --git a/src/errorpage.cc b/src/errorpage.cc index 1535f6387f..7e867e877f 100644 --- a/src/errorpage.cc +++ b/src/errorpage.cc @@ -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" @@ -24,14 +26,15 @@ #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 @@ -57,14 +60,85 @@ /// \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(atoi(page_name))) { - ErrorDynamicPageInfo *info = new ErrorDynamicPageInfo; - info->id = id; - info->page_name = xstrdup(page_name); - info->page_redirect = static_cast(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 "% 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(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(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(); } diff --git a/src/errorpage.h b/src/errorpage.h index 15bc3fc4b9..002db460d6 100644 --- a/src/errorpage.h +++ b/src/errorpage.h @@ -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 diff --git a/src/esi/Esi.cc b/src/esi/Esi.cc index ebc1ecdf68..c7c6a78622 100644 --- a/src/esi/Esi.cc +++ b/src/esi/Esi.cc @@ -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 */ diff --git a/src/format/Format.cc b/src/format/Format.cc index b0e79639c7..062d199e28 100644 --- a/src/format/Format.cc +++ b/src/format/Format.cc @@ -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, "e); + 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(tokenSize); +} + void Format::Format::dump(StoreEntry * entry, const char *directiveName, bool eol) const { diff --git a/src/format/Format.h b/src/format/Format.h index 3887d7d65f..afa220f544 100644 --- a/src/format/Format.h +++ b/src/format/Format.h @@ -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 */ diff --git a/src/format/Token.cc b/src/format/Token.cc index e731b46948..edb4ce5175 100644 --- a/src/format/Token.cc +++ b/src/format/Token.cc @@ -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 diff --git a/src/gopher.cc b/src/gopher.cc index a50ad5f3e4..93a2bb914e 100644 --- a/src/gopher.cc +++ b/src/gopher.cc @@ -710,7 +710,7 @@ gopherTimeout(const CommTimeoutCbParams &io) GopherStateData *gopherState = static_cast(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()); diff --git a/src/http.cc b/src/http.cc index fecdaf2d69..0a96bd950e 100644 --- a/src/http.cc +++ b/src/http.cc @@ -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); } diff --git a/src/internal.cc b/src/internal.cc index 11fae9bd47..5e18ab5995 100644 --- a/src/internal.cc +++ b/src/internal.cc @@ -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); } } diff --git a/src/internal.h b/src/internal.h index 267c0b84c2..158f787a1e 100644 --- a/src/internal.h +++ b/src/internal.h @@ -15,12 +15,13 @@ #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); diff --git a/src/log/Makefile.am b/src/log/Makefile.am index 00763a4ddf..ade3545b3d 100644 --- a/src/log/Makefile.am +++ b/src/log/Makefile.am @@ -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 index 0000000000..9c19ba08f0 --- /dev/null +++ b/src/log/forward.h @@ -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 AccessLogEntryPointer; + +#endif /* SQUID_FORMAT_FORWARD_H */ + diff --git a/src/mgr/Forwarder.cc b/src/mgr/Forwarder.cc index fd282b3199..2fb8120ad4 100644 --- a/src/mgr/Forwarder.cc +++ b/src/mgr/Forwarder.cc @@ -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); } diff --git a/src/mgr/Forwarder.h b/src/mgr/Forwarder.h index 761e0a9779..d1bb79be6e 100644 --- a/src/mgr/Forwarder.h +++ b/src/mgr/Forwarder.h @@ -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 diff --git a/src/mgr/Inquirer.cc b/src/mgr/Inquirer.cc index 71706a4b79..34f7b9fac2 100644 --- a/src/mgr/Inquirer.cc +++ b/src/mgr/Inquirer.cc @@ -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 reply(err.BuildHttpReply()); replyBuf.reset(reply->pack()); } else { diff --git a/src/peer_select.cc b/src/peer_select.cc index 298c785c6d..2a265003de 100644 --- a/src/peer_select.cc +++ b/src/peer_select.cc @@ -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; } } diff --git a/src/security/PeerConnector.cc b/src/security/PeerConnector.cc index da2e0ac44e..050c8a747d 100644 --- a/src/security/PeerConnector.cc +++ b/src/security/PeerConnector.cc @@ -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; diff --git a/src/ssl/ErrorDetail.cc b/src/ssl/ErrorDetail.cc index a1be196910..3ab5476e7f 100644 --- a/src/ssl/ErrorDetail.cc +++ b/src/ssl/ErrorDetail.cc @@ -596,6 +596,7 @@ int Ssl::ErrorDetail::convert(const char *code, const char **value) const return len; } } + // TODO: Support logformat %codes. return 0; } diff --git a/src/ssl/ErrorDetailManager.cc b/src/ssl/ErrorDetailManager.cc index f2b94bd078..f2cffb1103 100644 --- a/src/ssl/ErrorDetailManager.cc +++ b/src/ssl/ErrorDetailManager.cc @@ -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 static_cast(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; } diff --git a/src/urn.cc b/src/urn.cc index 32982f7324..398bc4d5c7 100644 --- a/src/urn.cc +++ b/src/urn.cc @@ -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( "Select URL for %s\n" "\n" "

Select URL for %s

\n" @@ -382,7 +381,7 @@ urnHandleReply(void *data, StoreIOBuffer result) "\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); diff --git a/src/urn.h b/src/urn.h index 46518d55a5..60a411daf5 100644 --- a/src/urn.h +++ b/src/urn.h @@ -11,13 +11,11 @@ #ifndef SQUID_URN_H_ #define SQUID_URN_H_ -class AccessLogEntry; +#include "log/forward.h" + class HttpRequest; class StoreEntry; -template class RefCount; -typedef RefCount AccessLogEntryPointer; - void urnStart(HttpRequest *, StoreEntry *, const AccessLogEntryPointer &ale); #endif /* SQUID_URN_H_ */ diff --git a/src/whois.cc b/src/whois.cc index decc506dae..448d7238b3 100644 --- a/src/whois.cc +++ b/src/whois.cc @@ -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();