blob: f2ac6c1d760d3ec722a67640928e450ce4a6cf98 [file] [log] [blame]
///////////////////////////////////////////////////////////////////////////////
//
/// \file metadata_decoder.c
/// \brief Decodes metadata stored in Metadata Blocks
//
// 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 "metadata_decoder.h"
#include "block_decoder.h"
/// Maximum size of a single Extra Record. Again, this is mostly to make
/// sure that the parsed lzma_vli fits into size_t. Still, maybe this should
/// be smaller.
#define EXTRA_SIZE_MAX (SIZE_MAX / 4)
struct lzma_coder_s {
enum {
SEQ_FLAGS,
SEQ_HEADER_METADATA_SIZE,
SEQ_TOTAL_SIZE,
SEQ_UNCOMPRESSED_SIZE,
SEQ_INDEX_COUNT,
SEQ_INDEX_ALLOC,
SEQ_INDEX_TOTAL_SIZE,
SEQ_INDEX_UNCOMPRESSED_SIZE,
SEQ_EXTRA_PREPARE,
SEQ_EXTRA_ALLOC,
SEQ_EXTRA_ID,
SEQ_EXTRA_SIZE,
SEQ_EXTRA_DATA_ALLOC,
SEQ_EXTRA_DATA_COPY,
SEQ_EXTRA_DUMMY_ALLOC,
SEQ_EXTRA_DUMMY_ID,
SEQ_EXTRA_DUMMY_SIZE,
SEQ_EXTRA_DUMMY_COPY,
} sequence;
/// Number of "things" left to be parsed. If we hit end of input
/// when this isn't zero, we have corrupt Metadata Block.
size_t todo_count;
/// Position in variable-length integers
size_t pos;
/// Temporary variable needed to decode variables whose type
/// is size_t instead of lzma_vli.
lzma_vli tmp;
/// Pointer to target structure to hold the parsed results.
lzma_metadata *metadata;
/// The Index Record we currently are parsing
lzma_index *index_current;
/// Number of Records in Index
size_t index_count;
/// Sum of Total Size fields in the Index
lzma_vli index_total_size;
/// Sum of Uncompressed Size fields in the Index
lzma_vli index_uncompressed_size;
/// True if Extra is present.
bool has_extra;
/// True if we have been requested to store the Extra to *metadata.
bool want_extra;
/// Pointer to the end of the Extra Record list.
lzma_extra *extra_tail;
/// Dummy Extra Record used when only verifying integrity of Extra
/// (not storing it to RAM).
lzma_extra extra_dummy;
/// Block decoder
lzma_next_coder block_decoder;
/// buffer[buffer_pos] is the next byte to process.
size_t buffer_pos;
/// buffer[buffer_size] is the first byte to not process.
size_t buffer_size;
/// Temporary buffer to which encoded Metadata is read before
/// it is parsed.
uint8_t buffer[LZMA_BUFFER_SIZE];
};
/// Reads a variable-length integer to coder->num.
#define read_vli(num) \
do { \
const lzma_ret ret = lzma_vli_decode( \
&num, &coder->pos, \
coder->buffer, &coder->buffer_pos, \
coder->buffer_size); \
if (ret != LZMA_STREAM_END) \
return ret; \
\
coder->pos = 0; \
} while (0)
static lzma_ret
process(lzma_coder *coder, lzma_allocator *allocator)
{
while (coder->buffer_pos < coder->buffer_size)
switch (coder->sequence) {
case SEQ_FLAGS:
// Reserved bits must be unset.
if (coder->buffer[coder->buffer_pos] & 0x70)
return LZMA_HEADER_ERROR;
// If Size of Header Metadata is present, prepare the
// variable for variable-length integer decoding. Otherwise
// set it to LZMA_VLI_VALUE_UNKNOWN to indicate that the
// field isn't present.
if (coder->buffer[coder->buffer_pos] & 0x01) {
coder->metadata->header_metadata_size = 0;
++coder->todo_count;
}
if (coder->buffer[coder->buffer_pos] & 0x02) {
coder->metadata->total_size = 0;
++coder->todo_count;
}
if (coder->buffer[coder->buffer_pos] & 0x04) {
coder->metadata->uncompressed_size = 0;
++coder->todo_count;
}
if (coder->buffer[coder->buffer_pos] & 0x08) {
// Setting index_count to 1 is just to indicate that
// Index is present. The real size is parsed later.
coder->index_count = 1;
++coder->todo_count;
}
coder->has_extra = (coder->buffer[coder->buffer_pos] & 0x80)
!= 0;
++coder->buffer_pos;
coder->sequence = SEQ_HEADER_METADATA_SIZE;
break;
case SEQ_HEADER_METADATA_SIZE:
if (coder->metadata->header_metadata_size
!= LZMA_VLI_VALUE_UNKNOWN) {
read_vli(coder->metadata->header_metadata_size);
if (coder->metadata->header_metadata_size == 0)
return LZMA_DATA_ERROR;
--coder->todo_count;
}
coder->sequence = SEQ_TOTAL_SIZE;
break;
case SEQ_TOTAL_SIZE:
if (coder->metadata->total_size != LZMA_VLI_VALUE_UNKNOWN) {
read_vli(coder->metadata->total_size);
if (coder->metadata->total_size == 0)
return LZMA_DATA_ERROR;
--coder->todo_count;
}
coder->sequence = SEQ_UNCOMPRESSED_SIZE;
break;
case SEQ_UNCOMPRESSED_SIZE:
if (coder->metadata->uncompressed_size
!= LZMA_VLI_VALUE_UNKNOWN) {
read_vli(coder->metadata->uncompressed_size);
--coder->todo_count;
}
coder->sequence = SEQ_INDEX_COUNT;
break;
case SEQ_INDEX_COUNT:
if (coder->index_count == 0) {
coder->sequence = SEQ_EXTRA_PREPARE;
break;
}
read_vli(coder->tmp);
// Index must not be empty nor far too big (wouldn't fit
// in RAM).
if (coder->tmp == 0 || coder->tmp
>= SIZE_MAX / sizeof(lzma_index))
return LZMA_DATA_ERROR;
coder->index_count = (size_t)(coder->tmp);
coder->tmp = 0;
coder->sequence = SEQ_INDEX_ALLOC;
break;
case SEQ_INDEX_ALLOC: {
lzma_index *i = lzma_alloc(sizeof(lzma_index), allocator);
if (i == NULL)
return LZMA_MEM_ERROR;
i->total_size = 0;
i->uncompressed_size = 0;
i->next = NULL;
if (coder->metadata->index == NULL)
coder->metadata->index = i;
else
coder->index_current->next = i;
coder->index_current = i;
coder->sequence = SEQ_INDEX_TOTAL_SIZE;
}
// Fall through
case SEQ_INDEX_TOTAL_SIZE: {
read_vli(coder->index_current->total_size);
coder->index_total_size += coder->index_current->total_size;
if (coder->index_total_size > LZMA_VLI_VALUE_MAX)
return LZMA_DATA_ERROR;
// No Block can have Total Size of zero bytes.
if (coder->index_current->total_size == 0)
return LZMA_DATA_ERROR;
if (--coder->index_count == 0) {
// If Total Size is present, it must match the sum
// of Total Sizes in Index.
if (coder->metadata->total_size
!= LZMA_VLI_VALUE_UNKNOWN
&& coder->metadata->total_size
!= coder->index_total_size)
return LZMA_DATA_ERROR;
coder->index_current = coder->metadata->index;
coder->sequence = SEQ_INDEX_UNCOMPRESSED_SIZE;
} else {
coder->sequence = SEQ_INDEX_ALLOC;
}
break;
}
case SEQ_INDEX_UNCOMPRESSED_SIZE: {
read_vli(coder->index_current->uncompressed_size);
coder->index_uncompressed_size
+= coder->index_current->uncompressed_size;
if (coder->index_uncompressed_size > LZMA_VLI_VALUE_MAX)
return LZMA_DATA_ERROR;
coder->index_current = coder->index_current->next;
if (coder->index_current == NULL) {
if (coder->metadata->uncompressed_size
!= LZMA_VLI_VALUE_UNKNOWN
&& coder->metadata->uncompressed_size
!= coder->index_uncompressed_size)
return LZMA_DATA_ERROR;
--coder->todo_count;
coder->sequence = SEQ_EXTRA_PREPARE;
}
break;
}
case SEQ_EXTRA_PREPARE:
assert(coder->todo_count == 0);
// If we get here, we have at least one byte of input left.
// If "Extra is present" flag is unset in Metadata Flags,
// it means that there is some garbage and we return an error.
if (!coder->has_extra)
return LZMA_DATA_ERROR;
if (!coder->want_extra) {
coder->extra_tail = &coder->extra_dummy;
coder->sequence = SEQ_EXTRA_DUMMY_ALLOC;
break;
}
coder->sequence = SEQ_EXTRA_ALLOC;
// Fall through
case SEQ_EXTRA_ALLOC: {
lzma_extra *e = lzma_alloc(sizeof(lzma_extra), allocator);
if (e == NULL)
return LZMA_MEM_ERROR;
e->next = NULL;
e->id = 0;
e->size = 0;
e->data = NULL;
if (coder->metadata->extra == NULL)
coder->metadata->extra = e;
else
coder->extra_tail->next = e;
coder->extra_tail = e;
coder->todo_count = 1;
coder->sequence = SEQ_EXTRA_ID;
}
// Fall through
case SEQ_EXTRA_ID:
case SEQ_EXTRA_DUMMY_ID:
read_vli(coder->extra_tail->id);
if (coder->extra_tail->id == 0) {
coder->extra_tail->size = 0;
coder->extra_tail->data = NULL;
coder->todo_count = 0;
--coder->sequence;
} else {
++coder->sequence;
}
break;
case SEQ_EXTRA_SIZE:
case SEQ_EXTRA_DUMMY_SIZE:
read_vli(coder->tmp);
++coder->sequence;
break;
case SEQ_EXTRA_DATA_ALLOC: {
if (coder->tmp > EXTRA_SIZE_MAX)
return LZMA_DATA_ERROR;
coder->extra_tail->size = (size_t)(coder->tmp);
coder->tmp = 0;
uint8_t *d = lzma_alloc((size_t)(coder->extra_tail->size),
allocator);
if (d == NULL)
return LZMA_MEM_ERROR;
coder->extra_tail->data = d;
coder->sequence = SEQ_EXTRA_DATA_COPY;
}
// Fall through
case SEQ_EXTRA_DATA_COPY:
bufcpy(coder->buffer, &coder->buffer_pos, coder->buffer_size,
coder->extra_tail->data, &coder->pos,
(size_t)(coder->extra_tail->size));
if ((size_t)(coder->extra_tail->size) == coder->pos) {
coder->pos = 0;
coder->todo_count = 0;
coder->sequence = SEQ_EXTRA_ALLOC;
}
break;
case SEQ_EXTRA_DUMMY_ALLOC:
// Not really alloc, just initialize the dummy entry.
coder->extra_dummy = (lzma_extra){
.next = NULL,
.id = 0,
.size = 0,
.data = NULL,
};
coder->todo_count = 1;
coder->sequence = SEQ_EXTRA_DUMMY_ID;
break;
case SEQ_EXTRA_DUMMY_COPY: {
// Simply skip as many bytes as indicated by Extra Record Size.
// We don't check lzma_extra_size_max because we don't
// allocate any memory to hold the data.
const size_t in_avail = coder->buffer_size - coder->buffer_pos;
const size_t skip = MIN((lzma_vli)(in_avail), coder->tmp);
coder->buffer_pos += skip;
coder->tmp -= skip;
if (coder->tmp == 0) {
coder->todo_count = 0;
coder->sequence = SEQ_EXTRA_DUMMY_ALLOC;
}
break;
}
default:
return LZMA_PROG_ERROR;
}
return LZMA_OK;
}
static lzma_ret
metadata_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)))
{
bool end_was_reached = false;
while (true) {
// Fill the buffer if it is empty.
if (coder->buffer_pos == coder->buffer_size) {
coder->buffer_pos = 0;
coder->buffer_size = 0;
const lzma_ret ret = coder->block_decoder.code(
coder->block_decoder.coder, allocator,
in, in_pos, in_size, coder->buffer,
&coder->buffer_size, LZMA_BUFFER_SIZE,
LZMA_RUN);
switch (ret) {
case LZMA_OK:
// Return immediatelly if we got no new data.
if (coder->buffer_size == 0)
return LZMA_OK;
break;
case LZMA_STREAM_END:
end_was_reached = true;
break;
default:
return ret;
}
}
// Process coder->buffer.
const lzma_ret ret = process(coder, allocator);
if (ret != LZMA_OK)
return ret;
// On success, process() eats all the input.
assert(coder->buffer_pos == coder->buffer_size);
if (end_was_reached) {
// Check that the sequence is not in the
// middle of anything.
if (coder->todo_count != 0)
return LZMA_DATA_ERROR;
return LZMA_STREAM_END;
}
}
}
static void
metadata_decoder_end(lzma_coder *coder, lzma_allocator *allocator)
{
lzma_free(coder, allocator);
return;
}
static lzma_ret
metadata_decoder_init(lzma_next_coder *next, lzma_allocator *allocator,
lzma_options_block *options, lzma_metadata *metadata,
bool want_extra)
{
if (options == NULL || metadata == NULL)
return LZMA_PROG_ERROR;
if (next->coder == NULL) {
next->coder = lzma_alloc(sizeof(lzma_coder), allocator);
if (next->coder == NULL)
return LZMA_MEM_ERROR;
next->code = &metadata_decode;
next->end = &metadata_decoder_end;
next->coder->block_decoder = LZMA_NEXT_CODER_INIT;
}
metadata->header_metadata_size = LZMA_VLI_VALUE_UNKNOWN;
metadata->total_size = LZMA_VLI_VALUE_UNKNOWN;
metadata->uncompressed_size = LZMA_VLI_VALUE_UNKNOWN;
metadata->index = NULL;
metadata->extra = NULL;
next->coder->sequence = SEQ_FLAGS;
next->coder->todo_count = 0;
next->coder->pos = 0;
next->coder->tmp = 0;
next->coder->metadata = metadata;
next->coder->index_current = NULL;
next->coder->index_count = 0;
next->coder->index_total_size = 0;
next->coder->index_uncompressed_size = 0;
next->coder->want_extra = want_extra;
next->coder->extra_tail = NULL;
next->coder->buffer_pos = 0;
next->coder->buffer_size = 0;
return lzma_block_decoder_init(
&next->coder->block_decoder, allocator, options);
}
extern lzma_ret
lzma_metadata_decoder_init(lzma_next_coder *next, lzma_allocator *allocator,
lzma_options_block *options, lzma_metadata *metadata,
bool want_extra)
{
lzma_next_coder_init(metadata_decoder_init, next, allocator,
options, metadata, want_extra);
}
extern LZMA_API lzma_ret
lzma_metadata_decoder(lzma_stream *strm, lzma_options_block *options,
lzma_metadata *metadata, lzma_bool want_extra)
{
lzma_next_strm_init(strm, lzma_metadata_decoder_init,
options, metadata, want_extra);
strm->internal->supported_actions[LZMA_RUN] = true;
return LZMA_OK;
}