]> git.ipfire.org Git - thirdparty/squid.git/blob - src/errorpage.cc
Cleanup: replace urlCanonical() with HttpRequest::effectiveReuqestUri()
[thirdparty/squid.git] / src / errorpage.cc
1 /*
2 * Copyright (C) 1996-2015 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 "cache_cf.h"
13 #include "clients/forward.h"
14 #include "comm/Connection.h"
15 #include "comm/Write.h"
16 #include "disk.h"
17 #include "err_detail_type.h"
18 #include "errorpage.h"
19 #include "fde.h"
20 #include "html_quote.h"
21 #include "HttpHeaderTools.h"
22 #include "HttpReply.h"
23 #include "HttpRequest.h"
24 #include "MemBuf.h"
25 #include "MemObject.h"
26 #include "rfc1738.h"
27 #include "SquidConfig.h"
28 #include "Store.h"
29 #include "tools.h"
30 #include "URL.h"
31 #include "wordlist.h"
32 #if USE_AUTH
33 #include "auth/UserRequest.h"
34 #endif
35 #include "SquidTime.h"
36 #if USE_OPENSSL
37 #include "ssl/ErrorDetailManager.h"
38 #endif
39
40 /**
41 \defgroup ErrorPageInternal Error Page Internals
42 \ingroup ErrorPageAPI
43 *
44 \section Abstract Abstract:
45 * These routines are used to generate error messages to be
46 * sent to clients. The error type is used to select between
47 * the various message formats. (formats are stored in the
48 * Config.errorDirectory)
49 */
50
51 #if !defined(DEFAULT_SQUID_ERROR_DIR)
52 /** Where to look for errors if config path fails.
53 \note Please use ./configure --datadir=/path instead of patching
54 */
55 #define DEFAULT_SQUID_ERROR_DIR DEFAULT_SQUID_DATA_DIR"/errors"
56 #endif
57
58 /// \ingroup ErrorPageInternal
59 CBDATA_CLASS_INIT(ErrorState);
60
61 /* local types */
62
63 /// \ingroup ErrorPageInternal
64 typedef struct {
65 int id;
66 char *page_name;
67 Http::StatusCode page_redirect;
68 } ErrorDynamicPageInfo;
69
70 /* local constant and vars */
71
72 /**
73 \ingroup ErrorPageInternal
74 *
75 \note hard coded error messages are not appended with %S
76 * automagically to give you more control on the format
77 */
78 static const struct {
79 int type; /* and page_id */
80 const char *text;
81 }
82
83 error_hard_text[] = {
84
85 {
86 ERR_SQUID_SIGNATURE,
87 "\n<br>\n"
88 "<hr>\n"
89 "<div id=\"footer\">\n"
90 "Generated %T by %h (%s)\n"
91 "</div>\n"
92 "</body></html>\n"
93 },
94 {
95 TCP_RESET,
96 "reset"
97 }
98 };
99
100 /// \ingroup ErrorPageInternal
101 static std::vector<ErrorDynamicPageInfo *> ErrorDynamicPages;
102
103 /* local prototypes */
104
105 /// \ingroup ErrorPageInternal
106 static const int error_hard_text_count = sizeof(error_hard_text) / sizeof(*error_hard_text);
107
108 /// \ingroup ErrorPageInternal
109 static char **error_text = NULL;
110
111 /// \ingroup ErrorPageInternal
112 static int error_page_count = 0;
113
114 /// \ingroup ErrorPageInternal
115 static MemBuf error_stylesheet;
116
117 static const char *errorFindHardText(err_type type);
118 static ErrorDynamicPageInfo *errorDynamicPageInfoCreate(int id, const char *page_name);
119 static void errorDynamicPageInfoDestroy(ErrorDynamicPageInfo * info);
120 static IOCB errorSendComplete;
121
122 /// \ingroup ErrorPageInternal
123 /// manages an error page template
124 class ErrorPageFile: public TemplateFile
125 {
126 public:
127 ErrorPageFile(const char *name, const err_type code) : TemplateFile(name,code) {textBuf.init();}
128
129 /// The template text data read from disk
130 const char *text() { return textBuf.content(); }
131
132 private:
133 /// stores the data read from disk to a local buffer
134 virtual bool parse(const char *buf, int len, bool) {
135 if (len)
136 textBuf.append(buf, len);
137 return true;
138 }
139
140 MemBuf textBuf; ///< A buffer to store the error page
141 };
142
143 /// \ingroup ErrorPageInternal
144 err_type &operator++ (err_type &anErr)
145 {
146 int tmp = (int)anErr;
147 anErr = (err_type)(++tmp);
148 return anErr;
149 }
150
151 /// \ingroup ErrorPageInternal
152 int operator - (err_type const &anErr, err_type const &anErr2)
153 {
154 return (int)anErr - (int)anErr2;
155 }
156
157 void
158 errorInitialize(void)
159 {
160 err_type i;
161 const char *text;
162 error_page_count = ERR_MAX + ErrorDynamicPages.size();
163 error_text = static_cast<char **>(xcalloc(error_page_count, sizeof(char *)));
164
165 for (i = ERR_NONE, ++i; i < error_page_count; ++i) {
166 safe_free(error_text[i]);
167
168 if ((text = errorFindHardText(i))) {
169 /**\par
170 * Index any hard-coded error text into defaults.
171 */
172 error_text[i] = xstrdup(text);
173
174 } else if (i < ERR_MAX) {
175 /**\par
176 * Index precompiled fixed template files from one of two sources:
177 * (a) default language translation directory (error_default_language)
178 * (b) admin specified custom directory (error_directory)
179 */
180 ErrorPageFile errTmpl(err_type_str[i], i);
181 error_text[i] = errTmpl.loadDefault() ? xstrdup(errTmpl.text()) : NULL;
182 } else {
183 /** \par
184 * Index any unknown file names used by deny_info.
185 */
186 ErrorDynamicPageInfo *info = ErrorDynamicPages.at(i - ERR_MAX);
187 assert(info && info->id == i && info->page_name);
188
189 const char *pg = info->page_name;
190 if (info->page_redirect != Http::scNone)
191 pg = info->page_name +4;
192
193 if (strchr(pg, ':') == NULL) {
194 /** But only if they are not redirection URL. */
195 ErrorPageFile errTmpl(pg, ERR_MAX);
196 error_text[i] = errTmpl.loadDefault() ? xstrdup(errTmpl.text()) : NULL;
197 }
198 }
199 }
200
201 error_stylesheet.reset();
202
203 // look for and load stylesheet into global MemBuf for it.
204 if (Config.errorStylesheet) {
205 ErrorPageFile tmpl("StylesSheet", ERR_MAX);
206 tmpl.loadFromFile(Config.errorStylesheet);
207 error_stylesheet.appendf("%s",tmpl.text());
208 }
209
210 #if USE_OPENSSL
211 Ssl::errorDetailInitialize();
212 #endif
213 }
214
215 void
216 errorClean(void)
217 {
218 if (error_text) {
219 int i;
220
221 for (i = ERR_NONE + 1; i < error_page_count; ++i)
222 safe_free(error_text[i]);
223
224 safe_free(error_text);
225 }
226
227 while (!ErrorDynamicPages.empty()) {
228 errorDynamicPageInfoDestroy(ErrorDynamicPages.back());
229 ErrorDynamicPages.pop_back();
230 }
231
232 error_page_count = 0;
233
234 #if USE_OPENSSL
235 Ssl::errorDetailClean();
236 #endif
237 }
238
239 /// \ingroup ErrorPageInternal
240 static const char *
241 errorFindHardText(err_type type)
242 {
243 int i;
244
245 for (i = 0; i < error_hard_text_count; ++i)
246 if (error_hard_text[i].type == type)
247 return error_hard_text[i].text;
248
249 return NULL;
250 }
251
252 TemplateFile::TemplateFile(const char *name, const err_type code): silent(false), wasLoaded(false), templateName(name), templateCode(code)
253 {
254 assert(name);
255 }
256
257 bool
258 TemplateFile::loadDefault()
259 {
260 if (loaded()) // already loaded?
261 return true;
262
263 /** test error_directory configured location */
264 if (Config.errorDirectory) {
265 char path[MAXPATHLEN];
266 snprintf(path, sizeof(path), "%s/%s", Config.errorDirectory, templateName.termedBuf());
267 loadFromFile(path);
268 }
269
270 #if USE_ERR_LOCALES
271 /** test error_default_language location */
272 if (!loaded() && Config.errorDefaultLanguage) {
273 if (!tryLoadTemplate(Config.errorDefaultLanguage)) {
274 debugs(1, (templateCode < TCP_RESET ? DBG_CRITICAL : 3), "Unable to load default error language files. Reset to backups.");
275 }
276 }
277 #endif
278
279 /* test default location if failed (templates == English translation base templates) */
280 if (!loaded()) {
281 tryLoadTemplate("templates");
282 }
283
284 /* giving up if failed */
285 if (!loaded()) {
286 debugs(1, (templateCode < TCP_RESET ? DBG_CRITICAL : 3), "WARNING: failed to find or read error text file " << templateName);
287 parse("Internal Error: Missing Template ", 33, '\0');
288 parse(templateName.termedBuf(), templateName.size(), '\0');
289 }
290
291 return true;
292 }
293
294 bool
295 TemplateFile::tryLoadTemplate(const char *lang)
296 {
297 assert(lang);
298
299 char path[MAXPATHLEN];
300 /* TODO: prep the directory path string to prevent snprintf ... */
301 snprintf(path, sizeof(path), "%s/%s/%s",
302 DEFAULT_SQUID_ERROR_DIR, lang, templateName.termedBuf());
303 path[MAXPATHLEN-1] = '\0';
304
305 if (loadFromFile(path))
306 return true;
307
308 #if HAVE_GLOB
309 if ( strlen(lang) == 2) {
310 /* TODO glob the error directory for sub-dirs matching: <tag> '-*' */
311 /* use first result. */
312 debugs(4,2, HERE << "wildcard fallback errors not coded yet.");
313 }
314 #endif
315
316 return false;
317 }
318
319 bool
320 TemplateFile::loadFromFile(const char *path)
321 {
322 int fd;
323 char buf[4096];
324 ssize_t len;
325
326 if (loaded()) // already loaded?
327 return true;
328
329 fd = file_open(path, O_RDONLY | O_TEXT);
330
331 if (fd < 0) {
332 /* with dynamic locale negotiation we may see some failures before a success. */
333 if (!silent && templateCode < TCP_RESET)
334 debugs(4, DBG_CRITICAL, HERE << "'" << path << "': " << xstrerror());
335 wasLoaded = false;
336 return wasLoaded;
337 }
338
339 while ((len = FD_READ_METHOD(fd, buf, sizeof(buf))) > 0) {
340 if (!parse(buf, len, false)) {
341 debugs(4, DBG_CRITICAL, HERE << " parse error while reading template file: " << path);
342 wasLoaded = false;
343 return wasLoaded;
344 }
345 }
346 parse(buf, 0, true);
347
348 if (len < 0) {
349 debugs(4, DBG_CRITICAL, HERE << "failed to fully read: '" << path << "': " << xstrerror());
350 }
351
352 file_close(fd);
353
354 wasLoaded = true;
355 return wasLoaded;
356 }
357
358 bool strHdrAcptLangGetItem(const String &hdr, char *lang, int langLen, size_t &pos)
359 {
360 while (pos < hdr.size()) {
361 char *dt = lang;
362
363 /* skip any initial whitespace. */
364 while (pos < hdr.size() && xisspace(hdr[pos]))
365 ++pos;
366
367 /*
368 * Header value format:
369 * - sequence of whitespace delimited tags
370 * - each tag may suffix with ';'.* which we can ignore.
371 * - IFF a tag contains only two characters we can wildcard ANY translations matching: <it> '-'? .*
372 * with preference given to an exact match.
373 */
374 bool invalid_byte = false;
375 while (pos < hdr.size() && hdr[pos] != ';' && hdr[pos] != ',' && !xisspace(hdr[pos]) && dt < (lang + (langLen -1)) ) {
376 if (!invalid_byte) {
377 #if USE_HTTP_VIOLATIONS
378 // if accepting violations we may as well accept some broken browsers
379 // which may send us the right code, wrong ISO formatting.
380 if (hdr[pos] == '_')
381 *dt = '-';
382 else
383 #endif
384 *dt = xtolower(hdr[pos]);
385 // valid codes only contain A-Z, hyphen (-) and *
386 if (*dt != '-' && *dt != '*' && (*dt < 'a' || *dt > 'z') )
387 invalid_byte = true;
388 else
389 ++dt; // move to next destination byte.
390 }
391 ++pos;
392 }
393 *dt = '\0'; // nul-terminated the filename content string before system use.
394 ++dt;
395
396 // if we terminated the tag on garbage or ';' we need to skip to the next ',' or end of header.
397 while (pos < hdr.size() && hdr[pos] != ',')
398 ++pos;
399
400 if (pos < hdr.size() && hdr[pos] == ',')
401 ++pos;
402
403 debugs(4, 9, HERE << "STATE: dt='" << dt << "', lang='" << lang << "', pos=" << pos << ", buf='" << ((pos < hdr.size()) ? hdr.substr(pos,hdr.size()) : "") << "'");
404
405 /* if we found anything we might use, try it. */
406 if (*lang != '\0' && !invalid_byte)
407 return true;
408 }
409 return false;
410 }
411
412 bool
413 TemplateFile::loadFor(const HttpRequest *request)
414 {
415 String hdr;
416
417 #if USE_ERR_LOCALES
418 if (loaded()) // already loaded?
419 return true;
420
421 if (!request || !request->header.getList(HDR_ACCEPT_LANGUAGE, &hdr) )
422 return false;
423
424 char lang[256];
425 size_t pos = 0; // current parsing position in header string
426
427 debugs(4, 6, HERE << "Testing Header: '" << hdr << "'");
428
429 while ( strHdrAcptLangGetItem(hdr, lang, 256, pos) ) {
430
431 /* wildcard uses the configured default language */
432 if (lang[0] == '*' && lang[1] == '\0') {
433 debugs(4, 6, HERE << "Found language '" << lang << "'. Using configured default.");
434 return false;
435 }
436
437 debugs(4, 6, HERE << "Found language '" << lang << "', testing for available template");
438
439 if (tryLoadTemplate(lang)) {
440 /* store the language we found for the Content-Language reply header */
441 errLanguage = lang;
442 break;
443 } else if (Config.errorLogMissingLanguages) {
444 debugs(4, DBG_IMPORTANT, "WARNING: Error Pages Missing Language: " << lang);
445 }
446 }
447 #endif
448
449 return loaded();
450 }
451
452 /// \ingroup ErrorPageInternal
453 static ErrorDynamicPageInfo *
454 errorDynamicPageInfoCreate(int id, const char *page_name)
455 {
456 ErrorDynamicPageInfo *info = new ErrorDynamicPageInfo;
457 info->id = id;
458 info->page_name = xstrdup(page_name);
459 info->page_redirect = static_cast<Http::StatusCode>(atoi(page_name));
460
461 /* WARNING on redirection status:
462 * 2xx are permitted, but not documented officially.
463 * - might be useful for serving static files (PAC etc) in special cases
464 * 3xx require a URL suitable for Location: header.
465 * - the current design does not allow for a Location: URI as well as a local file template
466 * although this possibility is explicitly permitted in the specs.
467 * 4xx-5xx require a local file template.
468 * - sending Location: on these codes with no body is invalid by the specs.
469 * - current result is Squid crashing or XSS problems as dynamic deny_info load random disk files.
470 * - a future redesign of the file loading may result in loading remote objects sent inline as local body.
471 */
472 if (info->page_redirect == Http::scNone)
473 ; // special case okay.
474 else if (info->page_redirect < 200 || info->page_redirect > 599) {
475 // out of range
476 debugs(0, DBG_CRITICAL, "FATAL: status " << info->page_redirect << " is not valid on '" << page_name << "'");
477 self_destruct();
478 } else if ( /* >= 200 && */ info->page_redirect < 300 && strchr(&(page_name[4]), ':')) {
479 // 2xx require a local template file
480 debugs(0, DBG_CRITICAL, "FATAL: status " << info->page_redirect << " requires a template on '" << page_name << "'");
481 self_destruct();
482 } else if (info->page_redirect >= 300 && info->page_redirect <= 399 && !strchr(&(page_name[4]), ':')) {
483 // 3xx require an absolute URL
484 debugs(0, DBG_CRITICAL, "FATAL: status " << info->page_redirect << " requires a URL on '" << page_name << "'");
485 self_destruct();
486 } else if (info->page_redirect >= 400 /* && <= 599 */ && strchr(&(page_name[4]), ':')) {
487 // 4xx/5xx require a local template file
488 debugs(0, DBG_CRITICAL, "FATAL: status " << info->page_redirect << " requires a template on '" << page_name << "'");
489 self_destruct();
490 }
491 // else okay.
492
493 return info;
494 }
495
496 /// \ingroup ErrorPageInternal
497 static void
498 errorDynamicPageInfoDestroy(ErrorDynamicPageInfo * info)
499 {
500 assert(info);
501 safe_free(info->page_name);
502 delete info;
503 }
504
505 /// \ingroup ErrorPageInternal
506 static int
507 errorPageId(const char *page_name)
508 {
509 for (int i = 0; i < ERR_MAX; ++i) {
510 if (strcmp(err_type_str[i], page_name) == 0)
511 return i;
512 }
513
514 for (size_t j = 0; j < ErrorDynamicPages.size(); ++j) {
515 if (strcmp(ErrorDynamicPages[j]->page_name, page_name) == 0)
516 return j + ERR_MAX;
517 }
518
519 return ERR_NONE;
520 }
521
522 err_type
523 errorReservePageId(const char *page_name)
524 {
525 ErrorDynamicPageInfo *info;
526 int id = errorPageId(page_name);
527
528 if (id == ERR_NONE) {
529 info = errorDynamicPageInfoCreate(ERR_MAX + ErrorDynamicPages.size(), page_name);
530 ErrorDynamicPages.push_back(info);
531 id = info->id;
532 }
533
534 return (err_type)id;
535 }
536
537 /// \ingroup ErrorPageInternal
538 const char *
539 errorPageName(int pageId)
540 {
541 if (pageId >= ERR_NONE && pageId < ERR_MAX) /* common case */
542 return err_type_str[pageId];
543
544 if (pageId >= ERR_MAX && pageId - ERR_MAX < (ssize_t)ErrorDynamicPages.size())
545 return ErrorDynamicPages[pageId - ERR_MAX]->page_name;
546
547 return "ERR_UNKNOWN"; /* should not happen */
548 }
549
550 ErrorState *
551 ErrorState::NewForwarding(err_type type, HttpRequest *request)
552 {
553 assert(request);
554 const Http::StatusCode status = request->flags.needValidation ?
555 Http::scGatewayTimeout : Http::scServiceUnavailable;
556 return new ErrorState(type, status, request);
557 }
558
559 ErrorState::ErrorState(err_type t, Http::StatusCode status, HttpRequest * req) :
560 type(t),
561 page_id(t),
562 err_language(NULL),
563 httpStatus(status),
564 #if USE_AUTH
565 auth_user_request (NULL),
566 #endif
567 request(NULL),
568 url(NULL),
569 xerrno(0),
570 port(0),
571 dnsError(),
572 ttl(0),
573 src_addr(),
574 redirect_url(NULL),
575 callback(NULL),
576 callback_data(NULL),
577 request_hdrs(NULL),
578 err_msg(NULL),
579 #if USE_OPENSSL
580 detail(NULL),
581 #endif
582 detailCode(ERR_DETAIL_NONE)
583 {
584 memset(&ftp, 0, sizeof(ftp));
585
586 if (page_id >= ERR_MAX && ErrorDynamicPages[page_id - ERR_MAX]->page_redirect != Http::scNone)
587 httpStatus = ErrorDynamicPages[page_id - ERR_MAX]->page_redirect;
588
589 if (req != NULL) {
590 request = req;
591 HTTPMSGLOCK(request);
592 src_addr = req->client_addr;
593 }
594 }
595
596 void
597 errorAppendEntry(StoreEntry * entry, ErrorState * err)
598 {
599 assert(entry->mem_obj != NULL);
600 assert (entry->isEmpty());
601 debugs(4, 4, "Creating an error page for entry " << entry <<
602 " with errorstate " << err <<
603 " page id " << err->page_id);
604
605 if (entry->store_status != STORE_PENDING) {
606 debugs(4, 2, "Skipping error page due to store_status: " << entry->store_status);
607 /*
608 * If the entry is not STORE_PENDING, then no clients
609 * care about it, and we don't need to generate an
610 * error message
611 */
612 assert(EBIT_TEST(entry->flags, ENTRY_ABORTED));
613 assert(entry->mem_obj->nclients == 0);
614 delete err;
615 return;
616 }
617
618 if (err->page_id == TCP_RESET) {
619 if (err->request) {
620 debugs(4, 2, "RSTing this reply");
621 err->request->flags.resetTcp = true;
622 }
623 }
624
625 entry->storeErrorResponse(err->BuildHttpReply());
626 delete err;
627 }
628
629 void
630 errorSend(const Comm::ConnectionPointer &conn, ErrorState * err)
631 {
632 HttpReply *rep;
633 debugs(4, 3, HERE << conn << ", err=" << err);
634 assert(Comm::IsConnOpen(conn));
635
636 rep = err->BuildHttpReply();
637
638 MemBuf *mb = rep->pack();
639 AsyncCall::Pointer call = commCbCall(78, 5, "errorSendComplete",
640 CommIoCbPtrFun(&errorSendComplete, err));
641 Comm::Write(conn, mb, call);
642 delete mb;
643
644 delete rep;
645 }
646
647 /**
648 \ingroup ErrorPageAPI
649 *
650 * Called by commHandleWrite() after data has been written
651 * to the client socket.
652 *
653 \note If there is a callback, the callback is responsible for
654 * closing the FD, otherwise we do it ourselves.
655 */
656 static void
657 errorSendComplete(const Comm::ConnectionPointer &conn, char *, size_t size, Comm::Flag errflag, int, void *data)
658 {
659 ErrorState *err = static_cast<ErrorState *>(data);
660 debugs(4, 3, HERE << conn << ", size=" << size);
661
662 if (errflag != Comm::ERR_CLOSING) {
663 if (err->callback) {
664 debugs(4, 3, "errorSendComplete: callback");
665 err->callback(conn->fd, err->callback_data, size);
666 } else {
667 debugs(4, 3, "errorSendComplete: comm_close");
668 conn->close();
669 }
670 }
671
672 delete err;
673 }
674
675 ErrorState::~ErrorState()
676 {
677 HTTPMSGUNLOCK(request);
678 safe_free(redirect_url);
679 safe_free(url);
680 safe_free(request_hdrs);
681 wordlistDestroy(&ftp.server_msg);
682 safe_free(ftp.request);
683 safe_free(ftp.reply);
684 #if USE_AUTH
685 auth_user_request = NULL;
686 #endif
687 safe_free(err_msg);
688 #if USE_ERR_LOCALES
689 if (err_language != Config.errorDefaultLanguage)
690 #endif
691 safe_free(err_language);
692 #if USE_OPENSSL
693 delete detail;
694 #endif
695 }
696
697 int
698 ErrorState::Dump(MemBuf * mb)
699 {
700 MemBuf str;
701 char ntoabuf[MAX_IPSTRLEN];
702
703 str.reset();
704 /* email subject line */
705 str.appendf("CacheErrorInfo - %s", errorPageName(type));
706 mb->appendf("?subject=%s", rfc1738_escape_part(str.buf));
707 str.reset();
708 /* email body */
709 str.appendf("CacheHost: %s\r\n", getMyHostname());
710 /* - Err Msgs */
711 str.appendf("ErrPage: %s\r\n", errorPageName(type));
712
713 if (xerrno) {
714 str.appendf("Err: (%d) %s\r\n", xerrno, strerror(xerrno));
715 } else {
716 str.append("Err: [none]\r\n", 13);
717 }
718 #if USE_AUTH
719 if (auth_user_request.getRaw() && auth_user_request->denyMessage())
720 str.appendf("Auth ErrMsg: %s\r\n", auth_user_request->denyMessage());
721 #endif
722 if (dnsError.size() > 0)
723 str.appendf("DNS ErrMsg: %s\r\n", dnsError.termedBuf());
724
725 /* - TimeStamp */
726 str.appendf("TimeStamp: %s\r\n\r\n", mkrfc1123(squid_curtime));
727
728 /* - IP stuff */
729 str.appendf("ClientIP: %s\r\n", src_addr.toStr(ntoabuf,MAX_IPSTRLEN));
730
731 if (request && request->hier.host[0] != '\0') {
732 str.appendf("ServerIP: %s\r\n", request->hier.host);
733 }
734
735 str.append("\r\n", 2);
736 /* - HTTP stuff */
737 str.append("HTTP Request:\r\n", 15);
738 if (request) {
739 str.appendf(SQUIDSBUFPH " " SQUIDSBUFPH " %s/%d.%d\n",
740 SQUIDSBUFPRINT(request->method.image()),
741 SQUIDSBUFPRINT(request->url.path()),
742 AnyP::ProtocolType_str[request->http_ver.protocol],
743 request->http_ver.major, request->http_ver.minor);
744 request->header.packInto(&str);
745 }
746
747 str.append("\r\n", 2);
748 /* - FTP stuff */
749
750 if (ftp.request) {
751 str.appendf("FTP Request: %s\r\n", ftp.request);
752 str.appendf("FTP Reply: %s\r\n", (ftp.reply? ftp.reply:"[none]"));
753 str.append("FTP Msg: ", 9);
754 wordlistCat(ftp.server_msg, &str);
755 str.append("\r\n", 2);
756 }
757
758 str.append("\r\n", 2);
759 mb->appendf("&body=%s", rfc1738_escape_part(str.buf));
760 str.clean();
761 return 0;
762 }
763
764 /// \ingroup ErrorPageInternal
765 #define CVT_BUF_SZ 512
766
767 const char *
768 ErrorState::Convert(char token, bool building_deny_info_url, bool allowRecursion)
769 {
770 static MemBuf mb;
771 const char *p = NULL; /* takes priority over mb if set */
772 int do_quote = 1;
773 int no_urlescape = 0; /* if true then item is NOT to be further URL-encoded */
774 char ntoabuf[MAX_IPSTRLEN];
775
776 mb.reset();
777
778 switch (token) {
779
780 case 'a':
781 #if USE_AUTH
782 if (request && request->auth_user_request != NULL)
783 p = request->auth_user_request->username();
784 if (!p)
785 #endif
786 p = "-";
787 break;
788
789 case 'b':
790 mb.appendf("%u", getMyPort());
791 break;
792
793 case 'B':
794 if (building_deny_info_url) break;
795 if (request) {
796 const SBuf &tmp = Ftp::UrlWith2f(request);
797 mb.append(tmp.rawContent(), tmp.length());
798 } else
799 p = "[no URL]";
800 break;
801
802 case 'c':
803 if (building_deny_info_url) break;
804 p = errorPageName(type);
805 break;
806
807 case 'D':
808 if (!allowRecursion)
809 p = "%D"; // if recursion is not allowed, do not convert
810 #if USE_OPENSSL
811 // currently only SSL error details implemented
812 else if (detail) {
813 detail->useRequest(request);
814 const String &errDetail = detail->toString();
815 if (errDetail.size() > 0) {
816 MemBuf *detail_mb = ConvertText(errDetail.termedBuf(), false);
817 mb.append(detail_mb->content(), detail_mb->contentSize());
818 delete detail_mb;
819 do_quote = 0;
820 }
821 }
822 #endif
823 if (!mb.contentSize())
824 mb.append("[No Error Detail]", 17);
825 break;
826
827 case 'e':
828 mb.appendf("%d", xerrno);
829 break;
830
831 case 'E':
832 if (xerrno)
833 mb.appendf("(%d) %s", xerrno, strerror(xerrno));
834 else
835 mb.append("[No Error]", 10);
836 break;
837
838 case 'f':
839 if (building_deny_info_url) break;
840 /* FTP REQUEST LINE */
841 if (ftp.request)
842 p = ftp.request;
843 else
844 p = "nothing";
845 break;
846
847 case 'F':
848 if (building_deny_info_url) break;
849 /* FTP REPLY LINE */
850 if (ftp.reply)
851 p = ftp.reply;
852 else
853 p = "nothing";
854 break;
855
856 case 'g':
857 if (building_deny_info_url) break;
858 /* FTP SERVER RESPONSE */
859 if (ftp.listing) {
860 mb.append(ftp.listing->content(), ftp.listing->contentSize());
861 do_quote = 0;
862 } else if (ftp.server_msg) {
863 wordlistCat(ftp.server_msg, &mb);
864 }
865 break;
866
867 case 'h':
868 mb.appendf("%s", getMyHostname());
869 break;
870
871 case 'H':
872 if (request) {
873 if (request->hier.host[0] != '\0') // if non-empty string.
874 p = request->hier.host;
875 else
876 p = request->url.host();
877 } else if (!building_deny_info_url)
878 p = "[unknown host]";
879 break;
880
881 case 'i':
882 mb.appendf("%s", src_addr.toStr(ntoabuf,MAX_IPSTRLEN));
883 break;
884
885 case 'I':
886 if (request && request->hier.tcpServer != NULL)
887 p = request->hier.tcpServer->remote.toStr(ntoabuf,MAX_IPSTRLEN);
888 else if (!building_deny_info_url)
889 p = "[unknown]";
890 break;
891
892 case 'l':
893 if (building_deny_info_url) break;
894 mb.append(error_stylesheet.content(), error_stylesheet.contentSize());
895 do_quote = 0;
896 break;
897
898 case 'L':
899 if (building_deny_info_url) break;
900 if (Config.errHtmlText) {
901 mb.appendf("%s", Config.errHtmlText);
902 do_quote = 0;
903 } else
904 p = "[not available]";
905 break;
906
907 case 'm':
908 if (building_deny_info_url) break;
909 #if USE_AUTH
910 if (auth_user_request.getRaw())
911 p = auth_user_request->denyMessage("[not available]");
912 else
913 p = "[not available]";
914 #else
915 p = "-";
916 #endif
917 break;
918
919 case 'M':
920 if (request) {
921 const SBuf &m = request->method.image();
922 mb.append(m.rawContent(), m.length());
923 } else if (!building_deny_info_url)
924 p = "[unknown method]";
925 break;
926
927 case 'o':
928 p = request ? request->extacl_message.termedBuf() : external_acl_message;
929 if (!p && !building_deny_info_url)
930 p = "[not available]";
931 break;
932
933 case 'p':
934 if (request) {
935 mb.appendf("%u", request->url.port());
936 } else if (!building_deny_info_url) {
937 p = "[unknown port]";
938 }
939 break;
940
941 case 'P':
942 if (request) {
943 p = request->url.getScheme().c_str();
944 } else if (!building_deny_info_url) {
945 p = "[unknown protocol]";
946 }
947 break;
948
949 case 'R':
950 if (building_deny_info_url) {
951 if (request != NULL) {
952 SBuf tmp = request->url.path();
953 p = tmp.c_str();
954 no_urlescape = 1;
955 } else
956 p = "[no request]";
957 break;
958 }
959 if (request != NULL) {
960 mb.appendf(SQUIDSBUFPH " " SQUIDSBUFPH " %s/%d.%d\n",
961 SQUIDSBUFPRINT(request->method.image()),
962 SQUIDSBUFPRINT(request->url.path()),
963 AnyP::ProtocolType_str[request->http_ver.protocol],
964 request->http_ver.major, request->http_ver.minor);
965 request->header.packInto(&mb, true); //hide authorization data
966 } else if (request_hdrs) {
967 p = request_hdrs;
968 } else {
969 p = "[no request]";
970 }
971 break;
972
973 case 's':
974 /* for backward compat we make %s show the full URL. Drop this in some future release. */
975 if (building_deny_info_url) {
976 if (request) {
977 const SBuf &tmp = request->effectiveRequestUri();
978 mb.append(tmp.rawContent(), tmp.length());
979 } else
980 p = url;
981 debugs(0, DBG_CRITICAL, "WARNING: deny_info now accepts coded tags. Use %u to get the full URL instead of %s");
982 } else
983 p = visible_appname_string;
984 break;
985
986 case 'S':
987 if (building_deny_info_url) {
988 p = visible_appname_string;
989 break;
990 }
991 /* signature may contain %-escapes, recursion */
992 if (page_id != ERR_SQUID_SIGNATURE) {
993 const int saved_id = page_id;
994 page_id = ERR_SQUID_SIGNATURE;
995 MemBuf *sign_mb = BuildContent();
996 mb.append(sign_mb->content(), sign_mb->contentSize());
997 sign_mb->clean();
998 delete sign_mb;
999 page_id = saved_id;
1000 do_quote = 0;
1001 } else {
1002 /* wow, somebody put %S into ERR_SIGNATURE, stop recursion */
1003 p = "[%S]";
1004 }
1005 break;
1006
1007 case 't':
1008 mb.appendf("%s", Time::FormatHttpd(squid_curtime));
1009 break;
1010
1011 case 'T':
1012 mb.appendf("%s", mkrfc1123(squid_curtime));
1013 break;
1014
1015 case 'U':
1016 /* Using the fake-https version of absolute-URI so error pages see https:// */
1017 /* even when the url-path cannot be shown as more than '*' */
1018 if (request)
1019 p = urlCanonicalFakeHttps(request);
1020 else if (url)
1021 p = url;
1022 else if (!building_deny_info_url)
1023 p = "[no URL]";
1024 break;
1025
1026 case 'u':
1027 if (request) {
1028 const SBuf &tmp = request->effectiveRequestUri();
1029 mb.append(tmp.rawContent(), tmp.length());
1030 } else if (url)
1031 p = url;
1032 else if (!building_deny_info_url)
1033 p = "[no URL]";
1034 break;
1035
1036 case 'w':
1037 if (Config.adminEmail)
1038 mb.appendf("%s", Config.adminEmail);
1039 else if (!building_deny_info_url)
1040 p = "[unknown]";
1041 break;
1042
1043 case 'W':
1044 if (building_deny_info_url) break;
1045 if (Config.adminEmail && Config.onoff.emailErrData)
1046 Dump(&mb);
1047 no_urlescape = 1;
1048 break;
1049
1050 case 'x':
1051 #if USE_OPENSSL
1052 if (detail)
1053 mb.appendf("%s", detail->errorName());
1054 else
1055 #endif
1056 if (!building_deny_info_url)
1057 p = "[Unknown Error Code]";
1058 break;
1059
1060 case 'z':
1061 if (building_deny_info_url) break;
1062 if (dnsError.size() > 0)
1063 p = dnsError.termedBuf();
1064 else if (ftp.cwd_msg)
1065 p = ftp.cwd_msg;
1066 else
1067 p = "[unknown]";
1068 break;
1069
1070 case 'Z':
1071 if (building_deny_info_url) break;
1072 if (err_msg)
1073 p = err_msg;
1074 else
1075 p = "[unknown]";
1076 break;
1077
1078 case '%':
1079 p = "%";
1080 break;
1081
1082 default:
1083 mb.appendf("%%%c", token);
1084 do_quote = 0;
1085 break;
1086 }
1087
1088 if (!p)
1089 p = mb.buf; /* do not use mb after this assignment! */
1090
1091 assert(p);
1092
1093 debugs(4, 3, "errorConvert: %%" << token << " --> '" << p << "'" );
1094
1095 if (do_quote)
1096 p = html_quote(p);
1097
1098 if (building_deny_info_url && !no_urlescape)
1099 p = rfc1738_escape_part(p);
1100
1101 return p;
1102 }
1103
1104 void
1105 ErrorState::DenyInfoLocation(const char *name, HttpRequest *, MemBuf &result)
1106 {
1107 char const *m = name;
1108 char const *p = m;
1109 char const *t;
1110
1111 if (m[0] == '3')
1112 m += 4; // skip "3xx:"
1113
1114 while ((p = strchr(m, '%'))) {
1115 result.append(m, p - m); /* copy */
1116 t = Convert(*++p, true, true); /* convert */
1117 result.appendf("%s", t); /* copy */
1118 m = p + 1; /* advance */
1119 }
1120
1121 if (*m)
1122 result.appendf("%s", m); /* copy tail */
1123
1124 assert((size_t)result.contentSize() == strlen(result.content()));
1125 }
1126
1127 HttpReply *
1128 ErrorState::BuildHttpReply()
1129 {
1130 HttpReply *rep = new HttpReply;
1131 const char *name = errorPageName(page_id);
1132 /* no LMT for error pages; error pages expire immediately */
1133
1134 if (name[0] == '3' || (name[0] != '2' && name[0] != '4' && name[0] != '5' && strchr(name, ':'))) {
1135 /* Redirection */
1136 Http::StatusCode status = Http::scFound;
1137 // Use configured 3xx reply status if set.
1138 if (name[0] == '3')
1139 status = httpStatus;
1140 else {
1141 // Use 307 for HTTP/1.1 non-GET/HEAD requests.
1142 if (request != NULL && request->method != Http::METHOD_GET && request->method != Http::METHOD_HEAD && request->http_ver >= Http::ProtocolVersion(1,1))
1143 status = Http::scTemporaryRedirect;
1144 }
1145
1146 rep->setHeaders(status, NULL, "text/html;charset=utf-8", 0, 0, -1);
1147
1148 if (request) {
1149 MemBuf redirect_location;
1150 redirect_location.init();
1151 DenyInfoLocation(name, request, redirect_location);
1152 httpHeaderPutStrf(&rep->header, HDR_LOCATION, "%s", redirect_location.content() );
1153 }
1154
1155 httpHeaderPutStrf(&rep->header, HDR_X_SQUID_ERROR, "%d %s", httpStatus, "Access Denied");
1156 } else {
1157 MemBuf *content = BuildContent();
1158 rep->setHeaders(httpStatus, NULL, "text/html;charset=utf-8", content->contentSize(), 0, -1);
1159 /*
1160 * include some information for downstream caches. Implicit
1161 * replaceable content. This isn't quite sufficient. xerrno is not
1162 * necessarily meaningful to another system, so we really should
1163 * expand it. Additionally, we should identify ourselves. Someone
1164 * might want to know. Someone _will_ want to know OTOH, the first
1165 * X-CACHE-MISS entry should tell us who.
1166 */
1167 httpHeaderPutStrf(&rep->header, HDR_X_SQUID_ERROR, "%s %d", name, xerrno);
1168
1169 #if USE_ERR_LOCALES
1170 /*
1171 * If error page auto-negotiate is enabled in any way, send the Vary.
1172 * RFC 2616 section 13.6 and 14.44 says MAY and SHOULD do this.
1173 * We have even better reasons though:
1174 * see http://wiki.squid-cache.org/KnowledgeBase/VaryNotCaching
1175 */
1176 if (!Config.errorDirectory) {
1177 /* We 'negotiated' this ONLY from the Accept-Language. */
1178 rep->header.delById(HDR_VARY);
1179 rep->header.putStr(HDR_VARY, "Accept-Language");
1180 }
1181
1182 /* add the Content-Language header according to RFC section 14.12 */
1183 if (err_language) {
1184 rep->header.putStr(HDR_CONTENT_LANGUAGE, err_language);
1185 } else
1186 #endif /* USE_ERROR_LOCALES */
1187 {
1188 /* default templates are in English */
1189 /* language is known unless error_directory override used */
1190 if (!Config.errorDirectory)
1191 rep->header.putStr(HDR_CONTENT_LANGUAGE, "en");
1192 }
1193
1194 rep->body.setMb(content);
1195 /* do not memBufClean() or delete the content, it was absorbed by httpBody */
1196 }
1197
1198 // Make sure error codes get back to the client side for logging and
1199 // error tracking.
1200 if (request) {
1201 int edc = ERR_DETAIL_NONE; // error detail code
1202 #if USE_OPENSSL
1203 if (detail)
1204 edc = detail->errorNo();
1205 else
1206 #endif
1207 if (detailCode)
1208 edc = detailCode;
1209 else
1210 edc = xerrno;
1211 request->detailError(type, edc);
1212 }
1213
1214 return rep;
1215 }
1216
1217 MemBuf *
1218 ErrorState::BuildContent()
1219 {
1220 const char *m = NULL;
1221
1222 assert(page_id > ERR_NONE && page_id < error_page_count);
1223
1224 #if USE_ERR_LOCALES
1225 ErrorPageFile *localeTmpl = NULL;
1226
1227 /** error_directory option in squid.conf overrides translations.
1228 * Custom errors are always found either in error_directory or the templates directory.
1229 * Otherwise locate the Accept-Language header
1230 */
1231 if (!Config.errorDirectory && page_id < ERR_MAX) {
1232 if (err_language && err_language != Config.errorDefaultLanguage)
1233 safe_free(err_language);
1234
1235 localeTmpl = new ErrorPageFile(err_type_str[page_id], static_cast<err_type>(page_id));
1236 if (localeTmpl->loadFor(request)) {
1237 m = localeTmpl->text();
1238 assert(localeTmpl->language());
1239 err_language = xstrdup(localeTmpl->language());
1240 }
1241 }
1242 #endif /* USE_ERR_LOCALES */
1243
1244 /** \par
1245 * If client-specific error templates are not enabled or available.
1246 * fall back to the old style squid.conf settings.
1247 */
1248 if (!m) {
1249 m = error_text[page_id];
1250 #if USE_ERR_LOCALES
1251 if (!Config.errorDirectory)
1252 err_language = Config.errorDefaultLanguage;
1253 #endif
1254 debugs(4, 2, HERE << "No existing error page language negotiated for " << errorPageName(page_id) << ". Using default error file.");
1255 }
1256
1257 MemBuf *result = ConvertText(m, true);
1258 #if USE_ERR_LOCALES
1259 if (localeTmpl)
1260 delete localeTmpl;
1261 #endif
1262 return result;
1263 }
1264
1265 MemBuf *ErrorState::ConvertText(const char *text, bool allowRecursion)
1266 {
1267 MemBuf *content = new MemBuf;
1268 const char *p;
1269 const char *m = text;
1270 assert(m);
1271 content->init();
1272
1273 while ((p = strchr(m, '%'))) {
1274 content->append(m, p - m); /* copy */
1275 const char *t = Convert(*++p, false, allowRecursion); /* convert */
1276 content->appendf("%s", t); /* copy */
1277 m = p + 1; /* advance */
1278 }
1279
1280 if (*m)
1281 content->appendf("%s", m); /* copy tail */
1282
1283 content->terminate();
1284
1285 assert((size_t)content->contentSize() == strlen(content->content()));
1286
1287 return content;
1288 }
1289