/////////////////////////////////////////////////////////////////////////////// // /// \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; }