]> git.sesse.net Git - vlc/blob - modules/demux/playlist/xspf.c
xspf demux: fix memleak
[vlc] / modules / demux / playlist / xspf.c
1 /*******************************************************************************
2  * xspf.c : XSPF playlist import functions
3  *******************************************************************************
4  * Copyright (C) 2006 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Daniel Stränger <vlc at schmaller dot de>
8  *          Yoann Peronneau <yoann@videolan.org>
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  ******************************************************************************/
24 /**
25  * \file modules/demux/playlist/xspf.c
26  * \brief XSPF playlist import functions
27  */
28
29 #ifdef HAVE_CONFIG_H
30 # include "config.h"
31 #endif
32
33 #include <vlc_common.h>
34 #include <vlc_demux.h>
35
36 #include <vlc_xml.h>
37 #include <vlc_strings.h>
38 #include <vlc_url.h>
39 #include "xspf.h"
40 #include "playlist.h"
41
42 struct demux_sys_t
43 {
44     input_item_t **pp_tracklist;
45     int i_tracklist_entries;
46     int i_track_id;
47     char * psz_base;
48 };
49
50 static int Control(demux_t *, int, va_list);
51 static int Demux(demux_t *);
52
53 /**
54  * \brief XSPF submodule initialization function
55  */
56 int Import_xspf(vlc_object_t *p_this)
57 {
58     DEMUX_BY_EXTENSION_OR_FORCED_MSG(".xspf", "xspf-open",
59                                       "using XSPF playlist reader");
60     return VLC_SUCCESS;
61 }
62
63 void Close_xspf(vlc_object_t *p_this)
64 {
65     demux_t *p_demux = (demux_t *)p_this;
66     demux_sys_t *p_sys = p_demux->p_sys;
67     for (int i = 0; i < p_sys->i_tracklist_entries; i++)
68         if (p_sys->pp_tracklist[i])
69             vlc_gc_decref(p_sys->pp_tracklist[i]);
70     free(p_sys->pp_tracklist);
71     free(p_sys->psz_base);
72     free(p_sys);
73 }
74
75 /**
76  * \brief demuxer function for XSPF parsing
77  */
78 static int Demux(demux_t *p_demux)
79 {
80     int i_ret = -1;
81     xml_reader_t *p_xml_reader = NULL;
82     const char *name = NULL;
83     input_item_t *p_current_input = GetCurrentItem(p_demux);
84     p_demux->p_sys->pp_tracklist = NULL;
85     p_demux->p_sys->i_tracklist_entries = 0;
86     p_demux->p_sys->i_track_id = -1;
87     p_demux->p_sys->psz_base = NULL;
88
89     /* create new xml parser from stream */
90     p_xml_reader = xml_ReaderCreate(p_demux, p_demux->s);
91     if (!p_xml_reader)
92         goto end;
93
94     /* locating the root node */
95     if (xml_ReaderNextNode(p_xml_reader, &name) != XML_READER_STARTELEM)
96     {
97         msg_Err(p_demux, "can't read xml stream");
98         goto end;
99     }
100
101     /* checking root node name */
102     if (strcmp(name, "playlist"))
103     {
104         msg_Err(p_demux, "invalid root node name <%s>", name);
105         goto end;
106     }
107
108     input_item_node_t *p_subitems =
109         input_item_node_Create(p_current_input);
110
111     i_ret = parse_playlist_node(p_demux, p_subitems,
112                                  p_xml_reader, "playlist") ? 0 : -1;
113
114     for (int i = 0 ; i < p_demux->p_sys->i_tracklist_entries ; i++)
115     {
116         input_item_t *p_new_input = p_demux->p_sys->pp_tracklist[i];
117         if (p_new_input)
118         {
119             input_item_node_AppendItem(p_subitems, p_new_input);
120         }
121     }
122
123     input_item_node_PostAndDelete(p_subitems);
124
125 end:
126     vlc_gc_decref(p_current_input);
127     if (p_xml_reader)
128         xml_ReaderDelete(p_xml_reader);
129     return i_ret; /* Needed for correct operation of go back */
130 }
131
132 /** \brief dummy function for demux callback interface */
133 static int Control(demux_t *p_demux, int i_query, va_list args)
134 {
135     VLC_UNUSED(p_demux); VLC_UNUSED(i_query); VLC_UNUSED(args);
136     return VLC_EGENERIC;
137 }
138
139 /**
140  * \brief parse the root node of a XSPF playlist
141  * \param p_demux demuxer instance
142  * \param p_input_item current input item
143  * \param p_xml_reader xml reader instance
144  * \param psz_element name of element to parse
145  */
146 static bool parse_playlist_node COMPLEX_INTERFACE
147 {
148     input_item_t *p_input_item = p_input_node->p_item;
149     char *psz_value = NULL;
150     bool b_version_found = false;
151     int i_node;
152     bool b_ret = false;
153     xml_elem_hnd_t *p_handler = NULL;
154
155     xml_elem_hnd_t pl_elements[] =
156         { {"title",        SIMPLE_CONTENT,  {.smpl = set_item_info} },
157           {"creator",      SIMPLE_CONTENT,  {.smpl = set_item_info} },
158           {"annotation",   SIMPLE_CONTENT,  {.smpl = set_item_info} },
159           {"info",         SIMPLE_CONTENT,  {NULL} },
160           {"location",     SIMPLE_CONTENT,  {NULL} },
161           {"identifier",   SIMPLE_CONTENT,  {NULL} },
162           {"image",        SIMPLE_CONTENT,  {.smpl = set_item_info} },
163           {"date",         SIMPLE_CONTENT,  {NULL} },
164           {"license",      SIMPLE_CONTENT,  {NULL} },
165           {"attribution",  COMPLEX_CONTENT, {.cmplx = skip_element} },
166           {"link",         SIMPLE_CONTENT,  {NULL} },
167           {"meta",         SIMPLE_CONTENT,  {NULL} },
168           {"extension",    COMPLEX_CONTENT, {.cmplx = parse_extension_node} },
169           {"trackList",    COMPLEX_CONTENT, {.cmplx = parse_tracklist_node} },
170           {NULL,           UNKNOWN_CONTENT, {NULL} }
171         };
172
173     /* read all playlist attributes */
174     const char *name, *value;
175     while ((name = xml_ReaderNextAttr(p_xml_reader, &value)) != NULL)
176     {
177         /* attribute: version */
178         if (!strcmp(name, "version"))
179         {
180             b_version_found = true;
181             if (strcmp(value, "0") && strcmp(value, "1"))
182                 msg_Warn(p_demux, "unsupported XSPF version %s", value);
183         }
184         /* attribute: xmlns */
185         else if (!strcmp(name, "xmlns") || !strcmp(name, "xmlns:vlc"))
186             ;
187         else if (!strcmp(name, "xml:base"))
188         {
189             free(p_demux->p_sys->psz_base);
190             p_demux->p_sys->psz_base = strdup(psz_value);
191         }
192         /* unknown attribute */
193         else
194             msg_Warn(p_demux, "invalid <playlist> attribute: \"%s\"", name);
195     }
196     /* attribute version is mandatory !!! */
197     if (!b_version_found)
198         msg_Warn(p_demux, "<playlist> requires \"version\" attribute");
199
200     /* parse the child elements - we only take care of <trackList> */
201     psz_value = NULL;
202     while ((i_node = xml_ReaderNextNode(p_xml_reader, &name)) > 0)
203         switch (i_node)
204     {
205     /*  element start tag  */
206     case XML_READER_STARTELEM:
207         if (!*name)
208         {
209             msg_Err(p_demux, "invalid XML stream");
210             goto end;
211         }
212         /* choose handler */
213         for (p_handler = pl_elements;
214              p_handler->name && strcmp(name, p_handler->name);
215              p_handler++);
216         if (!p_handler->name)
217         {
218             msg_Err(p_demux, "unexpected element <%s>", name);
219             goto end;
220         }
221         /* complex content is parsed in a separate function */
222         if (p_handler->type == COMPLEX_CONTENT)
223         {
224             FREE_VALUE();
225             if (!p_handler->pf_handler.cmplx(p_demux, p_input_node,
226                                              p_xml_reader, p_handler->name))
227                 return false;
228             p_handler = NULL;
229         }
230         break;
231
232     /* simple element content */
233     case XML_READER_TEXT:
234         psz_value = strdup(name);
235         if (unlikely(!name))
236             goto end;
237         break;
238
239     /* element end tag */
240     case XML_READER_ENDELEM:
241         /* leave if the current parent node <playlist> is terminated */
242         if (!strcmp(name, psz_element))
243         {
244             b_ret = true;
245             goto end;
246         }
247         /* there MUST have been a start tag for that element name */
248         if (!p_handler || !p_handler->name || strcmp(p_handler->name, name))
249         {
250             msg_Err(p_demux, "there's no open element left for <%s>", name);
251             goto end;
252         }
253
254         if (p_handler->pf_handler.smpl)
255             p_handler->pf_handler.smpl(p_input_item, p_handler->name, psz_value);
256         FREE_ATT();
257         p_handler = NULL;
258         break;
259     }
260
261 end:
262     free(psz_value);
263     return b_ret;
264 }
265
266 /**
267  * \brief parses the tracklist node which only may contain <track>s
268  */
269 static bool parse_tracklist_node COMPLEX_INTERFACE
270 {
271     VLC_UNUSED(psz_element);
272     const char *name;
273     unsigned i_ntracks = 0;
274     int i_node;
275
276     /* now parse the <track>s */
277     while ((i_node = xml_ReaderNextNode(p_xml_reader, &name)) > 0)
278     {
279         if (i_node == XML_READER_STARTELEM)
280         {
281             if (strcmp(name, "track"))
282             {
283                 msg_Err(p_demux, "unexpected child of <trackList>: <%s>",
284                          name);
285                 return false;
286             }
287
288             /* parse the track data in a separate function */
289             if (parse_track_node(p_demux, p_input_node, p_xml_reader, "track"))
290                 i_ntracks++;
291         }
292         else if (i_node == XML_READER_ENDELEM)
293             break;
294     }
295
296     /* the <trackList> has to be terminated */
297     if (i_node != XML_READER_ENDELEM)
298     {
299         msg_Err(p_demux, "there's a missing </trackList>");
300         return false;
301     }
302     if (strcmp(name, "trackList"))
303     {
304         msg_Err(p_demux, "expected: </trackList>, found: </%s>", name);
305         return false;
306     }
307
308     msg_Dbg(p_demux, "parsed %u tracks successfully", i_ntracks);
309     return true;
310 }
311
312 /**
313  * \brief parse one track element
314  * \param COMPLEX_INTERFACE
315  */
316 static bool parse_track_node COMPLEX_INTERFACE
317 {
318     input_item_t *p_input_item = p_input_node->p_item;
319     const char *name;
320     char *psz_value = NULL;
321     const xml_elem_hnd_t *p_handler = NULL;
322     demux_sys_t *p_sys = p_demux->p_sys;
323     int i_node;
324
325     static const xml_elem_hnd_t track_elements[] =
326         { {"location",     SIMPLE_CONTENT,  {NULL} },
327           {"identifier",   SIMPLE_CONTENT,  {NULL} },
328           {"title",        SIMPLE_CONTENT,  {.smpl = set_item_info} },
329           {"creator",      SIMPLE_CONTENT,  {.smpl = set_item_info} },
330           {"annotation",   SIMPLE_CONTENT,  {.smpl = set_item_info} },
331           {"info",         SIMPLE_CONTENT,  {NULL} },
332           {"image",        SIMPLE_CONTENT,  {.smpl = set_item_info} },
333           {"album",        SIMPLE_CONTENT,  {.smpl = set_item_info} },
334           {"trackNum",     SIMPLE_CONTENT,  {.smpl = set_item_info} },
335           {"duration",     SIMPLE_CONTENT,  {.smpl = set_item_info} },
336           {"link",         SIMPLE_CONTENT,  {NULL} },
337           {"meta",         SIMPLE_CONTENT,  {NULL} },
338           {"extension",    COMPLEX_CONTENT, {.cmplx = parse_extension_node} },
339           {NULL,           UNKNOWN_CONTENT, {NULL} }
340         };
341
342     input_item_t *p_new_input = input_item_New(NULL, NULL);
343     if (!p_new_input)
344         return false;
345     input_item_node_t *p_new_node = input_item_node_Create(p_new_input);
346
347     /* reset i_track_id */
348     p_sys->i_track_id = -1;
349
350     while ((i_node = xml_ReaderNextNode(p_xml_reader, &name)) > 0)
351         switch (i_node)
352     {
353     /*  element start tag  */
354     case XML_READER_STARTELEM:
355         if (!*name)
356         {
357             msg_Err(p_demux, "invalid XML stream");
358             goto end;
359         }
360         /* choose handler */
361         for (p_handler = track_elements;
362              p_handler->name && strcmp(name, p_handler->name);
363              p_handler++);
364         if (!p_handler->name)
365         {
366             msg_Err(p_demux, "unexpected element <%s>", name);
367             goto end;
368         }
369         /* complex content is parsed in a separate function */
370         if (p_handler->type == COMPLEX_CONTENT)
371         {
372             FREE_VALUE();
373
374             if (!p_handler->pf_handler.cmplx(p_demux, p_new_node,
375                                              p_xml_reader, p_handler->name)) {
376                 input_item_node_Delete(p_new_node);
377                 return false;
378             }
379
380             p_handler = NULL;
381         }
382         break;
383
384     /* simple element content */
385     case XML_READER_TEXT:
386         free(psz_value);
387         psz_value = strdup(name);
388         if (unlikely(!psz_value))
389             goto end;
390         break;
391
392     /* element end tag */
393     case XML_READER_ENDELEM:
394         /* leave if the current parent node <track> is terminated */
395         if (!strcmp(name, psz_element))
396         {
397             free(psz_value);
398
399             /* Make sure we have a URI */
400             char *psz_uri = input_item_GetURI(p_new_input);
401             if (!psz_uri)
402                 input_item_SetURI(p_new_input, "vlc://nop");
403             else
404                 free(psz_uri);
405
406             if (p_sys->i_track_id < 0
407              || p_sys->i_track_id >= (SIZE_MAX / sizeof(p_new_input)))
408             {
409                 input_item_node_AppendNode(p_input_node, p_new_node);
410                 vlc_gc_decref(p_new_input);
411                 return true;
412             }
413
414             if (p_sys->i_track_id >= p_sys->i_tracklist_entries)
415             {
416                 input_item_t **pp;
417                 pp = realloc(p_sys->pp_tracklist,
418                     (p_sys->i_track_id + 1) * sizeof(*pp));
419                 if (!pp)
420                 {
421                     vlc_gc_decref(p_new_input);
422                     input_item_node_Delete(p_new_node);
423                     return false;
424                 }
425                 p_sys->pp_tracklist = pp;
426                 while (p_sys->i_track_id >= p_sys->i_tracklist_entries)
427                     pp[p_sys->i_tracklist_entries++] = NULL;
428             }
429             else if (p_sys->pp_tracklist[p_sys->i_track_id] != NULL)
430             {
431                 msg_Err(p_demux, "track ID %d collision", p_sys->i_track_id);
432                 vlc_gc_decref(p_new_input);
433                 input_item_node_Delete(p_new_node);
434                 return false;
435             }
436
437             p_sys->pp_tracklist[ p_sys->i_track_id ] = p_new_input;
438             input_item_node_Delete(p_new_node);
439             return true;
440         }
441         /* there MUST have been a start tag for that element name */
442         if (!p_handler || !p_handler->name || strcmp(p_handler->name, name))
443         {
444             msg_Err(p_demux, "there's no open element left for <%s>", name);
445             goto end;
446         }
447
448         /* special case: location */
449         if (!strcmp(p_handler->name, "location"))
450         {
451             if (psz_value == NULL)
452                 input_item_SetURI(p_new_input, "vlc://nop");
453             else
454             /* FIXME (#4005): This is broken. Scheme-relative (//...) locations
455              * and anchors (#...) are not resolved correctly. Also,
456              * host-relative (/...) and directory-relative locations
457              * ("relative path" in vernacular) should be resolved.
458              * Last, psz_base should default to the XSPF resource
459              * location if missing (not the current working directory).
460              * -- Courmisch */
461             if (p_sys->psz_base && !strstr(psz_value, "://"))
462             {
463                 char* psz_tmp;
464                 if (asprintf(&psz_tmp, "%s%s", p_sys->psz_base, psz_value)
465                     == -1)
466                 {
467                     goto end;
468                 }
469                 input_item_SetURI(p_new_input, psz_tmp);
470                 free(psz_tmp);
471             }
472             else
473                 input_item_SetURI(p_new_input, psz_value);
474             input_item_CopyOptions(p_input_item, p_new_input);
475         }
476         else
477         {
478             /* there MUST be an item */
479             if (p_handler->pf_handler.smpl)
480             {
481                 p_handler->pf_handler.smpl(p_new_input,
482                                             p_handler->name,
483                                             psz_value);
484                 FREE_ATT();
485             }
486         }
487         FREE_ATT();
488         p_handler = NULL;
489         break;
490     }
491     msg_Err(p_demux, "unexpected end of xml data");
492
493 end:
494
495     input_item_node_Delete(p_new_node);
496     free(psz_value);
497     return false;
498 }
499
500 /**
501  * \brief handles the supported <track> sub-elements
502  */
503 static bool set_item_info SIMPLE_INTERFACE
504 {
505     /* exit if setting is impossible */
506     if (!psz_name || !psz_value || !p_input)
507         return false;
508
509     /* re-convert xml special characters inside psz_value */
510     resolve_xml_special_chars(psz_value);
511
512     /* handle each info element in a separate "if" clause */
513     if (!strcmp(psz_name, "title"))
514         input_item_SetTitle(p_input, psz_value);
515     else if (!strcmp(psz_name, "creator"))
516         input_item_SetArtist(p_input, psz_value);
517     else if (!strcmp(psz_name, "album"))
518         input_item_SetAlbum(p_input, psz_value);
519     else if (!strcmp(psz_name, "trackNum"))
520         input_item_SetTrackNum(p_input, psz_value);
521     else if (!strcmp(psz_name, "duration"))
522     {
523         long i_num = atol(psz_value);
524         input_item_SetDuration(p_input, (mtime_t) i_num*1000);
525     }
526     else if (!strcmp(psz_name, "annotation"))
527         input_item_SetDescription(p_input, psz_value);
528     else if (!strcmp(psz_name, "image"))
529         input_item_SetArtURL(p_input, psz_value);
530     return true;
531 }
532
533 /**
534  * \brief handles the <vlc:option> elements
535  */
536 static bool set_option SIMPLE_INTERFACE
537 {
538     /* exit if setting is impossible */
539     if (!psz_name || !psz_value || !p_input)
540         return false;
541
542     /* re-convert xml special characters inside psz_value */
543     resolve_xml_special_chars(psz_value);
544
545     input_item_AddOption(p_input, psz_value, 0);
546
547     return true;
548 }
549
550 /**
551  * \brief parse the extension node of a XSPF playlist
552  */
553 static bool parse_extension_node COMPLEX_INTERFACE
554 {
555     input_item_t *p_input_item = p_input_node->p_item;
556     char *psz_value = NULL;
557     char *psz_title = NULL;
558     char *psz_application = NULL;
559     int i_node;
560     bool b_release_input_item = false;
561     xml_elem_hnd_t *p_handler = NULL;
562     input_item_t *p_new_input = NULL;
563
564     xml_elem_hnd_t pl_elements[] =
565         { {"vlc:node",   COMPLEX_CONTENT, {.cmplx = parse_extension_node} },
566           {"vlc:item",   COMPLEX_CONTENT, {.cmplx = parse_extitem_node} },
567           {"vlc:id",     SIMPLE_CONTENT, {NULL} },
568           {"vlc:option", SIMPLE_CONTENT, {.smpl = set_option} },
569           {NULL,    UNKNOWN_CONTENT, {NULL} }
570         };
571
572     /* read all extension node attributes */
573     const char *name, *value;
574     while ((name = xml_ReaderNextAttr(p_xml_reader, &value)) != NULL)
575     {
576         /* attribute: title */
577         if (!strcmp(name, "title"))
578         {
579             free(psz_title);
580             psz_title = strdup(value);
581             if (likely(psz_title != NULL))
582                 resolve_xml_special_chars(psz_title);
583         }
584         /* extension attribute: application */
585         else if (!strcmp(name, "application"))
586         {
587             free(psz_application);
588             psz_application = strdup(value);
589         }
590         /* unknown attribute */
591         else
592             msg_Warn(p_demux, "invalid <%s> attribute:\"%s\"", psz_element,
593                       name);
594     }
595
596     /* attribute title is mandatory except for <extension> */
597     if (!strcmp(psz_element, "vlc:node"))
598     {
599         if (!psz_title)
600         {
601             msg_Warn(p_demux, "<vlc:node> requires \"title\" attribute");
602             return false;
603         }
604         p_new_input = input_item_NewWithType("vlc://nop", psz_title,
605                                               0, NULL, 0, -1,
606                                               ITEM_TYPE_DIRECTORY);
607         if (p_new_input)
608         {
609             p_input_node =
610                 input_item_node_AppendItem(p_input_node, p_new_input);
611             p_input_item = p_new_input;
612             b_release_input_item = true;
613         }
614         free(psz_title);
615     }
616     else if (!strcmp(psz_element, "extension"))
617     {
618         if (!psz_application)
619         {
620             msg_Warn(p_demux, "<extension> requires \"application\" attribute");
621             return false;
622         }
623         /* Skip the extension if the application is not vlc
624            This will skip all children of the current node */
625         else if (strcmp(psz_application, "http://www.videolan.org/vlc/playlist/0"))
626         {
627             msg_Dbg(p_demux, "Skipping \"%s\" extension tag", psz_application);
628             free(psz_application);
629             /* Skip all children */
630             for (unsigned lvl = 1; lvl;)
631                 switch (xml_ReaderNextNode(p_xml_reader, NULL))
632                 {
633                     case XML_READER_STARTELEM: lvl++; break;
634                     case XML_READER_ENDELEM:   lvl--; break;
635                     case 0: case -1: return -1;
636                 }
637             return true;
638         }
639     }
640     free(psz_application);
641
642
643     /* parse the child elements */
644     while ((i_node = xml_ReaderNextNode(p_xml_reader, &name)) > 0)
645     {
646         switch (i_node)
647         {
648             /*  element start tag  */
649             case XML_READER_STARTELEM:
650                 if (!*name)
651                 {
652                     msg_Err(p_demux, "invalid xml stream");
653                     FREE_ATT();
654                     if (b_release_input_item) vlc_gc_decref(p_new_input);
655                     return false;
656                 }
657                 /* choose handler */
658                 for (p_handler = pl_elements;
659                      p_handler->name && strcmp(name, p_handler->name);
660                      p_handler++);
661                 if (!p_handler->name)
662                 {
663                     msg_Err(p_demux, "unexpected element <%s>", name);
664                     FREE_ATT();
665                     if (b_release_input_item) vlc_gc_decref(p_new_input);
666                     return false;
667                 }
668                 /* complex content is parsed in a separate function */
669                 if (p_handler->type == COMPLEX_CONTENT)
670                 {
671                     if (p_handler->pf_handler.cmplx(p_demux,
672                                                      p_input_node,
673                                                      p_xml_reader,
674                                                      p_handler->name))
675                     {
676                         p_handler = NULL;
677                         FREE_ATT();
678                     }
679                     else
680                     {
681                         FREE_ATT();
682                         if (b_release_input_item) vlc_gc_decref(p_new_input);
683                         return false;
684                     }
685                 }
686                 break;
687
688             case XML_READER_TEXT:
689                 /* simple element content */
690                 FREE_ATT();
691                 psz_value = strdup(name);
692                 if (unlikely(!psz_value))
693                 {
694                     FREE_ATT();
695                     if (b_release_input_item) vlc_gc_decref(p_new_input);
696                     return false;
697                 }
698                 break;
699
700             /* element end tag */
701             case XML_READER_ENDELEM:
702                 /* leave if the current parent node is terminated */
703                 if (!strcmp(name, psz_element))
704                 {
705                     FREE_ATT();
706                     if (b_release_input_item) vlc_gc_decref(p_new_input);
707                     return true;
708                 }
709                 /* there MUST have been a start tag for that element name */
710                 if (!p_handler || !p_handler->name
711                     || strcmp(p_handler->name, name))
712                 {
713                     msg_Err(p_demux, "there's no open element left for <%s>",
714                              name);
715                     FREE_ATT();
716                     if (b_release_input_item) vlc_gc_decref(p_new_input);
717                     return false;
718                 }
719
720                 /* special tag <vlc:id> */
721                 if (!strcmp(p_handler->name, "vlc:id"))
722                 {
723                     p_demux->p_sys->i_track_id = atoi(psz_value);
724                 }
725                 else if (p_handler->pf_handler.smpl)
726                 {
727                     p_handler->pf_handler.smpl(p_input_item, p_handler->name,
728                                                 psz_value);
729                 }
730                 FREE_ATT();
731                 p_handler = NULL;
732                 break;
733         }
734     }
735     if (b_release_input_item) vlc_gc_decref(p_new_input);
736     return false;
737 }
738
739 /**
740  * \brief parse the extension item node of a XSPF playlist
741  */
742 static bool parse_extitem_node COMPLEX_INTERFACE
743 {
744     VLC_UNUSED(psz_element);
745     input_item_t *p_new_input = NULL;
746     int i_tid = -1;
747
748     /* read all extension item attributes */
749     const char *name, *value;
750     while ((name = xml_ReaderNextAttr(p_xml_reader, &value)) != NULL)
751     {
752         /* attribute: href */
753         if (!strcmp(name, "tid"))
754             i_tid = atoi(value);
755         /* unknown attribute */
756         else
757             msg_Warn(p_demux, "invalid <vlc:item> attribute: \"%s\"", name);
758     }
759
760     /* attribute href is mandatory */
761     if (i_tid < 0)
762     {
763         msg_Warn(p_demux, "<vlc:item> requires \"tid\" attribute");
764         return false;
765     }
766
767     if (i_tid >= p_demux->p_sys->i_tracklist_entries)
768     {
769         msg_Warn(p_demux, "invalid \"tid\" attribute");
770         return false;
771     }
772
773     p_new_input = p_demux->p_sys->pp_tracklist[ i_tid ];
774     if (p_new_input)
775     {
776         input_item_node_AppendItem(p_input_node, p_new_input);
777         vlc_gc_decref(p_new_input);
778         p_demux->p_sys->pp_tracklist[i_tid] = NULL;
779     }
780
781     return true;
782 }
783
784 /**
785  * \brief skips complex element content that we can't manage
786  */
787 static bool skip_element COMPLEX_INTERFACE
788 {
789     VLC_UNUSED(p_demux); VLC_UNUSED(p_input_node);
790     VLC_UNUSED(psz_element);
791
792     for (unsigned lvl = 1; lvl;)
793         switch (xml_ReaderNextNode(p_xml_reader, NULL))
794         {
795             case XML_READER_STARTELEM: lvl++; break;
796             case XML_READER_ENDELEM:   lvl--; break;
797             case 0: case -1: return false;
798         }
799
800     return true;
801 }