1 #include <vector>
2 #include <cstdio>
3 #include <cstring>
4 #include "char_ucs.h"
5 #include "char_utf8.h"
6 #include "id3v2.h"
7 #include "getid3v2.h"
8 
9 /*
10 
11   copyright (c) 2004, 2005, 2015 squell <squell@alumina.nl>
12 
13   use, modification, copying and distribution of this software is permitted
14   under the conditions described in the file 'COPYING'.
15 
16 */
17 
18 using namespace std;
19 using namespace charset;
20 
21 using tag::read::ID3v2;
22 using tag::ID3field;
23 
24 static bool getframe(ID3FRAME f, const char* field, size_t n);
25 
26 // setid3v2 also needs the following function; but the outside world doesn't
27 namespace tag {
28     extern ID3v2::value_string unbinarize(ID3FRAME, charset::conv<>& descriptor);
29 }
30 
ID3v2(const char * fn)31 ID3v2::ID3v2(const char* fn) : tag(ID3_readf(fn,0))
32 {
33 }
34 
ID3v2(const void * id3v2_data)35 ID3v2::ID3v2(const void* id3v2_data) : tag(id3v2_data)
36 {
37 }
38 
~ID3v2()39 ID3v2::~ID3v2()
40 {
41     ID3_free(tag);
42 }
43 
operator [](ID3field field) const44 ID3v2::value_string ID3v2::operator[](ID3field field) const
45 {
46     const char* const fieldtag[FIELD_MAX][2] = {       // ID3field order!
47         { "TT2"  "TT3"  "TOF",
48           "TIT2" "TIT3" "TOFN" "TRSN" },
49         { "TP1"  "TCM"  "TP2"  "TP3"  "TP4"  "TXT"  "TOA"  "TOL"  "TPB",
50           "TPE1" "TCOM" "TPE2" "TPE3" "TPE4" "TEXT" "TOPE" "TOLY" "TPUB" "TRSO" },
51         { "TAL"  "TOT",
52           "TALB" "TOAL" },
53         { "TYE"  "TOR",
54           "TYER" "TORY" },
55         { "COM"  "TCR",
56           "COMM" "TCOP" "USER" },
57         { "TRK"  "TPA",
58           "TRCK" "TPOS" },
59         { "TCO"  "TT1",
60           "TCON" "TIT1" }
61     };
62 
63     ID3FRAME f;
64     if(tag && field < FIELD_MAX) {
65         const bool  version = ID3_start(f, tag) > 2;
66         const char*  id_str = fieldtag[field][version];
67         const size_t id_len = 3+version;
68 
69         // return the first ID of the above list that produces a clear match
70         for( ; *id_str != '\0'; id_str += id_len) {
71             ID3_start(f, tag);
72             while( getframe(f, id_str, id_len) ) {
73                 charset::conv<local> desc;
74                 value_string val = unbinarize(f, desc);
75                 if(desc.length() <= 5) // allow for "::lan"; which works because
76                     return val;        // in fieldtag: has_desc() implies has_lang()
77             }
78         }
79     } else if(tag && field == FIELD_MAX) {
80         ID3_start(f, tag);
81         char buf[] = "ID3v2._";
82         buf[6] = f->_rev+'2';
83         return buf;
84     }
85     return value_string();
86 }
87 
88 /* ====================================================== */
89 
listing() const90 ID3v2::array ID3v2::listing() const
91 {
92     ID3FRAME f;
93 
94     array vec;
95     if(tag) {
96         ID3VER version = ID3_start(f, tag);
97         char   vstr[4] = { char(version+'0'), '.', '0', };
98         vec.push_back( array::value_type("ID3v2", vstr) );
99         while(ID3_frame(f)) {
100             charset::conv<local> desc;
101             value_string val = unbinarize(f, desc);
102             vec.push_back( array::value_type(f->ID+string(desc), val) );
103         }
104     }
105     return vec;
106 }
107 
108 /* ====================================================== */
109 
getframe(ID3FRAME f,const char * field,size_t n)110 static bool getframe(ID3FRAME f, const char* field, size_t n)
111 {
112     while(ID3_frame(f))
113         if(strncmp(field, f->ID, n) == 0) {
114             if(f->ID[0] == 'T') {
115                 if(f->size > 1) return 1;
116             } else
117                 return 1;
118         }
119     return 0;
120 }
121 
122   // safely find a terminator in multi-string frames
123 
membrk0(const char * buf,size_t size,bool wide)124 static const char *membrk0(const char* buf, size_t size, bool wide)
125 {
126     const char* const end = buf + size - wide;
127     const int step = 1+wide;
128     for( ; buf < end; buf += step) {
129         if(!buf[0] && !buf[wide])
130             return buf;
131     }
132     return 0;
133 }
134 
unbinarize(ID3FRAME f,charset::conv<> & descriptor)135 extern ID3v2::value_string tag::unbinarize(ID3FRAME f, charset::conv<>& descriptor)
136 {
137     typedef conv<latin1> cs;
138 
139     const string field  = f->ID;
140     const char*  p      = f->data + 1;
141     bool wide           = *f->data == 1 || *f->data == 2;
142 
143     if(f->packed || f->encrypted || f->grouped)
144         return ID3v2::value_string(cs("<compressed or encrypted>"),0);
145 
146     if(ID3v2::is_counter(field)) {
147         char buf[12];                          // enough for 32bits
148         unsigned long t = 0;
149         for(size_t n = 0; n < f->size; ++n)
150             t = t << 8 | (f->data[n] & 0xFF);
151         sprintf(buf, "%lu", t & 0xFFFFFFFFul);
152         return conv<latin1>(buf);
153     }
154 
155     const char* lang = 0;
156     if(ID3v2::has_lang(field)) {
157         lang = p;
158         if(memchr(lang,'\0',3))                // some programs use null-bytes
159             lang = "xxx";
160         p += 3;                                // skip-ignore language field
161     }
162     if(ID3v2::has_desc(field)) {
163         const char *q = membrk0(p, f->size - (p - f->data), wide);
164         if(!q) return conv<>();                // malformed frame
165         descriptor = conv<charset::latin1>(":");
166         switch(*f->data) {
167             case 0: descriptor += conv<charset::latin1> (p, q-p); break;
168             case 1: descriptor += conv<charset::utf16>  (p, q-p); break;
169             case 2: descriptor += conv<charset::utf16be>(p, q-p); break;
170             case 3: descriptor += conv<charset::utf8>   (p, q-p); break;
171         }
172         p = q+1+wide;
173     } else if(lang) {
174         descriptor += ':';                     // only used by USER; enfore consistency in notation
175     }
176     if(lang)
177         (descriptor += ':') += conv<charset::latin1>(lang,3);
178 
179     size_t hdrsiz = p - f->data;
180     if(hdrsiz > 1 || ID3v2::is_text(field)) {
181         size_t txtsiz = f->size - hdrsiz;
182         char encoding = *f->data;
183         if(ID3v2::is_url(field)) {
184             wide = encoding = 0;               // the WXXX frame is so weird
185         }
186         if(const char *q = membrk0(p, txtsiz, wide)) {
187             txtsiz = q - p;                    // useless null-terminator
188         }
189         switch(encoding) {
190         case  0:
191             return conv<latin1> (p, txtsiz);
192         case  1:
193             return conv<utf16>  (p, txtsiz);
194         case  2:
195             return conv<utf16be>(p, txtsiz);
196         case  3:
197             return conv<utf8>   (p, txtsiz);
198         default:
199             return ID3v2::value_string(cs("<unsupported encoding>"),0);
200         };
201     } else if(ID3v2::is_url(field)) {
202         return conv<latin1>(f->data, f->size);
203     } else {
204         return ID3v2::value_string(cs("<binary data>"), 0);
205     }
206 }
207 
208