| OLD | NEW |
| 1 // Copyright 2012 Google Inc. All Rights Reserved. | 1 // Copyright 2012 Google Inc. All Rights Reserved. |
| 2 // | 2 // |
| 3 // Use of this source code is governed by a BSD-style license | 3 // Use of this source code is governed by a BSD-style license |
| 4 // that can be found in the COPYING file in the root of the source | 4 // that can be found in the COPYING file in the root of the source |
| 5 // tree. An additional intellectual property rights grant can be found | 5 // tree. An additional intellectual property rights grant can be found |
| 6 // in the file PATENTS. All contributing project authors may | 6 // in the file PATENTS. All contributing project authors may |
| 7 // be found in the AUTHORS file in the root of the source tree. | 7 // be found in the AUTHORS file in the root of the source tree. |
| 8 // ----------------------------------------------------------------------------- | 8 // ----------------------------------------------------------------------------- |
| 9 // | 9 // |
| 10 // WebP container demux. | 10 // WebP container demux. |
| 11 // | 11 // |
| 12 | 12 |
| 13 #ifdef HAVE_CONFIG_H | 13 #ifdef HAVE_CONFIG_H |
| 14 #include "config.h" | 14 #include "config.h" |
| 15 #endif | 15 #endif |
| 16 | 16 |
| 17 #include <assert.h> | 17 #include <assert.h> |
| 18 #include <stdlib.h> | 18 #include <stdlib.h> |
| 19 #include <string.h> | 19 #include <string.h> |
| 20 | 20 |
| 21 #include "../utils/utils.h" | 21 #include "../utils/utils.h" |
| 22 #include "../webp/decode.h" // WebPGetFeatures | 22 #include "../webp/decode.h" // WebPGetFeatures |
| 23 #include "../webp/demux.h" | 23 #include "../webp/demux.h" |
| 24 #include "../webp/format_constants.h" | 24 #include "../webp/format_constants.h" |
| 25 | 25 |
| 26 #if defined(__cplusplus) || defined(c_plusplus) | |
| 27 extern "C" { | |
| 28 #endif | |
| 29 | |
| 30 #define DMUX_MAJ_VERSION 0 | 26 #define DMUX_MAJ_VERSION 0 |
| 31 #define DMUX_MIN_VERSION 1 | 27 #define DMUX_MIN_VERSION 2 |
| 32 #define DMUX_REV_VERSION 1 | 28 #define DMUX_REV_VERSION 0 |
| 33 | 29 |
| 34 typedef struct { | 30 typedef struct { |
| 35 size_t start_; // start location of the data | 31 size_t start_; // start location of the data |
| 36 size_t end_; // end location | 32 size_t end_; // end location |
| 37 size_t riff_end_; // riff chunk end location, can be > end_. | 33 size_t riff_end_; // riff chunk end location, can be > end_. |
| 38 size_t buf_size_; // size of the buffer | 34 size_t buf_size_; // size of the buffer |
| 39 const uint8_t* buf_; | 35 const uint8_t* buf_; |
| 40 } MemBuffer; | 36 } MemBuffer; |
| 41 | 37 |
| 42 typedef struct { | 38 typedef struct { |
| (...skipping 25 matching lines...) Expand all Loading... |
| 68 WebPDemuxState state_; | 64 WebPDemuxState state_; |
| 69 int is_ext_format_; | 65 int is_ext_format_; |
| 70 uint32_t feature_flags_; | 66 uint32_t feature_flags_; |
| 71 int canvas_width_, canvas_height_; | 67 int canvas_width_, canvas_height_; |
| 72 int loop_count_; | 68 int loop_count_; |
| 73 uint32_t bgcolor_; | 69 uint32_t bgcolor_; |
| 74 int num_frames_; | 70 int num_frames_; |
| 75 Frame* frames_; | 71 Frame* frames_; |
| 76 Frame** frames_tail_; | 72 Frame** frames_tail_; |
| 77 Chunk* chunks_; // non-image chunks | 73 Chunk* chunks_; // non-image chunks |
| 74 Chunk** chunks_tail_; |
| 78 }; | 75 }; |
| 79 | 76 |
| 80 typedef enum { | 77 typedef enum { |
| 81 PARSE_OK, | 78 PARSE_OK, |
| 82 PARSE_NEED_MORE_DATA, | 79 PARSE_NEED_MORE_DATA, |
| 83 PARSE_ERROR | 80 PARSE_ERROR |
| 84 } ParseStatus; | 81 } ParseStatus; |
| 85 | 82 |
| 86 typedef struct ChunkParser { | 83 typedef struct ChunkParser { |
| 87 uint8_t id[4]; | 84 uint8_t id[4]; |
| (...skipping 84 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 172 const uint8_t* const data = mem->buf_ + mem->start_; | 169 const uint8_t* const data = mem->buf_ + mem->start_; |
| 173 const uint32_t val = GetLE32(data); | 170 const uint32_t val = GetLE32(data); |
| 174 Skip(mem, 4); | 171 Skip(mem, 4); |
| 175 return val; | 172 return val; |
| 176 } | 173 } |
| 177 | 174 |
| 178 // ----------------------------------------------------------------------------- | 175 // ----------------------------------------------------------------------------- |
| 179 // Secondary chunk parsing | 176 // Secondary chunk parsing |
| 180 | 177 |
| 181 static void AddChunk(WebPDemuxer* const dmux, Chunk* const chunk) { | 178 static void AddChunk(WebPDemuxer* const dmux, Chunk* const chunk) { |
| 182 Chunk** c = &dmux->chunks_; | 179 *dmux->chunks_tail_ = chunk; |
| 183 while (*c != NULL) c = &(*c)->next_; | |
| 184 *c = chunk; | |
| 185 chunk->next_ = NULL; | 180 chunk->next_ = NULL; |
| 181 dmux->chunks_tail_ = &chunk->next_; |
| 186 } | 182 } |
| 187 | 183 |
| 188 // Add a frame to the end of the list, ensuring the last frame is complete. | 184 // Add a frame to the end of the list, ensuring the last frame is complete. |
| 189 // Returns true on success, false otherwise. | 185 // Returns true on success, false otherwise. |
| 190 static int AddFrame(WebPDemuxer* const dmux, Frame* const frame) { | 186 static int AddFrame(WebPDemuxer* const dmux, Frame* const frame) { |
| 191 const Frame* const last_frame = *dmux->frames_tail_; | 187 const Frame* const last_frame = *dmux->frames_tail_; |
| 192 if (last_frame != NULL && !last_frame->complete_) return 0; | 188 if (last_frame != NULL && !last_frame->complete_) return 0; |
| 193 | 189 |
| 194 *dmux->frames_tail_ = frame; | 190 *dmux->frames_tail_ = frame; |
| 195 frame->next_ = NULL; | 191 frame->next_ = NULL; |
| (...skipping 98 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 294 if (MemDataSize(mem) < min_size) return PARSE_NEED_MORE_DATA; | 290 if (MemDataSize(mem) < min_size) return PARSE_NEED_MORE_DATA; |
| 295 | 291 |
| 296 *frame = (Frame*)calloc(1, sizeof(**frame)); | 292 *frame = (Frame*)calloc(1, sizeof(**frame)); |
| 297 return (*frame == NULL) ? PARSE_ERROR : PARSE_OK; | 293 return (*frame == NULL) ? PARSE_ERROR : PARSE_OK; |
| 298 } | 294 } |
| 299 | 295 |
| 300 // Parse a 'ANMF' chunk and any image bearing chunks that immediately follow. | 296 // Parse a 'ANMF' chunk and any image bearing chunks that immediately follow. |
| 301 // 'frame_chunk_size' is the previously validated, padded chunk size. | 297 // 'frame_chunk_size' is the previously validated, padded chunk size. |
| 302 static ParseStatus ParseAnimationFrame( | 298 static ParseStatus ParseAnimationFrame( |
| 303 WebPDemuxer* const dmux, uint32_t frame_chunk_size) { | 299 WebPDemuxer* const dmux, uint32_t frame_chunk_size) { |
| 304 const int has_frames = !!(dmux->feature_flags_ & ANIMATION_FLAG); | 300 const int is_animation = !!(dmux->feature_flags_ & ANIMATION_FLAG); |
| 305 const uint32_t anmf_payload_size = frame_chunk_size - ANMF_CHUNK_SIZE; | 301 const uint32_t anmf_payload_size = frame_chunk_size - ANMF_CHUNK_SIZE; |
| 306 int added_frame = 0; | 302 int added_frame = 0; |
| 307 int bits; | 303 int bits; |
| 308 MemBuffer* const mem = &dmux->mem_; | 304 MemBuffer* const mem = &dmux->mem_; |
| 309 Frame* frame; | 305 Frame* frame; |
| 310 ParseStatus status = | 306 ParseStatus status = |
| 311 NewFrame(mem, ANMF_CHUNK_SIZE, frame_chunk_size, &frame); | 307 NewFrame(mem, ANMF_CHUNK_SIZE, frame_chunk_size, &frame); |
| 312 if (status != PARSE_OK) return status; | 308 if (status != PARSE_OK) return status; |
| 313 | 309 |
| 314 frame->x_offset_ = 2 * ReadLE24s(mem); | 310 frame->x_offset_ = 2 * ReadLE24s(mem); |
| 315 frame->y_offset_ = 2 * ReadLE24s(mem); | 311 frame->y_offset_ = 2 * ReadLE24s(mem); |
| 316 frame->width_ = 1 + ReadLE24s(mem); | 312 frame->width_ = 1 + ReadLE24s(mem); |
| 317 frame->height_ = 1 + ReadLE24s(mem); | 313 frame->height_ = 1 + ReadLE24s(mem); |
| 318 frame->duration_ = ReadLE24s(mem); | 314 frame->duration_ = ReadLE24s(mem); |
| 319 bits = ReadByte(mem); | 315 bits = ReadByte(mem); |
| 320 frame->dispose_method_ = | 316 frame->dispose_method_ = |
| 321 (bits & 1) ? WEBP_MUX_DISPOSE_BACKGROUND : WEBP_MUX_DISPOSE_NONE; | 317 (bits & 1) ? WEBP_MUX_DISPOSE_BACKGROUND : WEBP_MUX_DISPOSE_NONE; |
| 322 frame->blend_method_ = (bits & 2) ? WEBP_MUX_NO_BLEND : WEBP_MUX_BLEND; | 318 frame->blend_method_ = (bits & 2) ? WEBP_MUX_NO_BLEND : WEBP_MUX_BLEND; |
| 323 if (frame->width_ * (uint64_t)frame->height_ >= MAX_IMAGE_AREA) { | 319 if (frame->width_ * (uint64_t)frame->height_ >= MAX_IMAGE_AREA) { |
| 324 free(frame); | 320 free(frame); |
| 325 return PARSE_ERROR; | 321 return PARSE_ERROR; |
| 326 } | 322 } |
| 327 | 323 |
| 328 // Store a frame only if the animation flag is set there is some data for | 324 // Store a frame only if the animation flag is set there is some data for |
| 329 // this frame is available. | 325 // this frame is available. |
| 330 status = StoreFrame(dmux->num_frames_ + 1, anmf_payload_size, mem, frame); | 326 status = StoreFrame(dmux->num_frames_ + 1, anmf_payload_size, mem, frame); |
| 331 if (status != PARSE_ERROR && has_frames && frame->frame_num_ > 0) { | 327 if (status != PARSE_ERROR && is_animation && frame->frame_num_ > 0) { |
| 332 added_frame = AddFrame(dmux, frame); | 328 added_frame = AddFrame(dmux, frame); |
| 333 if (added_frame) { | 329 if (added_frame) { |
| 334 ++dmux->num_frames_; | 330 ++dmux->num_frames_; |
| 335 } else { | 331 } else { |
| 336 status = PARSE_ERROR; | 332 status = PARSE_ERROR; |
| 337 } | 333 } |
| 338 } | 334 } |
| 339 | 335 |
| 340 if (!added_frame) free(frame); | 336 if (!added_frame) free(frame); |
| 341 return status; | 337 return status; |
| 342 } | 338 } |
| 343 | 339 |
| 344 #ifdef WEBP_EXPERIMENTAL_FEATURES | 340 #ifdef WEBP_EXPERIMENTAL_FEATURES |
| 345 // Parse a 'FRGM' chunk and any image bearing chunks that immediately follow. | 341 // Parse a 'FRGM' chunk and any image bearing chunks that immediately follow. |
| 346 // 'fragment_chunk_size' is the previously validated, padded chunk size. | 342 // 'fragment_chunk_size' is the previously validated, padded chunk size. |
| 347 static ParseStatus ParseFragment(WebPDemuxer* const dmux, | 343 static ParseStatus ParseFragment(WebPDemuxer* const dmux, |
| 348 uint32_t fragment_chunk_size) { | 344 uint32_t fragment_chunk_size) { |
| 349 const int frame_num = 1; // All fragments belong to the 1st (and only) frame. | 345 const int frame_num = 1; // All fragments belong to the 1st (and only) frame. |
| 350 const int has_fragments = !!(dmux->feature_flags_ & FRAGMENTS_FLAG); | 346 const int is_fragmented = !!(dmux->feature_flags_ & FRAGMENTS_FLAG); |
| 351 const uint32_t frgm_payload_size = fragment_chunk_size - FRGM_CHUNK_SIZE; | 347 const uint32_t frgm_payload_size = fragment_chunk_size - FRGM_CHUNK_SIZE; |
| 352 int added_fragment = 0; | 348 int added_fragment = 0; |
| 353 MemBuffer* const mem = &dmux->mem_; | 349 MemBuffer* const mem = &dmux->mem_; |
| 354 Frame* frame; | 350 Frame* frame; |
| 355 ParseStatus status = | 351 ParseStatus status = |
| 356 NewFrame(mem, FRGM_CHUNK_SIZE, fragment_chunk_size, &frame); | 352 NewFrame(mem, FRGM_CHUNK_SIZE, fragment_chunk_size, &frame); |
| 357 if (status != PARSE_OK) return status; | 353 if (status != PARSE_OK) return status; |
| 358 | 354 |
| 359 frame->is_fragment_ = 1; | 355 frame->is_fragment_ = 1; |
| 360 frame->x_offset_ = 2 * ReadLE24s(mem); | 356 frame->x_offset_ = 2 * ReadLE24s(mem); |
| 361 frame->y_offset_ = 2 * ReadLE24s(mem); | 357 frame->y_offset_ = 2 * ReadLE24s(mem); |
| 362 | 358 |
| 363 // Store a fragment only if the fragments flag is set there is some data for | 359 // Store a fragment only if the 'fragments' flag is set and there is some |
| 364 // this fragment is available. | 360 // data available. |
| 365 status = StoreFrame(frame_num, frgm_payload_size, mem, frame); | 361 status = StoreFrame(frame_num, frgm_payload_size, mem, frame); |
| 366 if (status != PARSE_ERROR && has_fragments && frame->frame_num_ > 0) { | 362 if (status != PARSE_ERROR && is_fragmented && frame->frame_num_ > 0) { |
| 367 added_fragment = AddFrame(dmux, frame); | 363 added_fragment = AddFrame(dmux, frame); |
| 368 if (!added_fragment) { | 364 if (!added_fragment) { |
| 369 status = PARSE_ERROR; | 365 status = PARSE_ERROR; |
| 370 } else { | 366 } else { |
| 371 dmux->num_frames_ = 1; | 367 dmux->num_frames_ = 1; |
| 372 } | 368 } |
| 373 } | 369 } |
| 374 | 370 |
| 375 if (!added_fragment) free(frame); | 371 if (!added_fragment) free(frame); |
| 376 return status; | 372 return status; |
| (...skipping 11 matching lines...) Expand all Loading... |
| 388 | 384 |
| 389 chunk->data_.offset_ = start_offset; | 385 chunk->data_.offset_ = start_offset; |
| 390 chunk->data_.size_ = size; | 386 chunk->data_.size_ = size; |
| 391 AddChunk(dmux, chunk); | 387 AddChunk(dmux, chunk); |
| 392 return 1; | 388 return 1; |
| 393 } | 389 } |
| 394 | 390 |
| 395 // ----------------------------------------------------------------------------- | 391 // ----------------------------------------------------------------------------- |
| 396 // Primary chunk parsing | 392 // Primary chunk parsing |
| 397 | 393 |
| 398 static int ReadHeader(MemBuffer* const mem) { | 394 static ParseStatus ReadHeader(MemBuffer* const mem) { |
| 399 const size_t min_size = RIFF_HEADER_SIZE + CHUNK_HEADER_SIZE; | 395 const size_t min_size = RIFF_HEADER_SIZE + CHUNK_HEADER_SIZE; |
| 400 uint32_t riff_size; | 396 uint32_t riff_size; |
| 401 | 397 |
| 402 // Basic file level validation. | 398 // Basic file level validation. |
| 403 if (MemDataSize(mem) < min_size) return 0; | 399 if (MemDataSize(mem) < min_size) return PARSE_NEED_MORE_DATA; |
| 404 if (memcmp(GetBuffer(mem), "RIFF", CHUNK_SIZE_BYTES) || | 400 if (memcmp(GetBuffer(mem), "RIFF", CHUNK_SIZE_BYTES) || |
| 405 memcmp(GetBuffer(mem) + CHUNK_HEADER_SIZE, "WEBP", CHUNK_SIZE_BYTES)) { | 401 memcmp(GetBuffer(mem) + CHUNK_HEADER_SIZE, "WEBP", CHUNK_SIZE_BYTES)) { |
| 406 return 0; | 402 return PARSE_ERROR; |
| 407 } | 403 } |
| 408 | 404 |
| 409 riff_size = GetLE32(GetBuffer(mem) + TAG_SIZE); | 405 riff_size = GetLE32(GetBuffer(mem) + TAG_SIZE); |
| 410 if (riff_size < CHUNK_HEADER_SIZE) return 0; | 406 if (riff_size < CHUNK_HEADER_SIZE) return PARSE_ERROR; |
| 411 if (riff_size > MAX_CHUNK_PAYLOAD) return 0; | 407 if (riff_size > MAX_CHUNK_PAYLOAD) return PARSE_ERROR; |
| 412 | 408 |
| 413 // There's no point in reading past the end of the RIFF chunk | 409 // There's no point in reading past the end of the RIFF chunk |
| 414 mem->riff_end_ = riff_size + CHUNK_HEADER_SIZE; | 410 mem->riff_end_ = riff_size + CHUNK_HEADER_SIZE; |
| 415 if (mem->buf_size_ > mem->riff_end_) { | 411 if (mem->buf_size_ > mem->riff_end_) { |
| 416 mem->buf_size_ = mem->end_ = mem->riff_end_; | 412 mem->buf_size_ = mem->end_ = mem->riff_end_; |
| 417 } | 413 } |
| 418 | 414 |
| 419 Skip(mem, RIFF_HEADER_SIZE); | 415 Skip(mem, RIFF_HEADER_SIZE); |
| 420 return 1; | 416 return PARSE_OK; |
| 421 } | 417 } |
| 422 | 418 |
| 423 static ParseStatus ParseSingleImage(WebPDemuxer* const dmux) { | 419 static ParseStatus ParseSingleImage(WebPDemuxer* const dmux) { |
| 424 const size_t min_size = CHUNK_HEADER_SIZE; | 420 const size_t min_size = CHUNK_HEADER_SIZE; |
| 425 MemBuffer* const mem = &dmux->mem_; | 421 MemBuffer* const mem = &dmux->mem_; |
| 426 Frame* frame; | 422 Frame* frame; |
| 427 ParseStatus status; | 423 ParseStatus status; |
| 424 int image_added = 0; |
| 428 | 425 |
| 429 if (dmux->frames_ != NULL) return PARSE_ERROR; | 426 if (dmux->frames_ != NULL) return PARSE_ERROR; |
| 430 if (SizeIsInvalid(mem, min_size)) return PARSE_ERROR; | 427 if (SizeIsInvalid(mem, min_size)) return PARSE_ERROR; |
| 431 if (MemDataSize(mem) < min_size) return PARSE_NEED_MORE_DATA; | 428 if (MemDataSize(mem) < min_size) return PARSE_NEED_MORE_DATA; |
| 432 | 429 |
| 433 frame = (Frame*)calloc(1, sizeof(*frame)); | 430 frame = (Frame*)calloc(1, sizeof(*frame)); |
| 434 if (frame == NULL) return PARSE_ERROR; | 431 if (frame == NULL) return PARSE_ERROR; |
| 435 | 432 |
| 436 // For the single image case we allow parsing of a partial frame, but we need | 433 // For the single image case we allow parsing of a partial frame, but we need |
| 437 // at least CHUNK_HEADER_SIZE for parsing. | 434 // at least CHUNK_HEADER_SIZE for parsing. |
| 438 status = StoreFrame(1, CHUNK_HEADER_SIZE, &dmux->mem_, frame); | 435 status = StoreFrame(1, CHUNK_HEADER_SIZE, &dmux->mem_, frame); |
| 439 if (status != PARSE_ERROR) { | 436 if (status != PARSE_ERROR) { |
| 440 const int has_alpha = !!(dmux->feature_flags_ & ALPHA_FLAG); | 437 const int has_alpha = !!(dmux->feature_flags_ & ALPHA_FLAG); |
| 441 // Clear any alpha when the alpha flag is missing. | 438 // Clear any alpha when the alpha flag is missing. |
| 442 if (!has_alpha && frame->img_components_[1].size_ > 0) { | 439 if (!has_alpha && frame->img_components_[1].size_ > 0) { |
| 443 frame->img_components_[1].offset_ = 0; | 440 frame->img_components_[1].offset_ = 0; |
| 444 frame->img_components_[1].size_ = 0; | 441 frame->img_components_[1].size_ = 0; |
| 445 frame->has_alpha_ = 0; | 442 frame->has_alpha_ = 0; |
| 446 } | 443 } |
| 447 | 444 |
| 448 // Use the frame width/height as the canvas values for non-vp8x files. | 445 // Use the frame width/height as the canvas values for non-vp8x files. |
| 449 // Also, set ALPHA_FLAG if this is a lossless image with alpha. | 446 // Also, set ALPHA_FLAG if this is a lossless image with alpha. |
| 450 if (!dmux->is_ext_format_ && frame->width_ > 0 && frame->height_ > 0) { | 447 if (!dmux->is_ext_format_ && frame->width_ > 0 && frame->height_ > 0) { |
| 451 dmux->state_ = WEBP_DEMUX_PARSED_HEADER; | 448 dmux->state_ = WEBP_DEMUX_PARSED_HEADER; |
| 452 dmux->canvas_width_ = frame->width_; | 449 dmux->canvas_width_ = frame->width_; |
| 453 dmux->canvas_height_ = frame->height_; | 450 dmux->canvas_height_ = frame->height_; |
| 454 dmux->feature_flags_ |= frame->has_alpha_ ? ALPHA_FLAG : 0; | 451 dmux->feature_flags_ |= frame->has_alpha_ ? ALPHA_FLAG : 0; |
| 455 } | 452 } |
| 456 AddFrame(dmux, frame); | 453 if (!AddFrame(dmux, frame)) { |
| 457 dmux->num_frames_ = 1; | 454 status = PARSE_ERROR; // last frame was left incomplete |
| 458 } else { | 455 } else { |
| 459 free(frame); | 456 image_added = 1; |
| 457 dmux->num_frames_ = 1; |
| 458 } |
| 460 } | 459 } |
| 461 | 460 |
| 461 if (!image_added) free(frame); |
| 462 return status; | 462 return status; |
| 463 } | 463 } |
| 464 | 464 |
| 465 static ParseStatus ParseVP8X(WebPDemuxer* const dmux) { | 465 static ParseStatus ParseVP8XChunks(WebPDemuxer* const dmux) { |
| 466 const int is_animation = !!(dmux->feature_flags_ & ANIMATION_FLAG); |
| 466 MemBuffer* const mem = &dmux->mem_; | 467 MemBuffer* const mem = &dmux->mem_; |
| 467 int anim_chunks = 0; | 468 int anim_chunks = 0; |
| 468 uint32_t vp8x_size; | |
| 469 ParseStatus status = PARSE_OK; | 469 ParseStatus status = PARSE_OK; |
| 470 | 470 |
| 471 if (MemDataSize(mem) < CHUNK_HEADER_SIZE) return PARSE_NEED_MORE_DATA; | |
| 472 | |
| 473 dmux->is_ext_format_ = 1; | |
| 474 Skip(mem, TAG_SIZE); // VP8X | |
| 475 vp8x_size = ReadLE32(mem); | |
| 476 if (vp8x_size > MAX_CHUNK_PAYLOAD) return PARSE_ERROR; | |
| 477 if (vp8x_size < VP8X_CHUNK_SIZE) return PARSE_ERROR; | |
| 478 vp8x_size += vp8x_size & 1; | |
| 479 if (SizeIsInvalid(mem, vp8x_size)) return PARSE_ERROR; | |
| 480 if (MemDataSize(mem) < vp8x_size) return PARSE_NEED_MORE_DATA; | |
| 481 | |
| 482 dmux->feature_flags_ = ReadByte(mem); | |
| 483 Skip(mem, 3); // Reserved. | |
| 484 dmux->canvas_width_ = 1 + ReadLE24s(mem); | |
| 485 dmux->canvas_height_ = 1 + ReadLE24s(mem); | |
| 486 if (dmux->canvas_width_ * (uint64_t)dmux->canvas_height_ >= MAX_IMAGE_AREA) { | |
| 487 return PARSE_ERROR; // image final dimension is too large | |
| 488 } | |
| 489 Skip(mem, vp8x_size - VP8X_CHUNK_SIZE); // skip any trailing data. | |
| 490 dmux->state_ = WEBP_DEMUX_PARSED_HEADER; | |
| 491 | |
| 492 if (SizeIsInvalid(mem, CHUNK_HEADER_SIZE)) return PARSE_ERROR; | |
| 493 if (MemDataSize(mem) < CHUNK_HEADER_SIZE) return PARSE_NEED_MORE_DATA; | |
| 494 | |
| 495 do { | 471 do { |
| 496 int store_chunk = 1; | 472 int store_chunk = 1; |
| 497 const size_t chunk_start_offset = mem->start_; | 473 const size_t chunk_start_offset = mem->start_; |
| 498 const uint32_t fourcc = ReadLE32(mem); | 474 const uint32_t fourcc = ReadLE32(mem); |
| 499 const uint32_t chunk_size = ReadLE32(mem); | 475 const uint32_t chunk_size = ReadLE32(mem); |
| 500 const uint32_t chunk_size_padded = chunk_size + (chunk_size & 1); | 476 const uint32_t chunk_size_padded = chunk_size + (chunk_size & 1); |
| 501 | 477 |
| 502 if (chunk_size > MAX_CHUNK_PAYLOAD) return PARSE_ERROR; | 478 if (chunk_size > MAX_CHUNK_PAYLOAD) return PARSE_ERROR; |
| 503 if (SizeIsInvalid(mem, chunk_size_padded)) return PARSE_ERROR; | 479 if (SizeIsInvalid(mem, chunk_size_padded)) return PARSE_ERROR; |
| 504 | 480 |
| 505 switch (fourcc) { | 481 switch (fourcc) { |
| 506 case MKFOURCC('V', 'P', '8', 'X'): { | 482 case MKFOURCC('V', 'P', '8', 'X'): { |
| 507 return PARSE_ERROR; | 483 return PARSE_ERROR; |
| 508 } | 484 } |
| 509 case MKFOURCC('A', 'L', 'P', 'H'): | 485 case MKFOURCC('A', 'L', 'P', 'H'): |
| 510 case MKFOURCC('V', 'P', '8', ' '): | 486 case MKFOURCC('V', 'P', '8', ' '): |
| 511 case MKFOURCC('V', 'P', '8', 'L'): { | 487 case MKFOURCC('V', 'P', '8', 'L'): { |
| 512 const int has_frames = !!(dmux->feature_flags_ & ANIMATION_FLAG); | |
| 513 // check that this isn't an animation (all frames should be in an ANMF). | 488 // check that this isn't an animation (all frames should be in an ANMF). |
| 514 if (anim_chunks > 0 || has_frames) return PARSE_ERROR; | 489 if (anim_chunks > 0 || is_animation) return PARSE_ERROR; |
| 515 | 490 |
| 516 Rewind(mem, CHUNK_HEADER_SIZE); | 491 Rewind(mem, CHUNK_HEADER_SIZE); |
| 517 status = ParseSingleImage(dmux); | 492 status = ParseSingleImage(dmux); |
| 518 break; | 493 break; |
| 519 } | 494 } |
| 520 case MKFOURCC('A', 'N', 'I', 'M'): { | 495 case MKFOURCC('A', 'N', 'I', 'M'): { |
| 521 if (chunk_size_padded < ANIM_CHUNK_SIZE) return PARSE_ERROR; | 496 if (chunk_size_padded < ANIM_CHUNK_SIZE) return PARSE_ERROR; |
| 522 | 497 |
| 523 if (MemDataSize(mem) < chunk_size_padded) { | 498 if (MemDataSize(mem) < chunk_size_padded) { |
| 524 status = PARSE_NEED_MORE_DATA; | 499 status = PARSE_NEED_MORE_DATA; |
| (...skipping 16 matching lines...) Expand all Loading... |
| 541 #ifdef WEBP_EXPERIMENTAL_FEATURES | 516 #ifdef WEBP_EXPERIMENTAL_FEATURES |
| 542 case MKFOURCC('F', 'R', 'G', 'M'): { | 517 case MKFOURCC('F', 'R', 'G', 'M'): { |
| 543 status = ParseFragment(dmux, chunk_size_padded); | 518 status = ParseFragment(dmux, chunk_size_padded); |
| 544 break; | 519 break; |
| 545 } | 520 } |
| 546 #endif | 521 #endif |
| 547 case MKFOURCC('I', 'C', 'C', 'P'): { | 522 case MKFOURCC('I', 'C', 'C', 'P'): { |
| 548 store_chunk = !!(dmux->feature_flags_ & ICCP_FLAG); | 523 store_chunk = !!(dmux->feature_flags_ & ICCP_FLAG); |
| 549 goto Skip; | 524 goto Skip; |
| 550 } | 525 } |
| 526 case MKFOURCC('E', 'X', 'I', 'F'): { |
| 527 store_chunk = !!(dmux->feature_flags_ & EXIF_FLAG); |
| 528 goto Skip; |
| 529 } |
| 551 case MKFOURCC('X', 'M', 'P', ' '): { | 530 case MKFOURCC('X', 'M', 'P', ' '): { |
| 552 store_chunk = !!(dmux->feature_flags_ & XMP_FLAG); | 531 store_chunk = !!(dmux->feature_flags_ & XMP_FLAG); |
| 553 goto Skip; | 532 goto Skip; |
| 554 } | 533 } |
| 555 case MKFOURCC('E', 'X', 'I', 'F'): { | |
| 556 store_chunk = !!(dmux->feature_flags_ & EXIF_FLAG); | |
| 557 goto Skip; | |
| 558 } | |
| 559 Skip: | 534 Skip: |
| 560 default: { | 535 default: { |
| 561 if (chunk_size_padded <= MemDataSize(mem)) { | 536 if (chunk_size_padded <= MemDataSize(mem)) { |
| 562 if (store_chunk) { | 537 if (store_chunk) { |
| 563 // Store only the chunk header and unpadded size as only the payload | 538 // Store only the chunk header and unpadded size as only the payload |
| 564 // will be returned to the user. | 539 // will be returned to the user. |
| 565 if (!StoreChunk(dmux, chunk_start_offset, | 540 if (!StoreChunk(dmux, chunk_start_offset, |
| 566 CHUNK_HEADER_SIZE + chunk_size)) { | 541 CHUNK_HEADER_SIZE + chunk_size)) { |
| 567 return PARSE_ERROR; | 542 return PARSE_ERROR; |
| 568 } | 543 } |
| 569 } | 544 } |
| 570 Skip(mem, chunk_size_padded); | 545 Skip(mem, chunk_size_padded); |
| 571 } else { | 546 } else { |
| 572 status = PARSE_NEED_MORE_DATA; | 547 status = PARSE_NEED_MORE_DATA; |
| 573 } | 548 } |
| 574 } | 549 } |
| 575 } | 550 } |
| 576 | 551 |
| 577 if (mem->start_ == mem->riff_end_) { | 552 if (mem->start_ == mem->riff_end_) { |
| 578 break; | 553 break; |
| 579 } else if (MemDataSize(mem) < CHUNK_HEADER_SIZE) { | 554 } else if (MemDataSize(mem) < CHUNK_HEADER_SIZE) { |
| 580 status = PARSE_NEED_MORE_DATA; | 555 status = PARSE_NEED_MORE_DATA; |
| 581 } | 556 } |
| 582 } while (status == PARSE_OK); | 557 } while (status == PARSE_OK); |
| 583 | 558 |
| 584 return status; | 559 return status; |
| 585 } | 560 } |
| 586 | 561 |
| 562 static ParseStatus ParseVP8X(WebPDemuxer* const dmux) { |
| 563 MemBuffer* const mem = &dmux->mem_; |
| 564 uint32_t vp8x_size; |
| 565 |
| 566 if (MemDataSize(mem) < CHUNK_HEADER_SIZE) return PARSE_NEED_MORE_DATA; |
| 567 |
| 568 dmux->is_ext_format_ = 1; |
| 569 Skip(mem, TAG_SIZE); // VP8X |
| 570 vp8x_size = ReadLE32(mem); |
| 571 if (vp8x_size > MAX_CHUNK_PAYLOAD) return PARSE_ERROR; |
| 572 if (vp8x_size < VP8X_CHUNK_SIZE) return PARSE_ERROR; |
| 573 vp8x_size += vp8x_size & 1; |
| 574 if (SizeIsInvalid(mem, vp8x_size)) return PARSE_ERROR; |
| 575 if (MemDataSize(mem) < vp8x_size) return PARSE_NEED_MORE_DATA; |
| 576 |
| 577 dmux->feature_flags_ = ReadByte(mem); |
| 578 Skip(mem, 3); // Reserved. |
| 579 dmux->canvas_width_ = 1 + ReadLE24s(mem); |
| 580 dmux->canvas_height_ = 1 + ReadLE24s(mem); |
| 581 if (dmux->canvas_width_ * (uint64_t)dmux->canvas_height_ >= MAX_IMAGE_AREA) { |
| 582 return PARSE_ERROR; // image final dimension is too large |
| 583 } |
| 584 Skip(mem, vp8x_size - VP8X_CHUNK_SIZE); // skip any trailing data. |
| 585 dmux->state_ = WEBP_DEMUX_PARSED_HEADER; |
| 586 |
| 587 if (SizeIsInvalid(mem, CHUNK_HEADER_SIZE)) return PARSE_ERROR; |
| 588 if (MemDataSize(mem) < CHUNK_HEADER_SIZE) return PARSE_NEED_MORE_DATA; |
| 589 |
| 590 return ParseVP8XChunks(dmux); |
| 591 } |
| 592 |
| 587 // ----------------------------------------------------------------------------- | 593 // ----------------------------------------------------------------------------- |
| 588 // Format validation | 594 // Format validation |
| 589 | 595 |
| 590 static int IsValidSimpleFormat(const WebPDemuxer* const dmux) { | 596 static int IsValidSimpleFormat(const WebPDemuxer* const dmux) { |
| 591 const Frame* const frame = dmux->frames_; | 597 const Frame* const frame = dmux->frames_; |
| 592 if (dmux->state_ == WEBP_DEMUX_PARSING_HEADER) return 1; | 598 if (dmux->state_ == WEBP_DEMUX_PARSING_HEADER) return 1; |
| 593 | 599 |
| 594 if (dmux->canvas_width_ <= 0 || dmux->canvas_height_ <= 0) return 0; | 600 if (dmux->canvas_width_ <= 0 || dmux->canvas_height_ <= 0) return 0; |
| 595 if (dmux->state_ == WEBP_DEMUX_DONE && frame == NULL) return 0; | 601 if (dmux->state_ == WEBP_DEMUX_DONE && frame == NULL) return 0; |
| 596 | 602 |
| (...skipping 16 matching lines...) Expand all Loading... |
| 613 } | 619 } |
| 614 } else { | 620 } else { |
| 615 if (frame->x_offset_ < 0 || frame->y_offset_ < 0) return 0; | 621 if (frame->x_offset_ < 0 || frame->y_offset_ < 0) return 0; |
| 616 if (frame->width_ + frame->x_offset_ > canvas_width) return 0; | 622 if (frame->width_ + frame->x_offset_ > canvas_width) return 0; |
| 617 if (frame->height_ + frame->y_offset_ > canvas_height) return 0; | 623 if (frame->height_ + frame->y_offset_ > canvas_height) return 0; |
| 618 } | 624 } |
| 619 return 1; | 625 return 1; |
| 620 } | 626 } |
| 621 | 627 |
| 622 static int IsValidExtendedFormat(const WebPDemuxer* const dmux) { | 628 static int IsValidExtendedFormat(const WebPDemuxer* const dmux) { |
| 623 const int has_fragments = !!(dmux->feature_flags_ & FRAGMENTS_FLAG); | 629 const int is_animation = !!(dmux->feature_flags_ & ANIMATION_FLAG); |
| 624 const int has_frames = !!(dmux->feature_flags_ & ANIMATION_FLAG); | 630 const int is_fragmented = !!(dmux->feature_flags_ & FRAGMENTS_FLAG); |
| 625 const Frame* f = dmux->frames_; | 631 const Frame* f = dmux->frames_; |
| 626 | 632 |
| 627 if (dmux->state_ == WEBP_DEMUX_PARSING_HEADER) return 1; | 633 if (dmux->state_ == WEBP_DEMUX_PARSING_HEADER) return 1; |
| 628 | 634 |
| 629 if (dmux->canvas_width_ <= 0 || dmux->canvas_height_ <= 0) return 0; | 635 if (dmux->canvas_width_ <= 0 || dmux->canvas_height_ <= 0) return 0; |
| 630 if (dmux->loop_count_ < 0) return 0; | 636 if (dmux->loop_count_ < 0) return 0; |
| 631 if (dmux->state_ == WEBP_DEMUX_DONE && dmux->frames_ == NULL) return 0; | 637 if (dmux->state_ == WEBP_DEMUX_DONE && dmux->frames_ == NULL) return 0; |
| 632 #ifndef WEBP_EXPERIMENTAL_FEATURES | 638 #ifndef WEBP_EXPERIMENTAL_FEATURES |
| 633 if (has_fragments) return 0; | 639 if (is_fragmented) return 0; |
| 634 #endif | 640 #endif |
| 635 | 641 |
| 636 while (f != NULL) { | 642 while (f != NULL) { |
| 637 const int cur_frame_set = f->frame_num_; | 643 const int cur_frame_set = f->frame_num_; |
| 638 int frame_count = 0, fragment_count = 0; | 644 int frame_count = 0, fragment_count = 0; |
| 639 | 645 |
| 640 // Check frame properties and if the image is composed of fragments that | 646 // Check frame properties and if the image is composed of fragments that |
| 641 // each fragment came from a fragment. | 647 // each fragment came from a fragment. |
| 642 for (; f != NULL && f->frame_num_ == cur_frame_set; f = f->next_) { | 648 for (; f != NULL && f->frame_num_ == cur_frame_set; f = f->next_) { |
| 643 const ChunkData* const image = f->img_components_; | 649 const ChunkData* const image = f->img_components_; |
| 644 const ChunkData* const alpha = f->img_components_ + 1; | 650 const ChunkData* const alpha = f->img_components_ + 1; |
| 645 | 651 |
| 646 if (has_fragments && !f->is_fragment_) return 0; | 652 if (is_fragmented && !f->is_fragment_) return 0; |
| 647 if (!has_fragments && f->is_fragment_) return 0; | 653 if (!is_fragmented && f->is_fragment_) return 0; |
| 648 if (!has_frames && f->frame_num_ > 1) return 0; | 654 if (!is_animation && f->frame_num_ > 1) return 0; |
| 649 | 655 |
| 650 if (f->complete_) { | 656 if (f->complete_) { |
| 651 if (alpha->size_ == 0 && image->size_ == 0) return 0; | 657 if (alpha->size_ == 0 && image->size_ == 0) return 0; |
| 652 // Ensure alpha precedes image bitstream. | 658 // Ensure alpha precedes image bitstream. |
| 653 if (alpha->size_ > 0 && alpha->offset_ > image->offset_) { | 659 if (alpha->size_ > 0 && alpha->offset_ > image->offset_) { |
| 654 return 0; | 660 return 0; |
| 655 } | 661 } |
| 656 | 662 |
| 657 if (f->width_ <= 0 || f->height_ <= 0) return 0; | 663 if (f->width_ <= 0 || f->height_ <= 0) return 0; |
| 658 } else { | 664 } else { |
| 659 // There shouldn't be a partial frame in a complete file. | 665 // There shouldn't be a partial frame in a complete file. |
| 660 if (dmux->state_ == WEBP_DEMUX_DONE) return 0; | 666 if (dmux->state_ == WEBP_DEMUX_DONE) return 0; |
| 661 | 667 |
| 662 // Ensure alpha precedes image bitstream. | 668 // Ensure alpha precedes image bitstream. |
| 663 if (alpha->size_ > 0 && image->size_ > 0 && | 669 if (alpha->size_ > 0 && image->size_ > 0 && |
| 664 alpha->offset_ > image->offset_) { | 670 alpha->offset_ > image->offset_) { |
| 665 return 0; | 671 return 0; |
| 666 } | 672 } |
| 667 // There shouldn't be any frames after an incomplete one. | 673 // There shouldn't be any frames after an incomplete one. |
| 668 if (f->next_ != NULL) return 0; | 674 if (f->next_ != NULL) return 0; |
| 669 } | 675 } |
| 670 | 676 |
| 671 if (f->width_ > 0 && f->height_ > 0 && | 677 if (f->width_ > 0 && f->height_ > 0 && |
| 672 !CheckFrameBounds(f, !(has_frames || has_fragments), | 678 !CheckFrameBounds(f, !(is_animation || is_fragmented), |
| 673 dmux->canvas_width_, dmux->canvas_height_)) { | 679 dmux->canvas_width_, dmux->canvas_height_)) { |
| 674 return 0; | 680 return 0; |
| 675 } | 681 } |
| 676 | 682 |
| 677 fragment_count += f->is_fragment_; | 683 fragment_count += f->is_fragment_; |
| 678 ++frame_count; | 684 ++frame_count; |
| 679 } | 685 } |
| 680 if (!has_fragments && frame_count > 1) return 0; | 686 if (!is_fragmented && frame_count > 1) return 0; |
| 681 if (fragment_count > 0 && frame_count != fragment_count) return 0; | 687 if (fragment_count > 0 && frame_count != fragment_count) return 0; |
| 682 if (f == NULL) break; | |
| 683 } | 688 } |
| 684 return 1; | 689 return 1; |
| 685 } | 690 } |
| 686 | 691 |
| 687 // ----------------------------------------------------------------------------- | 692 // ----------------------------------------------------------------------------- |
| 688 // WebPDemuxer object | 693 // WebPDemuxer object |
| 689 | 694 |
| 690 static void InitDemux(WebPDemuxer* const dmux, const MemBuffer* const mem) { | 695 static void InitDemux(WebPDemuxer* const dmux, const MemBuffer* const mem) { |
| 691 dmux->state_ = WEBP_DEMUX_PARSING_HEADER; | 696 dmux->state_ = WEBP_DEMUX_PARSING_HEADER; |
| 692 dmux->loop_count_ = 1; | 697 dmux->loop_count_ = 1; |
| 693 dmux->bgcolor_ = 0xFFFFFFFF; // White background by default. | 698 dmux->bgcolor_ = 0xFFFFFFFF; // White background by default. |
| 694 dmux->canvas_width_ = -1; | 699 dmux->canvas_width_ = -1; |
| 695 dmux->canvas_height_ = -1; | 700 dmux->canvas_height_ = -1; |
| 696 dmux->frames_tail_ = &dmux->frames_; | 701 dmux->frames_tail_ = &dmux->frames_; |
| 702 dmux->chunks_tail_ = &dmux->chunks_; |
| 697 dmux->mem_ = *mem; | 703 dmux->mem_ = *mem; |
| 698 } | 704 } |
| 699 | 705 |
| 700 WebPDemuxer* WebPDemuxInternal(const WebPData* data, int allow_partial, | 706 WebPDemuxer* WebPDemuxInternal(const WebPData* data, int allow_partial, |
| 701 WebPDemuxState* state, int version) { | 707 WebPDemuxState* state, int version) { |
| 702 const ChunkParser* parser; | 708 const ChunkParser* parser; |
| 703 int partial; | 709 int partial; |
| 704 ParseStatus status = PARSE_ERROR; | 710 ParseStatus status = PARSE_ERROR; |
| 705 MemBuffer mem; | 711 MemBuffer mem; |
| 706 WebPDemuxer* dmux; | 712 WebPDemuxer* dmux; |
| 707 | 713 |
| 714 if (state != NULL) *state = WEBP_DEMUX_PARSE_ERROR; |
| 715 |
| 708 if (WEBP_ABI_IS_INCOMPATIBLE(version, WEBP_DEMUX_ABI_VERSION)) return NULL; | 716 if (WEBP_ABI_IS_INCOMPATIBLE(version, WEBP_DEMUX_ABI_VERSION)) return NULL; |
| 709 if (data == NULL || data->bytes == NULL || data->size == 0) return NULL; | 717 if (data == NULL || data->bytes == NULL || data->size == 0) return NULL; |
| 710 | 718 |
| 711 if (!InitMemBuffer(&mem, data->bytes, data->size)) return NULL; | 719 if (!InitMemBuffer(&mem, data->bytes, data->size)) return NULL; |
| 712 if (!ReadHeader(&mem)) return NULL; | 720 status = ReadHeader(&mem); |
| 721 if (status != PARSE_OK) { |
| 722 if (state != NULL) { |
| 723 *state = (status == PARSE_NEED_MORE_DATA) ? WEBP_DEMUX_PARSING_HEADER |
| 724 : WEBP_DEMUX_PARSE_ERROR; |
| 725 } |
| 726 return NULL; |
| 727 } |
| 713 | 728 |
| 714 partial = (mem.buf_size_ < mem.riff_end_); | 729 partial = (mem.buf_size_ < mem.riff_end_); |
| 715 if (!allow_partial && partial) return NULL; | 730 if (!allow_partial && partial) return NULL; |
| 716 | 731 |
| 717 dmux = (WebPDemuxer*)calloc(1, sizeof(*dmux)); | 732 dmux = (WebPDemuxer*)calloc(1, sizeof(*dmux)); |
| 718 if (dmux == NULL) return NULL; | 733 if (dmux == NULL) return NULL; |
| 719 InitDemux(dmux, &mem); | 734 InitDemux(dmux, &mem); |
| 720 | 735 |
| 736 status = PARSE_ERROR; |
| 721 for (parser = kMasterChunks; parser->parse != NULL; ++parser) { | 737 for (parser = kMasterChunks; parser->parse != NULL; ++parser) { |
| 722 if (!memcmp(parser->id, GetBuffer(&dmux->mem_), TAG_SIZE)) { | 738 if (!memcmp(parser->id, GetBuffer(&dmux->mem_), TAG_SIZE)) { |
| 723 status = parser->parse(dmux); | 739 status = parser->parse(dmux); |
| 724 if (status == PARSE_OK) dmux->state_ = WEBP_DEMUX_DONE; | 740 if (status == PARSE_OK) dmux->state_ = WEBP_DEMUX_DONE; |
| 725 if (status == PARSE_NEED_MORE_DATA && !partial) status = PARSE_ERROR; | 741 if (status == PARSE_NEED_MORE_DATA && !partial) status = PARSE_ERROR; |
| 726 if (status != PARSE_ERROR && !parser->valid(dmux)) status = PARSE_ERROR; | 742 if (status != PARSE_ERROR && !parser->valid(dmux)) status = PARSE_ERROR; |
| 743 if (status == PARSE_ERROR) dmux->state_ = WEBP_DEMUX_PARSE_ERROR; |
| 727 break; | 744 break; |
| 728 } | 745 } |
| 729 } | 746 } |
| 730 if (state) *state = dmux->state_; | 747 if (state != NULL) *state = dmux->state_; |
| 731 | 748 |
| 732 if (status == PARSE_ERROR) { | 749 if (status == PARSE_ERROR) { |
| 733 WebPDemuxDelete(dmux); | 750 WebPDemuxDelete(dmux); |
| 734 return NULL; | 751 return NULL; |
| 735 } | 752 } |
| 736 return dmux; | 753 return dmux; |
| 737 } | 754 } |
| 738 | 755 |
| 739 void WebPDemuxDelete(WebPDemuxer* dmux) { | 756 void WebPDemuxDelete(WebPDemuxer* dmux) { |
| 740 Chunk* c; | 757 Chunk* c; |
| (...skipping 235 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 976 (const char*)iter->chunk.bytes - CHUNK_HEADER_SIZE; | 993 (const char*)iter->chunk.bytes - CHUNK_HEADER_SIZE; |
| 977 return SetChunk(fourcc, iter->chunk_num - 1, iter); | 994 return SetChunk(fourcc, iter->chunk_num - 1, iter); |
| 978 } | 995 } |
| 979 return 0; | 996 return 0; |
| 980 } | 997 } |
| 981 | 998 |
| 982 void WebPDemuxReleaseChunkIterator(WebPChunkIterator* iter) { | 999 void WebPDemuxReleaseChunkIterator(WebPChunkIterator* iter) { |
| 983 (void)iter; | 1000 (void)iter; |
| 984 } | 1001 } |
| 985 | 1002 |
| 986 #if defined(__cplusplus) || defined(c_plusplus) | |
| 987 } // extern "C" | |
| 988 #endif | |
| OLD | NEW |