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