/* ====================================================================
* The Apache Software License, Version 1.1
*
* Copyright (c) 2000-2003 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Apache" and "Apache Software Foundation" must
* not be used to endorse or promote products derived from this
* software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache",
* nor may "Apache" appear in their name, without prior written
* permission of the Apache Software Foundation.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* .
*
* Portions of this software are based upon public domain software
* originally written at the National Center for Supercomputing Applications,
* University of Illinois, Urbana-Champaign.
*/
/* Cache and garbage collection routines for Apache proxy */
#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"
#include
#include "multithread.h"
#include "ap_md5.h"
struct gc_ent {
unsigned long int len;
time_t expire;
char file[HASH_LEN + 1];
};
/* Poor man's 61 bit arithmetic */
typedef struct {
long lower; /* lower 30 bits of result */
long upper; /* upper 31 bits of result */
} long61_t;
/* FIXME: The block size can be different on a `per file system' base.
* This would make automatic detection highly OS specific.
* In the GNU fileutils code for du(1), you can see how complicated it can
* become to detect the block size. And, with BSD-4.x fragments, it
* it even more difficult to get precise results.
* As a compromise (and to improve on the incorrect counting of cache
* size on byte level, omitting directory sizes entirely, which was
* used up to apache-1.3b7) we're rounding to multiples of 512 here.
* Your file system may be using larger blocks (I certainly hope so!)
* but it will hardly use smaller blocks.
* (So this approximation is still closer to reality than the old behavior).
* The best solution would be automatic detection, the next best solution
* IMHO is a sensible default and the possibility to override it.
*/
#define ROUNDUP2BLOCKS(_bytes) (((_bytes)+block_size-1) & ~(block_size-1))
static long block_size = 512; /* this must be a power of 2 */
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);
static void detached_proxy_garbage_coll(request_rec *r);
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(); /* avoid SIGALRM on big cache cleanup */
if (should_proxy_garbage_coll(r))
detached_proxy_garbage_coll(r);
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)
{
/* Add in lower 30 bits */
accu->lower += (val & 0x3FFFFFFFL);
/* add in upper bits, and carry */
accu->upper += (val >> 30) + ((accu->lower & ~0x3FFFFFFFL) != 0L);
/* Clear carry */
accu->lower &= 0x3FFFFFFFL;
}
static void sub_long61(long61_t *accu, long val)
{
int carry = (val & 0x3FFFFFFFL) > accu->lower;
/* Subtract lower 30 bits */
accu->lower = accu->lower - (val & 0x3FFFFFFFL) + ((carry) ? 0x40000000 : 0);
/* add in upper bits, and carry */
accu->upper -= (val >> 30) + carry;
}
/* Compare two long61's:
* return <0 when left < right
* return 0 when left == right
* return >0 when left > right
*/
static long cmp_long61(long61_t *left, long61_t *right)
{
return (left->upper == right->upper) ? (left->lower - right->lower)
: (left->upper - right->upper);
}
/* Compare two gc_ent's, sort them by expiration date */
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;
}
static void detached_proxy_garbage_coll(request_rec *r)
{
pid_t pid;
int status;
pid_t pgrp;
switch (pid = fork()) {
case -1:
ap_log_error(APLOG_MARK, APLOG_ERR, r->server,
"proxy: fork() for cache cleanup failed");
return;
case 0: /* Child */
/* close all sorts of things, including the socket fd */
ap_cleanup_for_exec();
/* Fork twice to disassociate from the child */
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: /* Child */
/* The setpgrp() stuff was snarfed from http_main.c */
if ((pgrp = setsid()) == -1) {
perror("setsid");
fprintf(stderr, "%s: setsid failed\n",
ap_server_argv0);
exit(1);
}
help_proxy_garbage_coll(r);
exit(0);
default: /* Father */
/* After grandson has been forked off, */
/* there's nothing else to do. */
exit(0);
}
default:
/* Wait until grandson has been forked off */
/* (without wait we'd leave a zombie) */
waitpid(pid, &status, 0);
return;
}
}
#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;
size_t fnlen;
struct stat buf;
int timefd;
time_t every = conf->gcinterval;
static time_t lastcheck = BAD_DATE; /* static (per-process) data!!! */
if (cachedir == NULL || every == -1)
return 0;
fnlen = strlen(cachedir) + strlen(DOT_TIME) + 1;
filename = ap_palloc(r->pool, fnlen);
garbage_now = time(NULL);
/*
* Usually, the modification time of /.time can only increase.
* Thus, even with several child processes having their own copy of
* lastcheck, if time(NULL) still < lastcheck then it's not time for GC
* yet.
*/
if (garbage_now != -1 && lastcheck != BAD_DATE && garbage_now < lastcheck + every)
return 0;
strlcpy(filename, cachedir, fnlen);
strlcat(filename, DOT_TIME, fnlen);
/*
* At this point we have a bit of an engineering compromise. We could
* either create and/or mark the .time file (prior to the fork which
* might fail on a resource issue) or wait until we are safely forked.
* The advantage of doing it now in this process is that we get some
* usefull live out of the global last check variable. (XXX which should
* go scoreboard IMHO.) Note that the actual counting is at a later
* moment.
*/
if (stat(filename, &buf) == -1) { /* does not exist */
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; /* someone else got in there */
return 0;
}
close(timefd);
}
else {
lastcheck = buf.st_mtime; /* save the time */
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);
/* configured size is given in kB. Make it bytes, convert to long61_t: */
cachesize.lower = cachesize.upper = 0;
add_long61(&cachesize, conf->space << 10);
ap_block_alarms(); /* avoid SIGALRM on big cache cleanup */
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;
}
/* sort the files we found by expiration date */
qsort(files->elts, files->nelts, sizeof(struct gc_ent), gcdiff);
for (i = 0; i < files->nelts; i++) {
fent = &((struct gc_ent *) files->elts)[i];
snprintf(filename, sizeof(fent->file), "%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;
struct dirent *ent;
struct gc_ent *fent;
int nfiles = 0;
char *filename;
size_t fnlen;
ap_snprintf(cachedir, sizeof(cachedir), "%s%s", cachebasedir, cachesubdir);
fnlen = strlen(cachedir) + HASH_LEN + 2;
filename = ap_palloc(r->pool, fnlen);
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;
snprintf(filename, fnlen, "%s%s", cachedir, ent->d_name);
ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "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)
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;
/* 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 | 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;
}
if (S_ISDIR(buf.st_mode)) {
char newcachedir[HUGE_STRING_LEN];
close(fd);
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 {
/* Directory is not empty. Account for its size: */
add_long61(&curbytes, ROUNDUP2BLOCKS(buf.st_size));
}
continue;
}
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) {
/* bad file */
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;
}
/*
* 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.
*
*/
fent = (struct gc_ent *) ap_push_array(files);
fent->len = buf.st_size;
fent->expire = garbage_expire;
strlcpy(fent->file, cachesubdir, sizeof(fent->file));
strlcat(fent->file, ent->d_name, sizeof(fent->file));
/* accumulate in blocks, to cope with directories > 4Gb */
add_long61(&curbytes, ROUNDUP2BLOCKS(buf.st_size));
}
closedir(dir);
return nfiles;
}
/*
* Read a cache file;
* returns 1 on success,
* 0 on failure (bad file or wrong URL)
* -1 on UNIX error
*
* We read the cache hex header, then the message response line and
* response headers, and finally we return with the filepointer
* pointing at the start of the message body itself, ready to be
* shipped to the client later on, if appropriate.
*/
static int rdcache(request_rec *r, BUFF *cachefp, cache_req *c)
{
char urlbuff[HUGE_STRING_LEN], *strp;
int len;
/* read the data from the cache file */
/*
* Format:
*
* The cache needs to keep track of the following information: - Date,
* LastMod, Version, ReqTime, RespTime, ContentLength - The original
* request headers (for Vary) - The original response headers (for
* returning with a cached response) - The body of the message
*
* date SP lastmod SP expire SP count SP request-time SP response-time SP
* content-lengthCRLF (dates are stored as hex seconds since 1970)
* Original URLCRLF Original Request Headers CRLF Original Response
* Headers CRLF Body
*
*/
/* retrieve cachefile information values */
len = ap_bgets(urlbuff, sizeof urlbuff, cachefp);
if (len == -1) {
/* Delete broken cache file */
unlink(c->filename);
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));
/* check that we have the same URL */
len = ap_bgets(urlbuff, sizeof urlbuff, cachefp);
if (len == -1) {
/* Delete broken cache file */
unlink(c->filename);
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;
/* then the original request headers */
c->req_hdrs = ap_proxy_read_headers(r, urlbuff, sizeof urlbuff, cachefp);
if (c->req_hdrs == NULL) {
/* Delete broken cache file */
unlink(c->filename);
return -1;
}
/* then the original response headers */
len = ap_bgets(urlbuff, sizeof urlbuff, cachefp);
if (len == -1) {
/* Delete broken cache file */
unlink(c->filename);
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) {
/* Delete broken cache file */
unlink(c->filename);
return -1;
}
if (c->len != -1) /* add a content-length header */
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;
}
/*
* Call this to check the possible conditional status of
* the client request, and return the response from the cache
*
* Conditionals include If-Modified-Since, If-Match, If-Unmodified-Since
* and If-None-Match.
*
* We don't yet understand If-Range, but we will...
*/
int ap_proxy_cache_conditional(request_rec *r, cache_req *c, BUFF *cachefp)
{
const char *etag, *wetag = NULL;
/* get etag */
if ((etag = ap_table_get(c->hdrs, "Etag"))) {
wetag = ap_pstrcat(r->pool, "W/", etag, NULL);
}
/* check for If-Match, If-Unmodified-Since */
while (1) {
/*
* check If-Match and If-Unmodified-Since exist
*
* If neither of these exist, the request is not conditional, and we
* serve it normally
*/
if (!c->im && BAD_DATE == c->ius) {
break;
}
/*
* check If-Match
*
* we check if the Etag on the cached file is in the list of Etags in
* the If-Match field. The comparison must be a strong comparison, so
* the Etag cannot be marked as weak. If the comparision fails we
* return 412 Precondition Failed.
*
* if If-Match is specified AND If-Match is not a "*" AND Etag is
* missing or weak or not in the list THEN return 412 Precondition
* Failed
*/
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;
}
}
/*
* check If-Unmodified-Since
*
* if If-Unmodified-Since is specified AND Last-Modified is specified
* somewhere AND If-Unmodified-Since is in the past compared to
* Last-Modified THEN return 412 Precondition Failed
*/
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 cache file is being updated */
if (c->origfp) {
ap_proxy_write_headers(c, c->resp_line, c->hdrs);
ap_proxy_send_fb(c->origfp, r, c, c->len, 1, 0, IOBUFSIZE);
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;
}
/* check for If-None-Match, If-Modified-Since */
while (1) {
/*
* check for existance of If-None-Match and If-Modified-Since
*
* if neither of these headers have been set, then the request is not
* conditional, and we just send the cached response and be done with
* it.
*/
if (!c->inm && BAD_DATE == c->ims) {
break;
}
/*
* check If-None-Match
*
* we check if the Etag on the cached file is in the list of Etags in
* the If-None-Match field. The comparison must be a strong
* comparison, so the Etag cannot be marked as weak. If the
* comparision fails we return 412 Precondition Failed.
*
* if If-None-Match is specified: if If-None-Match is a "*" THEN 304
* else if Etag is specified AND we get a match THEN 304 else if Weak
* Etag is specified AND we get a match THEN 304 else sent the
* original object
*/
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;
}
/*
* check If-Modified-Since
*
* if If-Modified-Since is specified AND Last-Modified is specified
* somewhere: if last modification date is earlier than
* If-Modified-Since THEN 304 else send the original object
*/
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;
}
/* are we updating the cache file? */
if (c->origfp) {
ap_proxy_write_headers(c, c->resp_line, c->hdrs);
ap_proxy_send_fb(c->origfp, r, c, c->len, 1, 0, IOBUFSIZE);
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;
}
/* No conditional - just send it cousin! */
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;
/* Prepare and send headers to client */
ap_proxy_table_replace(r->headers_out, c->hdrs);
/* make sure our X-Cache header does not stomp on a previous header */
ap_table_mergen(r->headers_out, "X-Cache", c->xcache);
/* content type is already set in the headers */
r->content_type = ap_table_get(r->headers_out, "Content-Type");
ap_send_http_header(r);
/* are we rewriting the cache file? */
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, 0, IOBUFSIZE);
ap_proxy_cache_tidy(c);
return OK;
}
/* no, we not */
if (!r->header_only) {
ap_proxy_send_fb(cachefp, r, NULL, c->len, 0, 0, IOBUFSIZE);
}
else {
ap_pclosef(r->pool, ap_bfileno(cachefp, B_WR));
}
return OK;
}
/*
* 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 cached file is expired then
* if last modified after if-modified-since then add
* last modified date to request
*/
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;
cache_req *c;
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;
/* get the If-Modified-Since date of the request, if it exists */
c->ims = BAD_DATE;
datestr = ap_table_get(r->headers_in, "If-Modified-Since");
if (datestr != NULL) {
/* this may modify the value in the original table */
datestr = ap_proxy_date_canon(r->pool, datestr);
c->ims = ap_parseHTTPdate(datestr);
if (c->ims == BAD_DATE) /* bad or out of range date; remove it */
ap_table_unset(r->headers_in, "If-Modified-Since");
}
/* get the If-Unmodified-Since date of the request, if it exists */
c->ius = BAD_DATE;
datestr = ap_table_get(r->headers_in, "If-Unmodified-Since");
if (datestr != NULL) {
/* this may modify the value in the original table */
datestr = ap_proxy_date_canon(r->pool, datestr);
c->ius = ap_parseHTTPdate(datestr);
if (c->ius == BAD_DATE) /* bad or out of range date; remove it */
ap_table_unset(r->headers_in, "If-Unmodified-Since");
}
/* get the If-Match of the request, if it exists */
c->im = ap_table_get(r->headers_in, "If-Match");
/* get the If-None-Match of the request, if it exists */
c->inm = ap_table_get(r->headers_in, "If-None-Match");
/* find the filename for this cache entry */
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;
}
/* find certain cache controlling headers */
pragma_req = ap_table_get(r->headers_in, "Pragma");
cc_req = ap_table_get(r->headers_in, "Cache-Control");
/* first things first - does the request allow us to return
* cached information at all? If not, just decline the request.
*
* Note that there is a big difference between not being allowed
* to cache a request (no-store) and not being allowed to return
* a cached request without revalidation (max-age=0).
*
* Caching is forbidden under the following circumstances:
*
* - RFC2616 14.9.2 Cache-Control: no-store
* we are not supposed to store this request at all. Behave as a tunnel.
*
*/
if (ap_proxy_liststr(cc_req, "no-store", NULL)) {
/* delete the previously cached file */
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;
}
/* if the cache file exists, open it */
cachefp = NULL;
ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "Request for %s, pragma_req=%s, ims=%ld", url,
(pragma_req == NULL) ? "(unset)" : pragma_req, (long)c->ims);
/* find out about whether the request can access the cache */
if (c->filename != NULL && r->method_number == M_GET &&
strlen(url) < 1024) {
cachefp = ap_proxy_open_cachefile(r, c->filename);
}
/*
* if a cache file exists, try reading body and headers from cache file
*/
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");
if ((agestr = ap_table_get(c->hdrs, "Age"))) {
age_c = atoi(agestr);
}
}
}
/* if a cache file does not exist, create empty header array */
/* fixed? in this case, we want to get the headers from the remote server
it will be handled later if we don't do this (I hope ;-)
if (cachefp == NULL)
c->hdrs = ap_make_table(r->pool, 20);
*/
/* FIXME: Shouldn't we check the URL somewhere? */
/*
* Check Content-Negotiation - Vary
*
* At this point we need to make sure that the object we found in the cache
* is the same object that would be delivered to the client, when the
* effects of content negotiation are taken into effect.
*
* In plain english, we want to make sure that a language-negotiated
* document in one language is not given to a client asking for a
* language negotiated document in a different language by mistake.
*
* RFC2616 13.6 and 14.44 describe the Vary mechanism.
*/
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;
/* isolate header name */
while (*vary && !ap_isspace(*vary) && (*vary != ','))
++vary;
while (*vary && (ap_isspace(*vary) || (*vary == ','))) {
*vary = '\0';
++vary;
}
/*
* is this header in the request and the header in the cached
* request identical? If not, we give up and do a straight get
*/
h1 = ap_table_get(r->headers_in, name);
h2 = ap_table_get(c->req_hdrs, name);
if (h1 == h2) {
/* both headers NULL, so a match - do nothing */
}
else if (h1 && h2 && !strcmp(h1, h2)) {
/* both headers exist and are equal - do nothing */
}
else {
/* headers do not match, so Vary failed */
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;
}
}
}
/*
* We now want to check if our cached data is still fresh. This depends
* on a few things, in this order:
*
* - RFC2616 14.9.4 End to end reload, Cache-Control: no-cache no-cache in
* either the request or the cached response means that we must
* revalidate the request unconditionally, overriding any expiration
* mechanism. It's equivalent to max-age=0,must-revalidate.
*
* - RFC2616 14.32 Pragma: no-cache This is treated the same as
* Cache-Control: no-cache.
*
* - RFC2616 14.9.3 Cache-Control: max-stale, must-revalidate,
* proxy-revalidate if the max-stale request header exists, modify the
* stale calculations below so that an object can be at most
* seconds stale before we request a revalidation, _UNLESS_ a
* must-revalidate or proxy-revalidate cached response header exists to
* stop us doing this.
*
* - RFC2616 14.9.3 Cache-Control: s-maxage the origin server specifies the
* maximum age an object can be before it is considered stale. This
* directive has the effect of proxy|must revalidate, which in turn means
* simple ignore any max-stale setting.
*
* - RFC2616 14.9.4 Cache-Control: max-age this header can appear in both
* requests and responses. If both are specified, the smaller of the two
* takes priority.
*
* - RFC2616 14.21 Expires: if this request header exists in the cached
* entity, and it's value is in the past, it has expired.
*
*/
/* calculate age of object */
age = ap_proxy_current_age(c, age_c);
/* extract s-maxage */
if (cc_cresp && ap_proxy_liststr(cc_cresp, "s-maxage", &val))
smaxage = atoi(val);
else
smaxage = -1;
/* extract max-age from request */
if (cc_req && ap_proxy_liststr(cc_req, "max-age", &val))
maxage_req = atoi(val);
else
maxage_req = -1;
/* extract max-age from response */
if (cc_cresp && ap_proxy_liststr(cc_cresp, "max-age", &val))
maxage_cresp = atoi(val);
else
maxage_cresp = -1;
/*
* if both maxage request and response, the smaller one takes priority
*/
if (-1 == maxage_req)
maxage = maxage_cresp;
else if (-1 == maxage_cresp)
maxage = maxage_req;
else
maxage = MIN(maxage_req, maxage_cresp);
/* extract max-stale */
if (cc_req && ap_proxy_liststr(cc_req, "max-stale", &val))
maxstale = atoi(val);
else
maxstale = 0;
/* extract min-fresh */
if (cc_req && ap_proxy_liststr(cc_req, "min-fresh", &val))
minfresh = atoi(val);
else
minfresh = 0;
/* override maxstale if must-revalidate or proxy-revalidate */
if (maxstale && ((cc_cresp && ap_proxy_liststr(cc_cresp, "must-revalidate", NULL)) || (cc_cresp && ap_proxy_liststr(cc_cresp, "proxy-revalidate", NULL))))
maxstale = 0;
if (cachefp != NULL &&
/* handle no-cache */
!((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))) &&
/* handle expiration */
((-1 < smaxage && age < (smaxage - minfresh)) ||
(-1 < maxage && age < (maxage + maxstale - minfresh)) ||
(c->expire != BAD_DATE && age < (c->expire - c->date + maxstale - minfresh)))
) {
/* it's fresh darlings... */
ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "Unexpired data available");
/* set age header on response */
ap_table_set(c->hdrs, "Age",
ap_psprintf(r->pool, "%lu", (unsigned long)age));
/* add warning if maxstale overrode freshness calculation */
if (!((-1 < smaxage && age < smaxage) ||
(-1 < maxage && age < maxage) ||
(c->expire != BAD_DATE && (c->expire - c->date) > age))) {
/* make sure we don't stomp on a previous warning */
ap_table_merge(c->hdrs, "Warning", "110 Response is stale");
}
/* check conditionals (If-Modified-Since, etc) */
c->xcache = ap_pstrcat(r->pool, "HIT from ", ap_get_server_name(r), NULL);
return ap_proxy_cache_conditional(r, c, cachefp);
}
/*
* at this point we have determined our cached data needs revalidation
* but first - we check 1 thing:
*
* RFC2616 14.9.4 - if "only-if-cached" specified, send a 504 Gateway
* Timeout - we're not allowed to revalidate the object
*/
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 we already have cached data and a last-modified date, and it is not
* a head request, then add an If-Modified-Since.
*
* If we also have an Etag, then the object must have come from an HTTP/1.1
* server. Add an If-None-Match as well.
*
* See RFC2616 13.3.4
*/
if (cachefp != NULL && !r->header_only) {
const char *etag = ap_table_get(c->hdrs, "Etag");
/* If-Modified-Since */
if (c->lmod != BAD_DATE) {
/*
* use the later of the one from the request and the
* last-modified date from the cache
*/
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-None-Match */
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;
}
/*
* 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
*/
int ap_proxy_cache_update(cache_req *c, table *resp_hdrs,
const int is_HTTP1, int nocache)
{
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;
size_t tflen;
cc_resp = ap_table_get(resp_hdrs, "Cache-Control");
c->tempfile = NULL;
/* we've received the response from the origin server */
/*
* read expiry date; if a bad date, then leave it so the client can read
* it
*/
expire = ap_table_get(resp_hdrs, "Expires");
if (expire != NULL)
expc = ap_parseHTTPdate(expire);
else
expc = BAD_DATE;
/* read the last-modified date; if the date is bad, then delete it */
lmods = ap_table_get(resp_hdrs, "Last-Modified");
if (lmods != NULL) {
lmod = ap_parseHTTPdate(lmods);
if (lmod == BAD_DATE) {
/* kill last modified date */
lmods = NULL;
}
}
else
lmod = BAD_DATE;
/*
* what responses should we not cache?
*
* At this point we decide based on the response headers whether it is
* appropriate _NOT_ to cache the data from the server. There are a whole
* lot of conditions that prevent us from caching this data. They are
* tested here one by one to be clear and unambiguous.
*/
/*
* RFC2616 13.4 we are allowed to cache 200, 203, 206, 300, 301 or 410 We
* don't cache 206, because we don't (yet) cache partial responses. We
* include 304 Not Modified here too as this is the origin server telling
* us to serve the cached copy.
*/
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) ||
/* if a broken Expires header is present, don't cache it */
(expire != NULL && expc == BAD_DATE) ||
/*
* if the server said 304 Not Modified but we have no cache file - pass
* this untouched to the user agent, it's not for us.
*/
(r->status == HTTP_NOT_MODIFIED && (c == NULL || c->fp == NULL)) ||
/*
* 200 OK response from HTTP/1.0 and up without a Last-Modified header
*/
(r->status == HTTP_OK && lmods == NULL && is_HTTP1) ||
/* HEAD requests */
r->header_only ||
/*
* RFC2616 14.9.2 Cache-Control: no-store response indicating do not
* cache, or stop now if you are trying to cache it
*/
ap_proxy_liststr(cc_resp, "no-store", NULL) ||
/*
* RFC2616 14.9.1 Cache-Control: private this object is marked for this
* user's eyes only. Behave as a tunnel.
*/
ap_proxy_liststr(cc_resp, "private", NULL) ||
/*
* RFC2616 14.8 Authorisation: if authorisation is included in the
* request, we don't cache, but we can cache if the following exceptions
* are true: 1) If Cache-Control: s-maxage is included 2) If
* Cache-Control: must-revalidate is included 3) If Cache-Control: public
* is included
*/
(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))
) ||
/* or we've been asked not to cache it above */
nocache) {
ap_log_error(APLOG_MARK, APLOG_DEBUG | APLOG_NOERRNO, r->server, "Response is not cacheable, unlinking %s", c->filename);
/* close the file */
if (c->fp != NULL) {
ap_pclosef(r->pool, ap_bfileno(c->fp, B_WR));
c->fp = NULL;
}
/* delete the previously cached file */
if (c->filename)
unlink(c->filename);
return DECLINED; /* send data to client but not cache */
}
/*
* It's safe to cache the response.
*
* We now want to update the cache file header information with the new
* date, last modified, expire and content length and write it away to
* our cache file. First, we determine these values from the response,
* using heuristics if appropriate.
*
* In addition, we make HTTP/1.1 age calculations and write them away too.
*/
/* Read the date. Generate one if one is not supplied */
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) { /* 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;
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");
}
/* set response_time for HTTP/1.1 age calculations */
c->resp_time = now;
/* check last-modified date */
if (lmod != BAD_DATE && lmod > date)
/* if its in the future, then replace by 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 the response did not contain the header, then use the cached version */
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");
}
/* we now need to calculate the expire data for the object. */
if (expire == NULL && c->fp != NULL) { /* no expiry data sent in
* response */
expire = ap_table_get(c->hdrs, "Expires");
if (expire != NULL)
expc = ap_parseHTTPdate(expire);
}
/* 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
*/
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);
}
/* get the content-length header */
clen = ap_table_get(resp_hdrs, "Content-Length");
if (clen == NULL)
c->len = -1;
else
c->len = ap_strtol(clen, NULL, 10);
/* we have all the header information we need - write it to the cache file */
c->version++;
ap_proxy_sec2hex(date, buff + 17 * (0), sizeof(buff) - 17 * 0);
buff[17 * (1) - 1] = ' ';
ap_proxy_sec2hex(lmod, buff + 17 * (1), sizeof(buff) - 17 * 1);
buff[17 * (2) - 1] = ' ';
ap_proxy_sec2hex(expc, buff + 17 * (2), sizeof(buff) - 17 * 2);
buff[17 * (3) - 1] = ' ';
ap_proxy_sec2hex(c->version, buff + 17 * (3), sizeof(buff) - 17 * 3);
buff[17 * (4) - 1] = ' ';
ap_proxy_sec2hex(c->req_time, buff + 17 * (4), sizeof(buff) - 17 * 4);
buff[17 * (5) - 1] = ' ';
ap_proxy_sec2hex(c->resp_time, buff + 17 * (5), sizeof(buff) - 17 * 5);
buff[17 * (6) - 1] = ' ';
ap_proxy_sec2hex(c->len, buff + 17 * (6), sizeof(buff) - 17 * 6);
buff[17 * (7) - 1] = '\n';
buff[17 * (7)] = '\0';
/* Was the server response a 304 Not Modified?
*
* If it was, it means that we requested a revalidation, and that
* the result of that revalidation was that the object was fresh.
*
*/
/* if response from server 304 not modified */
if (r->status == HTTP_NOT_MODIFIED) {
/* Have the headers changed?
*
* if not - we fulfil the request and return now.
*/
if (c->hdrs) {
/* recall at this point that c->len is already set from resp_hdrs.
If Content-Length was NULL, then c->len is -1, otherwise it's
set to whatever the value was. */
if (c->len == 0 || c->len == -1) {
const char *c_clen_str;
off_t c_clen;
if ( (c_clen_str = ap_table_get(c->hdrs, "Content-Length")) &&
( (c_clen = ap_strtol(c_clen_str, NULL, 10)) > 0) ) {
ap_table_set(resp_hdrs, "Content-Length", c_clen_str);
c->len = c_clen;
ap_proxy_sec2hex(c->len, buff + 17 * (6),
sizeof(buff) - 17 * 6);
buff[17 * (7) - 1] = '\n';
buff[17 * (7)] = '\0';
}
}
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 we get here - the headers have changed. Go through the motions
* of creating a new temporary cache file below, we'll then serve
* the request like we would have in ap_proxy_cache_conditional()
* above, and at the same time we will also rewrite the contents
* to the new temporary file.
*/
}
/*
* Ok - lets prepare and open the cached file
*
* If a cached file (in c->fp) is already open, then we want to
* update that cached file. Copy the c->fp to c->origfp and open
* up a new one.
*
* If the cached file (in c->fp) is NULL, we must open a new cached
* file from scratch.
*
* The new cache file will be moved to it's final location in the
* directory tree later, overwriting the old cache file should it exist.
*/
/* if a cache file was already open */
if (c->fp != NULL) {
c->origfp = c->fp;
}
while (1) {
/* create temporary filename */
#define TMPFILESTR "/tmpXXXXXXXXXX"
if (conf->cache.root == NULL) {
c = ap_proxy_cache_error(c);
break;
}
tflen = strlen(conf->cache.root) + sizeof(TMPFILESTR);
c->tempfile = ap_palloc(r->pool, tflen);
strlcpy(c->tempfile, conf->cache.root, tflen);
strlcat(c->tempfile, TMPFILESTR, tflen);
#undef TMPFILESTR
p = mktemp(c->tempfile);
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);
/* create the new file */
c->fp = ap_proxy_create_cachefile(r, c->tempfile);
if (NULL == c->fp) {
c = ap_proxy_cache_error(c);
break;
}
/* write away the cache header and the URL */
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;
}
/* get original request headers */
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);
/* remove hop-by-hop headers */
ap_proxy_clear_connection(r->pool, req_hdrs);
/* save original request headers */
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;
}
/* Was the server response a 304 Not Modified?
*
* If so, we have some work to do that we didn't do when we first
* checked above. We need to fulfil the request, and we need to
* copy the body from the old object to the new one.
*/
/* if response from server 304 not modified */
if (r->status == HTTP_NOT_MODIFIED) {
/* fulfil the request */
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;
/* don't care how much was sent, but rather how much was written to cache
ap_bgetopt(c->req->connection->client, BO_BYTECT, &bc);
*/
bc = c->written;
if (c->len != -1) {
/* file lengths don't match; don't cache it */
if (bc != c->len) {
ap_pclosef(c->req->pool, ap_bfileno(c->fp, B_WR)); /* no need to flush */
unlink(c->tempfile);
return;
}
}
/* don't care if aborted, cache it if fully retrieved from host!
else if (c->req->connection->aborted) {
ap_pclosef(c->req->pool, c->fp->fd); / no need to flush /
unlink(c->tempfile);
return;
}
*/
else {
/* update content-length of file */
char buff[17];
off_t curpos;
c->len = bc;
ap_bflush(c->fp);
ap_proxy_sec2hex(c->len, buff, sizeof(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->filename);
(void)unlink(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 (mkdir(c->filename, S_IREAD | S_IWRITE | S_IEXEC) < 0 && errno != EEXIST)
ap_log_error(APLOG_MARK, APLOG_ERR, s,
"proxy: error creating cache directory %s",
c->filename);
*p = '/';
++p;
}
if (link(c->tempfile, c->filename) == -1)
ap_log_error(APLOG_MARK, APLOG_INFO, 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);
}
}