Coverage Report

Created: 2026-01-17 06:57

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