Line | Count | Source |
1 | | /******************************************************************** |
2 | | * * |
3 | | * THIS FILE IS PART OF THE libopusfile SOFTWARE CODEC SOURCE CODE. * |
4 | | * USE, DISTRIBUTION AND REPRODUCTION OF THIS LIBRARY SOURCE IS * |
5 | | * GOVERNED BY A BSD-STYLE SOURCE LICENSE INCLUDED WITH THIS SOURCE * |
6 | | * IN 'COPYING'. PLEASE READ THESE TERMS BEFORE DISTRIBUTING. * |
7 | | * * |
8 | | * THE libopusfile SOURCE CODE IS (C) COPYRIGHT 2012-2020 * |
9 | | * by the Xiph.Org Foundation and contributors https://xiph.org/ * |
10 | | * * |
11 | | ********************************************************************/ |
12 | | #ifdef HAVE_CONFIG_H |
13 | | #include "config.h" |
14 | | #endif |
15 | | |
16 | | #include "internal.h" |
17 | | #include <limits.h> |
18 | | #include <string.h> |
19 | | |
20 | 38.1k | static unsigned op_parse_uint16le(const unsigned char *_data){ |
21 | 38.1k | return _data[0]|_data[1]<<8; |
22 | 38.1k | } |
23 | | |
24 | 38.1k | static int op_parse_int16le(const unsigned char *_data){ |
25 | 38.1k | int ret; |
26 | 38.1k | ret=_data[0]|_data[1]<<8; |
27 | 38.1k | return (ret^0x8000)-0x8000; |
28 | 38.1k | } |
29 | | |
30 | 121k | static opus_uint32 op_parse_uint32le(const unsigned char *_data){ |
31 | 121k | return _data[0]|(opus_uint32)_data[1]<<8| |
32 | 121k | (opus_uint32)_data[2]<<16|(opus_uint32)_data[3]<<24; |
33 | 121k | } |
34 | | |
35 | 0 | static opus_uint32 op_parse_uint32be(const unsigned char *_data){ |
36 | 0 | return _data[3]|(opus_uint32)_data[2]<<8| |
37 | 0 | (opus_uint32)_data[1]<<16|(opus_uint32)_data[0]<<24; |
38 | 0 | } |
39 | | |
40 | 40.0k | int opus_head_parse(OpusHead *_head,const unsigned char *_data,size_t _len){ |
41 | 40.0k | OpusHead head; |
42 | 40.0k | if(_len<8)return OP_ENOTFORMAT; |
43 | 39.0k | if(memcmp(_data,"OpusHead",8)!=0)return OP_ENOTFORMAT; |
44 | 38.1k | if(_len<9)return OP_EBADHEADER; |
45 | 38.1k | head.version=_data[8]; |
46 | 38.1k | if(head.version>15)return OP_EVERSION; |
47 | 38.1k | if(_len<19)return OP_EBADHEADER; |
48 | 38.1k | head.channel_count=_data[9]; |
49 | 38.1k | head.pre_skip=op_parse_uint16le(_data+10); |
50 | 38.1k | head.input_sample_rate=op_parse_uint32le(_data+12); |
51 | 38.1k | head.output_gain=op_parse_int16le(_data+16); |
52 | 38.1k | head.mapping_family=_data[18]; |
53 | 38.1k | if(head.mapping_family==0){ |
54 | 35.6k | if(head.channel_count<1||head.channel_count>2)return OP_EBADHEADER; |
55 | 35.6k | if(head.version<=1&&_len>19)return OP_EBADHEADER; |
56 | 35.6k | head.stream_count=1; |
57 | 35.6k | head.coupled_count=head.channel_count-1; |
58 | 35.6k | if(_head!=NULL){ |
59 | 35.6k | _head->mapping[0]=0; |
60 | 35.6k | _head->mapping[1]=1; |
61 | 35.6k | } |
62 | 35.6k | } |
63 | 2.51k | else if(head.mapping_family==1){ |
64 | 2.50k | size_t size; |
65 | 2.50k | int ci; |
66 | 2.50k | if(head.channel_count<1||head.channel_count>8)return OP_EBADHEADER; |
67 | 2.50k | size=21+head.channel_count; |
68 | 2.50k | if(_len<size||head.version<=1&&_len>size)return OP_EBADHEADER; |
69 | 2.50k | head.stream_count=_data[19]; |
70 | 2.50k | if(head.stream_count<1)return OP_EBADHEADER; |
71 | 2.50k | head.coupled_count=_data[20]; |
72 | 2.50k | if(head.coupled_count>head.stream_count)return OP_EBADHEADER; |
73 | 9.73k | for(ci=0;ci<head.channel_count;ci++){ |
74 | 7.22k | if(_data[21+ci]>=head.stream_count+head.coupled_count |
75 | 4.66k | &&_data[21+ci]!=255){ |
76 | 0 | return OP_EBADHEADER; |
77 | 0 | } |
78 | 7.22k | } |
79 | 2.50k | if(_head!=NULL)memcpy(_head->mapping,_data+21,head.channel_count); |
80 | 2.50k | } |
81 | | /*General purpose players should not attempt to play back content with |
82 | | channel mapping family 255.*/ |
83 | 4 | else if(head.mapping_family==255)return OP_EIMPL; |
84 | | /*No other channel mapping families are currently defined.*/ |
85 | 4 | else return OP_EBADHEADER; |
86 | 38.1k | if(_head!=NULL)memcpy(_head,&head,head.mapping-(unsigned char *)&head); |
87 | 38.1k | return 0; |
88 | 38.1k | } |
89 | | |
90 | 37.7k | void opus_tags_init(OpusTags *_tags){ |
91 | 37.7k | memset(_tags,0,sizeof(*_tags)); |
92 | 37.7k | } |
93 | | |
94 | 37.7k | void opus_tags_clear(OpusTags *_tags){ |
95 | 37.7k | int ncomments; |
96 | 37.7k | int ci; |
97 | 37.7k | ncomments=_tags->comments; |
98 | 37.7k | if(_tags->user_comments!=NULL)ncomments++; |
99 | 41 | else{ |
100 | 41 | OP_ASSERT(ncomments==0); |
101 | 41 | } |
102 | 83.2k | for(ci=ncomments;ci-->0;)_ogg_free(_tags->user_comments[ci]); |
103 | 37.7k | _ogg_free(_tags->user_comments); |
104 | 37.7k | _ogg_free(_tags->comment_lengths); |
105 | 37.7k | _ogg_free(_tags->vendor); |
106 | 37.7k | } |
107 | | |
108 | | /*Ensure there's room for up to _ncomments comments.*/ |
109 | 37.7k | static int op_tags_ensure_capacity(OpusTags *_tags,size_t _ncomments){ |
110 | 37.7k | char **user_comments; |
111 | 37.7k | int *comment_lengths; |
112 | 37.7k | int cur_ncomments; |
113 | 37.7k | size_t size; |
114 | 37.7k | if(OP_UNLIKELY(_ncomments>=(size_t)INT_MAX))return OP_EFAULT; |
115 | 37.7k | size=sizeof(*_tags->comment_lengths)*(_ncomments+1); |
116 | 37.7k | if(size/sizeof(*_tags->comment_lengths)!=_ncomments+1)return OP_EFAULT; |
117 | 37.7k | cur_ncomments=_tags->comments; |
118 | | /*We only support growing. |
119 | | Trimming requires cleaning up the allocated strings in the old space, and |
120 | | is best handled separately if it's ever needed.*/ |
121 | 37.7k | OP_ASSERT(_ncomments>=(size_t)cur_ncomments); |
122 | 37.7k | comment_lengths=(int *)_ogg_realloc(_tags->comment_lengths,size); |
123 | 37.7k | if(OP_UNLIKELY(comment_lengths==NULL))return OP_EFAULT; |
124 | 37.7k | if(_tags->comment_lengths==NULL){ |
125 | 37.7k | OP_ASSERT(cur_ncomments==0); |
126 | 37.7k | comment_lengths[cur_ncomments]=0; |
127 | 37.7k | } |
128 | 37.7k | comment_lengths[_ncomments]=comment_lengths[cur_ncomments]; |
129 | 37.7k | _tags->comment_lengths=comment_lengths; |
130 | 37.7k | size=sizeof(*_tags->user_comments)*(_ncomments+1); |
131 | 37.7k | if(size/sizeof(*_tags->user_comments)!=_ncomments+1)return OP_EFAULT; |
132 | 37.7k | user_comments=(char **)_ogg_realloc(_tags->user_comments,size); |
133 | 37.7k | if(OP_UNLIKELY(user_comments==NULL))return OP_EFAULT; |
134 | 37.7k | if(_tags->user_comments==NULL){ |
135 | 37.7k | OP_ASSERT(cur_ncomments==0); |
136 | 37.7k | user_comments[cur_ncomments]=NULL; |
137 | 37.7k | } |
138 | 37.7k | user_comments[_ncomments]=user_comments[cur_ncomments]; |
139 | 37.7k | _tags->user_comments=user_comments; |
140 | 37.7k | return 0; |
141 | 37.7k | } |
142 | | |
143 | | /*Duplicate a (possibly non-NUL terminated) string with a known length.*/ |
144 | 45.4k | static char *op_strdup_with_len(const char *_s,size_t _len){ |
145 | 45.4k | size_t size; |
146 | 45.4k | char *ret; |
147 | 45.4k | size=sizeof(*ret)*(_len+1); |
148 | 45.4k | if(OP_UNLIKELY(size<_len))return NULL; |
149 | 45.4k | ret=(char *)_ogg_malloc(size); |
150 | 45.4k | if(OP_LIKELY(ret!=NULL)){ |
151 | 45.4k | ret=(char *)memcpy(ret,_s,sizeof(*ret)*_len); |
152 | 45.4k | ret[_len]='\0'; |
153 | 45.4k | } |
154 | 45.4k | return ret; |
155 | 45.4k | } |
156 | | |
157 | | /*The actual implementation of opus_tags_parse(). |
158 | | Unlike the public API, this function requires _tags to already be |
159 | | initialized, modifies its contents before success is guaranteed, and assumes |
160 | | the caller will clear it on error.*/ |
161 | | static int opus_tags_parse_impl(OpusTags *_tags, |
162 | 37.7k | const unsigned char *_data,size_t _len){ |
163 | 37.7k | opus_uint32 count; |
164 | 37.7k | size_t len; |
165 | 37.7k | int ncomments; |
166 | 37.7k | int ci; |
167 | 37.7k | len=_len; |
168 | 37.7k | if(len<8)return OP_ENOTFORMAT; |
169 | 37.7k | if(memcmp(_data,"OpusTags",8)!=0)return OP_ENOTFORMAT; |
170 | 37.7k | if(len<16)return OP_EBADHEADER; |
171 | 37.7k | _data+=8; |
172 | 37.7k | len-=8; |
173 | 37.7k | count=op_parse_uint32le(_data); |
174 | 37.7k | _data+=4; |
175 | 37.7k | len-=4; |
176 | 37.7k | if(count>len)return OP_EBADHEADER; |
177 | 37.7k | if(_tags!=NULL){ |
178 | 37.7k | _tags->vendor=op_strdup_with_len((char *)_data,count); |
179 | 37.7k | if(_tags->vendor==NULL)return OP_EFAULT; |
180 | 37.7k | } |
181 | 37.7k | _data+=count; |
182 | 37.7k | len-=count; |
183 | 37.7k | if(len<4)return OP_EBADHEADER; |
184 | 37.7k | count=op_parse_uint32le(_data); |
185 | 37.7k | _data+=4; |
186 | 37.7k | len-=4; |
187 | | /*Check to make sure there's minimally sufficient data left in the packet.*/ |
188 | 37.7k | if(count>len>>2)return OP_EBADHEADER; |
189 | | /*Check for overflow (the API limits this to an int).*/ |
190 | 37.7k | if(count>(opus_uint32)INT_MAX-1)return OP_EFAULT; |
191 | 37.7k | if(_tags!=NULL){ |
192 | 37.7k | int ret; |
193 | 37.7k | ret=op_tags_ensure_capacity(_tags,count); |
194 | 37.7k | if(ret<0)return ret; |
195 | 37.7k | } |
196 | 37.7k | ncomments=(int)count; |
197 | 45.4k | for(ci=0;ci<ncomments;ci++){ |
198 | | /*Check to make sure there's minimally sufficient data left in the packet.*/ |
199 | 7.72k | if((size_t)(ncomments-ci)>len>>2)return OP_EBADHEADER; |
200 | 7.72k | count=op_parse_uint32le(_data); |
201 | 7.72k | _data+=4; |
202 | 7.72k | len-=4; |
203 | 7.72k | if(count>len)return OP_EBADHEADER; |
204 | | /*Check for overflow (the API limits this to an int).*/ |
205 | 7.72k | if(count>(opus_uint32)INT_MAX)return OP_EFAULT; |
206 | 7.72k | if(_tags!=NULL){ |
207 | 7.72k | _tags->user_comments[ci]=op_strdup_with_len((char *)_data,count); |
208 | 7.72k | if(_tags->user_comments[ci]==NULL)return OP_EFAULT; |
209 | 7.72k | _tags->comment_lengths[ci]=(int)count; |
210 | 7.72k | _tags->comments=ci+1; |
211 | | /*Needed by opus_tags_clear() if we fail before parsing the (optional) |
212 | | binary metadata.*/ |
213 | 7.72k | _tags->user_comments[ci+1]=NULL; |
214 | 7.72k | } |
215 | 7.72k | _data+=count; |
216 | 7.72k | len-=count; |
217 | 7.72k | } |
218 | 37.7k | if(len>0&&(_data[0]&1)){ |
219 | 12.8k | if(len>(opus_uint32)INT_MAX)return OP_EFAULT; |
220 | 12.8k | if(_tags!=NULL){ |
221 | 12.8k | _tags->user_comments[ncomments]=(char *)_ogg_malloc(len); |
222 | 12.8k | if(OP_UNLIKELY(_tags->user_comments[ncomments]==NULL))return OP_EFAULT; |
223 | 12.8k | memcpy(_tags->user_comments[ncomments],_data,len); |
224 | 12.8k | _tags->comment_lengths[ncomments]=(int)len; |
225 | 12.8k | } |
226 | 12.8k | } |
227 | 37.7k | return 0; |
228 | 37.7k | } |
229 | | |
230 | 37.7k | int opus_tags_parse(OpusTags *_tags,const unsigned char *_data,size_t _len){ |
231 | 37.7k | if(_tags!=NULL){ |
232 | 37.7k | OpusTags tags; |
233 | 37.7k | int ret; |
234 | 37.7k | opus_tags_init(&tags); |
235 | 37.7k | ret=opus_tags_parse_impl(&tags,_data,_len); |
236 | 37.7k | if(ret<0)opus_tags_clear(&tags); |
237 | 37.7k | else *_tags=*&tags; |
238 | 37.7k | return ret; |
239 | 37.7k | } |
240 | 0 | else return opus_tags_parse_impl(NULL,_data,_len); |
241 | 37.7k | } |
242 | | |
243 | | /*The actual implementation of opus_tags_copy(). |
244 | | Unlike the public API, this function requires _dst to already be |
245 | | initialized, modifies its contents before success is guaranteed, and assumes |
246 | | the caller will clear it on error.*/ |
247 | 0 | static int opus_tags_copy_impl(OpusTags *_dst,const OpusTags *_src){ |
248 | 0 | char *vendor; |
249 | 0 | int ncomments; |
250 | 0 | int ret; |
251 | 0 | int ci; |
252 | 0 | vendor=_src->vendor; |
253 | 0 | _dst->vendor=op_strdup_with_len(vendor,strlen(vendor)); |
254 | 0 | if(OP_UNLIKELY(_dst->vendor==NULL))return OP_EFAULT; |
255 | 0 | ncomments=_src->comments; |
256 | 0 | ret=op_tags_ensure_capacity(_dst,ncomments); |
257 | 0 | if(OP_UNLIKELY(ret<0))return ret; |
258 | 0 | for(ci=0;ci<ncomments;ci++){ |
259 | 0 | int len; |
260 | 0 | len=_src->comment_lengths[ci]; |
261 | 0 | OP_ASSERT(len>=0); |
262 | 0 | _dst->user_comments[ci]=op_strdup_with_len(_src->user_comments[ci],len); |
263 | 0 | if(OP_UNLIKELY(_dst->user_comments[ci]==NULL))return OP_EFAULT; |
264 | 0 | _dst->comment_lengths[ci]=len; |
265 | 0 | _dst->comments=ci+1; |
266 | 0 | } |
267 | 0 | if(_src->comment_lengths!=NULL){ |
268 | 0 | int len; |
269 | 0 | len=_src->comment_lengths[ncomments]; |
270 | 0 | if(len>0){ |
271 | 0 | _dst->user_comments[ncomments]=(char *)_ogg_malloc(len); |
272 | 0 | if(OP_UNLIKELY(_dst->user_comments[ncomments]==NULL))return OP_EFAULT; |
273 | 0 | memcpy(_dst->user_comments[ncomments],_src->user_comments[ncomments],len); |
274 | 0 | _dst->comment_lengths[ncomments]=len; |
275 | 0 | } |
276 | 0 | } |
277 | 0 | return 0; |
278 | 0 | } |
279 | | |
280 | 0 | int opus_tags_copy(OpusTags *_dst,const OpusTags *_src){ |
281 | 0 | OpusTags dst; |
282 | 0 | int ret; |
283 | 0 | opus_tags_init(&dst); |
284 | 0 | ret=opus_tags_copy_impl(&dst,_src); |
285 | 0 | if(OP_UNLIKELY(ret<0))opus_tags_clear(&dst); |
286 | 0 | else *_dst=*&dst; |
287 | 0 | return ret; |
288 | 0 | } |
289 | | |
290 | 0 | int opus_tags_add(OpusTags *_tags,const char *_tag,const char *_value){ |
291 | 0 | char *comment; |
292 | 0 | size_t tag_len; |
293 | 0 | size_t value_len; |
294 | 0 | int ncomments; |
295 | 0 | int ret; |
296 | 0 | ncomments=_tags->comments; |
297 | 0 | ret=op_tags_ensure_capacity(_tags,ncomments+1); |
298 | 0 | if(OP_UNLIKELY(ret<0))return ret; |
299 | 0 | tag_len=strlen(_tag); |
300 | 0 | value_len=strlen(_value); |
301 | | /*+2 for '=' and '\0'.*/ |
302 | 0 | if(tag_len+value_len<tag_len)return OP_EFAULT; |
303 | 0 | if(tag_len+value_len>(size_t)INT_MAX-2)return OP_EFAULT; |
304 | 0 | comment=(char *)_ogg_malloc(sizeof(*comment)*(tag_len+value_len+2)); |
305 | 0 | if(OP_UNLIKELY(comment==NULL))return OP_EFAULT; |
306 | 0 | memcpy(comment,_tag,sizeof(*comment)*tag_len); |
307 | 0 | comment[tag_len]='='; |
308 | 0 | memcpy(comment+tag_len+1,_value,sizeof(*comment)*(value_len+1)); |
309 | 0 | _tags->user_comments[ncomments]=comment; |
310 | 0 | _tags->comment_lengths[ncomments]=(int)(tag_len+value_len+1); |
311 | 0 | _tags->comments=ncomments+1; |
312 | 0 | return 0; |
313 | 0 | } |
314 | | |
315 | 0 | int opus_tags_add_comment(OpusTags *_tags,const char *_comment){ |
316 | 0 | char *comment; |
317 | 0 | int comment_len; |
318 | 0 | int ncomments; |
319 | 0 | int ret; |
320 | 0 | ncomments=_tags->comments; |
321 | 0 | ret=op_tags_ensure_capacity(_tags,ncomments+1); |
322 | 0 | if(OP_UNLIKELY(ret<0))return ret; |
323 | 0 | comment_len=(int)strlen(_comment); |
324 | 0 | comment=op_strdup_with_len(_comment,comment_len); |
325 | 0 | if(OP_UNLIKELY(comment==NULL))return OP_EFAULT; |
326 | 0 | _tags->user_comments[ncomments]=comment; |
327 | 0 | _tags->comment_lengths[ncomments]=comment_len; |
328 | 0 | _tags->comments=ncomments+1; |
329 | 0 | return 0; |
330 | 0 | } |
331 | | |
332 | | int opus_tags_set_binary_suffix(OpusTags *_tags, |
333 | 0 | const unsigned char *_data,int _len){ |
334 | 0 | unsigned char *binary_suffix_data; |
335 | 0 | int ncomments; |
336 | 0 | int ret; |
337 | 0 | if(_len<0||_len>0&&(_data==NULL||!(_data[0]&1)))return OP_EINVAL; |
338 | 0 | ncomments=_tags->comments; |
339 | 0 | ret=op_tags_ensure_capacity(_tags,ncomments); |
340 | 0 | if(OP_UNLIKELY(ret<0))return ret; |
341 | 0 | if(_len!=0){ |
342 | 0 | binary_suffix_data= |
343 | 0 | (unsigned char *)_ogg_realloc(_tags->user_comments[ncomments],_len); |
344 | 0 | if(OP_UNLIKELY(binary_suffix_data==NULL))return OP_EFAULT; |
345 | 0 | memcpy(binary_suffix_data,_data,_len); |
346 | 0 | } |
347 | 0 | else{ |
348 | 0 | _ogg_free(_tags->user_comments[ncomments]); |
349 | 0 | binary_suffix_data=NULL; |
350 | 0 | } |
351 | 0 | _tags->user_comments[ncomments]=(char *)binary_suffix_data; |
352 | 0 | _tags->comment_lengths[ncomments]=_len; |
353 | 0 | return 0; |
354 | 0 | } |
355 | | |
356 | 0 | int opus_tagcompare(const char *_tag_name,const char *_comment){ |
357 | 0 | size_t tag_len; |
358 | 0 | tag_len=strlen(_tag_name); |
359 | 0 | if(OP_UNLIKELY(tag_len>(size_t)INT_MAX))return -1; |
360 | 0 | return opus_tagncompare(_tag_name,(int)tag_len,_comment); |
361 | 0 | } |
362 | | |
363 | 6.26k | int opus_tagncompare(const char *_tag_name,int _tag_len,const char *_comment){ |
364 | 6.26k | int ret; |
365 | 6.26k | OP_ASSERT(_tag_len>=0); |
366 | 6.26k | ret=op_strncasecmp(_tag_name,_comment,_tag_len); |
367 | 6.26k | return ret?ret:'='-_comment[_tag_len]; |
368 | 6.26k | } |
369 | | |
370 | 0 | const char *opus_tags_query(const OpusTags *_tags,const char *_tag,int _count){ |
371 | 0 | char **user_comments; |
372 | 0 | size_t tag_len; |
373 | 0 | int found; |
374 | 0 | int ncomments; |
375 | 0 | int ci; |
376 | 0 | tag_len=strlen(_tag); |
377 | 0 | if(OP_UNLIKELY(tag_len>(size_t)INT_MAX))return NULL; |
378 | 0 | ncomments=_tags->comments; |
379 | 0 | user_comments=_tags->user_comments; |
380 | 0 | found=0; |
381 | 0 | for(ci=0;ci<ncomments;ci++){ |
382 | 0 | if(!opus_tagncompare(_tag,(int)tag_len,user_comments[ci])){ |
383 | | /*We return a pointer to the data, not a copy.*/ |
384 | 0 | if(_count==found++)return user_comments[ci]+tag_len+1; |
385 | 0 | } |
386 | 0 | } |
387 | | /*Didn't find anything.*/ |
388 | 0 | return NULL; |
389 | 0 | } |
390 | | |
391 | 0 | int opus_tags_query_count(const OpusTags *_tags,const char *_tag){ |
392 | 0 | char **user_comments; |
393 | 0 | size_t tag_len; |
394 | 0 | int found; |
395 | 0 | int ncomments; |
396 | 0 | int ci; |
397 | 0 | tag_len=strlen(_tag); |
398 | 0 | if(OP_UNLIKELY(tag_len>(size_t)INT_MAX))return 0; |
399 | 0 | ncomments=_tags->comments; |
400 | 0 | user_comments=_tags->user_comments; |
401 | 0 | found=0; |
402 | 0 | for(ci=0;ci<ncomments;ci++){ |
403 | 0 | if(!opus_tagncompare(_tag,(int)tag_len,user_comments[ci]))found++; |
404 | 0 | } |
405 | 0 | return found; |
406 | 0 | } |
407 | | |
408 | | const unsigned char *opus_tags_get_binary_suffix(const OpusTags *_tags, |
409 | 13.0k | int *_len){ |
410 | 13.0k | int ncomments; |
411 | 13.0k | int len; |
412 | 13.0k | ncomments=_tags->comments; |
413 | 13.0k | len=_tags->comment_lengths==NULL?0:_tags->comment_lengths[ncomments]; |
414 | 13.0k | *_len=len; |
415 | 13.0k | OP_ASSERT(len==0||_tags->user_comments!=NULL); |
416 | 13.0k | return len>0?(const unsigned char *)_tags->user_comments[ncomments]:NULL; |
417 | 13.0k | } |
418 | | |
419 | | static int opus_tags_get_gain(const OpusTags *_tags,int *_gain_q8, |
420 | 0 | const char *_tag_name,size_t _tag_len){ |
421 | 0 | char **comments; |
422 | 0 | int ncomments; |
423 | 0 | int ci; |
424 | 0 | comments=_tags->user_comments; |
425 | 0 | ncomments=_tags->comments; |
426 | | /*Look for the first valid tag with the name _tag_name and use that.*/ |
427 | 0 | for(ci=0;ci<ncomments;ci++){ |
428 | 0 | OP_ASSERT(_tag_len<=(size_t)INT_MAX); |
429 | 0 | if(opus_tagncompare(_tag_name,(int)_tag_len,comments[ci])==0){ |
430 | 0 | char *p; |
431 | 0 | opus_int32 gain_q8; |
432 | 0 | int negative; |
433 | 0 | p=comments[ci]+_tag_len+1; |
434 | 0 | negative=0; |
435 | 0 | if(*p=='-'){ |
436 | 0 | negative=-1; |
437 | 0 | p++; |
438 | 0 | } |
439 | 0 | else if(*p=='+')p++; |
440 | 0 | gain_q8=0; |
441 | 0 | while(*p>='0'&&*p<='9'){ |
442 | 0 | gain_q8=10*gain_q8+*p-'0'; |
443 | 0 | if(gain_q8>32767-negative)break; |
444 | 0 | p++; |
445 | 0 | } |
446 | | /*This didn't look like a signed 16-bit decimal integer. |
447 | | Not a valid gain tag.*/ |
448 | 0 | if(*p!='\0')continue; |
449 | 0 | *_gain_q8=(int)(gain_q8+negative^negative); |
450 | 0 | return 0; |
451 | 0 | } |
452 | 0 | } |
453 | 0 | return OP_FALSE; |
454 | 0 | } |
455 | | |
456 | 0 | int opus_tags_get_album_gain(const OpusTags *_tags,int *_gain_q8){ |
457 | 0 | return opus_tags_get_gain(_tags,_gain_q8,"R128_ALBUM_GAIN",15); |
458 | 0 | } |
459 | | |
460 | 0 | int opus_tags_get_track_gain(const OpusTags *_tags,int *_gain_q8){ |
461 | 0 | return opus_tags_get_gain(_tags,_gain_q8,"R128_TRACK_GAIN",15); |
462 | 0 | } |
463 | | |
464 | 0 | static int op_is_jpeg(const unsigned char *_buf,size_t _buf_sz){ |
465 | 0 | return _buf_sz>=3&&memcmp(_buf,"\xFF\xD8\xFF",3)==0; |
466 | 0 | } |
467 | | |
468 | | /*Tries to extract the width, height, bits per pixel, and palette size of a |
469 | | JPEG. |
470 | | On failure, simply leaves its outputs unmodified.*/ |
471 | | static void op_extract_jpeg_params(const unsigned char *_buf,size_t _buf_sz, |
472 | | opus_uint32 *_width,opus_uint32 *_height, |
473 | 0 | opus_uint32 *_depth,opus_uint32 *_colors,int *_has_palette){ |
474 | 0 | if(op_is_jpeg(_buf,_buf_sz)){ |
475 | 0 | size_t offs; |
476 | 0 | offs=2; |
477 | 0 | for(;;){ |
478 | 0 | size_t segment_len; |
479 | 0 | int marker; |
480 | 0 | while(offs<_buf_sz&&_buf[offs]!=0xFF)offs++; |
481 | 0 | while(offs<_buf_sz&&_buf[offs]==0xFF)offs++; |
482 | 0 | marker=_buf[offs]; |
483 | 0 | offs++; |
484 | | /*If we hit EOI* (end of image), or another SOI* (start of image), |
485 | | or SOS (start of scan), then stop now.*/ |
486 | 0 | if(offs>=_buf_sz||(marker>=0xD8&&marker<=0xDA))break; |
487 | | /*RST* (restart markers): skip (no segment length).*/ |
488 | 0 | else if(marker>=0xD0&&marker<=0xD7)continue; |
489 | | /*Read the length of the marker segment.*/ |
490 | 0 | if(_buf_sz-offs<2)break; |
491 | 0 | segment_len=_buf[offs]<<8|_buf[offs+1]; |
492 | 0 | if(segment_len<2||_buf_sz-offs<segment_len)break; |
493 | 0 | if(marker==0xC0||(marker>0xC0&&marker<0xD0&&(marker&3)!=0)){ |
494 | | /*Found a SOFn (start of frame) marker segment:*/ |
495 | 0 | if(segment_len>=8){ |
496 | 0 | *_height=_buf[offs+3]<<8|_buf[offs+4]; |
497 | 0 | *_width=_buf[offs+5]<<8|_buf[offs+6]; |
498 | 0 | *_depth=_buf[offs+2]*_buf[offs+7]; |
499 | 0 | *_colors=0; |
500 | 0 | *_has_palette=0; |
501 | 0 | } |
502 | 0 | break; |
503 | 0 | } |
504 | | /*Other markers: skip the whole marker segment.*/ |
505 | 0 | offs+=segment_len; |
506 | 0 | } |
507 | 0 | } |
508 | 0 | } |
509 | | |
510 | 0 | static int op_is_png(const unsigned char *_buf,size_t _buf_sz){ |
511 | 0 | return _buf_sz>=8&&memcmp(_buf,"\x89PNG\x0D\x0A\x1A\x0A",8)==0; |
512 | 0 | } |
513 | | |
514 | | /*Tries to extract the width, height, bits per pixel, and palette size of a |
515 | | PNG. |
516 | | On failure, simply leaves its outputs unmodified.*/ |
517 | | static void op_extract_png_params(const unsigned char *_buf,size_t _buf_sz, |
518 | | opus_uint32 *_width,opus_uint32 *_height, |
519 | 0 | opus_uint32 *_depth,opus_uint32 *_colors,int *_has_palette){ |
520 | 0 | if(op_is_png(_buf,_buf_sz)){ |
521 | 0 | size_t offs; |
522 | 0 | offs=8; |
523 | 0 | while(_buf_sz-offs>=12){ |
524 | 0 | ogg_uint32_t chunk_len; |
525 | 0 | chunk_len=op_parse_uint32be(_buf+offs); |
526 | 0 | if(chunk_len>_buf_sz-(offs+12))break; |
527 | 0 | else if(chunk_len==13&&memcmp(_buf+offs+4,"IHDR",4)==0){ |
528 | 0 | int color_type; |
529 | 0 | *_width=op_parse_uint32be(_buf+offs+8); |
530 | 0 | *_height=op_parse_uint32be(_buf+offs+12); |
531 | 0 | color_type=_buf[offs+17]; |
532 | 0 | if(color_type==3){ |
533 | 0 | *_depth=24; |
534 | 0 | *_has_palette=1; |
535 | 0 | } |
536 | 0 | else{ |
537 | 0 | int sample_depth; |
538 | 0 | sample_depth=_buf[offs+16]; |
539 | 0 | if(color_type==0)*_depth=sample_depth; |
540 | 0 | else if(color_type==2)*_depth=sample_depth*3; |
541 | 0 | else if(color_type==4)*_depth=sample_depth*2; |
542 | 0 | else if(color_type==6)*_depth=sample_depth*4; |
543 | 0 | *_colors=0; |
544 | 0 | *_has_palette=0; |
545 | 0 | break; |
546 | 0 | } |
547 | 0 | } |
548 | 0 | else if(*_has_palette>0&&memcmp(_buf+offs+4,"PLTE",4)==0){ |
549 | 0 | *_colors=chunk_len/3; |
550 | 0 | break; |
551 | 0 | } |
552 | 0 | offs+=12+chunk_len; |
553 | 0 | } |
554 | 0 | } |
555 | 0 | } |
556 | | |
557 | 0 | static int op_is_gif(const unsigned char *_buf,size_t _buf_sz){ |
558 | 0 | return _buf_sz>=6&&(memcmp(_buf,"GIF87a",6)==0||memcmp(_buf,"GIF89a",6)==0); |
559 | 0 | } |
560 | | |
561 | | /*Tries to extract the width, height, bits per pixel, and palette size of a |
562 | | GIF. |
563 | | On failure, simply leaves its outputs unmodified.*/ |
564 | | static void op_extract_gif_params(const unsigned char *_buf,size_t _buf_sz, |
565 | | opus_uint32 *_width,opus_uint32 *_height, |
566 | 0 | opus_uint32 *_depth,opus_uint32 *_colors,int *_has_palette){ |
567 | 0 | if(op_is_gif(_buf,_buf_sz)&&_buf_sz>=14){ |
568 | 0 | *_width=_buf[6]|_buf[7]<<8; |
569 | 0 | *_height=_buf[8]|_buf[9]<<8; |
570 | | /*libFLAC hard-codes the depth to 24.*/ |
571 | 0 | *_depth=24; |
572 | 0 | *_colors=1<<((_buf[10]&7)+1); |
573 | 0 | *_has_palette=1; |
574 | 0 | } |
575 | 0 | } |
576 | | |
577 | | /*The actual implementation of opus_picture_tag_parse(). |
578 | | Unlike the public API, this function requires _pic to already be |
579 | | initialized, modifies its contents before success is guaranteed, and assumes |
580 | | the caller will clear it on error.*/ |
581 | | static int opus_picture_tag_parse_impl(OpusPictureTag *_pic,const char *_tag, |
582 | 0 | unsigned char *_buf,size_t _buf_sz,size_t _base64_sz){ |
583 | 0 | opus_int32 picture_type; |
584 | 0 | opus_uint32 mime_type_length; |
585 | 0 | char *mime_type; |
586 | 0 | opus_uint32 description_length; |
587 | 0 | char *description; |
588 | 0 | opus_uint32 width; |
589 | 0 | opus_uint32 height; |
590 | 0 | opus_uint32 depth; |
591 | 0 | opus_uint32 colors; |
592 | 0 | opus_uint32 data_length; |
593 | 0 | opus_uint32 file_width; |
594 | 0 | opus_uint32 file_height; |
595 | 0 | opus_uint32 file_depth; |
596 | 0 | opus_uint32 file_colors; |
597 | 0 | int format; |
598 | 0 | int has_palette; |
599 | 0 | int colors_set; |
600 | 0 | size_t i; |
601 | | /*Decode the BASE64 data.*/ |
602 | 0 | OP_ASSERT(_base64_sz>=11); |
603 | 0 | for(i=0;i<_base64_sz;i++){ |
604 | 0 | opus_uint32 value; |
605 | 0 | int j; |
606 | 0 | value=0; |
607 | 0 | for(j=0;j<4;j++){ |
608 | 0 | unsigned c; |
609 | 0 | unsigned d; |
610 | 0 | c=(unsigned char)_tag[4*i+j]; |
611 | 0 | if(c=='+')d=62; |
612 | 0 | else if(c=='/')d=63; |
613 | 0 | else if(c>='0'&&c<='9')d=52+c-'0'; |
614 | 0 | else if(c>='a'&&c<='z')d=26+c-'a'; |
615 | 0 | else if(c>='A'&&c<='Z')d=c-'A'; |
616 | 0 | else if(c=='='&&3*i+j>_buf_sz)d=0; |
617 | 0 | else return OP_ENOTFORMAT; |
618 | 0 | value=value<<6|d; |
619 | 0 | } |
620 | 0 | _buf[3*i]=(unsigned char)(value>>16); |
621 | 0 | if(3*i+1<_buf_sz){ |
622 | 0 | _buf[3*i+1]=(unsigned char)(value>>8); |
623 | 0 | if(3*i+2<_buf_sz)_buf[3*i+2]=(unsigned char)value; |
624 | 0 | } |
625 | 0 | } |
626 | 0 | i=0; |
627 | 0 | picture_type=op_parse_uint32be(_buf+i); |
628 | 0 | i+=4; |
629 | | /*Extract the MIME type.*/ |
630 | 0 | mime_type_length=op_parse_uint32be(_buf+i); |
631 | 0 | i+=4; |
632 | 0 | if(mime_type_length>_buf_sz-32)return OP_ENOTFORMAT; |
633 | 0 | mime_type=(char *)_ogg_malloc(sizeof(*_pic->mime_type)*(mime_type_length+1)); |
634 | 0 | if(mime_type==NULL)return OP_EFAULT; |
635 | 0 | memcpy(mime_type,_buf+i,sizeof(*mime_type)*mime_type_length); |
636 | 0 | mime_type[mime_type_length]='\0'; |
637 | 0 | _pic->mime_type=mime_type; |
638 | 0 | i+=mime_type_length; |
639 | | /*Extract the description string.*/ |
640 | 0 | description_length=op_parse_uint32be(_buf+i); |
641 | 0 | i+=4; |
642 | 0 | if(description_length>_buf_sz-mime_type_length-32)return OP_ENOTFORMAT; |
643 | 0 | description= |
644 | 0 | (char *)_ogg_malloc(sizeof(*_pic->mime_type)*(description_length+1)); |
645 | 0 | if(description==NULL)return OP_EFAULT; |
646 | 0 | memcpy(description,_buf+i,sizeof(*description)*description_length); |
647 | 0 | description[description_length]='\0'; |
648 | 0 | _pic->description=description; |
649 | 0 | i+=description_length; |
650 | | /*Extract the remaining fields.*/ |
651 | 0 | width=op_parse_uint32be(_buf+i); |
652 | 0 | i+=4; |
653 | 0 | height=op_parse_uint32be(_buf+i); |
654 | 0 | i+=4; |
655 | 0 | depth=op_parse_uint32be(_buf+i); |
656 | 0 | i+=4; |
657 | 0 | colors=op_parse_uint32be(_buf+i); |
658 | 0 | i+=4; |
659 | | /*If one of these is set, they all must be, but colors==0 is a valid value.*/ |
660 | 0 | colors_set=width!=0||height!=0||depth!=0||colors!=0; |
661 | 0 | if((width==0||height==0||depth==0)&&colors_set)return OP_ENOTFORMAT; |
662 | 0 | data_length=op_parse_uint32be(_buf+i); |
663 | 0 | i+=4; |
664 | 0 | if(data_length>_buf_sz-i)return OP_ENOTFORMAT; |
665 | | /*Trim extraneous data so we don't copy it below.*/ |
666 | 0 | _buf_sz=i+data_length; |
667 | | /*Attempt to determine the image format.*/ |
668 | 0 | format=OP_PIC_FORMAT_UNKNOWN; |
669 | 0 | if(mime_type_length==3&&strcmp(mime_type,"-->")==0){ |
670 | 0 | format=OP_PIC_FORMAT_URL; |
671 | | /*Picture type 1 must be a 32x32 PNG.*/ |
672 | 0 | if(picture_type==1&&(width!=0||height!=0)&&(width!=32||height!=32)){ |
673 | 0 | return OP_ENOTFORMAT; |
674 | 0 | } |
675 | | /*Append a terminating NUL for the convenience of our callers.*/ |
676 | 0 | _buf[_buf_sz++]='\0'; |
677 | 0 | } |
678 | 0 | else{ |
679 | 0 | if(mime_type_length==10 |
680 | 0 | &&op_strncasecmp(mime_type,"image/jpeg",mime_type_length)==0){ |
681 | 0 | if(op_is_jpeg(_buf+i,data_length))format=OP_PIC_FORMAT_JPEG; |
682 | 0 | } |
683 | 0 | else if(mime_type_length==9 |
684 | 0 | &&op_strncasecmp(mime_type,"image/png",mime_type_length)==0){ |
685 | 0 | if(op_is_png(_buf+i,data_length))format=OP_PIC_FORMAT_PNG; |
686 | 0 | } |
687 | 0 | else if(mime_type_length==9 |
688 | 0 | &&op_strncasecmp(mime_type,"image/gif",mime_type_length)==0){ |
689 | 0 | if(op_is_gif(_buf+i,data_length))format=OP_PIC_FORMAT_GIF; |
690 | 0 | } |
691 | 0 | else if(mime_type_length==0||(mime_type_length==6 |
692 | 0 | &&op_strncasecmp(mime_type,"image/",mime_type_length)==0)){ |
693 | 0 | if(op_is_jpeg(_buf+i,data_length))format=OP_PIC_FORMAT_JPEG; |
694 | 0 | else if(op_is_png(_buf+i,data_length))format=OP_PIC_FORMAT_PNG; |
695 | 0 | else if(op_is_gif(_buf+i,data_length))format=OP_PIC_FORMAT_GIF; |
696 | 0 | } |
697 | 0 | file_width=file_height=file_depth=file_colors=0; |
698 | 0 | has_palette=-1; |
699 | 0 | switch(format){ |
700 | 0 | case OP_PIC_FORMAT_JPEG:{ |
701 | 0 | op_extract_jpeg_params(_buf+i,data_length, |
702 | 0 | &file_width,&file_height,&file_depth,&file_colors,&has_palette); |
703 | 0 | }break; |
704 | 0 | case OP_PIC_FORMAT_PNG:{ |
705 | 0 | op_extract_png_params(_buf+i,data_length, |
706 | 0 | &file_width,&file_height,&file_depth,&file_colors,&has_palette); |
707 | 0 | }break; |
708 | 0 | case OP_PIC_FORMAT_GIF:{ |
709 | 0 | op_extract_gif_params(_buf+i,data_length, |
710 | 0 | &file_width,&file_height,&file_depth,&file_colors,&has_palette); |
711 | 0 | }break; |
712 | 0 | } |
713 | 0 | if(has_palette>=0){ |
714 | | /*If we successfully extracted these parameters from the image, override |
715 | | any declared values.*/ |
716 | 0 | width=file_width; |
717 | 0 | height=file_height; |
718 | 0 | depth=file_depth; |
719 | 0 | colors=file_colors; |
720 | 0 | } |
721 | | /*Picture type 1 must be a 32x32 PNG.*/ |
722 | 0 | if(picture_type==1&&(format!=OP_PIC_FORMAT_PNG||width!=32||height!=32)){ |
723 | 0 | return OP_ENOTFORMAT; |
724 | 0 | } |
725 | 0 | } |
726 | | /*Adjust _buf_sz instead of using data_length to capture the terminating NUL |
727 | | for URLs.*/ |
728 | 0 | _buf_sz-=i; |
729 | 0 | memmove(_buf,_buf+i,sizeof(*_buf)*_buf_sz); |
730 | 0 | if(_buf_sz>0){ |
731 | 0 | unsigned char *shrunk_buf; |
732 | 0 | shrunk_buf=(unsigned char *)_ogg_realloc(_buf,_buf_sz); |
733 | | /*Failure is okay here, since we were strictly trying to reduce the buffer |
734 | | size. |
735 | | We can just proceed with the original, larger buffer.*/ |
736 | 0 | if(shrunk_buf!=NULL)_buf=shrunk_buf; |
737 | 0 | } |
738 | 0 | else{ |
739 | 0 | _ogg_free(_buf); |
740 | 0 | _buf=NULL; |
741 | 0 | } |
742 | 0 | _pic->type=picture_type; |
743 | 0 | _pic->width=width; |
744 | 0 | _pic->height=height; |
745 | 0 | _pic->depth=depth; |
746 | 0 | _pic->colors=colors; |
747 | 0 | _pic->data_length=data_length; |
748 | 0 | _pic->data=_buf; |
749 | 0 | _pic->format=format; |
750 | 0 | return 0; |
751 | 0 | } |
752 | | |
753 | 0 | int opus_picture_tag_parse(OpusPictureTag *_pic,const char *_tag){ |
754 | 0 | OpusPictureTag pic; |
755 | 0 | unsigned char *buf; |
756 | 0 | size_t base64_sz; |
757 | 0 | size_t buf_sz; |
758 | 0 | size_t tag_length; |
759 | 0 | int ret; |
760 | 0 | if(opus_tagncompare("METADATA_BLOCK_PICTURE",22,_tag)==0)_tag+=23; |
761 | | /*Figure out how much BASE64-encoded data we have.*/ |
762 | 0 | tag_length=strlen(_tag); |
763 | 0 | if(tag_length&3)return OP_ENOTFORMAT; |
764 | 0 | base64_sz=tag_length>>2; |
765 | 0 | buf_sz=3*base64_sz; |
766 | 0 | if(buf_sz<32)return OP_ENOTFORMAT; |
767 | 0 | if(_tag[tag_length-1]=='=')buf_sz--; |
768 | 0 | if(_tag[tag_length-2]=='=')buf_sz--; |
769 | 0 | if(buf_sz<32)return OP_ENOTFORMAT; |
770 | | /*Allocate an extra byte to allow appending a terminating NUL to URL data.*/ |
771 | 0 | buf=(unsigned char *)_ogg_malloc(sizeof(*buf)*(buf_sz+1)); |
772 | 0 | if(buf==NULL)return OP_EFAULT; |
773 | 0 | opus_picture_tag_init(&pic); |
774 | 0 | ret=opus_picture_tag_parse_impl(&pic,_tag,buf,buf_sz,base64_sz); |
775 | 0 | if(ret<0){ |
776 | 0 | opus_picture_tag_clear(&pic); |
777 | 0 | _ogg_free(buf); |
778 | 0 | } |
779 | 0 | else *_pic=*&pic; |
780 | 0 | return ret; |
781 | 0 | } |
782 | | |
783 | 0 | void opus_picture_tag_init(OpusPictureTag *_pic){ |
784 | 0 | memset(_pic,0,sizeof(*_pic)); |
785 | 0 | } |
786 | | |
787 | 0 | void opus_picture_tag_clear(OpusPictureTag *_pic){ |
788 | 0 | _ogg_free(_pic->description); |
789 | 0 | _ogg_free(_pic->mime_type); |
790 | | _ogg_free(_pic->data); |
791 | 0 | } |