1 // Copyright 2017 Google Inc. All Rights Reserved.
2 //
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
5 // tree. An additional intellectual property rights grant can be found
6 // in the file PATENTS. All contributing project authors may
7 // be found in the AUTHORS file in the root of the source tree.
8 // -----------------------------------------------------------------------------
9 //
10 //  Command-line tool to print out the chunk level structure of WebP files
11 //  along with basic integrity checks.
12 //
13 //  Author: Hui Su (huisu@google.com)
14 
15 #include <assert.h>
16 #include <stdio.h>
17 
18 #ifdef HAVE_CONFIG_H
19 #include "webp/config.h"
20 #endif
21 
22 #include "../imageio/imageio_util.h"
23 #include "./unicode.h"
24 #include "webp/decode.h"
25 #include "webp/format_constants.h"
26 #include "webp/mux_types.h"
27 
28 #if defined(_MSC_VER) && _MSC_VER < 1900
29 #define snprintf _snprintf
30 #endif
31 
32 #define LOG_ERROR(MESSAGE)                     \
33   do {                                         \
34     if (webp_info->show_diagnosis_) {          \
35       fprintf(stderr, "Error: %s\n", MESSAGE); \
36     }                                          \
37   } while (0)
38 
39 #define LOG_WARN(MESSAGE)                        \
40   do {                                           \
41     if (webp_info->show_diagnosis_) {            \
42       fprintf(stderr, "Warning: %s\n", MESSAGE); \
43     }                                            \
44   } while (0)
45 
46 static const char* const kFormats[3] = {
47   "Unknown",
48   "Lossy",
49   "Lossless"
50 };
51 
52 static const char* const kLosslessTransforms[4] = {
53   "Predictor",
54   "Cross Color",
55   "Subtract Green",
56   "Color Indexing"
57 };
58 
59 static const char* const kAlphaFilterMethods[4] = {
60   "None",
61   "Horizontal",
62   "Vertical",
63   "Gradient"
64 };
65 
66 typedef enum {
67   WEBP_INFO_OK = 0,
68   WEBP_INFO_TRUNCATED_DATA,
69   WEBP_INFO_PARSE_ERROR,
70   WEBP_INFO_INVALID_PARAM,
71   WEBP_INFO_BITSTREAM_ERROR,
72   WEBP_INFO_MISSING_DATA,
73   WEBP_INFO_INVALID_COMMAND
74 } WebPInfoStatus;
75 
76 typedef enum ChunkID {
77   CHUNK_VP8,
78   CHUNK_VP8L,
79   CHUNK_VP8X,
80   CHUNK_ALPHA,
81   CHUNK_ANIM,
82   CHUNK_ANMF,
83   CHUNK_ICCP,
84   CHUNK_EXIF,
85   CHUNK_XMP,
86   CHUNK_UNKNOWN,
87   CHUNK_TYPES = CHUNK_UNKNOWN
88 } ChunkID;
89 
90 typedef struct {
91   size_t start_;
92   size_t end_;
93   const uint8_t* buf_;
94 } MemBuffer;
95 
96 typedef struct {
97   size_t offset_;
98   size_t size_;
99   const uint8_t* payload_;
100   ChunkID id_;
101 } ChunkData;
102 
103 typedef struct WebPInfo {
104   int canvas_width_;
105   int canvas_height_;
106   int loop_count_;
107   int num_frames_;
108   int chunk_counts_[CHUNK_TYPES];
109   int anmf_subchunk_counts_[3];  // 0 VP8; 1 VP8L; 2 ALPH.
110   uint32_t bgcolor_;
111   int feature_flags_;
112   int has_alpha_;
113   // Used for parsing ANMF chunks.
114   int frame_width_, frame_height_;
115   size_t anim_frame_data_size_;
116   int is_processing_anim_frame_, seen_alpha_subchunk_, seen_image_subchunk_;
117   // Print output control.
118   int quiet_, show_diagnosis_, show_summary_;
119   int parse_bitstream_;
120 } WebPInfo;
121 
WebPInfoInit(WebPInfo * const webp_info)122 static void WebPInfoInit(WebPInfo* const webp_info) {
123   memset(webp_info, 0, sizeof(*webp_info));
124 }
125 
126 static const char kWebPChunkTags[CHUNK_TYPES][4] = {
127   { 'V', 'P', '8', ' ' },
128   { 'V', 'P', '8', 'L' },
129   { 'V', 'P', '8', 'X' },
130   { 'A', 'L', 'P', 'H' },
131   { 'A', 'N', 'I', 'M' },
132   { 'A', 'N', 'M', 'F' },
133   { 'I', 'C', 'C', 'P' },
134   { 'E', 'X', 'I', 'F' },
135   { 'X', 'M', 'P', ' ' },
136 };
137 
138 // -----------------------------------------------------------------------------
139 // Data reading.
140 
GetLE16(const uint8_t * const data)141 static int GetLE16(const uint8_t* const data) {
142   return (data[0] << 0) | (data[1] << 8);
143 }
144 
GetLE24(const uint8_t * const data)145 static int GetLE24(const uint8_t* const data) {
146   return GetLE16(data) | (data[2] << 16);
147 }
148 
GetLE32(const uint8_t * const data)149 static uint32_t GetLE32(const uint8_t* const data) {
150   return GetLE16(data) | ((uint32_t)GetLE16(data + 2) << 16);
151 }
152 
ReadLE16(const uint8_t ** data)153 static int ReadLE16(const uint8_t** data) {
154   const int val = GetLE16(*data);
155   *data += 2;
156   return val;
157 }
158 
ReadLE24(const uint8_t ** data)159 static int ReadLE24(const uint8_t** data) {
160   const int val = GetLE24(*data);
161   *data += 3;
162   return val;
163 }
164 
ReadLE32(const uint8_t ** data)165 static uint32_t ReadLE32(const uint8_t** data) {
166   const uint32_t val = GetLE32(*data);
167   *data += 4;
168   return val;
169 }
170 
ReadFileToWebPData(const char * const filename,WebPData * const webp_data)171 static int ReadFileToWebPData(const char* const filename,
172                               WebPData* const webp_data) {
173   const uint8_t* data;
174   size_t size;
175   if (!ImgIoUtilReadFile(filename, &data, &size)) return 0;
176   webp_data->bytes = data;
177   webp_data->size = size;
178   return 1;
179 }
180 
181 // -----------------------------------------------------------------------------
182 // MemBuffer object.
183 
InitMemBuffer(MemBuffer * const mem,const WebPData * webp_data)184 static void InitMemBuffer(MemBuffer* const mem, const WebPData* webp_data) {
185   mem->buf_ = webp_data->bytes;
186   mem->start_ = 0;
187   mem->end_ = webp_data->size;
188 }
189 
MemDataSize(const MemBuffer * const mem)190 static size_t MemDataSize(const MemBuffer* const mem) {
191   return (mem->end_ - mem->start_);
192 }
193 
GetBuffer(MemBuffer * const mem)194 static const uint8_t* GetBuffer(MemBuffer* const mem) {
195   return mem->buf_ + mem->start_;
196 }
197 
Skip(MemBuffer * const mem,size_t size)198 static void Skip(MemBuffer* const mem, size_t size) {
199   mem->start_ += size;
200 }
201 
ReadMemBufLE32(MemBuffer * const mem)202 static uint32_t ReadMemBufLE32(MemBuffer* const mem) {
203   const uint8_t* const data = mem->buf_ + mem->start_;
204   const uint32_t val = GetLE32(data);
205   assert(MemDataSize(mem) >= 4);
206   Skip(mem, 4);
207   return val;
208 }
209 
210 // -----------------------------------------------------------------------------
211 // Lossy bitstream analysis.
212 
GetBits(const uint8_t * const data,size_t data_size,size_t nb,int * val,uint64_t * const bit_pos)213 static int GetBits(const uint8_t* const data, size_t data_size, size_t nb,
214                    int* val, uint64_t* const bit_pos) {
215   *val = 0;
216   while (nb-- > 0) {
217     const uint64_t p = (*bit_pos)++;
218     if ((p >> 3) >= data_size) {
219       return 0;
220     } else {
221       const int bit = !!(data[p >> 3] & (128 >> ((p & 7))));
222       *val = (*val << 1) | bit;
223     }
224   }
225   return 1;
226 }
227 
GetSignedBits(const uint8_t * const data,size_t data_size,size_t nb,int * val,uint64_t * const bit_pos)228 static int GetSignedBits(const uint8_t* const data, size_t data_size, size_t nb,
229                          int* val, uint64_t* const bit_pos) {
230   int sign;
231   if (!GetBits(data, data_size, nb, val, bit_pos)) return 0;
232   if (!GetBits(data, data_size, 1, &sign, bit_pos)) return 0;
233   if (sign) *val = -(*val);
234   return 1;
235 }
236 
237 #define GET_BITS(v, n)                                 \
238   do {                                                 \
239     if (!GetBits(data, data_size, n, &(v), bit_pos)) { \
240       LOG_ERROR("Truncated lossy bitstream.");         \
241       return WEBP_INFO_TRUNCATED_DATA;                 \
242     }                                                  \
243   } while (0)
244 
245 #define GET_SIGNED_BITS(v, n)                                \
246   do {                                                       \
247     if (!GetSignedBits(data, data_size, n, &(v), bit_pos)) { \
248       LOG_ERROR("Truncated lossy bitstream.");               \
249       return WEBP_INFO_TRUNCATED_DATA;                       \
250     }                                                        \
251   } while (0)
252 
ParseLossySegmentHeader(const WebPInfo * const webp_info,const uint8_t * const data,size_t data_size,uint64_t * const bit_pos)253 static WebPInfoStatus ParseLossySegmentHeader(const WebPInfo* const webp_info,
254                                               const uint8_t* const data,
255                                               size_t data_size,
256                                               uint64_t* const bit_pos) {
257   int use_segment;
258   GET_BITS(use_segment, 1);
259   printf("  Use segment:      %d\n", use_segment);
260   if (use_segment) {
261     int update_map, update_data;
262     GET_BITS(update_map, 1);
263     GET_BITS(update_data, 1);
264     printf("  Update map:       %d\n"
265            "  Update data:      %d\n",
266            update_map, update_data);
267     if (update_data) {
268       int i, a_delta;
269       int quantizer[4] = {0, 0, 0, 0};
270       int filter_strength[4] = {0, 0, 0, 0};
271       GET_BITS(a_delta, 1);
272       printf("  Absolute delta:   %d\n", a_delta);
273       for (i = 0; i < 4; ++i) {
274         int bit;
275         GET_BITS(bit, 1);
276         if (bit) GET_SIGNED_BITS(quantizer[i], 7);
277       }
278       for (i = 0; i < 4; ++i) {
279         int bit;
280         GET_BITS(bit, 1);
281         if (bit) GET_SIGNED_BITS(filter_strength[i], 6);
282       }
283       printf("  Quantizer:        %d %d %d %d\n", quantizer[0], quantizer[1],
284              quantizer[2], quantizer[3]);
285       printf("  Filter strength:  %d %d %d %d\n", filter_strength[0],
286              filter_strength[1], filter_strength[2], filter_strength[3]);
287     }
288     if (update_map) {
289       int i;
290       int prob_segment[3] = {255, 255, 255};
291       for (i = 0; i < 3; ++i) {
292         int bit;
293         GET_BITS(bit, 1);
294         if (bit) GET_BITS(prob_segment[i], 8);
295       }
296       printf("  Prob segment:     %d %d %d\n",
297              prob_segment[0], prob_segment[1], prob_segment[2]);
298     }
299   }
300   return WEBP_INFO_OK;
301 }
302 
ParseLossyFilterHeader(const WebPInfo * const webp_info,const uint8_t * const data,size_t data_size,uint64_t * const bit_pos)303 static WebPInfoStatus ParseLossyFilterHeader(const WebPInfo* const webp_info,
304                                              const uint8_t* const data,
305                                              size_t data_size,
306                                              uint64_t* const bit_pos) {
307   int simple_filter, level, sharpness, use_lf_delta;
308   GET_BITS(simple_filter, 1);
309   GET_BITS(level, 6);
310   GET_BITS(sharpness, 3);
311   GET_BITS(use_lf_delta, 1);
312   printf("  Simple filter:    %d\n", simple_filter);
313   printf("  Level:            %d\n", level);
314   printf("  Sharpness:        %d\n", sharpness);
315   printf("  Use lf delta:     %d\n", use_lf_delta);
316   if (use_lf_delta) {
317     int update;
318     GET_BITS(update, 1);
319     printf("  Update lf delta:  %d\n", update);
320     if (update) {
321       int i;
322       for (i = 0; i < 4 + 4; ++i) {
323         int temp;
324         GET_BITS(temp, 1);
325         if (temp) GET_BITS(temp, 7);
326       }
327     }
328   }
329   return WEBP_INFO_OK;
330 }
331 
ParseLossyHeader(const ChunkData * const chunk_data,const WebPInfo * const webp_info)332 static WebPInfoStatus ParseLossyHeader(const ChunkData* const chunk_data,
333                                        const WebPInfo* const webp_info) {
334   const uint8_t* data = chunk_data->payload_;
335   size_t data_size = chunk_data->size_ - CHUNK_HEADER_SIZE;
336   const uint32_t bits = (uint32_t)data[0] | (data[1] << 8) | (data[2] << 16);
337   const int key_frame = !(bits & 1);
338   const int profile = (bits >> 1) & 7;
339   const int display = (bits >> 4) & 1;
340   const uint32_t partition0_length = (bits >> 5);
341   WebPInfoStatus status = WEBP_INFO_OK;
342   uint64_t bit_position = 0;
343   uint64_t* const bit_pos = &bit_position;
344   int colorspace, clamp_type;
345   printf("  Parsing lossy bitstream...\n");
346   // Calling WebPGetFeatures() in ProcessImageChunk() should ensure this.
347   assert(chunk_data->size_ >= CHUNK_HEADER_SIZE + 10);
348   if (profile > 3) {
349     LOG_ERROR("Unknown profile.");
350     return WEBP_INFO_BITSTREAM_ERROR;
351   }
352   if (!display) {
353     LOG_ERROR("Frame is not displayable.");
354     return WEBP_INFO_BITSTREAM_ERROR;
355   }
356   data += 3;
357   data_size -= 3;
358   printf("  Key frame:        %s\n"
359          "  Profile:          %d\n"
360          "  Display:          %s\n"
361          "  Part. 0 length:   %d\n",
362          key_frame ? "Yes" : "No", profile,
363          display ? "Yes" : "No", partition0_length);
364   if (key_frame) {
365     if (!(data[0] == 0x9d && data[1] == 0x01 && data[2] == 0x2a)) {
366       LOG_ERROR("Invalid lossy bitstream signature.");
367       return WEBP_INFO_BITSTREAM_ERROR;
368     }
369     printf("  Width:            %d\n"
370            "  X scale:          %d\n"
371            "  Height:           %d\n"
372            "  Y scale:          %d\n",
373            ((data[4] << 8) | data[3]) & 0x3fff, data[4] >> 6,
374            ((data[6] << 8) | data[5]) & 0x3fff, data[6] >> 6);
375     data += 7;
376     data_size -= 7;
377   } else {
378     LOG_ERROR("Non-keyframe detected in lossy bitstream.");
379     return WEBP_INFO_BITSTREAM_ERROR;
380   }
381   if (partition0_length >= data_size) {
382     LOG_ERROR("Bad partition length.");
383     return WEBP_INFO_BITSTREAM_ERROR;
384   }
385   GET_BITS(colorspace, 1);
386   GET_BITS(clamp_type, 1);
387   printf("  Color space:      %d\n", colorspace);
388   printf("  Clamp type:       %d\n", clamp_type);
389   status = ParseLossySegmentHeader(webp_info, data, data_size, bit_pos);
390   if (status != WEBP_INFO_OK) return status;
391   status = ParseLossyFilterHeader(webp_info, data, data_size, bit_pos);
392   if (status != WEBP_INFO_OK) return status;
393   {  // Partition number and size.
394     const uint8_t* part_size = data + partition0_length;
395     int num_parts, i;
396     size_t part_data_size;
397     GET_BITS(num_parts, 2);
398     num_parts = 1 << num_parts;
399     if ((int)(data_size - partition0_length) < (num_parts - 1) * 3) {
400       LOG_ERROR("Truncated lossy bitstream.");
401       return WEBP_INFO_TRUNCATED_DATA;
402     }
403     part_data_size = data_size - partition0_length - (num_parts - 1) * 3;
404     printf("  Total partitions: %d\n", num_parts);
405     for (i = 1; i < num_parts; ++i) {
406       const size_t psize =
407           part_size[0] | (part_size[1] << 8) | (part_size[2] << 16);
408       if (psize > part_data_size) {
409         LOG_ERROR("Truncated partition.");
410         return WEBP_INFO_TRUNCATED_DATA;
411       }
412       printf("  Part. %d length:   %d\n", i, (int)psize);
413       part_data_size -= psize;
414       part_size += 3;
415     }
416   }
417   // Quantizer.
418   {
419     int base_q, bit;
420     int dq_y1_dc = 0, dq_y2_dc = 0, dq_y2_ac = 0, dq_uv_dc = 0, dq_uv_ac = 0;
421     GET_BITS(base_q, 7);
422     GET_BITS(bit, 1);
423     if (bit) GET_SIGNED_BITS(dq_y1_dc, 4);
424     GET_BITS(bit, 1);
425     if (bit) GET_SIGNED_BITS(dq_y2_dc, 4);
426     GET_BITS(bit, 1);
427     if (bit) GET_SIGNED_BITS(dq_y2_ac, 4);
428     GET_BITS(bit, 1);
429     if (bit) GET_SIGNED_BITS(dq_uv_dc, 4);
430     GET_BITS(bit, 1);
431     if (bit) GET_SIGNED_BITS(dq_uv_ac, 4);
432     printf("  Base Q:           %d\n", base_q);
433     printf("  DQ Y1 DC:         %d\n", dq_y1_dc);
434     printf("  DQ Y2 DC:         %d\n", dq_y2_dc);
435     printf("  DQ Y2 AC:         %d\n", dq_y2_ac);
436     printf("  DQ UV DC:         %d\n", dq_uv_dc);
437     printf("  DQ UV AC:         %d\n", dq_uv_ac);
438   }
439   if ((*bit_pos >> 3) >= partition0_length) {
440     LOG_ERROR("Truncated lossy bitstream.");
441     return WEBP_INFO_TRUNCATED_DATA;
442   }
443   return WEBP_INFO_OK;
444 }
445 
446 // -----------------------------------------------------------------------------
447 // Lossless bitstream analysis.
448 
LLGetBits(const uint8_t * const data,size_t data_size,size_t nb,int * val,uint64_t * const bit_pos)449 static int LLGetBits(const uint8_t* const data, size_t data_size, size_t nb,
450                      int* val, uint64_t* const bit_pos) {
451   uint32_t i = 0;
452   *val = 0;
453   while (i < nb) {
454     const uint64_t p = (*bit_pos)++;
455     if ((p >> 3) >= data_size) {
456       return 0;
457     } else {
458       const int bit = !!(data[p >> 3] & (1 << ((p & 7))));
459       *val = *val | (bit << i);
460       ++i;
461     }
462   }
463   return 1;
464 }
465 
466 #define LL_GET_BITS(v, n)                                \
467   do {                                                   \
468     if (!LLGetBits(data, data_size, n, &(v), bit_pos)) { \
469       LOG_ERROR("Truncated lossless bitstream.");        \
470       return WEBP_INFO_TRUNCATED_DATA;                   \
471     }                                                    \
472   } while (0)
473 
ParseLosslessTransform(WebPInfo * const webp_info,const uint8_t * const data,size_t data_size,uint64_t * const bit_pos)474 static WebPInfoStatus ParseLosslessTransform(WebPInfo* const webp_info,
475                                              const uint8_t* const data,
476                                              size_t data_size,
477                                              uint64_t* const  bit_pos) {
478   int use_transform, block_size, n_colors;
479   LL_GET_BITS(use_transform, 1);
480   printf("  Use transform:    %s\n", use_transform ? "Yes" : "No");
481   if (use_transform) {
482     int type;
483     LL_GET_BITS(type, 2);
484     printf("  1st transform:    %s (%d)\n", kLosslessTransforms[type], type);
485     switch (type) {
486       case PREDICTOR_TRANSFORM:
487       case CROSS_COLOR_TRANSFORM:
488         LL_GET_BITS(block_size, 3);
489         block_size = 1 << (block_size + 2);
490         printf("  Tran. block size: %d\n", block_size);
491         break;
492       case COLOR_INDEXING_TRANSFORM:
493         LL_GET_BITS(n_colors, 8);
494         n_colors += 1;
495         printf("  No. of colors:    %d\n", n_colors);
496         break;
497       default: break;
498     }
499   }
500   return WEBP_INFO_OK;
501 }
502 
ParseLosslessHeader(const ChunkData * const chunk_data,WebPInfo * const webp_info)503 static WebPInfoStatus ParseLosslessHeader(const ChunkData* const chunk_data,
504                                           WebPInfo* const webp_info) {
505   const uint8_t* data = chunk_data->payload_;
506   size_t data_size = chunk_data->size_ - CHUNK_HEADER_SIZE;
507   uint64_t bit_position = 0;
508   uint64_t* const bit_pos = &bit_position;
509   WebPInfoStatus status;
510   printf("  Parsing lossless bitstream...\n");
511   if (data_size < VP8L_FRAME_HEADER_SIZE) {
512     LOG_ERROR("Truncated lossless bitstream.");
513     return WEBP_INFO_TRUNCATED_DATA;
514   }
515   if (data[0] != VP8L_MAGIC_BYTE) {
516     LOG_ERROR("Invalid lossless bitstream signature.");
517     return WEBP_INFO_BITSTREAM_ERROR;
518   }
519   data += 1;
520   data_size -= 1;
521   {
522     int width, height, has_alpha, version;
523     LL_GET_BITS(width, 14);
524     LL_GET_BITS(height, 14);
525     LL_GET_BITS(has_alpha, 1);
526     LL_GET_BITS(version, 3);
527     width += 1;
528     height += 1;
529     printf("  Width:            %d\n", width);
530     printf("  Height:           %d\n", height);
531     printf("  Alpha:            %d\n", has_alpha);
532     printf("  Version:          %d\n", version);
533   }
534   status = ParseLosslessTransform(webp_info, data, data_size, bit_pos);
535   if (status != WEBP_INFO_OK) return status;
536   return WEBP_INFO_OK;
537 }
538 
ParseAlphaHeader(const ChunkData * const chunk_data,WebPInfo * const webp_info)539 static WebPInfoStatus ParseAlphaHeader(const ChunkData* const chunk_data,
540                                        WebPInfo* const webp_info) {
541   const uint8_t* data = chunk_data->payload_;
542   size_t data_size = chunk_data->size_ - CHUNK_HEADER_SIZE;
543   if (data_size <= ALPHA_HEADER_LEN) {
544     LOG_ERROR("Truncated ALPH chunk.");
545     return WEBP_INFO_TRUNCATED_DATA;
546   }
547   printf("  Parsing ALPH chunk...\n");
548   {
549     const int compression_method = (data[0] >> 0) & 0x03;
550     const int filter = (data[0] >> 2) & 0x03;
551     const int pre_processing = (data[0] >> 4) & 0x03;
552     const int reserved_bits = (data[0] >> 6) & 0x03;
553     printf("  Compression:      %d\n", compression_method);
554     printf("  Filter:           %s (%d)\n",
555            kAlphaFilterMethods[filter], filter);
556     printf("  Pre-processing:   %d\n", pre_processing);
557     if (compression_method > ALPHA_LOSSLESS_COMPRESSION) {
558       LOG_ERROR("Invalid Alpha compression method.");
559       return WEBP_INFO_BITSTREAM_ERROR;
560     }
561     if (pre_processing > ALPHA_PREPROCESSED_LEVELS) {
562       LOG_ERROR("Invalid Alpha pre-processing method.");
563       return WEBP_INFO_BITSTREAM_ERROR;
564     }
565     if (reserved_bits != 0) {
566       LOG_WARN("Reserved bits in ALPH chunk header are not all 0.");
567     }
568     data += ALPHA_HEADER_LEN;
569     data_size -= ALPHA_HEADER_LEN;
570     if (compression_method == ALPHA_LOSSLESS_COMPRESSION) {
571       uint64_t bit_pos = 0;
572       WebPInfoStatus status =
573           ParseLosslessTransform(webp_info, data, data_size, &bit_pos);
574       if (status != WEBP_INFO_OK) return status;
575     }
576   }
577   return WEBP_INFO_OK;
578 }
579 
580 // -----------------------------------------------------------------------------
581 // Chunk parsing.
582 
ParseRIFFHeader(const WebPInfo * const webp_info,MemBuffer * const mem)583 static WebPInfoStatus ParseRIFFHeader(const WebPInfo* const webp_info,
584                                       MemBuffer* const mem) {
585   const size_t min_size = RIFF_HEADER_SIZE + CHUNK_HEADER_SIZE;
586   size_t riff_size;
587 
588   if (MemDataSize(mem) < min_size) {
589     LOG_ERROR("Truncated data detected when parsing RIFF header.");
590     return WEBP_INFO_TRUNCATED_DATA;
591   }
592   if (memcmp(GetBuffer(mem), "RIFF", CHUNK_SIZE_BYTES) ||
593       memcmp(GetBuffer(mem) + CHUNK_HEADER_SIZE, "WEBP", CHUNK_SIZE_BYTES)) {
594     LOG_ERROR("Corrupted RIFF header.");
595     return WEBP_INFO_PARSE_ERROR;
596   }
597   riff_size = GetLE32(GetBuffer(mem) + TAG_SIZE);
598   if (riff_size < CHUNK_HEADER_SIZE) {
599     LOG_ERROR("RIFF size is too small.");
600     return WEBP_INFO_PARSE_ERROR;
601   }
602   if (riff_size > MAX_CHUNK_PAYLOAD) {
603     LOG_ERROR("RIFF size is over limit.");
604     return WEBP_INFO_PARSE_ERROR;
605   }
606   riff_size += CHUNK_HEADER_SIZE;
607   if (!webp_info->quiet_) {
608     printf("RIFF HEADER:\n");
609     printf("  File size: %6d\n", (int)riff_size);
610   }
611   if (riff_size < mem->end_) {
612     LOG_WARN("RIFF size is smaller than the file size.");
613     mem->end_ = riff_size;
614   } else if (riff_size > mem->end_) {
615     LOG_ERROR("Truncated data detected when parsing RIFF payload.");
616     return WEBP_INFO_TRUNCATED_DATA;
617   }
618   Skip(mem, RIFF_HEADER_SIZE);
619   return WEBP_INFO_OK;
620 }
621 
ParseChunk(const WebPInfo * const webp_info,MemBuffer * const mem,ChunkData * const chunk_data)622 static WebPInfoStatus ParseChunk(const WebPInfo* const webp_info,
623                                  MemBuffer* const mem,
624                                  ChunkData* const chunk_data) {
625   memset(chunk_data, 0, sizeof(*chunk_data));
626   if (MemDataSize(mem) < CHUNK_HEADER_SIZE) {
627     LOG_ERROR("Truncated data detected when parsing chunk header.");
628     return WEBP_INFO_TRUNCATED_DATA;
629   } else {
630     const size_t chunk_start_offset = mem->start_;
631     const uint32_t fourcc = ReadMemBufLE32(mem);
632     const uint32_t payload_size = ReadMemBufLE32(mem);
633     const uint32_t payload_size_padded = payload_size + (payload_size & 1);
634     const size_t chunk_size = CHUNK_HEADER_SIZE + payload_size_padded;
635     int i;
636     if (payload_size > MAX_CHUNK_PAYLOAD) {
637       LOG_ERROR("Size of chunk payload is over limit.");
638       return WEBP_INFO_INVALID_PARAM;
639     }
640     if (payload_size_padded > MemDataSize(mem)){
641       LOG_ERROR("Truncated data detected when parsing chunk payload.");
642       return WEBP_INFO_TRUNCATED_DATA;
643     }
644     for (i = 0; i < CHUNK_TYPES; ++i) {
645       if (!memcmp(kWebPChunkTags[i], &fourcc, TAG_SIZE)) break;
646     }
647     chunk_data->offset_ = chunk_start_offset;
648     chunk_data->size_ = chunk_size;
649     chunk_data->id_ = (ChunkID)i;
650     chunk_data->payload_ = GetBuffer(mem);
651     if (chunk_data->id_ == CHUNK_ANMF) {
652       if (payload_size != payload_size_padded) {
653         LOG_ERROR("ANMF chunk size should always be even.");
654         return WEBP_INFO_PARSE_ERROR;
655       }
656       // There are sub-chunks to be parsed in an ANMF chunk.
657       Skip(mem, ANMF_CHUNK_SIZE);
658     } else {
659       Skip(mem, payload_size_padded);
660     }
661     return WEBP_INFO_OK;
662   }
663 }
664 
665 // -----------------------------------------------------------------------------
666 // Chunk analysis.
667 
ProcessVP8XChunk(const ChunkData * const chunk_data,WebPInfo * const webp_info)668 static WebPInfoStatus ProcessVP8XChunk(const ChunkData* const chunk_data,
669                                        WebPInfo* const webp_info) {
670   const uint8_t* data = chunk_data->payload_;
671   if (webp_info->chunk_counts_[CHUNK_VP8] ||
672       webp_info->chunk_counts_[CHUNK_VP8L] ||
673       webp_info->chunk_counts_[CHUNK_VP8X]) {
674     LOG_ERROR("Already seen a VP8/VP8L/VP8X chunk when parsing VP8X chunk.");
675     return WEBP_INFO_PARSE_ERROR;
676   }
677   if (chunk_data->size_ != VP8X_CHUNK_SIZE + CHUNK_HEADER_SIZE) {
678     LOG_ERROR("Corrupted VP8X chunk.");
679     return WEBP_INFO_PARSE_ERROR;
680   }
681   ++webp_info->chunk_counts_[CHUNK_VP8X];
682   webp_info->feature_flags_ = *data;
683   data += 4;
684   webp_info->canvas_width_ = 1 + ReadLE24(&data);
685   webp_info->canvas_height_ = 1 + ReadLE24(&data);
686   if (!webp_info->quiet_) {
687     printf("  ICCP: %d\n  Alpha: %d\n  EXIF: %d\n  XMP: %d\n  Animation: %d\n",
688            (webp_info->feature_flags_ & ICCP_FLAG) != 0,
689            (webp_info->feature_flags_ & ALPHA_FLAG) != 0,
690            (webp_info->feature_flags_ & EXIF_FLAG) != 0,
691            (webp_info->feature_flags_ & XMP_FLAG) != 0,
692            (webp_info->feature_flags_ & ANIMATION_FLAG) != 0);
693     printf("  Canvas size %d x %d\n",
694            webp_info->canvas_width_, webp_info->canvas_height_);
695   }
696   if (webp_info->canvas_width_ > MAX_CANVAS_SIZE) {
697     LOG_WARN("Canvas width is out of range in VP8X chunk.");
698   }
699   if (webp_info->canvas_height_ > MAX_CANVAS_SIZE) {
700     LOG_WARN("Canvas height is out of range in VP8X chunk.");
701   }
702   if ((uint64_t)webp_info->canvas_width_ * webp_info->canvas_height_ >
703       MAX_IMAGE_AREA) {
704     LOG_WARN("Canvas area is out of range in VP8X chunk.");
705   }
706   return WEBP_INFO_OK;
707 }
708 
ProcessANIMChunk(const ChunkData * const chunk_data,WebPInfo * const webp_info)709 static WebPInfoStatus ProcessANIMChunk(const ChunkData* const chunk_data,
710                                        WebPInfo* const webp_info) {
711   const uint8_t* data = chunk_data->payload_;
712   if (!webp_info->chunk_counts_[CHUNK_VP8X]) {
713     LOG_ERROR("ANIM chunk detected before VP8X chunk.");
714     return WEBP_INFO_PARSE_ERROR;
715   }
716   if (chunk_data->size_ != ANIM_CHUNK_SIZE + CHUNK_HEADER_SIZE) {
717     LOG_ERROR("Corrupted ANIM chunk.");
718     return WEBP_INFO_PARSE_ERROR;
719   }
720   webp_info->bgcolor_ = ReadLE32(&data);
721   webp_info->loop_count_ = ReadLE16(&data);
722   ++webp_info->chunk_counts_[CHUNK_ANIM];
723   if (!webp_info->quiet_) {
724     printf("  Background color:(ARGB) %02x %02x %02x %02x\n",
725            (webp_info->bgcolor_ >> 24) & 0xff,
726            (webp_info->bgcolor_ >> 16) & 0xff,
727            (webp_info->bgcolor_ >> 8) & 0xff,
728            webp_info->bgcolor_ & 0xff);
729     printf("  Loop count      : %d\n", webp_info->loop_count_);
730   }
731   if (webp_info->loop_count_ > MAX_LOOP_COUNT) {
732     LOG_WARN("Loop count is out of range in ANIM chunk.");
733   }
734   return WEBP_INFO_OK;
735 }
736 
ProcessANMFChunk(const ChunkData * const chunk_data,WebPInfo * const webp_info)737 static WebPInfoStatus ProcessANMFChunk(const ChunkData* const chunk_data,
738                                        WebPInfo* const webp_info) {
739   const uint8_t* data = chunk_data->payload_;
740   int offset_x, offset_y, width, height, duration, blend, dispose, temp;
741   if (webp_info->is_processing_anim_frame_) {
742     LOG_ERROR("ANMF chunk detected within another ANMF chunk.");
743     return WEBP_INFO_PARSE_ERROR;
744   }
745   if (!webp_info->chunk_counts_[CHUNK_ANIM]) {
746     LOG_ERROR("ANMF chunk detected before ANIM chunk.");
747     return WEBP_INFO_PARSE_ERROR;
748   }
749   if (chunk_data->size_ <= CHUNK_HEADER_SIZE + ANMF_CHUNK_SIZE) {
750     LOG_ERROR("Truncated data detected when parsing ANMF chunk.");
751     return WEBP_INFO_TRUNCATED_DATA;
752   }
753   offset_x = 2 * ReadLE24(&data);
754   offset_y = 2 * ReadLE24(&data);
755   width = 1 + ReadLE24(&data);
756   height = 1 + ReadLE24(&data);
757   duration = ReadLE24(&data);
758   temp = *data;
759   dispose = temp & 1;
760   blend = (temp >> 1) & 1;
761   ++webp_info->chunk_counts_[CHUNK_ANMF];
762   if (!webp_info->quiet_) {
763     printf("  Offset_X: %d\n  Offset_Y: %d\n  Width: %d\n  Height: %d\n"
764            "  Duration: %d\n  Dispose: %d\n  Blend: %d\n",
765            offset_x, offset_y, width, height, duration, dispose, blend);
766   }
767   if (duration > MAX_DURATION) {
768     LOG_ERROR("Invalid duration parameter in ANMF chunk.");
769     return WEBP_INFO_INVALID_PARAM;
770   }
771   if (offset_x > MAX_POSITION_OFFSET || offset_y > MAX_POSITION_OFFSET) {
772     LOG_ERROR("Invalid offset parameters in ANMF chunk.");
773     return WEBP_INFO_INVALID_PARAM;
774   }
775   if ((uint64_t)offset_x + width > (uint64_t)webp_info->canvas_width_ ||
776       (uint64_t)offset_y + height > (uint64_t)webp_info->canvas_height_) {
777     LOG_ERROR("Frame exceeds canvas in ANMF chunk.");
778     return WEBP_INFO_INVALID_PARAM;
779   }
780   webp_info->is_processing_anim_frame_ = 1;
781   webp_info->seen_alpha_subchunk_ = 0;
782   webp_info->seen_image_subchunk_ = 0;
783   webp_info->frame_width_ = width;
784   webp_info->frame_height_ = height;
785   webp_info->anim_frame_data_size_ =
786       chunk_data->size_ - CHUNK_HEADER_SIZE - ANMF_CHUNK_SIZE;
787   return WEBP_INFO_OK;
788 }
789 
ProcessImageChunk(const ChunkData * const chunk_data,WebPInfo * const webp_info)790 static WebPInfoStatus ProcessImageChunk(const ChunkData* const chunk_data,
791                                         WebPInfo* const webp_info) {
792   const uint8_t* data = chunk_data->payload_ - CHUNK_HEADER_SIZE;
793   WebPBitstreamFeatures features;
794   const VP8StatusCode vp8_status =
795       WebPGetFeatures(data, chunk_data->size_, &features);
796   if (vp8_status != VP8_STATUS_OK) {
797     LOG_ERROR("VP8/VP8L bitstream error.");
798     return WEBP_INFO_BITSTREAM_ERROR;
799   }
800   if (!webp_info->quiet_) {
801     assert(features.format >= 0 && features.format <= 2);
802     printf("  Width: %d\n  Height: %d\n  Alpha: %d\n  Animation: %d\n"
803            "  Format: %s (%d)\n",
804            features.width, features.height, features.has_alpha,
805            features.has_animation, kFormats[features.format], features.format);
806   }
807   if (webp_info->is_processing_anim_frame_) {
808     ++webp_info->anmf_subchunk_counts_[chunk_data->id_ == CHUNK_VP8 ? 0 : 1];
809     if (chunk_data->id_ == CHUNK_VP8L && webp_info->seen_alpha_subchunk_) {
810       LOG_ERROR("Both VP8L and ALPH sub-chunks are present in an ANMF chunk.");
811       return WEBP_INFO_PARSE_ERROR;
812     }
813     if (webp_info->frame_width_ != features.width ||
814         webp_info->frame_height_ != features.height) {
815       LOG_ERROR("Frame size in VP8/VP8L sub-chunk differs from ANMF header.");
816       return WEBP_INFO_PARSE_ERROR;
817     }
818     if (webp_info->seen_image_subchunk_) {
819       LOG_ERROR("Consecutive VP8/VP8L sub-chunks in an ANMF chunk.");
820       return WEBP_INFO_PARSE_ERROR;
821     }
822     webp_info->seen_image_subchunk_ = 1;
823   } else {
824     if (webp_info->chunk_counts_[CHUNK_VP8] ||
825         webp_info->chunk_counts_[CHUNK_VP8L]) {
826       LOG_ERROR("Multiple VP8/VP8L chunks detected.");
827       return WEBP_INFO_PARSE_ERROR;
828     }
829     if (chunk_data->id_ == CHUNK_VP8L &&
830         webp_info->chunk_counts_[CHUNK_ALPHA]) {
831       LOG_WARN("Both VP8L and ALPH chunks are detected.");
832     }
833     if (webp_info->chunk_counts_[CHUNK_ANIM] ||
834         webp_info->chunk_counts_[CHUNK_ANMF]) {
835       LOG_ERROR("VP8/VP8L chunk and ANIM/ANMF chunk are both detected.");
836       return WEBP_INFO_PARSE_ERROR;
837     }
838     if (webp_info->chunk_counts_[CHUNK_VP8X]) {
839       if (webp_info->canvas_width_ != features.width ||
840           webp_info->canvas_height_ != features.height) {
841         LOG_ERROR("Image size in VP8/VP8L chunk differs from VP8X chunk.");
842         return WEBP_INFO_PARSE_ERROR;
843       }
844     } else {
845       webp_info->canvas_width_ = features.width;
846       webp_info->canvas_height_ = features.height;
847       if (webp_info->canvas_width_ < 1 || webp_info->canvas_height_ < 1 ||
848           webp_info->canvas_width_ > MAX_CANVAS_SIZE ||
849           webp_info->canvas_height_ > MAX_CANVAS_SIZE ||
850           (uint64_t)webp_info->canvas_width_ * webp_info->canvas_height_ >
851               MAX_IMAGE_AREA) {
852         LOG_WARN("Invalid parameters in VP8/VP8L chunk.");
853       }
854     }
855     ++webp_info->chunk_counts_[chunk_data->id_];
856   }
857   ++webp_info->num_frames_;
858   webp_info->has_alpha_ |= features.has_alpha;
859   if (webp_info->parse_bitstream_) {
860     const int is_lossy = (chunk_data->id_ == CHUNK_VP8);
861     const WebPInfoStatus status =
862         is_lossy ? ParseLossyHeader(chunk_data, webp_info)
863                  : ParseLosslessHeader(chunk_data, webp_info);
864     if (status != WEBP_INFO_OK) return status;
865   }
866   return WEBP_INFO_OK;
867 }
868 
ProcessALPHChunk(const ChunkData * const chunk_data,WebPInfo * const webp_info)869 static WebPInfoStatus ProcessALPHChunk(const ChunkData* const chunk_data,
870                                        WebPInfo* const webp_info) {
871   if (webp_info->is_processing_anim_frame_) {
872     ++webp_info->anmf_subchunk_counts_[2];
873     if (webp_info->seen_alpha_subchunk_) {
874       LOG_ERROR("Consecutive ALPH sub-chunks in an ANMF chunk.");
875       return WEBP_INFO_PARSE_ERROR;
876     }
877     webp_info->seen_alpha_subchunk_ = 1;
878 
879     if (webp_info->seen_image_subchunk_) {
880       LOG_ERROR("ALPHA sub-chunk detected after VP8 sub-chunk "
881                 "in an ANMF chunk.");
882       return WEBP_INFO_PARSE_ERROR;
883     }
884   } else {
885     if (webp_info->chunk_counts_[CHUNK_ANIM] ||
886         webp_info->chunk_counts_[CHUNK_ANMF]) {
887       LOG_ERROR("ALPHA chunk and ANIM/ANMF chunk are both detected.");
888       return WEBP_INFO_PARSE_ERROR;
889     }
890     if (!webp_info->chunk_counts_[CHUNK_VP8X]) {
891       LOG_ERROR("ALPHA chunk detected before VP8X chunk.");
892       return WEBP_INFO_PARSE_ERROR;
893     }
894     if (webp_info->chunk_counts_[CHUNK_VP8]) {
895       LOG_ERROR("ALPHA chunk detected after VP8 chunk.");
896       return WEBP_INFO_PARSE_ERROR;
897     }
898     if (webp_info->chunk_counts_[CHUNK_ALPHA]) {
899       LOG_ERROR("Multiple ALPHA chunks detected.");
900       return WEBP_INFO_PARSE_ERROR;
901     }
902     ++webp_info->chunk_counts_[CHUNK_ALPHA];
903   }
904   webp_info->has_alpha_ = 1;
905   if (webp_info->parse_bitstream_) {
906     const WebPInfoStatus status = ParseAlphaHeader(chunk_data, webp_info);
907     if (status != WEBP_INFO_OK) return status;
908   }
909   return WEBP_INFO_OK;
910 }
911 
ProcessICCPChunk(const ChunkData * const chunk_data,WebPInfo * const webp_info)912 static WebPInfoStatus ProcessICCPChunk(const ChunkData* const chunk_data,
913                                        WebPInfo* const webp_info) {
914   (void)chunk_data;
915   if (!webp_info->chunk_counts_[CHUNK_VP8X]) {
916     LOG_ERROR("ICCP chunk detected before VP8X chunk.");
917     return WEBP_INFO_PARSE_ERROR;
918   }
919   if (webp_info->chunk_counts_[CHUNK_VP8] ||
920       webp_info->chunk_counts_[CHUNK_VP8L] ||
921       webp_info->chunk_counts_[CHUNK_ANIM]) {
922     LOG_ERROR("ICCP chunk detected after image data.");
923     return WEBP_INFO_PARSE_ERROR;
924   }
925   ++webp_info->chunk_counts_[CHUNK_ICCP];
926   return WEBP_INFO_OK;
927 }
928 
ProcessChunk(const ChunkData * const chunk_data,WebPInfo * const webp_info)929 static WebPInfoStatus ProcessChunk(const ChunkData* const chunk_data,
930                                    WebPInfo* const webp_info) {
931   WebPInfoStatus status = WEBP_INFO_OK;
932   ChunkID id = chunk_data->id_;
933   if (chunk_data->id_ == CHUNK_UNKNOWN) {
934     char error_message[50];
935     snprintf(error_message, 50, "Unknown chunk at offset %6d, length %6d",
936             (int)chunk_data->offset_, (int)chunk_data->size_);
937     LOG_WARN(error_message);
938   } else {
939     if (!webp_info->quiet_) {
940       const char* tag = kWebPChunkTags[chunk_data->id_];
941       printf("Chunk %c%c%c%c at offset %6d, length %6d\n",
942              tag[0], tag[1], tag[2], tag[3], (int)chunk_data->offset_,
943              (int)chunk_data->size_);
944     }
945   }
946   switch (id) {
947     case CHUNK_VP8:
948     case CHUNK_VP8L:
949       status = ProcessImageChunk(chunk_data, webp_info);
950       break;
951     case CHUNK_VP8X:
952       status = ProcessVP8XChunk(chunk_data, webp_info);
953       break;
954     case CHUNK_ALPHA:
955       status = ProcessALPHChunk(chunk_data, webp_info);
956       break;
957     case CHUNK_ANIM:
958       status = ProcessANIMChunk(chunk_data, webp_info);
959       break;
960     case CHUNK_ANMF:
961       status = ProcessANMFChunk(chunk_data, webp_info);
962       break;
963     case CHUNK_ICCP:
964       status = ProcessICCPChunk(chunk_data, webp_info);
965       break;
966     case CHUNK_EXIF:
967     case CHUNK_XMP:
968       ++webp_info->chunk_counts_[id];
969       break;
970     case CHUNK_UNKNOWN:
971     default:
972       break;
973   }
974   if (webp_info->is_processing_anim_frame_ && id != CHUNK_ANMF) {
975     if (webp_info->anim_frame_data_size_ == chunk_data->size_) {
976       if (!webp_info->seen_image_subchunk_) {
977         LOG_ERROR("No VP8/VP8L chunk detected in an ANMF chunk.");
978         return WEBP_INFO_PARSE_ERROR;
979       }
980       webp_info->is_processing_anim_frame_ = 0;
981     } else if (webp_info->anim_frame_data_size_ > chunk_data->size_) {
982       webp_info->anim_frame_data_size_ -= chunk_data->size_;
983     } else {
984       LOG_ERROR("Truncated data detected when parsing ANMF chunk.");
985       return WEBP_INFO_TRUNCATED_DATA;
986     }
987   }
988   return status;
989 }
990 
Validate(const WebPInfo * const webp_info)991 static WebPInfoStatus Validate(const WebPInfo* const webp_info) {
992   if (webp_info->num_frames_ < 1) {
993     LOG_ERROR("No image/frame detected.");
994     return WEBP_INFO_MISSING_DATA;
995   }
996   if (webp_info->chunk_counts_[CHUNK_VP8X]) {
997     const int iccp = !!(webp_info->feature_flags_ & ICCP_FLAG);
998     const int exif = !!(webp_info->feature_flags_ & EXIF_FLAG);
999     const int xmp = !!(webp_info->feature_flags_ & XMP_FLAG);
1000     const int animation = !!(webp_info->feature_flags_ & ANIMATION_FLAG);
1001     const int alpha = !!(webp_info->feature_flags_ & ALPHA_FLAG);
1002     if (!alpha && webp_info->has_alpha_) {
1003       LOG_ERROR("Unexpected alpha data detected.");
1004       return WEBP_INFO_PARSE_ERROR;
1005     }
1006     if (alpha && !webp_info->has_alpha_) {
1007       LOG_WARN("Alpha flag is set with no alpha data present.");
1008     }
1009     if (iccp && !webp_info->chunk_counts_[CHUNK_ICCP]) {
1010       LOG_ERROR("Missing ICCP chunk.");
1011       return WEBP_INFO_MISSING_DATA;
1012     }
1013     if (exif && !webp_info->chunk_counts_[CHUNK_EXIF]) {
1014       LOG_ERROR("Missing EXIF chunk.");
1015       return WEBP_INFO_MISSING_DATA;
1016     }
1017     if (xmp && !webp_info->chunk_counts_[CHUNK_XMP]) {
1018       LOG_ERROR("Missing XMP chunk.");
1019       return WEBP_INFO_MISSING_DATA;
1020     }
1021     if (!iccp && webp_info->chunk_counts_[CHUNK_ICCP]) {
1022       LOG_ERROR("Unexpected ICCP chunk detected.");
1023       return WEBP_INFO_PARSE_ERROR;
1024     }
1025     if (!exif && webp_info->chunk_counts_[CHUNK_EXIF]) {
1026       LOG_ERROR("Unexpected EXIF chunk detected.");
1027       return WEBP_INFO_PARSE_ERROR;
1028     }
1029     if (!xmp && webp_info->chunk_counts_[CHUNK_XMP]) {
1030       LOG_ERROR("Unexpected XMP chunk detected.");
1031       return WEBP_INFO_PARSE_ERROR;
1032     }
1033     // Incomplete animation frame.
1034     if (webp_info->is_processing_anim_frame_) return WEBP_INFO_MISSING_DATA;
1035     if (!animation && webp_info->num_frames_ > 1) {
1036       LOG_ERROR("More than 1 frame detected in non-animation file.");
1037       return WEBP_INFO_PARSE_ERROR;
1038     }
1039     if (animation && (!webp_info->chunk_counts_[CHUNK_ANIM] ||
1040         !webp_info->chunk_counts_[CHUNK_ANMF])) {
1041       LOG_ERROR("No ANIM/ANMF chunk detected in animation file.");
1042       return WEBP_INFO_PARSE_ERROR;
1043     }
1044   }
1045   return WEBP_INFO_OK;
1046 }
1047 
ShowSummary(const WebPInfo * const webp_info)1048 static void ShowSummary(const WebPInfo* const webp_info) {
1049   int i;
1050   printf("Summary:\n");
1051   printf("Number of frames: %d\n", webp_info->num_frames_);
1052   printf("Chunk type  :  VP8 VP8L VP8X ALPH ANIM ANMF(VP8 /VP8L/ALPH) ICCP "
1053       "EXIF  XMP\n");
1054   printf("Chunk counts: ");
1055   for (i = 0; i < CHUNK_TYPES; ++i) {
1056     printf("%4d ", webp_info->chunk_counts_[i]);
1057     if (i == CHUNK_ANMF) {
1058       printf("%4d %4d %4d  ",
1059              webp_info->anmf_subchunk_counts_[0],
1060              webp_info->anmf_subchunk_counts_[1],
1061              webp_info->anmf_subchunk_counts_[2]);
1062     }
1063   }
1064   printf("\n");
1065 }
1066 
AnalyzeWebP(WebPInfo * const webp_info,const WebPData * webp_data)1067 static WebPInfoStatus AnalyzeWebP(WebPInfo* const webp_info,
1068                                   const WebPData* webp_data) {
1069   ChunkData chunk_data;
1070   MemBuffer mem_buffer;
1071   WebPInfoStatus webp_info_status = WEBP_INFO_OK;
1072 
1073   InitMemBuffer(&mem_buffer, webp_data);
1074   webp_info_status = ParseRIFFHeader(webp_info, &mem_buffer);
1075   if (webp_info_status != WEBP_INFO_OK) goto Error;
1076 
1077   //  Loop through all the chunks. Terminate immediately in case of error.
1078   while (webp_info_status == WEBP_INFO_OK && MemDataSize(&mem_buffer) > 0) {
1079     webp_info_status = ParseChunk(webp_info, &mem_buffer, &chunk_data);
1080     if (webp_info_status != WEBP_INFO_OK) goto Error;
1081     webp_info_status = ProcessChunk(&chunk_data, webp_info);
1082   }
1083   if (webp_info_status != WEBP_INFO_OK) goto Error;
1084   if (webp_info->show_summary_) ShowSummary(webp_info);
1085 
1086   //  Final check.
1087   webp_info_status = Validate(webp_info);
1088 
1089  Error:
1090   if (!webp_info->quiet_) {
1091     if (webp_info_status == WEBP_INFO_OK) {
1092       printf("No error detected.\n");
1093     } else {
1094       printf("Errors detected.\n");
1095     }
1096   }
1097   return webp_info_status;
1098 }
1099 
HelpShort(void)1100 static void HelpShort(void) {
1101   printf("Usage: webpinfo [options] in_files\n"
1102          "Try -longhelp for an exhaustive list of options.\n");
1103 }
1104 
HelpLong(void)1105 static void HelpLong(void) {
1106   printf("Usage: webpinfo [options] in_files\n"
1107          "Note: there could be multiple input files;\n"
1108          "      options must come before input files.\n"
1109          "Options:\n"
1110          "  -version ........... Print version number and exit.\n"
1111          "  -quiet ............. Do not show chunk parsing information.\n"
1112          "  -diag .............. Show parsing error diagnosis.\n"
1113          "  -summary ........... Show chunk stats summary.\n"
1114          "  -bitstream_info .... Parse bitstream header.\n");
1115 }
1116 
main(int argc,const char * argv[])1117 int main(int argc, const char* argv[]) {
1118   int c, quiet = 0, show_diag = 0, show_summary = 0;
1119   int parse_bitstream = 0;
1120   WebPInfoStatus webp_info_status = WEBP_INFO_OK;
1121   WebPInfo webp_info;
1122 
1123   INIT_WARGV(argc, argv);
1124 
1125   if (argc == 1) {
1126     HelpShort();
1127     FREE_WARGV_AND_RETURN(WEBP_INFO_OK);
1128   }
1129 
1130   // Parse command-line input.
1131   for (c = 1; c < argc; ++c) {
1132     if (!strcmp(argv[c], "-h") || !strcmp(argv[c], "-help")) {
1133       HelpShort();
1134       FREE_WARGV_AND_RETURN(WEBP_INFO_OK);
1135     } else if (!strcmp(argv[c], "-H") || !strcmp(argv[c], "-longhelp")) {
1136       HelpLong();
1137       FREE_WARGV_AND_RETURN(WEBP_INFO_OK);
1138     } else if (!strcmp(argv[c], "-quiet")) {
1139       quiet = 1;
1140     } else if (!strcmp(argv[c], "-diag")) {
1141       show_diag = 1;
1142     } else if (!strcmp(argv[c], "-summary")) {
1143       show_summary = 1;
1144     } else if (!strcmp(argv[c], "-bitstream_info")) {
1145       parse_bitstream = 1;
1146     } else if (!strcmp(argv[c], "-version")) {
1147       const int version = WebPGetDecoderVersion();
1148       printf("WebP Decoder version: %d.%d.%d\n",
1149              (version >> 16) & 0xff, (version >> 8) & 0xff, version & 0xff);
1150       FREE_WARGV_AND_RETURN(0);
1151     } else {  // Assume the remaining are all input files.
1152       break;
1153     }
1154   }
1155 
1156   if (c == argc) {
1157     HelpShort();
1158     FREE_WARGV_AND_RETURN(WEBP_INFO_INVALID_COMMAND);
1159   }
1160 
1161   // Process input files one by one.
1162   for (; c < argc; ++c) {
1163     WebPData webp_data;
1164     const W_CHAR* in_file = NULL;
1165     WebPInfoInit(&webp_info);
1166     webp_info.quiet_ = quiet;
1167     webp_info.show_diagnosis_ = show_diag;
1168     webp_info.show_summary_ = show_summary;
1169     webp_info.parse_bitstream_ = parse_bitstream;
1170     in_file = GET_WARGV(argv, c);
1171     if (in_file == NULL ||
1172         !ReadFileToWebPData((const char*)in_file, &webp_data)) {
1173       webp_info_status = WEBP_INFO_INVALID_COMMAND;
1174       WFPRINTF(stderr, "Failed to open input file %s.\n", in_file);
1175       continue;
1176     }
1177     if (!webp_info.quiet_) WPRINTF("File: %s\n", in_file);
1178     webp_info_status = AnalyzeWebP(&webp_info, &webp_data);
1179     WebPDataClear(&webp_data);
1180   }
1181   FREE_WARGV_AND_RETURN(webp_info_status);
1182 }
1183