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