aboutsummaryrefslogblamecommitdiff
path: root/src/liblzma/common/stream_decoder.c
blob: 56de3d9f4f53a10c8d5fa9dcfcc5da22cbac9d92 (plain) (tree)














































































































































                                                                               
                                                                      
 


                                                                 

























































                                                                               







                                                                             
                                                               
                                                                        
                                                        























































































































































                                                                              


                                                                              





























                                                                         




                                                                         






                                                                        



                                                                              


















































                                                                              
///////////////////////////////////////////////////////////////////////////////
//
/// \file       stream_decoder.c
/// \brief      Decodes .lzma Streams
//
//  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 "stream_common.h"
#include "check.h"
#include "stream_flags_decoder.h"
#include "block_decoder.h"
#include "metadata_decoder.h"


struct lzma_coder_s {
	enum {
		SEQ_STREAM_HEADER_CODE,
		SEQ_BLOCK_HEADER_INIT,
		SEQ_BLOCK_HEADER_CODE,
		SEQ_METADATA_CODE,
		SEQ_DATA_CODE,
		SEQ_STREAM_TAIL_INIT,
		SEQ_STREAM_TAIL_CODE,
	} sequence;

	/// Position in variable-length integers and in some other things.
	size_t pos;

	/// Block or Metadata decoder. This takes little memory and the same
	/// data structure can be used to decode every Block Header, so it's
	/// a good idea to have a separate lzma_next_coder structure for it.
	lzma_next_coder block_decoder;

	/// Block Header decoder; this is separate
	lzma_next_coder block_header_decoder;

	lzma_options_block block_options;

	/// Information about the sizes of the Blocks
	lzma_info *info;

	/// Current Block in *info
	lzma_info_iter iter;

	/// Number of bytes not yet processed from Data Blocks in the Stream.
	/// This can be LZMA_VLI_VALUE_UNKNOWN. If it is known, it is
	/// decremented while decoding and verified to match the reality.
	lzma_vli total_left;

	/// Like uncompressed_left above but for uncompressed data from
	/// Data Blocks.
	lzma_vli uncompressed_left;

	/// Stream Flags from Stream Header
	lzma_stream_flags header_flags;

	/// Stream Flags from Stream tail
	lzma_stream_flags tail_flags;

	/// Decoder for Stream Header and Stream tail. This takes very
	/// little memory and the same data structure can be used for
	/// both Header and tail, so it's a good idea to have a separate
	/// lzma_next_coder structure for it.
	lzma_next_coder flags_decoder;

	/// Temporary destination for the decoded Metadata.
	lzma_metadata metadata;

	/// Pointer to application-supplied pointer where to store the list
	/// of Extra Records from the Header Metadata Block.
	lzma_extra **header_extra;

	/// Same as above but Footer Metadata Block
	lzma_extra **footer_extra;
};


static lzma_ret
metadata_init(lzma_coder *coder, lzma_allocator *allocator)
{
	assert(coder->metadata.index == NULL);
	assert(coder->metadata.extra == NULL);

	// Single-Block Streams don't have Metadata Blocks.
	if (!coder->header_flags.is_multi)
		return LZMA_DATA_ERROR;

	coder->block_options.total_limit = LZMA_VLI_VALUE_UNKNOWN;

	// Limit the Uncompressed Size of a Metadata Block. This is to
	// prevent security issues where input file would have very huge
	// Metadata.
	//
	// FIXME: Hardcoded constant is ugly. Maybe we should provide
	// some way to specify this from the application.
	coder->block_options.uncompressed_limit = LZMA_VLI_C(1) << 23;

	lzma_info_size size_type;
	bool want_extra;

	// If we haven't decoded any Data Blocks yet, this is Header
	// Metadata Block.
	if (lzma_info_index_count_get(coder->info) == 0) {
		coder->block_options.has_backward_size = false;
		coder->block_options.handle_padding = true;
		size_type = LZMA_INFO_HEADER_METADATA;
		want_extra = coder->header_extra != NULL;
	} else {
		if (lzma_info_index_finish(coder->info))
			return LZMA_DATA_ERROR;

		coder->block_options.has_backward_size = true;
		coder->block_options.handle_padding = false;
		size_type = LZMA_INFO_FOOTER_METADATA;
		want_extra = coder->footer_extra != NULL;
	}

	coder->block_options.has_uncompressed_size_in_footer = false;
	coder->block_options.total_size = lzma_info_size_get(
			coder->info, size_type);

	coder->sequence = SEQ_METADATA_CODE;

	return lzma_metadata_decoder_init(&coder->block_decoder, allocator,
			&coder->block_options, &coder->metadata, want_extra);
}


static lzma_ret
data_init(lzma_coder *coder, lzma_allocator *allocator)
{
	return_if_error(lzma_info_iter_next(&coder->iter, allocator));

	return_if_error(lzma_info_iter_set(
			&coder->iter, LZMA_VLI_VALUE_UNKNOWN,
			coder->block_options.uncompressed_size));

	coder->block_options.total_size = coder->iter.total_size;
	coder->block_options.uncompressed_size = coder->iter.uncompressed_size;
	coder->block_options.total_limit = coder->total_left;
	coder->block_options.uncompressed_limit = coder->uncompressed_left;

	if (coder->header_flags.is_multi) {
		coder->block_options.has_uncompressed_size_in_footer = false;
		coder->block_options.has_backward_size = false;
		coder->block_options.handle_padding = true;
	} else {
		coder->block_options.has_uncompressed_size_in_footer
				= coder->iter.uncompressed_size
					== LZMA_VLI_VALUE_UNKNOWN;
		coder->block_options.has_backward_size = true;
		coder->block_options.handle_padding = false;
	}

	coder->sequence = SEQ_DATA_CODE;

	return lzma_block_decoder_init(&coder->block_decoder, allocator,
			&coder->block_options);
}


static lzma_ret
stream_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,
		size_t *restrict out_pos, size_t out_size, lzma_action action)
{
	while (*out_pos < out_size && (*in_pos < in_size
			|| coder->sequence == SEQ_DATA_CODE))
	switch (coder->sequence) {
	case SEQ_STREAM_HEADER_CODE: {
		const lzma_ret ret = coder->flags_decoder.code(
				coder->flags_decoder.coder,
				allocator, in, in_pos, in_size,
				NULL, NULL, 0, LZMA_RUN);
		if (ret != LZMA_STREAM_END)
			return ret;

		coder->sequence = SEQ_BLOCK_HEADER_INIT;

		// Detect if the Check type is supported and give appropriate
		// warning if it isn't. We don't warn every time a new Block
		// is started.
		lzma_check tmp;
		if (lzma_check_init(&tmp, coder->header_flags.check))
			return LZMA_UNSUPPORTED_CHECK;

		break;
	}

	case SEQ_BLOCK_HEADER_INIT: {
		coder->block_options.check = coder->header_flags.check;
		coder->block_options.has_crc32 = coder->header_flags.has_crc32;

		for (size_t i = 0;
				i < ARRAY_SIZE(coder->block_options.filters);
				++i) {
			lzma_free(coder->block_options.filters[i].options,
					allocator);
			coder->block_options.filters[i].options = NULL;
		}

		return_if_error(lzma_block_header_decoder_init(
				&coder->block_header_decoder, allocator,
				&coder->block_options));

		coder->sequence = SEQ_BLOCK_HEADER_CODE;
	}

	// Fall through

	case SEQ_BLOCK_HEADER_CODE: {
		lzma_ret ret = coder->block_header_decoder.code(
				coder->block_header_decoder.coder,
				allocator, in, in_pos, in_size,
				NULL, NULL, 0, LZMA_RUN);

		if (ret != LZMA_STREAM_END)
			return ret;

		if (coder->block_options.is_metadata)
			ret = metadata_init(coder, allocator);
		else
			ret = data_init(coder, allocator);

		if (ret != LZMA_OK)
			return ret;

		break;
	}

	case SEQ_METADATA_CODE: {
		lzma_ret ret = coder->block_decoder.code(
				coder->block_decoder.coder, allocator,
				in, in_pos, in_size, NULL, NULL, 0, LZMA_RUN);
		if (ret != LZMA_STREAM_END)
			return ret;

		const bool is_header_metadata = lzma_info_index_count_get(
				coder->info) == 0;

		if (is_header_metadata) {
			if (coder->header_extra != NULL) {
				*coder->header_extra = coder->metadata.extra;
				coder->metadata.extra = NULL;
			}

			if (lzma_info_size_set(coder->info,
					LZMA_INFO_HEADER_METADATA,
					coder->block_options.total_size)
					!= LZMA_OK)
				return LZMA_PROG_ERROR;

			coder->sequence = SEQ_BLOCK_HEADER_INIT;
		} else {
			if (coder->footer_extra != NULL) {
				*coder->footer_extra = coder->metadata.extra;
				coder->metadata.extra = NULL;
			}

			coder->sequence = SEQ_STREAM_TAIL_INIT;
		}

		assert(coder->metadata.extra == NULL);

		ret = lzma_info_metadata_set(coder->info, allocator,
				&coder->metadata, is_header_metadata, true);
		if (ret != LZMA_OK)
			return ret;

		// Intialize coder->total_size and coder->uncompressed_size
		// from Header Metadata.
		if (is_header_metadata) {
			coder->total_left = lzma_info_size_get(
					coder->info, LZMA_INFO_TOTAL);
			coder->uncompressed_left = lzma_info_size_get(
					coder->info, LZMA_INFO_UNCOMPRESSED);
		}

		break;
	}

	case SEQ_DATA_CODE: {
		lzma_ret ret = coder->block_decoder.code(
				coder->block_decoder.coder, allocator,
				in, in_pos, in_size, out, out_pos, out_size,
				action);

		if (ret != LZMA_STREAM_END)
			return ret;

		ret = lzma_info_iter_set(&coder->iter,
				coder->block_options.total_size,
				coder->block_options.uncompressed_size);
		if (ret != LZMA_OK)
			return ret;

		// These won't overflow since lzma_info_iter_set() succeeded.
		if (coder->total_left != LZMA_VLI_VALUE_UNKNOWN)
			coder->total_left -= coder->block_options.total_size;
		if (coder->uncompressed_left != LZMA_VLI_VALUE_UNKNOWN)
			coder->uncompressed_left -= coder->block_options
					.uncompressed_size;

		if (!coder->header_flags.is_multi) {
			ret = lzma_info_index_finish(coder->info);
			if (ret != LZMA_OK)
				return ret;

			coder->sequence = SEQ_STREAM_TAIL_INIT;
			break;
		}

		coder->sequence = SEQ_BLOCK_HEADER_INIT;
		break;
	}

	case SEQ_STREAM_TAIL_INIT: {
		lzma_ret ret = lzma_info_index_finish(coder->info);
		if (ret != LZMA_OK)
			return ret;

		ret = lzma_stream_tail_decoder_init(&coder->flags_decoder,
				allocator, &coder->tail_flags);
		if (ret != LZMA_OK)
			return ret;

		coder->sequence = SEQ_STREAM_TAIL_CODE;
	}

	// Fall through

	case SEQ_STREAM_TAIL_CODE: {
		const lzma_ret ret = coder->flags_decoder.code(
				coder->flags_decoder.coder, allocator,
				in, in_pos, in_size, NULL, NULL, 0, LZMA_RUN);
		if (ret != LZMA_STREAM_END)
			return ret;

		if (!lzma_stream_flags_is_equal(
				coder->header_flags, coder->tail_flags))
			return LZMA_DATA_ERROR;

		return LZMA_STREAM_END;
	}

	default:
		return LZMA_PROG_ERROR;
	}

	return LZMA_OK;
}


static void
stream_decoder_end(lzma_coder *coder, lzma_allocator *allocator)
{
	for (size_t i = 0; i < ARRAY_SIZE(coder->block_options.filters); ++i)
		lzma_free(coder->block_options.filters[i].options, allocator);

	lzma_next_coder_end(&coder->block_decoder, allocator);
	lzma_next_coder_end(&coder->block_header_decoder, allocator);
	lzma_next_coder_end(&coder->flags_decoder, allocator);
	lzma_info_free(coder->info, allocator);
	lzma_index_free(coder->metadata.index, allocator);
	lzma_extra_free(coder->metadata.extra, allocator);
	lzma_free(coder, allocator);
	return;
}


static lzma_ret
stream_decoder_init(lzma_next_coder *next, lzma_allocator *allocator,
		lzma_extra **header, lzma_extra **footer)
{
	if (next->coder == NULL) {
		next->coder = lzma_alloc(sizeof(lzma_coder), allocator);
		if (next->coder == NULL)
			return LZMA_MEM_ERROR;

		next->code = &stream_decode;
		next->end = &stream_decoder_end;

		next->coder->block_decoder = LZMA_NEXT_CODER_INIT;
		next->coder->block_header_decoder = LZMA_NEXT_CODER_INIT;
		next->coder->info = NULL;
		next->coder->flags_decoder = LZMA_NEXT_CODER_INIT;
		next->coder->metadata.index = NULL;
		next->coder->metadata.extra = NULL;
	} else {
		for (size_t i = 0; i < ARRAY_SIZE(
				next->coder->block_options.filters); ++i)
			lzma_free(next->coder->block_options
					.filters[i].options, allocator);

		lzma_index_free(next->coder->metadata.index, allocator);
		next->coder->metadata.index = NULL;

		lzma_extra_free(next->coder->metadata.extra, allocator);
		next->coder->metadata.extra = NULL;
	}

	for (size_t i = 0; i < ARRAY_SIZE(next->coder->block_options.filters);
			++i)
		next->coder->block_options.filters[i].options = NULL;

	next->coder->info = lzma_info_init(next->coder->info, allocator);
	if (next->coder->info == NULL)
		return LZMA_MEM_ERROR;

	lzma_info_iter_begin(next->coder->info, &next->coder->iter);

	// Initialize Stream Header decoder.
	return_if_error(lzma_stream_header_decoder_init(
				&next->coder->flags_decoder, allocator,
				&next->coder->header_flags));

	// Reset the *foo_extra pointers to NULL. This way the caller knows
	// if there were no Extra Records. (We don't support appending
	// Records to Extra list.)
	if (header != NULL)
		*header = NULL;
	if (footer != NULL)
		*footer = NULL;

	// Reset some variables.
	next->coder->sequence = SEQ_STREAM_HEADER_CODE;
	next->coder->pos = 0;
	next->coder->uncompressed_left = LZMA_VLI_VALUE_UNKNOWN;
	next->coder->total_left = LZMA_VLI_VALUE_UNKNOWN;
	next->coder->header_extra = header;
	next->coder->footer_extra = footer;

	return LZMA_OK;
}


extern lzma_ret
lzma_stream_decoder_init(lzma_next_coder *next, lzma_allocator *allocator,
		lzma_extra **header, lzma_extra **footer)
{
	lzma_next_coder_init(
			stream_decoder_init, next, allocator, header, footer);
}


extern LZMA_API lzma_ret
lzma_stream_decoder(lzma_stream *strm,
		lzma_extra **header, lzma_extra **footer)
{
	lzma_next_strm_init(strm, stream_decoder_init, header, footer);

	strm->internal->supported_actions[LZMA_RUN] = true;
	strm->internal->supported_actions[LZMA_SYNC_FLUSH] = true;

	return LZMA_OK;
}