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