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