summaryrefslogtreecommitdiff
path: root/compress.c
diff options
context:
space:
mode:
Diffstat (limited to 'compress.c')
-rw-r--r--compress.c621
1 files changed, 621 insertions, 0 deletions
diff --git a/compress.c b/compress.c
new file mode 100644
index 0000000..a36979d
--- /dev/null
+++ b/compress.c
@@ -0,0 +1,621 @@
+#include "links.h"
+
+unsigned long decompressed_cache_size;
+
+static int display_error(struct terminal *term, unsigned char *msg, int *errp)
+{
+ struct window *win;
+ if (errp) *errp = 1;
+ if (!term) return 0;
+ if (!errp) foreach(win, term->windows) if (win->handler == dialog_func) {
+ struct dialog_data *d = win->data;
+ if (d->dlg->title == msg) {
+ return 0;
+ }
+ }
+ return 1;
+}
+
+#ifdef HAVE_ANY_COMPRESSION
+
+static void decoder_memory_init(unsigned char **p, size_t *size, off_t init_length)
+{
+ if (init_length > 0 && init_length < MAXINT) *size = init_length;
+ else *size = 4096;
+ *p = mem_alloc(*size);
+}
+
+static int decoder_memory_expand(unsigned char **p, size_t size, size_t *addsize)
+{
+ unsigned char *pp;
+ size_t add = size / 4 + 1;
+ if (size + add < size) {
+ if (add > 1) add >>= 1;
+ else overalloc();
+ }
+ pp = mem_realloc_mayfail(*p, size + add);
+ if (!pp) {
+ *addsize = 0;
+ return -1;
+ }
+ *addsize = add;
+ *p = pp;
+ return 0;
+}
+
+static void decompress_error(struct terminal *term, struct cache_entry *ce, unsigned char *lib, unsigned char *msg, int *errp)
+{
+ unsigned char *u, *uu, *server;
+ if ((u = parse_http_header(ce->head, "Content-Encoding", NULL))) {
+ mem_free(u);
+ if ((server = get_host_name(ce->url))) {
+ add_blacklist_entry(server, BL_NO_COMPRESSION);
+ mem_free(server);
+ }
+ }
+ if (!display_error(term, TEXT_(T_DECOMPRESSION_ERROR), errp)) return;
+ u = stracpy(ce->url);
+ if ((uu = strchr(u, POST_CHAR))) *uu = 0;
+ msg_box(term, getml(u, NULL), TEXT_(T_DECOMPRESSION_ERROR), AL_CENTER | AL_EXTD_TEXT, TEXT_(T_ERROR_DECOMPRESSING_), u, TEXT_(T__wITH_), lib, ": ", msg, NULL, NULL, 1, TEXT_(T_CANCEL), NULL, B_ENTER | B_ESC);
+}
+
+#endif
+
+#ifdef HAVE_ZLIB
+#include <zlib.h>
+static int decode_gzip(struct terminal *term, struct cache_entry *ce, unsigned char **p_start, size_t *p_len, int defl, int *errp)
+{
+ unsigned char err;
+ unsigned char memory_error;
+ unsigned char skip_gzip_header;
+ unsigned char old_zlib;
+ z_stream z;
+ off_t offset;
+ size_t header;
+ int r;
+ unsigned char *p;
+ struct fragment *f;
+ size_t size;
+
+ retry_after_memory_error:
+ memory_error = 0;
+ header = 0;
+ decoder_memory_init(&p, &size, ce->length);
+ init_again:
+ err = 0;
+ skip_gzip_header = 0;
+ old_zlib = 0;
+ memset(&z, 0, sizeof z);
+ z.next_in = NULL;
+ z.avail_in = 0;
+ z.next_out = p;
+ z.avail_out = size;
+ z.zalloc = NULL;
+ z.zfree = NULL;
+ z.opaque = NULL;
+ r = inflateInit2(&z, defl == 1 ? 15 : defl == 2 ? -15 : 15 + 16);
+ init_failed:
+ switch (r) {
+ case Z_OK: break;
+ case Z_MEM_ERROR: memory_error = 1;
+ err = 1;
+ goto after_inflateend;
+ case Z_STREAM_ERROR:
+ if (!defl && !old_zlib) {
+ if (defrag_entry(ce)) {
+ memory_error = 1;
+ err = 1;
+ goto after_inflateend;
+ }
+ r = inflateInit2(&z, -15);
+ skip_gzip_header = 1;
+ old_zlib = 1;
+ goto init_failed;
+ }
+ decompress_error(term, ce, "zlib", z.msg ? (unsigned char *)z.msg : (unsigned char *)"Invalid parameter", errp);
+ err = 1;
+ goto after_inflateend;
+ case Z_VERSION_ERROR: decompress_error(term, ce, "zlib", z.msg ? (unsigned char *)z.msg : (unsigned char *)"Bad zlib version", errp);
+ err = 1;
+ goto after_inflateend;
+ default: decompress_error(term, ce, "zlib", z.msg ? (unsigned char *)z.msg : (unsigned char *)"Unknown return value on inflateInit2", errp);
+ err = 1;
+ goto after_inflateend;
+ }
+ offset = 0;
+ foreach(f, ce->frag) {
+ if (f->offset != offset) break;
+ z.next_in = f->data;
+ z.avail_in = f->length;
+ if (header && !offset) {
+ z.next_in = (unsigned char *)z.next_in + header;
+ z.avail_in -= header;
+ }
+ repeat_frag:
+ if (skip_gzip_header == 2) {
+ if (z.avail_in < 8) goto finish;
+ z.next_in = (unsigned char *)z.next_in + 8;
+ z.avail_in -= 8;
+ skip_gzip_header = 1;
+ }
+ if (skip_gzip_header) {
+ /* if zlib is old, we have to skip gzip header manually
+ otherwise zlib 1.2.x can do it automatically */
+ unsigned char *head = z.next_in;
+ unsigned headlen = 10;
+ if (z.avail_in <= 11) goto finish;
+ if (head[0] != 0x1f || head[1] != 0x8b) {
+ decompress_error(term, ce, "zlib", TEXT_(T_COMPRESSED_ERROR), errp);
+ err = 1;
+ goto finish;
+ }
+ if (head[2] != 8 || head[3] & 0xe0) {
+ decompress_error(term, ce, "zlib", TEXT_(T_UNKNOWN_COMPRESSION_METHOD), errp);
+ err = 1;
+ goto finish;
+ }
+ if (head[3] & 0x04) {
+ headlen += 2 + head[10] + (head[11] << 8);
+ if (headlen >= z.avail_in) goto finish;
+ }
+ if (head[3] & 0x08) {
+ do {
+ headlen++;
+ if (headlen >= z.avail_in) goto finish;
+ } while (head[headlen - 1]);
+ }
+ if (head[3] & 0x10) {
+ do {
+ headlen++;
+ if (headlen >= z.avail_in) goto finish;
+ } while (head[headlen - 1]);
+ }
+ if (head[3] & 0x01) {
+ headlen += 2;
+ if (headlen >= z.avail_in) goto finish;
+ }
+ z.next_in = (unsigned char *)z.next_in + headlen;
+ z.avail_in -= headlen;
+ skip_gzip_header = 0;
+ }
+ r = inflate(&z, (void *)f->next == (void *)&ce->frag ? Z_SYNC_FLUSH : Z_NO_FLUSH);
+ switch (r) {
+ case Z_OK: break;
+ case Z_BUF_ERROR: break;
+ case Z_STREAM_END: r = inflateEnd(&z);
+ if (r != Z_OK) goto end_failed;
+ r = inflateInit2(&z, old_zlib ? -15 : defl ? 15 : 15 + 16);
+ if (r != Z_OK) {
+ old_zlib = 0;
+ goto init_failed;
+ }
+ if (old_zlib) {
+ skip_gzip_header = 2;
+ }
+ break;
+ case Z_NEED_DICT:
+ case Z_DATA_ERROR: if (defl == 1) {
+ defl = 2;
+ r = inflateEnd(&z);
+ if (r != Z_OK) goto end_failed;
+ goto init_again;
+ }
+ decompress_error(term, ce, "zlib", z.msg ? (unsigned char *)z.msg : TEXT_(T_COMPRESSED_ERROR), errp);
+ err = 1;
+ goto finish;
+ case Z_STREAM_ERROR: decompress_error(term, ce, "zlib", z.msg ? (unsigned char *)z.msg : (unsigned char *)"Internal error on inflate", errp);
+ err = 1;
+ goto finish;
+ case Z_MEM_ERROR:
+ mem_error: memory_error = 1;
+ err = 1;
+ goto finish;
+ default: decompress_error(term, ce, "zlib", z.msg ? (unsigned char *)z.msg : (unsigned char *)"Unknown return value on inflate", errp);
+ err = 1;
+ break;
+ }
+ if (!z.avail_out) {
+ size_t addsize;
+ if (decoder_memory_expand(&p, size, &addsize) < 0)
+ goto mem_error;
+ z.next_out = p + size;
+ z.avail_out = addsize;
+ size += addsize;
+ }
+ if (z.avail_in) goto repeat_frag;
+ /* In zlib 1.1.3, inflate(Z_SYNC_FLUSH) doesn't work.
+ The following line fixes it --- for last fragment, loop until
+ we get an eof. */
+ if (r == Z_OK && (void *)f->next == (void *)&ce->frag) goto repeat_frag;
+ offset += f->length;
+ }
+ finish:
+ r = inflateEnd(&z);
+ end_failed:
+ switch (r) {
+ case Z_OK: break;
+ case Z_STREAM_ERROR: decompress_error(term, ce, "zlib", z.msg ? (unsigned char *)z.msg : (unsigned char *)"Internal error on inflateEnd", errp);
+ err = 1;
+ break;
+ case Z_MEM_ERROR: memory_error = 1;
+ err = 1;
+ break;
+ default: decompress_error(term, ce, "zlib", z.msg ? (unsigned char *)z.msg : (unsigned char *)"Unknown return value on inflateEnd", errp);
+ err = 1;
+ break;
+ }
+ after_inflateend:
+ if (memory_error) {
+ mem_free(p);
+ if (out_of_memory(NULL, 0))
+ goto retry_after_memory_error;
+ decompress_error(term, ce, "zlib", z.msg ? (unsigned char *)z.msg : TEXT_(T_OUT_OF_MEMORY), errp);
+ return 1;
+ }
+ if (err && (unsigned char *)z.next_out == p) {
+ mem_free(p);
+ return 1;
+ }
+ *p_start = p;
+ *p_len = (unsigned char *)z.next_out - (unsigned char *)p;
+ *p_start = mem_realloc(*p_start, *p_len);
+ return 0;
+}
+#endif
+
+#ifdef HAVE_BZIP2
+#include <bzlib.h>
+static int decode_bzip2(struct terminal *term, struct cache_entry *ce, unsigned char **p_start, size_t *p_len, int *errp)
+{
+ unsigned char err;
+ unsigned char memory_error;
+ bz_stream z;
+ off_t offset;
+ int r;
+ unsigned char *p;
+ struct fragment *f;
+ size_t size;
+
+ retry_after_memory_error:
+ err = 0;
+ memory_error = 0;
+ decoder_memory_init(&p, &size, ce->length);
+ memset(&z, 0, sizeof z);
+ z.next_in = NULL;
+ z.avail_in = 0;
+ z.next_out = p;
+ z.avail_out = size;
+ z.bzalloc = NULL;
+ z.bzfree = NULL;
+ z.opaque = NULL;
+ r = BZ2_bzDecompressInit(&z, 0, 0);
+ init_failed:
+ switch (r) {
+ case BZ_OK: break;
+ case BZ_MEM_ERROR: memory_error = 1;
+ err = 1;
+ goto after_inflateend;
+ case BZ_PARAM_ERROR:
+ decompress_error(term, ce, "bzip2", "Invalid parameter", errp);
+ err = 1;
+ goto after_inflateend;
+ case BZ_CONFIG_ERROR: decompress_error(term, ce, "bzip2", "Bzlib is miscompiled", errp);
+ err = 1;
+ goto after_inflateend;
+ default: decompress_error(term, ce, "bzip2", "Unknown return value on BZ2_bzDecompressInit", errp);
+ err = 1;
+ goto after_inflateend;
+ }
+ offset = 0;
+ foreach(f, ce->frag) {
+ if (f->offset != offset) break;
+ z.next_in = f->data;
+ z.avail_in = f->length;
+ repeat_frag:
+ r = BZ2_bzDecompress(&z);
+ switch (r) {
+ case BZ_OK: break;
+ case BZ_STREAM_END:
+ r = BZ2_bzDecompressEnd(&z);
+ if (r != BZ_OK) goto end_failed;
+ r = BZ2_bzDecompressInit(&z, 0, 0);
+ if (r != BZ_OK) goto init_failed;
+ break;
+ case BZ_DATA_ERROR_MAGIC:
+ case BZ_DATA_ERROR: decompress_error(term, ce, "bzip2", TEXT_(T_COMPRESSED_ERROR), errp);
+ err = 1;
+ goto finish;
+ case BZ_PARAM_ERROR: decompress_error(term, ce, "bzip2", "Internal error on BZ2_bzDecompress", errp);
+ err = 1;
+ goto finish;
+ case BZ_MEM_ERROR:
+ mem_error: memory_error = 1;
+ err = 1;
+ goto finish;
+ default: decompress_error(term, ce, "bzip2", "Unknown return value on BZ2_bzDecompress", errp);
+ err = 1;
+ break;
+ }
+ if (!z.avail_out) {
+ size_t addsize;
+ if (decoder_memory_expand(&p, size, &addsize) < 0)
+ goto mem_error;
+ z.next_out = p + size;
+ z.avail_out = addsize;
+ size += addsize;
+ }
+ if (z.avail_in) goto repeat_frag;
+ offset += f->length;
+ }
+ finish:
+ r = BZ2_bzDecompressEnd(&z);
+ end_failed:
+ switch (r) {
+ case BZ_OK: break;
+ case BZ_PARAM_ERROR: decompress_error(term, ce, "bzip2", "Internal error on BZ2_bzDecompressEnd", errp);
+ err = 1;
+ break;
+ case BZ_MEM_ERROR: memory_error = 1;
+ err = 1;
+ break;
+ default: decompress_error(term, ce, "bzip2", "Unknown return value on BZ2_bzDecompressEnd", errp);
+ err = 1;
+ break;
+ }
+ after_inflateend:
+ if (memory_error) {
+ mem_free(p);
+ if (out_of_memory(NULL, 0))
+ goto retry_after_memory_error;
+ decompress_error(term, ce, "bzip2", TEXT_(T_OUT_OF_MEMORY), errp);
+ return 1;
+ }
+ if (err && (unsigned char *)z.next_out == p) {
+ mem_free(p);
+ return 1;
+ }
+ *p_start = p;
+ *p_len = (unsigned char *)z.next_out - (unsigned char *)p;
+ *p_start = mem_realloc(*p_start, *p_len);
+ return 0;
+}
+#endif
+
+#ifdef HAVE_LZMA
+#undef internal
+#include <lzma.h>
+#define internal internal_
+static int decode_lzma(struct terminal *term, struct cache_entry *ce, unsigned char **p_start, size_t *p_len, int *errp)
+{
+ unsigned char err;
+ unsigned char memory_error;
+ lzma_stream z = LZMA_STREAM_INIT;
+ off_t offset;
+ int r;
+ unsigned char *p;
+ struct fragment *f;
+ size_t size;
+
+ retry_after_memory_error:
+ err = 0;
+ memory_error = 0;
+ decoder_memory_init(&p, &size, ce->length);
+ z.next_in = NULL;
+ z.avail_in = 0;
+ z.next_out = p;
+ z.avail_out = size;
+ r = lzma_auto_decoder(&z, UINT64_MAX, 0);
+ init_failed:
+ switch (r) {
+ case LZMA_OK: break;
+ case LZMA_MEM_ERROR: memory_error = 1;
+ err = 1;
+ goto after_inflateend;
+ case LZMA_OPTIONS_ERROR:
+ decompress_error(term, ce, "lzma", "Invalid parameter", errp);
+ err = 1;
+ goto after_inflateend;
+ case LZMA_PROG_ERROR: decompress_error(term, ce, "lzma", "Lzma is miscompiled", errp);
+ err = 1;
+ goto after_inflateend;
+ default: decompress_error(term, ce, "lzma", "Unknown return value on lzma_auto_decoder", errp);
+ err = 1;
+ goto after_inflateend;
+ }
+ offset = 0;
+ foreach(f, ce->frag) {
+ if (f->offset != offset) break;
+ z.next_in = f->data;
+ z.avail_in = f->length;
+ repeat_frag:
+ r = lzma_code(&z, LZMA_RUN);
+ switch (r) {
+ case LZMA_OK:
+ case LZMA_NO_CHECK:
+ case LZMA_UNSUPPORTED_CHECK:
+ case LZMA_GET_CHECK:
+ break;
+ case LZMA_STREAM_END:
+ lzma_end(&z);
+ r = lzma_auto_decoder(&z, UINT64_MAX, 0);
+ if (r != LZMA_OK) goto init_failed;
+ break;
+ case LZMA_MEM_ERROR:
+ mem_error: memory_error = 1;
+ err = 1;
+ goto finish;
+ case LZMA_MEMLIMIT_ERROR:
+ decompress_error(term, ce, "lzma", "Memory limit was exceeded", errp);
+ err = 1;
+ goto finish;
+ case LZMA_FORMAT_ERROR:
+ case LZMA_DATA_ERROR:
+ case LZMA_BUF_ERROR:
+ decompress_error(term, ce, "lzma", TEXT_(T_COMPRESSED_ERROR), errp);
+ err = 1;
+ goto finish;
+ case LZMA_OPTIONS_ERROR:decompress_error(term, ce, "lzma", "File contains unsupported options", errp);
+ err = 1;
+ goto finish;
+ case LZMA_PROG_ERROR: decompress_error(term, ce, "lzma", "Lzma is miscompiled", errp);
+ err = 1;
+ goto finish;
+ default: decompress_error(term, ce, "lzma", "Unknown return value on lzma_code", errp);
+ err = 1;
+ break;
+ }
+ if (!z.avail_out) {
+ size_t addsize;
+ if (decoder_memory_expand(&p, size, &addsize) < 0)
+ goto mem_error;
+ z.next_out = p + size;
+ z.avail_out = addsize;
+ size += addsize;
+ }
+ if (z.avail_in) goto repeat_frag;
+ offset += f->length;
+ }
+ finish:
+ lzma_end(&z);
+ after_inflateend:
+ if (memory_error) {
+ mem_free(p);
+ if (out_of_memory(NULL, 0))
+ goto retry_after_memory_error;
+ decompress_error(term, ce, "lzma", TEXT_(T_OUT_OF_MEMORY), errp);
+ return 1;
+ }
+ if (err && (unsigned char *)z.next_out == p) {
+ mem_free(p);
+ return 1;
+ }
+ *p_start = p;
+ *p_len = (unsigned char *)z.next_out - (unsigned char *)p;
+ *p_start = mem_realloc(*p_start, *p_len);
+ return 0;
+}
+#endif
+
+int get_file_by_term(struct terminal *term, struct cache_entry *ce, unsigned char **start, unsigned char **end, int *errp)
+{
+ unsigned char *enc;
+ struct fragment *fr;
+ int e;
+ if (errp) *errp = 0;
+ *start = *end = NULL;
+ if (!ce) return 1;
+ if (ce->decompressed) {
+#if defined(HAVE_ANY_COMPRESSION)
+ return_decompressed:
+#endif
+ *start = ce->decompressed;
+ *end = ce->decompressed + ce->decompressed_len;
+ return 0;
+ }
+ enc = get_content_encoding(ce->head, ce->url);
+ if (enc) {
+#ifdef HAVE_ZLIB
+ if (!strcasecmp(enc, "gzip") || !strcasecmp(enc, "x-gzip") || !strcasecmp(enc, "deflate")) {
+ int defl = !strcasecmp(enc, "deflate");
+ mem_free(enc);
+ if (decode_gzip(term, ce, &ce->decompressed, &ce->decompressed_len, defl, errp)) goto uncompressed;
+ decompressed_cache_size += ce->decompressed_len;
+ goto return_decompressed;
+ }
+#endif
+#ifdef HAVE_BZIP2
+ if (!strcasecmp(enc, "bzip2")) {
+ mem_free(enc);
+ if (decode_bzip2(term, ce, &ce->decompressed, &ce->decompressed_len, errp)) goto uncompressed;
+ decompressed_cache_size += ce->decompressed_len;
+ goto return_decompressed;
+ }
+#endif
+#ifdef HAVE_LZMA
+ if (!strcasecmp(enc, "lzma") || !strcasecmp(enc, "lzma2")) {
+ mem_free(enc);
+ if (decode_lzma(term, ce, &ce->decompressed, &ce->decompressed_len, errp)) goto uncompressed;
+ decompressed_cache_size += ce->decompressed_len;
+ goto return_decompressed;
+ }
+#endif
+ mem_free(enc);
+ goto uncompressed;
+ }
+ uncompressed:
+ if ((e = defrag_entry(ce)) < 0) {
+ unsigned char *msg = get_err_msg(e);
+ if (display_error(term, TEXT_(T_ERROR), errp)) {
+ unsigned char *u = stracpy(ce->url), *uu;
+ if ((uu = strchr(u, POST_CHAR))) *uu = 0;
+ msg_box(term, getml(u, NULL), TEXT_(T_ERROR), AL_CENTER | AL_EXTD_TEXT, TEXT_(T_ERROR_LOADING), " ", u, ":\n\n", msg, NULL, NULL, 1, TEXT_(T_CANCEL), NULL, B_ENTER | B_ESC);
+ }
+ }
+ fr = ce->frag.next;
+ if ((void *)fr == &ce->frag || fr->offset || !fr->length) return 1;
+ *start = fr->data, *end = fr->data + fr->length;
+ return 0;
+}
+
+int get_file(struct object_request *o, unsigned char **start, unsigned char **end)
+{
+ struct terminal *term;
+ *start = *end = NULL;
+ if (!o) return 1;
+ foreach(term, terminals) if (o->term == term->count) goto ok;
+ term = NULL;
+ ok:
+ return get_file_by_term(term, o->ce, start, end, NULL);
+}
+
+void free_decompressed_data(struct cache_entry *e)
+{
+ if (e->decompressed) {
+ if (decompressed_cache_size < e->decompressed_len)
+ internal("free_decompressed_data: decompressed_cache_size underflow %lu, %lu", decompressed_cache_size, (unsigned long)e->decompressed_len);
+ decompressed_cache_size -= e->decompressed_len;
+ mem_free(e->decompressed);
+ e->decompressed = NULL;
+ e->decompressed_len = 0;
+ }
+}
+
+#ifdef HAVE_ANY_COMPRESSION
+
+void add_compress_methods(unsigned char **s, int *l)
+{
+ int cl = 0;
+#ifdef HAVE_ZLIB
+ {
+ if (!cl) cl = 1; else add_to_str(s, l, ", ");
+ add_to_str(s, l, "ZLIB");
+#ifdef zlib_version
+ add_to_str(s, l, " (");
+ add_to_str(s, l, (unsigned char *)zlib_version);
+ add_to_str(s, l, ")");
+#endif
+ }
+#endif
+#ifdef HAVE_BZIP2
+ {
+ unsigned char *b = (unsigned char *)BZ2_bzlibVersion();
+ int bl = strcspn(b, ",");
+ if (!cl) cl = 1; else add_to_str(s, l, ", ");
+ add_to_str(s, l, "BZIP2");
+ add_to_str(s, l, " (");
+ add_bytes_to_str(s, l, b, bl);
+ add_to_str(s, l, ")");
+ }
+#endif
+#ifdef HAVE_LZMA
+ {
+ if (!cl) cl = 1; else add_to_str(s, l, ", ");
+ add_to_str(s, l, "LZMA");
+ add_to_str(s, l, " (");
+ add_to_str(s, l, (unsigned char *)lzma_version_string());
+ add_to_str(s, l, ")");
+ }
+#endif
+}
+
+#endif