/*
- * $Id: errorpage.cc,v 1.230 2008/02/26 21:49:34 amosjeffries Exp $
+ * $Id$
*
* DEBUG: section 4 Error Generation
* AUTHOR: Duane Wessels
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
- *
+ *
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
- *
+ *
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
#include "config.h"
#include "errorpage.h"
-#include "AuthUserRequest.h"
+#include "auth/UserRequest.h"
#include "SquidTime.h"
#include "Store.h"
#include "HttpReply.h"
/* local types */
/// \ingroup ErrorPageInternal
-typedef struct
-{
+typedef struct {
int id;
char *page_name;
} ErrorDynamicPageInfo;
\note hard coded error messages are not appended with %S
* automagically to give you more control on the format
*/
-static const struct
-{
+static const struct {
int type; /* and page_id */
const char *text;
}
error_hard_text[] = {
- {
- ERR_SQUID_SIGNATURE,
- "\n<br>\n"
- "<hr>\n"
- "<div id=\"footer\">\n"
- "Generated %T by %h (%s)\n"
- "</div>\n"
- "</body></html>\n"
- },
- {
- TCP_RESET,
- "reset"
- }
- };
+ {
+ ERR_SQUID_SIGNATURE,
+ "\n<br>\n"
+ "<hr>\n"
+ "<div id=\"footer\">\n"
+ "Generated %T by %h (%s)\n"
+ "</div>\n"
+ "</body></html>\n"
+ },
+ {
+ TCP_RESET,
+ "reset"
+ }
+};
/// \ingroup ErrorPageInternal
static Vector<ErrorDynamicPageInfo *> ErrorDynamicPages;
/// \ingroup ErrorPageInternal
static int error_page_count = 0;
+/// \ingroup ErrorPageInternal
+static MemBuf error_stylesheet;
+
static char *errorTryLoadText(const char *page_name, const char *dir, bool silent = false);
static char *errorLoadText(const char *page_name);
static const char *errorFindHardText(err_type type);
}
}
}
+
+ error_stylesheet.reset();
+
+ // look for and load stylesheet into global MemBuf for it.
+ if (Config.errorStylesheet) {
+ char *temp = errorTryLoadText(Config.errorStylesheet,NULL);
+ if (temp) {
+ error_stylesheet.Printf("%s",temp);
+ safe_free(temp);
+ }
+ }
}
void
char *text = NULL;
/** test error_directory configured location */
- if(Config.errorDirectory)
+ if (Config.errorDirectory)
text = errorTryLoadText(page_name, Config.errorDirectory);
#if USE_ERR_LOCALES
/** test error_default_language location */
- if(!text && Config.errorDefaultLanguage) {
+ if (!text && Config.errorDefaultLanguage) {
char dir[256];
snprintf(dir,256,"%s/%s", DEFAULT_SQUID_ERROR_DIR, Config.errorDefaultLanguage);
text = errorTryLoadText(page_name, dir);
- if(!text) {
+ if (!text) {
debugs(1, DBG_CRITICAL, "Unable to load default error language files. Reset to backups.");
}
}
ssize_t len;
MemBuf textbuf;
- snprintf(path, sizeof(path), "%s/%s", dir, page_name);
+ // maybe received compound parts, maybe an absolute page_name and no dir
+ if (dir)
+ snprintf(path, sizeof(path), "%s/%s", dir, page_name);
+ else
+ snprintf(path, sizeof(path), "%s", page_name);
+
fd = file_open(path, O_RDONLY | O_TEXT);
if (fd < 0) {
/* with dynamic locale negotiation we may see some failures before a success. */
- if(!silent)
+ if (!silent)
debugs(4, DBG_CRITICAL, HERE << "'" << path << "': " << xstrerror());
return NULL;
}
textbuf.init();
- while((len = FD_READ_METHOD(fd, buf, sizeof(buf))) > 0) {
+ while ((len = FD_READ_METHOD(fd, buf, sizeof(buf))) > 0) {
textbuf.append(buf, len);
}
file_close(fd);
- if (strstr(textbuf.buf, "%s") == NULL)
- textbuf.append("%S", 2); /* add signature */
-
/* Shrink memory size down to exact size. MemBuf has a tencendy
* to be rather large..
*/
* to the client socket.
*
\note If there is a callback, the callback is responsible for
- * closeing the FD, otherwise we do it ourseves.
+ * closing the FD, otherwise we do it ourselves.
*/
static void
errorSendComplete(int fd, char *bufnotused, size_t size, comm_err_t errflag, int xerrno, void *data)
HTTPMSGUNLOCK(err->request);
safe_free(err->redirect_url);
safe_free(err->url);
- safe_free(err->dnsserver_msg);
safe_free(err->request_hdrs);
wordlistDestroy(&err->ftp.server_msg);
safe_free(err->ftp.request);
AUTHUSERREQUESTUNLOCK(err->auth_user_request, "errstate");
safe_free(err->err_msg);
#if USE_ERR_LOCALES
- if(err->err_language != Config.errorDefaultLanguage)
+ if (err->err_language != Config.errorDefaultLanguage)
#endif
safe_free(err->err_language);
cbdataFree(err);
if (auth_user_request->denyMessage())
str.Printf("Auth ErrMsg: %s\r\n", auth_user_request->denyMessage());
- if (dnsserver_msg) {
- str.Printf("DNS Server ErrMsg: %s\r\n", dnsserver_msg);
- }
+ if (dnsError.size() > 0)
+ str.Printf("DNS ErrMsg: %s\r\n", dnsError.termedBuf());
/* - TimeStamp */
str.Printf("TimeStamp: %s\r\n\r\n", mkrfc1123(squid_curtime));
/* - IP stuff */
str.Printf("ClientIP: %s\r\n", src_addr.NtoA(ntoabuf,MAX_IPSTRLEN));
- if (request && request->hier.host) {
+ if (request && request->hier.host[0] != '\0') {
str.Printf("ServerIP: %s\r\n", request->hier.host);
}
if (NULL != request) {
Packer p;
- str.Printf("%s %s HTTP/%d.%d\n",
+ String urlpath_or_slash;
+
+ if (request->urlpath.size() != 0)
+ urlpath_or_slash = request->urlpath;
+ else
+ urlpath_or_slash = "/";
+
+ str.Printf("%s " SQUIDSTRINGPH " HTTP/%d.%d\n",
RequestMethodStr(request->method),
- request->urlpath.size() ? request->urlpath.buf() : "/",
+ SQUIDSTRINGPRINT(urlpath_or_slash),
request->http_ver.major, request->http_ver.minor);
packerToMemInit(&p, &str);
request->header.packInto(&p);
case 'h':
mb.Printf("%s", getMyHostname());
-
break;
case 'H':
if (request) {
- if (request->hier.host)
+ if (request->hier.host[0] != '\0') // if non-empty string.
p = request->hier.host;
else
p = request->GetHost();
break;
case 'I':
- if (request && request->hier.host) {
+ if (request && request->hier.host[0] != '\0') // if non-empty string
mb.Printf("%s", request->hier.host);
- } else
+ else
p = "[unknown]";
break;
+ case 'l':
+ mb.append(error_stylesheet.content(), error_stylesheet.contentSize());
+ do_quote = 0;
+ break;
+
case 'L':
if (Config.errHtmlText) {
mb.Printf("%s", Config.errHtmlText);
if (NULL != request) {
Packer p;
- mb.Printf("%s %s HTTP/%d.%d\n",
+ String urlpath_or_slash;
+
+ if (request->urlpath.size() != 0)
+ urlpath_or_slash = request->urlpath;
+ else
+ urlpath_or_slash = "/";
+
+ mb.Printf("%s " SQUIDSTRINGPH " HTTP/%d.%d\n",
RequestMethodStr(request->method),
- request->urlpath.size() ? request->urlpath.buf() : "/",
+ SQUIDSTRINGPRINT(urlpath_or_slash),
request->http_ver.major, request->http_ver.minor);
packerToMemInit(&p, &mb);
request->header.packInto(&p);
break;
case 'U':
- p = request ? urlCanonicalClean(request) : url ? url : "[no URL]";
+ /* Using the fake-https version of canonical so error pages see https:// */
+ /* even when the url-path cannot be shown as more than '*' */
+ p = request ? urlCanonicalFakeHttps(request) : url ? url : "[no URL]";
break;
case 'u':
break;
case 'z':
- if (dnsserver_msg)
- p = dnsserver_msg;
+ if (dnsError.size() > 0)
+ p = dnsError.termedBuf();
else
p = "[unknown]";
if (strchr(name, ':')) {
/* Redirection */
- rep->setHeaders(version, HTTP_MOVED_TEMPORARILY, NULL, "text/html", 0, 0, squid_curtime);
+ rep->setHeaders(version, HTTP_MOVED_TEMPORARILY, NULL, "text/html", 0, 0, -1);
if (request) {
char *quoted_url = rfc1738_escape_part(urlCanonical(request));
httpHeaderPutStrf(&rep->header, HDR_X_SQUID_ERROR, "%d %s", httpStatus, "Access Denied");
} else {
MemBuf *content = BuildContent();
- rep->setHeaders(version, httpStatus, NULL, "text/html", content->contentSize(), 0, squid_curtime);
+ rep->setHeaders(version, httpStatus, NULL, "text/html", content->contentSize(), 0, -1);
/*
* include some information for downstream caches. Implicit
* replaceable content. This isn't quite sufficient. xerrno is not
* We have even better reasons though:
* see http://wiki.squid-cache.org/KnowledgeBase/VaryNotCaching
*/
- if(!Config.errorDirectory) {
+ if (!Config.errorDirectory) {
/* We 'negotiated' this ONLY from the Accept-Language. */
- httpHeaderDelById(&rep->header, HDR_VARY);
- httpHeaderPutStrf(&rep->header, HDR_VARY, "Accept-Language");
+ rep->header.delById(HDR_VARY);
+ rep->header.putStr(HDR_VARY, "Accept-Language");
}
/* add the Content-Language header according to RFC section 14.12 */
- if(err_language) {
- httpHeaderPutStrf(&rep->header, HDR_CONTENT_LANGUAGE, "%s", err_language);
- }
- else
+ if (err_language) {
+ rep->header.putStr(HDR_CONTENT_LANGUAGE, err_language);
+ } else
#endif /* USE_ERROR_LOCALES */
{
/* default templates are in English */
/* language is known unless error_directory override used */
- if(!Config.errorDirectory)
- httpHeaderPutStrf(&rep->header, HDR_CONTENT_LANGUAGE, "en");
+ if (!Config.errorDirectory)
+ rep->header.putStr(HDR_CONTENT_LANGUAGE, "en");
}
httpBodySet(&rep->body, content);
/** error_directory option in squid.conf overrides translations.
* Otherwise locate the Accept-Language header
*/
- if(!Config.errorDirectory && request->header.getList(HDR_ACCEPT_LANGUAGE, &hdr) ) {
+ if (!Config.errorDirectory && request && request->header.getList(HDR_ACCEPT_LANGUAGE, &hdr) ) {
- const char *buf = hdr.buf(); // raw header string for parsing
- int pos = 0; // current parsing position in header string
+ size_t pos = 0; // current parsing position in header string
char *reset = NULL; // where to reset the p pointer for each new tag file
char *dt = NULL;
debugs(4, 6, HERE << "Testing Header: '" << hdr << "'");
- while( pos < hdr.size() ) {
+ while ( pos < hdr.size() ) {
-/*
- * Header value format:
- * - sequence of whitespace delimited tags
- * - each tag may suffix with ';'.* which we can ignore.
- * - IFF a tag contains only two characters we can wildcard ANY translations matching: <it> '-'? .*
- * with preference given to an exact match.
- */
- while(pos < hdr.size() && buf[pos] != ';' && buf[pos] != ',' && !xisspace(buf[pos]) && dt < (dir+256) ) {
- *dt++ = xtolower(buf[pos++]);
+ /* skip any initial whitespace. */
+ while (pos < hdr.size() && xisspace(hdr[pos])) pos++;
+
+ /*
+ * Header value format:
+ * - sequence of whitespace delimited tags
+ * - each tag may suffix with ';'.* which we can ignore.
+ * - IFF a tag contains only two characters we can wildcard ANY translations matching: <it> '-'? .*
+ * with preference given to an exact match.
+ */
+ bool invalid_byte = false;
+ while (pos < hdr.size() && hdr[pos] != ';' && hdr[pos] != ',' && !xisspace(hdr[pos]) && dt < (dir+256) ) {
+ if (!invalid_byte) {
+#if HTTP_VIOLATIONS
+ // if accepting violations we may as well accept some broken browsers
+ // which may send us the right code, wrong ISO formatting.
+ if (hdr[pos] == '_')
+ *dt = '-';
+ else
+#endif
+ *dt = xtolower(hdr[pos]);
+ // valid codes only contain A-Z, hyphen (-) and *
+ if (*dt != '-' && *dt != '*' && (*dt < 'a' || *dt > 'z') )
+ invalid_byte = true;
+ else
+ dt++; // move to next destination byte.
+ }
+ pos++;
}
*dt++ = '\0'; // nul-terminated the filename content string before system use.
- debugs(4, 9, HERE << "STATE: dt='" << dt << "', reset='" << reset << "', reset[1]='" << reset[1] << "', pos=" << pos << ", buf='" << &buf[pos] << "'");
+ debugs(4, 9, HERE << "STATE: dt='" << dt << "', reset='" << reset << "', pos=" << pos << ", buf='" << ((pos < hdr.size()) ? hdr.substr(pos,hdr.size()) : "") << "'");
/* if we found anything we might use, try it. */
- if(*reset != '\0') {
+ if (*reset != '\0' && !invalid_byte) {
+
+ /* wildcard uses the configured default language */
+ if (reset[0] == '*' && reset[1] == '\0') {
+ debugs(4, 6, HERE << "Found language '" << reset << "'. Using configured default.");
+ m = error_text[page_id];
+ if (!Config.errorDirectory)
+ err_language = Config.errorDefaultLanguage;
+ break;
+ }
debugs(4, 6, HERE << "Found language '" << reset << "', testing for available template in: '" << dir << "'");
+
m = errorTryLoadText( err_type_str[page_id], dir, false);
- if(m) {
+ if (m) {
/* store the language we found for the Content-Language reply header */
err_language = xstrdup(reset);
break;
+ } else if (Config.errorLogMissingLanguages) {
+ debugs(4, DBG_IMPORTANT, "WARNING: Error Pages Missing Language: " << reset);
}
#if HAVE_GLOB
- if( (dt - reset) == 2) {
+ if ( (dt - reset) == 2) {
/* TODO glob the error directory for sub-dirs matching: <tag> '-*' */
/* use first result. */
debugs(4,2, HERE << "wildcard fallback errors not coded yet.");
dt = reset; // reset for next tag testing. we replace the failed name instead of cloning.
- // IFF we terminated the tag on ';' we need to skip the 'q=' bit to the next ',' or end.
- while(pos < hdr.size() && buf[pos] != ',') pos++;
- if(buf[pos] == ',') pos++;
+ // IFF we terminated the tag on whitespace or ';' we need to skip to the next ',' or end of header.
+ while (pos < hdr.size() && hdr[pos] != ',') pos++;
+ if (hdr[pos] == ',') pos++;
}
}
#endif /* USE_ERR_LOCALES */
* If client-specific error templates are not enabled or available.
* fall back to the old style squid.conf settings.
*/
- if(!m) {
+ if (!m) {
m = error_text[page_id];
#if USE_ERR_LOCALES
- if(!Config.errorDirectory)
+ if (!Config.errorDirectory)
err_language = Config.errorDefaultLanguage;
#endif
debugs(4, 2, HERE << "No existing error page language negotiated for " << errorPageName(page_id) << ". Using default error file.");