1 /* Original Copyright:
2 *
3 Copyright (c) 2007 Jeremy Evans
4
5 Permission is hereby granted, free of charge, to any person obtaining a copy
6 of this software and associated documentation files (the "Software"), to deal
7 in the Software without restriction, including without limitation the rights
8 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 copies of the Software, and to permit persons to whom the Software is
10 furnished to do so, subject to the following conditions:
11
12 The above copyright notice and this permission notice shall be included in
13 all copies or substantial portions of the Software.
14
15 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 SOFTWARE.
22
23 Refactored heavily by Dan Sully
24
25 */
26
27 #include "ape.h"
28
_ape_error(ApeTag * tag,char * error,int ret)29 static int _ape_error(ApeTag *tag, char *error, int ret) {
30 LOG_WARN("APE: [%s] %s\n", error, tag->filename);
31 return ret;
32 }
33
_ape_parse(ApeTag * tag)34 int _ape_parse(ApeTag* tag) {
35 int ret = 0;
36
37 if (!(tag->flags & APE_CHECKED_APE)) {
38 if ((ret = _ape_get_tag_info(tag)) < 0) {
39 return ret;
40 }
41 }
42
43 if ((tag->flags & APE_HAS_APE) && !(tag->flags & APE_CHECKED_FIELDS)) {
44 if ((ret = _ape_parse_fields(tag)) < 0) {
45 return ret;
46 }
47 }
48
49 return 0;
50 }
51
52 // Parses the header and footer of the tag to get information about it.
53 // Returns 0 on success, <0 on error;
_ape_get_tag_info(ApeTag * tag)54 int _ape_get_tag_info(ApeTag* tag) {
55 int id3_length = 0;
56 uint32_t lyrics_size = 0;
57 long data_size = 0;
58 off_t file_size = 0;
59 unsigned char compare[12];
60 unsigned char *tmp_ptr;
61
62 file_size = _file_size(tag->fd);
63
64 /* No ape or id3 tag possible in this size */
65 if (file_size < APE_MINIMUM_TAG_SIZE) {
66 tag->flags |= APE_CHECKED_APE;
67 tag->flags &= ~(APE_HAS_APE | APE_HAS_ID3);
68 return 0;
69 }
70
71 if (!(tag->flags & APE_NO_ID3)) {
72
73 if (file_size < APE_ID3_MIN_TAG_SIZE) {
74
75 /* No id3 tag possible in this size */
76 tag->flags &= ~APE_HAS_ID3;
77
78 } else {
79
80 char id3[APE_ID3_MIN_TAG_SIZE];
81
82 /* Check for id3 tag. We need to seek past it if it exists. */
83 if ((PerlIO_seek(tag->fd, file_size - APE_ID3_MIN_TAG_SIZE, SEEK_SET)) == -1) {
84 return _ape_error(tag, "Couldn't seek (id3 offset)", -1);
85 }
86
87 if (PerlIO_read(tag->fd, &id3, APE_ID3_MIN_TAG_SIZE) < APE_ID3_MIN_TAG_SIZE) {
88 return _ape_error(tag, "Couldn't read (id3 offset)", -2);
89 }
90
91 if (id3[0] == 'T' && id3[1] == 'A' && id3[2] == 'G') {
92 id3_length = APE_ID3_MIN_TAG_SIZE;
93 tag->flags |= APE_HAS_ID3;
94 } else {
95 tag->flags &= ~APE_HAS_ID3;
96 }
97 }
98
99 /* Recheck possibility for ape tag now that id3 presence is known */
100 if (file_size < APE_MINIMUM_TAG_SIZE + id3_length) {
101 tag->flags &= ~APE_HAS_APE;
102 tag->flags |= APE_CHECKED_APE;
103 return 0;
104 }
105 }
106
107 /* Check for existance of ape tag footer */
108 if (PerlIO_seek(tag->fd, file_size - APE_TAG_FOOTER_LEN - id3_length, SEEK_SET) == -1) {
109 return _ape_error(tag, "Couldn't seek (tag footer)", -1);
110 }
111
112 buffer_init(&tag->tag_footer, APE_TAG_FOOTER_LEN);
113
114 if (!_check_buf(tag->fd, &tag->tag_footer, APE_TAG_FOOTER_LEN, APE_TAG_FOOTER_LEN)) {
115 return _ape_error(tag, "Couldn't read tag footer", -2);
116 }
117
118 buffer_get(&tag->tag_footer, &compare, 8);
119
120 // XXX this is pretty messy, but will work until I can refactor this whole file
121 if (memcmp(APE_PREAMBLE, &compare, 8)) {
122 // Check for Lyricsv2 tag between APE and ID3
123 char *bptr;
124
125 buffer_consume(&tag->tag_footer, 15);
126 bptr = buffer_ptr(&tag->tag_footer);
127 if ( bptr[0] == 'L' && bptr[1] == 'Y' && bptr[2] == 'R'
128 && bptr[3] == 'I' && bptr[4] == 'C' && bptr[5] == 'S'
129 && bptr[6] == '2' && bptr[7] == '0' && bptr[8] == '0'
130 ) {
131 // read Lyrics tag size, stored as a 6-digit number (!?)
132 // http://www.id3.org/Lyrics3v2
133 bptr -= 6;
134 lyrics_size = atoi(bptr);
135
136 if ( (PerlIO_seek(tag->fd, file_size - (160 + lyrics_size + 15), SEEK_SET)) == -1 ) {
137 return _ape_error(tag, "Couldn't seek (tag footer)", -1);
138 }
139
140 buffer_clear(&tag->tag_footer);
141 if ( !_check_buf(tag->fd, &tag->tag_footer, APE_TAG_FOOTER_LEN, APE_TAG_FOOTER_LEN) ) {
142 return _ape_error(tag, "Couldn't read tag footer", -2);
143 }
144
145 buffer_get(&tag->tag_footer, &compare, 8);
146
147 if (memcmp(APE_PREAMBLE, &compare, 8)) {
148 tag->flags &= ~APE_HAS_APE;
149 tag->flags |= APE_CHECKED_APE;
150 return 0;
151 }
152 }
153 else {
154 tag->flags &= ~APE_HAS_APE;
155 tag->flags |= APE_CHECKED_APE;
156 return 0;
157 }
158 }
159
160 tag->version = buffer_get_int_le(&tag->tag_footer) / 1000;
161 tag->size = buffer_get_int_le(&tag->tag_footer);
162 tag->item_count = buffer_get_int_le(&tag->tag_footer);
163 tag->footer_flags = buffer_get_int_le(&tag->tag_footer);
164 tag->size += APE_TAG_FOOTER_LEN;
165 data_size = tag->size - APE_TAG_HEADER_LEN - APE_TAG_FOOTER_LEN;
166
167 DEBUG_TRACE("Found APEv%d tag, size %d with %d items\n", tag->version, tag->size, tag->item_count);
168
169 my_hv_store( tag->info, "ape_version", newSVpvf( "APEv%d", tag->version ) );
170
171 /* Check tag footer for validity */
172 if (tag->size < APE_MINIMUM_TAG_SIZE) {
173 return _ape_error(tag, "Tag smaller than minimum possible size", -3);
174 }
175
176 if (tag->size > APE_MAXIMUM_TAG_SIZE) {
177 return _ape_error(tag, "Tag larger than maximum possible size", -3);
178 }
179
180 if (tag->size + (uint32_t)id3_length > (unsigned long)file_size) {
181 return _ape_error(tag, "Tag larger than possible size", -3);
182 }
183
184 if (tag->item_count > APE_MAXIMUM_ITEM_COUNT) {
185 return _ape_error(tag, "Tag item count larger than allowed", -3);
186 }
187
188 if (tag->item_count > (tag->size - APE_MINIMUM_TAG_SIZE)/APE_ITEM_MINIMUM_SIZE) {
189 return _ape_error(tag, "Tag item count larger than possible", -3);
190 }
191
192 if (PerlIO_seek(tag->fd, (file_size -(long)tag->size - id3_length - (lyrics_size ? (lyrics_size + 15) : 0)), SEEK_SET) == -1) {
193 return _ape_error(tag, "Couldn't seek to tag offset", -1);
194 }
195
196 tag->offset = file_size -(long)tag->size - id3_length - (lyrics_size ? (lyrics_size + 15) : 0);
197 DEBUG_TRACE("APE tag offset %d\n", tag->offset);
198
199 /* ---------- Read tag header and data --------------- */
200 buffer_init(&tag->tag_header, APE_TAG_HEADER_LEN);
201 buffer_init(&tag->tag_data, data_size);
202
203 if (tag->footer_flags & APE_TAG_CONTAINS_HEADER) {
204 // Bug 15324, Header may or may not be present, only read if footer flag says it is
205 if (!_check_buf(tag->fd, &tag->tag_header, APE_TAG_HEADER_LEN, APE_TAG_HEADER_LEN)) {
206 return _ape_error(tag, "Couldn't read tag header", -2);
207 }
208
209 buffer_get(&tag->tag_header, &compare, 12);
210 tmp_ptr = buffer_ptr(&tag->tag_header);
211
212 /* Check tag header for validity */
213 if (memcmp(APE_PREAMBLE, &compare, 8) ||
214 (tmp_ptr[8] != '\0' && tmp_ptr[8] != '\1')) {
215 return _ape_error(tag, "Bad tag header flags", -3);
216 }
217
218 if (tag->size != (buffer_get_int_le(&tag->tag_header) + APE_TAG_HEADER_LEN)) {
219 return _ape_error(tag, "Header and footer size do not match", -3);
220 }
221
222 if (tag->item_count != buffer_get_int_le(&tag->tag_header)) {
223 return _ape_error(tag, "Header and footer item count do not match", -3);
224 }
225 }
226 else {
227 // Skip junk where header should be, APE format is really stupid...
228 if (PerlIO_seek(tag->fd, APE_TAG_HEADER_LEN, SEEK_CUR) == -1) {
229 return _ape_error(tag, "Couldn't seek to tag offset", -1);
230 }
231 }
232
233 tag->offset += APE_TAG_HEADER_LEN;
234
235 if (!_check_buf(tag->fd, &tag->tag_data, data_size, data_size)) {
236 return _ape_error(tag, "Couldn't read tag data", -2);
237 }
238
239 tag->flags |= APE_CHECKED_APE | APE_HAS_APE;
240
241 // Reduce the size of the audio_size value
242 if (my_hv_exists(tag->info, "audio_size")) {
243 int audio_size = SvIV(*(my_hv_fetch(tag->info, "audio_size")));
244 if (lyrics_size > 0)
245 lyrics_size += 15;
246
247 my_hv_store(tag->info, "audio_size", newSVuv(audio_size - tag->size - lyrics_size));
248 DEBUG_TRACE("Reduced audio_size value by APE/Lyrics2 tag size %d\n", tag->size + lyrics_size);
249 }
250
251 return 1;
252 }
253
_ape_parse_fields(ApeTag * tag)254 int _ape_parse_fields(ApeTag* tag) {
255 int ret = 0;
256 uint32_t i;
257
258 /* Don't exceed the maximum number of items allowed */
259 if (tag->num_fields >= APE_MAXIMUM_ITEM_COUNT) {
260 return _ape_error(tag, "Maximum item count exceeded", -3);
261 }
262
263 for (i = 0; i < tag->item_count; i++) {
264 if ((ret = _ape_parse_field(tag)) != 0) {
265 return ret;
266 }
267 }
268
269 if (buffer_len(&tag->tag_data) != 0) {
270 return _ape_error(tag, "Data remaining after specified number of items parsed", -3);
271 }
272
273 tag->flags |= APE_CHECKED_FIELDS;
274
275 return 0;
276 }
277
_ape_parse_field(ApeTag * tag)278 int _ape_parse_field(ApeTag* tag) {
279
280 /* Ape tag item format:
281 *
282 * <value_size:4 bytes>
283 * <flags:4 bytes>
284 * <key:N bytes, 2 to 255 chars, ASCII range. 0x00 terminated>
285 * <value:value_size bytes>
286 */
287 uint32_t data_size = tag->size - APE_MINIMUM_TAG_SIZE;
288 uint32_t size, flags, key_length = 0, val_length = 0;
289 unsigned char *tmp_ptr;
290 SV *key = NULL;
291 SV *value = NULL;
292
293 if (buffer_len(&tag->tag_data) < 8)
294 return _ape_error(tag, "Ran out of tag data before number of items was reached", -3);
295
296 size = buffer_get_int_le(&tag->tag_data);
297 flags = buffer_get_int_le(&tag->tag_data);
298
299 tmp_ptr = buffer_ptr(&tag->tag_data);
300 while (tmp_ptr[0] != '\0') {
301 key_length += 1;
302 tmp_ptr += 1;
303 }
304
305 key = newSVpvn( buffer_ptr(&tag->tag_data), key_length );
306 buffer_consume(&tag->tag_data, key_length + 1);
307
308 // Bug 9942, APE tags can contain multiple items with a null separator
309 tmp_ptr = buffer_ptr(&tag->tag_data);
310 while (tmp_ptr[0] != '\0' && val_length <= size) {
311 val_length += 1;
312 tmp_ptr += 1;
313 }
314
315 tag->offset += 8 + key_length + 1;
316
317 DEBUG_TRACE("key_length: %d / val_length: %d / size: %d / flags %x @ %d\n", key_length, val_length, size, flags, tag->offset);
318
319 if (flags & APE_TAG_TYPE_BINARY) {
320 // Binary data, just copy it as-is
321
322 // Special handling if the tag is cover art, strip the filename from the front of
323 // the cover art data
324 if ( sv_len(key) == 17 && !memcmp( upcase(SvPVX(key)), "COVER ART (FRONT)", 17 ) ) {
325 if ( _env_true("AUDIO_SCAN_NO_ARTWORK") ) {
326 // Don't read artwork, just return the size
327 value = newSVuv(size - (val_length + 1) );
328
329 my_hv_store( tag->tags, "COVER ART (FRONT)_offset", newSVuv(tag->offset + val_length + 1) );
330
331 buffer_consume(&tag->tag_data, size);
332 }
333 else {
334 buffer_consume(&tag->tag_data, val_length + 1); // consume filename + null
335 size -= val_length + 1;
336 }
337 }
338
339 if ( value == NULL ) {
340 value = newSVpvn( buffer_ptr(&tag->tag_data), size );
341 buffer_consume(&tag->tag_data, size);
342 }
343
344 tag->offset += val_length + 1;
345 }
346 else if (val_length >= size - 1) {
347 // Single item
348 value = newSVpvn( buffer_ptr(&tag->tag_data), val_length < size ? val_length : size );
349
350 buffer_consume(&tag->tag_data, size);
351
352 // Don't add invalid items
353 if (_ape_check_validity(tag, flags, SvPVX(key), SvPVX(value)) != 0) {
354 // skip this item
355 return 0;
356 }
357 else {
358 sv_utf8_decode(value);
359 DEBUG_TRACE(" %s = %s\n", SvPVX(key), SvPVX(value));
360 }
361
362 tag->offset += val_length < size ? val_length : size;
363 }
364 else {
365 // Multiple items
366 AV *av = newAV();
367 SV *tmp_val;
368 uint32_t done = 0;
369
370 while ( done < size ) {
371 val_length = 0;
372 tmp_ptr = buffer_ptr(&tag->tag_data);
373 while (tmp_ptr[0] != '\0' && done < size) {
374 val_length++;
375 tmp_ptr++;
376 done++;
377 }
378
379 tmp_val = newSVpvn( buffer_ptr(&tag->tag_data), val_length );
380 buffer_consume(&tag->tag_data, val_length);
381
382 tag->offset += val_length;
383
384 // Don't add invalid items
385 if (_ape_check_validity(tag, flags, SvPVX(key), SvPVX(tmp_val)) != 0) {
386 // skip this item
387 buffer_consume(&tag->tag_data, size - done);
388 return 0;
389 }
390 else {
391 sv_utf8_decode(tmp_val);
392 }
393
394 DEBUG_TRACE(" %s = %s\n", SvPVX(key), SvPVX(tmp_val));
395
396 av_push(av, tmp_val);
397
398 if ( done < size ) {
399 // Still more to read, consume the null separator
400 buffer_consume(&tag->tag_data, 1);
401 tag->offset++;
402 done++;
403 }
404 }
405
406 value = newRV_noinc( (SV *)av );
407 }
408
409 /* Find and check start of value */
410 if (size + buffer_len(&tag->tag_data) + APE_ITEM_MINIMUM_SIZE > data_size) {
411 return _ape_error(tag, "Impossible item length (greater than remaining space)", -3);
412 }
413
414 my_hv_store(tag->tags, upcase(SvPVX(key)), value);
415
416 SvREFCNT_dec(key);
417
418 tag->num_fields++;
419
420 return 0;
421 }
422
_ape_check_validity(ApeTag * tag,uint32_t flags,char * key,char * value)423 int _ape_check_validity(ApeTag* tag, uint32_t flags, char* key, char* value) {
424 unsigned long key_length;
425 char* key_end;
426 char* c;
427
428 /* Check valid flags */
429 if (flags > 7) {
430 return _ape_error(tag, "Invalid item flags", -3);
431 }
432
433 /* Check valid key */
434 key_length = strlen(key);
435 key_end = key + (long)key_length;
436
437 if (key_length < 2) {
438 return _ape_error(tag, "Invalid item key, too short (<2)", -3);
439 }
440
441 if (key_length > 255) {
442 return _ape_error(tag, "Invalid item key, too long (>255)", -3);
443 }
444
445 if (key_length == 3) {
446 #ifdef _MSC_VER
447 if (strnicmp(key, "id3", 3) == 0 ||
448 strnicmp(key, "tag", 3) == 0 ||
449 strnicmp(key, "mp+", 3) == 0) {
450 #else
451 if (strncasecmp(key, "id3", 3) == 0 ||
452 strncasecmp(key, "tag", 3) == 0 ||
453 strncasecmp(key, "mp+", 3) == 0) {
454 #endif
455 return _ape_error(tag, "Invalid item key 'id3, tag or mp+'", -3);
456 }
457 }
458
459 #ifdef _MSC_VER
460 if (key_length == 4 && strnicmp(key, "oggs", 4) == 0) {
461 #else
462 if (key_length == 4 && strncasecmp(key, "oggs", 4) == 0) {
463 #endif
464 return _ape_error(tag, "Invalid item key 'oggs'", -3);
465 }
466
467 for (c = key; c < key_end; c++) {
468 if ((unsigned char)(*c) < 0x20 || (unsigned char)(*c) > 0x7f) {
469 return _ape_error(tag, "Invalid or non-ASCII key character", -3);
470 }
471 }
472
473 if (tag->version > 1) {
474 /* Check value is utf-8 if flags specify utf8 or external format*/
475 if (((flags & APE_ITEM_TYPE_FLAGS) & 2) == 0 && !is_utf8_string((unsigned char*)(value), strlen(value))) {
476 return _ape_error(tag, "Invalid UTF-8 value", -3);
477 }
478 }
479
480 return 0;
481 }
482
483 static int
484 get_ape_metadata(PerlIO *infile, char *file, HV *info, HV *tags)
485 {
486 int status = -1;
487 ApeTag* tag;
488
489 Newz(0, tag, sizeof(ApeTag), ApeTag);
490
491 if (tag == NULL) {
492 PerlIO_printf(PerlIO_stderr(), "APE: [Couldn't allocate memory (ApeTag)] %s\n", file);
493 return status;
494 }
495
496 tag->fd = infile;
497 tag->info = info;
498 tag->tags = tags;
499 tag->filename = file;
500 tag->flags = 0;
501 tag->size = 0;
502 tag->offset = 0;
503 tag->item_count = 0;
504 tag->num_fields = 0;
505
506 status = _ape_parse(tag);
507
508 buffer_free(&tag->tag_header);
509 buffer_free(&tag->tag_footer);
510 buffer_free(&tag->tag_data);
511 Safefree(tag);
512
513 return status;
514 }
515