]> git.ipfire.org Git - thirdparty/squid.git/blobdiff - src/errorpage.cc
Removed squid-old.h
[thirdparty/squid.git] / src / errorpage.cc
index d4f7ee7bc922162d502aba46c478f1c5417cc50a..e37e51063fa802b160b517111de546a149f32e46 100644 (file)
  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
  *
  */
-#include "config.h"
+#include "squid.h"
+#include "comm/Connection.h"
 #include "comm/Write.h"
+#include "err_detail_type.h"
 #include "errorpage.h"
+#if USE_AUTH
 #include "auth/UserRequest.h"
+#endif
 #include "SquidTime.h"
+#if USE_SSL
+#include "ssl/ErrorDetailManager.h"
+#endif
 #include "Store.h"
 #include "html_quote.h"
 #include "HttpReply.h"
 #include "MemObject.h"
 #include "fde.h"
 #include "MemBuf.h"
+#include "protos.h"
 #include "rfc1738.h"
 #include "URLScheme.h"
 #include "wordlist.h"
-#include "err_detail_type.h"
 
 /**
  \defgroup ErrorPageInternal Error Page Internals
@@ -60,7 +67,7 @@
  */
 
 
-#ifndef DEFAULT_SQUID_ERROR_DIR
+#if !defined(DEFAULT_SQUID_ERROR_DIR)
 /** Where to look for errors if config path fails.
  \note Please use ./configure --datadir=/path instead of patching
  */
@@ -126,13 +133,31 @@ 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);
 static ErrorDynamicPageInfo *errorDynamicPageInfoCreate(int id, const char *page_name);
 static void errorDynamicPageInfoDestroy(ErrorDynamicPageInfo * info);
 static IOCB errorSendComplete;
 
+/// \ingroup ErrorPageInternal
+/// manages an error page template
+class ErrorPageFile: public TemplateFile
+{
+public:
+    ErrorPageFile(const char *name, const err_type code): TemplateFile(name,code) { textBuf.init();}
+
+    /// The template text data read from disk
+    const char *text() { return textBuf.content(); }
+
+private:
+    /// stores the data read from disk to a local buffer
+    virtual bool parse(const char *buf, int len, bool eof) {
+        if (len)
+            textBuf.append(buf, len);
+        return true;
+    }
+
+    MemBuf textBuf; ///< A buffer to store the error page
+};
 
 /// \ingroup ErrorPageInternal
 err_type &operator++ (err_type &anErr)
@@ -171,8 +196,8 @@ errorInitialize(void)
              *  (a) default language translation directory (error_default_language)
              *  (b) admin specified custom directory (error_directory)
              */
-            error_text[i] = errorLoadText(err_type_str[i]);
-
+            ErrorPageFile errTmpl(err_type_str[i], i);
+            error_text[i] = errTmpl.loadDefault() ? xstrdup(errTmpl.text()) : NULL;
         } else {
             /** \par
              * Index any unknown file names used by deny_info.
@@ -186,7 +211,8 @@ errorInitialize(void)
 
             if (strchr(pg, ':') == NULL) {
                 /** But only if they are not redirection URL. */
-                error_text[i] = errorLoadText(pg);
+                ErrorPageFile errTmpl(pg, ERR_MAX);
+                error_text[i] = errTmpl.loadDefault() ? xstrdup(errTmpl.text()) : NULL;
             }
         }
     }
@@ -195,12 +221,14 @@ errorInitialize(void)
 
     // 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);
-        }
+        ErrorPageFile tmpl("StylesSheet", ERR_MAX);
+        tmpl.loadFromFile(Config.errorStylesheet);
+        error_stylesheet.Printf("%s",tmpl.text());
     }
+
+#if USE_SSL
+    Ssl::errorDetailInitialize();
+#endif
 }
 
 void
@@ -209,7 +237,7 @@ errorClean(void)
     if (error_text) {
         int i;
 
-        for (i = ERR_NONE + 1; i < error_page_count; i++)
+        for (i = ERR_NONE + 1; i < error_page_count; ++i)
             safe_free(error_text[i]);
 
         safe_free(error_text);
@@ -219,6 +247,10 @@ errorClean(void)
         errorDynamicPageInfoDestroy(ErrorDynamicPages.pop_back());
 
     error_page_count = 0;
+
+#if USE_SSL
+    Ssl::errorDetailClean();
+#endif
 }
 
 /// \ingroup ErrorPageInternal
@@ -227,85 +259,108 @@ errorFindHardText(err_type type)
 {
     int i;
 
-    for (i = 0; i < error_hard_text_count; i++)
+    for (i = 0; i < error_hard_text_count; ++i)
         if (error_hard_text[i].type == type)
             return error_hard_text[i].text;
 
     return NULL;
 }
 
-/**
- * \ingroup ErrorPageInternal
- *
- * Load into the in-memory error text Index a file probably available at:
- *  (a) admin specified custom directory (error_directory)
- *  (b) default language translation directory (error_default_language)
- *  (c) English sub-directory where errors should ALWAYS exist
- */
-static char *
-errorLoadText(const char *page_name)
+TemplateFile::TemplateFile(const char *name, const err_type code): silent(false), wasLoaded(false), templateName(name), templateCode(code)
 {
-    char *text = NULL;
+    assert(name);
+}
+
+bool
+TemplateFile::loadDefault()
+{
+    if (loaded()) // already loaded?
+        return true;
 
     /** test error_directory configured location */
-    if (Config.errorDirectory)
-        text = errorTryLoadText(page_name, Config.errorDirectory);
+    if (Config.errorDirectory) {
+        char path[MAXPATHLEN];
+        snprintf(path, sizeof(path), "%s/%s", Config.errorDirectory, templateName.termedBuf());
+        loadFromFile(path);
+    }
 
 #if USE_ERR_LOCALES
     /** test error_default_language location */
-    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) {
-            debugs(1, DBG_CRITICAL, "Unable to load default error language files. Reset to backups.");
+    if (!loaded() && Config.errorDefaultLanguage) {
+        if (!tryLoadTemplate(Config.errorDefaultLanguage)) {
+            debugs(1, (templateCode < TCP_RESET ? DBG_CRITICAL : 3), "Unable to load default error language files. Reset to backups.");
         }
     }
 #endif
 
     /* test default location if failed (templates == English translation base templates) */
-    if (!text) {
-        text = errorTryLoadText(page_name, DEFAULT_SQUID_ERROR_DIR"/templates");
+    if (!loaded()) {
+        tryLoadTemplate("templates");
     }
 
     /* giving up if failed */
-    if (!text)
-        fatal("failed to find or read error text file.");
+    if (!loaded()) {
+        debugs(1, (templateCode < TCP_RESET ? DBG_CRITICAL : 3), "WARNING: failed to find or read error text file " << templateName);
+        parse("Internal Error: Missing Template ", 33, '\0');
+        parse(templateName.termedBuf(), templateName.size(), '\0');
+    }
 
-    return text;
+    return true;
 }
 
-/// \ingroup ErrorPageInternal
-static char *
-errorTryLoadText(const char *page_name, const char *dir, bool silent)
+bool
+TemplateFile::tryLoadTemplate(const char *lang)
 {
-    int fd;
+    assert(lang);
+
     char path[MAXPATHLEN];
+    /* TODO: prep the directory path string to prevent snprintf ... */
+    snprintf(path, sizeof(path), "%s/%s/%s",
+             DEFAULT_SQUID_ERROR_DIR, lang, templateName.termedBuf());
+    path[MAXPATHLEN-1] = '\0';
+
+    if (loadFromFile(path))
+        return true;
+
+#if HAVE_GLOB
+    if ( strlen(lang) == 2) {
+        /* TODO glob the error directory for sub-dirs matching: <tag> '-*'   */
+        /* use first result. */
+        debugs(4,2, HERE << "wildcard fallback errors not coded yet.");
+    }
+#endif
+
+    return false;
+}
+
+bool
+TemplateFile::loadFromFile(const char *path)
+{
+    int fd;
     char buf[4096];
-    char *text;
     ssize_t len;
-    MemBuf textbuf;
 
-    // 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);
+    if (loaded()) // already loaded?
+        return true;
 
     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 && templateCode < TCP_RESET)
             debugs(4, DBG_CRITICAL, HERE << "'" << path << "': " << xstrerror());
-        return NULL;
+        wasLoaded = false;
+        return wasLoaded;
     }
 
-    textbuf.init();
-
     while ((len = FD_READ_METHOD(fd, buf, sizeof(buf))) > 0) {
-        textbuf.append(buf, len);
+        if (!parse(buf, len, false)) {
+            debugs(4, DBG_CRITICAL, HERE << " parse error while reading template file: " << path);
+            wasLoaded = false;
+            return wasLoaded;
+        }
     }
+    parse(buf, 0, true);
 
     if (len < 0) {
         debugs(4, DBG_CRITICAL, HERE << "failed to fully read: '" << path << "': " << xstrerror());
@@ -313,14 +368,103 @@ errorTryLoadText(const char *page_name, const char *dir, bool silent)
 
     file_close(fd);
 
-    /* Shrink memory size down to exact size. MemBuf has a tencendy
-     * to be rather large..
-     */
-    text = xstrdup(textbuf.buf);
+    wasLoaded = true;
+    return wasLoaded;
+}
+
+bool strHdrAcptLangGetItem(const String &hdr, char *lang, int langLen, size_t &pos)
+{
+    while (pos < hdr.size()) {
+        char *dt = lang;
+
+        if (!pos) {
+            /* skip any initial whitespace. */
+            while (pos < hdr.size() && xisspace(hdr[pos]))
+                ++pos;
+        } else {
+            // 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;
+        }
+
+        /*
+         * 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 < (lang + (langLen -1)) ) {
+            if (!invalid_byte) {
+#if USE_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.
+        ++dt;
+
+        debugs(4, 9, HERE << "STATE: dt='" << dt << "', lang='" << lang << "', pos=" << pos << ", buf='" << ((pos < hdr.size()) ? hdr.substr(pos,hdr.size()) : "") << "'");
+
+        /* if we found anything we might use, try it. */
+        if (*lang != '\0' && !invalid_byte)
+            return true;
+    }
+    return false;
+}
+
+bool
+TemplateFile::loadFor(HttpRequest *request)
+{
+    String hdr;
+
+#if USE_ERR_LOCALES
+    if (loaded()) // already loaded?
+        return true;
+
+    if (!request || !request->header.getList(HDR_ACCEPT_LANGUAGE, &hdr) )
+        return false;
+
+    char lang[256];
+    size_t pos = 0; // current parsing position in header string
+
+    debugs(4, 6, HERE << "Testing Header: '" << hdr << "'");
+
+    while ( strHdrAcptLangGetItem(hdr, lang, 256, pos) ) {
 
-    textbuf.clean();
+        /* wildcard uses the configured default language */
+        if (lang[0] == '*' && lang[1] == '\0') {
+            debugs(4, 6, HERE << "Found language '" << lang << "'. Using configured default.");
+            return false;
+        }
+
+        debugs(4, 6, HERE << "Found language '" << lang << "', testing for available template");
 
-    return text;
+        if (tryLoadTemplate(lang)) {
+            /* store the language we found for the Content-Language reply header */
+            errLanguage = lang;
+            break;
+        } else if (Config.errorLogMissingLanguages) {
+            debugs(4, DBG_IMPORTANT, "WARNING: Error Pages Missing Language: " << lang);
+        }
+    }
+#endif
+
+    return loaded();
 }
 
 /// \ingroup ErrorPageInternal
@@ -351,15 +495,15 @@ errorDynamicPageInfoCreate(int id, const char *page_name)
         self_destruct();
     } else if ( /* >= 200 && */ info->page_redirect < 300 && strchr(&(page_name[4]), ':')) {
         // 2xx require a local template file
-        debugs(0, DBG_CRITICAL, "FATAL: status " << info->page_redirect << " is not valid on '" << page_name << "'");
+        debugs(0, DBG_CRITICAL, "FATAL: status " << info->page_redirect << " requires a template on '" << page_name << "'");
         self_destruct();
-    } else if (/* >= 300 && */ info->page_redirect <= 399 && !strchr(&(page_name[4]), ':')) {
+    } else if (info->page_redirect >= 300 && info->page_redirect <= 399 && !strchr(&(page_name[4]), ':')) {
         // 3xx require an absolute URL
-        debugs(0, DBG_CRITICAL, "FATAL: status " << info->page_redirect << " is not valid on '" << page_name << "'");
+        debugs(0, DBG_CRITICAL, "FATAL: status " << info->page_redirect << " requires a URL on '" << page_name << "'");
         self_destruct();
     } else if (info->page_redirect >= 400 /* && <= 599 */ && strchr(&(page_name[4]), ':')) {
         // 4xx/5xx require a local template file
-        debugs(0, DBG_CRITICAL, "FATAL: status " << info->page_redirect << " is not valid on '" << page_name << "'");
+        debugs(0, DBG_CRITICAL, "FATAL: status " << info->page_redirect << " requires a template on '" << page_name << "'");
         self_destruct();
     }
     // else okay.
@@ -380,12 +524,12 @@ errorDynamicPageInfoDestroy(ErrorDynamicPageInfo * info)
 static int
 errorPageId(const char *page_name)
 {
-    for (int i = 0; i < ERR_MAX; i++) {
+    for (int i = 0; i < ERR_MAX; ++i) {
         if (strcmp(err_type_str[i], page_name) == 0)
             return i;
     }
 
-    for (size_t j = 0; j < ErrorDynamicPages.size(); j++) {
+    for (size_t j = 0; j < ErrorDynamicPages.size(); ++j) {
         if (strcmp(ErrorDynamicPages.items[j]->page_name, page_name) == 0)
             return j + ERR_MAX;
     }
@@ -421,24 +565,41 @@ errorPageName(int pageId)
     return "ERR_UNKNOWN";      /* should not happen */
 }
 
-ErrorState *
-errorCon(err_type type, http_status status, HttpRequest * request)
+ErrorState::ErrorState(err_type t, http_status status, HttpRequest * req) :
+        type(t),
+        page_id(t),
+        err_language(NULL),
+        httpStatus(status),
+#if USE_AUTH
+        auth_user_request (NULL),
+#endif
+        request(NULL),
+        url(NULL),
+        xerrno(0),
+        port(0),
+        dnsError(),
+        ttl(0),
+        src_addr(),
+        redirect_url(NULL),
+        callback(NULL),
+        callback_data(NULL),
+        request_hdrs(NULL),
+        err_msg(NULL),
+#if USE_SSL
+        detail(NULL),
+#endif
+        detailCode(ERR_DETAIL_NONE)
 {
-    ErrorState *err = new ErrorState;
-    err->page_id = type;       /* has to be reset manually if needed */
-    err->err_language = NULL;
-    err->type = type;
-    err->httpStatus = status;
-    if (err->page_id >= ERR_MAX && ErrorDynamicPages.items[err->page_id - ERR_MAX]->page_redirect != HTTP_STATUS_NONE)
-        err->httpStatus = ErrorDynamicPages.items[err->page_id - ERR_MAX]->page_redirect;
-
-    if (request != NULL) {
-        err->request = HTTPMSGLOCK(request);
-        err->src_addr = request->client_addr;
-        request->detailError(type, ERR_DETAIL_NONE);
-    }
+    memset(&flags, 0, sizeof(flags));
+    memset(&ftp, 0, sizeof(ftp));
 
-    return err;
+    if (page_id >= ERR_MAX && ErrorDynamicPages.items[page_id - ERR_MAX]->page_redirect != HTTP_STATUS_NONE)
+        httpStatus = ErrorDynamicPages.items[page_id - ERR_MAX]->page_redirect;
+
+    if (req != NULL) {
+        request = HTTPMSGLOCK(req);
+        src_addr = req->client_addr;
+    }
 }
 
 void
@@ -459,7 +620,7 @@ errorAppendEntry(StoreEntry * entry, ErrorState * err)
          */
         assert(EBIT_TEST(entry->flags, ENTRY_ABORTED));
         assert(entry->mem_obj->nclients == 0);
-        errorStateFree(err);
+        delete err;
         return;
     }
 
@@ -479,22 +640,15 @@ errorAppendEntry(StoreEntry * entry, ErrorState * err)
     entry->negativeCache();
     entry->releaseRequest();
     entry->unlock();
-    errorStateFree(err);
+    delete err;
 }
 
 void
-errorSend(int fd, ErrorState * err)
+errorSend(const Comm::ConnectionPointer &conn, ErrorState * err)
 {
     HttpReply *rep;
-    debugs(4, 3, "errorSend: FD " << fd << ", err=" << err);
-    assert(fd >= 0);
-    /*
-     * ugh, this is how we make sure error codes get back to
-     * the client side for logging and error tracking.
-     */
-
-    if (err->request)
-        err->request->detailError(err->type, err->xerrno);
+    debugs(4, 3, HERE << conn << ", err=" << err);
+    assert(Comm::IsConnOpen(conn));
 
     /* moved in front of errorBuildBuf @?@ */
     err->flags.flag_cbdata = 1;
@@ -504,7 +658,7 @@ errorSend(int fd, ErrorState * err)
     MemBuf *mb = rep->pack();
     AsyncCall::Pointer call = commCbCall(78, 5, "errorSendComplete",
                                          CommIoCbPtrFun(&errorSendComplete, err));
-    Comm::Write(fd, mb, call);
+    Comm::Write(conn, mb, call);
     delete mb;
 
     delete rep;
@@ -520,51 +674,50 @@ errorSend(int fd, ErrorState * err)
  *     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)
+errorSendComplete(const Comm::ConnectionPointer &conn, char *bufnotused, size_t size, comm_err_t errflag, int xerrno, void *data)
 {
     ErrorState *err = static_cast<ErrorState *>(data);
-    debugs(4, 3, "errorSendComplete: FD " << fd << ", size=" << size);
+    debugs(4, 3, HERE << conn << ", size=" << size);
 
     if (errflag != COMM_ERR_CLOSING) {
         if (err->callback) {
             debugs(4, 3, "errorSendComplete: callback");
-            err->callback(fd, err->callback_data, size);
+            err->callback(conn->fd, err->callback_data, size);
         } else {
-            comm_close(fd);
             debugs(4, 3, "errorSendComplete: comm_close");
+            conn->close();
         }
     }
 
-    errorStateFree(err);
+    delete err;
 }
 
-void
-errorStateFree(ErrorState * err)
+ErrorState::~ErrorState()
 {
-    HTTPMSGUNLOCK(err->request);
-    safe_free(err->redirect_url);
-    safe_free(err->url);
-    safe_free(err->request_hdrs);
-    wordlistDestroy(&err->ftp.server_msg);
-    safe_free(err->ftp.request);
-    safe_free(err->ftp.reply);
-    err->auth_user_request = NULL;
-    safe_free(err->err_msg);
+    HTTPMSGUNLOCK(request);
+    safe_free(redirect_url);
+    safe_free(url);
+    safe_free(request_hdrs);
+    wordlistDestroy(&ftp.server_msg);
+    safe_free(ftp.request);
+    safe_free(ftp.reply);
+#if USE_AUTH
+    auth_user_request = NULL;
+#endif
+    safe_free(err_msg);
 #if USE_ERR_LOCALES
-    if (err->err_language != Config.errorDefaultLanguage)
+    if (err_language != Config.errorDefaultLanguage)
 #endif
-        safe_free(err->err_language);
+        safe_free(err_language);
 #if USE_SSL
-    delete err->detail;
+    delete detail;
 #endif
-    cbdataFree(err);
 }
 
 int
 ErrorState::Dump(MemBuf * mb)
 {
     MemBuf str;
-    const char *p = NULL;      /* takes priority over mb if set */
     char ntoabuf[MAX_IPSTRLEN];
 
     str.reset();
@@ -582,10 +735,10 @@ ErrorState::Dump(MemBuf * mb)
     } else {
         str.Printf("Err: [none]\r\n");
     }
-
+#if USE_AUTH
     if (auth_user_request->denyMessage())
         str.Printf("Auth ErrMsg: %s\r\n", auth_user_request->denyMessage());
-
+#endif
     if (dnsError.size() > 0)
         str.Printf("DNS ErrMsg: %s\r\n", dnsError.termedBuf());
 
@@ -612,17 +765,14 @@ ErrorState::Dump(MemBuf * mb)
         else
             urlpath_or_slash = "/";
 
-        str.Printf("%s " SQUIDSTRINGPH " HTTP/%d.%d\n",
+        str.Printf("%s " SQUIDSTRINGPH " %s/%d.%d\n",
                    RequestMethodStr(request->method),
                    SQUIDSTRINGPRINT(urlpath_or_slash),
+                   AnyP::ProtocolType_str[request->http_ver.protocol],
                    request->http_ver.major, request->http_ver.minor);
         packerToMemInit(&pck, &str);
         request->header.packInto(&pck);
         packerClean(&pck);
-    } else if (request_hdrs) {
-        p = request_hdrs;
-    } else {
-        p = "[none]";
     }
 
     str.Printf("\r\n");
@@ -630,7 +780,7 @@ ErrorState::Dump(MemBuf * mb)
 
     if (ftp.request) {
         str.Printf("FTP Request: %s\r\n", ftp.request);
-        str.Printf("FTP Reply: %s\r\n", ftp.reply);
+        str.Printf("FTP Reply: %s\r\n", (ftp.reply? ftp.reply:"[none]"));
         str.Printf("FTP Msg: ");
         wordlistCat(ftp.server_msg, &str);
         str.Printf("\r\n");
@@ -659,12 +809,18 @@ ErrorState::Convert(char token, bool building_deny_info_url, bool allowRecursion
     switch (token) {
 
     case 'a':
+#if USE_AUTH
         if (request && request->auth_user_request != NULL)
             p = request->auth_user_request->username();
         if (!p)
+#endif
             p = "-";
         break;
 
+    case 'b':
+        mb.Printf("%d", getMyPort());
+        break;
+
     case 'B':
         if (building_deny_info_url) break;
         p = request ? ftpUrlWith2f(request) : "[no URL]";
@@ -681,13 +837,17 @@ ErrorState::Convert(char token, bool building_deny_info_url, bool allowRecursion
 #if USE_SSL
         // currently only SSL error details implemented
         else if (detail) {
+            detail->useRequest(request);
             const String &errDetail = detail->toString();
-            MemBuf *detail_mb  = ConvertText(errDetail.termedBuf(), false);
-            mb.append(detail_mb->content(), detail_mb->contentSize());
-            delete detail_mb;
-            do_quote = 0;
-        } else
+            if (errDetail.defined()) {
+                MemBuf *detail_mb  = ConvertText(errDetail.termedBuf(), false);
+                mb.append(detail_mb->content(), detail_mb->contentSize());
+                delete detail_mb;
+                do_quote = 0;
+            }
+        }
 #endif
+        if (!mb.contentSize())
             mb.Printf("[No Error Detail]");
         break;
 
@@ -714,7 +874,7 @@ ErrorState::Convert(char token, bool building_deny_info_url, bool allowRecursion
     case 'F':
         if (building_deny_info_url) break;
         /* FTP REPLY LINE */
-        if (ftp.request)
+        if (ftp.reply)
             p = ftp.reply;
         else
             p = "nothing";
@@ -750,8 +910,8 @@ ErrorState::Convert(char token, bool building_deny_info_url, bool allowRecursion
         break;
 
     case 'I':
-        if (request && request->hier.host[0] != '\0') // if non-empty string
-            mb.Printf("%s", request->hier.host);
+        if (request && request->hier.tcpServer != NULL)
+            p = request->hier.tcpServer->remote.NtoA(ntoabuf,MAX_IPSTRLEN);
         else if (!building_deny_info_url)
             p = "[unknown]";
         break;
@@ -773,7 +933,11 @@ ErrorState::Convert(char token, bool building_deny_info_url, bool allowRecursion
 
     case 'm':
         if (building_deny_info_url) break;
+#if USE_AUTH
         p = auth_user_request->denyMessage("[not available]");
+#else
+        p = "-";
+#endif
         break;
 
     case 'M':
@@ -799,7 +963,7 @@ ErrorState::Convert(char token, bool building_deny_info_url, bool allowRecursion
 
     case 'P':
         if (request) {
-            p = ProtocolStr[request->protocol];
+            p = AnyP::ProtocolType_str[request->protocol];
         } else if (!building_deny_info_url) {
             p = "[unknown protocol]";
         }
@@ -808,6 +972,7 @@ ErrorState::Convert(char token, bool building_deny_info_url, bool allowRecursion
     case 'R':
         if (building_deny_info_url) {
             p = (request->urlpath.size() != 0 ? request->urlpath.termedBuf() : "/");
+            no_urlescape = 1;
             break;
         }
         if (NULL != request) {
@@ -819,12 +984,13 @@ ErrorState::Convert(char token, bool building_deny_info_url, bool allowRecursion
             else
                 urlpath_or_slash = "/";
 
-            mb.Printf("%s " SQUIDSTRINGPH " HTTP/%d.%d\n",
+            mb.Printf("%s " SQUIDSTRINGPH " %s/%d.%d\n",
                       RequestMethodStr(request->method),
                       SQUIDSTRINGPRINT(urlpath_or_slash),
+                      AnyP::ProtocolType_str[request->http_ver.protocol],
                       request->http_ver.major, request->http_ver.minor);
             packerToMemInit(&pck, &mb);
-            request->header.packInto(&pck);
+            request->header.packInto(&pck, true); //hide authorization data
             packerClean(&pck);
         } else if (request_hdrs) {
             p = request_hdrs;
@@ -837,7 +1003,7 @@ ErrorState::Convert(char token, bool building_deny_info_url, bool allowRecursion
         /* for backward compat we make %s show the full URL. Drop this in some future release. */
         if (building_deny_info_url) {
             p = request ? urlCanonical(request) : url;
-            debugs(0,0, "WARNING: deny_info now accepts coded tags. Use %u to get the full URL instead of %s");
+            debugs(0, DBG_CRITICAL, "WARNING: deny_info now accepts coded tags. Use %u to get the full URL instead of %s");
         } else
             p = visible_appname_string;
         break;
@@ -905,6 +1071,16 @@ ErrorState::Convert(char token, bool building_deny_info_url, bool allowRecursion
         no_urlescape = 1;
         break;
 
+    case 'x':
+#if USE_SSL
+        if (detail)
+            mb.Printf("%s", detail->errorName());
+        else
+#endif
+            if (!building_deny_info_url)
+                p = "[Unknown Error Code]";
+        break;
+
     case 'z':
         if (building_deny_info_url) break;
         if (dnsError.size() > 0)
@@ -979,7 +1155,7 @@ ErrorState::BuildHttpReply()
     const char *name = errorPageName(page_id);
     /* no LMT for error pages; error pages expire immediately */
 
-    if (name[0] == '3' || (name[0] != '4' && name[0] != '5' && strchr(name, ':'))) {
+    if (name[0] == '3' || (name[0] != '2' && name[0] != '4' && name[0] != '5' && strchr(name, ':'))) {
         /* Redirection */
         http_status status = HTTP_MOVED_TEMPORARILY;
         // Use configured 3xx reply status if set.
@@ -1039,10 +1215,26 @@ ErrorState::BuildHttpReply()
                 rep->header.putStr(HDR_CONTENT_LANGUAGE, "en");
         }
 
-        httpBodySet(&rep->body, content);
+        rep->body.setMb(content);
         /* do not memBufClean() or delete the content, it was absorbed by httpBody */
     }
 
+    // Make sure error codes get back to the client side for logging and
+    // error tracking.
+    if (request) {
+        int edc = ERR_DETAIL_NONE; // error detail code
+#if USE_SSL
+        if (detail)
+            edc = detail->errorNo();
+        else
+#endif
+            if (detailCode)
+                edc = detailCode;
+            else
+                edc = xerrno;
+        request->detailError(type, edc);
+    }
+
     return rep;
 }
 
@@ -1054,103 +1246,21 @@ ErrorState::BuildContent()
     assert(page_id > ERR_NONE && page_id < error_page_count);
 
 #if USE_ERR_LOCALES
-    String hdr;
-    char dir[256];
-    int l = 0;
-    const char *freePage = NULL;
+    ErrorPageFile *localeTmpl = NULL;
 
     /** error_directory option in squid.conf overrides translations.
      * Custom errors are always found either in error_directory or the templates directory.
      * Otherwise locate the Accept-Language header
      */
-    if (!Config.errorDirectory && page_id < ERR_MAX && request && request->header.getList(HDR_ACCEPT_LANGUAGE, &hdr) ) {
-
-        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;
-
-        /* prep the directory path string to prevent snprintf ... */
-        l = strlen(DEFAULT_SQUID_ERROR_DIR);
-        memcpy(dir, DEFAULT_SQUID_ERROR_DIR, l);
-        dir[ l++ ] = '/';
-        reset = dt = dir + l;
-
-        debugs(4, 6, HERE << "Testing Header: '" << hdr << "'");
-
-        while ( pos < hdr.size() ) {
-
-            /* 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 USE_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 << "', pos=" << pos << ", buf='" << ((pos < hdr.size()) ? hdr.substr(pos,hdr.size()) : "") << "'");
-
-            /* if we found anything we might use, try it. */
-            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) {
-                    /* store the language we found for the Content-Language reply header */
-                    err_language = xstrdup(reset);
-                    freePage = m;
-                    break;
-                } else if (Config.errorLogMissingLanguages) {
-                    debugs(4, DBG_IMPORTANT, "WARNING: Error Pages Missing Language: " << reset);
-                }
-
-#if HAVE_GLOB
-                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.");
-                }
-#endif
-            }
-
-            dt = reset; // reset for next tag testing. we replace the failed name instead of cloning.
-
-            // 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++;
+    if (!Config.errorDirectory && page_id < ERR_MAX) {
+        if (err_language && err_language != Config.errorDefaultLanguage)
+            safe_free(err_language);
+
+        localeTmpl = new ErrorPageFile(err_type_str[page_id], static_cast<err_type>(page_id));
+        if (localeTmpl->loadFor(request)) {
+            m = localeTmpl->text();
+            assert(localeTmpl->language());
+            err_language = xstrdup(localeTmpl->language());
         }
     }
 #endif /* USE_ERR_LOCALES */
@@ -1168,11 +1278,12 @@ ErrorState::BuildContent()
         debugs(4, 2, HERE << "No existing error page language negotiated for " << errorPageName(page_id) << ". Using default error file.");
     }
 
+    MemBuf *result = ConvertText(m, true);
 #if USE_ERR_LOCALES
-    safe_free(freePage);
+    if (localeTmpl)
+        delete localeTmpl;
 #endif
-
-    return ConvertText(m, true);
+    return result;
 }
 
 MemBuf *ErrorState::ConvertText(const char *text, bool allowRecursion)