flac: Saner EOF handling
[cmus.git] / ape.c
blob63c5758af686eb161e9e4bb2d4524aee738be28b
1 /*
2 * Copyright 2006 Chun-Yu Shei <cshei AT cs.indiana.edu>
4 * Cleaned up by Timo Hirvonen <tihirvon@gmail.com>
5 */
7 #include "ip.h"
8 #include "ape.h"
9 #include "file.h"
10 #include "xmalloc.h"
11 #include "utils.h"
13 #include <errno.h>
14 #include <sys/types.h>
15 #include <sys/stat.h>
16 #include <unistd.h>
18 /* http://www.personal.uni-jena.de/~pfk/mpp/sv8/apetag.html */
20 #define PREAMBLE_SIZE (8)
21 static const char preamble[PREAMBLE_SIZE] = { 'A', 'P', 'E', 'T', 'A', 'G', 'E', 'X' };
23 /* NOTE: not sizeof(struct ape_header)! */
24 #define HEADER_SIZE (32)
26 /* returns position of APE header or -1 if not found */
27 static int find_ape_tag_slow(int fd)
29 char buf[4096];
30 int match = 0;
31 int pos = 0;
33 /* seek to start of file */
34 if (lseek(fd, pos, SEEK_SET) == -1)
35 return -1;
37 while (1) {
38 int i, got = read(fd, buf, sizeof(buf));
40 if (got == -1) {
41 if (errno == EAGAIN || errno == EINTR)
42 continue;
43 break;
45 if (got == 0)
46 break;
48 for (i = 0; i < got; i++) {
49 if (buf[i] != preamble[match]) {
50 match = 0;
51 continue;
54 match++;
55 if (match == PREAMBLE_SIZE)
56 return pos + i + 1 - PREAMBLE_SIZE;
58 pos += got;
60 return -1;
63 static int ape_parse_header(const char *buf, struct ape_header *h)
65 if (memcmp(buf, preamble, PREAMBLE_SIZE))
66 return 0;
68 h->version = read_le32(buf + 8);
69 h->size = read_le32(buf + 12);
70 h->count = read_le32(buf + 16);
71 h->flags = read_le32(buf + 20);
72 return 1;
75 static int read_header(int fd, struct ape_header *h)
77 char buf[HEADER_SIZE];
79 if (read_all(fd, buf, sizeof(buf)) != sizeof(buf))
80 return 0;
81 return ape_parse_header(buf, h);
84 /* sets fd right after the header and returns 1 if found,
85 * otherwise returns 0
87 static int find_ape_tag(int fd, struct ape_header *h, int slow)
89 int pos;
91 if (lseek(fd, -HEADER_SIZE, SEEK_END) == -1)
92 return 0;
93 if (read_header(fd, h))
94 return 1;
96 if (!slow)
97 return 0;
99 pos = find_ape_tag_slow(fd);
100 if (pos == -1)
101 return 0;
102 if (lseek(fd, pos, SEEK_SET) == -1)
103 return 0;
104 return read_header(fd, h);
108 * All keys are ASCII and length is 2..255
110 * UTF-8: Artist, Album, Title, Genre
111 * Integer: Track (N or N/M)
112 * Date: Year (release), "Record Date"
114 * UTF-8 strings are NOT zero terminated.
116 * Also support "discnumber" (vorbis) and "disc" (non-standard)
118 static int ape_parse_one(const char *buf, int size, char **keyp, char **valp)
120 int pos = 0;
122 while (size - pos > 8) {
123 uint32_t val_len, flags;
124 char *key, *val;
125 int max_key_len, key_len;
127 val_len = read_le32(buf + pos); pos += 4;
128 flags = read_le32(buf + pos); pos += 4;
130 max_key_len = size - pos - val_len - 1;
131 if (max_key_len < 0) {
132 /* corrupt */
133 break;
136 for (key_len = 0; key_len < max_key_len && buf[pos + key_len]; key_len++)
137 ; /* nothing */
138 if (buf[pos + key_len]) {
139 /* corrupt */
140 break;
143 if (!AF_IS_UTF8(flags)) {
144 /* ignore binary data */
145 pos += key_len + 1 + val_len;
146 continue;
149 key = xstrdup(buf + pos);
150 pos += key_len + 1;
152 /* should not be NUL-terminated */
153 val = xstrndup(buf + pos, val_len);
154 pos += val_len;
156 /* could be moved to comment.c but I don't think anyone else would use it */
157 if (!strcasecmp(key, "record date") || !strcasecmp(key, "year")) {
158 free(key);
159 key = xstrdup("date");
162 if (!strcasecmp(key, "date")) {
163 /* Date format
165 * 1999-08-11 12:34:56
166 * 1999-08-11 12:34
167 * 1999-08-11
168 * 1999-08
169 * 1999
170 * 1999-W34 (week 34, totally crazy)
172 * convert to year, pl.c supports only years anyways
174 * FIXME: which one is the most common tag (year or record date)?
176 if (strlen(val) > 4)
177 val[4] = 0;
180 *keyp = key;
181 *valp = val;
182 return pos;
184 return -1;
187 static off_t get_size(int fd)
189 struct stat statbuf;
191 if (fstat(fd, &statbuf) || !(statbuf.st_mode & S_IFREG))
192 return 0;
193 return statbuf.st_size;
196 /* return the number of comments, or -1 */
197 int ape_read_tags(struct apetag *ape, int fd, int slow)
199 struct ape_header *h = &ape->header;
200 int old_pos, rc = -1;
202 /* save position */
203 old_pos = lseek(fd, 0, SEEK_CUR);
205 if (!find_ape_tag(fd, h, slow))
206 goto fail;
208 if (AF_IS_FOOTER(h->flags)) {
209 /* seek back right after the header */
210 if (lseek(fd, get_size(fd) - h->size, SEEK_SET) == -1)
211 goto fail;
214 /* ignore insane tags */
215 if (h->size > 1024 * 1024)
216 goto fail;
218 ape->buf = xnew(char, h->size);
219 if (read_all(fd, ape->buf, h->size) != h->size)
220 goto fail;
222 rc = h->count;
224 fail:
225 lseek(fd, old_pos, SEEK_SET);
226 return rc;
229 /* returned key-name must be free'd */
230 char *ape_get_comment(struct apetag *ape, char **val)
232 struct ape_header *h = &ape->header;
233 char *key;
234 int rc;
236 if (ape->pos >= h->size)
237 return NULL;
239 rc = ape_parse_one(ape->buf + ape->pos, h->size - ape->pos, &key, val);
240 if (rc < 0)
241 return NULL;
242 ape->pos += rc;
244 return key;