]> git.ipfire.org Git - thirdparty/squid.git/blobdiff - src/errorpage.cc
Merge form trunk
[thirdparty/squid.git] / src / errorpage.cc
index a0b0891a697eda25b3dd3db32c97c53afed8129c..80d0ae344dfb09806e05dc7b3aca3116edebf1b1 100644 (file)
@@ -1,8 +1,7 @@
-
 /*
  * $Id$
  *
- * DEBUG: section     Error Generation
+ * DEBUG: section 04    Error Generation
  * AUTHOR: Duane Wessels
  *
  * SQUID Web Proxy Cache          http://www.squid-cache.org/
  */
 #include "config.h"
 
-#include "errorpage.h"
 #include "auth/UserRequest.h"
-#include "SquidTime.h"
-#include "Store.h"
+#include "comm/Connection.h"
+#include "err_detail_type.h"
+#include "errorpage.h"
+#include "fde.h"
+#include "html_quote.h"
 #include "HttpReply.h"
 #include "HttpRequest.h"
-#include "MemObject.h"
-#include "fde.h"
 #include "MemBuf.h"
+#include "MemObject.h"
+#include "rfc1738.h"
+#include "SquidTime.h"
+#include "Store.h"
 #include "URLScheme.h"
 #include "wordlist.h"
 
@@ -368,7 +371,7 @@ errorReservePageId(const char *page_name)
 }
 
 /// \ingroup ErrorPageInternal
-static const char *
+const char *
 errorPageName(int pageId)
 {
     if (pageId >= ERR_NONE && pageId < ERR_MAX)                /* common case */
@@ -392,6 +395,7 @@ errorCon(err_type type, http_status status, HttpRequest * request)
     if (request != NULL) {
         err->request = HTTPMSGLOCK(request);
         err->src_addr = request->client_addr;
+        request->detailError(type, ERR_DETAIL_NONE);
     }
 
     return err;
@@ -439,26 +443,26 @@ errorAppendEntry(StoreEntry * entry, ErrorState * 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);
+    debugs(4, 3, HERE << conn << ", err=" << err);
+    assert(Comm::IsConnOpen(conn));
     /*
      * 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->errType = err->type;
+        err->request->detailError(err->type, err->xerrno);
 
     /* moved in front of errorBuildBuf @?@ */
     err->flags.flag_cbdata = 1;
 
     rep = err->BuildHttpReply();
-
-    comm_write_mbuf(fd, rep->pack(), errorSendComplete, err);
-
+    MemBuf *mb = rep->pack();
+    comm_write_mbuf(conn, mb, errorSendComplete, err);
+    delete mb;
     delete rep;
 }
 
@@ -472,18 +476,18 @@ 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();
         }
     }
 
@@ -500,7 +504,7 @@ errorStateFree(ErrorState * err)
     wordlistDestroy(&err->ftp.server_msg);
     safe_free(err->ftp.request);
     safe_free(err->ftp.reply);
-    AUTHUSERREQUESTUNLOCK(err->auth_user_request, "errstate");
+    err->auth_user_request = NULL;
     safe_free(err->err_msg);
 #if USE_ERR_LOCALES
     if (err->err_language != Config.errorDefaultLanguage)
@@ -553,7 +557,7 @@ ErrorState::Dump(MemBuf * mb)
     str.Printf("HTTP Request:\r\n");
 
     if (NULL != request) {
-        Packer p;
+        Packer pck;
         String urlpath_or_slash;
 
         if (request->urlpath.size() != 0)
@@ -565,9 +569,9 @@ ErrorState::Dump(MemBuf * mb)
                    RequestMethodStr(request->method),
                    SQUIDSTRINGPRINT(urlpath_or_slash),
                    request->http_ver.major, request->http_ver.minor);
-        packerToMemInit(&p, &str);
-        request->header.packInto(&p);
-        packerClean(&p);
+        packerToMemInit(&pck, &str);
+        request->header.packInto(&pck);
+        packerClean(&pck);
     } else if (request_hdrs) {
         p = request_hdrs;
     } else {
@@ -595,11 +599,12 @@ ErrorState::Dump(MemBuf * mb)
 #define CVT_BUF_SZ 512
 
 const char *
-ErrorState::Convert(char token)
+ErrorState::Convert(char token, bool building_deny_info_url)
 {
     static MemBuf mb;
     const char *p = NULL;      /* takes priority over mb if set */
     int do_quote = 1;
+    int no_urlescape = 0;       /* if true then item is NOT to be further URL-encoded */
     char ntoabuf[MAX_IPSTRLEN];
 
     mb.reset();
@@ -607,61 +612,60 @@ ErrorState::Convert(char token)
     switch (token) {
 
     case 'a':
-
-        if (request && request->auth_user_request)
+        if (request && request->auth_user_request != NULL)
             p = request->auth_user_request->username();
-
         if (!p)
             p = "-";
-
         break;
 
     case 'B':
+        if (building_deny_info_url) break;
         p = request ? ftpUrlWith2f(request) : "[no URL]";
-
         break;
 
     case 'c':
+        if (building_deny_info_url) break;
         p = errorPageName(type);
-
         break;
 
     case 'e':
         mb.Printf("%d", xerrno);
-
         break;
 
     case 'E':
-
         if (xerrno)
             mb.Printf("(%d) %s", xerrno, strerror(xerrno));
         else
             mb.Printf("[No Error]");
-
         break;
 
     case 'f':
+        if (building_deny_info_url) break;
         /* FTP REQUEST LINE */
         if (ftp.request)
             p = ftp.request;
         else
             p = "nothing";
-
         break;
 
     case 'F':
+        if (building_deny_info_url) break;
         /* FTP REPLY LINE */
         if (ftp.request)
             p = ftp.reply;
         else
             p = "nothing";
-
         break;
 
     case 'g':
+        if (building_deny_info_url) break;
         /* FTP SERVER MESSAGE */
-        wordlistCat(ftp.server_msg, &mb);
-
+        if (ftp.server_msg)
+            wordlistCat(ftp.server_msg, &mb);
+        else if (ftp.listing) {
+            mb.append(ftp.listing->content(), ftp.listing->contentSize());
+            do_quote = 0;
+        }
         break;
 
     case 'h':
@@ -674,70 +678,77 @@ ErrorState::Convert(char token)
                 p = request->hier.host;
             else
                 p = request->GetHost();
-        } else
+        } else if (!building_deny_info_url)
             p = "[unknown host]";
-
         break;
 
     case 'i':
         mb.Printf("%s", src_addr.NtoA(ntoabuf,MAX_IPSTRLEN));
-
         break;
 
     case 'I':
         if (request && request->hier.host[0] != '\0') // if non-empty string
             mb.Printf("%s", request->hier.host);
-        else
+        else if (!building_deny_info_url)
             p = "[unknown]";
-
         break;
 
     case 'l':
+        if (building_deny_info_url) break;
         mb.append(error_stylesheet.content(), error_stylesheet.contentSize());
         do_quote = 0;
         break;
 
     case 'L':
+        if (building_deny_info_url) break;
         if (Config.errHtmlText) {
             mb.Printf("%s", Config.errHtmlText);
             do_quote = 0;
         } else
             p = "[not available]";
-
         break;
 
     case 'm':
+        if (building_deny_info_url) break;
         p = auth_user_request->denyMessage("[not available]");
-
         break;
 
     case 'M':
-        p = request ? RequestMethodStr(request->method) : "[unknown method]";
-
+        if (request)
+            p = RequestMethodStr(request->method);
+        else if (!building_deny_info_url)
+            p= "[unknown method]";
         break;
 
     case 'o':
-        p = external_acl_message ? external_acl_message : "[not available]";
-
+        p = request ? request->extacl_message.termedBuf() : external_acl_message;
+        if (!p && !building_deny_info_url)
+            p = "[not available]";
         break;
 
     case 'p':
         if (request) {
             mb.Printf("%d", (int) request->port);
-        } else {
+        } else if (!building_deny_info_url) {
             p = "[unknown port]";
         }
-
         break;
 
     case 'P':
-        p = request ? ProtocolStr[request->protocol] : "[unknown protocol]";
+        if (request) {
+            p = ProtocolStr[request->protocol];
+        } else if (!building_deny_info_url) {
+            p = "[unknown protocol]";
+        }
         break;
 
     case 'R':
-
+        if (building_deny_info_url) {
+            p = (request->urlpath.size() != 0 ? request->urlpath.termedBuf() : "/");
+            break;
+        }
         if (NULL != request) {
-            Packer p;
+            Packer pck;
             String urlpath_or_slash;
 
             if (request->urlpath.size() != 0)
@@ -749,24 +760,31 @@ ErrorState::Convert(char token)
                       RequestMethodStr(request->method),
                       SQUIDSTRINGPRINT(urlpath_or_slash),
                       request->http_ver.major, request->http_ver.minor);
-            packerToMemInit(&p, &mb);
-            request->header.packInto(&p);
-            packerClean(&p);
+            packerToMemInit(&pck, &mb);
+            request->header.packInto(&pck);
+            packerClean(&pck);
         } else if (request_hdrs) {
             p = request_hdrs;
         } else {
             p = "[no request]";
         }
-
         break;
 
     case 's':
-        p = visible_appname_string;
+        /* 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");
+        } else
+            p = visible_appname_string;
         break;
 
     case 'S':
+        if (building_deny_info_url) {
+            p = visible_appname_string;
+            break;
+        }
         /* signature may contain %-escapes, recursion */
-
         if (page_id != ERR_SQUID_SIGNATURE) {
             const int saved_id = page_id;
             page_id = ERR_SQUID_SIGNATURE;
@@ -780,7 +798,6 @@ ErrorState::Convert(char token)
             /* wow, somebody put %S into ERR_SIGNATURE, stop recursion */
             p = "[%S]";
         }
-
         break;
 
     case 't':
@@ -794,54 +811,62 @@ ErrorState::Convert(char token)
     case 'U':
         /* 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]";
+        if (request)
+            p = urlCanonicalFakeHttps(request);
+        else if (url)
+            p = url;
+        else if (!building_deny_info_url)
+            p = "[no URL]";
         break;
 
     case 'u':
-        p = request ? urlCanonical(request) : url ? url : "[no URL]";
+        if (request)
+            p = urlCanonical(request);
+        else if (url)
+            p = url;
+        else if (!building_deny_info_url)
+            p = "[no URL]";
         break;
 
     case 'w':
-
         if (Config.adminEmail)
             mb.Printf("%s", Config.adminEmail);
-        else
+        else if (!building_deny_info_url)
             p = "[unknown]";
-
         break;
 
     case 'W':
+        if (building_deny_info_url) break;
         if (Config.adminEmail && Config.onoff.emailErrData)
             Dump(&mb);
-
+        no_urlescape = 1;
         break;
 
     case 'z':
+        if (building_deny_info_url) break;
         if (dnsError.size() > 0)
             p = dnsError.termedBuf();
+        else if (ftp.cwd_msg)
+            p = ftp.cwd_msg;
         else
             p = "[unknown]";
-
         break;
 
     case 'Z':
+        if (building_deny_info_url) break;
         if (err_msg)
             p = err_msg;
         else
             p = "[unknown]";
-
         break;
 
     case '%':
         p = "%";
-
         break;
 
     default:
         mb.Printf("%%%c", token);
-
         do_quote = 0;
-
         break;
     }
 
@@ -855,30 +880,54 @@ ErrorState::Convert(char token)
     if (do_quote)
         p = html_quote(p);
 
+    if (building_deny_info_url && !no_urlescape)
+        p = rfc1738_escape_part(p);
+
     return p;
 }
 
+void
+ErrorState::DenyInfoLocation(const char *name, HttpRequest *aRequest, MemBuf &result)
+{
+    char const *m = name;
+    char const *p = m;
+    char const *t;
+
+    while ((p = strchr(m, '%'))) {
+        result.append(m, p - m);       /* copy */
+        t = Convert(*++p, true);       /* convert */
+        result.Printf("%s", t);        /* copy */
+        m = p + 1;                     /* advance */
+    }
+
+    if (*m)
+        result.Printf("%s", m);        /* copy tail */
+
+    assert((size_t)result.contentSize() == strlen(result.content()));
+}
+
 HttpReply *
 ErrorState::BuildHttpReply()
 {
     HttpReply *rep = new HttpReply;
     const char *name = errorPageName(page_id);
     /* no LMT for error pages; error pages expire immediately */
-    HttpVersion version(1, 0);
 
     if (strchr(name, ':')) {
         /* Redirection */
-        rep->setHeaders(version, HTTP_MOVED_TEMPORARILY, NULL, "text/html", 0, 0, -1);
+        rep->setHeaders(HTTP_MOVED_TEMPORARILY, NULL, "text/html", 0, 0, -1);
 
         if (request) {
-            char *quoted_url = rfc1738_escape_part(urlCanonical(request));
-            httpHeaderPutStrf(&rep->header, HDR_LOCATION, name, quoted_url);
+            MemBuf redirect_location;
+            redirect_location.init();
+            DenyInfoLocation(name, request, redirect_location);
+            httpHeaderPutStrf(&rep->header, HDR_LOCATION, "%s", redirect_location.content() );
         }
 
         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, -1);
+        rep->setHeaders(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
@@ -937,9 +986,10 @@ ErrorState::BuildContent()
     int l = 0;
 
     /** 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 && request && request->header.getList(HDR_ACCEPT_LANGUAGE, &hdr) ) {
+    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
@@ -955,6 +1005,9 @@ ErrorState::BuildContent()
 
         while ( pos < hdr.size() ) {
 
+            /* skip any initial whitespace. */
+            while (pos < hdr.size() && xisspace(hdr[pos])) pos++;
+
             /*
              * Header value format:
              *  - sequence of whitespace delimited tags
@@ -962,17 +1015,43 @@ ErrorState::BuildContent()
              *  - 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) ) {
-                *dt++ = xtolower(hdr[pos++]);
+                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 << "', reset[1]='" << reset[1] << "', pos=" << pos << ", buf='" << hdr.substr(pos,hdr.size()) << "'");
+            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) {
@@ -994,7 +1073,7 @@ ErrorState::BuildContent()
 
             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.
+            // 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++;
         }
@@ -1019,7 +1098,7 @@ ErrorState::BuildContent()
 
     while ((p = strchr(m, '%'))) {
         content->append(m, p - m);     /* copy */
-        t = Convert(*++p);             /* convert */
+        t = Convert(*++p, false);      /* convert */
         content->Printf("%s", t);      /* copy */
         m = p + 1;                     /* advance */
     }