in modules/filters/mod_deflate.c [1139:1539]
static apr_status_t deflate_in_filter(ap_filter_t *f,
apr_bucket_brigade *bb,
ap_input_mode_t mode,
apr_read_type_e block,
apr_off_t readbytes)
{
apr_bucket *bkt;
request_rec *r = f->r;
deflate_ctx *ctx = f->ctx;
int zRC;
apr_status_t rv;
deflate_filter_config *c;
deflate_dirconf_t *dc;
apr_off_t inflate_limit;
/* just get out of the way of things we don't want. */
if (mode != AP_MODE_READBYTES) {
return ap_get_brigade(f->next, bb, mode, block, readbytes);
}
c = ap_get_module_config(r->server->module_config, &deflate_module);
dc = ap_get_module_config(r->per_dir_config, &deflate_module);
if (!ctx || ctx->header_len < sizeof(ctx->header)) {
apr_size_t len;
if (!ctx) {
/* only work on main request/no subrequests */
if (!ap_is_initial_req(r)) {
ap_remove_input_filter(f);
return ap_get_brigade(f->next, bb, mode, block, readbytes);
}
/* We can't operate on Content-Ranges */
if (apr_table_get(r->headers_in, "Content-Range") != NULL) {
ap_remove_input_filter(f);
return ap_get_brigade(f->next, bb, mode, block, readbytes);
}
/* Check whether request body is gzipped.
*
* If it is, we're transforming the contents, invalidating
* some request headers including Content-Encoding.
*
* If not, we just remove ourself.
*/
if (check_gzip(r, r->headers_in, NULL) == 0) {
ap_remove_input_filter(f);
return ap_get_brigade(f->next, bb, mode, block, readbytes);
}
f->ctx = ctx = apr_pcalloc(f->r->pool, sizeof(*ctx));
ctx->bb = apr_brigade_create(r->pool, f->c->bucket_alloc);
ctx->proc_bb = apr_brigade_create(r->pool, f->c->bucket_alloc);
ctx->buffer = apr_palloc(r->pool, c->bufferSize);
}
do {
apr_brigade_cleanup(ctx->bb);
len = sizeof(ctx->header) - ctx->header_len;
rv = ap_get_brigade(f->next, ctx->bb, AP_MODE_READBYTES, block,
len);
/* ap_get_brigade may return success with an empty brigade for
* a non-blocking read which would block (an empty brigade for
* a blocking read is an issue which is simply forwarded here).
*/
if (rv != APR_SUCCESS || APR_BRIGADE_EMPTY(ctx->bb)) {
return rv;
}
/* zero length body? step aside */
bkt = APR_BRIGADE_FIRST(ctx->bb);
if (APR_BUCKET_IS_EOS(bkt)) {
if (ctx->header_len) {
/* If the header was (partially) read it's an error, this
* is not a gzip Content-Encoding, as claimed.
*/
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02619)
"Encountered premature end-of-stream while "
"reading inflate header");
return APR_EGENERAL;
}
APR_BUCKET_REMOVE(bkt);
APR_BRIGADE_INSERT_TAIL(bb, bkt);
ap_remove_input_filter(f);
return APR_SUCCESS;
}
rv = apr_brigade_flatten(ctx->bb,
ctx->header + ctx->header_len, &len);
if (rv != APR_SUCCESS) {
return rv;
}
if (len && !ctx->header_len) {
apr_table_unset(r->headers_in, "Content-Length");
apr_table_unset(r->headers_in, "Content-MD5");
}
ctx->header_len += len;
} while (ctx->header_len < sizeof(ctx->header));
/* We didn't get the magic bytes. */
if (ctx->header[0] != deflate_magic[0] ||
ctx->header[1] != deflate_magic[1]) {
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01387)
"Zlib: Invalid header");
return APR_EGENERAL;
}
ctx->zlib_flags = ctx->header[3];
if ((ctx->zlib_flags & RESERVED)) {
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01388)
"Zlib: Invalid flags %02x", ctx->zlib_flags);
return APR_EGENERAL;
}
zRC = inflateInit2(&ctx->stream, c->windowSize);
if (zRC != Z_OK) {
f->ctx = NULL;
inflateEnd(&ctx->stream);
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(01389)
"unable to init Zlib: "
"inflateInit2 returned %d: URL %s",
zRC, r->uri);
ap_remove_input_filter(f);
return ap_get_brigade(f->next, bb, mode, block, readbytes);
}
/* initialize deflate output buffer */
ctx->stream.next_out = ctx->buffer;
ctx->stream.avail_out = c->bufferSize;
apr_brigade_cleanup(ctx->bb);
}
inflate_limit = dc->inflate_limit;
if (inflate_limit == 0) {
/* The core is checking the deflated body, we'll check the inflated */
inflate_limit = ap_get_limit_req_body(f->r);
}
if (APR_BRIGADE_EMPTY(ctx->proc_bb)) {
rv = ap_get_brigade(f->next, ctx->bb, mode, block, readbytes);
/* Don't terminate on EAGAIN (or success with an empty brigade in
* non-blocking mode), just return focus.
*/
if (block == APR_NONBLOCK_READ
&& (APR_STATUS_IS_EAGAIN(rv)
|| (rv == APR_SUCCESS && APR_BRIGADE_EMPTY(ctx->bb)))) {
return rv;
}
if (rv != APR_SUCCESS) {
inflateEnd(&ctx->stream);
return rv;
}
for (bkt = APR_BRIGADE_FIRST(ctx->bb);
bkt != APR_BRIGADE_SENTINEL(ctx->bb);
bkt = APR_BUCKET_NEXT(bkt))
{
const char *data;
apr_size_t len;
if (APR_BUCKET_IS_EOS(bkt)) {
if (!ctx->done) {
inflateEnd(&ctx->stream);
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02481)
"Encountered premature end-of-stream while inflating");
return APR_EGENERAL;
}
/* Move everything to the returning brigade. */
APR_BUCKET_REMOVE(bkt);
APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, bkt);
break;
}
if (APR_BUCKET_IS_FLUSH(bkt)) {
apr_bucket *tmp_b;
if (!ctx->done) {
ctx->inflate_total += ctx->stream.avail_out;
zRC = inflate(&(ctx->stream), Z_SYNC_FLUSH);
ctx->inflate_total -= ctx->stream.avail_out;
if (zRC != Z_OK) {
inflateEnd(&ctx->stream);
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01391)
"Zlib error %d inflating data (%s)", zRC,
ctx->stream.msg);
return APR_EGENERAL;
}
if (inflate_limit && ctx->inflate_total > inflate_limit) {
inflateEnd(&ctx->stream);
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02647)
"Inflated content length of %" APR_OFF_T_FMT
" is larger than the configured limit"
" of %" APR_OFF_T_FMT,
ctx->inflate_total, inflate_limit);
return APR_ENOSPC;
}
if (!check_ratio(r, ctx, dc)) {
inflateEnd(&ctx->stream);
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02805)
"Inflated content ratio is larger than the "
"configured limit %i by %i time(s)",
dc->ratio_limit, dc->ratio_burst);
return APR_EINVAL;
}
consume_buffer(ctx, c, c->bufferSize - ctx->stream.avail_out,
UPDATE_CRC, ctx->proc_bb);
}
/* Flush everything so far in the returning brigade, but continue
* reading should EOS/more follow (don't lose them).
*/
tmp_b = APR_BUCKET_PREV(bkt);
APR_BUCKET_REMOVE(bkt);
APR_BRIGADE_INSERT_TAIL(ctx->proc_bb, bkt);
bkt = tmp_b;
continue;
}
/* sanity check - data after completed compressed body and before eos? */
if (ctx->done) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02482)
"Encountered extra data after compressed data");
return APR_EGENERAL;
}
/* read */
apr_bucket_read(bkt, &data, &len, APR_BLOCK_READ);
if (!len) {
continue;
}
if (len > APR_INT32_MAX) {
apr_bucket_split(bkt, APR_INT32_MAX);
apr_bucket_read(bkt, &data, &len, APR_BLOCK_READ);
}
if (ctx->zlib_flags) {
rv = consume_zlib_flags(ctx, &data, &len);
if (rv == APR_SUCCESS) {
ctx->zlib_flags = 0;
}
if (!len) {
continue;
}
}
/* pass through zlib inflate. */
ctx->stream.next_in = (unsigned char *)data;
ctx->stream.avail_in = (int)len;
if (!ctx->validation_buffer) {
while (ctx->stream.avail_in != 0) {
if (ctx->stream.avail_out == 0) {
consume_buffer(ctx, c, c->bufferSize, UPDATE_CRC,
ctx->proc_bb);
}
ctx->inflate_total += ctx->stream.avail_out;
zRC = inflate(&ctx->stream, Z_NO_FLUSH);
ctx->inflate_total -= ctx->stream.avail_out;
if (zRC != Z_OK && zRC != Z_STREAM_END) {
inflateEnd(&ctx->stream);
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01392)
"Zlib error %d inflating data (%s)", zRC,
ctx->stream.msg);
return APR_EGENERAL;
}
if (inflate_limit && ctx->inflate_total > inflate_limit) {
inflateEnd(&ctx->stream);
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02648)
"Inflated content length of %" APR_OFF_T_FMT
" is larger than the configured limit"
" of %" APR_OFF_T_FMT,
ctx->inflate_total, inflate_limit);
return APR_ENOSPC;
}
if (!check_ratio(r, ctx, dc)) {
inflateEnd(&ctx->stream);
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(02649)
"Inflated content ratio is larger than the "
"configured limit %i by %i time(s)",
dc->ratio_limit, dc->ratio_burst);
return APR_EINVAL;
}
if (zRC == Z_STREAM_END) {
ctx->validation_buffer = apr_pcalloc(r->pool,
VALIDATION_SIZE);
ctx->validation_buffer_length = 0;
break;
}
}
}
if (ctx->validation_buffer) {
apr_size_t avail, valid;
unsigned char *buf = ctx->validation_buffer;
avail = ctx->stream.avail_in;
valid = (apr_size_t)VALIDATION_SIZE -
ctx->validation_buffer_length;
/*
* We have inflated all data. Now try to capture the
* validation bytes. We may not have them all available
* right now, but capture what is there.
*/
if (avail < valid) {
memcpy(buf + ctx->validation_buffer_length,
ctx->stream.next_in, avail);
ctx->validation_buffer_length += avail;
continue;
}
memcpy(buf + ctx->validation_buffer_length,
ctx->stream.next_in, valid);
ctx->validation_buffer_length += valid;
ap_log_rerror(APLOG_MARK, APLOG_DEBUG, 0, r, APLOGNO(01393)
"Zlib: Inflated %" APR_UINT64_T_FMT
" to %" APR_UINT64_T_FMT " : URL %s",
(apr_uint64_t)ctx->stream.total_in,
(apr_uint64_t)ctx->stream.total_out, r->uri);
consume_buffer(ctx, c, c->bufferSize - ctx->stream.avail_out,
UPDATE_CRC, ctx->proc_bb);
{
unsigned long compCRC, compLen;
compCRC = getLong(buf);
if (ctx->crc != compCRC) {
inflateEnd(&ctx->stream);
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01394)
"Zlib: CRC error inflating data");
return APR_EGENERAL;
}
compLen = getLong(buf + VALIDATION_SIZE / 2);
/* gzip stores original size only as 4 byte value */
if ((ctx->stream.total_out & 0xFFFFFFFF) != compLen) {
inflateEnd(&ctx->stream);
ap_log_rerror(APLOG_MARK, APLOG_WARNING, 0, r, APLOGNO(01395)
"Zlib: Length %" APR_UINT64_T_FMT
" of inflated data does not match"
" expected value %ld",
(apr_uint64_t)ctx->stream.total_out, compLen);
return APR_EGENERAL;
}
}
inflateEnd(&ctx->stream);
ctx->done = 1;
/* Did we have trailing data behind the closing 8 bytes? */
if (avail > valid) {
ap_log_rerror(APLOG_MARK, APLOG_ERR, 0, r, APLOGNO(02485)
"Encountered extra data after compressed data");
return APR_EGENERAL;
}
}
}
apr_brigade_cleanup(ctx->bb);
}
/* If we are about to return nothing for a 'blocking' read and we have
* some data in our zlib buffer, flush it out so we can return something.
*/
if (block == APR_BLOCK_READ &&
APR_BRIGADE_EMPTY(ctx->proc_bb) &&
ctx->stream.avail_out < c->bufferSize) {
consume_buffer(ctx, c, c->bufferSize - ctx->stream.avail_out,
UPDATE_CRC, ctx->proc_bb);
}
if (!APR_BRIGADE_EMPTY(ctx->proc_bb)) {
if (apr_brigade_partition(ctx->proc_bb, readbytes, &bkt) == APR_INCOMPLETE) {
APR_BRIGADE_CONCAT(bb, ctx->proc_bb);
}
else {
APR_BRIGADE_CONCAT(bb, ctx->proc_bb);
apr_brigade_split_ex(bb, bkt, ctx->proc_bb);
}
if (APR_BUCKET_IS_EOS(APR_BRIGADE_LAST(bb))) {
ap_remove_input_filter(f);
}
}
return APR_SUCCESS;
}