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