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 // (limited) PNM decoder
11 
12 #include "./pnmdec.h"
13 
14 #include <assert.h>
15 #include <ctype.h>
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <string.h>
19 
20 #include "webp/encode.h"
21 #include "./imageio_util.h"
22 
23 typedef enum {
24   WIDTH_FLAG      = 1 << 0,
25   HEIGHT_FLAG     = 1 << 1,
26   DEPTH_FLAG      = 1 << 2,
27   MAXVAL_FLAG     = 1 << 3,
28   TUPLE_FLAG      = 1 << 4,
29   ALL_NEEDED_FLAGS = 0x1f
30 } PNMFlags;
31 
32 typedef struct {
33   const uint8_t* data;
34   size_t data_size;
35   int width, height;
36   int bytes_per_px;   // 1, 3, 4
37   int depth;
38   int max_value;
39   int type;           // 5, 6 or 7
40   int seen_flags;
41 } PNMInfo;
42 
43 // -----------------------------------------------------------------------------
44 // PNM decoding
45 
46 #define MAX_LINE_SIZE 1024
47 static const size_t kMinPNMHeaderSize = 3;
48 
ReadLine(const uint8_t * const data,size_t off,size_t data_size,char out[MAX_LINE_SIZE+1],size_t * const out_size)49 static size_t ReadLine(const uint8_t* const data, size_t off, size_t data_size,
50                        char out[MAX_LINE_SIZE + 1], size_t* const out_size) {
51   size_t i = 0;
52   *out_size = 0;
53  redo:
54   for (i = 0; i < MAX_LINE_SIZE && off < data_size; ++i) {
55     out[i] = data[off++];
56     if (out[i] == '\n') break;
57   }
58   if (off < data_size) {
59     if (i == 0) goto redo;         // empty line
60     if (out[0] == '#') goto redo;  // skip comment
61   }
62   out[i] = 0;   // safety sentinel
63   *out_size = i;
64   return off;
65 }
66 
FlagError(const char flag[])67 static size_t FlagError(const char flag[]) {
68   fprintf(stderr, "PAM header error: flags '%s' already seen.\n", flag);
69   return 0;
70 }
71 
72 // inspired from http://netpbm.sourceforge.net/doc/pam.html
ReadPAMFields(PNMInfo * const info,size_t off)73 static size_t ReadPAMFields(PNMInfo* const info, size_t off) {
74   char out[MAX_LINE_SIZE + 1];
75   size_t out_size;
76   int tmp;
77   assert(info != NULL);
78   while (1) {
79     off = ReadLine(info->data, off, info->data_size, out, &out_size);
80     if (off == 0) return 0;
81     if (sscanf(out, "WIDTH %d", &tmp) == 1) {
82       if (info->seen_flags & WIDTH_FLAG) return FlagError("WIDTH");
83       info->seen_flags |= WIDTH_FLAG;
84       info->width = tmp;
85     } else if (sscanf(out, "HEIGHT %d", &tmp) == 1) {
86       if (info->seen_flags & HEIGHT_FLAG) return FlagError("HEIGHT");
87       info->seen_flags |= HEIGHT_FLAG;
88       info->height = tmp;
89     } else if (sscanf(out, "DEPTH %d", &tmp) == 1) {
90       if (info->seen_flags & DEPTH_FLAG) return FlagError("DEPTH");
91       info->seen_flags |= DEPTH_FLAG;
92       info->depth = tmp;
93     } else if (sscanf(out, "MAXVAL %d", &tmp) == 1) {
94       if (info->seen_flags & MAXVAL_FLAG) return FlagError("MAXVAL");
95       info->seen_flags |= MAXVAL_FLAG;
96       info->max_value = tmp;
97     } else if (!strcmp(out, "TUPLTYPE RGB_ALPHA")) {
98       info->bytes_per_px = 4;
99       info->seen_flags |= TUPLE_FLAG;
100     } else if (!strcmp(out, "TUPLTYPE RGB")) {
101       info->bytes_per_px = 3;
102       info->seen_flags |= TUPLE_FLAG;
103     } else if (!strcmp(out, "TUPLTYPE GRAYSCALE")) {
104       info->bytes_per_px = 1;
105       info->seen_flags |= TUPLE_FLAG;
106     } else if (!strcmp(out, "ENDHDR")) {
107       break;
108     } else {
109       static const char kEllipsis[] = " ...";
110       int i;
111       if (out_size > 20) sprintf(out + 20 - strlen(kEllipsis), kEllipsis);
112       for (i = 0; i < (int)strlen(out); ++i) {
113         if (!isprint(out[i])) out[i] = ' ';
114       }
115       fprintf(stderr, "PAM header error: unrecognized entry [%s]\n", out);
116       return 0;
117     }
118   }
119   if (!(info->seen_flags & TUPLE_FLAG)) {
120     if (info->depth > 0 && info->depth <= 4 && info->depth != 2) {
121       info->seen_flags |= TUPLE_FLAG;
122       info->bytes_per_px = info->depth * (info->max_value > 255 ? 2 : 1);
123     } else {
124       fprintf(stderr, "PAM: invalid bitdepth (%d).\n", info->depth);
125       return 0;
126     }
127   }
128   if (info->seen_flags != ALL_NEEDED_FLAGS) {
129     fprintf(stderr, "PAM: incomplete header.\n");
130     return 0;
131   }
132   return off;
133 }
134 
ReadHeader(PNMInfo * const info)135 static size_t ReadHeader(PNMInfo* const info) {
136   size_t off = 0;
137   char out[MAX_LINE_SIZE + 1];
138   size_t out_size;
139   if (info == NULL) return 0;
140   if (info->data == NULL || info->data_size < kMinPNMHeaderSize) return 0;
141 
142   info->width = info->height = 0;
143   info->type = -1;
144   info->seen_flags = 0;
145   info->bytes_per_px = 0;
146   info->depth = 0;
147   info->max_value = 0;
148 
149   off = ReadLine(info->data, off, info->data_size, out, &out_size);
150   if (off == 0 || sscanf(out, "P%d", &info->type) != 1) return 0;
151   if (info->type == 7) {
152     off = ReadPAMFields(info, off);
153   } else {
154     off = ReadLine(info->data, off, info->data_size, out, &out_size);
155     if (off == 0 || sscanf(out, "%d %d", &info->width, &info->height) != 2) {
156       return 0;
157     }
158     off = ReadLine(info->data, off, info->data_size, out, &out_size);
159     if (off == 0 || sscanf(out, "%d", &info->max_value) != 1) return 0;
160 
161     // finish initializing missing fields
162     info->depth = (info->type == 5) ? 1 : 3;
163     info->bytes_per_px = info->depth * (info->max_value > 255 ? 2 : 1);
164   }
165   // perform some basic numerical validation
166   if (info->width <= 0 || info->height <= 0 ||
167       info->type <= 0 || info->type >= 9 ||
168       info->depth <= 0 || info->depth == 2 || info->depth > 4 ||
169       info->bytes_per_px < info->depth ||
170       info->max_value <= 0 || info->max_value >= 65536) {
171     return 0;
172   }
173   return off;
174 }
175 
ReadPNM(const uint8_t * const data,size_t data_size,WebPPicture * const pic,int keep_alpha,struct Metadata * const metadata)176 int ReadPNM(const uint8_t* const data, size_t data_size,
177             WebPPicture* const pic, int keep_alpha,
178             struct Metadata* const metadata) {
179   int ok = 0;
180   int i, j;
181   uint64_t stride, pixel_bytes;
182   uint8_t* rgb = NULL, *tmp_rgb;
183   size_t offset;
184   PNMInfo info;
185 
186   info.data = data;
187   info.data_size = data_size;
188   offset = ReadHeader(&info);
189   if (offset == 0) {
190     fprintf(stderr, "Error parsing PNM header.\n");
191     goto End;
192   }
193 
194   if (info.type < 5 || info.type > 7) {
195     fprintf(stderr, "Unsupported P%d PNM format.\n", info.type);
196     goto End;
197   }
198 
199   // Some basic validations.
200   if (pic == NULL) goto End;
201   if (info.width > WEBP_MAX_DIMENSION || info.height > WEBP_MAX_DIMENSION) {
202     fprintf(stderr, "Invalid %dx%d dimension for PNM\n",
203                     info.width, info.height);
204     goto End;
205   }
206 
207   pixel_bytes = (uint64_t)info.width * info.height * info.bytes_per_px;
208   if (data_size < offset + pixel_bytes) {
209     fprintf(stderr, "Truncated PNM file (P%d).\n", info.type);
210     goto End;
211   }
212   stride =
213       (uint64_t)(info.bytes_per_px < 3 ? 3 : info.bytes_per_px) * info.width;
214   if (stride != (size_t)stride ||
215       !ImgIoUtilCheckSizeArgumentsOverflow(stride, info.height)) {
216     goto End;
217   }
218 
219   rgb = (uint8_t*)malloc((size_t)stride * info.height);
220   if (rgb == NULL) goto End;
221 
222   // Convert input
223   tmp_rgb = rgb;
224   for (j = 0; j < info.height; ++j) {
225     assert(offset + info.bytes_per_px * info.width <= data_size);
226     if (info.depth == 1) {
227       // convert grayscale -> RGB
228       for (i = 0; i < info.width; ++i) {
229         const uint8_t v = data[offset + i];
230         tmp_rgb[3 * i + 0] = tmp_rgb[3 * i + 1] = tmp_rgb[3 * i + 2] = v;
231       }
232     } else if (info.depth == 3) {   // RGB
233       memcpy(tmp_rgb, data + offset, 3 * info.width * sizeof(*data));
234     } else if (info.depth == 4) {   // RGBA
235       memcpy(tmp_rgb, data + offset, 4 * info.width * sizeof(*data));
236     }
237     offset += info.bytes_per_px * info.width;
238     tmp_rgb += stride;
239   }
240 
241   // WebP conversion.
242   pic->width = info.width;
243   pic->height = info.height;
244   ok = (info.depth == 4) ? WebPPictureImportRGBA(pic, rgb, (int)stride)
245                          : WebPPictureImportRGB(pic, rgb, (int)stride);
246   if (!ok) goto End;
247 
248   ok = 1;
249  End:
250   free((void*)rgb);
251 
252   (void)metadata;
253   (void)keep_alpha;
254   return ok;
255 }
256 
257 // -----------------------------------------------------------------------------
258