1 /***************************************************************************
2     copyright            : (C) 2013 by Lukas Krejci
3     email                : krejclu6@fel.cvut.cz
4  ***************************************************************************/
5 
6 /***************************************************************************
7  *   This library is free software; you can redistribute it and/or modify  *
8  *   it under the terms of the GNU Lesser General Public License version   *
9  *   2.1 as published by the Free Software Foundation.                     *
10  *                                                                         *
11  *   This library is distributed in the hope that it will be useful, but   *
12  *   WITHOUT ANY WARRANTY; without even the implied warranty of            *
13  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU     *
14  *   Lesser General Public License for more details.                       *
15  *                                                                         *
16  *   You should have received a copy of the GNU Lesser General Public      *
17  *   License along with this library; if not, write to the Free Software   *
18  *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA         *
19  *   02110-1301  USA                                                       *
20  *                                                                         *
21  *   Alternatively, this file is available under the Mozilla Public        *
22  *   License Version 1.1.  You may obtain a copy of the License at         *
23  *   http://www.mozilla.org/MPL/                                           *
24  ***************************************************************************/
25 
26 #include <tbytevectorlist.h>
27 #include <tpropertymap.h>
28 #include <tdebug.h>
29 
30 #include "tableofcontentsframe.h"
31 
32 using namespace TagLib;
33 using namespace ID3v2;
34 
35 class TableOfContentsFrame::TableOfContentsFramePrivate
36 {
37 public:
TableOfContentsFramePrivate()38   TableOfContentsFramePrivate() :
39     tagHeader(0),
40     isTopLevel(false),
41     isOrdered(false)
42   {
43     embeddedFrameList.setAutoDelete(true);
44   }
45 
46   const ID3v2::Header *tagHeader;
47   ByteVector elementID;
48   bool isTopLevel;
49   bool isOrdered;
50   ByteVectorList childElements;
51   FrameListMap embeddedFrameListMap;
52   FrameList embeddedFrameList;
53 };
54 
55 namespace {
56 
57   // These functions are needed to try to aim for backward compatibility with
58   // an API that previously (unreasonably) required null bytes to be appended
59   // at the end of identifiers explicitly by the API user.
60 
61   // BIC: remove these
62 
strip(ByteVector & b)63   ByteVector &strip(ByteVector &b)
64   {
65     if(b.endsWith('\0'))
66       b.resize(b.size() - 1);
67     return b;
68   }
69 
strip(ByteVectorList & l)70   ByteVectorList &strip(ByteVectorList &l)
71   {
72     for(ByteVectorList::Iterator it = l.begin(); it != l.end(); ++it)
73     {
74       strip(*it);
75     }
76     return l;
77   }
78 }
79 
80 ////////////////////////////////////////////////////////////////////////////////
81 // public methods
82 ////////////////////////////////////////////////////////////////////////////////
83 
TableOfContentsFrame(const ID3v2::Header * tagHeader,const ByteVector & data)84 TableOfContentsFrame::TableOfContentsFrame(const ID3v2::Header *tagHeader, const ByteVector &data) :
85   ID3v2::Frame(data),
86   d(new TableOfContentsFramePrivate())
87 {
88   d->tagHeader = tagHeader;
89   setData(data);
90 }
91 
TableOfContentsFrame(const ByteVector & elementID,const ByteVectorList & children,const FrameList & embeddedFrames)92 TableOfContentsFrame::TableOfContentsFrame(const ByteVector &elementID,
93                                            const ByteVectorList &children,
94                                            const FrameList &embeddedFrames) :
95   ID3v2::Frame("CTOC"),
96   d(new TableOfContentsFramePrivate())
97 {
98   d->elementID = elementID;
99   strip(d->elementID);
100   d->childElements = children;
101 
102   for(FrameList::ConstIterator it = embeddedFrames.begin(); it != embeddedFrames.end(); ++it)
103     addEmbeddedFrame(*it);
104 }
105 
~TableOfContentsFrame()106 TableOfContentsFrame::~TableOfContentsFrame()
107 {
108   delete d;
109 }
110 
elementID() const111 ByteVector TableOfContentsFrame::elementID() const
112 {
113   return d->elementID;
114 }
115 
isTopLevel() const116 bool TableOfContentsFrame::isTopLevel() const
117 {
118   return d->isTopLevel;
119 }
120 
isOrdered() const121 bool TableOfContentsFrame::isOrdered() const
122 {
123   return d->isOrdered;
124 }
125 
entryCount() const126 unsigned int TableOfContentsFrame::entryCount() const
127 {
128   return d->childElements.size();
129 }
130 
childElements() const131 ByteVectorList TableOfContentsFrame::childElements() const
132 {
133   return d->childElements;
134 }
135 
setElementID(const ByteVector & eID)136 void TableOfContentsFrame::setElementID(const ByteVector &eID)
137 {
138   d->elementID = eID;
139   strip(d->elementID);
140 }
141 
setIsTopLevel(const bool & t)142 void TableOfContentsFrame::setIsTopLevel(const bool &t)
143 {
144   d->isTopLevel = t;
145 }
146 
setIsOrdered(const bool & o)147 void TableOfContentsFrame::setIsOrdered(const bool &o)
148 {
149   d->isOrdered = o;
150 }
151 
setChildElements(const ByteVectorList & l)152 void TableOfContentsFrame::setChildElements(const ByteVectorList &l)
153 {
154   d->childElements = l;
155   strip(d->childElements);
156 }
157 
addChildElement(const ByteVector & cE)158 void TableOfContentsFrame::addChildElement(const ByteVector &cE)
159 {
160   d->childElements.append(cE);
161   strip(d->childElements);
162 }
163 
removeChildElement(const ByteVector & cE)164 void TableOfContentsFrame::removeChildElement(const ByteVector &cE)
165 {
166   ByteVectorList::Iterator it = d->childElements.find(cE);
167 
168   if(it == d->childElements.end())
169     it = d->childElements.find(cE + ByteVector("\0"));
170 
171   d->childElements.erase(it);
172 }
173 
embeddedFrameListMap() const174 const FrameListMap &TableOfContentsFrame::embeddedFrameListMap() const
175 {
176   return d->embeddedFrameListMap;
177 }
178 
embeddedFrameList() const179 const FrameList &TableOfContentsFrame::embeddedFrameList() const
180 {
181   return d->embeddedFrameList;
182 }
183 
embeddedFrameList(const ByteVector & frameID) const184 const FrameList &TableOfContentsFrame::embeddedFrameList(const ByteVector &frameID) const
185 {
186   return d->embeddedFrameListMap[frameID];
187 }
188 
addEmbeddedFrame(Frame * frame)189 void TableOfContentsFrame::addEmbeddedFrame(Frame *frame)
190 {
191   d->embeddedFrameList.append(frame);
192   d->embeddedFrameListMap[frame->frameID()].append(frame);
193 }
194 
removeEmbeddedFrame(Frame * frame,bool del)195 void TableOfContentsFrame::removeEmbeddedFrame(Frame *frame, bool del)
196 {
197   // remove the frame from the frame list
198   FrameList::Iterator it = d->embeddedFrameList.find(frame);
199   d->embeddedFrameList.erase(it);
200 
201   // ...and from the frame list map
202   it = d->embeddedFrameListMap[frame->frameID()].find(frame);
203   d->embeddedFrameListMap[frame->frameID()].erase(it);
204 
205   // ...and delete as desired
206   if(del)
207     delete frame;
208 }
209 
removeEmbeddedFrames(const ByteVector & id)210 void TableOfContentsFrame::removeEmbeddedFrames(const ByteVector &id)
211 {
212   FrameList l = d->embeddedFrameListMap[id];
213   for(FrameList::ConstIterator it = l.begin(); it != l.end(); ++it)
214     removeEmbeddedFrame(*it, true);
215 }
216 
toString() const217 String TableOfContentsFrame::toString() const
218 {
219   String s = String(d->elementID) +
220              ": top level: " + (d->isTopLevel ? "true" : "false") +
221              ", ordered: " + (d->isOrdered ? "true" : "false");
222 
223   if(!d->childElements.isEmpty()) {
224     s+= ", chapters: [ " + String(d->childElements.toByteVector(", ")) + " ]";
225   }
226 
227   if(!d->embeddedFrameList.isEmpty()) {
228     StringList frameIDs;
229     for(FrameList::ConstIterator it = d->embeddedFrameList.begin();
230         it != d->embeddedFrameList.end(); ++it)
231       frameIDs.append((*it)->frameID());
232     s += ", sub-frames: [ " + frameIDs.toString(", ") + " ]";
233   }
234 
235   return s;
236 }
237 
asProperties() const238 PropertyMap TableOfContentsFrame::asProperties() const
239 {
240   PropertyMap map;
241 
242   map.unsupportedData().append(frameID() + String("/") + d->elementID);
243 
244   return map;
245 }
246 
findByElementID(const ID3v2::Tag * tag,const ByteVector & eID)247 TableOfContentsFrame *TableOfContentsFrame::findByElementID(const ID3v2::Tag *tag,
248                                                             const ByteVector &eID) // static
249 {
250   ID3v2::FrameList tablesOfContents = tag->frameList("CTOC");
251 
252   for(ID3v2::FrameList::ConstIterator it = tablesOfContents.begin();
253       it != tablesOfContents.end();
254       ++it)
255   {
256     TableOfContentsFrame *frame = dynamic_cast<TableOfContentsFrame *>(*it);
257     if(frame && frame->elementID() == eID)
258       return frame;
259   }
260 
261   return 0;
262 }
263 
findTopLevel(const ID3v2::Tag * tag)264 TableOfContentsFrame *TableOfContentsFrame::findTopLevel(const ID3v2::Tag *tag) // static
265 {
266   ID3v2::FrameList tablesOfContents = tag->frameList("CTOC");
267 
268   for(ID3v2::FrameList::ConstIterator it = tablesOfContents.begin();
269       it != tablesOfContents.end();
270       ++it)
271   {
272     TableOfContentsFrame *frame = dynamic_cast<TableOfContentsFrame *>(*it);
273     if(frame && frame->isTopLevel() == true)
274       return frame;
275   }
276 
277   return 0;
278 }
279 
parseFields(const ByteVector & data)280 void TableOfContentsFrame::parseFields(const ByteVector &data)
281 {
282   unsigned int size = data.size();
283   if(size < 6) {
284     debug("A CTOC frame must contain at least 6 bytes (1 byte element ID terminated by "
285           "null, 1 byte flags, 1 byte entry count and 1 byte child element ID terminated "
286           "by null.");
287     return;
288   }
289 
290   int pos = 0;
291   unsigned int embPos = 0;
292   d->elementID = readStringField(data, String::Latin1, &pos).data(String::Latin1);
293   d->isTopLevel = (data.at(pos) & 2) != 0;
294   d->isOrdered = (data.at(pos++) & 1) != 0;
295   unsigned int entryCount = static_cast<unsigned char>(data.at(pos++));
296   for(unsigned int i = 0; i < entryCount; i++) {
297     ByteVector childElementID = readStringField(data, String::Latin1, &pos).data(String::Latin1);
298     d->childElements.append(childElementID);
299   }
300 
301   size -= pos;
302 
303   if(size < header()->size())
304     return;
305 
306   while(embPos < size - header()->size()) {
307     Frame *frame = FrameFactory::instance()->createFrame(data.mid(pos + embPos), d->tagHeader);
308 
309     if(!frame)
310       return;
311 
312     // Checks to make sure that frame parsed correctly.
313     if(frame->size() <= 0) {
314       delete frame;
315       return;
316     }
317 
318     embPos += frame->size() + header()->size();
319     addEmbeddedFrame(frame);
320   }
321 }
322 
renderFields() const323 ByteVector TableOfContentsFrame::renderFields() const
324 {
325   ByteVector data;
326 
327   data.append(d->elementID);
328   data.append('\0');
329   char flags = 0;
330   if(d->isTopLevel)
331     flags += 2;
332   if(d->isOrdered)
333     flags += 1;
334   data.append(flags);
335   data.append((char)(entryCount()));
336   ByteVectorList::ConstIterator it = d->childElements.begin();
337   while(it != d->childElements.end()) {
338     data.append(*it);
339     data.append('\0');
340     it++;
341   }
342   FrameList l = d->embeddedFrameList;
343   for(FrameList::ConstIterator it = l.begin(); it != l.end(); ++it)
344     data.append((*it)->render());
345 
346   return data;
347 }
348 
TableOfContentsFrame(const ID3v2::Header * tagHeader,const ByteVector & data,Header * h)349 TableOfContentsFrame::TableOfContentsFrame(const ID3v2::Header *tagHeader,
350                                            const ByteVector &data, Header *h) :
351   Frame(h),
352   d(new TableOfContentsFramePrivate())
353 {
354   d->tagHeader = tagHeader;
355   parseFields(fieldData(data));
356 }
357