]> git.ipfire.org Git - thirdparty/squid.git/blobdiff - src/errorpage.cc
SSL->HTTP gatewaying support by Benno Rice
[thirdparty/squid.git] / src / errorpage.cc
index 9d2f85e75c6663c64696f262e2f5ac63cb0dd1f7..43511f77fc03fd8c895b48d58481ba422755bd2e 100644 (file)
@@ -1,17 +1,21 @@
 
 /*
- * $Id: errorpage.cc,v 1.74 1997/10/16 23:59:54 wessels Exp $
+ * $Id: errorpage.cc,v 1.164 2001/04/14 00:03:22 hno Exp $
  *
  * DEBUG: section 4     Error Generation
  * AUTHOR: Duane Wessels
  *
- * SQUID Internet Object Cache  http://squid.nlanr.net/Squid/
- * --------------------------------------------------------
+ * SQUID Web Proxy Cache          http://www.squid-cache.org/
+ * ----------------------------------------------------------
  *
- *  Squid is the result of efforts by numerous individuals from the
- *  Internet community.  Development is led by Duane Wessels of the
- *  National Laboratory for Applied Network Research and funded by
- *  the National Science Foundation.
+ *  Squid is the result of efforts by numerous individuals from
+ *  the Internet community; see the CONTRIBUTORS file for full
+ *  details.   Many organizations have provided support for Squid's
+ *  development; see the SPONSORS file for full details.  Squid is
+ *  Copyrighted (C) 2001 by the Regents of the University of
+ *  California; see the COPYRIGHT file for full details.  Squid
+ *  incorporates software developed and/or copyrighted by other
+ *  sources; see the CREDITS file for full details.
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License as published by
  *  
  *  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., 675 Mass Ave, Cambridge, MA 02139, USA.
- *  
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111, USA.
+ *
+ */
+
+/*
+ * Abstract:  These routines are used to generate error messages to be
+ *              sent to clients.  The error type is used to select between
+ *              the various message formats. (formats are stored in the
+ *              Config.errorDirectory)
  */
 
 #include "squid.h"
 
-const char *err_string[] =
-{
-    "ERR_NONE",
-    "ERR_NO_CLIENTS",
-    "ERR_READ_TIMEOUT",
-    "ERR_LIFETIME_EXP",
-    "ERR_READ_ERROR",
-    "ERR_WRITE_ERROR",
-    "ERR_CLIENT_ABORT",
-    "ERR_CONNECT_FAIL",
-    "ERR_INVALID_REQ",
-    "ERR_UNSUP_REQ",
-    "ERR_INVALID_URL",
-    "ERR_SOCKET_FAILURE",
-    "ERR_DNS_FAIL",
-    "ERR_CANNOT_FORWARD",
-    "ERR_NO_RELAY",
-    "ERR_ZERO_SIZE_OBJECT",
-    "ERR_FTP_DISABLED",
-    "ERR_ACCESS_DENIED",
-    "ERR_MAX"
+
+/* local types */
+
+typedef struct {
+    int id;
+    char *page_name;
+} ErrorDynamicPageInfo;
+
+/* local constant and vars */
+
+/*
+ * note: hard coded error messages are not appended with %S automagically
+ * to give you more control on the format
+ */
+static const struct {
+    int type;                  /* and page_id */
+    const char *text;
+} error_hard_text[] = {
+
+    {
+       ERR_SQUID_SIGNATURE,
+           "\n<br clear=\"all\">\n"
+           "<hr noshade size=1>\n"
+           "Generated %T by %h (%s)\n"
+           "</BODY></HTML>\n"
+    }
 };
 
-static char *error_text[ERR_MAX];
+static Stack ErrorDynamicPages;
+
+/* local prototypes */
 
-static void errorStateFree _PARAMS((ErrorState * err));
-static char *errorConvert _PARAMS((char token, ErrorState * err));
-static char *errorBuildBuf _PARAMS((ErrorState * err, int *len));
+static const int error_hard_text_count = sizeof(error_hard_text) / sizeof(*error_hard_text);
+static char **error_text = NULL;
+static int error_page_count = 0;
+
+static char *errorTryLoadText(const char *page_name, const char *dir);
+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 MemBuf errorBuildContent(ErrorState * err);
+static const char *errorConvert(char token, ErrorState * err);
 static CWCB errorSendComplete;
 
+/*
+ * Function:  errorInitialize
+ *
+ * Abstract:  This function finds the error messages formats, and stores
+ *            them in error_text[];
+ *
+ * Global effects:
+ *            error_text[] - is modified
+ */
 void
 errorInitialize(void)
 {
     err_type i;
+    const char *text;
+    error_page_count = ERR_MAX + ErrorDynamicPages.count;
+    error_text = xcalloc(error_page_count, sizeof(char *));
+    for (i = ERR_NONE, i++; i < error_page_count; i++) {
+       safe_free(error_text[i]);
+       /* hard-coded ? */
+       if ((text = errorFindHardText(i)))
+           error_text[i] = xstrdup(text);
+       else if (i < ERR_MAX) {
+           /* precompiled ? */
+           error_text[i] = errorLoadText(err_type_str[i]);
+       } else {
+           /* dynamic */
+           ErrorDynamicPageInfo *info = ErrorDynamicPages.items[i - ERR_MAX];
+           assert(info && info->id == i && info->page_name);
+           error_text[i] = errorLoadText(info->page_name);
+       }
+       assert(error_text[i]);
+    }
+}
+
+void
+errorClean(void)
+{
+    if (error_text) {
+       int i;
+       for (i = ERR_NONE + 1; i < error_page_count; i++)
+           safe_free(error_text[i]);
+       safe_free(error_text);
+    }
+    while (ErrorDynamicPages.count)
+       errorDynamicPageInfoDestroy(stackPop(&ErrorDynamicPages));
+    error_page_count = 0;
+}
+
+static const char *
+errorFindHardText(err_type type)
+{
+    int 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;
+}
+
+
+static char *
+errorLoadText(const char *page_name)
+{
+    /* test configured location */
+    char *text = errorTryLoadText(page_name, Config.errorDirectory);
+    /* test default location if failed */
+    if (!text && strcmp(Config.errorDirectory, DEFAULT_SQUID_ERROR_DIR))
+       text = errorTryLoadText(page_name, DEFAULT_SQUID_ERROR_DIR);
+    /* giving up if failed */
+    if (!text)
+       fatal("failed to find or read error text file.");
+    return text;
+}
+
+static char *
+errorTryLoadText(const char *page_name, const char *dir)
+{
     int fd;
     char path[MAXPATHLEN];
     struct stat sb;
-    assert(sizeof(err_string) == (ERR_MAX + 1) * 4);
-    for (i = ERR_NONE + 1; i < ERR_MAX; i++) {
-       snprintf(path, MAXPATHLEN, "%s/%s",
-           Config.errorDirectory, err_string[i]);
-       fd = file_open(path, O_RDONLY, NULL, NULL);
-       if (fd < 0) {
-           debug(4, 0) ("errorInitialize: %s: %s\n", path, xstrerror());
-           fatal("Failed to open error text file");
-       }
-       if (fstat(fd, &sb) < 0)
-           fatal_dump("stat() failed on error text file");
-       safe_free(error_text[i]);
-       error_text[i] = xcalloc(sb.st_size, 1);
-       if (read(fd, error_text[i], sb.st_size) != sb.st_size)
-           fatal_dump("failed to fully read error text file");
-       file_close(fd);
+    char *text;
+
+    snprintf(path, sizeof(path), "%s/%s", dir, page_name);
+    fd = file_open(path, O_RDONLY | O_TEXT);
+    if (fd < 0 || fstat(fd, &sb) < 0) {
+       debug(4, 0) ("errorTryLoadText: '%s': %s\n", path, xstrerror());
+       if (fd >= 0)
+           file_close(fd);
+       return NULL;
+    }
+    text = xcalloc(sb.st_size + 2 + 1, 1);     /* 2 == space for %S */
+    if (FD_READ_METHOD(fd, text, sb.st_size) != sb.st_size) {
+       debug(4, 0) ("errorTryLoadText: failed to fully read: '%s': %s\n",
+           path, xstrerror());
+       xfree(text);
+       text = NULL;
     }
+    file_close(fd);
+    if (strstr(text, "%s") == NULL)
+       strcat(text, "%S");     /* add signature */
+    return text;
+}
+
+static ErrorDynamicPageInfo *
+errorDynamicPageInfoCreate(int id, const char *page_name)
+{
+    ErrorDynamicPageInfo *info = xcalloc(1, sizeof(ErrorDynamicPageInfo));
+    info->id = id;
+    info->page_name = xstrdup(page_name);
+    return info;
 }
 
 static void
+errorDynamicPageInfoDestroy(ErrorDynamicPageInfo * info)
+{
+    assert(info);
+    xfree(info->page_name);
+    xfree(info);
+}
+
+int
+errorReservePageId(const char *page_name)
+{
+    ErrorDynamicPageInfo *info =
+    errorDynamicPageInfoCreate(ERR_MAX + ErrorDynamicPages.count, page_name);
+    stackPush(&ErrorDynamicPages, info);
+    return info->id;
+}
+
+static const char *
+errorPageName(int pageId)
+{
+    if (pageId >= ERR_NONE && pageId < ERR_MAX)                /* common case */
+       return err_type_str[pageId];
+    if (pageId >= ERR_MAX && pageId - ERR_MAX < ErrorDynamicPages.count)
+       return ((ErrorDynamicPageInfo *) ErrorDynamicPages.
+           items[pageId - ERR_MAX])->page_name;
+    return "ERR_UNKNOWN";      /* should not happen */
+}
+
+/*
+ * Function:  errorCon
+ *
+ * Abstract:  This function creates a ErrorState object.
+ */
+ErrorState *
+errorCon(err_type type, http_status status)
+{
+    ErrorState *err;
+    err = cbdataAlloc(ErrorState);
+    err->page_id = type;       /* has to be reset manually if needed */
+    err->type = type;
+    err->http_status = status;
+    return err;
+}
+
+/*
+ * Function:  errorAppendEntry
+ *
+ * Arguments: err - This object is destroyed after use in this function.
+ *
+ * Abstract:  This function generates a error page from the info contained
+ *            by 'err' and then stores the text in the specified store
+ *            entry.  This function should only be called by ``server
+ *            side routines'' which need to communicate errors to the
+ *            client side.  It should also be called from client_side.c
+ *            because we now support persistent connections, and
+ *            cannot assume that we can immediately write to the socket
+ *            for an error.
+ */
+void
+errorAppendEntry(StoreEntry * entry, ErrorState * err)
+{
+    HttpReply *rep;
+    MemObject *mem = entry->mem_obj;
+    assert(mem != NULL);
+    assert(mem->inmem_hi == 0);
+    if (entry->store_status != STORE_PENDING) {
+       /*
+        * If the entry is not STORE_PENDING, then no clients
+        * care about it, and we don't need to generate an
+        * error message
+        */
+       assert(EBIT_TEST(entry->flags, ENTRY_ABORTED));
+       assert(mem->nclients == 0);
+       errorStateFree(err);
+       return;
+    }
+    storeLockObject(entry);
+    storeBuffer(entry);
+    rep = errorBuildReply(err);
+    /* Add authentication header */
+    /* TODO: alter errorstate to be accel on|off aware. The 0 on the next line
+     * depends on authenticate behaviour: all schemes to date send no extra data
+     * on 407/401 responses, and do not check the accel state on 401/407 responses 
+     */
+    authenticateFixHeader(rep, err->auth_user_request, err->request, 0);
+    httpReplySwapOut(rep, entry);
+    httpReplyAbsorb(mem->reply, rep);
+    EBIT_CLR(entry->flags, ENTRY_FWD_HDR_WAIT);
+    storeBufferFlush(entry);
+    storeComplete(entry);
+    storeNegativeCache(entry);
+    storeReleaseRequest(entry);
+    storeUnlockObject(entry);
+    errorStateFree(err);
+}
+
+/*
+ * Function:  errorSend
+ *
+ * Arguments: err - This object is destroyed after use in this function.
+ *
+ * Abstract:  This function generates a error page from the info contained
+ *            by 'err' and then sends it to the client.
+ *            The callback function errorSendComplete() is called after
+ *            the page has been written to the client socket (fd).
+ *            errorSendComplete() deallocates 'err'.  We need to add
+ *            'err' to the cbdata because comm_write() requires it
+ *            for all callback data pointers.
+ *
+ *            Note, normally errorSend() should only be called from
+ *            routines in ssl.c and pass.c, where we don't have any
+ *            StoreEntry's.  In client_side.c we must allocate a StoreEntry
+ *            for errors and use errorAppendEntry() to account for
+ *            persistent/pipeline connections.
+ */
+void
+errorSend(int fd, ErrorState * err)
+{
+    HttpReply *rep;
+    debug(4, 3) ("errorSend: FD %d, err=%p\n", fd, 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->err_type = err->type;
+    /* moved in front of errorBuildBuf @?@ */
+    err->flags.flag_cbdata = 1;
+    rep = errorBuildReply(err);
+    comm_write_mbuf(fd, httpReplyPack(rep), errorSendComplete, err);
+    httpReplyDestroy(rep);
+}
+
+/*
+ * Function:  errorSendComplete
+ *
+ * Abstract:  Called by commHandleWrite() after data has been written
+ *            to the client socket.
+ *
+ * Note:      If there is a callback, the callback is responsible for
+ *            closeing the FD, otherwise we do it ourseves.
+ */
+static void
+errorSendComplete(int fd, char *bufnotused, size_t size, int errflag, void *data)
+{
+    ErrorState *err = data;
+    debug(4, 3) ("errorSendComplete: FD %d, size=%d\n", fd, size);
+    if (errflag != COMM_ERR_CLOSING) {
+       if (err->callback) {
+           debug(4, 3) ("errorSendComplete: callback\n");
+           err->callback(fd, err->callback_data, size);
+       } else {
+           comm_close(fd);
+           debug(4, 3) ("errorSendComplete: comm_close\n");
+       }
+    }
+    errorStateFree(err);
+}
+
+void
 errorStateFree(ErrorState * err)
 {
     requestUnlink(err->request);
     safe_free(err->redirect_url);
     safe_free(err->url);
+    safe_free(err->host);
+    safe_free(err->dnsserver_msg);
+    safe_free(err->request_hdrs);
+    wordlistDestroy(&err->ftp.server_msg);
+    safe_free(err->ftp.request);
+    safe_free(err->ftp.reply);
+    if (err->auth_user_request)
+       authenticateAuthUserRequestUnlock(err->auth_user_request);
     cbdataFree(err);
 }
 
 #define CVT_BUF_SZ 512
-static char *
+
+/*
+ * B - URL with FTP %2f hack                    x
+ * c - Squid error code                         x
+ * d - seconds elapsed since request received   x
+ * e - errno                                    x
+ * E - strerror()                               x
+ * f - FTP request line                         x
+ * F - FTP reply line                           x
+ * g - FTP server message                       x
+ * h - cache hostname                           x
+ * H - server host name                         x
+ * i - client IP address                        x
+ * I - server IP address                        x
+ * L - HREF link for more info/contact          x
+ * M - Request Method                           x
+ * m - Error message returned by external Auth. x 
+ * p - URL port #                               x
+ * P - Protocol                                 x
+ * R - Full HTTP Request                        x
+ * S - squid signature from ERR_SIGNATURE       x
+ * s - caching proxy software with version      x
+ * t - local time                               x
+ * T - UTC                                      x
+ * U - URL without password                     x
+ * u - URL without password, %2f added to path  x
+ * w - cachemgr email address                   x
+ * z - dns server error message                 x
+ */
+
+static const char *
 errorConvert(char token, ErrorState * err)
 {
-    char *p = NULL;
     request_t *r = err->request;
-    static char buf[CVT_BUF_SZ];
+    static MemBuf mb = MemBufNULL;
+    const char *p = NULL;      /* takes priority over mb if set */
+    int do_quote = 1;
+
+    memBufReset(&mb);
     switch (token) {
-    case 'U':
-       p = r ? urlCanonicalClean(r) : err->url;
+    case 'B':
+       p = r ? ftpUrlWith2f(r) : "[no URL]";
+       break;
+    case 'c':
+       assert(err->type >= ERR_NONE);
+       assert(err->type < ERR_MAX);
+       p = err_type_str[err->type];
+       break;
+    case 'e':
+       memBufPrintf(&mb, "%d", err->xerrno);
+       break;
+    case 'E':
+       if (err->xerrno)
+           memBufPrintf(&mb, "(%d) %s", err->xerrno, strerror(err->xerrno));
+       else
+           memBufPrintf(&mb, "[No Error]");
+       break;
+    case 'f':
+       /* FTP REQUEST LINE */
+       if (err->ftp.request)
+           p = err->ftp.request;
+       else
+           p = "nothing";
+       break;
+    case 'F':
+       /* FTP REPLY LINE */
+       if (err->ftp.request)
+           p = err->ftp.reply;
+       else
+           p = "nothing";
+       break;
+    case 'g':
+       /* FTP SERVER MESSAGE */
+       wordlistCat(err->ftp.server_msg, &mb);
+       break;
+    case 'h':
+       memBufPrintf(&mb, "%s", getMyHostname());
        break;
     case 'H':
        p = r ? r->host : "[unknown host]";
        break;
+    case 'i':
+       memBufPrintf(&mb, "%s", inet_ntoa(err->src_addr));
+       break;
+    case 'I':
+       if (err->host) {
+           memBufPrintf(&mb, "%s", err->host);
+       } else
+           p = "[unknown]";
+       break;
+    case 'L':
+       if (Config.errHtmlText) {
+           memBufPrintf(&mb, "%s", Config.errHtmlText);
+       } else
+           p = "[not available]";
+       break;
+    case 'm':
+       p = authenticateAuthUserRequestMessage(err->auth_user_request) ? authenticateAuthUserRequestMessage(err->auth_user_request) : "[not available]";
+       break;
+    case 'M':
+       p = r ? RequestMethodStr[r->method] : "[unkown method]";
+       break;
     case 'p':
        if (r) {
-           snprintf(buf, CVT_BUF_SZ, "%d", (int) r->port);
-           p = buf;
+           memBufPrintf(&mb, "%d", (int) r->port);
        } else {
            p = "[unknown port]";
        }
        break;
     case 'P':
-       p = r ? (char *) ProtocolStr[r->protocol] : "[unkown protocol]";
+       p = r ? ProtocolStr[r->protocol] : "[unkown protocol]";
        break;
-    case 'M':
-       p = r ? (char *) RequestMethodStr[r->method] : "[unkown method]";
+    case 'R':
+       if (NULL != r) {
+           Packer p;
+           memBufPrintf(&mb, "%s %s HTTP/%d.%d\n",
+               RequestMethodStr[r->method],
+               strLen(r->urlpath) ? strBuf(r->urlpath) : "/",
+               r->http_ver.major, r->http_ver.minor);
+           packerToMemInit(&p, &mb);
+           httpHeaderPackInto(&r->header, &p);
+           packerClean(&p);
+       } else if (err->request_hdrs) {
+           p = err->request_hdrs;
+       } else {
+           p = "[no request]";
+       }
        break;
-    case 'z':
-       p = err->dnsserver_msg;
+    case 's':
+       p = full_appname_string;
        break;
-    case 'e':
-       snprintf(buf, CVT_BUF_SZ, "%d", err->errno);
-       p=buf;
+    case 'S':
+       /* signature may contain %-escapes, recursion */
+       if (err->page_id != ERR_SQUID_SIGNATURE) {
+           const int saved_id = err->page_id;
+           MemBuf sign_mb;
+           err->page_id = ERR_SQUID_SIGNATURE;
+           sign_mb = errorBuildContent(err);
+           memBufPrintf(&mb, "%s", sign_mb.buf);
+           memBufClean(&sign_mb);
+           err->page_id = saved_id;
+           do_quote = 0;
+       } else {
+           /* wow, somebody put %S into ERR_SIGNATURE, stop recursion */
+           p = "[%S]";
+       }
        break;
-    case 'E':
-       snprintf(buf, CVT_BUF_SZ, "(%d) %s", err->errno, strerror(err->errno));
+    case 't':
+       memBufPrintf(&mb, "%s", mkhttpdlogtime(&squid_curtime));
+       break;
+    case 'T':
+       memBufPrintf(&mb, "%s", mkrfc1123(squid_curtime));
+       break;
+    case 'U':
+       p = r ? urlCanonicalClean(r) : err->url ? err->url : "[no URL]";
        break;
     case 'w':
-       snprintf(buf, CVT_BUF_SZ, "%s",Config.adminEmail);
+       if (Config.adminEmail)
+           memBufPrintf(&mb, "%s", Config.adminEmail);
+       else
+           p = "[unknown]";
        break;
-    case 'h':
-       snprintf(buf, CVT_BUF_SZ, "%s", getMyHostname());
+    case 'z':
+       if (err->dnsserver_msg)
+           p = err->dnsserver_msg;
+       else
+           p = "[unknown]";
+       break;
+    case '%':
+       p = "%";
        break;
-/*
- * e - errno                                   x
- * E - strerror()                              x
- * t - local time
- * T - UTC
- * c - Squid error code
- * I - server IP address
- * i - client IP address
- * L - HREF link for more info/contact
- * w - cachemgr email address                  x
- * h - cache hostname                          x
- * d - seconds elapsed since request received
- * p - URL port #                              x
- */
     default:
-       p = "%UNKNOWN%";
+       memBufPrintf(&mb, "%%%c", token);
        break;
     }
-    if (p == NULL)
-       p = "<NULL>";
+    if (!p)
+       p = mb.buf;             /* do not use mb after this assignment! */
+    assert(p);
     debug(4, 3) ("errorConvert: %%%c --> '%s'\n", token, p);
+    if (do_quote)
+       p = html_quote(p);
     return p;
 }
 
-static char *
-errorBuildBuf(ErrorState * err, int *len)
-{
-    LOCAL_ARRAY(char, buf, ERROR_BUF_SZ);
-    LOCAL_ARRAY(char, content, ERROR_BUF_SZ);
-    char *hdr;
-    int clen;
-    int tlen;
-    char *m;
-    char *mx;
-    char *p;
-    char *t;
-    assert(err != NULL);
-    assert(err->type > ERR_NONE && err->type < ERR_MAX);
-    mx = m = xstrdup(error_text[err->type]);
-    clen = 0;
-    while ((p = strchr(m, '%'))) {
-       *p = '\0';              /* terminate */
-       xstrncpy(content + clen, m, ERROR_BUF_SZ - clen);       /* copy */
-       clen += (p - m);        /* advance */
-       if (clen >= ERROR_BUF_SZ)
-           break;
-       p++;
-       m = p + 1;
-       t = errorConvert(*p, err);      /* convert */
-       xstrncpy(content + clen, t, ERROR_BUF_SZ - clen);       /* copy */
-       clen += strlen(t);      /* advance */
-       if (clen >= ERROR_BUF_SZ)
-           break;
-    }
-    if (clen < ERROR_BUF_SZ && m != NULL) {
-       xstrncpy(content + clen, m, ERROR_BUF_SZ - clen);
-       clen += strlen(m);
-    }
-    if (clen >= ERROR_BUF_SZ) {
-       clen = ERROR_BUF_SZ - 1;
-       *(content + clen) = '\0';
-    }
-    assert(clen == strlen(content));
-    hdr = httpReplyHeader((double) 1.0,
-       err->http_status,
-       "text/html",
-       clen,
-       0,                      /* no LMT for error pages */
-       squid_curtime);
-    tlen = snprintf(buf, ERROR_BUF_SZ, "%s\r\n%s", hdr, content);
-    if (len)
-       *len = tlen;
-    xfree(mx);
-    return buf;
-}
-
-void
-errorSend(int fd, ErrorState * err)
-{
-    char *buf;
-    int len;
-    assert(fd >= 0);
-    buf = errorBuildBuf(err, &len);
-    cbdataAdd(err);
-    cbdataLock(err);
-    comm_write(fd, xstrdup(buf), len, errorSendComplete, err, xfree);
-}
-
-void
-errorAppendEntry(StoreEntry * entry, ErrorState * err)
+/* allocates and initializes an error response */
+HttpReply *
+errorBuildReply(ErrorState * err)
 {
-    char *buf;
-    MemObject *mem = entry->mem_obj;
-    int len;
-    assert(entry->store_status == STORE_PENDING);
-    buf = errorBuildBuf(err, &len);
-    storeAppend(entry, buf, len);
-    if (mem)
-       mem->reply->code = err->http_status;
+    HttpReply *rep = httpReplyCreate();
+    MemBuf content = errorBuildContent(err);
+    http_version_t version;
+    /* no LMT for error pages; error pages expire immediately */
+    httpBuildVersion(&version, 1, 0);
+    httpReplySetHeaders(rep, version, err->http_status, NULL, "text/html", content.size, 0, squid_curtime);
+    /*
+     * include some information for downstream caches. Implicit
+     * replaceable content. This isn't quite sufficient. xerrno is not
+     * necessarily meaningful to another system, so we really should
+     * expand it. Additionally, we should identify ourselves. Someone
+     * might want to know. Someone _will_ want to know OTOH, the first
+     * X-CACHE-MISS entry should tell us who.
+     */
+    httpHeaderPutStrf(&rep->header, HDR_X_SQUID_ERROR, "%s %d",
+       errorPageName(err->page_id), err->xerrno);
+    httpBodySet(&rep->body, &content);
+    /* do not memBufClean() the content, it was absorbed by httpBody */
+    return rep;
 }
 
-static void
-errorSendComplete(int fd, char *buf, int size, int errflag, void *data)
+static MemBuf
+errorBuildContent(ErrorState * err)
 {
-    ErrorState *err = data;
-    if (err->callback)
-       err->callback(fd, err->callback_data, size);
-    cbdataUnlock(err);
-    errorStateFree(err);
+    MemBuf content;
+    const char *m;
+    const char *p;
+    const char *t;
+    assert(err != NULL);
+    assert(err->page_id > ERR_NONE && err->page_id < error_page_count);
+    memBufDefInit(&content);
+    m = error_text[err->page_id];
+    assert(m);
+    while ((p = strchr(m, '%'))) {
+       memBufAppend(&content, m, p - m);       /* copy */
+       t = errorConvert(*++p, err);    /* convert */
+       memBufPrintf(&content, "%s", t);        /* copy */
+       m = p + 1;              /* advance */
+    }
+    if (*m)
+       memBufPrintf(&content, "%s", m);        /* copy tail */
+    assert(content.size == strlen(content.buf));
+    return content;
 }