1 /*
2  * Copyright 2008-2013 Various Authors
3  * Copyright 2006 Chun-Yu Shei <cshei AT cs.indiana.edu>
4  *
5  * Cleaned up by Timo Hirvonen <tihirvon@gmail.com>
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License as
9  * published by the Free Software Foundation; either version 2 of the
10  * License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 #include "ape.h"
22 #include "file.h"
23 #include "xmalloc.h"
24 #include "utils.h"
25 
26 #include <errno.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <unistd.h>
30 #include <strings.h>
31 
32 /* http://www.personal.uni-jena.de/~pfk/mpp/sv8/apetag.html */
33 
34 #define PREAMBLE_SIZE (8)
35 static const char preamble[PREAMBLE_SIZE] = { 'A', 'P', 'E', 'T', 'A', 'G', 'E', 'X' };
36 
37 /* NOTE: not sizeof(struct ape_header)! */
38 #define HEADER_SIZE (32)
39 
40 /* returns position of APE header or -1 if not found */
find_ape_tag_slow(int fd)41 static int find_ape_tag_slow(int fd)
42 {
43 	char buf[4096];
44 	int match = 0;
45 	int pos = 0;
46 
47 	/* seek to start of file */
48 	if (lseek(fd, pos, SEEK_SET) == -1)
49 		return -1;
50 
51 	while (1) {
52 		int i, got = read(fd, buf, sizeof(buf));
53 
54 		if (got == -1) {
55 			if (errno == EAGAIN || errno == EINTR)
56 				continue;
57 			break;
58 		}
59 		if (got == 0)
60 			break;
61 
62 		for (i = 0; i < got; i++) {
63 			if (buf[i] != preamble[match]) {
64 				match = 0;
65 				continue;
66 			}
67 
68 			match++;
69 			if (match == PREAMBLE_SIZE)
70 				return pos + i + 1 - PREAMBLE_SIZE;
71 		}
72 		pos += got;
73 	}
74 	return -1;
75 }
76 
ape_parse_header(const char * buf,struct ape_header * h)77 static int ape_parse_header(const char *buf, struct ape_header *h)
78 {
79 	if (memcmp(buf, preamble, PREAMBLE_SIZE))
80 		return 0;
81 
82 	h->version = read_le32(buf + 8);
83 	h->size = read_le32(buf + 12);
84 	h->count = read_le32(buf + 16);
85 	h->flags = read_le32(buf + 20);
86 	return 1;
87 }
88 
read_header(int fd,struct ape_header * h)89 static int read_header(int fd, struct ape_header *h)
90 {
91 	char buf[HEADER_SIZE];
92 
93 	if (read_all(fd, buf, sizeof(buf)) != sizeof(buf))
94 		return 0;
95 
96 	return ape_parse_header(buf, h);
97 }
98 
99 /* sets fd right after the header and returns 1 if found,
100  * otherwise returns 0
101  */
find_ape_tag(int fd,struct ape_header * h,int slow)102 static int find_ape_tag(int fd, struct ape_header *h, int slow)
103 {
104 	int pos;
105 
106 	if (lseek(fd, -HEADER_SIZE, SEEK_END) == -1)
107 		return 0;
108 	if (read_header(fd, h))
109 		return 1;
110 
111 	/* try to skip ID3v1 tag at the end of the file */
112 	if (lseek(fd, -(HEADER_SIZE + 128), SEEK_END) == -1)
113 		return 0;
114 	if (read_header(fd, h))
115 		return 1;
116 
117 	if (!slow)
118 		return 0;
119 
120 	pos = find_ape_tag_slow(fd);
121 	if (pos == -1)
122 		return 0;
123 	if (lseek(fd, pos, SEEK_SET) == -1)
124 		return 0;
125 	return read_header(fd, h);
126 }
127 
128 /*
129  * All keys are ASCII and length is 2..255
130  *
131  * UTF-8:	Artist, Album, Title, Genre
132  * Integer:	Track (N or N/M)
133  * Date:	Year (release), "Record Date"
134  *
135  * UTF-8 strings are NOT zero terminated.
136  *
137  * Also support "discnumber" (vorbis) and "disc" (non-standard)
138  */
ape_parse_one(const char * buf,int size,char ** keyp,char ** valp)139 static int ape_parse_one(const char *buf, int size, char **keyp, char **valp)
140 {
141 	int pos = 0;
142 
143 	while (size - pos > 8) {
144 		uint32_t val_len, flags;
145 		char *key, *val;
146 		int64_t max_key_len, key_len;
147 
148 		val_len = read_le32(buf + pos); pos += 4;
149 		flags = read_le32(buf + pos); pos += 4;
150 
151 		max_key_len = size - pos - (int64_t)val_len - 1;
152 		if (max_key_len < 0) {
153 			/* corrupt */
154 			break;
155 		}
156 
157 		for (key_len = 0; key_len < max_key_len && buf[pos + key_len]; key_len++)
158 			; /* nothing */
159 		if (buf[pos + key_len]) {
160 			/* corrupt */
161 			break;
162 		}
163 
164 		if (!AF_IS_UTF8(flags)) {
165 			/* ignore binary data */
166 			pos += key_len + 1 + val_len;
167 			continue;
168 		}
169 
170 		key = xstrdup(buf + pos);
171 		pos += key_len + 1;
172 
173 		/* should not be NUL-terminated */
174 		val = xstrndup(buf + pos, val_len);
175 		pos += val_len;
176 
177 		/* could be moved to comment.c but I don't think anyone else would use it */
178 		if (!strcasecmp(key, "record date") || !strcasecmp(key, "year")) {
179 			free(key);
180 			key = xstrdup("date");
181 		}
182 
183 		if (!strcasecmp(key, "date")) {
184 			/* Date format
185 			 *
186 			 * 1999-08-11 12:34:56
187 			 * 1999-08-11 12:34
188 			 * 1999-08-11
189 			 * 1999-08
190 			 * 1999
191 			 * 1999-W34	(week 34, totally crazy)
192 			 *
193 			 * convert to year, pl.c supports only years anyways
194 			 *
195 			 * FIXME: which one is the most common tag (year or record date)?
196 			 */
197 			if (strlen(val) > 4)
198 				val[4] = 0;
199 		}
200 
201 		*keyp = key;
202 		*valp = val;
203 		return pos;
204 	}
205 	return -1;
206 }
207 
208 /* return the number of comments, or -1 */
ape_read_tags(struct apetag * ape,int fd,int slow)209 int ape_read_tags(struct apetag *ape, int fd, int slow)
210 {
211 	struct ape_header *h = &ape->header;
212 	int rc = -1;
213 	off_t old_pos;
214 
215 	/* save position */
216 	old_pos = lseek(fd, 0, SEEK_CUR);
217 
218 	if (!find_ape_tag(fd, h, slow))
219 		goto fail;
220 
221 	if (AF_IS_FOOTER(h->flags)) {
222 		/* seek back right after the header */
223 		if (lseek(fd, -((int)h->size), SEEK_CUR) == -1)
224 			goto fail;
225 	}
226 
227 	/* ignore insane tags */
228 	if (h->size > 1024 * 1024)
229 		goto fail;
230 
231 	ape->buf = xnew(char, h->size);
232 	if (read_all(fd, ape->buf, h->size) != h->size)
233 		goto fail;
234 
235 	rc = h->count;
236 
237 fail:
238 	lseek(fd, old_pos, SEEK_SET);
239 	return rc;
240 }
241 
242 /* returned key-name must be free'd */
ape_get_comment(struct apetag * ape,char ** val)243 char *ape_get_comment(struct apetag *ape, char **val)
244 {
245 	struct ape_header *h = &ape->header;
246 	char *key;
247 	int rc;
248 
249 	if (ape->pos >= h->size)
250 		return NULL;
251 
252 	rc = ape_parse_one(ape->buf + ape->pos, h->size - ape->pos, &key, val);
253 	if (rc < 0)
254 		return NULL;
255 	ape->pos += rc;
256 
257 	return key;
258 }
259