Update to gupnp-av-0.12.4
[profile/ivi/GUPnP-AV.git] / libgupnp-av / gupnp-didl-lite-parser.c
1 /*
2  * Copyright (C) 2007 Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
3  *
4  * Authors: Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  */
21
22 /**
23  * SECTION:gupnp-didl-lite-parser
24  * @short_description: A/V DIDL-Lite XML parser
25  *
26  * #GUPnPDIDLLiteParser parses DIDL-Lite XML strings.
27  *
28  */
29
30 #include <string.h>
31 #include <ctype.h>
32 #include "gupnp-av.h"
33 #include "gupnp-didl-lite-object-private.h"
34 #include "xml-util.h"
35 #include "gupnp-didl-lite-parser-private.h"
36
37 G_DEFINE_TYPE (GUPnPDIDLLiteParser,
38                gupnp_didl_lite_parser,
39                G_TYPE_OBJECT);
40
41 enum {
42         OBJECT_AVAILABLE,
43         ITEM_AVAILABLE,
44         CONTAINER_AVAILABLE,
45         SIGNAL_LAST
46 };
47
48 static guint signals[SIGNAL_LAST];
49
50 static gboolean
51 verify_didl_attributes (xmlNode *node)
52 {
53         const char *content;
54
55         content = xml_util_get_child_element_content (node, "date");
56         if (content) {
57                 /* try to roughly verify the passed date with ^\d{4}-\d{2}-\d{2} */
58                 char *ptr = (char *) content;
59                 int state = 0;
60                 while (*ptr) {
61                         if (state == 4 || state == 7) {
62                                 if (*ptr != '-')
63                                         return FALSE;
64                         } else {
65                                 if (!isdigit (*ptr))
66                                         return FALSE;
67                         }
68
69                         ptr++;
70                         state++;
71                         if (state == 10)
72                                 break;
73                 }
74         }
75
76         return xml_util_verify_attribute_is_boolean (node, "restricted");
77 }
78
79 static gboolean
80 parse_elements (GUPnPDIDLLiteParser *parser,
81                 xmlNode             *node,
82                 GUPnPXMLDoc         *xml_doc,
83                 xmlNs               *upnp_ns,
84                 xmlNs               *dc_ns,
85                 xmlNs               *dlna_ns,
86                 xmlNs               *pv_ns,
87                 gboolean             recursive,
88                 GError             **error);
89
90 static void
91 gupnp_didl_lite_parser_init (G_GNUC_UNUSED GUPnPDIDLLiteParser *parser)
92 {
93 }
94
95 static void
96 gupnp_didl_lite_parser_dispose (GObject *object)
97 {
98         GObjectClass   *gobject_class;
99
100         gobject_class = G_OBJECT_CLASS (gupnp_didl_lite_parser_parent_class);
101         gobject_class->dispose (object);
102 }
103
104 static void
105 gupnp_didl_lite_parser_class_init (GUPnPDIDLLiteParserClass *klass)
106 {
107         GObjectClass *object_class;
108
109         object_class = G_OBJECT_CLASS (klass);
110
111         object_class->dispose = gupnp_didl_lite_parser_dispose;
112
113         /**
114          * GUPnPDIDLLiteParser::object-available:
115          * @parser: The #GUPnPDIDLLiteParser that received the signal
116          * @object: The now available #GUPnPDIDLLiteObject
117          *
118          * The ::object-available signal is emitted each time an object is
119          * found in the DIDL-Lite XML being parsed.
120          **/
121         signals[OBJECT_AVAILABLE] =
122                 g_signal_new ("object-available",
123                               GUPNP_TYPE_DIDL_LITE_PARSER,
124                               G_SIGNAL_RUN_LAST,
125                               G_STRUCT_OFFSET (GUPnPDIDLLiteParserClass,
126                                                object_available),
127                               NULL,
128                               NULL,
129                               g_cclosure_marshal_VOID__OBJECT,
130                               G_TYPE_NONE,
131                               1,
132                               GUPNP_TYPE_DIDL_LITE_OBJECT);
133
134         /**
135          * GUPnPDIDLLiteParser::item-available:
136          * @parser: The #GUPnPDIDLLiteParser that received the signal
137          * @item: The now available #GUPnPDIDLLiteItem
138          *
139          * The ::item-available signal is emitted each time an item is found in
140          * the DIDL-Lite XML being parsed.
141          **/
142         signals[ITEM_AVAILABLE] =
143                 g_signal_new ("item-available",
144                               GUPNP_TYPE_DIDL_LITE_PARSER,
145                               G_SIGNAL_RUN_LAST,
146                               G_STRUCT_OFFSET (GUPnPDIDLLiteParserClass,
147                                                item_available),
148                               NULL,
149                               NULL,
150                               g_cclosure_marshal_VOID__OBJECT,
151                               G_TYPE_NONE,
152                               1,
153                               GUPNP_TYPE_DIDL_LITE_ITEM);
154
155         /**
156          * GUPnPDIDLLiteParser::container-available:
157          * @parser: The #GUPnPDIDLLiteParser that received the signal
158          * @container: The now available #GUPnPDIDLLiteContainer
159          *
160          * The ::container-available signal is emitted each time a container is
161          * found in the DIDL-Lite XML being parsed.
162          **/
163         signals[CONTAINER_AVAILABLE] =
164                 g_signal_new ("container-available",
165                               GUPNP_TYPE_DIDL_LITE_PARSER,
166                               G_SIGNAL_RUN_LAST,
167                               G_STRUCT_OFFSET (GUPnPDIDLLiteParserClass,
168                                                container_available),
169                               NULL,
170                               NULL,
171                               g_cclosure_marshal_VOID__OBJECT,
172                               G_TYPE_NONE,
173                               1,
174                               GUPNP_TYPE_DIDL_LITE_CONTAINER);
175 }
176
177 /**
178  * gupnp_didl_lite_parser_new:
179  *
180  * Return value: A new #GUPnPDIDLLiteParser object.
181  **/
182 GUPnPDIDLLiteParser *
183 gupnp_didl_lite_parser_new (void)
184 {
185         return g_object_new (GUPNP_TYPE_DIDL_LITE_PARSER, NULL);
186 }
187
188 /**
189  * gupnp_didl_lite_parser_parse_didl:
190  * @parser: A #GUPnPDIDLLiteParser
191  * @didl: The DIDL-Lite XML string to be parsed
192  * @error: The location where to store any error, or NULL
193  *
194  * Parses DIDL-Lite XML string @didl, emitting the ::object-available,
195  * ::item-available and ::container-available signals appropriately during the
196  * process.
197  *
198  * Return value: TRUE on success.
199  **/
200 gboolean
201 gupnp_didl_lite_parser_parse_didl (GUPnPDIDLLiteParser *parser,
202                                    const char          *didl,
203                                    GError             **error)
204 {
205         return gupnp_didl_lite_parser_parse_didl_recursive (parser,
206                                                             didl,
207                                                             FALSE,
208                                                             error);
209 }
210
211 /**
212  * gupnp_didl_lite_parser_parse_didl_recursive:
213  * @parser: A #GUPnPDIDLLiteParser
214  * @didl: The DIDL-Lite XML string to be parsed
215  * @error: The location where to store any error, or NULL
216  *
217  * Parses DIDL-Lite XML string @didl, emitting the ::object-available,
218  * ::item-available and ::container-available signals appropriately during the
219  * process.
220  *
221  * Return value: TRUE on success.
222  **/
223 gboolean
224 gupnp_didl_lite_parser_parse_didl_recursive (GUPnPDIDLLiteParser *parser,
225                                              const char          *didl,
226                                              gboolean             recursive,
227                                              GError             **error)
228 {
229         xmlDoc       *doc;
230         xmlNode      *element;
231         xmlNs       **ns_list;
232         xmlNs        *upnp_ns = NULL;
233         xmlNs        *dc_ns   = NULL;
234         xmlNs        *dlna_ns = NULL;
235         xmlNs        *pv_ns   = NULL;
236         GUPnPXMLDoc  *xml_doc;
237         gboolean      result;
238
239         doc = xmlRecoverMemory (didl, strlen (didl));
240         if (doc == NULL) {
241                 g_set_error (error,
242                              GUPNP_XML_ERROR,
243                              GUPNP_XML_ERROR_PARSE,
244                              "Could not parse DIDL-Lite XML:\n%s", didl);
245
246                 return FALSE;
247         }
248
249         /* Get a pointer to root element */
250         element = xml_util_get_element ((xmlNode *) doc,
251                                         "DIDL-Lite",
252                                         NULL);
253         if (element == NULL) {
254                 g_set_error (error,
255                              GUPNP_XML_ERROR,
256                              GUPNP_XML_ERROR_NO_NODE,
257                              "No 'DIDL-Lite' node in the DIDL-Lite XML:\n%s",
258                              didl);
259                 xmlFreeDoc (doc);
260
261                 return FALSE;
262         }
263
264         if (element->children == NULL) {
265                 g_set_error (error,
266                              GUPNP_XML_ERROR,
267                              GUPNP_XML_ERROR_EMPTY_NODE,
268                              "Empty 'DIDL-Lite' node in the DIDL-Lite XML:\n%s",
269                              didl);
270                 xmlFreeDoc (doc);
271
272                 return FALSE;
273         }
274
275         /* Lookup UPnP and DC namespaces */
276         ns_list = xmlGetNsList (doc,
277                                 xmlDocGetRootElement (doc));
278
279         if (ns_list) {
280                 short i;
281
282                 for (i = 0; ns_list[i] != NULL; i++) {
283                         const char *prefix = (const char *) ns_list[i]->prefix;
284
285                         if (prefix == NULL)
286                                 continue;
287
288                         if (! upnp_ns &&
289                             g_ascii_strcasecmp (prefix, "upnp") == 0)
290                                 upnp_ns = ns_list[i];
291                         else if (! dc_ns &&
292                                  g_ascii_strcasecmp (prefix, "dc") == 0)
293                                 dc_ns = ns_list[i];
294                         else if (! dlna_ns &&
295                                  g_ascii_strcasecmp (prefix, "dlna") == 0)
296                                 dlna_ns = ns_list[i];
297                         else if (! pv_ns &&
298                                 g_ascii_strcasecmp (prefix, "pv") == 0)
299                                 pv_ns = ns_list[i];
300                 }
301
302                 xmlFree (ns_list);
303         }
304
305         /* Create UPnP and DC namespaces if they don't exist */
306         if (! upnp_ns)
307                 upnp_ns = xmlNewNs (xmlDocGetRootElement (doc),
308                                     (unsigned char *)
309                                     "urn:schemas-upnp-org:metadata-1-0/upnp/",
310                                     (unsigned char *)
311                                     GUPNP_DIDL_LITE_WRITER_NAMESPACE_UPNP);
312         if (! dc_ns)
313                 dc_ns = xmlNewNs (xmlDocGetRootElement (doc),
314                                   (unsigned char *)
315                                   "http://purl.org/dc/elements/1.1/",
316                                   (unsigned char *)
317                                   GUPNP_DIDL_LITE_WRITER_NAMESPACE_DC);
318         if (! dlna_ns)
319                 dlna_ns = xmlNewNs (xmlDocGetRootElement (doc),
320                                     (unsigned char *)
321                                     "urn:schemas-dlna-org:metadata-2-0/",
322                                     (unsigned char *)
323                                     GUPNP_DIDL_LITE_WRITER_NAMESPACE_DLNA);
324         if (! pv_ns)
325                 dlna_ns = xmlNewNs (xmlDocGetRootElement (doc),
326                                     (unsigned char *)
327                                     "http://www.pv.com/pvns/",
328                                     (unsigned char *)
329                                     GUPNP_DIDL_LITE_WRITER_NAMESPACE_PV);
330
331         xml_doc = gupnp_xml_doc_new (doc);
332
333         result = parse_elements (parser,
334                                  element,
335                                  xml_doc,
336                                  upnp_ns,
337                                  dc_ns,
338                                  dlna_ns,
339                                  pv_ns,
340                                  recursive,
341                                  error);
342         g_object_unref (xml_doc);
343
344         return result;
345 }
346
347 static gboolean
348 parse_elements (GUPnPDIDLLiteParser *parser,
349                 xmlNode             *node,
350                 GUPnPXMLDoc         *xml_doc,
351                 xmlNs               *upnp_ns,
352                 xmlNs               *dc_ns,
353                 xmlNs               *dlna_ns,
354                 xmlNs               *pv_ns,
355                 gboolean             recursive,
356                 GError             **error)
357 {
358         xmlNode *element;
359
360         for (element = node->children; element; element = element->next) {
361                 GUPnPDIDLLiteObject *object;
362
363                 object = gupnp_didl_lite_object_new_from_xml (element, xml_doc,
364                                                               upnp_ns, dc_ns,
365                                                               dlna_ns, pv_ns);
366
367                 if (object == NULL)
368                         continue;
369
370                 if (GUPNP_IS_DIDL_LITE_CONTAINER (object)) {
371                         g_signal_emit (parser,
372                                        signals[CONTAINER_AVAILABLE],
373                                        0,
374                                        object);
375                         if (recursive &&
376                             !parse_elements (parser,
377                                              element,
378                                              xml_doc,
379                                              upnp_ns,
380                                              dc_ns,
381                                              dlna_ns,
382                                              pv_ns,
383                                              recursive,
384                                              error)) {
385                                 g_object_unref (object);
386
387                                 return FALSE;
388                         }
389                 } else if (GUPNP_IS_DIDL_LITE_ITEM (object)) {
390                         node = gupnp_didl_lite_object_get_xml_node (object);
391                         if (!verify_didl_attributes (node)) {
392                                 g_object_unref (object);
393                                 g_set_error (error,
394                                              GUPNP_XML_ERROR,
395                                              GUPNP_XML_ERROR_INVALID_ATTRIBUTE,
396                                              "Could not parse DIDL-Lite XML");
397
398                                 return FALSE;
399                         }
400
401                         g_signal_emit (parser,
402                                        signals[ITEM_AVAILABLE],
403                                        0,
404                                        object);
405                 }
406
407                 g_signal_emit (parser,
408                                signals[OBJECT_AVAILABLE],
409                                0,
410                                object);
411
412                 g_object_unref (object);
413         }
414
415         return TRUE;
416 }