aboutsummaryrefslogtreecommitdiff
path: root/src/liblzma/common/block_header_decoder.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/liblzma/common/block_header_decoder.c')
-rw-r--r--src/liblzma/common/block_header_decoder.c373
1 files changed, 373 insertions, 0 deletions
diff --git a/src/liblzma/common/block_header_decoder.c b/src/liblzma/common/block_header_decoder.c
new file mode 100644
index 00000000..7676c795
--- /dev/null
+++ b/src/liblzma/common/block_header_decoder.c
@@ -0,0 +1,373 @@
+///////////////////////////////////////////////////////////////////////////////
+//
+/// \file block_header_decoder.c
+/// \brief Decodes Block Header from .lzma files
+//
+// Copyright (C) 2007 Lasse Collin
+//
+// This library is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Lesser General Public
+// License as published by the Free Software Foundation; either
+// version 2.1 of the License, or (at your option) any later version.
+//
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "common.h"
+#include "check.h"
+
+
+struct lzma_coder_s {
+ lzma_options_block *options;
+
+ enum {
+ SEQ_FLAGS_1,
+ SEQ_FLAGS_2,
+ SEQ_COMPRESSED_SIZE,
+ SEQ_UNCOMPRESSED_SIZE,
+ SEQ_FILTER_FLAGS_INIT,
+ SEQ_FILTER_FLAGS_DECODE,
+ SEQ_CRC32,
+ SEQ_PADDING
+ } sequence;
+
+ /// Position in variable-length integers
+ size_t pos;
+
+ /// CRC32 of the Block Header
+ uint32_t crc32;
+
+ lzma_next_coder filter_flags_decoder;
+};
+
+
+static bool
+update_sequence(lzma_coder *coder)
+{
+ switch (coder->sequence) {
+ case SEQ_FLAGS_2:
+ if (coder->options->compressed_size
+ != LZMA_VLI_VALUE_UNKNOWN) {
+ coder->pos = 0;
+ coder->sequence = SEQ_COMPRESSED_SIZE;
+ break;
+ }
+
+ // Fall through
+
+ case SEQ_COMPRESSED_SIZE:
+ if (coder->options->uncompressed_size
+ != LZMA_VLI_VALUE_UNKNOWN) {
+ coder->pos = 0;
+ coder->sequence = SEQ_UNCOMPRESSED_SIZE;
+ break;
+ }
+
+ // Fall through
+
+ case SEQ_UNCOMPRESSED_SIZE:
+ coder->pos = 0;
+
+ // Fall through
+
+ case SEQ_FILTER_FLAGS_DECODE:
+ if (coder->options->filters[coder->pos].id
+ != LZMA_VLI_VALUE_UNKNOWN) {
+ coder->sequence = SEQ_FILTER_FLAGS_INIT;
+ break;
+ }
+
+ if (coder->options->has_crc32) {
+ coder->pos = 0;
+ coder->sequence = SEQ_CRC32;
+ break;
+ }
+
+ case SEQ_CRC32:
+ if (coder->options->padding != 0) {
+ coder->pos = 0;
+ coder->sequence = SEQ_PADDING;
+ break;
+ }
+
+ return true;
+
+ default:
+ assert(0);
+ return true;
+ }
+
+ return false;
+}
+
+
+static lzma_ret
+block_header_decode(lzma_coder *coder, lzma_allocator *allocator,
+ const uint8_t *restrict in, size_t *restrict in_pos,
+ size_t in_size, uint8_t *restrict out lzma_attribute((unused)),
+ size_t *restrict out_pos lzma_attribute((unused)),
+ size_t out_size lzma_attribute((unused)),
+ lzma_action action lzma_attribute((unused)))
+{
+ while (*in_pos < in_size)
+ switch (coder->sequence) {
+ case SEQ_FLAGS_1:
+ // Check that the reserved bit is unset. Use HEADER_ERROR
+ // because newer version of liblzma may support the reserved
+ // bit, although it is likely that this is just a broken file.
+ if (in[*in_pos] & 0x40)
+ return LZMA_HEADER_ERROR;
+
+ // Number of filters: we prepare appropriate amount of
+ // variables for variable-length integer parsing. The
+ // initialization function has already reset the rest
+ // of the values to LZMA_VLI_VALUE_UNKNOWN, which allows
+ // us to later know how many filters there are.
+ for (int i = (int)(in[*in_pos] & 0x07) - 1; i >= 0; --i)
+ coder->options->filters[i].id = 0;
+
+ // End of Payload Marker flag
+ coder->options->has_eopm = (in[*in_pos] & 0x08) != 0;
+
+ // Compressed Size: Prepare for variable-length integer
+ // parsing if it is known.
+ if (in[*in_pos] & 0x10)
+ coder->options->compressed_size = 0;
+
+ // Uncompressed Size: the same.
+ if (in[*in_pos] & 0x20)
+ coder->options->uncompressed_size = 0;
+
+ // Is Metadata Block flag
+ coder->options->is_metadata = (in[*in_pos] & 0x80) != 0;
+
+ // We need at least one: Uncompressed Size or EOPM.
+ if (coder->options->uncompressed_size == LZMA_VLI_VALUE_UNKNOWN
+ && !coder->options->has_eopm)
+ return LZMA_DATA_ERROR;
+
+ // Update header CRC32.
+ coder->crc32 = lzma_crc32(in + *in_pos, 1, coder->crc32);
+
+ ++*in_pos;
+ coder->sequence = SEQ_FLAGS_2;
+ break;
+
+ case SEQ_FLAGS_2:
+ // Check that the reserved bits are unset.
+ if (in[*in_pos] & 0xE0)
+ return LZMA_DATA_ERROR;
+
+ // Get the size of Header Padding.
+ coder->options->padding = in[*in_pos] & 0x1F;
+
+ coder->crc32 = lzma_crc32(in + *in_pos, 1, coder->crc32);
+
+ ++*in_pos;
+
+ if (update_sequence(coder))
+ return LZMA_STREAM_END;
+
+ break;
+
+ case SEQ_COMPRESSED_SIZE: {
+ // Store the old input position to be used when
+ // updating coder->header_crc32.
+ const size_t in_start = *in_pos;
+
+ const lzma_ret ret = lzma_vli_decode(
+ &coder->options->compressed_size,
+ &coder->pos, in, in_pos, in_size);
+
+ const size_t in_used = *in_pos - in_start;
+
+ coder->options->compressed_reserve += in_used;
+ assert(coder->options->compressed_reserve
+ <= LZMA_VLI_BYTES_MAX);
+
+ coder->options->header_size += in_used;
+
+ coder->crc32 = lzma_crc32(in + in_start, in_used,
+ coder->crc32);
+
+ if (ret != LZMA_STREAM_END)
+ return ret;
+
+ if (update_sequence(coder))
+ return LZMA_STREAM_END;
+
+ break;
+ }
+
+ case SEQ_UNCOMPRESSED_SIZE: {
+ const size_t in_start = *in_pos;
+
+ const lzma_ret ret = lzma_vli_decode(
+ &coder->options->uncompressed_size,
+ &coder->pos, in, in_pos, in_size);
+
+ const size_t in_used = *in_pos - in_start;
+
+ coder->options->uncompressed_reserve += in_used;
+ assert(coder->options->uncompressed_reserve
+ <= LZMA_VLI_BYTES_MAX);
+
+ coder->options->header_size += in_used;
+
+ coder->crc32 = lzma_crc32(in + in_start, in_used,
+ coder->crc32);
+
+ if (ret != LZMA_STREAM_END)
+ return ret;
+
+ if (update_sequence(coder))
+ return LZMA_STREAM_END;
+
+ break;
+ }
+
+ case SEQ_FILTER_FLAGS_INIT: {
+ assert(coder->options->filters[coder->pos].id
+ != LZMA_VLI_VALUE_UNKNOWN);
+
+ const lzma_ret ret = lzma_filter_flags_decoder_init(
+ &coder->filter_flags_decoder, allocator,
+ &coder->options->filters[coder->pos]);
+ if (ret != LZMA_OK)
+ return ret;
+
+ coder->sequence = SEQ_FILTER_FLAGS_DECODE;
+ }
+
+ // Fall through
+
+ case SEQ_FILTER_FLAGS_DECODE: {
+ const size_t in_start = *in_pos;
+
+ const lzma_ret ret = coder->filter_flags_decoder.code(
+ coder->filter_flags_decoder.coder,
+ allocator, in, in_pos, in_size,
+ NULL, NULL, 0, LZMA_RUN);
+
+ const size_t in_used = *in_pos - in_start;
+ coder->options->header_size += in_used;
+ coder->crc32 = lzma_crc32(in + in_start,
+ in_used, coder->crc32);
+
+ if (ret != LZMA_STREAM_END)
+ return ret;
+
+ ++coder->pos;
+
+ if (update_sequence(coder))
+ return LZMA_STREAM_END;
+
+ break;
+ }
+
+ case SEQ_CRC32:
+ assert(coder->options->has_crc32);
+
+ if (in[*in_pos] != ((coder->crc32 >> (coder->pos * 8)) & 0xFF))
+ return LZMA_DATA_ERROR;
+
+ ++*in_pos;
+ ++coder->pos;
+
+ // Check if we reached end of the CRC32 field.
+ if (coder->pos == 4) {
+ coder->options->header_size += 4;
+
+ if (update_sequence(coder))
+ return LZMA_STREAM_END;
+ }
+
+ break;
+
+ case SEQ_PADDING:
+ if (in[*in_pos] != 0x00)
+ return LZMA_DATA_ERROR;
+
+ ++*in_pos;
+ ++coder->options->header_size;
+ ++coder->pos;
+
+ if (coder->pos < (size_t)(coder->options->padding))
+ break;
+
+ return LZMA_STREAM_END;
+
+ default:
+ return LZMA_PROG_ERROR;
+ }
+
+ return LZMA_OK;
+}
+
+
+static void
+block_header_decoder_end(lzma_coder *coder, lzma_allocator *allocator)
+{
+ lzma_next_coder_end(&coder->filter_flags_decoder, allocator);
+ lzma_free(coder, allocator);
+ return;
+}
+
+
+extern lzma_ret
+lzma_block_header_decoder_init(lzma_next_coder *next,
+ lzma_allocator *allocator, lzma_options_block *options)
+{
+ if (next->coder == NULL) {
+ next->coder = lzma_alloc(sizeof(lzma_coder), allocator);
+ if (next->coder == NULL)
+ return LZMA_MEM_ERROR;
+
+ next->code = &block_header_decode;
+ next->end = &block_header_decoder_end;
+ next->coder->filter_flags_decoder = LZMA_NEXT_CODER_INIT;
+ }
+
+ // Assume that Compressed Size and Uncompressed Size are unknown.
+ options->compressed_size = LZMA_VLI_VALUE_UNKNOWN;
+ options->uncompressed_size = LZMA_VLI_VALUE_UNKNOWN;
+
+ // We will calculate the sizes of these fields too so that the
+ // application may rewrite the header if it wishes so.
+ options->compressed_reserve = 0;
+ options->uncompressed_reserve = 0;
+
+ // The Block Flags field is always present, so include its size here
+ // and we don't need to worry about it in block_header_decode().
+ options->header_size = 2;
+
+ // Reset filters[] to indicate empty list of filters.
+ // See SEQ_FLAGS_1 in block_header_decode() for reasoning of this.
+ for (size_t i = 0; i < 8; ++i) {
+ options->filters[i].id = LZMA_VLI_VALUE_UNKNOWN;
+ options->filters[i].options = NULL;
+ }
+
+ next->coder->options = options;
+ next->coder->sequence = SEQ_FLAGS_1;
+ next->coder->pos = 0;
+ next->coder->crc32 = 0;
+
+ return LZMA_OK;
+}
+
+
+extern LZMA_API lzma_ret
+lzma_block_header_decoder(lzma_stream *strm,
+ lzma_options_block *options)
+{
+ lzma_next_strm_init(strm, lzma_block_header_decoder_init, options);
+
+ strm->internal->supported_actions[LZMA_RUN] = true;
+
+ return LZMA_OK;
+}