implemented protocol decoding
[httpd-crcsyncproxy.git] / crccache / mod_crccache_client.c
blobafe0402d58bf6cfa624bc2cad69a9b0551d75f85
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 #include "apr_file_io.h"
18 #include "apr_strings.h"
19 #include "mod_cache.h"
20 #include "mod_disk_cache.h"
21 #include "ap_provider.h"
22 #include "util_filter.h"
23 #include "util_script.h"
24 #include "util_charset.h"
26 #include "crccache.h"
27 #include <crcsync/crcsync.h>
30 * mod_disk_cache: Disk Based HTTP 1.1 Cache.
32 * Flow to Find the .data file:
33 * Incoming client requests URI /foo/bar/baz
34 * Generate <hash> off of /foo/bar/baz
35 * Open <hash>.header
36 * Read in <hash>.header file (may contain Format #1 or Format #2)
37 * If format #1 (Contains a list of Vary Headers):
38 * Use each header name (from .header) with our request values (headers_in) to
39 * regenerate <hash> using HeaderName+HeaderValue+.../foo/bar/baz
40 * re-read in <hash>.header (must be format #2)
41 * read in <hash>.data
43 * Format #1:
44 * apr_uint32_t format;
45 * apr_time_t expire;
46 * apr_array_t vary_headers (delimited by CRLF)
48 * Format #2:
49 * disk_cache_info_t (first sizeof(apr_uint32_t) bytes is the format)
50 * entity name (dobj->name) [length is in disk_cache_info_t->name_len]
51 * r->headers_out (delimited by CRLF)
52 * CRLF
53 * r->headers_in (delimited by CRLF)
54 * CRLF
57 module AP_MODULE_DECLARE_DATA crccache_client_module;
59 /* Forward declarations */
60 static int remove_entity(cache_handle_t *h);
61 static apr_status_t store_headers(cache_handle_t *h, request_rec *r,
62 cache_info *i);
63 static apr_status_t store_body(cache_handle_t *h, request_rec *r,
64 apr_bucket_brigade *b);
65 static apr_status_t recall_headers(cache_handle_t *h, request_rec *r);
66 static apr_status_t recall_body(cache_handle_t *h, apr_pool_t *p,
67 apr_bucket_brigade *bb);
68 static apr_status_t read_array(request_rec *r, apr_array_header_t* arr,
69 apr_file_t *file);
71 static ap_filter_rec_t *crccache_decode_filter_handle;
73 typedef enum decoding_state {
74 DECODING_NEW_SECTION,
75 DECODING_LITERAL_HEADER,
76 DECODING_LITERAL,
77 DECODING_BLOCK_HEADER,
78 DECODING_BLOCK
79 } decoding_state;
81 typedef struct crccache_client_ctx_t {
82 apr_bucket_brigade *bb;
83 size_t block_size;
85 decoding_state state;
86 unsigned section_length;
87 unsigned processed_length;
88 char section_header[4];
89 // need to add pointer to file data here
90 } crccache_client_ctx;
93 * Local static functions
96 static char *header_file(apr_pool_t *p, disk_cache_conf *conf,
97 disk_cache_object_t *dobj, const char *name) {
98 if (!dobj->hashfile) {
99 dobj->hashfile = ap_cache_generate_name(p, conf->dirlevels,
100 conf->dirlength, name);
103 if (dobj->prefix) {
104 return apr_pstrcat(p, dobj->prefix, CACHE_VDIR_SUFFIX, "/",
105 dobj->hashfile, CACHE_HEADER_SUFFIX, NULL);
106 } else {
107 return apr_pstrcat(p, conf->cache_root, "/", dobj->hashfile,
108 CACHE_HEADER_SUFFIX, NULL);
112 static char *data_file(apr_pool_t *p, disk_cache_conf *conf,
113 disk_cache_object_t *dobj, const char *name) {
114 if (!dobj->hashfile) {
115 dobj->hashfile = ap_cache_generate_name(p, conf->dirlevels,
116 conf->dirlength, name);
119 if (dobj->prefix) {
120 return apr_pstrcat(p, dobj->prefix, CACHE_VDIR_SUFFIX, "/",
121 dobj->hashfile, CACHE_DATA_SUFFIX, NULL);
122 } else {
123 return apr_pstrcat(p, conf->cache_root, "/", dobj->hashfile,
124 CACHE_DATA_SUFFIX, NULL);
128 static void mkdir_structure(disk_cache_conf *conf, const char *file,
129 apr_pool_t *pool) {
130 apr_status_t rv;
131 char *p;
133 for (p = (char*) file + conf->cache_root_len + 1;;) {
134 p = strchr(p, '/');
135 if (!p)
136 break;
137 *p = '\0';
139 rv = apr_dir_make(file, APR_UREAD | APR_UWRITE | APR_UEXECUTE, pool);
140 if (rv != APR_SUCCESS && !APR_STATUS_IS_EEXIST(rv)) {
141 /* XXX */
143 *p = '/';
144 ++p;
148 /* htcacheclean may remove directories underneath us.
149 * So, we'll try renaming three times at a cost of 0.002 seconds.
151 static apr_status_t safe_file_rename(disk_cache_conf *conf, const char *src,
152 const char *dest, apr_pool_t *pool) {
153 apr_status_t rv;
155 rv = apr_file_rename(src, dest, pool);
157 if (rv != APR_SUCCESS) {
158 int i;
160 for (i = 0; i < 2 && rv != APR_SUCCESS; i++) {
161 /* 1000 micro-seconds aka 0.001 seconds. */
162 apr_sleep(1000);
164 mkdir_structure(conf, dest, pool);
166 rv = apr_file_rename(src, dest, pool);
170 return rv;
173 static apr_status_t file_cache_el_final(disk_cache_object_t *dobj,
174 request_rec *r) {
175 /* move the data over */
176 if (dobj->tfd) {
177 apr_status_t rv;
179 apr_file_close(dobj->tfd);
181 /* This assumes that the tempfile is on the same file system
182 * as the cache_root. If not, then we need a file copy/move
183 * rather than a rename.
185 rv = apr_file_rename(dobj->tempfile, dobj->datafile, r->pool);
186 if (rv != APR_SUCCESS) {
187 ap_log_error(APLOG_MARK, APLOG_WARNING, rv,r->server, "disk_cache: rename tempfile to datafile failed:"
188 " %s -> %s", dobj->tempfile, dobj->datafile);
189 apr_file_remove(dobj->tempfile, r->pool);
192 dobj->tfd = NULL;
195 return APR_SUCCESS;
198 static apr_status_t file_cache_errorcleanup(disk_cache_object_t *dobj,
199 request_rec *r) {
200 /* Remove the header file and the body file. */
201 apr_file_remove(dobj->hdrsfile, r->pool);
202 apr_file_remove(dobj->datafile, r->pool);
204 /* If we opened the temporary data file, close and remove it. */
205 if (dobj->tfd) {
206 apr_file_close(dobj->tfd);
207 apr_file_remove(dobj->tempfile, r->pool);
208 dobj->tfd = NULL;
211 return APR_SUCCESS;
214 /* These two functions get and put state information into the data
215 * file for an ap_cache_el, this state information will be read
216 * and written transparent to clients of this module
218 static int file_cache_recall_mydata(apr_file_t *fd, cache_info *info,
219 disk_cache_object_t *dobj, request_rec *r) {
220 apr_status_t rv;
221 char *urlbuff;
222 disk_cache_info_t disk_info;
223 apr_size_t len;
225 /* read the data from the cache file */
226 len = sizeof(disk_cache_info_t);
227 rv = apr_file_read_full(fd, &disk_info, len, &len);
228 if (rv != APR_SUCCESS) {
229 return rv;
232 /* Store it away so we can get it later. */
233 dobj->disk_info = disk_info;
235 info->status = disk_info.status;
236 info->date = disk_info.date;
237 info->expire = disk_info.expire;
238 info->request_time = disk_info.request_time;
239 info->response_time = disk_info.response_time;
241 /* Note that we could optimize this by conditionally doing the palloc
242 * depending upon the size. */
243 urlbuff = apr_palloc(r->pool, disk_info.name_len + 1);
244 len = disk_info.name_len;
245 rv = apr_file_read_full(fd, urlbuff, len, &len);
246 if (rv != APR_SUCCESS) {
247 return rv;
249 urlbuff[disk_info.name_len] = '\0';
251 /* check that we have the same URL */
252 /* Would strncmp be correct? */
253 if (strcmp(urlbuff, dobj->name) != 0) {
254 return APR_EGENERAL;
257 return APR_SUCCESS;
260 static const char* regen_key(apr_pool_t *p, apr_table_t *headers,
261 apr_array_header_t *varray, const char *oldkey) {
262 struct iovec *iov;
263 int i, k;
264 int nvec;
265 const char *header;
266 const char **elts;
268 nvec = (varray->nelts * 2) + 1;
269 iov = apr_palloc(p, sizeof(struct iovec) * nvec);
270 elts = (const char **) varray->elts;
272 /* TODO:
273 * - Handle multiple-value headers better. (sort them?)
274 * - Handle Case in-sensitive Values better.
275 * This isn't the end of the world, since it just lowers the cache
276 * hit rate, but it would be nice to fix.
278 * The majority are case insenstive if they are values (encoding etc).
279 * Most of rfc2616 is case insensitive on header contents.
281 * So the better solution may be to identify headers which should be
282 * treated case-sensitive?
283 * HTTP URI's (3.2.3) [host and scheme are insensitive]
284 * HTTP method (5.1.1)
285 * HTTP-date values (3.3.1)
286 * 3.7 Media Types [exerpt]
287 * The type, subtype, and parameter attribute names are case-
288 * insensitive. Parameter values might or might not be case-sensitive,
289 * depending on the semantics of the parameter name.
290 * 4.20 Except [exerpt]
291 * Comparison of expectation values is case-insensitive for unquoted
292 * tokens (including the 100-continue token), and is case-sensitive for
293 * quoted-string expectation-extensions.
296 for (i = 0, k = 0; i < varray->nelts; i++) {
297 header = apr_table_get(headers, elts[i]);
298 if (!header) {
299 header = "";
301 iov[k].iov_base = (char*) elts[i];
302 iov[k].iov_len = strlen(elts[i]);
303 k++;
304 iov[k].iov_base = (char*) header;
305 iov[k].iov_len = strlen(header);
306 k++;
308 iov[k].iov_base = (char*) oldkey;
309 iov[k].iov_len = strlen(oldkey);
310 k++;
312 return apr_pstrcatv(p, iov, k, NULL);
315 static int array_alphasort(const void *fn1, const void *fn2) {
316 return strcmp(*(char**) fn1, *(char**) fn2);
319 static void tokens_to_array(apr_pool_t *p, const char *data,
320 apr_array_header_t *arr) {
321 char *token;
323 while ((token = ap_get_list_item(p, &data)) != NULL) {
324 *((const char **) apr_array_push(arr)) = token;
327 /* Sort it so that "Vary: A, B" and "Vary: B, A" are stored the same. */
328 qsort((void *) arr->elts, arr->nelts, sizeof(char *), array_alphasort);
332 * Hook and mod_cache callback functions
334 static int create_entity(cache_handle_t *h, request_rec *r, const char *key,
335 apr_off_t len) {
336 disk_cache_conf *conf = ap_get_module_config(r->server->module_config,
337 &crccache_client_module);
338 cache_object_t *obj;
339 disk_cache_object_t *dobj;
341 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "create_entity");
343 if (conf->cache_root == NULL) {
344 return DECLINED;
347 /* Allocate and initialize cache_object_t and disk_cache_object_t */
348 h->cache_obj = obj = apr_pcalloc(r->pool, sizeof(*obj));
349 obj->vobj = dobj = apr_pcalloc(r->pool, sizeof(*dobj));
351 obj->key = apr_pstrdup(r->pool, key);
353 dobj->name = obj->key;
354 dobj->prefix = NULL;
355 /* Save the cache root */
356 dobj->root = apr_pstrndup(r->pool, conf->cache_root, conf->cache_root_len);
357 dobj->root_len = conf->cache_root_len;
358 dobj->datafile = data_file(r->pool, conf, dobj, key);
359 dobj->hdrsfile = header_file(r->pool, conf, dobj, key);
360 dobj->tempfile = apr_pstrcat(r->pool, conf->cache_root, AP_TEMPFILE, NULL);
362 return OK;
365 static int open_entity(cache_handle_t *h, request_rec *r, const char *key) {
366 apr_uint32_t format;
367 apr_size_t len;
368 const char *nkey;
369 apr_status_t rc;
370 static int error_logged = 0;
371 disk_cache_conf *conf = ap_get_module_config(r->server->module_config,
372 &crccache_client_module);
373 apr_finfo_t finfo;
374 cache_object_t *obj;
375 cache_info *info;
376 disk_cache_object_t *dobj;
377 int flags;
378 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "open_entity");
380 h->cache_obj = NULL;
382 /* Look up entity keyed to 'url' */
383 if (conf->cache_root == NULL) {
384 if (!error_logged) {
385 error_logged = 1;
386 ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
387 "disk_cache: Cannot cache files to disk without a CacheRoot specified.");
389 return DECLINED;
392 /* Create and init the cache object */
393 h->cache_obj = obj = apr_pcalloc(r->pool, sizeof(cache_object_t));
394 obj->vobj = dobj = apr_pcalloc(r->pool, sizeof(disk_cache_object_t));
396 info = &(obj->info);
398 /* Open the headers file */
399 dobj->prefix = NULL;
401 /* Save the cache root */
402 dobj->root = apr_pstrndup(r->pool, conf->cache_root, conf->cache_root_len);
403 dobj->root_len = conf->cache_root_len;
405 dobj->hdrsfile = header_file(r->pool, conf, dobj, key);
406 flags = APR_READ|APR_BINARY|APR_BUFFERED;
407 rc = apr_file_open(&dobj->hfd, dobj->hdrsfile, flags, 0, r->pool);
408 if (rc != APR_SUCCESS) {
409 return DECLINED;
412 /* read the format from the cache file */
413 len = sizeof(format);
414 apr_file_read_full(dobj->hfd, &format, len, &len);
416 if (format == VARY_FORMAT_VERSION) {
417 apr_array_header_t* varray;
418 apr_time_t expire;
420 len = sizeof(expire);
421 apr_file_read_full(dobj->hfd, &expire, len, &len);
423 varray = apr_array_make(r->pool, 5, sizeof(char*));
424 rc = read_array(r, varray, dobj->hfd);
425 if (rc != APR_SUCCESS) {
426 ap_log_error(APLOG_MARK, APLOG_ERR, rc, r->server,
427 "disk_cache: Cannot parse vary header file: %s",
428 dobj->hdrsfile);
429 return DECLINED;
431 apr_file_close(dobj->hfd);
433 nkey = regen_key(r->pool, r->headers_in, varray, key);
435 dobj->hashfile = NULL;
436 dobj->prefix = dobj->hdrsfile;
437 dobj->hdrsfile = header_file(r->pool, conf, dobj, nkey);
439 flags = APR_READ|APR_BINARY|APR_BUFFERED;
440 rc = apr_file_open(&dobj->hfd, dobj->hdrsfile, flags, 0, r->pool);
441 if (rc != APR_SUCCESS) {
442 return DECLINED;
445 else if (format != DISK_FORMAT_VERSION) {
446 ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
447 "cache_disk: File '%s' has a version mismatch. File had version: %d.",
448 dobj->hdrsfile, format);
449 return DECLINED;
451 else {
452 apr_off_t offset = 0;
453 /* This wasn't a Vary Format file, so we must seek to the
454 * start of the file again, so that later reads work.
456 apr_file_seek(dobj->hfd, APR_SET, &offset);
457 nkey = key;
460 obj->key = nkey;
461 dobj->key = nkey;
462 dobj->name = key;
463 dobj->datafile = data_file(r->pool, conf, dobj, nkey);
464 dobj->tempfile = apr_pstrcat(r->pool, conf->cache_root, AP_TEMPFILE, NULL);
466 /* Open the data file */
467 flags = APR_READ|APR_BINARY;
468 #ifdef APR_SENDFILE_ENABLED
469 flags |= APR_SENDFILE_ENABLED;
470 #endif
471 rc = apr_file_open(&dobj->fd, dobj->datafile, flags, 0, r->pool);
472 if (rc != APR_SUCCESS) {
473 /* XXX: Log message */
474 return DECLINED;
477 rc = apr_file_info_get(&finfo, APR_FINFO_SIZE, dobj->fd);
478 if (rc == APR_SUCCESS) {
479 dobj->file_size = finfo.size;
482 /* Read the bytes to setup the cache_info fields */
483 rc = file_cache_recall_mydata(dobj->hfd, info, dobj, r);
484 if (rc != APR_SUCCESS) {
485 /* XXX log message */
486 return DECLINED;
489 /* Initialize the cache_handle callback functions */
490 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
491 "disk_cache: Recalled cached URL info header %s", dobj->name);
492 return OK;
495 static int remove_entity(cache_handle_t *h) {
496 /* Null out the cache object pointer so next time we start from scratch */
497 h->cache_obj = NULL;
498 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL,"remove_entity" );
500 return OK;
503 static int remove_url(cache_handle_t *h, apr_pool_t *p) {
504 apr_status_t rc;
505 disk_cache_object_t *dobj;
507 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL,"remove_url" );
509 /* Get disk cache object from cache handle */
510 dobj = (disk_cache_object_t *) h->cache_obj->vobj;
511 if (!dobj) {
512 return DECLINED;
515 /* Delete headers file */
516 if (dobj->hdrsfile) {
517 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL,
518 "disk_cache: Deleting %s from cache.", dobj->hdrsfile);
520 rc = apr_file_remove(dobj->hdrsfile, p);
521 if ((rc != APR_SUCCESS) && !APR_STATUS_IS_ENOENT(rc)) {
522 /* Will only result in an output if httpd is started with -e debug.
523 * For reason see log_error_core for the case s == NULL.
525 ap_log_error(APLOG_MARK, APLOG_DEBUG, rc, NULL,
526 "disk_cache: Failed to delete headers file %s from cache.",
527 dobj->hdrsfile);
528 return DECLINED;
532 /* Delete data file */
533 if (dobj->datafile) {
534 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL,
535 "disk_cache: Deleting %s from cache.", dobj->datafile);
537 rc = apr_file_remove(dobj->datafile, p);
538 if ((rc != APR_SUCCESS) && !APR_STATUS_IS_ENOENT(rc)) {
539 /* Will only result in an output if httpd is started with -e debug.
540 * For reason see log_error_core for the case s == NULL.
542 ap_log_error(APLOG_MARK, APLOG_DEBUG, rc, NULL,
543 "disk_cache: Failed to delete data file %s from cache.",
544 dobj->datafile);
545 return DECLINED;
549 /* now delete directories as far as possible up to our cache root */
550 if (dobj->root) {
551 const char *str_to_copy;
553 str_to_copy = dobj->hdrsfile ? dobj->hdrsfile : dobj->datafile;
554 if (str_to_copy) {
555 char *dir, *slash, *q;
557 dir = apr_pstrdup(p, str_to_copy);
559 /* remove filename */
560 slash = strrchr(dir, '/');
561 *slash = '\0';
564 * now walk our way back to the cache root, delete everything
565 * in the way as far as possible
567 * Note: due to the way we constructed the file names in
568 * header_file and data_file, we are guaranteed that the
569 * cache_root is suffixed by at least one '/' which will be
570 * turned into a terminating null by this loop. Therefore,
571 * we won't either delete or go above our cache root.
573 for (q = dir + dobj->root_len; *q; ) {
574 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL,
575 "disk_cache: Deleting directory %s from cache",
576 dir);
578 rc = apr_dir_remove(dir, p);
579 if (rc != APR_SUCCESS && !APR_STATUS_IS_ENOENT(rc)) {
580 break;
582 slash = strrchr(q, '/');
583 *slash = '\0';
588 return OK;
591 static apr_status_t read_array(request_rec *r, apr_array_header_t* arr,
592 apr_file_t *file) {
593 char w[MAX_STRING_LEN];
594 int p;
595 apr_status_t rv;
597 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "read_array");
599 while (1) {
600 rv = apr_file_gets(w, MAX_STRING_LEN - 1, file);
601 if (rv != APR_SUCCESS) {
602 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
603 "Premature end of vary array.");
604 return rv;
607 p = strlen(w);
608 if (p> 0 && w[p - 1] == '\n') {
609 if (p> 1 && w[p - 2] == CR) {
610 w[p - 2] = '\0';
612 else {
613 w[p - 1] = '\0';
617 /* If we've finished reading the array, break out of the loop. */
618 if (w[0] == '\0') {
619 break;
622 *((const char **) apr_array_push(arr)) = apr_pstrdup(r->pool, w);
625 return APR_SUCCESS;
628 static apr_status_t store_array(apr_file_t *fd, apr_array_header_t* arr) {
629 int i;
630 apr_status_t rv;
631 struct iovec iov[2];
632 apr_size_t amt;
633 const char **elts;
634 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL,"store)array" );
636 elts = (const char **) arr->elts;
638 for (i = 0; i < arr->nelts; i++) {
639 iov[0].iov_base = (char*) elts[i];
640 iov[0].iov_len = strlen(elts[i]);
641 iov[1].iov_base = CRLF;
642 iov[1].iov_len = sizeof(CRLF) - 1;
644 rv = apr_file_writev(fd, (const struct iovec *) &iov, 2,
645 &amt);
646 if (rv != APR_SUCCESS) {
647 return rv;
651 iov[0].iov_base = CRLF;
652 iov[0].iov_len = sizeof(CRLF) - 1;
654 return apr_file_writev(fd, (const struct iovec *) &iov, 1,
655 &amt);
658 static apr_status_t read_table(cache_handle_t *handle, request_rec *r,
659 apr_table_t *table, apr_file_t *file) {
660 char w[MAX_STRING_LEN];
661 char *l;
662 int p;
663 apr_status_t rv;
664 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "read_table");
666 while (1) {
668 /* ### What about APR_EOF? */
669 rv = apr_file_gets(w, MAX_STRING_LEN - 1, file);
670 if (rv != APR_SUCCESS) {
671 ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r,
672 "Premature end of cache headers.");
673 return rv;
676 /* Delete terminal (CR?)LF */
678 p = strlen(w);
679 /* Indeed, the host's '\n':
680 '\012' for UNIX; '\015' for MacOS; '\025' for OS/390
681 -- whatever the script generates.
683 if (p> 0 && w[p - 1] == '\n') {
684 if (p> 1 && w[p - 2] == CR) {
685 w[p - 2] = '\0';
687 else {
688 w[p - 1] = '\0';
692 /* If we've finished reading the headers, break out of the loop. */
693 if (w[0] == '\0') {
694 break;
697 #if APR_CHARSET_EBCDIC
698 /* Chances are that we received an ASCII header text instead of
699 * the expected EBCDIC header lines. Try to auto-detect:
701 if (!(l = strchr(w, ':'))) {
702 int maybeASCII = 0, maybeEBCDIC = 0;
703 unsigned char *cp, native;
704 apr_size_t inbytes_left, outbytes_left;
706 for (cp = w; *cp != '\0'; ++cp) {
707 native = apr_xlate_conv_byte(ap_hdrs_from_ascii, *cp);
708 if (apr_isprint(*cp) && !apr_isprint(native))
709 ++maybeEBCDIC;
710 if (!apr_isprint(*cp) && apr_isprint(native))
711 ++maybeASCII;
713 if (maybeASCII> maybeEBCDIC) {
714 ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
715 "CGI Interface Error: Script headers apparently ASCII: (CGI = %s)",
716 r->filename);
717 inbytes_left = outbytes_left = cp - w;
718 apr_xlate_conv_buffer(ap_hdrs_from_ascii,
719 w, &inbytes_left, w, &outbytes_left);
722 #endif /*APR_CHARSET_EBCDIC*/
724 /* if we see a bogus header don't ignore it. Shout and scream */
725 if (!(l = strchr(w, ':'))) {
726 return APR_EGENERAL;
729 *l++ = '\0';
730 while (*l && apr_isspace(*l)) {
731 ++l;
734 apr_table_add(table, w, l);
737 return APR_SUCCESS;
741 * Reads headers from a buffer and returns an array of headers.
742 * Returns NULL on file error
743 * This routine tries to deal with too long lines and continuation lines.
744 * @@@: XXX: FIXME: currently the headers are passed thru un-merged.
745 * Is that okay, or should they be collapsed where possible?
747 static apr_status_t recall_headers(cache_handle_t *h, request_rec *r) {
748 const char *data;
749 apr_size_t len;
750 apr_bucket *e;
751 unsigned i;
753 disk_cache_object_t *dobj = (disk_cache_object_t *) h->cache_obj->vobj;
755 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "recall_headers");
757 /* This case should not happen... */
758 if (!dobj->hfd) {
759 /* XXX log message */
760 return APR_NOTFOUND;
763 h->req_hdrs = apr_table_make(r->pool, 20);
764 h->resp_hdrs = apr_table_make(r->pool, 20);
766 /* Call routine to read the header lines/status line */
767 read_table(h, r, h->resp_hdrs, dobj->hfd);
768 read_table(h, r, h->req_hdrs, dobj->hfd);
770 // TODO: JUST FOR DEBUGGING
771 apr_table_set(h->resp_hdrs, "Cache-Control", "max-age=30");
773 // TODO: We only really want to add our block hashes if the cache is not fresh
774 // TODO: We could achieve that by adding a filter here on sending the request
775 // and then doing all of this in the filter 'JIT'
776 e = apr_bucket_file_create(dobj->fd, 0, (apr_size_t) dobj->file_size, r->pool,
777 r->connection->bucket_alloc);
779 /* read */
780 apr_bucket_read(e, &data, &len, APR_BLOCK_READ);
782 ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r->server,
783 "crccache: generatinf hash, read %ld bytes",len);
785 // this will be rounded down, but thats okay
786 size_t blocksize = len/BLOCK_COUNT;
788 // sanity check for very small files
789 if (blocksize> 4)
791 ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r->server,
792 "crccache: %d blocks of %ld bytes",BLOCK_COUNT,blocksize);
794 // add one for base 64 overflow and null terminator
795 char hash_set[HASH_HEADER_SIZE+HASH_BASE64_SIZE_PADDING+1];
796 // use buffer to set block size first
797 snprintf(hash_set,HASH_HEADER_SIZE,"%ld",blocksize);
798 apr_table_set(r->headers_in, "Block-Size", hash_set);
800 uint32_t crcs[BLOCK_COUNT+1];
801 crc_of_blocks(data, len, blocksize, 30, crcs);
803 for (i = 0; i < BLOCK_COUNT;++i)
805 // encode the hase into base64
806 encode_30bithash(crcs[i],&hash_set[i*HASH_BASE64_SIZE_TX]);
807 ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r->server,
808 "crccache: block %d, hash %08X",i,crcs[i]);
810 apr_bucket_delete(e);
812 // TODO: do we want to cache the hashes here?
813 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "adding block-hashes header: %s",hash_set);
814 apr_table_set(r->headers_in, "Block-Hashes", hash_set);
816 crccache_client_ctx * ctx;
817 ctx = apr_pcalloc(r->pool, sizeof(*ctx));
818 ctx->bb = apr_brigade_create(r->pool, r->connection->bucket_alloc);
819 ctx->block_size = blocksize;
820 ctx->state = DECODING_NEW_SECTION;
822 // we want to add a filter here so that we can decode the response.
823 // we need access to the original cached data when we get the response as
824 // we need that to fill in the matched blocks.
825 ap_add_output_filter_handle(crccache_decode_filter_handle,
826 ctx, r, r->connection);
828 apr_file_close(dobj->hfd);
830 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
831 "disk_cache: Recalled headers for URL %s", dobj->name);
832 return APR_SUCCESS;
835 static apr_status_t recall_body(cache_handle_t *h, apr_pool_t *p,
836 apr_bucket_brigade *bb) {
837 apr_bucket *e;
838 disk_cache_object_t *dobj = (disk_cache_object_t*) h->cache_obj->vobj;
840 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL,"recall_body" );
842 e = apr_bucket_file_create(dobj->fd, 0, (apr_size_t) dobj->file_size, p,
843 bb->bucket_alloc);
845 APR_BRIGADE_INSERT_HEAD(bb, e);
846 e = apr_bucket_eos_create(bb->bucket_alloc);
847 APR_BRIGADE_INSERT_TAIL(bb, e);
849 return APR_SUCCESS;
852 static apr_status_t store_table(apr_file_t *fd, apr_table_t *table) {
853 int i;
854 apr_status_t rv;
855 struct iovec iov[4];
856 apr_size_t amt;
857 apr_table_entry_t *elts;
859 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, NULL,"store_table" );
861 elts = (apr_table_entry_t *) apr_table_elts(table)->elts;
862 for (i = 0; i < apr_table_elts(table)->nelts; ++i) {
863 if (elts[i].key != NULL) {
864 iov[0].iov_base = elts[i].key;
865 iov[0].iov_len = strlen(elts[i].key);
866 iov[1].iov_base = ": ";
867 iov[1].iov_len = sizeof(": ") - 1;
868 iov[2].iov_base = elts[i].val;
869 iov[2].iov_len = strlen(elts[i].val);
870 iov[3].iov_base = CRLF;
871 iov[3].iov_len = sizeof(CRLF) - 1;
873 rv = apr_file_writev(fd, (const struct iovec *) &iov, 4,
874 &amt);
875 if (rv != APR_SUCCESS) {
876 return rv;
880 iov[0].iov_base = CRLF;
881 iov[0].iov_len = sizeof(CRLF) - 1;
882 rv = apr_file_writev(fd, (const struct iovec *) &iov, 1,
883 &amt);
884 return rv;
887 static apr_status_t store_headers(cache_handle_t *h, request_rec *r,
888 cache_info *info) {
889 disk_cache_conf *conf = ap_get_module_config(r->server->module_config,
890 &crccache_client_module);
891 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "store_headers");
893 apr_status_t rv;
894 apr_size_t amt;
895 disk_cache_object_t *dobj = (disk_cache_object_t*) h->cache_obj->vobj;
897 disk_cache_info_t disk_info;
898 struct iovec iov[2];
900 /* This is flaky... we need to manage the cache_info differently */
901 h->cache_obj->info = *info;
903 if (r->headers_out) {
904 const char *tmp;
906 tmp = apr_table_get(r->headers_out, "Vary");
908 if (tmp) {
909 apr_array_header_t* varray;
910 apr_uint32_t format = VARY_FORMAT_VERSION;
912 /* If we were initially opened as a vary format, rollback
913 * that internal state for the moment so we can recreate the
914 * vary format hints in the appropriate directory.
916 if (dobj->prefix) {
917 dobj->hdrsfile = dobj->prefix;
918 dobj->prefix = NULL;
921 mkdir_structure(conf, dobj->hdrsfile, r->pool);
923 rv = apr_file_mktemp(&dobj->tfd, dobj->tempfile,
924 APR_CREATE | APR_WRITE | APR_BINARY | APR_EXCL,
925 r->pool);
927 if (rv != APR_SUCCESS) {
928 return rv;
931 amt = sizeof(format);
932 apr_file_write(dobj->tfd, &format, &amt);
934 amt = sizeof(info->expire);
935 apr_file_write(dobj->tfd, &info->expire, &amt);
937 varray = apr_array_make(r->pool, 6, sizeof(char*));
938 tokens_to_array(r->pool, tmp, varray);
940 store_array(dobj->tfd, varray);
942 apr_file_close(dobj->tfd);
944 dobj->tfd = NULL;
946 rv = safe_file_rename(conf, dobj->tempfile, dobj->hdrsfile,
947 r->pool);
948 if (rv != APR_SUCCESS) {
949 ap_log_error(APLOG_MARK, APLOG_WARNING, rv, r->server,
950 "disk_cache: rename tempfile to varyfile failed: %s -> %s",
951 dobj->tempfile, dobj->hdrsfile);
952 apr_file_remove(dobj->tempfile, r->pool);
953 return rv;
956 dobj->tempfile = apr_pstrcat(r->pool, conf->cache_root, AP_TEMPFILE, NULL);
957 tmp = regen_key(r->pool, r->headers_in, varray, dobj->name);
958 dobj->prefix = dobj->hdrsfile;
959 dobj->hashfile = NULL;
960 dobj->datafile = data_file(r->pool, conf, dobj, tmp);
961 dobj->hdrsfile = header_file(r->pool, conf, dobj, tmp);
966 rv = apr_file_mktemp(&dobj->hfd, dobj->tempfile,
967 APR_CREATE | APR_WRITE | APR_BINARY |
968 APR_BUFFERED | APR_EXCL, r->pool);
970 if (rv != APR_SUCCESS) {
971 return rv;
974 disk_info.format = DISK_FORMAT_VERSION;
975 disk_info.date = info->date;
976 disk_info.expire = info->expire;
977 disk_info.entity_version = dobj->disk_info.entity_version++;
978 disk_info.request_time = info->request_time;
979 disk_info.response_time = info->response_time;
980 disk_info.status = info->status;
982 disk_info.name_len = strlen(dobj->name);
984 iov[0].iov_base = (void*)&disk_info;
985 iov[0].iov_len = sizeof(disk_cache_info_t);
986 iov[1].iov_base = (void*)dobj->name;
987 iov[1].iov_len = disk_info.name_len;
989 rv = apr_file_writev(dobj->hfd, (const struct iovec *) &iov, 2, &amt);
990 if (rv != APR_SUCCESS) {
991 return rv;
994 if (r->headers_out) {
995 apr_table_t *headers_out;
997 headers_out = ap_cache_cacheable_hdrs_out(r->pool, r->headers_out,
998 r->server);
1000 if (!apr_table_get(headers_out, "Content-Type")
1001 && r->content_type) {
1002 apr_table_setn(headers_out, "Content-Type",
1003 ap_make_content_type(r, r->content_type));
1006 headers_out = apr_table_overlay(r->pool, headers_out,
1007 r->err_headers_out);
1008 rv = store_table(dobj->hfd, headers_out);
1009 if (rv != APR_SUCCESS) {
1010 return rv;
1014 /* Parse the vary header and dump those fields from the headers_in. */
1015 /* FIXME: Make call to the same thing cache_select calls to crack Vary. */
1016 if (r->headers_in) {
1017 apr_table_t *headers_in;
1019 headers_in = ap_cache_cacheable_hdrs_out(r->pool, r->headers_in,
1020 r->server);
1021 rv = store_table(dobj->hfd, headers_in);
1022 if (rv != APR_SUCCESS) {
1023 return rv;
1027 apr_file_close(dobj->hfd); /* flush and close */
1029 /* Remove old file with the same name. If remove fails, then
1030 * perhaps we need to create the directory tree where we are
1031 * about to write the new headers file.
1033 rv = apr_file_remove(dobj->hdrsfile, r->pool);
1034 if (rv != APR_SUCCESS) {
1035 mkdir_structure(conf, dobj->hdrsfile, r->pool);
1038 rv = safe_file_rename(conf, dobj->tempfile, dobj->hdrsfile, r->pool);
1039 if (rv != APR_SUCCESS) {
1040 ap_log_error(APLOG_MARK, APLOG_WARNING, rv, r->server,
1041 "disk_cache: rename tempfile to hdrsfile failed: %s -> %s",
1042 dobj->tempfile, dobj->hdrsfile);
1043 apr_file_remove(dobj->tempfile, r->pool);
1044 return rv;
1047 dobj->tempfile = apr_pstrcat(r->pool, conf->cache_root, AP_TEMPFILE, NULL);
1049 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1050 "disk_cache: Stored headers for URL %s", dobj->name);
1051 return APR_SUCCESS;
1054 static apr_status_t store_body(cache_handle_t *h, request_rec *r,
1055 apr_bucket_brigade *bb) {
1056 apr_bucket *e;
1057 apr_status_t rv;
1059 disk_cache_object_t *dobj = (disk_cache_object_t *) h->cache_obj->vobj;
1060 disk_cache_conf *conf = ap_get_module_config(r->server->module_config,
1061 &crccache_client_module);
1063 /* We write to a temp file and then atomically rename the file over
1064 * in file_cache_el_final().
1066 if (!dobj->tfd) {
1067 rv = apr_file_mktemp(&dobj->tfd, dobj->tempfile, APR_CREATE | APR_WRITE
1068 | APR_BINARY | APR_BUFFERED | APR_EXCL, r->pool);
1069 if (rv != APR_SUCCESS) {
1070 return rv;
1072 dobj->file_size = 0;
1075 for (e = APR_BRIGADE_FIRST(bb); e != APR_BRIGADE_SENTINEL(bb); e = APR_BUCKET_NEXT(e)) {
1076 const char *str;
1077 apr_size_t length, written;
1078 rv = apr_bucket_read(e, &str, &length, APR_BLOCK_READ);
1079 if (rv != APR_SUCCESS) {
1080 ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
1081 "cache_disk: Error when reading bucket for URL %s",
1082 h->cache_obj->key);
1083 /* Remove the intermediate cache file and return non-APR_SUCCESS */
1084 file_cache_errorcleanup(dobj, r);
1085 return rv;
1087 rv = apr_file_write_full(dobj->tfd, str, length, &written);
1088 if (rv != APR_SUCCESS) {
1089 ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server,
1090 "cache_disk: Error when writing cache file for URL %s",
1091 h->cache_obj->key);
1092 /* Remove the intermediate cache file and return non-APR_SUCCESS */
1093 file_cache_errorcleanup(dobj, r);
1094 return rv;
1096 dobj->file_size += written;
1097 if (dobj->file_size> conf->maxfs) {
1098 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1099 "cache_disk: URL %s failed the size check "
1100 "(%" APR_OFF_T_FMT " > %" APR_OFF_T_FMT ")",
1101 h->cache_obj->key, dobj->file_size, conf->maxfs);
1102 /* Remove the intermediate cache file and return non-APR_SUCCESS */
1103 file_cache_errorcleanup(dobj, r);
1104 return APR_EGENERAL;
1108 /* Was this the final bucket? If yes, close the temp file and perform
1109 * sanity checks.
1111 if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(bb))) {
1112 if (r->connection->aborted || r->no_cache) {
1113 ap_log_error(APLOG_MARK, APLOG_INFO, 0, r->server,
1114 "disk_cache: Discarding body for URL %s "
1115 "because connection has been aborted.",
1116 h->cache_obj->key);
1117 /* Remove the intermediate cache file and return non-APR_SUCCESS */
1118 file_cache_errorcleanup(dobj, r);
1119 return APR_EGENERAL;
1121 if (dobj->file_size < conf->minfs) {
1122 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1123 "cache_disk: URL %s failed the size check "
1124 "(%" APR_OFF_T_FMT " < %" APR_OFF_T_FMT ")",
1125 h->cache_obj->key, dobj->file_size, conf->minfs);
1126 /* Remove the intermediate cache file and return non-APR_SUCCESS */
1127 file_cache_errorcleanup(dobj, r);
1128 return APR_EGENERAL;
1131 /* All checks were fine. Move tempfile to final destination */
1132 /* Link to the perm file, and close the descriptor */
1133 file_cache_el_final(dobj, r);
1134 ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server,
1135 "disk_cache: Body for URL %s cached.", dobj->name);
1138 return APR_SUCCESS;
1142 * CACHE_DECODE filter
1143 * ----------------
1145 * Deliver cached content (headers and body) up the stack.
1147 static int crccache_decode_filter(ap_filter_t *f, apr_bucket_brigade *bb) {
1148 apr_bucket *e;
1149 request_rec *r = f->r;
1150 // TODO: set up context type struct
1151 crccache_client_ctx *ctx = f->ctx;
1153 // TODO: make this work if we have multiple encodings
1154 const char * content_encoding;
1155 content_encoding = apr_table_get(r->headers_out, "Content-Encoding");
1156 if (content_encoding == NULL || strcmp(CRCCACHE_ENCODING, content_encoding)
1157 != 0) {
1158 ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r->server,
1159 "CRCSYNC not decoding, content encoding bad (%s)", content_encoding?content_encoding:"NULL");
1160 ap_remove_output_filter(f);
1161 return ap_pass_brigade(f->next, bb);
1163 // TODO: Remove crcsync from the content encoding header
1165 // TODO: Fix up the etag as well
1167 /* Do nothing if asked to filter nothing. */
1168 if (APR_BRIGADE_EMPTY(bb)) {
1169 return ap_pass_brigade(f->next, bb);
1172 /* We require that we have a context already, otherwise we dont have our cached file
1173 * to fill in the gaps with.
1175 if (!ctx) {
1176 ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r->server,
1177 "No context availavle %s", r->uri);
1178 ap_remove_output_filter(f);
1179 return ap_pass_brigade(f->next, bb);
1182 while (!APR_BRIGADE_EMPTY(bb))
1184 const char *data;
1185 // apr_bucket *b;
1186 apr_size_t len;
1188 e = APR_BRIGADE_FIRST(bb);
1190 if (APR_BUCKET_IS_EOS(e)) {
1192 /* Remove EOS from the old list, and insert into the new. */
1193 APR_BUCKET_REMOVE(e);
1194 APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
1196 /* This filter is done once it has served up its content */
1197 ap_remove_output_filter(f);
1199 /* Okay, we've seen the EOS.
1200 * Time to pass it along down the chain.
1202 return ap_pass_brigade(f->next, ctx->bb);
1205 if (APR_BUCKET_IS_FLUSH(e)) {
1206 apr_status_t rv;
1208 /* Remove flush bucket from old brigade anf insert into the new. */
1209 APR_BUCKET_REMOVE(e);
1210 APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
1211 rv = ap_pass_brigade(f->next, ctx->bb);
1212 if (rv != APR_SUCCESS) {
1213 return rv;
1215 continue;
1218 if (APR_BUCKET_IS_METADATA(e)) {
1220 * Remove meta data bucket from old brigade and insert into the
1221 * new.
1223 APR_BUCKET_REMOVE(e);
1224 APR_BRIGADE_INSERT_TAIL(ctx->bb, e);
1225 continue;
1228 /* read */
1229 apr_bucket_read(e, &data, &len, APR_BLOCK_READ);
1231 size_t consumed_bytes = 0;
1232 while (consumed_bytes < len)
1234 // no guaruntee that our buckets line up with our encoding sections
1235 // so we need a processing state machine stored in our context
1236 switch (ctx->state)
1238 case DECODING_NEW_SECTION:
1239 ctx->processed_length = 0;
1241 // check if we have a literal section or a block section
1242 if (data[consumed_bytes] == ENCODING_LITERAL)
1243 ctx->state = DECODING_LITERAL_HEADER;
1244 else if (data[consumed_bytes] == ENCODING_BLOCK)
1245 ctx->state = DECODING_BLOCK_HEADER;
1246 else
1247 ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r->server,
1248 "CRCSYNC-DECODE, unknown section %d(%c)",data[consumed_bytes],data[consumed_bytes]);
1250 ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r->server,
1251 "CRCSYNC-DECODE, found a new section %d",ctx->state);
1252 consumed_bytes++;
1253 break;
1254 case DECODING_LITERAL_HEADER:
1255 // how long a literal do we have
1256 if (ctx->processed_length == 0 && (len-consumed_bytes)>=4)
1258 ctx->section_length = ntohl(*(unsigned long*)&data[consumed_bytes]);
1259 consumed_bytes+=4;
1260 ctx->state = DECODING_LITERAL;
1261 ctx->processed_length = 0;
1262 ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r->server,
1263 "CRCSYNC-DECODE, lit section, length %d",ctx->section_length);
1264 break;
1266 else
1268 // we didnt get the whole length in one go, so assemble it in our ctx buffer
1269 int remaining_header_count = MIN(len - consumed_bytes, 4 - ctx->processed_length);
1270 memcpy(&ctx->section_header[ctx->processed_length],&data[consumed_bytes],remaining_header_count);
1271 ctx->processed_length += remaining_header_count;
1272 consumed_bytes += remaining_header_count;
1273 if (ctx->processed_length == 4)
1275 ctx->section_length = ntohl(*(unsigned long*)&data[consumed_bytes]);
1276 ctx->state = DECODING_LITERAL;
1277 ctx->processed_length = 0;
1278 ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r->server,
1279 "CRCSYNC-DECODE, literal section, lenfth %d",ctx->section_length);
1280 break;
1283 break;
1284 case DECODING_BLOCK_HEADER:
1286 unsigned char block_number = data[consumed_bytes];
1287 consumed_bytes++;
1288 ctx->state = DECODING_NEW_SECTION;
1290 // TODO: Output the indicated block here
1291 ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r->server,
1292 "CRCSYNC-DECODE, block section, block %d",block_number);
1294 char * buf = apr_palloc(r->pool, ctx->block_size);
1295 memset(buf,'B',ctx->block_size);
1296 apr_bucket * b = apr_bucket_pool_create(buf, ctx->block_size, r->pool, f->c->bucket_alloc);
1297 APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
1299 break;
1301 case DECODING_LITERAL:
1303 unsigned literal_data_len = MIN(len - consumed_bytes, ctx->section_length - ctx->processed_length);
1304 // TODO dump literal data here
1305 char * buf = apr_palloc(r->pool, literal_data_len);
1306 memcpy(buf,&data[consumed_bytes],literal_data_len);
1307 apr_bucket * b = apr_bucket_pool_create(buf, literal_data_len, r->pool, f->c->bucket_alloc);
1308 APR_BRIGADE_INSERT_TAIL(ctx->bb, b);
1310 consumed_bytes += literal_data_len;
1311 ctx->processed_length += literal_data_len;
1312 ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r->server,
1313 "CRCSYNC-DECODE, lit, bytes %d, %d %d",literal_data_len, ctx->processed_length, ctx->section_length);
1315 if (ctx->processed_length == ctx->section_length)
1317 ctx->state = DECODING_NEW_SECTION;
1319 break;
1321 default:
1323 ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r->server,
1324 "CRCSYNC-DECODE, unknown state %d, terminating transaction",ctx->state);
1325 apr_brigade_cleanup(bb);
1326 return APR_SUCCESS;
1329 APR_BUCKET_REMOVE(e);
1333 apr_brigade_cleanup(bb);
1334 return APR_SUCCESS;
1337 static void *create_config(apr_pool_t *p, server_rec *s) {
1338 disk_cache_conf *conf = apr_pcalloc(p, sizeof(disk_cache_conf));
1340 /* XXX: Set default values */
1341 conf->dirlevels = DEFAULT_DIRLEVELS;
1342 conf->dirlength = DEFAULT_DIRLENGTH;
1343 conf->maxfs = DEFAULT_MAX_FILE_SIZE;
1344 conf->minfs = DEFAULT_MIN_FILE_SIZE;
1346 conf->cache_root = NULL;
1347 conf->cache_root_len = 0;
1349 return conf;
1353 * mod_disk_cache configuration directives handlers.
1355 static const char *set_cache_root(cmd_parms *parms, void *in_struct_ptr,
1356 const char *arg) {
1357 disk_cache_conf *conf = ap_get_module_config(parms->server->module_config,
1358 &crccache_client_module);
1359 conf->cache_root = arg;
1360 conf->cache_root_len = strlen(arg);
1361 /* TODO: canonicalize cache_root and strip off any trailing slashes */
1363 return NULL;
1367 * Consider eliminating the next two directives in favor of
1368 * Ian's prime number hash...
1369 * key = hash_fn( r->uri)
1370 * filename = "/key % prime1 /key %prime2/key %prime3"
1372 static const char *set_cache_dirlevels(cmd_parms *parms, void *in_struct_ptr,
1373 const char *arg) {
1374 disk_cache_conf *conf = ap_get_module_config(parms->server->module_config,
1375 &crccache_client_module);
1376 int val = atoi(arg);
1377 if (val < 1)
1378 return "CacheDirLevels value must be an integer greater than 0";
1379 if (val * conf->dirlength > CACHEFILE_LEN)
1380 return "CacheDirLevels*CacheDirLength value must not be higher than 20";
1381 conf->dirlevels = val;
1382 return NULL;
1384 static const char *set_cache_dirlength(cmd_parms *parms, void *in_struct_ptr,
1385 const char *arg) {
1386 disk_cache_conf *conf = ap_get_module_config(parms->server->module_config,
1387 &crccache_client_module);
1388 int val = atoi(arg);
1389 if (val < 1)
1390 return "CacheDirLength value must be an integer greater than 0";
1391 if (val * conf->dirlevels > CACHEFILE_LEN)
1392 return "CacheDirLevels*CacheDirLength value must not be higher than 20";
1394 conf->dirlength = val;
1395 return NULL;
1398 static const char *set_cache_minfs(cmd_parms *parms, void *in_struct_ptr,
1399 const char *arg) {
1400 disk_cache_conf *conf = ap_get_module_config(parms->server->module_config,
1401 &crccache_client_module);
1403 if (apr_strtoff(&conf->minfs, arg, NULL, 0) != APR_SUCCESS || conf->minfs
1404 < 0) {
1405 return "CacheMinFileSize argument must be a non-negative integer representing the min size of a file to cache in bytes.";
1407 return NULL;
1410 static const char *set_cache_maxfs(cmd_parms *parms, void *in_struct_ptr,
1411 const char *arg) {
1412 disk_cache_conf *conf = ap_get_module_config(parms->server->module_config,
1413 &crccache_client_module);
1414 if (apr_strtoff(&conf->maxfs, arg, NULL, 0) != APR_SUCCESS || conf->maxfs
1415 < 0) {
1416 return "CacheMaxFileSize argument must be a non-negative integer representing the max size of a file to cache in bytes.";
1418 return NULL;
1421 static const command_rec disk_cache_cmds[] = { AP_INIT_TAKE1("CacheRoot", set_cache_root, NULL, RSRC_CONF,
1422 "The directory to store cache files"), AP_INIT_TAKE1("CacheDirLevels", set_cache_dirlevels, NULL, RSRC_CONF,
1423 "The number of levels of subdirectories in the cache"), AP_INIT_TAKE1("CacheDirLength", set_cache_dirlength, NULL, RSRC_CONF,
1424 "The number of characters in subdirectory names"), AP_INIT_TAKE1("CacheMinFileSize", set_cache_minfs, NULL, RSRC_CONF,
1425 "The minimum file size to cache a document"), AP_INIT_TAKE1("CacheMaxFileSize", set_cache_maxfs, NULL, RSRC_CONF,
1426 "The maximum file size to cache a document"), { NULL } };
1428 static const cache_provider crccache_client_provider = { &remove_entity,
1429 &store_headers, &store_body, &recall_headers, &recall_body,
1430 &create_entity, &open_entity, &remove_url, };
1432 static void disk_cache_register_hook(apr_pool_t *p) {
1433 /* cache initializer */
1434 ap_register_provider(p, CACHE_PROVIDER_GROUP, "crccache_client", "0",
1435 &crccache_client_provider);
1437 * CACHE_OUT must go into the filter chain after a possible DEFLATE
1438 * filter to ensure that already compressed cache objects do not
1439 * get compressed again. Incrementing filter type by 1 ensures
1440 * his happens.
1442 crccache_decode_filter_handle = ap_register_output_filter(
1443 "CRCCACHE_DECODE", crccache_decode_filter, NULL,
1444 AP_FTYPE_CONTENT_SET + 1);
1447 module AP_MODULE_DECLARE_DATA crccache_client_module = {
1448 STANDARD20_MODULE_STUFF, NULL, /* create per-directory config structure */
1449 NULL , /* merge per-directory config structures */
1450 create_config, /* create per-server config structure */
1451 NULL , /* merge per-server config structures */
1452 disk_cache_cmds, /* command apr_table_t */
1453 disk_cache_register_hook /* register hooks */