/* ==================================================================== * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. * ==================================================================== */ #include #include /* This conditional isn't defined anywhere yet. */ #ifdef HAVE_ZUTIL_H #include #endif #include "serf.h" #include "serf_bucket_util.h" /* magic header */ static char deflate_magic[2] = { '\037', '\213' }; #define DEFLATE_MAGIC_SIZE 10 #define DEFLATE_VERIFY_SIZE 8 #define DEFLATE_BUFFER_SIZE 8096 static const int DEFLATE_WINDOW_SIZE = -15; static const int DEFLATE_MEMLEVEL = 9; typedef struct { serf_bucket_t *stream; serf_bucket_t *inflate_stream; int format; /* Are we 'deflate' or 'gzip'? */ enum { STATE_READING_HEADER, /* reading the gzip header */ STATE_HEADER, /* read the gzip header */ STATE_INIT, /* init'ing zlib functions */ STATE_INFLATE, /* inflating the content now */ STATE_READING_VERIFY, /* reading the final gzip CRC */ STATE_VERIFY, /* verifying the final gzip CRC */ STATE_FINISH, /* clean up after reading body */ STATE_DONE, /* body is done; we'll return EOF here */ } state; z_stream zstream; char hdr_buffer[DEFLATE_MAGIC_SIZE]; unsigned char buffer[DEFLATE_BUFFER_SIZE]; unsigned long crc; int windowSize; int memLevel; int bufferSize; /* How much of the chunk, or the terminator, do we have left to read? */ apr_size_t stream_left; /* How much are we supposed to read? */ apr_size_t stream_size; int stream_status; /* What was the last status we read? */ } deflate_context_t; /* Inputs a string and returns a long. */ static unsigned long getLong(unsigned char *string) { return ((unsigned long)string[0]) | (((unsigned long)string[1]) << 8) | (((unsigned long)string[2]) << 16) | (((unsigned long)string[3]) << 24); } serf_bucket_t *serf_bucket_deflate_create( serf_bucket_t *stream, serf_bucket_alloc_t *allocator, int format) { deflate_context_t *ctx; ctx = serf_bucket_mem_alloc(allocator, sizeof(*ctx)); ctx->stream = stream; ctx->stream_status = APR_SUCCESS; ctx->inflate_stream = serf_bucket_aggregate_create(allocator); ctx->format = format; ctx->crc = 0; /* zstream must be NULL'd out. */ memset(&ctx->zstream, 0, sizeof(ctx->zstream)); switch (ctx->format) { case SERF_DEFLATE_GZIP: ctx->state = STATE_READING_HEADER; break; case SERF_DEFLATE_DEFLATE: /* deflate doesn't have a header. */ ctx->state = STATE_INIT; break; default: /* Not reachable */ return NULL; } /* Initial size of gzip header. */ ctx->stream_left = ctx->stream_size = DEFLATE_MAGIC_SIZE; ctx->windowSize = DEFLATE_WINDOW_SIZE; ctx->memLevel = DEFLATE_MEMLEVEL; ctx->bufferSize = DEFLATE_BUFFER_SIZE; return serf_bucket_create(&serf_bucket_type_deflate, allocator, ctx); } static void serf_deflate_destroy_and_data(serf_bucket_t *bucket) { deflate_context_t *ctx = bucket->data; if (ctx->state > STATE_INIT && ctx->state <= STATE_FINISH) inflateEnd(&ctx->zstream); /* We may have appended inflate_stream into the stream bucket. * If so, avoid free'ing it twice. */ if (ctx->inflate_stream) { serf_bucket_destroy(ctx->inflate_stream); } serf_bucket_destroy(ctx->stream); serf_default_destroy_and_data(bucket); } static apr_status_t serf_deflate_read(serf_bucket_t *bucket, apr_size_t requested, const char **data, apr_size_t *len) { deflate_context_t *ctx = bucket->data; apr_status_t status; const char *private_data; apr_size_t private_len; int zRC; while (1) { switch (ctx->state) { case STATE_READING_HEADER: case STATE_READING_VERIFY: status = serf_bucket_read(ctx->stream, ctx->stream_left, &private_data, &private_len); if (SERF_BUCKET_READ_ERROR(status)) { return status; } memcpy(ctx->hdr_buffer + (ctx->stream_size - ctx->stream_left), private_data, private_len); ctx->stream_left -= private_len; if (ctx->stream_left == 0) { ctx->state++; if (APR_STATUS_IS_EAGAIN(status)) { *len = 0; return status; } } else if (status) { *len = 0; return status; } break; case STATE_HEADER: if (ctx->hdr_buffer[0] != deflate_magic[0] || ctx->hdr_buffer[1] != deflate_magic[1]) { return SERF_ERROR_DECOMPRESSION_FAILED; } if (ctx->hdr_buffer[3] != 0) { return SERF_ERROR_DECOMPRESSION_FAILED; } ctx->state++; break; case STATE_VERIFY: { unsigned long compCRC, compLen, actualLen; /* Do the checksum computation. */ compCRC = getLong((unsigned char*)ctx->hdr_buffer); if (ctx->crc != compCRC) { return SERF_ERROR_DECOMPRESSION_FAILED; } compLen = getLong((unsigned char*)ctx->hdr_buffer + 4); /* The length in the trailer is module 2^32, so do the same for the actual length. */ actualLen = ctx->zstream.total_out; actualLen &= 0xFFFFFFFF; if (actualLen != compLen) { return SERF_ERROR_DECOMPRESSION_FAILED; } ctx->state++; break; } case STATE_INIT: zRC = inflateInit2(&ctx->zstream, ctx->windowSize); if (zRC != Z_OK) { return SERF_ERROR_DECOMPRESSION_FAILED; } ctx->zstream.next_out = ctx->buffer; ctx->zstream.avail_out = ctx->bufferSize; ctx->state++; break; case STATE_FINISH: inflateEnd(&ctx->zstream); serf_bucket_aggregate_prepend(ctx->stream, ctx->inflate_stream); ctx->inflate_stream = 0; ctx->state++; break; case STATE_INFLATE: /* Do we have anything already uncompressed to read? */ status = serf_bucket_read(ctx->inflate_stream, requested, data, len); if (SERF_BUCKET_READ_ERROR(status)) { return status; } /* Hide EOF. */ if (APR_STATUS_IS_EOF(status)) { status = ctx->stream_status; if (APR_STATUS_IS_EOF(status)) { /* We've read all of the data from our stream, but we * need to continue to iterate until we flush * out the zlib buffer. */ status = APR_SUCCESS; } } if (*len != 0) { return status; } /* We tried; but we have nothing buffered. Fetch more. */ /* It is possible that we maxed out avail_out before * exhausting avail_in; therefore, continue using the * previous buffer. Otherwise, fetch more data from * our stream bucket. */ if (ctx->zstream.avail_in == 0) { /* When we empty our inflated stream, we'll return this * status - this allow us to eventually pass up EAGAINs. */ ctx->stream_status = serf_bucket_read(ctx->stream, ctx->bufferSize, &private_data, &private_len); if (SERF_BUCKET_READ_ERROR(ctx->stream_status)) { return ctx->stream_status; } if (!private_len && APR_STATUS_IS_EAGAIN(ctx->stream_status)) { *len = 0; status = ctx->stream_status; ctx->stream_status = APR_SUCCESS; return status; } ctx->zstream.next_in = (unsigned char*)private_data; ctx->zstream.avail_in = private_len; } while (1) { zRC = inflate(&ctx->zstream, Z_NO_FLUSH); /* We're full or zlib requires more space. Either case, clear out our buffer, reset, and return. */ if (zRC == Z_BUF_ERROR || ctx->zstream.avail_out == 0) { serf_bucket_t *tmp; ctx->zstream.next_out = ctx->buffer; private_len = ctx->bufferSize - ctx->zstream.avail_out; ctx->crc = crc32(ctx->crc, (const Bytef *)ctx->buffer, private_len); /* FIXME: There probably needs to be a free func. */ tmp = SERF_BUCKET_SIMPLE_STRING_LEN((char *)ctx->buffer, private_len, bucket->allocator); serf_bucket_aggregate_append(ctx->inflate_stream, tmp); ctx->zstream.avail_out = ctx->bufferSize; break; } if (zRC == Z_STREAM_END) { serf_bucket_t *tmp; private_len = ctx->bufferSize - ctx->zstream.avail_out; ctx->crc = crc32(ctx->crc, (const Bytef *)ctx->buffer, private_len); /* FIXME: There probably needs to be a free func. */ tmp = SERF_BUCKET_SIMPLE_STRING_LEN((char *)ctx->buffer, private_len, bucket->allocator); serf_bucket_aggregate_append(ctx->inflate_stream, tmp); ctx->zstream.avail_out = ctx->bufferSize; /* Push back the remaining data to be read. */ tmp = serf_bucket_aggregate_create(bucket->allocator); serf_bucket_aggregate_prepend(tmp, ctx->stream); ctx->stream = tmp; /* We now need to take the remaining avail_in and * throw it in ctx->stream so our next read picks it up. */ tmp = SERF_BUCKET_SIMPLE_STRING_LEN( (const char*)ctx->zstream.next_in, ctx->zstream.avail_in, bucket->allocator); serf_bucket_aggregate_prepend(ctx->stream, tmp); switch (ctx->format) { case SERF_DEFLATE_GZIP: ctx->stream_left = ctx->stream_size = DEFLATE_VERIFY_SIZE; ctx->state++; break; case SERF_DEFLATE_DEFLATE: /* Deflate does not have a verify footer. */ ctx->state = STATE_FINISH; break; default: /* Not reachable */ return APR_EGENERAL; } break; } /* Any other error? */ if (zRC != Z_OK) { return SERF_ERROR_DECOMPRESSION_FAILED; } /* As long as zRC == Z_OK, just keep looping. */ } /* Okay, we've inflated. Try to read. */ status = serf_bucket_read(ctx->inflate_stream, requested, data, len); /* Hide EOF. */ if (APR_STATUS_IS_EOF(status)) { status = ctx->stream_status; /* If the inflation wasn't finished, return APR_SUCCESS. */ if (zRC != Z_STREAM_END) return APR_SUCCESS; /* If our stream is finished too and all data was inflated, * return SUCCESS so we'll iterate one more time. */ if (APR_STATUS_IS_EOF(status)) { /* No more data to read from the stream, and everything inflated. If all data was received correctly, state should have been advanced to STATE_READING_VERIFY or STATE_FINISH. If not, then the data was incomplete and we have an error. */ if (ctx->state != STATE_INFLATE) return APR_SUCCESS; else return SERF_ERROR_DECOMPRESSION_FAILED; } } return status; case STATE_DONE: /* We're done inflating. Use our finished buffer. */ return serf_bucket_read(ctx->stream, requested, data, len); default: /* Not reachable */ return APR_EGENERAL; } } /* NOTREACHED */ } /* ### need to implement */ #define serf_deflate_readline NULL #define serf_deflate_peek NULL const serf_bucket_type_t serf_bucket_type_deflate = { "DEFLATE", serf_deflate_read, serf_deflate_readline, serf_default_read_iovec, serf_default_read_for_sendfile, serf_default_read_bucket, serf_deflate_peek, serf_deflate_destroy_and_data, };