#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include "zipint.h"
struct context {
zip_error_t error;
bool end_of_input;
bool end_of_stream;
bool can_store;
bool is_stored;
bool compress;
zip_int32_t method;
zip_uint64_t size;
zip_int64_t first_read;
zip_uint8_t buffer[BUFSIZE];
zip_compression_algorithm_t *algorithm;
void *ud;
};
struct implementation {
zip_uint16_t method;
zip_compression_algorithm_t *compress;
zip_compression_algorithm_t *decompress;
};
static struct implementation implementations[] = {
{ZIP_CM_DEFLATE, &zip_algorithm_deflate_compress, &zip_algorithm_deflate_decompress},
#if defined(HAVE_LIBBZ2)
{ZIP_CM_BZIP2, &zip_algorithm_bzip2_compress, &zip_algorithm_bzip2_decompress},
#endif
};
static size_t implementations_size = sizeof(implementations) / sizeof(implementations[0]);
static zip_source_t *compression_source_new(zip_t *za, zip_source_t *src, zip_int32_t method, bool compress, int compression_flags);
static zip_int64_t compress_callback(zip_source_t *, void *, void *, zip_uint64_t, zip_source_cmd_t);
static void context_free(struct context *ctx);
static struct context *context_new(zip_int32_t method, bool compress, int compression_flags, zip_compression_algorithm_t *algorithm);
static zip_int64_t compress_read(zip_source_t *, struct context *, void *, zip_uint64_t);
static zip_compression_algorithm_t *
get_algorithm(zip_int32_t method, bool compress) {
size_t i;
zip_uint16_t real_method = ZIP_CM_ACTUAL(method);
for (i = 0; i < implementations_size; i++) {
if (implementations[i].method == real_method) {
if (compress) {
return implementations[i].compress;
}
else {
return implementations[i].decompress;
}
}
}
return NULL;
}
bool
zip_compression_method_supported(zip_int32_t method, bool compress) {
if (method == ZIP_CM_STORE) {
return true;
}
return get_algorithm(method, compress) != NULL;
}
zip_source_t *
zip_source_compress(zip_t *za, zip_source_t *src, zip_int32_t method, int compression_flags) {
return compression_source_new(za, src, method, true, compression_flags);
}
zip_source_t *
zip_source_decompress(zip_t *za, zip_source_t *src, zip_int32_t method) {
return compression_source_new(za, src, method, false, 0);
}
static zip_source_t *
compression_source_new(zip_t *za, zip_source_t *src, zip_int32_t method, bool compress, int compression_flags) {
struct context *ctx;
zip_source_t *s2;
zip_compression_algorithm_t *algorithm = NULL;
if (src == NULL) {
zip_error_set(&za->error, ZIP_ER_INVAL, 0);
return NULL;
}
if ((algorithm = get_algorithm(method, compress)) == NULL) {
zip_error_set(&za->error, ZIP_ER_COMPNOTSUPP, 0);
return NULL;
}
if ((ctx = context_new(method, compress, compression_flags, algorithm)) == NULL) {
zip_error_set(&za->error, ZIP_ER_MEMORY, 0);
return NULL;
}
if ((s2 = zip_source_layered(za, src, compress_callback, ctx)) == NULL) {
context_free(ctx);
return NULL;
}
return s2;
}
static struct context *
context_new(zip_int32_t method, bool compress, int compression_flags, zip_compression_algorithm_t *algorithm) {
struct context *ctx;
if ((ctx = (struct context *)malloc(sizeof(*ctx))) == NULL) {
return NULL;
}
zip_error_init(&ctx->error);
ctx->can_store = compress ? ZIP_CM_IS_DEFAULT(method) : false;
ctx->algorithm = algorithm;
ctx->method = method;
ctx->compress = compress;
ctx->end_of_input = false;
ctx->end_of_stream = false;
ctx->is_stored = false;
if ((ctx->ud = ctx->algorithm->allocate(ZIP_CM_ACTUAL(method), compression_flags, &ctx->error)) == NULL) {
zip_error_fini(&ctx->error);
free(ctx);
return NULL;
}
return ctx;
}
static void
context_free(struct context *ctx) {
if (ctx == NULL) {
return;
}
ctx->algorithm->deallocate(ctx->ud);
zip_error_fini(&ctx->error);
free(ctx);
}
static zip_int64_t
compress_read(zip_source_t *src, struct context *ctx, void *data, zip_uint64_t len) {
zip_compression_status_t ret;
bool end;
zip_int64_t n;
zip_uint64_t out_offset;
zip_uint64_t out_len;
if (zip_error_code_zip(&ctx->error) != ZIP_ER_OK) {
return -1;
}
if (len == 0 || ctx->end_of_stream) {
return 0;
}
out_offset = 0;
end = false;
while (!end && out_offset < len) {
out_len = len - out_offset;
ret = ctx->algorithm->process(ctx->ud, (zip_uint8_t *)data + out_offset, &out_len);
if (ret != ZIP_COMPRESSION_ERROR) {
out_offset += out_len;
}
switch (ret) {
case ZIP_COMPRESSION_END:
ctx->end_of_stream = true;
if (!ctx->end_of_input) {
}
if (ctx->first_read < 0) {
zip_error_set(&ctx->error, ZIP_ER_INTERNAL, 0);
end = true;
break;
}
if (ctx->can_store && (zip_uint64_t)ctx->first_read <= out_offset) {
ctx->is_stored = true;
ctx->size = (zip_uint64_t)ctx->first_read;
memcpy(data, ctx->buffer, ctx->size);
return (zip_int64_t)ctx->size;
}
end = true;
break;
case ZIP_COMPRESSION_OK:
break;
case ZIP_COMPRESSION_NEED_DATA:
if (ctx->end_of_input) {
end = true;
break;
}
if ((n = zip_source_read(src, ctx->buffer, sizeof(ctx->buffer))) < 0) {
_zip_error_set_from_source(&ctx->error, src);
end = true;
break;
}
else if (n == 0) {
ctx->end_of_input = true;
ctx->algorithm->end_of_input(ctx->ud);
if (ctx->first_read < 0) {
ctx->first_read = 0;
}
}
else {
if (ctx->first_read >= 0) {
ctx->can_store = false;
}
else {
ctx->first_read = n;
}
ctx->algorithm->input(ctx->ud, ctx->buffer, (zip_uint64_t)n);
}
break;
case ZIP_COMPRESSION_ERROR:
if (zip_error_code_zip(&ctx->error) == ZIP_ER_OK) {
zip_error_set(&ctx->error, ZIP_ER_INTERNAL, 0);
}
end = true;
break;
}
}
if (out_offset > 0) {
ctx->can_store = false;
ctx->size += out_offset;
return (zip_int64_t)out_offset;
}
return (zip_error_code_zip(&ctx->error) == ZIP_ER_OK) ? 0 : -1;
}
static zip_int64_t
compress_callback(zip_source_t *src, void *ud, void *data, zip_uint64_t len, zip_source_cmd_t cmd) {
struct context *ctx;
ctx = (struct context *)ud;
switch (cmd) {
case ZIP_SOURCE_OPEN:
ctx->size = 0;
ctx->end_of_input = false;
ctx->end_of_stream = false;
ctx->is_stored = false;
ctx->first_read = -1;
if (!ctx->algorithm->start(ctx->ud)) {
return -1;
}
return 0;
case ZIP_SOURCE_READ:
return compress_read(src, ctx, data, len);
case ZIP_SOURCE_CLOSE:
if (!ctx->algorithm->end(ctx->ud)) {
return -1;
}
return 0;
case ZIP_SOURCE_STAT: {
zip_stat_t *st;
st = (zip_stat_t *)data;
if (ctx->compress) {
if (ctx->end_of_stream) {
st->comp_method = ctx->is_stored ? ZIP_CM_STORE : ZIP_CM_ACTUAL(ctx->method);
st->comp_size = ctx->size;
st->valid |= ZIP_STAT_COMP_SIZE | ZIP_STAT_COMP_METHOD;
}
else {
st->valid &= ~(ZIP_STAT_COMP_SIZE | ZIP_STAT_COMP_METHOD);
}
}
else {
st->comp_method = ZIP_CM_STORE;
st->valid |= ZIP_STAT_COMP_METHOD;
if (ctx->end_of_stream) {
st->size = ctx->size;
st->valid |= ZIP_STAT_SIZE;
}
else {
st->valid &= ~ZIP_STAT_SIZE;
}
}
}
return 0;
case ZIP_SOURCE_GET_COMPRESSION_FLAGS:
return ctx->is_stored ? 0 : ctx->algorithm->compression_flags(ctx->ud);
case ZIP_SOURCE_ERROR:
return zip_error_to_data(&ctx->error, data, len);
case ZIP_SOURCE_FREE:
context_free(ctx);
return 0;
case ZIP_SOURCE_SUPPORTS:
return ZIP_SOURCE_SUPPORTS_READABLE | zip_source_make_command_bitmap(ZIP_SOURCE_GET_COMPRESSION_FLAGS, -1);
default:
zip_error_set(&ctx->error, ZIP_ER_INTERNAL, 0);
return -1;
}
}