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