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: tag.c,v 1.20 2004/02/17 02:04:10 rob Exp $
20  */
21 
22 # ifdef HAVE_CONFIG_H
23 #  include "config.h"
24 # endif
25 
26 # include "global.h"
27 
28 # include <string.h>
29 # include <stdlib.h>
30 
31 # ifdef HAVE_ASSERT_H
32 #  include <assert.h>
33 # endif
34 
35 # include "id3tag.h"
36 # include "tag.h"
37 # include "frame.h"
38 # include "compat.h"
39 # include "parse.h"
40 # include "render.h"
41 # include "latin1.h"
42 # include "ucs4.h"
43 # include "genre.h"
44 # include "crc.h"
45 # include "field.h"
46 # include "util.h"
47 
48 /*
49  * NAME:	tag->new()
50  * DESCRIPTION:	allocate and return a new, empty tag
51  */
id3_tag_new(void)52 struct id3_tag *id3_tag_new(void)
53 {
54   struct id3_tag *tag;
55 
56   tag = malloc(sizeof(*tag));
57   if (tag) {
58     tag->refcount      = 0;
59     tag->version       = ID3_TAG_VERSION;
60     tag->flags         = 0;
61     tag->extendedflags = 0;
62     tag->restrictions  = 0;
63     tag->options       = /* ID3_TAG_OPTION_UNSYNCHRONISATION | */
64                          ID3_TAG_OPTION_COMPRESSION | ID3_TAG_OPTION_CRC;
65     tag->nframes       = 0;
66     tag->frames        = 0;
67     tag->paddedsize    = 0;
68   }
69 
70   return tag;
71 }
72 
73 /*
74  * NAME:	tag->delete()
75  * DESCRIPTION:	destroy a tag and deallocate all associated memory
76  */
id3_tag_delete(struct id3_tag * tag)77 void id3_tag_delete(struct id3_tag *tag)
78 {
79   assert(tag);
80 
81   if (tag->refcount == 0) {
82     id3_tag_clearframes(tag);
83 
84     if (tag->frames)
85       free(tag->frames);
86 
87     free(tag);
88   }
89 }
90 
91 /*
92  * NAME:	tag->addref()
93  * DESCRIPTION:	add an external reference to a tag
94  */
id3_tag_addref(struct id3_tag * tag)95 void id3_tag_addref(struct id3_tag *tag)
96 {
97   assert(tag);
98 
99   ++tag->refcount;
100 }
101 
102 /*
103  * NAME:	tag->delref()
104  * DESCRIPTION:	remove an external reference to a tag
105  */
id3_tag_delref(struct id3_tag * tag)106 void id3_tag_delref(struct id3_tag *tag)
107 {
108   assert(tag && tag->refcount > 0);
109 
110   --tag->refcount;
111 }
112 
113 /*
114  * NAME:	tag->version()
115  * DESCRIPTION:	return the tag's original ID3 version number
116  */
id3_tag_version(struct id3_tag const * tag)117 unsigned int id3_tag_version(struct id3_tag const *tag)
118 {
119   assert(tag);
120 
121   return tag->version;
122 }
123 
124 /*
125  * NAME:	tag->options()
126  * DESCRIPTION:	get or set tag options
127  */
id3_tag_options(struct id3_tag * tag,int mask,int values)128 int id3_tag_options(struct id3_tag *tag, int mask, int values)
129 {
130   assert(tag);
131 
132   if (mask)
133     tag->options = (tag->options & ~mask) | (values & mask);
134 
135   return tag->options;
136 }
137 
138 /*
139  * NAME:	tag->setlength()
140  * DESCRIPTION:	set the minimum rendered tag size
141  */
id3_tag_setlength(struct id3_tag * tag,id3_length_t length)142 void id3_tag_setlength(struct id3_tag *tag, id3_length_t length)
143 {
144   assert(tag);
145 
146   tag->paddedsize = length;
147 }
148 
149 /*
150  * NAME:	tag->clearframes()
151  * DESCRIPTION:	detach and delete all frames associated with a tag
152  */
id3_tag_clearframes(struct id3_tag * tag)153 void id3_tag_clearframes(struct id3_tag *tag)
154 {
155   unsigned int i;
156 
157   assert(tag);
158 
159   for (i = 0; i < tag->nframes; ++i) {
160     id3_frame_delref(tag->frames[i]);
161     id3_frame_delete(tag->frames[i]);
162   }
163 
164   tag->nframes = 0;
165 }
166 
167 /*
168  * NAME:	tag->attachframe()
169  * DESCRIPTION:	attach a frame to a tag
170  */
id3_tag_attachframe(struct id3_tag * tag,struct id3_frame * frame)171 int id3_tag_attachframe(struct id3_tag *tag, struct id3_frame *frame)
172 {
173   struct id3_frame **frames;
174 
175   assert(tag && frame);
176 
177   frames = realloc(tag->frames, (tag->nframes + 1) * sizeof(*frames));
178   if (frames == 0)
179     return -1;
180 
181   tag->frames = frames;
182   tag->frames[tag->nframes++] = frame;
183 
184   id3_frame_addref(frame);
185 
186   return 0;
187 }
188 
189 /*
190  * NAME:	tag->detachframe()
191  * DESCRIPTION:	detach (but don't delete) a frame from a tag
192  */
id3_tag_detachframe(struct id3_tag * tag,struct id3_frame * frame)193 int id3_tag_detachframe(struct id3_tag *tag, struct id3_frame *frame)
194 {
195   unsigned int i;
196 
197   assert(tag && frame);
198 
199   for (i = 0; i < tag->nframes; ++i) {
200     if (tag->frames[i] == frame)
201       break;
202   }
203 
204   if (i == tag->nframes)
205     return -1;
206 
207   --tag->nframes;
208   while (i++ < tag->nframes)
209     tag->frames[i - 1] = tag->frames[i];
210 
211   id3_frame_delref(frame);
212 
213   return 0;
214 }
215 
216 /*
217  * NAME:	tag->findframe()
218  * DESCRIPTION:	find in a tag the nth (0-based) frame with the given frame ID
219  */
id3_tag_findframe(struct id3_tag const * tag,char const * id,unsigned int index)220 struct id3_frame *id3_tag_findframe(struct id3_tag const *tag,
221 				    char const *id, unsigned int index)
222 {
223   unsigned int len, i;
224 
225   assert(tag);
226 
227   if (id == 0 || *id == 0)
228     return (index < tag->nframes) ? tag->frames[index] : 0;
229 
230   len = strlen(id);
231 
232   if (len == 4) {
233     struct id3_compat const *compat;
234 
235     compat = id3_compat_lookup(id, len);
236     if (compat && compat->equiv && !compat->translate) {
237       id  = compat->equiv;
238       len = strlen(id);
239     }
240   }
241 
242   for (i = 0; i < tag->nframes; ++i) {
243     if (strncmp(tag->frames[i]->id, id, len) == 0 && index-- == 0)
244       return tag->frames[i];
245   }
246 
247   return 0;
248 }
249 
250 enum tagtype {
251   TAGTYPE_NONE = 0,
252   TAGTYPE_ID3V1,
253   TAGTYPE_ID3V2,
254   TAGTYPE_ID3V2_FOOTER
255 };
256 
257 static
tagtype(id3_byte_t const * data,id3_length_t length)258 enum tagtype tagtype(id3_byte_t const *data, id3_length_t length)
259 {
260   if (length >= 3 &&
261       data[0] == 'T' && data[1] == 'A' && data[2] == 'G')
262     return TAGTYPE_ID3V1;
263 
264   if (length >= 10 &&
265       ((data[0] == 'I' && data[1] == 'D' && data[2] == '3') ||
266        (data[0] == '3' && data[1] == 'D' && data[2] == 'I')) &&
267       data[3] < 0xff && data[4] < 0xff &&
268       data[6] < 0x80 && data[7] < 0x80 && data[8] < 0x80 && data[9] < 0x80)
269     return data[0] == 'I' ? TAGTYPE_ID3V2 : TAGTYPE_ID3V2_FOOTER;
270 
271   return TAGTYPE_NONE;
272 }
273 
274 static
parse_header(id3_byte_t const ** ptr,unsigned int * version,int * flags,id3_length_t * size)275 void parse_header(id3_byte_t const **ptr,
276 		  unsigned int *version, int *flags, id3_length_t *size)
277 {
278   *ptr += 3;
279 
280   *version = id3_parse_uint(ptr, 2);
281   *flags   = id3_parse_uint(ptr, 1);
282   *size    = id3_parse_syncsafe(ptr, 4);
283 }
284 
285 /*
286  * NAME:	tag->query()
287  * DESCRIPTION:	if a tag begins at the given location, return its size
288  */
id3_tag_query(id3_byte_t const * data,id3_length_t length)289 signed long id3_tag_query(id3_byte_t const *data, id3_length_t length)
290 {
291   unsigned int version;
292   int flags;
293   id3_length_t size;
294 
295   assert(data);
296 
297   switch (tagtype(data, length)) {
298   case TAGTYPE_ID3V1:
299     return 128;
300 
301   case TAGTYPE_ID3V2:
302     parse_header(&data, &version, &flags, &size);
303 
304     if (flags & ID3_TAG_FLAG_FOOTERPRESENT)
305       size += 10;
306 
307     return 10 + size;
308 
309   case TAGTYPE_ID3V2_FOOTER:
310     parse_header(&data, &version, &flags, &size);
311     return -size - 10;
312 
313   case TAGTYPE_NONE:
314     break;
315   }
316 
317   return 0;
318 }
319 
320 static
trim(char * str)321 void trim(char *str)
322 {
323   char *ptr;
324 
325   ptr = str + strlen(str);
326   while (ptr > str && ptr[-1] == ' ')
327     --ptr;
328 
329   *ptr = 0;
330 }
331 
332 static
v1_attachstr(struct id3_tag * tag,char const * id,char * text,unsigned long number)333 int v1_attachstr(struct id3_tag *tag, char const *id,
334 		 char *text, unsigned long number)
335 {
336   struct id3_frame *frame;
337   id3_ucs4_t ucs4[31];
338 
339   if (text) {
340     trim(text);
341     if (*text == 0)
342       return 0;
343   }
344 
345   frame = id3_frame_new(id);
346   if (frame == 0)
347     return -1;
348 
349   if (id3_field_settextencoding(&frame->fields[0],
350 				ID3_FIELD_TEXTENCODING_ISO_8859_1) == -1)
351     goto fail;
352 
353   if (text)
354     id3_latin1_decode(text, ucs4);
355   else
356     id3_ucs4_putnumber(ucs4, number);
357 
358   if (strcmp(id, ID3_FRAME_COMMENT) == 0) {
359     if (id3_field_setlanguage(&frame->fields[1], "XXX") == -1 ||
360 	id3_field_setstring(&frame->fields[2], id3_ucs4_empty) == -1 ||
361 	id3_field_setfullstring(&frame->fields[3], ucs4) == -1)
362       goto fail;
363   }
364   else {
365     id3_ucs4_t *ptr = ucs4;
366 
367     if (id3_field_setstrings(&frame->fields[1], 1, &ptr) == -1)
368       goto fail;
369   }
370 
371   if (id3_tag_attachframe(tag, frame) == -1)
372     goto fail;
373 
374   return 0;
375 
376  fail:
377   id3_frame_delete(frame);
378   return -1;
379 }
380 
381 static
v1_parse(id3_byte_t const * data)382 struct id3_tag *v1_parse(id3_byte_t const *data)
383 {
384   struct id3_tag *tag;
385 
386   tag = id3_tag_new();
387   if (tag) {
388     char title[31], artist[31], album[31], year[5], comment[31];
389     unsigned int genre, track;
390 
391     tag->version = 0x0100;
392 
393     tag->options |=  ID3_TAG_OPTION_ID3V1;
394     tag->options &= ~ID3_TAG_OPTION_COMPRESSION;
395 
396     tag->restrictions =
397       ID3_TAG_RESTRICTION_TEXTENCODING_LATIN1_UTF8 |
398       ID3_TAG_RESTRICTION_TEXTSIZE_30_CHARS;
399 
400     title[30] = artist[30] = album[30] = year[4] = comment[30] = 0;
401 
402     memcpy(title,   &data[3],  30);
403     memcpy(artist,  &data[33], 30);
404     memcpy(album,   &data[63], 30);
405     memcpy(year,    &data[93],  4);
406     memcpy(comment, &data[97], 30);
407 
408     genre = data[127];
409 
410     track = 0;
411     if (comment[28] == 0 && comment[29] != 0) {
412       track = comment[29];
413       tag->version = 0x0101;
414     }
415 
416     /* populate tag frames */
417 
418     if (v1_attachstr(tag, ID3_FRAME_TITLE,  title,  0) == -1 ||
419 	v1_attachstr(tag, ID3_FRAME_ARTIST, artist, 0) == -1 ||
420 	v1_attachstr(tag, ID3_FRAME_ALBUM,  album,  0) == -1 ||
421 	v1_attachstr(tag, ID3_FRAME_YEAR,   year,   0) == -1 ||
422 	(track        && v1_attachstr(tag, ID3_FRAME_TRACK, 0, track) == -1) ||
423 	(genre < 0xff && v1_attachstr(tag, ID3_FRAME_GENRE, 0, genre) == -1) ||
424 	v1_attachstr(tag, ID3_FRAME_COMMENT, comment, 0) == -1) {
425       id3_tag_delete(tag);
426       tag = 0;
427     }
428   }
429 
430   return tag;
431 }
432 
433 static
v2_parse(id3_byte_t const * ptr)434 struct id3_tag *v2_parse(id3_byte_t const *ptr)
435 {
436   struct id3_tag *tag;
437   id3_byte_t *mem = 0;
438 
439   tag = id3_tag_new();
440   if (tag) {
441     id3_byte_t const *end;
442     id3_length_t size;
443 
444     parse_header(&ptr, &tag->version, &tag->flags, &size);
445 
446     tag->paddedsize = 10 + size;
447 
448     if ((tag->flags & ID3_TAG_FLAG_UNSYNCHRONISATION) &&
449 	ID3_TAG_VERSION_MAJOR(tag->version) < 4) {
450       mem = malloc(size);
451       if (mem == 0)
452 	goto fail;
453 
454       memcpy(mem, ptr, size);
455 
456       size = id3_util_deunsynchronise(mem, size);
457       ptr  = mem;
458     }
459 
460     end = ptr + size;
461 
462     if (tag->flags & ID3_TAG_FLAG_EXTENDEDHEADER) {
463       switch (ID3_TAG_VERSION_MAJOR(tag->version)) {
464       case 2:
465 	goto fail;
466 
467       case 3:
468 	{
469 	  id3_byte_t const *ehptr, *ehend;
470 	  id3_length_t ehsize;
471 
472 	  enum {
473 	    EH_FLAG_CRC = 0x8000  /* CRC data present */
474 	  };
475 
476 	  if (end - ptr < 4)
477 	    goto fail;
478 
479 	  ehsize = id3_parse_uint(&ptr, 4);
480 
481 	  if (ehsize > end - ptr)
482 	    goto fail;
483 
484 	  ehptr = ptr;
485 	  ehend = ptr + ehsize;
486 
487 	  ptr = ehend;
488 
489 	  if (ehend - ehptr >= 6) {
490 	    int ehflags;
491 	    id3_length_t padsize;
492 
493 	    ehflags = id3_parse_uint(&ehptr, 2);
494 	    padsize = id3_parse_uint(&ehptr, 4);
495 
496 	    if (padsize > end - ptr)
497 	      goto fail;
498 
499 	    end -= padsize;
500 
501 	    if (ehflags & EH_FLAG_CRC) {
502 	      unsigned long crc;
503 
504 	      if (ehend - ehptr < 4)
505 		goto fail;
506 
507 	      crc = id3_parse_uint(&ehptr, 4);
508 
509 	      if (crc != id3_crc_compute(ptr, end - ptr))
510 		goto fail;
511 
512 	      tag->extendedflags |= ID3_TAG_EXTENDEDFLAG_CRCDATAPRESENT;
513 	    }
514 	  }
515 	}
516 	break;
517 
518       case 4:
519 	{
520 	  id3_byte_t const *ehptr, *ehend;
521 	  id3_length_t ehsize;
522 	  unsigned int bytes;
523 
524 	  if (end - ptr < 4)
525 	    goto fail;
526 
527 	  ehptr  = ptr;
528 	  ehsize = id3_parse_syncsafe(&ptr, 4);
529 
530 	  if (ehsize < 6 || ehsize > end - ehptr)
531 	    goto fail;
532 
533 	  ehend = ehptr + ehsize;
534 
535 	  bytes = id3_parse_uint(&ptr, 1);
536 
537 	  if (bytes < 1 || bytes > ehend - ptr)
538 	    goto fail;
539 
540 	  ehptr = ptr + bytes;
541 
542 	  /* verify extended header size */
543 	  {
544 	    id3_byte_t const *flagsptr = ptr, *dataptr = ehptr;
545 	    unsigned int datalen;
546 	    int ehflags;
547 
548 	    while (bytes--) {
549 	      for (ehflags = id3_parse_uint(&flagsptr, 1); ehflags;
550 		   ehflags = (ehflags << 1) & 0xff) {
551 		if (ehflags & 0x80) {
552 		  if (dataptr == ehend)
553 		    goto fail;
554 		  datalen = id3_parse_uint(&dataptr, 1);
555 		  if (datalen > 0x7f || datalen > ehend - dataptr)
556 		    goto fail;
557 		  dataptr += datalen;
558 		}
559 	      }
560 	    }
561 	  }
562 
563 	  tag->extendedflags = id3_parse_uint(&ptr, 1);
564 
565 	  ptr = ehend;
566 
567 	  if (tag->extendedflags & ID3_TAG_EXTENDEDFLAG_TAGISANUPDATE) {
568 	    bytes  = id3_parse_uint(&ehptr, 1);
569 	    ehptr += bytes;
570 	  }
571 
572 	  if (tag->extendedflags & ID3_TAG_EXTENDEDFLAG_CRCDATAPRESENT) {
573 	    unsigned long crc;
574 
575 	    bytes = id3_parse_uint(&ehptr, 1);
576 	    if (bytes < 5)
577 	      goto fail;
578 
579 	    crc = id3_parse_syncsafe(&ehptr, 5);
580 	    ehptr += bytes - 5;
581 
582 	    if (crc != id3_crc_compute(ptr, end - ptr))
583 	      goto fail;
584 	  }
585 
586 	  if (tag->extendedflags & ID3_TAG_EXTENDEDFLAG_TAGRESTRICTIONS) {
587 	    bytes = id3_parse_uint(&ehptr, 1);
588 	    if (bytes < 1)
589 	      goto fail;
590 
591 	    tag->restrictions = id3_parse_uint(&ehptr, 1);
592 	    ehptr += bytes - 1;
593 	  }
594 	}
595 	break;
596       }
597     }
598 
599     /* frames */
600 
601     while (ptr < end) {
602       struct id3_frame *frame;
603 
604       if (*ptr == 0)
605 	break;  /* padding */
606 
607       frame = id3_frame_parse(&ptr, end - ptr, tag->version);
608       if (frame == 0 || id3_tag_attachframe(tag, frame) == -1)
609 	goto fail;
610     }
611 
612     if (ID3_TAG_VERSION_MAJOR(tag->version) < 4 &&
613 	id3_compat_fixup(tag) == -1)
614       goto fail;
615   }
616 
617   if (0) {
618   fail:
619     id3_tag_delete(tag);
620     tag = 0;
621   }
622 
623   if (mem)
624     free(mem);
625 
626   return tag;
627 }
628 
629 /*
630  * NAME:	tag->parse()
631  * DESCRIPTION:	parse a complete ID3 tag
632  */
id3_tag_parse(id3_byte_t const * data,id3_length_t length)633 struct id3_tag *id3_tag_parse(id3_byte_t const *data, id3_length_t length)
634 {
635   id3_byte_t const *ptr;
636   unsigned int version;
637   int flags;
638   id3_length_t size;
639 
640   assert(data);
641 
642   switch (tagtype(data, length)) {
643   case TAGTYPE_ID3V1:
644     return (length < 128) ? 0 : v1_parse(data);
645 
646   case TAGTYPE_ID3V2:
647     break;
648 
649   case TAGTYPE_ID3V2_FOOTER:
650   case TAGTYPE_NONE:
651     return 0;
652   }
653 
654   /* ID3v2.x */
655 
656   ptr = data;
657   parse_header(&ptr, &version, &flags, &size);
658 
659   switch (ID3_TAG_VERSION_MAJOR(version)) {
660   case 4:
661     if (flags & ID3_TAG_FLAG_FOOTERPRESENT)
662       size += 10;
663   case 2:
664   case 3:
665     return (length < 10 + size) ? 0 : v2_parse(data);
666   }
667 
668   return 0;
669 }
670 
671 static
v1_renderstr(struct id3_tag const * tag,char const * frameid,id3_byte_t ** buffer,id3_length_t length)672 void v1_renderstr(struct id3_tag const *tag, char const *frameid,
673 		  id3_byte_t **buffer, id3_length_t length)
674 {
675   struct id3_frame *frame;
676   id3_ucs4_t const *string;
677 
678   frame = id3_tag_findframe(tag, frameid, 0);
679   if (frame == 0)
680     string = id3_ucs4_empty;
681   else {
682     if (strcmp(frameid, ID3_FRAME_COMMENT) == 0)
683       string = id3_field_getfullstring(&frame->fields[3]);
684     else
685       string = id3_field_getstrings(&frame->fields[1], 0);
686   }
687 
688   id3_render_paddedstring(buffer, string, length);
689 }
690 
691 /*
692  * NAME:	v1->render()
693  * DESCRIPTION:	render an ID3v1 (or ID3v1.1) tag
694  */
695 static
v1_render(struct id3_tag const * tag,id3_byte_t * buffer)696 id3_length_t v1_render(struct id3_tag const *tag, id3_byte_t *buffer)
697 {
698   id3_byte_t data[128], *ptr;
699   struct id3_frame *frame;
700   unsigned int i;
701   int genre = -1;
702 
703   ptr = data;
704 
705   id3_render_immediate(&ptr, "TAG", 3);
706 
707   v1_renderstr(tag, ID3_FRAME_TITLE,   &ptr, 30);
708   v1_renderstr(tag, ID3_FRAME_ARTIST,  &ptr, 30);
709   v1_renderstr(tag, ID3_FRAME_ALBUM,   &ptr, 30);
710   v1_renderstr(tag, ID3_FRAME_YEAR,    &ptr,  4);
711   v1_renderstr(tag, ID3_FRAME_COMMENT, &ptr, 30);
712 
713   /* ID3v1.1 track number */
714 
715   frame = id3_tag_findframe(tag, ID3_FRAME_TRACK, 0);
716   if (frame) {
717     unsigned int track;
718 
719     track = id3_ucs4_getnumber(id3_field_getstrings(&frame->fields[1], 0));
720     if (track > 0 && track <= 0xff) {
721       ptr[-2] = 0;
722       ptr[-1] = track;
723     }
724   }
725 
726   /* ID3v1 genre number */
727 
728   frame = id3_tag_findframe(tag, ID3_FRAME_GENRE, 0);
729   if (frame) {
730     unsigned int nstrings;
731 
732     nstrings = id3_field_getnstrings(&frame->fields[1]);
733 
734     for (i = 0; i < nstrings; ++i) {
735       genre = id3_genre_number(id3_field_getstrings(&frame->fields[1], i));
736       if (genre != -1)
737 	break;
738     }
739 
740     if (i == nstrings && nstrings > 0)
741       genre = ID3_GENRE_OTHER;
742   }
743 
744   id3_render_int(&ptr, genre, 1);
745 
746   /* make sure the tag is not empty */
747 
748   if (genre == -1) {
749     for (i = 3; i < 127; ++i) {
750       if (data[i] != ' ')
751 	break;
752     }
753 
754     if (i == 127)
755       return 0;
756   }
757 
758   if (buffer)
759     memcpy(buffer, data, 128);
760 
761   return 128;
762 }
763 
764 /*
765  * NAME:	tag->render()
766  * DESCRIPTION:	render a complete ID3 tag
767  */
id3_tag_render(struct id3_tag const * tag,id3_byte_t * buffer)768 id3_length_t id3_tag_render(struct id3_tag const *tag, id3_byte_t *buffer)
769 {
770   id3_length_t size = 0;
771   id3_byte_t **ptr,
772     *header_ptr = 0, *tagsize_ptr = 0, *crc_ptr = 0, *frames_ptr = 0;
773   int flags, extendedflags;
774   unsigned int i;
775 
776   assert(tag);
777 
778   if (tag->options & ID3_TAG_OPTION_ID3V1)
779     return v1_render(tag, buffer);
780 
781   /* a tag must contain at least one (renderable) frame */
782 
783   for (i = 0; i < tag->nframes; ++i) {
784     if (id3_frame_render(tag->frames[i], 0, 0) > 0)
785       break;
786   }
787 
788   if (i == tag->nframes)
789     return 0;
790 
791   ptr = buffer ? &buffer : 0;
792 
793   /* get flags */
794 
795   flags         = tag->flags         & ID3_TAG_FLAG_KNOWNFLAGS;
796   extendedflags = tag->extendedflags & ID3_TAG_EXTENDEDFLAG_KNOWNFLAGS;
797 
798   extendedflags &= ~ID3_TAG_EXTENDEDFLAG_CRCDATAPRESENT;
799   if (tag->options & ID3_TAG_OPTION_CRC)
800     extendedflags |= ID3_TAG_EXTENDEDFLAG_CRCDATAPRESENT;
801 
802   extendedflags &= ~ID3_TAG_EXTENDEDFLAG_TAGRESTRICTIONS;
803   if (tag->restrictions)
804     extendedflags |= ID3_TAG_EXTENDEDFLAG_TAGRESTRICTIONS;
805 
806   flags &= ~ID3_TAG_FLAG_UNSYNCHRONISATION;
807   if (tag->options & ID3_TAG_OPTION_UNSYNCHRONISATION)
808     flags |= ID3_TAG_FLAG_UNSYNCHRONISATION;
809 
810   flags &= ~ID3_TAG_FLAG_EXTENDEDHEADER;
811   if (extendedflags)
812     flags |= ID3_TAG_FLAG_EXTENDEDHEADER;
813 
814   flags &= ~ID3_TAG_FLAG_FOOTERPRESENT;
815   if (tag->options & ID3_TAG_OPTION_APPENDEDTAG)
816     flags |= ID3_TAG_FLAG_FOOTERPRESENT;
817 
818   /* header */
819 
820   if (ptr)
821     header_ptr = *ptr;
822 
823   size += id3_render_immediate(ptr, "ID3", 3);
824   size += id3_render_int(ptr, ID3_TAG_VERSION, 2);
825   size += id3_render_int(ptr, flags, 1);
826 
827   if (ptr)
828     tagsize_ptr = *ptr;
829 
830   size += id3_render_syncsafe(ptr, 0, 4);
831 
832   /* extended header */
833 
834   if (flags & ID3_TAG_FLAG_EXTENDEDHEADER) {
835     id3_length_t ehsize = 0;
836     id3_byte_t *ehsize_ptr = 0;
837 
838     if (ptr)
839       ehsize_ptr = *ptr;
840 
841     ehsize += id3_render_syncsafe(ptr, 0, 4);
842     ehsize += id3_render_int(ptr, 1, 1);
843     ehsize += id3_render_int(ptr, extendedflags, 1);
844 
845     if (extendedflags & ID3_TAG_EXTENDEDFLAG_TAGISANUPDATE)
846       ehsize += id3_render_int(ptr, 0, 1);
847 
848     if (extendedflags & ID3_TAG_EXTENDEDFLAG_CRCDATAPRESENT) {
849       ehsize += id3_render_int(ptr, 5, 1);
850 
851       if (ptr)
852 	crc_ptr = *ptr;
853 
854       ehsize += id3_render_syncsafe(ptr, 0, 5);
855     }
856 
857     if (extendedflags & ID3_TAG_EXTENDEDFLAG_TAGRESTRICTIONS) {
858       ehsize += id3_render_int(ptr, 1, 1);
859       ehsize += id3_render_int(ptr, tag->restrictions, 1);
860     }
861 
862     if (ehsize_ptr)
863       id3_render_syncsafe(&ehsize_ptr, ehsize, 4);
864 
865     size += ehsize;
866   }
867 
868   /* frames */
869 
870   if (ptr)
871     frames_ptr = *ptr;
872 
873   for (i = 0; i < tag->nframes; ++i)
874     size += id3_frame_render(tag->frames[i], ptr, tag->options);
875 
876   /* padding */
877 
878   if (!(flags & ID3_TAG_FLAG_FOOTERPRESENT)) {
879     if (size < tag->paddedsize)
880       size += id3_render_padding(ptr, 0, tag->paddedsize - size);
881     else if (tag->options & ID3_TAG_OPTION_UNSYNCHRONISATION) {
882       if (ptr == 0)
883 	size += 1;
884       else {
885 	if ((*ptr)[-1] == 0xff)
886 	  size += id3_render_padding(ptr, 0, 1);
887       }
888     }
889   }
890 
891   /* patch tag size and CRC */
892 
893   if (tagsize_ptr)
894     id3_render_syncsafe(&tagsize_ptr, size - 10, 4);
895 
896   if (crc_ptr) {
897     id3_render_syncsafe(&crc_ptr,
898 			id3_crc_compute(frames_ptr, *ptr - frames_ptr), 5);
899   }
900 
901   /* footer */
902 
903   if (flags & ID3_TAG_FLAG_FOOTERPRESENT) {
904     size += id3_render_immediate(ptr, "3DI", 3);
905     size += id3_render_binary(ptr, header_ptr + 3, 7);
906   }
907 
908   return size;
909 }
910