=== modified file 'src/AccessLogEntry.h' --- src/AccessLogEntry.h 2013-03-17 12:19:16 +0000 +++ src/AccessLogEntry.h 2013-04-07 02:39:03 +0000 @@ -48,10 +48,10 @@ #include "ssl/gadgets.h" #endif -/* forward decls */ +class ErrorState; +class CustomLog; class HttpReply; class HttpRequest; -class CustomLog; class AccessLogEntry: public RefCountable { @@ -60,7 +60,7 @@ typedef RefCount Pointer; AccessLogEntry() : url(NULL), tcpClient(), reply(NULL), request(NULL), - adapted_request(NULL) {} + adapted_request(NULL), errorDetails(NULL) {} ~AccessLogEntry(); /// Fetch the client IP log string into the given buffer. @@ -279,6 +279,9 @@ } icap; #endif + + /// details of the error page presented to the client (if any) + ErrorState *errorDetails; }; class ACLChecklist; === modified file 'src/base/StringArea.h' --- src/base/StringArea.h 2011-10-05 00:19:46 +0000 +++ src/base/StringArea.h 2013-04-07 02:39:36 +0000 @@ -35,6 +35,7 @@ #if HAVE_CSTRING #include #endif +#include /** A char* plus length combination. Useful for temporary storing * and quickly looking up strings. @@ -56,6 +57,8 @@ return (theLen < s.theLen || (theLen == s.theLen && memcmp(theStart,s.theStart,theLen) < 0)) ; } + void print(std::ostream &os) const { os.write(theStart, theLen); } + private: /// pointed to the externally-managed memory area const char *theStart; @@ -63,4 +66,11 @@ size_t theLen; }; +inline std::ostream & +operator <<(std::ostream &os, const StringArea &a) +{ + a.print(os); + return os; +} + #endif /* SQUID_STRINGAREA_H */ === modified file 'src/errorpage.cc' --- src/errorpage.cc 2013-03-17 12:19:16 +0000 +++ src/errorpage.cc 2013-04-07 02:39:42 +0000 @@ -31,11 +31,13 @@ */ #include "squid.h" #include "cache_cf.h" +#include "client_side.h" #include "comm/Connection.h" #include "comm/Write.h" #include "disk.h" #include "err_detail_type.h" #include "errorpage.h" +#include "format/Format.h" #include "ftp.h" #include "Store.h" #include "html_quote.h" @@ -591,7 +593,9 @@ #if USE_SSL detail(NULL), #endif - detailCode(ERR_DETAIL_NONE) + detailCode(ERR_DETAIL_NONE), + building_deny_info_url(false), + allowRecursion(true) { memset(&ftp, 0, sizeof(ftp)); @@ -694,6 +698,10 @@ ErrorState::~ErrorState() { + if (ale_ != NULL) { + // ensure that the AccessLogEntry object has no reference to us + ale_->errorDetails = NULL; + } HTTPMSGUNLOCK(request); safe_free(redirect_url); safe_free(url); @@ -796,7 +804,7 @@ #define CVT_BUF_SZ 512 const char * -ErrorState::Convert(char token, bool building_deny_info_url, bool allowRecursion) +ErrorState::Convert(Format::ByteCode_t token) { static MemBuf mb; const char *p = NULL; /* takes priority over mb if set */ @@ -808,30 +816,7 @@ switch (token) { - case 'a': -#if USE_AUTH - if (request && request->auth_user_request != NULL) - p = request->auth_user_request->username(); - if (!p) -#endif - p = "-"; - break; - - case 'b': - mb.Printf("%d", getMyPort()); - break; - - case 'B': - if (building_deny_info_url) break; - p = request ? ftpUrlWith2f(request) : "[no URL]"; - break; - - case 'c': - if (building_deny_info_url) break; - p = errorPageName(type); - break; - - case 'D': + case Format::LFT_ERR_PAGE_UD: // '%D' if (!allowRecursion) p = "%D"; // if recursion is not allowed, do not convert #if USE_SSL @@ -840,7 +825,9 @@ detail->useRequest(request); const String &errDetail = detail->toString(); if (errDetail.defined()) { - MemBuf *detail_mb = ConvertText(errDetail.termedBuf(), false); + allowRecursion = false; + MemBuf *detail_mb = ConvertText(errDetail.termedBuf()); + allowRecursion = true; mb.append(detail_mb->content(), detail_mb->contentSize()); delete detail_mb; do_quote = 0; @@ -851,18 +838,18 @@ mb.Printf("[No Error Detail]"); break; - case 'e': + case Format::LFT_ERR_PAGE_LE: // '%e' mb.Printf("%d", xerrno); break; - case 'E': + case Format::LFT_ERR_PAGE_UE: // '%E' if (xerrno) mb.Printf("(%d) %s", xerrno, strerror(xerrno)); else mb.Printf("[No Error]"); break; - case 'f': + case Format::LFT_ERR_PAGE_LF: // '%f' if (building_deny_info_url) break; /* FTP REQUEST LINE */ if (ftp.request) @@ -871,7 +858,7 @@ p = "nothing"; break; - case 'F': + case Format::LFT_ERR_PAGE_UF: // '%F' if (building_deny_info_url) break; /* FTP REPLY LINE */ if (ftp.reply) @@ -880,7 +867,7 @@ p = "nothing"; break; - case 'g': + case Format::LFT_ERR_PAGE_LG: // '%g' if (building_deny_info_url) break; /* FTP SERVER RESPONSE */ if (ftp.listing) { @@ -891,38 +878,13 @@ } break; - case 'h': - mb.Printf("%s", getMyHostname()); - break; - - case 'H': - if (request) { - if (request->hier.host[0] != '\0') // if non-empty string. - p = request->hier.host; - else - p = request->GetHost(); - } else if (!building_deny_info_url) - p = "[unknown host]"; - break; - - case 'i': - mb.Printf("%s", src_addr.NtoA(ntoabuf,MAX_IPSTRLEN)); - break; - - case 'I': - if (request && request->hier.tcpServer != NULL) - p = request->hier.tcpServer->remote.NtoA(ntoabuf,MAX_IPSTRLEN); - else if (!building_deny_info_url) - p = "[unknown]"; - break; - - case 'l': + case Format::LFT_ERR_PAGE_LL: // '%l' if (building_deny_info_url) break; mb.append(error_stylesheet.content(), error_stylesheet.contentSize()); do_quote = 0; break; - case 'L': + case Format::LFT_ERR_PAGE_UL: // '%L' if (building_deny_info_url) break; if (Config.errHtmlText) { mb.Printf("%s", Config.errHtmlText); @@ -931,7 +893,7 @@ p = "[not available]"; break; - case 'm': + case Format::LFT_ERR_PAGE_LM: // '%m' if (building_deny_info_url) break; #if USE_AUTH p = auth_user_request->denyMessage("[not available]"); @@ -940,28 +902,13 @@ #endif break; - case 'M': - if (request) - p = RequestMethodStr(request->method); - else if (!building_deny_info_url) - p= "[unknown method]"; - break; - - case 'o': + case Format::LFT_ERR_PAGE_LO: // '%o' p = request ? request->extacl_message.termedBuf() : external_acl_message; if (!p && !building_deny_info_url) p = "[not available]"; break; - case 'p': - if (request) { - mb.Printf("%d", (int) request->port); - } else if (!building_deny_info_url) { - p = "[unknown port]"; - } - break; - - case 'P': + case Format::LFT_ERR_PAGE_UP: // '%P' if (request) { p = AnyP::ProtocolType_str[request->protocol]; } else if (!building_deny_info_url) { @@ -969,7 +916,7 @@ } break; - case 'R': + case Format::LFT_ERR_PAGE_UR: // '%R' if (building_deny_info_url) { p = (request->urlpath.size() != 0 ? request->urlpath.termedBuf() : "/"); no_urlescape = 1; @@ -999,7 +946,7 @@ } break; - case 's': + case Format::LFT_ERR_PAGE_LS: // '%s' /* for backward compat we make %s show the full URL. Drop this in some future release. */ if (building_deny_info_url) { p = request ? urlCanonical(request) : url; @@ -1008,7 +955,7 @@ p = visible_appname_string; break; - case 'S': + case Format::LFT_ERR_PAGE_US: // '%S' if (building_deny_info_url) { p = visible_appname_string; break; @@ -1029,49 +976,14 @@ } break; - case 't': - mb.Printf("%s", Time::FormatHttpd(squid_curtime)); - break; - - case 'T': - mb.Printf("%s", mkrfc1123(squid_curtime)); - break; - - case 'U': - /* Using the fake-https version of canonical so error pages see https:// */ - /* even when the url-path cannot be shown as more than '*' */ - if (request) - p = urlCanonicalFakeHttps(request); - else if (url) - p = url; - else if (!building_deny_info_url) - p = "[no URL]"; - break; - - case 'u': - if (request) - p = urlCanonical(request); - else if (url) - p = url; - else if (!building_deny_info_url) - p = "[no URL]"; - break; - - case 'w': - if (Config.adminEmail) - mb.Printf("%s", Config.adminEmail); - else if (!building_deny_info_url) - p = "[unknown]"; - break; - - case 'W': + case Format::LFT_ERR_PAGE_UW: // '%W' if (building_deny_info_url) break; if (Config.adminEmail && Config.onoff.emailErrData) Dump(&mb); no_urlescape = 1; break; - case 'x': + case Format::LFT_ERR_PAGE_LX: // '%x' #if USE_SSL if (detail) mb.Printf("%s", detail->errorName()); @@ -1081,7 +993,7 @@ p = "[Unknown Error Code]"; break; - case 'z': + case Format::LFT_ERR_PAGE_LZ: // '%z' if (building_deny_info_url) break; if (dnsError.size() > 0) p = dnsError.termedBuf(); @@ -1091,7 +1003,7 @@ p = "[unknown]"; break; - case 'Z': + case Format::LFT_ERR_PAGE_UZ: // '%Z' if (building_deny_info_url) break; if (err_msg) p = err_msg; @@ -1099,13 +1011,7 @@ p = "[unknown]"; break; - case '%': - p = "%"; - break; - default: - mb.Printf("%%%c", token); - do_quote = 0; break; } @@ -1128,24 +1034,27 @@ void ErrorState::DenyInfoLocation(const char *name, HttpRequest *aRequest, MemBuf &result) { + building_deny_info_url = true; + + // XXX: make this process much faster by pre-parsing the deny_info pattern + // and just assembling the Location: output here. + 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.Printf("%s", t); /* copy */ - m = p + 1; /* advance */ - } - - if (*m) - result.Printf("%s", m); /* copy tail */ - - assert((size_t)result.contentSize() == strlen(result.content())); + MemBuf *res = ConvertText(m); + + // yuck! steal the res buffer for our output + result.buf = res->buf; + result.size = res->size; + result.capacity = res->capacity; + result.max_capacity = res->max_capacity; + res->stolen = true; // prevent delete erasing the buffer we stole. + delete res; + + building_deny_info_url = false; } HttpReply * @@ -1278,7 +1187,7 @@ debugs(4, 2, HERE << "No existing error page language negotiated for " << errorPageName(page_id) << ". Using default error file."); } - MemBuf *result = ConvertText(m, true); + MemBuf *result = ConvertText(m); #if USE_ERR_LOCALES if (localeTmpl) delete localeTmpl; @@ -1286,25 +1195,46 @@ return result; } -MemBuf *ErrorState::ConvertText(const char *text, bool allowRecursion) +MemBuf * +ErrorState::ConvertText(const char *text) { + // parse the text using Format:: parser + Format::Format fmt("errorPageTemporary"); + fmt.parse(text); + + // XXX: if request exists, we might use ALE from request->lientConnectionManager->http.al ?? + + // if we are in a recursive pattern (%D or %S) ALE may already be setup + // or if the creator of this ErrorState was kind enough to supply the real transaction ALE object + if (ale_ == NULL) { + ale_ = new AccessLogEntry(); + + // fill ale_ with all the details Format::assemble() will need to process error page codes. + + assert(request); + + if (request->auth_user_request != NULL) + ale_->cache.authuser = request->auth_user_request->username(); + // XXX: we only know about one request here. + ale_->request = request; + HTTPMSGLOCK(request); + ale_->adapted_request = request; + HTTPMSGLOCK(request); + ale_->_private.method_str = RequestMethodStr(request->method); + const char *hierHost = (request->hier.host[0] != '\0' ? request->hier.host : request->GetHost()); + ale_->hier.note(request->hier.tcpServer, hierHost); + + if (request->clientConnectionManager.valid()) + ale_->tcpClient = request->clientConnectionManager->clientConnection; + + ale_->url = (request ? urlCanonicalFakeHttps(request) : url); + } + ale_->errorDetails = this; + + // build the actual object text 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->Printf("%s", t); /* copy */ - m = p + 1; /* advance */ - } - - if (*m) - content->Printf("%s", m); /* copy tail */ - - assert((size_t)content->contentSize() == strlen(content->content())); + fmt.assemble(*content, ale_, 0); return content; } === modified file 'src/errorpage.h' --- src/errorpage.h 2013-03-16 04:57:43 +0000 +++ src/errorpage.h 2013-04-07 02:39:42 +0000 @@ -34,10 +34,12 @@ #ifndef SQUID_ERRORPAGE_H #define SQUID_ERRORPAGE_H +#include "AccessLogEntry.h" #include "cbdata.h" #include "comm/forward.h" #include "err_detail_type.h" #include "err_type.h" +#include "format/ByteCode.h" #include "http/StatusCode.h" #include "ip/Address.h" #include "SquidString.h" @@ -109,6 +111,17 @@ /// set error type-specific detail code void detailError(int dCode) {detailCode = dCode;} + /** \deprecated + * Map some Error page and deny_info template % codes into textual output. + * Only the LFT_ERR_PAGE_* ones are mapped, others are ignored. + * + * 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 + */ + const char *Convert(Format::ByteCode_t token); + private: /** * Locates error page template to be used for this error @@ -120,9 +133,8 @@ * 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); + MemBuf *ConvertText(const char *text); /** * Generates the Location: header value for a deny_info error page @@ -131,18 +143,6 @@ void DenyInfoLocation(const char *name, HttpRequest *request, MemBuf &result); /** - * 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); - - /** * CacheManager / Debug dump of the ErrorState object. * Writes output into the given MemBuf. \retval 0 successful completion. @@ -164,7 +164,7 @@ String dnsError; ///< DNS lookup error message time_t ttl; - Ip::Address src_addr; + Ip::Address src_addr; // client Comm::Connection ?? char *redirect_url; ERCB *callback; void *callback_data; @@ -186,7 +186,17 @@ /// type-specific detail about the transaction error; /// overwrites xerrno; overwritten by detail, if any. int detailCode; + private: + /// Perform special deny_info actions, such as URL-encoding and token skipping. + bool building_deny_info_url; + + /// Whether to convert codes which output may contain codes + bool allowRecursion; + + /// an AccessLogEntry we are using to generate output + AccessLogEntry::Pointer ale_; + CBDATA_CLASS2(ErrorState); }; === modified file 'src/format/ByteCode.h' --- src/format/ByteCode.h 2013-02-11 23:11:12 +0000 +++ src/format/ByteCode.h 2013-04-07 02:39:45 +0000 @@ -136,6 +136,7 @@ LFT_TIME_SUBSECOND, LFT_TIME_LOCALTIME, LFT_TIME_GMT, + LFT_TIME_GMT_RFC1123, /* processing time details */ LFT_TIME_TO_HANDLE_REQUEST, @@ -143,6 +144,12 @@ LFT_TOTAL_SERVER_SIDE_RESPONSE_TIME, LFT_DNS_WAIT_TIME, + /* Squid application details */ + LFT_SQUID_KID_HOSTNAME, + LFT_SQUID_KID_PORT, + LFT_SQUID_KID_APPSTRING, + LFT_SQUID_KID_ADMINEMAIL, + /* Squid internal processing details */ LFT_SQUID_STATUS, LFT_SQUID_ERROR, @@ -196,6 +203,26 @@ LFT_SSL_USER_CERT_ISSUER, #endif + /* Squid Error Page special items U=Upper, L=lower cased alphabet codes */ + LFT_ERR_PAGE_UD, + LFT_ERR_PAGE_LE, + LFT_ERR_PAGE_UE, + LFT_ERR_PAGE_LF, + LFT_ERR_PAGE_UF, + LFT_ERR_PAGE_LG, + LFT_ERR_PAGE_LL, + LFT_ERR_PAGE_UL, + LFT_ERR_PAGE_LM, + LFT_ERR_PAGE_LO, + LFT_ERR_PAGE_UP, + LFT_ERR_PAGE_UR, + LFT_ERR_PAGE_LS, + LFT_ERR_PAGE_US, + LFT_ERR_PAGE_UW, + LFT_ERR_PAGE_LX, + LFT_ERR_PAGE_LZ, + LFT_ERR_PAGE_UZ, + LFT_NOTE, LFT_PERCENT /* special string cases for escaped chars */ } ByteCode_t; === modified file 'src/format/Format.cc' --- src/format/Format.cc 2013-03-16 04:57:43 +0000 +++ src/format/Format.cc 2013-04-07 02:39:45 +0000 @@ -9,11 +9,14 @@ #include "format/Quoting.h" #include "format/Token.h" #include "fqdncache.h" +#include "globals.h" #include "HttpRequest.h" #include "MemBuf.h" #include "rfc1738.h" +#include "SquidConfig.h" #include "SquidTime.h" #include "Store.h" +#include "tools.h" #include "URL.h" #if USE_SSL #include "ssl/ErrorDetail.h" @@ -442,7 +445,7 @@ break; case LFT_TIME_LOCALTIME: - + case LFT_TIME_GMT_RFC1123: case LFT_TIME_GMT: { const char *spec; @@ -453,6 +456,12 @@ if (!spec) spec = "%d/%b/%Y:%H:%M:%S %z"; t = localtime(&squid_curtime); + + } else if (fmt->type == LFT_TIME_GMT_RFC1123) { + if (!spec) + spec = "%a, %d %b %Y %H:%M:%S GMT"; + t = gmtime(&squid_curtime); + } else { if (!spec) spec = "%d/%b/%Y:%H:%M:%S"; @@ -819,6 +828,23 @@ // or internal error messages). break; + case LFT_SQUID_KID_HOSTNAME: + out = getMyHostname(); + break; + + case LFT_SQUID_KID_PORT: + outint = getMyPort(); + doint = 1; + break; + + case LFT_SQUID_KID_APPSTRING: + out = visible_appname_string; + break; + + case LFT_SQUID_KID_ADMINEMAIL: + out = Config.adminEmail; + break; + case LFT_SQUID_STATUS: if (al->http.timedout || al->http.aborted) { snprintf(tmp, sizeof(tmp), "%s%s", LogTags_str[al->cache.code], @@ -860,6 +886,31 @@ } break; + case LFT_ERR_PAGE_UD: + case LFT_ERR_PAGE_LE: + case LFT_ERR_PAGE_UE: + case LFT_ERR_PAGE_LF: + case LFT_ERR_PAGE_UF: + case LFT_ERR_PAGE_LG: + case LFT_ERR_PAGE_LL: + case LFT_ERR_PAGE_UL: + case LFT_ERR_PAGE_LM: + case LFT_ERR_PAGE_LO: + case LFT_ERR_PAGE_UP: + case LFT_ERR_PAGE_UR: + case LFT_ERR_PAGE_LS: + case LFT_ERR_PAGE_US: + case LFT_ERR_PAGE_UW: + case LFT_ERR_PAGE_LX: + case LFT_ERR_PAGE_LZ: + case LFT_ERR_PAGE_UZ: + // NP: these code types are used only by the ErrorState page generator + // for backward compatibility use the old code to generate the display output + if (al->errorDetails) { + out = al->errorDetails->Convert(fmt->type); + } + break; + case LFT_SQUID_HIERARCHY: if (al->hier.ping.timedout) mb.append("TIMEOUT_", 8); === modified file 'src/format/Token.cc' --- src/format/Token.cc 2013-02-11 23:11:12 +0000 +++ src/format/Token.cc 2013-04-07 02:39:46 +0000 @@ -1,4 +1,5 @@ #include "squid.h" +#include "base/StringArea.h" #include "format/Config.h" #include "format/Token.h" #include "format/TokenTableEntry.h" @@ -31,6 +32,46 @@ {"%", LFT_PERCENT}, + // 1-byte Tokens used by the Error page templates + + // these ones map easily .. + {"a", LFT_USER_LOGIN}, + {"b", LFT_SQUID_KID_PORT}, + {"B", LFT_SERVER_REQ_URI}, + {"c", LFT_SQUID_ERROR}, + /*{"d", LFT_TIME_TO_HANDLE_REQUEST},*/ + {"h", LFT_SQUID_KID_HOSTNAME}, + {"H", LFT_SERVER_FQDN_OR_PEER_NAME}, + {"i", LFT_CLIENT_IP_ADDRESS}, + {"I", LFT_SERVER_IP_ADDRESS}, + {"M", LFT_REQUEST_METHOD}, + {"p", LFT_SERVER_PORT}, + {"t", LFT_TIME_LOCALTIME}, + {"T", LFT_TIME_GMT_RFC1123}, + {"u", LFT_CLIENT_REQ_URI}, + {"U", LFT_REQUEST_URI}, + {"w", LFT_SQUID_KID_ADMINEMAIL}, + + // NP: this bunch are specific to the ErrorState object generator + {"D", LFT_ERR_PAGE_UD}, + {"e", LFT_ERR_PAGE_LE}, + {"E", LFT_ERR_PAGE_UE}, + {"f", LFT_ERR_PAGE_LF}, + {"F", LFT_ERR_PAGE_UF}, + {"g", LFT_ERR_PAGE_LG}, + {"l", LFT_ERR_PAGE_LL}, + {"L", LFT_ERR_PAGE_UL}, + {"m", LFT_ERR_PAGE_LM}, + {"o", LFT_ERR_PAGE_LO}, + {"P", LFT_ERR_PAGE_UP}, + {"R", LFT_ERR_PAGE_UR}, + {"s", LFT_ERR_PAGE_LS}, + {"S", LFT_ERR_PAGE_US}, + {"W", LFT_ERR_PAGE_UW}, + {"x", LFT_ERR_PAGE_LX}, + {"z", LFT_ERR_PAGE_LZ}, + {"Z", LFT_ERR_PAGE_UZ}, + {NULL, LFT_NONE} /* this must be last */ }; @@ -373,7 +414,12 @@ } if (type == LFT_NONE) { - fatalf("Can't parse configuration token: '%s'\n", def); + debugs(46, 3, "Can't parse configuration token: '" << StringArea(def,min(strlen(def), static_cast(10))) << "'"); + // NP: unknown token found. Assume it should have been %% instead of %. + if (def[0] == '%') { + type = LFT_PERCENT; + ++cur; + } } if (*cur == ' ') {