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