1 ////////////////////////////////////////////////////////////////////////////
2 //                           **** WAVPACK ****                            //
3 //                  Hybrid Lossless Wavefile Compressor                   //
4 //              Copyright (c) 1998 - 2013 Conifer Software.               //
5 //                          All Rights Reserved.                          //
6 //      Distributed under the BSD Software License (see license.txt)      //
7 ////////////////////////////////////////////////////////////////////////////
8 
9 // tags.c
10 
11 // This module provides support for reading metadata tags (either ID3v1 or
12 // APEv2) from WavPack files. No actual creation or manipulation of the tags
13 // is done in this module; this is just internal code to load the tags into
14 // memory. The high-level API functions are in the tag_utils.c module.
15 
16 #include <stdlib.h>
17 #include <string.h>
18 
19 #include "wavpack_local.h"
20 
21 // This function attempts to load an ID3v1 or APEv2 tag from the specified
22 // file into the specified M_Tag structure. The ID3 tag fits in completely,
23 // but an APEv2 tag is variable length and so space must be allocated here
24 // to accommodate the data, and this will need to be freed later. A return
25 // value of TRUE indicates a valid tag was found and loaded. Note that the
26 // file pointer is undefined when this function exits.
27 
load_tag(WavpackContext * wpc)28 int load_tag (WavpackContext *wpc)
29 {
30     int ape_tag_length, ape_tag_items;
31     M_Tag *m_tag = &wpc->m_tag;
32 
33     CLEAR (*m_tag);
34 
35     // This is a loop because we can try up to three times to look for an APEv2 tag. In order, we look:
36     //
37     //  1. At the end of the file for a APEv2 footer (this is the preferred location)
38     //  2. If there's instead an ID3v1 tag at the end of the file, try looking for an APEv2 footer right before that
39     //  3. If all else fails, look for an APEv2 header the the beginning of the file (use is strongly discouraged)
40 
41     while (1) {
42 
43         // seek based on specific location that we are looking for tag (see above list)
44 
45         if (m_tag->tag_begins_file)                     // case #3
46             wpc->reader->set_pos_abs (wpc->wv_in, 0);
47         else if (m_tag->id3_tag.tag_id [0] == 'T')      // case #2
48             wpc->reader->set_pos_rel (wpc->wv_in, -(int32_t)(sizeof (APE_Tag_Hdr) + sizeof (ID3_Tag)), SEEK_END);
49         else                                            // case #1
50             wpc->reader->set_pos_rel (wpc->wv_in, -(int32_t)sizeof (APE_Tag_Hdr), SEEK_END);
51 
52         // read a possible APEv2 tag header/footer and see if there's one there...
53 
54         if (wpc->reader->read_bytes (wpc->wv_in, &m_tag->ape_tag_hdr, sizeof (APE_Tag_Hdr)) == sizeof (APE_Tag_Hdr) &&
55             !strncmp (m_tag->ape_tag_hdr.ID, "APETAGEX", 8)) {
56 
57                 WavpackLittleEndianToNative (&m_tag->ape_tag_hdr, APE_Tag_Hdr_Format);
58 
59                 if (m_tag->ape_tag_hdr.version == 2000 && m_tag->ape_tag_hdr.item_count &&
60                     m_tag->ape_tag_hdr.length > (int) sizeof (m_tag->ape_tag_hdr) &&
61                     m_tag->ape_tag_hdr.length <= APE_TAG_MAX_LENGTH &&
62                     (m_tag->ape_tag_data = (unsigned char *)malloc (m_tag->ape_tag_hdr.length)) != NULL) {
63 
64                         ape_tag_items = m_tag->ape_tag_hdr.item_count;
65                         ape_tag_length = m_tag->ape_tag_hdr.length;
66 
67                         // If this is a APEv2 footer (which is normal if we are searching at the end of the file)...
68 
69                         if (!(m_tag->ape_tag_hdr.flags & APE_TAG_THIS_IS_HEADER)) {
70 
71                             if (m_tag->id3_tag.tag_id [0] == 'T')
72                                 m_tag->tag_file_pos = -(int32_t)sizeof (ID3_Tag);
73                             else
74                                 m_tag->tag_file_pos = 0;
75 
76                             m_tag->tag_file_pos -= ape_tag_length;
77 
78                             // if the footer claims there is a header present also, we will read that and use it
79                             // instead of the footer (after verifying it, of course) for enhanced robustness
80 
81                             if (m_tag->ape_tag_hdr.flags & APE_TAG_CONTAINS_HEADER)
82                                 m_tag->tag_file_pos -= sizeof (APE_Tag_Hdr);
83 
84                             wpc->reader->set_pos_rel (wpc->wv_in, m_tag->tag_file_pos, SEEK_END);
85 
86                             if (m_tag->ape_tag_hdr.flags & APE_TAG_CONTAINS_HEADER) {
87                                 if (wpc->reader->read_bytes (wpc->wv_in, &m_tag->ape_tag_hdr, sizeof (APE_Tag_Hdr)) !=
88                                     sizeof (APE_Tag_Hdr) || strncmp (m_tag->ape_tag_hdr.ID, "APETAGEX", 8)) {
89                                         free (m_tag->ape_tag_data);
90                                         CLEAR (*m_tag);
91                                         return FALSE;       // something's wrong...
92                                 }
93 
94                                 WavpackLittleEndianToNative (&m_tag->ape_tag_hdr, APE_Tag_Hdr_Format);
95 
96                                 if (m_tag->ape_tag_hdr.version != 2000 || m_tag->ape_tag_hdr.item_count != ape_tag_items ||
97                                     m_tag->ape_tag_hdr.length != ape_tag_length) {
98                                         free (m_tag->ape_tag_data);
99                                         CLEAR (*m_tag);
100                                         return FALSE;       // something's wrong...
101                                 }
102                             }
103                         }
104 
105                         if (wpc->reader->read_bytes (wpc->wv_in, m_tag->ape_tag_data,
106                             ape_tag_length - sizeof (APE_Tag_Hdr)) != ape_tag_length - sizeof (APE_Tag_Hdr)) {
107                                 free (m_tag->ape_tag_data);
108                                 CLEAR (*m_tag);
109                                 return FALSE;       // something's wrong...
110                         }
111                         else {
112                             CLEAR (m_tag->id3_tag); // ignore ID3v1 tag if we found APEv2 tag
113                             return TRUE;
114                         }
115                 }
116         }
117 
118         // we come here if the search for the APEv2 tag failed (otherwise we would have returned with it)
119 
120         if (m_tag->id3_tag.tag_id [0] == 'T') {     // settle for the ID3v1 tag that we found
121             CLEAR (m_tag->ape_tag_hdr);
122             return TRUE;
123         }
124 
125         // if this was the search for the APEv2 tag at the beginning of the file (which is our
126         // last resort) then we have nothing, so return failure
127 
128         if (m_tag->tag_begins_file) {
129             CLEAR (*m_tag);
130             return FALSE;
131         }
132 
133         // If we get here, then we have failed the first APEv2 tag search (at end of file) and so now we
134         // look for an ID3v1 tag at the same position. If that succeeds, then we'll loop back and look for
135         // an APEv2 tag immediately before the ID3v1 tag, otherwise our last resort is to look for an
136         // APEv2 tag at the beginning of the file. These are strongly discouraged (and not editable) but
137         // they have been seen in the wild so we attempt to handle them here (at least well enough to
138         // allow a proper transcoding).
139 
140         m_tag->tag_file_pos = -(int32_t)sizeof (ID3_Tag);
141         wpc->reader->set_pos_rel (wpc->wv_in, m_tag->tag_file_pos, SEEK_END);
142 
143         if (wpc->reader->read_bytes (wpc->wv_in, &m_tag->id3_tag, sizeof (ID3_Tag)) != sizeof (ID3_Tag) ||
144             strncmp (m_tag->id3_tag.tag_id, "TAG", 3)) {
145                 m_tag->tag_begins_file = 1;     // failed ID3v1, so look for APEv2 at beginning of file
146                 CLEAR (m_tag->id3_tag);
147             }
148     }
149 }
150 
151 // Return TRUE is a valid ID3v1 or APEv2 tag has been loaded.
152 
valid_tag(M_Tag * m_tag)153 int valid_tag (M_Tag *m_tag)
154 {
155     if (m_tag->ape_tag_hdr.ID [0] == 'A')
156         return 'A';
157     else if (m_tag->id3_tag.tag_id [0] == 'T')
158         return 'T';
159     else
160         return 0;
161 }
162 
163 // Return FALSE if a valid APEv2 tag was only found at the beginning of the file (these are read-only
164 // because they cannot be edited without possibly shifting the entire file)
165 
editable_tag(M_Tag * m_tag)166 int editable_tag (M_Tag *m_tag)
167 {
168     return !m_tag->tag_begins_file;
169 }
170 
171 // Free the data for any APEv2 tag that was allocated.
172 
free_tag(M_Tag * m_tag)173 void free_tag (M_Tag *m_tag)
174 {
175     if (m_tag->ape_tag_data) {
176         free (m_tag->ape_tag_data);
177         m_tag->ape_tag_data = NULL;
178     }
179 }
180