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 server module
19 * This module is designed to run as a proxy server on the remote end of a slow
20 * internet link. This module uses a crc32 running hash algorithm to reduce
21 * data transfer in cached but modified downstream files.
23 * CRC algorithm uses the crcsync library created by Rusty Russel
25 * Authors: Toby Collett (2009), Alex Wulms (2009)
31 #include <apr_file_io.h>
32 #include <apr_strings.h>
33 #include <apr_base64.h>
36 #include "ap_provider.h"
38 #include "util_filter.h"
39 #include "util_script.h"
40 #include "util_charset.h"
43 #include "ap_log_helper.h"
46 #include "mod_crccache_server.h"
48 #include <crcsync/crcsync.h>
51 module AP_MODULE_DECLARE_DATA crccache_server_module
;
53 // Possible states for the output compression
55 COMPRESSION_BUFFER_EMPTY
,
56 COMPRESSION_FIRST_DATA_RECEIVED
,
57 COMPRESSION_FIRST_BLOCK_WRITTEN
,
59 } compression_state_t
;
62 typedef struct encodings_s encodings_t
;
68 typedef struct decoder_modules_s decoder_modules_t
;
69 struct decoder_modules_s
{
70 decoder_modules_t
*next
;
72 encodings_t
*encodings
;
75 typedef struct regexs_s regexs_t
;
80 encodings_t
*mime_types
;
83 /* Static information about the crccache server */
86 decoder_modules_t
*decoder_modules
;
87 unsigned decoder_modules_cnt
;
89 regexs_t
*regexs_tail
;
90 } crccache_server_conf
;
93 static void *crccache_server_create_config(apr_pool_t
*p
, server_rec
*s
) {
94 crccache_server_conf
*conf
= apr_pcalloc(p
, sizeof(crccache_server_conf
));
96 conf
->decoder_modules
= NULL
;
97 conf
->decoder_modules_cnt
= 0;
99 conf
->regexs_tail
= NULL
;
103 typedef enum { GS_INIT
, GS_HEADERS_SAVED
, GS_ENCODING
} global_state_t
;
105 typedef struct crccache_ctx_t
{
106 global_state_t global_state
;
107 char *old_content_encoding
;
109 unsigned char *buffer
;
110 size_t buffer_digest_getpos
;
111 size_t buffer_read_getpos
;
112 size_t buffer_putpos
;
114 long crc_read_block_result
;
115 size_t crc_read_block_ndigested
;
116 apr_bucket_brigade
*bb
;
117 unsigned block_count
;
119 size_t tail_block_size
;
121 struct crc_context
*crcctx
;
124 size_t tx_uncompressed_length
;
125 compression_state_t compression_state
;
126 z_stream
*compression_stream
;
127 struct apr_sha1_ctx_t sha1_ctx
;
128 int debug_skip_writing
; // ____
133 * Only enable CRCCache Server when requested through the config file
134 * so that the user can switch CRCCache server on in a specific virtual server
136 static const char *set_crccache_server(cmd_parms
*parms
, void *dummy
, int flag
)
138 crccache_server_conf
*conf
= ap_get_module_config(parms
->server
->module_config
,
139 &crccache_server_module
);
140 conf
->enabled
= flag
;
144 static const char *add_crccache_similar_page_regex (cmd_parms
*parms
, void *in_struct_ptr
, const char *arg
)
146 crccache_server_conf
*conf
= ap_get_module_config(parms
->server
->module_config
,
147 &crccache_server_module
);
149 // Allocate the regexs structure
150 regexs_t
*regexs
= apr_palloc(parms
->pool
, sizeof(regexs_t
));
153 return "Out of memory exception while allocating regexs structure";
155 regexs
->preg
= apr_palloc(parms
->pool
, sizeof(ap_regex_t
));
158 return "Out of memory exception while allocating regexs->preg field";
160 char *parsed_arg
= apr_pstrdup(parms
->pool
, arg
);
161 if (parsed_arg
== NULL
)
163 return "Out of memory exception while allocating parsed_args field";
166 char *separator
= strstr(parsed_arg
, ";"); // Find separator between mime-types and regex
167 if (separator
== NULL
)
169 return "Can't find ; separator between mime-types and regular expression";
171 *separator
++ = 0; // Null-terminate the mime-type(s) part of the string
172 while (*separator
== ' ')
174 separator
++; // skip any whitespace before the regex
178 return "Found an empty regular expression after the ; separator";
180 regexs
->regex
= separator
; // Regex starts here
181 // Compile the regular expression
182 int rslt
= ap_regcomp(regexs
->preg
, regexs
->regex
, 0);
185 ap_log_error(APLOG_MARK
, APLOG_ERR
, APR_SUCCESS
, parms
->server
,
186 "CRCCACHE-ENCODE ap_regcomp return code: %d for regex %s", rslt
, regexs
->regex
);
187 return "Failure to compile regular expression. See error log for further details";
190 // Now start splitting the mime-types themselves into separate tokens
191 ap_regex_t
*validate_mime_type_regex
= apr_palloc(parms
->pool
, sizeof(ap_regex_t
));
192 if (validate_mime_type_regex
== NULL
)
194 return "Out of memory exception while allocationg validate_mime_type_regex structure";
196 rslt
= ap_regcomp(validate_mime_type_regex
,
197 "^((\\*/\\*)|([^]()<>@,;:\\\"/[?.=* ]+/(\\*|[^]()<>@,;:\\\"/[?.=* ]+)))$", 0);
200 ap_log_error(APLOG_MARK
, APLOG_ERR
, APR_SUCCESS
, parms
->server
,
201 "CRCCACHE-ENCODE ap_regcomp return code: %d for validate_mime_type_regex", rslt
);
202 return "Failure to compile regular expression. See error log for further details";
204 regexs
->mime_types
= NULL
;
206 char *token
= apr_strtok(parsed_arg
, ", ", &last
);
207 while (token
!= NULL
)
209 if (ap_regexec(validate_mime_type_regex
, token
, 0, NULL
, AP_REG_ICASE
) != 0)
211 ap_log_error(APLOG_MARK
, APLOG_ERR
, APR_SUCCESS
, parms
->server
,
212 "CRCCACHE-ENCODE ap_regexec returned mismatch for mime-type %s", token
);
213 return "Invalid mime-type format specified. See error log for further details";
215 encodings_t
*mime_type
= apr_palloc(parms
->pool
, sizeof(*mime_type
));
216 if (mime_type
== NULL
)
218 return "Out of memory exception while allocationg mime_type structure";
220 mime_type
->next
= regexs
->mime_types
;
221 regexs
->mime_types
= mime_type
;
222 // Store the wild-card mime-type (*/*) as a NULL pointer so that it can be quickly recognized
223 mime_type
->encoding
= (strcmp(token
, "*/*") == 0) ? NULL
: token
;
224 token
= apr_strtok(NULL
, ", ", &last
);
226 ap_regfree(validate_mime_type_regex
); // Free the memory used by the mime type validation regex.
227 if (regexs
->mime_types
== NULL
)
229 return "Could not find any mime-types before the ; separator";
232 // Add regular expression to the tail of the regular expressions list
234 if (conf
->regexs_tail
== NULL
)
236 conf
->regexs
= regexs
;
237 conf
->regexs_tail
= regexs
;
241 conf
->regexs_tail
->next
= regexs
;
242 conf
->regexs_tail
= regexs
;
249 static const char *set_crccache_decoder_module(cmd_parms
*parms
, void *in_struct_ptr
, const char *arg
)
251 crccache_server_conf
*conf
= ap_get_module_config(parms
->server
->module_config
,
252 &crccache_server_module
);
253 decoder_modules_t
*decoder_modules
= apr_palloc(parms
->pool
, sizeof(*decoder_modules
));
254 if (decoder_modules
== NULL
)
256 return "Out of memory exception while allocating decoder_modules structure";
261 char *data
= apr_pstrdup(parms
->pool
, arg
);
264 return "Out of memory exception while parsing DecoderModule parameter";
267 tok
= apr_strtok(data
, ": ", &last
);
270 return "DecoderModule value must be of format: filtername:encoding[,encoding]*";
273 decoder_modules
->name
= apr_pstrdup(parms
->pool
, tok
);
274 if (decoder_modules
->name
== NULL
)
276 return "Out of memory exception while storing name in decoder_module structure";
279 tok
= apr_strtok(NULL
, ": ", &last
);
282 return "DecoderModule value must be of format: filtername:encoding[,encoding]*";
285 decoder_modules
->encodings
= NULL
;
286 for (tok
= apr_strtok(tok
, ", ", &last
); tok
!= NULL
; tok
= apr_strtok(NULL
, ", ", &last
))
288 encodings_t
*encodings
= apr_palloc(parms
->pool
, sizeof(*encodings
));
289 if (encodings
== NULL
)
291 return "Out of memory exception while allocating encoding structure";
294 encodings
->encoding
= apr_pstrdup(parms
->pool
, tok
);
295 if (encodings
->encoding
== NULL
)
297 return "Out of memory exception while storing encoding value in encoding structure";
300 // Insert new encoding to the head of the encodings list
301 encodings
->next
= decoder_modules
->encodings
;
302 decoder_modules
->encodings
= encodings
;
305 // Insert (new) decoder module to the head of the decoder_modules list
306 decoder_modules
->next
= conf
->decoder_modules
;
307 conf
->decoder_modules
= decoder_modules
;
308 conf
->decoder_modules_cnt
++;
313 static const command_rec crccache_server_cmds
[] =
315 AP_INIT_FLAG("CRCcacheServer", set_crccache_server
, NULL
, RSRC_CONF
, "Enable the CRCCache server in this virtual server"),
316 AP_INIT_TAKE1("DecoderModule", set_crccache_decoder_module
, NULL
, RSRC_CONF
, "DecoderModules to decode content-types (e.g. INFLATE:gzip,x-gzip)"),
317 AP_INIT_ITERATE("AddSimilarPageRegEx", add_crccache_similar_page_regex
, NULL
, RSRC_CONF
, "Regular expression to indicate which pages are similar to each other, per mime-type"),
321 static ap_filter_rec_t
*crccache_out_filter_handle
;
322 static ap_filter_rec_t
*crccache_out_save_headers_filter_handle
;
325 int decode_if_block_header(request_rec
*r
, const char * header
, int * version
, size_t * file_size
, char ** hashes
)
329 *hashes
= NULL
; // this will be set to the appropriate value when hashes have been extracted
330 char *extracted_hashes
; // buffer to extract hashes to
335 size_t headerlen
= strlen(header
);
336 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, 0, r
->server
, "CRCCACHE-ENCODE headerlen: %" APR_SIZE_T_FMT
, headerlen
);
338 extracted_hashes
= malloc(headerlen
);
339 if (extracted_hashes
== NULL
)
341 ap_log_error(APLOG_MARK
, APLOG_ERR
, 0, r
->server
, "CRCCACHE-ENCODE can not allocate buffer to extract hashes into");
345 for (ii
= 0; ii
< headerlen
;++ii
)
347 if (header
[ii
] == ';' || ii
== headerlen
-1)
349 sscanf(&header
[start
]," v=%d",version
);
350 sscanf(&header
[start
]," fs=%zu",file_size
);
351 if (sscanf(&header
[start
]," h=%s", extracted_hashes
))
353 *hashes
= extracted_hashes
;
361 ap_log_error(APLOG_MARK
, APLOG_ERR
, 0, r
->server
, "CRCCACHE-ENCODE no hashes reported in header");
366 ap_log_error(APLOG_MARK
, APLOG_ERR
, 0, r
->server
, "CRCCACHE-ENCODE Unsupported header version, %d",*version
);
371 ap_log_error(APLOG_MARK
, APLOG_ERR
, 0, r
->server
, "CRCCACHE-ENCODE no file size reported in header");
374 if (rslt
!= 0 && *hashes
!= NULL
)
382 static int crccache_server_header_parser_handler(request_rec
*r
) {
383 crccache_server_conf
*conf
= ap_get_module_config(r
->server
->module_config
,
384 &crccache_server_module
);
387 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, 0, r
->server
, "CRCCACHE-ENCODE Checking for headers, for uri %s", r
->unparsed_uri
);
389 header
= apr_table_get(r
->headers_in
, BLOCK_HEADER
);
390 crccache_ctx
*ctx
= apr_pcalloc(r
->pool
, sizeof(*ctx
));
391 ctx
->global_state
= GS_INIT
;
392 ctx
->old_content_encoding
= NULL
;
393 ctx
->old_etag
= NULL
;
401 if (decode_if_block_header(r
,header
,&version
,&file_size
,&hashes
) < 0)
403 // failed to decode if block header so only put the Capability header in the response
404 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, 0, r
->server
, "CRCCACHE-ENCODE Failed to decode block header (%s: %s)",BLOCK_HEADER
, header
);
405 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, 0, r
->server
, "CRCCACHE-ENCODE Enabling crccache_out_filter to set Capability and Crcsync-Similar header only");
406 ap_add_output_filter_handle(crccache_out_filter_handle
, ctx
, r
, r
->connection
);
409 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, 0, r
->server
, "CRCCACHE-ENCODE Block Hashes header found (hashes: %s)",hashes
);
413 // Add the filter to save the headers, so that they can be restored after an optional INFLATE or other decoder module
414 ap_add_output_filter_handle(crccache_out_save_headers_filter_handle
,
415 ctx
, r
, r
->connection
);
417 char *accept_encoding
= apr_pstrdup(r
->pool
, apr_table_get(r
->headers_in
, ACCEPT_ENCODING_HEADER
));
418 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, 0, r
->server
, "CRCCACHE-ENCODE Incoming Accept-Encoding header: %s", accept_encoding
== NULL
? "NULL" : accept_encoding
);
419 if (accept_encoding
!= NULL
)
421 decoder_modules_t
*required_dms
[conf
->decoder_modules_cnt
];
422 unsigned required_dms_size
= 0;
425 decoder_modules_t
*dm
;
428 // Build the list of filter modules to handle the requested encodings and
429 // remove all non-supported encodings from the header
430 apr_table_unset(r
->headers_in
, ACCEPT_ENCODING_HEADER
);
431 for (tok
= apr_strtok(accept_encoding
, ", ", &last
); tok
!= NULL
; tok
= apr_strtok(NULL
, ", ", &last
)) {
432 for (dm
= conf
->decoder_modules
; dm
!= NULL
; dm
= dm
->next
) {
433 for (enc
= dm
->encodings
; enc
!= NULL
; enc
= enc
->next
) {
434 if (strcmp(tok
, enc
->encoding
) == 0)
436 // This module supports the requested encoding
437 // Add it to the list if it is not already present
438 for (cnt
= 0; cnt
!= required_dms_size
; cnt
++)
440 if (required_dms
[cnt
] == dm
)
441 break; // module is already inserted in list
443 if (cnt
== required_dms_size
)
445 required_dms
[required_dms_size
++] = dm
;
447 apr_table_mergen(r
->headers_in
, ACCEPT_ENCODING_HEADER
, tok
);
452 // Enable the requested filter modules
453 for (cnt
= 0; cnt
!= required_dms_size
; cnt
++) {
454 dm
= required_dms
[cnt
];
455 ap_filter_t
*filter
= ap_add_output_filter(dm
->name
, NULL
, r
, r
->connection
);
456 if (filter
== NULL
) {
457 ap_log_error(APLOG_MARK
, APLOG_WARNING
, APR_SUCCESS
, r
->server
, "CRCCACHE-ENCODE Could not enable %s filter", dm
->name
);
458 // Remove the encodings handled by this filter from the list of accepted encodings
459 accept_encoding
= apr_pstrdup(r
->pool
, apr_table_get(r
->headers_in
, ACCEPT_ENCODING_HEADER
));
460 apr_table_unset(r
->headers_in
, ACCEPT_ENCODING_HEADER
);
461 for (tok
= apr_strtok(accept_encoding
, ", ", &last
); tok
!= NULL
; tok
= apr_strtok(NULL
, ", ", &last
)) {
462 for (enc
= dm
->encodings
; enc
!= NULL
; enc
= enc
->next
) {
463 if (strcmp(tok
, enc
->encoding
)==0) {
464 ap_log_error(APLOG_MARK
, APLOG_WARNING
, APR_SUCCESS
, r
->server
, "CRCCACHE-ENCODE Removing encoding %s", tok
);
469 // Did not find the tok encoding in the list. It can be merged back into the header
470 apr_table_mergen(r
->headers_in
, ACCEPT_ENCODING_HEADER
, tok
);
476 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, APR_SUCCESS
, r
->server
, "CRCCACHE-ENCODE Successfully enabled %s filter", dm
->name
);
479 const char *updated_accept_encoding
= apr_table_get(r
->headers_in
, ACCEPT_ENCODING_HEADER
);
480 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, 0, r
->server
, "CRCCACHE-ENCODE Modified Accept-Encoding header: %s", updated_accept_encoding
== NULL
? "NULL" : updated_accept_encoding
);
482 // Add the crccache filter itself, after the decoder modules
483 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, 0, r
->server
, "CRCCACHE-ENCODE Enabling crccache_out_filter to crcencode output");
484 ap_add_output_filter_handle(crccache_out_filter_handle
,
485 ctx
, r
, r
->connection
);
489 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, 0, r
->server
, "CRCCACHE-ENCODE Did not detect blockheader (%s)", BLOCK_HEADER
);
490 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, 0, r
->server
, "CRCCACHE-ENCODE Enabling crccache_out_filter to set Capability and Crcsync-Similar header only");
491 ap_add_output_filter_handle(crccache_out_filter_handle
, ctx
, r
, r
->connection
);
494 /* // All is okay, so set response header to IM Used
495 ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r->server, "CRCCACHE-ENCODE Setting 226 header");
497 r->status_line="226 IM Used";
503 /*static int crccache_server_header_filter_handler(ap_filter_t *f, apr_bucket_brigade *b) {
505 request_rec *r = f->r;
507 ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r->server,"CRCCACHE-ENCODE Setting return status code");
509 // All is okay, so set response header to IM Used
511 r->status_line="HTTP/1.1 226 IM Used";
515 static void crccache_check_etag(request_rec
*r
, crccache_ctx
*ctx
, const char *transform
) {
516 const char *etag
= ctx
->old_etag
;
518 apr_table_set(r
->headers_out
, ETAG_HEADER
,
523 ctx
->old_content_encoding
== NULL
? "identity" : ctx
->old_content_encoding
,
527 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, APR_SUCCESS
, r
->server
, "CRCCACHE-ENCODE Changed ETag header to %s", apr_table_get(r
->headers_out
, ETAG_HEADER
));
531 static apr_status_t
write_compress_buffer(ap_filter_t
*f
, int flush
)
533 unsigned char compress_buf
[30000];
534 request_rec
*r
= f
->r
;
535 crccache_ctx
*ctx
= f
->ctx
;
536 z_stream
*strm
= ctx
->compression_stream
;
538 if (ctx
->debug_skip_writing
)
543 strm
->avail_out
= sizeof(compress_buf
);
544 strm
->next_out
= compress_buf
;
545 uInt avail_in_pre_deflate
= strm
->avail_in
;
546 int zRC
= deflate(strm
, flush
);
547 if (zRC
== Z_STREAM_ERROR
)
549 ap_log_error(APLOG_MARK
, APLOG_ERR
, APR_EGENERAL
, r
->server
,"CRCCACHE-ENCODE deflate error: %d", zRC
);
552 int have
= sizeof(compress_buf
) - strm
->avail_out
;
553 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, APR_SUCCESS
, r
->server
,
554 "CRCCACHE-ENCODE deflate rslt %d, flush %d, consumed %d, produced %d",
555 zRC
, flush
, avail_in_pre_deflate
- strm
->avail_in
, have
);
558 // output buffer contains some data to be written
559 // ap_log_hex(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r->server, compress_buf, have);
560 unsigned bucket_size
= have
;
561 if (ctx
->compression_state
!= COMPRESSION_FIRST_BLOCK_WRITTEN
)
563 bucket_size
+= ENCODING_COMPRESSED_HEADER_SIZE
;
565 ctx
->tx_length
+= bucket_size
;
566 char * buf
= apr_palloc(r
->pool
, bucket_size
);
568 if (ctx
->compression_state
!= COMPRESSION_FIRST_BLOCK_WRITTEN
)
570 buf
[0] = ENCODING_COMPRESSED
;
571 memcpy(buf
+ ENCODING_COMPRESSED_HEADER_SIZE
, compress_buf
, have
);
572 ctx
->compression_state
= COMPRESSION_FIRST_BLOCK_WRITTEN
;
576 memcpy(buf
, compress_buf
, have
);
578 apr_bucket
* b
= apr_bucket_pool_create(buf
, bucket_size
, r
->pool
, f
->c
->bucket_alloc
);
579 APR_BRIGADE_INSERT_TAIL(ctx
->bb
, b
);
582 while (strm
->avail_out
== 0);
583 if (strm
->avail_in
!= 0)
585 ap_log_error(APLOG_MARK
, APLOG_ERR
, APR_EGENERAL
, r
->server
,"CRCCACHE-ENCODE deflate still has %d input bytes available", strm
->avail_in
);
593 static apr_status_t
flush_compress_buffer(ap_filter_t
*f
)
595 crccache_ctx
*ctx
= f
->ctx
;
596 apr_status_t rslt
= APR_SUCCESS
; // assume all will be fine
598 if (ctx
->debug_skip_writing
)
601 if (ctx
->compression_state
!= COMPRESSION_BUFFER_EMPTY
)
603 rslt
= write_compress_buffer(f
, Z_FINISH
); // take the real status
604 deflateReset(ctx
->compression_stream
);
605 ctx
->compression_state
= COMPRESSION_BUFFER_EMPTY
;
606 // ____ ctx->debug_skip_writing = 1; // skip writing after handling first compressed block
614 static apr_status_t
write_literal(ap_filter_t
*f
, unsigned char *buffer
, long count
)
616 crccache_ctx
*ctx
= f
->ctx
;
618 if (ctx
->debug_skip_writing
)
622 if (ctx
->compression_state
== COMPRESSION_BUFFER_EMPTY
)
624 ctx
->compression_state
= COMPRESSION_FIRST_DATA_RECEIVED
;
626 ctx
->compression_stream
->avail_in
= count
;
627 ctx
->compression_stream
->next_in
= buffer
;
628 // ap_log_hex(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, f->r->server, buffer, count);
629 rslt
= write_compress_buffer(f
, Z_NO_FLUSH
);
630 ctx
->tx_uncompressed_length
+= count
;
637 static apr_status_t
write_hash(ap_filter_t
*f
, unsigned char *buffer
, long count
)
639 request_rec
*r
= f
->r
;
640 crccache_ctx
*ctx
= f
->ctx
;
643 rslt
= flush_compress_buffer(f
);
644 if (rslt
!= APR_SUCCESS
)
649 if (ctx
->debug_skip_writing
)
652 unsigned bucket_size
= count
+ 1;
653 ctx
->tx_length
+= bucket_size
;
654 ctx
->tx_uncompressed_length
+= bucket_size
;
655 char * buf
= apr_palloc(r
->pool
, bucket_size
);
657 buf
[0] = ENCODING_HASH
;
658 memcpy(&buf
[1],buffer
,count
);
659 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, APR_SUCCESS
, r
->server
,"CRCCACHE-ENCODE HASH");
660 apr_bucket
* b
= apr_bucket_pool_create(buf
, bucket_size
, r
->pool
, f
->c
->bucket_alloc
);
661 APR_BRIGADE_INSERT_TAIL(ctx
->bb
, b
);
667 * Write a block reference
669 static apr_status_t
write_block_reference(ap_filter_t
*f
, long result
)
671 request_rec
*r
= f
->r
;
672 crccache_ctx
*ctx
= f
->ctx
;
675 rslt
= flush_compress_buffer(f
);
676 if (rslt
!= APR_SUCCESS
)
681 if (ctx
->debug_skip_writing
)
684 unsigned bucket_size
= ENCODING_BLOCK_HEADER_SIZE
;
685 ctx
->tx_length
+= bucket_size
;
686 ctx
->tx_uncompressed_length
+= bucket_size
;
687 char * buf
= apr_palloc(r
->pool
, bucket_size
);
689 buf
[0] = ENCODING_BLOCK
;
690 buf
[1] = (unsigned char) ((-result
)-1); // invert and get back to zero based
691 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, APR_SUCCESS
, r
->server
,"CRCCACHE-ENCODE block %d",buf
[1]);
692 apr_bucket
* b
= apr_bucket_pool_create(buf
, bucket_size
, r
->pool
, f
->c
->bucket_alloc
);
693 APR_BRIGADE_INSERT_TAIL(ctx
->bb
, b
);
698 * Process one block of data: try to match it against the CRC, append
699 * the result to the ouput ring and remember the result (e.g. was
700 * it a block-match or was a literal processed)
702 static apr_status_t
process_block(ap_filter_t
*f
)
704 request_rec
*r
= f
->r
;
705 crccache_ctx
*ctx
= f
->ctx
;
706 apr_status_t rslt
= APR_SUCCESS
;
708 // ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r->server,"CRCCACHE-ENCODE invoking crc_read_block");
709 if (ctx
->crcctx
== NULL
)
711 // This should never happen
712 ap_log_error(APLOG_MARK
, APLOG_ERR
, APR_SUCCESS
, r
->server
,"CRCCACHE-ENCODE crcctx = null");
717 size_t ndigested
= crc_read_block(
720 ctx
->buffer
+ctx
->buffer_digest_getpos
,
721 ctx
->buffer_putpos
-ctx
->buffer_digest_getpos
723 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, APR_SUCCESS
, r
->server
,
724 "CRCCACHE-ENCODE crc_read_block ndigested: %" APR_SIZE_T_FMT
", result %ld", ndigested
, rd_block_rslt
);
727 // rd_block_rslt = 0: do nothing (it is a 'literal' block of exactly 'tail_blocksize' bytes at the end of the buffer,
728 // it will have to be moved to the beginning of the moving window so that it can be written upon the next call to
729 // crc_read_block or crc_read_flush)
730 // rd_block_rslt > 0: send literal
731 // rd_block_rslt < 0: send block
732 if (rd_block_rslt
> 0)
734 rslt
= write_literal(f
, ctx
->buffer
+ctx
->buffer_read_getpos
, rd_block_rslt
);
735 ctx
->buffer_read_getpos
+= rd_block_rslt
;
737 else if (rd_block_rslt
< 0)
739 rslt
= write_block_reference(f
, rd_block_rslt
);
740 unsigned char blocknum
= (unsigned char) ((-rd_block_rslt
)-1);
741 ctx
->buffer_read_getpos
+= (blocknum
== ctx
->block_count
-1) ? ctx
->tail_block_size
: ctx
->block_size
;
744 // Update the context with the results
745 ctx
->crc_read_block_result
= rd_block_rslt
;
746 ctx
->crc_read_block_ndigested
= ndigested
;
747 ctx
->buffer_digest_getpos
+= ndigested
;
752 * Flush one block of data: get it from the crccontext, append
753 * the result to the ouput ring and remember the result (e.g. was
754 * it a block-match or was a literal processed)
756 static apr_status_t
flush_block(ap_filter_t
*f
)
758 request_rec
*r
= f
->r
;
759 crccache_ctx
*ctx
= f
->ctx
;
760 apr_status_t rslt
= APR_SUCCESS
;
762 // ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r->server,"CRCCACHE-ENCODE invoking crc_read_flush");
763 if (ctx
->crcctx
== NULL
)
765 // This should never happen
766 ap_log_error(APLOG_MARK
, APLOG_ERR
, APR_SUCCESS
, r
->server
,"CRCCACHE-ENCODE crcctx = null");
769 long rd_flush_rslt
= crc_read_flush(ctx
->crcctx
);
770 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, APR_SUCCESS
, r
->server
,"CRCCACHE-ENCODE crc_read_flush result %ld", rd_flush_rslt
);
772 // rd_flush_rslt = 0: do nothing
773 // rd_flush_rslt > 0: send literal that was already digested but not yet returned by read-block
774 // rd_flush_rslt < 0: send block that was already digested but not yet returned by read-block
775 if (rd_flush_rslt
> 0)
777 rslt
= write_literal(f
, ctx
->buffer
+ctx
->buffer_read_getpos
, rd_flush_rslt
);
778 ctx
->buffer_read_getpos
+= rd_flush_rslt
;
780 else if (rd_flush_rslt
< 0)
782 rslt
= write_block_reference(f
, rd_flush_rslt
);
783 unsigned char blocknum
= (unsigned char) ((-rd_flush_rslt
)-1);
784 ctx
->buffer_read_getpos
+= (blocknum
== ctx
->block_count
-1) ? ctx
->tail_block_size
: ctx
->block_size
;
787 // Update the context with the results
788 ctx
->crc_read_block_result
= rd_flush_rslt
;
789 ctx
->crc_read_block_ndigested
= 0;
794 * Clean-up memory used by helper libraries, that don't know about apr_palloc
795 * and that (probably) use classical malloc/free
797 static apr_status_t
deflate_ctx_cleanup(void *data
)
799 crccache_ctx
*ctx
= (crccache_ctx
*)data
;
803 if (ctx
->compression_state
!= COMPRESSION_ENDED
)
805 deflateEnd(ctx
->compression_stream
);
806 ctx
->compression_state
= COMPRESSION_ENDED
;
808 if (ctx
->crcctx
!= NULL
)
810 crc_context_free(ctx
->crcctx
);
817 * End of stream has been reached:
818 * Process any data still in the buffer and flush all internal
819 * structures of crcsync and of zlib
820 * Furthermore, add a strong hash
822 static apr_status_t
process_eos(ap_filter_t
*f
)
824 crccache_ctx
*ctx
= f
->ctx
;
827 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, APR_SUCCESS
, f
->r
->server
,"CRCCACHE-ENCODE EOS reached for APR bucket");
830 while (ctx
->buffer_digest_getpos
< ctx
->buffer_putpos
)
832 // There is still data in the buffer. Process it.
833 rslt
= process_block(f
);
834 if (rslt
!= APR_SUCCESS
)
842 // Flush remaining block in the crcctx
843 rslt
= flush_block(f
);
844 if (rslt
!= APR_SUCCESS
)
849 while (ctx
->crc_read_block_result
!= 0);
851 // Flush anything that is remaining in the compress buffer
852 rslt
= flush_compress_buffer(f
);
853 if (rslt
!= APR_SUCCESS
)
858 unsigned char sha1_value
[APR_SHA1_DIGESTSIZE
];
859 apr_sha1_final(sha1_value
, &ctx
->sha1_ctx
);
860 write_hash(f
, sha1_value
, APR_SHA1_DIGESTSIZE
);
861 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, APR_SUCCESS
, f
->r
->server
,
862 "CRCCACHE-ENCODE complete size %f%% (encoded-uncompressed=%" APR_SIZE_T_FMT
" encoded=%" APR_SIZE_T_FMT
" original=%" APR_SIZE_T_FMT
") for uri %s",100.0*((float)ctx
->tx_length
/(float)ctx
->orig_length
),ctx
->tx_uncompressed_length
, ctx
->tx_length
, ctx
->orig_length
, f
->r
->unparsed_uri
);
868 * Process a data bucket; append data into a moving window buffer
869 * and encode it with crcsync algorithm when window contains enough
870 * data for crcsync to find potential matches
872 static apr_status_t
process_data_bucket(ap_filter_t
*f
, apr_bucket
*e
)
874 request_rec
*r
= f
->r
;
875 crccache_ctx
*ctx
= f
->ctx
;
882 apr_bucket_read(e
, &data
, &len
, APR_BLOCK_READ
);
883 ctx
->orig_length
+= len
;
884 // update our sha1 hash
885 apr_sha1_update_binary(&ctx
->sha1_ctx
, (const unsigned char *)data
, len
);
886 // ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r->server,"CRCCACHE-ENCODE normal data in APR bucket, read %ld", len);
888 // append data to the buffer and encode buffer content using the crc_read_block magic
889 size_t bucket_used_count
= 0;
890 size_t bucket_data_left
;
891 while(bucket_used_count
< len
)
893 /* Append as much data as possible into the buffer */
894 bucket_data_left
= len
- bucket_used_count
;
895 size_t copy_size
= MIN(ctx
->buffer_size
-ctx
->buffer_putpos
, bucket_data_left
);
896 memcpy(ctx
->buffer
+ctx
->buffer_putpos
, data
+bucket_used_count
, copy_size
);
897 bucket_used_count
+= copy_size
;
898 bucket_data_left
-= copy_size
;
899 ctx
->buffer_putpos
+= copy_size
;
900 /* flush the buffer if it is appropriate */
901 if (ctx
->buffer_putpos
== ctx
->buffer_size
)
903 // Buffer is filled to the end. Flush as much as possible
904 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, APR_SUCCESS
, r
->server
,
905 "CRCCACHE-ENCODE Buffer is filled to end, read_getpos: %" APR_SIZE_T_FMT
", digest_getpos: %" APR_SIZE_T_FMT
", putpos: %" APR_SIZE_T_FMT
", putpos-digest_getpos: %" APR_SIZE_T_FMT
" (tail_block_size: %" APR_SIZE_T_FMT
")",
906 ctx
->buffer_read_getpos
, ctx
->buffer_digest_getpos
, ctx
->buffer_putpos
, ctx
->buffer_putpos
-ctx
->buffer_digest_getpos
, ctx
->tail_block_size
);
907 while (ctx
->buffer_putpos
- ctx
->buffer_digest_getpos
> ctx
->tail_block_size
)
909 // We can still scan at least 1 tail block + 1 byte forward: try to flush next part
910 rslt
= process_block(f
);
911 if (rslt
!= APR_SUCCESS
)
915 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, APR_SUCCESS
, r
->server
,
916 "CRCCACHE-ENCODE Processed a block, read_getpos: %" APR_SIZE_T_FMT
", digest_getpos: %" APR_SIZE_T_FMT
", putpos: %" APR_SIZE_T_FMT
", putpos-digest_getpos: %" APR_SIZE_T_FMT
" (tail_block_size: %" APR_SIZE_T_FMT
")",
917 ctx
->buffer_read_getpos
, ctx
->buffer_digest_getpos
, ctx
->buffer_putpos
, ctx
->buffer_putpos
-ctx
->buffer_digest_getpos
, ctx
->tail_block_size
);
920 if (ctx
->buffer_putpos
!= ctx
->buffer_read_getpos
)
922 // Copy the remaining part of the buffer to the start of the buffer,
923 // so that it can be filled again as new data arrive
924 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, APR_SUCCESS
, r
->server
,
925 "CRCCACHE-ENCODE Moving %" APR_SIZE_T_FMT
" bytes to begin of buffer",
926 ctx
->buffer_putpos
- ctx
->buffer_read_getpos
);
927 memcpy(ctx
->buffer
, ctx
->buffer
+ ctx
->buffer_read_getpos
, ctx
->buffer_putpos
- ctx
->buffer_read_getpos
);
929 // Reset getpos to the beginning of the buffer and putpos accordingly
930 ctx
->buffer_putpos
-= ctx
->buffer_read_getpos
;
931 ctx
->buffer_digest_getpos
-= ctx
->buffer_read_getpos
;
932 ctx
->buffer_read_getpos
= 0;
934 while (ctx
->crc_read_block_result
< 0 && ctx
->buffer_putpos
- ctx
->buffer_digest_getpos
> ctx
->tail_block_size
)
936 // Previous block matched exactly. Let's hope the next block as well
937 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, APR_SUCCESS
, r
->server
,
938 "CRCCACHE-ENCODE Previous block matched, read_getpos: %" APR_SIZE_T_FMT
", digest_getpos: %" APR_SIZE_T_FMT
", putpos: %" APR_SIZE_T_FMT
", putpos-digest_getpos: %" APR_SIZE_T_FMT
" (tail_block_size: %" APR_SIZE_T_FMT
")",
939 ctx
->buffer_read_getpos
, ctx
->buffer_digest_getpos
, ctx
->buffer_putpos
, ctx
->buffer_putpos
-ctx
->buffer_digest_getpos
, ctx
->tail_block_size
);
940 rslt
= process_block(f
);
941 if (rslt
!= APR_SUCCESS
)
947 return APR_SUCCESS
; // Yahoo, all went well
950 static int match_content_type(request_rec
*r
, encodings_t
*allowed_mime_types
, const char *resp_content_type
)
952 // Response content type consists of mime-type, optionally followed by a ; and parameters
953 // E.g. text/html; charset=ISO-8859-15
954 // An allowed mime-type consists of one of:
955 // -The NULL pointer to allow any mime-type (*/* in the config file)
956 // -The string of format type/subtype, which must match exactly with the mime-type
957 // -The string of format type/*, which means the subtype can be anything
958 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, APR_SUCCESS
, r
->server
,"resp_content_type: %s", resp_content_type
);
959 while (allowed_mime_types
!= NULL
)
961 const char *allowed_pos
= allowed_mime_types
->encoding
;
962 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, APR_SUCCESS
, r
->server
,"allowed: %s", allowed_pos
);
963 if (allowed_pos
== NULL
)
965 // User specified wild-card. This matches per definition.
966 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, APR_SUCCESS
, r
->server
,"matched wildcard");
969 const char *resp_pos
= resp_content_type
;
972 while ((ch_r
= *resp_pos
) != 0 && ch_r
!= ';' && (ch_a
= *allowed_pos
) != 0 && ch_r
== ch_a
) {
977 if (((ch_r
== 0 || ch_r
== ';') && ch_a
== 0) || (ch_r
!= 0 && ch_a
== '*'))
979 // It's OK if the mime-type part of the response content type matches exactly
980 // with the allowed mime type
981 // It is also OK if the (mime-type part of the) content-type has still characters
982 // remaining but the allowed mime-type is at the * wild-card
983 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, APR_SUCCESS
, r
->server
,"matched specific");
986 allowed_mime_types
= allowed_mime_types
->next
;
988 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, APR_SUCCESS
, r
->server
,"nothing matched");
996 * Deliver cached content (headers and body) up the stack.
998 static apr_status_t
crccache_out_filter(ap_filter_t
*f
, apr_bucket_brigade
*bb
) {
1000 request_rec
*r
= f
->r
;
1001 crccache_ctx
*ctx
= f
->ctx
;
1003 int return_code
= APR_SUCCESS
;
1005 /* Do nothing if asked to filter nothing. */
1006 if (APR_BRIGADE_EMPTY(bb
)) {
1007 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, APR_SUCCESS
, r
->server
,"CRCCACHE-ENCODE bucket brigade is empty -> nothing todo for uri %s", r
->unparsed_uri
);
1008 return ap_pass_brigade(f
->next
, bb
);
1011 /* If state is not yet GS_ENCODING content, we need to ensure that it is okay to send
1012 * the encoded content. If the state is GS_ENCODING, that means we've done
1013 * this before and we liked it.
1014 * This could be not so nice if we always fail. But, if we succeed,
1015 * we're in better shape.
1017 if (ctx
->global_state
!= GS_ENCODING
)
1019 const char *encoding
;
1021 /* only work on main request/no subrequests */
1022 if (r
->main
!= NULL
) {
1023 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, APR_SUCCESS
, r
->server
,"CRCCACHE-ENCODE bucket brigade is empty -> nothing todo for uri %s", r
->unparsed_uri
);
1024 ap_remove_output_filter(f
);
1025 return ap_pass_brigade(f
->next
, bb
);
1028 /* We can't operate on Content-Ranges */
1029 if (apr_table_get(r
->headers_out
, "Content-Range") != NULL
) {
1030 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, APR_SUCCESS
, r
->server
,"CRCCACHE-ENCODE Content-Range header found -> nothing todo for uri %s", r
->unparsed_uri
);
1031 ap_remove_output_filter(f
);
1032 return ap_pass_brigade(f
->next
, bb
);
1035 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, APR_SUCCESS
, r
->server
,"CRCCACHE-ENCODE initializing filter process for uri %s", r
->unparsed_uri
);
1037 // Advertise crcsync capability and preferred blocksize multiple
1038 apr_table_mergen(r
->headers_out
, CAPABILITY_HEADER
, "crcsync; m=1");
1040 // Add Crcsync-Similar header if relevant
1041 crccache_server_conf
*conf
= ap_get_module_config(r
->server
->module_config
,
1042 &crccache_server_module
);
1043 regexs_t
*regexs
= conf
->regexs
;
1044 const char *content_type
= apr_table_get(r
->headers_out
, CONTENT_TYPE_HEADER
);
1045 if (content_type
!= NULL
)
1047 while (regexs
!= NULL
)
1049 if (match_content_type(r
, regexs
->mime_types
, content_type
) &&
1050 ap_regexec(regexs
->preg
, r
->unparsed_uri
, 0, NULL
, AP_REG_ICASE
) == 0)
1052 // Found a regex to which this page is similar. Store it in the header
1053 apr_table_set(r
->headers_out
, CRCSYNC_SIMILAR_HEADER
, apr_pstrdup(r
->pool
, regexs
->regex
));
1056 regexs
=regexs
->next
;
1060 if (ctx
->global_state
== GS_INIT
)
1062 // Still in GS_INIT state implies there is no need to encode.
1063 // It is sufficient that the Capability and Crcsync-Similar headers have been set
1064 ap_remove_output_filter(f
);
1065 return ap_pass_brigade(f
->next
, bb
);
1068 if (ctx
->global_state
!= GS_HEADERS_SAVED
)
1070 ap_log_error(APLOG_MARK
, APLOG_ERR
, APR_SUCCESS
, r
->server
, "CRCCACHE-ENCODE unexpected ctx-state: %d, expected: %d", ctx
->global_state
, GS_HEADERS_SAVED
);
1071 return APR_EGENERAL
;
1074 /* Indicate to caches that they may only re-use this response for a request
1075 * with the same BLOCK_HEADER value as the current request
1076 * Indicate to clients that the server supports crcsync, even if checks
1077 * further down prevent this specific response from being crc-encoded
1079 apr_table_mergen(r
->headers_out
, VARY_HEADER
, BLOCK_HEADER
);
1081 /* If Content-Encoding is present and differs from "identity", we can't handle it */
1082 encoding
= apr_table_get(r
->headers_out
, ENCODING_HEADER
);
1083 if (encoding
&& strcasecmp(encoding
, "identity")) {
1084 ap_log_error(APLOG_MARK
, APLOG_INFO
, APR_SUCCESS
, r
->server
,
1085 "Not encoding with crccache. It is already encoded with: %s", encoding
);
1086 ap_remove_output_filter(f
);
1087 return ap_pass_brigade(f
->next
, bb
);
1090 /* For a 304 or 204 response there is no entity included in
1091 * the response and hence nothing to crc-encode. */
1092 if (r
->status
== HTTP_NOT_MODIFIED
|| r
->status
==HTTP_NO_CONTENT
)
1094 ap_remove_output_filter(f
);
1095 return ap_pass_brigade(f
->next
, bb
);
1098 /* All Ok. We're cool with filtering this. */
1099 ctx
->global_state
= GS_ENCODING
;
1100 ctx
->debug_skip_writing
= 0;
1101 ctx
->orig_length
= 0;
1103 ctx
->tx_uncompressed_length
= 0;
1104 ctx
->bb
= apr_brigade_create(r
->pool
, f
->c
->bucket_alloc
);
1106 /* Parse the input headers */
1107 const char * header
;
1108 header
= apr_table_get(r
->headers_in
, BLOCK_HEADER
);
1112 if (decode_if_block_header(r
,header
,&version
,&file_size
,&hashes
) < 0)
1114 ap_log_error(APLOG_MARK
, APLOG_ERR
, 0, r
->server
,"crccache: failed to decode if-block header");
1115 ap_remove_output_filter(f
);
1116 return ap_pass_brigade(f
->next
, bb
);
1118 // Decode the hashes
1119 ctx
->block_count
= apr_base64_decode_len(hashes
)/(HASH_SIZE
/8);
1120 // this may over allocate by a couple of bytes but no big deal
1121 ctx
->hashes
= apr_palloc(r
->pool
, apr_base64_decode_len(hashes
));
1122 apr_base64_decode((char *)ctx
->hashes
, hashes
);
1126 ctx
->block_size
= file_size
/ctx
->block_count
;
1127 ctx
->tail_block_size
= ctx
->block_size
+ file_size
% ctx
->block_count
;
1128 size_t block_count_including_final_block
= ctx
->block_count
;// + (ctx->tail_block_size != 0);
1129 ap_log_error(APLOG_MARK
, APLOG_INFO
, APR_SUCCESS
, r
->server
,
1130 "If-block header decoded, version %d: %d hashes of %d and one of %d", version
, ctx
->block_count
-1,(int)ctx
->block_size
,(int)ctx
->tail_block_size
);
1132 // swap to network byte order
1134 for (i
= 0; i
< block_count_including_final_block
;++i
)
1136 htobe64(ctx
->hashes
[i
]);
1139 // Data come in at chunks that are potentially smaller then block_size or tail_block_size
1140 // Accumulate those chunks into a buffer.
1141 // The buffer must be at least 2*tail_block_size+1 so that crc_read_block(...) can find a matching block, regardless
1142 // of the data alignment compared to the original page.
1143 // The buffer is basically a moving window in the new page. So sometimes the last part of the buffer must be
1144 // copied to the beginning again. The larger the buffer, the less often such a copy operation is required
1145 // Though, the larger the buffer, the bigger the memory demand.
1146 // A size of 4*tail_block_size+1 seems to be a good balance
1148 // TODO: tune the buffer-size depending on the mime-type. Already compressed data (zip, gif, jpg, mpg, etc) will
1149 // probably only have matching blocks if the file is totally unmodified. As soon as one byte differs in the original
1150 // uncompressed data, the entire compressed data stream will be different anyway, so in such case it does not make
1151 // much sense to even keep invoking the crc_read_block(...) function as soon as a difference has been found.
1152 // Hence, no need to make a (potentially huge, think about movies) buffer for these type of compressed data types.
1153 ctx
->buffer_size
= ctx
->tail_block_size
*4 + 1;
1154 ctx
->buffer_digest_getpos
= 0;
1155 ctx
->buffer_read_getpos
= 0;
1156 ctx
->buffer_putpos
= 0;
1157 ctx
->crc_read_block_result
= 0;
1158 ctx
->buffer
= apr_palloc(r
->pool
, ctx
->buffer_size
);
1160 /* Setup deflate for compressing non-matched literal data */
1161 ctx
->compression_state
= COMPRESSION_BUFFER_EMPTY
;
1162 // TODO: should I pass some apr_palloc based function to prevent memory leaks
1163 //in case of unexpected errors?
1165 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, APR_SUCCESS
, r
->server
,"CRCCACHE-ENCODE size of compression stream: %" APR_SIZE_T_FMT
, sizeof(*(ctx
->compression_stream
)));
1166 ctx
->compression_stream
= apr_palloc(r
->pool
, sizeof(*(ctx
->compression_stream
)));
1167 ctx
->compression_stream
->zalloc
= Z_NULL
;
1168 ctx
->compression_stream
->zfree
= Z_NULL
;
1169 ctx
->compression_stream
->opaque
= Z_NULL
;
1170 zRC
= deflateInit(ctx
->compression_stream
, Z_DEFAULT_COMPRESSION
); // TODO: make compression level configurable
1173 // Can't initialize the compression engine for compressing literal data
1174 deflateEnd(ctx
->compression_stream
); // free memory used by deflate
1175 free(ctx
->compression_stream
);
1176 ctx
->compression_stream
= NULL
;
1177 ap_log_rerror(APLOG_MARK
, APLOG_ERR
, 0, r
,
1178 "unable to init Zlib: "
1179 "deflateInit returned %d: URL %s",
1181 ap_remove_output_filter(f
);
1182 return ap_pass_brigade(f
->next
, bb
);
1185 // initialise the context for our sha1 digest of the unencoded response
1186 apr_sha1_init(&ctx
->sha1_ctx
);
1188 // now initialise the crcsync context that will do the real work
1189 ctx
->crcctx
= crc_context_new(ctx
->block_size
, HASH_SIZE
,ctx
->hashes
, block_count_including_final_block
, ctx
->tail_block_size
);
1191 // Register a cleanup function to cleanup internal libz and crcsync resources
1192 apr_pool_cleanup_register(r
->pool
, ctx
, deflate_ctx_cleanup
,
1193 apr_pool_cleanup_null
);
1195 // All checks and initializations are OK
1196 // Modify headers that are impacted by this transformation
1197 apr_table_setn(r
->headers_out
, ENCODING_HEADER
, CRCCACHE_ENCODING
);
1198 apr_table_unset(r
->headers_out
, "Content-Length");
1199 apr_table_unset(r
->headers_out
, "Content-MD5");
1200 crccache_check_etag(r
, ctx
, CRCCACHE_ENCODING
);
1202 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, APR_SUCCESS
, r
->server
, "CRCCACHE Server end of context setup");
1205 if (ctx
->global_state
!= GS_ENCODING
)
1207 ap_log_error(APLOG_MARK
, APLOG_ERR
, APR_SUCCESS
, r
->server
, "CRCCACHE-ENCODE unexpected ctx-state: %d, expected: %d", ctx
->global_state
, GS_ENCODING
);
1208 return APR_EGENERAL
;
1211 while (!APR_BRIGADE_EMPTY(bb
))
1217 e
= APR_BRIGADE_FIRST(bb
);
1219 if (APR_BUCKET_IS_EOS(e
))
1221 // Process end of stream: flush data buffers, compression buffers, etc.
1222 // and calculate a strong hash.
1223 rslt
= process_eos(f
);
1225 /* Remove EOS from the old list, and insert into the new. */
1226 APR_BUCKET_REMOVE(e
);
1227 APR_BRIGADE_INSERT_TAIL(ctx
->bb
, e
);
1229 /* This filter is done once it has served up its content */
1230 ap_remove_output_filter(f
);
1232 if (rslt
!= APR_SUCCESS
)
1234 return rslt
; // A problem occurred. Abort the processing
1237 /* Okay, we've seen the EOS.
1238 * Time to pass it along down the chain.
1240 return ap_pass_brigade(f
->next
, ctx
->bb
);
1243 if (APR_BUCKET_IS_FLUSH(e
))
1245 // ap_log_error(APLOG_MARK, APLOG_DEBUG, APR_SUCCESS, r->server,"CRCCACHE-ENCODE flush APR bucket");
1248 /* Remove flush bucket from old brigade and insert into the new. */
1249 APR_BUCKET_REMOVE(e
);
1250 // TODO: optimize; do not insert two consecutive flushes when no intermediate
1251 // output block was written
1252 APR_BRIGADE_INSERT_TAIL(ctx
->bb
, e
);
1253 rv
= ap_pass_brigade(f
->next
, ctx
->bb
);
1254 if (rv
!= APR_SUCCESS
) {
1260 if (APR_BUCKET_IS_METADATA(e
)) {
1262 * Remove meta data bucket from old brigade and insert into the
1265 apr_bucket_read(e
, &data
, &len
, APR_BLOCK_READ
);
1267 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, APR_SUCCESS
, r
->server
,
1268 "CRCCACHE-ENCODE Metadata, read %" APR_SIZE_T_FMT
", %d %d %d",len
,data
[0],data
[1],data
[2]);
1270 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, APR_SUCCESS
, r
->server
,
1271 "CRCCACHE-ENCODE Metadata, read %" APR_SIZE_T_FMT
,len
);
1272 APR_BUCKET_REMOVE(e
);
1273 APR_BRIGADE_INSERT_TAIL(ctx
->bb
, e
);
1277 // Bucket is non of the above types. Assume it is a data bucket
1278 // which means it can be encoded with the crcsync algorithm
1279 rslt
= process_data_bucket(f
, e
);
1281 APR_BUCKET_REMOVE(e
);
1282 if (rslt
!= APR_SUCCESS
)
1284 break; // A problem occurred. Abort the processing
1288 apr_brigade_cleanup(bb
);
1294 * CACHE_OUT_SAVE_HEADERS filter
1297 * Save headers into the context
1299 static apr_status_t
crccache_out_save_headers_filter(ap_filter_t
*f
, apr_bucket_brigade
*bb
) {
1300 request_rec
*r
= f
->r
;
1301 crccache_ctx
*ctx
= f
->ctx
;
1303 /* Do nothing if asked to filter nothing. */
1304 if (APR_BRIGADE_EMPTY(bb
)) {
1305 ap_log_error(APLOG_MARK
, APLOG_DEBUG
, APR_SUCCESS
, r
->server
,"CRCCACHE-ENCODE (save headers) bucket brigade is empty -> nothing todo");
1306 return ap_pass_brigade(f
->next
, bb
);
1309 if (ctx
->global_state
!= GS_INIT
)
1311 ap_log_error(APLOG_MARK
, APLOG_ERR
, APR_SUCCESS
, r
->server
, "CRCCACHE-ENCODE (save headers) unexpected ctx-state: %d, expected: %d", ctx
->global_state
, GS_INIT
);
1312 return APR_EGENERAL
;
1315 /* only work on main request/no subrequests */
1316 if (r
->main
!= NULL
) {
1317 ap_remove_output_filter(f
);
1318 return ap_pass_brigade(f
->next
, bb
);
1321 /* We can't operate on Content-Ranges */
1322 if (apr_table_get(r
->headers_out
, "Content-Range") != NULL
) {
1323 ap_remove_output_filter(f
);
1324 return ap_pass_brigade(f
->next
, bb
);
1327 /* Save content-encoding and etag header for later usage by the crcsync
1330 const char *encoding
= apr_table_get(r
->headers_out
, ENCODING_HEADER
);
1331 if (encoding
!= NULL
)
1333 ctx
->old_content_encoding
= apr_pstrdup(r
->pool
, encoding
);
1334 ap_log_error(APLOG_MARK
, APLOG_INFO
, APR_SUCCESS
, r
->server
,
1335 "Saved old content-encoding: %s", encoding
);
1337 const char *etag
= apr_table_get(r
->headers_out
, ETAG_HEADER
);
1340 ctx
->old_etag
= apr_pstrdup(r
->pool
, etag
);
1341 ap_log_error(APLOG_MARK
, APLOG_INFO
, APR_SUCCESS
, r
->server
,
1342 "Saved old etag: %s", etag
);
1344 ctx
->global_state
= GS_HEADERS_SAVED
;
1346 /* Done saving headers. Nothing left to do */
1347 ap_remove_output_filter(f
);
1348 return ap_pass_brigade(f
->next
, bb
);
1352 static void crccache_server_register_hook(apr_pool_t
*p
) {
1353 ap_log_error(APLOG_MARK
, APLOG_INFO
, 0, NULL
,
1354 "Registering crccache server module, (C) 2009, Toby Collett and Alex Wulms");
1356 ap_hook_header_parser(crccache_server_header_parser_handler
, NULL
, NULL
,
1359 ap_register_output_filter("CRCCACHE_HEADER", crccache_server_header_filter_handler,
1360 NULL, AP_FTYPE_PROTOCOL);
1362 crccache_out_save_headers_filter_handle
= ap_register_output_filter("CRCCACHE_OUT_SAVE_HEADERS",
1363 crccache_out_save_headers_filter
, NULL
, AP_FTYPE_RESOURCE
-1); // make sure to handle it *before* INFLATE filter (or other decode modules)
1365 crccache_out_filter_handle
= ap_register_output_filter("CRCCACHE_OUT",
1366 crccache_out_filter
, NULL
, AP_FTYPE_CONTENT_SET
);
1369 module AP_MODULE_DECLARE_DATA crccache_server_module
= {
1370 STANDARD20_MODULE_STUFF
, NULL
, /* create per-directory config structure */
1371 NULL
, /* merge per-directory config structures */
1372 crccache_server_create_config
, /* create per-server config structure */
1373 NULL
, /* merge per-server config structures */
1374 crccache_server_cmds
, /* command apr_table_t */
1375 crccache_server_register_hook
/* register hooks */