1 /*
2 Copyright (C) 2007 Bastien Nocera <hadess@hadess.net>
3
4 The Gnome Library is free software; you can redistribute it and/or
5 modify it under the terms of the GNU Library General Public License as
6 published by the Free Software Foundation; either version 2 of the
7 License, or (at your option) any later version.
8
9 The Gnome Library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 Library General Public License for more details.
13
14 You should have received a copy of the GNU Library General Public
15 License along with the Gnome Library; see the file COPYING.LIB. If not,
16 write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 Boston, MA 02110-1301 USA.
18
19 Author: Bastien Nocera <hadess@hadess.net>
20 */
21
22 #include "config.h"
23
24 #include <string.h>
25 #include <glib.h>
26
27 #ifndef TOTEM_PL_PARSER_MINI
28 #include "xmlparser.h"
29 #include "totem-pl-parser.h"
30 #include "totem-disc.h"
31 #endif /* !TOTEM_PL_PARSER_MINI */
32
33 #include "totem-pl-parser-mini.h"
34 #include "totem-pl-parser-podcast.h"
35 #include "totem-pl-parser-videosite.h"
36 #include "totem-pl-parser-private.h"
37
38 #define RSS_NEEDLE "<rss "
39 #define RSS_NEEDLE2 "<rss\n"
40 #define ATOM_NEEDLE "<feed "
41 #define OPML_NEEDLE "<opml "
42
43 const char *
totem_pl_parser_is_rss(const char * data,gsize len)44 totem_pl_parser_is_rss (const char *data, gsize len)
45 {
46 if (len == 0)
47 return FALSE;
48 if (len > MIME_READ_CHUNK_SIZE)
49 len = MIME_READ_CHUNK_SIZE;
50
51 if (g_strstr_len (data, len, RSS_NEEDLE) != NULL)
52 return RSS_MIME_TYPE;
53 if (g_strstr_len (data, len, RSS_NEEDLE2) != NULL)
54 return RSS_MIME_TYPE;
55
56 return NULL;
57 }
58
59 const char *
totem_pl_parser_is_atom(const char * data,gsize len)60 totem_pl_parser_is_atom (const char *data, gsize len)
61 {
62 if (len == 0)
63 return FALSE;
64 if (len > MIME_READ_CHUNK_SIZE)
65 len = MIME_READ_CHUNK_SIZE;
66
67 if (g_strstr_len (data, len, ATOM_NEEDLE) != NULL)
68 return ATOM_MIME_TYPE;
69
70 return NULL;
71 }
72
73 const char *
totem_pl_parser_is_opml(const char * data,gsize len)74 totem_pl_parser_is_opml (const char *data, gsize len)
75 {
76 if (len == 0)
77 return FALSE;
78 if (len > MIME_READ_CHUNK_SIZE)
79 len = MIME_READ_CHUNK_SIZE;
80
81 if (g_strstr_len (data, len, OPML_NEEDLE) != NULL)
82 return OPML_MIME_TYPE;
83
84 return NULL;
85 }
86
87 const char *
totem_pl_parser_is_xml_feed(const char * data,gsize len)88 totem_pl_parser_is_xml_feed (const char *data, gsize len)
89 {
90 if (totem_pl_parser_is_rss (data, len) != NULL)
91 return RSS_MIME_TYPE;
92 if (totem_pl_parser_is_atom (data, len) != NULL)
93 return ATOM_MIME_TYPE;
94 if (totem_pl_parser_is_opml (data, len) != NULL)
95 return OPML_MIME_TYPE;
96 return NULL;
97 }
98
99 #ifndef TOTEM_PL_PARSER_MINI
100
101 static gboolean
is_image(const char * url)102 is_image (const char *url)
103 {
104 char *content_type;
105 gboolean retval = FALSE;
106
107 content_type = g_content_type_guess (url, NULL, 0, NULL);
108 if (content_type == NULL)
109 return FALSE;
110 if (g_content_type_is_a (content_type, "image/*"))
111 retval = TRUE;
112 g_free (content_type);
113 return retval;
114 }
115
116 static TotemPlParserResult
parse_rss_item(TotemPlParser * parser,xml_node_t * parent)117 parse_rss_item (TotemPlParser *parser, xml_node_t *parent)
118 {
119 const char *title, *uri, *description, *author, *img;
120 const char *pub_date, *duration, *filesize, *content_type, *id;
121 xml_node_t *node;
122
123 title = uri = description = author = content_type = NULL;
124 img = pub_date = duration = filesize = id = NULL;
125
126 for (node = parent->child; node != NULL; node = node->next) {
127 if (node->name == NULL)
128 continue;
129
130 if (g_ascii_strcasecmp (node->name, "title") == 0) {
131 title = node->data;
132 } else if (g_ascii_strcasecmp (node->name, "url") == 0) {
133 uri = node->data;
134 } else if (g_ascii_strcasecmp (node->name, "pubDate") == 0) {
135 pub_date = node->data;
136 } else if (g_ascii_strcasecmp (node->name, "guid") == 0) {
137 id = node->data;
138 } else if (g_ascii_strcasecmp (node->name, "description") == 0
139 || g_ascii_strcasecmp (node->name, "itunes:summary") == 0) {
140 description = node->data;
141 } else if (g_ascii_strcasecmp (node->name, "author") == 0
142 || g_ascii_strcasecmp (node->name, "itunes:author") == 0) {
143 author = node->data;
144 } else if (g_ascii_strcasecmp (node->name, "itunes:duration") == 0) {
145 duration = node->data;
146 } else if (g_ascii_strcasecmp (node->name, "length") == 0) {
147 filesize = node->data;
148 } else if (g_ascii_strcasecmp (node->name, "media:content") == 0) {
149 const char *tmp;
150
151 tmp = xml_parser_get_property (node, "medium");
152 if (tmp != NULL && g_str_equal (tmp, "image")) {
153 img = xml_parser_get_property (node, "url");
154 continue;
155 }
156
157 tmp = xml_parser_get_property (node, "type");
158 if (tmp != NULL &&
159 g_str_has_prefix (tmp, "audio/") == FALSE) {
160 if (g_str_has_prefix (tmp, "image/"))
161 img = xml_parser_get_property (node, "url");
162 continue;
163 }
164 content_type = tmp;
165
166 tmp = xml_parser_get_property (node, "url");
167 if (tmp != NULL)
168 uri = tmp;
169 else
170 continue;
171
172 tmp = xml_parser_get_property (node, "fileSize");
173 if (tmp != NULL)
174 filesize = tmp;
175
176 tmp = xml_parser_get_property (node, "duration");
177 if (tmp != NULL)
178 duration = tmp;
179 } else if (g_ascii_strcasecmp (node->name, "enclosure") == 0) {
180 const char *tmp;
181
182 tmp = xml_parser_get_property (node, "url");
183 if (tmp != NULL && is_image (tmp) == FALSE)
184 uri = tmp;
185 else
186 continue;
187 tmp = xml_parser_get_property (node, "length");
188 if (tmp != NULL)
189 filesize = tmp;
190 } else if (g_ascii_strcasecmp (node->name, "link") == 0 &&
191 totem_pl_parser_is_videosite (node->data, FALSE) != FALSE) {
192 uri = node->data;
193 }
194 }
195
196 if (id != NULL &&
197 uri == NULL &&
198 totem_pl_parser_is_videosite (id, FALSE) != FALSE)
199 uri = id;
200
201 if (uri != NULL) {
202 totem_pl_parser_add_uri (parser,
203 TOTEM_PL_PARSER_FIELD_URI, uri,
204 TOTEM_PL_PARSER_FIELD_ID, id,
205 TOTEM_PL_PARSER_FIELD_TITLE, title,
206 TOTEM_PL_PARSER_FIELD_PUB_DATE, pub_date,
207 TOTEM_PL_PARSER_FIELD_DESCRIPTION, description,
208 TOTEM_PL_PARSER_FIELD_AUTHOR, author,
209 TOTEM_PL_PARSER_FIELD_DURATION, duration,
210 TOTEM_PL_PARSER_FIELD_FILESIZE, filesize,
211 TOTEM_PL_PARSER_FIELD_CONTENT_TYPE, content_type,
212 TOTEM_PL_PARSER_FIELD_IMAGE_URI, img,
213 NULL);
214 }
215
216 return TOTEM_PL_PARSER_RESULT_SUCCESS;
217 }
218
219 static TotemPlParserResult
parse_rss_items(TotemPlParser * parser,const char * uri,xml_node_t * parent)220 parse_rss_items (TotemPlParser *parser, const char *uri, xml_node_t *parent)
221 {
222 const char *title, *language, *description, *author;
223 const char *contact, *img, *pub_date, *copyright;
224 xml_node_t *node;
225
226 title = language = description = author = NULL;
227 contact = img = pub_date = copyright = NULL;
228
229 /* We need to parse for the feed metadata first, then for the items */
230 for (node = parent->child; node != NULL; node = node->next) {
231 if (node->name == NULL)
232 continue;
233
234 if (g_ascii_strcasecmp (node->name, "title") == 0) {
235 title = node->data;
236 } else if (g_ascii_strcasecmp (node->name, "language") == 0) {
237 language = node->data;
238 } else if (g_ascii_strcasecmp (node->name, "description") == 0
239 || g_ascii_strcasecmp (node->name, "itunes:subtitle") == 0) {
240 description = node->data;
241 } else if (g_ascii_strcasecmp (node->name, "author") == 0
242 || g_ascii_strcasecmp (node->name, "itunes:author") == 0
243 || (g_ascii_strcasecmp (node->name, "generator") == 0 && author == NULL)) {
244 author = node->data;
245 } else if (g_ascii_strcasecmp (node->name, "webMaster") == 0) {
246 contact = node->data;
247 } else if (g_ascii_strcasecmp (node->name, "image") == 0) {
248 img = node->data;
249 } else if (g_ascii_strcasecmp (node->name, "itunes:image") == 0) {
250 const char *href;
251
252 href = xml_parser_get_property (node, "href");
253 if (href != NULL)
254 img = href;
255 } else if (g_ascii_strcasecmp (node->name, "lastBuildDate") == 0
256 || g_ascii_strcasecmp (node->name, "pubDate") == 0) {
257 pub_date = node->data;
258 } else if (g_ascii_strcasecmp (node->name, "copyright") == 0) {
259 copyright = node->data;
260 }
261 }
262
263 /* Send the info we already have about the feed */
264 totem_pl_parser_add_uri (parser,
265 TOTEM_PL_PARSER_FIELD_IS_PLAYLIST, TRUE,
266 TOTEM_PL_PARSER_FIELD_URI, uri,
267 TOTEM_PL_PARSER_FIELD_TITLE, title,
268 TOTEM_PL_PARSER_FIELD_LANGUAGE, language,
269 TOTEM_PL_PARSER_FIELD_DESCRIPTION, description,
270 TOTEM_PL_PARSER_FIELD_AUTHOR, author,
271 TOTEM_PL_PARSER_FIELD_PUB_DATE, pub_date,
272 TOTEM_PL_PARSER_FIELD_COPYRIGHT, copyright,
273 TOTEM_PL_PARSER_FIELD_IMAGE_URI, img,
274 TOTEM_PL_PARSER_FIELD_CONTACT, contact,
275 NULL);
276
277 for (node = parent->child; node != NULL; node = node->next) {
278 if (node->name == NULL)
279 continue;
280
281 if (g_ascii_strcasecmp (node->name, "item") == 0)
282 parse_rss_item (parser, node);
283 }
284
285 totem_pl_parser_playlist_end (parser, uri);
286
287 return TOTEM_PL_PARSER_RESULT_SUCCESS;
288 }
289
290 TotemPlParserResult
totem_pl_parser_add_rss(TotemPlParser * parser,GFile * file,GFile * base_file,TotemPlParseData * parse_data,gpointer data)291 totem_pl_parser_add_rss (TotemPlParser *parser,
292 GFile *file,
293 GFile *base_file,
294 TotemPlParseData *parse_data,
295 gpointer data)
296 {
297 xml_node_t* doc, *channel;
298 char *contents;
299 gsize size;
300
301 if (g_file_load_contents (file, NULL, &contents, &size, NULL, NULL) == FALSE)
302 return TOTEM_PL_PARSER_RESULT_ERROR;
303
304 doc = totem_pl_parser_parse_xml_relaxed (contents, size);
305 if (doc == NULL) {
306 g_free (contents);
307 return TOTEM_PL_PARSER_RESULT_ERROR;
308 }
309
310 /* If the document has no name */
311 if (doc->name == NULL
312 || (g_ascii_strcasecmp (doc->name , "rss") != 0
313 && g_ascii_strcasecmp (doc->name , "rss\n") != 0)) {
314 g_free (contents);
315 xml_parser_free_tree (doc);
316 return TOTEM_PL_PARSER_RESULT_ERROR;
317 }
318
319 for (channel = doc->child; channel != NULL; channel = channel->next) {
320 if (g_ascii_strcasecmp (channel->name, "channel") == 0) {
321 char *uri;
322
323 uri = g_file_get_uri (file);
324 parse_rss_items (parser, uri, channel);
325 g_free (uri);
326
327 /* One channel per file */
328 break;
329 }
330 }
331
332 g_free (contents);
333 xml_parser_free_tree (doc);
334
335 return TOTEM_PL_PARSER_RESULT_SUCCESS;
336 }
337
338 /* http://www.apple.com/itunes/store/podcaststechspecs.html */
339 TotemPlParserResult
totem_pl_parser_add_itpc(TotemPlParser * parser,GFile * file,GFile * base_file,TotemPlParseData * parse_data,gpointer data)340 totem_pl_parser_add_itpc (TotemPlParser *parser,
341 GFile *file,
342 GFile *base_file,
343 TotemPlParseData *parse_data,
344 gpointer data)
345 {
346 TotemPlParserResult ret;
347 char *uri, *new_uri, *uri_scheme;
348 GFile *new_file;
349
350 uri = g_file_get_uri (file);
351 uri_scheme = g_file_get_uri_scheme (file);
352 new_uri = g_strdup_printf ("http%s", uri + strlen (uri_scheme));
353 g_free (uri);
354 g_free (uri_scheme);
355
356 new_file = g_file_new_for_uri (new_uri);
357 g_free (new_uri);
358
359 ret = totem_pl_parser_add_rss (parser, new_file, base_file, parse_data, data);
360
361 g_object_unref (new_file);
362
363 return ret;
364 }
365
366 TotemPlParserResult
totem_pl_parser_add_zune(TotemPlParser * parser,GFile * file,GFile * base_file,TotemPlParseData * parse_data,gpointer data)367 totem_pl_parser_add_zune (TotemPlParser *parser,
368 GFile *file,
369 GFile *base_file,
370 TotemPlParseData *parse_data,
371 gpointer data)
372 {
373 TotemPlParserResult ret;
374 char *uri, *new_uri;
375 GFile *new_file;
376
377 uri = g_file_get_uri (file);
378 if (g_str_has_prefix (uri, "zune://subscribe/?") == FALSE) {
379 g_free (uri);
380 return TOTEM_PL_PARSER_RESULT_UNHANDLED;
381 }
382
383 new_uri = strchr (uri + strlen ("zune://subscribe/?"), '=');
384 if (new_uri == NULL) {
385 g_free (uri);
386 return TOTEM_PL_PARSER_RESULT_UNHANDLED;
387 }
388 /* Skip over the '=' */
389 new_uri++;
390
391 new_file = g_file_new_for_uri (new_uri);
392 g_free (uri);
393
394 ret = totem_pl_parser_add_rss (parser, new_file, base_file, parse_data, data);
395
396 g_object_unref (new_file);
397
398 return ret;
399 }
400
401 /* Atom docs:
402 * http://www.atomenabled.org/developers/syndication/atom-format-spec.php#rfc.section.4.1
403 * http://tools.ietf.org/html/rfc4287
404 * http://tools.ietf.org/html/rfc4946 */
405 static TotemPlParserResult
parse_atom_entry(TotemPlParser * parser,xml_node_t * parent)406 parse_atom_entry (TotemPlParser *parser, xml_node_t *parent)
407 {
408 const char *title, *author, *uri, *filesize;
409 const char *copyright, *pub_date, *description, *img;
410 xml_node_t *node;
411
412 title = author = uri = filesize = NULL;
413 copyright = pub_date = description = img = NULL;
414
415 for (node = parent->child; node != NULL; node = node->next) {
416 if (node->name == NULL)
417 continue;
418
419 if (g_ascii_strcasecmp (node->name, "title") == 0) {
420 title = node->data;
421 } else if (g_ascii_strcasecmp (node->name, "author") == 0) {
422 xml_node_t *child;
423
424 for (child = node->child; child != NULL; child = child->next) {
425 if (child->name == NULL)
426 continue;
427
428 if (g_ascii_strcasecmp (child->name, "name") == 0) {
429 author = child->data;
430 break;
431 }
432 }
433 } else if (g_ascii_strcasecmp (node->name, "link") == 0) {
434 const char *rel;
435
436 //FIXME how do we choose the default enclosure type?
437 rel = xml_parser_get_property (node, "rel");
438 if (g_ascii_strcasecmp (rel, "enclosure") == 0) {
439 const char *href;
440
441 //FIXME what's the difference between url and href there?
442 href = xml_parser_get_property (node, "href");
443 if (href == NULL)
444 continue;
445 uri = href;
446 filesize = xml_parser_get_property (node, "length");
447 } else if (g_ascii_strcasecmp (rel, "license") == 0) {
448 const char *href;
449
450 href = xml_parser_get_property (node, "href");
451 if (href == NULL)
452 continue;
453 /* This isn't really a copyright, but what the hey */
454 copyright = href;
455 } else if (g_ascii_strcasecmp (rel, "alternate") == 0) {
456 const char *href;
457
458 href = xml_parser_get_property (node, "href");
459 if (href == NULL)
460 continue;
461 if (!totem_pl_parser_is_videosite (href, FALSE))
462 continue;
463 uri = href;
464 }
465 } else if (g_ascii_strcasecmp (node->name, "updated") == 0
466 || (g_ascii_strcasecmp (node->name, "modified") == 0 && pub_date == NULL)) {
467 pub_date = node->data;
468 } else if (g_ascii_strcasecmp (node->name, "summary") == 0
469 || (g_ascii_strcasecmp (node->name, "content") == 0 && description == NULL)) {
470 const char *type;
471
472 type = xml_parser_get_property (node, "content");
473 if (type != NULL && g_ascii_strcasecmp (type, "text/plain") == 0)
474 description = node->data;
475 } else if (g_ascii_strcasecmp (node->name, "media:group") == 0) {
476 xml_node_t *child;
477
478 for (child = node->child; child != NULL; child = child->next) {
479 if (child->name == NULL)
480 continue;
481
482 if (g_ascii_strcasecmp (child->name, "media:title") == 0 &&
483 title == NULL) {
484 title = child->data;
485 } else if (g_ascii_strcasecmp (child->name, "media:description") == 0 &&
486 description == NULL) {
487 description = child->data;
488 } else if (g_ascii_strcasecmp (child->name, "media:content") == 0 &&
489 uri == NULL) {
490 const char *prop;
491
492 prop = xml_parser_get_property (child, "url");
493 if (prop == NULL)
494 continue;
495 if (!totem_pl_parser_is_videosite (prop, FALSE))
496 continue;
497 uri = prop;
498 } else if (g_ascii_strcasecmp (child->name, "media:thumbnail") == 0) {
499 img = xml_parser_get_property (child, "url");
500 }
501 }
502 }
503 //FIXME handle category
504 }
505
506 if (uri != NULL) {
507 totem_pl_parser_add_uri (parser,
508 TOTEM_PL_PARSER_FIELD_TITLE, title,
509 TOTEM_PL_PARSER_FIELD_AUTHOR, author,
510 TOTEM_PL_PARSER_FIELD_URI, uri,
511 TOTEM_PL_PARSER_FIELD_FILESIZE, filesize,
512 TOTEM_PL_PARSER_FIELD_COPYRIGHT, copyright,
513 TOTEM_PL_PARSER_FIELD_PUB_DATE, pub_date,
514 TOTEM_PL_PARSER_FIELD_DESCRIPTION, description,
515 TOTEM_PL_PARSER_FIELD_IMAGE_URI, img,
516 NULL);
517 }
518
519 return TOTEM_PL_PARSER_RESULT_SUCCESS;
520 }
521
522 static TotemPlParserResult
parse_atom_entries(TotemPlParser * parser,const char * uri,xml_node_t * parent)523 parse_atom_entries (TotemPlParser *parser, const char *uri, xml_node_t *parent)
524 {
525 const char *title, *pub_date, *description;
526 const char *author, *img;
527 xml_node_t *node;
528 gboolean started = FALSE;
529
530 title = pub_date = description = NULL;
531 author = img = NULL;
532
533 for (node = parent->child; node != NULL; node = node->next) {
534 if (node->name == NULL)
535 continue;
536
537 if (g_ascii_strcasecmp (node->name, "title") == 0) {
538 title = node->data;
539 } else if (g_ascii_strcasecmp (node->name, "tagline") == 0) {
540 description = node->data;
541 } else if (g_ascii_strcasecmp (node->name, "modified") == 0
542 || g_ascii_strcasecmp (node->name, "updated") == 0) {
543 pub_date = node->data;
544 } else if (g_ascii_strcasecmp (node->name, "author") == 0
545 || (g_ascii_strcasecmp (node->name, "generator") == 0 && author == NULL)) {
546 author = node->data;
547 } else if ((g_ascii_strcasecmp (node->name, "icon") == 0 && img == NULL)
548 || g_ascii_strcasecmp (node->name, "logo") == 0) {
549 img = node->data;
550 }
551
552 if (g_ascii_strcasecmp (node->name, "entry") == 0) {
553 if (started == FALSE) {
554 /* Send the info we already have about the feed */
555 totem_pl_parser_add_uri (parser,
556 TOTEM_PL_PARSER_FIELD_IS_PLAYLIST, TRUE,
557 TOTEM_PL_PARSER_FIELD_URI, uri,
558 TOTEM_PL_PARSER_FIELD_TITLE, title,
559 TOTEM_PL_PARSER_FIELD_DESCRIPTION, description,
560 TOTEM_PL_PARSER_FIELD_AUTHOR, author,
561 TOTEM_PL_PARSER_FIELD_PUB_DATE, pub_date,
562 TOTEM_PL_PARSER_FIELD_IMAGE_URI, img,
563 NULL);
564 started = TRUE;
565 }
566
567 parse_atom_entry (parser, node);
568 }
569 }
570
571 totem_pl_parser_playlist_end (parser, uri);
572
573 return TOTEM_PL_PARSER_RESULT_SUCCESS;
574 }
575
576 TotemPlParserResult
totem_pl_parser_add_atom(TotemPlParser * parser,GFile * file,GFile * base_file,TotemPlParseData * parse_data,gpointer data)577 totem_pl_parser_add_atom (TotemPlParser *parser,
578 GFile *file,
579 GFile *base_file,
580 TotemPlParseData *parse_data,
581 gpointer data)
582 {
583 xml_node_t* doc;
584 char *contents, *uri;
585 gsize size;
586
587 if (g_file_load_contents (file, NULL, &contents, &size, NULL, NULL) == FALSE)
588 return TOTEM_PL_PARSER_RESULT_ERROR;
589
590 doc = totem_pl_parser_parse_xml_relaxed (contents, size);
591 if (doc == NULL) {
592 g_free (contents);
593 return TOTEM_PL_PARSER_RESULT_ERROR;
594 }
595
596 /* If the document has no name */
597 if (doc->name == NULL
598 || g_ascii_strcasecmp (doc->name , "feed") != 0) {
599 g_free (contents);
600 xml_parser_free_tree (doc);
601 return TOTEM_PL_PARSER_RESULT_ERROR;
602 }
603
604 uri = g_file_get_uri (file);
605 parse_atom_entries (parser, uri, doc);
606 g_free (uri);
607
608 g_free (contents);
609 xml_parser_free_tree (doc);
610
611 return TOTEM_PL_PARSER_RESULT_SUCCESS;
612 }
613
614 TotemPlParserResult
totem_pl_parser_add_xml_feed(TotemPlParser * parser,GFile * file,GFile * base_file,TotemPlParseData * parse_data,gpointer data)615 totem_pl_parser_add_xml_feed (TotemPlParser *parser,
616 GFile *file,
617 GFile *base_file,
618 TotemPlParseData *parse_data,
619 gpointer data)
620 {
621 guint len;
622
623 if (data == NULL)
624 return TOTEM_PL_PARSER_RESULT_UNHANDLED;
625
626 len = strlen (data);
627
628 if (totem_pl_parser_is_rss (data, len) != FALSE)
629 return totem_pl_parser_add_rss (parser, file, base_file, parse_data, data);
630 if (totem_pl_parser_is_atom (data, len) != FALSE)
631 return totem_pl_parser_add_atom (parser, file, base_file, parse_data, data);
632 if (totem_pl_parser_is_opml (data, len) != FALSE)
633 return totem_pl_parser_add_opml (parser, file, base_file, parse_data, data);
634
635 return TOTEM_PL_PARSER_RESULT_UNHANDLED;
636 }
637
638 static char *
totem_pl_parser_parse_json(char * data,gsize len,gboolean debug)639 totem_pl_parser_parse_json (char *data, gsize len, gboolean debug)
640 {
641 char *s, *end;
642
643 s = g_strstr_len (data, len, "feedUrl\":\"");
644 if (s == NULL)
645 return NULL;
646 s += strlen ("feedUrl\":\"");
647 if (*s == '\0')
648 return NULL;
649 end = g_strstr_len (s, len - (s - data), "\"");
650 if (end == NULL)
651 return NULL;
652 return g_strndup (s, end - s);
653 }
654
655 static char *
get_itms_id(GFile * file)656 get_itms_id (GFile *file)
657 {
658 char *uri, *start, *end, *id;
659
660 uri = g_file_get_uri (file);
661 if (!uri)
662 return NULL;
663
664 start = strstr (uri, "/id");
665 if (!start) {
666 g_free (uri);
667 return NULL;
668 }
669
670 end = strchr (start, '?');
671 if (!end)
672 end = strchr (start, '#');
673 if (!end || end - start <= 3) {
674 g_free (uri);
675 return NULL;
676 }
677
678 id = g_strndup (start + 3, end - start - 3);
679 g_free (uri);
680
681 return id;
682 }
683
684 TotemPlParserResult
totem_pl_parser_add_itms(TotemPlParser * parser,GFile * file,GFile * base_file,TotemPlParseData * parse_data,gpointer data)685 totem_pl_parser_add_itms (TotemPlParser *parser,
686 GFile *file,
687 GFile *base_file,
688 TotemPlParseData *parse_data,
689 gpointer data)
690 {
691 GFile *json_file, *feed_file;
692 TotemPlParserResult ret;
693 char *contents, *id, *json_uri, *feed_url;
694 gsize len;
695
696 id = get_itms_id (file);
697 if (id == NULL) {
698 DEBUG(file, g_print ("Could not get ITMS ID for URL '%s'\n", uri));
699 return TOTEM_PL_PARSER_RESULT_ERROR;
700 }
701
702 DEBUG(file, g_print ("Got ID '%s' for URL '%s'", id, uri));
703
704 json_uri = g_strdup_printf ("https://itunes.apple.com/lookup?id=%s&entity=podcast", id);
705 g_free (id);
706 json_file = g_file_new_for_uri (json_uri);
707 g_free (json_uri);
708
709 if (g_file_load_contents (json_file, NULL, &contents, &len, NULL, NULL) == FALSE) {
710 DEBUG(json_file, g_print ("Failed to load URL '%s'\n", uri));
711 g_object_unref (json_file);
712 return TOTEM_PL_PARSER_RESULT_ERROR;
713 }
714
715 feed_url = totem_pl_parser_parse_json (contents, len, totem_pl_parser_is_debugging_enabled (parser));
716 g_free (contents);
717 if (feed_url == NULL) {
718 DEBUG(json_file, g_print ("Failed to parse JSON file at '%s'\n", uri));
719 g_object_unref (json_file);
720 return TOTEM_PL_PARSER_RESULT_ERROR;
721 }
722
723 g_object_unref (json_file);
724 feed_file = g_file_new_for_uri (feed_url);
725 g_free (feed_url);
726
727 DEBUG(feed_file, g_print ("Found feed URI: %s\n", uri));
728
729 ret = totem_pl_parser_add_rss (parser, feed_file, NULL, parse_data, NULL);
730 g_object_unref (feed_file);
731
732 return ret;
733 }
734
735 gboolean
totem_pl_parser_is_itms_feed(GFile * file)736 totem_pl_parser_is_itms_feed (GFile *file)
737 {
738 char *uri;
739
740 g_return_val_if_fail (file != NULL, FALSE);
741
742 uri = g_file_get_uri (file);
743
744 if (g_file_has_uri_scheme (file, "itms") != FALSE ||
745 g_file_has_uri_scheme (file, "itmss") != FALSE ||
746 (g_file_has_uri_scheme (file, "http") != FALSE &&
747 strstr (uri, ".apple.com/") != FALSE)) {
748 if (strstr (uri, "/podcast/") != NULL ||
749 strstr (uri, "viewPodcast") != NULL) {
750 g_free (uri);
751 return TRUE;
752 }
753 }
754
755 g_free (uri);
756
757 return FALSE;
758 }
759
760 static TotemPlParserResult
parse_opml_outline(TotemPlParser * parser,xml_node_t * parent)761 parse_opml_outline (TotemPlParser *parser, xml_node_t *parent)
762 {
763 xml_node_t* node;
764
765 for (node = parent->child; node != NULL; node = node->next) {
766 const char *title, *uri;
767
768 if (node->name == NULL || g_ascii_strcasecmp (node->name, "outline") != 0)
769 continue;
770
771 uri = xml_parser_get_property (node, "xmlUrl");
772 title = xml_parser_get_property (node, "text");
773
774 if (uri == NULL)
775 continue;
776
777 totem_pl_parser_add_uri (parser,
778 TOTEM_PL_PARSER_FIELD_TITLE, title,
779 TOTEM_PL_PARSER_FIELD_URI, uri,
780 NULL);
781 }
782
783 return TOTEM_PL_PARSER_RESULT_SUCCESS;
784 }
785
786 static TotemPlParserResult
parse_opml_head_body(TotemPlParser * parser,const char * uri,xml_node_t * parent)787 parse_opml_head_body (TotemPlParser *parser, const char *uri, xml_node_t *parent)
788 {
789 xml_node_t* node;
790 gboolean started;
791
792 started = FALSE;
793
794 for (node = parent->child; node != NULL; node = node->next) {
795 if (node->name == NULL)
796 continue;
797
798 if (g_ascii_strcasecmp (node->name, "body") == 0) {
799 if (started == FALSE) {
800 /* Send the info we already have about the feed */
801 totem_pl_parser_add_uri (parser,
802 TOTEM_PL_PARSER_FIELD_IS_PLAYLIST, TRUE,
803 TOTEM_PL_PARSER_FIELD_URI, uri,
804 NULL);
805 started = TRUE;
806 }
807
808 parse_opml_outline (parser, node);
809 }
810 }
811
812 return TOTEM_PL_PARSER_RESULT_SUCCESS;
813 }
814
815 TotemPlParserResult
totem_pl_parser_add_opml(TotemPlParser * parser,GFile * file,GFile * base_file,TotemPlParseData * parse_data,gpointer data)816 totem_pl_parser_add_opml (TotemPlParser *parser,
817 GFile *file,
818 GFile *base_file,
819 TotemPlParseData *parse_data,
820 gpointer data)
821 {
822 xml_node_t* doc;
823 char *contents, *uri;
824 gsize size;
825
826 if (g_file_load_contents (file, NULL, &contents, &size, NULL, NULL) == FALSE)
827 return TOTEM_PL_PARSER_RESULT_ERROR;
828
829 doc = totem_pl_parser_parse_xml_relaxed (contents, size);
830 if (doc == NULL) {
831 g_free (contents);
832 return TOTEM_PL_PARSER_RESULT_ERROR;
833 }
834
835 /* If the document has no name */
836 if (doc->name == NULL
837 || g_ascii_strcasecmp (doc->name , "opml") != 0) {
838 g_free (contents);
839 xml_parser_free_tree (doc);
840 return TOTEM_PL_PARSER_RESULT_ERROR;
841 }
842
843 uri = g_file_get_uri (file);
844 parse_opml_head_body (parser, uri, doc);
845 g_free (uri);
846
847 g_free (contents);
848 xml_parser_free_tree (doc);
849
850 return TOTEM_PL_PARSER_RESULT_SUCCESS;
851 }
852
853 #endif /* !TOTEM_PL_PARSER_MINI */
854
855