1 /*
2  * libid3tag - ID3 tag manipulation library
3  * Copyright (C) 2000-2004 Underbit Technologies, Inc.
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  *
19  * $Id: file.c,v 1.21 2004/01/23 09:41:32 rob Exp $
20  */
21 
22 # ifdef HAVE_CONFIG_H
23 #  include "config.h"
24 # endif
25 
26 # include "global.h"
27 
28 # include <stdio.h>
29 # include <stdlib.h>
30 # include <string.h>
31 
32 # ifdef HAVE_UNISTD_H
33 #  include <unistd.h>
34 # endif
35 
36 # ifdef HAVE_ASSERT_H
37 #  include <assert.h>
38 # endif
39 
40 # include "id3tag.h"
41 # include "file.h"
42 # include "tag.h"
43 # include "field.h"
44 
45 struct filetag {
46   struct id3_tag *tag;
47   unsigned long location;
48   id3_length_t length;
49 };
50 
51 struct id3_file {
52   FILE *iofile;
53   enum id3_file_mode mode;
54   char *path;
55 
56   int flags;
57 
58   struct id3_tag *primary;
59 
60   unsigned int ntags;
61   struct filetag *tags;
62 };
63 
64 enum {
65   ID3_FILE_FLAG_ID3V1 = 0x0001
66 };
67 
68 /*
69  * NAME:	query_tag()
70  * DESCRIPTION:	check for a tag at a file's current position
71  */
72 static
query_tag(FILE * iofile)73 signed long query_tag(FILE *iofile)
74 {
75   fpos_t save_position;
76   id3_byte_t query[ID3_TAG_QUERYSIZE];
77   signed long size;
78 
79   if (fgetpos(iofile, &save_position) == -1)
80     return 0;
81 
82   size = id3_tag_query(query, fread(query, 1, sizeof(query), iofile));
83 
84   if (fsetpos(iofile, &save_position) == -1)
85     return 0;
86 
87   return size;
88 }
89 
90 /*
91  * NAME:	read_tag()
92  * DESCRIPTION:	read and parse a tag at a file's current position
93  */
94 static
read_tag(FILE * iofile,id3_length_t size)95 struct id3_tag *read_tag(FILE *iofile, id3_length_t size)
96 {
97   id3_byte_t *data;
98   struct id3_tag *tag = 0;
99 
100   data = malloc(size);
101   if (data) {
102     if (fread(data, size, 1, iofile) == 1)
103       tag = id3_tag_parse(data, size);
104 
105     free(data);
106   }
107 
108   return tag;
109 }
110 
111 /*
112  * NAME:	update_primary()
113  * DESCRIPTION:	update the primary tag with data from a new tag
114  */
115 static
update_primary(struct id3_tag * tag,struct id3_tag const * new)116 int update_primary(struct id3_tag *tag, struct id3_tag const *new)
117 {
118   unsigned int i;
119   struct id3_frame *frame;
120 
121   if (new) {
122     if (!(new->extendedflags & ID3_TAG_EXTENDEDFLAG_TAGISANUPDATE))
123       id3_tag_clearframes(tag);
124 
125     i = 0;
126     while ((frame = id3_tag_findframe(new, 0, i++))) {
127       if (id3_tag_attachframe(tag, frame) == -1)
128 	return -1;
129     }
130   }
131 
132   return 0;
133 }
134 
135 /*
136  * NAME:	tag_compare()
137  * DESCRIPTION:	tag sort function for qsort()
138  */
139 static
tag_compare(const void * a,const void * b)140 int tag_compare(const void *a, const void *b)
141 {
142   struct filetag const *tag1 = a, *tag2 = b;
143 
144   if (tag1->location < tag2->location)
145     return -1;
146   else if (tag1->location > tag2->location)
147     return +1;
148 
149   return 0;
150 }
151 
152 /*
153  * NAME:	add_filetag()
154  * DESCRIPTION:	add a new file tag entry
155  */
156 static
add_filetag(struct id3_file * file,struct filetag const * filetag)157 int add_filetag(struct id3_file *file, struct filetag const *filetag)
158 {
159   struct filetag *tags;
160 
161   tags = realloc(file->tags, (file->ntags + 1) * sizeof(*tags));
162   if (tags == 0)
163     return -1;
164 
165   file->tags = tags;
166   file->tags[file->ntags++] = *filetag;
167 
168   /* sort tags by location */
169 
170   if (file->ntags > 1)
171     qsort(file->tags, file->ntags, sizeof(file->tags[0]), tag_compare);
172 
173   return 0;
174 }
175 
176 /*
177  * NAME:	del_filetag()
178  * DESCRIPTION:	delete a file tag entry
179  */
180 static
del_filetag(struct id3_file * file,unsigned int index)181 void del_filetag(struct id3_file *file, unsigned int index)
182 {
183   assert(index < file->ntags);
184 
185   while (index < file->ntags - 1) {
186     file->tags[index] = file->tags[index + 1];
187     ++index;
188   }
189 
190   --file->ntags;
191 }
192 
193 /*
194  * NAME:	add_tag()
195  * DESCRIPTION:	read, parse, and add a tag to a file structure
196  */
197 static
add_tag(struct id3_file * file,id3_length_t length)198 struct id3_tag *add_tag(struct id3_file *file, id3_length_t length)
199 {
200   long location;
201   unsigned int i;
202   struct filetag filetag;
203   struct id3_tag *tag;
204 
205   location = ftell(file->iofile);
206   if (location == -1)
207     return 0;
208 
209   /* check for duplication/overlap */
210   {
211     unsigned long begin1, end1, begin2, end2;
212 
213     begin1 = location;
214     end1   = begin1 + length;
215 
216     for (i = 0; i < file->ntags; ++i) {
217       begin2 = file->tags[i].location;
218       end2   = begin2 + file->tags[i].length;
219 
220       if (begin1 == begin2 && end1 == end2)
221 	return file->tags[i].tag;  /* duplicate */
222 
223       if (begin1 < end2 && end1 > begin2)
224 	return 0;  /* overlap */
225     }
226   }
227 
228   tag = read_tag(file->iofile, length);
229 
230   filetag.tag      = tag;
231   filetag.location = location;
232   filetag.length   = length;
233 
234   if (add_filetag(file, &filetag) == -1 ||
235       update_primary(file->primary, tag) == -1) {
236     if (tag)
237       id3_tag_delete(tag);
238     return 0;
239   }
240 
241   if (tag)
242     id3_tag_addref(tag);
243 
244   return tag;
245 }
246 
247 /*
248  * NAME:	search_tags()
249  * DESCRIPTION:	search for tags in a file
250  */
251 static
search_tags(struct id3_file * file)252 int search_tags(struct id3_file *file)
253 {
254   fpos_t save_position;
255   signed long size;
256 
257   /*
258    * save the current seek position
259    *
260    * We also verify the stream is seekable by calling fsetpos(), since
261    * fgetpos() alone is not reliable enough for this purpose.
262    *
263    * [Apparently not even fsetpos() is sufficient under Win32.]
264    */
265 
266   if (fgetpos(file->iofile, &save_position) == -1 ||
267       fsetpos(file->iofile, &save_position) == -1)
268     return -1;
269 
270   /* look for an ID3v1 tag */
271 
272   if (fseek(file->iofile, -128, SEEK_END) == 0) {
273     size = query_tag(file->iofile);
274     if (size > 0) {
275       struct id3_tag const *tag;
276 
277       tag = add_tag(file, size);
278 
279       /* if this is indeed an ID3v1 tag, mark the file so */
280 
281       if (tag && (ID3_TAG_VERSION_MAJOR(id3_tag_version(tag)) == 1))
282 	file->flags |= ID3_FILE_FLAG_ID3V1;
283     }
284   }
285 
286   /* look for a tag at the beginning of the file */
287 
288   rewind(file->iofile);
289 
290   size = query_tag(file->iofile);
291   if (size > 0) {
292     struct id3_tag const *tag;
293     struct id3_frame const *frame;
294 
295     tag = add_tag(file, size);
296 
297     /* locate tags indicated by SEEK frames */
298 
299     while (tag && (frame = id3_tag_findframe(tag, "SEEK", 0))) {
300       long seek;
301 
302       seek = id3_field_getint(id3_frame_field(frame, 0));
303       if (seek < 0 || fseek(file->iofile, seek, SEEK_CUR) == -1)
304 	break;
305 
306       size = query_tag(file->iofile);
307       tag  = (size > 0) ? add_tag(file, size) : 0;
308     }
309   }
310 
311   /* look for a tag at the end of the file (before any ID3v1 tag) */
312 
313   if (fseek(file->iofile, ((file->flags & ID3_FILE_FLAG_ID3V1) ? -128 : 0) +
314 	    -10, SEEK_END) == 0) {
315     size = query_tag(file->iofile);
316     if (size < 0 && fseek(file->iofile, size, SEEK_CUR) == 0) {
317       size = query_tag(file->iofile);
318       if (size > 0)
319 	add_tag(file, size);
320     }
321   }
322 
323   clearerr(file->iofile);
324 
325   /* restore seek position */
326 
327   if (fsetpos(file->iofile, &save_position) == -1)
328     return -1;
329 
330   /* set primary tag options and target padded length for convenience */
331 
332   if ((file->ntags > 0 && !(file->flags & ID3_FILE_FLAG_ID3V1)) ||
333       (file->ntags > 1 &&  (file->flags & ID3_FILE_FLAG_ID3V1))) {
334     if (file->tags[0].location == 0)
335       id3_tag_setlength(file->primary, file->tags[0].length);
336     else
337       id3_tag_options(file->primary, ID3_TAG_OPTION_APPENDEDTAG, ~0);
338   }
339 
340   return 0;
341 }
342 
343 /*
344  * NAME:	finish_file()
345  * DESCRIPTION:	release memory associated with a file
346  */
347 static
finish_file(struct id3_file * file)348 void finish_file(struct id3_file *file)
349 {
350   unsigned int i;
351 
352   if (file->path)
353     free(file->path);
354 
355   if (file->primary) {
356     id3_tag_delref(file->primary);
357     id3_tag_delete(file->primary);
358   }
359 
360   for (i = 0; i < file->ntags; ++i) {
361     struct id3_tag *tag;
362 
363     tag = file->tags[i].tag;
364     if (tag) {
365       id3_tag_delref(tag);
366       id3_tag_delete(tag);
367     }
368   }
369 
370   if (file->tags)
371     free(file->tags);
372 
373   free(file);
374 }
375 
376 /*
377  * NAME:	new_file()
378  * DESCRIPTION:	create a new file structure and load tags
379  */
380 static
new_file(FILE * iofile,enum id3_file_mode mode,char const * path)381 struct id3_file *new_file(FILE *iofile, enum id3_file_mode mode,
382 			  char const *path)
383 {
384   struct id3_file *file;
385 
386   file = malloc(sizeof(*file));
387   if (file == 0)
388     goto fail;
389 
390   file->iofile  = iofile;
391   file->mode    = mode;
392   file->path    = path ? strdup(path) : 0;
393 
394   file->flags   = 0;
395 
396   file->ntags   = 0;
397   file->tags    = 0;
398 
399   file->primary = id3_tag_new();
400   if (file->primary == 0)
401     goto fail;
402 
403   id3_tag_addref(file->primary);
404 
405   /* load tags from the file */
406 
407   if (search_tags(file) == -1)
408     goto fail;
409 
410   id3_tag_options(file->primary, ID3_TAG_OPTION_ID3V1,
411 		  (file->flags & ID3_FILE_FLAG_ID3V1) ? ~0 : 0);
412 
413   if (0) {
414   fail:
415     if (file) {
416       finish_file(file);
417       file = 0;
418     }
419   }
420 
421   return file;
422 }
423 
424 /*
425  * NAME:	file->open()
426  * DESCRIPTION:	open a file given its pathname
427  */
id3_file_open(char const * path,enum id3_file_mode mode)428 struct id3_file *id3_file_open(char const *path, enum id3_file_mode mode)
429 {
430   FILE *iofile;
431   struct id3_file *file;
432 
433   assert(path);
434 
435   iofile = fopen(path, (mode == ID3_FILE_MODE_READWRITE) ? "r+b" : "rb");
436   if (iofile == 0)
437     return 0;
438 
439   file = new_file(iofile, mode, path);
440   if (file == 0)
441     fclose(iofile);
442 
443   return file;
444 }
445 
446 /*
447  * NAME:	file->fdopen()
448  * DESCRIPTION:	open a file using an existing file descriptor
449  */
id3_file_fdopen(int fd,enum id3_file_mode mode)450 struct id3_file *id3_file_fdopen(int fd, enum id3_file_mode mode)
451 {
452 # if 1 || defined(HAVE_UNISTD_H)
453   FILE *iofile;
454   struct id3_file *file;
455 
456   iofile = fdopen(fd, (mode == ID3_FILE_MODE_READWRITE) ? "r+b" : "rb");
457   if (iofile == 0)
458     return 0;
459 
460   file = new_file(iofile, mode, 0);
461   if (file == 0) {
462     int save_fd;
463 
464     /* close iofile without closing fd */
465 
466     save_fd = dup(fd);
467 
468     fclose(iofile);
469 
470     dup2(save_fd, fd);
471     close(save_fd);
472   }
473 
474   return file;
475 # else
476   return 0;
477 # endif
478 }
479 
480 /*
481  * NAME:	file->close()
482  * DESCRIPTION:	close a file and delete its associated tags
483  */
id3_file_close(struct id3_file * file)484 int id3_file_close(struct id3_file *file)
485 {
486   int result = 0;
487 
488   assert(file);
489 
490   if (fclose(file->iofile) == EOF)
491     result = -1;
492 
493   finish_file(file);
494 
495   return result;
496 }
497 
498 /*
499  * NAME:	file->tag()
500  * DESCRIPTION:	return the primary tag structure for a file
501  */
id3_file_tag(struct id3_file const * file)502 struct id3_tag *id3_file_tag(struct id3_file const *file)
503 {
504   assert(file);
505 
506   return file->primary;
507 }
508 
509 /*
510  * NAME:	v1_write()
511  * DESCRIPTION:	write ID3v1 tag modifications to a file
512  */
513 static
v1_write(struct id3_file * file,id3_byte_t const * data,id3_length_t length)514 int v1_write(struct id3_file *file,
515 	     id3_byte_t const *data, id3_length_t length)
516 {
517   assert(!data || length == 128);
518 
519   if (data) {
520     long location;
521 
522     if (fseek(file->iofile, (file->flags & ID3_FILE_FLAG_ID3V1) ? -128 : 0,
523 	      SEEK_END) == -1 ||
524 	(location = ftell(file->iofile)) == -1 ||
525 	fwrite(data, 128, 1, file->iofile) != 1 ||
526 	fflush(file->iofile) == EOF)
527       return -1;
528 
529     /* add file tag reference */
530 
531     if (!(file->flags & ID3_FILE_FLAG_ID3V1)) {
532       struct filetag filetag;
533 
534       filetag.tag      = 0;
535       filetag.location = location;
536       filetag.length   = 128;
537 
538       if (add_filetag(file, &filetag) == -1)
539 	return -1;
540 
541       file->flags |= ID3_FILE_FLAG_ID3V1;
542     }
543   }
544 # if defined(HAVE_FTRUNCATE)
545   else if (file->flags & ID3_FILE_FLAG_ID3V1) {
546     long length;
547 
548     if (fseek(file->iofile, 0, SEEK_END) == -1)
549       return -1;
550 
551     length = ftell(file->iofile);
552     if (length == -1 ||
553 	(length >= 0 && length < 128))
554       return -1;
555 
556     if (ftruncate(fileno(file->iofile), length - 128) == -1)
557       return -1;
558 
559     /* delete file tag reference */
560 
561     del_filetag(file, file->ntags - 1);
562 
563     file->flags &= ~ID3_FILE_FLAG_ID3V1;
564   }
565 # endif
566 
567   return 0;
568 }
569 
570 /*
571  * NAME:	v2_write()
572  * DESCRIPTION:	write ID3v2 tag modifications to a file
573  */
574 static
v2_write(struct id3_file * file,id3_byte_t const * data,id3_length_t length)575 int v2_write(struct id3_file *file,
576 	     id3_byte_t const *data, id3_length_t length)
577 {
578   assert(!data || length > 0);
579 
580   if (data &&
581       ((file->ntags == 1 && !(file->flags & ID3_FILE_FLAG_ID3V1)) ||
582        (file->ntags == 2 &&  (file->flags & ID3_FILE_FLAG_ID3V1))) &&
583       file->tags[0].length == length) {
584     /* easy special case: rewrite existing tag in-place */
585 
586     if (fseek(file->iofile, file->tags[0].location, SEEK_SET) == -1 ||
587 	fwrite(data, length, 1, file->iofile) != 1 ||
588 	fflush(file->iofile) == EOF)
589       return -1;
590 
591     goto done;
592   }
593 
594   /* hard general case: rewrite entire file */
595 
596   /* ... */
597 
598  done:
599   return 0;
600 }
601 
602 /*
603  * NAME:	file->update()
604  * DESCRIPTION:	rewrite tag(s) to a file
605  */
id3_file_update(struct id3_file * file)606 int id3_file_update(struct id3_file *file)
607 {
608   int options, result = 0;
609   id3_length_t v1size = 0, v2size = 0;
610   id3_byte_t id3v1_data[128], *id3v1 = 0, *id3v2 = 0;
611 
612   assert(file);
613 
614   if (file->mode != ID3_FILE_MODE_READWRITE)
615     return -1;
616 
617   options = id3_tag_options(file->primary, 0, 0);
618 
619   /* render ID3v1 */
620 
621   if (options & ID3_TAG_OPTION_ID3V1) {
622     v1size = id3_tag_render(file->primary, 0);
623     if (v1size) {
624       assert(v1size == sizeof(id3v1_data));
625 
626       v1size = id3_tag_render(file->primary, id3v1_data);
627       if (v1size) {
628 	assert(v1size == sizeof(id3v1_data));
629 	id3v1 = id3v1_data;
630       }
631     }
632   }
633 
634   /* render ID3v2 */
635 
636   id3_tag_options(file->primary, ID3_TAG_OPTION_ID3V1, 0);
637 
638   v2size = id3_tag_render(file->primary, 0);
639   if (v2size) {
640     id3v2 = malloc(v2size);
641     if (id3v2 == 0)
642       goto fail;
643 
644     v2size = id3_tag_render(file->primary, id3v2);
645     if (v2size == 0) {
646       free(id3v2);
647       id3v2 = 0;
648     }
649   }
650 
651   /* write tags */
652 
653   if (v2_write(file, id3v2, v2size) == -1 ||
654       v1_write(file, id3v1, v1size) == -1)
655     goto fail;
656 
657   rewind(file->iofile);
658 
659   /* update file tags array? ... */
660 
661   if (0) {
662   fail:
663     result = -1;
664   }
665 
666   /* clean up; restore tag options */
667 
668   if (id3v2)
669     free(id3v2);
670 
671   id3_tag_options(file->primary, ~0, options);
672 
673   return result;
674 }
675