mirror of
https://github.com/danog/Telegram.git
synced 2024-12-14 02:17:22 +01:00
500 lines
17 KiB
C
500 lines
17 KiB
C
/* Copyright (C)2007-2013 Xiph.Org Foundation
|
|
File: picture.c
|
|
|
|
Redistribution and use in source and binary forms, with or without
|
|
modification, are permitted provided that the following conditions
|
|
are met:
|
|
|
|
- Redistributions of source code must retain the above copyright
|
|
notice, this list of conditions and the following disclaimer.
|
|
|
|
- Redistributions in binary form must reproduce the above copyright
|
|
notice, this list of conditions and the following disclaimer in the
|
|
documentation and/or other materials provided with the distribution.
|
|
|
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
|
|
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
|
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
|
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
|
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
|
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
|
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include "picture.h"
|
|
|
|
static const char BASE64_TABLE[64]={
|
|
'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
|
|
'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
|
|
'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',
|
|
'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/'
|
|
};
|
|
|
|
/*Utility function for base64 encoding METADATA_BLOCK_PICTURE tags.
|
|
Stores BASE64_LENGTH(len)+1 bytes in dst (including a terminating NUL).*/
|
|
void base64_encode(char *dst, const char *src, int len){
|
|
unsigned s0;
|
|
unsigned s1;
|
|
unsigned s2;
|
|
int ngroups;
|
|
int i;
|
|
ngroups=len/3;
|
|
for(i=0;i<ngroups;i++){
|
|
s0=(unsigned char)src[3*i+0];
|
|
s1=(unsigned char)src[3*i+1];
|
|
s2=(unsigned char)src[3*i+2];
|
|
dst[4*i+0]=BASE64_TABLE[s0>>2];
|
|
dst[4*i+1]=BASE64_TABLE[(s0&3)<<4|s1>>4];
|
|
dst[4*i+2]=BASE64_TABLE[(s1&15)<<2|s2>>6];
|
|
dst[4*i+3]=BASE64_TABLE[s2&63];
|
|
}
|
|
len-=3*i;
|
|
if(len==1){
|
|
s0=(unsigned char)src[3*i+0];
|
|
dst[4*i+0]=BASE64_TABLE[s0>>2];
|
|
dst[4*i+1]=BASE64_TABLE[(s0&3)<<4];
|
|
dst[4*i+2]='=';
|
|
dst[4*i+3]='=';
|
|
i++;
|
|
}
|
|
else if(len==2){
|
|
s0=(unsigned char)src[3*i+0];
|
|
s1=(unsigned char)src[3*i+1];
|
|
dst[4*i+0]=BASE64_TABLE[s0>>2];
|
|
dst[4*i+1]=BASE64_TABLE[(s0&3)<<4|s1>>4];
|
|
dst[4*i+2]=BASE64_TABLE[(s1&15)<<2];
|
|
dst[4*i+3]='=';
|
|
i++;
|
|
}
|
|
dst[4*i]='\0';
|
|
}
|
|
|
|
/*A version of strncasecmp() that is guaranteed to only ignore the case of
|
|
ASCII characters.*/
|
|
int oi_strncasecmp(const char *a, const char *b, int n){
|
|
int i;
|
|
for(i=0;i<n;i++){
|
|
int aval;
|
|
int bval;
|
|
int diff;
|
|
aval=a[i];
|
|
bval=b[i];
|
|
if(aval>='a'&&aval<='z') {
|
|
aval-='a'-'A';
|
|
}
|
|
if(bval>='a'&&bval<='z'){
|
|
bval-='a'-'A';
|
|
}
|
|
diff=aval-bval;
|
|
if(diff){
|
|
return diff;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int is_jpeg(const unsigned char *buf, size_t length){
|
|
return length>=11&&memcmp(buf,"\xFF\xD8\xFF\xE0",4)==0
|
|
&&(buf[4]<<8|buf[5])>=16&&memcmp(buf+6,"JFIF",5)==0;
|
|
}
|
|
|
|
int is_png(const unsigned char *buf, size_t length){
|
|
return length>=8&&memcmp(buf,"\x89PNG\x0D\x0A\x1A\x0A",8)==0;
|
|
}
|
|
|
|
int is_gif(const unsigned char *buf, size_t length){
|
|
return length>=6
|
|
&&(memcmp(buf,"GIF87a",6)==0||memcmp(buf,"GIF89a",6)==0);
|
|
}
|
|
|
|
#define READ_U32_BE(buf) \
|
|
(((buf)[0]<<24)|((buf)[1]<<16)|((buf)[2]<<8)|((buf)[3]&0xff))
|
|
|
|
/*Tries to extract the width, height, bits per pixel, and palette size of a
|
|
PNG.
|
|
On failure, simply leaves its outputs unmodified.*/
|
|
void extract_png_params(const unsigned char *data, size_t data_length,
|
|
ogg_uint32_t *width, ogg_uint32_t *height,
|
|
ogg_uint32_t *depth, ogg_uint32_t *colors,
|
|
int *has_palette){
|
|
if(is_png(data,data_length)){
|
|
size_t offs;
|
|
offs=8;
|
|
while(data_length-offs>=12){
|
|
ogg_uint32_t chunk_len;
|
|
chunk_len=READ_U32_BE(data+offs);
|
|
if(chunk_len>data_length-(offs+12))break;
|
|
else if(chunk_len==13&&memcmp(data+offs+4,"IHDR",4)==0){
|
|
int color_type;
|
|
*width=READ_U32_BE(data+offs+8);
|
|
*height=READ_U32_BE(data+offs+12);
|
|
color_type=data[offs+17];
|
|
if(color_type==3){
|
|
*depth=24;
|
|
*has_palette=1;
|
|
}
|
|
else{
|
|
int sample_depth;
|
|
sample_depth=data[offs+16];
|
|
if(color_type==0)*depth=sample_depth;
|
|
else if(color_type==2)*depth=sample_depth*3;
|
|
else if(color_type==4)*depth=sample_depth*2;
|
|
else if(color_type==6)*depth=sample_depth*4;
|
|
*colors=0;
|
|
*has_palette=0;
|
|
break;
|
|
}
|
|
}
|
|
else if(*has_palette>0&&memcmp(data+offs+4,"PLTE",4)==0){
|
|
*colors=chunk_len/3;
|
|
break;
|
|
}
|
|
offs+=12+chunk_len;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*Tries to extract the width, height, bits per pixel, and palette size of a
|
|
GIF.
|
|
On failure, simply leaves its outputs unmodified.*/
|
|
void extract_gif_params(const unsigned char *data, size_t data_length,
|
|
ogg_uint32_t *width, ogg_uint32_t *height,
|
|
ogg_uint32_t *depth, ogg_uint32_t *colors,
|
|
int *has_palette){
|
|
if(is_gif(data,data_length)&&data_length>=14){
|
|
*width=data[6]|data[7]<<8;
|
|
*height=data[8]|data[9]<<8;
|
|
/*libFLAC hard-codes the depth to 24.*/
|
|
*depth=24;
|
|
*colors=1<<((data[10]&7)+1);
|
|
*has_palette=1;
|
|
}
|
|
}
|
|
|
|
|
|
/*Tries to extract the width, height, bits per pixel, and palette size of a
|
|
JPEG.
|
|
On failure, simply leaves its outputs unmodified.*/
|
|
void extract_jpeg_params(const unsigned char *data, size_t data_length,
|
|
ogg_uint32_t *width, ogg_uint32_t *height,
|
|
ogg_uint32_t *depth, ogg_uint32_t *colors,
|
|
int *has_palette){
|
|
if(is_jpeg(data,data_length)){
|
|
size_t offs;
|
|
offs=2;
|
|
for(;;){
|
|
size_t segment_len;
|
|
int marker;
|
|
while(offs<data_length&&data[offs]!=0xFF)offs++;
|
|
while(offs<data_length&&data[offs]==0xFF)offs++;
|
|
marker=data[offs];
|
|
offs++;
|
|
/*If we hit EOI* (end of image), or another SOI* (start of image),
|
|
or SOS (start of scan), then stop now.*/
|
|
if(offs>=data_length||(marker>=0xD8&&marker<=0xDA))break;
|
|
/*RST* (restart markers): skip (no segment length).*/
|
|
else if(marker>=0xD0&&marker<=0xD7)continue;
|
|
/*Read the length of the marker segment.*/
|
|
if(data_length-offs<2)break;
|
|
segment_len=data[offs]<<8|data[offs+1];
|
|
if(segment_len<2||data_length-offs<segment_len)break;
|
|
if(marker==0xC0||(marker>0xC0&&marker<0xD0&&(marker&3)!=0)){
|
|
/*Found a SOFn (start of frame) marker segment:*/
|
|
if(segment_len>=8){
|
|
*height=data[offs+3]<<8|data[offs+4];
|
|
*width=data[offs+5]<<8|data[offs+6];
|
|
*depth=data[offs+2]*data[offs+7];
|
|
*colors=0;
|
|
*has_palette=0;
|
|
}
|
|
break;
|
|
}
|
|
/*Other markers: skip the whole marker segment.*/
|
|
offs+=segment_len;
|
|
}
|
|
}
|
|
}
|
|
|
|
#define IMAX(a,b) ((a) > (b) ? (a) : (b))
|
|
|
|
/*Parse a picture SPECIFICATION as given on the command-line.
|
|
spec: The specification.
|
|
error_message: Returns an error message on error.
|
|
seen_file_icons: Bit flags used to track if any pictures of type 1 or type 2
|
|
have already been added, to ensure only one is allowed.
|
|
Return: A Base64-encoded string suitable for use in a METADATA_BLOCK_PICTURE
|
|
tag.*/
|
|
char *parse_picture_specification(const char *spec,
|
|
const char **error_message,
|
|
int *seen_file_icons){
|
|
FILE *picture_file;
|
|
unsigned long picture_type;
|
|
unsigned long width;
|
|
unsigned long height;
|
|
unsigned long depth;
|
|
unsigned long colors;
|
|
const char *mime_type;
|
|
const char *mime_type_end;
|
|
const char *description;
|
|
const char *description_end;
|
|
const char *filename;
|
|
unsigned char *buf;
|
|
char *out;
|
|
size_t cbuf;
|
|
size_t nbuf;
|
|
size_t data_offset;
|
|
size_t data_length;
|
|
size_t b64_length;
|
|
int is_url;
|
|
/*If a filename has a '|' in it, there's no way we can distinguish it from a
|
|
full specification just from the spec string.
|
|
Instead, try to open the file.
|
|
If it exists, the user probably meant the file.*/
|
|
picture_type=3;
|
|
width=height=depth=colors=0;
|
|
mime_type=mime_type_end=description=description_end=filename=spec;
|
|
is_url=0;
|
|
picture_file=fopen(filename,"rb");
|
|
if(picture_file==NULL&&strchr(spec,'|')){
|
|
const char *p;
|
|
char *q;
|
|
/*We don't have a plain file, and there is a pipe character: assume it's
|
|
the full form of the specification.*/
|
|
picture_type=strtoul(spec,&q,10);
|
|
if(*q!='|'||picture_type>20){
|
|
*error_message="invalid picture type";
|
|
return NULL;
|
|
}
|
|
if(picture_type>=1&&picture_type<=2&&(*seen_file_icons&picture_type)){
|
|
*error_message=picture_type==1?
|
|
"only one picture of type 1 (32x32 icon) allowed":
|
|
"only one picture of type 2 (icon) allowed";
|
|
return NULL;
|
|
}
|
|
/*An empty field implies a default of 'Cover (front)'.*/
|
|
if(spec==q)picture_type=3;
|
|
mime_type=q+1;
|
|
mime_type_end=mime_type+strcspn(mime_type,"|");
|
|
if(*mime_type_end!='|'){
|
|
*error_message="invalid picture specification: not enough fields";
|
|
return NULL;
|
|
}
|
|
/*The mime type must be composed of ASCII printable characters 0x20-0x7E.*/
|
|
for(p=mime_type;p<mime_type_end;p++)if(*p<0x20||*p>0x7E){
|
|
*error_message="invalid characters in mime type";
|
|
return NULL;
|
|
}
|
|
is_url=mime_type_end-mime_type==3
|
|
&&strncmp("-->",mime_type,mime_type_end-mime_type)==0;
|
|
description=mime_type_end+1;
|
|
description_end=description+strcspn(description,"|");
|
|
if(*description_end!='|'){
|
|
*error_message="invalid picture specification: not enough fields";
|
|
return NULL;
|
|
}
|
|
p=description_end+1;
|
|
if(*p!='|'){
|
|
width=strtoul(p,&q,10);
|
|
if(*q!='x'){
|
|
*error_message=
|
|
"invalid picture specification: can't parse resolution/color field";
|
|
return NULL;
|
|
}
|
|
p=q+1;
|
|
height=strtoul(p,&q,10);
|
|
if(*q!='x'){
|
|
*error_message=
|
|
"invalid picture specification: can't parse resolution/color field";
|
|
return NULL;
|
|
}
|
|
p=q+1;
|
|
depth=strtoul(p,&q,10);
|
|
if(*q=='/'){
|
|
p=q+1;
|
|
colors=strtoul(p,&q,10);
|
|
}
|
|
if(*q!='|'){
|
|
*error_message=
|
|
"invalid picture specification: can't parse resolution/color field";
|
|
return NULL;
|
|
}
|
|
p=q;
|
|
}
|
|
filename=p+1;
|
|
if(!is_url)picture_file=fopen(filename,"rb");
|
|
}
|
|
/*Buffer size: 8 static 4-byte fields plus 2 dynamic fields, plus the
|
|
file/URL data.
|
|
We reserve at least 10 bytes for the mime type, in case we still need to
|
|
extract it from the file.*/
|
|
data_offset=32+(description_end-description)+IMAX(mime_type_end-mime_type,10);
|
|
buf=NULL;
|
|
if(is_url){
|
|
/*Easy case: just stick the URL at the end.
|
|
We don't do anything to verify it's a valid URL.*/
|
|
data_length=strlen(filename);
|
|
cbuf=nbuf=data_offset+data_length;
|
|
buf=(unsigned char *)malloc(cbuf);
|
|
memcpy(buf+data_offset,filename,data_length);
|
|
}
|
|
else{
|
|
ogg_uint32_t file_width;
|
|
ogg_uint32_t file_height;
|
|
ogg_uint32_t file_depth;
|
|
ogg_uint32_t file_colors;
|
|
int has_palette;
|
|
/*Complicated case: we have a real file.
|
|
Read it in, attempt to parse the mime type and image dimensions if
|
|
necessary, and validate what the user passed in.*/
|
|
if(picture_file==NULL){
|
|
*error_message="error opening picture file";
|
|
return NULL;
|
|
}
|
|
nbuf=data_offset;
|
|
/*Add a reasonable starting image file size.*/
|
|
cbuf=data_offset+65536;
|
|
for(;;){
|
|
unsigned char *new_buf;
|
|
size_t nread;
|
|
new_buf=realloc(buf,cbuf);
|
|
if(new_buf==NULL){
|
|
fclose(picture_file);
|
|
free(buf);
|
|
*error_message="insufficient memory";
|
|
return NULL;
|
|
}
|
|
buf=new_buf;
|
|
nread=fread(buf+nbuf,1,cbuf-nbuf,picture_file);
|
|
nbuf+=nread;
|
|
if(nbuf<cbuf){
|
|
int error;
|
|
error=ferror(picture_file);
|
|
fclose(picture_file);
|
|
if(error){
|
|
free(buf);
|
|
*error_message="error reading picture file";
|
|
return NULL;
|
|
}
|
|
break;
|
|
}
|
|
if(cbuf==0xFFFFFFFF){
|
|
fclose(picture_file);
|
|
free(buf);
|
|
*error_message="file too large";
|
|
return NULL;
|
|
}
|
|
else if(cbuf>0x7FFFFFFFU)cbuf=0xFFFFFFFFU;
|
|
else cbuf=cbuf<<1|1;
|
|
}
|
|
data_length=nbuf-data_offset;
|
|
/*If there was no mimetype, try to extract it from the file data.*/
|
|
if(mime_type_end==mime_type){
|
|
if(is_jpeg(buf+data_offset,data_length)){
|
|
mime_type="image/jpeg";
|
|
mime_type_end=mime_type+10;
|
|
}
|
|
else if(is_png(buf+data_offset,data_length)){
|
|
mime_type="image/png";
|
|
mime_type_end=mime_type+9;
|
|
}
|
|
else if(is_gif(buf+data_offset,data_length)){
|
|
mime_type="image/gif";
|
|
mime_type_end=mime_type+9;
|
|
}
|
|
else{
|
|
free(buf);
|
|
*error_message="unable to guess MIME type from file, "
|
|
"must set it explicitly";
|
|
return NULL;
|
|
}
|
|
}
|
|
/*Try to extract the image dimensions/color information from the file.*/
|
|
file_width=file_height=file_depth=file_colors=0;
|
|
has_palette=-1;
|
|
if(mime_type_end-mime_type==9
|
|
&&oi_strncasecmp("image/png",mime_type,mime_type_end-mime_type)==0){
|
|
extract_png_params(buf+data_offset,data_length,
|
|
&file_width,&file_height,&file_depth,&file_colors,&has_palette);
|
|
}
|
|
else if(mime_type_end-mime_type==9
|
|
&&oi_strncasecmp("image/gif",mime_type,mime_type_end-mime_type)==0){
|
|
extract_gif_params(buf+data_offset,data_length,
|
|
&file_width,&file_height,&file_depth,&file_colors,&has_palette);
|
|
}
|
|
else if(mime_type_end-mime_type==10
|
|
&&oi_strncasecmp("image/jpeg",mime_type,mime_type_end-mime_type)==0){
|
|
extract_jpeg_params(buf+data_offset,data_length,
|
|
&file_width,&file_height,&file_depth,&file_colors,&has_palette);
|
|
}
|
|
if(!width)width=file_width;
|
|
if(!height)height=file_height;
|
|
if(!depth)depth=file_depth;
|
|
if(!colors)colors=file_colors;
|
|
if((file_width&&width!=file_width)
|
|
||(file_height&&height!=file_height)
|
|
||(file_depth&&depth!=file_depth)
|
|
/*We use has_palette to ensure we also reject non-0 user color counts for
|
|
images we've positively identified as non-paletted.*/
|
|
||(has_palette>=0&&colors!=file_colors)){
|
|
free(buf);
|
|
*error_message="invalid picture specification: "
|
|
"resolution/color field does not match file";
|
|
return NULL;
|
|
}
|
|
}
|
|
/*These fields MUST be set correctly OR all set to zero.
|
|
So if any of them (except colors, for which 0 is a valid value) are still
|
|
zero, clear the rest to zero.*/
|
|
if(width==0||height==0||depth==0)width=height=depth=colors=0;
|
|
if(picture_type==1&&(width!=32||height!=32
|
|
||mime_type_end-mime_type!=9
|
|
||oi_strncasecmp("image/png",mime_type,mime_type_end-mime_type)!=0)){
|
|
free(buf);
|
|
*error_message="pictures of type 1 MUST be 32x32 PNGs";
|
|
return NULL;
|
|
}
|
|
/*Build the METADATA_BLOCK_PICTURE buffer.
|
|
We do this backwards from data_offset, because we didn't necessarily know
|
|
how big the mime type string was before we read the data in.*/
|
|
data_offset-=4;
|
|
WRITE_U32_BE(buf+data_offset,(unsigned long)data_length);
|
|
data_offset-=4;
|
|
WRITE_U32_BE(buf+data_offset,colors);
|
|
data_offset-=4;
|
|
WRITE_U32_BE(buf+data_offset,depth);
|
|
data_offset-=4;
|
|
WRITE_U32_BE(buf+data_offset,height);
|
|
data_offset-=4;
|
|
WRITE_U32_BE(buf+data_offset,width);
|
|
data_offset-=description_end-description;
|
|
memcpy(buf+data_offset,description,description_end-description);
|
|
data_offset-=4;
|
|
WRITE_U32_BE(buf+data_offset,(unsigned long)(description_end-description));
|
|
data_offset-=mime_type_end-mime_type;
|
|
memcpy(buf+data_offset,mime_type,mime_type_end-mime_type);
|
|
data_offset-=4;
|
|
WRITE_U32_BE(buf+data_offset,(unsigned long)(mime_type_end-mime_type));
|
|
data_offset-=4;
|
|
WRITE_U32_BE(buf+data_offset,picture_type);
|
|
data_length=nbuf-data_offset;
|
|
b64_length=BASE64_LENGTH(data_length);
|
|
out=(char *)malloc(b64_length+1);
|
|
if(out!=NULL){
|
|
base64_encode(out,(char *)buf+data_offset,data_length);
|
|
if(picture_type>=1&&picture_type<=2)*seen_file_icons|=picture_type;
|
|
}
|
|
free(buf);
|
|
return out;
|
|
}
|