You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@httpd.apache.org by Roy Fielding <fi...@hyperreal.com> on 1997/03/20 18:10:17 UTC

cvs commit: apache/src CHANGES httpd.h util.c alloc.h alloc.c http_protocol.h http_protocol.c

fielding    97/03/20 09:10:16

  Modified:    src       CHANGES httpd.h util.c alloc.h alloc.c
                        http_protocol.h  http_protocol.c
  Log:
  Fixed the handling of module and script-added header fields.
  Improved the interface for sending header fields and reduced
  the duplication of code between sending okay responses and errors.
  We now always send both headers_out and err_headers_out, and
  ensure that the server-reserved fields are not being overridden,
  while not overriding those that are not reserved.
  
  Fixed the determination of whether or not we should make the
  connection persistent for all of the cases where some other part
  of the server has already indicated that we should not.  Also
  improved the ordering of the test so that chunked encoding will
  be set whenever it is desired instead of only when KeepAlive
  is enabled. Added persistent connection capability for most error
  responses (those that do not indicate a bad input stream) when
  accessed by an HTTP/1.1 client.
  
  Added missing timeouts for sending header fields, error responses,
  and the last chunk of chunked encoding, each of which could have
  resulted in a process being stuck in write forever.
  
  Reviewed by: Randy Terbush, Dean Gaudet
  
  Revision  Changes    Path
  1.205     +20 -0     apache/src/CHANGES
  
  Index: CHANGES
  ===================================================================
  RCS file: /export/home/cvs/apache/src/CHANGES,v
  retrieving revision 1.204
  retrieving revision 1.205
  diff -C3 -r1.204 -r1.205
  *** CHANGES	1997/03/19 03:12:51	1.204
  --- CHANGES	1997/03/20 17:10:08	1.205
  ***************
  *** 1,5 ****
  --- 1,25 ----
    Changes with Apache 1.2b8
    
  +   *) Fixed the handling of module and script-added header fields.
  +      Improved the interface for sending header fields and reduced
  +      the duplication of code between sending okay responses and errors.
  +      We now always send both headers_out and err_headers_out, and
  +      ensure that the server-reserved fields are not being overridden,
  +      while not overriding those that are not reserved.  [Roy Fielding]
  + 
  +   *) Fixed the determination of whether or not we should make the
  +      connection persistent for all of the cases where some other part
  +      of the server has already indicated that we should not.  Also
  +      improved the ordering of the test so that chunked encoding will
  +      be set whenever it is desired instead of only when KeepAlive
  +      is enabled. Added persistent connection capability for most error
  +      responses (those that do not indicate a bad input stream) when
  +      accessed by an HTTP/1.1 client. [Roy Fielding]
  + 
  +   *) Added missing timeouts for sending header fields, error responses,
  +      and the last chunk of chunked encoding, each of which could have
  +      resulted in a process being stuck in write forever. [Roy Fielding]
  + 
      *) mod_auth_anon required an @ or a . in the email address, not both.
         [Dirk vanGulik]
    
  
  
  
  1.92      +10 -1     apache/src/httpd.h
  
  Index: httpd.h
  ===================================================================
  RCS file: /export/home/cvs/apache/src/httpd.h,v
  retrieving revision 1.91
  retrieving revision 1.92
  diff -C3 -r1.91 -r1.92
  *** httpd.h	1997/03/11 06:04:39	1.91
  --- httpd.h	1997/03/20 17:10:09	1.92
  ***************
  *** 330,335 ****
  --- 330,343 ----
    #define is_HTTP_CLIENT_ERROR(x) (((x) >= 400)&&((x) < 500))
    #define is_HTTP_SERVER_ERROR(x) (((x) >= 500)&&((x) < 600))
    
  + #define status_drops_connection(x) (((x) == HTTP_BAD_REQUEST)           || \
  +                                     ((x) == HTTP_REQUEST_TIME_OUT)      || \
  +                                     ((x) == HTTP_LENGTH_REQUIRED)       || \
  +                                     ((x) == HTTP_REQUEST_ENTITY_TOO_LARGE) || \
  +                                     ((x) == HTTP_REQUEST_URI_TOO_LARGE) || \
  +                                     ((x) == HTTP_INTERNAL_SERVER_ERROR) || \
  +                                     ((x) == HTTP_SERVICE_UNAVAILABLE))
  + 
    
    #define METHODS 8
    #define M_GET 0
  ***************
  *** 459,465 ****
       * write modules to add to that environment.
       *
       * The difference between headers_out and err_headers_out is that the
  !    * latter are printed even on error, and persist across internal redirects
       * (so the headers printed for ErrorDocument handlers will have them).
       *
       * The 'notes' table is for notes from one module to another, with no
  --- 467,473 ----
       * write modules to add to that environment.
       *
       * The difference between headers_out and err_headers_out is that the
  !    * latter persist across internal redirects
       * (so the headers printed for ErrorDocument handlers will have them).
       *
       * The 'notes' table is for notes from one module to another, with no
  ***************
  *** 647,652 ****
  --- 655,661 ----
    
    char *get_token (pool *p, char **accept_line, int accept_white);
    int find_token (pool *p, const char *line, const char *tok);
  + int find_last_token (pool *p, const char *line, const char *tok);
         
    int is_url(const char *u);
    extern int unescape_url(char *url);
  
  
  
  1.50      +16 -0     apache/src/util.c
  
  Index: util.c
  ===================================================================
  RCS file: /export/home/cvs/apache/src/util.c,v
  retrieving revision 1.49
  retrieving revision 1.50
  diff -C3 -r1.49 -r1.50
  *** util.c	1997/03/11 03:24:24	1.49
  --- util.c	1997/03/20 17:10:09	1.50
  ***************
  *** 676,681 ****
  --- 676,697 ----
        return 0;
    }
    
  + int find_last_token (pool *p, const char *line, const char *tok)
  + {
  +     int llen, tlen, lidx;
  + 
  +     if (!line) return 0;
  + 
  +     llen = strlen(line);
  +     tlen = strlen(tok);
  +     lidx = llen - tlen;
  + 
  +     if ((lidx < 0) ||
  +         ((lidx > 0) && !(isspace(line[lidx-1]) || line[lidx-1] == ',')))
  +         return 0;
  + 
  +     return (strncasecmp(&line[lidx], tok, tlen) == 0);
  + }
    
    char *escape_shell_cmd(pool *p, const char *s) {
        register int x,y,l;
  
  
  
  1.17      +1 -0      apache/src/alloc.h
  
  Index: alloc.h
  ===================================================================
  RCS file: /export/home/cvs/apache/src/alloc.h,v
  retrieving revision 1.16
  retrieving revision 1.17
  diff -C3 -r1.16 -r1.17
  *** alloc.h	1997/01/01 18:10:14	1.16
  --- alloc.h	1997/03/20 17:10:10	1.17
  ***************
  *** 149,154 ****
  --- 149,155 ----
    void table_merge (table *, const char *name, const char *more_val);
    void table_unset (table *, const char *key);
    void table_add (table *, const char *name, const char *val);
  + void table_do (int (*comp)(void *, char *, char *), void *rec, table *t, ...);
    
    table *overlay_tables (pool *p, const table *overlay, const table *base);     
    
  
  
  
  1.24      +69 -20    apache/src/alloc.c
  
  Index: alloc.c
  ===================================================================
  RCS file: /export/home/cvs/apache/src/alloc.c,v
  retrieving revision 1.23
  retrieving revision 1.24
  diff -C3 -r1.23 -r1.24
  *** alloc.c	1997/03/10 09:27:41	1.23
  --- alloc.c	1997/03/20 17:10:10	1.24
  ***************
  *** 571,615 ****
    
    void table_set (table *t, const char *key, const char *val)
    {
        table_entry *elts = (table_entry *)t->elts;
  !     int i;
    
        for (i = 0; i < t->nelts; ++i)
  !         if (!strcasecmp (elts[i].key, key)) {
  ! 	    elts[i].val = pstrdup (t->pool, val);
  ! 	    return;
    	}
    
  !     elts = (table_entry *)push_array(t);
  !     elts->key = pstrdup (t->pool, key);
  !     elts->val = pstrdup (t->pool, val);
    }
    
    void table_unset( table *t, const char *key ) 
    {
        table_entry *elts = (table_entry *)t->elts;
  -     int i;   
  -     int j;   
     
        for (i = 0; i < t->nelts; ++i)
            if (!strcasecmp (elts[i].key, key)) {
     
  !             /* found the element to skip over
                 * there are any number of ways to remove an element from
                 * a contiguous block of memory.  I've chosen one that
                 * doesn't do a memcpy/bcopy/array_delete, *shrug*...
                 */
  !             j = i;
  !             ++i;
  !             for ( ; i < t->nelts; ) {
  !                 elts[j].key = elts[i].key;
  !                 elts[j].val = elts[i].val;
  !                 ++i;
  !                 ++j;
  !             };
                --t->nelts;
  - 
  -             return;
            }
    }     
    
  --- 571,620 ----
    
    void table_set (table *t, const char *key, const char *val)
    {
  +     register int i, j, k;
        table_entry *elts = (table_entry *)t->elts;
  !     int done = 0;
    
        for (i = 0; i < t->nelts; ++i)
  ! 	if (!strcasecmp (elts[i].key, key)) {
  ! 	    if (!done) {
  ! 	        elts[i].val = pstrdup(t->pool, val);
  ! 	        done = 1;
  ! 	    }
  ! 	    else {     /* delete an extraneous element */
  !                 for (j = i, k = i + 1; k < t->nelts; ++j, ++k) {
  !                     elts[j].key = elts[k].key;
  !                     elts[j].val = elts[k].val;
  !                 }
  !                 --t->nelts;
  ! 	    }
    	}
    
  !     if (!done) {
  !         elts = (table_entry *)push_array(t);
  !         elts->key = pstrdup (t->pool, key);
  !         elts->val = pstrdup (t->pool, val);
  !     }
    }
    
    void table_unset( table *t, const char *key ) 
    {
  +     register int i, j, k;   
        table_entry *elts = (table_entry *)t->elts;
     
        for (i = 0; i < t->nelts; ++i)
            if (!strcasecmp (elts[i].key, key)) {
     
  !             /* found an element to skip over
                 * there are any number of ways to remove an element from
                 * a contiguous block of memory.  I've chosen one that
                 * doesn't do a memcpy/bcopy/array_delete, *shrug*...
                 */
  !             for (j = i, k = i + 1; k < t->nelts; ++j, ++k) {
  !                 elts[j].key = elts[k].key;
  !                 elts[j].val = elts[k].val;
  !             }
                --t->nelts;
            }
    }     
    
  ***************
  *** 641,646 ****
  --- 646,695 ----
    table* overlay_tables (pool *p, const table *overlay, const table *base)
    {
        return append_arrays (p, overlay, base);
  + }
  + 
  + /* And now for something completely abstract ...
  +  *
  +  * For each key value given as a vararg:
  +  *   run the function pointed to as
  +  *     int comp(void *r, char *key, char *value);
  +  *   on each valid key-value pair in the table t that matches the vararg key,
  +  *   or once for every valid key-value pair if the vararg list is empty,
  +  *   until the function returns false (0) or we finish the table.
  +  *
  +  * Note that we restart the traversal for each vararg, which means that
  +  * duplicate varargs will result in multiple executions of the function
  +  * for each matching key.  Note also that if the vararg list is empty,
  +  * only one traversal will be made and will cut short if comp returns 0.
  +  *
  +  * Note that the table_get and table_merge functions assume that each key in
  +  * the table is unique (i.e., no multiple entries with the same key).  This
  +  * function does not make that assumption, since it (unfortunately) isn't
  +  * true for some of Apache's tables.
  +  *
  +  * Note that rec is simply passed-on to the comp function, so that the
  +  * caller can pass additional info for the task.
  +  */
  + void table_do (int (*comp)(void *, char *, char *), void *rec, table *t, ...)
  + {
  +     va_list vp;
  +     char *argp;
  +     table_entry *elts = (table_entry *)t->elts;
  +     int rv, i;
  +   
  +     va_start(vp, t);
  + 
  +     argp = va_arg(vp, char *);
  + 
  +     do {
  +         for (rv = 1, i = 0; rv && (i < t->nelts); ++i) {
  +             if (elts[i].key && (!argp || !strcasecmp(elts[i].key, argp))) {
  +                 rv = (*comp)(rec, elts[i].key, elts[i].val);
  +             }
  +         }
  +     } while (argp && ((argp = va_arg(vp, char *)) != NULL));
  + 
  +     va_end(vp);
    }
    
    /*****************************************************************
  
  
  
  1.18      +5 -1      apache/src/http_protocol.h
  
  Index: http_protocol.h
  ===================================================================
  RCS file: /export/home/cvs/apache/src/http_protocol.h,v
  retrieving revision 1.17
  retrieving revision 1.18
  diff -C3 -r1.17 -r1.18
  *** http_protocol.h	1997/01/10 19:28:53	1.17
  --- http_protocol.h	1997/03/20 17:10:10	1.18
  ***************
  *** 59,65 ****
    
    request_rec *read_request (conn_rec *c);
    
  ! /* Send header for http response */
    
    void send_http_header (request_rec *l);     
    
  --- 59,69 ----
    
    request_rec *read_request (conn_rec *c);
    
  ! /* Send a single HTTP header field */
  ! 
  ! int send_header_field (request_rec *r, char *fieldname, char *fieldval);
  ! 
  ! /* Send the Status-Line and header fields for HTTP response */
    
    void send_http_header (request_rec *l);     
    
  
  
  
  1.109     +253 -249  apache/src/http_protocol.c
  
  Index: http_protocol.c
  ===================================================================
  RCS file: /export/home/cvs/apache/src/http_protocol.c,v
  retrieving revision 1.108
  retrieving revision 1.109
  diff -C3 -r1.108 -r1.109
  *** http_protocol.c	1997/03/19 03:09:24	1.108
  --- http_protocol.c	1997/03/20 17:10:11	1.109
  ***************
  *** 110,162 ****
    
    int set_byterange (request_rec *r)
    {
  !     char *range = table_get (r->headers_in, "Range");
  !     char *if_range = table_get (r->headers_in, "If-Range");
  !     char ts[MAX_STRING_LEN], *match;
        long range_start, range_end;
    
  !     /* Also check, for backwards-compatibility with second-draft
  !      * Luotonen/Franks byte-ranges (e.g. Netscape Navigator 2-3)
         *
         * We support this form, with Request-Range, and (farther down) we
         * send multipart/x-byteranges instead of multipart/byteranges for
         * Request-Range based requests to work around a bug in Netscape
  !      * Navigator 2 and 3.
         */
    
  !     if (!range) range = table_get (r->headers_in, "Request-Range");
    
  -     /* Reasons we won't do ranges... */
  - 
  -     if (!r->clength || r->assbackwards) return 0;
        if (!range || strncmp(range, "bytes=", 6)) {
    	table_set (r->headers_out, "Accept-Ranges", "bytes");
    	return 0;
        }
    
  !     /* Check the If-Range header. Golly, this is a long if statement */
    
  !     if (if_range
  ! 	&& !((if_range[0] == '"') /* an entity tag */
  ! 	     && (match = table_get(r->headers_out, "Etag"))
  ! 	     && (match[0] == '"') && !strcasecmp(if_range, match))
  ! 	&& !((if_range[0] != '"') /* a date */
  ! 	     && (match = table_get(r->headers_out, "Last-Modified"))
  ! 	     && (!strcasecmp(if_range, match))))
  ! 	return 0;
        
        if (!strchr(range, ',')) {
    	/* A single range */
    	if (!parse_byterange(pstrdup(r->pool, range + 6), r->clength,
  ! 			     &range_start, &range_end))
    	    return 0;
    
    	r->byterange = 1;
    
  ! 	ap_snprintf(ts, sizeof(ts), "bytes %ld-%ld/%ld", range_start, range_end,
  ! 		r->clength);
  ! 	table_set(r->headers_out, "Content-Range",
  ! 		  pstrdup(r->pool, ts));
    	ap_snprintf(ts, sizeof(ts), "%ld", range_end - range_start + 1);
    	table_set(r->headers_out, "Content-Length", ts);
        }
  --- 110,163 ----
    
    int set_byterange (request_rec *r)
    {
  !     char *range, *if_range, *match;
  !     char ts[MAX_STRING_LEN];
        long range_start, range_end;
    
  !     if (!r->clength || r->assbackwards) return 0;
  ! 
  !     /* Check for Range request-header (HTTP/1.1) or Request-Range for
  !      * backwards-compatibility with second-draft Luotonen/Franks
  !      * byte-ranges (e.g. Netscape Navigator 2-3).
         *
         * We support this form, with Request-Range, and (farther down) we
         * send multipart/x-byteranges instead of multipart/byteranges for
         * Request-Range based requests to work around a bug in Netscape
  !      * Navigator 2-3 and MSIE 3.
         */
    
  !     if (!(range = table_get(r->headers_in, "Range")))
  !           range = table_get(r->headers_in, "Request-Range");
    
        if (!range || strncmp(range, "bytes=", 6)) {
    	table_set (r->headers_out, "Accept-Ranges", "bytes");
    	return 0;
        }
    
  !     /* Check the If-Range header for Etag or Date */
    
  !     if ((if_range = table_get(r->headers_in, "If-Range"))) {
  !         if (if_range[0] == '"') {
  !             if (!(match = table_get(r->headers_out, "Etag")) ||
  !                 (strcasecmp(if_range, match) != 0))
  !                 return 0;
  !         }
  !         else if (!(match = table_get(r->headers_out, "Last-Modified")) ||
  !                  (strcasecmp(if_range, match) != 0))
  !             return 0;
  !     }
        
        if (!strchr(range, ',')) {
    	/* A single range */
    	if (!parse_byterange(pstrdup(r->pool, range + 6), r->clength,
  ! 	                     &range_start, &range_end))
    	    return 0;
    
    	r->byterange = 1;
    
  ! 	ap_snprintf(ts, sizeof(ts), "bytes %ld-%ld/%ld",
  ! 	            range_start, range_end, r->clength);
  ! 	table_set(r->headers_out, "Content-Range", ts);
    	ap_snprintf(ts, sizeof(ts), "%ld", range_end - range_start + 1);
    	table_set(r->headers_out, "Content-Length", ts);
        }
  ***************
  *** 167,173 ****
    	long tlength = 0;
    	
    	r->byterange = 2;
  ! 	ap_snprintf(boundary, sizeof(boundary), "%lx%lx", r->request_time, (long)getpid());
    	r->boundary = pstrdup(r->pool, boundary);
    	while (internal_byterange(0, &tlength, r, &r_range, NULL, NULL));
    	ap_snprintf(ts, sizeof(ts), "%ld", tlength);
  --- 168,175 ----
    	long tlength = 0;
    	
    	r->byterange = 2;
  ! 	ap_snprintf(boundary, sizeof(boundary), "%lx%lx",
  ! 	            r->request_time, (long)getpid());
    	r->boundary = pstrdup(r->pool, boundary);
    	while (internal_byterange(0, &tlength, r, &r_range, NULL, NULL));
    	ap_snprintf(ts, sizeof(ts), "%ld", tlength);
  ***************
  *** 180,186 ****
        return 1;
    }
    
  ! int each_byterange (request_rec *r, long *offset, long *length) {
        return internal_byterange(1, NULL, r, &r->range, offset, length);
    }
    
  --- 182,189 ----
        return 1;
    }
    
  ! int each_byterange (request_rec *r, long *offset, long *length)
  ! {
        return internal_byterange(1, NULL, r, &r->range, offset, length);
    }
    
  ***************
  *** 197,203 ****
     */
    
    static int internal_byterange(int realreq, long *tlength, request_rec *r,
  ! 			      char **r_range, long *offset, long *length) {
        long range_start, range_end;
        char *range;
    
  --- 200,207 ----
     */
    
    static int internal_byterange(int realreq, long *tlength, request_rec *r,
  ! 			      char **r_range, long *offset, long *length)
  ! {
        long range_start, range_end;
        char *range;
    
  ***************
  *** 248,281 ****
        r->clength = clength;
    
        ap_snprintf (ts, sizeof(ts), "%ld", clength);
  !     table_set (r->headers_out, "Content-Length", pstrdup (r->pool, ts));
    
        return 0;
    }
    
    int set_keepalive(request_rec *r)
    {
  !     char *conn   = table_get(r->headers_in,  "Connection");
  !     char *length = table_get(r->headers_out, "Content-Length");
  !     char *tenc   = table_get(r->headers_out, "Transfer-Encoding");
  !     int ka_sent;
  ! 
  !     if (r->connection->keepalive == -1)  /* Did we get bad input? */
  !         r->connection->keepalive = 0;
  !     else if (r->server->keep_alive && (!r->server->keep_alive_max ||
  ! 	(r->server->keep_alive_max > r->connection->keepalives)) &&
  ! 	(r->server->keep_alive_timeout > 0) &&
  ! 	(r->status == HTTP_NOT_MODIFIED || r->status == HTTP_NO_CONTENT
  ! 	 || r->header_only || length || tenc ||
  ! 	 ((r->proto_num >= 1001) && (r->chunked = 1))) &&
  ! 	(!find_token(r->pool, conn, "close")) &&
  ! 	((ka_sent = find_token(r->pool, conn, "keep-alive")) ||
  ! 	 r->proto_num >= 1001)) {
  ! 	/*
  ! 	 * The (r->chunked = 1) in the above expression is a side-effect
  ! 	 * that sets the output to chunked encoding if it is not already
  ! 	 * length-delimited.  It is not a bug, though it is annoying.
  ! 	 */
    	char header[256];
    	int left = r->server->keep_alive_max - r->connection->keepalives;
    	
  --- 252,313 ----
        r->clength = clength;
    
        ap_snprintf (ts, sizeof(ts), "%ld", clength);
  !     table_set (r->headers_out, "Content-Length", ts);
    
        return 0;
    }
    
    int set_keepalive(request_rec *r)
    {
  !     int ka_sent = 0;
  !     int wimpy   = find_token(r->pool,
  !                              table_get(r->headers_out, "Connection"), "close");
  !     char *conn  = table_get(r->headers_in, "Connection");
  ! 
  !     /* The following convoluted conditional determines whether or not
  !      * the current connection should remain persistent after this response
  !      * (a.k.a. HTTP Keep-Alive) and whether or not the output message
  !      * body should use the HTTP/1.1 chunked transfer-coding.  In English,
  !      *
  !      *   IF  we have not marked this connection as errored;
  !      *   and the response body has a defined length due to the status code
  !      *       being 304 or 204, the request method being HEAD, already
  !      *       having defined Content-Length or Transfer-Encoding: chunked, or
  !      *       the request version being HTTP/1.1 and thus capable of being set
  !      *       as chunked [we know the (r->chunked = 1) side-effect is ugly];
  !      *   and the server configuration enables keep-alive;
  !      *   and the server configuration has a reasonable inter-request timeout;
  !      *   and there is no maximum # requests or the max hasn't been reached;
  !      *   and the response status does not require a close;
  !      *   and the response generator has not already indicated close;
  !      *   and the client did not request non-persistence (Connection: close);
  !      *   and    the client is requesting an HTTP/1.0-style keep-alive
  !      *          and we haven't been configured to ignore the buggy twit,
  !      *       or the client claims to be HTTP/1.1 compliant (perhaps a proxy);
  !      *   THEN we can be persistent, which requires more headers be output.
  !      *
  !      * Note that the condition evaluation order is extremely important.
  !      */
  !     if ((r->connection->keepalive != -1) &&
  !         ((r->status == HTTP_NOT_MODIFIED) ||
  !          (r->status == HTTP_NO_CONTENT) ||
  !          r->header_only ||
  !          table_get(r->headers_out, "Content-Length") ||
  !          find_last_token(r->pool,
  !                          table_get(r->headers_out, "Transfer-Encoding"),
  !                          "chunked") ||
  !          ((r->proto_num >= 1001) && (r->chunked = 1))) &&
  !         r->server->keep_alive &&
  !         (r->server->keep_alive_timeout > 0) &&
  !         ((r->server->keep_alive_max == 0) ||
  !          (r->server->keep_alive_max > r->connection->keepalives)) &&
  !         !status_drops_connection(r->status) &&
  !         !wimpy &&
  !         !find_token(r->pool, conn, "close") &&
  !         (((ka_sent = find_token(r->pool, conn, "keep-alive")) &&
  !           !table_get(r->subprocess_env, "nokeepalive")) ||
  !          (r->proto_num >= 1001))
  !        ) {
    	char header[256];
    	int left = r->server->keep_alive_max - r->connection->keepalives;
    	
  ***************
  *** 290,308 ****
    	    else
    		ap_snprintf(header, sizeof(header), "timeout=%d",
    			    r->server->keep_alive_timeout);
  ! 	    rputs("Connection: Keep-Alive\015\012", r);
  ! 	    rvputs(r, "Keep-Alive: ", header, "\015\012", NULL);
    	}
    
    	return 1;
        }
    
  !     /* We only really need to send this to HTTP/1.1 clients, but we
         * always send it anyway, because a broken proxy may identify itself
         * as HTTP/1.0, but pass our request along with our HTTP/1.1 tag
         * to a HTTP/1.1 client. Better safe than sorry.
         */
  !     rputs("Connection: close\015\012", r);
    
        return 0;
    }
  --- 322,345 ----
    	    else
    		ap_snprintf(header, sizeof(header), "timeout=%d",
    			    r->server->keep_alive_timeout);
  ! 	    table_set(r->headers_out, "Keep-Alive", header);
  ! 	    table_merge(r->headers_out, "Connection", "Keep-Alive");
    	}
    
    	return 1;
        }
    
  !     /* Otherwise, we need to indicate that we will be closing this
  !      * connection immediately after the current response.
  !      *
  !      * We only really need to send "close" to HTTP/1.1 clients, but we
         * always send it anyway, because a broken proxy may identify itself
         * as HTTP/1.0, but pass our request along with our HTTP/1.1 tag
         * to a HTTP/1.1 client. Better safe than sorry.
         */
  !     table_merge(r->headers_out, "Connection", "close");
  ! 
  !     r->connection->keepalive = 0;
    
        return 0;
    }
  ***************
  *** 310,319 ****
    int set_last_modified(request_rec *r, time_t mtime)
    {
        char *etag, weak_etag[MAX_STRING_LEN];
  !     char *if_modified_since = table_get(r->headers_in, "If-Modified-Since");
  !     char *if_unmodified     = table_get(r->headers_in, "If-Unmodified-Since");
  !     char *if_nonematch      = table_get(r->headers_in, "If-None-Match");
  !     char *if_match          = table_get(r->headers_in, "If-Match");
        time_t now = time(NULL);
    
        if (now < 0)
  --- 347,353 ----
    int set_last_modified(request_rec *r, time_t mtime)
    {
        char *etag, weak_etag[MAX_STRING_LEN];
  !     char *if_match, *if_modified_since, *if_unmodified, *if_nonematch;
        time_t now = time(NULL);
    
        if (now < 0)
  ***************
  *** 343,349 ****
    		(unsigned long)mtime);
    
        etag = weak_etag + ((r->request_time - mtime > 1) ? 2 : 0);
  !     table_set (r->headers_out, "ETag", etag);
    
        /* Check for conditional requests --- note that we only want to do
         * this if we are successful so far and we are not processing a
  --- 377,383 ----
    		(unsigned long)mtime);
    
        etag = weak_etag + ((r->request_time - mtime > 1) ? 2 : 0);
  !     table_set(r->headers_out, "ETag", etag);
    
        /* Check for conditional requests --- note that we only want to do
         * this if we are successful so far and we are not processing a
  ***************
  *** 362,368 ****
         *    respond with a status of 412 (Precondition Failed).
         */
    
  !     if (if_match) {
            if ((if_match[0] != '*') && !find_token(r->pool, if_match, etag))
                return HTTP_PRECONDITION_FAILED;
        }
  --- 396,402 ----
         *    respond with a status of 412 (Precondition Failed).
         */
    
  !     if ((if_match = table_get(r->headers_in, "If-Match")) != NULL) {
            if ((if_match[0] != '*') && !find_token(r->pool, if_match, etag))
                return HTTP_PRECONDITION_FAILED;
        }
  ***************
  *** 373,379 ****
         *    respond with a status of 412 (Precondition Failed).
         */
    
  !     else if (if_unmodified) {
            time_t ius = parseHTTPdate(if_unmodified);
    
            if ((ius != BAD_DATE) && (mtime > ius))
  --- 407,414 ----
         *    respond with a status of 412 (Precondition Failed).
         */
    
  !     else if ((if_unmodified = table_get(r->headers_in, "If-Unmodified-Since"))
  !              != NULL) {
            time_t ius = parseHTTPdate(if_unmodified);
    
            if ((ius != BAD_DATE) && (mtime > ius))
  ***************
  *** 389,395 ****
         *       respond with a status of 412 (Precondition Failed).
         */
    
  !     if (if_nonematch) {
            if ((if_nonematch[0] == '*') || find_token(r->pool,if_nonematch,etag))
                return (r->method_number == M_GET) ? HTTP_NOT_MODIFIED
                                                   : HTTP_PRECONDITION_FAILED;
  --- 424,430 ----
         *       respond with a status of 412 (Precondition Failed).
         */
    
  !     if ((if_nonematch = table_get(r->headers_in, "If-None-Match")) != NULL) {
            if ((if_nonematch[0] == '*') || find_token(r->pool,if_nonematch,etag))
                return (r->method_number == M_GET) ? HTTP_NOT_MODIFIED
                                                   : HTTP_PRECONDITION_FAILED;
  ***************
  *** 403,409 ****
         * A date later than the server's current request time is invalid.
         */
    
  !     else if (if_modified_since && (r->method_number == M_GET)) {
            time_t ims = parseHTTPdate(if_modified_since);
    
            if ((ims >= mtime) && (ims <= r->request_time))
  --- 438,445 ----
         * A date later than the server's current request time is invalid.
         */
    
  !     else if ((r->method_number == M_GET) && ((if_modified_since =
  !               table_get(r->headers_in, "If-Modified-Since")) != NULL)) {
            time_t ims = parseHTTPdate(if_modified_since);
    
            if ((ims >= mtime) && (ims <= r->request_time))
  ***************
  *** 746,756 ****
    
        /* Get the request... */
        
  !     keepalive_timeout ("read", r);
  !     if (!read_request_line (r)) return NULL;
  !     if (!r->assbackwards) get_mime_headers (r);
    
  ! /* handle Host header here, to get virtual server */
    
        if (r->hostname || (r->hostname = table_get(r->headers_in, "Host")))
          check_hostalias(r);
  --- 782,799 ----
    
        /* Get the request... */
        
  !     keepalive_timeout("read request line", r);
  !     if (!read_request_line (r)) {
  !         kill_timeout(r);
  !         return NULL;
  !     }
  !     if (!r->assbackwards) {
  !         hard_timeout("read request headers", r);
  !         get_mime_headers (r);
  !     }
  !     kill_timeout(r);
    
  !     /* handle Host header here, to get virtual server */
    
        if (r->hostname || (r->hostname = table_get(r->headers_in, "Host")))
          check_hostalias(r);
  ***************
  *** 760,767 ****
        /* we may have switched to another server */
        r->per_dir_config = r->server->lookup_defaults;
    
  !     kill_timeout (r);
  !     conn->keptalive = 0;   /* We now have a request - so no more short timeouts */
        
        if(!strcmp(r->method, "HEAD")) {
            r->header_only=1;
  --- 803,809 ----
        /* we may have switched to another server */
        r->per_dir_config = r->server->lookup_defaults;
    
  !     conn->keptalive = 0;   /* We now have a request to play with */
        
        if(!strcmp(r->method, "HEAD")) {
            r->header_only=1;
  ***************
  *** 966,1015 ****
       return LEVEL_500;                  /* 600 or above is also illegal */
    }
    
    
    void basic_http_header (request_rec *r)
    {
  !     BUFF *fd = r->connection->client;
  !     char *t;
        
        if (r->assbackwards) return;
        
        if (!r->status_line)
            r->status_line = status_lines[index_of_response(r->status)];
        
  !     if(table_get(r->subprocess_env,"force-response-1.0"))
  ! 	t="HTTP/1.0";
        else
  ! 	t=SERVER_PROTOCOL;
  !     bvputs(fd, t, " ", r->status_line, "\015\012", NULL);
  !     bvputs(fd,"Date: ",gm_timestr_822 (r->pool, r->request_time),
  ! 	   "\015\012", NULL);
  !     bvputs(fd,"Server: ", SERVER_VERSION, "\015\012", NULL);
  ! }
    
  ! char *nuke_mime_parms (pool *p, char *content_type)
  ! {
  !     /* How marvelous.  Arena doesn't *accept* "text/html; level=3"
  !      * as a MIME type, so we have to strip off the parms.
  !      */
    
  ! #ifndef ARENA_BUG_WORKAROUND
  !     return content_type;
  ! #else
  ! 
  !     char *cp = strchr(content_type, ';');
  ! 
  !     if (cp) {
  !         content_type = pstrdup (p, content_type);
  ! 	cp = strchr (content_type, ';');
  ! 	
  !         while (cp > content_type && isspace (cp[-1]))
  ! 	    --cp;
  ! 	*cp = '\0';
  !     }
    
  !     return content_type;
  ! #endif
    }
    
    static char *make_allow(request_rec *r)
  --- 1008,1048 ----
       return LEVEL_500;                  /* 600 or above is also illegal */
    }
    
  + /* Send a single HTTP header field to the client.  Note that this function
  +  * is used in calls to table_do(), so their interfaces are co-dependent.
  +  * In other words, don't change this one without checking table_do in alloc.c.
  +  * It returns true unless there was a write error of some kind.
  +  */
  + int send_header_field (request_rec *r, char *fieldname, char *fieldval)
  + {
  +     return (0 < bvputs(r->connection->client,
  +                        fieldname, ": ", fieldval, "\015\012", NULL));
  + }
    
    void basic_http_header (request_rec *r)
    {
  !     char *protocol;
        
        if (r->assbackwards) return;
        
        if (!r->status_line)
            r->status_line = status_lines[index_of_response(r->status)];
        
  !     if (table_get(r->subprocess_env,"force-response-1.0"))
  ! 	protocol = "HTTP/1.0";
        else
  ! 	protocol = SERVER_PROTOCOL;
    
  !     /* Output the HTTP/1.x Status-Line and the Date and Server fields */
    
  !     bvputs(r->connection->client,
  !            protocol, " ", r->status_line, "\015\012", NULL);
    
  !     send_header_field(r, "Date", gm_timestr_822(r->pool, r->request_time));
  !     send_header_field(r, "Server", SERVER_VERSION);
  ! 
  !     table_unset(r->headers_out, "Date");    /* Avoid bogosity */
  !     table_unset(r->headers_out, "Server");
    }
    
    static char *make_allow(request_rec *r)
  ***************
  *** 1037,1050 ****
    
    int send_http_trace (request_rec *r)
    {
  -     array_header *hdrs_arr = table_elts(r->headers_in);
  -     table_entry *hdrs = (table_entry *)hdrs_arr->elts;
  -     int i;
  - 
        /* Get the original request */
        while (r->prev) r = r->prev;
    
  !     soft_timeout ("send", r);
    
        r->content_type = "message/http";
        send_http_header(r);
  --- 1070,1079 ----
    
    int send_http_trace (request_rec *r)
    {
        /* Get the original request */
        while (r->prev) r = r->prev;
    
  !     soft_timeout ("send TRACE", r);
    
        r->content_type = "message/http";
        send_http_header(r);
  ***************
  *** 1053,1062 ****
    
        rvputs( r, r->the_request, "\015\012", NULL );
    
  !     for (i = 0; i < hdrs_arr->nelts; ++i) {
  !       if (!hdrs[i].key) continue;
  !       rvputs(r, hdrs[i].key, ": ", hdrs[i].val, "\015\012", NULL);
  !     }
    
        kill_timeout(r);
        return OK;
  --- 1082,1090 ----
    
        rvputs( r, r->the_request, "\015\012", NULL );
    
  !     table_do((int (*)(void *, char *, char *))send_header_field,
  !              (void *)r, r->headers_in, NULL);
  !     bputs("\015\012", r->connection->client);
    
        kill_timeout(r);
        return OK;
  ***************
  *** 1064,1083 ****
    
    int send_http_options(request_rec *r)
    {
  !     BUFF *fd = r->connection->client;
  !     const long int zero=0L;
    
        if (r->assbackwards) return DECLINED;
    
  !     soft_timeout ("send", r);
    
        basic_http_header(r);
  -     bputs("Connection: close\015\012", fd);
  -     bvputs(fd, "Allow: ", make_allow(r), "\015\012", NULL);
  -     bputs("\015\012", fd);
    
  !     bsetopt(fd, BO_BYTECT, &zero);
  !     kill_timeout (r);
    
        return OK;
    }
  --- 1092,1115 ----
    
    int send_http_options(request_rec *r)
    {
  !     const long int zero = 0L;
    
        if (r->assbackwards) return DECLINED;
    
  !     soft_timeout("send OPTIONS", r);
    
        basic_http_header(r);
    
  !     table_set(r->headers_out, "Content-Length", "0");
  !     table_set(r->headers_out, "Allow", make_allow(r));
  !     set_keepalive(r);
  ! 
  !     table_do((int (*)(void *, char *, char *))send_header_field,
  !              (void *)r, r->headers_out, NULL);
  !     bputs("\015\012", r->connection->client);
  ! 
  !     kill_timeout(r);
  !     bsetopt(r->connection->client, BO_BYTECT, &zero);
    
        return OK;
    }
  ***************
  *** 1099,1204 ****
    
    void send_http_header(request_rec *r)
    {
  -     conn_rec *c = r->connection;
  -     BUFF *fd = c->client;
  -     const long int zero=0L;
  -     array_header *hdrs_arr;
  -     table_entry *hdrs;
        int i;
  !     
        core_dir_config *dir_conf =
          (core_dir_config *)get_module_config(r->per_dir_config, &core_module);
        char *default_type = dir_conf->default_type;
      
        if (r->assbackwards) {
            if(!r->main)
  ! 	    bsetopt(fd, BO_BYTECT, &zero);
    	r->sent_bodyct = 1;
    	return;
        }
        
  !     basic_http_header (r);
    
  !     if (!table_get(r->subprocess_env, "nokeepalive"))
  !         set_keepalive (r);
    
        if (r->chunked) {
  ! 	bputs("Transfer-Encoding: chunked\015\012", fd);
  ! 	/* RFC2068 #4.4: Messages MUST NOT include both a Content-Length
  ! 	 * header field and the "chunked" transfer coding. */
            table_unset(r->headers_out, "Content-Length");
        }
    
        if (r->byterange > 1)
  !         bvputs(fd, "Content-Type: multipart/",
  ! 	       use_range_x(r) ? "x-byteranges" : "byteranges",
  ! 	       "; boundary=", r->boundary, "\015\012", NULL);
        else if (r->content_type)
  !         bvputs(fd, "Content-Type: ", 
  ! 		 nuke_mime_parms (r->pool, r->content_type), "\015\012", NULL);
        else if (default_type)
  !         bvputs(fd, "Content-Type: ", default_type, "\015\012", NULL);
        
        if (r->content_encoding)
  !         bvputs(fd,"Content-Encoding: ", r->content_encoding, "\015\012", NULL);
        
        if (r->content_languages && r->content_languages->nelts) {
  ! 	int i;
  ! 	bputs("Content-Language: ", fd);
  ! 	for (i = 0; i < r->content_languages->nelts; ++i) {
  ! 	    char *lang = ((char**)(r->content_languages->elts))[i];
  ! 	    bvputs(fd, i ? ", " : "", lang, NULL);
  ! 	}
  ! 	bputs("\015\012", fd);
        }
        else if (r->content_language)
  !         bvputs(fd,"Content-Language: ", r->content_language, "\015\012", NULL);
  ! 
  !     /* We now worry about this here */
    
  !     if (r->no_cache && (r->proto_num >= 1001))
  !         bputs ("Cache-Control: private\015\012", fd);
  !     else if (r->no_cache)
  !         bvputs(fd,"Expires: ", gm_timestr_822(r->pool, r->request_time),
  ! 	       "\015\012", NULL);
    
  !     hdrs_arr = table_elts(r->headers_out);
  !     hdrs = (table_entry *)hdrs_arr->elts;
  !     for (i = 0; i < hdrs_arr->nelts; ++i) {
  !         if (!hdrs[i].key) continue;
  ! 	if (r->no_cache && !strcasecmp(hdrs[i].key, "Expires")) continue;
  ! 	bvputs(fd, hdrs[i].key, ": ", hdrs[i].val, "\015\012", NULL);
        }
    
  !     hdrs_arr = table_elts(r->err_headers_out);
  !     hdrs = (table_entry *)hdrs_arr->elts;
  !     for (i = 0; i < hdrs_arr->nelts; ++i) {
  !         if (!hdrs[i].key) continue;
  ! 	if (r->no_cache && !strcasecmp(hdrs[i].key, "Expires")) continue;
  ! 	bvputs(fd, hdrs[i].key, ": ", hdrs[i].val, "\015\012", NULL);
  !     }
    
  !     bputs("\015\012",fd);
    
  !     bsetopt(fd, BO_BYTECT, &zero);
        r->sent_bodyct = 1;		/* Whatever follows is real body stuff... */
    
        /* Set buffer flags for the body */
  !     if (r->chunked) bsetflag(fd, B_CHUNK, 1);
    }
    
  ! void finalize_request_protocol (request_rec *r) {
  !     BUFF *fd = r->connection->client;
  ! 
        /* Turn off chunked encoding */
    
        if (r->chunked) {
  !         bsetflag(fd, B_CHUNK, 0);
  ! 	bputs("0\015\012", fd);
    	/* If we had footer "headers", we'd send them now */
  ! 	bputs("\015\012", fd);
        }
  - 
    }
    
    /* Here we deal with getting the request message body from the client.
  --- 1131,1227 ----
    
    void send_http_header(request_rec *r)
    {
        int i;
  !     const long int zero = 0L;
        core_dir_config *dir_conf =
          (core_dir_config *)get_module_config(r->per_dir_config, &core_module);
        char *default_type = dir_conf->default_type;
      
        if (r->assbackwards) {
            if(!r->main)
  ! 	    bsetopt(r->connection->client, BO_BYTECT, &zero);
    	r->sent_bodyct = 1;
    	return;
        }
  + 
  +     /* Now that we are ready to send a response, we need to combine the two
  +      * header field tables into a single table.  If we don't do this, our
  +      * later attempts to set or unset a given fieldname might be bypassed.
  +      */
  +     r->headers_out=overlay_tables(r->pool, r->err_headers_out, r->headers_out);
        
  !     soft_timeout("send headers", r);
    
  !     basic_http_header(r);
  ! 
  !     set_keepalive(r);
    
        if (r->chunked) {
  !         table_merge(r->headers_out, "Transfer-Encoding", "chunked");
            table_unset(r->headers_out, "Content-Length");
        }
    
        if (r->byterange > 1)
  !         table_set(r->headers_out, "Content-Type",
  !                   pstrcat(r->pool, "multipart", use_range_x(r) ? "/x-" : "/",
  !                           "byteranges; boundary=", r->boundary, NULL));
        else if (r->content_type)
  !         table_set(r->headers_out, "Content-Type", r->content_type);
        else if (default_type)
  !         table_set(r->headers_out, "Content-Type", default_type);
        
        if (r->content_encoding)
  !         table_set(r->headers_out, "Content-Encoding", r->content_encoding);
        
        if (r->content_languages && r->content_languages->nelts) {
  !         for (i = 0; i < r->content_languages->nelts; ++i) {
  !             table_merge(r->headers_out, "Content-Language",
  !                         ((char**)(r->content_languages->elts))[i]);
  !         }
        }
        else if (r->content_language)
  !         table_set(r->headers_out, "Content-Language", r->content_language);
    
  !     /* Control cachability for non-cachable responses if not already set
  !      * by some other part of the server configuration.
  !      */
  !     if (r->no_cache) {
  !         if ((r->proto_num >= 1001) &&
  !             !table_get(r->headers_out, "Cache-Control"))
  !             table_add(r->headers_out, "Cache-Control", "private");
    
  !         if (!table_get(r->headers_out, "Expires"))
  !             table_add(r->headers_out, "Expires",
  !                       gm_timestr_822(r->pool, r->request_time));
        }
    
  !     /* Send the entire table of header fields, terminated by an empty line. */
  ! 
  !     table_do((int (*)(void *, char *, char *))send_header_field,
  !              (void *)r, r->headers_out, NULL);
  !     bputs("\015\012", r->connection->client);
    
  !     kill_timeout(r);
    
  !     bsetopt(r->connection->client, BO_BYTECT, &zero);
        r->sent_bodyct = 1;		/* Whatever follows is real body stuff... */
    
        /* Set buffer flags for the body */
  !     if (r->chunked) bsetflag(r->connection->client, B_CHUNK, 1);
    }
    
  ! void finalize_request_protocol (request_rec *r)
  ! {
        /* Turn off chunked encoding */
    
        if (r->chunked) {
  !         soft_timeout("send ending chunk", r);
  !         bsetflag(r->connection->client, B_CHUNK, 0);
  ! 	bputs("0\015\012", r->connection->client);
    	/* If we had footer "headers", we'd send them now */
  ! 	bputs("\015\012", r->connection->client);
  !         kill_timeout(r);
        }
    }
    
    /* Here we deal with getting the request message body from the client.
  ***************
  *** 1243,1249 ****
    int setup_client_block (request_rec *r, int read_policy)
    {
        char *tenc = table_get(r->headers_in, "Transfer-Encoding");
  !     char *lenp = table_get(r->headers_in, "Content-length");
    
        r->read_body    = read_policy;
        r->read_chunked = 0;
  --- 1266,1272 ----
    int setup_client_block (request_rec *r, int read_policy)
    {
        char *tenc = table_get(r->headers_in, "Transfer-Encoding");
  !     char *lenp = table_get(r->headers_in, "Content-Length");
    
        r->read_body    = read_policy;
        r->read_chunked = 0;
  ***************
  *** 1482,1488 ****
                w=bwrite(c->client, &buf[o], n);
    	    if(w <= 0)
    		break;
  ! 	    reset_timeout(r); /* reset timeout after successfule write */
                n-=w;
    	    o+=w;
            }
  --- 1505,1511 ----
                w=bwrite(c->client, &buf[o], n);
    	    if(w <= 0)
    		break;
  ! 	    reset_timeout(r); /* reset timeout after successful write */
                n-=w;
    	    o+=w;
            }
  ***************
  *** 1500,1507 ****
        return c;
    }
    
  ! int
  ! rputs(const char *str, request_rec *r)
    {
        if (r->connection->aborted) return EOF;
        SET_BYTES_SENT(r);
  --- 1523,1529 ----
        return c;
    }
    
  ! int rputs(const char *str, request_rec *r)
    {
        if (r->connection->aborted) return EOF;
        SET_BYTES_SENT(r);
  ***************
  *** 1518,1524 ****
    }
    
    int rprintf(request_rec *r,const char *fmt,...)
  !     {
        va_list vlist;
        int n;
    
  --- 1540,1546 ----
    }
    
    int rprintf(request_rec *r,const char *fmt,...)
  ! {
        va_list vlist;
        int n;
    
  ***************
  *** 1528,1537 ****
        va_end(vlist);
        SET_BYTES_SENT(r);
        return n;
  !     }
    
  ! int
  ! rvputs(request_rec *r, ...)
    {
        va_list args;
        int i, j, k;
  --- 1550,1558 ----
        va_end(vlist);
        SET_BYTES_SENT(r);
        return n;
  ! }
    
  ! int rvputs(request_rec *r, ...)
    {
        va_list args;
        int i, j, k;
  ***************
  *** 1564,1643 ****
        return bflush(r->connection->client);
    }
    
  - static void send_header(request_rec *r, char *hdr)
  - {
  -     char *val = table_get(r->headers_out, hdr);
  -     if (val) bvputs(r->connection->client, hdr, ": ", val, "\015\012", NULL);
  - }
  - 
    void send_error_response (request_rec *r, int recursive_error)
    {
  !     conn_rec *c = r->connection;
  !     char *custom_response;
  !     char *location = table_get (r->headers_out, "Location");
        int status = r->status;
        int idx = index_of_response (status);
    
        if (!r->assbackwards) {
  - 	int i;
  - 	table *err_hdrs_arr = r->err_headers_out;
  - 	table_entry *err_hdrs = (table_entry *)err_hdrs_arr->elts;
  - 	table *hdrs_arr = r->headers_out;
  - 	table_entry *hdrs = (table_entry *)hdrs_arr->elts;
      
  -         basic_http_header (r);
  - 	
    	/* For non-error statuses (2xx and 3xx), send out all the normal
    	 * headers unless it is a 304. Don't send a Location unless its
    	 * a redirect status (3xx).
    	 */
    
  - 	if ((is_HTTP_SUCCESS(status) || is_HTTP_REDIRECT(status)) &&
  - 	    status != HTTP_NOT_MODIFIED) {
  - 	    for (i = 0; i < hdrs_arr->nelts; ++i) {
  - 		if (!hdrs[i].key) continue;
  - 		if (!strcasecmp(hdrs[i].key, "Location") &&
  - 		    !is_HTTP_REDIRECT(status))
  - 		    continue;
  - 		bvputs(c->client, hdrs[i].key, ": ", hdrs[i].val,
  - 		       "\015\012", NULL);
  - 	    }
  - 	}
  - 	
    	if (status == HTTP_NOT_MODIFIED) {
  ! 	    send_header(r, "ETag");
  ! 	    send_header(r, "Content-Location");
  ! 	    send_header(r, "Expires");
  ! 	    send_header(r, "Cache-Control");
  ! 	    send_header(r, "Vary");
  ! 	    send_header(r, "Warning");
  ! 	    send_header(r, "WWW-Authenticate");
    	    set_keepalive(r);
  ! 	    bputs("\015\012", c->client);
    	    return;
    	}
    
  - 	/* Someday, we'd like to have persistent connections here.
  - 	 * They're especially useful for redirects, multiple choices
  - 	 * and auth requests. But we need to rewrite the rest of thi
  - 	 * section, so for now, we don't use it.
  - 	 */
  - 	bputs("Connection: close\015\012", c->client);
  - 
    	if ((status == METHOD_NOT_ALLOWED) || (status == NOT_IMPLEMENTED))
  ! 	    bvputs(c->client, "Allow: ", make_allow(r), "\015\012", NULL);
  ! 	
  ! 	for (i = 0; i < err_hdrs_arr->nelts; ++i) {
  ! 	    if (!err_hdrs[i].key) continue;
  ! 	    bvputs(c->client, err_hdrs[i].key, ": ", err_hdrs[i].val,
  ! 		   "\015\012", NULL);
  ! 	}
    
  ! 	bputs("Content-type: text/html\015\012\015\012", c->client);
  !     }
    
  !     if (r->header_only) return;
        
        if ((custom_response = response_code_string (r, idx))) {
            /*
    	 * We have a custom response output. This should only be
  --- 1585,1648 ----
        return bflush(r->connection->client);
    }
    
    void send_error_response (request_rec *r, int recursive_error)
    {
  !     BUFF *fd = r->connection->client;
        int status = r->status;
        int idx = index_of_response (status);
  +     char *custom_response;
  +     char *location = pstrdup(r->pool, table_get(r->headers_out, "Location"));
    
        if (!r->assbackwards) {
      
    	/* For non-error statuses (2xx and 3xx), send out all the normal
    	 * headers unless it is a 304. Don't send a Location unless its
    	 * a redirect status (3xx).
    	 */
    
    	if (status == HTTP_NOT_MODIFIED) {
  ! 	    r->headers_out = overlay_tables(r->pool, r->err_headers_out,
  ! 	                                             r->headers_out);
  ! 	    soft_timeout("send 304", r);
  ! 
  ! 	    basic_http_header(r);
    	    set_keepalive(r);
  ! 
  ! 	    table_do((int (*)(void *, char *, char *))send_header_field,
  ! 	             (void *)r, r->headers_out,
  ! 	             "Connection",
  ! 	             "Keep-Alive",
  ! 	             "ETag",
  ! 	             "Content-Location",
  ! 	             "Expires",
  ! 	             "Cache-Control",
  ! 	             "Vary",
  ! 	             "Warning",
  ! 	             "WWW-Authenticate",
  ! 	             NULL);
  ! 	    bputs("\015\012", fd);
  ! 
  ! 	    kill_timeout(r);
    	    return;
    	}
    
    	if ((status == METHOD_NOT_ALLOWED) || (status == NOT_IMPLEMENTED))
  ! 	    table_set(r->headers_out, "Allow", make_allow(r));
    
  ! 	if (!is_HTTP_REDIRECT(status))
  ! 	    table_unset(r->headers_out, "Location");
    
  ! 	r->content_type = "text/html";
  ! 	send_http_header(r);
  ! 
  ! 	if (r->header_only || (status == HTTP_NO_CONTENT)) {
  ! 	    finalize_request_protocol(r);
  ! 	    return;
  ! 	}
  !     }
        
  +     soft_timeout("send error body", r);
  + 
        if ((custom_response = response_code_string (r, idx))) {
            /*
    	 * We have a custom response output. This should only be
  ***************
  *** 1651,1671 ****
    	 * "). If it doesn't, we've got a recursive error, so find
    	 * the original error and output that as well.
    	 */
  !         if (custom_response[0] == '\"') { 
  !             bputs(custom_response+1, c->client);
  ! 	      return;
    	}
    	/* Redirect failed, so get back the original error
    	 */
    	while (r->prev && (r->prev->status != HTTP_OK))
  !           r = r->prev;
        }
        {
    	char *title = status_lines[idx];
    	/* folks decided they didn't want the error code in the H1 text */
    
    	char *h1 = 4 + status_lines[idx];
  - 	BUFF *fd = c->client;
    	
            bvputs
    	    (
  --- 1656,1677 ----
    	 * "). If it doesn't, we've got a recursive error, so find
    	 * the original error and output that as well.
    	 */
  ! 	if (custom_response[0] == '\"') { 
  ! 	    bputs(custom_response+1, fd);
  ! 	    kill_timeout(r);
  ! 	    finalize_request_protocol(r);
  ! 	    return;
    	}
    	/* Redirect failed, so get back the original error
    	 */
    	while (r->prev && (r->prev->status != HTTP_OK))
  ! 	    r = r->prev;
        }
        {
    	char *title = status_lines[idx];
    	/* folks decided they didn't want the error code in the H1 text */
    
    	char *h1 = 4 + status_lines[idx];
    	
            bvputs
    	    (
  ***************
  *** 1800,1817 ****
    	    break;
    	}
    
  !         if (recursive_error) {
  ! 	    char x[80];
  ! 	    ap_snprintf (x, sizeof(x), 
  ! 		"Additionally, an error of type %d was encountered\n",
  ! 		recursive_error);
  ! 	    bputs(x, fd);
  ! 	    bputs("while trying to use an ErrorDocument to\n", fd);
  ! 	    bputs("handle the request.\n", fd);
    	}
    	bputs("</BODY></HTML>\n", fd);
        }
  !         
    }
    
    /* Finally, this... it's here to support nph- scripts
  --- 1806,1821 ----
    	    break;
    	}
    
  ! 	if (recursive_error) {
  ! 	    bvputs(fd, "<P>Additionally, a ",
  ! 	           status_lines[index_of_response(recursive_error)],
  ! 	           "\nerror was encountered while trying to use an "
  ! 	           "ErrorDocument to handle the request.\n", NULL);
    	}
    	bputs("</BODY></HTML>\n", fd);
        }
  !     kill_timeout(r);
  !     finalize_request_protocol(r);
    }
    
    /* Finally, this... it's here to support nph- scripts