]> git.ipfire.org Git - thirdparty/squid.git/blame - src/errorpage.cc
Make pod2man an optional dependency
[thirdparty/squid.git] / src / errorpage.cc
CommitLineData
30a4f2a8 1/*
bde978a6 2 * Copyright (C) 1996-2015 The Squid Software Foundation and contributors
e25c139f 3 *
bbc27441
AJ
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.
30a4f2a8 7 */
bbc27441
AJ
8
9/* DEBUG: section 04 Error Generation */
10
f7f3304a 11#include "squid.h"
8a01b99e 12#include "cache_cf.h"
92ae4c86 13#include "clients/forward.h"
e0d28505 14#include "comm/Connection.h"
ec41b64c 15#include "comm/Write.h"
438b04d4 16#include "disk.h"
582c2af2 17#include "err_detail_type.h"
aa839030 18#include "errorpage.h"
602d9612 19#include "fde.h"
25f98340 20#include "html_quote.h"
a5bac1d2 21#include "HttpHeaderTools.h"
528b2c61 22#include "HttpReply.h"
23#include "HttpRequest.h"
0eb49b6d 24#include "MemBuf.h"
602d9612 25#include "MemObject.h"
1fa9b1a7 26#include "rfc1738.h"
4d5904f7 27#include "SquidConfig.h"
602d9612
A
28#include "Store.h"
29#include "tools.h"
b1bd952a 30#include "URL.h"
d295d770 31#include "wordlist.h"
4e540555
FC
32#if USE_AUTH
33#include "auth/UserRequest.h"
34#endif
35#include "SquidTime.h"
cb4f4424 36#if USE_OPENSSL
4e540555
FC
37#include "ssl/ErrorDetailManager.h"
38#endif
02922e76 39
63be0a78 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
605f2c3e 51#if !defined(DEFAULT_SQUID_ERROR_DIR)
43000484
AJ
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
63be0a78 58/// \ingroup ErrorPageInternal
aa839030 59CBDATA_CLASS_INIT(ErrorState);
60
02922e76 61/* local types */
62
63be0a78 63/// \ingroup ErrorPageInternal
9e008dda 64typedef struct {
02922e76 65 int id;
66 char *page_name;
955394ce 67 Http::StatusCode page_redirect;
2fadd50d 68} ErrorDynamicPageInfo;
02922e76 69
70/* local constant and vars */
71
63be0a78 72/**
cfdb8f88 73 \ingroup ErrorPageInternal
63be0a78 74 *
75 \note hard coded error messages are not appended with %S
76 * automagically to give you more control on the format
1d803566 77 */
9e008dda 78static const struct {
f53969cc 79 int type; /* and page_id */
2ac76861 80 const char *text;
62e76326 81}
82
83error_hard_text[] = {
84
9e008dda
AJ
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};
02922e76 99
63be0a78 100/// \ingroup ErrorPageInternal
c8ea3cc0 101static std::vector<ErrorDynamicPageInfo *> ErrorDynamicPages;
02922e76 102
103/* local prototypes */
104
63be0a78 105/// \ingroup ErrorPageInternal
2ac76861 106static const int error_hard_text_count = sizeof(error_hard_text) / sizeof(*error_hard_text);
63be0a78 107
108/// \ingroup ErrorPageInternal
02922e76 109static char **error_text = NULL;
63be0a78 110
111/// \ingroup ErrorPageInternal
02922e76 112static int error_page_count = 0;
9b312a19 113
5b52cb6c 114/// \ingroup ErrorPageInternal
0ae3294a 115static MemBuf error_stylesheet;
5b52cb6c 116
1d803566 117static const char *errorFindHardText(err_type type);
c68e9c6b 118static ErrorDynamicPageInfo *errorDynamicPageInfoCreate(int id, const char *page_name);
119static void errorDynamicPageInfoDestroy(ErrorDynamicPageInfo * info);
2b663917 120static IOCB errorSendComplete;
1e74c110 121
02259ff8
CT
122/// \ingroup ErrorPageInternal
123/// manages an error page template
dc49061a
A
124class ErrorPageFile: public TemplateFile
125{
02259ff8 126public:
ced8def3 127 ErrorPageFile(const char *name, const err_type code) : TemplateFile(name,code) {textBuf.init();}
02259ff8
CT
128
129 /// The template text data read from disk
130 const char *text() { return textBuf.content(); }
131
132private:
133 /// stores the data read from disk to a local buffer
ced8def3 134 virtual bool parse(const char *buf, int len, bool) {
02259ff8
CT
135 if (len)
136 textBuf.append(buf, len);
137 return true;
138 }
139
140 MemBuf textBuf; ///< A buffer to store the error page
141};
e6ccf245 142
63be0a78 143/// \ingroup ErrorPageInternal
e6ccf245 144err_type &operator++ (err_type &anErr)
145{
1f1ae50a 146 int tmp = (int)anErr;
147 anErr = (err_type)(++tmp);
e6ccf245 148 return anErr;
149}
4ff59fc7 150
63be0a78 151/// \ingroup ErrorPageInternal
62e76326 152int operator - (err_type const &anErr, err_type const &anErr2)
153{
4ff59fc7 154 return (int)anErr - (int)anErr2;
155}
156
b8d8561b 157void
0673c0ba 158errorInitialize(void)
fa966b74 159{
728da2ee 160 err_type i;
1d803566 161 const char *text;
4ff59fc7 162 error_page_count = ERR_MAX + ErrorDynamicPages.size();
e6ccf245 163 error_text = static_cast<char **>(xcalloc(error_page_count, sizeof(char *)));
62e76326 164
e6ccf245 165 for (i = ERR_NONE, ++i; i < error_page_count; ++i) {
62e76326 166 safe_free(error_text[i]);
62e76326 167
43000484
AJ
168 if ((text = errorFindHardText(i))) {
169 /**\par
170 * Index any hard-coded error text into defaults.
171 */
62e76326 172 error_text[i] = xstrdup(text);
43000484
AJ
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 */
8ff2520a 180 ErrorPageFile errTmpl(err_type_str[i], i);
02259ff8 181 error_text[i] = errTmpl.loadDefault() ? xstrdup(errTmpl.text()) : NULL;
62e76326 182 } else {
43000484
AJ
183 /** \par
184 * Index any unknown file names used by deny_info.
185 */
e6334d92 186 ErrorDynamicPageInfo *info = ErrorDynamicPages.at(i - ERR_MAX);
62e76326 187 assert(info && info->id == i && info->page_name);
188
aed9a15b 189 const char *pg = info->page_name;
955394ce 190 if (info->page_redirect != Http::scNone)
aed9a15b
AJ
191 pg = info->page_name +4;
192
193 if (strchr(pg, ':') == NULL) {
43000484 194 /** But only if they are not redirection URL. */
8ff2520a 195 ErrorPageFile errTmpl(pg, ERR_MAX);
02259ff8 196 error_text[i] = errTmpl.loadDefault() ? xstrdup(errTmpl.text()) : NULL;
62e76326 197 }
198 }
1d803566 199 }
5b52cb6c 200
0ae3294a
AJ
201 error_stylesheet.reset();
202
5b52cb6c 203 // look for and load stylesheet into global MemBuf for it.
9e008dda 204 if (Config.errorStylesheet) {
8ff2520a 205 ErrorPageFile tmpl("StylesSheet", ERR_MAX);
02259ff8
CT
206 tmpl.loadFromFile(Config.errorStylesheet);
207 error_stylesheet.Printf("%s",tmpl.text());
5b52cb6c 208 }
02259ff8 209
cb4f4424 210#if USE_OPENSSL
02259ff8 211 Ssl::errorDetailInitialize();
c259af96 212#endif
1d803566 213}
214
c68e9c6b 215void
216errorClean(void)
217{
218 if (error_text) {
62e76326 219 int i;
220
95dc7ff4 221 for (i = ERR_NONE + 1; i < error_page_count; ++i)
62e76326 222 safe_free(error_text[i]);
223
224 safe_free(error_text);
c68e9c6b 225 }
62e76326 226
385acf91 227 while (!ErrorDynamicPages.empty()) {
38c22baf
FC
228 errorDynamicPageInfoDestroy(ErrorDynamicPages.back());
229 ErrorDynamicPages.pop_back();
230 }
62e76326 231
c68e9c6b 232 error_page_count = 0;
02259ff8 233
cb4f4424 234#if USE_OPENSSL
02259ff8 235 Ssl::errorDetailClean();
c259af96 236#endif
c68e9c6b 237}
238
63be0a78 239/// \ingroup ErrorPageInternal
1d803566 240static const char *
241errorFindHardText(err_type type)
242{
243 int i;
62e76326 244
95dc7ff4 245 for (i = 0; i < error_hard_text_count; ++i)
62e76326 246 if (error_hard_text[i].type == type)
247 return error_hard_text[i].text;
248
1d803566 249 return NULL;
250}
251
8ff2520a 252TemplateFile::TemplateFile(const char *name, const err_type code): silent(false), wasLoaded(false), templateName(name), templateCode(code)
1d803566 253{
02259ff8
CT
254 assert(name);
255}
256
257bool
258TemplateFile::loadDefault()
259{
260 if (loaded()) // already loaded?
261 return true;
43000484
AJ
262
263 /** test error_directory configured location */
02259ff8
CT
264 if (Config.errorDirectory) {
265 char path[MAXPATHLEN];
266 snprintf(path, sizeof(path), "%s/%s", Config.errorDirectory, templateName.termedBuf());
267 loadFromFile(path);
268 }
43000484
AJ
269
270#if USE_ERR_LOCALES
271 /** test error_default_language location */
02259ff8
CT
272 if (!loaded() && Config.errorDefaultLanguage) {
273 if (!tryLoadTemplate(Config.errorDefaultLanguage)) {
8ff2520a 274 debugs(1, (templateCode < TCP_RESET ? DBG_CRITICAL : 3), "Unable to load default error language files. Reset to backups.");
43000484
AJ
275 }
276 }
277#endif
62e76326 278
43000484 279 /* test default location if failed (templates == English translation base templates) */
02259ff8
CT
280 if (!loaded()) {
281 tryLoadTemplate("templates");
5bf33a09 282 }
62e76326 283
1d803566 284 /* giving up if failed */
b073fc4b 285 if (!loaded()) {
8ff2520a 286 debugs(1, (templateCode < TCP_RESET ? DBG_CRITICAL : 3), "WARNING: failed to find or read error text file " << templateName);
b073fc4b
AJ
287 parse("Internal Error: Missing Template ", 33, '\0');
288 parse(templateName.termedBuf(), templateName.size(), '\0');
289 }
62e76326 290
02259ff8 291 return true;
1d803566 292}
293
02259ff8
CT
294bool
295TemplateFile::tryLoadTemplate(const char *lang)
1d803566 296{
02259ff8
CT
297 assert(lang);
298
9b312a19 299 char path[MAXPATHLEN];
02259ff8
CT
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
319bool
320TemplateFile::loadFromFile(const char *path)
321{
322 int fd;
e8f6c5c7 323 char buf[4096];
e8f6c5c7 324 ssize_t len;
1d803566 325
02259ff8
CT
326 if (loaded()) // already loaded?
327 return true;
45308e4d 328
c4aefe96 329 fd = file_open(path, O_RDONLY | O_TEXT);
e8f6c5c7 330
331 if (fd < 0) {
43000484 332 /* with dynamic locale negotiation we may see some failures before a success. */
8ff2520a 333 if (!silent && templateCode < TCP_RESET)
43000484 334 debugs(4, DBG_CRITICAL, HERE << "'" << path << "': " << xstrerror());
02259ff8
CT
335 wasLoaded = false;
336 return wasLoaded;
1d803566 337 }
62e76326 338
9e008dda 339 while ((len = FD_READ_METHOD(fd, buf, sizeof(buf))) > 0) {
02259ff8
CT
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 }
e8f6c5c7 345 }
02259ff8 346 parse(buf, 0, true);
62e76326 347
e8f6c5c7 348 if (len < 0) {
c70281f8 349 debugs(4, DBG_CRITICAL, HERE << "failed to fully read: '" << path << "': " << xstrerror());
1e74c110 350 }
62e76326 351
1d803566 352 file_close(fd);
e8f6c5c7 353
02259ff8
CT
354 wasLoaded = true;
355 return wasLoaded;
356}
357
358bool strHdrAcptLangGetItem(const String &hdr, char *lang, int langLen, size_t &pos)
359{
dc49061a 360 while (pos < hdr.size()) {
02259ff8
CT
361 char *dt = lang;
362
a32e7644
AJ
363 /* skip any initial whitespace. */
364 while (pos < hdr.size() && xisspace(hdr[pos]))
365 ++pos;
62e76326 366
02259ff8
CT
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
95dc7ff4 389 ++dt; // move to next destination byte.
02259ff8 390 }
95dc7ff4 391 ++pos;
02259ff8 392 }
a38ec4b1
FC
393 *dt = '\0'; // nul-terminated the filename content string before system use.
394 ++dt;
62e76326 395
a32e7644
AJ
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
02259ff8
CT
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
412bool
b248c2a3 413TemplateFile::loadFor(const HttpRequest *request)
02259ff8
CT
414{
415 String hdr;
416
cf0a0bdf 417#if USE_ERR_LOCALES
02259ff8
CT
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 }
cf0a0bdf 447#endif
02259ff8
CT
448
449 return loaded();
6eb42cae 450}
451
63be0a78 452/// \ingroup ErrorPageInternal
02922e76 453static ErrorDynamicPageInfo *
454errorDynamicPageInfoCreate(int id, const char *page_name)
455{
4ff59fc7 456 ErrorDynamicPageInfo *info = new ErrorDynamicPageInfo;
02922e76 457 info->id = id;
458 info->page_name = xstrdup(page_name);
955394ce 459 info->page_redirect = static_cast<Http::StatusCode>(atoi(page_name));
aed9a15b
AJ
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 */
955394ce 472 if (info->page_redirect == Http::scNone)
aed9a15b
AJ
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
0ff52055 480 debugs(0, DBG_CRITICAL, "FATAL: status " << info->page_redirect << " requires a template on '" << page_name << "'");
aed9a15b 481 self_destruct();
0ff52055 482 } else if (info->page_redirect >= 300 && info->page_redirect <= 399 && !strchr(&(page_name[4]), ':')) {
aed9a15b 483 // 3xx require an absolute URL
0ff52055 484 debugs(0, DBG_CRITICAL, "FATAL: status " << info->page_redirect << " requires a URL on '" << page_name << "'");
aed9a15b
AJ
485 self_destruct();
486 } else if (info->page_redirect >= 400 /* && <= 599 */ && strchr(&(page_name[4]), ':')) {
487 // 4xx/5xx require a local template file
0ff52055 488 debugs(0, DBG_CRITICAL, "FATAL: status " << info->page_redirect << " requires a template on '" << page_name << "'");
aed9a15b
AJ
489 self_destruct();
490 }
491 // else okay.
492
02922e76 493 return info;
494}
495
63be0a78 496/// \ingroup ErrorPageInternal
02922e76 497static void
1afe05c5 498errorDynamicPageInfoDestroy(ErrorDynamicPageInfo * info)
02922e76 499{
500 assert(info);
c70281f8 501 safe_free(info->page_name);
4ff59fc7 502 delete info;
02922e76 503}
504
63be0a78 505/// \ingroup ErrorPageInternal
76cdc28d 506static int
507errorPageId(const char *page_name)
508{
95dc7ff4 509 for (int i = 0; i < ERR_MAX; ++i) {
62e76326 510 if (strcmp(err_type_str[i], page_name) == 0)
511 return i;
76cdc28d 512 }
62e76326 513
95dc7ff4 514 for (size_t j = 0; j < ErrorDynamicPages.size(); ++j) {
4c9eadc2 515 if (strcmp(ErrorDynamicPages[j]->page_name, page_name) == 0)
190154cf 516 return j + ERR_MAX;
76cdc28d 517 }
62e76326 518
76cdc28d 519 return ERR_NONE;
520}
521
e6ccf245 522err_type
02922e76 523errorReservePageId(const char *page_name)
524{
76cdc28d 525 ErrorDynamicPageInfo *info;
526 int id = errorPageId(page_name);
62e76326 527
76cdc28d 528 if (id == ERR_NONE) {
62e76326 529 info = errorDynamicPageInfoCreate(ERR_MAX + ErrorDynamicPages.size(), page_name);
530 ErrorDynamicPages.push_back(info);
531 id = info->id;
76cdc28d 532 }
62e76326 533
e6ccf245 534 return (err_type)id;
02922e76 535}
536
63be0a78 537/// \ingroup ErrorPageInternal
64b66b76 538const char *
c68e9c6b 539errorPageName(int pageId)
53ad48e6 540{
f53969cc 541 if (pageId >= ERR_NONE && pageId < ERR_MAX) /* common case */
62e76326 542 return err_type_str[pageId];
543
4ff59fc7 544 if (pageId >= ERR_MAX && pageId - ERR_MAX < (ssize_t)ErrorDynamicPages.size())
4c9eadc2 545 return ErrorDynamicPages[pageId - ERR_MAX]->page_name;
62e76326 546
f53969cc 547 return "ERR_UNKNOWN"; /* should not happen */
53ad48e6 548}
549
a23223bf
CT
550ErrorState *
551ErrorState::NewForwarding(err_type type, HttpRequest *request)
552{
553 assert(request);
554 const Http::StatusCode status = request->flags.needValidation ?
e2849af8 555 Http::scGatewayTimeout : Http::scServiceUnavailable;
a23223bf
CT
556 return new ErrorState(type, status, request);
557}
558
955394ce 559ErrorState::ErrorState(err_type t, Http::StatusCode status, HttpRequest * req) :
f53969cc
SM
560 type(t),
561 page_id(t),
562 err_language(NULL),
563 httpStatus(status),
913524f0 564#if USE_AUTH
f53969cc 565 auth_user_request (NULL),
913524f0 566#endif
f53969cc
SM
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),
cb4f4424 579#if USE_OPENSSL
f53969cc 580 detail(NULL),
913524f0 581#endif
f53969cc 582 detailCode(ERR_DETAIL_NONE)
fe40a877 583{
913524f0
AJ
584 memset(&ftp, 0, sizeof(ftp));
585
4c9eadc2
FC
586 if (page_id >= ERR_MAX && ErrorDynamicPages[page_id - ERR_MAX]->page_redirect != Http::scNone)
587 httpStatus = ErrorDynamicPages[page_id - ERR_MAX]->page_redirect;
913524f0
AJ
588
589 if (req != NULL) {
b248c2a3
AJ
590 request = req;
591 HTTPMSGLOCK(request);
913524f0 592 src_addr = req->client_addr;
2cc81f1f 593 }
fe40a877 594}
595
fe40a877 596void
597errorAppendEntry(StoreEntry * entry, ErrorState * err)
598{
e4a67a80 599 assert(entry->mem_obj != NULL);
528b2c61 600 assert (entry->isEmpty());
0b86805b 601 debugs(4, 4, "Creating an error page for entry " << entry <<
602 " with errorstate " << err <<
603 " page id " << err->page_id);
62e76326 604
1ea80e98 605 if (entry->store_status != STORE_PENDING) {
0b86805b 606 debugs(4, 2, "Skipping error page due to store_status: " << entry->store_status);
62e76326 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));
6ea35247 613 assert(entry->mem_obj->nclients == 0);
913524f0 614 delete err;
62e76326 615 return;
1ea80e98 616 }
62e76326 617
76cdc28d 618 if (err->page_id == TCP_RESET) {
62e76326 619 if (err->request) {
bf8fe701 620 debugs(4, 2, "RSTing this reply");
e857372a 621 err->request->flags.resetTcp = true;
62e76326 622 }
98264874 623 }
62e76326 624
eacfca83 625 entry->storeErrorResponse(err->BuildHttpReply());
913524f0 626 delete err;
fe40a877 627}
628
fe40a877 629void
e0d28505 630errorSend(const Comm::ConnectionPointer &conn, ErrorState * err)
fe40a877 631{
cb69b4c7 632 HttpReply *rep;
e0d28505
AJ
633 debugs(4, 3, HERE << conn << ", err=" << err);
634 assert(Comm::IsConnOpen(conn));
62e76326 635
c70281f8 636 rep = err->BuildHttpReply();
62e76326 637
ddbe383d 638 MemBuf *mb = rep->pack();
ec41b64c
AJ
639 AsyncCall::Pointer call = commCbCall(78, 5, "errorSendComplete",
640 CommIoCbPtrFun(&errorSendComplete, err));
b0388924 641 Comm::Write(conn, mb, call);
ddbe383d 642 delete mb;
62e76326 643
06a5ae20 644 delete rep;
fe40a877 645}
646
63be0a78 647/**
648 \ingroup ErrorPageAPI
fe40a877 649 *
63be0a78 650 * Called by commHandleWrite() after data has been written
651 * to the client socket.
fe40a877 652 *
63be0a78 653 \note If there is a callback, the callback is responsible for
b3802bdc 654 * closing the FD, otherwise we do it ourselves.
fe40a877 655 */
656static void
ced8def3 657errorSendComplete(const Comm::ConnectionPointer &conn, char *, size_t size, Comm::Flag errflag, int, void *data)
fe40a877 658{
e6ccf245 659 ErrorState *err = static_cast<ErrorState *>(data);
e0d28505 660 debugs(4, 3, HERE << conn << ", size=" << size);
62e76326 661
c8407295 662 if (errflag != Comm::ERR_CLOSING) {
62e76326 663 if (err->callback) {
bf8fe701 664 debugs(4, 3, "errorSendComplete: callback");
e0d28505 665 err->callback(conn->fd, err->callback_data, size);
62e76326 666 } else {
bf8fe701 667 debugs(4, 3, "errorSendComplete: comm_close");
80463bb4 668 conn->close();
62e76326 669 }
fe40a877 670 }
62e76326 671
913524f0 672 delete err;
fe40a877 673}
674
913524f0 675ErrorState::~ErrorState()
6eb42cae 676{
913524f0
AJ
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);
2f1431ea 684#if USE_AUTH
913524f0 685 auth_user_request = NULL;
2f1431ea 686#endif
913524f0 687 safe_free(err_msg);
5bf33a09 688#if USE_ERR_LOCALES
913524f0 689 if (err_language != Config.errorDefaultLanguage)
5bf33a09 690#endif
913524f0 691 safe_free(err_language);
cb4f4424 692#if USE_OPENSSL
913524f0 693 delete detail;
4d16918e 694#endif
1e74c110 695}
8213067d 696
c70281f8
AJ
697int
698ErrorState::Dump(MemBuf * mb)
b5fb34f1 699{
032785bf 700 MemBuf str;
cc192b50 701 char ntoabuf[MAX_IPSTRLEN];
702
2fe7eff9 703 str.reset();
b5fb34f1 704 /* email subject line */
c70281f8 705 str.Printf("CacheErrorInfo - %s", errorPageName(type));
2fe7eff9 706 mb->Printf("?subject=%s", rfc1738_escape_part(str.buf));
707 str.reset();
b5fb34f1 708 /* email body */
2fe7eff9 709 str.Printf("CacheHost: %s\r\n", getMyHostname());
b5fb34f1 710 /* - Err Msgs */
c70281f8 711 str.Printf("ErrPage: %s\r\n", errorPageName(type));
62e76326 712
c70281f8
AJ
713 if (xerrno) {
714 str.Printf("Err: (%d) %s\r\n", xerrno, strerror(xerrno));
b5fb34f1 715 } else {
2fe7eff9 716 str.Printf("Err: [none]\r\n");
b5fb34f1 717 }
2f1431ea 718#if USE_AUTH
98340a7b 719 if (auth_user_request.getRaw() && auth_user_request->denyMessage())
c70281f8 720 str.Printf("Auth ErrMsg: %s\r\n", auth_user_request->denyMessage());
2f1431ea 721#endif
3ff65596
AR
722 if (dnsError.size() > 0)
723 str.Printf("DNS ErrMsg: %s\r\n", dnsError.termedBuf());
62e76326 724
b5fb34f1 725 /* - TimeStamp */
2fe7eff9 726 str.Printf("TimeStamp: %s\r\n\r\n", mkrfc1123(squid_curtime));
62e76326 727
b5fb34f1 728 /* - IP stuff */
4dd643d5 729 str.Printf("ClientIP: %s\r\n", src_addr.toStr(ntoabuf,MAX_IPSTRLEN));
62e76326 730
b3802bdc 731 if (request && request->hier.host[0] != '\0') {
c70281f8 732 str.Printf("ServerIP: %s\r\n", request->hier.host);
b5fb34f1 733 }
62e76326 734
2fe7eff9 735 str.Printf("\r\n");
b5fb34f1 736 /* - HTTP stuff */
2fe7eff9 737 str.Printf("HTTP Request:\r\n");
62e76326 738
c70281f8 739 if (NULL != request) {
e053c141 740 Packer pck;
bb790702
FC
741 String urlpath_or_slash;
742
743 if (request->urlpath.size() != 0)
744 urlpath_or_slash = request->urlpath;
745 else
746 urlpath_or_slash = "/";
747
7f06a3d8
AJ
748 str.Printf(SQUIDSBUFPH " " SQUIDSTRINGPH " %s/%d.%d\n",
749 SQUIDSBUFPRINT(request->method.image()),
bb790702 750 SQUIDSTRINGPRINT(urlpath_or_slash),
c9fd01b4 751 AnyP::ProtocolType_str[request->http_ver.protocol],
c70281f8 752 request->http_ver.major, request->http_ver.minor);
e053c141
FC
753 packerToMemInit(&pck, &str);
754 request->header.packInto(&pck);
755 packerClean(&pck);
b5fb34f1 756 }
62e76326 757
2fe7eff9 758 str.Printf("\r\n");
b5fb34f1 759 /* - FTP stuff */
62e76326 760
c70281f8
AJ
761 if (ftp.request) {
762 str.Printf("FTP Request: %s\r\n", ftp.request);
a636cbbf 763 str.Printf("FTP Reply: %s\r\n", (ftp.reply? ftp.reply:"[none]"));
2fe7eff9 764 str.Printf("FTP Msg: ");
c70281f8 765 wordlistCat(ftp.server_msg, &str);
2fe7eff9 766 str.Printf("\r\n");
b5fb34f1 767 }
62e76326 768
2fe7eff9 769 str.Printf("\r\n");
770 mb->Printf("&body=%s", rfc1738_escape_part(str.buf));
771 str.clean();
b5fb34f1 772 return 0;
773}
774
63be0a78 775/// \ingroup ErrorPageInternal
2658f489 776#define CVT_BUF_SZ 512
fe40a877 777
c70281f8 778const char *
4d16918e 779ErrorState::Convert(char token, bool building_deny_info_url, bool allowRecursion)
8213067d 780{
032785bf 781 static MemBuf mb;
f53969cc 782 const char *p = NULL; /* takes priority over mb if set */
10270faa 783 int do_quote = 1;
4ad8d23d 784 int no_urlescape = 0; /* if true then item is NOT to be further URL-encoded */
cc192b50 785 char ntoabuf[MAX_IPSTRLEN];
eeb423fb 786
2fe7eff9 787 mb.reset();
62e76326 788
9b312a19 789 switch (token) {
62e76326 790
523f44f5 791 case 'a':
2f1431ea 792#if USE_AUTH
a33a428a 793 if (request && request->auth_user_request != NULL)
c70281f8 794 p = request->auth_user_request->username();
523f44f5 795 if (!p)
2f1431ea 796#endif
523f44f5 797 p = "-";
523f44f5 798 break;
88d1e459
AJ
799
800 case 'b':
801 mb.Printf("%d", getMyPort());
802 break;
803
8f872bb6 804 case 'B':
1e98e28b 805 if (building_deny_info_url) break;
5517260a 806 p = request ? Ftp::UrlWith2f(request) : "[no URL]";
62e76326 807 break;
808
42b51993 809 case 'c':
1e98e28b 810 if (building_deny_info_url) break;
c70281f8 811 p = errorPageName(type);
62e76326 812 break;
813
4d16918e
CT
814 case 'D':
815 if (!allowRecursion)
816 p = "%D"; // if recursion is not allowed, do not convert
cb4f4424 817#if USE_OPENSSL
4d16918e
CT
818 // currently only SSL error details implemented
819 else if (detail) {
02259ff8 820 detail->useRequest(request);
4d16918e 821 const String &errDetail = detail->toString();
b38b26cb 822 if (errDetail.size() > 0) {
8211c314
CT
823 MemBuf *detail_mb = ConvertText(errDetail.termedBuf(), false);
824 mb.append(detail_mb->content(), detail_mb->contentSize());
825 delete detail_mb;
826 do_quote = 0;
827 }
828 }
4d16918e 829#endif
8211c314 830 if (!mb.contentSize())
4d16918e
CT
831 mb.Printf("[No Error Detail]");
832 break;
833
042461c3 834 case 'e':
c70281f8 835 mb.Printf("%d", xerrno);
62e76326 836 break;
837
042461c3 838 case 'E':
c70281f8
AJ
839 if (xerrno)
840 mb.Printf("(%d) %s", xerrno, strerror(xerrno));
62e76326 841 else
2fe7eff9 842 mb.Printf("[No Error]");
62e76326 843 break;
844
fe40a877 845 case 'f':
1e98e28b 846 if (building_deny_info_url) break;
62e76326 847 /* FTP REQUEST LINE */
c70281f8
AJ
848 if (ftp.request)
849 p = ftp.request;
62e76326 850 else
851 p = "nothing";
62e76326 852 break;
853
fe40a877 854 case 'F':
1e98e28b 855 if (building_deny_info_url) break;
62e76326 856 /* FTP REPLY LINE */
c6b684dd 857 if (ftp.reply)
c70281f8 858 p = ftp.reply;
62e76326 859 else
860 p = "nothing";
62e76326 861 break;
862
7131112f 863 case 'g':
1e98e28b 864 if (building_deny_info_url) break;
a91d3505
AJ
865 /* FTP SERVER RESPONSE */
866 if (ftp.listing) {
0477a072
AJ
867 mb.append(ftp.listing->content(), ftp.listing->contentSize());
868 do_quote = 0;
a91d3505
AJ
869 } else if (ftp.server_msg) {
870 wordlistCat(ftp.server_msg, &mb);
0477a072 871 }
62e76326 872 break;
873
03d7b07f 874 case 'h':
2fe7eff9 875 mb.Printf("%s", getMyHostname());
62e76326 876 break;
877
fe40a877 878 case 'H':
c70281f8 879 if (request) {
b3802bdc 880 if (request->hier.host[0] != '\0') // if non-empty string.
c70281f8 881 p = request->hier.host;
beed27a2 882 else
c70281f8 883 p = request->GetHost();
1e98e28b 884 } else if (!building_deny_info_url)
beed27a2 885 p = "[unknown host]";
62e76326 886 break;
887
f787fb1e 888 case 'i':
4dd643d5 889 mb.Printf("%s", src_addr.toStr(ntoabuf,MAX_IPSTRLEN));
62e76326 890 break;
891
f787fb1e 892 case 'I':
72e4bea1 893 if (request && request->hier.tcpServer != NULL)
4dd643d5 894 p = request->hier.tcpServer->remote.toStr(ntoabuf,MAX_IPSTRLEN);
1e98e28b 895 else if (!building_deny_info_url)
62e76326 896 p = "[unknown]";
62e76326 897 break;
898
5b52cb6c 899 case 'l':
1e98e28b 900 if (building_deny_info_url) break;
0ae3294a 901 mb.append(error_stylesheet.content(), error_stylesheet.contentSize());
5b52cb6c
AJ
902 do_quote = 0;
903 break;
904
fe40a877 905 case 'L':
1e98e28b 906 if (building_deny_info_url) break;
62e76326 907 if (Config.errHtmlText) {
2fe7eff9 908 mb.Printf("%s", Config.errHtmlText);
62e76326 909 do_quote = 0;
1e98e28b 910 } else
62e76326 911 p = "[not available]";
62e76326 912 break;
913
066ed5c1 914 case 'm':
1e98e28b 915 if (building_deny_info_url) break;
2f1431ea 916#if USE_AUTH
52ba2948
CT
917 if (auth_user_request.getRaw())
918 p = auth_user_request->denyMessage("[not available]");
919 else
920 p = "[not available]";
2f1431ea
AJ
921#else
922 p = "-";
923#endif
62e76326 924 break;
925
fe40a877 926 case 'M':
7f06a3d8 927 if (request) {
e2849af8
A
928 const SBuf &m = request->method.image();
929 mb.append(m.rawContent(), m.length());
7f06a3d8
AJ
930 } else if (!building_deny_info_url)
931 p = "[unknown method]";
62e76326 932 break;
933
4a972fa2 934 case 'o':
8c93a598 935 p = request ? request->extacl_message.termedBuf() : external_acl_message;
1e98e28b 936 if (!p && !building_deny_info_url)
d73b593b 937 p = "[not available]";
4a972fa2 938 break;
939
fe40a877 940 case 'p':
c70281f8
AJ
941 if (request) {
942 mb.Printf("%d", (int) request->port);
1e98e28b 943 } else if (!building_deny_info_url) {
62e76326 944 p = "[unknown port]";
945 }
62e76326 946 break;
947
fe40a877 948 case 'P':
e7aae06b 949 if (request) {
4e3f4dc7 950 p = request->url.getScheme().c_str();
1e98e28b 951 } else if (!building_deny_info_url) {
e7aae06b
A
952 p = "[unknown protocol]";
953 }
62e76326 954 break;
955
b5af8569 956 case 'R':
1e98e28b 957 if (building_deny_info_url) {
15b02e9a 958 p = (request->urlpath.size() != 0 ? request->urlpath.termedBuf() : "/");
2ce66c0f 959 no_urlescape = 1;
15b02e9a
AJ
960 break;
961 }
c70281f8 962 if (NULL != request) {
d5f8d05f 963 Packer pck;
bb790702
FC
964 String urlpath_or_slash;
965
966 if (request->urlpath.size() != 0)
967 urlpath_or_slash = request->urlpath;
968 else
969 urlpath_or_slash = "/";
970
7f06a3d8
AJ
971 mb.Printf(SQUIDSBUFPH " " SQUIDSTRINGPH " %s/%d.%d\n",
972 SQUIDSBUFPRINT(request->method.image()),
bb790702 973 SQUIDSTRINGPRINT(urlpath_or_slash),
c9fd01b4 974 AnyP::ProtocolType_str[request->http_ver.protocol],
c70281f8 975 request->http_ver.major, request->http_ver.minor);
d5f8d05f 976 packerToMemInit(&pck, &mb);
d8f6c79c 977 request->header.packInto(&pck, true); //hide authorization data
d5f8d05f 978 packerClean(&pck);
c70281f8
AJ
979 } else if (request_hdrs) {
980 p = request_hdrs;
62e76326 981 } else {
982 p = "[no request]";
983 }
62e76326 984 break;
985
1d803566 986 case 's':
15b02e9a 987 /* for backward compat we make %s show the full URL. Drop this in some future release. */
1e98e28b 988 if (building_deny_info_url) {
15b02e9a 989 p = request ? urlCanonical(request) : url;
fa84c01d 990 debugs(0, DBG_CRITICAL, "WARNING: deny_info now accepts coded tags. Use %u to get the full URL instead of %s");
05320519 991 } else
15b02e9a 992 p = visible_appname_string;
62e76326 993 break;
994
1d803566 995 case 'S':
1e98e28b 996 if (building_deny_info_url) {
4ad8d23d 997 p = visible_appname_string;
e7aae06b
A
998 break;
999 }
62e76326 1000 /* signature may contain %-escapes, recursion */
c70281f8
AJ
1001 if (page_id != ERR_SQUID_SIGNATURE) {
1002 const int saved_id = page_id;
1003 page_id = ERR_SQUID_SIGNATURE;
1004 MemBuf *sign_mb = BuildContent();
2fe7eff9 1005 mb.Printf("%s", sign_mb->content());
1006 sign_mb->clean();
032785bf 1007 delete sign_mb;
c70281f8 1008 page_id = saved_id;
62e76326 1009 do_quote = 0;
1010 } else {
1011 /* wow, somebody put %S into ERR_SIGNATURE, stop recursion */
1012 p = "[%S]";
1013 }
62e76326 1014 break;
1015
fe40a877 1016 case 't':
20efa1c2 1017 mb.Printf("%s", Time::FormatHttpd(squid_curtime));
62e76326 1018 break;
1019
f8291f8f 1020 case 'T':
2fe7eff9 1021 mb.Printf("%s", mkrfc1123(squid_curtime));
62e76326 1022 break;
1023
fe40a877 1024 case 'U':
b3802bdc
AJ
1025 /* Using the fake-https version of canonical so error pages see https:// */
1026 /* even when the url-path cannot be shown as more than '*' */
e7aae06b
A
1027 if (request)
1028 p = urlCanonicalFakeHttps(request);
1029 else if (url)
1030 p = url;
1e98e28b 1031 else if (!building_deny_info_url)
e7aae06b 1032 p = "[no URL]";
62e76326 1033 break;
1034
76cdc28d 1035 case 'u':
1e98e28b
AJ
1036 if (request)
1037 p = urlCanonical(request);
1038 else if (url)
1039 p = url;
1040 else if (!building_deny_info_url)
1041 p = "[no URL]";
62e76326 1042 break;
1043
fe40a877 1044 case 'w':
62e76326 1045 if (Config.adminEmail)
2fe7eff9 1046 mb.Printf("%s", Config.adminEmail);
1e98e28b 1047 else if (!building_deny_info_url)
62e76326 1048 p = "[unknown]";
62e76326 1049 break;
1050
b5fb34f1 1051 case 'W':
1e98e28b 1052 if (building_deny_info_url) break;
62e76326 1053 if (Config.adminEmail && Config.onoff.emailErrData)
c70281f8 1054 Dump(&mb);
4ad8d23d 1055 no_urlescape = 1;
62e76326 1056 break;
1057
e4a8468d 1058 case 'x':
cb4f4424 1059#if USE_OPENSSL
e4a8468d
CT
1060 if (detail)
1061 mb.Printf("%s", detail->errorName());
0ab68838
CT
1062 else
1063#endif
1064 if (!building_deny_info_url)
1065 p = "[Unknown Error Code]";
e4a8468d
CT
1066 break;
1067
fe40a877 1068 case 'z':
1e98e28b 1069 if (building_deny_info_url) break;
3ff65596
AR
1070 if (dnsError.size() > 0)
1071 p = dnsError.termedBuf();
0477a072
AJ
1072 else if (ftp.cwd_msg)
1073 p = ftp.cwd_msg;
62e76326 1074 else
1075 p = "[unknown]";
62e76326 1076 break;
1077
43ae1d95 1078 case 'Z':
1e98e28b 1079 if (building_deny_info_url) break;
c70281f8
AJ
1080 if (err_msg)
1081 p = err_msg;
43ae1d95 1082 else
1083 p = "[unknown]";
43ae1d95 1084 break;
1085
e347f8e5 1086 case '%':
62e76326 1087 p = "%";
62e76326 1088 break;
1089
9b312a19 1090 default:
2fe7eff9 1091 mb.Printf("%%%c", token);
cbba2ba2 1092 do_quote = 0;
62e76326 1093 break;
9b312a19 1094 }
62e76326 1095
137ee196 1096 if (!p)
f53969cc 1097 p = mb.buf; /* do not use mb after this assignment! */
62e76326 1098
137ee196 1099 assert(p);
62e76326 1100
bf8fe701 1101 debugs(4, 3, "errorConvert: %%" << token << " --> '" << p << "'" );
62e76326 1102
10270faa 1103 if (do_quote)
62e76326 1104 p = html_quote(p);
1105
1e98e28b 1106 if (building_deny_info_url && !no_urlescape)
15b02e9a
AJ
1107 p = rfc1738_escape_part(p);
1108
9b312a19 1109 return p;
8213067d 1110}
e381a13d 1111
15b02e9a 1112void
ced8def3 1113ErrorState::DenyInfoLocation(const char *name, HttpRequest *, MemBuf &result)
15b02e9a
AJ
1114{
1115 char const *m = name;
1116 char const *p = m;
1117 char const *t;
1118
aed9a15b
AJ
1119 if (m[0] == '3')
1120 m += 4; // skip "3xx:"
1121
15b02e9a
AJ
1122 while ((p = strchr(m, '%'))) {
1123 result.append(m, p - m); /* copy */
4d16918e 1124 t = Convert(*++p, true, true); /* convert */
15b02e9a
AJ
1125 result.Printf("%s", t); /* copy */
1126 m = p + 1; /* advance */
1127 }
1128
1129 if (*m)
1130 result.Printf("%s", m); /* copy tail */
1131
1132 assert((size_t)result.contentSize() == strlen(result.content()));
1133}
1134
cb69b4c7 1135HttpReply *
c70281f8 1136ErrorState::BuildHttpReply()
cb69b4c7 1137{
06a5ae20 1138 HttpReply *rep = new HttpReply;
c70281f8 1139 const char *name = errorPageName(page_id);
cb69b4c7 1140 /* no LMT for error pages; error pages expire immediately */
62e76326 1141
d4d63422 1142 if (name[0] == '3' || (name[0] != '2' && name[0] != '4' && name[0] != '5' && strchr(name, ':'))) {
62e76326 1143 /* Redirection */
f11c8e2f 1144 Http::StatusCode status = Http::scFound;
aed9a15b
AJ
1145 // Use configured 3xx reply status if set.
1146 if (name[0] == '3')
1147 status = httpStatus;
1148 else {
1149 // Use 307 for HTTP/1.1 non-GET/HEAD requests.
526ed14e 1150 if (request->method != Http::METHOD_GET && request->method != Http::METHOD_HEAD && request->http_ver >= Http::ProtocolVersion(1,1))
955394ce 1151 status = Http::scTemporaryRedirect;
aed9a15b
AJ
1152 }
1153
ec80c740 1154 rep->setHeaders(status, NULL, "text/html;charset=utf-8", 0, 0, -1);
c44950c4 1155
c70281f8 1156 if (request) {
15b02e9a
AJ
1157 MemBuf redirect_location;
1158 redirect_location.init();
1159 DenyInfoLocation(name, request, redirect_location);
1160 httpHeaderPutStrf(&rep->header, HDR_LOCATION, "%s", redirect_location.content() );
c44950c4 1161 }
1162
c70281f8 1163 httpHeaderPutStrf(&rep->header, HDR_X_SQUID_ERROR, "%d %s", httpStatus, "Access Denied");
76cdc28d 1164 } else {
c70281f8 1165 MemBuf *content = BuildContent();
ec80c740 1166 rep->setHeaders(httpStatus, NULL, "text/html;charset=utf-8", content->contentSize(), 0, -1);
62e76326 1167 /*
1168 * include some information for downstream caches. Implicit
1169 * replaceable content. This isn't quite sufficient. xerrno is not
1170 * necessarily meaningful to another system, so we really should
1171 * expand it. Additionally, we should identify ourselves. Someone
1172 * might want to know. Someone _will_ want to know OTOH, the first
1173 * X-CACHE-MISS entry should tell us who.
1174 */
c70281f8 1175 httpHeaderPutStrf(&rep->header, HDR_X_SQUID_ERROR, "%s %d", name, xerrno);
ccb24616 1176
5bf33a09
AJ
1177#if USE_ERR_LOCALES
1178 /*
1179 * If error page auto-negotiate is enabled in any way, send the Vary.
1180 * RFC 2616 section 13.6 and 14.44 says MAY and SHOULD do this.
1181 * We have even better reasons though:
1182 * see http://wiki.squid-cache.org/KnowledgeBase/VaryNotCaching
1183 */
9e008dda 1184 if (!Config.errorDirectory) {
5bf33a09 1185 /* We 'negotiated' this ONLY from the Accept-Language. */
70d1b64c
AJ
1186 rep->header.delById(HDR_VARY);
1187 rep->header.putStr(HDR_VARY, "Accept-Language");
5bf33a09
AJ
1188 }
1189
1190 /* add the Content-Language header according to RFC section 14.12 */
9e008dda 1191 if (err_language) {
70d1b64c 1192 rep->header.putStr(HDR_CONTENT_LANGUAGE, err_language);
9e008dda 1193 } else
5bf33a09
AJ
1194#endif /* USE_ERROR_LOCALES */
1195 {
ccb24616 1196 /* default templates are in English */
5bf33a09 1197 /* language is known unless error_directory override used */
9e008dda 1198 if (!Config.errorDirectory)
70d1b64c 1199 rep->header.putStr(HDR_CONTENT_LANGUAGE, "en");
ccb24616
AJ
1200 }
1201
0521f8be 1202 rep->body.setMb(content);
032785bf 1203 /* do not memBufClean() or delete the content, it was absorbed by httpBody */
76cdc28d 1204 }
62e76326 1205
7a957a93
AR
1206 // Make sure error codes get back to the client side for logging and
1207 // error tracking.
129fe2a1
CT
1208 if (request) {
1209 int edc = ERR_DETAIL_NONE; // error detail code
cb4f4424 1210#if USE_OPENSSL
129fe2a1
CT
1211 if (detail)
1212 edc = detail->errorNo();
1213 else
1214#endif
1215 if (detailCode)
1216 edc = detailCode;
1217 else
1218 edc = xerrno;
1219 request->detailError(type, edc);
1220 }
1221
cb69b4c7 1222 return rep;
1223}
1224
c70281f8
AJ
1225MemBuf *
1226ErrorState::BuildContent()
cb69b4c7 1227{
43000484 1228 const char *m = NULL;
43000484 1229
c70281f8 1230 assert(page_id > ERR_NONE && page_id < error_page_count);
43000484
AJ
1231
1232#if USE_ERR_LOCALES
8ff2520a 1233 ErrorPageFile *localeTmpl = NULL;
43000484
AJ
1234
1235 /** error_directory option in squid.conf overrides translations.
30b78ef1 1236 * Custom errors are always found either in error_directory or the templates directory.
43000484
AJ
1237 * Otherwise locate the Accept-Language header
1238 */
02259ff8
CT
1239 if (!Config.errorDirectory && page_id < ERR_MAX) {
1240 if (err_language && err_language != Config.errorDefaultLanguage)
1241 safe_free(err_language);
1242
8ff2520a 1243 localeTmpl = new ErrorPageFile(err_type_str[page_id], static_cast<err_type>(page_id));
02259ff8
CT
1244 if (localeTmpl->loadFor(request)) {
1245 m = localeTmpl->text();
1246 assert(localeTmpl->language());
1247 err_language = xstrdup(localeTmpl->language());
43000484
AJ
1248 }
1249 }
1250#endif /* USE_ERR_LOCALES */
1251
1252 /** \par
1253 * If client-specific error templates are not enabled or available.
1254 * fall back to the old style squid.conf settings.
1255 */
9e008dda 1256 if (!m) {
43000484 1257 m = error_text[page_id];
5bf33a09 1258#if USE_ERR_LOCALES
9e008dda 1259 if (!Config.errorDirectory)
5bf33a09
AJ
1260 err_language = Config.errorDefaultLanguage;
1261#endif
58d4b38b 1262 debugs(4, 2, HERE << "No existing error page language negotiated for " << errorPageName(page_id) << ". Using default error file.");
43000484
AJ
1263 }
1264
6551ae9d 1265 MemBuf *result = ConvertText(m, true);
960cb599 1266#if USE_ERR_LOCALES
02259ff8
CT
1267 if (localeTmpl)
1268 delete localeTmpl;
960cb599 1269#endif
6551ae9d 1270 return result;
4d16918e
CT
1271}
1272
1273MemBuf *ErrorState::ConvertText(const char *text, bool allowRecursion)
1274{
1275 MemBuf *content = new MemBuf;
1276 const char *p;
1277 const char *m = text;
1d803566 1278 assert(m);
43000484 1279 content->init();
62e76326 1280
cb69b4c7 1281 while ((p = strchr(m, '%'))) {
f53969cc
SM
1282 content->append(m, p - m); /* copy */
1283 const char *t = Convert(*++p, false, allowRecursion); /* convert */
1284 content->Printf("%s", t); /* copy */
1285 m = p + 1; /* advance */
cb69b4c7 1286 }
62e76326 1287
1d803566 1288 if (*m)
f53969cc 1289 content->Printf("%s", m); /* copy tail */
62e76326 1290
857e4512
AJ
1291 content->terminate();
1292
032785bf 1293 assert((size_t)content->contentSize() == strlen(content->content()));
62e76326 1294
cb69b4c7 1295 return content;
1296}
f53969cc 1297