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