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