You are viewing a plain text version of this content. The canonical link for it is here.
Posted to cvs@httpd.apache.org by Chuck Murcko <ch...@hyperreal.com> on 1996/10/01 09:11:49 UTC

cvs commit: apache/src/modules/proxy mod_proxy.h proxy_cache.c proxy_connect.c proxy_ftp.c proxy_http.c proxy_util.c Makefile mod_proxy.c

chuck       96/10/01 00:11:48

  Modified:    src/modules/proxy  Makefile mod_proxy.c
  Added:       src/modules/proxy  mod_proxy.h proxy_cache.c proxy_connect.c
                        proxy_ftp.c  proxy_http.c proxy_util.c
  Log:
  Phase II - The Great Proxy Reorganization
  
  Layout with protocol abstraction, daemon gc in mind.
  
  Revision  Changes    Path
  1.3       +15 -11    apache/src/modules/proxy/Makefile
  
  Index: Makefile
  ===================================================================
  RCS file: /export/home/cvs/apache/src/modules/proxy/Makefile,v
  retrieving revision 1.2
  retrieving revision 1.3
  diff -C3 -r1.2 -r1.3
  *** Makefile	1996/09/29 13:58:55	1.2
  --- Makefile	1996/10/01 07:11:41	1.3
  ***************
  *** 50,56 ****
    # 
    # Makefile for the Apache proxy library
    # 
  ! # $Id: Makefile,v 1.2 1996/09/29 13:58:55 chuck Exp $
    #
    
    SHELL = /bin/sh
  --- 50,56 ----
    # 
    # Makefile for the Apache proxy library
    # 
  ! # $Id: Makefile,v 1.3 1996/10/01 07:11:41 chuck Exp $
    #
    
    SHELL = /bin/sh
  ***************
  *** 59,70 ****
    
    LIB=libproxy.a
    
  ! # define -DEXPLAIN if you want verbose debugging output
    CFLAGS=-I. -I$(INCDIR) $(AUX_CFLAGS)
    
    # Internal stuff, should not need changing.
  ! OBJS=mod_proxy.o
  ! PROXYSRC=mod_proxy.c
    
    default:	$(LIB)
    
  --- 59,73 ----
    
    LIB=libproxy.a
    
  ! # AUX_CFLAGS comes from higher level Makefile
    CFLAGS=-I. -I$(INCDIR) $(AUX_CFLAGS)
    
    # Internal stuff, should not need changing.
  ! OBJS=mod_proxy.o proxy_cache.o proxy_connect.o proxy_ftp.o proxy_http.o \
  ! proxy_util.o
  ! 
  ! PROXYSRC=mod_proxy.c proxy_cache.c proxy_connect.c proxy_ftp.c proxy_http.c \
  ! proxy_util.c
    
    default:	$(LIB)
    
  ***************
  *** 74,86 ****
    	$(RANLIB) $@
    
    # dependencies
  ! mod_proxy.o: $(INCDIR)/http_log.h
  ! mod_proxy.o: $(INCDIR)/http_main.h
  ! mod_proxy.o: $(INCDIR)/http_protocol.h
  ! mod_proxy.o: $(INCDIR)/http_config.h
  ! mod_proxy.o: $(INCDIR)/httpd.h
  ! mod_proxy.o: $(INCDIR)/md5.h
  ! mod_proxy.o: $(INCDIR)/explain.h
    
    # various forms of cleanup
    tidy:
  --- 77,90 ----
    	$(RANLIB) $@
    
    # dependencies
  ! mod_proxy.o proxy_cache.o proxy_connect.o proxy_ftp.o proxy_http.o \
  ! proxy_util.o: mod_proxy.h
  ! mod_proxy.o: $(INCDIR)/httpd.h $(INCDIR)/http_config.h
  ! proxy_cache.o: $(INCDIR)/httpd.h $(INCDIR)/http_config.h $(INCDIR)/md5.h
  ! proxy_connect.o: $(INCDIR)/httpd.h $(INCDIR)/http_config.h
  ! proxy_ftp.o: $(INCDIR)/httpd.h $(INCDIR)/http_config.h
  ! proxy_http.o: $(INCDIR)/httpd.h $(INCDIR)/http_config.h
  ! proxy_util.o: $(INCDIR)/httpd.h $(INCDIR)/http_config.h $(INCDIR)/md5.h
    
    # various forms of cleanup
    tidy:
  
  
  
  1.3       +51 -3052  apache/src/modules/proxy/mod_proxy.c
  
  Index: mod_proxy.c
  ===================================================================
  RCS file: /export/home/cvs/apache/src/modules/proxy/mod_proxy.c,v
  retrieving revision 1.2
  retrieving revision 1.3
  diff -C3 -r1.2 -r1.3
  *** mod_proxy.c	1996/09/29 14:10:58	1.2
  --- mod_proxy.c	1996/10/01 07:11:42	1.3
  ***************
  *** 50,197 ****
     *
     */
    
  ! /* $Id: mod_proxy.c,v 1.2 1996/09/29 14:10:58 chuck Exp $ */
    
  ! /*
  ! Note that the Explain() stuff is not yet complete.
  ! Also note numerous FIXMEs and CHECKMEs which should be eliminated.
  ! 
  ! If TESTING is set, then garbage collection doesn't delete ... probably a good
  ! idea when hacking.
  ! 
  ! This code is still experimental!
  ! 
  ! Things to do:
  ! 
  ! 1. Make it garbage collect in the background, not while someone is waiting for
  ! a response!
  ! 
  ! 2. Check the logic thoroughly.
  ! 
  ! 3. Empty directories are only removed the next time round (but this does avoid
  ! two passes). Consider doing them the first time round.
  ! 
  ! Ben Laurie <be...@algroup.co.uk> 30 Mar 96
  ! 
  ! More things to do:
  ! 
  ! 0. Massive code cleanup & break into multiple files; link as a lib
  ! 
  ! 1. add PASV mode for ftp now that it works
  ! 
  ! 2. Add gopher & WAIS
  ! 
  ! 3. Various other fixups to insure no NULL strings parsed, etc.
  ! 
  ! 4. NoProxy directive for excluding sites to proxy
  !  
  ! 5. Imply NoCache * if cache directory is not configured, to enable proxy
  !    without cache (and avoid SIGSEGV)
  !  
  ! 6. Implement protocol handler struct a la Apache module handlers
  !  
  ! 7. Use a cache expiry database for more efficient GC
  ! 
  ! 8. Handle multiple IPs for doconnect()
  ! 
  ! 9. Bulletproof GC against SIGALRM
  ! 
  ! Chuck Murcko <ch...@telebase.com> 28 Sep 96
  ! 
  ! */
  ! 
  ! #define TESTING	0
  ! #undef EXPLAIN
  ! 
  ! #include "httpd.h"
  ! #include "http_config.h"
  ! #include "http_log.h"
  ! #include "http_main.h"
  ! #include "http_protocol.h"
  ! 
  ! #include "md5.h"
  ! 
  ! #include <utime.h>
  ! 
  ! #include "explain.h"
  ! 
  ! DEF_Explain
  ! 
  ! #define	SEC_ONE_DAY		86400	/* one day, in seconds */
  ! #define	SEC_ONE_HR		3600	/* one hour, in seconds */
  ! 
  ! #define	DEFAULT_FTP_DATA_PORT	20
  ! #define	DEFAULT_FTP_PORT	21
  ! #define	DEFAULT_GOPHER_PORT	70
  ! #define	DEFAULT_NNTP_PORT	119
  ! #define	DEFAULT_WAIS_PORT	210
  ! #define	DEFAULT_HTTPS_PORT	443
  ! #define	DEFAULT_SNEWS_PORT	563
  ! #define	DEFAULT_PROSPERO_PORT	1525	/* WARNING: conflict w/Oracle */
  ! 
  ! /* Some WWW schemes and their default ports; this is basically /etc/services */
  ! static struct
  ! {
  !     const char *scheme;
  !     int port;
  ! } defports[]={
  !     { "ftp",      DEFAULT_FTP_PORT},
  !     { "gopher",   DEFAULT_GOPHER_PORT},
  !     { "http",     DEFAULT_PORT},
  !     { "nntp",     DEFAULT_NNTP_PORT},
  !     { "wais",     DEFAULT_WAIS_PORT},
  !     { "https",    DEFAULT_HTTPS_PORT},
  !     { "snews",    DEFAULT_SNEWS_PORT},
  !     { "prospero", DEFAULT_PROSPERO_PORT},
  !     { NULL, -1}  /* unknown port */
  ! };
  ! 
  ! 
  ! /* static information about a remote proxy */
  ! struct proxy_remote
  ! {
  !     const char *scheme;    /* the schemes handled by this proxy, or '*' */
  !     const char *protocol;  /* the scheme used to talk to this proxy */
  !     const char *hostname;  /* the hostname of this proxy */
  !     int port;              /* the port for this proxy */
  ! };
  ! 
  ! struct proxy_alias {
  !     char *real;
  !     char *fake;
  ! };
  ! 
  ! struct nocache_entry {
  !     char *name;
  ! };
  ! 
  ! #define DEFAULT_CACHE_SPACE 5
  ! #define DEFAULT_CACHE_MAXEXPIRE SEC_ONE_DAY
  ! #define DEFAULT_CACHE_EXPIRE    SEC_ONE_HR
  ! #define DEFAULT_CACHE_LMFACTOR (0.1)
  ! 
  ! /* static information about the local cache */
  ! struct cache_conf
  ! {
  !     const char *root;   /* the location of the cache directory */
  !     int space;          /* Maximum cache size (in 1024 bytes) */
  !     int maxexpire;      /* Maximum time to keep cached files in secs */
  !     int defaultexpire;  /* default time to keep cached file in secs */
  !     double lmfactor;    /* factor for estimating expires date */
  !     int gcinterval;     /* garbage collection interval, in seconds */
  !     int dirlevels;	/* Number of levels of subdirectories */
  !     int dirlength;	/* Length of subdirectory names */
  ! };
  ! 
  ! typedef struct
  ! {
  ! 
  !     struct cache_conf cache;  /* cache configuration */
  !     array_header *proxies;
  !     array_header *aliases;
  !     array_header *nocaches;
  !     int req;                 /* true if proxy requests are enabled */
  ! } proxy_server_conf;
    
    /*
     * A Web proxy module. Stages:
  --- 50,58 ----
     *
     */
    
  ! /* $Id: mod_proxy.c,v 1.3 1996/10/01 07:11:42 chuck Exp $ */
    
  ! #include "mod_proxy.h"
    
    /*
     * A Web proxy module. Stages:
  ***************
  *** 203,252 ****
     *  handler:        handle proxy requests
     */
    
  - struct hdr_entry
  - {
  -     char *field;
  -     char *value;
  - };
  - 
  - /* caching information about a request */
  - struct cache_req
  - {
  -     request_rec *req;  /* the request */
  -     char *url;         /* the URL requested */
  -     char *filename;    /* name of the cache file, or NULL if no cache */
  -     char *tempfile;    /* name of the temporary file, of NULL if not caching */
  -     time_t ims;        /* if-modified-since date of request; -1 if no header */
  -     BUFF *fp;          /* the cache file descriptor if the file is cached
  -                           and may be returned, or NULL if the file is
  -                           not cached (or must be reloaded) */
  -     time_t expire;      /* calculated expire date of cached entity */
  -     time_t lmod;        /* last-modified date of cached entity */
  -     time_t date;        /* the date the cached file was last touched */
  -     int version;        /* update count of the file */
  -     unsigned int len;   /* content length */
  -     char *protocol;     /* Protocol, and major/minor number, e.g. HTTP/1.1 */
  -     int status;         /* the status of the cached file */
  -     char *resp_line;    /* the whole status like (protocol, code + message) */
  -     array_header *hdrs; /* the HTTP headers of the file */
  - };
  -       
  - 
  - extern module proxy_module;
  - 
  - 
  - static int http_canon(request_rec *r, char *url, const char *scheme,
  - 		      int def_port);
  - static int ftp_canon(request_rec *r, char *url);
  - 
  - static int http_handler(request_rec *r, struct cache_req *c, char *url,
  - 			const char *proxyhost, int proxyport);
  - static int ftp_handler(request_rec *r, struct cache_req *c, char *url);
  - 
  - static int connect_handler(request_rec *r, struct cache_req *c, char *url);
  - 
  - static BUFF *cache_error(struct cache_req *r);
  - 
    /* -------------------------------------------------------------- */
    /* Translate the URL into a 'filename' */
    
  --- 64,69 ----
  ***************
  *** 332,2270 ****
    proxy_fixup(request_rec *r)
    {
        char *url, *p;
  !     int i;
  ! 
  !     if (strncmp(r->filename, "proxy:", 6) != 0) return DECLINED;
  ! 
  !     url = &r->filename[6];
  ! /* lowercase the scheme */
  !     p = strchr(url, ':');
  !     if (p == NULL || p == url) return BAD_REQUEST;
  !     for (i=0; i != p - url; i++) url[i] = tolower(url[i]);
  ! 
  ! /* canonicalise each specific scheme */
  !     if (strncmp(url, "http:", 5) == 0)
  ! 	return http_canon(r, url+5, "http", DEFAULT_PORT);
  !     else if (strncmp(url, "ftp:", 4) == 0) return ftp_canon(r, url+4);
  !     else return OK; /* otherwise; we've done the best we can */
  ! }
  ! 
  ! /* already called in the knowledge that the characters are hex digits */
  ! static int
  ! hex2c(const char *x)
  ! {
  !     int i, ch;
  ! 
  !     ch = x[0];
  !     if (isdigit(ch)) i = ch - '0';
  !     else if (isupper(ch)) i = ch - ('A' - 10);
  !     else i = ch - ('a' - 10);
  !     i <<= 4;
  ! 
  !     ch = x[1];
  !     if (isdigit(ch)) i += ch - '0';
  !     else if (isupper(ch)) i += ch - ('A' - 10);
  !     else i += ch - ('a' - 10);
  !     return i;
  ! }
  ! 
  ! 
  ! static void
  ! c2hex(int ch, char *x)
  ! {
  !     int i;
  ! 
  !     x[0] = '%';
  !     i = (ch & 0xF0) >> 4;
  !     if (i >= 10) x[1] = ('A' - 10) + i;
  !     else x[1] = '0' + i;
  ! 
  !     i = ch & 0x0F;
  !     if (i >= 10) x[2] = ('A' - 10) + i;
  !     else x[2] = '0' + i;
  ! }
  ! 
  ! /*
  !  * canonicalise a URL-encoded string
  !  */
  ! 
  ! enum enctype { enc_path, enc_search, enc_user, enc_fpath, enc_parm };
  ! 
  ! /*
  !  * Decodes a '%' escaped string, and returns the number of characters
  !  */
  ! static int
  ! decodeenc(char *x)
  ! {
  !     int i, j, ch;
  ! 
  !     if (x[0] == '\0') return 0; /* special case for no characters */
  !     for (i=0, j=0; x[i] != '\0'; i++, j++)
  !     {
  ! /* decode it if not already done */
  ! 	ch = x[i];
  ! 	if ( ch == '%' && isxdigit(x[i+1]) && isxdigit(x[i+2]))
  ! 	{
  ! 	    ch = hex2c(&x[i+1]);
  ! 	    i += 2;
  ! 	}
  ! 	x[j] = ch;
  !     }
  !     x[j] = '\0';
  !     return j;
  ! }
  ! 
  ! 
  ! /*
  !  * Convert a URL-encoded string to canonical form.
  !  * It decodes characters which need not be encoded,
  !  * and encodes those which must be encoded, and does not touch
  !  * those which must not be touched.
  !  */
  ! static char *
  ! canonenc(pool *p, const char *x, int len, enum enctype t, int isenc)
  ! {
  !     int i, j, ispath, ch;
  !     char *y;
  !     const char *allowed;  /* characters which should not be encoded */
  !     const char *reserved;  /* characters which much not be en/de-coded */
  ! 
  ! /* N.B. in addition to :@&=, this allows ';' in an http path
  !  * and '?' in an ftp path -- this may be revised
  !  * 
  !  * Also, it makes a '+' character in a search string reserved, as
  !  * it may be form-encoded. (Although RFC 1738 doesn't allow this -
  !  * it only permits ; / ? : @ = & as reserved chars.)
  !  */
  !     if (t == enc_path) allowed = "$-_.+!*'(),;:@&=";
  !     else if (t == enc_search) allowed = "$-_.!*'(),;:@&=";
  !     else if (t == enc_user) allowed = "$-_.+!*'(),;@&=";
  !     else if (t == enc_fpath) allowed = "$-_.+!*'(),?:@&=";
  !     else /* if (t == enc_parm) */ allowed = "$-_.+!*'(),?/:@&=";
  ! 
  !     if (t == enc_path) reserved = "/";
  !     else if (t == enc_search) reserved = "+";
  !     else reserved = "";
  ! 
  !     y = palloc(p, 3*len+1);
  !     ispath = (t == enc_path);
  ! 
  !     for (i=0, j=0; i < len; i++, j++)
  !     {
  ! /* always handle '/' first */
  ! 	ch = x[i];
  ! 	if (ind(reserved, ch) != -1)
  ! 	{
  ! 	    y[j] = ch;
  ! 	    continue;
  ! 	}
  ! /* decode it if not already done */
  ! 	if (isenc && ch == '%')
  ! 	{
  ! 	    if (!isxdigit(x[i+1]) || !isxdigit(x[i+2]))
  ! 		return NULL;
  ! 	    ch = hex2c(&x[i+1]);
  ! 	    i += 2;
  ! 	    if (ch != 0 && ind(reserved, ch) != -1)
  ! 	    {  /* keep it encoded */
  ! 		c2hex(ch, &y[j]);
  ! 		j += 2;
  ! 		continue;
  ! 	    }
  ! 	}
  ! /* recode it, if necessary */
  ! 	if (!isalnum(ch) && ind(allowed, ch) == -1)
  ! 	{
  ! 	    c2hex(ch, &y[j]);
  ! 	    j += 2;
  ! 	} else y[j] = ch;
  !     }
  !     y[j] = '\0';
  !     return y;
  ! }
  ! 
  ! /*
  !  * Parses network-location.
  !  *    urlp           on input the URL; on output the path, after the leading /
  !  *    user           NULL if no user/password permitted
  !  *    password       holder for password
  !  *    host           holder for host
  !  *    port           port number; only set if one is supplied.
  !  *
  !  * Returns an error string.
  !  */
  ! static char *
  ! canon_netloc(pool *pool, char **const urlp, char **userp, char **passwordp,
  ! 	    char **hostp, int *port)
  ! {
  !     int i;
  !     char *p, *host, *url=*urlp;
  ! 
  !     if (url[0] != '/' || url[1] != '/') return "Malformed URL";
  !     host = url + 2;
  !     url = strchr(host, '/');
  !     if (url == NULL)
  ! 	url = "";
  !     else
  ! 	*(url++) = '\0';  /* skip seperating '/' */
  ! 
  !     if (userp != NULL)
  !     {
  ! 	char *user=NULL, *password = NULL;
  ! 	p = strchr(host, '@');
  ! 
  ! 	if (p != NULL)
  ! 	{
  ! 	    *p = '\0';
  ! 	    user = host;
  ! 	    host = p + 1;
  ! 
  ! /* find password */
  ! 	    p = strchr(user, ':');
  ! 	    if (p != NULL)
  ! 	    {
  ! 		*p = '\0';
  ! 		password = canonenc(pool, p+1, strlen(p+1), enc_user, 1);
  ! 		if (password == NULL)
  ! 		    return "Bad %-escape in URL (password)";
  ! 	    }
  ! 
  ! 	    user = canonenc(pool, user, strlen(user), enc_user, 1);
  ! 	    if (user == NULL) return "Bad %-escape in URL (username)";
  ! 	}
  ! 	*userp = user;
  ! 	*passwordp = password;
  !     }
  ! 
  !     p = strchr(host, ':');
  !     if (p != NULL)
  !     {
  ! 	*(p++) = '\0';
  ! 	
  ! 	for (i=0; p[i] != '\0'; i++)
  ! 	    if (!isdigit(p[i])) break;
  ! 
  ! 	if (i == 0 || p[i] != '\0')
  ! 	    return "Bad port number in URL";
  ! 	*port = atoi(p);
  ! 	if (*port > 65535) return "Port number in URL > 65535";
  !     }
  !     str_tolower(host); /* DNS names are case-insensitive */
  !     if (*host == '\0') return "Missing host in URL";
  ! /* check hostname syntax */
  !     for (i=0; host[i] != '\0'; i++)
  ! 	if (!isdigit(host[i]) && host[i] != '.')
  ! 	    break;
  !  /* must be an IP address */
  !     if (host[i] == '\0' && (inet_addr(host) == -1 || inet_network(host) == -1))
  ! 	    return "Bad IP address in URL";
  ! 
  !     *urlp = url;
  !     *hostp = host;
  ! 
  !     return NULL;
  ! }
  ! 
  ! /*
  !  * checks an encoded ftp string for bad characters, namely, CR, LF or
  !  * non-ascii character
  !  */
  ! static int
  ! ftp_check_string(const char *x)
  ! {
  !     int i, ch;
  ! 
  !     for (i=0; x[i] != '\0'; i++)
  !     {
  ! 	ch = x[i];
  ! 	if ( ch == '%' && isxdigit(x[i+1]) && isxdigit(x[i+2]))
  ! 	{
  ! 	    ch = hex2c(&x[i+1]);
  ! 	    i += 2;
  ! 	}
  ! 	if (ch == '\015' || ch == '\012' || (ch & 0x80)) return 0;
  !     }
  !     return 1;
  ! }
  ! 
  ! /*
  !  * Canonicalise ftp URLs.
  !  */
  ! static int
  ! ftp_canon(request_rec *r, char *url)
  ! {
  !     char *user, *password, *host, *path, *parms, *p, sport[7];
  !     const char *err;
  !     int port;
  ! 
  !     port = DEFAULT_FTP_PORT;
  !     err = canon_netloc(r->pool, &url, &user, &password, &host, &port);
  !     if (err) return BAD_REQUEST;
  !     if (user != NULL && !ftp_check_string(user)) return BAD_REQUEST;
  !     if (password != NULL && !ftp_check_string(password)) return BAD_REQUEST;
  ! 
  ! /* now parse path/parameters args, according to rfc1738 */
  ! /* N.B. if this isn't a true proxy request, then the URL path
  !  * (but not query args) has already been decoded.
  !  * This gives rise to the problem of a ; being decoded into the
  !  * path.
  !  */
  !     p = strchr(url, ';');
  !     if (p != NULL)
  !     {
  ! 	*(p++) = '\0';
  ! 	parms = canonenc(r->pool, p, strlen(p), enc_parm, r->proxyreq);
  ! 	if (parms == NULL) return BAD_REQUEST;
  !     } else
  ! 	parms = "";
  ! 
  !     path = canonenc(r->pool, url, strlen(url), enc_path, r->proxyreq);
  !     if (path == NULL) return BAD_REQUEST;
  !     if (!ftp_check_string(path)) return BAD_REQUEST;
  ! 
  !     if (!r->proxyreq && r->args != NULL)
  !     {
  ! 	if (p != NULL)
  ! 	{
  ! 	    p = canonenc(r->pool, r->args, strlen(r->args), enc_parm, 1);
  ! 	    if (p == NULL) return BAD_REQUEST;
  ! 	    parms = pstrcat(r->pool, parms, "?", p, NULL);
  ! 	}
  ! 	else
  ! 	{
  ! 	    p = canonenc(r->pool, r->args, strlen(r->args), enc_path, 1);
  ! 	    if (p == NULL) return BAD_REQUEST;
  ! 	    path = pstrcat(r->pool, path, "?", p, NULL);
  ! 	}
  ! 	r->args = NULL;
  !     }
  ! 
  ! /* now, rebuild URL */
  ! 
  !     if (port != DEFAULT_FTP_PORT) sprintf(sport, ":%d", port);
  !     else sport[0] = '\0';
  ! 
  !     r->filename = pstrcat(r->pool, "proxy:ftp://", (user != NULL) ? user : "",
  ! 			  (password != NULL) ? ":" : "",
  ! 			  (password != NULL) ? password : "",
  ! 			  (user != NULL) ? "@" : "", host, sport, "/", path,
  ! 			  (parms[0] != '\0') ? ";" : "", parms, NULL);
  ! 
  !     return OK;
  ! }
  ! 
  ! 
  ! /*
  !  * Canonicalise http-like URLs.
  !  *  scheme is the scheme for the URL
  !  *  url    is the URL starting with the first '/'
  !  *  def_port is the default port for this scheme.
  !  */
  ! static int
  ! http_canon(request_rec *r, char *url, const char *scheme, int def_port)
  ! {
  !     char *host, *path, *search, *p, sport[7];
  !     const char *err;
  !     int port;
  ! 
  ! /* do syntatic check.
  !  * We break the URL into host, port, path, search
  !  */
  !     port = def_port;
  !     err = canon_netloc(r->pool, &url, NULL, NULL, &host, &port);
  !     if (err) return BAD_REQUEST;
  ! 
  ! /* now parse path/search args, according to rfc1738 */
  ! /* N.B. if this isn't a true proxy request, then the URL _path_
  !  * has already been decoded
  !  */
  !     if (r->proxyreq)
  !     {
  ! 	p = strchr(url, '?');
  ! 	if (p != NULL) *(p++) = '\0';
  !     } else
  ! 	p = r->args;
  ! 
  ! /* process path */
  !     path = canonenc(r->pool, url, strlen(url), enc_path, r->proxyreq);
  !     if (path == NULL) return BAD_REQUEST;
  ! 
  ! /* process search */
  !     if (p != NULL)
  !     {
  ! 	search = p;
  ! 	if (search == NULL) return BAD_REQUEST;
  !     } else
  ! 	search = NULL;
  ! 
  !     if (port != def_port) sprintf(sport, ":%d", port);
  !     else sport[0] = '\0';
  ! 
  !     r->filename = pstrcat(r->pool, "proxy:", scheme, "://", host, sport, "/",
  ! 			  path, (search) ? "?" : "", (search) ? search : "", NULL);
  !     return OK;
  ! }
  ! 
  ! static const char *lwday[7]=
  ! {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
  ! static const char *wday[7]=
  ! {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"};
  ! static const char *months[12]=
  ! {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov",
  !  "Dec"};
  ! 
  ! /*
  !  * If the date is a valid RFC 850 date or asctime() date, then it
  !  * is converted to the RFC 1123 format, otherwise it is not modified.
  !  * This routine is not very fast at doing conversions, as it uses
  !  * sscanf and sprintf. However, if the date is already correctly
  !  * formatted, then it exits very quickly.
  !  */
  ! static char *
  ! date_canon(pool *p, char *x)
  ! {
  !     int wk, mday, year, hour, min, sec, mon;
  !     char *q, month[4], zone[4], week[4];
  !     
  !     q = strchr(x, ',');
  !     /* check for RFC 850 date */
  !     if (q != NULL && q - x > 3 && q[1] == ' ')
  !     {
  ! 	*q = '\0';
  ! 	for (wk=0; wk < 7; wk++)
  ! 	    if (strcmp(x, lwday[wk]) == 0) break;
  ! 	*q = ',';
  ! 	if (wk == 7) return x;  /* not a valid date */
  ! 	if (q[4] != '-' || q[8] != '-' || q[11] != ' ' || q[14] != ':' ||
  ! 	    q[17] != ':' || strcmp(&q[20], " GMT") != 0) return x;
  ! 	if (sscanf(q+2, "%u-%3s-%u %u:%u:%u %3s", &mday, month, &year,
  ! 		   &hour, &min, &sec, zone) != 7) return x;
  ! 	if (year < 70) year += 2000;
  ! 	else year += 1900;
  !     } else
  !     {
  ! /* check for acstime() date */
  ! 	if (x[3] != ' ' || x[7] != ' ' || x[10] != ' ' || x[13] != ':' ||
  ! 	    x[16] != ':' || x[19] != ' ' || x[24] != '\0') return x;
  ! 	if (sscanf(x, "%3s %3s %u %u:%u:%u %u", week, month, &mday, &hour,
  ! 		   &min, &sec, &year) != 7) return x;
  ! 	for (wk=0; wk < 7; wk++)
  ! 	    if (strcmp(week, wday[wk]) == 0) break;
  ! 	if (wk == 7) return x;
  !     }
  ! 
  ! /* check date */
  !     for (mon=0; mon < 12; mon++) if (strcmp(month, months[mon]) == 0) break;
  !     if (mon == 12) return x;
  ! /*
  !  *  it doesn't do any harm to convert an invalid date from one format to
  !  * another
  !  */
  ! #if 0
  !     if (hour > 23 || min > 60 || sec > 62 || mday == 0 || mday > 31) return x;
  !     if (mday == 31 && (mon == 1 || mon == 3 || mon == 5 || mon == 8 || mon == 10))
  ! 	return x;
  !     if (mday > 29 && mon == 1) return x;
  !     if (mday == 29 && mon == 1)
  ! 	if (year%4 != 0 || (year%100 == 0 && year%400 != 0)) return x;
  ! #endif
  ! 
  !     if (strlen(x) < 31) x = palloc(p, 31);
  !     sprintf(x, "%s, %.2d %s %d %.2d:%.2d:%.2d GMT", wday[wk], mday,
  ! 	    months[mon], year, hour, min, sec);
  !     return x;
  ! }
  ! 
  ! 
  ! /* -------------------------------------------------------------- */
  ! /* Invoke handler */
  ! 
  ! /* Utility routines */
  ! 
  ! /*
  !  * Reads headers from a connection and returns an array of headers.
  !  * Returns NULL on file error
  !  */
  ! static array_header *
  ! read_headers(pool *pool, char *buffer, int size, BUFF *f)
  ! {
  !     int gotcr, len, i, j;
  !     array_header *resp_hdrs;
  !     struct hdr_entry *hdr;
  !     char *p;
  ! 
  !     resp_hdrs = make_array(pool, 10, sizeof(struct hdr_entry));
  !     hdr = NULL;
  ! 
  !     gotcr = 1;
  !     for (;;)
  !     {
  ! 	len = bgets(buffer, size, f);
  ! 	if (len == -1) return NULL;
  ! 	if (len == 0) break;
  ! 	if (buffer[len-1] == '\n')
  ! 	{
  ! 	    buffer[--len] = '\0';
  ! 	    i = 1;
  ! 	} else
  ! 	    i = 0;
  ! 
  ! 	if (!gotcr || buffer[0] == ' ' || buffer[0] == '\t')
  ! 	{
  ! 	    /* a continuation header */
  ! 	    if (hdr == NULL)
  ! 	    {
  ! 		/* error!! */
  ! 		if (!i)
  ! 		{
  ! 		    i = bskiplf(f);
  ! 		    if (i == -1) return NULL;
  ! 		}
  ! 		gotcr = 1;
  ! 		continue;
  ! 	    }
  ! 	    hdr->value = pstrcat(pool, hdr->value, buffer, NULL);
  ! 	}
  ! 	else if (gotcr && len == 0) break;
  ! 	else
  ! 	{
  ! 	    p = strchr(buffer, ':');
  ! 	    if (p == NULL)
  ! 	    {
  ! 		/* error!! */
  ! 		if (!gotcr)
  ! 		{
  ! 		    i = bskiplf(f);
  ! 		    if (i == -1) return NULL;
  ! 		}
  ! 		gotcr = 1;
  ! 		hdr = NULL;
  ! 		continue;
  ! 	    }
  ! 	    hdr = push_array(resp_hdrs);
  ! 	    *(p++) = '\0';
  ! 	    hdr->field = pstrdup(pool, buffer);
  ! 	    while (*p == ' ' || *p == '\t') p++;
  ! 	    hdr->value = pstrdup(pool, p);
  ! 	    gotcr = i;
  ! 	}
  !     }
  ! 
  !     hdr = (struct hdr_entry *)resp_hdrs->elts;
  !     for (i=0; i < resp_hdrs->nelts; i++)
  !     {
  ! 	p = hdr[i].value;
  ! 	j = strlen(p);
  ! 	while (j > 0 && (p[j-1] == ' ' || p[j-1] == '\t')) j--;
  ! 	p[j] = '\0';
  !     }
  ! 
  !     return resp_hdrs;
  ! }
  ! 
  ! static long int
  ! send_dir(BUFF *f, request_rec *r, BUFF *f2, struct cache_req *c, char *url)
  ! {
  !     char buf[IOBUFSIZE];
  !     char buf2[IOBUFSIZE];
  !     char *filename;
  !     char urlptr[100];
  !     long total_bytes_sent;
  !     register int n, o, w;
  !     conn_rec *con = r->connection;
  ! 
  !     sprintf(buf,"<HTML><HEAD><TITLE>%s</TITLE></HEAD><BODY><H1>Directory %s</H1><HR><PRE>", url, url);
  !     bwrite(con->client, buf, strlen(buf));
  !     if (f2 != NULL) bwrite(f2, buf, strlen(buf));
  !     total_bytes_sent=strlen(buf);
  !     while(!con->aborted)
  !     {
  !         n = bgets(buf, IOBUFSIZE, f);
  !         if (n == -1) /* input error */
  !         {
  !             if (f2 != NULL) f2 = cache_error(c);
  !             break;
  !         }
  !         if (n == 0) break; /* EOF */
  !         if(buf[0]=='l')
  !         {
  !             char *link;
  ! 
  !             link=strstr(buf, " -> ");
  !             filename=link;
  !             do filename--; while (filename[0]!=' ');
  !             *(filename++)=0;
  !             *(link++)=0;
  !             sprintf(urlptr, "%s%s%s",url,(url[strlen(url)-1]=='/' ? "" : "/"), filename);
  !             sprintf(buf2, "%s <A HREF=\"%s\">%s %s</A>\015\012", buf, urlptr, filename, link);
  !             strcpy(buf, buf2);
  !             n=strlen(buf);
  !         }
  !         else if(buf[0]=='d' || buf[0]=='-' || buf[0]=='l')
  !         {
  !             filename=strrchr(buf, ' ');
  !             *(filename++)=0;
  !             filename[strlen(filename)-1]=0;
  !             /* Special handling for '.' and '..' */
  !             if (!strcmp(filename, "."))
  !             {
  !                 sprintf(urlptr, "%s",url);
  !                 sprintf(buf2, "%s <A HREF=\"%s\">%s</A>\015\012", buf, urlptr, filename);
  !             }
  !             else if (!strcmp(filename, ".."))
  !             {
  !                 char temp[200];
  !                 char newpath[200];
  !                 char *method, *host, *path, *file, *newfile;
  !    
  !                 strcpy(temp,url);
  !                 method=temp;
  ! 
  !                 host=strchr(method,':');
  !                 if (host == NULL) host="";
  !                 else *(host++)=0;
  !                 host++; host++;
  !                 
  !                 path=strchr(host,'/');
  !                 if (path == NULL) path="";
  !                 else *(path++)=0;
  !                 
  !                 strcpy(newpath,path);
  !                 newfile=strrchr(newpath,'/');
  !                 if (newfile) *(newfile)=0;
  !                 else newpath[0]=0;
  ! 
  !                 sprintf(urlptr,"%s://%s/%s",method,host,newpath);
  !                 sprintf(buf2, "%s <A HREF=\"%s\">%s</A>\015\012", buf, urlptr, filename);
  !             }
  !             else 
  !             {
  !                 sprintf(urlptr, "%s%s%s",url,(url[strlen(url)-1]=='/' ? "" : "/"), filename);
  !                 sprintf(buf2, "%s <A HREF=\"%s\">%s</A>\015\012", buf, urlptr, filename);
  !             }
  !             strcpy(buf, buf2);
  !             n=strlen(buf);
  !         }      
  ! 
  !         o=0;
  ! 	total_bytes_sent += n;
  ! 
  ! 	if (f2 != NULL)
  ! 	    if (bwrite(f2, buf, n) != n) f2 = cache_error(c);
  ! 	
  !         while(n && !r->connection->aborted) {
  !             w = bwrite(con->client, &buf[o], n);
  ! 	    if (w <= 0)
  ! 		break;
  ! 	    reset_timeout(r); /* reset timeout after successfule write */
  !             n-=w;
  !             o+=w;
  !         }
  !     }
  !     sprintf(buf,"</PRE><HR><I><A HREF=\"http://www.apache.org\">%s</A></I></BODY></HTML>", SERVER_VERSION);
  !     bwrite(con->client, buf, strlen(buf));
  !     if (f2 != NULL) bwrite(f2, buf, strlen(buf));
  !     total_bytes_sent+=strlen(buf);
  !     bflush(con->client);
  !     
  !     return total_bytes_sent;
  ! }
  ! 
  ! static long int
  ! send_fb(BUFF *f, request_rec *r, BUFF *f2, struct cache_req *c)
  ! {
  !     char buf[IOBUFSIZE];
  !     long total_bytes_sent;
  !     register int n,o,w;
  !     conn_rec *con = r->connection;
  !     
  !     total_bytes_sent = 0;
  !     while (!con->aborted) {
  ! 	n = bread(f, buf, IOBUFSIZE);
  ! 	if (n == -1) /* input error */
  ! 	{
  ! 	    if (f2 != NULL) f2 = cache_error(c);
  ! 	    break;
  ! 	}
  ! 	if (n == 0) break; /* EOF */
  !         o=0;
  ! 	total_bytes_sent += n;
  ! 
  ! 	if (f2 != NULL)
  ! 	    if (bwrite(f2, buf, n) != n) f2 = cache_error(c);
  ! 	
  !         while(n && !r->connection->aborted) {
  !             w = bwrite(con->client, &buf[o], n);
  ! 	    if (w <= 0)
  ! 		break;
  ! 	    reset_timeout(r); /* reset timeout after successfule write */
  !             n-=w;
  !             o+=w;
  !         }
  !     }
  !     bflush(con->client);
  !     
  !     return total_bytes_sent;
  ! }
  ! 
  ! /*
  !  * Read a header from the array, returning the first entry
  !  */
  ! static struct hdr_entry *
  ! get_header(array_header *hdrs_arr, const char *name)
  ! {
  !     struct hdr_entry *hdrs;
  !     int i;
  ! 
  !     hdrs = (struct hdr_entry *)hdrs_arr->elts;
  !     for (i = 0; i < hdrs_arr->nelts; i++)
  !         if (hdrs[i].field != NULL && strcasecmp(name, hdrs[i].field) == 0)
  ! 	    return &hdrs[i];
  ! 
  !     return NULL;
  ! }
  ! 
  ! #define HDR_APP (0)
  ! #define HDR_REP (1)
  ! 
  ! /*
  !  * Add to the header reply, either concatenating, or replacing existin
  !  * headers. It stores the pointers provided, so make sure the data
  !  * is not subsequently overwritten
  !  */
  ! static struct hdr_entry *
  ! add_header(array_header *hdrs_arr, char *field, char *value,
  ! 	   int rep)
  ! {
  !     int i;
  !     struct hdr_entry *hdrs;
  ! 
  !     hdrs = (struct hdr_entry *)hdrs_arr->elts;
  !     if (rep)
  ! 	for (i = 0; i < hdrs_arr->nelts; i++)
  ! 	    if (hdrs[i].field != NULL && strcasecmp(field, hdrs[i].field) == 0)
  ! 	    {
  ! 		hdrs[i].value = value;
  ! 		return hdrs;
  ! 	    }
  ! 	
  !     hdrs = push_array(hdrs_arr);
  !     hdrs->field = field;
  !     hdrs->value = value;
  ! 
  !     return hdrs;
  ! }
  ! 
  ! #ifdef NEEDED
  ! static void
  ! del_header(array_header *hdrs_arr, const char *field)
  ! {
  !     int i;
  !     struct hdr_entry *hdrs;
  ! 
  !     hdrs = (struct hdr_entry *)hdrs_arr->elts;
  ! 
  !     for (i = 0; i < hdrs_arr->nelts; i++)
  ! 	if (hdrs[i].field != NULL && strcasecmp(field, hdrs[i].field) == 0)
  ! 	    hdrs[i].value = NULL;
  ! }
  ! #endif
  ! 
  ! /*
  !  * Sends response line and headers
  !  */
  ! static void
  ! send_headers(BUFF *fp, const char *respline, array_header *hdrs_arr)
  ! {
  !     struct hdr_entry *hdrs;
  !     int i;
  ! 
  !     hdrs = (struct hdr_entry *)hdrs_arr->elts;
  ! 
  !     bputs(respline, fp);
  !     bputs("\015\012", fp);
  !     for (i = 0; i < hdrs_arr->nelts; i++)
  !     {
  !         if (hdrs[i].field == NULL) continue;
  ! 	bvputs(fp, hdrs[i].field, ": ", hdrs[i].value, "\015\012", NULL);
  !     }
  ! 
  !     bputs("\015\012", fp);
  ! }
  ! 
  ! 
  ! /*
  !  * list is a comma-separated list of case-insensitive tokens, with
  !  * optional whitespace around the tokens.
  !  * The return returns 1 if the token val is found in the list, or 0
  !  * otherwise.
  !  */
  ! static int
  ! liststr(const char *list, const char *val)
  ! {
  !     int len, i;
  !     const char *p;
  ! 
  !     len = strlen(val);
  ! 
  !     while (list != NULL)
  !     {
  ! 	p = strchr(list, ',');
  ! 	if (p != NULL)
  ! 	{
  ! 	    i = p - list;
  ! 	    do p++; while (isspace(*p));
  ! 	} 
  ! 	else
  ! 	    i = strlen(list);
  ! 
  ! 	while (i > 0 && isspace(list[i-1])) i--;
  ! 	if (i == len && strncasecmp(list, val, len) == 0) return 1;
  ! 	list = p;
  !     }
  !     return 0;
  ! }
  ! 
  ! /* number of characters in the hash */
  ! #define HASH_LEN (22*2)
  ! 
  ! static void
  ! hash(const char *it, char *val,int ndepth,int nlength)
  ! {
  !     MD5_CTX context;
  !     unsigned char digest[16];
  !     char tmp[22];
  !     int i, k, d;
  !     unsigned int x;
  !     static const char table[64]=
  ! "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_@";
  ! 
  !     MD5Init(&context);
  !     MD5Update(&context, (const unsigned char *)it, strlen(it));
  !     MD5Final(digest, &context);
  ! 
  ! /* encode 128 bits as 22 characters, using a modified uuencoding */
  ! /* the encoding is 3 bytes -> 4 characters
  !  * i.e. 128 bits is 5 x 3 bytes + 1 byte -> 5 * 4 characters + 2 characters
  !  */
  !     for (i=0, k=0; i < 15; i += 3)
  !     {
  ! 	x = (digest[i] << 16) | (digest[i+1] << 8) | digest[i+2];
  ! 	tmp[k++] = table[x >> 18];
  ! 	tmp[k++] = table[(x >> 12) & 0x3f];
  ! 	tmp[k++] = table[(x >> 6) & 0x3f];
  ! 	tmp[k++] = table[x & 0x3f];
  !     }
  ! /* one byte left */
  !     x = digest[15];
  !     tmp[k++] = table[x >> 2];  /* use up 6 bits */
  !     tmp[k++] = table[(x << 4) & 0x3f];
  !     /* now split into directory levels */
  ! 
  !     for(i=k=d=0 ; d < ndepth ; ++d)
  ! 	{
  ! 	strncpy(&val[i],&tmp[k],nlength);
  ! 	k+=nlength;
  ! 	val[i+nlength]='/';
  ! 	i+=nlength+1;
  ! 	}
  !     memcpy(&val[i],&tmp[k],22-k);
  !     val[i+22-k]='\0';
  ! }
  ! 
  ! /*
  !  * Compare a string to a mask
  !  * Mask characters:
  !  *   @ - uppercase letter
  !  *   # - lowercase letter
  !  *   & - hex digit
  !  *   # - digit
  !  *   * - swallow remaining characters 
  !  *  <x> - exact match for any other character
  !  */
  ! static int
  ! checkmask(const char *data, const char *mask)
  ! {
  !     int i, ch, d;
  ! 
  !     for (i=0; mask[i] != '\0' && mask[i] != '*'; i++)
  !     {
  ! 	ch = mask[i];
  ! 	d = data[i];
  ! 	if (ch == '@')
  ! 	{
  ! 	    if (!isupper(d)) return 0;
  ! 	} else if (ch == '$')
  ! 	{
  ! 	    if (!islower(d)) return 0;
  ! 	} else if (ch == '#')
  ! 	{
  ! 	    if (!isdigit(d)) return 0;
  ! 	} else if (ch == '&')
  ! 	{
  ! 	    if (!isxdigit(d)) return 0;
  ! 	} else if (ch != d) return 0;
  !     }
  ! 
  !     if (mask[i] == '*') return 1;
  !     else return (data[i] == '\0');
  ! }
  ! 
  ! /*
  !  * This routine converts a tm structure into the number of seconds
  !  * since 1st January 1970 UT
  !  * 
  !  * The return value is a non-negative integer on success or -1 if the
  !  * input date is out of the domain Thu, 01 Jan 1970 00:00:00 to
  !  * Tue, 19 Jan 2038 03:14:07 inclusive
  !  *
  !  * Notes
  !  *   This routine has been tested on 1000000 valid dates generated
  !  *   at random by gmtime().
  !  * 
  !  *   This routine is very fast, much faster than mktime().
  !  */
  ! static int
  ! tm2sec(const struct tm *t)
  ! {
  !     int days, year;
  !     static const int dayoffs[12]=
  !     {306, 337, 0, 31, 61, 92, 122, 153, 184, 214, 245, 275};
  ! 
  !     year = t->tm_year;
  ! /* shift new year to 1st March; which is where it should be */
  !     if (t->tm_mon < 2) year--;  /* now years and months since 1st March 1900 */
  !     days = t->tm_mday - 1 + dayoffs[t->tm_mon];
  ! 
  ! /* find the number of days since 1st March 1900 (in the Gregorian calendar) */
  !     days += year * 365 + year/4 - year/100 + (year/100 + 3)/4;
  !     days -= 25508; /* 1 jan 1970 is 25508 days since 1 mar 1900 */
  ! 
  !     days = ((days * 24 + t->tm_hour) * 60 + t->tm_min) * 60 + t->tm_sec;
  !     if (year < 69 || year > 138 || days < 0) /* must have overflowed */
  ! 	return -1;
  !     else
  ! 	return days;
  ! }
  ! 
  ! /*
  !  * Parses a standard HTTP date.
  !  * 
  !  * The restricted HTTP syntax is
  !  *   rfc1123-date = day "," SP 2DIGIT SP date SP time SP "GMT"
  !  *   date = 2DIGIT SP month SP 4DIGIT
  !  *   time = 2DIGIT ":" 2DIGIT ":" 2DIGIT
  !  *
  !  *   day = "Mon" | "Tue" | "Wed" | "Thu" | "Fri" | "Sat" | "Sun"
  !  *
  !  *   month = "Jan" | "Feb" | "Mar" | "Apr" | "May" | "Jun" |
  !  *           "Jul" | "Aug" | "Sep" | "Oct" | "Nov" | "Dec"
  !  *
  !  * The spec is not clear as to whether the day and months are
  !  * case-sensitive or not. This code assumes they are.
  !  *
  !  * It fills in the year, month, mday, hour, min, sec and is_dst fields of
  !  * date. It does not set the wday or yday fields.
  !  * On failure is sets the year to 0.
  !  * 
  !  * It also returns the number of seconds since 1 Jan 1970 UT, or
  !  * -1 if this would be out of range or if the date is invalid.
  !  *
  !  * Notes
  !  *   This routine has been tested on 100000 valid dates generated
  !  *   at random by strftime().
  !  * 
  !  *   This routine is very fast; it would be 10x slower if it
  !  *   used sscanf.
  !  *
  !  * From Andrew Daviel <an...@vancouver-webpages.com> 29 Jul 96:
  !  *
  !  * Expanded to include RFC850 date (used by Netscape)
  !  *    rfc850-date = weekday "," SP 2DIGIT "-" month "-" 2DIGIT SP time SP "GMT"
  !  * Netscape also appends "; length nnnn" to If-Modified-Since; allow this
  !  *
  !  */
  ! static int
  ! parsedate(const char *date, struct tm *d)
  ! {
  !     int mint, mon, year;
  !     char* comma;
  !     int lday;
  !     struct tm x;
  !     const int months[12]={
  ! 	('J' << 16) | ( 'a' << 8) | 'n', ('F' << 16) | ( 'e' << 8) | 'b',
  ! 	('M' << 16) | ( 'a' << 8) | 'r', ('A' << 16) | ( 'p' << 8) | 'r',
  ! 	('M' << 16) | ( 'a' << 8) | 'y', ('J' << 16) | ( 'u' << 8) | 'n',
  ! 	('J' << 16) | ( 'u' << 8) | 'l', ('A' << 16) | ( 'u' << 8) | 'g',
  ! 	('S' << 16) | ( 'e' << 8) | 'p', ('O' << 16) | ( 'c' << 8) | 't',
  ! 	('N' << 16) | ( 'o' << 8) | 'v', ('D' << 16) | ( 'e' << 8) | 'c'};
  !     if (d == NULL) d = &x;
  ! 
  !     d->tm_year = 0;  /* bad date */
  !     comma = index(date,',') ;
  !     lday =  (comma-date) ;
  !     
  !     if( lday >= 6 && lday <= 8) {   /* RFC850 */
  !       date = comma - 3 ;
  !       if (!checkmask(date, "day, ##-@$$-## ##:##:## GMT") && 
  !         !checkmask(date, "day, ##-@$$-## ##:##:## GMT;*")) return -1;
  !     } else { /* RFC1123 */
  !       if (!checkmask(date, "@$$, ## @$$ #### ##:##:## GMT") &&
  !         !checkmask(date, "@$$, ## @$$ #### ##:##:## GMT;*")) return -1;
  !     }
  ! 
  ! /* we don't test the weekday */
  !     d->tm_mday = (date[5] - '0') * 10 + (date[6] - '0');
  !     if (d->tm_mday == 0 || d->tm_mday > 31) return -1;
  ! 
  !     mint = (date[8] << 16) | (date[9] << 8) | date[10];
  !     for (mon=0; mon < 12; mon++) if (mint == months[mon]) break;
  !     if (mon == 12) return -1;
  !     
  !     d->tm_mon = mon;
  !     if( lday >= 6 && lday <= 8) {   /* RFC850 */
  !       year = 1900 + date[12] * 10 + date[13] - ('0' * 11) ;
  !       d->tm_hour = date[15] * 10 + date[16] - '0' * 11;
  !       d->tm_min  = date[18] * 10 + date[19] - '0' * 11;
  !       d->tm_sec = date[21] * 10 + date[22] - '0' * 11;
  !     } else { /* RFC1123 */
  !       year = date[12] * 1000 + date[13] * 100 + date[14] * 10 + date[15] -
  ! 	         ('0' * 1111);
  !       d->tm_hour = date[17] * 10 + date[18] - '0' * 11;
  !       d->tm_min  = date[20] * 10 + date[21] - '0' * 11;
  !       d->tm_sec = date[23] * 10 + date[24] - '0' * 11;
  !     }
  ! 
  !     if (d->tm_hour > 23 || d->tm_min > 59 || d->tm_sec > 61) return -1;
  ! 
  !     if (d->tm_mday == 31 && (mon == 1 || mon == 3 || mon == 5 || mon == 8 ||
  ! 			     mon == 10)) return -1;
  !     if (d->tm_mday > 29 && mon == 1) return -1;
  !     if (d->tm_mday == 29 && mon == 1)
  ! 	if (year%4 != 0 || (year%100 == 0 && year%400 != 0)) return -1;
  ! 
  !     d->tm_year = year - 1900;
  !     d->tm_isdst = 0;
  !     return tm2sec(d);
  ! }
  ! 
  ! /*
  !  * Converts 8 hex digits to a time integer
  !  */
  ! static int
  ! hex2sec(const char *x)
  ! {
  !     int i, ch;
  !     unsigned int j;
  ! 
  !     for (i=0, j=0; i < 8; i++)
  !     {
  ! 	ch = x[i];
  ! 	j <<= 4;
  ! 	if (isdigit(ch)) j |= ch - '0';
  ! 	else if (isupper(ch)) j |= ch - ('A' - 10);
  ! 	else j |= ch - ('a' - 10);
  !     }
  !     if (j == 0xffffffff) return -1;  /* so that it works with 8-byte ints */
  !     else return j;
  ! }
  ! 
  ! /*
  !  * Converts a time integer to 8 hex digits
  !  */
  ! static void
  ! sec2hex(int t, char *y)
  ! {
  !     int i, ch;
  !     unsigned int j=t;
  ! 
  !     for (i=7; i >= 0; i--)
  !     {
  ! 	ch = j & 0xF;
  ! 	j >>= 4;
  ! 	if (ch >= 10) y[i] = ch + ('A' - 10);
  ! 	else y[i] = ch + '0';
  !     }
  !     y[8] = '\0';
  ! }
  ! 
  ! 
  ! static void
  ! log_uerror(const char *routine, const char *file, const char *err,
  ! 	   server_rec *s)
  ! {
  !     char *p, *q;
  ! 
  !     q = get_time();
  !     p = strerror(errno);
  ! 
  !     if (err != NULL)
  !     {
  ! 	fprintf(s->error_log, "[%s] %s\n", q, err);
  ! 	if (file != NULL)
  ! 	    fprintf(s->error_log, "- %s: %s: %s\n", routine, file, p);
  ! 	else
  ! 	    fprintf(s->error_log, "- %s: %s\n", routine, p);
  !     } else
  !     {
  ! 	if (file != NULL)
  ! 	    fprintf(s->error_log, "[%s] %s: %s: %s\n", q, routine, file, p);
  ! 	else
  ! 	    fprintf(s->error_log, "[%s] %s: %s\n", q, routine, p);
  !     }
  ! 
  !     fflush(s->error_log);
  ! }
  ! 
  ! struct gc_ent
  ! {
  !     unsigned long int len;
  !     time_t expire;
  !     char file[HASH_LEN+1];
  ! 
  ! };
  ! 
  ! static int
  ! gcdiff(const void *ap, const void *bp)
  ! {
  !     const struct gc_ent *a=*(struct gc_ent **)ap, *b=*(struct gc_ent **)bp;
  ! 
  !     if (a->expire > b->expire) return 1;
  !     else if (a->expire < b->expire) return -1;
  !     else return 0;
  ! }
  ! 
  ! static int curbytes, cachesize, every;
  ! static unsigned long int curblocks;
  ! static time_t now, expire;
  ! static char *filename;
  ! 
  ! static int sub_garbage_coll(request_rec *r,array_header *files,
  ! 			    const char *cachedir,const char *cachesubdir);
  ! 
  ! static void garbage_coll(request_rec *r)
  !     {
  !     const char *cachedir;
  !     void *sconf = r->server->module_config;
  !     proxy_server_conf *pconf =
  !         (proxy_server_conf *)get_module_config(sconf, &proxy_module);
  !     const struct cache_conf *conf=&pconf->cache;
  !     array_header *files;
  !     struct stat buf;
  !     struct gc_ent *fent,**elts;    
  !     int i;
  !     static time_t lastcheck=-1;  /* static data!!! */
  ! 
  !     cachedir = conf->root;
  !     cachesize = conf->space;
  !     every = conf->gcinterval;
  ! 
  !     if (cachedir == NULL || every == -1) return;
  !     now = time(NULL);
  !     if (now != -1 && lastcheck != -1 && now < lastcheck + every) return;
  ! 
  !     block_alarms();	/* avoid SIGALRM on big cache cleanup */
  ! 
  !     filename = palloc(r->pool, strlen(cachedir) + HASH_LEN + 2);
  !     strcpy(filename, cachedir);
  !     strcat(filename, "/.time");
  !     if (stat(filename, &buf) == -1) /* does not exist */
  !     {
  ! 	if (errno != ENOENT)
  ! 	{
  ! 	    log_uerror("stat", filename, NULL, r->server);
  ! 	    return;
  ! 	}
  ! 	if (creat(filename, 0666) == -1)
  ! 	{
  ! 	    if (errno != EEXIST)
  ! 		log_uerror("creat", filename, NULL, r->server);
  ! 	    else
  ! 		lastcheck = now;  /* someone else got in there */
  ! 	    return;
  ! 	}
  !     } else
  !     {
  ! 	lastcheck = buf.st_mtime;  /* save the time */
  ! 	if (now < lastcheck + every) return;
  ! 	if (utime(filename, NULL) == -1)
  ! 	    log_uerror("utimes", filename, NULL, r->server);
  !     }
  !     files = make_array(r->pool, 100, sizeof(struct gc_ent *));
  !     curblocks = 0;
  !     curbytes = 0;
  ! 
  !     sub_garbage_coll(r,files,cachedir,"/");
  ! 
  !     if (curblocks < cachesize || curblocks + curbytes <= cachesize)
  ! 	return;
  ! 
  !     qsort(files->elts, files->nelts, sizeof(struct gc_ent *), gcdiff);
  ! 
  !     elts = (struct gc_ent **)files->elts;
  !     for (i=0; i < files->nelts; i++)
  !     {
  ! 	fent = elts[i];
  ! 	sprintf(filename, "%s%s", cachedir, fent->file);
  ! 	Explain3("GC Unlinking %s (expiry %ld, now %ld)",filename,fent->expire,now);
  ! #if TESTING
  ! 	fprintf(stderr,"Would unlink %s\n",filename);
  ! #else
  ! 	if (unlink(filename) == -1)
  ! 	{
  ! 	    if (errno != ENOENT)
  ! 		log_uerror("unlink", filename, NULL, r->server);
  ! 	}
  ! 	else
  ! #endif
  ! 	{
  ! 	    curblocks -= fent->len >> 10;
  ! 	    curbytes -= fent->len & 0x3FF;
  ! 	    if (curbytes < 0)
  ! 	    {
  ! 		curbytes += 1024;
  ! 		curblocks--;
  ! 	    }
  ! 	    if (curblocks < cachesize || curblocks + curbytes <= cachesize)
  ! 		break;
  ! 	}
  !     }
  !     unblock_alarms();
  ! }
  ! 
  ! static int sub_garbage_coll(request_rec *r,array_header *files,
  ! 			     const char *cachebasedir,const char *cachesubdir)
  ! {
  !     char line[27];
  !     char cachedir[HUGE_STRING_LEN];
  !     struct stat buf;
  !     int fd,i;
  !     DIR *dir;
  ! #if defined(NEXT)
  !     struct DIR_TYPE *ent;
  ! #else
  !     struct dirent *ent;
  ! #endif
  !     struct gc_ent *fent;
  !     int nfiles=0;
  ! 
  !     sprintf(cachedir,"%s%s",cachebasedir,cachesubdir);
  !     Explain1("GC Examining directory %s",cachedir);
  !     dir = opendir(cachedir);
  !     if (dir == NULL)
  !     {
  ! 	log_uerror("opendir", cachedir, NULL, r->server);
  ! 	return 0;
  !     }
  ! 
  !     while ((ent = readdir(dir)) != NULL)
  !     {
  ! 	if (ent->d_name[0] == '.') continue;
  ! 	sprintf(filename, "%s%s", cachedir, ent->d_name);
  ! 	Explain1("GC Examining file %s",filename);
  ! /* is it a temporary file? */
  ! 	if (strncmp(ent->d_name, "tmp", 3) == 0)
  ! 	{
  ! /* then stat it to see how old it is; delete temporary files > 1 day old */
  ! 	    if (stat(filename, &buf) == -1)
  ! 	    {
  ! 		if (errno != ENOENT)
  ! 		    log_uerror("stat", filename, NULL, r->server);
  ! 	    } else if (now != -1 && buf.st_atime < now - SEC_ONE_DAY &&
  ! 		       buf.st_mtime < now - SEC_ONE_DAY)
  ! 		{
  ! 		Explain1("GC unlink %s",filename);
  ! #if TESTING
  ! 		fprintf(stderr,"Would unlink %s\n",filename);
  ! #else
  ! 		unlink(filename);
  ! #endif
  ! 		}
  ! 	    continue;
  ! 	}
  ! 	++nfiles;
  ! /* is it another file? */
  ! 	/* FIXME: Shouldn't any unexpected files be deleted? */
  ! 	/*	if (strlen(ent->d_name) != HASH_LEN) continue; */
  ! 
  ! /* read the file */
  ! 	fd = open(filename, O_RDONLY);
  ! 	if (fd == -1)
  ! 	{
  ! 	    if (errno  != ENOENT) log_uerror("open", filename,NULL, r->server);
  ! 	    continue;
  ! 	}
  ! 	if (fstat(fd, &buf) == -1)
  ! 	{
  ! 	    log_uerror("fstat", filename, NULL, r->server);
  ! 	    close(fd);
  ! 	    continue;
  ! 	}
  ! 	if(S_ISDIR(buf.st_mode))
  ! 	    {
  ! 	    char newcachedir[HUGE_STRING_LEN];
  ! 	    close(fd);
  ! 	    sprintf(newcachedir,"%s%s/",cachesubdir,ent->d_name);
  ! 	    if(!sub_garbage_coll(r,files,cachebasedir,newcachedir))
  ! 		{
  ! 		sprintf(newcachedir,"%s%s",cachedir,ent->d_name);
  ! #if TESTING
  ! 		fprintf(stderr,"Would remove directory %s\n",newcachedir);
  ! #else
  ! 		rmdir(newcachedir);
  ! #endif
  ! 		--nfiles;
  ! 		}
  ! 	    continue;
  ! 	    }
  ! 	    
  ! 	i = read(fd, line, 26);
  ! 	if (i == -1)
  ! 	{
  ! 	    log_uerror("read", filename, NULL, r->server);
  ! 	    close(fd);
  ! 	    continue;
  ! 	}
  ! 	close(fd);
  ! 	line[i] = '\0';
  ! 	expire = hex2sec(line+18);
  ! 	if (!checkmask(line, "&&&&&&&& &&&&&&&& &&&&&&&&") || expire == -1)
  ! 	{
  ! 	    /* bad file */
  ! 	    if (now != -1 && buf.st_atime > now + SEC_ONE_DAY &&
  ! 		buf.st_mtime > now + SEC_ONE_DAY)
  ! 	    {
  ! 		log_error("proxy: deleting bad cache file", r->server);
  ! #if TESTING
  ! 		fprintf(stderr,"Would unlink bad file %s\n",filename);
  ! #else
  ! 		unlink(filename);
  ! #endif
  ! 	    }
  ! 	    continue;
  ! 	}
  ! 
  ! /*
  !  * we need to calculate an 'old' factor, and remove the 'oldest' files
  !  * so that the space requirement is met; sort by the expires date of the
  !  * file.
  !  *
  !  */
  ! 	/* FIXME: We should make the array an array of gc_ents, not gc_ent *s
  ! 	 */
  ! 	fent = palloc(r->pool, sizeof(struct gc_ent));
  ! 	fent->len = buf.st_size;
  ! 	fent->expire = expire;
  ! 	strcpy(fent->file,cachesubdir);
  ! 	strcat(fent->file, ent->d_name);
  ! 	*(struct gc_ent **)push_array(files) = fent;
  ! 
  ! /* accumulate in blocks, to cope with directories > 4Gb */
  ! 	curblocks += buf.st_size >> 10; /* Kbytes */
  ! 	curbytes += buf.st_size & 0x3FF;
  ! 	if (curbytes >= 1024)
  ! 	{
  ! 	    curbytes -= 1024;
  ! 	    curblocks++;
  ! 	}
  !     }
  ! 
  !     closedir(dir);
  ! 
  !     return nfiles;
  ! 
  ! }
  ! 
  ! /*
  !  * read a cache file;
  !  * returns 1 on success,
  !  *         0 on failure (bad file or wrong URL)
  !  *        -1 on UNIX error
  !  */
  ! static int
  ! rdcache(pool *pool, BUFF *cachefp, struct cache_req *c)
  ! {
  !     char urlbuff[1034], *p;
  !     int len;
  ! /* read the data from the cache file */
  ! /* format
  !  * date SP lastmod SP expire SP count SP content-length CRLF
  !  * dates are stored as hex seconds since 1970
  !  */
  !     len = bgets(urlbuff, 1034, cachefp);
  !     if (len == -1) return -1;
  !     if (len == 0 || urlbuff[len-1] != '\n') return 0;
  !     urlbuff[len-1] = '\0';
  ! 
  !     if (!checkmask(urlbuff, "&&&&&&&& &&&&&&&& &&&&&&&& &&&&&&&& &&&&&&&&"))
  ! 	return 0;
  ! 
  !     c->date = hex2sec(urlbuff);
  !     c->lmod = hex2sec(urlbuff+9);
  !     c->expire = hex2sec(urlbuff+18);
  !     c->version = hex2sec(urlbuff+27);
  !     c->len = hex2sec(urlbuff+36);
  ! 
  ! /* check that we have the same URL */
  !     len = bgets(urlbuff, 1034, cachefp);
  !     if (len == -1) return -1;
  !     if (len == 0 || strncmp(urlbuff, "X-URL: ", 7) != 0 ||
  ! 	urlbuff[len-1] != '\n')
  ! 	return 0;
  !     urlbuff[len-1] = '\0';
  !     if (strcmp(urlbuff+7, c->url) != 0) return 0;
  ! 
  ! /* What follows is the message */
  !     len = bgets(urlbuff, 1034, cachefp);
  !     if (len == -1) return -1;
  !     if (len == 0 || urlbuff[len-1] != '\n') return 0;
  !     urlbuff[--len] = '\0';
  ! 
  !     c->resp_line = pstrdup(pool, urlbuff);
  !     p = strchr(urlbuff, ' ');
  !     if (p == NULL) return 0;
  ! 
  !     c->status = atoi(p);
  !     c->hdrs = read_headers(pool, urlbuff, 1034, cachefp);
  !     if (c->hdrs == NULL) return -1;
  !     if (c->len != -1) /* add a content-length header */
  !     {
  ! 	struct hdr_entry *q;
  ! 	q = get_header(c->hdrs, "Content-Length");
  ! 	if (q == NULL)
  ! 	{
  ! 	    p = palloc(pool, 15);
  ! 	    sprintf(p, "%u", c->len);
  ! 	    add_header(c->hdrs, "Content-Length", p, HDR_REP);
  ! 	}
  !     }
  !     return 1;
  ! }
  ! 
  ! 
  ! /*
  !  * Call this to test for a resource in the cache
  !  * Returns DECLINED if we need to check the remote host
  !  * or an HTTP status code if successful
  !  *
  !  * Functions:
  !  *   if URL is cached then
  !  *      if cached file is not expired then
  !  *         if last modified after if-modified-since then send body
  !  *         else send 304 Not modified
  !  *      else
  !  *         if last modified after if-modified-since then add
  !  *            last modified date to request
  !  */
  ! static int
  ! cache_check(request_rec *r, char *url, struct cache_conf *conf,
  ! 	     struct cache_req **cr)
  ! {
  !     char hashfile[33], *imstr, *pragma, *p, *auth;
  !     struct cache_req *c;
  !     time_t now;
  !     BUFF *cachefp;
  !     int cfd, i;
  !     const long int zero=0L;
  !     void *sconf = r->server->module_config;
  !     proxy_server_conf *pconf =
  !         (proxy_server_conf *)get_module_config(sconf, &proxy_module);
  ! 
  !     c = pcalloc(r->pool, sizeof(struct cache_req));
  !     *cr = c;
  !     c->req = r;
  !     c->url = pstrdup(r->pool, url);
  ! 
  ! /* get the If-Modified-Since date of the request */
  !     c->ims = -1;
  !     imstr = table_get(r->headers_in, "If-Modified-Since");
  !     if (imstr != NULL)
  !     {
  ! /* this may modify the value in the original table */
  ! 	imstr = date_canon(r->pool, imstr);
  ! 	c->ims = parsedate(imstr, NULL);
  ! 	if (c->ims == -1)  /* bad or out of range date; remove it */
  ! 	    table_set(r->headers_in, "If-Modified-Since", NULL);
  !     }
  ! 
  ! /* find the filename for this cache entry */
  !     hash(url, hashfile,pconf->cache.dirlevels,pconf->cache.dirlength);
  !     if (conf->root != NULL)
  ! 	c->filename = pstrcat(r->pool, conf->root, "/", hashfile, NULL);
  !     else
  ! 	c->filename = NULL;
  ! 
  !     cachefp = NULL;
  ! /* find out about whether the request can access the cache */
  !     pragma = table_get(r->headers_in, "Pragma");
  !     auth = table_get(r->headers_in, "Authorization");
  !     Explain5("Request for %s, pragma=%s, auth=%s, ims=%ld, imstr=%s",url,
  !       pragma,auth,c->ims,imstr);
  !     if (c->filename != NULL && r->method_number == M_GET &&
  ! 	strlen(url) < 1024 && !liststr(pragma, "no-cache") && auth == NULL)
  !     {
  !         Explain1("Check file %s",c->filename);
  ! 	cfd = open(c->filename, O_RDWR);
  ! 	if (cfd != -1)
  ! 	{
  ! 	    note_cleanups_for_fd(r->pool, cfd);
  ! 	    cachefp = bcreate(r->pool, B_RD | B_WR);
  ! 	    bpushfd(cachefp, cfd, cfd);
  ! 	} else if (errno != ENOENT)
  ! 	    log_uerror("open", c->filename, "proxy: error opening cache file",
  ! 		       r->server);
  ! 	else
  ! 	    Explain1("File %s not found",c->filename);
  !     }
  !     
  !     if (cachefp != NULL)
  !     {
  ! 	i = rdcache(r->pool, cachefp, c);
  ! 	if (i == -1)
  ! 	    log_uerror("read", c->filename, "proxy: error reading cache file",
  ! 		       r->server);
  ! 	else if (i == 0)
  ! 	    log_error("proxy: bad cache file", r->server);
  ! 	if (i != 1)
  ! 	{
  ! 	    pclosef(r->pool, cachefp->fd);
  ! 	    cachefp = NULL;
  ! 	}
  !     }
  !     if (cachefp == NULL)
  ! 	c->hdrs = make_array(r->pool, 2, sizeof(struct hdr_entry));
  !     /* FIXME: Shouldn't we check the URL somewhere? */
  !     now = time(NULL);
  ! /* Ok, have we got some un-expired data? */
  !     if (cachefp != NULL && c->expire != -1 && now < c->expire)
  !     {
  !         Explain0("Unexpired data available");
  ! /* check IMS */
  ! 	if (c->lmod != -1 && c->ims != -1 && c->ims >= c->lmod)
  ! 	{
  ! /* has the cached file changed since this request? */
  ! 	    if (c->date == -1 || c->date > c->ims)
  ! 	    {
  ! /* No, but these header values may have changed, so we send them with the
  !  * 304 response
  !  */
  ! 	    /* CHECKME: surely this was wrong? (Ben)
  ! 		p = table_get(r->headers_in, "Expires");
  ! 		*/
  ! 		p = table_get(c->hdrs, "Expires");
  ! 		if (p != NULL) 	table_set(r->headers_out, "Expires", p);
  ! 	    }
  ! 	    pclosef(r->pool, cachefp->fd);
  ! 	    Explain0("Use local copy, cached file hasn't changed");
  ! 	    return USE_LOCAL_COPY;
  ! 	}
  ! 
  ! /* Ok, has been modified */
  ! 	Explain0("Local copy modified, send it");
  ! 	r->status_line = strchr(c->resp_line, ' ') + 1;
  ! 	r->status = c->status;
  ! 	soft_timeout ("send", r);
  ! 	if (!r->assbackwards)
  ! 	    send_headers(r->connection->client, c->resp_line,  c->hdrs);
  ! 	bsetopt(r->connection->client, BO_BYTECT, &zero);
  ! 	r->sent_bodyct = 1;
  ! 	if (!r->header_only) send_fb (cachefp, r, NULL, NULL);
  ! 	pclosef(r->pool, cachefp->fd);
  ! 	return OK;
  !     }
  ! 
  ! /* if we already have data and a last-modified date, and it is not a head
  !  * request, then add an If-Modified-Since
  !  */
  ! 
  !     if (cachefp != NULL && c->lmod != -1 && !r->header_only)
  !     {
  ! /*
  !  * use the later of the one from the request and the last-modified date
  !  * from the cache
  !  */
  ! 	if (c->ims == -1 || c->ims < c->lmod)
  ! 	{
  ! 	    struct hdr_entry *q;
  ! 
  ! 	    q = get_header(c->hdrs, "Last-Modified");
  ! 
  ! 	    if (q != NULL && q->value != NULL)
  ! 		table_set(r->headers_in, "If-Modified-Since",
  ! 			  (char *)q->value);
  ! 	}
  !     }
  !     c->fp = cachefp;
  ! 
  !     Explain0("Local copy not present or expired. Declining.");
  ! 
  !     return DECLINED;
  ! }
  ! 
  ! /*
  !  * Having read the response from the client, decide what to do
  !  * If the response is not cachable, then delete any previously cached
  !  * response, and copy data from remote server to client.
  !  * Functions:
  !  *  parse dates
  !  *  check for an uncachable response
  !  *  calculate an expiry date, if one is not provided
  !  *  if the remote file has not been modified, then return the document
  !  *  from the cache, maybe updating the header line
  !  *  otherwise, delete the old cached file and open a new temporary file
  !  */
  ! static int
  ! cache_update(struct cache_req *c, array_header *resp_hdrs,
  ! 	     const char *protocol, int nocache)
  ! {
  !     request_rec *r=c->req;
  !     char *p;
  !     int i;
  !     struct hdr_entry *expire, *dates, *lmods, *clen;
  !     time_t expc, date, lmod, now;
  !     char buff[46];
  !     void *sconf = r->server->module_config;
  !     proxy_server_conf *conf =
  !         (proxy_server_conf *)get_module_config(sconf, &proxy_module);
  !     const long int zero=0L;
  ! 
  !     c->tempfile = NULL;
  ! 
  ! /* we've received the response */
  ! /* read expiry date; if a bad date, then leave it so the client can
  !  * read it
  !  */
  !     expire = get_header(resp_hdrs, "Expires");
  !     if (expire != NULL) expc = parsedate(expire->value, NULL);
  !     else expc = -1;
  ! 
  ! /*
  !  * read the last-modified date; if the date is bad, then delete it
  !  */
  !     lmods = get_header(resp_hdrs, "Last-Modified");
  !     if (lmods != NULL)
  !     {
  ! 	lmod = parsedate(lmods->value, NULL);
  ! 	if (lmod == -1)
  ! 	{
  ! /* kill last modified date */
  ! 	    lmods->value = NULL;
  ! 	    lmods = NULL;
  ! 	}
  !     } else
  ! 	lmod = -1;
  ! 
  ! /*
  !  * what responses should we not cache?
  !  * Unknown status responses and those known to be uncacheable
  !  * 304 response when we have no valid cache file, or
  !  * 200 response from HTTP/1.0 and up without a Last-Modified header, or
  !  * HEAD requests, or
  !  * requests with an Authorization header, or
  !  * protocol requests nocache (e.g. ftp with user/password)
  !  */
  !     if ((r->status != 200 && r->status != 301 && r->status != 304) ||
  ! 	(expire != NULL && expc == -1) ||
  ! 	(r->status == 304 && c->fp == NULL) ||
  ! 	(r->status == 200 && lmods == NULL &&
  ! 	                     strncmp(protocol, "HTTP/1.", 7) == 0) ||
  ! 	r->header_only ||
  ! 	table_get(r->headers_in, "Authorization") != NULL ||
  ! 	nocache)
  !     {
  ! 	Explain1("Response is not cacheable, unlinking %s",c->filename);
  ! /* close the file */
  ! 	if (c->fp != NULL)
  ! 	{
  ! 	    pclosef(r->pool, c->fp->fd);
  ! 	    c->fp = NULL;
  ! 	}
  ! /* delete the previously cached file */
  ! 	unlink(c->filename);
  ! 	return DECLINED; /* send data to client but not cache */
  !     }
  ! 
  ! /* otherwise, we are going to cache the response */
  ! /*
  !  * Read the date. Generate one if one is not supplied
  !  */
  !     dates = get_header(resp_hdrs, "Date");
  !     if (dates != NULL) date = parsedate(dates->value, NULL);
  !     else date = -1;
  ! 	
  !     now = time(NULL);
  ! 
  !     if (date == -1) /* No, or bad date */
  !     {
  ! /* no date header! */
  ! /* add one; N.B. use the time _now_ rather than when we were checking the cache
  !  */
  ! 	date = now;
  ! 	p = gm_timestr_822(r->pool, now);
  ! 	dates = add_header(resp_hdrs, "Date", p, HDR_REP);
  ! 	Explain0("Added date header");
  !     }
  ! 
  ! /* check last-modified date */
  !     if (lmod != -1 && lmod > date)
  ! /* if its in the future, then replace by date */
  !     {
  ! 	lmod = date;
  ! 	lmods->value = dates->value;
  ! 	Explain0("Last modified is in the future, replacing with now");
  !     }
  ! /* if the response did not contain the header, then use the cached version */
  !     if (lmod == -1 && c->fp != NULL)
  ! 	{
  ! 	lmod = c->lmod;
  ! 	Explain0("Reusing cached last modified");
  ! 	}
  ! 
  ! /* we now need to calculate the expire data for the object. */
  !     if (expire == NULL && c->fp != NULL)  /* no expiry data sent in response */
  !     {
  ! 	expire = get_header(c->hdrs, "Expires");
  ! 	if (expire != NULL) expc = parsedate(expire->value, NULL);
  !     }
  ! /* so we now have the expiry date */
  ! /* if no expiry date then
  !  *   if lastmod
  !  *      expiry date = now + min((date - lastmod) * factor, maxexpire)
  !  *   else
  !  *      expire date = now + defaultexpire
  !  */
  !     Explain1("Expiry date is %ld",expc);
  !     if (expc == -1)
  !     {
  ! 	if (lmod != -1)
  ! 	{
  ! 	    double x = (double)(date - lmod)*conf->cache.lmfactor;
  ! 	    double maxex=conf->cache.maxexpire;
  ! 	    if (x > maxex) x = maxex;
  ! 	    expc = now + (int)x;
  ! 	} else
  ! 	    expc = now + conf->cache.defaultexpire;
  ! 	Explain1("Expiry date calculated %ld",expc);
  !     }
  ! 
  ! /* get the content-length header */
  !     clen = get_header(c->hdrs, "Content-Length");
  !     if (clen == NULL) c->len = -1;
  !     else c->len = atoi(clen->value);
  ! 
  !     sec2hex(date, buff);
  !     buff[8] = ' ';
  !     sec2hex(lmod, buff+9);
  !     buff[17] = ' ';
  !     sec2hex(expc, buff+18);
  !     buff[26] = ' ';
  !     sec2hex(c->version++, buff+27);
  !     buff[35] = ' ';
  !     sec2hex(c->len, buff+36);
  !     buff[44] = '\n';
  !     buff[45] = '\0';
  ! 
  ! /* if file not modified */
  !     if (r->status == 304)
  !     {
  ! 	if (c->ims != -1 && lmod != -1 && lmod <= c->ims)
  ! 	{
  ! /* set any changed headers somehow */
  ! /* update dates and version, but not content-length */
  ! 	    if (lmod != c->lmod || expc != c->expire || date != c->date)
  ! 	    {
  ! 		off_t curpos=lseek(c->fp->fd, 0, SEEK_SET);
  ! 		if (curpos == -1)
  ! 		    log_uerror("lseek", c->filename,
  ! 			       "proxy: error seeking on cache file",r->server);
  ! 		else if (write(c->fp->fd, buff, 35) == -1)
  ! 		    log_uerror("write", c->filename,
  ! 			       "proxy: error updating cache file", r->server);
  ! 	    }
  ! 	    pclosef(r->pool, c->fp->fd);
  ! 	    Explain0("Remote document not modified, use local copy");
  ! 	    /* CHECKME: Is this right? Shouldn't we check IMS again here? */
  ! 	    return USE_LOCAL_COPY;
  ! 	} else
  ! 	{
  ! /* return the whole document */
  ! 	    Explain0("Remote document updated, sending");
  ! 	    r->status_line = strchr(c->resp_line, ' ') + 1;
  ! 	    r->status = c->status;
  ! 	    soft_timeout ("send", r);
  ! 	    if (!r->assbackwards)
  ! 		send_headers(r->connection->client, c->resp_line,  c->hdrs);
  ! 	    bsetopt(r->connection->client, BO_BYTECT, &zero);
  ! 	    r->sent_bodyct = 1;
  ! 	    if (!r->header_only) send_fb (c->fp, r, NULL, NULL);
  ! /* set any changed headers somehow */
  ! /* update dates and version, but not content-length */
  ! 	    if (lmod != c->lmod || expc != c->expire || date != c->date)
  ! 	    {
  ! 		off_t curpos=lseek(c->fp->fd, 0, SEEK_SET);
  ! 
  ! 		if (curpos == -1)
  ! 		    log_uerror("lseek", c->filename,
  ! 			       "proxy: error seeking on cache file",r->server);
  ! 		else if (write(c->fp->fd, buff, 35) == -1)
  ! 		    log_uerror("write", c->filename,
  ! 			       "proxy: error updating cache file", r->server);
  ! 	    }
  ! 	    pclosef(r->pool, c->fp->fd);
  ! 	    return OK;
  ! 	}
  !     }
  ! /* new or modified file */	    
  !     if (c->fp != NULL)
  !     {
  ! 	pclosef(r->pool, c->fp->fd);
  ! 	c->fp->fd = -1;
  !     }
  !     c->version = 0;
  !     sec2hex(0, buff+27);
  !     buff[35] = ' ';
  ! 
  ! /* open temporary file */
  ! #define TMPFILESTR	"/tmpXXXXXX"
  !     c->tempfile=palloc(r->pool,strlen(conf->cache.root)+sizeof TMPFILESTR-1);
  !     strcpy(c->tempfile,conf->cache.root);
  !     /*
  !     p = strrchr(c->tempfile, '/');
  !     if (p == NULL) return DECLINED;
  !     strcpy(p, TMPFILESTR);
  !     */
  !     strcat(c->tempfile,TMPFILESTR);
  ! #undef TMPFILESTR
  !     p = mktemp(c->tempfile);
  !     if (p == NULL) return DECLINED;
  ! 
  !     Explain1("Create temporary file %s",c->tempfile);
  ! 
  !     i = open(c->tempfile, O_WRONLY | O_CREAT | O_EXCL, 0622);
  !     if (i == -1)
  !     {
  ! 	log_uerror("open", c->tempfile, "proxy: error creating cache file",
  ! 		   r->server);
  ! 	return DECLINED;
  !     }
  !     note_cleanups_for_fd(r->pool, i);
  !     c->fp = bcreate(r->pool, B_WR);
  !     bpushfd(c->fp, -1, i);
  ! 
  !     if (bvputs(c->fp, buff, "X-URL: ", c->url, "\n", NULL) == -1)
  !     {
  ! 	log_uerror("write", c->tempfile, "proxy: error writing cache file",
  ! 		   r->server);
  ! 	pclosef(r->pool, c->fp->fd);
  ! 	unlink(c->tempfile);
  ! 	c->fp = NULL;
  !     }
  !     return DECLINED;
  ! }
  ! 
  ! static void
  ! cache_tidy(struct cache_req *c)
  ! {
  !     server_rec *s=c->req->server;
  !     long int bc;
  ! 
  !     if (c->fp == NULL) return;
  ! 
  !     bgetopt(c->req->connection->client, BO_BYTECT, &bc);
  ! 
  !     if (c->len != -1)
  !     {
  ! /* file lengths don't match; don't cache it */
  ! 	if (bc != c->len)
  ! 	{
  ! 	    pclosef(c->req->pool, c->fp->fd);  /* no need to flush */
  ! 	    unlink(c->tempfile);
  ! 	    return;
  ! 	}
  !     } else
  !     {
  ! /* update content-length of file */
  ! 	char buff[9];
  ! 	off_t curpos;
  ! 
  ! 	c->len = bc;
  ! 	bflush(c->fp);
  ! 	sec2hex(c->len, buff);
  ! 	curpos = lseek(c->fp->fd, 36, SEEK_SET);
  ! 	if (curpos == -1)
  ! 	    log_uerror("lseek", c->tempfile,
  ! 		       "proxy: error seeking on cache file", s);
  ! 	else if (write(c->fp->fd, buff, 8) == -1)
  ! 	    log_uerror("write", c->tempfile,
  ! 		       "proxy: error updating cache file", s);
  !     }
  ! 
  !     if (bflush(c->fp) == -1)
  !     {
  ! 	log_uerror("write", c->tempfile, "proxy: error writing to cache file",
  ! 		   s);
  ! 	pclosef(c->req->pool, c->fp->fd);
  ! 	unlink(c->tempfile);
  ! 	return;
  !     }
  ! 
  !     if (pclosef(c->req->pool, c->fp->fd) == -1)
  !     {
  ! 	log_uerror("close", c->tempfile, "proxy: error closing cache file", s);
  ! 	unlink(c->tempfile);
  ! 	return;
  !     }
  ! 
  !     if (unlink(c->filename) == -1 && errno != ENOENT)
  !     {
  ! 	log_uerror("unlink", c->filename,
  ! 		   "proxy: error deleting old cache file", s);
  !     } else
  ! 	{
  ! 	char *p;
  ! 	proxy_server_conf *conf=
  ! 	  (proxy_server_conf *)get_module_config(s->module_config,&proxy_module);
  ! 
  ! 	for(p=c->filename+strlen(conf->cache.root)+1 ; ; )
  ! 	    {
  ! 	    p=strchr(p,'/');
  ! 	    if(!p)
  ! 		break;
  ! 	    *p='\0';
  ! 	    if(mkdir(c->filename,S_IREAD|S_IWRITE|S_IEXEC) < 0 && errno != EEXIST)
  ! 		log_uerror("mkdir",c->filename,"proxy: error creating cache directory",s);
  ! 	    *p='/';
  ! 	    ++p;
  ! 	    }
  ! #ifdef __EMX__
  !         /* Under OS/2 use rename. */            
  !         if (rename(c->tempfile, c->filename) == -1)
  !             log_uerror("rename", c->filename, "proxy: error renaming cache file", s);
  ! }
  ! #else            
  ! 
  ! 	if (link(c->tempfile, c->filename) == -1)
  ! 	    log_uerror("link", c->filename, "proxy: error linking cache file", s);
  ! 	}
    
  !     if (unlink(c->tempfile) == -1)
  ! 	log_uerror("unlink", c->tempfile, "proxy: error deleting temp file",s);
  ! #endif
    
  !     garbage_coll(c->req);
  ! }
    
  ! static BUFF *
  ! cache_error(struct cache_req *c)
  ! {
  !     log_uerror("write", c->tempfile, "proxy: error writing to cache file",
  ! 	       c->req->server);
  !     pclosef(c->req->pool, c->fp->fd);
  !     c->fp = NULL;
  !     unlink(c->tempfile);
  !     return NULL;
    }
    
    static int
    proxy_handler(request_rec *r)
    {
  --- 149,175 ----
    proxy_fixup(request_rec *r)
    {
        char *url, *p;
  !     int i;
    
  !     if (strncmp(r->filename, "proxy:", 6) != 0) return DECLINED;
    
  !     url = &r->filename[6];
  ! /* lowercase the scheme */
  !     p = strchr(url, ':');
  !     if (p == NULL || p == url) return BAD_REQUEST;
  !     for (i=0; i != p - url; i++) url[i] = tolower(url[i]);
    
  ! /* canonicalise each specific scheme */
  !     if (strncmp(url, "http:", 5) == 0)
  ! 	return proxy_http_canon(r, url+5, "http", DEFAULT_PORT);
  !     else if (strncmp(url, "ftp:", 4) == 0)
  ! 	return proxy_ftp_canon(r, url+4);
  !     else return OK; /* otherwise; we've done the best we can */
    }
    
  + /* -------------------------------------------------------------- */
  + /* Invoke handler */
  +  
    static int
    proxy_handler(request_rec *r)
    {
  ***************
  *** 2286,2292 ****
        p = strchr(url, ':');
        if (p == NULL) return BAD_REQUEST;
    
  !     rc = cache_check(r, url, &conf->cache, &cr);
        if (rc != DECLINED) return rc;
    
        *p = '\0';
  --- 191,197 ----
        p = strchr(url, ':');
        if (p == NULL) return BAD_REQUEST;
    
  !     rc = proxy_cache_check(r, url, &conf->cache, &cr);
        if (rc != DECLINED) return rc;
    
        *p = '\0';
  ***************
  *** 2305,2311 ****
    	{
    /* we only know how to handle communication to a proxy via http */
    	    if (strcmp(ents[i].protocol, "http") == 0)
  ! 		rc = http_handler(r, cr, url, ents[i].hostname, ents[i].port);
    	    else rc = DECLINED;
    
     /* an error or success */
  --- 210,217 ----
    	{
    /* we only know how to handle communication to a proxy via http */
    	    if (strcmp(ents[i].protocol, "http") == 0)
  ! 		rc = proxy_http_handler(r, cr, url, ents[i].hostname,
  ! 		    ents[i].port);
    	    else rc = DECLINED;
    
     /* an error or success */
  ***************
  *** 2319,3247 ****
     * give up??
     */
        /* handle the scheme */
  !     if (r->method_number == M_CONNECT) return connect_handler(r, cr, url);
  !     if (strcmp(scheme, "http") == 0) return http_handler(r, cr, url, NULL, 0);
  !     if (strcmp(scheme, "ftp") == 0) return ftp_handler(r, cr, url);
        else return NOT_IMPLEMENTED;
    }
    
  ! 
  ! static int
  ! proxyerror(request_rec *r, const char *message)
  ! {
  !     r->status = SERVER_ERROR;
  !     r->status_line = "500 Proxy Error";
  !     r->content_type = "text/html";
  ! 
  !     send_http_header(r);
  !     rvputs(r, "<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">\015\012\
  ! <html><head><title>Proxy Error</title><head>\015\012<body><h1>Proxy Error\
  ! </h1>\015\012The proxy server could not handle this request.\
  ! \015\012<p>\015\012Reason: <b>", message, "</b>\015\012</body><html>\015\012",
  ! 	   NULL);
  !     return OK;
  ! }
  ! 
  ! /*
  !  * This routine returns its own error message
  !  */
  ! static const char *
  ! host2addr(const char *host, struct in_addr *addr)
  ! {
  !     int i;
  !     unsigned long ipaddr;
  ! 
  !     for (i=0; host[i] != '\0'; i++)
  ! 	if (!isdigit(host[i]) && host[i] != '.')
  ! 	    break;
  ! 
  !     if (host[i] != '\0')
  !     {
  ! 	struct hostent *hp;
  ! 
  ! 	hp = gethostbyname(host);
  ! 	if (hp == NULL) return "Host not found";
  ! 	memcpy(addr, hp->h_addr, sizeof(struct in_addr));
  !     } else
  !     {
  ! 	if ((ipaddr = inet_addr(host)) == -1)
  ! 	    return "Bad IP address";
  ! 	memcpy(addr, &ipaddr, sizeof(unsigned long));
  !     }
  !     return NULL;
  ! }
  ! 
  ! /*
  !  * Returns the ftp status code;
  !  *  or -1 on I/O error, 0 on data error
  !  */
  ! int
  ! ftp_getrc(BUFF *f)
  ! {
  !     int i, len, status;
  !     char linebuff[100], buff[5];
  ! 
  !     len = bgets(linebuff, 100, f);
  !     if (len == -1) return -1;
  ! /* check format */
  !     if (len < 5 || !isdigit(linebuff[0]) || !isdigit(linebuff[1]) ||
  ! 	!isdigit(linebuff[2]) || (linebuff[3] != ' ' && linebuff[3] != '-'))
  ! 	return 0;
  !     status = 100 * linebuff[0] + 10 * linebuff[1] + linebuff[2] - 111 * '0';
  !     
  !     if (linebuff[len-1] != '\n')
  !     {
  ! 	i = bskiplf(f);
  ! 	if (i != 1) return i;
  !     }
  ! 
  ! /* skip continuation lines */    
  !     if (linebuff[3] == '-')
  !     {
  ! 	memcpy(buff, linebuff, 3);
  ! 	buff[3] = ' ';
  ! 	do
  ! 	{
  ! 	    len = bgets(linebuff, 100, f);
  ! 	    if (len == -1) return -1;
  ! 	    if (len < 5) return 0;
  ! 	    if (linebuff[len-1] != '\n')
  ! 	    {
  ! 		i = bskiplf(f);
  ! 		if (i != 1) return i;
  ! 	    }
  ! 	} while (memcmp(linebuff, buff, 4) != 0);
  !     }
  ! 
  !     return status;
  ! }
  ! 
  ! static int
  ! doconnect(int sock, struct sockaddr_in *addr, request_rec *r)
  ! {
  !     int i;
  ! 
  !     hard_timeout ("proxy connect", r);
  !     do	i = connect(sock, (struct sockaddr *)addr, sizeof(struct sockaddr_in));
  !     while (i == -1 && errno == EINTR);
  !     if (i == -1) log_uerror("connect", NULL, NULL, r->server);
  !     kill_timeout(r);
  ! 
  !     return i;
  ! }
  ! 
  ! /*
  !  * Handles direct access of ftp:// URLs
  !  * Original (Non-PASV) version from
  !  * Troy Morrison <sp...@zoom.com>
  !  */
  ! static int
  ! ftp_handler(request_rec *r, struct cache_req *c, char *url)
  ! {
  !     char *host, *path, *p, *user, *password, *parms;
  !     const char *err;
  !     int port, userlen, passlen, i, len, sock, dsock, csd, rc, nocache;
  !     struct sockaddr_in server;
  !     struct hdr_entry *hdr;
  !     array_header *resp_hdrs;
  !     BUFF *f, *cache, *data;
  !     pool *pool=r->pool;
  !     const int one=1;
  !     const long int zero=0L;
  ! 
  !     /* This appears to fix a bug(?) that generates an "Address family not
  !        supported by protocol" error in doconnect() later (along with
  !        making sure server.sin_family = AF_INET - cdm)*/
  !     memset(&server, 0, sizeof(struct sockaddr_in));
  ! 
  ! /* we only support GET and HEAD */
  !     if (r->method_number != M_GET) return NOT_IMPLEMENTED;
  ! 
  !     host = pstrdup(r->pool, url+6);
  ! /* We break the URL into host, port, path-search */
  !     port = DEFAULT_FTP_PORT;
  !     path = strchr(host, '/');
  !     if (path == NULL) path = "";
  !     else *(path++) = '\0';
  ! 
  !     user = password = NULL;
  !     nocache = 0;
  !     passlen=0;	/* not actually needed, but it shuts the compiler up */
  !     p = strchr(host, '@');
  !     if (p != NULL)
  !     {
  ! 	(*p++) = '\0';
  ! 	user = host;
  ! 	host = p;
  ! /* find password */
  ! 	p = strchr(user, ':');
  ! 	if (p != NULL)
  ! 	{
  ! 	    *(p++) = '\0';
  ! 	    password = p;
  ! 	    passlen = decodeenc(password);
  ! 	}
  ! 	userlen = decodeenc(user);
  ! 	nocache = 1; /* don't cache when a username is supplied */
  !     } else
  !     {
  ! 	user = "anonymous";
  ! 	userlen = 9;
  ! 
  ! 	password = "proxy_user@apache_host.org";
  ! 	passlen = strlen(password);
  !     }
  ! 
  !     p = strchr(host, ':');
  !     if (p != NULL)
  !     {
  ! 	*(p++) = '\0';
  ! 	port = atoi(p);
  !     }
  ! 
  !     Explain2("FTP: connect to %s:%d",host,port);
  ! 
  !     parms = strchr(path, ';');
  !     if (parms != NULL) *(parms++) = '\0';
  ! 
  !     server.sin_family = AF_INET;
  !     server.sin_port = htons(port);
  !     err = host2addr(host, &server.sin_addr);
  !     if (err != NULL) return proxyerror(r, err); /* give up */
  ! 
  !     sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
  !     if (sock == -1)
  !     {
  ! 	log_uerror("socket", NULL, "proxy: error creating socket", r->server);
  ! 	return SERVER_ERROR;
  !     }
  !     note_cleanups_for_fd(pool, sock);
  ! 
  !     if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char *)&one,
  ! 		   sizeof(int)) == -1)
  !     {
  ! 	log_uerror("setsockopt", NULL, "proxy: error setting reuseaddr option",
  ! 		   r->server);
  ! 	pclosef(pool, sock);
  ! 	return SERVER_ERROR;
  !     }
  ! 
  !     i = doconnect(sock, &server, r);
  !     if (i == -1) return proxyerror(r, "Could not connect to remote machine");
  ! 
  !     f = bcreate(pool, B_RDWR);
  !     bpushfd(f, sock, sock);
  ! /* shouldn't we implement telnet control options here? */
  ! 
  ! /* possible results: 120, 220, 421 */
  !     hard_timeout ("proxy ftp", r);
  !     i = ftp_getrc(f);
  !     Explain1("FTP: returned status %d", i);
  !     if (i == -1) return proxyerror(r, "Error reading from remote server");
  !     if (i != 220) return BAD_GATEWAY;
  ! 
  !     Explain0("FTP: connected.");
  ! 
  !     bputs("USER ", f);
  !     bwrite(f, user, userlen);
  !     bputs("\015\012", f);
  !     bflush(f); /* capture any errors */
  !     Explain1("FTP: USER %s",user);
  !     
  ! /* possible results; 230, 331, 332, 421, 500, 501, 530 */
  ! /* states: 1 - error, 2 - success; 3 - send password, 4,5 fail */
  !     i = ftp_getrc(f);
  !     Explain1("FTP: returned status %d",i);
  !     if (i == -1) return proxyerror(r, "Error sending to remote server");
  !     if (i == 530) return FORBIDDEN;
  !     else if (i != 230 && i != 331) return BAD_GATEWAY;
  ! 	
  !     if (i == 331) /* send password */
  !     {
  ! 	if (password == NULL) return FORBIDDEN;
  ! 	bputs("PASS ", f);
  ! 	bwrite(f, password, passlen);
  ! 	bputs("\015\012", f);
  ! 	bflush(f);
  !         Explain1("FTP: PASS %s",password);
  ! /* possible results 202, 230, 332, 421, 500, 501, 503, 530 */
  ! 	i = ftp_getrc(f);
  !         Explain1("FTP: returned status %d",i);
  ! 	if (i == -1) return proxyerror(r, "Error sending to remote server");
  ! 	if (i == 332 || i == 530) return FORBIDDEN;
  ! 	else if (i != 230 && i != 202) return BAD_GATEWAY;
  !     }  
  ! 
  ! /* set the directory */
  ! /* this is what we must do if we don't know the OS type of the remote
  !  * machine
  !  */
  !     for (;;)
  !     {
  ! 	p = strchr(path, '/');
  ! 	if (p == NULL) break;
  ! 	*p = '\0';
  ! 
  ! 	len = decodeenc(path);
  ! 	bputs("CWD ", f);
  ! 	bwrite(f, path, len);
  ! 	bputs("\015\012", f);
  !         bflush(f);
  !         Explain1("FTP: CWD %s",path);
  ! /* responses: 250, 421, 500, 501, 502, 530, 550 */
  ! /* 1,3 error, 2 success, 4,5 failure */
  ! 	i = ftp_getrc(f);
  !         Explain1("FTP: returned status %d",i);
  ! 	if (i == -1) return proxyerror(r, "Error sending to remote server");
  ! 	else if (i == 550) return NOT_FOUND;
  ! 	else if (i != 250) return BAD_GATEWAY;
  ! 
  ! 	path = p + 1;
  !     }
  ! 
  !     if (parms != NULL && strncmp(parms, "type=", 5) == 0)
  !     {
  ! 	parms += 5;
  ! 	if ((parms[0] != 'd' && parms[0] != 'a' && parms[0] != 'i') ||
  ! 	    parms[1] != '\0') parms = "";
  !     }
  !     else parms = "";
  ! 
  !     /* changed to make binary transfers the default */
  ! 
  !     if (parms[0] != 'a')
  !     {
  ! 	/* set type to image */
  !         /* TM - Added \015\012 to the end of TYPE I, otherwise it hangs the
  !            connection */
  ! 	bputs("TYPE I\015\012", f);
  ! 	bflush(f);
  !         Explain0("FTP: TYPE I");
  ! /* responses: 200, 421, 500, 501, 504, 530 */
  ! 	i = ftp_getrc(f);
  !         Explain1("FTP: returned status %d",i);
  ! 	if (i == -1) return proxyerror(r, "Error sending to remote server");
  ! 	else if (i != 200 && i != 504) return BAD_GATEWAY;
  ! /* Allow not implemented */
  ! 	else if (i == 504) parms[0] = '\0';
  !     }
  ! 
  ! /* set up data connection */
  !     len = sizeof(struct sockaddr_in);
  !     if (getsockname(sock, (struct sockaddr *)&server, &len) < 0)
  !     {
  ! 	log_uerror("getsockname", NULL,"proxy: error getting socket address",
  ! 		   r->server);
  ! 	pclosef(pool, sock);
  ! 	return SERVER_ERROR;
  !     }
  ! 
  !     dsock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
  !     if (dsock == -1)
  !     {
  ! 	log_uerror("socket", NULL, "proxy: error creating socket", r->server);
  ! 	pclosef(pool, sock);
  ! 	return SERVER_ERROR;
  !     }
  !     note_cleanups_for_fd(pool, dsock);
  ! 
  !     if (setsockopt(dsock, SOL_SOCKET, SO_REUSEADDR, (const char *)&one,
  ! 		   sizeof(int)) == -1)
  !     {
  ! 	log_uerror("setsockopt", NULL, "proxy: error setting reuseaddr option",
  ! 		   r->server);
  ! 	pclosef(pool, dsock);
  ! 	pclosef(pool, sock);
  ! 	return SERVER_ERROR;
  !     }
  ! 
  !     if (bind(dsock, (struct sockaddr *)&server, sizeof(struct sockaddr_in)) ==
  ! 	-1)
  !     {
  ! 	char buff[22];
  ! 
  ! 	sprintf(buff, "%s:%d", inet_ntoa(server.sin_addr), server.sin_port);
  ! 	log_uerror("bind", buff, "proxy: error binding to ftp data socket",
  ! 		   r->server);
  ! 	pclosef(pool, sock);
  ! 	pclosef(pool, dsock);
  !     }
  !     listen(dsock, 2); /* only need a short queue */
  ! 
  ! /* set request */
  !     len = decodeenc(path);
  ! 
  !     /* TM - if len == 0 then it must be a directory (you can't RETR nothing) */
  ! 
  !     if(len==0) parms="d";
  !     else
  !     {
  !         bputs("SIZE ", f);
  !         bwrite(f, path, len);
  !         bputs("\015\012", f);
  !         bflush(f);
  !         Explain1("FTP: SIZE %s",path);
  !         i = ftp_getrc(f);
  !         Explain1("FTP: returned status %d", i);
  !         if (i != 500) /* Size command not recognized */
  !         {
  !             if (i==550) /* Not a regular file */
  !             {
  !                 Explain0("FTP: SIZE shows this is a directory");
  !                 parms="d";
  !                 bputs("CWD ", f);
  !                 bwrite(f, path, len);
  !                 bputs("\015\012", f);
  !                 bflush(f);
  !                 Explain1("FTP: CWD %s",path);
  !                 i = ftp_getrc(f);
  !                 Explain1("FTP: returned status %d", i);
  !                 if (i == -1) return proxyerror(r, "Error sending to remote server");
  !                 else if (i == 550) return NOT_FOUND;
  !                 else if (i != 250) return BAD_GATEWAY;
  !                 path=""; len=0;
  !             }
  !         }
  !     }
  !             
  !     if (parms[0] == 'd')
  !     {
  ! 	if (len != 0) bputs("LIST ", f);
  ! 	else bputs("LIST -lag", f);
  !         Explain1("FTP: LIST %s",(len==0 ? "" : path));
  !     }
  !     else
  !     {
  !         bputs("RETR ", f);
  !         Explain1("FTP: RETR %s",path);
  !     }
  !     bwrite(f, path, len);
  !     bputs("\015\012", f);
  !     bflush(f);
  ! /* RETR: 110, 125, 150, 226, 250, 421, 425, 426, 450, 451, 500, 501, 530, 550
  !    NLST: 125, 150, 226, 250, 421, 425, 426, 450, 451, 500, 501, 502, 530 */
  !     rc = ftp_getrc(f);
  !     Explain1("FTP: returned status %d",rc);
  !     if (rc == -1) return proxyerror(r, "Error sending to remote server");
  !     if (rc == 550)
  !     {
  !        Explain0("FTP: RETR failed, trying LIST instead");
  !        parms="d";
  !        bputs("CWD ", f);
  !        bwrite(f, path, len);
  !        bputs("\015\012", f);
  !        bflush(f);
  !        Explain1("FTP: CWD %s", path);
  !        rc = ftp_getrc(f);
  !        Explain1("FTP: returned status %d", rc);
  !        if (rc == -1) return proxyerror(r, "Error sending to remote server");
  !        if (rc == 550) return NOT_FOUND;
  !        if (rc != 250) return BAD_GATEWAY;
  ! 
  !        bputs("LIST -lag\015\012", f);
  !        bflush(f);
  !        Explain0("FTP: LIST -lag");
  !        rc = ftp_getrc(f);
  !        Explain1("FTP: returned status %d", rc);
  !        if (rc == -1) return proxyerror(r, "Error sending to remote server");
  !     }   
  !     if (rc != 125 && rc != 150 && rc != 226 && rc != 250) return BAD_GATEWAY;
  !     kill_timeout(r);
  ! 
  !     r->status = 200;
  !     r->status_line = "200 OK";
  ! 
  !     resp_hdrs = make_array(pool, 2, sizeof(struct hdr_entry));
  !     if (parms[0] == 'd')
  ! 	add_header(resp_hdrs, "Content-Type", "text/html", HDR_REP);
  !     else
  !     {
  !         find_ct(r);
  !         if(r->content_type != NULL)
  !         {
  !             add_header(resp_hdrs, "Content-Type", r->content_type, HDR_REP);
  !             Explain1("FTP: Content-Type set to %s",r->content_type);
  !         }
  !     }
  !     i = cache_update(c, resp_hdrs, "FTP", nocache);
  !     if (i != DECLINED)
  !     {
  ! 	pclosef(pool, dsock);
  ! 	pclosef(pool, sock);
  ! 	return i;
  !     }
  !     cache = c->fp;
  ! 
  ! /* wait for connection */
  !     hard_timeout ("proxy ftp data connect", r);
  !     len = sizeof(struct sockaddr_in);
  !     do csd = accept(dsock, (struct sockaddr *)&server, &len);
  !     while (csd == -1 && errno == EINTR);	/* SHUDDER on SOCKS - cdm */
  !     if (csd == -1)
  !     {
  ! 	log_uerror("accept", NULL, "proxy: failed to accept data connection",
  ! 		   r->server);
  ! 	pclosef(pool, dsock);
  ! 	pclosef(pool, sock);
  ! 	cache_error(c);
  ! 	return BAD_GATEWAY;
  !     }
  !     note_cleanups_for_fd(pool, csd);
  !     data = bcreate(pool, B_RDWR);
  !     bpushfd(data, csd, -1);
  !     kill_timeout(r);
  ! 
  !     hard_timeout ("proxy receive", r);
  ! /* send response */
  ! /* write status line */
  !     if (!r->assbackwards)
  ! 	rvputs(r, SERVER_PROTOCOL, " ", r->status_line, "\015\012", NULL);
  !     if (cache != NULL)
  ! 	if (bvputs(cache, SERVER_PROTOCOL, " ", r->status_line, "\015\012",
  ! 		   NULL) == -1)
  ! 	    cache = cache_error(c);
  ! 
  ! /* send headers */
  !     len = resp_hdrs->nelts;
  !     hdr = (struct hdr_entry *)resp_hdrs->elts;
  !     for (i=0; i < len; i++)
  !     {
  ! 	if (hdr[i].field == NULL || hdr[i].value == NULL ||
  ! 	    hdr[i].value[0] == '\0') continue;
  ! 	if (!r->assbackwards)
  ! 	    rvputs(r, hdr[i].field, ": ", hdr[i].value, "\015\012", NULL);
  ! 	if (cache != NULL)
  ! 	    if (bvputs(cache, hdr[i].field, ": ", hdr[i].value, "\015\012",
  ! 		       NULL) == -1)
  ! 		cache = cache_error(c);
  !     }
  ! 
  !     if (!r->assbackwards) rputs("\015\012", r);
  !     if (cache != NULL)
  ! 	if (bputs("\015\012", cache) == -1) cache = cache_error(c);
  ! 
  !     bsetopt(r->connection->client, BO_BYTECT, &zero);
  !     r->sent_bodyct = 1;
  ! /* send body */
  !     if (!r->header_only)
  !     {
  ! 	if (parms[0] != 'd') send_fb(data, r, cache, c);
  !         else send_dir(data, r, cache, c, url);
  ! 
  ! 	if (rc == 125 || rc == 150) rc = ftp_getrc(f);
  ! 	if (rc != 226 && rc != 250) cache_error(c);
  !     }
  !     else
  !     {
  ! /* abort the transfer */
  ! 	bputs("ABOR\015\012", f);
  ! 	bflush(f);
  !         pclosef(pool, csd);
  !         Explain0("FTP: ABOR");
  ! /* responses: 225, 226, 421, 500, 501, 502 */
  ! 	i = ftp_getrc(f);
  !         Explain1("FTP: returned status %d",i);
  !     }
  ! 
  !     cache_tidy(c);
  ! 
  ! /* finish */
  !     bputs("QUIT\015\012", f);
  !     bflush(f);
  !     Explain0("FTP: QUIT");
  ! /* responses: 221, 500 */    
  ! 
  !     pclosef(pool, csd);
  !     pclosef(pool, dsock);
  !     pclosef(pool, sock);
  ! 
  !     return OK;
  ! }
  ! 
  ! /*  
  !  * This handles Netscape CONNECT method secure proxy requests.
  !  * A connection is opened to the specified host and data is
  !  * passed through between the WWW site and the browser.
  !  *
  !  * This code is based on the INTERNET-DRAFT document
  !  * "Tunneling SSL Through a WWW Proxy" currently at
  !  * http://www.mcom.com/newsref/std/tunneling_ssl.html.
  !  *
  !  * FIXME: this is bad, because it does its own socket I/O
  !  *        instead of using the I/O in buff.c.  However,
  !  *        the I/O in buff.c blocks on reads, and because
  !  *        this function doesn't know how much data will
  !  *        be sent either way (or when) it can't use blocking
  !  *        I/O.  This may be very implementation-specific
  !  *        (to Linux).  Any suggestions?
  !  * FIXME: this doesn't log the number of bytes sent, but
  !  *        that may be okay, since the data is supposed to
  !  *        be transparent. In fact, this doesn't log at all
  !  *	  yet. 8^)
  !  * FIXME: doesn't check any headers initally sent from the
  !  *        client.
  !  * FIXME: should allow authentication, but hopefully the
  !  *        generic proxy authentication is good enough.
  !  * FIXME: no check for r->assbackwards, whatever that is.
  !  */ 
  !  
  ! static int
  ! connect_handler(request_rec *r, struct cache_req *c, char *url)
  ! {
  !     struct sockaddr_in server;
  !     const char *host, *err;
  !     char *p;
  !     int   port, sock;
  !     char buffer[HUGE_STRING_LEN];
  !     int  nbytes, i;
  !     fd_set fds;
  ! 
  !     memset(&server, '\0', sizeof(server));
  !     server.sin_family=AF_INET;
  !  
  !     /* Break the URL into host:port pairs */
  ! 
  !     host = url;
  !     p = strchr(url, ':');
  !     if (p==NULL) port = DEFAULT_HTTPS_PORT;
  !     else
  !     {
  !       port = atoi(p+1);
  !       *p='\0';
  !     }
  !  
  !     switch (port)
  !     {
  ! 	case DEFAULT_HTTPS_PORT:
  ! 	case DEFAULT_SNEWS_PORT:
  ! 	    break;
  ! 	default:
  ! 	    return HTTP_SERVICE_UNAVAILABLE;
  !     }
  ! 
  !     Explain2("CONNECT to %s on port %d", host, port);
  !  
  !     server.sin_port = htons(port);
  !     err = host2addr(host, &server.sin_addr);
  !     if (err != NULL) return proxyerror(r, err); /* give up */
  !  
  !     sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);  
  !     if (sock == -1)
  !     {     
  !         log_error("proxy: error creating socket", r->server);
  !         return SERVER_ERROR;
  !     }     
  !     note_cleanups_for_fd(r->pool, sock);
  !  
  !     i = doconnect(sock, &server, r);
  !     if (i == -1 )
  !         return proxyerror(r, "Could not connect to remote machine");
  !  
  !     Explain0("Returning 200 OK Status");
  !  
  !     rvputs(r, "HTTP/1.0 200 Connection established\015\012", NULL);
  !     rvputs(r, "Proxy-agent: ", SERVER_VERSION, "\015\012\015\012", NULL);
  !     bflush(r->connection->client);
  ! 
  !     while (1) /* Infinite loop until error (one side closes the connection) */
  !     {
  !       FD_ZERO(&fds);
  !       FD_SET(sock, &fds);
  !       FD_SET(r->connection->client->fd, &fds);
  !     
  !       Explain0("Going to sleep (select)");
  !       i = select((r->connection->client->fd > sock ?
  ! 	r->connection->client->fd+1 :
  ! #ifdef HPUX
  ! 	sock+1), (int*)&fds, NULL, NULL, NULL);
  ! #else
  ! 	sock+1), &fds, NULL, NULL, NULL);
  ! #endif
  !       Explain1("Woke from select(), i=%d",i);
  !     
  !       if (i)
  !       {
  !         if (FD_ISSET(sock, &fds))
  !         {
  !            Explain0("sock was set");
  !            if((nbytes=read(sock,buffer,HUGE_STRING_LEN))!=0)
  !            {
  !               if(nbytes==-1) break;
  !               if(write(r->connection->client->fd, buffer, nbytes)==EOF)break;
  !               Explain1("Wrote %d bytes to client", nbytes);
  !            }
  !            else break;
  !         }
  !         else if (FD_ISSET(r->connection->client->fd, &fds))
  !         { 
  !            Explain0("client->fd was set");
  !            if((nbytes=read(r->connection->client->fd,buffer,
  ! 		HUGE_STRING_LEN))!=0)   
  !            {
  !               if(nbytes==-1) break;
  !               if(write(sock,buffer,nbytes)==EOF) break;
  !               Explain1("Wrote %d bytes to server", nbytes);
  !            }
  !            else break;
  !         }
  !         else break; /* Must be done waiting */
  !       }
  !       else break;
  !     }
  ! 
  !     pclosef(r->pool,sock);
  !     
  !     return OK;
  ! }     
  ! 
  ! /*
  !  * This handles http:// URLs, and other URLs using a remote proxy over http
  !  * If proxyhost is NULL, then contact the server directly, otherwise
  !  * go via the proxy.
  !  * Note that if a proxy is used, then URLs other than http: can be accessed,
  !  * also, if we have trouble which is clearly specific to the proxy, then
  !  * we return DECLINED so that we can try another proxy. (Or the direct
  !  * route.)
  !  */
  ! static int
  ! http_handler(request_rec *r, struct cache_req *c, char *url,
  ! 	     const char *proxyhost, int proxyport)
  ! {
  !     char *p;
  !     const char *err, *host;
  !     int port, i, sock, len;
  !     array_header *reqhdrs_arr, *resp_hdrs;
  !     table_entry *reqhdrs;
  !     struct sockaddr_in server;
  !     BUFF *f, *cache;
  !     struct hdr_entry *hdr;
  !     char buffer[HUGE_STRING_LEN], inprotocol[9], outprotocol[9];
  !     pool *pool=r->pool;
  !     const long int zero=0L;
  ! 
  !     void *sconf = r->server->module_config;
  !     proxy_server_conf *conf =
  !         (proxy_server_conf *)get_module_config(sconf, &proxy_module);
  !     struct nocache_entry *ent=(struct nocache_entry *)conf->nocaches->elts;
  !     int nocache = 0;
  ! 
  !     memset(&server, '\0', sizeof(server));
  !     server.sin_family = AF_INET;
  ! 
  !     if (proxyhost != NULL)
  !     {
  ! 	server.sin_port = htons(proxyport);
  ! 	err = host2addr(proxyhost, &server.sin_addr);
  ! 	if (err != NULL) return DECLINED;  /* try another */
  ! 	host = proxyhost;
  !     } else
  !     {
  ! 	url += 7;  /* skip http:// */
  ! /* We break the URL into host, port, path-search */
  ! 	port = DEFAULT_PORT;
  ! 	p = strchr(url, '/');
  ! 	if (p == NULL)
  ! 	{
  ! 	    host = pstrdup(pool, url);
  ! 	    url = "/";
  ! 	} else
  ! 	{
  ! 	    char *q = palloc(pool, p-url+1);
  ! 	    memcpy(q, url, p-url);
  ! 	    q[p-url] = '\0';
  ! 	    url = p;
  ! 	    host = q;
  ! 	}
  ! 
  ! 	p = strchr(host, ':');
  ! 	if (p != NULL)
  ! 	{
  ! 	    *(p++) = '\0';
  ! 	    port = atoi(p);
  ! 	}
  ! 	server.sin_port = htons(port);
  ! 	err = host2addr(host, &server.sin_addr);
  ! 	if (err != NULL) return proxyerror(r, err); /* give up */
  !     }
  ! 
  !     sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
  !     if (sock == -1)
  !     {
  ! 	log_error("proxy: error creating socket", r->server);
  ! 	return SERVER_ERROR;
  !     }
  !     note_cleanups_for_fd(pool, sock);
  ! 
  !     i = doconnect(sock, &server, r);
  !     if (i == -1)
  !     {
  ! 	if (proxyhost != NULL) return DECLINED; /* try again another way */
  ! 	else return proxyerror(r, "Could not connect to remote machine");
  !     }
  ! 
  !     f = bcreate(pool, B_RDWR);
  !     bpushfd(f, sock, sock);
  ! 
  !     hard_timeout ("proxy send", r);
  !     bvputs(f, r->method, " ", url, " HTTP/1.0\015\012", NULL);
  ! 
  !     reqhdrs_arr = table_elts (r->headers_in);
  !     reqhdrs = (table_entry *)reqhdrs_arr->elts;
  !     for (i=0; i < reqhdrs_arr->nelts; i++)
  !     {
  ! 	if (reqhdrs[i].key == NULL || reqhdrs[i].val == NULL) continue;
  ! 	if (!strcasecmp(reqhdrs[i].key, "Connection")) continue;
  ! 	bvputs(f, reqhdrs[i].key, ": ", reqhdrs[i].val, "\015\012", NULL);
  !     }
  ! 
  !     bputs("\015\012", f);
  ! /* send the request data, if any. N.B. should we trap SIGPIPE ? */
  ! 
  !     if (should_client_block(r))
  !     {
  ! 	while ((i = read_client_block (r, buffer, HUGE_STRING_LEN)))
  !             bwrite(f, buffer, i);
  !     }
  !     bflush(f);
  !     kill_timeout(r);
  ! 
  !     hard_timeout ("proxy receive", r);
  !     
  !     len = bgets(buffer, HUGE_STRING_LEN-1, f);
  !     if (len == -1 || len == 0)
  !     {
  ! 	pclosef(pool, sock);
  ! 	return proxyerror(r, "Error reading from remote server");
  !     }
  ! 
  ! /* Is it an HTTP/1 response? */
  !     if (checkmask(buffer,  "HTTP/#.# ### *"))
  !     {
  ! /* If not an HTTP/1 messsage or if the status line was > 8192 bytes */
  ! 	if (buffer[5] != '1' || buffer[len-1] != '\n')
  ! 	{
  ! 	    pclosef(pool, sock);
  ! 	    return BAD_GATEWAY;
  ! 	}
  ! 	buffer[--len] = '\0';
  ! 	memcpy(inprotocol, buffer, 8);
  ! 	inprotocol[8] = '\0';
  ! 
  ! /* we use the same protocol on output as on input */
  ! 	strcpy(outprotocol, inprotocol);
  ! 	buffer[12] = '\0';
  ! 	r->status = atoi(&buffer[9]);
  ! 	buffer[12] = ' ';
  ! 	r->status_line = pstrdup(pool, &buffer[9]);
  ! 
  ! /* read the headers. */
  ! /* N.B. for HTTP/1.0 clients, we have to fold line-wrapped headers */
  ! /* Also, take care with headers with multiple occurences. */
  ! 
  ! 	resp_hdrs = read_headers(pool, buffer, HUGE_STRING_LEN, f);
  !     } else
  !     {
  ! /* an http/0.9 response */
  ! 	strcpy(inprotocol, "HTTP/0.9");
  ! 	strcpy(outprotocol, "HTTP/1.0");
  ! 	r->status = 200;
  ! 	r->status_line = "200 OK";
  ! 
  ! /* no headers */
  ! 	resp_hdrs = make_array(pool, 2, sizeof(struct hdr_entry));
  !     }
  ! 
  !     kill_timeout(r);
  ! 
  ! /*
  !  * HTTP/1.0 requires us to accept 3 types of dates, but only generate
  !  * one type
  !  */
  !     
  !     len = resp_hdrs->nelts;
  !     hdr = (struct hdr_entry *)resp_hdrs->elts;
  !     for (i=0; i < len; i++)
  !     {
  ! 	if (hdr[i].value[0] == '\0') continue;
  ! 	p = hdr[i].field;
  ! 	if (strcasecmp(p, "Date") == 0 ||
  ! 	    strcasecmp(p, "Last-Modified") == 0 ||
  ! 	    strcasecmp(p, "Expires") == 0)
  ! 	    hdr[i].value = date_canon(pool, hdr[i].value);
  !     }
  ! 
  ! /* check if NoCache directive on this host */
  !     for (i=0; i < conf->nocaches->nelts; i++)
  !     {
  !         if (ent[i].name[0] == '*' || (ent[i].name != NULL &&
  !           strstr(host, ent[i].name) != NULL))
  ! 	    nocache = 1; 
  !     }
  ! 
  !     i = cache_update(c, resp_hdrs, inprotocol, nocache);
  !     if (i != DECLINED)
  !     {
  ! 	pclosef(pool, sock);
  ! 	return i;
  !     }
  ! 
  !     cache = c->fp;
  ! 
  !     hard_timeout ("proxy receive", r);
  ! 
  ! /* write status line */
  !     if (!r->assbackwards)
  !         rvputs(r, "HTTP/1.0 ", r->status_line, "\015\012", NULL);
  !     if (cache != NULL)
  ! 	if (bvputs(cache, outprotocol, " ", r->status_line, "\015\012", NULL)
  ! 	    == -1)
  ! 	    cache = cache_error(c);
  ! 
  ! /* send headers */
  !     len = resp_hdrs->nelts;
  !     for (i=0; i < len; i++)
  !     {
  ! 	if (hdr[i].field == NULL || hdr[i].value == NULL ||
  ! 	    hdr[i].value[0] == '\0') continue;
  ! 	if (!r->assbackwards)
  ! 	    rvputs(r, hdr[i].field, ": ", hdr[i].value, "\015\012", NULL);
  ! 	if (cache != NULL)
  ! 	    if (bvputs(cache, hdr[i].field, ": ", hdr[i].value, "\015\012",
  ! 		       NULL) == -1)
  ! 		cache = cache_error(c);
  !     }
  ! 
  !     if (!r->assbackwards) rputs("\015\012", r);
  !     if (cache != NULL)
  ! 	if (bputs("\015\012", cache) == -1) cache = cache_error(c);
  ! 
  !     bsetopt(r->connection->client, BO_BYTECT, &zero);
  !     r->sent_bodyct = 1;
  ! /* Is it an HTTP/0.9 respose? If so, send the extra data */
  !     if (strcmp(inprotocol, "HTTP/0.9") == 0)
  !     {
  ! 	bwrite(r->connection->client, buffer, len);
  ! 	if (cache != NULL)
  ! 	    if (bwrite(f, buffer, len) != len) cache = cache_error(c);
  !     }
  ! 
  ! /* send body */
  ! /* if header only, then cache will be NULL */
  ! /* HTTP/1.0 tells us to read to EOF, rather than content-length bytes */
  !     if (!r->header_only) send_fb(f, r, cache, c);
  ! 
  !     cache_tidy(c);
  ! 
  !     pclosef(pool, sock);
  ! 
  !     return OK;
  ! }
  ! 
  ! static handler_rec proxy_handlers[] = {
  ! { "proxy-server", proxy_handler },
  ! { NULL }
  ! };
  ! 
    
    static void *
    create_proxy_config(pool *p, server_rec *s)
  --- 225,241 ----
     * give up??
     */
        /* handle the scheme */
  !     if (r->method_number == M_CONNECT)
  ! 	return proxy_connect_handler(r, cr, url);
  !     if (strcmp(scheme, "http") == 0)
  ! 	return proxy_http_handler(r, cr, url, NULL, 0);
  !     if (strcmp(scheme, "ftp") == 0)
  ! 	return proxy_ftp_handler(r, cr, url);
        else return NOT_IMPLEMENTED;
    }
    
  ! /* -------------------------------------------------------------- */
  ! /* Setup configurable data */
    
    static void *
    create_proxy_config(pool *p, server_rec *s)
  ***************
  *** 3453,3464 ****
        return NULL;
    }
    
    static command_rec proxy_cmds[] = {
    { "ProxyRequests", set_proxy_req, NULL, RSRC_CONF, FLAG,
      "on if the true proxy requests should be accepted"},
  ! { "ProxyRemote", add_proxy, NULL, RSRC_CONF, TAKE2, 
        "a scheme, partial URL or '*' and a proxy server"},
  ! { "ProxyPass", add_pass, NULL, RSRC_CONF, TAKE2, 
        "a virtual path and a URL"},
    { "CacheRoot", set_cache_root, NULL, RSRC_CONF, TAKE1,
          "The directory to store cache files"},
  --- 447,463 ----
        return NULL;
    }
    
  + static handler_rec proxy_handlers[] = {
  + { "proxy-server", proxy_handler },
  + { NULL } 
  + };  
  +     
    static command_rec proxy_cmds[] = {
    { "ProxyRequests", set_proxy_req, NULL, RSRC_CONF, FLAG,
      "on if the true proxy requests should be accepted"},
  ! { "ProxyRemote", add_proxy, NULL, RSRC_CONF, TAKE2,
        "a scheme, partial URL or '*' and a proxy server"},
  ! { "ProxyPass", add_pass, NULL, RSRC_CONF, TAKE2,
        "a virtual path and a URL"},
    { "CacheRoot", set_cache_root, NULL, RSRC_CONF, TAKE1,
          "The directory to store cache files"},
  ***************
  *** 3467,3473 ****
    { "CacheMaxExpire", set_cache_maxex, NULL, RSRC_CONF, TAKE1,
          "The maximum time in hours to cache a document"},
    { "CacheDefaultExpire", set_cache_defex, NULL, RSRC_CONF, TAKE1,
  !       "The default time in hours to cache a document"},
    { "CacheLastModifiedFactor", set_cache_factor, NULL, RSRC_CONF, TAKE1,
          "The factor used to estimate Expires date from LastModified date"},
    { "CacheGcInterval", set_cache_gcint, NULL, RSRC_CONF, TAKE1,
  --- 466,472 ----
    { "CacheMaxExpire", set_cache_maxex, NULL, RSRC_CONF, TAKE1,
          "The maximum time in hours to cache a document"},
    { "CacheDefaultExpire", set_cache_defex, NULL, RSRC_CONF, TAKE1,
  !       "The default time in hours to cache a document"}, 
    { "CacheLastModifiedFactor", set_cache_factor, NULL, RSRC_CONF, TAKE1,
          "The factor used to estimate Expires date from LastModified date"},
    { "CacheGcInterval", set_cache_gcint, NULL, RSRC_CONF, TAKE1,
  ***************
  *** 3481,3501 ****
    { NULL }
    };
    
  - 
    module proxy_module = {
       STANDARD_MODULE_STUFF,
  !    NULL,			/* initializer */
  !    NULL,			/* create per-directory config structure */
  !    NULL,			/* merge per-directory config structures */
  !    create_proxy_config,		/* create per-server config structure */
  !    NULL,                 	/* merge per-server config structures */
  !    proxy_cmds,			/* command table */
  !    proxy_handlers,	        /* handlers */
  !    proxy_trans,			/* translate_handler */
  !    NULL,			/* check_user_id */
  !    NULL,			/* check auth */
  !    NULL,			/* check access */
  !    NULL,			/* type_checker */
  !    proxy_fixup,			/* pre-run fixups */
  !    NULL				/* logger */
    };
  --- 480,500 ----
    { NULL }
    };
    
    module proxy_module = {
       STANDARD_MODULE_STUFF,
  !    NULL,                        /* initializer */
  !    NULL,                        /* create per-directory config structure */
  !    NULL,                        /* merge per-directory config structures */
  !    create_proxy_config,         /* create per-server config structure */
  !    NULL,                        /* merge per-server config structures */
  !    proxy_cmds,                  /* command table */
  !    proxy_handlers,              /* handlers */
  !    proxy_trans,                 /* translate_handler */
  !    NULL,                        /* check_user_id */
  !    NULL,                        /* check auth */
  !    NULL,                        /* check access */
  !    NULL,                        /* type_checker */
  !    proxy_fixup,                 /* pre-run fixups */
  !    NULL                         /* logger */
    };
  +