]> git.ipfire.org Git - thirdparty/squid.git/blame - src/errorpage.cc
Source Format Enforcement (#532)
[thirdparty/squid.git] / src / errorpage.cc
CommitLineData
30a4f2a8 1/*
77b1029d 2 * Copyright (C) 1996-2020 The Squid Software Foundation and contributors
e25c139f 3 *
bbc27441
AJ
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.
30a4f2a8 7 */
bbc27441
AJ
8
9/* DEBUG: section 04 Error Generation */
10
f7f3304a 11#include "squid.h"
7e6eabbc 12#include "AccessLogEntry.h"
8a01b99e 13#include "cache_cf.h"
92ae4c86 14#include "clients/forward.h"
e0d28505 15#include "comm/Connection.h"
ec41b64c 16#include "comm/Write.h"
582c2af2 17#include "err_detail_type.h"
aa839030 18#include "errorpage.h"
602d9612 19#include "fde.h"
7e6eabbc 20#include "format/Format.h"
b3f7fd88 21#include "fs_io.h"
25f98340 22#include "html_quote.h"
a5bac1d2 23#include "HttpHeaderTools.h"
528b2c61 24#include "HttpReply.h"
25#include "HttpRequest.h"
0eb49b6d 26#include "MemBuf.h"
602d9612 27#include "MemObject.h"
1fa9b1a7 28#include "rfc1738.h"
7e6eabbc 29#include "sbuf/Stream.h"
4d5904f7 30#include "SquidConfig.h"
7e6eabbc 31#include "SquidTime.h"
602d9612
A
32#include "Store.h"
33#include "tools.h"
d295d770 34#include "wordlist.h"
4e540555
FC
35#if USE_AUTH
36#include "auth/UserRequest.h"
37#endif
cb4f4424 38#if USE_OPENSSL
4e540555
FC
39#include "ssl/ErrorDetailManager.h"
40#endif
02922e76 41
63be0a78 42/**
43 \defgroup ErrorPageInternal Error Page Internals
44 \ingroup ErrorPageAPI
45 *
f439fbd2 46 \section ErrorPagesAbstract Abstract:
63be0a78 47 * These routines are used to generate error messages to be
48 * sent to clients. The error type is used to select between
49 * the various message formats. (formats are stored in the
50 * Config.errorDirectory)
51 */
52
605f2c3e 53#if !defined(DEFAULT_SQUID_ERROR_DIR)
43000484
AJ
54/** Where to look for errors if config path fails.
55 \note Please use ./configure --datadir=/path instead of patching
56 */
57#define DEFAULT_SQUID_ERROR_DIR DEFAULT_SQUID_DATA_DIR"/errors"
58#endif
59
63be0a78 60/// \ingroup ErrorPageInternal
aa839030 61CBDATA_CLASS_INIT(ErrorState);
62
7e6eabbc
CT
63const SBuf ErrorState::LogformatMagic("@Squid{");
64
02922e76 65/* local types */
66
7e6eabbc
CT
67/// an error page created from admin-configurable metadata (e.g. deny_info)
68class ErrorDynamicPageInfo {
69public:
70 ErrorDynamicPageInfo(const int anId, const char *aName, const SBuf &aCfgLocation);
71 ~ErrorDynamicPageInfo() { xfree(page_name); }
72
73 /// error_text[] index for response body (unused in redirection responses)
02922e76 74 int id;
7e6eabbc
CT
75
76 /// Primary deny_info parameter:
77 /// * May start with an HTTP status code.
78 /// * Either a well-known error page name, a filename, or a redirect URL.
02922e76 79 char *page_name;
7e6eabbc
CT
80
81 /// admin-configured HTTP Location header value for redirection responses
82 const char *uri;
83
84 /// admin-configured name for the error page template (custom or standard)
85 const char *filename;
86
87 /// deny_info directive position in squid.conf (for reporting)
88 SBuf cfgLocation;
89
90 // XXX: Misnamed. Not just for redirects.
91 /// admin-configured HTTP status code
955394ce 92 Http::StatusCode page_redirect;
7e6eabbc
CT
93
94private:
95 // no copying of any kind
96 ErrorDynamicPageInfo(ErrorDynamicPageInfo &&) = delete;
97};
98
99namespace ErrorPage {
100
101/// state and parameters shared by several ErrorState::compile*() methods
102class Build
103{
104public:
105 SBuf output; ///< compilation result
106 const char *input = nullptr; ///< template bytes that need to be compiled
107 bool building_deny_info_url = false; ///< whether we compile deny_info URI
108 bool allowRecursion = false; ///< whether top-level compile() calls are OK
109};
110
111/// pretty-prints error page/deny_info building error
112class BuildErrorPrinter
113{
114public:
115 BuildErrorPrinter(const SBuf &anInputLocation, int aPage, const char *aMsg, const char *aNear): inputLocation(anInputLocation), page_id(aPage), msg(aMsg), near(aNear) {}
116
117 /// reports error details (for admin-visible exceptions and debugging)
118 std::ostream &print(std::ostream &) const;
119
120 /// print() helper to report where the error was found
121 std::ostream &printLocation(std::ostream &os) const;
122
123 /* saved constructor parameters */
124 const SBuf &inputLocation;
125 const int page_id;
126 const char *msg;
127 const char *near;
128};
129
130static inline std::ostream &
131operator <<(std::ostream &os, const BuildErrorPrinter &context)
132{
133 return context.print(os);
134}
135
136static const char *IsDenyInfoUri(const int page_id);
137
138static void ImportStaticErrorText(const int page_id, const char *text, const SBuf &inputLocation);
139static void ValidateStaticError(const int page_id, const SBuf &inputLocation);
140
141} // namespace ErrorPage
02922e76 142
143/* local constant and vars */
144
63be0a78 145/**
cfdb8f88 146 \ingroup ErrorPageInternal
63be0a78 147 *
148 \note hard coded error messages are not appended with %S
149 * automagically to give you more control on the format
1d803566 150 */
9e008dda 151static const struct {
f53969cc 152 int type; /* and page_id */
2ac76861 153 const char *text;
62e76326 154}
155
156error_hard_text[] = {
157
9e008dda
AJ
158 {
159 ERR_SQUID_SIGNATURE,
160 "\n<br>\n"
161 "<hr>\n"
162 "<div id=\"footer\">\n"
163 "Generated %T by %h (%s)\n"
164 "</div>\n"
165 "</body></html>\n"
166 },
167 {
168 TCP_RESET,
169 "reset"
7e6eabbc
CT
170 },
171 {
172 ERR_SECURE_ACCEPT_FAIL,
173 "secure accept fail"
174 },
175 {
176 ERR_REQUEST_START_TIMEOUT,
177 "request start timedout"
178 },
179 {
180 MGR_INDEX,
181 "mgr_index"
9e008dda
AJ
182 }
183};
02922e76 184
63be0a78 185/// \ingroup ErrorPageInternal
c8ea3cc0 186static std::vector<ErrorDynamicPageInfo *> ErrorDynamicPages;
02922e76 187
188/* local prototypes */
189
63be0a78 190/// \ingroup ErrorPageInternal
2ac76861 191static const int error_hard_text_count = sizeof(error_hard_text) / sizeof(*error_hard_text);
63be0a78 192
193/// \ingroup ErrorPageInternal
02922e76 194static char **error_text = NULL;
63be0a78 195
196/// \ingroup ErrorPageInternal
02922e76 197static int error_page_count = 0;
9b312a19 198
5b52cb6c 199/// \ingroup ErrorPageInternal
0ae3294a 200static MemBuf error_stylesheet;
5b52cb6c 201
1d803566 202static const char *errorFindHardText(err_type type);
2b663917 203static IOCB errorSendComplete;
1e74c110 204
02259ff8
CT
205/// \ingroup ErrorPageInternal
206/// manages an error page template
dc49061a
A
207class ErrorPageFile: public TemplateFile
208{
02259ff8 209public:
7e6eabbc 210 ErrorPageFile(const char *name, const err_type code) : TemplateFile(name, code) {}
02259ff8
CT
211
212 /// The template text data read from disk
7e6eabbc 213 const char *text() { return template_.c_str(); }
02259ff8 214
7e6eabbc
CT
215protected:
216 virtual void setDefault() override {
217 template_ = "Internal Error: Missing Template ";
218 template_.append(templateName.termedBuf());
02259ff8 219 }
02259ff8 220};
e6ccf245 221
63be0a78 222/// \ingroup ErrorPageInternal
e6ccf245 223err_type &operator++ (err_type &anErr)
224{
1f1ae50a 225 int tmp = (int)anErr;
226 anErr = (err_type)(++tmp);
e6ccf245 227 return anErr;
228}
4ff59fc7 229
63be0a78 230/// \ingroup ErrorPageInternal
62e76326 231int operator - (err_type const &anErr, err_type const &anErr2)
232{
4ff59fc7 233 return (int)anErr - (int)anErr2;
234}
235
7e6eabbc
CT
236/// \return deny_info URL if the given page is a deny_info page with a URL
237/// \return nullptr otherwise
238static const char *
239ErrorPage::IsDenyInfoUri(const int page_id)
240{
241 if (ERR_MAX <= page_id && page_id < error_page_count)
242 return ErrorDynamicPages.at(page_id - ERR_MAX)->uri; // may be nil
243 return nullptr;
244}
245
b8d8561b 246void
0673c0ba 247errorInitialize(void)
fa966b74 248{
7e6eabbc
CT
249 using ErrorPage::ImportStaticErrorText;
250
728da2ee 251 err_type i;
1d803566 252 const char *text;
4ff59fc7 253 error_page_count = ERR_MAX + ErrorDynamicPages.size();
e6ccf245 254 error_text = static_cast<char **>(xcalloc(error_page_count, sizeof(char *)));
62e76326 255
e6ccf245 256 for (i = ERR_NONE, ++i; i < error_page_count; ++i) {
62e76326 257 safe_free(error_text[i]);
62e76326 258
43000484
AJ
259 if ((text = errorFindHardText(i))) {
260 /**\par
261 * Index any hard-coded error text into defaults.
262 */
7e6eabbc
CT
263 static const SBuf builtIn("built-in");
264 ImportStaticErrorText(i, text, builtIn);
43000484
AJ
265
266 } else if (i < ERR_MAX) {
267 /**\par
268 * Index precompiled fixed template files from one of two sources:
269 * (a) default language translation directory (error_default_language)
270 * (b) admin specified custom directory (error_directory)
271 */
8ff2520a 272 ErrorPageFile errTmpl(err_type_str[i], i);
7e6eabbc
CT
273 errTmpl.loadDefault();
274 ImportStaticErrorText(i, errTmpl.text(), errTmpl.filename);
62e76326 275 } else {
43000484
AJ
276 /** \par
277 * Index any unknown file names used by deny_info.
278 */
e6334d92 279 ErrorDynamicPageInfo *info = ErrorDynamicPages.at(i - ERR_MAX);
62e76326 280 assert(info && info->id == i && info->page_name);
281
7e6eabbc 282 if (info->filename) {
43000484 283 /** But only if they are not redirection URL. */
7e6eabbc
CT
284 ErrorPageFile errTmpl(info->filename, ERR_MAX);
285 errTmpl.loadDefault();
286 ImportStaticErrorText(i, errTmpl.text(), errTmpl.filename);
287 } else {
288 assert(info->uri);
289 ErrorPage::ValidateStaticError(i, info->cfgLocation);
62e76326 290 }
291 }
1d803566 292 }
5b52cb6c 293
0ae3294a
AJ
294 error_stylesheet.reset();
295
5b52cb6c 296 // look for and load stylesheet into global MemBuf for it.
9e008dda 297 if (Config.errorStylesheet) {
8ff2520a 298 ErrorPageFile tmpl("StylesSheet", ERR_MAX);
02259ff8 299 tmpl.loadFromFile(Config.errorStylesheet);
4391cd15 300 error_stylesheet.appendf("%s",tmpl.text());
5b52cb6c 301 }
02259ff8 302
cb4f4424 303#if USE_OPENSSL
02259ff8 304 Ssl::errorDetailInitialize();
c259af96 305#endif
1d803566 306}
307
c68e9c6b 308void
309errorClean(void)
310{
311 if (error_text) {
62e76326 312 int i;
313
95dc7ff4 314 for (i = ERR_NONE + 1; i < error_page_count; ++i)
62e76326 315 safe_free(error_text[i]);
316
317 safe_free(error_text);
c68e9c6b 318 }
62e76326 319
385acf91 320 while (!ErrorDynamicPages.empty()) {
7e6eabbc 321 delete ErrorDynamicPages.back();
38c22baf
FC
322 ErrorDynamicPages.pop_back();
323 }
62e76326 324
c68e9c6b 325 error_page_count = 0;
02259ff8 326
cb4f4424 327#if USE_OPENSSL
02259ff8 328 Ssl::errorDetailClean();
c259af96 329#endif
c68e9c6b 330}
331
63be0a78 332/// \ingroup ErrorPageInternal
1d803566 333static const char *
334errorFindHardText(err_type type)
335{
336 int i;
62e76326 337
95dc7ff4 338 for (i = 0; i < error_hard_text_count; ++i)
62e76326 339 if (error_hard_text[i].type == type)
340 return error_hard_text[i].text;
341
1d803566 342 return NULL;
343}
344
8ff2520a 345TemplateFile::TemplateFile(const char *name, const err_type code): silent(false), wasLoaded(false), templateName(name), templateCode(code)
1d803566 346{
02259ff8
CT
347 assert(name);
348}
349
7e6eabbc 350void
02259ff8
CT
351TemplateFile::loadDefault()
352{
353 if (loaded()) // already loaded?
7e6eabbc 354 return;
43000484
AJ
355
356 /** test error_directory configured location */
02259ff8
CT
357 if (Config.errorDirectory) {
358 char path[MAXPATHLEN];
359 snprintf(path, sizeof(path), "%s/%s", Config.errorDirectory, templateName.termedBuf());
360 loadFromFile(path);
361 }
43000484
AJ
362
363#if USE_ERR_LOCALES
364 /** test error_default_language location */
02259ff8
CT
365 if (!loaded() && Config.errorDefaultLanguage) {
366 if (!tryLoadTemplate(Config.errorDefaultLanguage)) {
8ff2520a 367 debugs(1, (templateCode < TCP_RESET ? DBG_CRITICAL : 3), "Unable to load default error language files. Reset to backups.");
43000484
AJ
368 }
369 }
370#endif
62e76326 371
43000484 372 /* test default location if failed (templates == English translation base templates) */
02259ff8
CT
373 if (!loaded()) {
374 tryLoadTemplate("templates");
5bf33a09 375 }
62e76326 376
1d803566 377 /* giving up if failed */
b073fc4b 378 if (!loaded()) {
8ff2520a 379 debugs(1, (templateCode < TCP_RESET ? DBG_CRITICAL : 3), "WARNING: failed to find or read error text file " << templateName);
7e6eabbc
CT
380 template_.clear();
381 setDefault();
382 wasLoaded = true;
b073fc4b 383 }
1d803566 384}
385
02259ff8
CT
386bool
387TemplateFile::tryLoadTemplate(const char *lang)
1d803566 388{
02259ff8
CT
389 assert(lang);
390
9b312a19 391 char path[MAXPATHLEN];
02259ff8
CT
392 /* TODO: prep the directory path string to prevent snprintf ... */
393 snprintf(path, sizeof(path), "%s/%s/%s",
394 DEFAULT_SQUID_ERROR_DIR, lang, templateName.termedBuf());
395 path[MAXPATHLEN-1] = '\0';
396
397 if (loadFromFile(path))
398 return true;
399
400#if HAVE_GLOB
401 if ( strlen(lang) == 2) {
402 /* TODO glob the error directory for sub-dirs matching: <tag> '-*' */
403 /* use first result. */
404 debugs(4,2, HERE << "wildcard fallback errors not coded yet.");
405 }
406#endif
407
408 return false;
409}
410
411bool
412TemplateFile::loadFromFile(const char *path)
413{
414 int fd;
e8f6c5c7 415 char buf[4096];
e8f6c5c7 416 ssize_t len;
1d803566 417
02259ff8
CT
418 if (loaded()) // already loaded?
419 return true;
45308e4d 420
c4aefe96 421 fd = file_open(path, O_RDONLY | O_TEXT);
e8f6c5c7 422
423 if (fd < 0) {
43000484 424 /* with dynamic locale negotiation we may see some failures before a success. */
b69e9ffa
AJ
425 if (!silent && templateCode < TCP_RESET) {
426 int xerrno = errno;
ec1c5408 427 debugs(4, DBG_CRITICAL, "ERROR: loading file '" << path << "': " << xstrerr(xerrno));
b69e9ffa 428 }
02259ff8
CT
429 wasLoaded = false;
430 return wasLoaded;
1d803566 431 }
62e76326 432
7e6eabbc 433 template_.clear();
9e008dda 434 while ((len = FD_READ_METHOD(fd, buf, sizeof(buf))) > 0) {
7e6eabbc 435 template_.append(buf, len);
e8f6c5c7 436 }
62e76326 437
e8f6c5c7 438 if (len < 0) {
b69e9ffa 439 int xerrno = errno;
7e6eabbc 440 file_close(fd);
b69e9ffa 441 debugs(4, DBG_CRITICAL, MYNAME << "ERROR: failed to fully read: '" << path << "': " << xstrerr(xerrno));
7e6eabbc
CT
442 wasLoaded = false;
443 return false;
1e74c110 444 }
62e76326 445
1d803566 446 file_close(fd);
e8f6c5c7 447
7e6eabbc
CT
448 filename = SBuf(path);
449
450 if (!parse()) {
451 debugs(4, DBG_CRITICAL, "ERROR: parsing error in template file: " << path);
452 wasLoaded = false;
453 return false;
454 }
455
02259ff8
CT
456 wasLoaded = true;
457 return wasLoaded;
458}
459
460bool strHdrAcptLangGetItem(const String &hdr, char *lang, int langLen, size_t &pos)
461{
dc49061a 462 while (pos < hdr.size()) {
02259ff8 463
a32e7644
AJ
464 /* skip any initial whitespace. */
465 while (pos < hdr.size() && xisspace(hdr[pos]))
466 ++pos;
62e76326 467
02259ff8
CT
468 /*
469 * Header value format:
470 * - sequence of whitespace delimited tags
471 * - each tag may suffix with ';'.* which we can ignore.
472 * - IFF a tag contains only two characters we can wildcard ANY translations matching: <it> '-'? .*
473 * with preference given to an exact match.
474 */
475 bool invalid_byte = false;
3d4c0e37 476 char *dt = lang;
02259ff8
CT
477 while (pos < hdr.size() && hdr[pos] != ';' && hdr[pos] != ',' && !xisspace(hdr[pos]) && dt < (lang + (langLen -1)) ) {
478 if (!invalid_byte) {
479#if USE_HTTP_VIOLATIONS
480 // if accepting violations we may as well accept some broken browsers
481 // which may send us the right code, wrong ISO formatting.
482 if (hdr[pos] == '_')
483 *dt = '-';
484 else
485#endif
486 *dt = xtolower(hdr[pos]);
487 // valid codes only contain A-Z, hyphen (-) and *
488 if (*dt != '-' && *dt != '*' && (*dt < 'a' || *dt > 'z') )
489 invalid_byte = true;
490 else
95dc7ff4 491 ++dt; // move to next destination byte.
02259ff8 492 }
95dc7ff4 493 ++pos;
02259ff8 494 }
a38ec4b1 495 *dt = '\0'; // nul-terminated the filename content string before system use.
62e76326 496
a32e7644
AJ
497 // if we terminated the tag on garbage or ';' we need to skip to the next ',' or end of header.
498 while (pos < hdr.size() && hdr[pos] != ',')
499 ++pos;
500
501 if (pos < hdr.size() && hdr[pos] == ',')
502 ++pos;
503
3d4c0e37 504 debugs(4, 9, "STATE: lang=" << lang << ", pos=" << pos << ", buf='" << ((pos < hdr.size()) ? hdr.substr(pos,hdr.size()) : "") << "'");
02259ff8
CT
505
506 /* if we found anything we might use, try it. */
507 if (*lang != '\0' && !invalid_byte)
508 return true;
509 }
510 return false;
511}
512
513bool
b248c2a3 514TemplateFile::loadFor(const HttpRequest *request)
02259ff8
CT
515{
516 String hdr;
517
cf0a0bdf 518#if USE_ERR_LOCALES
02259ff8
CT
519 if (loaded()) // already loaded?
520 return true;
521
4c7c97f3 522 if (!request || !request->header.getList(Http::HdrType::ACCEPT_LANGUAGE, &hdr))
02259ff8
CT
523 return false;
524
525 char lang[256];
526 size_t pos = 0; // current parsing position in header string
527
528 debugs(4, 6, HERE << "Testing Header: '" << hdr << "'");
529
530 while ( strHdrAcptLangGetItem(hdr, lang, 256, pos) ) {
531
532 /* wildcard uses the configured default language */
533 if (lang[0] == '*' && lang[1] == '\0') {
534 debugs(4, 6, HERE << "Found language '" << lang << "'. Using configured default.");
535 return false;
536 }
537
538 debugs(4, 6, HERE << "Found language '" << lang << "', testing for available template");
539
540 if (tryLoadTemplate(lang)) {
541 /* store the language we found for the Content-Language reply header */
542 errLanguage = lang;
543 break;
544 } else if (Config.errorLogMissingLanguages) {
545 debugs(4, DBG_IMPORTANT, "WARNING: Error Pages Missing Language: " << lang);
546 }
547 }
cf0a0bdf 548#endif
02259ff8
CT
549
550 return loaded();
6eb42cae 551}
552
7e6eabbc
CT
553ErrorDynamicPageInfo::ErrorDynamicPageInfo(const int anId, const char *aName, const SBuf &aCfgLocation):
554 id(anId),
555 page_name(xstrdup(aName)),
556 uri(nullptr),
557 filename(nullptr),
558 cfgLocation(aCfgLocation),
559 page_redirect(static_cast<Http::StatusCode>(atoi(page_name)))
02922e76 560{
7e6eabbc
CT
561 const char *filenameOrUri = nullptr;
562 if (xisdigit(*page_name)) {
563 if (const char *statusCodeEnd = strchr(page_name, ':'))
564 filenameOrUri = statusCodeEnd + 1;
565 } else {
566 assert(!page_redirect);
567 filenameOrUri = page_name;
568 }
569
570 // Guessed uri, filename, or both values may be nil or malformed.
571 // They are validated later.
572 if (!page_redirect) {
573 if (filenameOrUri && strchr(filenameOrUri, ':')) // looks like a URL
574 uri = filenameOrUri;
575 else
576 filename = filenameOrUri;
577 }
578 else if (page_redirect/100 == 3) {
579 // redirects imply a URL
580 uri = filenameOrUri;
581 } else {
582 // non-redirects imply an error page name
583 filename = filenameOrUri;
584 }
585
586 const auto info = this; // source code change reduction hack
587 // TODO: Move and refactor to avoid self_destruct()s in reconfigure.
aed9a15b
AJ
588
589 /* WARNING on redirection status:
590 * 2xx are permitted, but not documented officially.
591 * - might be useful for serving static files (PAC etc) in special cases
592 * 3xx require a URL suitable for Location: header.
593 * - the current design does not allow for a Location: URI as well as a local file template
594 * although this possibility is explicitly permitted in the specs.
595 * 4xx-5xx require a local file template.
596 * - sending Location: on these codes with no body is invalid by the specs.
597 * - current result is Squid crashing or XSS problems as dynamic deny_info load random disk files.
598 * - a future redesign of the file loading may result in loading remote objects sent inline as local body.
599 */
955394ce 600 if (info->page_redirect == Http::scNone)
aed9a15b
AJ
601 ; // special case okay.
602 else if (info->page_redirect < 200 || info->page_redirect > 599) {
603 // out of range
604 debugs(0, DBG_CRITICAL, "FATAL: status " << info->page_redirect << " is not valid on '" << page_name << "'");
605 self_destruct();
606 } else if ( /* >= 200 && */ info->page_redirect < 300 && strchr(&(page_name[4]), ':')) {
607 // 2xx require a local template file
0ff52055 608 debugs(0, DBG_CRITICAL, "FATAL: status " << info->page_redirect << " requires a template on '" << page_name << "'");
aed9a15b 609 self_destruct();
0ff52055 610 } else if (info->page_redirect >= 300 && info->page_redirect <= 399 && !strchr(&(page_name[4]), ':')) {
aed9a15b 611 // 3xx require an absolute URL
0ff52055 612 debugs(0, DBG_CRITICAL, "FATAL: status " << info->page_redirect << " requires a URL on '" << page_name << "'");
aed9a15b
AJ
613 self_destruct();
614 } else if (info->page_redirect >= 400 /* && <= 599 */ && strchr(&(page_name[4]), ':')) {
615 // 4xx/5xx require a local template file
0ff52055 616 debugs(0, DBG_CRITICAL, "FATAL: status " << info->page_redirect << " requires a template on '" << page_name << "'");
aed9a15b
AJ
617 self_destruct();
618 }
619 // else okay.
02922e76 620}
621
63be0a78 622/// \ingroup ErrorPageInternal
76cdc28d 623static int
624errorPageId(const char *page_name)
625{
95dc7ff4 626 for (int i = 0; i < ERR_MAX; ++i) {
62e76326 627 if (strcmp(err_type_str[i], page_name) == 0)
628 return i;
76cdc28d 629 }
62e76326 630
95dc7ff4 631 for (size_t j = 0; j < ErrorDynamicPages.size(); ++j) {
4c9eadc2 632 if (strcmp(ErrorDynamicPages[j]->page_name, page_name) == 0)
190154cf 633 return j + ERR_MAX;
76cdc28d 634 }
62e76326 635
76cdc28d 636 return ERR_NONE;
637}
638
e6ccf245 639err_type
7e6eabbc 640errorReservePageId(const char *page_name, const SBuf &cfgLocation)
02922e76 641{
76cdc28d 642 int id = errorPageId(page_name);
62e76326 643
76cdc28d 644 if (id == ERR_NONE) {
7e6eabbc
CT
645 id = ERR_MAX + ErrorDynamicPages.size();
646 const auto info = new ErrorDynamicPageInfo(id, page_name, cfgLocation);
62e76326 647 ErrorDynamicPages.push_back(info);
76cdc28d 648 }
62e76326 649
e6ccf245 650 return (err_type)id;
02922e76 651}
652
63be0a78 653/// \ingroup ErrorPageInternal
64b66b76 654const char *
c68e9c6b 655errorPageName(int pageId)
53ad48e6 656{
f53969cc 657 if (pageId >= ERR_NONE && pageId < ERR_MAX) /* common case */
62e76326 658 return err_type_str[pageId];
659
4ff59fc7 660 if (pageId >= ERR_MAX && pageId - ERR_MAX < (ssize_t)ErrorDynamicPages.size())
4c9eadc2 661 return ErrorDynamicPages[pageId - ERR_MAX]->page_name;
62e76326 662
f53969cc 663 return "ERR_UNKNOWN"; /* should not happen */
53ad48e6 664}
665
a23223bf 666ErrorState *
7e6eabbc 667ErrorState::NewForwarding(err_type type, HttpRequestPointer &request, const AccessLogEntry::Pointer &ale)
a23223bf 668{
4c7c97f3 669 const Http::StatusCode status = (request && request->flags.needValidation) ?
e2849af8 670 Http::scGatewayTimeout : Http::scServiceUnavailable;
7e6eabbc 671 return new ErrorState(type, status, request.getRaw(), ale);
a23223bf
CT
672}
673
f5e17947 674ErrorState::ErrorState(err_type t) :
f53969cc
SM
675 type(t),
676 page_id(t),
f5e17947
CT
677 callback(nullptr)
678{
679}
680
681ErrorState::ErrorState(err_type t, Http::StatusCode status, HttpRequest * req, const AccessLogEntry::Pointer &anAle) :
682 ErrorState(t)
fe40a877 683{
4c9eadc2
FC
684 if (page_id >= ERR_MAX && ErrorDynamicPages[page_id - ERR_MAX]->page_redirect != Http::scNone)
685 httpStatus = ErrorDynamicPages[page_id - ERR_MAX]->page_redirect;
f5e17947
CT
686 else
687 httpStatus = status;
688
689 if (req) {
690 request = req;
691 src_addr = req->client_addr;
692 }
693
694 ale = anAle;
695}
696
697ErrorState::ErrorState(HttpRequest * req, HttpReply *errorReply) :
698 ErrorState(ERR_RELAY_REMOTE)
699{
700 Must(errorReply);
701 response_ = errorReply;
702 httpStatus = errorReply->sline.status();
913524f0 703
fc94fd66 704 if (req) {
b248c2a3 705 request = req;
913524f0 706 src_addr = req->client_addr;
2cc81f1f 707 }
fe40a877 708}
709
fe40a877 710void
711errorAppendEntry(StoreEntry * entry, ErrorState * err)
712{
e4a67a80 713 assert(entry->mem_obj != NULL);
528b2c61 714 assert (entry->isEmpty());
0b86805b 715 debugs(4, 4, "Creating an error page for entry " << entry <<
716 " with errorstate " << err <<
717 " page id " << err->page_id);
62e76326 718
1ea80e98 719 if (entry->store_status != STORE_PENDING) {
0b86805b 720 debugs(4, 2, "Skipping error page due to store_status: " << entry->store_status);
62e76326 721 /*
722 * If the entry is not STORE_PENDING, then no clients
723 * care about it, and we don't need to generate an
724 * error message
725 */
726 assert(EBIT_TEST(entry->flags, ENTRY_ABORTED));
6ea35247 727 assert(entry->mem_obj->nclients == 0);
913524f0 728 delete err;
62e76326 729 return;
1ea80e98 730 }
62e76326 731
76cdc28d 732 if (err->page_id == TCP_RESET) {
62e76326 733 if (err->request) {
bf8fe701 734 debugs(4, 2, "RSTing this reply");
e857372a 735 err->request->flags.resetTcp = true;
62e76326 736 }
98264874 737 }
62e76326 738
eacfca83 739 entry->storeErrorResponse(err->BuildHttpReply());
913524f0 740 delete err;
fe40a877 741}
742
fe40a877 743void
e0d28505 744errorSend(const Comm::ConnectionPointer &conn, ErrorState * err)
fe40a877 745{
4c7c97f3 746 debugs(4, 3, conn << ", err=" << err);
e0d28505 747 assert(Comm::IsConnOpen(conn));
62e76326 748
4c7c97f3 749 HttpReplyPointer rep(err->BuildHttpReply());
62e76326 750
ddbe383d 751 MemBuf *mb = rep->pack();
ec41b64c
AJ
752 AsyncCall::Pointer call = commCbCall(78, 5, "errorSendComplete",
753 CommIoCbPtrFun(&errorSendComplete, err));
b0388924 754 Comm::Write(conn, mb, call);
ddbe383d 755 delete mb;
fe40a877 756}
757
63be0a78 758/**
759 \ingroup ErrorPageAPI
fe40a877 760 *
63be0a78 761 * Called by commHandleWrite() after data has been written
762 * to the client socket.
fe40a877 763 *
63be0a78 764 \note If there is a callback, the callback is responsible for
b3802bdc 765 * closing the FD, otherwise we do it ourselves.
fe40a877 766 */
767static void
ced8def3 768errorSendComplete(const Comm::ConnectionPointer &conn, char *, size_t size, Comm::Flag errflag, int, void *data)
fe40a877 769{
e6ccf245 770 ErrorState *err = static_cast<ErrorState *>(data);
e0d28505 771 debugs(4, 3, HERE << conn << ", size=" << size);
62e76326 772
c8407295 773 if (errflag != Comm::ERR_CLOSING) {
62e76326 774 if (err->callback) {
bf8fe701 775 debugs(4, 3, "errorSendComplete: callback");
e0d28505 776 err->callback(conn->fd, err->callback_data, size);
62e76326 777 } else {
bf8fe701 778 debugs(4, 3, "errorSendComplete: comm_close");
80463bb4 779 conn->close();
62e76326 780 }
fe40a877 781 }
62e76326 782
913524f0 783 delete err;
fe40a877 784}
785
913524f0 786ErrorState::~ErrorState()
6eb42cae 787{
913524f0
AJ
788 safe_free(redirect_url);
789 safe_free(url);
790 safe_free(request_hdrs);
791 wordlistDestroy(&ftp.server_msg);
792 safe_free(ftp.request);
793 safe_free(ftp.reply);
913524f0 794 safe_free(err_msg);
5bf33a09 795#if USE_ERR_LOCALES
913524f0 796 if (err_language != Config.errorDefaultLanguage)
5bf33a09 797#endif
913524f0 798 safe_free(err_language);
cb4f4424 799#if USE_OPENSSL
913524f0 800 delete detail;
4d16918e 801#endif
1e74c110 802}
8213067d 803
c70281f8
AJ
804int
805ErrorState::Dump(MemBuf * mb)
b5fb34f1 806{
032785bf 807 MemBuf str;
cc192b50 808 char ntoabuf[MAX_IPSTRLEN];
809
2fe7eff9 810 str.reset();
b5fb34f1 811 /* email subject line */
4391cd15
AJ
812 str.appendf("CacheErrorInfo - %s", errorPageName(type));
813 mb->appendf("?subject=%s", rfc1738_escape_part(str.buf));
2fe7eff9 814 str.reset();
b5fb34f1 815 /* email body */
4391cd15 816 str.appendf("CacheHost: %s\r\n", getMyHostname());
b5fb34f1 817 /* - Err Msgs */
4391cd15 818 str.appendf("ErrPage: %s\r\n", errorPageName(type));
62e76326 819
c70281f8 820 if (xerrno) {
4391cd15 821 str.appendf("Err: (%d) %s\r\n", xerrno, strerror(xerrno));
b5fb34f1 822 } else {
4391cd15 823 str.append("Err: [none]\r\n", 13);
b5fb34f1 824 }
2f1431ea 825#if USE_AUTH
98340a7b 826 if (auth_user_request.getRaw() && auth_user_request->denyMessage())
4391cd15 827 str.appendf("Auth ErrMsg: %s\r\n", auth_user_request->denyMessage());
2f1431ea 828#endif
3ff65596 829 if (dnsError.size() > 0)
4391cd15 830 str.appendf("DNS ErrMsg: %s\r\n", dnsError.termedBuf());
62e76326 831
b5fb34f1 832 /* - TimeStamp */
4391cd15 833 str.appendf("TimeStamp: %s\r\n\r\n", mkrfc1123(squid_curtime));
62e76326 834
b5fb34f1 835 /* - IP stuff */
4391cd15 836 str.appendf("ClientIP: %s\r\n", src_addr.toStr(ntoabuf,MAX_IPSTRLEN));
62e76326 837
b3802bdc 838 if (request && request->hier.host[0] != '\0') {
4391cd15 839 str.appendf("ServerIP: %s\r\n", request->hier.host);
b5fb34f1 840 }
62e76326 841
4391cd15 842 str.append("\r\n", 2);
b5fb34f1 843 /* - HTTP stuff */
4391cd15 844 str.append("HTTP Request:\r\n", 15);
51b5dcf5
AJ
845 if (request) {
846 str.appendf(SQUIDSBUFPH " " SQUIDSBUFPH " %s/%d.%d\n",
4391cd15 847 SQUIDSBUFPRINT(request->method.image()),
51b5dcf5 848 SQUIDSBUFPRINT(request->url.path()),
4391cd15
AJ
849 AnyP::ProtocolType_str[request->http_ver.protocol],
850 request->http_ver.major, request->http_ver.minor);
10201568 851 request->header.packInto(&str);
b5fb34f1 852 }
62e76326 853
4391cd15 854 str.append("\r\n", 2);
b5fb34f1 855 /* - FTP stuff */
62e76326 856
c70281f8 857 if (ftp.request) {
4391cd15
AJ
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);
c70281f8 861 wordlistCat(ftp.server_msg, &str);
4391cd15 862 str.append("\r\n", 2);
b5fb34f1 863 }
62e76326 864
4391cd15
AJ
865 str.append("\r\n", 2);
866 mb->appendf("&body=%s", rfc1738_escape_part(str.buf));
2fe7eff9 867 str.clean();
b5fb34f1 868 return 0;
869}
870
63be0a78 871/// \ingroup ErrorPageInternal
2658f489 872#define CVT_BUF_SZ 512
fe40a877 873
7e6eabbc
CT
874void
875ErrorState::compileLogformatCode(Build &build)
876{
877 assert(LogformatMagic.cmp(build.input, LogformatMagic.length()) == 0);
878
879 try {
880 const auto logformat = build.input + LogformatMagic.length();
881
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");
889
890 static MemBuf result;
891 result.reset();
892 const auto logformatLen = Format::AssembleOne(logformat, result, ale);
893 assert(logformatLen > 0);
894 const auto closure = logformat + logformatLen;
895 if (*closure != '}')
896 throw TexcHere("Missing closing brace (})");
897 build.output.append(result.content(), result.contentSize());
898 build.input = closure + 1;
899 return;
900 } catch (...) {
901 noteBuildError("Bad @Squid{logformat} sequence", build.input);
902 }
903
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;
908}
909
910void
911ErrorState::compileLegacyCode(Build &build)
8213067d 912{
032785bf 913 static MemBuf mb;
f53969cc 914 const char *p = NULL; /* takes priority over mb if set */
10270faa 915 int do_quote = 1;
4ad8d23d 916 int no_urlescape = 0; /* if true then item is NOT to be further URL-encoded */
cc192b50 917 char ntoabuf[MAX_IPSTRLEN];
eeb423fb 918
2fe7eff9 919 mb.reset();
62e76326 920
7e6eabbc
CT
921 const auto &building_deny_info_url = build.building_deny_info_url; // a change reduction hack
922
923 const auto letter = build.input[1];
924
925 switch (letter) {
62e76326 926
523f44f5 927 case 'a':
2f1431ea 928#if USE_AUTH
4c7c97f3 929 if (request && request->auth_user_request)
c70281f8 930 p = request->auth_user_request->username();
523f44f5 931 if (!p)
2f1431ea 932#endif
523f44f5 933 p = "-";
523f44f5 934 break;
88d1e459 935
ea35939b
A
936 case 'A':
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));
940 else
941 p = "-";
942 break;
943
88d1e459 944 case 'b':
4391cd15 945 mb.appendf("%u", getMyPort());
88d1e459
AJ
946 break;
947
8f872bb6 948 case 'B':
1e98e28b 949 if (building_deny_info_url) break;
851feda6 950 if (request) {
4c7c97f3 951 const SBuf &tmp = Ftp::UrlWith2f(request.getRaw());
851feda6
AJ
952 mb.append(tmp.rawContent(), tmp.length());
953 } else
954 p = "[no URL]";
62e76326 955 break;
956
42b51993 957 case 'c':
1e98e28b 958 if (building_deny_info_url) break;
c70281f8 959 p = errorPageName(type);
62e76326 960 break;
961
4d16918e 962 case 'D':
7e6eabbc 963 if (!build.allowRecursion)
4d16918e 964 p = "%D"; // if recursion is not allowed, do not convert
cb4f4424 965#if USE_OPENSSL
4d16918e
CT
966 // currently only SSL error details implemented
967 else if (detail) {
4c7c97f3 968 detail->useRequest(request.getRaw());
4d16918e 969 const String &errDetail = detail->toString();
b38b26cb 970 if (errDetail.size() > 0) {
7e6eabbc
CT
971 const auto compiledDetail = compileBody(errDetail.termedBuf(), false);
972 mb.append(compiledDetail.rawContent(), compiledDetail.length());
8211c314
CT
973 do_quote = 0;
974 }
975 }
4d16918e 976#endif
8211c314 977 if (!mb.contentSize())
4391cd15 978 mb.append("[No Error Detail]", 17);
4d16918e
CT
979 break;
980
042461c3 981 case 'e':
4391cd15 982 mb.appendf("%d", xerrno);
62e76326 983 break;
984
042461c3 985 case 'E':
c70281f8 986 if (xerrno)
4391cd15 987 mb.appendf("(%d) %s", xerrno, strerror(xerrno));
62e76326 988 else
4391cd15 989 mb.append("[No Error]", 10);
62e76326 990 break;
991
fe40a877 992 case 'f':
1e98e28b 993 if (building_deny_info_url) break;
62e76326 994 /* FTP REQUEST LINE */
c70281f8
AJ
995 if (ftp.request)
996 p = ftp.request;
62e76326 997 else
998 p = "nothing";
62e76326 999 break;
1000
fe40a877 1001 case 'F':
1e98e28b 1002 if (building_deny_info_url) break;
62e76326 1003 /* FTP REPLY LINE */
c6b684dd 1004 if (ftp.reply)
c70281f8 1005 p = ftp.reply;
62e76326 1006 else
1007 p = "nothing";
62e76326 1008 break;
1009
7131112f 1010 case 'g':
1e98e28b 1011 if (building_deny_info_url) break;
a91d3505
AJ
1012 /* FTP SERVER RESPONSE */
1013 if (ftp.listing) {
0477a072
AJ
1014 mb.append(ftp.listing->content(), ftp.listing->contentSize());
1015 do_quote = 0;
a91d3505
AJ
1016 } else if (ftp.server_msg) {
1017 wordlistCat(ftp.server_msg, &mb);
0477a072 1018 }
62e76326 1019 break;
1020
03d7b07f 1021 case 'h':
4391cd15 1022 mb.appendf("%s", getMyHostname());
62e76326 1023 break;
1024
fe40a877 1025 case 'H':
c70281f8 1026 if (request) {
b3802bdc 1027 if (request->hier.host[0] != '\0') // if non-empty string.
c70281f8 1028 p = request->hier.host;
beed27a2 1029 else
5c51bffb 1030 p = request->url.host();
1e98e28b 1031 } else if (!building_deny_info_url)
beed27a2 1032 p = "[unknown host]";
62e76326 1033 break;
1034
f787fb1e 1035 case 'i':
4391cd15 1036 mb.appendf("%s", src_addr.toStr(ntoabuf,MAX_IPSTRLEN));
62e76326 1037 break;
1038
f787fb1e 1039 case 'I':
4c7c97f3 1040 if (request && request->hier.tcpServer)
4dd643d5 1041 p = request->hier.tcpServer->remote.toStr(ntoabuf,MAX_IPSTRLEN);
1e98e28b 1042 else if (!building_deny_info_url)
62e76326 1043 p = "[unknown]";
62e76326 1044 break;
1045
5b52cb6c 1046 case 'l':
1e98e28b 1047 if (building_deny_info_url) break;
0ae3294a 1048 mb.append(error_stylesheet.content(), error_stylesheet.contentSize());
5b52cb6c
AJ
1049 do_quote = 0;
1050 break;
1051
fe40a877 1052 case 'L':
1e98e28b 1053 if (building_deny_info_url) break;
62e76326 1054 if (Config.errHtmlText) {
4391cd15 1055 mb.appendf("%s", Config.errHtmlText);
62e76326 1056 do_quote = 0;
1e98e28b 1057 } else
62e76326 1058 p = "[not available]";
62e76326 1059 break;
1060
066ed5c1 1061 case 'm':
1e98e28b 1062 if (building_deny_info_url) break;
2f1431ea 1063#if USE_AUTH
52ba2948
CT
1064 if (auth_user_request.getRaw())
1065 p = auth_user_request->denyMessage("[not available]");
1066 else
1067 p = "[not available]";
2f1431ea
AJ
1068#else
1069 p = "-";
1070#endif
62e76326 1071 break;
1072
fe40a877 1073 case 'M':
7f06a3d8 1074 if (request) {
e2849af8
A
1075 const SBuf &m = request->method.image();
1076 mb.append(m.rawContent(), m.length());
7f06a3d8
AJ
1077 } else if (!building_deny_info_url)
1078 p = "[unknown method]";
62e76326 1079 break;
1080
05dbf66c 1081 case 'O':
f3bd9c5e
AJ
1082 if (!building_deny_info_url)
1083 do_quote = 0;
4a972fa2 1084 case 'o':
8c93a598 1085 p = request ? request->extacl_message.termedBuf() : external_acl_message;
1e98e28b 1086 if (!p && !building_deny_info_url)
d73b593b 1087 p = "[not available]";
4a972fa2 1088 break;
1089
fe40a877 1090 case 'p':
c70281f8 1091 if (request) {
5c51bffb 1092 mb.appendf("%u", request->url.port());
1e98e28b 1093 } else if (!building_deny_info_url) {
62e76326 1094 p = "[unknown port]";
1095 }
62e76326 1096 break;
1097
fe40a877 1098 case 'P':
e7aae06b 1099 if (request) {
d31d59d8
AJ
1100 const SBuf &m = request->url.getScheme().image();
1101 mb.append(m.rawContent(), m.length());
1e98e28b 1102 } else if (!building_deny_info_url) {
e7aae06b
A
1103 p = "[unknown protocol]";
1104 }
62e76326 1105 break;
1106
b5af8569 1107 case 'R':
1e98e28b 1108 if (building_deny_info_url) {
1b091aec 1109 if (request != NULL) {
b2b6c9f4
AJ
1110 const SBuf &tmp = request->url.path();
1111 mb.append(tmp.rawContent(), tmp.length());
1b091aec
CT
1112 no_urlescape = 1;
1113 } else
1114 p = "[no request]";
15b02e9a
AJ
1115 break;
1116 }
4c7c97f3 1117 if (request) {
51b5dcf5 1118 mb.appendf(SQUIDSBUFPH " " SQUIDSBUFPH " %s/%d.%d\n",
4391cd15 1119 SQUIDSBUFPRINT(request->method.image()),
51b5dcf5 1120 SQUIDSBUFPRINT(request->url.path()),
4391cd15
AJ
1121 AnyP::ProtocolType_str[request->http_ver.protocol],
1122 request->http_ver.major, request->http_ver.minor);
10201568 1123 request->header.packInto(&mb, true); //hide authorization data
c70281f8
AJ
1124 } else if (request_hdrs) {
1125 p = request_hdrs;
62e76326 1126 } else {
1127 p = "[no request]";
1128 }
62e76326 1129 break;
1130
1d803566 1131 case 's':
15b02e9a 1132 /* for backward compat we make %s show the full URL. Drop this in some future release. */
1e98e28b 1133 if (building_deny_info_url) {
851feda6
AJ
1134 if (request) {
1135 const SBuf &tmp = request->effectiveRequestUri();
1136 mb.append(tmp.rawContent(), tmp.length());
1137 } else
1138 p = url;
fa84c01d 1139 debugs(0, DBG_CRITICAL, "WARNING: deny_info now accepts coded tags. Use %u to get the full URL instead of %s");
05320519 1140 } else
15b02e9a 1141 p = visible_appname_string;
62e76326 1142 break;
1143
1d803566 1144 case 'S':
1e98e28b 1145 if (building_deny_info_url) {
4ad8d23d 1146 p = visible_appname_string;
e7aae06b
A
1147 break;
1148 }
62e76326 1149 /* signature may contain %-escapes, recursion */
c70281f8
AJ
1150 if (page_id != ERR_SQUID_SIGNATURE) {
1151 const int saved_id = page_id;
1152 page_id = ERR_SQUID_SIGNATURE;
7e6eabbc
CT
1153 const auto signature = buildBody();
1154 mb.append(signature.rawContent(), signature.length());
c70281f8 1155 page_id = saved_id;
62e76326 1156 do_quote = 0;
1157 } else {
1158 /* wow, somebody put %S into ERR_SIGNATURE, stop recursion */
1159 p = "[%S]";
1160 }
62e76326 1161 break;
1162
fe40a877 1163 case 't':
4391cd15 1164 mb.appendf("%s", Time::FormatHttpd(squid_curtime));
62e76326 1165 break;
1166
f8291f8f 1167 case 'T':
4391cd15 1168 mb.appendf("%s", mkrfc1123(squid_curtime));
62e76326 1169 break;
1170
fe40a877 1171 case 'U':
851feda6 1172 /* Using the fake-https version of absolute-URI so error pages see https:// */
b3802bdc 1173 /* even when the url-path cannot be shown as more than '*' */
e7aae06b 1174 if (request)
4c7c97f3 1175 p = urlCanonicalFakeHttps(request.getRaw());
e7aae06b
A
1176 else if (url)
1177 p = url;
1e98e28b 1178 else if (!building_deny_info_url)
e7aae06b 1179 p = "[no URL]";
62e76326 1180 break;
1181
76cdc28d 1182 case 'u':
851feda6
AJ
1183 if (request) {
1184 const SBuf &tmp = request->effectiveRequestUri();
1185 mb.append(tmp.rawContent(), tmp.length());
1186 } else if (url)
1e98e28b
AJ
1187 p = url;
1188 else if (!building_deny_info_url)
1189 p = "[no URL]";
62e76326 1190 break;
1191
fe40a877 1192 case 'w':
62e76326 1193 if (Config.adminEmail)
4391cd15 1194 mb.appendf("%s", Config.adminEmail);
1e98e28b 1195 else if (!building_deny_info_url)
62e76326 1196 p = "[unknown]";
62e76326 1197 break;
1198
b5fb34f1 1199 case 'W':
1e98e28b 1200 if (building_deny_info_url) break;
62e76326 1201 if (Config.adminEmail && Config.onoff.emailErrData)
c70281f8 1202 Dump(&mb);
4ad8d23d 1203 no_urlescape = 1;
62e76326 1204 break;
1205
e4a8468d 1206 case 'x':
cb4f4424 1207#if USE_OPENSSL
e4a8468d 1208 if (detail)
4391cd15 1209 mb.appendf("%s", detail->errorName());
0ab68838
CT
1210 else
1211#endif
1212 if (!building_deny_info_url)
1213 p = "[Unknown Error Code]";
e4a8468d
CT
1214 break;
1215
fe40a877 1216 case 'z':
1e98e28b 1217 if (building_deny_info_url) break;
3ff65596
AR
1218 if (dnsError.size() > 0)
1219 p = dnsError.termedBuf();
0477a072
AJ
1220 else if (ftp.cwd_msg)
1221 p = ftp.cwd_msg;
62e76326 1222 else
1223 p = "[unknown]";
62e76326 1224 break;
1225
43ae1d95 1226 case 'Z':
1e98e28b 1227 if (building_deny_info_url) break;
c70281f8
AJ
1228 if (err_msg)
1229 p = err_msg;
43ae1d95 1230 else
1231 p = "[unknown]";
43ae1d95 1232 break;
1233
e347f8e5 1234 case '%':
62e76326 1235 p = "%";
62e76326 1236 break;
1237
9b312a19 1238 default:
7e6eabbc
CT
1239 if (building_deny_info_url)
1240 bypassBuildErrorXXX("Unsupported deny_info %code", build.input);
1241 else if (letter != ';')
1242 bypassBuildErrorXXX("Unsupported error page %code", build.input);
1243 // else too many "font-size: 100%;" template errors to report
1244
1245 mb.append(build.input, 2);
cbba2ba2 1246 do_quote = 0;
62e76326 1247 break;
9b312a19 1248 }
62e76326 1249
137ee196 1250 if (!p)
f53969cc 1251 p = mb.buf; /* do not use mb after this assignment! */
62e76326 1252
137ee196 1253 assert(p);
62e76326 1254
7e6eabbc 1255 debugs(4, 3, "%" << letter << " --> '" << p << "'" );
62e76326 1256
10270faa 1257 if (do_quote)
62e76326 1258 p = html_quote(p);
1259
1e98e28b 1260 if (building_deny_info_url && !no_urlescape)
15b02e9a
AJ
1261 p = rfc1738_escape_part(p);
1262
7e6eabbc
CT
1263 // TODO: Optimize by replacing mb with direct build.output usage.
1264 build.output.append(p, strlen(p));
1265 build.input += 2;
8213067d 1266}
e381a13d 1267
15b02e9a 1268void
7e6eabbc 1269ErrorState::validate()
15b02e9a 1270{
7e6eabbc
CT
1271 if (const auto urlTemplate = ErrorPage::IsDenyInfoUri(page_id)) {
1272 (void)compile(urlTemplate, true, true);
1273 } else {
1274 assert(page_id > ERR_NONE);
1275 assert(page_id < error_page_count);
1276 (void)compileBody(error_text[page_id], true);
15b02e9a 1277 }
15b02e9a
AJ
1278}
1279
cb69b4c7 1280HttpReply *
c70281f8 1281ErrorState::BuildHttpReply()
cb69b4c7 1282{
f5e17947
CT
1283 if (response_)
1284 return response_.getRaw();
1285
06a5ae20 1286 HttpReply *rep = new HttpReply;
c70281f8 1287 const char *name = errorPageName(page_id);
cb69b4c7 1288 /* no LMT for error pages; error pages expire immediately */
62e76326 1289
7e6eabbc 1290 if (const auto urlTemplate = ErrorPage::IsDenyInfoUri(page_id)) {
62e76326 1291 /* Redirection */
f11c8e2f 1292 Http::StatusCode status = Http::scFound;
aed9a15b
AJ
1293 // Use configured 3xx reply status if set.
1294 if (name[0] == '3')
1295 status = httpStatus;
1296 else {
1297 // Use 307 for HTTP/1.1 non-GET/HEAD requests.
4c7c97f3 1298 if (request && request->method != Http::METHOD_GET && request->method != Http::METHOD_HEAD && request->http_ver >= Http::ProtocolVersion(1,1))
955394ce 1299 status = Http::scTemporaryRedirect;
aed9a15b
AJ
1300 }
1301
ec80c740 1302 rep->setHeaders(status, NULL, "text/html;charset=utf-8", 0, 0, -1);
c44950c4 1303
c70281f8 1304 if (request) {
7e6eabbc
CT
1305 auto location = compile(urlTemplate, true, true);
1306 rep->header.putStr(Http::HdrType::LOCATION, location.c_str());
c44950c4 1307 }
1308
789217a2 1309 httpHeaderPutStrf(&rep->header, Http::HdrType::X_SQUID_ERROR, "%d %s", httpStatus, "Access Denied");
76cdc28d 1310 } else {
7e6eabbc
CT
1311 const auto body = buildBody();
1312 rep->setHeaders(httpStatus, NULL, "text/html;charset=utf-8", body.length(), 0, -1);
62e76326 1313 /*
1314 * include some information for downstream caches. Implicit
1315 * replaceable content. This isn't quite sufficient. xerrno is not
1316 * necessarily meaningful to another system, so we really should
1317 * expand it. Additionally, we should identify ourselves. Someone
1318 * might want to know. Someone _will_ want to know OTOH, the first
1319 * X-CACHE-MISS entry should tell us who.
1320 */
789217a2 1321 httpHeaderPutStrf(&rep->header, Http::HdrType::X_SQUID_ERROR, "%s %d", name, xerrno);
ccb24616 1322
5bf33a09
AJ
1323#if USE_ERR_LOCALES
1324 /*
1325 * If error page auto-negotiate is enabled in any way, send the Vary.
1326 * RFC 2616 section 13.6 and 14.44 says MAY and SHOULD do this.
1327 * We have even better reasons though:
1328 * see http://wiki.squid-cache.org/KnowledgeBase/VaryNotCaching
1329 */
9e008dda 1330 if (!Config.errorDirectory) {
5bf33a09 1331 /* We 'negotiated' this ONLY from the Accept-Language. */
789217a2
FC
1332 rep->header.delById(Http::HdrType::VARY);
1333 rep->header.putStr(Http::HdrType::VARY, "Accept-Language");
5bf33a09
AJ
1334 }
1335
1336 /* add the Content-Language header according to RFC section 14.12 */
9e008dda 1337 if (err_language) {
789217a2 1338 rep->header.putStr(Http::HdrType::CONTENT_LANGUAGE, err_language);
9e008dda 1339 } else
5bf33a09
AJ
1340#endif /* USE_ERROR_LOCALES */
1341 {
ccb24616 1342 /* default templates are in English */
5bf33a09 1343 /* language is known unless error_directory override used */
9e008dda 1344 if (!Config.errorDirectory)
789217a2 1345 rep->header.putStr(Http::HdrType::CONTENT_LANGUAGE, "en");
ccb24616
AJ
1346 }
1347
7e6eabbc 1348 rep->body.set(body);
76cdc28d 1349 }
62e76326 1350
7a957a93
AR
1351 // Make sure error codes get back to the client side for logging and
1352 // error tracking.
129fe2a1
CT
1353 if (request) {
1354 int edc = ERR_DETAIL_NONE; // error detail code
cb4f4424 1355#if USE_OPENSSL
129fe2a1
CT
1356 if (detail)
1357 edc = detail->errorNo();
1358 else
1359#endif
1360 if (detailCode)
1361 edc = detailCode;
1362 else
1363 edc = xerrno;
1364 request->detailError(type, edc);
1365 }
1366
cb69b4c7 1367 return rep;
1368}
1369
7e6eabbc
CT
1370SBuf
1371ErrorState::buildBody()
cb69b4c7 1372{
c70281f8 1373 assert(page_id > ERR_NONE && page_id < error_page_count);
43000484
AJ
1374
1375#if USE_ERR_LOCALES
43000484 1376 /** error_directory option in squid.conf overrides translations.
30b78ef1 1377 * Custom errors are always found either in error_directory or the templates directory.
43000484
AJ
1378 * Otherwise locate the Accept-Language header
1379 */
02259ff8
CT
1380 if (!Config.errorDirectory && page_id < ERR_MAX) {
1381 if (err_language && err_language != Config.errorDefaultLanguage)
1382 safe_free(err_language);
1383
7e6eabbc
CT
1384 ErrorPageFile localeTmpl(err_type_str[page_id], static_cast<err_type>(page_id));
1385 if (localeTmpl.loadFor(request.getRaw())) {
1386 inputLocation = localeTmpl.filename;
1387 assert(localeTmpl.language());
1388 err_language = xstrdup(localeTmpl.language());
1389 return compileBody(localeTmpl.text(), true);
43000484
AJ
1390 }
1391 }
1392#endif /* USE_ERR_LOCALES */
1393
1394 /** \par
1395 * If client-specific error templates are not enabled or available.
1396 * fall back to the old style squid.conf settings.
1397 */
5bf33a09 1398#if USE_ERR_LOCALES
7e6eabbc
CT
1399 if (!Config.errorDirectory)
1400 err_language = Config.errorDefaultLanguage;
5bf33a09 1401#endif
7e6eabbc
CT
1402 debugs(4, 2, "No existing error page language negotiated for " << errorPageName(page_id) << ". Using default error file.");
1403 return compileBody(error_text[page_id], true);
1404}
43000484 1405
7e6eabbc
CT
1406SBuf
1407ErrorState::compileBody(const char *input, bool allowRecursion)
1408{
1409 return compile(input, false, allowRecursion);
4d16918e
CT
1410}
1411
7e6eabbc
CT
1412SBuf
1413ErrorState::compile(const char *input, bool building_deny_info_url, bool allowRecursion)
4d16918e 1414{
7e6eabbc
CT
1415 assert(input);
1416
1417 Build build;
1418 build.building_deny_info_url = building_deny_info_url;
1419 build.allowRecursion = allowRecursion;
1420 build.input = input;
1421
1422 auto blockStart = build.input;
1423 while (const auto letter = *build.input) {
1424 if (letter == '%') {
1425 build.output.append(blockStart, build.input - blockStart);
1426 compileLegacyCode(build);
1427 blockStart = build.input;
1428 }
1429 else if (letter == '@' && LogformatMagic.cmp(build.input, LogformatMagic.length()) == 0) {
1430 build.output.append(blockStart, build.input - blockStart);
1431 compileLogformatCode(build);
1432 blockStart = build.input;
1433 } else {
1434 ++build.input;
1435 }
1436 }
1437 build.output.append(blockStart, build.input - blockStart);
1438 return build.output;
1439}
1440
1441/// react to a compile() error
1442/// \param msg description of what went wrong
1443/// \param near approximate start of the problematic input
1444/// \param forceBypass whether detection of this error was introduced late,
1445/// after old configurations containing this error could have been
1446/// successfully validated and deployed (i.e. the admin may not be
1447/// able to fix this newly detected but old problem quickly)
1448void
1449ErrorState::noteBuildError_(const char *msg, const char *near, const bool forceBypass)
1450{
1451 using ErrorPage::BuildErrorPrinter;
1452 const auto runtime = !starting_up;
1453 if (runtime || forceBypass) {
1454 // swallow this problem because the admin may not be (and/or the page
1455 // building code is not) ready to handle throwing consequences
1456
1457 static unsigned int seenErrors = 0;
1458 ++seenErrors;
1459
1460 const auto debugLevel =
1461 (seenErrors > 100) ? DBG_DATA:
1462 (starting_up || reconfiguring) ? DBG_CRITICAL:
1463 3; // most other errors have been reported as configuration errors
1464
1465 // Error fatality depends on the error context: Reconfiguration errors
1466 // are, like startup ones, DBG_CRITICAL but will never become FATAL.
1467 if (starting_up && seenErrors <= 10)
1468 debugs(4, debugLevel, "WARNING: The following configuration error will be fatal in future Squid versions");
1469
1470 debugs(4, debugLevel, "ERROR: " << BuildErrorPrinter(inputLocation, page_id, msg, near));
1471 } else {
1472 throw TexcHere(ToSBuf(BuildErrorPrinter(inputLocation, page_id, msg, near)));
1473 }
1474}
1475
1476/* ErrorPage::BuildErrorPrinter */
1477
1478std::ostream &
1479ErrorPage::BuildErrorPrinter::printLocation(std::ostream &os) const {
1480 if (!inputLocation.isEmpty())
1481 return os << inputLocation;
1482
1483 if (page_id < ERR_NONE || page_id >= error_page_count)
1484 return os << "[error page " << page_id << "]"; // should not happen
1485
1486 if (page_id < ERR_MAX)
1487 return os << err_type_str[page_id];
1488
1489 return os << "deny_info " << ErrorDynamicPages.at(page_id - ERR_MAX)->page_name;
1490}
1491
1492std::ostream &
1493ErrorPage::BuildErrorPrinter::print(std::ostream &os) const {
1494 printLocation(os) << ": " << msg << " near ";
1495
1496 // TODO: Add support for prefix printing to Raw
1497 const size_t maxContextLength = 15; // plus "..."
1498 if (strlen(near) > maxContextLength) {
1499 os.write(near, maxContextLength);
1500 os << "...";
1501 } else {
1502 os << near;
cb69b4c7 1503 }
62e76326 1504
7e6eabbc
CT
1505 // XXX: We should not be converting (inner) exception to text if we are
1506 // going to throw again. See "add arbitrary (re)thrower-supplied details"
1507 // TODO in TextException.h for a long-term in-catcher solution.
1508 if (std::current_exception())
1509 os << "\n additional info: " << CurrentException;
62e76326 1510
7e6eabbc
CT
1511 return os;
1512}
857e4512 1513
7e6eabbc
CT
1514/// add error page template to the global index
1515static void
1516ErrorPage::ImportStaticErrorText(const int page_id, const char *text, const SBuf &inputLocation)
1517{
1518 assert(!error_text[page_id]);
1519 error_text[page_id] = xstrdup(text);
1520 ValidateStaticError(page_id, inputLocation);
1521}
62e76326 1522
7e6eabbc
CT
1523/// validate static error page
1524static void
1525ErrorPage::ValidateStaticError(const int page_id, const SBuf &inputLocation)
1526{
1527 // Supplying nil ALE pointer limits validation to logformat %code
1528 // recognition by Format::Token::parse(). This is probably desirable
1529 // because actual %code assembly is slow and should not affect validation
1530 // when our ALE cannot have any real data (this code is not associated
1531 // with any real transaction).
1532 ErrorState anErr(err_type(page_id), Http::scNone, nullptr, nullptr);
1533 anErr.inputLocation = inputLocation;
1534 anErr.validate();
cb69b4c7 1535}
f53969cc 1536