diff --git a/examples/webpmux.c b/examples/webpmux.c index e5cc4b54..0f6d12a9 100644 --- a/examples/webpmux.c +++ b/examples/webpmux.c @@ -355,15 +355,16 @@ static int ReadData(const char* filename, void** data_ptr, uint32_t* size_ptr) { static int ReadFile(const char* const filename, WebPMux** mux) { uint32_t size = 0; void* data = NULL; + WebPMuxState mux_state; assert(mux != NULL); if (!ReadData(filename, &data, &size)) return 0; - *mux = WebPMuxCreate((const uint8_t*)data, size, 1); + *mux = WebPMuxCreate((const uint8_t*)data, size, 1, &mux_state); free(data); - if (*mux != NULL) return 1; - fprintf(stderr, "Failed to create mux object from file %s.\n", - filename); + if (*mux != NULL && mux_state == WEBP_MUX_STATE_COMPLETE) return 1; + fprintf(stderr, "Failed to create mux object from file %s. mux_state = %d.\n", + filename, mux_state); return 0; } @@ -377,14 +378,16 @@ static int ReadImage(const char* filename, WebPMux* mux; WebPMuxError err; int ok = 0; + WebPMuxState mux_state; if (!ReadData(filename, &data, &size)) return 0; - mux = WebPMuxCreate((const uint8_t*)data, size, 1); + mux = WebPMuxCreate((const uint8_t*)data, size, 1, &mux_state); free(data); - if (mux == NULL) { - fprintf(stderr, "Failed to create mux object from file %s.\n", - filename); + if (mux == NULL || mux_state != WEBP_MUX_STATE_COMPLETE) { + fprintf(stderr, + "Failed to create mux object from file %s. mux_state = %d.\n", + filename, mux_state); return 0; } err = WebPMuxGetImage(mux, (const uint8_t**)&data, &size, diff --git a/src/mux/muxedit.c b/src/mux/muxedit.c index c73e84eb..f50eb06f 100644 --- a/src/mux/muxedit.c +++ b/src/mux/muxedit.c @@ -23,6 +23,7 @@ extern "C" { static int MuxInit(WebPMux* const mux) { if (mux == NULL) return 0; memset(mux, 0, sizeof(*mux)); + mux->state_ = WEBP_MUX_STATE_PARTIAL; return 1; } @@ -179,8 +180,11 @@ static WebPMuxError GetImageData(const uint8_t* data, uint32_t size, // It is webp file data. Extract image data from it. WebPMux* mux; WebPMuxError err; - mux = WebPMuxCreate(data, size, 0); - if (mux == NULL) return WEBP_MUX_BAD_DATA; + WebPMuxState mux_state; + mux = WebPMuxCreate(data, size, 0, &mux_state); + if (mux == NULL || mux_state != WEBP_MUX_STATE_COMPLETE) { + return WEBP_MUX_BAD_DATA; + } err = WebPMuxGetImage(mux, image_data, image_size, alpha_data, alpha_size); WebPMuxDelete(mux); @@ -634,6 +638,9 @@ WebPMuxError WebPMuxAssemble(WebPMux* const mux, uint8_t** output_data, err = CreateVP8XChunk(mux); if (err != WEBP_MUX_OK) return err; + // Mark mux as complete. + mux->state_ = WEBP_MUX_STATE_COMPLETE; + // Allocate data. size = ChunksListDiskSize(mux->vp8x_) + ChunksListDiskSize(mux->iccp_) + ChunksListDiskSize(mux->loop_) + MuxImageListDiskSize(mux->images_) diff --git a/src/mux/muxi.h b/src/mux/muxi.h index 2fa6d6ce..0320f811 100644 --- a/src/mux/muxi.h +++ b/src/mux/muxi.h @@ -59,6 +59,7 @@ struct WebPMuxImage { // Main mux object. Stores data chunks. struct WebPMux { + WebPMuxState state_; WebPMuxImage* images_; WebPChunk* iccp_; WebPChunk* meta_; @@ -121,6 +122,10 @@ static WEBP_INLINE void PutLE32(uint8_t* const data, uint32_t val) { PutLE16(data + 2, val >> 16); } +static WEBP_INLINE uint32_t SizeWithPadding(uint32_t chunk_size) { + return CHUNK_HEADER_SIZE + ((chunk_size + 1) & ~1U); +} + //------------------------------------------------------------------------------ // Chunk object management. @@ -154,7 +159,7 @@ WebPChunk* ChunkDelete(WebPChunk* const chunk); // Size of a chunk including header and padding. static WEBP_INLINE uint32_t ChunkDiskSize(const WebPChunk* chunk) { assert(chunk->payload_size_ < MAX_CHUNK_PAYLOAD); - return CHUNK_HEADER_SIZE + ((chunk->payload_size_ + 1) & ~1U); + return SizeWithPadding(chunk->payload_size_); } // Total size of a list of chunks. @@ -169,6 +174,9 @@ uint8_t* ChunkListEmit(const WebPChunk* chunk_list, uint8_t* dst); // Initialize. void MuxImageInit(WebPMuxImage* const wpi); +// Delete image 'wpi'. +WebPMuxImage* MuxImageDelete(WebPMuxImage* const wpi); + // Delete all images in 'wpi_list'. void MuxImageDeleteAll(WebPMuxImage** const wpi_list); diff --git a/src/mux/muxinternal.c b/src/mux/muxinternal.c index 22a3c235..99e37b26 100644 --- a/src/mux/muxinternal.c +++ b/src/mux/muxinternal.c @@ -344,7 +344,7 @@ WebPMuxError MuxImageSetNth(const WebPMuxImage* wpi, WebPMuxImage** wpi_list, //------------------------------------------------------------------------------ // MuxImage deletion methods. -static WebPMuxImage* DeleteImage(WebPMuxImage* const wpi) { +WebPMuxImage* MuxImageDelete(WebPMuxImage* const wpi) { WebPMuxImage* const next = MuxImageRelease(wpi); free(wpi); return next; @@ -352,7 +352,7 @@ static WebPMuxImage* DeleteImage(WebPMuxImage* const wpi) { void MuxImageDeleteAll(WebPMuxImage** const wpi_list) { while (*wpi_list) { - *wpi_list = DeleteImage(*wpi_list); + *wpi_list = MuxImageDelete(*wpi_list); } } @@ -362,7 +362,7 @@ WebPMuxError MuxImageDeleteNth(WebPMuxImage** wpi_list, uint32_t nth, if (!SearchImageToGetOrDelete(wpi_list, nth, id, &wpi_list)) { return WEBP_MUX_NOT_FOUND; } - *wpi_list = DeleteImage(*wpi_list); + *wpi_list = MuxImageDelete(*wpi_list); return WEBP_MUX_OK; } @@ -474,7 +474,12 @@ WebPMuxError WebPMuxValidate(const WebPMux* const mux) { WebPMuxError err; // Verify mux is not NULL. - if (mux == NULL) return WEBP_MUX_INVALID_ARGUMENT; + if (mux == NULL || mux->state_ == WEBP_MUX_STATE_ERROR) { + return WEBP_MUX_INVALID_ARGUMENT; + } + + // No further checks if mux is partial. + if (mux->state_ == WEBP_MUX_STATE_PARTIAL) return WEBP_MUX_OK; // Verify mux has at least one image. if (mux->images_ == NULL) return WEBP_MUX_INVALID_ARGUMENT; diff --git a/src/mux/muxread.c b/src/mux/muxread.c index 95557501..7b8c622e 100644 --- a/src/mux/muxread.c +++ b/src/mux/muxread.c @@ -53,107 +53,142 @@ static WebPMuxError MuxGet(const WebPMux* const mux, TAG_ID id, uint32_t nth, // Fill the chunk with the given data, after verifying that the data size // doesn't exceed 'max_size'. static WebPMuxError ChunkAssignData(WebPChunk* chunk, const uint8_t* data, - uint32_t max_size, int copy_data) { - uint32_t size = 0; - assert(max_size >= CHUNK_HEADER_SIZE); + uint32_t data_size, uint32_t riff_size, + int copy_data) { + uint32_t chunk_size; - size = GetLE32(data + 4); - assert(size <= MAX_CHUNK_PAYLOAD); - if (size + CHUNK_HEADER_SIZE > max_size) { - return WEBP_MUX_INVALID_ARGUMENT; + // Sanity checks. + if (data_size < TAG_SIZE) return WEBP_MUX_NOT_ENOUGH_DATA; + chunk_size = GetLE32(data + TAG_SIZE); + + { + const uint32_t chunk_disk_size = SizeWithPadding(chunk_size); + if (chunk_disk_size > riff_size) return WEBP_MUX_BAD_DATA; + if (chunk_disk_size > data_size) return WEBP_MUX_NOT_ENOUGH_DATA; } - return ChunkAssignDataImageInfo(chunk, data + CHUNK_HEADER_SIZE, size, NULL, - copy_data, GetLE32(data + 0)); + // Data assignment. + return ChunkAssignDataImageInfo(chunk, data + CHUNK_HEADER_SIZE, chunk_size, + NULL, copy_data, GetLE32(data + 0)); } //------------------------------------------------------------------------------ // Create a mux object from WebP-RIFF data. -WebPMux* WebPMuxCreate(const uint8_t* data, uint32_t size, int copy_data) { - uint32_t mux_size; +WebPMux* WebPMuxCreate(const uint8_t* data, uint32_t size, int copy_data, + WebPMuxState* const mux_state) { + uint32_t riff_size; uint32_t tag; const uint8_t* end; TAG_ID id; - WebPMux* mux; - WebPMuxImage wpi; + WebPMux* mux = NULL; + WebPMuxImage* wpi = NULL; - // Sanity checks on size and leading bytes. - if (data == NULL) return NULL; - if (size < RIFF_HEADER_SIZE + CHUNK_HEADER_SIZE) { - return NULL; - } + if (mux_state) *mux_state = WEBP_MUX_STATE_PARTIAL; + + // Sanity checks. + if (data == NULL) goto Err; + if (size < RIFF_HEADER_SIZE) return NULL; if (GetLE32(data + 0) != mktag('R', 'I', 'F', 'F') || - GetLE32(data + 8) != mktag('W', 'E', 'B', 'P')) { - return NULL; + GetLE32(data + CHUNK_HEADER_SIZE) != mktag('W', 'E', 'B', 'P')) { + goto Err; } - mux_size = CHUNK_HEADER_SIZE + GetLE32(data + TAG_SIZE); - if (mux_size > size) { - return NULL; + + mux = WebPMuxNew(); + if (mux == NULL) goto Err; + + if (size < RIFF_HEADER_SIZE + TAG_SIZE) { + mux->state_ = WEBP_MUX_STATE_PARTIAL; + goto Ok; } + tag = GetLE32(data + RIFF_HEADER_SIZE); if (tag != kChunks[IMAGE_ID].chunkTag && tag != kChunks[VP8X_ID].chunkTag) { // First chunk should be either VP8X or VP8. - return NULL; + goto Err; } - end = data + mux_size; + + riff_size = SizeWithPadding(GetLE32(data + TAG_SIZE)); + if (riff_size > MAX_CHUNK_PAYLOAD) { + goto Err; + } else if (riff_size > size) { + mux->state_ = WEBP_MUX_STATE_PARTIAL; + } else { + mux->state_ = WEBP_MUX_STATE_COMPLETE; + if (riff_size < size) { // Redundant data after last chunk. + size = riff_size; // To make sure we don't read any data beyond mux_size. + } + } + + end = data + size; data += RIFF_HEADER_SIZE; - mux_size -= RIFF_HEADER_SIZE; + size -= RIFF_HEADER_SIZE; - mux = WebPMuxNew(); - if (mux == NULL) return NULL; - - MuxImageInit(&wpi); + wpi = (WebPMuxImage*)malloc(sizeof(*wpi)); + MuxImageInit(wpi); // Loop over chunks. while (data != end) { WebPChunk chunk; - uint32_t data_size; + WebPMuxError err; ChunkInit(&chunk); - if (ChunkAssignData(&chunk, data, mux_size, copy_data) != WEBP_MUX_OK) { - goto Err; + err = ChunkAssignData(&chunk, data, size, riff_size, copy_data); + if (err != WEBP_MUX_OK) { + if (err == WEBP_MUX_NOT_ENOUGH_DATA && + mux->state_ == WEBP_MUX_STATE_PARTIAL) { + goto Ok; + } else { + goto Err; + } } - data_size = ChunkDiskSize(&chunk); id = ChunkGetIdFromTag(chunk.tag_); if (IsWPI(id)) { // An image chunk (frame/tile/alpha/vp8). WebPChunk** wpi_chunk_ptr; - wpi_chunk_ptr = MuxImageGetListFromId(&wpi, id); // Image chunk to set. + wpi_chunk_ptr = MuxImageGetListFromId(wpi, id); // Image chunk to set. assert(wpi_chunk_ptr != NULL); if (*wpi_chunk_ptr != NULL) goto Err; // Consecutive alpha chunks or // consecutive frame/tile chunks. if (ChunkSetNth(&chunk, wpi_chunk_ptr, 1) != WEBP_MUX_OK) goto Err; if (id == IMAGE_ID) { - wpi.is_partial_ = 0; // wpi is completely filled. + wpi->is_partial_ = 0; // wpi is completely filled. // Add this to mux->images_ list. - if (MuxImageSetNth(&wpi, &mux->images_, 0) != WEBP_MUX_OK) goto Err; - MuxImageInit(&wpi); // Reset for reading next image. + if (MuxImageSetNth(wpi, &mux->images_, 0) != WEBP_MUX_OK) goto Err; + MuxImageInit(wpi); // Reset for reading next image. } else { - wpi.is_partial_ = 1; // wpi is only partially filled. + wpi->is_partial_ = 1; // wpi is only partially filled. } } else { // A non-image chunk. WebPChunk** chunk_list; - if (wpi.is_partial_) goto Err; // Encountered a non-image chunk before - // getting all chunks of an image. + if (wpi->is_partial_) goto Err; // Encountered a non-image chunk before + // getting all chunks of an image. chunk_list = GetChunkListFromId(mux, id); // List for adding this chunk. if (chunk_list == NULL) chunk_list = (WebPChunk**)&mux->unknown_; if (ChunkSetNth(&chunk, chunk_list, 0) != WEBP_MUX_OK) goto Err; } - data += data_size; - mux_size -= data_size; + { + const uint32_t data_size = ChunkDiskSize(&chunk); + data += data_size; + size -= data_size; + } } - // Validate mux. + // Validate mux if complete. if (WebPMuxValidate(mux) != WEBP_MUX_OK) goto Err; + Ok: + MuxImageDelete(wpi); + if (mux_state) *mux_state = mux->state_; return mux; // All OK; Err: // Something bad happened. - WebPMuxDelete(mux); - return NULL; + MuxImageDelete(wpi); + WebPMuxDelete(mux); + if (mux_state) *mux_state = WEBP_MUX_STATE_ERROR; + return NULL; } //------------------------------------------------------------------------------ @@ -165,18 +200,29 @@ WebPMuxError WebPMuxGetFeatures(const WebPMux* const mux, uint32_t* flags) { WebPMuxError err; if (mux == NULL || flags == NULL) return WEBP_MUX_INVALID_ARGUMENT; + *flags = 0; + // Check if VP8X chunk is present. err = MuxGet(mux, VP8X_ID, 1, &data, &data_size); - if (err == WEBP_MUX_NOT_FOUND) { // Single image case. - *flags = 0; - return WEBP_MUX_OK; + if (err == WEBP_MUX_NOT_FOUND) { + // Check if VP8 chunk is present. + err = WebPMuxGetImage(mux, &data, &data_size, NULL, NULL); + if (err == WEBP_MUX_NOT_FOUND && // Data not available (yet). + mux->state_ == WEBP_MUX_STATE_PARTIAL) { // Incremental case. + return WEBP_MUX_NOT_ENOUGH_DATA; + } else { + return err; + } + } else if (err != WEBP_MUX_OK) { + return err; } - // Multiple image case. - if (err != WEBP_MUX_OK) return err; + // TODO(urvang): Add a '#define CHUNK_SIZE_BYTES 4' and use it instead of + // hard-coded value of 4 everywhere. if (data_size < 4) return WEBP_MUX_BAD_DATA; - *flags = GetLE32(data); + // All OK. Fill up flags. + *flags = GetLE32(data); return WEBP_MUX_OK; } diff --git a/src/webp/mux.h b/src/webp/mux.h index a833c39a..5c62bd9f 100644 --- a/src/webp/mux.h +++ b/src/webp/mux.h @@ -34,7 +34,7 @@ // // int copy_data = 0; // // ... (Read data from file). -// WebPMux* mux = WebPMuxCreate(data, data_size, copy_data); +// WebPMux* mux = WebPMuxCreate(data, data_size, copy_data, NULL); // WebPMuxGetImage(mux, &image_data, &image_data_size, // &alpha_data, &alpha_size); // // ... (Consume image_data; e.g. call WebPDecode() to decode the data). @@ -60,9 +60,16 @@ typedef enum { WEBP_MUX_INVALID_ARGUMENT = -2, WEBP_MUX_INVALID_PARAMETER = -3, WEBP_MUX_BAD_DATA = -4, - WEBP_MUX_MEMORY_ERROR = -5 + WEBP_MUX_MEMORY_ERROR = -5, + WEBP_MUX_NOT_ENOUGH_DATA = -6 } WebPMuxError; +typedef enum { + WEBP_MUX_STATE_PARTIAL = 0, + WEBP_MUX_STATE_COMPLETE = 1, + WEBP_MUX_STATE_ERROR = -1 +} WebPMuxState; + // Flag values for different features used in VP8X chunk. typedef enum { TILE_FLAG = 0x00000001, @@ -96,11 +103,13 @@ WEBP_EXTERN(void) WebPMuxDelete(WebPMux* const mux); // size - (in) size of raw data // copy_data - (in) value 1 indicates given data WILL copied to the mux, and // value 0 indicates data will NOT be copied. +// mux_state - (out) indicates the state of the mux returned. Can be passed +// NULL if not required. // Returns: // A pointer to the mux object created from given data - on success. // NULL - In case of invalid data or memory error. WEBP_EXTERN(WebPMux*) WebPMuxCreate(const uint8_t* data, uint32_t size, - int copy_data); + int copy_data, WebPMuxState* mux_state); //------------------------------------------------------------------------------ // Single Image.