Synchronize local git repo with remote git repo
[httpd-crcsyncproxy.git] / crccache / mod_crccache_client.c
blob6e069dc4b3a4b4305aa6d12f683fe972d625f437
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 /* crcsync/crccache apache client module
19 * This module is designed to run as a cache server on the local end of a slow
20 * internet link. This module uses a crc32 running hash algorithm to reduce
21 * data transfer in cached but modified upstream files.
23 * CRC algorithm uses the crcsync library created by Rusty Russel
25 * Author: Toby Collett (2009)
26 * Contributor: Alex Wulms (2009)
32 #include <assert.h>
34 #include "apr_file_io.h"
35 #include "apr_strings.h"
36 #include "mod_cache.h"
37 #include "mod_disk_cache.h"
38 #include "ap_provider.h"
39 #include "util_filter.h"
40 #include "util_script.h"
41 #include "util_charset.h"
43 #include "crccache.h"
44 #include "ap_wrapper.h"
45 #include <crcsync/crcsync.h>
46 #include "zlib.h"
49 * mod_disk_cache: Disk Based HTTP 1.1 Cache.
51 * Flow to Find the .data file:
52 * Incoming client requests URI /foo/bar/baz
53 * Generate <hash> off of /foo/bar/baz
54 * Open <hash>.header
55 * Read in <hash>.header file (may contain Format #1 or Format #2)
56 * If format #1 (Contains a list of Vary Headers):
57 * Use each header name (from .header) with our request values (headers_in) to
58 * regenerate <hash> using HeaderName+HeaderValue+.../foo/bar/baz
59 * re-read in <hash>.header (must be format #2)
60 * read in <hash>.data
62 * Format #1:
63 * apr_uint32_t format;
64 * apr_time_t expire;
65 * apr_array_t vary_headers (delimited by CRLF)
67 * Format #2:
68 * disk_cache_info_t (first sizeof(apr_uint32_t) bytes is the format)
69 * entity name (dobj->name) [length is in disk_cache_info_t->name_len]
70 * r->headers_out (delimited by CRLF)
71 * CRLF
72 * r->headers_in (delimited by CRLF)
73 * CRLF
76 module AP_MODULE_DECLARE_DATA crccache_client_module;
78 /* Forward declarations */
79 static int remove_entity(cache_handle_t *h);
80 static apr_status_t store_headers(cache_handle_t *h, request_rec *r,
81 cache_info *i);
82 static apr_status_t store_body(cache_handle_t *h, request_rec *r,
83 apr_bucket_brigade *b);
84 static apr_status_t recall_headers(cache_handle_t *h, request_rec *r);
85 static apr_status_t recall_body(cache_handle_t *h, apr_pool_t *p,
86 apr_bucket_brigade *bb);
87 static apr_status_t read_array(request_rec *r, apr_array_header_t* arr,
88 apr_file_t *file);
90 static ap_filter_rec_t *crccache_decode_filter_handle;
92 typedef enum decoding_state {
93 DECODING_NEW_SECTION,
94 DECODING_COMPRESSED,
95 DECODING_BLOCK_HEADER,
96 DECODING_BLOCK
97 } decoding_state;
99 typedef enum {
100 DECOMPRESSION_INITIALIZED,
101 DECOMPRESSION_ENDED
102 } decompression_state_t;
104 typedef struct crccache_client_ctx_t {
105 apr_bucket_brigade *bb;
106 size_t block_size;
107 size_t tail_block_size;
108 apr_bucket * cached_bucket;// original data so we can fill in the matched blocks
110 decoding_state state;
111 decompression_state_t decompression_state;
112 z_stream *decompression_stream;
113 } crccache_client_ctx;
116 * Local static functions
119 static char *header_file(apr_pool_t *p, disk_cache_conf *conf,
120 disk_cache_object_t *dobj, const char *name) {
121 if (!dobj->hashfile) {
122 dobj->hashfile = ap_cache_generate_name(p, conf->dirlevels,
123 conf->dirlength, name);
126 if (dobj->prefix) {
127 return apr_pstrcat(p, dobj->prefix, CACHE_VDIR_SUFFIX, "/",
128 dobj->hashfile, CACHE_HEADER_SUFFIX, NULL);
129 } else {
130 return apr_pstrcat(p, conf->cache_root, "/", dobj->hashfile,
131 CACHE_HEADER_SUFFIX, NULL);
135 static char *data_file(apr_pool_t *p, disk_cache_conf *conf,
136 disk_cache_object_t *dobj, const char *name) {
137 if (!dobj->hashfile) {
138 dobj->hashfile = ap_cache_generate_name(p, conf->dirlevels,
139 conf->dirlength, name);
142 if (dobj->prefix) {
143 return apr_pstrcat(p, dobj->prefix, CACHE_VDIR_SUFFIX, "/",
144 dobj->hashfile, CACHE_DATA_SUFFIX, NULL);
145 } else {
146 return apr_pstrcat(p, conf->cache_root, "/", dobj->hashfile,
147 CACHE_DATA_SUFFIX, NULL);
151 static void mkdir_structure(disk_cache_conf *conf, const char *file,
152 apr_pool_t *pool) {
153 apr_status_t rv;
154 char *p;
156 for (p = (char*) file + conf->cache_root_len + 1;;) {
157 p = strchr(p, '/');
158 if (!p)
159 break;
160 *p = '\0';
162 rv = apr_dir_make(file, APR_UREAD | APR_UWRITE | APR_UEXECUTE, pool);
163 if (rv != APR_SUCCESS && !APR_STATUS_IS_EEXIST(rv)) {
164 /* XXX */
166 *p = '/';
167 ++p;
171 /* htcacheclean may remove directories underneath us.
172 * So, we'll try renaming three times at a cost of 0.002 seconds.
174 static apr_status_t safe_file_rename(disk_cache_conf *conf, const char *src,
175 const char *dest, apr_pool_t *pool) {
176 apr_status_t rv;
178 rv = apr_file_rename(src, dest, pool);
180 if (rv != APR_SUCCESS) {
181 int i;
183 for (i = 0; i < 2 && rv != APR_SUCCESS; i++) {
184 /* 1000 micro-seconds aka 0.001 seconds. */
185 apr_sleep(1000);
187 mkdir_structure(conf, dest, pool);
189 rv = apr_file_rename(src, dest, pool);
193 return rv;
196 static apr_status_t file_cache_el_final(disk_cache_object_t *dobj,
197 request_rec *r) {
198 /* move the data over */
199 if (dobj->tfd) {
200 apr_status_t rv;
202 apr_file_close(dobj->tfd);
204 /* This assumes that the tempfile is on the same file system
205 * as the cache_root. If not, then we need a file copy/move
206 * rather than a rename.
208 rv = apr_file_rename(dobj->tempfile, dobj->datafile, r->pool);
209 if (rv != APR_SUCCESS) {
210 ap_log_error(APLOG_MARK, APLOG_WARNING, rv,r->server, "disk_cache: rename tempfile to datafile failed:"
211 " %s -> %s", dobj->tempfile, dobj->datafile);
212 apr_file_remove(dobj->tempfile, r->pool);
215 dobj->tfd = NULL;
218 return APR_SUCCESS;
221 static apr_status_t file_cache_errorcleanup(disk_cache_object_t *dobj,
222 request_rec *r) {
223 /* Remove the header file and the body file. */
224 apr_file_remove(dobj->hdrsfile, r->pool);
225 apr_file_remove(dobj->datafile, r->pool);
227 /* If we opened the temporary data file, close and remove it. */
228 if (dobj->tfd) {
229 apr_file_close(dobj->tfd);
230 apr_file_remove(dobj->tempfile, r->pool);
231 dobj->tfd = NULL;
234 return APR_SUCCESS;
237 /* These two functions get and put state information into the data
238 * file for an ap_cache_el, this state information will be read
239 * and written transparent to clients of this module
241 static int file_cache_recall_mydata(apr_file_t *fd, cache_info *info,
242 disk_cache_object_t *dobj, request_rec *r) {
243 apr_status_t rv;
244 char *urlbuff;
245 disk_cache_info_t disk_info;
246 apr_size_t len;
248 /* read the data from the cache file */
249 len = sizeof(disk_cache_info_t);
250 rv = apr_file_read_full(fd, &disk_info, len, &len);
251 if (rv != APR_SUCCESS) {
252 return rv;
255 /* Store it away so we can get it later. */
256 dobj->disk_info = disk_info;
258 info->status = disk_info.status;
259 info->date = disk_info.date;
260 info->expire = disk_info.expire;
261 info->request_time = disk_info.request_time;
262 info->response_time = disk_info.response_time;
264 /* Note that we could optimize this by conditionally doing the palloc
265 * depending upon the size. */
266 urlbuff = apr_palloc(r->pool, disk_info.name_len + 1);
267 len = disk_info.name_len;
268 rv = apr_file_read_full(fd, urlbuff, len, &len);
269 if (rv != APR_SUCCESS) {
270 return rv;
272 urlbuff[disk_info.name_len] = '\0';
274 /* check that we have the same URL */
275 /* Would strncmp be correct? */
276 if (strcmp(urlbuff, dobj->name) != 0) {
277 return APR_EGENERAL;
280 return APR_SUCCESS;
283 static const char* regen_key(apr_pool_t *p, apr_table_t *headers,
284 apr_array_header_t *varray, const char *oldkey) {
285 struct iovec *iov;
286 int i, k;
287 int nvec;
288 const char *header;
289 const char **elts;
291 nvec = (varray->nelts * 2) + 1;
292 iov = apr_palloc(p, sizeof(struct iovec) * nvec);
293 elts = (const char **) varray->elts;
295 /* TODO:
296 * - Handle multiple-value headers better. (sort them?)
297 * - Handle Case in-sensitive Values better.
298 * This isn't the end of the world, since it just lowers the cache
299 * hit rate, but it would be nice to fix.
301 * The majority are case insenstive if they are values (encoding etc).
302 * Most of rfc2616 is case insensitive on header contents.
304 * So the better solution may be to identify headers which should be
305 * treated case-sensitive?
306 * HTTP URI's (3.2.3) [host and scheme are insensitive]
307 * HTTP method (5.1.1)
308 * HTTP-date values (3.3.1)
309 * 3.7 Media Types [exerpt]
310 * The type, subtype, and parameter attribute names are case-
311 * insensitive. Parameter values might or might not be case-sensitive,
312 * depending on the semantics of the parameter name.
313 * 4.20 Except [exerpt]
314 * Comparison of expectation values is case-insensitive for unquoted
315 * tokens (including the 100-continue token), and is case-sensitive for
316 * quoted-string expectation-extensions.
319 for (i = 0, k = 0; i < varray->nelts; i++) {
320 header = apr_table_get(headers, elts[i]);
321 if (!header) {
322 header = "";
324 iov[k].iov_base = (char*) elts[i];
325 iov[k].iov_len = strlen(elts[i]);
326 k++;
327 iov[k].iov_base = (char*) header;
328 iov[k].iov_len = strlen(header);
329 k++;
331 iov[k].iov_base = (char*) oldkey;
332 iov[k].iov_len = strlen(oldkey);
333 k++;
335 return apr_pstrcatv(p, iov, k, NULL);
338 static int array_alphasort(const void *fn1, const void *fn2) {
339 return strcmp(*(char**) fn1, *(char**) fn2);
342 static void tokens_to_array(apr_pool_t *p, const char *data,
343 apr_array_header_t *arr) {
344 char *token;
346 while ((token = ap_get_list_item(p, &data)) != NULL) {
347 *((const char **) apr_array_push(arr)) = token;
350 /* Sort it so that "Vary: A, B" and "Vary: B, A" are stored the same. */
351 qsort((void *) arr->elts, arr->nelts, sizeof(char *), array_alphasort);
355 * Hook and mod_cache callback functions
357 static int create_entity(cache_handle_t *h, request_rec *r, const char *key,
358 apr_off_t len) {
359 disk_cache_conf *conf = ap_get_module_config(r->server->module_config,
360 &crccache_client_module);
361 cache_object_t *obj;
362 disk_cache_object_t *dobj;
364 if (conf->cache_root == NULL) {
365 return DECLINED;
368 /* Allocate and initialize cache_object_t and disk_cache_object_t */
369 h->cache_obj = obj = apr_pcalloc(r->pool, sizeof(*obj));
370 obj->vobj = dobj = apr_pcalloc(r->pool, sizeof(*dobj));
372 obj->key = apr_pstrdup(r->pool, key);
374 dobj->name = obj->key;
375 dobj->prefix = NULL;
376 /* Save the cache root */
377 dobj->root = apr_pstrndup(r->pool, conf->cache_root, conf->cache_root_len);
378 dobj->root_len = conf->cache_root_len;
379 dobj->datafile = data_file(r->pool, conf, dobj, key);
380 dobj->hdrsfile = header_file(r->pool, conf, dobj, key);
381 dobj->tempfile = apr_pstrcat(r->pool, conf->cache_root, AP_TEMPFILE, NULL);
383 return OK;
386 static int open_entity(cache_handle_t *h, request_rec *r, const char *key) {
387 apr_uint32_t format;
388 apr_size_t len;
389 const char *nkey;
390 apr_status_t rc;
391 static int error_logged = 0;
392 disk_cache_conf *conf = ap_get_module_config(r->server->module_config,
393 &crccache_client_module);
394 apr_finfo_t finfo;
395 cache_object_t *obj;
396 cache_info *info;
397 disk_cache_object_t *dobj;
398 int flags;
399 h->cache_obj = NULL;
401 /* Look up entity keyed to 'url' */
402 if (conf->cache_root == NULL) {
403 if (!error_logged) {
404 error_logged = 1;
405 ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
406 "disk_cache: Cannot cache files to disk without a CacheRootClient specified.");
408 return DECLINED;
411 /* Create and init the cache object */
412 h->cache_obj = obj = apr_pcalloc(r->pool, sizeof(cache_object_t));
413 obj->vobj = dobj = apr_pcalloc(r->pool, sizeof(disk_cache_object_t));
415 info = &(obj->info);
417 /* Open the headers file */
418 dobj->prefix = NULL;
420 /* Save the cache root */
421 dobj->root = apr_pstrndup(r->pool, conf->cache_root, conf->cache_root_len);
422 dobj->root_len = conf->cache_root_len;
424 dobj->hdrsfile = header_file(r->pool, conf, dobj, key);
425 flags = APR_READ|APR_BINARY|APR_BUFFERED;
426 rc = apr_file_open(&dobj->hfd, dobj->hdrsfile, flags, 0, r->pool);
427 if (rc != APR_SUCCESS) {
428 return DECLINED;
431 /* read the format from the cache file */
432 len = sizeof(format);
433 apr_file_read_full(dobj->hfd, &format, len, &len);
435 if (format == VARY_FORMAT_VERSION) {
436 apr_array_header_t* varray;
437 apr_time_t expire;
439 len = sizeof(expire);
440 apr_file_read_full(dobj->hfd, &expire, len, &len);
442 varray = apr_array_make(r->pool, 5, sizeof(char*));
443 rc = read_array(r, varray, dobj->hfd);
444 if (rc != APR_SUCCESS) {
445 ap_log_error(APLOG_MARK, APLOG_ERR, rc, r->server,
446 "disk_cache: Cannot parse vary header file: %s",
447 dobj->hdrsfile);
448 return DECLINED;
450 apr_file_close(dobj->hfd);
452 nkey = regen_key(r->pool, r->headers_in, varray, key);
454 dobj->hashfile = NULL;
455 dobj->prefix = dobj->hdrsfile;
456 dobj->hdrsfile = header_file(r->pool, conf, dobj, nkey);
458 flags = APR_READ|APR_BINARY|APR_BUFFERED;
459 rc = apr_file_open(&dobj->hfd, dobj->hdrsfile, flags, 0, r->pool);
460 if (rc != APR_SUCCESS) {
461 return DECLINED;
464 else if (format != DISK_FORMAT_VERSION) {
465 ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
466 "cache_disk: File '%s' has a version mismatch. File had version: %d.",
467 dobj->hdrsfile, format);
468 return DECLINED;
470 else {
471 apr_off_t offset = 0;
472 /* This wasn't a Vary Format file, so we must seek to the
473 * start of the file again, so that later reads work.
475 apr_file_seek(dobj->hfd, APR_SET, &offset);
476 nkey = key;
479 obj->key = nkey;
480 dobj->key = nkey;
481 dobj->name = key;
482 dobj->datafile = data_file(r->pool, conf, dobj, nkey);
483 dobj->tempfile = apr_pstrcat(r->pool, conf->cache_root, AP_TEMPFILE, NULL);
485 /* Open the data file */
486 flags = APR_READ|APR_BINARY;
487 #ifdef APR_SENDFILE_ENABLED
488 flags |= APR_SENDFILE_ENABLED;
489 #endif
490 rc = apr_file_open(&dobj->fd, dobj->datafile, flags, 0, r->pool);
491 if (rc != APR_SUCCESS) {
492 /* XXX: Log message */
493 return DECLINED;
496 rc = apr_file_info_get(&finfo, APR_FINFO_SIZE, dobj->fd);
497 if (rc == APR_SUCCESS) {
498 dobj->file_size = finfo.size;
501 /* Read the bytes to setup the cache_info fields */
502 rc = file_cache_recall_mydata(dobj->hfd, info, dobj, r);
503 if (rc != APR_SUCCESS) {
504 /* XXX log message */
505 return DECLINED;
508 /* Initialize the cache_handle callback functions */
509 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
510 "disk_cache: Recalled cached URL info header %s", dobj->name);
511 return OK;
514 static int remove_entity(cache_handle_t *h) {
515 /* Null out the cache object pointer so next time we start from scratch */
516 h->cache_obj = NULL;
518 return OK;
521 static int remove_url(cache_handle_t *h, apr_pool_t *p) {
522 apr_status_t rc;
523 disk_cache_object_t *dobj;
525 /* Get disk cache object from cache handle */
526 dobj = (disk_cache_object_t *) h->cache_obj->vobj;
527 if (!dobj) {
528 return DECLINED;
531 /* Delete headers file */
532 if (dobj->hdrsfile) {
533 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL,
534 "disk_cache: Deleting %s from cache.", dobj->hdrsfile);
536 rc = apr_file_remove(dobj->hdrsfile, p);
537 if ((rc != APR_SUCCESS) && !APR_STATUS_IS_ENOENT(rc)) {
538 /* Will only result in an output if httpd is started with -e debug.
539 * For reason see log_error_core for the case s == NULL.
541 ap_log_error(APLOG_MARK, APLOG_DEBUG, rc, NULL,
542 "disk_cache: Failed to delete headers file %s from cache.",
543 dobj->hdrsfile);
544 return DECLINED;
548 /* Delete data file */
549 if (dobj->datafile) {
550 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL,
551 "disk_cache: Deleting %s from cache.", dobj->datafile);
553 rc = apr_file_remove(dobj->datafile, p);
554 if ((rc != APR_SUCCESS) && !APR_STATUS_IS_ENOENT(rc)) {
555 /* Will only result in an output if httpd is started with -e debug.
556 * For reason see log_error_core for the case s == NULL.
558 ap_log_error(APLOG_MARK, APLOG_DEBUG, rc, NULL,
559 "disk_cache: Failed to delete data file %s from cache.",
560 dobj->datafile);
561 return DECLINED;
565 /* now delete directories as far as possible up to our cache root */
566 if (dobj->root) {
567 const char *str_to_copy;
569 str_to_copy = dobj->hdrsfile ? dobj->hdrsfile : dobj->datafile;
570 if (str_to_copy) {
571 char *dir, *slash, *q;
573 dir = apr_pstrdup(p, str_to_copy);
575 /* remove filename */
576 slash = strrchr(dir, '/');
577 *slash = '\0';
580 * now walk our way back to the cache root, delete everything
581 * in the way as far as possible
583 * Note: due to the way we constructed the file names in
584 * header_file and data_file, we are guaranteed that the
585 * cache_root is suffixed by at least one '/' which will be
586 * turned into a terminating null by this loop. Therefore,
587 * we won't either delete or go above our cache root.
589 for (q = dir + dobj->root_len; *q; ) {
590 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL,
591 "disk_cache: Deleting directory %s from cache",
592 dir);
594 rc = apr_dir_remove(dir, p);
595 if (rc != APR_SUCCESS && !APR_STATUS_IS_ENOENT(rc)) {
596 break;
598 slash = strrchr(q, '/');
599 *slash = '\0';
604 return OK;
607 static apr_status_t read_array(request_rec *r, apr_array_header_t* arr,
608 apr_file_t *file) {
609 char w[MAX_STRING_LEN];
610 int p;
611 apr_status_t rv;
613 while (1) {
614 rv = apr_file_gets(w, MAX_STRING_LEN - 1, file);
615 if (rv != APR_SUCCESS) {
616 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
617 "Premature end of vary array.");
618 return rv;
621 p = strlen(w);
622 if (p> 0 && w[p - 1] == '\n') {
623 if (p> 1 && w[p - 2] == CR) {
624 w[p - 2] = '\0';
626 else {
627 w[p - 1] = '\0';
631 /* If we've finished reading the array, break out of the loop. */
632 if (w[0] == '\0') {
633 break;
636 *((const char **) apr_array_push(arr)) = apr_pstrdup(r->pool, w);
639 return APR_SUCCESS;
642 static apr_status_t store_array(apr_file_t *fd, apr_array_header_t* arr) {
643 int i;
644 apr_status_t rv;
645 struct iovec iov[2];
646 apr_size_t amt;
647 const char **elts;
649 elts = (const char **) arr->elts;
651 for (i = 0; i < arr->nelts; i++) {
652 iov[0].iov_base = (char*) elts[i];
653 iov[0].iov_len = strlen(elts[i]);
654 iov[1].iov_base = CRLF;
655 iov[1].iov_len = sizeof(CRLF) - 1;
657 rv = apr_file_writev(fd, (const struct iovec *) &iov, 2,
658 &amt);
659 if (rv != APR_SUCCESS) {
660 return rv;
664 iov[0].iov_base = CRLF;
665 iov[0].iov_len = sizeof(CRLF) - 1;
667 return apr_file_writev(fd, (const struct iovec *) &iov, 1,
668 &amt);
671 static apr_status_t read_table(cache_handle_t *handle, request_rec *r,
672 apr_table_t *table, apr_file_t *file) {
673 char w[MAX_STRING_LEN];
674 char *l;
675 int p;
676 apr_status_t rv;
678 while (1) {
680 /* ### What about APR_EOF? */
681 rv = apr_file_gets(w, MAX_STRING_LEN - 1, file);
682 if (rv != APR_SUCCESS) {
683 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
684 "Premature end of cache headers.");
685 return rv;
688 /* Delete terminal (CR?)LF */
690 p = strlen(w);
691 /* Indeed, the host's '\n':
692 '\012' for UNIX; '\015' for MacOS; '\025' for OS/390
693 -- whatever the script generates.
695 if (p> 0 && w[p - 1] == '\n') {
696 if (p> 1 && w[p - 2] == CR) {
697 w[p - 2] = '\0';
699 else {
700 w[p - 1] = '\0';
704 /* If we've finished reading the headers, break out of the loop. */
705 if (w[0] == '\0') {
706 break;
709 #if APR_CHARSET_EBCDIC
710 /* Chances are that we received an ASCII header text instead of
711 * the expected EBCDIC header lines. Try to auto-detect:
713 if (!(l = strchr(w, ':'))) {
714 int maybeASCII = 0, maybeEBCDIC = 0;
715 unsigned char *cp, native;
716 apr_size_t inbytes_left, outbytes_left;
718 for (cp = w; *cp != '\0'; ++cp) {
719 native = apr_xlate_conv_byte(ap_hdrs_from_ascii, *cp);
720 if (apr_isprint(*cp) && !apr_isprint(native))
721 ++maybeEBCDIC;
722 if (!apr_isprint(*cp) && apr_isprint(native))
723 ++maybeASCII;
725 if (maybeASCII> maybeEBCDIC) {
726 ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
727 "CGI Interface Error: Script headers apparently ASCII: (CGI = %s)",
728 r->filename);
729 inbytes_left = outbytes_left = cp - w;
730 apr_xlate_conv_buffer(ap_hdrs_from_ascii,
731 w, &inbytes_left, w, &outbytes_left);
734 #endif /*APR_CHARSET_EBCDIC*/
736 /* if we see a bogus header don't ignore it. Shout and scream */
737 if (!(l = strchr(w, ':'))) {
738 return APR_EGENERAL;
741 *l++ = '\0';
742 while (*l && apr_isspace(*l)) {
743 ++l;
746 apr_table_add(table, w, l);
749 return APR_SUCCESS;
753 * Clean-up memory used by helper libraries, that don't know about apr_palloc
754 * and that (probably) use classical malloc/free
756 static apr_status_t deflate_ctx_cleanup(void *data)
758 crccache_client_ctx *ctx = (crccache_client_ctx *)data;
760 if (ctx != NULL)
762 if (ctx->decompression_state != DECOMPRESSION_ENDED)
764 inflateEnd(ctx->decompression_stream);
765 ctx->decompression_state = DECOMPRESSION_ENDED;
768 return APR_SUCCESS;
773 * Reads headers from a buffer and returns an array of headers.
774 * Returns NULL on file error
775 * This routine tries to deal with too long lines and continuation lines.
776 * @@@: XXX: FIXME: currently the headers are passed thru un-merged.
777 * Is that okay, or should they be collapsed where possible?
779 static apr_status_t recall_headers(cache_handle_t *h, request_rec *r) {
780 const char *data;
781 apr_size_t len;
782 apr_bucket *e;
783 unsigned i;
784 int z_RC;
786 disk_cache_object_t *dobj = (disk_cache_object_t *) h->cache_obj->vobj;
789 /* This case should not happen... */
790 if (!dobj->hfd) {
791 /* XXX log message */
792 return APR_NOTFOUND;
795 h->req_hdrs = apr_table_make(r->pool, 20);
796 h->resp_hdrs = apr_table_make(r->pool, 20);
798 /* Call routine to read the header lines/status line */
799 read_table(h, r, h->resp_hdrs, dobj->hfd);
800 read_table(h, r, h->req_hdrs, dobj->hfd);
802 // TODO: We only really want to add our block hashes if the cache is not fresh
803 // TODO: We could achieve that by adding a filter here on sending the request
804 // and then doing all of this in the filter 'JIT'
805 e = apr_bucket_file_create(dobj->fd, 0, (apr_size_t) dobj->file_size, r->pool,
806 r->connection->bucket_alloc);
808 /* read */
809 apr_bucket_read(e, &data, &len, APR_BLOCK_READ);
811 // this will be rounded down, but thats okay
812 size_t blocksize = len/FULL_BLOCK_COUNT;
813 size_t tail_block_size = len % FULL_BLOCK_COUNT;
814 size_t block_count_including_final_block = FULL_BLOCK_COUNT + (tail_block_size != 0);
815 // sanity check for very small files
816 if (blocksize> 4)
818 //ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r->server,"crccache: %d blocks of %ld bytes",FULL_BLOCK_COUNT,blocksize);
820 crccache_client_ctx * ctx;
821 ctx = apr_pcalloc(r->pool, sizeof(*ctx));
822 ctx->bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
823 ctx->block_size = blocksize;
824 ctx->tail_block_size = tail_block_size;
825 ctx->state = DECODING_NEW_SECTION;
826 ctx->cached_bucket = e;
828 // Setup inflate for decompressing non-matched literal data
829 ctx->decompression_stream = apr_palloc(r->pool, sizeof(*(ctx->decompression_stream)));
830 ctx->decompression_stream->zalloc = Z_NULL;
831 ctx->decompression_stream->zfree = Z_NULL;
832 ctx->decompression_stream->opaque = Z_NULL;
833 ctx->decompression_stream->avail_in = 0;
834 ctx->decompression_stream->next_in = Z_NULL;
835 z_RC = inflateInit(ctx->decompression_stream);
836 if (z_RC != Z_OK)
838 ap_log_error(APLOG_MARK, APLOG_WARNING, 0, r->server,
839 "Can not initialize decompression engine, return code: %d", z_RC);
840 return APR_SUCCESS;
842 ctx->decompression_state = DECOMPRESSION_INITIALIZED;
844 // Register a cleanup function to cleanup internal libz resources
845 apr_pool_cleanup_register(r->pool, ctx, deflate_ctx_cleanup,
846 apr_pool_cleanup_null);
848 // All OK to go for the crcsync decoding: add the headers
849 // and set-up the decoding filter
851 // add one for base 64 overflow and null terminator
852 char hash_set[HASH_HEADER_SIZE+HASH_BASE64_SIZE_PADDING+1];
853 // use buffer to set block size first
854 snprintf(hash_set,HASH_HEADER_SIZE,"%zu",len);
855 apr_table_set(r->headers_in, "File-Size", hash_set);
857 uint32_t crcs[block_count_including_final_block];
858 crc_of_blocks(data, len, blocksize, 30, crcs);
860 for (i = 0; i < block_count_including_final_block;++i)
862 // encode the hash into base64;
863 encode_30bithash(crcs[i],&hash_set[i*HASH_BASE64_SIZE_TX]);
864 //ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r->server,"crccache: block %d, hash %08X",i,crcs[i]);
866 //apr_bucket_delete(e);
867 apr_table_set(r->headers_in, "Block-Hashes", hash_set);
869 // TODO: do we want to cache the hashes here?
870 //ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "adding block-hashes header: %s",hash_set);
872 // we want to add a filter here so that we can decode the response.
873 // we need access to the original cached data when we get the response as
874 // we need that to fill in the matched blocks.
875 ap_add_output_filter_handle(crccache_decode_filter_handle,
876 ctx, r, r->connection);
878 // TODO: why is hfd file only closed in this case?
879 apr_file_close(dobj->hfd);
881 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
882 "disk_cache: Recalled headers for URL %s", dobj->name);
883 return APR_SUCCESS;
886 static apr_status_t recall_body(cache_handle_t *h, apr_pool_t *p,
887 apr_bucket_brigade *bb) {
888 apr_bucket *e;
889 disk_cache_object_t *dobj = (disk_cache_object_t*) h->cache_obj->vobj;
891 e = apr_bucket_file_create(dobj->fd, 0, (apr_size_t) dobj->file_size, p,
892 bb->bucket_alloc);
894 APR_BRIGADE_INSERT_HEAD(bb, e);
895 e = apr_bucket_eos_create(bb->bucket_alloc);
896 APR_BRIGADE_INSERT_TAIL(bb, e);
898 return APR_SUCCESS;
901 static apr_status_t store_table(apr_file_t *fd, apr_table_t *table) {
902 int i;
903 apr_status_t rv;
904 struct iovec iov[4];
905 apr_size_t amt;
906 apr_table_entry_t *elts;
908 elts = (apr_table_entry_t *) apr_table_elts(table)->elts;
909 for (i = 0; i < apr_table_elts(table)->nelts; ++i) {
910 if (elts[i].key != NULL) {
911 iov[0].iov_base = elts[i].key;
912 iov[0].iov_len = strlen(elts[i].key);
913 iov[1].iov_base = ": ";
914 iov[1].iov_len = sizeof(": ") - 1;
915 iov[2].iov_base = elts[i].val;
916 iov[2].iov_len = strlen(elts[i].val);
917 iov[3].iov_base = CRLF;
918 iov[3].iov_len = sizeof(CRLF) - 1;
920 rv = apr_file_writev(fd, (const struct iovec *) &iov, 4,
921 &amt);
922 if (rv != APR_SUCCESS) {
923 return rv;
927 iov[0].iov_base = CRLF;
928 iov[0].iov_len = sizeof(CRLF) - 1;
929 rv = apr_file_writev(fd, (const struct iovec *) &iov, 1,
930 &amt);
931 return rv;
934 static apr_status_t store_headers(cache_handle_t *h, request_rec *r,
935 cache_info *info) {
936 disk_cache_conf *conf = ap_get_module_config(r->server->module_config,
937 &crccache_client_module);
939 apr_status_t rv;
940 apr_size_t amt;
941 disk_cache_object_t *dobj = (disk_cache_object_t*) h->cache_obj->vobj;
943 disk_cache_info_t disk_info;
944 struct iovec iov[2];
946 /* This is flaky... we need to manage the cache_info differently */
947 h->cache_obj->info = *info;
949 if (r->headers_out) {
950 const char *tmp;
952 tmp = apr_table_get(r->headers_out, "Vary");
954 if (tmp) {
955 apr_array_header_t* varray;
956 apr_uint32_t format = VARY_FORMAT_VERSION;
958 /* If we were initially opened as a vary format, rollback
959 * that internal state for the moment so we can recreate the
960 * vary format hints in the appropriate directory.
962 if (dobj->prefix) {
963 dobj->hdrsfile = dobj->prefix;
964 dobj->prefix = NULL;
967 mkdir_structure(conf, dobj->hdrsfile, r->pool);
969 rv = apr_file_mktemp(&dobj->tfd, dobj->tempfile,
970 APR_CREATE | APR_WRITE | APR_BINARY | APR_EXCL,
971 r->pool);
973 if (rv != APR_SUCCESS) {
974 return rv;
977 amt = sizeof(format);
978 apr_file_write(dobj->tfd, &format, &amt);
980 amt = sizeof(info->expire);
981 apr_file_write(dobj->tfd, &info->expire, &amt);
983 varray = apr_array_make(r->pool, 6, sizeof(char*));
984 tokens_to_array(r->pool, tmp, varray);
986 store_array(dobj->tfd, varray);
988 apr_file_close(dobj->tfd);
990 dobj->tfd = NULL;
992 rv = safe_file_rename(conf, dobj->tempfile, dobj->hdrsfile,
993 r->pool);
994 if (rv != APR_SUCCESS) {
995 ap_log_error(APLOG_MARK, APLOG_WARNING, rv, r->server,
996 "disk_cache: rename tempfile to varyfile failed: %s -> %s",
997 dobj->tempfile, dobj->hdrsfile);
998 apr_file_remove(dobj->tempfile, r->pool);
999 return rv;
1002 dobj->tempfile = apr_pstrcat(r->pool, conf->cache_root, AP_TEMPFILE, NULL);
1003 tmp = regen_key(r->pool, r->headers_in, varray, dobj->name);
1004 dobj->prefix = dobj->hdrsfile;
1005 dobj->hashfile = NULL;
1006 dobj->datafile = data_file(r->pool, conf, dobj, tmp);
1007 dobj->hdrsfile = header_file(r->pool, conf, dobj, tmp);
1012 rv = apr_file_mktemp(&dobj->hfd, dobj->tempfile,
1013 APR_CREATE | APR_WRITE | APR_BINARY |
1014 APR_BUFFERED | APR_EXCL, r->pool);
1016 if (rv != APR_SUCCESS) {
1017 return rv;
1020 disk_info.format = DISK_FORMAT_VERSION;
1021 disk_info.date = info->date;
1022 disk_info.expire = info->expire;
1023 disk_info.entity_version = dobj->disk_info.entity_version++;
1024 disk_info.request_time = info->request_time;
1025 disk_info.response_time = info->response_time;
1026 disk_info.status = info->status;
1028 disk_info.name_len = strlen(dobj->name);
1030 iov[0].iov_base = (void*)&disk_info;
1031 iov[0].iov_len = sizeof(disk_cache_info_t);
1032 iov[1].iov_base = (void*)dobj->name;
1033 iov[1].iov_len = disk_info.name_len;
1035 rv = apr_file_writev(dobj->hfd, (const struct iovec *) &iov, 2, &amt);
1036 if (rv != APR_SUCCESS) {
1037 return rv;
1040 if (r->headers_out) {
1041 apr_table_t *headers_out;
1043 headers_out = ap_cache_cacheable_hdrs_out(r->pool, r->headers_out,
1044 r->server);
1046 if (!apr_table_get(headers_out, "Content-Type")
1047 && r->content_type) {
1048 apr_table_setn(headers_out, "Content-Type",
1049 ap_make_content_type(r, r->content_type));
1052 headers_out = apr_table_overlay(r->pool, headers_out,
1053 r->err_headers_out);
1054 rv = store_table(dobj->hfd, headers_out);
1055 if (rv != APR_SUCCESS) {
1056 return rv;
1060 /* Parse the vary header and dump those fields from the headers_in. */
1061 /* FIXME: Make call to the same thing cache_select calls to crack Vary. */
1062 if (r->headers_in) {
1063 apr_table_t *headers_in;
1065 headers_in = ap_cache_cacheable_hdrs_out(r->pool, r->headers_in,
1066 r->server);
1067 rv = store_table(dobj->hfd, headers_in);
1068 if (rv != APR_SUCCESS) {
1069 return rv;
1073 apr_file_close(dobj->hfd); /* flush and close */
1075 /* Remove old file with the same name. If remove fails, then
1076 * perhaps we need to create the directory tree where we are
1077 * about to write the new headers file.
1079 rv = apr_file_remove(dobj->hdrsfile, r->pool);
1080 if (rv != APR_SUCCESS) {
1081 mkdir_structure(conf, dobj->hdrsfile, r->pool);
1084 rv = safe_file_rename(conf, dobj->tempfile, dobj->hdrsfile, r->pool);
1085 if (rv != APR_SUCCESS) {
1086 ap_log_error(APLOG_MARK, APLOG_WARNING, rv, r->server,
1087 "disk_cache: rename tempfile to hdrsfile failed: %s -> %s",
1088 dobj->tempfile, dobj->hdrsfile);
1089 apr_file_remove(dobj->tempfile, r->pool);
1090 return rv;
1093 dobj->tempfile = apr_pstrcat(r->pool, conf->cache_root, AP_TEMPFILE, NULL);
1095 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1096 "disk_cache: Stored headers for URL %s", dobj->name);
1097 return APR_SUCCESS;
1100 static apr_status_t store_body(cache_handle_t *h, request_rec *r,
1101 apr_bucket_brigade *bb) {
1102 apr_bucket *e;
1103 apr_status_t rv;
1105 disk_cache_object_t *dobj = (disk_cache_object_t *) h->cache_obj->vobj;
1106 disk_cache_conf *conf = ap_get_module_config(r->server->module_config,
1107 &crccache_client_module);
1109 /* We write to a temp file and then atomically rename the file over
1110 * in file_cache_el_final().
1112 if (!dobj->tfd) {
1113 rv = apr_file_mktemp(&dobj->tfd, dobj->tempfile, APR_CREATE | APR_WRITE
1114 | APR_BINARY | APR_BUFFERED | APR_EXCL, r->pool);
1115 if (rv != APR_SUCCESS) {
1116 return rv;
1118 dobj->file_size = 0;
1121 for (e = APR_BRIGADE_FIRST(bb); e != APR_BRIGADE_SENTINEL(bb); e = APR_BUCKET_NEXT(e)) {
1122 const char *str;
1123 apr_size_t length, written;
1124 rv = apr_bucket_read(e, &str, &length, APR_BLOCK_READ);
1125 if (rv != APR_SUCCESS) {
1126 ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
1127 "cache_disk: Error when reading bucket for URL %s",
1128 h->cache_obj->key);
1129 /* Remove the intermediate cache file and return non-APR_SUCCESS */
1130 file_cache_errorcleanup(dobj, r);
1131 return rv;
1133 rv = apr_file_write_full(dobj->tfd, str, length, &written);
1134 if (rv != APR_SUCCESS) {
1135 ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
1136 "cache_disk: Error when writing cache file for URL %s",
1137 h->cache_obj->key);
1138 /* Remove the intermediate cache file and return non-APR_SUCCESS */
1139 file_cache_errorcleanup(dobj, r);
1140 return rv;
1142 dobj->file_size += written;
1143 if (dobj->file_size> conf->maxfs) {
1144 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1145 "cache_disk: URL %s failed the size check "
1146 "(%" APR_OFF_T_FMT " > %" APR_OFF_T_FMT ")",
1147 h->cache_obj->key, dobj->file_size, conf->maxfs);
1148 /* Remove the intermediate cache file and return non-APR_SUCCESS */
1149 file_cache_errorcleanup(dobj, r);
1150 return APR_EGENERAL;
1154 /* Was this the final bucket? If yes, close the temp file and perform
1155 * sanity checks.
1157 if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(bb))) {
1158 if (r->connection->aborted || r->no_cache) {
1159 ap_log_error(APLOG_MARK, APLOG_INFO, 0, r->server,
1160 "disk_cache: Discarding body for URL %s "
1161 "because connection has been aborted.",
1162 h->cache_obj->key);
1163 /* Remove the intermediate cache file and return non-APR_SUCCESS */
1164 file_cache_errorcleanup(dobj, r);
1165 return APR_EGENERAL;
1167 if (dobj->file_size < conf->minfs) {
1168 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1169 "cache_disk: URL %s failed the size check "
1170 "(%" APR_OFF_T_FMT " < %" APR_OFF_T_FMT ")",
1171 h->cache_obj->key, dobj->file_size, conf->minfs);
1172 /* Remove the intermediate cache file and return non-APR_SUCCESS */
1173 file_cache_errorcleanup(dobj, r);
1174 return APR_EGENERAL;
1177 /* All checks were fine. Move tempfile to final destination */
1178 /* Link to the perm file, and close the descriptor */
1179 file_cache_el_final(dobj, r);
1180 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1181 "disk_cache: Body for URL %s cached.", dobj->name);
1184 return APR_SUCCESS;
1188 * CACHE_DECODE filter
1189 * ----------------
1191 * Deliver cached content (headers and body) up the stack.
1193 static int crccache_decode_filter(ap_filter_t *f, apr_bucket_brigade *bb) {
1194 apr_bucket *e;
1195 request_rec *r = f->r;
1196 // TODO: set up context type struct
1197 crccache_client_ctx *ctx = f->ctx;
1199 // TODO: make this work if we have multiple encodings
1200 const char * content_encoding;
1201 content_encoding = apr_table_get(r->headers_out, "Content-Encoding");
1202 if (content_encoding == NULL || strcmp(CRCCACHE_ENCODING, content_encoding)
1203 != 0) {
1204 ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r->server,
1205 "CRCSYNC not decoding, content encoding bad (%s)", content_encoding?content_encoding:"NULL");
1206 ap_remove_output_filter(f);
1207 return ap_pass_brigade(f->next, bb);
1209 // TODO: Remove crcsync from the content encoding header
1211 // TODO: Fix up the etag as well
1213 /* Do nothing if asked to filter nothing. */
1214 if (APR_BRIGADE_EMPTY(bb)) {
1215 return ap_pass_brigade(f->next, bb);
1218 /* We require that we have a context already, otherwise we dont have our cached file
1219 * to fill in the gaps with.
1221 if (!ctx) {
1222 ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r->server,
1223 "No context available %s", r->uri);
1224 ap_remove_output_filter(f);
1225 return ap_pass_brigade(f->next, bb);
1228 while (!APR_BRIGADE_EMPTY(bb))
1230 const char *data;
1231 apr_size_t len;
1233 e = APR_BRIGADE_FIRST(bb);
1235 if (APR_BUCKET_IS_EOS(e)) {
1237 /* Remove EOS from the old list, and insert into the new. */
1238 APR_BUCKET_REMOVE(e);
1239 APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
1241 /* This filter is done once it has served up its content */
1242 ap_remove_output_filter(f);
1244 // TODO: check strong hash here
1246 /* Okay, we've seen the EOS.
1247 * Time to pass it along down the chain.
1249 return ap_pass_brigade(f->next, ctx->bb);
1252 if (APR_BUCKET_IS_FLUSH(e)) {
1253 apr_status_t rv;
1255 /* Remove flush bucket from old brigade anf insert into the new. */
1256 APR_BUCKET_REMOVE(e);
1257 APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
1258 rv = ap_pass_brigade(f->next, ctx->bb);
1259 if (rv != APR_SUCCESS) {
1260 return rv;
1262 continue;
1265 if (APR_BUCKET_IS_METADATA(e)) {
1267 * Remove meta data bucket from old brigade and insert into the
1268 * new.
1270 APR_BUCKET_REMOVE(e);
1271 APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
1272 continue;
1275 /* read */
1276 apr_bucket_read(e, &data, &len, APR_BLOCK_READ);
1277 //ap_log_error_wrapper(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r->server,"CRCSYNC-DECODE read %zd bytes",len);
1279 apr_size_t consumed_bytes = 0;
1280 while (consumed_bytes < len)
1282 //ap_log_error_wrapper(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r->server,"CRCSYNC-DECODE remaining %zd bytes",len - consumed_bytes);
1283 // no guaruntee that our buckets line up with our encoding sections
1284 // so we need a processing state machine stored in our context
1285 switch (ctx->state)
1287 case DECODING_NEW_SECTION:
1289 // check if we have a compressed section or a block section
1290 if (data[consumed_bytes] == ENCODING_COMPRESSED)
1291 ctx->state = DECODING_COMPRESSED;
1292 else if (data[consumed_bytes] == ENCODING_BLOCK)
1293 ctx->state = DECODING_BLOCK_HEADER;
1294 else
1296 ap_log_error(APLOG_MARK, APLOG_ERR, APR_SUCCESS, r->server,
1297 "CRCSYNC-DECODE, unknown section %d(%c)",data[consumed_bytes],data[consumed_bytes]);
1298 apr_brigade_cleanup(bb);
1299 return APR_EGENERAL;
1301 //ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r->server,"CRCSYNC-DECODE found a new section %d",ctx->state);
1302 consumed_bytes++;
1303 break;
1305 case DECODING_BLOCK_HEADER:
1307 unsigned char block_number = data[consumed_bytes];
1308 consumed_bytes++;
1309 ctx->state = DECODING_NEW_SECTION;
1311 // TODO: Output the indicated block here
1312 size_t current_block_size = block_number < FULL_BLOCK_COUNT ? ctx->block_size : ctx->tail_block_size;
1313 ap_log_error_wrapper(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r->server,
1314 "CRCSYNC-DECODE block section, block %d, size %zu" ,block_number, current_block_size);
1316 char * buf = apr_palloc(r->pool, current_block_size);
1317 const char * source_data;
1318 size_t source_len;
1319 apr_bucket_read(ctx->cached_bucket, &source_data, &source_len, APR_BLOCK_READ);
1320 assert(block_number < (FULL_BLOCK_COUNT + (ctx->tail_block_size != 0)));
1321 memcpy(buf,&source_data[block_number*ctx->block_size],current_block_size);
1322 apr_bucket * b = apr_bucket_pool_create(buf, current_block_size, r->pool, f->c->bucket_alloc);
1323 APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
1324 break;
1326 case DECODING_COMPRESSED:
1328 unsigned char decompressed_data_buf[30000];
1329 int z_RC;
1330 z_stream *strm = ctx->decompression_stream;
1331 strm->avail_in = len - consumed_bytes;
1332 strm->next_in = (Bytef *)(data + consumed_bytes);
1333 // ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r->server, "CRCSYNC-DECODE inflating %d bytes", strm.avail_in);
1334 // ap_log_hex(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r->server, strm.next_in, strm.avail_in);
1335 do {
1336 strm->avail_out = sizeof(decompressed_data_buf);
1337 strm->next_out = decompressed_data_buf;
1338 uInt avail_in_pre_inflate = strm->avail_in;
1339 z_RC = inflate(strm, Z_NO_FLUSH);
1340 if (z_RC == Z_NEED_DICT || z_RC == Z_DATA_ERROR || z_RC == Z_MEM_ERROR)
1342 ap_log_error(APLOG_MARK, APLOG_ERR, APR_EGENERAL, r->server, "CRCSYNC-DECODE inflate error: %d", z_RC);
1343 apr_brigade_cleanup(bb);
1344 return APR_EGENERAL;
1346 int have = sizeof(decompressed_data_buf) - strm->avail_out;
1347 ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r->server,
1348 "CRCSYNC-DECODE inflate rslt %d, consumed %d, produced %d",
1349 z_RC, avail_in_pre_inflate - strm->avail_in, have);
1350 if (have)
1352 // write output data
1353 char * buf = apr_palloc(r->pool, have);
1354 memcpy(buf,decompressed_data_buf,have);
1355 apr_bucket * b = apr_bucket_pool_create(buf, have, r->pool, f->c->bucket_alloc);
1356 APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
1358 } while (strm->avail_out == 0);
1359 consumed_bytes = len - strm->avail_in;
1360 if (z_RC == Z_STREAM_END)
1362 ctx->state = DECODING_NEW_SECTION;
1363 inflateReset(strm);
1365 break;
1367 default:
1369 ap_log_error(APLOG_MARK, APLOG_ERR, APR_SUCCESS, r->server,
1370 "CRCSYNC-DECODE, unknown state %d, terminating transaction",ctx->state);
1371 apr_brigade_cleanup(bb);
1372 return APR_EGENERAL; // TODO: figure out how to pass the error on to the client
1375 APR_BUCKET_REMOVE(e);
1379 apr_brigade_cleanup(bb);
1380 return APR_SUCCESS;
1383 static void *create_config(apr_pool_t *p, server_rec *s) {
1384 disk_cache_conf *conf = apr_pcalloc(p, sizeof(disk_cache_conf));
1386 /* XXX: Set default values */
1387 conf->dirlevels = DEFAULT_DIRLEVELS;
1388 conf->dirlength = DEFAULT_DIRLENGTH;
1389 conf->maxfs = DEFAULT_MAX_FILE_SIZE;
1390 conf->minfs = DEFAULT_MIN_FILE_SIZE;
1392 conf->cache_root = NULL;
1393 conf->cache_root_len = 0;
1395 return conf;
1399 * mod_disk_cache configuration directives handlers.
1401 static const char *set_cache_root(cmd_parms *parms, void *in_struct_ptr,
1402 const char *arg) {
1403 disk_cache_conf *conf = ap_get_module_config(parms->server->module_config,
1404 &crccache_client_module);
1405 conf->cache_root = arg;
1406 conf->cache_root_len = strlen(arg);
1407 /* TODO: canonicalize cache_root and strip off any trailing slashes */
1409 return NULL;
1413 * Consider eliminating the next two directives in favor of
1414 * Ian's prime number hash...
1415 * key = hash_fn( r->uri)
1416 * filename = "/key % prime1 /key %prime2/key %prime3"
1418 static const char *set_cache_dirlevels(cmd_parms *parms, void *in_struct_ptr,
1419 const char *arg) {
1420 disk_cache_conf *conf = ap_get_module_config(parms->server->module_config,
1421 &crccache_client_module);
1422 int val = atoi(arg);
1423 if (val < 1)
1424 return "CacheDirLevelsClient value must be an integer greater than 0";
1425 if (val * conf->dirlength > CACHEFILE_LEN)
1426 return "CacheDirLevelsClient*CacheDirLengthClient value must not be higher than 20";
1427 conf->dirlevels = val;
1428 return NULL;
1430 static const char *set_cache_dirlength(cmd_parms *parms, void *in_struct_ptr,
1431 const char *arg) {
1432 disk_cache_conf *conf = ap_get_module_config(parms->server->module_config,
1433 &crccache_client_module);
1434 int val = atoi(arg);
1435 if (val < 1)
1436 return "CacheDirLengthClient value must be an integer greater than 0";
1437 if (val * conf->dirlevels > CACHEFILE_LEN)
1438 return "CacheDirLevelsClient*CacheDirLengthClient value must not be higher than 20";
1440 conf->dirlength = val;
1441 return NULL;
1444 static const char *set_cache_minfs(cmd_parms *parms, void *in_struct_ptr,
1445 const char *arg) {
1446 disk_cache_conf *conf = ap_get_module_config(parms->server->module_config,
1447 &crccache_client_module);
1449 if (apr_strtoff(&conf->minfs, arg, NULL, 0) != APR_SUCCESS || conf->minfs
1450 < 0) {
1451 return "CacheMinFileSizeClient argument must be a non-negative integer representing the min size of a file to cache in bytes.";
1453 return NULL;
1456 static const char *set_cache_maxfs(cmd_parms *parms, void *in_struct_ptr,
1457 const char *arg) {
1458 disk_cache_conf *conf = ap_get_module_config(parms->server->module_config,
1459 &crccache_client_module);
1460 if (apr_strtoff(&conf->maxfs, arg, NULL, 0) != APR_SUCCESS || conf->maxfs
1461 < 0) {
1462 return "CacheMaxFileSizeClient argument must be a non-negative integer representing the max size of a file to cache in bytes.";
1464 return NULL;
1467 static const command_rec disk_cache_cmds[] = { AP_INIT_TAKE1("CacheRootClient", set_cache_root, NULL, RSRC_CONF,
1468 "The directory to store cache files"), AP_INIT_TAKE1("CacheDirLevelsClient", set_cache_dirlevels, NULL, RSRC_CONF,
1469 "The number of levels of subdirectories in the cache"), AP_INIT_TAKE1("CacheDirLengthClient", set_cache_dirlength, NULL, RSRC_CONF,
1470 "The number of characters in subdirectory names"), AP_INIT_TAKE1("CacheMinFileSizeClient", set_cache_minfs, NULL, RSRC_CONF,
1471 "The minimum file size to cache a document"), AP_INIT_TAKE1("CacheMaxFileSizeClient", set_cache_maxfs, NULL, RSRC_CONF,
1472 "The maximum file size to cache a document"), { NULL } };
1474 static const cache_provider crccache_client_provider = { &remove_entity,
1475 &store_headers, &store_body, &recall_headers, &recall_body,
1476 &create_entity, &open_entity, &remove_url, };
1478 static void disk_cache_register_hook(apr_pool_t *p) {
1479 ap_log_error(APLOG_MARK, APLOG_INFO, 0, NULL,
1480 "Registering crccache client module, (C) 2009, Toby Collett");
1482 /* cache initializer */
1483 ap_register_provider(p, CACHE_PROVIDER_GROUP, "crccache_client", "0",
1484 &crccache_client_provider);
1486 * CACHE_OUT must go into the filter chain after a possible DEFLATE
1487 * filter to ensure that already compressed cache objects do not
1488 * get compressed again. Incrementing filter type by 1 ensures
1489 * his happens.
1491 crccache_decode_filter_handle = ap_register_output_filter(
1492 "CRCCACHE_DECODE", crccache_decode_filter, NULL,
1493 AP_FTYPE_CONTENT_SET + 1);
1496 module AP_MODULE_DECLARE_DATA crccache_client_module = {
1497 STANDARD20_MODULE_STUFF, NULL, /* create per-directory config structure */
1498 NULL , /* merge per-directory config structures */
1499 create_config, /* create per-server config structure */
1500 NULL , /* merge per-server config structures */
1501 disk_cache_cmds, /* command apr_table_t */
1502 disk_cache_register_hook /* register hooks */