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 <[EMAIL PROTECTED]> 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 <[EMAIL PROTECTED]> 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 <[EMAIL PROTECTED]> 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, [EMAIL PROTECTED] ##:##:## GMT") && 
  !         !checkmask(date, "day, [EMAIL PROTECTED] ##:##:## 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 <[EMAIL PROTECTED]>
  !  */
  ! 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 = "[EMAIL PROTECTED]";
  !     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 */
    };
  + 
  
  
  

Reply via email to