Add ChangeLog entry
[libxspf.git] / src / XspfReader.cpp
1 /*
2  * libxspf - XSPF playlist handling library
3  *
4  * Copyright (C) 2006-2008, Sebastian Pipping / Xiph.Org Foundation
5  * All rights reserved.
6  *
7  * Redistribution  and use in source and binary forms, with or without
8  * modification,  are permitted provided that the following conditions
9  * are met:
10  *
11  *     * Redistributions   of  source  code  must  retain  the   above
12  *       copyright  notice, this list of conditions and the  following
13  *       disclaimer.
14  *
15  *     * Redistributions  in  binary  form must  reproduce  the  above
16  *       copyright  notice, this list of conditions and the  following
17  *       disclaimer   in  the  documentation  and/or  other  materials
18  *       provided with the distribution.
19  *
20  *     * Neither  the name of the Xiph.Org Foundation nor the names of
21  *       its  contributors may be used to endorse or promote  products
22  *       derived  from  this software without specific  prior  written
23  *       permission.
24  *
25  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
26  * "AS  IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT  NOT
27  * LIMITED  TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND  FITNESS
28  * FOR  A  PARTICULAR  PURPOSE ARE DISCLAIMED. IN NO EVENT  SHALL  THE
29  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
30  * INCIDENTAL,    SPECIAL,   EXEMPLARY,   OR   CONSEQUENTIAL   DAMAGES
31  * (INCLUDING,  BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
32  * SERVICES;  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
34  * STRICT  LIABILITY,  OR  TORT (INCLUDING  NEGLIGENCE  OR  OTHERWISE)
35  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
36  * OF THE POSSIBILITY OF SUCH DAMAGE.
37  *
38  * Sebastian Pipping, sping@xiph.org
39  */
40
41 /**
42  * @file XspfReader.cpp
43  * Implementation of XspfReader.
44  */
45
46 #include <xspf/XspfReader.h>
47 #include <xspf/XspfProps.h>
48 #include <xspf/XspfDateTime.h>
49 #include <xspf/XspfTrack.h>
50 #include <xspf/XspfToolbox.h>
51 #include <xspf/XspfStack.h>
52 #include "XspfSkipExtensionReader.h"
53 #include <xspf/XspfExtensionReaderFactory.h>
54 #include <xspf/XspfChunkCallback.h>
55 #include "XspfStrictReaderCallback.h"
56 #include <uriparser/Uri.h>
57 #include <cassert>
58 #include <cstdio>
59 #include <algorithm>
60 #include <cstdlib> // atoi
61 #include <cstring> // size_t
62 #include <cstddef> // int
63
64 #if (URI_VER_MINOR < 7) || ((URI_VER_MINOR == 7) && (URI_VER_RELEASE < 2))
65 # error uriparser 0.7.2 or later is required
66 #endif
67
68
69 namespace {
70
71 int const XSPF_FALLBACK_VERSION = 1;
72
73 struct EntityInfo {
74         int valueLen;
75         int lookupSum;
76         int lookupDepth;
77
78         EntityInfo(int valueLen, int lookupSum, int lookupDepth)
79                         : valueLen(valueLen), lookupSum(lookupSum),
80                         lookupDepth(lookupDepth) { }
81 };
82
83 typedef std::basic_string<XML_Char> StringType;
84 typedef std::map<StringType, EntityInfo> MapType;
85 typedef std::pair<StringType, EntityInfo> PairType;
86
87 int const MAX_LENGTH_PER_ENTITY_VALUE        = 100000;
88 int const MAX_LOOKUP_SUM_PER_ENTITY_VALUE    = 10000;
89 int const MAX_LOOKUP_DEPTH_PER_ENTITY_VALUE  = 5;
90
91 XML_Char *
92 makeString(XML_Char const * first, XML_Char const * afterLast) {
93         size_t const len = afterLast - first;
94         XML_Char * dup = new XML_Char[len + 1];
95         ::PORT_STRNCPY(dup, first, len);
96         dup[len] = '\0';
97         return dup;
98 }
99
100 XML_Char *
101 nextEntityRefMalloc(XML_Char const * start,
102                 XML_Char const * & atAmpersand,
103                 XML_Char const * & afterSemiColon) {
104         XML_Char const * walker = start;
105         while (true) {
106                 switch (walker[0]) {
107                 case '\0':
108                         // No complete entity found
109                         atAmpersand = start;
110                         afterSemiColon = walker;
111                         return NULL;
112
113                 case '&':
114                         // Entity start found
115                         atAmpersand = walker;
116                         break;
117
118                 case ';':
119                         // Entity stop found
120                         if (atAmpersand != NULL) {
121                                 afterSemiColon = walker + 1;
122                                 return makeString(atAmpersand + 1, walker);
123                         }
124                         break;
125                 }
126                 walker++;
127         }
128 }
129
130 } // anon namespace
131
132 namespace Xspf {
133
134
135
136 /// @cond DOXYGEN_NON_API
137
138 /**
139  * D object for XspfReader.
140  */
141 class XspfReaderPrivate {
142
143         friend class XspfReader;
144         friend class XspfExtensionReader;
145
146 private:
147         XspfStack<unsigned int> elementStack; ///< Element stack
148         XspfStack<std::basic_string<XML_Char> > baseUriStack; ///< Element stack
149         XspfProps * props; ///< Playlist properties slot
150         XspfTrack * track; ///< Track slot
151         int version; ///< XSPF version
152
153         XML_Parser parser; ///< Expat parser handle
154         XspfReaderCallback * callback; ///< Reader callback
155         bool ownCallback;
156         std::basic_string<XML_Char> accum; ///< Element content accumulator
157         std::basic_string<XML_Char> lastRelValue; ///< Value of the previous "rel" attribute
158
159         XspfExtensionReader * extensionReader; ///< Current extension reader
160         XspfExtensionReaderFactory * extensionReaderFactory; ///< Extension reader factory
161
162         int errorCode; ///< Error code
163
164         bool insideExtension; ///< Flag wether the parser is inside an extension element
165         bool skip; ///< Flag indicating skip-reading to ignore certain kinds of errors
166         unsigned int skipStopLevel; ///< Level on which skipping should stop
167
168         bool firstPlaylistAnnotation; ///< First annotation in playlist flag
169         bool firstPlaylistAttribution; ///< First attributation in playlist flag
170         bool firstPlaylistCreator; ///< First creator in playlist flag
171         bool firstPlaylistDate; ///< First date in playlist flag
172         bool firstPlaylistIdentifier; ///< First identifier in playlist flag
173         bool firstPlaylistImage; ///< First image in playlist flag
174         bool firstPlaylistInfo; ///< First info in playlist flag
175         bool firstPlaylistLicense; ///< First license in playlist flag
176         bool firstPlaylistLocation; ///< First location in playlist flag
177         bool firstPlaylistTitle; ///< First title in playlist flag
178         bool firstPlaylistTrackList; ///< First trackList in playlist flag
179
180         bool firstTrackTitle; ///< First title in track flag
181         bool firstTrackCreator; ///< First creator in track flag
182         bool firstTrackAnnotation; ///< First annotation in track flag
183         bool firstTrackInfo; ///< First info in track flag
184         bool firstTrackImage; ///< First image in track flag
185         bool firstTrackAlbum; ///< First album in track flag
186         bool firstTrackTrackNum; ///< First trackNum in track flag
187         bool firstTrackDuration; ///< First duration in track flag
188
189         bool firstTrack; ///< First track flag
190
191         MapType entityNameToValueLen; // TODO
192
193         int maxLengthPerEntity; // TODO
194         int maxTotalLookupsPerEntity; // TODO
195         int maxLookupDepthPerEntity; // TODO
196         bool limitLengthPerEntityValue; // TODO
197         bool limitLookupSumPerEntityValue; // TODO
198         bool limitLookupDepthPerEntityValue; // TODO
199
200         /**
201          * Creates a new D object.
202          *
203          * @param handlerFactory  Factory used to create handlers
204          */
205         XspfReaderPrivate(XspfExtensionReaderFactory * handlerFactory)
206                         : elementStack(),
207                         props(NULL),
208                         track(NULL),
209                         version(-1),
210                         callback(NULL),
211                         ownCallback(false),
212                         extensionReader(NULL),
213                         extensionReaderFactory(handlerFactory),
214                         errorCode(XSPF_READER_SUCCESS),
215                         insideExtension(false),
216                         skip(false),
217                         skipStopLevel(0),
218                         firstPlaylistAnnotation(true),
219                         firstPlaylistAttribution(true),
220                         firstPlaylistCreator(true),
221                         firstPlaylistDate(true),
222                         firstPlaylistIdentifier(true),
223                         firstPlaylistImage(true),
224                         firstPlaylistInfo(true),
225                         firstPlaylistLicense(true),
226                         firstPlaylistLocation(true),
227                         firstPlaylistTitle(true),
228                         firstPlaylistTrackList(true),
229                         firstTrackTitle(true),
230                         firstTrackCreator(true),
231                         firstTrackAnnotation(true),
232                         firstTrackInfo(true),
233                         firstTrackImage(true),
234                         firstTrackAlbum(true),
235                         firstTrackTrackNum(true),
236                         firstTrackDuration(true),
237                         firstTrack(true),
238                         entityNameToValueLen(),
239                         maxLengthPerEntity(MAX_LENGTH_PER_ENTITY_VALUE),
240                         maxTotalLookupsPerEntity(MAX_LOOKUP_SUM_PER_ENTITY_VALUE),
241                         maxLookupDepthPerEntity(MAX_LOOKUP_DEPTH_PER_ENTITY_VALUE),
242                         limitLengthPerEntityValue(false),
243                         limitLookupSumPerEntityValue(false),
244                         limitLookupDepthPerEntityValue(false) {
245
246         }
247
248         /**
249          * Copy constructor.
250          *
251          * @param source  Source to copy from
252          */
253         XspfReaderPrivate(XspfReaderPrivate const & source)
254                         : elementStack(source.elementStack),
255                         props((source.props != NULL)
256                                 ? new XspfProps(*(source.props))
257                                 : NULL),
258                         track((source.track != NULL)
259                                 ? new XspfTrack(*(source.track))
260                                 : NULL),
261                         version(source.version),
262                         callback(source.ownCallback
263                                 ? new XspfStrictReaderCallback
264                                 : source.callback),
265                         ownCallback(source.ownCallback),
266                         extensionReader((source.extensionReader != NULL)
267                                 ? source.extensionReader->createBrother()
268                                 : NULL),
269                         extensionReaderFactory(source.extensionReaderFactory),
270                         errorCode(source.errorCode),
271                         insideExtension(source.insideExtension),
272                         skip(source.skip),
273                         skipStopLevel(source.skipStopLevel),
274                         firstPlaylistAnnotation(source.firstPlaylistAnnotation),
275                         firstPlaylistAttribution(source.firstPlaylistAttribution),
276                         firstPlaylistCreator(source.firstPlaylistCreator),
277                         firstPlaylistDate(source.firstPlaylistDate),
278                         firstPlaylistIdentifier(source.firstPlaylistIdentifier),
279                         firstPlaylistImage(source.firstPlaylistImage),
280                         firstPlaylistInfo(source.firstPlaylistInfo),
281                         firstPlaylistLicense(source.firstPlaylistLicense),
282                         firstPlaylistLocation(source.firstPlaylistLocation),
283                         firstPlaylistTitle(source.firstPlaylistTitle),
284                         firstPlaylistTrackList(source.firstPlaylistTrackList),
285                         firstTrackTitle(source.firstTrackTitle),
286                         firstTrackCreator(source.firstTrackCreator),
287                         firstTrackAnnotation(source.firstTrackAnnotation),
288                         firstTrackInfo(source.firstTrackInfo),
289                         firstTrackImage(source.firstTrackImage),
290                         firstTrackAlbum(source.firstTrackAlbum),
291                         firstTrackTrackNum(source.firstTrackTrackNum),
292                         firstTrackDuration(source.firstTrackDuration),
293                         firstTrack(source.firstTrack),
294                         entityNameToValueLen(source.entityNameToValueLen),
295                         maxLengthPerEntity(source.maxLengthPerEntity),
296                         maxTotalLookupsPerEntity(source.maxTotalLookupsPerEntity),
297                         maxLookupDepthPerEntity(source.maxLookupDepthPerEntity),
298                         limitLengthPerEntityValue(source.limitLengthPerEntityValue),
299                         limitLookupSumPerEntityValue(source.limitLookupSumPerEntityValue),
300                         limitLookupDepthPerEntityValue(source.limitLookupDepthPerEntityValue) {
301
302         }
303
304         /**
305          * Assignment operator.
306          *
307          * @param source  Source to copy from
308          */
309         XspfReaderPrivate & operator=(XspfReaderPrivate const & source) {
310                 if (this != &source) {
311                         // stack
312                         this->elementStack = source.elementStack;
313
314                         // props
315                         if (this->props != NULL) {
316                                 delete this->props;
317                         }
318                         this->props = (source.props != NULL)
319                                 ? new XspfProps(*(source.props))
320                                 : NULL;
321
322                         // props
323                         if (this->track != NULL) {
324                                 delete this->track;
325                         }
326                         this->track = (source.track != NULL)
327                                 ? new XspfTrack(*(source.track))
328                                 : NULL;
329
330                         this->version = source.version;
331                         this->callback = source.ownCallback
332                                         ? new XspfStrictReaderCallback
333                                         : source.callback;
334                         this->ownCallback = source.ownCallback;
335
336                         // extension reader
337                         if (this->extensionReader != NULL) {
338                                 delete this->track;
339                         }
340                         this->extensionReader = (source.extensionReader != NULL)
341                                 ? source.extensionReader->createBrother()
342                                 : NULL;
343
344                         this->extensionReaderFactory = source.extensionReaderFactory;
345                         this->errorCode = source.errorCode;
346                         this->insideExtension = source.insideExtension;
347                         this->skip = source.skip;
348                         this->skipStopLevel = source.skipStopLevel;
349                         this->firstPlaylistAnnotation = source.firstPlaylistAnnotation;
350                         this->firstPlaylistAttribution = source.firstPlaylistAttribution;
351                         this->firstPlaylistCreator = source.firstPlaylistCreator;
352                         this->firstPlaylistDate = source.firstPlaylistDate;
353                         this->firstPlaylistIdentifier = source.firstPlaylistIdentifier;
354                         this->firstPlaylistImage = source.firstPlaylistImage;
355                         this->firstPlaylistInfo = source.firstPlaylistInfo;
356                         this->firstPlaylistLicense = source.firstPlaylistLicense;
357                         this->firstPlaylistLocation = source.firstPlaylistLocation;
358                         this->firstPlaylistTitle = source.firstPlaylistTitle;
359                         this->firstPlaylistTrackList = source.firstPlaylistTrackList;
360                         this->firstTrackTitle = source.firstTrackTitle;
361                         this->firstTrackCreator = source.firstTrackCreator;
362                         this->firstTrackAnnotation = source.firstTrackAnnotation;
363                         this->firstTrackInfo = source.firstTrackInfo;
364                         this->firstTrackImage = source.firstTrackImage;
365                         this->firstTrackAlbum = source.firstTrackAlbum;
366                         this->firstTrackTrackNum = source.firstTrackTrackNum;
367                         this->firstTrackDuration = source.firstTrackDuration;
368                         this->firstTrack = source.firstTrack;
369                         this->entityNameToValueLen = source.entityNameToValueLen;
370                         this->maxLengthPerEntity = source.maxLengthPerEntity;
371                         this->maxTotalLookupsPerEntity = source.maxTotalLookupsPerEntity;
372                         this->maxLookupDepthPerEntity = source.maxLookupDepthPerEntity;
373                         this->limitLengthPerEntityValue = source.limitLengthPerEntityValue;
374                         this->limitLookupSumPerEntityValue = source.limitLookupSumPerEntityValue;
375                         this->limitLookupDepthPerEntityValue = source.limitLookupDepthPerEntityValue;
376                 }
377                 return *this;
378         }
379
380         /**
381          * Destroys this D object.
382          */
383         ~XspfReaderPrivate() {
384                 if (this->props != NULL) {
385                         delete this->props;
386                 }
387                 if (this->track != NULL) {
388                         delete this->track;
389                 }
390                 if (this->extensionReader != NULL) {
391                         delete this->extensionReader;
392                 }
393                 if (this->ownCallback) {
394                         delete this->callback;
395                 }
396         }
397
398 };
399
400 /// @endcond
401
402
403 XspfReader::XspfReader(XspfExtensionReaderFactory * handlerFactory)
404                 : d(new XspfReaderPrivate(handlerFactory)) { }
405
406
407 void XspfReader::makeReusable() {
408         // Reset everything but the error state
409         this->d->elementStack.clear();
410         this->d->baseUriStack.clear();
411         if (this->d->props != NULL) {
412                 delete this->d->props;
413                 this->d->props = NULL;
414         }
415         if (this->d->track != NULL) {
416                 delete this->d->track;
417                 this->d->track = NULL;
418         }
419         if (this->d->ownCallback) {
420                 delete this->d->callback;
421                 this->d->ownCallback = false;
422         }
423         this->d->callback = NULL;
424         this->d->accum.clear();
425         this->d->lastRelValue.clear();
426
427         this->d->firstPlaylistAnnotation = true;
428         this->d->firstPlaylistAttribution = true;
429         this->d->firstPlaylistCreator = true;
430         this->d->firstPlaylistDate = true;
431         this->d->firstPlaylistIdentifier = true;
432         this->d->firstPlaylistImage = true;
433         this->d->firstPlaylistInfo = true;
434         this->d->firstPlaylistLicense = true;
435         this->d->firstPlaylistLocation = true;
436         this->d->firstPlaylistTitle = true;
437         this->d->firstPlaylistTrackList = true;
438
439         this->d->firstTrackTitle = true;
440         this->d->firstTrackCreator = true;
441         this->d->firstTrackAnnotation = true;
442         this->d->firstTrackInfo = true;
443         this->d->firstTrackImage = true;
444         this->d->firstTrackAlbum = true;
445         this->d->firstTrackTrackNum = true;
446         this->d->firstTrackDuration = true;
447
448         this->d->firstTrack = true;
449
450         this->d->insideExtension = false;
451         this->d->skip = false;
452         this->d->skipStopLevel = 0;
453         this->d->version = -1;
454
455         if (this->d->extensionReader != NULL) {
456                 delete this->d->extensionReader;
457                 this->d->extensionReader = NULL;
458         }
459
460         this->d->entityNameToValueLen.clear();
461 }
462
463
464 XspfReader::XspfReader(XspfReader const & source)
465                 : d(new XspfReaderPrivate(*(source.d))) { }
466
467
468 XspfReader & XspfReader::operator=(XspfReader const & source) {
469         if (this != &source) {
470                 *(this->d) = *(source.d);
471         }
472         return *this;
473 }
474
475
476 XspfReader::~XspfReader() {
477         delete this->d;
478 }
479
480
481 bool
482 XspfReader::onBeforeParse(XspfReaderCallback * callback,
483                 XML_Char const * baseUri) {
484         this->d->ownCallback = (callback == NULL);
485         this->d->callback = (callback == NULL)
486                         ? new XspfStrictReaderCallback
487                         : callback;
488
489         // Init base URI stack
490         if (!Toolbox::isAbsoluteUri(baseUri)) {
491                 handleFatalError(XSPF_READER_ERROR_BASE_URI_USELESS,
492                                 _PT("Base URI is not a valid absolute URI."));
493                 return false;
494         }
495         std::basic_string<XML_Char> baseUriString(baseUri);
496         this->d->baseUriStack.push(baseUriString);
497
498         clearError();
499
500         // Create parser
501         this->d->parser = ::XML_ParserCreateNS(NULL, XSPF_NS_SEP_CHAR);
502
503         // Put class pointer into user data
504         ::XML_SetUserData(this->d->parser, this);
505
506         // Register handlers
507         ::XML_SetElementHandler(this->d->parser, masterStart, masterEnd);
508         ::XML_SetCharacterDataHandler(this->d->parser, masterCharacters);
509         ::XML_SetEntityDeclHandler(this->d->parser, masterEntityDeclaration);
510         return true;
511 }
512
513
514 void
515 XspfReader::onAfterParse() {
516         ::XML_ParserFree(this->d->parser);
517         makeReusable();
518 }
519
520
521 /*static*/ bool
522 XspfReader::isXmlBase(XML_Char const * attributeKey) {
523         return (!::PORT_STRNCMP(attributeKey, XML_NS_HOME,
524                         XML_NS_HOME_LEN) && !::PORT_STRCMP(attributeKey
525                         + XML_NS_HOME_LEN + 1, _PT("base")));
526 }
527
528
529 bool
530 XspfReader::handleXmlBaseAttribute(XML_Char const * xmlBase) {
531         // Check URI
532         if (!Toolbox::isUri(xmlBase)) {
533                 if (!handleError(XSPF_READER_ERROR_ATTRIBUTE_INVALID,
534                                 XSPF_READER_TEXT_ZERO_WRONG_ATTRIBUTE_TYPE(
535                                 _PT("xml:base"), _PT("URI")))) {
536                         return false;
537                 }
538         }
539
540         // Make absolute and push on URI stack
541         XML_Char const * const resolveAgainst
542                                 = this->d->baseUriStack.top().c_str();
543         XML_Char * const resolvedUri = Toolbox::makeAbsoluteUri(
544                         xmlBase, resolveAgainst);
545         this->d->baseUriStack.push(std::basic_string<XML_Char>(
546                         const_cast<XML_Char const *>(resolvedUri)));
547         delete [] resolvedUri;
548         return true;
549 }
550
551
552 XML_Char *
553 XspfReader::makeAbsoluteUri(XML_Char const * sourceUri) const {
554         XML_Char const * const baseUri = this->d->baseUriStack.top().c_str();
555         XML_Char * const res = Toolbox::makeAbsoluteUri(sourceUri, baseUri);
556         return res;
557 }
558
559
560 void XspfReader::setExpatError() {
561         XML_Error const expatCode = ::XML_GetErrorCode(this->d->parser);
562         handleFatalError(XSPF_READER_ERROR_EXPAT + static_cast<int>(expatCode),
563                         XSPF_READER_TEXT_ONE_EXPAT_ERROR,
564                         ::XML_ErrorString(expatCode));
565 }
566
567
568 int XspfReader::parseFile(XML_Char const * filename,
569                 XspfReaderCallback * callback, XML_Char const * baseUri) {
570         // Init
571         if (!onBeforeParse(callback, baseUri)) {
572                 return this->d->errorCode;
573         }
574
575         // Check filename
576         if (filename == NULL) {
577                 handleFatalError(XSPF_READER_ERROR_NO_INPUT,
578                                 XSPF_READER_TEXT_ZERO_FILENAME_NULL);
579                 return this->d->errorCode;
580         }
581
582         // Open file
583         FILE * file = ::PORT_FOPEN(filename, _PT("r"));
584         if (file == NULL) {
585                 handleFatalError(XSPF_READER_ERROR_NO_INPUT, XSPF_READER_TEXT_ONE_FILE_READING_ERROR, filename);
586                 return this->d->errorCode;
587         }
588
589         // Get filesize
590         ::fseek(file, 0, SEEK_END);
591         long const filesize = ::ftell(file);
592         ::fseek(file, 0, SEEK_SET);
593
594         // Read and parse file
595         void * buffer;
596         if (filesize > XSPF_MAX_BLOCK_SIZE) {
597                 // In several blocks
598                 long sizeLeft = filesize;
599                 while (sizeLeft > 0) {
600                         long const blockSize = std::min<long>(sizeLeft, XSPF_MAX_BLOCK_SIZE);
601                         buffer = ::XML_GetBuffer(this->d->parser, blockSize);
602                         ::fread(buffer, 1, blockSize, file);
603                         sizeLeft -= blockSize;
604                         if (::XML_ParseBuffer(this->d->parser, blockSize, sizeLeft == 0)
605                                         == XML_STATUS_ERROR) {
606                                 if (this->d->errorCode == XSPF_READER_SUCCESS) {
607                                         setExpatError();
608                                 }
609                                 break;
610                         }
611                 }
612                 ::fclose(file);
613         } else {
614                 // One single go
615                 buffer = ::XML_GetBuffer(this->d->parser, filesize);
616                 ::fread(buffer, 1, filesize, file);
617                 ::fclose(file);
618
619                 if (::XML_ParseBuffer(this->d->parser, filesize, 1)
620                                 == XML_STATUS_ERROR) {
621                         if (this->d->errorCode == XSPF_READER_SUCCESS) {
622                                 setExpatError();
623                         }
624                 }
625         }
626
627         notifySuccess();
628
629         // Cleanup
630         onAfterParse();
631
632         return this->d->errorCode;
633 }
634
635
636 int XspfReader::parseMemory(char const * memory, int numBytes,
637                 XspfReaderCallback * callback, XML_Char const * baseUri) {
638         // Init
639         if (!onBeforeParse(callback, baseUri)) {
640                 return this->d->errorCode;
641         }
642
643         // Parse
644         if (::XML_Parse(this->d->parser, memory, numBytes, 1)
645                         == XML_STATUS_ERROR) {
646                 if (this->d->errorCode == XSPF_READER_SUCCESS) {
647                         setExpatError();
648                 }
649         }
650
651         notifySuccess();
652
653         // Cleanup
654         onAfterParse();
655
656         return this->d->errorCode;
657 }
658
659
660 int XspfReader::parseChunks(XspfChunkCallback * inputCallback,
661                 XspfReaderCallback * dataCallback, XML_Char const * baseUri) {
662         // Init
663         if (!onBeforeParse(dataCallback, baseUri)) {
664                 return this->d->errorCode;
665         }
666
667         // Parse chunks in a loop
668         for (;;) {
669                 // Ask callback for buffer size
670                 int const bufferByteSize = inputCallback->getMinimumBufferByteSize();
671                 void * buffer = NULL; // init not needed
672
673                 // Create and fill buffer
674                 int bytesToParse = 0;
675                 if (bufferByteSize > 0) {
676                         buffer = ::XML_GetBuffer(this->d->parser, bufferByteSize);
677                         bytesToParse = inputCallback->fillBuffer(buffer);
678                 }
679
680                 // Parse chunk
681                 if (::XML_ParseBuffer(this->d->parser, bytesToParse,
682                                 bytesToParse == 0) == XML_STATUS_ERROR) {
683                         // Error
684                         if (this->d->errorCode == XSPF_READER_SUCCESS) {
685                                 setExpatError();
686                         }
687                         break;
688                 } else {
689                         // Fine, continue?
690                         if (bytesToParse == 0) {
691                                 break;
692                         }
693                 }
694         }
695         inputCallback->notifyStop();
696         notifySuccess(); // .. on dataCallback
697
698         // Cleanup
699         onAfterParse();
700
701         return this->d->errorCode;
702 }
703
704
705 bool
706 XspfReader::checkAndSkipNamespace(XML_Char const * fullName,
707                 XML_Char const * & localName) {
708         if (::PORT_STRNCMP(fullName, XSPF_NS_HOME, XSPF_NS_HOME_LEN)) {
709                 if (!handleError(XSPF_READER_ERROR_ELEMENT_FORBIDDEN,
710                                 XSPF_READER_TEXT_ONE_ELEMENT_FORBIDDEN,
711                                 fullName)) {
712                         return false;
713                 }
714
715                 // Catch <xspf:tag> as <tag> instead
716                 localName = fullName;
717                 while ((localName[0] != _PT('\0'))
718                                 && (localName[0] != XSPF_NS_SEP_CHAR)) {
719                         localName++;
720                 }
721                 if (localName[0] == _PT('\0')) {
722                         // No namespace -> reset
723                         localName = fullName;
724                 } else {
725                         // Namespace -> jump to local name
726                         localName++;
727                 }
728         } else {
729                 localName = fullName + XSPF_NS_HOME_LEN + 1;
730         }
731         return true;
732 }
733
734
735 bool
736 XspfReader::handleStartOne(XML_Char const * fullName, XML_Char const ** atts) {
737         // Check and skip namespace
738         XML_Char const * localName;
739         if (!checkAndSkipNamespace(fullName, localName)) {
740                 return false;
741         }
742
743         // Check root name
744         if (::PORT_STRCMP(localName, _PT("playlist"))) {
745                 if (!handleError(XSPF_READER_ERROR_ELEMENT_FORBIDDEN,
746                                 XSPF_READER_TEXT_ONE_WRONG_ROOT_NAME,
747                                 fullName)) {
748                         return false;
749                 }
750         }
751
752         this->d->props = new XspfProps();
753         if (!handlePlaylistAttribs(atts)) {
754                 return false;
755         }
756
757         this->d->elementStack.push(TAG_PLAYLIST);
758         this->d->props->setVersion(this->d->version);
759         return true;
760 }
761
762
763 bool
764 XspfReader::handleStartTwo(XML_Char const * fullName,
765                 XML_Char const ** atts) {
766         // Check and skip namespace
767         XML_Char const * localName;
768         if (!checkAndSkipNamespace(fullName, localName)) {
769                 return false;
770         }
771
772         switch (localName[0]) {
773         case _PT('a'):
774                 switch (localName[1]) {
775                 case _PT('n'):
776                         if (::PORT_STRCMP(localName + 2, _PT("notation"))) {
777                                 break;
778                         }
779
780                         if (this->d->firstPlaylistAnnotation) {
781                                 this->d->firstPlaylistAnnotation = false;
782                         } else {
783                                 if (!handleError(XSPF_READER_ERROR_ELEMENT_TOOMANY,
784                                                 XSPF_READER_TEXT_ZERO_TOO_MANY_ELEMENTS(
785                                                 XSPF_NS_HOME, _PT("annotation")))) {
786                                         return false;
787                                 }
788                         }
789
790                         if (!handleNoAttribsExceptXmlBase(atts)) {
791                                 return false;
792                         } else {
793                                 this->d->elementStack.push(TAG_PLAYLIST_ANNOTATION);
794                                 return true;
795                         }
796                         break;
797
798                 case _PT('t'):
799                         if (::PORT_STRCMP(localName + 2, _PT("tribution"))) {
800                                 break;
801                         }
802
803                         if (this->d->firstPlaylistAttribution) {
804                                 this->d->firstPlaylistAttribution = false;
805                         } else {
806                                 if (!handleError(XSPF_READER_ERROR_ELEMENT_TOOMANY,
807                                                 XSPF_READER_TEXT_ZERO_TOO_MANY_ELEMENTS(
808                                                 XSPF_NS_HOME, _PT("attribution")))) {
809                                         return false;
810                                 }
811                         }
812
813                         if (!handleNoAttribsExceptXmlBase(atts)) {
814                                 return false;
815                         } else {
816                                 this->d->elementStack.push(TAG_PLAYLIST_ATTRIBUTION);
817                                 return true;
818                         }
819                         break;
820
821                 }
822                 break;
823
824         case _PT('c'):
825                 if (::PORT_STRCMP(localName + 1, _PT("reator"))) {
826                         break;
827                 }
828
829                 if (this->d->firstPlaylistCreator) {
830                         this->d->firstPlaylistCreator = false;
831                 } else {
832                         if (!handleError(XSPF_READER_ERROR_ELEMENT_TOOMANY,
833                                         XSPF_READER_TEXT_ZERO_TOO_MANY_ELEMENTS(
834                                         XSPF_NS_HOME, _PT("creator")))) {
835                                 return false;
836                         }
837                 }
838
839                 if (!handleNoAttribsExceptXmlBase(atts)) {
840                         return false;
841                 } else {
842                         this->d->elementStack.push(TAG_PLAYLIST_CREATOR);
843                         return true;
844                 }
845                 break;
846
847         case _PT('d'):
848                 if (::PORT_STRCMP(localName + 1, _PT("ate"))) {
849                         break;
850                 }
851
852                 if (this->d->firstPlaylistDate) {
853                         this->d->firstPlaylistDate = false;
854                 } else {
855                         if (!handleError(XSPF_READER_ERROR_ELEMENT_TOOMANY,
856                                         XSPF_READER_TEXT_ZERO_TOO_MANY_ELEMENTS(
857                                         XSPF_NS_HOME, _PT("date")))) {
858                                 return false;
859                         }
860                 }
861
862                 if (!handleNoAttribsExceptXmlBase(atts)) {
863                         return false;
864                 } else {
865                         this->d->elementStack.push(TAG_PLAYLIST_DATE);
866                         return true;
867                 }
868                 break;
869
870         case _PT('e'):
871                 if (::PORT_STRCMP(localName + 1, _PT("xtension"))) {
872                         break;
873                 }
874
875                 // Tag only allowed in v1
876                 if (this->d->version == 0) {
877                         if (!handleError(XSPF_READER_ERROR_ELEMENT_FORBIDDEN,
878                                         XSPF_READER_TEXT_ONE_ELEMENT_FORBIDDEN_VERSION_ZERO,
879                                         fullName)) {
880                                 return false;
881                         }
882                 }
883
884                 XML_Char const * applicationUri;
885                 if (handleExtensionAttribs(atts, applicationUri)) {
886                         if (applicationUri == NULL) {
887                                 this->d->elementStack.push(TAG_PLAYLIST_EXTENSION);
888                                 skipFromHere();
889                                 return true;
890                         } else {
891                                 this->d->insideExtension = true;
892
893                                 // Create suitable handler
894                                 if (this->d->extensionReaderFactory != NULL) {
895                                         this->d->extensionReader = this->d->extensionReaderFactory
896                                                         ->newPlaylistExtensionReader(applicationUri, this);
897                                 }
898                                 if (this->d->extensionReader == NULL) {
899                                         this->d->extensionReader = new XspfSkipExtensionReader(this);
900                                 }
901
902                                 return this->d->extensionReader->handleExtensionStart(fullName, atts);
903                         }
904                 } else {
905                         return false;
906                 }
907                 break;
908
909         case _PT('i'):
910                 switch (localName[1]) {
911                 case _PT('d'):
912                         if (::PORT_STRCMP(localName + 2, _PT("entifier"))) {
913                                 break;
914                         }
915
916                         if (this->d->firstPlaylistIdentifier) {
917                                 this->d->firstPlaylistIdentifier = false;
918                         } else {
919                                 if (!handleError(XSPF_READER_ERROR_ELEMENT_TOOMANY,
920                                                 XSPF_READER_TEXT_ZERO_TOO_MANY_ELEMENTS(
921                                                 XSPF_NS_HOME, _PT("identifier")))) {
922                                         return false;
923                                 }
924                         }
925
926                         if (!handleNoAttribsExceptXmlBase(atts)) {
927                                 return false;
928                         } else {
929                                 this->d->elementStack.push(TAG_PLAYLIST_IDENTIFIER);
930                                 return true;
931                         }
932                         break;
933
934                 case _PT('m'):
935                         if (::PORT_STRCMP(localName + 2, _PT("age"))) {
936                                 break;
937                         }
938
939                         if (this->d->firstPlaylistImage) {
940                                 this->d->firstPlaylistImage = false;
941                         } else {
942                                 if (!handleError(XSPF_READER_ERROR_ELEMENT_TOOMANY,
943                                                 XSPF_READER_TEXT_ZERO_TOO_MANY_ELEMENTS(
944                                                 XSPF_NS_HOME, _PT("image")))) {
945                                         return false;
946                                 }
947                         }
948
949                         if (!handleNoAttribsExceptXmlBase(atts)) {
950                                 return false;
951                         } else {
952                                 this->d->elementStack.push(TAG_PLAYLIST_IMAGE);
953                                 return true;
954                         }
955                         break;
956
957                 case _PT('n'):
958                         if (::PORT_STRCMP(localName + 2, _PT("fo"))) {
959                                 break;
960                         }
961
962                         if (this->d->firstPlaylistInfo) {
963                                 this->d->firstPlaylistInfo = false;
964                         } else {
965                                 if (!handleError(XSPF_READER_ERROR_ELEMENT_TOOMANY,
966                                                 XSPF_READER_TEXT_ZERO_TOO_MANY_ELEMENTS(
967                                                 XSPF_NS_HOME, _PT("info")))) {
968                                         return false;
969                                 }
970                         }
971
972                         if (!handleNoAttribsExceptXmlBase(atts)) {
973                                 return false;
974                         } else {
975                                 this->d->elementStack.push(TAG_PLAYLIST_INFO);
976                                 return true;
977                         }
978                         break;
979
980                 }
981                 break;
982
983         case _PT('l'):
984                 switch (localName[1]) {
985                 case _PT('i'):
986                         switch (localName[2]) {
987                         case _PT('c'):
988                                 if (::PORT_STRCMP(localName + 3, _PT("ense"))) {
989                                         break;
990                                 }
991
992                                 if (this->d->firstPlaylistLicense) {
993                                         this->d->firstPlaylistLicense = false;
994                                 } else {
995                                         if (!handleError(XSPF_READER_ERROR_ELEMENT_TOOMANY,
996                                                         XSPF_READER_TEXT_ZERO_TOO_MANY_ELEMENTS(
997                                                         XSPF_NS_HOME, _PT("license")))) {
998                                                 return false;
999                                         }
1000                                 }
1001
1002                                 if (!handleNoAttribsExceptXmlBase(atts)) {
1003                                         return false;
1004                                 } else {
1005                                         this->d->elementStack.push(TAG_PLAYLIST_LICENSE);
1006                                         return true;
1007                                 }
1008                                 break;
1009
1010                         case _PT('n'):
1011                                 if (::PORT_STRCMP(localName + 3, _PT("k"))) {
1012                                         break;
1013                                 }
1014
1015                                 {
1016                                         XML_Char const * rel = NULL; // No init needed
1017                                         if (handleMetaLinkAttribs(atts, rel)) {
1018                                                 this->d->elementStack.push(TAG_PLAYLIST_LINK);
1019                                                 if (rel != NULL) {
1020                                                         this->d->lastRelValue.assign(atts[1]);
1021                                                 } else {
1022                                                         skipFromHere();
1023                                                 }
1024                                                 return true;
1025                                         } else {
1026                                                 return false;
1027                                         }
1028                                 }
1029                                 break;
1030
1031                         }
1032                         break;
1033
1034                 case _PT('o'):
1035                         if (::PORT_STRCMP(localName + 2, _PT("cation"))) {
1036                                 break;
1037                         }
1038
1039                         if (this->d->firstPlaylistLocation) {
1040                                 this->d->firstPlaylistLocation = false;
1041                         } else {
1042                                 if (!handleError(XSPF_READER_ERROR_ELEMENT_TOOMANY,
1043                                                 XSPF_READER_TEXT_ZERO_TOO_MANY_ELEMENTS(
1044                                                 XSPF_NS_HOME, _PT("location")))) {
1045                                         return false;
1046                                 }
1047                         }
1048
1049                         if (!handleNoAttribsExceptXmlBase(atts)) {
1050                                 return false;
1051                         } else {
1052                                 this->d->elementStack.push(TAG_PLAYLIST_LOCATION);
1053                                 return true;
1054                         }
1055                         break;
1056
1057                 }
1058                 break;
1059
1060         case _PT('m'):
1061                 if (::PORT_STRCMP(localName + 1, _PT("eta"))) {
1062                         break;
1063                 }
1064
1065                 {
1066                         XML_Char const * rel = NULL; // No init needed
1067                         if (handleMetaLinkAttribs(atts, rel)) {
1068                                 this->d->elementStack.push(TAG_PLAYLIST_META);
1069                                 if (rel != NULL) {
1070                                         this->d->lastRelValue.assign(atts[1]);
1071                                 } else {
1072                                         skipFromHere();
1073                                 }
1074                                 return true;
1075                         } else {
1076                                 return false;
1077                         }
1078                 }
1079                 break;
1080
1081         case _PT('t'):
1082                 switch (localName[1]) {
1083                 case _PT('i'):
1084                         if (::PORT_STRCMP(localName + 2, _PT("tle"))) {
1085                                 break;
1086                         }
1087
1088                         if (this->d->firstPlaylistTitle) {
1089                                 this->d->firstPlaylistTitle = false;
1090                         } else {
1091                                 if (!handleError(XSPF_READER_ERROR_ELEMENT_TOOMANY,
1092                                                 XSPF_READER_TEXT_ZERO_TOO_MANY_ELEMENTS(
1093                                                 XSPF_NS_HOME, _PT("title")))) {
1094                                         return false;
1095                                 }
1096                         }
1097
1098                         if (!handleNoAttribsExceptXmlBase(atts)) {
1099                                 return false;
1100                         } else {
1101                                 this->d->elementStack.push(TAG_PLAYLIST_TITLE);
1102                                 return true;
1103                         }
1104                         break;
1105
1106                 case _PT('r'):
1107                         if (::PORT_STRCMP(localName + 2, _PT("ackList"))) {
1108                                 break;
1109                         }
1110
1111                         if (this->d->firstPlaylistTrackList) {
1112                                 this->d->firstPlaylistTrackList = false;
1113                         } else {
1114                                 if (!handleError(XSPF_READER_ERROR_ELEMENT_TOOMANY,
1115                                                 XSPF_READER_TEXT_ZERO_TOO_MANY_ELEMENTS(
1116                                                 XSPF_NS_HOME, _PT("trackList")))) {
1117                                         return false;
1118                                 }
1119                         }
1120
1121                         if (!handleNoAttribsExceptXmlBase(atts)) {
1122                                 return false;
1123                         } else {
1124                                 this->d->elementStack.push(TAG_PLAYLIST_TRACKLIST);
1125                                 return true;
1126                         }
1127                         break;
1128
1129                 }
1130                 break;
1131
1132         }
1133
1134         if (!handleError(XSPF_READER_ERROR_ELEMENT_FORBIDDEN,
1135                         XSPF_READER_TEXT_ONE_ELEMENT_FORBIDDEN,
1136                         fullName)) {
1137                 return false;
1138         }
1139
1140         this->d->elementStack.push(TAG_UNKNOWN);
1141         skipFromHere();
1142         return true;
1143 }
1144
1145
1146 bool XspfReader::handleStartThree(XML_Char const * fullName, XML_Char const ** atts) {
1147         // Check and skip namespace
1148         XML_Char const * localName;
1149         if (!checkAndSkipNamespace(fullName, localName)) {
1150                 return false;
1151         }
1152
1153         switch (this->d->elementStack.top()) {
1154         case TAG_PLAYLIST_ATTRIBUTION:
1155                 switch (localName[0]) {
1156                 case _PT('i'):
1157                         if (::PORT_STRCMP(localName + 1, _PT("dentifier"))) {
1158                                 break;
1159                         }
1160                         if (!handleNoAttribsExceptXmlBase(atts)) {
1161                                 return false;
1162                         }
1163                         this->d->elementStack.push(TAG_PLAYLIST_ATTRIBUTION_IDENTIFIER);
1164                         return true;
1165
1166                 case _PT('l'):
1167                         if (::PORT_STRCMP(localName + 1, _PT("ocation"))) {
1168                                 break;
1169                         }
1170                         if (!handleNoAttribsExceptXmlBase(atts)) {
1171                                 return false;
1172                         }
1173                         this->d->elementStack.push(TAG_PLAYLIST_ATTRIBUTION_LOCATION);
1174                         return true;
1175
1176                 }
1177                 break;
1178
1179         case TAG_PLAYLIST_TRACKLIST:
1180                 if (::PORT_STRCMP(localName, _PT("track"))) {
1181                         break;
1182                 }
1183                 if (!handleNoAttribsExceptXmlBase(atts)) {
1184                         return false;
1185                 }
1186                 this->d->firstTrack = false;
1187                 this->d->elementStack.push(TAG_PLAYLIST_TRACKLIST_TRACK);
1188                 this->d->track = new XspfTrack();
1189                 return true;
1190
1191         }
1192
1193         if (!handleError(XSPF_READER_ERROR_ELEMENT_FORBIDDEN,
1194                         XSPF_READER_TEXT_ONE_ELEMENT_FORBIDDEN,
1195                         fullName)) {
1196                 return false;
1197         }
1198
1199         this->d->elementStack.push(TAG_UNKNOWN);
1200         skipFromHere();
1201         return true;
1202 }
1203
1204
1205 bool XspfReader::handleStartFour(XML_Char const * fullName, XML_Char const ** atts) {
1206         if (this->d->elementStack.top() != TAG_PLAYLIST_TRACKLIST_TRACK) {
1207                 return false;
1208         }
1209
1210         // Check and skip namespace
1211         XML_Char const * localName;
1212         if (!checkAndSkipNamespace(fullName, localName)) {
1213                 return false;
1214         }
1215
1216         switch (localName[0]) {
1217         case _PT('a'):
1218                 switch (localName[1]) {
1219                 case _PT('l'):
1220                         if (::PORT_STRCMP(localName + 2, _PT("bum"))) {
1221                                 break;
1222                         }
1223
1224                         if (this->d->firstTrackAlbum) {
1225                                 this->d->firstTrackAlbum = false;
1226                         } else {
1227                                 if (!handleError(XSPF_READER_ERROR_ELEMENT_TOOMANY,
1228                                                 XSPF_READER_TEXT_ZERO_TOO_MANY_ELEMENTS(
1229                                                 XSPF_NS_HOME, _PT("album")))) {
1230                                         return false;
1231                                 }
1232                         }
1233
1234                         if (!handleNoAttribsExceptXmlBase(atts)) {
1235                                 return false;
1236                         } else {
1237                                 this->d->elementStack.push(TAG_PLAYLIST_TRACKLIST_TRACK_ALBUM);
1238                                 return true;
1239                         }
1240                         break;
1241
1242                 case _PT('n'):
1243                         if (::PORT_STRCMP(localName + 2, _PT("notation"))) {
1244                                 break;
1245                         }
1246
1247                         if (this->d->firstTrackAnnotation) {
1248                                 this->d->firstTrackAnnotation = false;
1249                         } else {
1250                                 if (!handleError(XSPF_READER_ERROR_ELEMENT_TOOMANY,
1251                                                 XSPF_READER_TEXT_ZERO_TOO_MANY_ELEMENTS(
1252                                                 XSPF_NS_HOME, _PT("annotation")))) {
1253                                         return false;
1254                                 }
1255                         }
1256
1257                         if (!handleNoAttribsExceptXmlBase(atts)) {
1258                                 return false;
1259                         } else {
1260                                 this->d->elementStack.push(TAG_PLAYLIST_TRACKLIST_TRACK_ANNOTATION);
1261                                 return true;
1262                         }
1263                         break;
1264
1265                 case _PT('r'):
1266                         if (::PORT_STRCMP(localName + 2, _PT("tist"))) {
1267                                 break;
1268                         }
1269
1270                         // Note: Element //playlist/trackList/track/artist
1271                         // is not valid XSPF. This is a loose fallback.
1272                         if (!handleError(XSPF_READER_ERROR_ELEMENT_FORBIDDEN,
1273                                         XSPF_READER_TEXT_ONE_ELEMENT_FORBIDDEN,
1274                                         fullName)) {
1275                                 return false;
1276                         }
1277
1278                         if (!handleNoAttribsExceptXmlBase(atts)) {
1279                                 return false;
1280                         } else {
1281                                 this->d->elementStack.push(TAG_PLAYLIST_TRACKLIST_TRACK_CREATOR);
1282                                 return true;
1283                         }
1284                         break;
1285
1286                 }
1287                 break;
1288
1289         case _PT('c'):
1290                 if (::PORT_STRCMP(localName + 1, _PT("reator"))) {
1291                         break;
1292                 }
1293
1294                 if (this->d->firstTrackCreator) {
1295                         this->d->firstTrackCreator = false;
1296                 } else {
1297                         if (!handleError(XSPF_READER_ERROR_ELEMENT_TOOMANY,
1298                                         XSPF_READER_TEXT_ZERO_TOO_MANY_ELEMENTS(
1299                                         XSPF_NS_HOME, _PT("creator")))) {
1300                                 return false;
1301                         }
1302                 }
1303
1304                 if (!handleNoAttribsExceptXmlBase(atts)) {
1305                         return false;
1306                 } else {
1307                         this->d->elementStack.push(TAG_PLAYLIST_TRACKLIST_TRACK_CREATOR);
1308                         return true;
1309                 }
1310                 break;
1311
1312         case _PT('d'):
1313                 if (::PORT_STRCMP(localName + 1, _PT("uration"))) {
1314                         break;
1315                 }
1316
1317                 if (this->d->firstTrackDuration) {
1318                         this->d->firstTrackDuration = false;
1319                 } else {
1320                         if (!handleError(XSPF_READER_ERROR_ELEMENT_TOOMANY,
1321                                         XSPF_READER_TEXT_ZERO_TOO_MANY_ELEMENTS(
1322                                         XSPF_NS_HOME, _PT("duration")))) {
1323                                 return false;
1324                         }
1325                 }
1326
1327                 if (!handleNoAttribsExceptXmlBase(atts)) {
1328                         return false;
1329                 } else {
1330                         this->d->elementStack.push(TAG_PLAYLIST_TRACKLIST_TRACK_DURATION);
1331                         return true;
1332                 }
1333                 break;
1334
1335         case _PT('e'):
1336                 if (::PORT_STRCMP(localName + 1, _PT("xtension"))) {
1337                         break;
1338                 }
1339
1340                 // Tag only allowed in v1
1341                 if (this->d->version == 0) {
1342                         if (!handleError(XSPF_READER_ERROR_ELEMENT_FORBIDDEN,
1343                                         XSPF_READER_TEXT_ONE_ELEMENT_FORBIDDEN_VERSION_ZERO,
1344                                         fullName)) {
1345                                 return false;
1346                         }
1347                 }
1348
1349                 XML_Char const * applicationUri;
1350                 if (handleExtensionAttribs(atts, applicationUri)) {
1351                         if (applicationUri == NULL) {
1352                                 this->d->elementStack.push(
1353                                                 TAG_PLAYLIST_TRACKLIST_TRACK_EXTENSION);
1354                                 skipFromHere();
1355                                 return true;
1356                         } else {
1357                                 this->d->insideExtension = true;
1358
1359                                 // Create suitable handler
1360                                 if (this->d->extensionReaderFactory != NULL) {
1361                                         this->d->extensionReader = this->d->extensionReaderFactory
1362                                                         ->newTrackExtensionReader(applicationUri, this);
1363                                 }
1364                                 if (this->d->extensionReader == NULL) {
1365                                         this->d->extensionReader = new XspfSkipExtensionReader(this);
1366                                 }
1367
1368                                 return this->d->extensionReader->handleExtensionStart(fullName, atts);
1369                         }
1370                 } else {
1371                         return false;
1372                 }
1373                 break;
1374
1375         case _PT('i'):
1376                 switch (localName[1]) {
1377                 case _PT('d'):
1378                         if (::PORT_STRCMP(localName + 2, _PT("entifier"))) {
1379                                 break;
1380                         }
1381                         if (!handleNoAttribsExceptXmlBase(atts)) {
1382                                 return false;
1383                         }
1384                         this->d->elementStack.push(TAG_PLAYLIST_TRACKLIST_TRACK_IDENTIFIER);
1385                         return true;
1386
1387                 case _PT('m'):
1388                         if (::PORT_STRCMP(localName + 2, _PT("age"))) {
1389                                 break;
1390                         }
1391
1392                         if (this->d->firstTrackImage) {
1393                                 this->d->firstTrackImage = false;
1394                         } else {
1395                                 if (!handleError(XSPF_READER_ERROR_ELEMENT_TOOMANY,
1396                                                 XSPF_READER_TEXT_ZERO_TOO_MANY_ELEMENTS(
1397                                                 XSPF_NS_HOME, _PT("image")))) {
1398                                         return false;
1399                                 }
1400                         }
1401
1402                         if (!handleNoAttribsExceptXmlBase(atts)) {
1403                                 return false;
1404                         } else {
1405                                 this->d->elementStack.push(TAG_PLAYLIST_TRACKLIST_TRACK_IMAGE);
1406                                 return true;
1407                         }
1408                         break;
1409
1410                 case _PT('n'):
1411                         if (::PORT_STRCMP(localName + 2, _PT("fo"))) {
1412                                 break;
1413                         }
1414
1415                         if (this->d->firstTrackInfo) {
1416                                 this->d->firstTrackInfo = false;
1417                         } else {
1418                                 if (!handleError(XSPF_READER_ERROR_ELEMENT_TOOMANY,
1419                                                 XSPF_READER_TEXT_ZERO_TOO_MANY_ELEMENTS(
1420                                                 XSPF_NS_HOME, _PT("info")))) {
1421                                         return false;
1422                                 }
1423                         }
1424
1425                         if (!handleNoAttribsExceptXmlBase(atts)) {
1426                                 return false;
1427                         } else {
1428                                 this->d->elementStack.push(TAG_PLAYLIST_TRACKLIST_TRACK_INFO);
1429                                 return true;
1430                         }
1431                         break;
1432
1433                 }
1434                 break;
1435
1436         case _PT('l'):
1437                 switch (localName[1]) {
1438                 case _PT('i'):
1439                         if (::PORT_STRCMP(localName + 2, _PT("nk"))) {
1440                                 break;
1441                         }
1442
1443                         {
1444                                 XML_Char const * rel = NULL; // No init needed
1445                                 if (handleMetaLinkAttribs(atts, rel)) {
1446                                         this->d->elementStack.push(TAG_PLAYLIST_TRACKLIST_TRACK_LINK);
1447                                         if (rel != NULL) {
1448                                                 this->d->lastRelValue.assign(atts[1]);
1449                                         } else {
1450                                                 skipFromHere();
1451                                         }
1452                                         return true;
1453                                 } else {
1454                                         return false;
1455                                 }
1456                         }
1457                         break;
1458
1459                 case _PT('o'):
1460                         if (::PORT_STRCMP(localName + 2, _PT("cation"))) {
1461                                 break;
1462                         }
1463                         if (!handleNoAttribsExceptXmlBase(atts)) {
1464                                 return false;
1465                         }
1466                         this->d->elementStack.push(TAG_PLAYLIST_TRACKLIST_TRACK_LOCATION);
1467                         return true;
1468
1469                 }
1470                 break;
1471
1472         case _PT('m'):
1473                 if (::PORT_STRCMP(localName + 1, _PT("eta"))) {
1474                         break;
1475                 }
1476
1477                 {
1478                         XML_Char const * rel = NULL; // No init needed
1479                         if (handleMetaLinkAttribs(atts, rel)) {
1480                                 this->d->elementStack.push(TAG_PLAYLIST_TRACKLIST_TRACK_META);
1481                                 if (rel != NULL) {
1482                                         this->d->lastRelValue.assign(atts[1]);
1483                                 } else {
1484                                         skipFromHere();
1485                                 }
1486                                 return true;
1487                         } else {
1488                                 return false;
1489                         }
1490                 }
1491                 break;
1492
1493         case _PT('t'):
1494                 switch (localName[1]) {
1495                 case _PT('i'):
1496                         if (::PORT_STRCMP(localName + 2, _PT("tle"))) {
1497                                 break;
1498                         }
1499
1500                         if (this->d->firstTrackTitle) {
1501                                 this->d->firstTrackTitle = false;
1502                         } else {
1503                                 if (!handleError(XSPF_READER_ERROR_ELEMENT_TOOMANY,
1504                                                 XSPF_READER_TEXT_ZERO_TOO_MANY_ELEMENTS(
1505                                                 XSPF_NS_HOME, _PT("title")))) {
1506                                         return false;
1507                                 }
1508                         }
1509
1510                         if (!handleNoAttribsExceptXmlBase(atts)) {
1511                                 return false;
1512                         } else {
1513                                 this->d->elementStack.push(TAG_PLAYLIST_TRACKLIST_TRACK_TITLE);
1514                                 return true;
1515                         }
1516                         break;
1517
1518                 case _PT('r'):
1519                         if (::PORT_STRCMP(localName + 2, _PT("ackNum"))) {
1520                                 break;
1521                         }
1522
1523                         if (this->d->firstTrackTrackNum) {
1524                                 this->d->firstTrackTrackNum = false;
1525                         } else {
1526                                 if (!handleError(XSPF_READER_ERROR_ELEMENT_TOOMANY,
1527                                                 XSPF_READER_TEXT_ZERO_TOO_MANY_ELEMENTS(
1528                                                 XSPF_NS_HOME, _PT("trackNum")))) {
1529                                         return false;
1530                                 }
1531                         }
1532
1533                         if (!handleNoAttribsExceptXmlBase(atts)) {
1534                                 return false;
1535                         } else {
1536                                 this->d->elementStack.push(TAG_PLAYLIST_TRACKLIST_TRACK_TRACKNUM);
1537                                 return true;
1538                         }
1539                         break;
1540
1541                 }
1542                 break;
1543
1544         }
1545
1546         if (!handleError(XSPF_READER_ERROR_ELEMENT_FORBIDDEN,
1547                         XSPF_READER_TEXT_ONE_ELEMENT_FORBIDDEN,
1548                         fullName)) {
1549                 return false;
1550         }
1551
1552         this->d->elementStack.push(TAG_UNKNOWN);
1553         skipFromHere();
1554         return true;
1555 }
1556
1557
1558 void XspfReader::handleStart(XML_Char const * fullName, XML_Char const ** atts) {
1559         if (this->d->skip) {
1560                 this->d->elementStack.push(TAG_UNKNOWN);
1561                 return;
1562         }
1563
1564         bool res = true;
1565         if (this->d->insideExtension) {
1566                 res = this->d->extensionReader->handleExtensionStart(fullName, atts);
1567         } else {
1568                 switch (this->d->elementStack.size() + 1) {
1569                 case 1:
1570                         res = handleStartOne(fullName, atts);
1571                         break;
1572
1573                 case 2:
1574                         res = handleStartTwo(fullName, atts);
1575                         break;
1576
1577                 case 3:
1578                         res = handleStartThree(fullName, atts);
1579                         break;
1580
1581                 case 4:
1582                         res = handleStartFour(fullName, atts);
1583                         break;
1584
1585                 case 5:
1586                         if (!handleError(XSPF_READER_ERROR_ELEMENT_FORBIDDEN,
1587                                         XSPF_READER_TEXT_ONE_ELEMENT_FORBIDDEN,
1588                                         fullName)) {
1589                                 res = false;
1590                         } else {
1591                                 this->d->elementStack.push(TAG_UNKNOWN);
1592                                 skipFromHere();
1593                         }
1594                         break;
1595
1596                 }
1597         }
1598
1599         if (!res) {
1600                 stop();
1601         }
1602
1603         // Grow base URI stack as needed
1604         size_t const curBaseUriCount = this->d->baseUriStack.size();
1605         size_t const wantedBaseUriCount = this->d->elementStack.size();
1606         for (size_t i = curBaseUriCount; i < wantedBaseUriCount; i++) {
1607                 this->d->baseUriStack.push(this->d->baseUriStack.top());
1608         }
1609 }
1610
1611
1612 bool XspfReader::handleEndOne(XML_Char const * /*fullName*/) {
1613         if (this->d->firstPlaylistTrackList) {
1614                 if (!handleError(XSPF_READER_ERROR_ELEMENT_MISSING,
1615                                 XSPF_READER_TEXT_ZERO_ELEMENT_MISSING(
1616                                 XSPF_NS_HOME, _PT("trackList")))) {
1617                         return false;
1618                 }
1619         }
1620
1621         // Call property callback
1622         assert(this->d->callback != NULL);
1623         // Note: setProps() deletes the props for us
1624         this->d->callback->setProps(this->d->props);
1625         this->d->props = NULL;
1626         return true;
1627 }
1628
1629
1630 bool XspfReader::handleEndTwo(XML_Char const * /*fullName*/) {
1631         const unsigned int stackTop = this->d->elementStack.top();
1632
1633         // Collapse elements
1634         // NOTE: whitespace in the middle of <dateTime>,
1635         // <nonNegativeInteger>, and <anyURI> is illegal anyway
1636         // which is why we we only cut head and tail here
1637         switch (stackTop) {
1638         case TAG_PLAYLIST_INFO:
1639         case TAG_PLAYLIST_LOCATION:
1640         case TAG_PLAYLIST_IDENTIFIER:
1641         case TAG_PLAYLIST_IMAGE:
1642         case TAG_PLAYLIST_DATE:
1643         case TAG_PLAYLIST_LICENSE:
1644         case TAG_PLAYLIST_LINK:
1645         case TAG_PLAYLIST_META:
1646                 Toolbox::trimString(this->d->accum);
1647                 break;
1648
1649         default:
1650                 ; // NOOP
1651         }
1652
1653         XML_Char const * const finalAccum = this->d->accum.c_str();
1654
1655         switch (stackTop) {
1656         case TAG_PLAYLIST_ANNOTATION:
1657                 this->d->props->giveAnnotation(finalAccum, XspfData::COPY);
1658                 break;
1659 /*
1660         case TAG_PLAYLIST_ATTRIBUTION:
1661                 break;
1662 */
1663         case TAG_PLAYLIST_CREATOR:
1664                 this->d->props->giveCreator(finalAccum, XspfData::COPY);
1665                 break;
1666
1667         case TAG_PLAYLIST_DATE:
1668                 {
1669                         XspfDateTime * const dateTime = new XspfDateTime;
1670                         if (!XspfDateTime::extractDateTime(finalAccum, dateTime)) {
1671                                 delete dateTime;
1672                                 if (!handleError(XSPF_READER_ERROR_CONTENT_INVALID,
1673                                                 XSPF_READER_TEXT_ZERO_WRONG_CONTENT_TYPE(
1674                                                 XSPF_NS_HOME, _PT("date"), _PT("dateTime")))) {
1675                                         return false;
1676                                 }
1677                         } else {
1678                                 this->d->props->giveDate(dateTime, XspfData::TRANSFER);
1679                         }
1680                 }
1681                 break;
1682 /*
1683         case TAG_PLAYLIST_EXTENSION:
1684                 break;
1685 */
1686         case TAG_PLAYLIST_IDENTIFIER:
1687                 if (!Toolbox::isUri(finalAccum)) {
1688                         if (!handleError(XSPF_READER_ERROR_CONTENT_INVALID,
1689                                         XSPF_READER_TEXT_ZERO_WRONG_CONTENT_TYPE(
1690                                         XSPF_NS_HOME, _PT("identifier"), _PT("URI")))) {
1691                                 return false;
1692                         }
1693                 } else {
1694                         this->d->props->giveIdentifier(makeAbsoluteUri(finalAccum), XspfData::TRANSFER);
1695                 }
1696                 break;
1697
1698         case TAG_PLAYLIST_IMAGE:
1699                 if (!Toolbox::isUri(finalAccum)) {
1700                         if (!handleError(XSPF_READER_ERROR_CONTENT_INVALID,
1701                                         XSPF_READER_TEXT_ZERO_WRONG_CONTENT_TYPE(
1702                                         XSPF_NS_HOME, _PT("image"), _PT("URI")))) {
1703                                 return false;
1704                         }
1705                 } else {
1706                         this->d->props->giveImage(makeAbsoluteUri(finalAccum), XspfData::TRANSFER);
1707                 }
1708                 break;
1709
1710         case TAG_PLAYLIST_INFO:
1711                 if (!Toolbox::isUri(finalAccum)) {
1712                         if (!handleError(XSPF_READER_ERROR_CONTENT_INVALID,
1713                                         XSPF_READER_TEXT_ZERO_WRONG_CONTENT_TYPE(
1714                                         XSPF_NS_HOME, _PT("info"), _PT("URI")))) {
1715                                 return false;
1716                         }
1717                 } else {
1718                         this->d->props->giveInfo(makeAbsoluteUri(finalAccum), XspfData::TRANSFER);
1719                 }
1720                 break;
1721
1722         case TAG_PLAYLIST_LICENSE:
1723                 if (!Toolbox::isUri(finalAccum)) {
1724                         if (!handleError(XSPF_READER_ERROR_CONTENT_INVALID,
1725                                         XSPF_READER_TEXT_ZERO_WRONG_CONTENT_TYPE(
1726                                         XSPF_NS_HOME, _PT("license"), _PT("URI")))) {
1727                                 return false;
1728                         }
1729                 } else {
1730                         this->d->props->giveLicense(makeAbsoluteUri(finalAccum), XspfData::TRANSFER);
1731                 }
1732                 break;
1733
1734         case TAG_PLAYLIST_LINK:
1735                 if (!Toolbox::isUri(finalAccum)) {
1736                         if (!handleError(XSPF_READER_ERROR_CONTENT_INVALID,
1737                                         XSPF_READER_TEXT_ZERO_WRONG_CONTENT_TYPE(
1738                                         XSPF_NS_HOME, _PT("link"), _PT("URI")))) {
1739                                 return false;
1740                         }
1741                 } else {
1742                         this->d->props->giveAppendLink(
1743                                         this->d->lastRelValue.c_str(), XspfData::COPY,
1744                                         makeAbsoluteUri(finalAccum), XspfData::TRANSFER);
1745                 }
1746                 break;
1747
1748         case TAG_PLAYLIST_LOCATION:
1749                 if (!Toolbox::isUri(finalAccum)) {
1750                         if (!handleError(XSPF_READER_ERROR_CONTENT_INVALID,
1751                                         XSPF_READER_TEXT_ZERO_WRONG_CONTENT_TYPE(
1752                                         XSPF_NS_HOME, _PT("location"), _PT("URI")))) {
1753                                 return false;
1754                         }
1755                 } else {
1756                         this->d->props->giveLocation(
1757                                         makeAbsoluteUri(finalAccum), XspfData::TRANSFER);
1758                 }
1759                 break;
1760
1761         case TAG_PLAYLIST_META:
1762                 this->d->props->giveAppendMeta(
1763                                 this->d->lastRelValue.c_str(), XspfData::COPY,
1764                                 finalAccum, XspfData::COPY);
1765                 break;
1766
1767         case TAG_PLAYLIST_TITLE:
1768                 this->d->props->giveTitle(finalAccum, XspfData::COPY);
1769                 break;
1770
1771         case TAG_PLAYLIST_TRACKLIST:
1772                 // Check if empty for v0
1773                 if ((this->d->version == 0) && (this->d->firstTrack)) {
1774                         if (!handleError(XSPF_READER_ERROR_ELEMENT_MISSING,
1775                                         XSPF_READER_TEXT_ZERO_ELEMENT_MISSING_VERSION_ZERO(
1776                                         XSPF_NS_HOME, _PT("track")))) {
1777                                 return false;
1778                         }
1779                 }
1780                 break;
1781
1782         }
1783
1784         this->d->accum.clear();
1785         return true;
1786 }
1787
1788
1789 bool XspfReader::handleEndThree(XML_Char const * /*fullName*/) {
1790         const unsigned int stackTop = this->d->elementStack.top();
1791
1792         // Collapse elements
1793         // NOTE: whitespace in the middle of <dateTime>,
1794         // <nonNegativeInteger>, and <anyURI> is illegal anyway
1795         // which is why we we only cut head and tail here
1796         switch (stackTop) {
1797         case TAG_PLAYLIST_ATTRIBUTION_IDENTIFIER:
1798         case TAG_PLAYLIST_ATTRIBUTION_LOCATION:
1799                 Toolbox::trimString(this->d->accum);
1800                 break;
1801
1802         default:
1803                 ; // NOOP
1804         }
1805
1806         XML_Char const * const finalAccum = this->d->accum.c_str();
1807
1808         switch (stackTop) {
1809         case TAG_PLAYLIST_ATTRIBUTION_IDENTIFIER:
1810                 if (!Toolbox::isUri(finalAccum)) {
1811                         if (!handleError(XSPF_READER_ERROR_CONTENT_INVALID,
1812                                         XSPF_READER_TEXT_ZERO_WRONG_CONTENT_TYPE(
1813                                         XSPF_NS_HOME, _PT("identifier"), _PT("URI")))) {
1814                                 return false;
1815                         }
1816                 } else {
1817                         this->d->props->giveAppendAttributionIdentifier(makeAbsoluteUri(finalAccum), XspfData::TRANSFER);
1818                 }
1819                 break;
1820
1821         case TAG_PLAYLIST_ATTRIBUTION_LOCATION:
1822                 if (!Toolbox::isUri(finalAccum)) {
1823                         if (!handleError(XSPF_READER_ERROR_CONTENT_INVALID,
1824                                         XSPF_READER_TEXT_ZERO_WRONG_CONTENT_TYPE(
1825                                         XSPF_NS_HOME, _PT("location"), _PT("URI")))) {
1826                                 return false;
1827                         }
1828                 } else {
1829                         this->d->props->giveAppendAttributionLocation(makeAbsoluteUri(finalAccum), XspfData::TRANSFER);
1830                 }
1831                 break;
1832
1833         case TAG_PLAYLIST_TRACKLIST_TRACK:
1834                 // Call track callback
1835                 assert(this->d->callback != NULL);
1836                 // Note: addTrack() deletes the track for us
1837                 this->d->callback->addTrack(this->d->track);
1838                 this->d->track = NULL;
1839
1840                 this->d->firstTrackTitle = true;
1841                 this->d->firstTrackCreator = true;
1842                 this->d->firstTrackAnnotation = true;
1843                 this->d->firstTrackInfo = true;
1844                 this->d->firstTrackImage = true;
1845                 this->d->firstTrackAlbum = true;
1846                 this->d->firstTrackTrackNum = true;
1847                 this->d->firstTrackDuration = true;
1848         }
1849
1850         this->d->accum.clear();
1851         return true;
1852 }
1853
1854
1855 bool XspfReader::handleEndFour(XML_Char const * /*fullName*/) {
1856         const unsigned int stackTop = this->d->elementStack.top();
1857
1858         // Collapse elements
1859         // NOTE: whitespace in the middle of <dateTime>,
1860         // <nonNegativeInteger>, and <anyURI> is illegal anyway
1861         // which is why we we only cut head and tail here
1862         switch (stackTop) {
1863         case TAG_PLAYLIST_TRACKLIST_TRACK_LOCATION:
1864         case TAG_PLAYLIST_TRACKLIST_TRACK_IDENTIFIER:
1865         case TAG_PLAYLIST_TRACKLIST_TRACK_INFO:
1866         case TAG_PLAYLIST_TRACKLIST_TRACK_IMAGE:
1867         case TAG_PLAYLIST_TRACKLIST_TRACK_TRACKNUM:
1868         case TAG_PLAYLIST_TRACKLIST_TRACK_DURATION:
1869         case TAG_PLAYLIST_TRACKLIST_TRACK_LINK:
1870         case TAG_PLAYLIST_TRACKLIST_TRACK_META:
1871                 Toolbox::trimString(this->d->accum);
1872                 break;
1873
1874         default:
1875                 ; // NOOP
1876         }
1877
1878         XML_Char const * const finalAccum = this->d->accum.c_str();
1879
1880         switch (stackTop) {
1881         case TAG_PLAYLIST_TRACKLIST_TRACK_ALBUM:
1882                 this->d->track->giveAlbum(finalAccum, XspfData::COPY);
1883                 break;
1884
1885         case TAG_PLAYLIST_TRACKLIST_TRACK_ANNOTATION:
1886                 this->d->track->giveAnnotation(finalAccum, XspfData::COPY);
1887                 break;
1888
1889         case TAG_PLAYLIST_TRACKLIST_TRACK_CREATOR:
1890                 this->d->track->giveCreator(finalAccum, XspfData::COPY);
1891                 break;
1892
1893         case TAG_PLAYLIST_TRACKLIST_TRACK_DURATION:
1894                 int duration;
1895                 if (!Toolbox::extractInteger(finalAccum, 0, &duration)) {
1896                         if (!handleError(XSPF_READER_ERROR_CONTENT_INVALID,
1897                                         XSPF_READER_TEXT_ZERO_WRONG_CONTENT_TYPE(
1898                                         XSPF_NS_HOME, _PT("duration"),
1899                                         _PT("unsigned integer")))) {
1900                                 return false;
1901                         }
1902                 } else {
1903                         this->d->track->setDuration(duration);
1904                 }
1905                 break;
1906 /*
1907         case TAG_PLAYLIST_TRACKLIST_TRACK_EXTENSION:
1908                 break;
1909 */
1910         case TAG_PLAYLIST_TRACKLIST_TRACK_IDENTIFIER:
1911                 if (!Toolbox::isUri(finalAccum)) {
1912                         if (!handleError(XSPF_READER_ERROR_CONTENT_INVALID,
1913                                         XSPF_READER_TEXT_ZERO_WRONG_CONTENT_TYPE(
1914                                         XSPF_NS_HOME, _PT("identifier"), _PT("URI")))) {
1915                                 return false;
1916                         }
1917                 } else {
1918                         this->d->track->giveAppendIdentifier(
1919                                         makeAbsoluteUri(finalAccum), XspfData::TRANSFER);
1920                 }
1921                 break;
1922
1923         case TAG_PLAYLIST_TRACKLIST_TRACK_IMAGE:
1924                 if (!Toolbox::isUri(finalAccum)) {
1925                         if (!handleError(XSPF_READER_ERROR_CONTENT_INVALID,
1926                                         XSPF_READER_TEXT_ZERO_WRONG_CONTENT_TYPE(
1927                                         XSPF_NS_HOME, _PT("image"), _PT("URI")))) {
1928                                 return false;
1929                         }
1930                 } else {
1931                         this->d->track->giveImage(makeAbsoluteUri(finalAccum), XspfData::TRANSFER);
1932                 }
1933                 break;
1934
1935         case TAG_PLAYLIST_TRACKLIST_TRACK_INFO:
1936                 if (!Toolbox::isUri(finalAccum)) {
1937                         if (!handleError(XSPF_READER_ERROR_CONTENT_INVALID,
1938                                         XSPF_READER_TEXT_ZERO_WRONG_CONTENT_TYPE(
1939                                         XSPF_NS_HOME, _PT("info"), _PT("URI")))) {
1940                                 return false;
1941                         }
1942                 } else {
1943                         this->d->track->giveInfo(makeAbsoluteUri(finalAccum), XspfData::TRANSFER);
1944                 }
1945                 break;
1946
1947         case TAG_PLAYLIST_TRACKLIST_TRACK_LINK:
1948                 if (!Toolbox::isUri(finalAccum)) {
1949                         if (!handleError(XSPF_READER_ERROR_CONTENT_INVALID,
1950                                         XSPF_READER_TEXT_ZERO_WRONG_CONTENT_TYPE(
1951                                         XSPF_NS_HOME, _PT("link"), _PT("URI")))) {
1952                                 return false;
1953                         }
1954                 } else {
1955                         this->d->track->giveAppendLink(
1956                                         this->d->lastRelValue.c_str(), XspfData::COPY,
1957                                         makeAbsoluteUri(finalAccum), XspfData::TRANSFER);
1958                 }
1959                 break;
1960
1961         case TAG_PLAYLIST_TRACKLIST_TRACK_LOCATION:
1962                 if (!Toolbox::isUri(finalAccum)) {
1963                         if (!handleError(XSPF_READER_ERROR_CONTENT_INVALID,
1964                                         XSPF_READER_TEXT_ZERO_WRONG_CONTENT_TYPE(
1965                                         XSPF_NS_HOME, _PT("location"), _PT("URI")))) {
1966                                 return false;
1967                         }
1968                 } else {
1969                         this->d->track->giveAppendLocation(makeAbsoluteUri(finalAccum), XspfData::TRANSFER);
1970                 }
1971                 break;
1972
1973         case TAG_PLAYLIST_TRACKLIST_TRACK_META:
1974                 this->d->track->giveAppendMeta(this->d->lastRelValue.c_str(), XspfData::COPY,
1975                                 finalAccum, XspfData::COPY);
1976                 break;
1977
1978         case TAG_PLAYLIST_TRACKLIST_TRACK_TITLE:
1979                 this->d->track->giveTitle(finalAccum, XspfData::COPY);
1980                 break;
1981
1982         case TAG_PLAYLIST_TRACKLIST_TRACK_TRACKNUM:
1983                 int trackNum;
1984                 if (!Toolbox::extractInteger(finalAccum, 1, &trackNum)) {
1985                         if (!handleError(XSPF_READER_ERROR_CONTENT_INVALID,
1986                                         XSPF_READER_TEXT_ZERO_WRONG_CONTENT_TYPE(
1987                                         XSPF_NS_HOME, _PT("trackNum"),
1988                                         _PT("unsigned integer greater zero")))) {
1989                                 return false;
1990                         }
1991                 } else {
1992                         this->d->track->setTrackNum(trackNum);
1993                 }
1994                 break;
1995
1996         }
1997
1998         this->d->accum.clear();
1999         return true;
2000 }
2001
2002
2003 void XspfReader::handleEnd(XML_Char const * fullName) {
2004         if (this->d->skip) {
2005                 if (this->d->elementStack.size() == this->d->skipStopLevel) {
2006                         this->d->skip = false;
2007                 }
2008                 this->d->elementStack.pop();
2009                 return;
2010         }
2011
2012         bool extensionLeft = false;
2013         int pushBackTag = TAG_UNKNOWN; // Init not needed but kills the warning...
2014         if (this->d->insideExtension) {
2015                 switch (this->d->elementStack.size()) {
2016                 case 2:
2017                         if (this->d->elementStack.top() == TAG_PLAYLIST_EXTENSION) {
2018                                 pushBackTag = TAG_PLAYLIST_EXTENSION;
2019                                 extensionLeft = true;
2020                         }
2021                         break;
2022
2023                 case 4:
2024                         if (this->d->elementStack.top() == TAG_PLAYLIST_TRACKLIST_TRACK_EXTENSION) {
2025                                 pushBackTag = TAG_PLAYLIST_TRACKLIST_TRACK_EXTENSION;
2026                                 extensionLeft = true;
2027                         }
2028                         break;
2029
2030                 }
2031
2032                 if (!this->d->extensionReader->handleExtensionEnd(fullName)) {
2033                         stop();
2034                         return;
2035                 }
2036
2037                 if (extensionLeft) {
2038                         this->d->insideExtension = false;
2039
2040                         // Add extension
2041                         XspfExtension * const extension = this->d->extensionReader->wrap();
2042                         if (extension != NULL) {
2043                                 XspfData * const target = (pushBackTag == TAG_PLAYLIST_EXTENSION)
2044                                                 ? static_cast<XspfData *>(this->d->props)
2045                                                 : static_cast<XspfData *>(this->d->track);
2046                                 target->giveAppendExtension(extension, XspfData::TRANSFER);
2047                         }
2048
2049                         // Destroy extension reader
2050                         delete this->d->extensionReader;
2051                         this->d->extensionReader = NULL;
2052
2053                         this->d->elementStack.push(pushBackTag);
2054                 } else {
2055                         return;
2056                 }
2057         }
2058
2059         bool res = false;
2060         switch (this->d->elementStack.size()) {
2061         case 1:
2062                 res = handleEndOne(fullName);
2063                 break;
2064
2065         case 2:
2066                 res = handleEndTwo(fullName);
2067                 break;
2068
2069         case 3:
2070                 res = handleEndThree(fullName);
2071                 break;
2072
2073         case 4:
2074                 res = handleEndFour(fullName);
2075                 break;
2076
2077         }
2078
2079         if (!res) {
2080                 stop();
2081                 return;
2082         }
2083
2084         // Shrink base URI stack while always keeping the external
2085         // base URI at the bottom of the stack
2086         size_t const curBaseUriCount = this->d->baseUriStack.size();
2087         size_t const wantedBaseUriCount = this->d->elementStack.size();
2088         for (size_t i = curBaseUriCount; i > wantedBaseUriCount; i--) {
2089                 this->d->baseUriStack.pop();
2090         }
2091
2092         // Prevent popping twice
2093 //      if (!extensionLeft) {
2094                 this->d->elementStack.pop();
2095 //      }
2096
2097 }
2098
2099
2100 void XspfReader::handleCharacters(XML_Char const * s, int len) {
2101         if (this->d->skip) {
2102                 return;
2103         }
2104
2105         if (this->d->insideExtension) {
2106                 if (!this->d->extensionReader->handleExtensionCharacters(s, len)) {
2107                         stop();
2108                 }
2109                 return;
2110         }
2111
2112         switch (this->d->elementStack.size()) {
2113         case 1:
2114                 // Must be all whitespace at root
2115                 if (!Toolbox::isWhiteSpace(s, len)) {
2116                         if (!handleError(XSPF_READER_ERROR_CONTENT_INVALID,
2117                                         XSPF_READER_TEXT_ZERO_TEXT_FORBIDDEN(
2118                                         XSPF_NS_HOME, _PT("playlist")))) {
2119                                 stop();
2120                                 return;
2121                         }
2122                 }
2123                 break;
2124
2125         case 2:
2126                 switch (this->d->elementStack.top()) {
2127                 case TAG_PLAYLIST_TRACKLIST:
2128                         // Must be all whitespace
2129                         if (!Toolbox::isWhiteSpace(s, len)) {
2130                                 if (!handleError(XSPF_READER_ERROR_CONTENT_INVALID,
2131                                                 XSPF_READER_TEXT_ZERO_TEXT_FORBIDDEN(
2132                                                 XSPF_NS_HOME, _PT("trackList")))) {
2133                                         stop();
2134                                         return;
2135                                 }
2136                         }
2137                         break;
2138
2139                 case TAG_PLAYLIST_ATTRIBUTION:
2140                         // Must be all whitespace
2141                         if (!Toolbox::isWhiteSpace(s, len)) {
2142                                 if (!handleError(XSPF_READER_ERROR_CONTENT_INVALID,
2143                                                 XSPF_READER_TEXT_ZERO_TEXT_FORBIDDEN(
2144                                                 XSPF_NS_HOME, _PT("attribution")))) {
2145                                         stop();
2146                                         return;
2147                                 }
2148                         }
2149                         break;
2150
2151                 default:
2152                         // Append unmodified
2153                         this->d->accum.append(s, len);
2154
2155                 }
2156                 break;
2157
2158         case 3:
2159                 switch (this->d->elementStack.top()) {
2160                 case TAG_PLAYLIST_TRACKLIST_TRACK:
2161                         // Must be all whitespace
2162                         if (!Toolbox::isWhiteSpace(s, len)) {
2163                                 if (!handleError(XSPF_READER_ERROR_CONTENT_INVALID,
2164                                                 XSPF_READER_TEXT_ZERO_TEXT_FORBIDDEN(
2165                                                 XSPF_NS_HOME, _PT("track")))) {
2166                                         stop();
2167                                         return;
2168                                 }
2169                         }
2170                         break;
2171
2172                 default:
2173                         // Append unmodified
2174                         this->d->accum.append(s, len);
2175
2176                 }
2177                 break;
2178
2179         case 4:
2180                 // Append unmodified
2181                 this->d->accum.append(s, len);
2182                 break;
2183
2184         }
2185 }
2186
2187
2188 void
2189 XspfReader::handleEntityDeclaration(XML_Char const * entityName,
2190                 XML_Char const * value) {
2191         XML_Char const * walker = value;
2192         int valueLen = 0;
2193         int lookupSum = 0;
2194         int lookupDepth = 0;
2195         while (walker[0] != _PT('\0')) {
2196                 XML_Char const * atAmpersand = NULL;
2197                 XML_Char const * afterSemiColon = NULL;
2198                 XML_Char * entityRefname = nextEntityRefMalloc(walker,
2199                                 atAmpersand, afterSemiColon);
2200                 valueLen += static_cast<int>(atAmpersand - walker);
2201                 if (entityRefname != NULL) {
2202                         MapType::iterator found = this->d->entityNameToValueLen.find(
2203                                         StringType(entityRefname));
2204                         delete[] entityRefname;
2205                         EntityInfo const info =
2206                                         (found != this->d->entityNameToValueLen.end())
2207                                         ? found->second
2208                                         : EntityInfo(1, 0, 0);
2209                         valueLen += info.valueLen;
2210                         lookupSum += info.lookupSum + 1;
2211                         int const minLookupDepth = info.lookupDepth + 1;
2212                         if (lookupDepth < minLookupDepth) {
2213                                 lookupDepth = minLookupDepth;
2214                         }
2215                 } else {
2216                         valueLen += static_cast<int>(afterSemiColon - walker);
2217                         break;
2218                 }
2219                 walker = afterSemiColon;
2220         }
2221
2222         EntityInfo const info(valueLen, lookupSum, lookupDepth);
2223         this->d->entityNameToValueLen.insert(PairType(entityName, info));
2224
2225         if (this->d->limitLengthPerEntityValue && (valueLen > this->d->maxLengthPerEntity)) {
2226                 handleFatalError(XSPF_READER_ERROR_MALICIOUS_SPACE,
2227                                 _PT("Input considered harmful: Entity taking too much space"));
2228                 stop();
2229         } else if (this->d->limitLookupSumPerEntityValue && (lookupSum > this->d->maxTotalLookupsPerEntity)) {
2230                 handleFatalError(XSPF_READER_ERROR_MALICIOUS_LOOKUP_SUM,
2231                                 _PT("Input considered harmful: Entity requiring too many lookups"));
2232                 stop();
2233         } else if (this->d->limitLookupDepthPerEntityValue && (lookupDepth > this->d->maxLookupDepthPerEntity)) {
2234                 handleFatalError(XSPF_READER_ERROR_MALICIOUS_LOOKUP_DEPTH,
2235                                 _PT("Input considered harmful: Entity requiring too deep lookup"));
2236                 stop();
2237         }
2238 }
2239
2240
2241 void XspfReader::stop() {
2242         // Remove handlers
2243         ::XML_SetElementHandler(this->d->parser, NULL, NULL);
2244         ::XML_SetCharacterDataHandler(this->d->parser, NULL);
2245
2246         // Full stop
2247         ::XML_StopParser(this->d->parser, XML_FALSE);
2248 }
2249
2250
2251 bool XspfReader::handlePlaylistAttribs(XML_Char const ** atts) {
2252         bool versionFound = false;
2253         for (int i = 0; atts[i] != NULL; i += 2) {
2254                 if (!::PORT_STRCMP(atts[i], _PT("version"))) {
2255                         // Check and set version
2256                         int dummyVersion;
2257                         if (!Toolbox::extractInteger(atts[i + 1], 0, &dummyVersion)
2258                                         || (dummyVersion > 1)) {
2259                                 if (!handleError(XSPF_READER_ERROR_ATTRIBUTE_INVALID,
2260                                                 XSPF_READER_TEXT_ONE_WRONG_VERSION,
2261                                                 atts[i + 1])) {
2262                                         return false;
2263                                 }
2264                                 dummyVersion = XSPF_FALLBACK_VERSION;
2265                         }
2266                         this->d->version = dummyVersion;
2267                         versionFound = true;
2268                 } else if (isXmlBase(atts[i])) {
2269                         // xml:base
2270                         XML_Char const * const xmlBase = atts[i + 1];
2271                         if (!handleXmlBaseAttribute(xmlBase)) {
2272                                 return false;
2273                         }
2274                 } else {
2275                         // Forbidden attribute
2276                         if (!handleError(XSPF_READER_ERROR_ATTRIBUTE_FORBIDDEN,
2277                                         XSPF_READER_TEXT_ONE_ATTRIBUTE_FORBIDDEN, atts[i])) {
2278                                 return false;
2279                         }
2280                 }
2281         }
2282
2283         if (!versionFound) {
2284                 if (!handleError(XSPF_READER_ERROR_ATTRIBUTE_MISSING,
2285                                 XSPF_READER_TEXT_ZERO_ATTRIBUTE_MISSING(
2286                                 _PT("version")))) {
2287                         return false;
2288                 }
2289                 this->d->version = XSPF_FALLBACK_VERSION;
2290         }
2291
2292         return true;
2293 }
2294
2295
2296 namespace {
2297         /**
2298          * Checks whether a key URI contains version information.
2299          */
2300         bool containsVersion(XML_Char const * text) {
2301                 if (text == NULL) {
2302                         return true;
2303                 }
2304                 while (true) {
2305                         switch (text[0]) {
2306                         case _PT('\0'):
2307                                 return false;
2308                         case _PT('0'): // fall through
2309                         case _PT('1'): // fall through
2310                         case _PT('2'): // fall through
2311                         case _PT('3'): // fall through
2312                         case _PT('4'): // fall through
2313                         case _PT('5'): // fall through
2314                         case _PT('6'): // fall through
2315                         case _PT('7'): // fall through
2316                         case _PT('8'): // fall through
2317                         case _PT('9'): // fall through
2318                                 return true;
2319                         default:
2320                                 text++;
2321                         }
2322                 }
2323         }
2324 } // anon namespace
2325
2326
2327 bool
2328 XspfReader::handleMetaLinkAttribs(XML_Char const ** atts, XML_Char const * & rel) {
2329         rel = NULL;
2330         for (int i = 0; atts[i] != NULL; i += 2) {
2331                 if (!::PORT_STRCMP(atts[0], _PT("rel"))) {
2332                         // Check URI
2333                         if (Toolbox::isUri(atts[1])) {
2334                                 rel = atts[1];
2335
2336                                 // Extra checks
2337                                 if (!Toolbox::isAbsoluteUri(atts[1])) {
2338                                         if (!handleWarning(XSPF_READER_WARNING_KEY_WITH_REL_URI, XSPF_READER_TEXT_ZERO_KEY_WITH_REL_URI(_PT("rel")))) {
2339                                                 return false;
2340                                         }
2341                                 }
2342                                 if (!containsVersion(atts[1])) {
2343                                         if (!handleWarning(XSPF_READER_WARNING_KEY_WITHOUT_VERSION, XSPF_READER_TEXT_ZERO_KEY_WITHOUT_VERSION(_PT("rel")))) {
2344                                                 return false;
2345                                         }
2346                                 }
2347                         } else {
2348                                 if (!handleError(XSPF_READER_ERROR_ATTRIBUTE_INVALID,
2349                                                 XSPF_READER_TEXT_ZERO_WRONG_ATTRIBUTE_TYPE(
2350                                                 _PT("rel"), _PT("URI")))) {
2351                                         return false;
2352                                 }
2353                         }
2354                 } else if (isXmlBase(atts[i])) {
2355                         // xml:base
2356                         XML_Char const * const xmlBase = atts[i + 1];
2357                         if (!handleXmlBaseAttribute(xmlBase)) {
2358                                 return false;
2359                         }
2360                 } else {
2361                         if (!handleError(XSPF_READER_ERROR_ATTRIBUTE_FORBIDDEN,
2362                                         XSPF_READER_TEXT_ONE_ATTRIBUTE_FORBIDDEN, atts[0])) {
2363                                 return false;
2364                         }
2365                 }
2366         }
2367
2368         if (rel == NULL) {
2369                 if (!handleError(XSPF_READER_ERROR_ATTRIBUTE_MISSING,
2370                                 XSPF_READER_TEXT_ZERO_ATTRIBUTE_MISSING(_PT("rel")))) {
2371                         return false;
2372                 }
2373         }
2374
2375         return true;
2376 }
2377
2378
2379 bool
2380 XspfReader::handleExtensionAttribs(XML_Char const ** atts,
2381                 XML_Char const * & application) {
2382         application = NULL;
2383         for (int i = 0; atts[i] != NULL; i += 2) {
2384                 if (!::PORT_STRCMP(atts[0], _PT("application"))) {
2385                         // Check URI
2386                         if (Toolbox::isUri(atts[1])) {
2387                                 application = atts[1];
2388                         } else {
2389                                 if (!handleError(XSPF_READER_ERROR_ATTRIBUTE_INVALID,
2390                                                 XSPF_READER_TEXT_ZERO_WRONG_ATTRIBUTE_TYPE(
2391                                                 _PT("application"), _PT("URI")))) {
2392                                         return false;
2393                                 }
2394                         }
2395                 } else if (isXmlBase(atts[i])) {
2396                         // xml:base
2397                         XML_Char const * const xmlBase = atts[i + 1];
2398                         if (!handleXmlBaseAttribute(xmlBase)) {
2399                                 return false;
2400                         }
2401                 } else {
2402                         if (!handleError(XSPF_READER_ERROR_ATTRIBUTE_FORBIDDEN,
2403                                         XSPF_READER_TEXT_ONE_ATTRIBUTE_FORBIDDEN,
2404                                         atts[0])) {
2405                                 return false;
2406                         }
2407                 }
2408         }
2409
2410         if (application == NULL) {
2411                 if (!handleError(XSPF_READER_ERROR_ATTRIBUTE_MISSING,
2412                                 XSPF_READER_TEXT_ZERO_ATTRIBUTE_MISSING(
2413                                 _PT("application")))) {
2414                         return false;
2415                 }
2416         }
2417
2418         return true;
2419 }
2420
2421
2422 bool XspfReader::handleNoAttribsExceptXmlBase(XML_Char const ** atts) {
2423         // No attributes?
2424         for (int i = 0; atts[i] != NULL; i += 2) {
2425                 if (isXmlBase(atts[i])) {
2426                         // xml:base
2427                         XML_Char const * const xmlBase = atts[i + 1];
2428                         if (!handleXmlBaseAttribute(xmlBase)) {
2429                                 return false;
2430                         }
2431                 } else {
2432                         if (!handleError(XSPF_READER_ERROR_ATTRIBUTE_FORBIDDEN,
2433                                         XSPF_READER_TEXT_ONE_ATTRIBUTE_FORBIDDEN,
2434                                         atts[0])) {
2435                                 return false;
2436                         }
2437                 }
2438         }
2439
2440         return true; // Continue parsing
2441 }
2442
2443
2444 bool
2445 XspfReader::handleError(int code, XML_Char const * text) {
2446         int const line = ::XML_GetCurrentLineNumber(this->d->parser);
2447         int const column = ::XML_GetCurrentColumnNumber(this->d->parser);
2448         XML_Char const * const finalText
2449                         = (text != NULL) ? text : _PT("");
2450         assert(this->d->callback != NULL);
2451         bool const keepParsing = this->d->callback->handleError(
2452                         line, column, code, finalText);
2453         if (!keepParsing) {
2454                 this->d->errorCode = code;
2455         }
2456         return keepParsing;
2457 }
2458
2459
2460 bool
2461 XspfReader::handleError(int code, XML_Char const * format,
2462                 XML_Char const * param) {
2463         XML_Char * finalText;
2464         if (param != NULL) {
2465                 size_t const charCount = ::PORT_STRLEN(format) + ::PORT_STRLEN(param) + 1;
2466                 finalText = new XML_Char[charCount];
2467                 ::PORT_SNPRINTF(finalText, charCount, format, param);
2468         } else {
2469                 finalText = const_cast<XML_Char *>(
2470                                 (format != NULL) ? format : _PT(""));
2471         }
2472
2473         int const line = ::XML_GetCurrentLineNumber(this->d->parser);
2474         int const column = ::XML_GetCurrentColumnNumber(this->d->parser);
2475         assert(this->d->callback != NULL);
2476         bool const keepParsing = this->d->callback->handleError(
2477                         line, column, code, finalText);
2478
2479         if (param != NULL) {
2480                 delete [] finalText;
2481         }
2482         if (!keepParsing) {
2483                 this->d->errorCode = code;
2484         }
2485         return keepParsing;
2486 }
2487
2488
2489 void
2490 XspfReader::handleFatalError(int code, XML_Char const * format,
2491                 XML_Char const * param) {
2492         XML_Char * finalText;
2493         if (param != NULL) {
2494                 size_t const charCount = ::PORT_STRLEN(format) + ::PORT_STRLEN(param) + 1;
2495                 finalText = new XML_Char[charCount];
2496                 ::PORT_SNPRINTF(finalText, charCount, format, param);
2497         } else {
2498                 finalText = const_cast<XML_Char *>(
2499                                 (format != NULL) ? format : _PT(""));
2500         }
2501
2502         int const line = ::XML_GetCurrentLineNumber(this->d->parser);
2503         int const column = ::XML_GetCurrentColumnNumber(this->d->parser);
2504         assert(this->d->callback != NULL);
2505         this->d->callback->notifyFatalError(
2506                         line, column, code, finalText);
2507         this->d->errorCode = code;
2508
2509         if (param != NULL) {
2510                 delete [] finalText;
2511         }
2512 }
2513
2514
2515 void
2516 XspfReader::handleFatalError(int code, XML_Char const * text) {
2517         int const line = ::XML_GetCurrentLineNumber(this->d->parser);
2518         int const column = ::XML_GetCurrentColumnNumber(this->d->parser);
2519         XML_Char const * const finalText
2520                         = (text != NULL) ? text : _PT("");
2521         assert(this->d->callback != NULL);
2522         this->d->callback->notifyFatalError(
2523                         line, column, code, finalText);
2524         this->d->errorCode = code;
2525 }
2526
2527
2528 bool
2529 XspfReader::handleWarning(int code, XML_Char const * text) {
2530         int const line = ::XML_GetCurrentLineNumber(this->d->parser);
2531         int const column = ::XML_GetCurrentColumnNumber(this->d->parser);
2532         XML_Char const * const finalText
2533                         = (text != NULL) ? text : _PT("");
2534         assert(this->d->callback != NULL);
2535         return this->d->callback->handleWarning(line, column,
2536                         code, finalText);
2537 }
2538
2539
2540 void XspfReader::clearError() {
2541         this->d->errorCode = XSPF_READER_SUCCESS;
2542 }
2543
2544
2545 /*static*/ void XspfReader::masterStart(void * userData, XML_Char const * fullName, XML_Char const ** atts) {
2546         XspfReader * const parser = reinterpret_cast<XspfReader *>(userData);
2547         parser->handleStart(fullName, atts);
2548 }
2549
2550
2551 /*static*/ void XspfReader::masterEnd(void * userData, XML_Char const * fullName) {
2552         XspfReader * const parser = reinterpret_cast<XspfReader *>(userData);
2553         parser->handleEnd(fullName);
2554 }
2555
2556
2557 /*static*/ void XspfReader::masterCharacters(void * userData, XML_Char const * s, int len) {
2558         XspfReader * const parser = reinterpret_cast<XspfReader *>(userData);
2559         parser->handleCharacters(s, len);
2560 }
2561
2562
2563 /*static*/ void
2564 XspfReader::masterEntityDeclaration(void * userData, XML_Char const * entityName,
2565                         int /*is_parameter_entity*/, XML_Char const * value, int value_length,
2566                         XML_Char const * /*base*/, XML_Char const * /*systemId*/,
2567                         XML_Char const * /*publicId*/, XML_Char const * /*notationName*/) {
2568         if (value == NULL) {
2569                 return;
2570         }
2571         XspfReader * const parser = reinterpret_cast<XspfReader *>(userData);
2572         XML_Char * const zeroTerminatedValue = new XML_Char[value_length + 1];
2573         ::PORT_STRNCPY(zeroTerminatedValue, value, value_length);
2574         zeroTerminatedValue[value_length] = _PT('\0');
2575         parser->handleEntityDeclaration(entityName, zeroTerminatedValue);
2576         delete[] zeroTerminatedValue;
2577 }
2578
2579
2580 XspfStack<unsigned int> &
2581 XspfReader::getElementStack() const {
2582         return this->d->elementStack;
2583 }
2584
2585
2586 XspfStack<std::basic_string<XML_Char> > &
2587 XspfReader::getBaseUriStack() const {
2588         return this->d->baseUriStack;
2589 }
2590
2591
2592 void
2593 XspfReader::notifySuccess() const {
2594         assert(this->d->callback != NULL);
2595         this->d->callback->notifySuccess();
2596 }
2597
2598
2599 void
2600 XspfReader::skipFromHere() {
2601         this->d->skip = true;
2602         this->d->skipStopLevel = static_cast<int>(
2603                         this->d->elementStack.size());
2604 }
2605
2606
2607 void
2608 XspfReader::limitLengthPerEntityValue(bool enabled) {
2609         this->d->limitLengthPerEntityValue = enabled;
2610 }
2611
2612
2613 void
2614 XspfReader::limitLookupSumPerEntityValue(bool enabled) {
2615         this->d->limitLookupSumPerEntityValue = enabled;
2616 }
2617
2618
2619 void
2620 XspfReader::limitLookupDepthPerEntityValue(bool enabled) {
2621         this->d->limitLookupDepthPerEntityValue = enabled;
2622 }
2623
2624
2625 void
2626 XspfReader::enableMaliciousXmlDetection(bool enabled) {
2627         limitLengthPerEntityValue(enabled);
2628         limitLookupSumPerEntityValue(enabled);
2629         limitLookupDepthPerEntityValue(enabled);
2630 }
2631
2632
2633 void
2634 XspfReader::setMaxLengthPerEntityValue(int maxLength) {
2635         this->d->maxLengthPerEntity = maxLength;
2636 }
2637
2638
2639 void
2640 XspfReader::setMaxLookupSumPerEntityValue(int maxLookupSum) {
2641         this->d->maxTotalLookupsPerEntity = maxLookupSum;
2642 }
2643
2644
2645 void
2646 XspfReader::setMaxLookupDepthPerEntityValue(int maxLookupDepth) {
2647         this->d->maxLookupDepthPerEntity = maxLookupDepth;
2648 }
2649
2650 } // namespace Xspf