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