#include "mod_proxy.h"
#include "http_conf_globals.h"
#include "http_log.h"
#include "http_main.h"
#include "http_core.h"
#include "util_date.h"
#ifdef WIN32
#include <sys/utime.h>
#else
#include <utime.h>
#endif /* WIN32 */
#include "multithread.h"
#include "ap_md5.h"
#ifdef __TANDEM
#include <sys/types.h>
#include <sys/stat.h>
#endif
#ifdef TPF
#include "os.h"
#endif
struct
gc_ent {
unsigned
long
int
len;
time_t
expire;
char
file[HASH_LEN + 1];
};
typedef
struct
{
long
lower;
long
upper;
} long61_t;
#define ROUNDUP2BLOCKS(_bytes) (((_bytes)+block_size-1) & ~(block_size-1))
static
long
block_size = 512;
static
long61_t curbytes, cachesize;
static
time_t
garbage_now, garbage_expire;
static
mutex *garbage_mutex = NULL;
int
ap_proxy_garbage_init(server_rec *r, pool *p)
{
if
(!garbage_mutex)
garbage_mutex = ap_create_mutex(NULL);
return
(0);
}
static
int
sub_garbage_coll(request_rec *r, array_header *files,
const
char
*cachedir,
const
char
*cachesubdir);
static
void
help_proxy_garbage_coll(request_rec *r);
static
int
should_proxy_garbage_coll(request_rec *r);
#if !defined(WIN32) && !defined(MPE) && !defined(OS2) && !defined(NETWARE) && !defined(TPF)
static
void
detached_proxy_garbage_coll(request_rec *r);
#endif
void
ap_proxy_garbage_coll(request_rec *r)
{
static
int
inside = 0;
(
void
) ap_acquire_mutex(garbage_mutex);
if
(inside == 1) {
(
void
) ap_release_mutex(garbage_mutex);
return
;
}
else
inside = 1;
(
void
) ap_release_mutex(garbage_mutex);
ap_block_alarms();
if
(should_proxy_garbage_coll(r))
#if !defined(WIN32) && !defined(MPE) && !defined(OS2) && !defined(NETWARE) && !defined(TPF)
detached_proxy_garbage_coll(r);
#else
help_proxy_garbage_coll(r);
#endif
ap_unblock_alarms();
(
void
) ap_acquire_mutex(garbage_mutex);
inside = 0;
(
void
) ap_release_mutex(garbage_mutex);
}
static
void
add_long61 (long61_t *accu,
long
val)
{
accu->lower += (val & 0x3FFFFFFFL);
accu->upper += (val >> 30) + ((accu->lower & ~0x3FFFFFFFL) != 0L);
accu->lower &= 0x3FFFFFFFL;
}
static
void
sub_long61 (long61_t *accu,
long
val)
{
int
carry = (val & 0x3FFFFFFFL) > accu->lower;
accu->lower = accu->lower - (val & 0x3FFFFFFFL) + ((carry) ? 0x40000000 : 0);
accu->upper -= (val >> 30) + carry;
}
static
long
cmp_long61 (long61_t *left, long61_t *right)
{
return
(left->upper == right->upper) ? (left->lower - right->lower)
: (left->upper - right->upper);
}
static
int
gcdiff(
const
void
*ap,
const
void
*bp)
{
const
struct
gc_ent *a = (
const
struct
gc_ent *) ap;
const
struct
gc_ent *b = (
const
struct
gc_ent *) bp;
if
(a->expire > b->expire)
return
1;
else
if
(a->expire < b->expire)
return
-1;
else
return
0;
}
#if !defined(WIN32) && !defined(MPE) && !defined(OS2) && !defined(NETWARE) && !defined(TPF)
static
void
detached_proxy_garbage_coll(request_rec *r)
{
pid_t pid;
int
status;
pid_t pgrp;
#if 0
ap_log_error(APLOG_MARK, APLOG_DEBUG, r->server,
"proxy: Guess what; we fork() again..."
);
#endif
switch
(pid = fork()) {
case
-1:
ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
"proxy: fork() for cache cleanup failed"
);
return
;
case
0:
ap_cleanup_for_exec();
switch
(pid = fork()) {
case
-1:
ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
"proxy: fork(2nd) for cache cleanup failed"
);
exit
(1);
case
0:
#ifndef NO_SETSID
if
((pgrp = setsid()) == -1) {
perror
(
"setsid"
);
fprintf
(stderr,
"%s: setsid failed\n"
,
ap_server_argv0);
exit
(1);
}
#elif defined(NEXT) || defined(NEWSOS)
if
(setpgrp(0, getpid()) == -1 || (pgrp = getpgrp(0)) == -1) {
perror
(
"setpgrp"
);
fprintf
(stderr,
"%S: setpgrp or getpgrp failed\n"
,
ap_server_argv0);
exit
(1);
}
#else
if
((pgrp = setpgrp(getpid(), 0)) == -1) {
perror
(
"setpgrp"
);
fprintf
(stderr,
"%s: setpgrp failed\n"
,
ap_server_argv0);
exit
(1);
}
#endif
help_proxy_garbage_coll(r);
exit
(0);
default
:
exit
(0);
}
default
:
waitpid(pid, &status, 0);
return
;
}
}
#endif /* ndef WIN32 */
#define DOT_TIME "/.time" /* marker */
static
int
should_proxy_garbage_coll(request_rec *r)
{
void
*sconf = r->server->module_config;
proxy_server_conf *pconf =
(proxy_server_conf *) ap_get_module_config(sconf, &proxy_module);
const
struct
cache_conf *conf = &pconf->cache;
const
char
*cachedir = conf->root;
char
*filename;
struct
stat buf;
int
timefd;
time_t
every = conf->gcinterval;
static
time_t
lastcheck = BAD_DATE;
if
(cachedir == NULL || every == -1)
return
0;
filename = ap_palloc(r->pool,
strlen
(cachedir) +
strlen
( DOT_TIME ) +1);
garbage_now =
time
(NULL);
if
(garbage_now != -1 && lastcheck != BAD_DATE && garbage_now < lastcheck + every)
return
0;
strcpy
(filename,cachedir);
strcat
(filename,DOT_TIME);
if
(stat(filename, &buf) == -1) {
if
(
errno
!= ENOENT) {
ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
"proxy: stat(%s)"
, filename);
return
0;
}
if
((timefd = creat(filename, 0666)) == -1) {
if
(
errno
!= EEXIST)
ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
"proxy: creat(%s)"
, filename);
else
lastcheck = garbage_now;
return
0;
}
close(timefd);
}
else
{
lastcheck = buf.st_mtime;
if
(garbage_now < lastcheck + every) {
return
0;
}
if
(utime(filename, NULL) == -1)
ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
"proxy: utimes(%s)"
, filename);
}
return
1;
}
static
void
help_proxy_garbage_coll(request_rec *r)
{
const
char
*cachedir;
void
*sconf = r->server->module_config;
proxy_server_conf *pconf =
(proxy_server_conf *) ap_get_module_config(sconf, &proxy_module);
const
struct
cache_conf *conf = &pconf->cache;
array_header *files;
struct
gc_ent *fent;
char
*filename;
int
i;
cachedir = conf->root;
filename = ap_palloc(r->pool,
strlen
(cachedir) + HASH_LEN + 2);
cachesize.lower = cachesize.upper = 0;
add_long61(&cachesize, conf->space << 10);
ap_block_alarms();
files = ap_make_array(r->pool, 100,
sizeof
(
struct
gc_ent));
curbytes.upper = curbytes.lower = 0L;
sub_garbage_coll(r, files, cachedir,
"/"
);
if
(cmp_long61(&curbytes, &cachesize) < 0L) {
ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r->server,
"proxy GC: Cache is %ld%% full (nothing deleted)"
,
(
long
)(((curbytes.upper<<20)|(curbytes.lower>>10))*100/conf->space));
ap_unblock_alarms();
return
;
}
qsort
(files->elts, files->nelts,
sizeof
(
struct
gc_ent), gcdiff);
for
(i = 0; i < files->nelts; i++) {
fent = &((
struct
gc_ent *) files->elts)[i];
sprintf
(filename,
"%s%s"
, cachedir, fent->file);
ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r->server,
"GC Unlinking %s (expiry %ld, garbage_now %ld)"
, filename, (
long
)fent->expire, (
long
)garbage_now);
#if TESTING
fprintf
(stderr,
"Would unlink %s\n"
, filename);
#else
if
(unlink(filename) == -1) {
if
(
errno
!= ENOENT)
ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
"proxy gc: unlink(%s)"
, filename);
}
else
#endif
{
sub_long61(&curbytes, ROUNDUP2BLOCKS(fent->len));
if
(cmp_long61(&curbytes, &cachesize) < 0)
break
;
}
}
ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r->server,
"proxy GC: Cache is %ld%% full (%d deleted)"
,
(
long
)(((curbytes.upper<<20)|(curbytes.lower>>10))*100/conf->space), i);
ap_unblock_alarms();
}
static
int
sub_garbage_coll(request_rec *r, array_header *files,
const
char
*cachebasedir,
const
char
*cachesubdir)
{
char
line[17*(3)];
char
cachedir[HUGE_STRING_LEN];
struct
stat buf;
int
fd, i;
DIR *dir;
#if defined(NEXT) || defined(WIN32)
struct
DIR_TYPE *ent;
#else
struct
dirent *ent;
#endif
struct
gc_ent *fent;
int
nfiles = 0;
char
*filename;
ap_snprintf(cachedir,
sizeof
(cachedir),
"%s%s"
, cachebasedir, cachesubdir);
filename = ap_palloc(r->pool,
strlen
(cachedir) + HASH_LEN + 2);
ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r->server,
"GC Examining directory %s"
, cachedir);
dir = opendir(cachedir);
if
(dir == NULL) {
ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
"proxy gc: opendir(%s)"
, cachedir);
return
0;
}
while
((ent = readdir(dir)) != NULL) {
if
(ent->d_name[0] ==
'.'
)
continue
;
sprintf
(filename,
"%s%s"
, cachedir, ent->d_name);
ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r->server,
"GC Examining file %s"
, filename);
if
(
strncmp
(ent->d_name,
"tmp"
, 3) == 0) {
if
(stat(filename, &buf) == -1) {
if
(
errno
!= ENOENT)
ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
"proxy gc: stat(%s)"
, filename);
}
else
if
(garbage_now != -1 && buf.st_atime < garbage_now - SEC_ONE_DAY &&
buf.st_mtime < garbage_now - SEC_ONE_DAY) {
ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r->server,
"GC unlink %s"
, filename);
ap_log_error(APLOG_MARK, APLOG_INFO|APLOG_NOERRNO, r->server,
"proxy gc: deleting orphaned cache file %s"
, filename);
#if TESTING
fprintf
(stderr,
"Would unlink %s\n"
, filename);
#else
unlink(filename);
#endif
}
continue
;
}
++nfiles;
#if defined(OS2) || defined(TPF)
#ifdef OS2
if
(ent->d_attr & A_DIR) {
#elif defined(TPF)
if
(stat(filename, &buf) == -1) {
if
(
errno
!= ENOENT)
ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
"proxy gc: stat(%s)"
, filename);
}
if
(S_ISDIR(buf.st_mode)) {
#endif
char
newcachedir[HUGE_STRING_LEN];
ap_snprintf(newcachedir,
sizeof
(newcachedir),
"%s%s/"
, cachesubdir, ent->d_name);
if
(!sub_garbage_coll(r, files, cachebasedir, newcachedir)) {
ap_snprintf(newcachedir,
sizeof
(newcachedir),
"%s%s"
, cachedir, ent->d_name);
#if TESTING
fprintf
(stderr,
"Would remove directory %s\n"
, newcachedir);
#else
rmdir(newcachedir);
#endif
--nfiles;
}
continue
;
}
#endif
#if defined(WIN32)
if
(stat(filename, &buf) == -1) {
ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
"proxy gc: stat(%s)"
, filename);
continue
;
}
fd = -1;
#else
fd = open(filename, O_RDONLY | O_BINARY);
if
(fd == -1) {
if
(
errno
!= ENOENT)
ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
"proxy gc: open(%s)"
, filename);
continue
;
}
if
(fstat(fd, &buf) == -1) {
ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
"proxy gc: fstat(%s)"
, filename);
close(fd);
continue
;
}
#endif
#if !defined(OS2) && !defined(TPF)
if
(S_ISDIR(buf.st_mode)) {
char
newcachedir[HUGE_STRING_LEN];
#if !defined(WIN32)
close(fd);
#endif
ap_snprintf(newcachedir,
sizeof
(newcachedir),
"%s%s/"
, cachesubdir, ent->d_name);
if
(!sub_garbage_coll(r, files, cachebasedir, newcachedir)) {
ap_snprintf(newcachedir,
sizeof
(newcachedir),
"%s%s"
, cachedir, ent->d_name);
#if TESTING
fprintf
(stderr,
"Would remove directory %s\n"
, newcachedir);
#else
rmdir(newcachedir);
#endif
--nfiles;
}
else
{
add_long61(&curbytes, ROUNDUP2BLOCKS(buf.st_size));
}
continue
;
}
#endif
#if defined(WIN32)
fd = open(filename, O_RDONLY | O_BINARY);
if
(fd == -1) {
if
(
errno
!= ENOENT)
ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
"proxy gc: open(%s) = %d"
, filename,
errno
);
continue
;
}
#endif
i = read(fd, line, 17*(3)-1);
close(fd);
if
(i == -1) {
ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
"proxy gc: read(%s)"
, filename);
continue
;
}
line[i] =
'\0'
;
garbage_expire = ap_proxy_hex2sec(line + 17*(2));
if
(!ap_checkmask(line,
"&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&&"
) ||
garbage_expire == BAD_DATE) {
if
(garbage_now != -1 && buf.st_atime > garbage_now + SEC_ONE_DAY &&
buf.st_mtime > garbage_now + SEC_ONE_DAY) {
ap_log_error(APLOG_MARK, APLOG_WARNING|APLOG_NOERRNO, r->server,
"proxy: deleting bad cache file with future date: %s"
, filename);
#if TESTING
fprintf
(stderr,
"Would unlink bad file %s\n"
, filename);
#else
unlink(filename);
#endif
}
continue
;
}
fent = (
struct
gc_ent *) ap_push_array(files);
fent->len = buf.st_size;
fent->expire = garbage_expire;
strcpy
(fent->file, cachesubdir);
strcat
(fent->file, ent->d_name);
add_long61(&curbytes, ROUNDUP2BLOCKS(buf.st_size));
}
closedir(dir);
return
nfiles;
}
static
int
rdcache(request_rec *r, BUFF *cachefp, cache_req *c)
{
char
urlbuff[HUGE_STRING_LEN], *strp;
int
len;
len = ap_bgets(urlbuff,
sizeof
urlbuff, cachefp);
if
(len == -1)
return
-1;
if
(len == 0 || urlbuff[len - 1] !=
'\n'
)
return
0;
urlbuff[len - 1] =
'\0'
;
if
(!ap_checkmask(urlbuff,
"&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&&"
))
return
0;
c->date = ap_proxy_hex2sec(urlbuff + 17*(0));
c->lmod = ap_proxy_hex2sec(urlbuff + 17*(1));
c->expire = ap_proxy_hex2sec(urlbuff + 17*(2));
c->version = ap_proxy_hex2sec(urlbuff + 17*(3));
c->req_time = ap_proxy_hex2sec(urlbuff + 17*(4));
c->resp_time = ap_proxy_hex2sec(urlbuff + 17*(5));
c->len = ap_proxy_hex2sec(urlbuff + 17*(6));
len = ap_bgets(urlbuff,
sizeof
urlbuff, 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;
c->req_hdrs = ap_proxy_read_headers(r, urlbuff,
sizeof
urlbuff, cachefp);
if
(c->req_hdrs == NULL)
return
-1;
len = ap_bgets(urlbuff,
sizeof
urlbuff, cachefp);
if
(len == -1)
return
-1;
if
(len == 0 || urlbuff[len - 1] !=
'\n'
)
return
0;
urlbuff[--len] =
'\0'
;
c->resp_line = ap_pstrdup(r->pool, urlbuff);
strp =
strchr
(urlbuff,
' '
);
if
(strp == NULL)
return
0;
c->status =
atoi
(strp);
c->hdrs = ap_proxy_read_headers(r, urlbuff,
sizeof
urlbuff, cachefp);
if
(c->hdrs == NULL)
return
-1;
if
(c->len != -1)
if
(ap_table_get(c->hdrs,
"Content-Length"
) == NULL) {
ap_table_set(c->hdrs,
"Content-Length"
,
ap_psprintf(r->pool,
"%lu"
, (unsigned
long
)c->len));
}
return
1;
}
int
ap_proxy_cache_conditional(request_rec *r, cache_req *c, BUFF *cachefp)
{
const
char
*etag, *wetag = NULL;
if
((etag = ap_table_get(c->hdrs,
"Etag"
))) {
wetag = ap_pstrcat(r->pool,
"W/"
, etag, NULL);
}
while
(1) {
if
(!c->im && BAD_DATE == c->ius) {
break
;
}
if
(c->im) {
if
(
strcmp
(c->im,
"*"
) &&
(!etag || (
strlen
(etag) > 1 &&
'W'
== etag[0] &&
'/'
== etag[1]) || !ap_proxy_liststr(c->im, etag, NULL))) {
ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r->server,
"If-Match specified, and it didn't - return 412"
);
}
else
{
ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r->server,
"If-Match specified, and it matched"
);
break
;
}
}
if
(BAD_DATE != c->ius && BAD_DATE != c->lmod) {
if
(c->ius < c->lmod) {
ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r->server,
"If-Unmodified-Since specified, but it wasn't - return 412"
);
}
else
{
ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r->server,
"If-Unmodified-Since specified, and it was unmodified"
);
break
;
}
}
if
(c->origfp) {
ap_proxy_write_headers(c, c->resp_line, c->hdrs);
ap_proxy_send_fb(c->origfp, r, c, c->len, 1);
ap_pclosef(r->pool, ap_bfileno(c->origfp, B_WR));
ap_proxy_cache_tidy(c);
}
else
ap_pclosef(r->pool, ap_bfileno(cachefp, B_WR));
ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r->server,
"Use your cached copy, conditional precondition failed."
);
return
HTTP_PRECONDITION_FAILED;
}
while
(1) {
if
(!c->inm && BAD_DATE == c->ims) {
break
;
}
if
(c->inm) {
if
(!
strcmp
(c->inm,
"*"
)) {
ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r->server,
"If-None-Match: * specified, return 304"
);
}
else
if
(etag && ap_proxy_liststr(c->inm, etag, NULL)) {
ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r->server,
"If-None-Match: specified and we got a strong match - return 304"
);
}
else
if
(wetag && ap_proxy_liststr(c->inm, wetag, NULL)) {
ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r->server,
"If-None-Match specified, and we got a weak match - return 304"
);
}
else
break
;
}
if
(BAD_DATE != c->ims && BAD_DATE != c->lmod) {
if
(c->ims >= c->lmod) {
ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r->server,
"If-Modified-Since specified and not modified, try return 304"
);
}
else
break
;
}
if
(c->origfp) {
ap_proxy_write_headers(c, c->resp_line, c->hdrs);
ap_proxy_send_fb(c->origfp, r, c, c->len, 1);
ap_pclosef(r->pool, ap_bfileno(c->origfp, B_WR));
ap_proxy_cache_tidy(c);
}
else
ap_pclosef(r->pool, ap_bfileno(cachefp, B_WR));
ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r->server,
"Use local copy, cached file hasn't changed"
);
return
HTTP_NOT_MODIFIED;
}
ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r->server,
"Local copy modified, send it"
);
r->status_line =
strchr
(c->resp_line,
' '
) + 1;
r->status = c->status;
ap_overlap_tables(r->headers_out, c->hdrs, AP_OVERLAP_TABLES_SET);
ap_table_setn(r->headers_out,
"X-Cache"
, c->xcache);
r->content_type = ap_table_get(r->headers_out,
"Content-Type"
);
ap_send_http_header(r);
if
(c->origfp) {
ap_proxy_write_headers(c, c->resp_line, c->hdrs);
ap_proxy_send_fb(c->origfp, r, c, c->len, r->header_only);
ap_pclosef(r->pool, ap_bfileno(c->origfp, B_WR));
ap_proxy_cache_tidy(c);
return
OK;
}
if
(!r->header_only)
ap_proxy_send_fb(cachefp, r, NULL, c->len, 0);
ap_pclosef(r->pool, ap_bfileno(cachefp, B_WR));
return
OK;
}
int
ap_proxy_cache_check(request_rec *r,
char
*url,
struct
cache_conf *conf,
cache_req **cr)
{
const
char
*datestr, *pragma_req = NULL, *pragma_cresp = NULL, *cc_req = NULL, *cc_cresp = NULL, *vary = NULL;
cache_req *c;
time_t
now;
BUFF *cachefp;
int
i;
void
*sconf = r->server->module_config;
proxy_server_conf *pconf =
(proxy_server_conf *) ap_get_module_config(sconf, &proxy_module);
const
char
*agestr = NULL;
char
*val;
time_t
age_c = 0;
time_t
age, maxage_req, maxage_cresp, maxage, smaxage, maxstale, minfresh;
c = ap_pcalloc(r->pool,
sizeof
(cache_req));
*cr = c;
c->req = r;
c->url = ap_pstrdup(r->pool, url);
c->filename = NULL;
c->tempfile = NULL;
c->fp = NULL;
c->origfp = NULL;
c->version = 0;
c->len = -1;
c->req_hdrs = NULL;
c->hdrs = NULL;
c->xcache = NULL;
c->ims = BAD_DATE;
datestr = ap_table_get(r->headers_in,
"If-Modified-Since"
);
if
(datestr != NULL) {
datestr = ap_proxy_date_canon(r->pool, datestr);
c->ims = ap_parseHTTPdate(datestr);
if
(c->ims == BAD_DATE)
ap_table_unset(r->headers_in,
"If-Modified-Since"
);
}
c->ius = BAD_DATE;
datestr = ap_table_get(r->headers_in,
"If-Unmodified-Since"
);
if
(datestr != NULL) {
datestr = ap_proxy_date_canon(r->pool, datestr);
c->ius = ap_parseHTTPdate(datestr);
if
(c->ius == BAD_DATE)
ap_table_unset(r->headers_in,
"If-Unmodified-Since"
);
}
c->im = ap_table_get(r->headers_in,
"If-Match"
);
c->inm = ap_table_get(r->headers_in,
"If-None-Match"
);
if
(conf->root != NULL) {
char
hashfile[66];
ap_proxy_hash(url, hashfile, pconf->cache.dirlevels, pconf->cache.dirlength);
c->filename = ap_pstrcat(r->pool, conf->root,
"/"
, hashfile, NULL);
}
else
{
c->filename = NULL;
c->fp = NULL;
ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r->server,
"No CacheRoot, so no caching. Declining."
);
return
DECLINED;
}
pragma_req = ap_table_get(r->headers_in,
"Pragma"
);
cc_req = ap_table_get(r->headers_in,
"Cache-Control"
);
if
(ap_proxy_liststr(cc_req,
"no-store"
, NULL)) {
if
(c->filename)
unlink(c->filename);
c->fp = NULL;
c->filename = NULL;
ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r->server,
"no-store forbids caching. Declining."
);
return
DECLINED;
}
cachefp = NULL;
ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r->server,
"Request for %s, pragma_req=%s, ims=%ld"
, url,
pragma_req, c->ims);
if
(c->filename != NULL && r->method_number == M_GET &&
strlen
(url) < 1024 ) {
cachefp = ap_proxy_open_cachefile(r, c->filename);
}
if
(cachefp != NULL) {
i = rdcache(r, cachefp, c);
if
(i == -1)
ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
"proxy: error reading cache file %s"
,
c->filename);
else
if
(i == 0)
ap_log_rerror(APLOG_MARK, APLOG_ERR|APLOG_NOERRNO, r,
"proxy: bad (short?) cache file: %s"
, c->filename);
if
(i != 1) {
ap_pclosef(r->pool, ap_bfileno(cachefp, B_WR));
cachefp = NULL;
}
if
(c->hdrs) {
cc_cresp = ap_table_get(c->hdrs,
"Cache-Control"
);
pragma_cresp = ap_table_get(c->hdrs,
"Pragma"
);
vary = ap_table_get(c->hdrs,
"Vary"
);
if
((agestr = ap_table_get(c->hdrs,
"Age"
))) {
age_c =
atoi
(agestr);
}
}
}
if
(c->hdrs && c->req_hdrs) {
char
*vary = ap_pstrdup(r->pool, ap_table_get(c->hdrs,
"Vary"
));
while
(vary && *vary) {
char
*name = vary;
const
char
*h1, *h2;
while
(*vary && !ap_isspace(*vary) && (*vary !=
','
))
++vary;
while
(*vary && (ap_isspace(*vary) || (*vary ==
','
))) {
*vary =
'\0'
;
++vary;
}
h1 = ap_table_get(r->headers_in, name);
h2 = ap_table_get(c->req_hdrs, name);
if
(h1 == h2) {
}
else
if
(h1 && h2 && !
strcmp
(h1, h2)) {
}
else
{
c->fp = cachefp;
ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r->server,
"Vary header mismatch - object must be fetched from scratch. Declining."
);
return
DECLINED;
}
}
}
age = ap_proxy_current_age(c, age_c);
if
(cc_cresp && ap_proxy_liststr(cc_cresp,
"s-maxage"
, &val))
smaxage =
atoi
(val);
else
smaxage = -1;
if
(cc_cresp && ap_proxy_liststr(cc_req,
"max-age"
, &val))
maxage_req =
atoi
(val);
else
maxage_req = -1;
if
(cc_cresp && ap_proxy_liststr(cc_cresp,
"max-age"
, &val))
maxage_cresp =
atoi
(val);
else
maxage_cresp = -1;
if
(-1 == maxage_req)
maxage = maxage_cresp;
else
if
(-1 == maxage_cresp)
maxage = maxage_req;
else
maxage = MIN(maxage_req, maxage_cresp);
if
(cc_req && ap_proxy_liststr(cc_req,
"max-stale"
, &val))
maxstale =
atoi
(val);
else
maxstale = 0;
if
(cc_req && ap_proxy_liststr(cc_req,
"min-fresh"
, &val))
minfresh =
atoi
(val);
else
minfresh = 0;
if
(maxstale && ( (cc_cresp && ap_proxy_liststr(cc_cresp,
"must-revalidate"
, NULL)) || (cc_cresp && ap_proxy_liststr(cc_cresp,
"proxy-revalidate"
, NULL)) ))
maxstale = 0;
now =
time
(NULL);
if
(cachefp != NULL &&
!( (cc_req && ap_proxy_liststr(cc_req,
"no-cache"
, NULL)) ||
(pragma_req && ap_proxy_liststr(pragma_req,
"no-cache"
, NULL)) ||
(cc_cresp && ap_proxy_liststr(cc_cresp,
"no-cache"
, NULL)) ||
(pragma_cresp && ap_proxy_liststr(pragma_cresp,
"no-cache"
, NULL)) ) &&
( (-1 < smaxage && age < (smaxage - minfresh)) ||
(-1 < maxage && age < (maxage + maxstale - minfresh)) ||
(c->expire != BAD_DATE && age < (c->expire - c->date + maxstale - minfresh)) )
) {
ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r->server,
"Unexpired data available"
);
ap_table_set(c->hdrs,
"Age"
,
ap_psprintf(r->pool,
"%lu"
, (unsigned
long
)age));
if
(!( (-1 < smaxage && age < smaxage) ||
(-1 < maxage && age < maxage) ||
(c->expire != BAD_DATE && (c->expire - c->date) > age) )) {
ap_table_set(c->hdrs,
"Warning"
,
"110 Response is stale"
);
}
c->xcache = ap_pstrcat(r->pool,
"HIT from "
, ap_get_server_name(r), NULL);
return
ap_proxy_cache_conditional(r, c, cachefp);
}
if
(ap_proxy_liststr(cc_req,
"only-if-cached"
, NULL)) {
if
(cachefp)
ap_pclosef(r->pool, ap_bfileno(cachefp, B_WR));
return
HTTP_GATEWAY_TIME_OUT;
}
if
(cachefp != NULL && !r->header_only) {
const
char
*etag = ap_table_get(c->hdrs,
"Etag"
);
if
(c->lmod != BAD_DATE) {
if
(c->ims == BAD_DATE || c->ims < c->lmod) {
const
char
*q;
if
((q = ap_table_get(c->hdrs,
"Last-Modified"
)) != NULL)
ap_table_set(r->headers_in,
"If-Modified-Since"
, (
char
*) q);
}
}
if
(etag) {
ap_table_set(r->headers_in,
"If-None-Match"
, etag);
}
}
c->fp = cachefp;
ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r->server,
"Local copy not present or expired. Declining."
);
return
DECLINED;
}
int
ap_proxy_cache_update(cache_req *c, table *resp_hdrs,
const
int
is_HTTP1,
int
nocache)
{
#if defined(ULTRIX_BRAIN_DEATH) || defined(SINIX_D_RESOLVER_BUG)
extern
char
*mktemp(
char
*
template
);
#endif
request_rec *r = c->req;
char
*p;
const
char
*expire, *lmods, *dates, *clen;
time_t
expc, date, lmod, now;
char
buff[17*7+1];
void
*sconf = r->server->module_config;
proxy_server_conf *conf =
(proxy_server_conf *) ap_get_module_config(sconf, &proxy_module);
const
char
*cc_resp;
table *req_hdrs;
cc_resp = ap_table_get(resp_hdrs,
"Cache-Control"
);
c->tempfile = NULL;
expire = ap_table_get(resp_hdrs,
"Expires"
);
if
(expire != NULL)
expc = ap_parseHTTPdate(expire);
else
expc = BAD_DATE;
lmods = ap_table_get(resp_hdrs,
"Last-Modified"
);
if
(lmods != NULL) {
lmod = ap_parseHTTPdate(lmods);
if
(lmod == BAD_DATE) {
lmods = NULL;
}
}
else
lmod = BAD_DATE;
if
((r->status != HTTP_OK && r->status != HTTP_NON_AUTHORITATIVE && r->status != HTTP_MULTIPLE_CHOICES && r->status != HTTP_MOVED_PERMANENTLY && r->status != HTTP_NOT_MODIFIED) ||
(expire != NULL && expc == BAD_DATE) ||
(r->status == HTTP_NOT_MODIFIED && (c == NULL || c->fp == NULL)) ||
(r->status == HTTP_OK && lmods == NULL && is_HTTP1) ||
r->header_only ||
ap_proxy_liststr(cc_resp,
"no-store"
, NULL) ||
ap_proxy_liststr(cc_resp,
"private"
, NULL) ||
(ap_table_get(r->headers_in,
"Authorization"
) != NULL
&& !(ap_proxy_liststr(cc_resp,
"s-maxage"
, NULL) || ap_proxy_liststr(cc_resp,
"must-revalidate"
, NULL) || ap_proxy_liststr(cc_resp,
"public"
, NULL))
) ||
nocache) {
ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r->server,
"Response is not cacheable, unlinking %s"
, c->filename);
if
(c->fp != NULL) {
ap_pclosef(r->pool, ap_bfileno(c->fp, B_WR));
c->fp = NULL;
}
if
(c->filename)
unlink(c->filename);
return
DECLINED;
}
dates = ap_table_get(resp_hdrs,
"Date"
);
if
(dates != NULL)
date = ap_parseHTTPdate(dates);
else
date = BAD_DATE;
now =
time
(NULL);
if
(date == BAD_DATE) {
date = now;
dates = ap_gm_timestr_822(r->pool, now);
ap_table_set(resp_hdrs,
"Date"
, dates);
ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r->server,
"Added date header"
);
}
c->resp_time = now;
if
(lmod != BAD_DATE && lmod > date)
{
lmod = date;
lmods = dates;
ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r->server,
"Last modified is in the future, replacing with now"
);
}
if
(lmod == BAD_DATE && c->fp != NULL) {
lmod = c->lmod;
ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r->server,
"Reusing cached last modified"
);
}
if
(expire == NULL && c->fp != NULL) {
expire = ap_table_get(c->hdrs,
"Expires"
);
if
(expire != NULL)
expc = ap_parseHTTPdate(expire);
}
ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r->server,
"Expiry date is %ld"
, (
long
)expc);
if
(expc == BAD_DATE) {
if
(lmod != BAD_DATE) {
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;
ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r->server,
"Expiry date calculated %ld"
, (
long
)expc);
}
clen = ap_table_get(resp_hdrs,
"Content-Length"
);
if
(clen == NULL)
c->len = -1;
else
c->len =
atoi
(clen);
c->version++;
ap_proxy_sec2hex(date, buff + 17*(0));
buff[17*(1)-1] =
' '
;
ap_proxy_sec2hex(lmod, buff + 17*(1));
buff[17*(2)-1] =
' '
;
ap_proxy_sec2hex(expc, buff + 17*(2));
buff[17*(3)-1] =
' '
;
ap_proxy_sec2hex(c->version, buff + 17*(3));
buff[17*(4)-1] =
' '
;
ap_proxy_sec2hex(c->req_time, buff + 17*(4));
buff[17*(5)-1] =
' '
;
ap_proxy_sec2hex(c->resp_time, buff + 17*(5));
buff[17*(6)-1] =
' '
;
ap_proxy_sec2hex(c->len, buff + 17*(6));
buff[17*(7)-1] =
'\n'
;
buff[17*(7)] =
'\0'
;
if
(r->status == HTTP_NOT_MODIFIED) {
if
(c->hdrs) {
if
(!ap_proxy_table_replace(c->hdrs, resp_hdrs)) {
c->xcache = ap_pstrcat(r->pool,
"HIT from "
, ap_get_server_name(r),
" (with revalidation)"
, NULL);
return
ap_proxy_cache_conditional(r, c, c->fp);
}
}
else
c->hdrs = resp_hdrs;
}
if
(c->fp != NULL) {
c->origfp = c->fp;
}
while
(1) {
#ifndef TPF
#define TMPFILESTR "/tmpXXXXXX"
if
(conf->cache.root == NULL) {
c = ap_proxy_cache_error(c);
break
;
}
c->tempfile = ap_palloc(r->pool,
strlen
(conf->cache.root) +
sizeof
(TMPFILESTR));
strcpy
(c->tempfile, conf->cache.root);
strcat
(c->tempfile, TMPFILESTR);
#undef TMPFILESTR
p = mktemp(c->tempfile);
#else
if
(conf->cache.root == NULL) {
c = ap_proxy_cache_error(c);
break
;
}
c->tempfile = ap_palloc(r->pool,
strlen
(conf->cache.root) +1+ L_tmpnam);
strcpy
(c->tempfile, conf->cache.root);
strcat
(c->tempfile,
"/"
);
p =
tmpnam
(NULL);
strcat
(c->tempfile, p);
#endif
if
(p == NULL) {
c = ap_proxy_cache_error(c);
break
;
}
ap_log_error(APLOG_MARK, APLOG_DEBUG|APLOG_NOERRNO, r->server,
"Create temporary file %s"
, c->tempfile);
c->fp = ap_proxy_create_cachefile(r, c->tempfile);
if
(NULL == c->fp) {
c = ap_proxy_cache_error(c);
break
;
}
if
(ap_bvputs(c->fp, buff,
"X-URL: "
, c->url,
"\n"
, NULL) == -1) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, r,
"proxy: error writing cache file(%s)"
, c->tempfile);
c = ap_proxy_cache_error(c);
break
;
}
if
(c->req_hdrs)
req_hdrs = ap_copy_table(r->pool, c->req_hdrs);
else
req_hdrs = ap_copy_table(r->pool, r->headers_in);
ap_proxy_clear_connection(r->pool, req_hdrs);
if
(c->req_hdrs)
ap_table_do(ap_proxy_send_hdr_line, c, c->req_hdrs, NULL);
else
ap_table_do(ap_proxy_send_hdr_line, c, r->headers_in, NULL);
if
(ap_bputs(CRLF, c->fp) == -1) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, c->req,
"proxy: error writing request headers terminating CRLF to %s"
, c->tempfile);
c = ap_proxy_cache_error(c);
break
;
}
break
;
}
if
(r->status == HTTP_NOT_MODIFIED) {
c->xcache = ap_pstrcat(r->pool,
"HIT from "
, ap_get_server_name(r),
" (with revalidation)"
, NULL);
return
ap_proxy_cache_conditional(r, c, c->fp);
}
return
DECLINED;
}
void
ap_proxy_cache_tidy(cache_req *c)
{
server_rec *s;
long
int
bc;
if
(!c || !c->fp)
return
;
s = c->req->server;
bc = c->written;
if
(c->len != -1) {
if
(bc != c->len) {
ap_pclosef(c->req->pool, ap_bfileno(c->fp, B_WR));
unlink(c->tempfile);
return
;
}
}
else
{
char
buff[17];
off_t curpos;
c->len = bc;
ap_bflush(c->fp);
ap_proxy_sec2hex(c->len, buff);
curpos = lseek(ap_bfileno(c->fp, B_WR), 17*6, SEEK_SET);
if
(curpos == -1)
ap_log_error(APLOG_MARK, APLOG_ERR, s,
"proxy: error seeking on cache file %s"
, c->tempfile);
else
if
(write(ap_bfileno(c->fp, B_WR), buff,
sizeof
(buff) - 1) == -1)
ap_log_error(APLOG_MARK, APLOG_ERR, s,
"proxy: error updating cache file %s"
, c->tempfile);
}
if
(ap_bflush(c->fp) == -1) {
ap_log_error(APLOG_MARK, APLOG_ERR, s,
"proxy: error writing to cache file %s"
,
c->tempfile);
ap_pclosef(c->req->pool, ap_bfileno(c->fp, B_WR));
unlink(c->tempfile);
return
;
}
if
(ap_pclosef(c->req->pool, ap_bfileno(c->fp, B_WR)) == -1) {
ap_log_error(APLOG_MARK, APLOG_ERR, s,
"proxy: error closing cache file %s"
, c->tempfile);
unlink(c->tempfile);
return
;
}
if
(unlink(c->filename) == -1 &&
errno
!= ENOENT) {
ap_log_error(APLOG_MARK, APLOG_ERR, s,
"proxy: error deleting old cache file %s"
,
c->tempfile);
}
else
{
char
*p;
proxy_server_conf *conf =
(proxy_server_conf *) ap_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 defined(WIN32) || defined(NETWARE)
if
(mkdir(c->filename) < 0 &&
errno
!= EEXIST)
#elif defined(__TANDEM)
if
(mkdir(c->filename, S_IRWXU | S_IRWXG | S_IRWXO) < 0 &&
errno
!= EEXIST)
#else
if
(mkdir(c->filename, S_IREAD | S_IWRITE | S_IEXEC) < 0 &&
errno
!= EEXIST)
#endif /* WIN32 */
ap_log_error(APLOG_MARK, APLOG_ERR, s,
"proxy: error creating cache directory %s"
,
c->filename);
*p =
'/'
;
++p;
}
#if defined(OS2) || defined(WIN32) || defined(NETWARE) || defined(MPE)
if
(
rename
(c->tempfile, c->filename) == -1)
ap_log_error(APLOG_MARK, APLOG_ERR, s,
"proxy: error renaming cache file %s to %s"
,
c->tempfile, c->filename);
}
#else
if
(link(c->tempfile, c->filename) == -1)
ap_log_error(APLOG_MARK, APLOG_ERR, s,
"proxy: error linking cache file %s to %s"
,
c->tempfile, c->filename);
}
if
(unlink(c->tempfile) == -1)
ap_log_error(APLOG_MARK, APLOG_ERR, s,
"proxy: error deleting temp file %s"
, c->tempfile);
#endif
}