1 /* Copyright (C)2007-2013 Xiph.Org Foundation
2    File: picture.c
3 
4    Redistribution and use in source and binary forms, with or without
5    modification, are permitted provided that the following conditions
6    are met:
7 
8    - Redistributions of source code must retain the above copyright
9    notice, this list of conditions and the following disclaimer.
10 
11    - Redistributions in binary form must reproduce the above copyright
12    notice, this list of conditions and the following disclaimer in the
13    documentation and/or other materials provided with the distribution.
14 
15    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
16    ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
17    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
18    A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR
19    CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20    EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21    PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22    PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
23    LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
24    NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25    SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27 
28 #ifdef HAVE_CONFIG_H
29 # include "config.h"
30 #endif
31 
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include "picture.h"
36 #include "unicode_support.h"
37 
38 static const char BASE64_TABLE[64]={
39   'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
40   'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
41   'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',
42   'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/'
43 };
44 
45 /*Utility function for base64 encoding METADATA_BLOCK_PICTURE tags.
46   Stores BASE64_LENGTH(len)+1 bytes in dst (including a terminating NUL).*/
base64_encode(char * dst,const char * src,int len)47 static void base64_encode(char *dst, const char *src, int len){
48   unsigned s0;
49   unsigned s1;
50   unsigned s2;
51   int      ngroups;
52   int      i;
53   ngroups=len/3;
54   for(i=0;i<ngroups;i++){
55     s0=(unsigned char)src[3*i+0];
56     s1=(unsigned char)src[3*i+1];
57     s2=(unsigned char)src[3*i+2];
58     dst[4*i+0]=BASE64_TABLE[s0>>2];
59     dst[4*i+1]=BASE64_TABLE[(s0&3)<<4|s1>>4];
60     dst[4*i+2]=BASE64_TABLE[(s1&15)<<2|s2>>6];
61     dst[4*i+3]=BASE64_TABLE[s2&63];
62   }
63   len-=3*i;
64   if(len==1){
65     s0=(unsigned char)src[3*i+0];
66     dst[4*i+0]=BASE64_TABLE[s0>>2];
67     dst[4*i+1]=BASE64_TABLE[(s0&3)<<4];
68     dst[4*i+2]='=';
69     dst[4*i+3]='=';
70     i++;
71   }
72   else if(len==2){
73     s0=(unsigned char)src[3*i+0];
74     s1=(unsigned char)src[3*i+1];
75     dst[4*i+0]=BASE64_TABLE[s0>>2];
76     dst[4*i+1]=BASE64_TABLE[(s0&3)<<4|s1>>4];
77     dst[4*i+2]=BASE64_TABLE[(s1&15)<<2];
78     dst[4*i+3]='=';
79     i++;
80   }
81   dst[4*i]='\0';
82 }
83 
84 /*A version of strncasecmp() that is guaranteed to only ignore the case of
85    ASCII characters.*/
oi_strncasecmp(const char * a,const char * b,int n)86 static int oi_strncasecmp(const char *a, const char *b, int n){
87   int i;
88   for(i=0;i<n;i++){
89     int aval;
90     int bval;
91     int diff;
92     aval=a[i];
93     bval=b[i];
94     if(aval>='a'&&aval<='z') {
95       aval-='a'-'A';
96     }
97     if(bval>='a'&&bval<='z'){
98       bval-='a'-'A';
99     }
100     diff=aval-bval;
101     if(diff){
102       return diff;
103     }
104   }
105   return 0;
106 }
107 
is_jpeg(const unsigned char * buf,size_t length)108 static int is_jpeg(const unsigned char *buf, size_t length){
109   return length>=3&&memcmp(buf,"\xFF\xD8\xFF",3)==0;
110 }
111 
is_png(const unsigned char * buf,size_t length)112 static int is_png(const unsigned char *buf, size_t length){
113   return length>=8&&memcmp(buf,"\x89PNG\x0D\x0A\x1A\x0A",8)==0;
114 }
115 
is_gif(const unsigned char * buf,size_t length)116 static int is_gif(const unsigned char *buf, size_t length){
117   return length>=6
118    &&(memcmp(buf,"GIF87a",6)==0||memcmp(buf,"GIF89a",6)==0);
119 }
120 
121 #define READ_U32_BE(buf) \
122     (((buf)[0]<<24)|((buf)[1]<<16)|((buf)[2]<<8)|((buf)[3]&0xff))
123 
124 /*Tries to extract the width, height, bits per pixel, and palette size of a
125    PNG.
126   On failure, simply leaves its outputs unmodified.*/
extract_png_params(const unsigned char * data,size_t data_length,opus_uint32 * width,opus_uint32 * height,opus_uint32 * depth,opus_uint32 * colors,int * has_palette)127 static void extract_png_params(const unsigned char *data, size_t data_length,
128                         opus_uint32 *width, opus_uint32 *height,
129                         opus_uint32 *depth, opus_uint32 *colors,
130                         int *has_palette){
131   if(is_png(data,data_length)){
132     size_t offs;
133     offs=8;
134     while(data_length-offs>=12){
135       opus_uint32 chunk_len;
136       chunk_len=READ_U32_BE(data+offs);
137       if(chunk_len>data_length-(offs+12))break;
138       else if(chunk_len==13&&memcmp(data+offs+4,"IHDR",4)==0){
139         int color_type;
140         *width=READ_U32_BE(data+offs+8);
141         *height=READ_U32_BE(data+offs+12);
142         color_type=data[offs+17];
143         if(color_type==3){
144           *depth=24;
145           *has_palette=1;
146         }
147         else{
148           int sample_depth;
149           sample_depth=data[offs+16];
150           if(color_type==0)*depth=sample_depth;
151           else if(color_type==2)*depth=sample_depth*3;
152           else if(color_type==4)*depth=sample_depth*2;
153           else if(color_type==6)*depth=sample_depth*4;
154           *colors=0;
155           *has_palette=0;
156           break;
157         }
158       }
159       else if(*has_palette>0&&memcmp(data+offs+4,"PLTE",4)==0){
160         *colors=chunk_len/3;
161         break;
162       }
163       offs+=12+chunk_len;
164     }
165   }
166 }
167 
168 /*Tries to extract the width, height, bits per pixel, and palette size of a
169    GIF.
170   On failure, simply leaves its outputs unmodified.*/
extract_gif_params(const unsigned char * data,size_t data_length,opus_uint32 * width,opus_uint32 * height,opus_uint32 * depth,opus_uint32 * colors,int * has_palette)171 static void extract_gif_params(const unsigned char *data, size_t data_length,
172                         opus_uint32 *width, opus_uint32 *height,
173                         opus_uint32 *depth, opus_uint32 *colors,
174                         int *has_palette){
175   if(is_gif(data,data_length)&&data_length>=14){
176     *width=data[6]|data[7]<<8;
177     *height=data[8]|data[9]<<8;
178     /*libFLAC hard-codes the depth to 24.*/
179     *depth=24;
180     *colors=1<<((data[10]&7)+1);
181     *has_palette=1;
182   }
183 }
184 
185 
186 /*Tries to extract the width, height, bits per pixel, and palette size of a
187    JPEG.
188   On failure, simply leaves its outputs unmodified.*/
extract_jpeg_params(const unsigned char * data,size_t data_length,opus_uint32 * width,opus_uint32 * height,opus_uint32 * depth,opus_uint32 * colors,int * has_palette)189 static void extract_jpeg_params(const unsigned char *data, size_t data_length,
190                          opus_uint32 *width, opus_uint32 *height,
191                          opus_uint32 *depth, opus_uint32 *colors,
192                          int *has_palette){
193   if(is_jpeg(data,data_length)){
194     size_t offs;
195     offs=2;
196     for(;;){
197       size_t segment_len;
198       int    marker;
199       while(offs<data_length&&data[offs]!=0xFF)offs++;
200       while(offs<data_length&&data[offs]==0xFF)offs++;
201       marker=data[offs];
202       offs++;
203       /*If we hit EOI* (end of image), or another SOI* (start of image),
204          or SOS (start of scan), then stop now.*/
205       if(offs>=data_length||(marker>=0xD8&&marker<=0xDA))break;
206       /*RST* (restart markers): skip (no segment length).*/
207       else if(marker>=0xD0&&marker<=0xD7)continue;
208       /*Read the length of the marker segment.*/
209       if(data_length-offs<2)break;
210       segment_len=data[offs]<<8|data[offs+1];
211       if(segment_len<2||data_length-offs<segment_len)break;
212       if(marker==0xC0||(marker>0xC0&&marker<0xD0&&(marker&3)!=0)){
213         /*Found a SOFn (start of frame) marker segment:*/
214         if(segment_len>=8){
215           *height=data[offs+3]<<8|data[offs+4];
216           *width=data[offs+5]<<8|data[offs+6];
217           *depth=data[offs+2]*data[offs+7];
218           *colors=0;
219           *has_palette=0;
220         }
221         break;
222       }
223       /*Other markers: skip the whole marker segment.*/
224       offs+=segment_len;
225     }
226   }
227 }
228 
229 #define IMAX(a,b) ((a) > (b) ? (a) : (b))
230 
opeint_read_picture_file(const char * filename,const char * description,int * error,size_t * size,size_t * offset)231 static unsigned char *opeint_read_picture_file(const char *filename, const char *description, int *error, size_t *size, size_t *offset) {
232   FILE          *picture_file;
233   size_t         cbuf;
234   size_t         nbuf;
235   size_t         data_offset;
236   unsigned char *buf;
237   picture_file=opeint_fopen(filename,"rb");
238   /*Buffer size: 8 static 4-byte fields plus 2 dynamic fields, plus the
239      file/URL data.
240     We reserve at least 10 bytes for the media type, in case we still need to
241      extract it from the file.*/
242   data_offset=32+strlen(description)+10;
243   buf=NULL;
244   /*Complicated case: we have a real file.
245     Read it in, attempt to parse the media type and image dimensions if
246      necessary, and validate what the user passed in.*/
247   if(picture_file==NULL){
248     *error = OPE_CANNOT_OPEN;
249     return NULL;
250   }
251   nbuf=data_offset;
252   /*Add a reasonable starting image file size.*/
253   cbuf=data_offset+65536;
254   for(;;){
255     unsigned char *new_buf;
256     size_t         nread;
257     new_buf=realloc(buf,cbuf);
258     if(new_buf==NULL){
259       fclose(picture_file);
260       free(buf);
261       *error = OPE_ALLOC_FAIL;
262       return NULL;
263     }
264     buf=new_buf;
265     nread=fread(buf+nbuf,1,cbuf-nbuf,picture_file);
266     nbuf+=nread;
267     if(nbuf<cbuf){
268       int file_error;
269       file_error=ferror(picture_file);
270       fclose(picture_file);
271       if(file_error){
272         free(buf);
273         *error = OPE_INVALID_PICTURE;
274         return NULL;
275       }
276       break;
277     }
278     if(cbuf==0xFFFFFFFF){
279       fclose(picture_file);
280       free(buf);
281       *error = OPE_INVALID_PICTURE;
282       return NULL;
283     }
284     else if(cbuf>0x7FFFFFFFU)cbuf=0xFFFFFFFFU;
285     else cbuf=cbuf<<1|1;
286   }
287   *size = nbuf;
288   *offset = data_offset;
289   return buf;
290 }
291 
validate_picture_type(int picture_type,int seen_file_icons)292 static int validate_picture_type(int picture_type, int seen_file_icons) {
293   if (picture_type > 20) return 0;
294   if(picture_type>=1&&picture_type<=2&&(seen_file_icons&picture_type)) return 0;
295   return 1;
296 }
297 
298 /*Parse a picture SPECIFICATION as given on the command-line.
299   spec: The specification.
300   error_message: Returns an error message on error.
301   seen_file_icons: Bit flags used to track if any pictures of type 1 or type 2
302    have already been added, to ensure only one is allowed.
303   Return: A Base64-encoded string suitable for use in a METADATA_BLOCK_PICTURE
304    tag.*/
opeint_parse_picture_specification_impl(unsigned char * buf,size_t nbuf,size_t data_offset,int picture_type,const char * description,int * error,int * seen_file_icons)305 static char *opeint_parse_picture_specification_impl(unsigned char *buf, size_t nbuf, size_t data_offset, int picture_type, const char *description,
306                                   int *error, int *seen_file_icons){
307   opus_uint32  width;
308   opus_uint32  height;
309   opus_uint32  depth;
310   opus_uint32  colors;
311   const char    *mime_type;
312   char          *out;
313   size_t         data_length;
314   size_t         b64_length;
315   int          has_palette;
316   *error = OPE_OK;
317   if (picture_type < 0) picture_type=3;
318   if (!validate_picture_type(picture_type, *seen_file_icons)) {
319     *error = OPE_INVALID_PICTURE;
320     return NULL;
321   }
322   if (buf == NULL) return NULL;
323   data_length=nbuf-data_offset;
324   /*Try to extract the image dimensions/color information from the file.*/
325   width=height=depth=colors=0;
326   has_palette=-1;
327   {
328     if(is_jpeg(buf+data_offset,data_length)){
329       mime_type="image/jpeg";
330       extract_jpeg_params(buf+data_offset,data_length,
331        &width,&height,&depth,&colors,&has_palette);
332     }
333     else if(is_png(buf+data_offset,data_length)){
334       mime_type="image/png";
335       extract_png_params(buf+data_offset,data_length,
336        &width,&height,&depth,&colors,&has_palette);
337     }
338     else if(is_gif(buf+data_offset,data_length)){
339       mime_type="image/gif";
340       extract_gif_params(buf+data_offset,data_length,
341        &width,&height,&depth,&colors,&has_palette);
342     }
343     else{
344       *error = OPE_INVALID_PICTURE;
345       return NULL;
346     }
347   }
348   /*These fields MUST be set correctly OR all set to zero.
349     So if any of them (except colors, for which 0 is a valid value) are still
350      zero, clear the rest to zero.*/
351   if(width==0||height==0||depth==0)width=height=depth=colors=0;
352   if(picture_type==1&&(width!=32||height!=32
353    ||strlen(mime_type)!=9
354    ||oi_strncasecmp("image/png",mime_type,9)!=0)){
355     *error = OPE_INVALID_ICON;
356     return NULL;
357   }
358   /*Build the METADATA_BLOCK_PICTURE buffer.
359     We do this backwards from data_offset, because we didn't necessarily know
360      how big the media type string was before we read the data in.*/
361   data_offset-=4;
362   WRITE_U32_BE(buf+data_offset,(unsigned long)data_length);
363   data_offset-=4;
364   WRITE_U32_BE(buf+data_offset,colors);
365   data_offset-=4;
366   WRITE_U32_BE(buf+data_offset,depth);
367   data_offset-=4;
368   WRITE_U32_BE(buf+data_offset,height);
369   data_offset-=4;
370   WRITE_U32_BE(buf+data_offset,width);
371   data_offset-=strlen(description);
372   memcpy(buf+data_offset,description,strlen(description));
373   data_offset-=4;
374   WRITE_U32_BE(buf+data_offset,strlen(description));
375   data_offset-=strlen(mime_type);
376   memcpy(buf+data_offset,mime_type,strlen(mime_type));
377   data_offset-=4;
378   WRITE_U32_BE(buf+data_offset,strlen(mime_type));
379   data_offset-=4;
380   WRITE_U32_BE(buf+data_offset,picture_type);
381   data_length=nbuf-data_offset;
382   b64_length=BASE64_LENGTH(data_length);
383   out=(char *)malloc(b64_length+1);
384   if(out!=NULL){
385     base64_encode(out,(char *)buf+data_offset,data_length);
386     if(picture_type>=1&&picture_type<=2)*seen_file_icons|=picture_type;
387   } else {
388     *error = OPE_ALLOC_FAIL;
389   }
390   return out;
391 }
392 
opeint_parse_picture_specification(const char * filename,int picture_type,const char * description,int * error,int * seen_file_icons)393 char *opeint_parse_picture_specification(const char *filename, int picture_type, const char *description,
394                                   int *error, int *seen_file_icons){
395   size_t nbuf;
396   size_t data_offset;
397   unsigned char *buf;
398   char *ret;
399   if (picture_type < 0) picture_type=3;
400   if (!validate_picture_type(picture_type, *seen_file_icons)) {
401     *error = OPE_INVALID_PICTURE;
402     return NULL;
403   }
404   if (description == NULL) description = "";
405   buf = opeint_read_picture_file(filename, description, error, &nbuf, &data_offset);
406   if (buf == NULL) return NULL;
407   ret = opeint_parse_picture_specification_impl(buf, nbuf, data_offset, picture_type, description, error, seen_file_icons);
408   free(buf);
409   return ret;
410 }
411 
opeint_parse_picture_specification_from_memory(const char * mem,size_t size,int picture_type,const char * description,int * error,int * seen_file_icons)412 char *opeint_parse_picture_specification_from_memory(const char *mem, size_t size, int picture_type, const char *description,
413                                   int *error, int *seen_file_icons){
414   size_t nbuf;
415   size_t data_offset;
416   unsigned char *buf;
417   char *ret;
418   if (picture_type < 0) picture_type=3;
419   if (!validate_picture_type(picture_type, *seen_file_icons)) {
420     *error = OPE_INVALID_PICTURE;
421     return NULL;
422   }
423   if (description == NULL) description = "";
424   data_offset=32+strlen(description)+10;
425   nbuf = data_offset + size;
426   buf = (unsigned char *)malloc(nbuf);
427   if (buf == NULL) {
428     *error = OPE_ALLOC_FAIL;
429     return NULL;
430   }
431   memcpy(buf+data_offset, mem, size);
432   ret = opeint_parse_picture_specification_impl(buf, nbuf, data_offset, picture_type, description, error, seen_file_icons);
433   free(buf);
434   return ret;
435 }
436