Coverage Report

Created: 2026-08-31 06:05

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/opusfile/src/info.c
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
}