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