File Coverage

src/ape.c
Criterion Covered Total %
statement 190 232 81.9
branch 115 172 66.8
condition n/a
subroutine n/a
pod n/a
total 305 404 75.5


line stmt bran cond sub pod time code
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              
29 2           static int _ape_error(ApeTag *tag, char *error, int ret) {
30 2           LOG_WARN("APE: [%s] %s\n", error, tag->filename);
31 2           return ret;
32             }
33              
34 13           int _ape_parse(ApeTag* tag) {
35 13           int ret = 0;
36              
37 13 50         if (!(tag->flags & APE_CHECKED_APE)) {
38 13 50         if ((ret = _ape_get_tag_info(tag)) < 0) {
39 0           return ret;
40             }
41             }
42              
43 13 100         if ((tag->flags & APE_HAS_APE) && !(tag->flags & APE_CHECKED_FIELDS)) {
    50          
44 12 100         if ((ret = _ape_parse_fields(tag)) < 0) {
45 1           return ret;
46             }
47             }
48              
49 12           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;
54 13           int _ape_get_tag_info(ApeTag* tag) {
55 13           int id3_length = 0;
56 13           uint32_t lyrics_size = 0;
57 13           long data_size = 0;
58 13           off_t file_size = 0;
59             unsigned char compare[12];
60             unsigned char *tmp_ptr;
61              
62 13           file_size = _file_size(tag->fd);
63              
64             /* No ape or id3 tag possible in this size */
65 13 50         if (file_size < APE_MINIMUM_TAG_SIZE) {
66 0           tag->flags |= APE_CHECKED_APE;
67 0           tag->flags &= ~(APE_HAS_APE | APE_HAS_ID3);
68 0           return 0;
69             }
70              
71 13 50         if (!(tag->flags & APE_NO_ID3)) {
72              
73 13 50         if (file_size < APE_ID3_MIN_TAG_SIZE) {
74              
75             /* No id3 tag possible in this size */
76 0           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 13 50         if ((PerlIO_seek(tag->fd, file_size - APE_ID3_MIN_TAG_SIZE, SEEK_SET)) == -1) {
84 0           return _ape_error(tag, "Couldn't seek (id3 offset)", -1);
85             }
86              
87 13 50         if (PerlIO_read(tag->fd, &id3, APE_ID3_MIN_TAG_SIZE) < APE_ID3_MIN_TAG_SIZE) {
88 0           return _ape_error(tag, "Couldn't read (id3 offset)", -2);
89             }
90              
91 13 100         if (id3[0] == 'T' && id3[1] == 'A' && id3[2] == 'G') {
    50          
    50          
92 4           id3_length = APE_ID3_MIN_TAG_SIZE;
93 4           tag->flags |= APE_HAS_ID3;
94             } else {
95 13           tag->flags &= ~APE_HAS_ID3;
96             }
97             }
98              
99             /* Recheck possibility for ape tag now that id3 presence is known */
100 13 50         if (file_size < APE_MINIMUM_TAG_SIZE + id3_length) {
101 0           tag->flags &= ~APE_HAS_APE;
102 0           tag->flags |= APE_CHECKED_APE;
103 0           return 0;
104             }
105             }
106              
107             /* Check for existance of ape tag footer */
108 13 50         if (PerlIO_seek(tag->fd, file_size - APE_TAG_FOOTER_LEN - id3_length, SEEK_SET) == -1) {
109 0           return _ape_error(tag, "Couldn't seek (tag footer)", -1);
110             }
111              
112 13           buffer_init(&tag->tag_footer, APE_TAG_FOOTER_LEN);
113              
114 13 50         if (!_check_buf(tag->fd, &tag->tag_footer, APE_TAG_FOOTER_LEN, APE_TAG_FOOTER_LEN)) {
115 0           return _ape_error(tag, "Couldn't read tag footer", -2);
116             }
117              
118 13           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 13 100         if (memcmp(APE_PREAMBLE, &compare, 8)) {
122             // Check for Lyricsv2 tag between APE and ID3
123             char *bptr;
124              
125 2           buffer_consume(&tag->tag_footer, 15);
126 2           bptr = buffer_ptr(&tag->tag_footer);
127 2 100         if ( bptr[0] == 'L' && bptr[1] == 'Y' && bptr[2] == 'R'
    50          
    50          
128 1 50         && bptr[3] == 'I' && bptr[4] == 'C' && bptr[5] == 'S'
    50          
    50          
129 1 50         && bptr[6] == '2' && bptr[7] == '0' && bptr[8] == '0'
    50          
    50          
130             ) {
131             // read Lyrics tag size, stored as a 6-digit number (!?)
132             // http://www.id3.org/Lyrics3v2
133 1           bptr -= 6;
134 1           lyrics_size = atoi(bptr);
135              
136 1 50         if ( (PerlIO_seek(tag->fd, file_size - (160 + lyrics_size + 15), SEEK_SET)) == -1 ) {
137 0           return _ape_error(tag, "Couldn't seek (tag footer)", -1);
138             }
139              
140 1           buffer_clear(&tag->tag_footer);
141 1 50         if ( !_check_buf(tag->fd, &tag->tag_footer, APE_TAG_FOOTER_LEN, APE_TAG_FOOTER_LEN) ) {
142 0           return _ape_error(tag, "Couldn't read tag footer", -2);
143             }
144              
145 1           buffer_get(&tag->tag_footer, &compare, 8);
146              
147 1 50         if (memcmp(APE_PREAMBLE, &compare, 8)) {
148 0           tag->flags &= ~APE_HAS_APE;
149 0           tag->flags |= APE_CHECKED_APE;
150 0           return 0;
151             }
152             }
153             else {
154 1           tag->flags &= ~APE_HAS_APE;
155 1           tag->flags |= APE_CHECKED_APE;
156 1           return 0;
157             }
158             }
159              
160 12           tag->version = buffer_get_int_le(&tag->tag_footer) / 1000;
161 12           tag->size = buffer_get_int_le(&tag->tag_footer);
162 12           tag->item_count = buffer_get_int_le(&tag->tag_footer);
163 12           tag->footer_flags = buffer_get_int_le(&tag->tag_footer);
164 12           tag->size += APE_TAG_FOOTER_LEN;
165 12           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 12           my_hv_store( tag->info, "ape_version", newSVpvf( "APEv%d", tag->version ) );
170              
171             /* Check tag footer for validity */
172 12 50         if (tag->size < APE_MINIMUM_TAG_SIZE) {
173 0           return _ape_error(tag, "Tag smaller than minimum possible size", -3);
174             }
175              
176 12 50         if (tag->size > APE_MAXIMUM_TAG_SIZE) {
177 0           return _ape_error(tag, "Tag larger than maximum possible size", -3);
178             }
179              
180 12 50         if (tag->size + (uint32_t)id3_length > (unsigned long)file_size) {
181 0           return _ape_error(tag, "Tag larger than possible size", -3);
182             }
183              
184 12 50         if (tag->item_count > APE_MAXIMUM_ITEM_COUNT) {
185 0           return _ape_error(tag, "Tag item count larger than allowed", -3);
186             }
187              
188 12 50         if (tag->item_count > (tag->size - APE_MINIMUM_TAG_SIZE)/APE_ITEM_MINIMUM_SIZE) {
189 0           return _ape_error(tag, "Tag item count larger than possible", -3);
190             }
191              
192 12 100         if (PerlIO_seek(tag->fd, (file_size -(long)tag->size - id3_length - (lyrics_size ? (lyrics_size + 15) : 0)), SEEK_SET) == -1) {
    50          
193 0           return _ape_error(tag, "Couldn't seek to tag offset", -1);
194             }
195              
196 12 100         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 12           buffer_init(&tag->tag_header, APE_TAG_HEADER_LEN);
201 12           buffer_init(&tag->tag_data, data_size);
202              
203 12 100         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 9 50         if (!_check_buf(tag->fd, &tag->tag_header, APE_TAG_HEADER_LEN, APE_TAG_HEADER_LEN)) {
206 0           return _ape_error(tag, "Couldn't read tag header", -2);
207             }
208              
209 9           buffer_get(&tag->tag_header, &compare, 12);
210 9           tmp_ptr = buffer_ptr(&tag->tag_header);
211              
212             /* Check tag header for validity */
213 9 50         if (memcmp(APE_PREAMBLE, &compare, 8) ||
    50          
214 0 0         (tmp_ptr[8] != '\0' && tmp_ptr[8] != '\1')) {
215 0           return _ape_error(tag, "Bad tag header flags", -3);
216             }
217              
218 9 50         if (tag->size != (buffer_get_int_le(&tag->tag_header) + APE_TAG_HEADER_LEN)) {
219 0           return _ape_error(tag, "Header and footer size do not match", -3);
220             }
221              
222 9 50         if (tag->item_count != buffer_get_int_le(&tag->tag_header)) {
223 0           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 3 50         if (PerlIO_seek(tag->fd, APE_TAG_HEADER_LEN, SEEK_CUR) == -1) {
229 0           return _ape_error(tag, "Couldn't seek to tag offset", -1);
230             }
231             }
232              
233 12           tag->offset += APE_TAG_HEADER_LEN;
234              
235 12 50         if (!_check_buf(tag->fd, &tag->tag_data, data_size, data_size)) {
236 0           return _ape_error(tag, "Couldn't read tag data", -2);
237             }
238              
239 12           tag->flags |= APE_CHECKED_APE | APE_HAS_APE;
240              
241             // Reduce the size of the audio_size value
242 12 50         if (my_hv_exists(tag->info, "audio_size")) {
243 12 50         int audio_size = SvIV(*(my_hv_fetch(tag->info, "audio_size")));
244 12 100         if (lyrics_size > 0)
245 1           lyrics_size += 15;
246              
247 12           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 13           return 1;
252             }
253              
254 12           int _ape_parse_fields(ApeTag* tag) {
255 12           int ret = 0;
256             uint32_t i;
257              
258             /* Don't exceed the maximum number of items allowed */
259 12 50         if (tag->num_fields >= APE_MAXIMUM_ITEM_COUNT) {
260 0           return _ape_error(tag, "Maximum item count exceeded", -3);
261             }
262              
263 125 100         for (i = 0; i < tag->item_count; i++) {
264 114 100         if ((ret = _ape_parse_field(tag)) != 0) {
265 1           return ret;
266             }
267             }
268              
269 11 50         if (buffer_len(&tag->tag_data) != 0) {
270 0           return _ape_error(tag, "Data remaining after specified number of items parsed", -3);
271             }
272              
273 11           tag->flags |= APE_CHECKED_FIELDS;
274              
275 11           return 0;
276             }
277              
278 114           int _ape_parse_field(ApeTag* tag) {
279              
280             /* Ape tag item format:
281             *
282             *
283             *
284             *
285             *
286             */
287 114           uint32_t data_size = tag->size - APE_MINIMUM_TAG_SIZE;
288 114           uint32_t size, flags, key_length = 0, val_length = 0;
289             unsigned char *tmp_ptr;
290 114           SV *key = NULL;
291 114           SV *value = NULL;
292              
293 114 100         if (buffer_len(&tag->tag_data) < 8)
294 1           return _ape_error(tag, "Ran out of tag data before number of items was reached", -3);
295              
296 113           size = buffer_get_int_le(&tag->tag_data);
297 113           flags = buffer_get_int_le(&tag->tag_data);
298              
299 113           tmp_ptr = buffer_ptr(&tag->tag_data);
300 1288 100         while (tmp_ptr[0] != '\0') {
301 1175           key_length += 1;
302 1175           tmp_ptr += 1;
303             }
304              
305 113           key = newSVpvn( buffer_ptr(&tag->tag_data), key_length );
306 113           buffer_consume(&tag->tag_data, key_length + 1);
307              
308             // Bug 9942, APE tags can contain multiple items with a null separator
309 113           tmp_ptr = buffer_ptr(&tag->tag_data);
310 1717 100         while (tmp_ptr[0] != '\0' && val_length <= size) {
    100          
311 1604           val_length += 1;
312 1604           tmp_ptr += 1;
313             }
314              
315 113           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 113 100         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 4 100         if ( sv_len(key) == 17 && !memcmp( upcase(SvPVX(key)), "COVER ART (FRONT)", 17 ) ) {
    50          
325 3 100         if ( _env_true("AUDIO_SCAN_NO_ARTWORK") ) {
326             // Don't read artwork, just return the size
327 1           value = newSVuv(size - (val_length + 1) );
328              
329 1           my_hv_store( tag->tags, "COVER ART (FRONT)_offset", newSVuv(tag->offset + val_length + 1) );
330              
331 1           buffer_consume(&tag->tag_data, size);
332             }
333             else {
334 2           buffer_consume(&tag->tag_data, val_length + 1); // consume filename + null
335 2           size -= val_length + 1;
336             }
337             }
338              
339 4 100         if ( value == NULL ) {
340 3           value = newSVpvn( buffer_ptr(&tag->tag_data), size );
341 3           buffer_consume(&tag->tag_data, size);
342             }
343              
344 4           tag->offset += val_length + 1;
345             }
346 109 100         else if (val_length >= size - 1) {
347             // Single item
348 104           value = newSVpvn( buffer_ptr(&tag->tag_data), val_length < size ? val_length : size );
349              
350 104           buffer_consume(&tag->tag_data, size);
351              
352             // Don't add invalid items
353 104 50         if (_ape_check_validity(tag, flags, SvPVX(key), SvPVX(value)) != 0) {
354             // skip this item
355 0           return 0;
356             }
357             else {
358 104           sv_utf8_decode(value);
359             DEBUG_TRACE(" %s = %s\n", SvPVX(key), SvPVX(value));
360             }
361              
362 104           tag->offset += val_length < size ? val_length : size;
363             }
364             else {
365             // Multiple items
366 5           AV *av = newAV();
367             SV *tmp_val;
368 5           uint32_t done = 0;
369              
370 13 100         while ( done < size ) {
371 9           val_length = 0;
372 9           tmp_ptr = buffer_ptr(&tag->tag_data);
373 118 100         while (tmp_ptr[0] != '\0' && done < size) {
    100          
374 109           val_length++;
375 109           tmp_ptr++;
376 109           done++;
377             }
378              
379 9           tmp_val = newSVpvn( buffer_ptr(&tag->tag_data), val_length );
380 9           buffer_consume(&tag->tag_data, val_length);
381              
382 9           tag->offset += val_length;
383              
384             // Don't add invalid items
385 9 100         if (_ape_check_validity(tag, flags, SvPVX(key), SvPVX(tmp_val)) != 0) {
386             // skip this item
387 1           buffer_consume(&tag->tag_data, size - done);
388 1           return 0;
389             }
390             else {
391 8           sv_utf8_decode(tmp_val);
392             }
393              
394             DEBUG_TRACE(" %s = %s\n", SvPVX(key), SvPVX(tmp_val));
395              
396 8           av_push(av, tmp_val);
397              
398 8 100         if ( done < size ) {
399             // Still more to read, consume the null separator
400 4           buffer_consume(&tag->tag_data, 1);
401 4           tag->offset++;
402 4           done++;
403             }
404             }
405              
406 4           value = newRV_noinc( (SV *)av );
407             }
408              
409             /* Find and check start of value */
410 112 50         if (size + buffer_len(&tag->tag_data) + APE_ITEM_MINIMUM_SIZE > data_size) {
411 0           return _ape_error(tag, "Impossible item length (greater than remaining space)", -3);
412             }
413              
414 112           my_hv_store(tag->tags, upcase(SvPVX(key)), value);
415              
416 112           SvREFCNT_dec(key);
417              
418 112           tag->num_fields++;
419              
420 112           return 0;
421             }
422              
423 113           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 113 50         if (flags > 7) {
430 0           return _ape_error(tag, "Invalid item flags", -3);
431             }
432              
433             /* Check valid key */
434 113           key_length = strlen(key);
435 113           key_end = key + (long)key_length;
436              
437 113 50         if (key_length < 2) {
438 0           return _ape_error(tag, "Invalid item key, too short (<2)", -3);
439             }
440              
441 113 50         if (key_length > 255) {
442 0           return _ape_error(tag, "Invalid item key, too long (>255)", -3);
443             }
444              
445 113 100         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 2 50         if (strncasecmp(key, "id3", 3) == 0 ||
    50          
452 2 50         strncasecmp(key, "tag", 3) == 0 ||
453 2           strncasecmp(key, "mp+", 3) == 0) {
454             #endif
455 0           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 113 100         if (key_length == 4 && strncasecmp(key, "oggs", 4) == 0) {
    50          
463             #endif
464 0           return _ape_error(tag, "Invalid item key 'oggs'", -3);
465             }
466              
467 1247 100         for (c = key; c < key_end; c++) {
468 1135 50         if ((unsigned char)(*c) < 0x20 || (unsigned char)(*c) > 0x7f) {
    100          
469 1           return _ape_error(tag, "Invalid or non-ASCII key character", -3);
470             }
471             }
472              
473 112 100         if (tag->version > 1) {
474             /* Check value is utf-8 if flags specify utf8 or external format*/
475 106 50         if (((flags & APE_ITEM_TYPE_FLAGS) & 2) == 0 && !is_utf8_string((unsigned char*)(value), strlen(value))) {
    50          
476 0           return _ape_error(tag, "Invalid UTF-8 value", -3);
477             }
478             }
479              
480 112           return 0;
481             }
482              
483             static int
484 13           get_ape_metadata(PerlIO *infile, char *file, HV *info, HV *tags)
485             {
486 13           int status = -1;
487             ApeTag* tag;
488              
489 13           Newz(0, tag, sizeof(ApeTag), ApeTag);
490              
491 13 50         if (tag == NULL) {
492 0           PerlIO_printf(PerlIO_stderr(), "APE: [Couldn't allocate memory (ApeTag)] %s\n", file);
493 0           return status;
494             }
495              
496 13           tag->fd = infile;
497 13           tag->info = info;
498 13           tag->tags = tags;
499 13           tag->filename = file;
500 13           tag->flags = 0;
501 13           tag->size = 0;
502 13           tag->offset = 0;
503 13           tag->item_count = 0;
504 13           tag->num_fields = 0;
505              
506 13           status = _ape_parse(tag);
507              
508 13           buffer_free(&tag->tag_header);
509 13           buffer_free(&tag->tag_footer);
510 13           buffer_free(&tag->tag_data);
511 13           Safefree(tag);
512              
513 13           return status;
514             }