1 #include <stdlib.h>
2 #include <stdio.h>
3 #include <string.h>
4 #include <ctype.h>
5 #include <errno.h>
6 #include <limits.h>
7 #include "fileops.h"
8 #include "id3v2.h"
9
10 /*
11
12 copyright (c) 2003-2006, 2015 squell <squell@alumina.nl>
13
14 use, modification, copying and distribution of this software is permitted
15 under the conditions described in the file 'COPYING'.
16
17 NOTE! Unsynchronization is only supported for reading
18
19 */
20
21 /* some programs write evil tags. it's probably nice to see failure modes */
22 #ifdef ID3v2_DEBUG
23 # define refuse(label, msg, info) { \
24 fprintf(stderr, "%s: id3v2 "msg"\n", fname, info); \
25 goto label; \
26 }
27 #else
28 # define refuse(label, msg, info) goto label;
29 #endif
30
31 /* fix bugs introduced by other programs? */
32 #define ID3v2_FIX 1
33
34 typedef unsigned long ulong;
35 typedef unsigned char uchar;
36
37 enum ID3_hdr_flag {
38 UNSYNC = 0x80,
39 XTND = 0x40, /* 2.3; in 2.2: unused compression bit */
40 XPER = 0x20,
41 FOOTER = 0x10 /* 2.4 */
42 };
43
44 enum ID3_frm_flag1 {
45 TAP = 0x80, /* 2.3, in 2.4: shifted 1 to the right */
46 FAP = 0x40,
47 RO = 0x20
48 };
49
50 enum ID3_frm_flag2 {
51 PACK = 0x80, /* 2.3 */
52 ENC = 0x40,
53 GRP = 0x20,
54
55 GRP4 = 0x40, /* 2.4 */
56 PACK4 = 0x08,
57 ENC4 = 0x04,
58 UNSYNC4= 0x02,
59 DLI4 = 0x01
60 };
61
62 struct raw_hdr {
63 uchar ID [3];
64 uchar ver;
65 uchar rev;
66 uchar flags;
67 uchar size [4];
68 };
69
70 union raw_frm {
71 uchar ID[4];
72 struct raw_frm_2 {
73 uchar ID [3];
74 uchar size [3];
75 } v2;
76 struct raw_frm_3 {
77 uchar ID [4];
78 uchar size [4];
79 uchar flags[2];
80 } v3;
81 };
82
83 typedef int raw_hdr_size_check [sizeof(struct raw_hdr)==10 ? 1 : -1];
84 typedef int raw_frm_size_check [sizeof(union raw_frm)==10 ? 1 : -1];
85
86 /* ==================================================== */
87
88 /* en-/de- unsynchronizing */
89
unsync_dec(uchar * dst,uchar * src,ulong size)90 static uchar *unsync_dec(uchar *dst, uchar *src, ulong size)
91 {
92 while(size--)
93 if( (*dst++ = *src++) == 0xFF && size > 0 ) {
94 size--;
95 if( (*dst = *src++) != 0x00 ) dst++;
96 }
97 return dst;
98 }
99
100 /* ==================================================== */
101
ul4(uchar n[4])102 static ulong ul4(uchar n[4])
103 {
104 return (ulong)n[0]<<24
105 | (ulong)n[1]<<16
106 | (ulong)n[2]<< 8
107 | (ulong)n[3]<< 0;
108 }
109
nbo4(uchar h[4],ulong n)110 static void nbo4(uchar h[4], ulong n)
111 {
112 h[0] = (n >> 24) & 0xFF;
113 h[1] = (n >> 16) & 0xFF;
114 h[2] = (n >> 8) & 0xFF;
115 h[3] = (n ) & 0xFF;
116 }
117
ul4ss(uchar h[4])118 static ulong ul4ss(uchar h[4]) /* "synch safe" */
119 {
120 return (ulong)(h[0] & 0x7F) << 21
121 | (ulong)(h[1] & 0x7F) << 14
122 | (ulong)(h[2] & 0x7F) << 7
123 | (ulong)(h[3] & 0x7F);
124 }
125
nbo4ss(uchar h[4],ulong n)126 static void nbo4ss(uchar h[4], ulong n)
127 {
128 h[0] = (n >> 21) & 0x7F;
129 h[1] = (n >> 14) & 0x7F;
130 h[2] = (n >> 7) & 0x7F;
131 h[3] = (n ) & 0x7F;
132 }
133
checkid(const char * ID,size_t n)134 static int checkid(const char *ID, size_t n) /* check ID for A..Z0..9 */
135 {
136 while(n--) {
137 if( !isupper(*ID) && !isdigit(*ID) ) return 0;
138 ++ID;
139 }
140 return 1;
141 }
142
calcsize(uchar * buf,ulong max)143 static long calcsize(uchar *buf, ulong max)
144 {
145 union raw_frm *frame;
146 ulong size = 0;
147 ulong step;
148 int version = buf[-1];
149 int ID_siz = 3+(version>2);
150
151 while(size < max && checkid((char*)buf, ID_siz)) {
152 frame = (union raw_frm*)buf;
153 switch(version) {
154 case 2: step = sizeof(frame->v2) + (ul4(frame->v2.size) >> 8); break;
155 case 3: step = sizeof(frame->v3) + ul4(frame->v3.size); break;
156 case 4: step = sizeof(frame->v3) + ul4ss(frame->v3.size); break;
157 default: return -1;
158 }
159 if(size+step <= size) return -1;
160 size += step;
161 buf += step;
162 }
163 return size<=max? (long)size : -1;
164 }
165
166 /* in v2.4, unsync is per-frame for not adequately explained reasons.
167 this function requires the actual size as determined by calcsize() */
168
unsync_frames_v2_4(uchar * buf,ulong size)169 static ulong unsync_frames_v2_4(uchar *buf, ulong size)
170 {
171 uchar *end = buf+size;
172 uchar *out = buf;
173
174 while(buf < end) {
175 union raw_frm *frame = (union raw_frm*)buf;
176 ulong step = sizeof(frame->v3) + ul4ss(frame->v3.size);
177 if( frame->v3.flags[1] & UNSYNC4 ) {
178 frame = (union raw_frm*)out;
179 out = unsync_dec(out, buf, step);
180 /* update frame size & clear UNSYNC4 bit */
181 nbo4ss(frame->v3.size, out-frame->ID - sizeof(*frame));
182 frame->v3.flags[1] &= ~UNSYNC4;
183 } else {
184 out = (uchar*)memmove(out, buf, step) + step;
185 }
186 buf += step;
187 }
188 memset(out, 0, buf-out);
189 return out - (end-size);
190 }
191
192 /* ==================================================== */
193
ID3_readf(const char * fname,size_t * tagsize)194 void *ID3_readf(const char *fname, size_t *tagsize)
195 {
196 struct raw_hdr rh;
197 uchar *buf;
198 long pad, size = 0;
199
200 FILE *f = fopen(fname, "rb");
201
202 if( !f )
203 refuse(abort, "could not open", 0);
204
205 if( fread(&rh, sizeof(struct raw_hdr), 1, f) != 1 )
206 refuse(abort_file, "file too small", 0); /* IO error */
207
208 if( memcmp(rh.ID, "ID3", 3) != 0 ) /* not an ID3v2 tag */
209 refuse(abort_file, "contains no ID3 identifier", 0);
210
211 if( rh.ver < 2 || rh.ver > 4 ) /* unknown version */
212 refuse(abort_file, "unsupported ID3v2.%d", rh.ver);
213
214 size = ul4ss(rh.size);
215
216 buf = malloc(size+1+4); /* over-alloc 4+1 chars */
217 if(!buf) /* ohhhhhhh.. crap. */
218 refuse(abort_file, "could not allocate tag (%ld bytes)", size);
219
220 (++buf)[-1] = rh.ver; /* prepend version byte */
221 buf[size] = 0; /* make sure we have a pseudoframe to terminate */
222
223 if( fread(buf,1,size,f) != size )
224 refuse(abort_mem, "could not read tag from file (%ld bytes)", size);
225
226 if( rh.flags & UNSYNC && rh.ver <= 3 ) /* unsync on entire tag */
227 size = unsync_dec(buf, buf, size) - buf;
228
229 if( rh.flags & XTND ) { /* get rid of extended header */
230 ulong xsiz = (rh.ver==3?ul4:ul4ss)(buf) + 4;
231 if(xsiz < 4 || xsiz >= size)
232 refuse(abort_mem, "extended header incorrect (%ld bytes)", xsiz);
233 size -= xsiz; /* but try anyway */
234 memmove(&buf[0], &buf[xsiz], size);
235 }
236
237 pad = size; /* check semantics of tag */
238 size = calcsize(buf, size);
239
240 if( rh.ver == 4 && size > 0 ) /* v2.4: just ignore the global UNSYNC */
241 size = unsync_frames_v2_4(buf, size);
242
243 if(tagsize) *tagsize = size;
244
245 if(size < 0) /* semantic error in tag */
246 refuse(abort_mem, "tag larger than reported size (%ld bytes)", pad);
247
248 while(size < pad)
249 if( buf[size++] == 0xff ) { /* padding not zero */
250 if(size-1 == pad-sizeof(struct raw_hdr) && ID3v2_FIX)
251 ; /* tag contains a rare bug; make an exception */
252 else
253 refuse(abort_mem, "padding contains framesync (%02x)", buf[size-1]);
254 }
255
256 ; /* nothing required to handle ID3v2.4 footers */
257
258 fclose(f);
259 return --buf;
260
261 abort_mem: /* de-alloc, close file and return failure */
262 free(--buf);
263 abort_file: /* close file and return failure */
264 fclose(f);
265 abort:
266 if(tagsize) *tagsize = size;
267 return 0;
268 }
269
270 #ifndef ID3v2_READONLY
271
_wfail(const char * srcname,const char * dstname)272 static void _wfail(const char *srcname, const char *dstname)
273 {
274 fprintf(stderr, "%s -> %s: %s\n", srcname, dstname, strerror(errno));
275 exit(255); /* sayonara! */
276 }
277
278 void (*ID3_wfail)(const char *srcname, const char *dstname) = _wfail;
279
ID3_writef(const char * fname,const void * buf,size_t reqsize)280 int ID3_writef(const char *fname, const void *buf, size_t reqsize)
281 {
282 struct raw_hdr new_h = { "ID3", 0, 0, 0, { 0, } };
283 struct raw_hdr rh = { { 0 } }; /* duct tape */
284 uchar* src;
285 long size = 0;
286
287 FILE *f = fopen(fname, "rb+");
288 if(!f) return 0;
289
290 if(buf) {
291 src = (uchar*)buf + 1;
292 size = calcsize(src, LONG_MAX);
293 new_h.ver = src[-1];
294 }
295
296 if(size < 0)
297 goto abort; /* error in caller */
298
299 if( fread(&rh, sizeof(struct raw_hdr), 1, f) && memcmp(rh.ID, "ID3", 3) == 0 ) {
300 long orig; /* allready tagged? */
301
302 if( rh.ver < 2 || rh.ver > 4 )
303 goto abort; /* handles ID3v2.[234] */
304
305 orig = ul4ss(rh.size);
306
307 if( fseek(f, orig, SEEK_CUR) != 0 )
308 goto abort;
309
310 if( ID3v2_FIX && fseek(f, -10, SEEK_CUR) == 0 ) {
311 if(ungetc(getc(f), f) == 0xFF) /* fix off-by-10 error */
312 orig -= 10;
313 else
314 if( fseek(f, 10, SEEK_CUR) != 0 ) goto abort;
315 }
316
317 if( size>0 && size<=orig && !reqsize) { /* enough reserved space */
318 nbo4ss(new_h.size, orig);
319 rewind(f);
320 fwrite(&new_h, sizeof new_h, 1, f);
321 fwrite(src, size, 1, f);
322 fpadd(0, orig-size, f);
323 if(ferror(f) | fclose(f)) { /* abandon all hope, */
324 ID3_wfail(fname, fname); /* ye who enter here */
325 return 0;
326 }
327 return 1;
328 }
329 } else {
330 if(size == 0) {
331 fclose(f);
332 return 1;
333 }
334 rewind(f);
335 }
336 /* file rewriter */
337 {
338 ulong nsize = ((size+sizeof new_h+0x1FF) & ~0x1FF) - sizeof new_h;
339 int ok; /* rnd to 512 bytes */
340
341 char *tmp;
342 FILE *nf = opentemp(fname, &tmp);
343
344 if( reqsize ) {
345 reqsize = (reqsize < sizeof new_h)? 0 : reqsize - sizeof new_h;
346 nsize = (size < reqsize)? reqsize : size;
347 }
348
349 if( !nf )
350 goto abort;
351
352 if(size != 0) {
353 nbo4ss(new_h.size, nsize);
354 ok = fwrite(&new_h, 1, sizeof new_h, nf) == sizeof new_h
355 && fwrite(src, 1, size, nf) == size
356 && fpadd(0, nsize-size, nf) == nsize-size
357 && fcopy(nf, f);
358 } else {
359 ok = fcopy(nf, f); /* remove ID3v2 tag only */
360 }
361 fclose(f);
362 ok = fclose(nf) == 0 && ok;
363
364 if(ok) {
365 ok = mvfile(tmp, fname);
366 if(!ok)
367 ID3_wfail(tmp, fname); /* grievous error */
368 } else {
369 remove(tmp); /* failure */
370 }
371 free(tmp);
372 return ok;
373 }
374
375 abort: /* close file and return failure */
376 fclose(f);
377 return 0;
378 }
379
380 #endif
381
ID3_free(const void * buf)382 void ID3_free(const void *buf)
383 {
384 free((void*)buf);
385 }
386
387 /* ==================================================== */
388
389 static const size_t raw_frm_sizeof[2]
390 = { sizeof(struct raw_frm_2), sizeof(struct raw_frm_3) };
391
ID3_start(ID3FRAME f,const void * buf)392 ID3VER ID3_start(ID3FRAME f, const void *buf)
393 {
394 register uchar ver = *(uchar*)buf;
395 f->_rev = ver-2;
396 f->ID[3] = f->ID[4] = 0;
397 f->data = (char*)buf + 1;
398 f->size = 0;
399
400 f->tag_volit = /* set highly useful data */
401 f->file_volit =
402 f->readonly =
403 f->packed =
404 f->encrypted =
405 f->grouped = 0;
406
407 return ver>=2 && ver<=4? 2+(ver>2) : 0; /* pretend v2.4 = v2.3 */
408 }
409
ID3_frame(ID3FRAME f)410 int ID3_frame(ID3FRAME f)
411 {
412 union raw_frm *frame = (union raw_frm*)(f->data + f->size);
413 int version = f->_rev+2;
414 int ID_siz = 3+(version>2);
415
416 f->data += f->size + raw_frm_sizeof[version>2];
417
418 memcpy(f->ID, frame->ID, ID_siz);
419
420 if(version==3) { /* ID3v2.3 stuff */
421 f->size = ul4(frame->v3.size); /* copy essentials */
422 f->tag_volit = !!( frame->v3.flags[0] & TAP );
423 f->file_volit = !!( frame->v3.flags[0] & FAP );
424 f->readonly = !!( frame->v3.flags[0] & RO );
425
426 f->packed = !!( frame->v3.flags[1] & PACK );
427 f->encrypted = !!( frame->v3.flags[1] & ENC );
428 f->grouped = !!( frame->v3.flags[1] & GRP );
429 } else if(version==4) {
430 f->size = ul4ss(frame->v3.size);
431 f->tag_volit = !!( frame->v3.flags[0]>>1 & TAP );
432 f->file_volit = !!( frame->v3.flags[0]>>1 & FAP );
433 f->readonly = !!( frame->v3.flags[0]>>1 & RO );
434
435 f->packed = !!( frame->v3.flags[1] & PACK4 );
436 f->encrypted = !!( frame->v3.flags[1] & ENC4 );
437 f->grouped = !!( frame->v3.flags[1] & GRP4 );
438
439 if( frame->v3.flags[1] & DLI4 ) /* id3v2.4 crufty stuff */
440 f->data += 4, f->size -= 4;
441 } else {
442 f->size = ul4(frame->v2.size) >> 8;
443 }
444
445 return checkid(f->ID, ID_siz);
446 }
447
448 /* ==================================================== */
449
450 #ifndef ID3v2_READONLY
451
ID3_put(void * dest,ID3VER version,const char ID[4],const void * src,size_t len)452 void *ID3_put(void *dest, ID3VER version, const char ID[4], const void *src, size_t len)
453 {
454 union raw_frm *frame = (union raw_frm*)dest;
455 uchar *cdest = dest;
456
457 if(!ID) {
458 (++cdest)[-1] = version; /* initialize */
459 cdest[0] = 0;
460 return cdest;
461 } else if((version|1) != 3 || !checkid(ID, version+1) || ID[version+1]) {
462 return cdest;
463 }
464
465 memcpy(frame->ID, ID, version+1);
466 if(version == 3) { /* ID3v2.3 stuff */
467 nbo4(frame->v3.size, len);
468 frame->v3.flags[0] = 0;
469 frame->v3.flags[1] = 0;
470 } else {
471 nbo4(frame->v2.size, len << 8); /* extra byte doesn't matter */
472 }
473
474 cdest = memcpy(cdest + raw_frm_sizeof[version==3], src, len);
475 cdest[len] = 0; /* suffixing 0 */
476 return cdest + len;
477 }
478
479 #endif
480