1/* Licensed to the Apache Software Foundation (ASF) under one or more
2 * contributor license agreements.  See the NOTICE file distributed with
3 * this work for additional information regarding copyright ownership.
4 * The ASF licenses this file to You under the Apache License, Version 2.0
5 * (the "License"); you may not use this file except in compliance with
6 * the License.  You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "apr_lib.h"
18#include "apr_file_io.h"
19#include "apr_strings.h"
20#include "apr_buckets.h"
21#include "httpd.h"
22#include "http_config.h"
23#include "http_log.h"
24#include "http_core.h"
25#include "ap_provider.h"
26#include "ap_socache.h"
27#include "util_filter.h"
28#include "util_script.h"
29#include "util_charset.h"
30#include "util_mutex.h"
31
32#include "mod_cache.h"
33
34#include "cache_socache_common.h"
35
36/*
37 * mod_cache_socache: Shared Object Cache Based HTTP 1.1 Cache.
38 *
39 * Flow to Find the entry:
40 *   Incoming client requests URI /foo/bar/baz
41 *   Fetch URI key (may contain Format #1 or Format #2)
42 *   If format #1 (Contains a list of Vary Headers):
43 *      Use each header name (from .header) with our request values (headers_in) to
44 *      regenerate key using HeaderName+HeaderValue+.../foo/bar/baz
45 *      re-read in key (must be format #2)
46 *
47 * Format #1:
48 *   apr_uint32_t format;
49 *   apr_time_t expire;
50 *   apr_array_t vary_headers (delimited by CRLF)
51 *
52 * Format #2:
53 *   cache_socache_info_t (first sizeof(apr_uint32_t) bytes is the format)
54 *   entity name (sobj->name) [length is in cache_socache_info_t->name_len]
55 *   r->headers_out (delimited by CRLF)
56 *   CRLF
57 *   r->headers_in (delimited by CRLF)
58 *   CRLF
59 */
60
61module AP_MODULE_DECLARE_DATA cache_socache_module;
62
63/*
64 * cache_socache_object_t
65 * Pointed to by cache_object_t::vobj
66 */
67typedef struct cache_socache_object_t
68{
69    apr_pool_t *pool; /* pool */
70    unsigned char *buffer; /* the cache buffer */
71    apr_size_t buffer_len; /* size of the buffer */
72    apr_bucket_brigade *body; /* brigade containing the body, if any */
73    apr_table_t *headers_in; /* Input headers to save */
74    apr_table_t *headers_out; /* Output headers to save */
75    cache_socache_info_t socache_info; /* Header information. */
76    apr_size_t body_offset; /* offset to the start of the body */
77    unsigned int newbody :1; /* whether a new body is present */
78    apr_time_t expire; /* when to expire the entry */
79
80    const char *name; /* Requested URI without vary bits - suitable for mortals. */
81    const char *key; /* On-disk prefix; URI with Vary bits (if present) */
82    apr_off_t file_size; /*  File size of the cached data file  */
83    apr_off_t offset; /* Max size to set aside */
84    apr_time_t timeout; /* Max time to set aside */
85    unsigned int done :1; /* Is the attempt to cache complete? */
86} cache_socache_object_t;
87
88/*
89 * mod_cache_socache configuration
90 */
91#define DEFAULT_MAX_FILE_SIZE 100*1024
92#define DEFAULT_MAXTIME 86400
93#define DEFAULT_MINTIME 600
94#define DEFAULT_READSIZE 0
95#define DEFAULT_READTIME 0
96
97typedef struct cache_socache_provider_conf
98{
99    const char *args;
100    ap_socache_provider_t *socache_provider;
101    ap_socache_instance_t *socache_instance;
102} cache_socache_provider_conf;
103
104typedef struct cache_socache_conf
105{
106    cache_socache_provider_conf *provider;
107} cache_socache_conf;
108
109typedef struct cache_socache_dir_conf
110{
111    apr_off_t max; /* maximum file size for cached files */
112    apr_time_t maxtime; /* maximum expiry time */
113    apr_time_t mintime; /* minimum expiry time */
114    apr_off_t readsize; /* maximum data to attempt to cache in one go */
115    apr_time_t readtime; /* maximum time taken to cache in one go */
116    unsigned int max_set :1;
117    unsigned int maxtime_set :1;
118    unsigned int mintime_set :1;
119    unsigned int readsize_set :1;
120    unsigned int readtime_set :1;
121} cache_socache_dir_conf;
122
123/* Shared object cache and mutex */
124static const char * const cache_socache_id = "cache-socache";
125static apr_global_mutex_t *socache_mutex = NULL;
126
127/*
128 * Local static functions
129 */
130
131static apr_status_t read_array(request_rec *r, apr_array_header_t *arr,
132        unsigned char *buffer, apr_size_t buffer_len, apr_size_t *slider)
133{
134    apr_size_t val = *slider;
135
136    while (*slider < buffer_len) {
137        if (buffer[*slider] == '\r') {
138            if (val == *slider) {
139                (*slider)++;
140                return APR_SUCCESS;
141            }
142            *((const char **) apr_array_push(arr)) = apr_pstrndup(r->pool,
143                    (const char *) buffer + val, *slider - val);
144            (*slider)++;
145            if (buffer[*slider] == '\n') {
146                (*slider)++;
147            }
148            val = *slider;
149        }
150        else if (buffer[*slider] == '\0') {
151            (*slider)++;
152            return APR_SUCCESS;
153        }
154        else {
155            (*slider)++;
156        }
157    }
158
159    return APR_EOF;
160}
161
162static apr_status_t store_array(apr_array_header_t *arr, unsigned char *buffer,
163        apr_size_t buffer_len, apr_size_t *slider)
164{
165    int i, len;
166    const char **elts;
167
168    elts = (const char **) arr->elts;
169
170    for (i = 0; i < arr->nelts; i++) {
171        apr_size_t e_len = strlen(elts[i]);
172        if (e_len + 3 >= buffer_len - *slider) {
173            return APR_EOF;
174        }
175        len = apr_snprintf(buffer ? (char *) buffer + *slider : NULL,
176                buffer ? buffer_len - *slider : 0, "%s" CRLF, elts[i]);
177        *slider += len;
178    }
179    if (buffer) {
180        memcpy(buffer + *slider, CRLF, sizeof(CRLF) - 1);
181    }
182    *slider += sizeof(CRLF) - 1;
183
184    return APR_SUCCESS;
185}
186
187static apr_status_t read_table(cache_handle_t *handle, request_rec *r,
188        apr_table_t *table, unsigned char *buffer, apr_size_t buffer_len,
189        apr_size_t *slider)
190{
191    apr_size_t key = *slider, colon = 0, len = 0;
192    ;
193
194    while (*slider < buffer_len) {
195        if (buffer[*slider] == ':') {
196            if (!colon) {
197                colon = *slider;
198            }
199            (*slider)++;
200        }
201        else if (buffer[*slider] == '\r') {
202            len = colon;
203            if (key == *slider) {
204                (*slider)++;
205                if (buffer[*slider] == '\n') {
206                    (*slider)++;
207                }
208                return APR_SUCCESS;
209            }
210            if (!colon || buffer[colon++] != ':') {
211                ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02344)
212                        "Premature end of cache headers.");
213                return APR_EGENERAL;
214            }
215            while (apr_isspace(buffer[colon])) {
216                colon++;
217            }
218            apr_table_addn(table, apr_pstrndup(r->pool, (const char *) buffer
219                    + key, len - key), apr_pstrndup(r->pool,
220                    (const char *) buffer + colon, *slider - colon));
221            (*slider)++;
222            if (buffer[*slider] == '\n') {
223                (*slider)++;
224            }
225            key = *slider;
226            colon = 0;
227        }
228        else if (buffer[*slider] == '\0') {
229            (*slider)++;
230            return APR_SUCCESS;
231        }
232        else {
233            (*slider)++;
234        }
235    }
236
237    return APR_EOF;
238}
239
240static apr_status_t store_table(apr_table_t *table, unsigned char *buffer,
241        apr_size_t buffer_len, apr_size_t *slider)
242{
243    int i, len;
244    apr_table_entry_t *elts;
245
246    elts = (apr_table_entry_t *) apr_table_elts(table)->elts;
247    for (i = 0; i < apr_table_elts(table)->nelts; ++i) {
248        if (elts[i].key != NULL) {
249            apr_size_t key_len = strlen(elts[i].key);
250            apr_size_t val_len = strlen(elts[i].val);
251            if (key_len + val_len + 5 >= buffer_len - *slider) {
252                return APR_EOF;
253            }
254            len = apr_snprintf(buffer ? (char *) buffer + *slider : NULL,
255                    buffer ? buffer_len - *slider : 0, "%s: %s" CRLF,
256                    elts[i].key, elts[i].val);
257            *slider += len;
258        }
259    }
260    if (3 >= buffer_len - *slider) {
261        return APR_EOF;
262    }
263    if (buffer) {
264        memcpy(buffer + *slider, CRLF, sizeof(CRLF) - 1);
265    }
266    *slider += sizeof(CRLF) - 1;
267
268    return APR_SUCCESS;
269}
270
271static const char* regen_key(apr_pool_t *p, apr_table_t *headers,
272        apr_array_header_t *varray, const char *oldkey)
273{
274    struct iovec *iov;
275    int i, k;
276    int nvec;
277    const char *header;
278    const char **elts;
279
280    nvec = (varray->nelts * 2) + 1;
281    iov = apr_palloc(p, sizeof(struct iovec) * nvec);
282    elts = (const char **) varray->elts;
283
284    /* TODO:
285     *    - Handle multiple-value headers better. (sort them?)
286     *    - Handle Case in-sensitive Values better.
287     *        This isn't the end of the world, since it just lowers the cache
288     *        hit rate, but it would be nice to fix.
289     *
290     * The majority are case insenstive if they are values (encoding etc).
291     * Most of rfc2616 is case insensitive on header contents.
292     *
293     * So the better solution may be to identify headers which should be
294     * treated case-sensitive?
295     *  HTTP URI's (3.2.3) [host and scheme are insensitive]
296     *  HTTP method (5.1.1)
297     *  HTTP-date values (3.3.1)
298     *  3.7 Media Types [exerpt]
299     *     The type, subtype, and parameter attribute names are case-
300     *     insensitive. Parameter values might or might not be case-sensitive,
301     *     depending on the semantics of the parameter name.
302     *  4.20 Except [exerpt]
303     *     Comparison of expectation values is case-insensitive for unquoted
304     *     tokens (including the 100-continue token), and is case-sensitive for
305     *     quoted-string expectation-extensions.
306     */
307
308    for (i = 0, k = 0; i < varray->nelts; i++) {
309        header = apr_table_get(headers, elts[i]);
310        if (!header) {
311            header = "";
312        }
313        iov[k].iov_base = (char*) elts[i];
314        iov[k].iov_len = strlen(elts[i]);
315        k++;
316        iov[k].iov_base = (char*) header;
317        iov[k].iov_len = strlen(header);
318        k++;
319    }
320    iov[k].iov_base = (char*) oldkey;
321    iov[k].iov_len = strlen(oldkey);
322    k++;
323
324    return apr_pstrcatv(p, iov, k, NULL);
325}
326
327static int array_alphasort(const void *fn1, const void *fn2)
328{
329    return strcmp(*(char**) fn1, *(char**) fn2);
330}
331
332static void tokens_to_array(apr_pool_t *p, const char *data,
333        apr_array_header_t *arr)
334{
335    char *token;
336
337    while ((token = ap_get_list_item(p, &data)) != NULL) {
338        *((const char **) apr_array_push(arr)) = token;
339    }
340
341    /* Sort it so that "Vary: A, B" and "Vary: B, A" are stored the same. */
342    qsort((void *) arr->elts, arr->nelts, sizeof(char *), array_alphasort);
343}
344
345/*
346 * Hook and mod_cache callback functions
347 */
348static int create_entity(cache_handle_t *h, request_rec *r, const char *key,
349        apr_off_t len, apr_bucket_brigade *bb)
350{
351    cache_socache_dir_conf *dconf =
352            ap_get_module_config(r->per_dir_config, &cache_socache_module);
353    cache_socache_conf *conf = ap_get_module_config(r->server->module_config,
354            &cache_socache_module);
355    cache_object_t *obj;
356    cache_socache_object_t *sobj;
357    apr_size_t total;
358
359    if (conf->provider == NULL) {
360        return DECLINED;
361    }
362
363    /* we don't support caching of range requests (yet) */
364    /* TODO: but we could */
365    if (r->status == HTTP_PARTIAL_CONTENT) {
366        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02345)
367                "URL %s partial content response not cached",
368                key);
369        return DECLINED;
370    }
371
372    /*
373     * We have a chicken and egg problem. We don't know until we
374     * attempt to store_headers just how big the response will be
375     * and whether it will fit in the cache limits set. But we
376     * need to make a decision now as to whether we plan to try.
377     * If we make the wrong decision, we could prevent another
378     * cache implementation, such as cache_disk, from getting the
379     * opportunity to cache, and that would be unfortunate.
380     *
381     * In a series of tests, from cheapest to most expensive,
382     * decide whether or not to ignore this attempt to cache,
383     * with a small margin just to be sure.
384     */
385    if (len < 0) {
386        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02346)
387                "URL '%s' had no explicit size, ignoring", key);
388        return DECLINED;
389    }
390    if (len > dconf->max) {
391        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02347)
392                "URL '%s' body larger than limit, ignoring "
393                "(%" APR_OFF_T_FMT " > %" APR_OFF_T_FMT ")",
394                key, len, dconf->max);
395        return DECLINED;
396    }
397
398    /* estimate the total cached size, given current headers */
399    total = len + sizeof(cache_socache_info_t) + strlen(key);
400    if (APR_SUCCESS != store_table(r->headers_out, NULL, dconf->max, &total)
401            || APR_SUCCESS != store_table(r->headers_in, NULL, dconf->max,
402                    &total)) {
403        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02348)
404                "URL '%s' estimated headers size larger than limit, ignoring "
405                "(%" APR_SIZE_T_FMT " > %" APR_OFF_T_FMT ")",
406                key, total, dconf->max);
407        return DECLINED;
408    }
409
410    if (total >= dconf->max) {
411        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02349)
412                "URL '%s' body and headers larger than limit, ignoring "
413                "(%" APR_OFF_T_FMT " > %" APR_OFF_T_FMT ")",
414                key, len, dconf->max);
415        return DECLINED;
416    }
417
418    /* Allocate and initialize cache_object_t and cache_socache_object_t */
419    h->cache_obj = obj = apr_pcalloc(r->pool, sizeof(*obj));
420    obj->vobj = sobj = apr_pcalloc(r->pool, sizeof(*sobj));
421
422    obj->key = apr_pstrdup(r->pool, key);
423    sobj->key = obj->key;
424    sobj->name = obj->key;
425
426    return OK;
427}
428
429static int open_entity(cache_handle_t *h, request_rec *r, const char *key)
430{
431    cache_socache_dir_conf *dconf =
432            ap_get_module_config(r->per_dir_config, &cache_socache_module);
433    cache_socache_conf *conf = ap_get_module_config(r->server->module_config,
434            &cache_socache_module);
435    apr_uint32_t format;
436    apr_size_t slider;
437    unsigned int buffer_len;
438    const char *nkey;
439    apr_status_t rc;
440    cache_object_t *obj;
441    cache_info *info;
442    cache_socache_object_t *sobj;
443    apr_size_t len;
444
445    nkey = NULL;
446    h->cache_obj = NULL;
447
448    if (!conf->provider || !conf->provider->socache_instance) {
449        return DECLINED;
450    }
451
452    /* Create and init the cache object */
453    obj = apr_pcalloc(r->pool, sizeof(cache_object_t));
454    sobj = apr_pcalloc(r->pool, sizeof(cache_socache_object_t));
455
456    info = &(obj->info);
457
458    /* Create a temporary pool for the buffer, and destroy it if something
459     * goes wrong so we don't have large buffers of unused memory hanging
460     * about for the lifetime of the response.
461     */
462    apr_pool_create(&sobj->pool, r->pool);
463
464    sobj->buffer = apr_palloc(sobj->pool, dconf->max + 1);
465    sobj->buffer_len = dconf->max + 1;
466
467    /* attempt to retrieve the cached entry */
468    if (socache_mutex) {
469        apr_status_t status = apr_global_mutex_lock(socache_mutex);
470        if (status != APR_SUCCESS) {
471            ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02350)
472                    "could not acquire lock, ignoring: %s", obj->key);
473            apr_pool_destroy(sobj->pool);
474            sobj->pool = NULL;
475            return DECLINED;
476        }
477    }
478    buffer_len = sobj->buffer_len;
479    rc = conf->provider->socache_provider->retrieve(
480            conf->provider->socache_instance, r->server, (unsigned char *) key,
481            strlen(key), sobj->buffer, &buffer_len, r->pool);
482    if (socache_mutex) {
483        apr_status_t status = apr_global_mutex_unlock(socache_mutex);
484        if (status != APR_SUCCESS) {
485            ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02351)
486                    "could not release lock, ignoring: %s", obj->key);
487            apr_pool_destroy(sobj->pool);
488            sobj->pool = NULL;
489            return DECLINED;
490        }
491    }
492    if (rc != APR_SUCCESS) {
493        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rc, r, APLOGNO(02352)
494                "Key not found in cache: %s", key);
495        apr_pool_destroy(sobj->pool);
496        sobj->pool = NULL;
497        return DECLINED;
498    }
499    if (buffer_len >= sobj->buffer_len) {
500        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rc, r, APLOGNO(02353)
501                "Key found in cache but too big, ignoring: %s", key);
502        apr_pool_destroy(sobj->pool);
503        sobj->pool = NULL;
504        return DECLINED;
505    }
506
507    /* read the format from the cache file */
508    memcpy(&format, sobj->buffer, sizeof(format));
509    slider = sizeof(format);
510
511    if (format == CACHE_SOCACHE_VARY_FORMAT_VERSION) {
512        apr_array_header_t* varray;
513        apr_time_t expire;
514
515        memcpy(&expire, sobj->buffer + slider, sizeof(expire));
516        slider += sizeof(expire);
517
518        varray = apr_array_make(r->pool, 5, sizeof(char*));
519        rc = read_array(r, varray, sobj->buffer, buffer_len, &slider);
520        if (rc != APR_SUCCESS) {
521            ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r, APLOGNO(02354)
522                    "Cannot parse vary entry for key: %s", key);
523            apr_pool_destroy(sobj->pool);
524            sobj->pool = NULL;
525            return DECLINED;
526        }
527
528        nkey = regen_key(r->pool, r->headers_in, varray, key);
529
530        /* attempt to retrieve the cached entry */
531        if (socache_mutex) {
532            apr_status_t status = apr_global_mutex_lock(socache_mutex);
533            if (status != APR_SUCCESS) {
534                ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02355)
535                        "could not acquire lock, ignoring: %s", obj->key);
536                apr_pool_destroy(sobj->pool);
537                sobj->pool = NULL;
538                return DECLINED;
539            }
540        }
541        buffer_len = sobj->buffer_len;
542        rc = conf->provider->socache_provider->retrieve(
543                conf->provider->socache_instance, r->server,
544                (unsigned char *) nkey, strlen(nkey), sobj->buffer,
545                &buffer_len, r->pool);
546        if (socache_mutex) {
547            apr_status_t status = apr_global_mutex_unlock(socache_mutex);
548            if (status != APR_SUCCESS) {
549                ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02356)
550                        "could not release lock, ignoring: %s", obj->key);
551                apr_pool_destroy(sobj->pool);
552                sobj->pool = NULL;
553                return DECLINED;
554            }
555        }
556        if (rc != APR_SUCCESS) {
557            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rc, r, APLOGNO(02357)
558                    "Key not found in cache: %s", key);
559            apr_pool_destroy(sobj->pool);
560            sobj->pool = NULL;
561            return DECLINED;
562        }
563        if (buffer_len >= sobj->buffer_len) {
564            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rc, r, APLOGNO(02358)
565                    "Key found in cache but too big, ignoring: %s", key);
566            goto fail;
567        }
568
569    }
570    else if (format != CACHE_SOCACHE_DISK_FORMAT_VERSION) {
571        ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02359)
572                "Key '%s' found in cache has version %d, expected %d, ignoring",
573                key, format, CACHE_SOCACHE_DISK_FORMAT_VERSION);
574        goto fail;
575    }
576    else {
577        nkey = key;
578    }
579
580    obj->key = nkey;
581    sobj->key = nkey;
582    sobj->name = key;
583
584    if (buffer_len >= sizeof(cache_socache_info_t)) {
585        memcpy(&sobj->socache_info, sobj->buffer, sizeof(cache_socache_info_t));
586    }
587    else {
588        ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r, APLOGNO(02360)
589                "Cache entry for key '%s' too short, removing", nkey);
590        goto fail;
591    }
592    slider = sizeof(cache_socache_info_t);
593
594    /* Store it away so we can get it later. */
595    info->status = sobj->socache_info.status;
596    info->date = sobj->socache_info.date;
597    info->expire = sobj->socache_info.expire;
598    info->request_time = sobj->socache_info.request_time;
599    info->response_time = sobj->socache_info.response_time;
600
601    memcpy(&info->control, &sobj->socache_info.control, sizeof(cache_control_t));
602
603    if (sobj->socache_info.name_len <= buffer_len - slider) {
604        if (strncmp((const char *) sobj->buffer + slider, sobj->name,
605                sobj->socache_info.name_len)) {
606            ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r, APLOGNO(02361)
607                    "Cache entry for key '%s' URL mismatch, ignoring", nkey);
608            apr_pool_destroy(sobj->pool);
609            sobj->pool = NULL;
610            return DECLINED;
611        }
612        slider += sobj->socache_info.name_len;
613    }
614    else {
615        ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r, APLOGNO(02362)
616                "Cache entry for key '%s' too short, removing", nkey);
617        goto fail;
618    }
619
620    /* Is this a cached HEAD request? */
621    if (sobj->socache_info.header_only && !r->header_only) {
622        ap_log_rerror(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r, APLOGNO(02363)
623                "HEAD request cached, non-HEAD requested, ignoring: %s",
624                sobj->key);
625        apr_pool_destroy(sobj->pool);
626        sobj->pool = NULL;
627        return DECLINED;
628    }
629
630    h->req_hdrs = apr_table_make(r->pool, 20);
631    h->resp_hdrs = apr_table_make(r->pool, 20);
632
633    /* Call routine to read the header lines/status line */
634    if (APR_SUCCESS != read_table(h, r, h->resp_hdrs, sobj->buffer, buffer_len,
635            &slider)) {
636        ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r, APLOGNO(02364)
637                "Cache entry for key '%s' response headers unreadable, removing", nkey);
638        goto fail;
639    }
640    if (APR_SUCCESS != read_table(h, r, h->req_hdrs, sobj->buffer, buffer_len,
641            &slider)) {
642        ap_log_rerror(APLOG_MARK, APLOG_ERR, rc, r, APLOGNO(02365)
643                "Cache entry for key '%s' request headers unreadable, removing", nkey);
644        goto fail;
645    }
646
647    /* Retrieve the body if we have one */
648    sobj->body = apr_brigade_create(r->pool, r->connection->bucket_alloc);
649    len = buffer_len - slider;
650
651    /*
652     *  Optimisation: if the body is small, we want to make a
653     *  copy of the body and free the temporary pool, as we
654     *  don't want large blocks of unused memory hanging around
655     *  to the end of the response. In contrast, if the body is
656     *  large, we would rather leave the body where it is in the
657     *  temporary pool, and save ourselves the copy.
658     */
659    if (len * 2 > dconf->max) {
660        apr_bucket *e;
661
662        /* large - use the brigade as is, we're done */
663        e = apr_bucket_immortal_create((const char *) sobj->buffer + slider,
664                len, r->connection->bucket_alloc);
665
666        APR_BRIGADE_INSERT_TAIL(sobj->body, e);
667    }
668    else {
669
670        /* small - make a copy of the data... */
671        apr_brigade_write(sobj->body, NULL, NULL, (const char *) sobj->buffer
672                + slider, len);
673
674        /* ...and get rid of the large memory buffer */
675        apr_pool_destroy(sobj->pool);
676        sobj->pool = NULL;
677    }
678
679    /* make the configuration stick */
680    h->cache_obj = obj;
681    obj->vobj = sobj;
682
683    return OK;
684
685fail:
686    if (socache_mutex) {
687        apr_status_t status = apr_global_mutex_lock(socache_mutex);
688        if (status != APR_SUCCESS) {
689            ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02366)
690                    "could not acquire lock, ignoring: %s", obj->key);
691            apr_pool_destroy(sobj->pool);
692            sobj->pool = NULL;
693            return DECLINED;
694        }
695    }
696    conf->provider->socache_provider->remove(
697            conf->provider->socache_instance, r->server,
698            (unsigned char *) nkey, strlen(nkey), r->pool);
699    if (socache_mutex) {
700        apr_status_t status = apr_global_mutex_unlock(socache_mutex);
701        if (status != APR_SUCCESS) {
702            ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02367)
703                    "could not release lock, ignoring: %s", obj->key);
704        }
705    }
706    apr_pool_destroy(sobj->pool);
707    sobj->pool = NULL;
708    return DECLINED;
709}
710
711static int remove_entity(cache_handle_t *h)
712{
713    /* Null out the cache object pointer so next time we start from scratch  */
714    h->cache_obj = NULL;
715    return OK;
716}
717
718static int remove_url(cache_handle_t *h, request_rec *r)
719{
720    cache_socache_conf *conf = ap_get_module_config(r->server->module_config,
721            &cache_socache_module);
722    cache_socache_object_t *sobj;
723
724    sobj = (cache_socache_object_t *) h->cache_obj->vobj;
725    if (!sobj) {
726        return DECLINED;
727    }
728
729    /* Remove the key from the cache */
730    if (socache_mutex) {
731        apr_status_t status = apr_global_mutex_lock(socache_mutex);
732        if (status != APR_SUCCESS) {
733            ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02368)
734                    "could not acquire lock, ignoring: %s", sobj->key);
735            apr_pool_destroy(sobj->pool);
736            sobj->pool = NULL;
737            return DECLINED;
738        }
739    }
740    conf->provider->socache_provider->remove(conf->provider->socache_instance,
741            r->server, (unsigned char *) sobj->key, strlen(sobj->key), r->pool);
742    if (socache_mutex) {
743        apr_status_t status = apr_global_mutex_unlock(socache_mutex);
744        if (status != APR_SUCCESS) {
745            ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02369)
746                    "could not release lock, ignoring: %s", sobj->key);
747            apr_pool_destroy(sobj->pool);
748            sobj->pool = NULL;
749            return DECLINED;
750        }
751    }
752
753    return OK;
754}
755
756static apr_status_t recall_headers(cache_handle_t *h, request_rec *r)
757{
758    /* we recalled the headers during open_entity, so do nothing */
759    return APR_SUCCESS;
760}
761
762static apr_status_t recall_body(cache_handle_t *h, apr_pool_t *p,
763        apr_bucket_brigade *bb)
764{
765    cache_socache_object_t *sobj = (cache_socache_object_t*) h->cache_obj->vobj;
766    apr_bucket *e;
767
768    e = APR_BRIGADE_FIRST(sobj->body);
769
770    if (e != APR_BRIGADE_SENTINEL(sobj->body)) {
771        APR_BUCKET_REMOVE(e);
772        APR_BRIGADE_INSERT_TAIL(bb, e);
773    }
774
775    return APR_SUCCESS;
776}
777
778static apr_status_t store_headers(cache_handle_t *h, request_rec *r,
779        cache_info *info)
780{
781    cache_socache_dir_conf *dconf =
782            ap_get_module_config(r->per_dir_config, &cache_socache_module);
783    cache_socache_conf *conf = ap_get_module_config(r->server->module_config,
784            &cache_socache_module);
785    apr_size_t slider;
786    apr_status_t rv;
787    cache_object_t *obj = h->cache_obj;
788    cache_socache_object_t *sobj = (cache_socache_object_t*) obj->vobj;
789    cache_socache_info_t *socache_info;
790
791    memcpy(&h->cache_obj->info, info, sizeof(cache_info));
792
793    if (r->headers_out) {
794        sobj->headers_out = ap_cache_cacheable_headers_out(r);
795    }
796
797    if (r->headers_in) {
798        sobj->headers_in = ap_cache_cacheable_headers_in(r);
799    }
800
801    sobj->expire
802            = obj->info.expire > r->request_time + dconf->maxtime ? r->request_time
803                    + dconf->maxtime
804                    : obj->info.expire + dconf->mintime;
805
806    apr_pool_create(&sobj->pool, r->pool);
807
808    sobj->buffer = apr_palloc(sobj->pool, dconf->max);
809    sobj->buffer_len = dconf->max;
810    socache_info = (cache_socache_info_t *) sobj->buffer;
811
812    if (sobj->headers_out) {
813        const char *vary;
814
815        vary = apr_table_get(sobj->headers_out, "Vary");
816
817        if (vary) {
818            apr_array_header_t* varray;
819            apr_uint32_t format = CACHE_SOCACHE_VARY_FORMAT_VERSION;
820
821            memcpy(sobj->buffer, &format, sizeof(format));
822            slider = sizeof(format);
823
824            memcpy(sobj->buffer + slider, &obj->info.expire,
825                    sizeof(obj->info.expire));
826            slider += sizeof(obj->info.expire);
827
828            varray = apr_array_make(r->pool, 6, sizeof(char*));
829            tokens_to_array(r->pool, vary, varray);
830
831            if (APR_SUCCESS != (rv = store_array(varray, sobj->buffer,
832                    sobj->buffer_len, &slider))) {
833                ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02370)
834                        "buffer too small for Vary array, caching aborted: %s",
835                        obj->key);
836                apr_pool_destroy(sobj->pool);
837                sobj->pool = NULL;
838                return rv;
839            }
840            if (socache_mutex) {
841                apr_status_t status = apr_global_mutex_lock(socache_mutex);
842                if (status != APR_SUCCESS) {
843                    ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02371)
844                            "could not acquire lock, ignoring: %s", obj->key);
845                    apr_pool_destroy(sobj->pool);
846                    sobj->pool = NULL;
847                    return status;
848                }
849            }
850            rv = conf->provider->socache_provider->store(
851                    conf->provider->socache_instance, r->server,
852                    (unsigned char *) obj->key, strlen(obj->key), sobj->expire,
853                    (unsigned char *) sobj->buffer, (unsigned int) slider,
854                    sobj->pool);
855            if (socache_mutex) {
856                apr_status_t status = apr_global_mutex_unlock(socache_mutex);
857                if (status != APR_SUCCESS) {
858                    ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02372)
859                            "could not release lock, ignoring: %s", obj->key);
860                }
861            }
862            if (rv != APR_SUCCESS) {
863                ap_log_rerror(APLOG_MARK, APLOG_DEBUG, rv, r, APLOGNO(02373)
864                        "Vary not written to cache, ignoring: %s", obj->key);
865                apr_pool_destroy(sobj->pool);
866                sobj->pool = NULL;
867                return rv;
868            }
869
870            obj->key = sobj->key = regen_key(r->pool, sobj->headers_in, varray,
871                    sobj->name);
872        }
873    }
874
875    socache_info->format = CACHE_SOCACHE_DISK_FORMAT_VERSION;
876    socache_info->date = obj->info.date;
877    socache_info->expire = obj->info.expire;
878    socache_info->entity_version = sobj->socache_info.entity_version++;
879    socache_info->request_time = obj->info.request_time;
880    socache_info->response_time = obj->info.response_time;
881    socache_info->status = obj->info.status;
882
883    if (r->header_only && r->status != HTTP_NOT_MODIFIED) {
884        socache_info->header_only = 1;
885    }
886    else {
887        socache_info->header_only = sobj->socache_info.header_only;
888    }
889
890    socache_info->name_len = strlen(sobj->name);
891
892    memcpy(&socache_info->control, &obj->info.control, sizeof(cache_control_t));
893    slider = sizeof(cache_socache_info_t);
894
895    if (slider + socache_info->name_len >= sobj->buffer_len) {
896        ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02374)
897                "cache buffer too small for name: %s",
898                sobj->name);
899        apr_pool_destroy(sobj->pool);
900        sobj->pool = NULL;
901        return APR_EGENERAL;
902    }
903    memcpy(sobj->buffer + slider, sobj->name, socache_info->name_len);
904    slider += socache_info->name_len;
905
906    if (sobj->headers_out) {
907        if (APR_SUCCESS != store_table(sobj->headers_out, sobj->buffer,
908                sobj->buffer_len, &slider)) {
909            ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02375)
910                    "out-headers didn't fit in buffer: %s", sobj->name);
911            apr_pool_destroy(sobj->pool);
912            sobj->pool = NULL;
913            return APR_EGENERAL;
914        }
915    }
916
917    /* Parse the vary header and dump those fields from the headers_in. */
918    /* TODO: Make call to the same thing cache_select calls to crack Vary. */
919    if (sobj->headers_in) {
920        if (APR_SUCCESS != store_table(sobj->headers_in, sobj->buffer,
921                sobj->buffer_len, &slider)) {
922            ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, r, APLOGNO(02376)
923                    "in-headers didn't fit in buffer %s",
924                    sobj->key);
925            apr_pool_destroy(sobj->pool);
926            sobj->pool = NULL;
927            return APR_EGENERAL;
928        }
929    }
930
931    sobj->body_offset = slider;
932
933    return APR_SUCCESS;
934}
935
936static apr_status_t store_body(cache_handle_t *h, request_rec *r,
937        apr_bucket_brigade *in, apr_bucket_brigade *out)
938{
939    apr_bucket *e;
940    apr_status_t rv = APR_SUCCESS;
941    cache_socache_object_t *sobj =
942            (cache_socache_object_t *) h->cache_obj->vobj;
943    cache_socache_dir_conf *dconf =
944            ap_get_module_config(r->per_dir_config, &cache_socache_module);
945    int seen_eos = 0;
946
947    if (!sobj->offset) {
948        sobj->offset = dconf->readsize;
949    }
950    if (!sobj->timeout && dconf->readtime) {
951        sobj->timeout = apr_time_now() + dconf->readtime;
952    }
953
954    if (!sobj->newbody) {
955        if (sobj->body) {
956            apr_brigade_cleanup(sobj->body);
957        }
958        else {
959            sobj->body = apr_brigade_create(r->pool,
960                    r->connection->bucket_alloc);
961        }
962        sobj->newbody = 1;
963    }
964    if (sobj->offset) {
965        apr_brigade_partition(in, sobj->offset, &e);
966    }
967
968    while (APR_SUCCESS == rv && !APR_BRIGADE_EMPTY(in)) {
969        const char *str;
970        apr_size_t length;
971
972        e = APR_BRIGADE_FIRST(in);
973
974        /* are we done completely? if so, pass any trailing buckets right through */
975        if (sobj->done || !sobj->pool) {
976            APR_BUCKET_REMOVE(e);
977            APR_BRIGADE_INSERT_TAIL(out, e);
978            continue;
979        }
980
981        /* have we seen eos yet? */
982        if (APR_BUCKET_IS_EOS(e)) {
983            seen_eos = 1;
984            sobj->done = 1;
985            APR_BUCKET_REMOVE(e);
986            APR_BRIGADE_INSERT_TAIL(out, e);
987            break;
988        }
989
990        /* honour flush buckets, we'll get called again */
991        if (APR_BUCKET_IS_FLUSH(e)) {
992            APR_BUCKET_REMOVE(e);
993            APR_BRIGADE_INSERT_TAIL(out, e);
994            break;
995        }
996
997        /* metadata buckets are preserved as is */
998        if (APR_BUCKET_IS_METADATA(e)) {
999            APR_BUCKET_REMOVE(e);
1000            APR_BRIGADE_INSERT_TAIL(out, e);
1001            continue;
1002        }
1003
1004        /* read the bucket, write to the cache */
1005        rv = apr_bucket_read(e, &str, &length, APR_BLOCK_READ);
1006        APR_BUCKET_REMOVE(e);
1007        APR_BRIGADE_INSERT_TAIL(out, e);
1008        if (rv != APR_SUCCESS) {
1009            ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02377)
1010                    "Error when reading bucket for URL %s",
1011                    h->cache_obj->key);
1012            /* Remove the intermediate cache file and return non-APR_SUCCESS */
1013            apr_pool_destroy(sobj->pool);
1014            sobj->pool = NULL;
1015            return rv;
1016        }
1017
1018        /* don't write empty buckets to the cache */
1019        if (!length) {
1020            continue;
1021        }
1022
1023        sobj->file_size += length;
1024        if (sobj->file_size >= sobj->buffer_len - sobj->body_offset) {
1025            ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02378)
1026                    "URL %s failed the buffer size check "
1027                    "(%" APR_OFF_T_FMT ">=%" APR_SIZE_T_FMT ")",
1028                    h->cache_obj->key, sobj->file_size, sobj->buffer_len - sobj->body_offset);
1029            apr_pool_destroy(sobj->pool);
1030            sobj->pool = NULL;
1031            return APR_EGENERAL;
1032        }
1033
1034        rv = apr_bucket_copy(e, &e);
1035        if (rv != APR_SUCCESS) {
1036            ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(02379)
1037                    "Error when copying bucket for URL %s",
1038                    h->cache_obj->key);
1039            apr_pool_destroy(sobj->pool);
1040            sobj->pool = NULL;
1041            return rv;
1042        }
1043        APR_BRIGADE_INSERT_TAIL(sobj->body, e);
1044
1045        /* have we reached the limit of how much we're prepared to write in one
1046         * go? If so, leave, we'll get called again. This prevents us from trying
1047         * to swallow too much data at once, or taking so long to write the data
1048         * the client times out.
1049         */
1050        sobj->offset -= length;
1051        if (sobj->offset <= 0) {
1052            sobj->offset = 0;
1053            break;
1054        }
1055        if ((dconf->readtime && apr_time_now() > sobj->timeout)) {
1056            sobj->timeout = 0;
1057            break;
1058        }
1059
1060    }
1061
1062    /* Was this the final bucket? If yes, perform sanity checks.
1063     */
1064    if (seen_eos) {
1065        const char *cl_header = apr_table_get(r->headers_out, "Content-Length");
1066
1067        if (r->connection->aborted || r->no_cache) {
1068            ap_log_rerror(APLOG_MARK, APLOG_INFO, 0, r, APLOGNO(02380)
1069                    "Discarding body for URL %s "
1070                    "because connection has been aborted.",
1071                    h->cache_obj->key);
1072            apr_pool_destroy(sobj->pool);
1073            sobj->pool = NULL;
1074            return APR_EGENERAL;
1075        }
1076        if (cl_header) {
1077            apr_int64_t cl = apr_atoi64(cl_header);
1078            if ((errno == 0) && (sobj->file_size != cl)) {
1079                ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02381)
1080                        "URL %s didn't receive complete response, not caching",
1081                        h->cache_obj->key);
1082                apr_pool_destroy(sobj->pool);
1083                sobj->pool = NULL;
1084                return APR_EGENERAL;
1085            }
1086        }
1087
1088        /* All checks were fine, we're good to go when the commit comes */
1089
1090    }
1091
1092    return APR_SUCCESS;
1093}
1094
1095static apr_status_t commit_entity(cache_handle_t *h, request_rec *r)
1096{
1097    cache_socache_conf *conf = ap_get_module_config(r->server->module_config,
1098            &cache_socache_module);
1099    cache_object_t *obj = h->cache_obj;
1100    cache_socache_object_t *sobj = (cache_socache_object_t *) obj->vobj;
1101    apr_status_t rv;
1102    apr_size_t len;
1103
1104    /* flatten the body into the buffer */
1105    len = sobj->buffer_len - sobj->body_offset;
1106    rv = apr_brigade_flatten(sobj->body, (char *) sobj->buffer
1107            + sobj->body_offset, &len);
1108    if (APR_SUCCESS != rv) {
1109        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(02382)
1110                "could not flatten brigade, not caching: %s",
1111                sobj->key);
1112        goto fail;
1113    }
1114    if (len >= sobj->buffer_len - sobj->body_offset) {
1115        ap_log_rerror(APLOG_MARK, APLOG_ERR, rv, r, APLOGNO(02383)
1116                "body too big for the cache buffer, not caching: %s",
1117                h->cache_obj->key);
1118        goto fail;
1119    }
1120
1121    if (socache_mutex) {
1122        apr_status_t status = apr_global_mutex_lock(socache_mutex);
1123        if (status != APR_SUCCESS) {
1124            ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02384)
1125                    "could not acquire lock, ignoring: %s", obj->key);
1126            apr_pool_destroy(sobj->pool);
1127            sobj->pool = NULL;
1128            return rv;
1129        }
1130    }
1131    rv = conf->provider->socache_provider->store(
1132            conf->provider->socache_instance, r->server,
1133            (unsigned char *) sobj->key, strlen(sobj->key), sobj->expire,
1134            sobj->buffer, (unsigned int) sobj->body_offset + len, sobj->pool);
1135    if (socache_mutex) {
1136        apr_status_t status = apr_global_mutex_unlock(socache_mutex);
1137        if (status != APR_SUCCESS) {
1138            ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02385)
1139                    "could not release lock, ignoring: %s", obj->key);
1140            apr_pool_destroy(sobj->pool);
1141            sobj->pool = NULL;
1142            return DECLINED;
1143        }
1144    }
1145    if (rv != APR_SUCCESS) {
1146        ap_log_rerror(APLOG_MARK, APLOG_WARNING, rv, r, APLOGNO(02386)
1147                "could not write to cache, ignoring: %s", sobj->key);
1148        goto fail;
1149    }
1150
1151    ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(02387)
1152            "commit_entity: Headers and body for URL %s cached for maximum of %d seconds.",
1153            sobj->name, (apr_uint32_t)apr_time_sec(sobj->expire - r->request_time));
1154
1155    apr_pool_destroy(sobj->pool);
1156    sobj->pool = NULL;
1157
1158    return APR_SUCCESS;
1159
1160fail:
1161    /* For safety, remove any existing entry on failure, just in case it could not
1162     * be revalidated successfully.
1163     */
1164    if (socache_mutex) {
1165        apr_status_t status = apr_global_mutex_lock(socache_mutex);
1166        if (status != APR_SUCCESS) {
1167            ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02388)
1168                    "could not acquire lock, ignoring: %s", obj->key);
1169            apr_pool_destroy(sobj->pool);
1170            sobj->pool = NULL;
1171            return rv;
1172        }
1173    }
1174    conf->provider->socache_provider->remove(conf->provider->socache_instance,
1175            r->server, (unsigned char *) sobj->key, strlen(sobj->key), r->pool);
1176    if (socache_mutex) {
1177        apr_status_t status = apr_global_mutex_unlock(socache_mutex);
1178        if (status != APR_SUCCESS) {
1179            ap_log_rerror(APLOG_MARK, APLOG_ERR, status, r, APLOGNO(02389)
1180                    "could not release lock, ignoring: %s", obj->key);
1181        }
1182    }
1183
1184    apr_pool_destroy(sobj->pool);
1185    sobj->pool = NULL;
1186    return rv;
1187}
1188
1189static apr_status_t invalidate_entity(cache_handle_t *h, request_rec *r)
1190{
1191    /* mark the entity as invalidated */
1192    h->cache_obj->info.control.invalidated = 1;
1193
1194    return commit_entity(h, r);
1195}
1196
1197static void *create_dir_config(apr_pool_t *p, char *dummy)
1198{
1199    cache_socache_dir_conf *dconf =
1200            apr_pcalloc(p, sizeof(cache_socache_dir_conf));
1201
1202    dconf->max = DEFAULT_MAX_FILE_SIZE;
1203    dconf->maxtime = apr_time_from_sec(DEFAULT_MAXTIME);
1204    dconf->mintime = apr_time_from_sec(DEFAULT_MINTIME);
1205    dconf->readsize = DEFAULT_READSIZE;
1206    dconf->readtime = DEFAULT_READTIME;
1207
1208    return dconf;
1209}
1210
1211static void *merge_dir_config(apr_pool_t *p, void *basev, void *addv)
1212{
1213    cache_socache_dir_conf
1214            *new =
1215                    (cache_socache_dir_conf *) apr_pcalloc(p, sizeof(cache_socache_dir_conf));
1216    cache_socache_dir_conf *add = (cache_socache_dir_conf *) addv;
1217    cache_socache_dir_conf *base = (cache_socache_dir_conf *) basev;
1218
1219    new->max = (add->max_set == 0) ? base->max : add->max;
1220    new->max_set = add->max_set || base->max_set;
1221    new->maxtime = (add->maxtime_set == 0) ? base->maxtime : add->maxtime;
1222    new->maxtime_set = add->maxtime_set || base->maxtime_set;
1223    new->mintime = (add->mintime_set == 0) ? base->mintime : add->mintime;
1224    new->mintime_set = add->mintime_set || base->mintime_set;
1225    new->readsize = (add->readsize_set == 0) ? base->readsize : add->readsize;
1226    new->readsize_set = add->readsize_set || base->readsize_set;
1227    new->readtime = (add->readtime_set == 0) ? base->readtime : add->readtime;
1228    new->readtime_set = add->readtime_set || base->readtime_set;
1229
1230    return new;
1231}
1232
1233static void *create_config(apr_pool_t *p, server_rec *s)
1234{
1235    cache_socache_conf *conf = apr_pcalloc(p, sizeof(cache_socache_conf));
1236
1237    return conf;
1238}
1239
1240static void *merge_config(apr_pool_t *p, void *basev, void *overridesv)
1241{
1242    cache_socache_conf *ps = apr_pcalloc(p, sizeof(cache_socache_conf));
1243    cache_socache_conf *base = (cache_socache_conf *) basev;
1244    cache_socache_conf *overrides = (cache_socache_conf *) overridesv;
1245
1246    ps = overrides ? overrides : base;
1247
1248    return ps;
1249}
1250
1251/*
1252 * mod_cache_socache configuration directives handlers.
1253 */
1254static const char *set_cache_socache(cmd_parms *cmd, void *in_struct_ptr,
1255        const char *arg)
1256{
1257    cache_socache_conf *conf = ap_get_module_config(cmd->server->module_config,
1258            &cache_socache_module);
1259    cache_socache_provider_conf *provider = conf->provider
1260            = apr_pcalloc(cmd->pool, sizeof(cache_socache_provider_conf));
1261
1262    const char *err = NULL, *sep, *name;
1263
1264    /* Argument is of form 'name:args' or just 'name'. */
1265    sep = ap_strchr_c(arg, ':');
1266    if (sep) {
1267        name = apr_pstrmemdup(cmd->pool, arg, sep - arg);
1268        sep++;
1269        provider->args = sep;
1270    }
1271    else {
1272        name = arg;
1273    }
1274
1275    provider->socache_provider = ap_lookup_provider(AP_SOCACHE_PROVIDER_GROUP,
1276            name, AP_SOCACHE_PROVIDER_VERSION);
1277    if (provider->socache_provider == NULL) {
1278        err = apr_psprintf(cmd->pool,
1279                "Unknown socache provider '%s'. Maybe you need "
1280                    "to load the appropriate socache module "
1281                    "(mod_socache_%s?)", name, name);
1282    }
1283    return err;
1284}
1285
1286static const char *set_cache_max(cmd_parms *parms, void *in_struct_ptr,
1287        const char *arg)
1288{
1289    cache_socache_dir_conf *dconf = (cache_socache_dir_conf *) in_struct_ptr;
1290
1291    if (apr_strtoff(&dconf->max, arg, NULL, 10) != APR_SUCCESS || dconf->max
1292            < 1024) {
1293        return "CacheSocacheMaxSize argument must be a integer representing the max size of a cached entry (headers and body), at least 1024";
1294    }
1295    dconf->max_set = 1;
1296    return NULL;
1297}
1298
1299static const char *set_cache_maxtime(cmd_parms *parms, void *in_struct_ptr,
1300        const char *arg)
1301{
1302    cache_socache_dir_conf *dconf = (cache_socache_dir_conf *) in_struct_ptr;
1303    apr_off_t seconds;
1304
1305    if (apr_strtoff(&seconds, arg, NULL, 10) != APR_SUCCESS || seconds < 0) {
1306        return "CacheSocacheMaxTime argument must be the maximum amount of time in seconds to cache an entry.";
1307    }
1308    dconf->maxtime = apr_time_from_sec(seconds);
1309    dconf->maxtime_set = 1;
1310    return NULL;
1311}
1312
1313static const char *set_cache_mintime(cmd_parms *parms, void *in_struct_ptr,
1314        const char *arg)
1315{
1316    cache_socache_dir_conf *dconf = (cache_socache_dir_conf *) in_struct_ptr;
1317    apr_off_t seconds;
1318
1319    if (apr_strtoff(&seconds, arg, NULL, 10) != APR_SUCCESS || seconds < 0) {
1320        return "CacheSocacheMinTime argument must be the minimum amount of time in seconds to cache an entry.";
1321    }
1322    dconf->mintime = apr_time_from_sec(seconds);
1323    dconf->mintime_set = 1;
1324    return NULL;
1325}
1326
1327static const char *set_cache_readsize(cmd_parms *parms, void *in_struct_ptr,
1328        const char *arg)
1329{
1330    cache_socache_dir_conf *dconf = (cache_socache_dir_conf *) in_struct_ptr;
1331
1332    if (apr_strtoff(&dconf->readsize, arg, NULL, 10) != APR_SUCCESS
1333            || dconf->readsize < 0) {
1334        return "CacheSocacheReadSize argument must be a non-negative integer representing the max amount of data to cache in go.";
1335    }
1336    dconf->readsize_set = 1;
1337    return NULL;
1338}
1339
1340static const char *set_cache_readtime(cmd_parms *parms, void *in_struct_ptr,
1341        const char *arg)
1342{
1343    cache_socache_dir_conf *dconf = (cache_socache_dir_conf *) in_struct_ptr;
1344    apr_off_t milliseconds;
1345
1346    if (apr_strtoff(&milliseconds, arg, NULL, 10) != APR_SUCCESS
1347            || milliseconds < 0) {
1348        return "CacheSocacheReadTime argument must be a non-negative integer representing the max amount of time taken to cache in go.";
1349    }
1350    dconf->readtime = apr_time_from_msec(milliseconds);
1351    dconf->readtime_set = 1;
1352    return NULL;
1353}
1354
1355static apr_status_t remove_lock(void *data)
1356{
1357    if (socache_mutex) {
1358        apr_global_mutex_destroy(socache_mutex);
1359        socache_mutex = NULL;
1360    }
1361    return APR_SUCCESS;
1362}
1363
1364static apr_status_t destroy_cache(void *data)
1365{
1366    server_rec *s = data;
1367    cache_socache_conf *conf =
1368            ap_get_module_config(s->module_config, &cache_socache_module);
1369    if (conf->provider && conf->provider->socache_instance) {
1370        conf->provider->socache_provider->destroy(
1371                conf->provider->socache_instance, s);
1372        conf->provider->socache_instance = NULL;
1373    }
1374    return APR_SUCCESS;
1375}
1376
1377static int socache_precfg(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t *ptmp)
1378{
1379    apr_status_t rv = ap_mutex_register(pconf, cache_socache_id, NULL,
1380            APR_LOCK_DEFAULT, 0);
1381    if (rv != APR_SUCCESS) {
1382        ap_log_perror(APLOG_MARK, APLOG_CRIT, rv, plog, APLOGNO(02390)
1383        "failed to register %s mutex", cache_socache_id);
1384        return 500; /* An HTTP status would be a misnomer! */
1385    }
1386    return OK;
1387}
1388
1389static int socache_post_config(apr_pool_t *pconf, apr_pool_t *plog,
1390        apr_pool_t *ptmp, server_rec *base_server)
1391{
1392    server_rec *s;
1393    apr_status_t rv;
1394    const char *errmsg;
1395    static struct ap_socache_hints socache_hints =
1396    { 64, 32, 60000000 };
1397
1398    for (s = base_server; s; s = s->next) {
1399        cache_socache_conf *conf =
1400                ap_get_module_config(s->module_config, &cache_socache_module);
1401
1402        if (!conf->provider) {
1403            continue;
1404        }
1405
1406        if (!socache_mutex && conf->provider->socache_provider->flags
1407                & AP_SOCACHE_FLAG_NOTMPSAFE) {
1408
1409            rv = ap_global_mutex_create(&socache_mutex, NULL, cache_socache_id,
1410                    NULL, s, pconf, 0);
1411            if (rv != APR_SUCCESS) {
1412                ap_log_perror(APLOG_MARK, APLOG_CRIT, rv, plog, APLOGNO(02391)
1413                "failed to create %s mutex", cache_socache_id);
1414                return 500; /* An HTTP status would be a misnomer! */
1415            }
1416            apr_pool_cleanup_register(pconf, NULL, remove_lock,
1417                    apr_pool_cleanup_null);
1418        }
1419
1420        errmsg = conf->provider->socache_provider->create(
1421                &conf->provider->socache_instance, conf->provider->args, ptmp,
1422                pconf);
1423        if (errmsg) {
1424            ap_log_perror(APLOG_MARK, APLOG_CRIT, 0, plog,
1425                    APLOGNO(02392) "%s", errmsg);
1426            return 500; /* An HTTP status would be a misnomer! */
1427        }
1428
1429        rv = conf->provider->socache_provider->init(
1430                conf->provider->socache_instance, cache_socache_id,
1431                &socache_hints, s, pconf);
1432        if (rv != APR_SUCCESS) {
1433            ap_log_perror(APLOG_MARK, APLOG_CRIT, rv, plog, APLOGNO(02393)
1434            "failed to initialise %s cache", cache_socache_id);
1435            return 500; /* An HTTP status would be a misnomer! */
1436        }
1437        apr_pool_cleanup_register(pconf, (void *) s, destroy_cache,
1438                apr_pool_cleanup_null);
1439
1440    }
1441
1442    return OK;
1443}
1444
1445static void socache_child_init(apr_pool_t *p, server_rec *s)
1446{
1447    const char *lock;
1448    apr_status_t rv;
1449    if (!socache_mutex) {
1450        return; /* don't waste the overhead of creating mutex & cache */
1451    }
1452    lock = apr_global_mutex_lockfile(socache_mutex);
1453    rv = apr_global_mutex_child_init(&socache_mutex, lock, p);
1454    if (rv != APR_SUCCESS) {
1455        ap_log_error(APLOG_MARK, APLOG_CRIT, rv, s, APLOGNO(02394)
1456                "failed to initialise mutex in child_init");
1457    }
1458}
1459
1460static const command_rec cache_socache_cmds[] =
1461{
1462    AP_INIT_TAKE1("CacheSocache", set_cache_socache, NULL, RSRC_CONF,
1463            "The shared object cache to store cache files"),
1464    AP_INIT_TAKE1("CacheSocacheMaxTime", set_cache_maxtime, NULL, RSRC_CONF | ACCESS_CONF,
1465            "The maximum cache expiry age to cache a document in seconds"),
1466    AP_INIT_TAKE1("CacheSocacheMinTime", set_cache_mintime, NULL, RSRC_CONF | ACCESS_CONF,
1467            "The minimum cache expiry age to cache a document in seconds"),
1468    AP_INIT_TAKE1("CacheSocacheMaxSize", set_cache_max, NULL, RSRC_CONF | ACCESS_CONF,
1469            "The maximum cache entry size (headers and body) to cache a document"),
1470    AP_INIT_TAKE1("CacheSocacheReadSize", set_cache_readsize, NULL, RSRC_CONF | ACCESS_CONF,
1471            "The maximum quantity of data to attempt to read and cache in one go"),
1472    AP_INIT_TAKE1("CacheSocacheReadTime", set_cache_readtime, NULL, RSRC_CONF | ACCESS_CONF,
1473            "The maximum time taken to attempt to read and cache in go"),
1474    { NULL }
1475};
1476
1477static const cache_provider cache_socache_provider =
1478{
1479    &remove_entity, &store_headers, &store_body, &recall_headers, &recall_body,
1480    &create_entity, &open_entity, &remove_url, &commit_entity,
1481    &invalidate_entity
1482};
1483
1484static void cache_socache_register_hook(apr_pool_t *p)
1485{
1486    /* cache initializer */
1487    ap_register_provider(p, CACHE_PROVIDER_GROUP, "socache", "0",
1488            &cache_socache_provider);
1489    ap_hook_pre_config(socache_precfg, NULL, NULL, APR_HOOK_MIDDLE);
1490    ap_hook_post_config(socache_post_config, NULL, NULL, APR_HOOK_MIDDLE);
1491    ap_hook_child_init(socache_child_init, NULL, NULL, APR_HOOK_MIDDLE);
1492}
1493
1494AP_DECLARE_MODULE(cache_socache) = { STANDARD20_MODULE_STUFF,
1495    create_dir_config,  /* create per-directory config structure */
1496    merge_dir_config, /* merge per-directory config structures */
1497    create_config, /* create per-server config structure */
1498    merge_config, /* merge per-server config structures */
1499    cache_socache_cmds, /* command apr_table_t */
1500    cache_socache_register_hook /* register hooks */
1501};
1502