2 * Copyright (C) 1996-2021 The Squid Software Foundation and contributors
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
9 /* DEBUG: section 04 Error Generation */
12 #include "AccessLogEntry.h"
14 #include "clients/forward.h"
15 #include "comm/Connection.h"
16 #include "comm/Write.h"
17 #include "error/Detail.h"
18 #include "error/SysErrorDetail.h"
19 #include "errorpage.h"
21 #include "format/Format.h"
23 #include "html_quote.h"
24 #include "HttpHeaderTools.h"
25 #include "HttpReply.h"
26 #include "HttpRequest.h"
28 #include "MemObject.h"
30 #include "sbuf/Stream.h"
31 #include "SquidConfig.h"
32 #include "SquidTime.h"
37 #include "auth/UserRequest.h"
40 #include "ssl/ErrorDetailManager.h"
44 \defgroup ErrorPageInternal Error Page Internals
47 \section ErrorPagesAbstract Abstract:
48 * These routines are used to generate error messages to be
49 * sent to clients. The error type is used to select between
50 * the various message formats. (formats are stored in the
51 * Config.errorDirectory)
54 #if !defined(DEFAULT_SQUID_ERROR_DIR)
55 /** Where to look for errors if config path fails.
56 \note Please use ./configure --datadir=/path instead of patching
58 #define DEFAULT_SQUID_ERROR_DIR DEFAULT_SQUID_DATA_DIR"/errors"
61 /// \ingroup ErrorPageInternal
62 CBDATA_CLASS_INIT(ErrorState
);
64 const SBuf
ErrorState::LogformatMagic("@Squid{");
68 /// an error page created from admin-configurable metadata (e.g. deny_info)
69 class ErrorDynamicPageInfo
{
71 ErrorDynamicPageInfo(const int anId
, const char *aName
, const SBuf
&aCfgLocation
);
72 ~ErrorDynamicPageInfo() { xfree(page_name
); }
74 /// error_text[] index for response body (unused in redirection responses)
77 /// Primary deny_info parameter:
78 /// * May start with an HTTP status code.
79 /// * Either a well-known error page name, a filename, or a redirect URL.
82 /// admin-configured HTTP Location header value for redirection responses
85 /// admin-configured name for the error page template (custom or standard)
88 /// deny_info directive position in squid.conf (for reporting)
91 // XXX: Misnamed. Not just for redirects.
92 /// admin-configured HTTP status code
93 Http::StatusCode page_redirect
;
96 // no copying of any kind
97 ErrorDynamicPageInfo(ErrorDynamicPageInfo
&&) = delete;
100 namespace ErrorPage
{
102 /// state and parameters shared by several ErrorState::compile*() methods
106 SBuf output
; ///< compilation result
107 const char *input
= nullptr; ///< template bytes that need to be compiled
108 bool building_deny_info_url
= false; ///< whether we compile deny_info URI
109 bool allowRecursion
= false; ///< whether top-level compile() calls are OK
112 /// pretty-prints error page/deny_info building error
113 class BuildErrorPrinter
116 BuildErrorPrinter(const SBuf
&anInputLocation
, int aPage
, const char *aMsg
, const char *aNear
): inputLocation(anInputLocation
), page_id(aPage
), msg(aMsg
), near(aNear
) {}
118 /// reports error details (for admin-visible exceptions and debugging)
119 std::ostream
&print(std::ostream
&) const;
121 /// print() helper to report where the error was found
122 std::ostream
&printLocation(std::ostream
&os
) const;
124 /* saved constructor parameters */
125 const SBuf
&inputLocation
;
131 static inline std::ostream
&
132 operator <<(std::ostream
&os
, const BuildErrorPrinter
&context
)
134 return context
.print(os
);
137 static const char *IsDenyInfoUri(const int page_id
);
139 static void ImportStaticErrorText(const int page_id
, const char *text
, const SBuf
&inputLocation
);
140 static void ValidateStaticError(const int page_id
, const SBuf
&inputLocation
);
142 } // namespace ErrorPage
144 /* local constant and vars */
147 \ingroup ErrorPageInternal
149 \note hard coded error messages are not appended with %S
150 * automagically to give you more control on the format
152 static const struct {
153 int type
; /* and page_id */
157 error_hard_text
[] = {
163 "<div id=\"footer\">\n"
164 "Generated %T by %h (%s)\n"
174 "unexpected client disconnect"
177 ERR_SECURE_ACCEPT_FAIL
,
181 ERR_REQUEST_START_TIMEOUT
,
182 "request start timedout"
190 /// \ingroup ErrorPageInternal
191 static std::vector
<ErrorDynamicPageInfo
*> ErrorDynamicPages
;
193 /* local prototypes */
195 /// \ingroup ErrorPageInternal
196 static const int error_hard_text_count
= sizeof(error_hard_text
) / sizeof(*error_hard_text
);
198 /// \ingroup ErrorPageInternal
199 static char **error_text
= NULL
;
201 /// \ingroup ErrorPageInternal
202 static int error_page_count
= 0;
204 /// \ingroup ErrorPageInternal
205 static MemBuf error_stylesheet
;
207 static const char *errorFindHardText(err_type type
);
208 static IOCB errorSendComplete
;
210 /// \ingroup ErrorPageInternal
211 /// manages an error page template
212 class ErrorPageFile
: public TemplateFile
215 ErrorPageFile(const char *name
, const err_type code
) : TemplateFile(name
, code
) {}
217 /// The template text data read from disk
218 const char *text() { return template_
.c_str(); }
221 virtual void setDefault() override
{
222 template_
= "Internal Error: Missing Template ";
223 template_
.append(templateName
.termedBuf());
227 /// \ingroup ErrorPageInternal
228 err_type
&operator++ (err_type
&anErr
)
230 int tmp
= (int)anErr
;
231 anErr
= (err_type
)(++tmp
);
235 /// \ingroup ErrorPageInternal
236 int operator - (err_type
const &anErr
, err_type
const &anErr2
)
238 return (int)anErr
- (int)anErr2
;
241 /// \return deny_info URL if the given page is a deny_info page with a URL
242 /// \return nullptr otherwise
244 ErrorPage::IsDenyInfoUri(const int page_id
)
246 if (ERR_MAX
<= page_id
&& page_id
< error_page_count
)
247 return ErrorDynamicPages
.at(page_id
- ERR_MAX
)->uri
; // may be nil
252 errorInitialize(void)
254 using ErrorPage::ImportStaticErrorText
;
258 error_page_count
= ERR_MAX
+ ErrorDynamicPages
.size();
259 error_text
= static_cast<char **>(xcalloc(error_page_count
, sizeof(char *)));
261 for (i
= ERR_NONE
, ++i
; i
< error_page_count
; ++i
) {
262 safe_free(error_text
[i
]);
264 if ((text
= errorFindHardText(i
))) {
266 * Index any hard-coded error text into defaults.
268 static const SBuf
builtIn("built-in");
269 ImportStaticErrorText(i
, text
, builtIn
);
271 } else if (i
< ERR_MAX
) {
273 * Index precompiled fixed template files from one of two sources:
274 * (a) default language translation directory (error_default_language)
275 * (b) admin specified custom directory (error_directory)
277 ErrorPageFile
errTmpl(err_type_str
[i
], i
);
278 errTmpl
.loadDefault();
279 ImportStaticErrorText(i
, errTmpl
.text(), errTmpl
.filename
);
282 * Index any unknown file names used by deny_info.
284 ErrorDynamicPageInfo
*info
= ErrorDynamicPages
.at(i
- ERR_MAX
);
285 assert(info
&& info
->id
== i
&& info
->page_name
);
287 if (info
->filename
) {
288 /** But only if they are not redirection URL. */
289 ErrorPageFile
errTmpl(info
->filename
, ERR_MAX
);
290 errTmpl
.loadDefault();
291 ImportStaticErrorText(i
, errTmpl
.text(), errTmpl
.filename
);
294 ErrorPage::ValidateStaticError(i
, info
->cfgLocation
);
299 error_stylesheet
.reset();
301 // look for and load stylesheet into global MemBuf for it.
302 if (Config
.errorStylesheet
) {
303 ErrorPageFile
tmpl("StylesSheet", ERR_MAX
);
304 tmpl
.loadFromFile(Config
.errorStylesheet
);
305 error_stylesheet
.appendf("%s",tmpl
.text());
309 Ssl::errorDetailInitialize();
319 for (i
= ERR_NONE
+ 1; i
< error_page_count
; ++i
)
320 safe_free(error_text
[i
]);
322 safe_free(error_text
);
325 while (!ErrorDynamicPages
.empty()) {
326 delete ErrorDynamicPages
.back();
327 ErrorDynamicPages
.pop_back();
330 error_page_count
= 0;
333 Ssl::errorDetailClean();
337 /// \ingroup ErrorPageInternal
339 errorFindHardText(err_type type
)
343 for (i
= 0; i
< error_hard_text_count
; ++i
)
344 if (error_hard_text
[i
].type
== type
)
345 return error_hard_text
[i
].text
;
350 TemplateFile::TemplateFile(const char *name
, const err_type code
): silent(false), wasLoaded(false), templateName(name
), templateCode(code
)
356 TemplateFile::loadDefault()
358 if (loaded()) // already loaded?
361 /** test error_directory configured location */
362 if (Config
.errorDirectory
) {
363 char path
[MAXPATHLEN
];
364 snprintf(path
, sizeof(path
), "%s/%s", Config
.errorDirectory
, templateName
.termedBuf());
369 /** test error_default_language location */
370 if (!loaded() && Config
.errorDefaultLanguage
) {
371 if (!tryLoadTemplate(Config
.errorDefaultLanguage
)) {
372 debugs(1, (templateCode
< TCP_RESET
? DBG_CRITICAL
: 3), "Unable to load default error language files. Reset to backups.");
377 /* test default location if failed (templates == English translation base templates) */
379 tryLoadTemplate("templates");
382 /* giving up if failed */
384 debugs(1, (templateCode
< TCP_RESET
? DBG_CRITICAL
: 3), "WARNING: failed to find or read error text file " << templateName
);
392 TemplateFile::tryLoadTemplate(const char *lang
)
396 char path
[MAXPATHLEN
];
397 /* TODO: prep the directory path string to prevent snprintf ... */
398 snprintf(path
, sizeof(path
), "%s/%s/%s",
399 DEFAULT_SQUID_ERROR_DIR
, lang
, templateName
.termedBuf());
400 path
[MAXPATHLEN
-1] = '\0';
402 if (loadFromFile(path
))
406 if ( strlen(lang
) == 2) {
407 /* TODO glob the error directory for sub-dirs matching: <tag> '-*' */
408 /* use first result. */
409 debugs(4,2, HERE
<< "wildcard fallback errors not coded yet.");
417 TemplateFile::loadFromFile(const char *path
)
423 if (loaded()) // already loaded?
426 fd
= file_open(path
, O_RDONLY
| O_TEXT
);
429 /* with dynamic locale negotiation we may see some failures before a success. */
430 if (!silent
&& templateCode
< TCP_RESET
) {
432 debugs(4, DBG_CRITICAL
, "ERROR: loading file '" << path
<< "': " << xstrerr(xerrno
));
439 while ((len
= FD_READ_METHOD(fd
, buf
, sizeof(buf
))) > 0) {
440 template_
.append(buf
, len
);
446 debugs(4, DBG_CRITICAL
, MYNAME
<< "ERROR: failed to fully read: '" << path
<< "': " << xstrerr(xerrno
));
453 filename
= SBuf(path
);
456 debugs(4, DBG_CRITICAL
, "ERROR: parsing error in template file: " << path
);
465 bool strHdrAcptLangGetItem(const String
&hdr
, char *lang
, int langLen
, size_t &pos
)
467 while (pos
< hdr
.size()) {
469 /* skip any initial whitespace. */
470 while (pos
< hdr
.size() && xisspace(hdr
[pos
]))
474 * Header value format:
475 * - sequence of whitespace delimited tags
476 * - each tag may suffix with ';'.* which we can ignore.
477 * - IFF a tag contains only two characters we can wildcard ANY translations matching: <it> '-'? .*
478 * with preference given to an exact match.
480 bool invalid_byte
= false;
482 while (pos
< hdr
.size() && hdr
[pos
] != ';' && hdr
[pos
] != ',' && !xisspace(hdr
[pos
]) && dt
< (lang
+ (langLen
-1)) ) {
484 #if USE_HTTP_VIOLATIONS
485 // if accepting violations we may as well accept some broken browsers
486 // which may send us the right code, wrong ISO formatting.
491 *dt
= xtolower(hdr
[pos
]);
492 // valid codes only contain A-Z, hyphen (-) and *
493 if (*dt
!= '-' && *dt
!= '*' && (*dt
< 'a' || *dt
> 'z') )
496 ++dt
; // move to next destination byte.
500 *dt
= '\0'; // nul-terminated the filename content string before system use.
502 // if we terminated the tag on garbage or ';' we need to skip to the next ',' or end of header.
503 while (pos
< hdr
.size() && hdr
[pos
] != ',')
506 if (pos
< hdr
.size() && hdr
[pos
] == ',')
509 debugs(4, 9, "STATE: lang=" << lang
<< ", pos=" << pos
<< ", buf='" << ((pos
< hdr
.size()) ? hdr
.substr(pos
,hdr
.size()) : "") << "'");
511 /* if we found anything we might use, try it. */
512 if (*lang
!= '\0' && !invalid_byte
)
519 TemplateFile::loadFor(const HttpRequest
*request
)
524 if (loaded()) // already loaded?
527 if (!request
|| !request
->header
.getList(Http::HdrType::ACCEPT_LANGUAGE
, &hdr
))
531 size_t pos
= 0; // current parsing position in header string
533 debugs(4, 6, HERE
<< "Testing Header: '" << hdr
<< "'");
535 while ( strHdrAcptLangGetItem(hdr
, lang
, 256, pos
) ) {
537 /* wildcard uses the configured default language */
538 if (lang
[0] == '*' && lang
[1] == '\0') {
539 debugs(4, 6, HERE
<< "Found language '" << lang
<< "'. Using configured default.");
543 debugs(4, 6, HERE
<< "Found language '" << lang
<< "', testing for available template");
545 if (tryLoadTemplate(lang
)) {
546 /* store the language we found for the Content-Language reply header */
549 } else if (Config
.errorLogMissingLanguages
) {
550 debugs(4, DBG_IMPORTANT
, "WARNING: Error Pages Missing Language: " << lang
);
558 ErrorDynamicPageInfo::ErrorDynamicPageInfo(const int anId
, const char *aName
, const SBuf
&aCfgLocation
):
560 page_name(xstrdup(aName
)),
563 cfgLocation(aCfgLocation
),
564 page_redirect(static_cast<Http::StatusCode
>(atoi(page_name
)))
566 const char *filenameOrUri
= nullptr;
567 if (xisdigit(*page_name
)) {
568 if (const char *statusCodeEnd
= strchr(page_name
, ':'))
569 filenameOrUri
= statusCodeEnd
+ 1;
571 assert(!page_redirect
);
572 filenameOrUri
= page_name
;
575 // Guessed uri, filename, or both values may be nil or malformed.
576 // They are validated later.
577 if (!page_redirect
) {
578 if (filenameOrUri
&& strchr(filenameOrUri
, ':')) // looks like a URL
581 filename
= filenameOrUri
;
583 else if (page_redirect
/100 == 3) {
584 // redirects imply a URL
587 // non-redirects imply an error page name
588 filename
= filenameOrUri
;
591 const auto info
= this; // source code change reduction hack
592 // TODO: Move and refactor to avoid self_destruct()s in reconfigure.
594 /* WARNING on redirection status:
595 * 2xx are permitted, but not documented officially.
596 * - might be useful for serving static files (PAC etc) in special cases
597 * 3xx require a URL suitable for Location: header.
598 * - the current design does not allow for a Location: URI as well as a local file template
599 * although this possibility is explicitly permitted in the specs.
600 * 4xx-5xx require a local file template.
601 * - sending Location: on these codes with no body is invalid by the specs.
602 * - current result is Squid crashing or XSS problems as dynamic deny_info load random disk files.
603 * - a future redesign of the file loading may result in loading remote objects sent inline as local body.
605 if (info
->page_redirect
== Http::scNone
)
606 ; // special case okay.
607 else if (info
->page_redirect
< 200 || info
->page_redirect
> 599) {
609 debugs(0, DBG_CRITICAL
, "FATAL: status " << info
->page_redirect
<< " is not valid on '" << page_name
<< "'");
611 } else if ( /* >= 200 && */ info
->page_redirect
< 300 && strchr(&(page_name
[4]), ':')) {
612 // 2xx require a local template file
613 debugs(0, DBG_CRITICAL
, "FATAL: status " << info
->page_redirect
<< " requires a template on '" << page_name
<< "'");
615 } else if (info
->page_redirect
>= 300 && info
->page_redirect
<= 399 && !strchr(&(page_name
[4]), ':')) {
616 // 3xx require an absolute URL
617 debugs(0, DBG_CRITICAL
, "FATAL: status " << info
->page_redirect
<< " requires a URL on '" << page_name
<< "'");
619 } else if (info
->page_redirect
>= 400 /* && <= 599 */ && strchr(&(page_name
[4]), ':')) {
620 // 4xx/5xx require a local template file
621 debugs(0, DBG_CRITICAL
, "FATAL: status " << info
->page_redirect
<< " requires a template on '" << page_name
<< "'");
627 /// \ingroup ErrorPageInternal
629 errorPageId(const char *page_name
)
631 for (int i
= 0; i
< ERR_MAX
; ++i
) {
632 if (strcmp(err_type_str
[i
], page_name
) == 0)
636 for (size_t j
= 0; j
< ErrorDynamicPages
.size(); ++j
) {
637 if (strcmp(ErrorDynamicPages
[j
]->page_name
, page_name
) == 0)
645 errorReservePageId(const char *page_name
, const SBuf
&cfgLocation
)
647 int id
= errorPageId(page_name
);
649 if (id
== ERR_NONE
) {
650 id
= ERR_MAX
+ ErrorDynamicPages
.size();
651 const auto info
= new ErrorDynamicPageInfo(id
, page_name
, cfgLocation
);
652 ErrorDynamicPages
.push_back(info
);
658 /// \ingroup ErrorPageInternal
660 errorPageName(int pageId
)
662 if (pageId
>= ERR_NONE
&& pageId
< ERR_MAX
) /* common case */
663 return err_type_str
[pageId
];
665 if (pageId
>= ERR_MAX
&& pageId
- ERR_MAX
< (ssize_t
)ErrorDynamicPages
.size())
666 return ErrorDynamicPages
[pageId
- ERR_MAX
]->page_name
;
668 return "ERR_UNKNOWN"; /* should not happen */
672 ErrorState::NewForwarding(err_type type
, HttpRequestPointer
&request
, const AccessLogEntry::Pointer
&ale
)
674 const Http::StatusCode status
= (request
&& request
->flags
.needValidation
) ?
675 Http::scGatewayTimeout
: Http::scServiceUnavailable
;
676 return new ErrorState(type
, status
, request
.getRaw(), ale
);
679 ErrorState::ErrorState(err_type t
) :
686 ErrorState::ErrorState(err_type t
, Http::StatusCode status
, HttpRequest
* req
, const AccessLogEntry::Pointer
&anAle
) :
689 if (page_id
>= ERR_MAX
&& ErrorDynamicPages
[page_id
- ERR_MAX
]->page_redirect
!= Http::scNone
)
690 httpStatus
= ErrorDynamicPages
[page_id
- ERR_MAX
]->page_redirect
;
696 src_addr
= req
->client_addr
;
702 ErrorState::ErrorState(HttpRequest
* req
, HttpReply
*errorReply
) :
703 ErrorState(ERR_RELAY_REMOTE
)
706 response_
= errorReply
;
707 httpStatus
= errorReply
->sline
.status();
711 src_addr
= req
->client_addr
;
716 errorAppendEntry(StoreEntry
* entry
, ErrorState
* err
)
718 assert(entry
->mem_obj
!= NULL
);
719 assert (entry
->isEmpty());
720 debugs(4, 4, "storing " << err
<< " in " << *entry
);
722 if (entry
->store_status
!= STORE_PENDING
) {
723 debugs(4, 2, "Skipping error page due to store_status: " << entry
->store_status
);
725 * If the entry is not STORE_PENDING, then no clients
726 * care about it, and we don't need to generate an
729 assert(EBIT_TEST(entry
->flags
, ENTRY_ABORTED
));
730 assert(entry
->mem_obj
->nclients
== 0);
735 if (err
->page_id
== TCP_RESET
) {
737 debugs(4, 2, "RSTing this reply");
738 err
->request
->flags
.resetTcp
= true;
742 entry
->storeErrorResponse(err
->BuildHttpReply());
747 errorSend(const Comm::ConnectionPointer
&conn
, ErrorState
* err
)
749 debugs(4, 3, conn
<< ", err=" << err
);
750 assert(Comm::IsConnOpen(conn
));
752 HttpReplyPointer
rep(err
->BuildHttpReply());
754 MemBuf
*mb
= rep
->pack();
755 AsyncCall::Pointer call
= commCbCall(78, 5, "errorSendComplete",
756 CommIoCbPtrFun(&errorSendComplete
, err
));
757 Comm::Write(conn
, mb
, call
);
762 \ingroup ErrorPageAPI
764 * Called by commHandleWrite() after data has been written
765 * to the client socket.
767 \note If there is a callback, the callback is responsible for
768 * closing the FD, otherwise we do it ourselves.
771 errorSendComplete(const Comm::ConnectionPointer
&conn
, char *, size_t size
, Comm::Flag errflag
, int, void *data
)
773 ErrorState
*err
= static_cast<ErrorState
*>(data
);
774 debugs(4, 3, HERE
<< conn
<< ", size=" << size
);
776 if (errflag
!= Comm::ERR_CLOSING
) {
778 debugs(4, 3, "errorSendComplete: callback");
779 err
->callback(conn
->fd
, err
->callback_data
, size
);
781 debugs(4, 3, "errorSendComplete: comm_close");
789 ErrorState::~ErrorState()
791 safe_free(redirect_url
);
793 safe_free(request_hdrs
);
794 wordlistDestroy(&ftp
.server_msg
);
795 safe_free(ftp
.request
);
796 safe_free(ftp
.reply
);
799 if (err_language
!= Config
.errorDefaultLanguage
)
801 safe_free(err_language
);
805 ErrorState::Dump(MemBuf
* mb
)
808 char ntoabuf
[MAX_IPSTRLEN
];
811 /* email subject line */
812 str
.appendf("CacheErrorInfo - %s", errorPageName(type
));
813 mb
->appendf("?subject=%s", rfc1738_escape_part(str
.buf
));
816 str
.appendf("CacheHost: %s\r\n", getMyHostname());
818 str
.appendf("ErrPage: %s\r\n", errorPageName(type
));
821 str
.appendf("Err: (%d) %s\r\n", xerrno
, strerror(xerrno
));
823 str
.append("Err: [none]\r\n", 13);
826 if (auth_user_request
.getRaw() && auth_user_request
->denyMessage())
827 str
.appendf("Auth ErrMsg: %s\r\n", auth_user_request
->denyMessage());
829 if (dnsError
.size() > 0)
830 str
.appendf("DNS ErrMsg: %s\r\n", dnsError
.termedBuf());
833 str
.appendf("TimeStamp: %s\r\n\r\n", mkrfc1123(squid_curtime
));
836 str
.appendf("ClientIP: %s\r\n", src_addr
.toStr(ntoabuf
,MAX_IPSTRLEN
));
838 if (request
&& request
->hier
.host
[0] != '\0') {
839 str
.appendf("ServerIP: %s\r\n", request
->hier
.host
);
842 str
.append("\r\n", 2);
844 str
.append("HTTP Request:\r\n", 15);
846 str
.appendf(SQUIDSBUFPH
" " SQUIDSBUFPH
" %s/%d.%d\n",
847 SQUIDSBUFPRINT(request
->method
.image()),
848 SQUIDSBUFPRINT(request
->url
.path()),
849 AnyP::ProtocolType_str
[request
->http_ver
.protocol
],
850 request
->http_ver
.major
, request
->http_ver
.minor
);
851 request
->header
.packInto(&str
);
854 str
.append("\r\n", 2);
858 str
.appendf("FTP Request: %s\r\n", ftp
.request
);
859 str
.appendf("FTP Reply: %s\r\n", (ftp
.reply
? ftp
.reply
:"[none]"));
860 str
.append("FTP Msg: ", 9);
861 wordlistCat(ftp
.server_msg
, &str
);
862 str
.append("\r\n", 2);
865 str
.append("\r\n", 2);
866 mb
->appendf("&body=%s", rfc1738_escape_part(str
.buf
));
871 /// \ingroup ErrorPageInternal
872 #define CVT_BUF_SZ 512
875 ErrorState::compileLogformatCode(Build
&build
)
877 assert(LogformatMagic
.cmp(build
.input
, LogformatMagic
.length()) == 0);
880 const auto logformat
= build
.input
+ LogformatMagic
.length();
882 // Logformat supports undocumented "external" encoding specifications
883 // like [%>h] or "%<a". To preserve the possibility of extending
884 // @Squid{} syntax to non-logformat sequences, we require logformat
885 // sequences to start with '%'. This restriction does not limit
886 // logformat quoting abilities. TODO: Deprecate "external" encoding?
887 if (*logformat
!= '%')
888 throw TexcHere("logformat expressions that do not start with % are not supported");
890 static MemBuf result
;
892 const auto logformatLen
= Format::AssembleOne(logformat
, result
, ale
);
893 assert(logformatLen
> 0);
894 const auto closure
= logformat
+ logformatLen
;
896 throw TexcHere("Missing closing brace (})");
897 build
.output
.append(result
.content(), result
.contentSize());
898 build
.input
= closure
+ 1;
901 noteBuildError("Bad @Squid{logformat} sequence", build
.input
);
904 // we cannot recover reliably so stop interpreting the rest of input
905 const auto remainingSize
= strlen(build
.input
);
906 build
.output
.append(build
.input
, remainingSize
);
907 build
.input
+= remainingSize
;
911 ErrorState::compileLegacyCode(Build
&build
)
914 const char *p
= NULL
; /* takes priority over mb if set */
916 int no_urlescape
= 0; /* if true then item is NOT to be further URL-encoded */
917 char ntoabuf
[MAX_IPSTRLEN
];
921 const auto &building_deny_info_url
= build
.building_deny_info_url
; // a change reduction hack
923 const auto letter
= build
.input
[1];
929 if (request
&& request
->auth_user_request
)
930 p
= request
->auth_user_request
->username();
937 // TODO: When/if we get ALE here, pass it as well
938 if (const auto addr
= FindListeningPortAddress(request
.getRaw(), nullptr))
939 mb
.appendf("%s", addr
->toStr(ntoabuf
, MAX_IPSTRLEN
));
945 mb
.appendf("%u", getMyPort());
949 if (building_deny_info_url
) break;
951 const SBuf
&tmp
= Ftp::UrlWith2f(request
.getRaw());
952 mb
.append(tmp
.rawContent(), tmp
.length());
958 if (building_deny_info_url
) break;
959 p
= errorPageName(type
);
963 if (!build
.allowRecursion
)
964 p
= "%D"; // if recursion is not allowed, do not convert
966 auto rawDetail
= detail
->verbose(request
);
967 // XXX: Performance regression. c_str() reallocates
968 const auto compiledDetail
= compileBody(rawDetail
.c_str(), false);
969 mb
.append(compiledDetail
.rawContent(), compiledDetail
.length());
972 if (!mb
.contentSize())
973 mb
.append("[No Error Detail]", 17);
977 mb
.appendf("%d", xerrno
);
982 mb
.appendf("(%d) %s", xerrno
, strerror(xerrno
));
984 mb
.append("[No Error]", 10);
988 if (building_deny_info_url
) break;
989 /* FTP REQUEST LINE */
997 if (building_deny_info_url
) break;
1006 if (building_deny_info_url
) break;
1007 /* FTP SERVER RESPONSE */
1009 mb
.append(ftp
.listing
->content(), ftp
.listing
->contentSize());
1011 } else if (ftp
.server_msg
) {
1012 wordlistCat(ftp
.server_msg
, &mb
);
1017 mb
.appendf("%s", getMyHostname());
1022 if (request
->hier
.host
[0] != '\0') // if non-empty string.
1023 p
= request
->hier
.host
;
1025 p
= request
->url
.host();
1026 } else if (!building_deny_info_url
)
1027 p
= "[unknown host]";
1031 mb
.appendf("%s", src_addr
.toStr(ntoabuf
,MAX_IPSTRLEN
));
1035 if (request
&& request
->hier
.tcpServer
)
1036 p
= request
->hier
.tcpServer
->remote
.toStr(ntoabuf
,MAX_IPSTRLEN
);
1037 else if (!building_deny_info_url
)
1042 if (building_deny_info_url
) break;
1043 mb
.append(error_stylesheet
.content(), error_stylesheet
.contentSize());
1048 if (building_deny_info_url
) break;
1049 if (Config
.errHtmlText
) {
1050 mb
.appendf("%s", Config
.errHtmlText
);
1053 p
= "[not available]";
1057 if (building_deny_info_url
) break;
1059 if (auth_user_request
.getRaw())
1060 p
= auth_user_request
->denyMessage("[not available]");
1062 p
= "[not available]";
1070 const SBuf
&m
= request
->method
.image();
1071 mb
.append(m
.rawContent(), m
.length());
1072 } else if (!building_deny_info_url
)
1073 p
= "[unknown method]";
1077 if (!building_deny_info_url
)
1080 p
= request
? request
->extacl_message
.termedBuf() : external_acl_message
;
1081 if (!p
&& !building_deny_info_url
)
1082 p
= "[not available]";
1087 mb
.appendf("%u", request
->url
.port());
1088 } else if (!building_deny_info_url
) {
1089 p
= "[unknown port]";
1095 const SBuf
&m
= request
->url
.getScheme().image();
1096 mb
.append(m
.rawContent(), m
.length());
1097 } else if (!building_deny_info_url
) {
1098 p
= "[unknown protocol]";
1103 if (building_deny_info_url
) {
1104 if (request
!= NULL
) {
1105 const SBuf
&tmp
= request
->url
.path();
1106 mb
.append(tmp
.rawContent(), tmp
.length());
1113 mb
.appendf(SQUIDSBUFPH
" " SQUIDSBUFPH
" %s/%d.%d\n",
1114 SQUIDSBUFPRINT(request
->method
.image()),
1115 SQUIDSBUFPRINT(request
->url
.path()),
1116 AnyP::ProtocolType_str
[request
->http_ver
.protocol
],
1117 request
->http_ver
.major
, request
->http_ver
.minor
);
1118 request
->header
.packInto(&mb
, true); //hide authorization data
1119 } else if (request_hdrs
) {
1127 /* for backward compat we make %s show the full URL. Drop this in some future release. */
1128 if (building_deny_info_url
) {
1130 const SBuf
&tmp
= request
->effectiveRequestUri();
1131 mb
.append(tmp
.rawContent(), tmp
.length());
1134 debugs(0, DBG_CRITICAL
, "WARNING: deny_info now accepts coded tags. Use %u to get the full URL instead of %s");
1136 p
= visible_appname_string
;
1140 if (building_deny_info_url
) {
1141 p
= visible_appname_string
;
1144 /* signature may contain %-escapes, recursion */
1145 if (page_id
!= ERR_SQUID_SIGNATURE
) {
1146 const int saved_id
= page_id
;
1147 page_id
= ERR_SQUID_SIGNATURE
;
1148 const auto signature
= buildBody();
1149 mb
.append(signature
.rawContent(), signature
.length());
1153 /* wow, somebody put %S into ERR_SIGNATURE, stop recursion */
1159 mb
.appendf("%s", Time::FormatHttpd(squid_curtime
));
1163 mb
.appendf("%s", mkrfc1123(squid_curtime
));
1167 /* Using the fake-https version of absolute-URI so error pages see https:// */
1168 /* even when the url-path cannot be shown as more than '*' */
1170 p
= urlCanonicalFakeHttps(request
.getRaw());
1173 else if (!building_deny_info_url
)
1179 const SBuf
&tmp
= request
->effectiveRequestUri();
1180 mb
.append(tmp
.rawContent(), tmp
.length());
1183 else if (!building_deny_info_url
)
1188 if (Config
.adminEmail
)
1189 mb
.appendf("%s", Config
.adminEmail
);
1190 else if (!building_deny_info_url
)
1195 if (building_deny_info_url
) break;
1196 if (Config
.adminEmail
&& Config
.onoff
.emailErrData
)
1203 const auto brief
= detail
->brief();
1204 mb
.append(brief
.rawContent(), brief
.length());
1205 } else if (!building_deny_info_url
) {
1206 p
= "[Unknown Error Code]";
1211 if (building_deny_info_url
) break;
1212 if (dnsError
.size() > 0)
1213 p
= dnsError
.termedBuf();
1214 else if (ftp
.cwd_msg
)
1221 if (building_deny_info_url
) break;
1233 if (building_deny_info_url
)
1234 bypassBuildErrorXXX("Unsupported deny_info %code", build
.input
);
1235 else if (letter
!= ';')
1236 bypassBuildErrorXXX("Unsupported error page %code", build
.input
);
1237 // else too many "font-size: 100%;" template errors to report
1239 mb
.append(build
.input
, 2);
1245 p
= mb
.buf
; /* do not use mb after this assignment! */
1249 debugs(4, 3, "%" << letter
<< " --> '" << p
<< "'" );
1254 if (building_deny_info_url
&& !no_urlescape
)
1255 p
= rfc1738_escape_part(p
);
1257 // TODO: Optimize by replacing mb with direct build.output usage.
1258 build
.output
.append(p
, strlen(p
));
1263 ErrorState::validate()
1265 if (const auto urlTemplate
= ErrorPage::IsDenyInfoUri(page_id
)) {
1266 (void)compile(urlTemplate
, true, true);
1268 assert(page_id
> ERR_NONE
);
1269 assert(page_id
< error_page_count
);
1270 (void)compileBody(error_text
[page_id
], true);
1275 ErrorState::BuildHttpReply()
1278 return response_
.getRaw();
1280 HttpReply
*rep
= new HttpReply
;
1281 const char *name
= errorPageName(page_id
);
1282 /* no LMT for error pages; error pages expire immediately */
1284 if (const auto urlTemplate
= ErrorPage::IsDenyInfoUri(page_id
)) {
1286 Http::StatusCode status
= Http::scFound
;
1287 // Use configured 3xx reply status if set.
1289 status
= httpStatus
;
1291 // Use 307 for HTTP/1.1 non-GET/HEAD requests.
1292 if (request
&& request
->method
!= Http::METHOD_GET
&& request
->method
!= Http::METHOD_HEAD
&& request
->http_ver
>= Http::ProtocolVersion(1,1))
1293 status
= Http::scTemporaryRedirect
;
1296 rep
->setHeaders(status
, NULL
, "text/html;charset=utf-8", 0, 0, -1);
1299 auto location
= compile(urlTemplate
, true, true);
1300 rep
->header
.putStr(Http::HdrType::LOCATION
, location
.c_str());
1303 httpHeaderPutStrf(&rep
->header
, Http::HdrType::X_SQUID_ERROR
, "%d %s", httpStatus
, "Access Denied");
1305 const auto body
= buildBody();
1306 rep
->setHeaders(httpStatus
, NULL
, "text/html;charset=utf-8", body
.length(), 0, -1);
1308 * include some information for downstream caches. Implicit
1309 * replaceable content. This isn't quite sufficient. xerrno is not
1310 * necessarily meaningful to another system, so we really should
1311 * expand it. Additionally, we should identify ourselves. Someone
1312 * might want to know. Someone _will_ want to know OTOH, the first
1313 * X-CACHE-MISS entry should tell us who.
1315 httpHeaderPutStrf(&rep
->header
, Http::HdrType::X_SQUID_ERROR
, "%s %d", name
, xerrno
);
1319 * If error page auto-negotiate is enabled in any way, send the Vary.
1320 * RFC 2616 section 13.6 and 14.44 says MAY and SHOULD do this.
1321 * We have even better reasons though:
1322 * see http://wiki.squid-cache.org/KnowledgeBase/VaryNotCaching
1324 if (!Config
.errorDirectory
) {
1325 /* We 'negotiated' this ONLY from the Accept-Language. */
1326 rep
->header
.delById(Http::HdrType::VARY
);
1327 rep
->header
.putStr(Http::HdrType::VARY
, "Accept-Language");
1330 /* add the Content-Language header according to RFC section 14.12 */
1332 rep
->header
.putStr(Http::HdrType::CONTENT_LANGUAGE
, err_language
);
1334 #endif /* USE_ERROR_LOCALES */
1336 /* default templates are in English */
1337 /* language is known unless error_directory override used */
1338 if (!Config
.errorDirectory
)
1339 rep
->header
.putStr(Http::HdrType::CONTENT_LANGUAGE
, "en");
1342 rep
->body
.set(body
);
1345 // Make sure error codes get back to the client side for logging and
1349 request
->detailError(type
, detail
);
1351 request
->detailError(type
, SysErrorDetail::NewIfAny(xerrno
));
1358 ErrorState::buildBody()
1360 assert(page_id
> ERR_NONE
&& page_id
< error_page_count
);
1363 /** error_directory option in squid.conf overrides translations.
1364 * Custom errors are always found either in error_directory or the templates directory.
1365 * Otherwise locate the Accept-Language header
1367 if (!Config
.errorDirectory
&& page_id
< ERR_MAX
) {
1368 if (err_language
&& err_language
!= Config
.errorDefaultLanguage
)
1369 safe_free(err_language
);
1371 ErrorPageFile
localeTmpl(err_type_str
[page_id
], static_cast<err_type
>(page_id
));
1372 if (localeTmpl
.loadFor(request
.getRaw())) {
1373 inputLocation
= localeTmpl
.filename
;
1374 assert(localeTmpl
.language());
1375 err_language
= xstrdup(localeTmpl
.language());
1376 return compileBody(localeTmpl
.text(), true);
1379 #endif /* USE_ERR_LOCALES */
1382 * If client-specific error templates are not enabled or available.
1383 * fall back to the old style squid.conf settings.
1386 if (!Config
.errorDirectory
)
1387 err_language
= Config
.errorDefaultLanguage
;
1389 debugs(4, 2, "No existing error page language negotiated for " << this << ". Using default error file.");
1390 return compileBody(error_text
[page_id
], true);
1394 ErrorState::compileBody(const char *input
, bool allowRecursion
)
1396 return compile(input
, false, allowRecursion
);
1400 ErrorState::compile(const char *input
, bool building_deny_info_url
, bool allowRecursion
)
1405 build
.building_deny_info_url
= building_deny_info_url
;
1406 build
.allowRecursion
= allowRecursion
;
1407 build
.input
= input
;
1409 auto blockStart
= build
.input
;
1410 while (const auto letter
= *build
.input
) {
1411 if (letter
== '%') {
1412 build
.output
.append(blockStart
, build
.input
- blockStart
);
1413 compileLegacyCode(build
);
1414 blockStart
= build
.input
;
1416 else if (letter
== '@' && LogformatMagic
.cmp(build
.input
, LogformatMagic
.length()) == 0) {
1417 build
.output
.append(blockStart
, build
.input
- blockStart
);
1418 compileLogformatCode(build
);
1419 blockStart
= build
.input
;
1424 build
.output
.append(blockStart
, build
.input
- blockStart
);
1425 return build
.output
;
1428 /// react to a compile() error
1429 /// \param msg description of what went wrong
1430 /// \param near approximate start of the problematic input
1431 /// \param forceBypass whether detection of this error was introduced late,
1432 /// after old configurations containing this error could have been
1433 /// successfully validated and deployed (i.e. the admin may not be
1434 /// able to fix this newly detected but old problem quickly)
1436 ErrorState::noteBuildError_(const char *msg
, const char *near
, const bool forceBypass
)
1438 using ErrorPage::BuildErrorPrinter
;
1439 const auto runtime
= !starting_up
;
1440 if (runtime
|| forceBypass
) {
1441 // swallow this problem because the admin may not be (and/or the page
1442 // building code is not) ready to handle throwing consequences
1444 static unsigned int seenErrors
= 0;
1447 const auto debugLevel
=
1448 (seenErrors
> 100) ? DBG_DATA
:
1449 (starting_up
|| reconfiguring
) ? DBG_CRITICAL
:
1450 3; // most other errors have been reported as configuration errors
1452 // Error fatality depends on the error context: Reconfiguration errors
1453 // are, like startup ones, DBG_CRITICAL but will never become FATAL.
1454 if (starting_up
&& seenErrors
<= 10)
1455 debugs(4, debugLevel
, "WARNING: The following configuration error will be fatal in future Squid versions");
1457 debugs(4, debugLevel
, "ERROR: " << BuildErrorPrinter(inputLocation
, page_id
, msg
, near
));
1459 throw TexcHere(ToSBuf(BuildErrorPrinter(inputLocation
, page_id
, msg
, near
)));
1463 /* ErrorPage::BuildErrorPrinter */
1466 ErrorPage::BuildErrorPrinter::printLocation(std::ostream
&os
) const {
1467 if (!inputLocation
.isEmpty())
1468 return os
<< inputLocation
;
1470 if (page_id
< ERR_NONE
|| page_id
>= error_page_count
)
1471 return os
<< "[error page " << page_id
<< "]"; // should not happen
1473 if (page_id
< ERR_MAX
)
1474 return os
<< err_type_str
[page_id
];
1476 return os
<< "deny_info " << ErrorDynamicPages
.at(page_id
- ERR_MAX
)->page_name
;
1480 ErrorPage::BuildErrorPrinter::print(std::ostream
&os
) const {
1481 printLocation(os
) << ": " << msg
<< " near ";
1483 // TODO: Add support for prefix printing to Raw
1484 const size_t maxContextLength
= 15; // plus "..."
1485 if (strlen(near
) > maxContextLength
) {
1486 os
.write(near
, maxContextLength
);
1492 // XXX: We should not be converting (inner) exception to text if we are
1493 // going to throw again. See "add arbitrary (re)thrower-supplied details"
1494 // TODO in TextException.h for a long-term in-catcher solution.
1495 if (std::current_exception())
1496 os
<< "\n additional info: " << CurrentException
;
1501 /// add error page template to the global index
1503 ErrorPage::ImportStaticErrorText(const int page_id
, const char *text
, const SBuf
&inputLocation
)
1505 assert(!error_text
[page_id
]);
1506 error_text
[page_id
] = xstrdup(text
);
1507 ValidateStaticError(page_id
, inputLocation
);
1510 /// validate static error page
1512 ErrorPage::ValidateStaticError(const int page_id
, const SBuf
&inputLocation
)
1514 // Supplying nil ALE pointer limits validation to logformat %code
1515 // recognition by Format::Token::parse(). This is probably desirable
1516 // because actual %code assembly is slow and should not affect validation
1517 // when our ALE cannot have any real data (this code is not associated
1518 // with any real transaction).
1519 ErrorState
anErr(err_type(page_id
), Http::scNone
, nullptr, nullptr);
1520 anErr
.inputLocation
= inputLocation
;
1525 operator <<(std::ostream
&os
, const ErrorState
*err
)
1528 os
<< errorPageName(err
->page_id
);