1 /*
2    Copyright (C) 2002, 2003, 2004, 2005, 2006 Bastien Nocera
3    Copyright (C) 2003, 2004 Colin Walters <walters@rhythmbox.org>
4 
5    The Gnome Library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Library General Public License as
7    published by the Free Software Foundation; either version 2 of the
8    License, or (at your option) any later version.
9 
10    The Gnome Library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Library General Public License for more details.
14 
15    You should have received a copy of the GNU Library General Public
16    License along with the Gnome Library; see the file COPYING.LIB.  If not,
17    write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18    Boston, MA 02110-1301  USA.
19 
20    Authors: Bastien Nocera <hadess@hadess.net>
21  */
22 
23 /**
24  * SECTION:totem-pl-parser
25  * @short_description: playlist parser
26  * @stability: Stable
27  * @include: totem-pl-parser.h
28  *
29  * #TotemPlParser is a general-purpose playlist parser and writer, with
30  * support for several different types of playlist. Note that totem-pl-parser requires a main loop
31  * to operate properly (e.g. for the #TotemPlParser::entry-parsed signal to be emitted).
32  *
33  * <example>
34  *  <title>Reading a Playlist</title>
35  *  <programlisting>
36  * TotemPlParser *pl = totem_pl_parser_new ();
37  * g_object_set (pl, "recurse", FALSE, "disable-unsafe", TRUE, NULL);
38  * g_signal_connect (G_OBJECT (pl), "playlist-started", G_CALLBACK (playlist_started), NULL);
39  * g_signal_connect (G_OBJECT (pl), "entry-parsed", G_CALLBACK (entry_parsed), NULL);
40  *
41  * if (totem_pl_parser_parse (pl, "http://example.com/playlist.pls", FALSE) != TOTEM_PL_PARSER_RESULT_SUCCESS)
42  * 	g_error ("Playlist parsing failed.");
43  *
44  * g_object_unref (pl);
45  *  </programlisting>
46  * </example>
47  *
48  * <example>
49  *  <title>Reading a Playlist Asynchronously</title>
50  *  <programlisting>
51  * TotemPlParser *pl = totem_pl_parser_new ();
52  * g_object_set (pl, "recurse", FALSE, "disable-unsafe", TRUE, NULL);
53  * g_signal_connect (G_OBJECT (pl), "playlist-started", G_CALLBACK (playlist_started), NULL);
54  * g_signal_connect (G_OBJECT (pl), "entry-parsed", G_CALLBACK (entry_parsed), NULL);
55  *
56  * totem_pl_parser_parse_async (pl, "http://example.com/playlist.pls", FALSE, NULL, parse_cb, NULL);
57  * g_object_unref (pl);
58  *
59  * static void
60  * parse_cb (TotemPlParser *parser, GAsyncResult *result, gpointer user_data)
61  * {
62  *	GError *error = NULL;
63  * 	if (totem_pl_parser_parse_finish (parser, result, &error) != TOTEM_PL_PARSER_RESULT_SUCCESS) {
64  * 		g_error ("Playlist parsing failed: %s", error->message);
65  * 		g_error_free (error);
66  * 	}
67  * }
68  *  </programlisting>
69  * </example>
70  *
71  * <example>
72  *  <title>Getting Metadata from Entries</title>
73  *  <programlisting>
74  * static void
75  * entry_parsed (TotemPlParser *parser, const gchar *uri, GHashTable *metadata, gpointer user_data)
76  * {
77  * 	gchar *title = g_hash_table_lookup (metadata, TOTEM_PL_PARSER_FIELD_TITLE);
78  * 	if (title != NULL)
79  * 		g_message ("Entry title: %s", title);
80  * 	else
81  * 		g_message ("Entry (URI: %s) has no title.", uri);
82  * }
83  *  </programlisting>
84  * </example>
85  *
86  *
87  * <example>
88  *  <title>Writing a Playlist</title>
89  *  <programlisting>
90  * {
91  * 	TotemPlParser *pl;
92  * 	TotemPlPlaylist *playlist;
93  * 	TotemPlPlaylistIter iter;
94  * 	GFile *file;
95  *
96  * 	pl = totem_pl_parser_new ();
97  * 	playlist = totem_pl_playlist_new ();
98  * 	file = g_file_new_for_path ("/tmp/playlist.pls");
99  *
100  * 	totem_pl_playlist_append (playlist, &iter);
101  * 	totem_pl_playlist_set (playlist, &iter,
102  * 			       TOTEM_PL_PARSER_FIELD_URI, "file:///1.ogg",
103  * 			       TOTEM_PL_PARSER_FIELD_TITLE, "1.ogg",
104  * 			       NULL);
105  *
106  * 	totem_pl_playlist_append (playlist, &iter);
107  * 	totem_pl_playlist_set (playlist, &iter,
108  * 			       TOTEM_PL_PARSER_FIELD_URI, "file:///2.ogg",
109  * 			       NULL);
110  *
111  * 	if (totem_pl_parser_save (pl, playlist, file, "Title",
112  * 				  TOTEM_PL_PARSER_PLS, NULL) != TRUE) {
113  * 		g_error ("Playlist writing failed.");
114  * 	}
115  *
116  * 	g_object_unref (playlist);
117  * 	g_object_unref (pl);
118  * 	g_object_unref (file);
119  * }
120  *  </programlisting>
121  * </example>
122  **/
123 
124 #include "config.h"
125 
126 #include <string.h>
127 #include <fnmatch.h>
128 #include <glib.h>
129 #include <glib/gstdio.h>
130 #include <glib/gi18n-lib.h>
131 #include <gio/gio.h>
132 
133 #ifndef TOTEM_PL_PARSER_MINI
134 #include <gobject/gvaluecollector.h>
135 
136 #include "totem-pl-parser.h"
137 #include "totemplparser-marshal.h"
138 #include "totem-disc.h"
139 #endif /* !TOTEM_PL_PARSER_MINI */
140 
141 #include "totem-pl-parser-mini.h"
142 #include "totem-pl-parser-decode-date.h"
143 #include "totem-pl-parser-wm.h"
144 #include "totem-pl-parser-qt.h"
145 #include "totem-pl-parser-pls.h"
146 #include "totem-pl-parser-xspf.h"
147 #include "totem-pl-parser-media.h"
148 #include "totem-pl-parser-smil.h"
149 #include "totem-pl-parser-pla.h"
150 #include "totem-pl-parser-podcast.h"
151 #include "totem-pl-parser-lines.h"
152 #include "totem-pl-parser-misc.h"
153 #include "totem-pl-parser-private.h"
154 #include "totem-pl-parser-videosite.h"
155 #include "totem-pl-parser-amz.h"
156 
157 #define READ_CHUNK_SIZE 8192
158 #define RECURSE_LEVEL_MAX 4
159 
160 #define D(x) if (debug) x
161 
162 typedef const char * (*PlaylistIdenCallback) (const char *data, gsize len);
163 
164 #ifndef TOTEM_PL_PARSER_MINI
165 typedef TotemPlParserResult (*PlaylistCallback) (TotemPlParser *parser, GFile *uri, GFile *base_file, TotemPlParseData *parse_data, gpointer data);
166 #endif
167 
168 typedef struct {
169 	const char *mimetype;
170 #ifndef TOTEM_PL_PARSER_MINI
171 	PlaylistCallback func;
172 #endif
173 	PlaylistIdenCallback iden;
174 #ifndef TOTEM_PL_PARSER_MINI
175 	guint unsafe : 1;
176 #endif
177 } PlaylistTypes;
178 
179 #ifndef TOTEM_PL_PARSER_MINI
180 #define PLAYLIST_TYPE(mime,cb,identcb,unsafe) { mime, cb, identcb, unsafe }
181 #define PLAYLIST_TYPE2(mime,cb,identcb) { mime, cb, identcb }
182 #define PLAYLIST_TYPE3(mime) { mime, NULL, NULL, FALSE }
183 #else
184 #define PLAYLIST_TYPE(mime,cb,identcb,unsafe) { mime }
185 #define PLAYLIST_TYPE2(mime,cb,identcb) { mime, identcb }
186 #define PLAYLIST_TYPE3(mime) { mime }
187 #endif
188 
189 /* These ones need a special treatment, mostly parser formats */
190 static PlaylistTypes special_types[] = {
191 	PLAYLIST_TYPE ("audio/x-mpegurl", totem_pl_parser_add_m3u, NULL, FALSE),
192 	PLAYLIST_TYPE ("video/vnd.mpegurl", totem_pl_parser_add_m4u, NULL, FALSE),
193 	PLAYLIST_TYPE ("audio/playlist", totem_pl_parser_add_m3u, NULL, FALSE),
194 	PLAYLIST_TYPE ("audio/x-scpls", totem_pl_parser_add_pls, NULL, FALSE),
195 	PLAYLIST_TYPE ("application/x-smil", totem_pl_parser_add_smil, NULL, FALSE),
196 	PLAYLIST_TYPE ("application/smil", totem_pl_parser_add_smil, NULL, FALSE),
197 	PLAYLIST_TYPE ("application/smil+xml", totem_pl_parser_add_smil, NULL, FALSE),
198 	PLAYLIST_TYPE ("application/vnd.ms-wpl", totem_pl_parser_add_smil, NULL, FALSE),
199 	PLAYLIST_TYPE ("video/x-ms-wvx", totem_pl_parser_add_asx, NULL, FALSE),
200 	PLAYLIST_TYPE ("audio/x-ms-wax", totem_pl_parser_add_asx, NULL, FALSE),
201 	PLAYLIST_TYPE ("application/xspf+xml", totem_pl_parser_add_xspf, NULL, FALSE),
202 	PLAYLIST_TYPE ("text/uri-list", totem_pl_parser_add_ra, totem_pl_parser_is_uri_list, FALSE),
203 	PLAYLIST_TYPE ("text/x-google-video-pointer", totem_pl_parser_add_gvp, NULL, FALSE),
204 	PLAYLIST_TYPE ("text/google-video-pointer", totem_pl_parser_add_gvp, NULL, FALSE),
205 	PLAYLIST_TYPE ("audio/x-iriver-pla", totem_pl_parser_add_pla, NULL, FALSE),
206 	PLAYLIST_TYPE ("application/atom+xml", totem_pl_parser_add_atom, NULL, FALSE),
207 	PLAYLIST_TYPE ("application/rss+xml", totem_pl_parser_add_rss, totem_pl_parser_is_rss, FALSE),
208 	PLAYLIST_TYPE ("text/x-opml+xml", totem_pl_parser_add_opml, NULL, FALSE),
209 	PLAYLIST_TYPE ("audio/x-amzxml", totem_pl_parser_add_amz, NULL, FALSE),
210 #ifndef TOTEM_PL_PARSER_MINI
211 	PLAYLIST_TYPE ("application/x-desktop", totem_pl_parser_add_desktop, NULL, TRUE),
212 	PLAYLIST_TYPE ("application/x-gnome-app-info", totem_pl_parser_add_desktop, NULL, TRUE),
213 	PLAYLIST_TYPE ("application/x-cd-image", totem_pl_parser_add_iso, NULL, TRUE),
214 	PLAYLIST_TYPE ("application/x-extension-img", totem_pl_parser_add_iso, NULL, TRUE),
215 	PLAYLIST_TYPE ("application/x-cue", totem_pl_parser_add_cue, NULL, TRUE),
216 	PLAYLIST_TYPE (DIR_MIME_TYPE, totem_pl_parser_add_directory, NULL, TRUE),
217 	PLAYLIST_TYPE (BLOCK_DEVICE_TYPE, totem_pl_parser_add_block, NULL, TRUE),
218 #endif
219 };
220 
221 /* These ones are "dual" types, might be a video, might be a parser
222  * Please keep the same _is_ functions together */
223 static PlaylistTypes dual_types[] = {
224 	PLAYLIST_TYPE2 ("audio/x-real-audio", totem_pl_parser_add_ra, totem_pl_parser_is_uri_list),
225 	PLAYLIST_TYPE2 ("audio/x-pn-realaudio", totem_pl_parser_add_ra, totem_pl_parser_is_uri_list),
226 	PLAYLIST_TYPE2 ("application/ram", totem_pl_parser_add_ra, totem_pl_parser_is_uri_list),
227 	PLAYLIST_TYPE2 ("application/vnd.rn-realmedia", totem_pl_parser_add_ra, totem_pl_parser_is_uri_list),
228 	PLAYLIST_TYPE2 ("audio/x-pn-realaudio-plugin", totem_pl_parser_add_ra, totem_pl_parser_is_uri_list),
229 	PLAYLIST_TYPE2 ("audio/vnd.rn-realaudio", totem_pl_parser_add_ra, totem_pl_parser_is_uri_list),
230 	PLAYLIST_TYPE2 ("audio/x-realaudio", totem_pl_parser_add_ra, totem_pl_parser_is_uri_list),
231 	PLAYLIST_TYPE2 ("text/plain", totem_pl_parser_add_ra, totem_pl_parser_is_uri_list),
232 	PLAYLIST_TYPE2 ("application/x-php", NULL, NULL),
233 	PLAYLIST_TYPE2 ("audio/x-ms-asx", totem_pl_parser_add_asx, totem_pl_parser_is_asx),
234 	PLAYLIST_TYPE2 ("video/x-ms-asf", totem_pl_parser_add_asf, totem_pl_parser_is_asf),
235 	PLAYLIST_TYPE2 ("application/vnd.ms-asf", totem_pl_parser_add_asf, totem_pl_parser_is_asf),
236 	PLAYLIST_TYPE2 ("video/x-ms-wmv", totem_pl_parser_add_asf, totem_pl_parser_is_asf),
237 	PLAYLIST_TYPE2 ("audio/x-ms-wma", totem_pl_parser_add_asf, totem_pl_parser_is_asf),
238 	PLAYLIST_TYPE2 ("video/quicktime", totem_pl_parser_add_quicktime, totem_pl_parser_is_quicktime),
239 	PLAYLIST_TYPE2 ("video/mp4", totem_pl_parser_add_quicktime, totem_pl_parser_is_quicktime),
240 	PLAYLIST_TYPE2 ("application/x-quicktime-media-link", totem_pl_parser_add_quicktime, totem_pl_parser_is_quicktime),
241 	PLAYLIST_TYPE2 ("application/x-quicktimeplayer", totem_pl_parser_add_quicktime, totem_pl_parser_is_quicktime),
242 	PLAYLIST_TYPE2 ("application/xml", totem_pl_parser_add_xml_feed, totem_pl_parser_is_xml_feed),
243 };
244 
245 static char *totem_pl_parser_mime_type_from_data (gconstpointer data, int len);
246 
247 #ifndef TOTEM_PL_PARSER_MINI
248 
249 static void totem_pl_parser_set_property (GObject *object,
250 					  guint prop_id,
251 					  const GValue *value,
252 					  GParamSpec *pspec);
253 static void totem_pl_parser_get_property (GObject *object,
254 					  guint prop_id,
255 					  GValue *value,
256 					  GParamSpec *pspec);
257 
258 struct TotemPlParserPrivate {
259 	GHashTable *ignore_schemes; /* key = char *, value = boolean */
260 	GHashTable *ignore_mimetypes; /*key = char *, value = boolean */
261 	GHashTable *ignore_globs; /*key = char *, value = boolean */
262 	GMutex ignore_mutex;
263 	GThread *main_thread; /* see CALL_ASYNC() in *-private.h */
264 
265 	guint recurse : 1;
266 	guint debug : 1;
267 	guint force : 1;
268 	guint disable_unsafe : 1;
269 };
270 
271 enum {
272 	PROP_NONE,
273 	PROP_RECURSE,
274 	PROP_DEBUG,
275 	PROP_FORCE,
276 	PROP_DISABLE_UNSAFE
277 };
278 
279 /* Signals */
280 enum {
281 	ENTRY_PARSED,
282 	PLAYLIST_STARTED,
283 	PLAYLIST_ENDED,
284 	LAST_SIGNAL
285 };
286 
287 static int totem_pl_parser_table_signals[LAST_SIGNAL];
288 static GParamSpecPool *totem_pl_parser_pspec_pool = NULL;
289 
290 static void totem_pl_parser_class_init (TotemPlParserClass *klass);
291 static void totem_pl_parser_base_class_finalize	(TotemPlParserClass *klass);
292 static void totem_pl_parser_init       (TotemPlParser *parser);
293 static void totem_pl_parser_finalize   (GObject *object);
294 
295 static gpointer totem_pl_parser_parent_class = NULL;
296 
297 #define TOTEM_PL_PARSER_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), TOTEM_TYPE_PL_PARSER, TotemPlParserPrivate))
298 
299 GType
totem_pl_parser_get_type(void)300 totem_pl_parser_get_type (void)
301 {
302 	static volatile gsize g_define_type_id__volatile = 0;
303 	if (g_once_init_enter (&g_define_type_id__volatile))
304 	{
305 		const GTypeInfo g_define_type_info = {
306 			sizeof (TotemPlParserClass),
307 			NULL,
308 			(GBaseFinalizeFunc) totem_pl_parser_base_class_finalize,
309 			(GClassInitFunc) totem_pl_parser_class_init,
310 			NULL,
311 			NULL,
312 			sizeof (TotemPlParser),
313 			0,
314 			(GInstanceInitFunc) totem_pl_parser_init,
315 		};
316 		GType g_define_type_id = g_type_register_static (G_TYPE_OBJECT, "TotemPlParser", &g_define_type_info, 0);
317 		g_once_init_leave (&g_define_type_id__volatile, g_define_type_id);
318 	}
319 	return g_define_type_id__volatile;
320 }
321 
322 static void
totem_pl_parser_class_init(TotemPlParserClass * klass)323 totem_pl_parser_class_init (TotemPlParserClass *klass)
324 {
325 	GParamSpec *pspec;
326 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
327 
328 	totem_pl_parser_parent_class = g_type_class_peek_parent (klass);
329 
330 	object_class->finalize = totem_pl_parser_finalize;
331 	object_class->set_property = totem_pl_parser_set_property;
332 	object_class->get_property = totem_pl_parser_get_property;
333 
334 	/* Properties */
335 
336 	/**
337 	 * TotemPlParser:recurse:
338 	 *
339 	 * If %TRUE, the parser will recursively fetch playlists linked to by
340 	 * the current one.
341 	 **/
342 	g_object_class_install_property (object_class,
343 					 PROP_RECURSE,
344 					 g_param_spec_boolean ("recurse",
345 							       "recurse",
346 							       "Whether or not to process URIs further",
347 							       TRUE,
348 							       G_PARAM_READWRITE | G_PARAM_CONSTRUCT));
349 
350 	/**
351 	 * TotemPlParser:debug:
352 	 *
353 	 * If %TRUE, the parser will output debug information.
354 	 **/
355 	g_object_class_install_property (object_class,
356 					 PROP_DEBUG,
357 					 g_param_spec_boolean ("debug",
358 							       "debug",
359 							       "Whether or not to enable debugging output",
360 							       FALSE,
361 							       G_PARAM_READWRITE));
362 
363 	/**
364 	 * TotemPlParser:force:
365 	 *
366 	 * If %TRUE, the parser will attempt to parse a playlist, even if it
367 	 * appears to be unsupported (usually because of its filename extension).
368 	 **/
369 	g_object_class_install_property (object_class,
370 					 PROP_FORCE,
371 					 g_param_spec_boolean ("force",
372 							       "force",
373 							       "Whether or not to force parsing the file if the playlist looks unsupported",
374 							       FALSE,
375 							       G_PARAM_READWRITE));
376 
377 	/**
378 	 * TotemPlParser:disable-unsafe:
379 	 *
380 	 * If %TRUE, the parser will not parse unsafe locations, such as local devices
381 	 * and local files if the playlist isn't local. This is useful if the library
382 	 * is parsing a playlist from a remote location such as a website.
383 	 **/
384 	g_object_class_install_property (object_class,
385 					 PROP_DISABLE_UNSAFE,
386 					 g_param_spec_boolean ("disable-unsafe",
387 							       "disable-unsafe",
388 							       "Whether or not to disable parsing of unsafe locations",
389 							       FALSE,
390 							       G_PARAM_READWRITE));
391 
392 	/**
393 	 * TotemPlParser::entry-parsed:
394 	 * @parser: the object which received the signal
395 	 * @uri: the URI of the entry parsed
396 	 * @metadata: (type GHashTable) (element-type utf8 utf8): a #GHashTable of metadata relating to the entry added
397 	 *
398 	 * The ::entry-parsed signal is emitted when a new entry is parsed.
399 	 */
400 	totem_pl_parser_table_signals[ENTRY_PARSED] =
401 		g_signal_new ("entry-parsed",
402 			      G_TYPE_FROM_CLASS (klass),
403 			      G_SIGNAL_RUN_LAST,
404 			      G_STRUCT_OFFSET (TotemPlParserClass, entry_parsed),
405 			      NULL, NULL,
406 			      _totemplparser_marshal_VOID__STRING_BOXED,
407 			      G_TYPE_NONE, 2, G_TYPE_STRING, TOTEM_TYPE_PL_PARSER_METADATA);
408 	/**
409 	 * TotemPlParser::playlist-started:
410 	 * @parser: the object which received the signal
411 	 * @uri: the URI of the new playlist started
412 	 * @metadata: (type GHashTable) (element-type utf8 utf8): a #GHashTable of metadata relating to the playlist that
413 	 * started.
414 	 *
415 	 * The ::playlist-started signal is emitted when a playlist parsing has
416 	 * started. This signal isn't emitted for all types of playlists, but
417 	 * can be relied on to be called for playlists which support playlist
418 	 * metadata, such as title.
419 	 */
420 	totem_pl_parser_table_signals[PLAYLIST_STARTED] =
421 		g_signal_new ("playlist-started",
422 			      G_TYPE_FROM_CLASS (klass),
423 			      G_SIGNAL_RUN_LAST,
424 			      G_STRUCT_OFFSET (TotemPlParserClass, playlist_started),
425 			      NULL, NULL,
426 			      _totemplparser_marshal_VOID__STRING_BOXED,
427 			      G_TYPE_NONE, 2, G_TYPE_STRING, TOTEM_TYPE_PL_PARSER_METADATA);
428 	/**
429 	 * TotemPlParser::playlist-ended:
430 	 * @parser: the object which received the signal
431 	 * @uri: the URI of the playlist that finished parsing.
432 	 *
433 	 * The ::playlist-ended signal is emitted when a playlist is finished
434 	 * parsing. It is only called when #TotemPlParser::playlist-started
435 	 * has been called for that playlist.
436 	 */
437 	totem_pl_parser_table_signals[PLAYLIST_ENDED] =
438 		g_signal_new ("playlist-ended",
439 			      G_TYPE_FROM_CLASS (klass),
440 			      G_SIGNAL_RUN_LAST,
441 			      G_STRUCT_OFFSET (TotemPlParserClass, playlist_ended),
442 			      NULL, NULL,
443 			      g_cclosure_marshal_VOID__STRING,
444 			      G_TYPE_NONE, 1, G_TYPE_STRING);
445 
446 	/* param specs */
447 	totem_pl_parser_pspec_pool = g_param_spec_pool_new (FALSE);
448 	pspec = g_param_spec_string ("url", "url",
449 				     "URI to be added", NULL,
450 				     G_PARAM_READABLE & G_PARAM_WRITABLE);
451 	g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
452 	pspec = g_param_spec_string ("title", "title",
453 				     "Title of the item to be added", NULL,
454 				     G_PARAM_READABLE & G_PARAM_WRITABLE);
455 	g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
456 	pspec = g_param_spec_string ("author", "author",
457 				     "Author of the item to be added", NULL,
458 				     G_PARAM_READABLE & G_PARAM_WRITABLE);
459 	g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
460 	pspec = g_param_spec_string ("genre", "genre",
461 				     "Genre of the item to be added", NULL,
462 				     G_PARAM_READABLE & G_PARAM_WRITABLE);
463 	g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
464 	pspec = g_param_spec_string ("album", "album",
465 				     "Album of the item to be added", NULL,
466 				     G_PARAM_READABLE & G_PARAM_WRITABLE);
467 	g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
468 	pspec = g_param_spec_string ("base", "base",
469 				     "Base URI of the item to be added", NULL,
470 				     G_PARAM_READABLE & G_PARAM_WRITABLE);
471 	g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
472 	pspec = g_param_spec_string ("volume", "volume",
473 				     "Default playback volume (in percents)", NULL,
474 				     G_PARAM_READABLE & G_PARAM_WRITABLE);
475 	g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
476 	pspec = g_param_spec_string ("autoplay", "autoplay",
477 				     "Whether or not to autoplay the stream", NULL,
478 				     G_PARAM_READABLE & G_PARAM_WRITABLE);
479 	g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
480 	pspec = g_param_spec_string ("duration", "duration",
481 				     "String representing the duration of the entry", NULL,
482 				     G_PARAM_READABLE & G_PARAM_WRITABLE);
483 	g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
484 	pspec = g_param_spec_string ("duration-ms", "duration-ms",
485 				     "String representing the duration of the entry in milliseconds", NULL,
486 				     G_PARAM_READABLE & G_PARAM_WRITABLE);
487 	g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
488 	pspec = g_param_spec_string ("starttime", "starttime",
489 				     "String representing the start time of the stream (initial seek)", NULL,
490 				     G_PARAM_READABLE & G_PARAM_WRITABLE);
491 	g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
492 	pspec = g_param_spec_string ("copyright", "copyright",
493 				     "Copyright of the item to be added", NULL,
494 				     G_PARAM_READABLE & G_PARAM_WRITABLE);
495 	g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
496 	pspec = g_param_spec_string ("abstract", "abstract",
497 				     "Abstract of the item to be added", NULL,
498 				     G_PARAM_READABLE & G_PARAM_WRITABLE);
499 	g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
500 	pspec = g_param_spec_string ("moreinfo", "moreinfo",
501 				     "URI to get more information for item to be added", NULL,
502 				     G_PARAM_READABLE & G_PARAM_WRITABLE);
503 	g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
504 	pspec = g_param_spec_string ("screensize", "screensize",
505 				     "String representing the default movie size (double, full or original)", NULL,
506 				     G_PARAM_READABLE & G_PARAM_WRITABLE);
507 	g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
508 	pspec = g_param_spec_string ("ui-mode", "ui-mode",
509 				     "String representing the default UI mode (only compact is supported)", NULL,
510 				     G_PARAM_READABLE & G_PARAM_WRITABLE);
511 	g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
512 	pspec = g_param_spec_string ("endtime", "endtime",
513 				     "String representing the end time of the stream", NULL,
514 				     G_PARAM_READABLE & G_PARAM_WRITABLE);
515 	g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
516 	pspec = g_param_spec_boolean ("is-playlist", "is-playlist",
517 				      "Boolean saying whether the entry pushed is the top-level of a playlist", FALSE,
518 				      G_PARAM_READABLE & G_PARAM_WRITABLE);
519 	g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
520 	pspec = g_param_spec_string ("description", "description",
521 				     "String representing the description of the stream", NULL,
522 				     G_PARAM_READABLE & G_PARAM_WRITABLE);
523 	g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
524 	pspec = g_param_spec_string ("publication-date", "publication-date",
525 				     "String representing the publication date of the stream", NULL,
526 				     G_PARAM_READABLE & G_PARAM_WRITABLE);
527 	g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
528 	pspec = g_param_spec_string ("filesize", "filesize",
529 				     "String representing the filesize of a file", NULL,
530 				     G_PARAM_READABLE & G_PARAM_WRITABLE);
531 	g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
532 	pspec = g_param_spec_string ("language", "language",
533 				     "String representing the language of a stream", NULL,
534 				     G_PARAM_READABLE & G_PARAM_WRITABLE);
535 	g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
536 	pspec = g_param_spec_string ("contact", "contact",
537 				     "String representing the contact for a playlist", NULL,
538 				     G_PARAM_READABLE & G_PARAM_WRITABLE);
539 	g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
540 	pspec = g_param_spec_string ("image-url", "image-url",
541 				     "String representing the location of an image for a playlist", NULL,
542 				     G_PARAM_READABLE & G_PARAM_WRITABLE);
543 	g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
544 	pspec = g_param_spec_object ("gfile-object", "gfile-object",
545 				     "Object representing the GFile for an entry", G_TYPE_FILE,
546 				     G_PARAM_READABLE & G_PARAM_WRITABLE);
547 	g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
548 	pspec = g_param_spec_object ("gfile-object-base", "gfile-object-base",
549 				     "Object representing the GFile for base URI of an entry", G_TYPE_FILE,
550 				     G_PARAM_READABLE & G_PARAM_WRITABLE);
551 	g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
552 	pspec = g_param_spec_string ("download-url", "download-url",
553 				     "String representing the location of a download URI", NULL,
554 				     G_PARAM_READABLE & G_PARAM_WRITABLE);
555 	g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
556 	pspec = g_param_spec_string ("id", "id",
557 				     "String representing the identifier for an entry", NULL,
558 				     G_PARAM_READABLE & G_PARAM_WRITABLE);
559 	g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
560 	pspec = g_param_spec_string ("subtitle-uri", "subtitle-uri",
561 				     "Subtitle URI to be added", NULL,
562 				     G_PARAM_READABLE & G_PARAM_WRITABLE);
563 	g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
564 	pspec = g_param_spec_string ("content-type", "content-type",
565 				     "Content type for the video stream", NULL,
566 				     G_PARAM_READABLE & G_PARAM_WRITABLE);
567 	g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
568 	pspec = g_param_spec_string ("playing", "playing",
569 				     "Whether the track is playing", NULL,
570 				     G_PARAM_READABLE & G_PARAM_WRITABLE);
571 	g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
572 	pspec = g_param_spec_string ("audio-track", "audio-track",
573 				     "The default audio-track to play", NULL,
574 				     G_PARAM_READABLE & G_PARAM_WRITABLE);
575 	g_param_spec_pool_insert (totem_pl_parser_pspec_pool, pspec, TOTEM_TYPE_PL_PARSER);
576 }
577 
578 static void
totem_pl_parser_base_class_finalize(TotemPlParserClass * klass)579 totem_pl_parser_base_class_finalize (TotemPlParserClass *klass)
580 {
581 	GList *list, *node;
582 
583 	list = g_param_spec_pool_list_owned (totem_pl_parser_pspec_pool, G_OBJECT_CLASS_TYPE (klass));
584 	for (node = list; node; node = node->next) {
585 		GParamSpec *pspec = node->data;
586 
587 		g_param_spec_pool_remove (totem_pl_parser_pspec_pool, pspec);
588 		g_param_spec_unref (pspec);
589 	}
590 	g_list_free (list);
591 }
592 
593 static void
totem_pl_parser_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)594 totem_pl_parser_set_property (GObject *object,
595 			      guint prop_id,
596 			      const GValue *value,
597 			      GParamSpec *pspec)
598 {
599 	TotemPlParser *parser = TOTEM_PL_PARSER (object);
600 
601 	switch (prop_id)
602 	{
603 	case PROP_RECURSE:
604 		parser->priv->recurse = g_value_get_boolean (value) != FALSE;
605 		break;
606 	case PROP_DEBUG:
607 		parser->priv->debug = g_value_get_boolean (value) != FALSE;
608 		break;
609 	case PROP_FORCE:
610 		parser->priv->force = g_value_get_boolean (value) != FALSE;
611 		break;
612 	case PROP_DISABLE_UNSAFE:
613 		parser->priv->disable_unsafe = g_value_get_boolean (value) != FALSE;
614 		break;
615 	default:
616 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
617 		break;
618 	}
619 }
620 
621 static void
totem_pl_parser_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)622 totem_pl_parser_get_property (GObject *object,
623 			      guint prop_id,
624 			      GValue *value,
625 			      GParamSpec *pspec)
626 {
627 	TotemPlParser *parser = TOTEM_PL_PARSER (object);
628 
629 	switch (prop_id)
630 	{
631 	case PROP_RECURSE:
632 		g_value_set_boolean (value, parser->priv->recurse);
633 		break;
634 	case PROP_DEBUG:
635 		g_value_set_boolean (value, parser->priv->debug);
636 		break;
637 	case PROP_FORCE:
638 		g_value_set_boolean (value, parser->priv->force);
639 		break;
640 	case PROP_DISABLE_UNSAFE:
641 		g_value_set_boolean (value, parser->priv->disable_unsafe);
642 		break;
643 	default:
644 		G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
645 		break;
646 	}
647 }
648 
649 GQuark
totem_pl_parser_error_quark(void)650 totem_pl_parser_error_quark (void)
651 {
652 	static GQuark quark;
653 	if (!quark)
654 		quark = g_quark_from_static_string ("totem_pl_parser_error");
655 
656 	return quark;
657 }
658 
659 static gpointer
totem_pl_parser_real_init_i18n(gpointer data)660 totem_pl_parser_real_init_i18n (gpointer data)
661 {
662 	bindtextdomain (GETTEXT_PACKAGE, GNOMELOCALEDIR);
663 	bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
664 	return NULL;
665 }
666 
667 static void
totem_pl_parser_init_i18n(void)668 totem_pl_parser_init_i18n (void)
669 {
670 	static GOnce my_once = G_ONCE_INIT;
671 	g_once (&my_once, totem_pl_parser_real_init_i18n, NULL);
672 }
673 
674 /**
675  * totem_pl_parser_new:
676  *
677  * Creates a #TotemPlParser object.
678  *
679  * Return value: a new #TotemPlParser
680  */
681 TotemPlParser *
totem_pl_parser_new(void)682 totem_pl_parser_new (void)
683 {
684 	totem_pl_parser_init_i18n ();
685 	return TOTEM_PL_PARSER (g_object_new (TOTEM_TYPE_PL_PARSER, NULL));
686 }
687 
688 typedef struct {
689 	TotemPlParser *parser;
690 	char *playlist_uri;
691 } PlaylistEndedSignalData;
692 
693 static gboolean
emit_playlist_ended_signal(PlaylistEndedSignalData * data)694 emit_playlist_ended_signal (PlaylistEndedSignalData *data)
695 {
696 	g_signal_emit (data->parser,
697 		       totem_pl_parser_table_signals[PLAYLIST_ENDED],
698 		       0, data->playlist_uri);
699 
700 	/* Free the data */
701 	g_object_unref (data->parser);
702 	g_free (data->playlist_uri);
703 	g_free (data);
704 
705 	return FALSE;
706 }
707 
708 /**
709  * totem_pl_parser_playlist_end:
710  * @parser: a #TotemPlParser
711  * @playlist_uri: the playlist URI
712  *
713  * Emits the #TotemPlParser::playlist-ended signal on @parser for
714  * the playlist @playlist_uri.
715  **/
716 void
totem_pl_parser_playlist_end(TotemPlParser * parser,const char * playlist_uri)717 totem_pl_parser_playlist_end (TotemPlParser *parser, const char *playlist_uri)
718 {
719 	PlaylistEndedSignalData *data;
720 
721 	data = g_new (PlaylistEndedSignalData, 1);
722 	data->parser = g_object_ref (parser);
723 	data->playlist_uri = g_strdup (playlist_uri);
724 
725 	CALL_ASYNC (parser, emit_playlist_ended_signal, data);
726 }
727 
728 static char *
my_g_file_info_get_mime_type_with_data(GFile * file,gpointer * data,TotemPlParser * parser)729 my_g_file_info_get_mime_type_with_data (GFile *file, gpointer *data, TotemPlParser *parser)
730 {
731 	char *buffer;
732 	gsize bytes_read;
733 	GFileInputStream *stream;
734 	GError *error = NULL;
735 
736 	*data = NULL;
737 
738 #ifndef _WIN32
739 	/* Stat for a block device, we're screwed as far as speed
740 	 * is concerned now */
741 	if (g_file_is_native (file) != FALSE) {
742 		struct stat buf;
743 		char *path;
744 
745 		path = g_file_get_path (file);
746 		if (g_stat (path, &buf) == 0 && S_ISBLK (buf.st_mode)) {
747 			g_free (path);
748 			return g_strdup (BLOCK_DEVICE_TYPE);
749 		}
750 		g_free (path);
751 	}
752 #endif
753 
754 	/* Open the file. */
755 	stream = g_file_read (file, NULL, &error);
756 	if (stream == NULL) {
757 		if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_IS_DIRECTORY) != FALSE) {
758 			g_error_free (error);
759 			return g_strdup (DIR_MIME_TYPE);
760 		}
761 		DEBUG(file, g_print ("URI '%s' couldn't be opened in _get_mime_type_with_data: '%s'\n", uri, error->message));
762 		g_error_free (error);
763 		return NULL;
764 	}
765 	DEBUG(file, g_print ("URI '%s' was opened successfully in _get_mime_type_with_data\n", uri));
766 
767 	/* Read the whole thing, up to MIME_READ_CHUNK_SIZE */
768 	buffer = g_malloc (MIME_READ_CHUNK_SIZE);
769 	if (g_input_stream_read_all (G_INPUT_STREAM (stream), buffer, MIME_READ_CHUNK_SIZE, &bytes_read, NULL, &error) == FALSE) {
770 		g_object_unref (stream);
771 		DEBUG(file, g_print ("Couldn't read data from '%s'\n", uri));
772 		g_free (buffer);
773 		return NULL;
774 	}
775 	g_object_unref (G_INPUT_STREAM (stream));
776 
777 	/* Empty file */
778 	if (bytes_read == 0) {
779 		g_free (buffer);
780 		DEBUG(file, g_print ("URI '%s' is empty in _get_mime_type_with_data\n", uri));
781 		return g_strdup (EMPTY_FILE_TYPE);
782 	}
783 
784 	/* Return the file null-terminated. */
785 	buffer = g_realloc (buffer, bytes_read + 1);
786 	buffer[bytes_read] = '\0';
787 	*data = buffer;
788 
789 	return totem_pl_parser_mime_type_from_data (*data, bytes_read);
790 }
791 
792 /**
793  * totem_pl_parser_is_debugging_enabled:
794  * @parser: a #TotemPlParser
795  *
796  * Returns whether debugging is enabled. This is a private method, not exposed by the library.
797  *
798  * Return value: %TRUE if debugging is enabled, %FALSE otherwise
799  **/
800 gboolean
totem_pl_parser_is_debugging_enabled(TotemPlParser * parser)801 totem_pl_parser_is_debugging_enabled (TotemPlParser *parser)
802 {
803 	return parser->priv->debug;
804 }
805 
806 /**
807  * totem_pl_parser_base_uri:
808  * @uri: a URI
809  *
810  * Returns the parent URI of @uri.
811  *
812  * Return value: a newly-allocated string containing @uri's parent URI, or %NULL
813  **/
814 char *
totem_pl_parser_base_uri(GFile * uri)815 totem_pl_parser_base_uri (GFile *uri)
816 {
817 	GFile *parent;
818 	char *ret;
819 
820 	parent = g_file_get_parent (uri);
821 	ret = g_file_get_uri (parent);
822 	g_object_unref (uri);
823 
824 	return ret;
825 }
826 
827 /**
828  * totem_pl_parser_line_is_empty:
829  * @line: a playlist line to check
830  *
831  * Checks to see if the given string line is empty or %NULL,
832  * counting tabs and spaces, but not newlines, as "empty".
833  *
834  * Return value: %TRUE if @line is empty
835  **/
836 gboolean
totem_pl_parser_line_is_empty(const char * line)837 totem_pl_parser_line_is_empty (const char *line)
838 {
839 	guint i;
840 
841 	if (line == NULL)
842 		return TRUE;
843 
844 	for (i = 0; line[i] != '\0'; i++) {
845 		if (line[i] != '\t' && line[i] != ' ')
846 			return FALSE;
847 	}
848 	return TRUE;
849 }
850 
851 /**
852  * totem_pl_parser_write_string:
853  * @handle: a #GFileOutputStream to an open file
854  * @buf: the string buffer to write out
855  * @cancellable: (allow-none): a #GCancellable, or %NULL
856  * @error: return location for a #GError, or %NULL
857  *
858  * Writes the string @buf out to the file specified by @handle.
859  * Possible error codes are as per totem_pl_parser_write_buffer().
860  *
861  * Return value: %TRUE on success
862  **/
863 gboolean
totem_pl_parser_write_string(GOutputStream * stream,const char * buf,GCancellable * cancellable,GError ** error)864 totem_pl_parser_write_string (GOutputStream  *stream,
865 			      const char     *buf,
866 			      GCancellable   *cancellable,
867 			      GError        **error)
868 {
869 	guint len;
870 
871 	len = strlen (buf);
872 	return totem_pl_parser_write_buffer (stream, buf, len, cancellable, error);
873 }
874 
875 /**
876  * totem_pl_parser_write_buffer:
877  * @stream: a #GFileOutputStream to an open file
878  * @buf: the string buffer to write out
879  * @len: the length of the string to write out
880  * @cancellable: (allow-none): a #GCancellable, or %NULL
881  * @error: return location for a #GError, or %NULL
882  *
883  * Writes @len bytes of @buf to the file specified by @handle.
884  *
885  * A value of @len greater than #G_MAXSSIZE will cause a #G_IO_ERROR_INVALID_ARGUMENT argument.
886  *
887  * Return value: %TRUE on success
888  **/
889 gboolean
totem_pl_parser_write_buffer(GOutputStream * stream,const char * buf,guint len,GCancellable * cancellable,GError ** error)890 totem_pl_parser_write_buffer (GOutputStream  *stream,
891 			      const char     *buf,
892 			      guint           len,
893 			      GCancellable   *cancellable,
894 			      GError        **error)
895 {
896 	gsize bytes_written;
897 
898 	if (g_output_stream_write_all (stream,
899 				       buf, len,
900 				       &bytes_written,
901 				       cancellable, error) == FALSE) {
902 		g_object_unref (stream);
903 		return FALSE;
904 	}
905 
906 	return TRUE;
907 }
908 
909 /**
910  * totem_pl_parser_num_entries:
911  * @parser: a #TotemPlParser
912  * @playlist: a #TotemPlPlaylist
913  *
914  * Returns the number of valid entries in @playlist.
915  *
916  * Return value: the number of entries in the playlist
917  **/
918 int
totem_pl_parser_num_entries(TotemPlParser * parser,TotemPlPlaylist * playlist)919 totem_pl_parser_num_entries (TotemPlParser   *parser,
920                              TotemPlPlaylist *playlist)
921 {
922 	int num_entries, ignored;
923         TotemPlPlaylistIter iter;
924         gboolean valid;
925 
926 	num_entries = totem_pl_playlist_size (playlist);
927         valid = totem_pl_playlist_iter_first (playlist, &iter);
928 	ignored = 0;
929 
930         while (valid) {
931                 gchar *uri;
932                 GFile *file;
933 
934                 totem_pl_playlist_get (playlist, &iter,
935                                        TOTEM_PL_PARSER_FIELD_URI, &uri,
936                                        NULL);
937 
938                 valid = totem_pl_playlist_iter_next (playlist, &iter);
939 
940                 if (!uri) {
941                         ignored++;
942                         continue;
943                 }
944 
945                 file = g_file_new_for_uri (uri);
946 
947                 if (totem_pl_parser_scheme_is_ignored (parser, file)) {
948                         ignored++;
949                 }
950 
951                 g_object_unref (file);
952                 g_free (uri);
953         }
954 
955 	return num_entries - ignored;
956 }
957 
958 char *
totem_pl_parser_relative(GFile * output,const char * filepath)959 totem_pl_parser_relative (GFile *output, const char *filepath)
960 {
961 	GFile *parent, *file;
962 	char *retval;
963 
964 	parent = g_file_get_parent (output);
965 	file = g_file_new_for_commandline_arg (filepath);
966 
967 	retval = g_file_get_relative_path (parent, file);
968 
969 	g_object_unref (parent);
970 	g_object_unref (file);
971 
972 	return retval;
973 }
974 
975 static char *
relative_uri_remove_query(const char * uri,char ** query)976 relative_uri_remove_query (const char *uri, char **query)
977 {
978 	char *qmark;
979 
980 	/* Look for '?' */
981 	qmark = strrchr (uri, '?');
982 	if (qmark == NULL)
983 		return NULL;
984 
985 	if (query != NULL)
986 		*query = g_strdup (qmark);
987 	return g_strndup (uri, qmark - uri);
988 }
989 
990 static const char *suffixes[] = {
991 	".jsp",
992 	".php",
993 	".asp"
994 };
995 
996 static gboolean
is_probably_dir(const char * filename)997 is_probably_dir (const char *filename)
998 {
999 	gboolean ret;
1000 	char *content_type, *short_name;
1001 
1002 	short_name = relative_uri_remove_query (filename, NULL);
1003 	if (short_name == NULL)
1004 		short_name = g_strdup (filename);
1005 	content_type = g_content_type_guess (short_name, NULL, 0, NULL);
1006 	if (g_content_type_is_unknown (content_type) != FALSE) {
1007 		guint i;
1008 		for (i = 0; i < G_N_ELEMENTS (suffixes); i++) {
1009 			if (g_str_has_suffix (short_name, suffixes[i]) != FALSE) {
1010 				g_free (content_type);
1011 				g_free (short_name);
1012 				return FALSE;
1013 			}
1014 		}
1015 		ret = TRUE;
1016 	} else {
1017 		ret = FALSE;
1018 	}
1019 	g_free (content_type);
1020 	g_free (short_name);
1021 
1022 	return ret;
1023 }
1024 
1025 char *
totem_pl_parser_resolve_uri(GFile * base_gfile,const char * relative_uri)1026 totem_pl_parser_resolve_uri (GFile *base_gfile,
1027 			     const char *relative_uri)
1028 {
1029 	char *uri, *scheme, *query, *new_relative_uri, *base_uri;
1030 	GFile *base_parent_gfile, *resolved_gfile;
1031 
1032 	if (relative_uri == NULL) {
1033 		if (base_gfile == NULL)
1034 			return NULL;
1035 		return g_file_get_uri (base_gfile);
1036 	}
1037 
1038 	if (base_gfile == NULL)
1039 		return g_strdup (relative_uri);
1040 
1041 	/* If |relative_uri| has a scheme, it's a full URI, just return it */
1042 	scheme = g_uri_parse_scheme (relative_uri);
1043 	if (scheme != NULL) {
1044 		g_free (scheme);
1045 		return g_strdup (relative_uri);
1046 	}
1047 
1048 	/* Check whether we need to get the parent for the base or not */
1049 	base_uri = g_file_get_path (base_gfile);
1050 	if (base_uri == NULL)
1051 		base_uri = g_file_get_uri (base_gfile);
1052 	if (is_probably_dir (base_uri) == FALSE)
1053 		base_parent_gfile = g_file_get_parent (base_gfile);
1054 	else
1055 		base_parent_gfile = g_object_ref (base_gfile);
1056 	g_free (base_uri);
1057 
1058 	if (base_parent_gfile == NULL) {
1059 		resolved_gfile = g_file_resolve_relative_path (base_gfile, relative_uri);
1060 		uri = g_file_get_uri (resolved_gfile);
1061 		g_object_unref (resolved_gfile);
1062 		return uri;
1063 	}
1064 
1065 	/* Remove the query portion of the URI, to transplant it again
1066 	 * if there is any */
1067 	query = NULL;
1068 	new_relative_uri = relative_uri_remove_query (relative_uri, &query);
1069 
1070 	if (new_relative_uri) {
1071 		char *tmpuri;
1072 
1073 		resolved_gfile = g_file_resolve_relative_path (base_parent_gfile, new_relative_uri);
1074 		g_object_unref (base_parent_gfile);
1075 		if (!resolved_gfile) {
1076 			base_uri = g_file_get_uri (base_gfile);
1077 			g_warning ("Failed to resolve relative URI '%s' against base '%s'\n", relative_uri, base_uri);
1078 			g_free (base_uri);
1079 			g_free (new_relative_uri);
1080 			g_free (query);
1081 			return NULL;
1082 		}
1083 
1084 		tmpuri = g_file_get_uri (resolved_gfile);
1085 		g_object_unref (resolved_gfile);
1086 		uri = g_strdup_printf ("%s%s", tmpuri, query);
1087 
1088 		g_free (tmpuri);
1089 		g_free (new_relative_uri);
1090 		g_free (query);
1091 
1092 		return uri;
1093 	} else {
1094 		resolved_gfile = g_file_resolve_relative_path (base_parent_gfile, relative_uri);
1095 		g_object_unref (base_parent_gfile);
1096 		if (!resolved_gfile) {
1097 			base_uri = g_file_get_uri (base_gfile);
1098 			g_warning ("Failed to resolve relative URI '%s' against base '%s'\n", relative_uri, base_uri);
1099 			g_free (base_uri);
1100 			return NULL;
1101 		}
1102 
1103 		uri = g_file_get_uri (resolved_gfile);
1104 		g_object_unref (resolved_gfile);
1105 
1106 		return uri;
1107 	}
1108 }
1109 
1110 #ifndef TOTEM_PL_PARSER_MINI
1111 typedef struct {
1112 	TotemPlPlaylist   *playlist;
1113 	GFile             *dest;
1114 	char              *title;
1115 	TotemPlParserType  type;
1116 } PlParserSaveData;
1117 
1118 static void
pl_parser_save_data_free(PlParserSaveData * data)1119 pl_parser_save_data_free (PlParserSaveData *data)
1120 {
1121 	g_clear_object (&data->playlist);
1122 	g_clear_object (&data->dest);
1123 	g_clear_pointer (&data->title, g_free);
1124 	g_free (data);
1125 }
1126 
1127 static void
pl_parser_save_thread(GTask * task,gpointer source_object,gpointer task_data,GCancellable * cancellable)1128 pl_parser_save_thread (GTask        *task,
1129 		       gpointer      source_object,
1130 		       gpointer      task_data,
1131 		       GCancellable *cancellable)
1132 {
1133 	PlParserSaveData *data = task_data;
1134 	GError *error = NULL;
1135 	gboolean ret = FALSE;
1136 
1137 	switch (data->type) {
1138 	case TOTEM_PL_PARSER_PLS:
1139 		ret = totem_pl_parser_save_pls (source_object,
1140 						data->playlist,
1141 						data->dest,
1142 						data->title,
1143 						cancellable,
1144 						&error);
1145 		break;
1146 	case TOTEM_PL_PARSER_M3U:
1147 	case TOTEM_PL_PARSER_M3U_DOS:
1148 		ret = totem_pl_parser_save_m3u (source_object,
1149 						data->playlist,
1150 						data->dest,
1151 						(data->type == TOTEM_PL_PARSER_M3U_DOS),
1152 						cancellable,
1153 						&error);
1154 		break;
1155 	case TOTEM_PL_PARSER_XSPF:
1156 		ret = totem_pl_parser_save_xspf (source_object,
1157 						 data->playlist,
1158 						 data->dest,
1159 						 data->title,
1160 						 cancellable,
1161 						 &error);
1162 		break;
1163 	case TOTEM_PL_PARSER_IRIVER_PLA:
1164 		ret = totem_pl_parser_save_pla (source_object,
1165 						data->playlist,
1166 						data->dest,
1167 						data->title,
1168 						cancellable,
1169 						&error);
1170 		break;
1171 	default:
1172 		g_assert_not_reached ();
1173 	}
1174 
1175 	if (ret == FALSE)
1176 		g_task_return_error (task, error);
1177 	else
1178 		g_task_return_boolean (task, TRUE);
1179 }
1180 
1181 static gboolean
pl_parser_save_check_size(TotemPlPlaylist * playlist,GTask * task)1182 pl_parser_save_check_size (TotemPlPlaylist *playlist,
1183 			   GTask           *task)
1184 {
1185 	if (totem_pl_playlist_size (playlist) > 0)
1186 		return TRUE;
1187 
1188 	/* FIXME add translation */
1189 	g_task_return_new_error (task,
1190 				 TOTEM_PL_PARSER_ERROR,
1191 				 TOTEM_PL_PARSER_ERROR_EMPTY_PLAYLIST,
1192 				 "Playlist selected for saving is empty");
1193 	g_object_unref (task);
1194 	return FALSE;
1195 }
1196 
1197 /**
1198  * totem_pl_parser_save:
1199  * @parser: a #TotemPlParser
1200  * @playlist: a #TotemPlPlaylist
1201  * @dest: output #GFile
1202  * @title: the playlist title
1203  * @type: a #TotemPlParserType for the outputted playlist
1204  * @error: return location for a #GError, or %NULL
1205  *
1206  * Writes the playlist held by @parser and @playlist out to the path
1207  * pointed by @dest. The playlist is written in the format @type and is
1208  * given the title @title.
1209  *
1210  * If the @output file is a directory the #G_IO_ERROR_IS_DIRECTORY error
1211  * will be returned, and if the file is some other form of non-regular file
1212  * then a #G_IO_ERROR_NOT_REGULAR_FILE error will be returned. Some file
1213  * systems don't allow all file names, and may return a
1214  * #G_IO_ERROR_INVALID_FILENAME error, and if the name is too long,
1215  * #G_IO_ERROR_FILENAME_TOO_LONG will be returned. Other errors are possible
1216  * too, and depend on what kind of filesystem the file is on.
1217  *
1218  * In extreme cases, a #G_IO_ERROR_INVALID_ARGUMENT error can be returned, if
1219  * parts of the playlist to be written are too long.
1220  *
1221  * If writing a PLA playlist and there is an error converting a URI's encoding,
1222  * a code from #GConvertError will be returned.
1223  *
1224  * Returns: %TRUE on success
1225  **/
1226 gboolean
totem_pl_parser_save(TotemPlParser * parser,TotemPlPlaylist * playlist,GFile * dest,const gchar * title,TotemPlParserType type,GError ** error)1227 totem_pl_parser_save (TotemPlParser      *parser,
1228                       TotemPlPlaylist    *playlist,
1229                       GFile              *dest,
1230                       const gchar        *title,
1231                       TotemPlParserType   type,
1232                       GError            **error)
1233 {
1234 	GTask *task;
1235 	PlParserSaveData *data;
1236 
1237 	g_return_val_if_fail (TOTEM_PL_IS_PARSER (parser), FALSE);
1238 	g_return_val_if_fail (TOTEM_PL_IS_PLAYLIST (playlist), FALSE);
1239 	g_return_val_if_fail (G_IS_FILE (dest), FALSE);
1240 
1241 	task = g_task_new (parser, NULL, NULL, NULL);
1242 	if (!pl_parser_save_check_size (playlist, task))
1243 		return g_task_propagate_boolean (task, error);
1244 
1245 	data = g_new0 (PlParserSaveData, 1);
1246 	data->playlist = g_object_ref (playlist);
1247 	data->dest = g_object_ref (dest);
1248 	data->title = g_strdup (title);
1249 	data->type = type;
1250 
1251 	g_task_set_task_data (task, data, (GDestroyNotify) pl_parser_save_data_free);
1252 	g_task_run_in_thread_sync (task, pl_parser_save_thread);
1253 
1254 	return g_task_propagate_boolean (task, error);
1255 }
1256 
1257 /**
1258  * totem_pl_parser_save_async:
1259  * @parser: a #TotemPlParser
1260  * @playlist: a #TotemPlPlaylist
1261  * @dest: output #GFile
1262  * @title: the playlist title
1263  * @type: a #TotemPlParserType for the outputted playlist
1264  * @cancellable: (allow-none): a #GCancellable, or %NULL
1265  * @callback: (allow-none): a #GAsyncReadyCallback to call when saving has finished
1266  * @user_data: data to pass to the @callback function
1267  *
1268  * Starts asynchronous version of totem_pl_parser_save(). For more details
1269  * see totem_pl_parser_save().
1270  *
1271  * When the operation is finished, @callback will be called. You can then call
1272  * totem_pl_parser_save_finish() to get the results of the operation.
1273  **/
1274 void
totem_pl_parser_save_async(TotemPlParser * parser,TotemPlPlaylist * playlist,GFile * dest,const gchar * title,TotemPlParserType type,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)1275 totem_pl_parser_save_async (TotemPlParser        *parser,
1276 			    TotemPlPlaylist      *playlist,
1277 			    GFile                *dest,
1278 			    const gchar          *title,
1279 			    TotemPlParserType     type,
1280 			    GCancellable         *cancellable,
1281 			    GAsyncReadyCallback   callback,
1282 			    gpointer              user_data)
1283 {
1284 	GTask *task;
1285 	PlParserSaveData *data;
1286 
1287 	g_return_if_fail (TOTEM_PL_IS_PARSER (parser));
1288 	g_return_if_fail (TOTEM_PL_IS_PLAYLIST (playlist));
1289 	g_return_if_fail (G_IS_FILE (dest));
1290 
1291 	task = g_task_new (parser, cancellable, callback, user_data);
1292 	if (!pl_parser_save_check_size (playlist, task))
1293 		return;
1294 
1295 	data = g_new0 (PlParserSaveData, 1);
1296 	data->playlist = g_object_ref (playlist);
1297 	data->dest = g_object_ref (dest);
1298 	data->title = g_strdup (title);
1299 	data->type = type;
1300 
1301 	g_task_set_task_data (task, data, (GDestroyNotify) pl_parser_save_data_free);
1302 	g_task_run_in_thread (task, pl_parser_save_thread);
1303 }
1304 
1305 /**
1306  * totem_pl_parser_save_finish:
1307  * @parser: a #TotemPlParser
1308  * @async_result: a #GAsyncResult
1309  * @error: a #GError, or %NULL
1310  *
1311  * Finishes an asynchronous playlist saving operation started with totem_pl_parser_save_async().
1312  *
1313  * If saving of the playlist is cancelled part-way through, %G_IO_ERROR_CANCELLED will be
1314  * returned when this function is called.
1315  *
1316  * Return value: %TRUE on success, %FALSE on failure.
1317  **/
1318 gboolean
totem_pl_parser_save_finish(TotemPlParser * parser,GAsyncResult * async_result,GError ** error)1319 totem_pl_parser_save_finish (TotemPlParser   *parser,
1320 			     GAsyncResult    *async_result,
1321 			     GError         **error)
1322 {
1323 	g_return_val_if_fail (g_task_is_valid (async_result, parser), NULL);
1324 
1325 	return g_task_propagate_boolean (G_TASK (async_result), error);
1326 }
1327 #endif /* TOTEM_PL_PARSER_MINI */
1328 
1329 /**
1330  * totem_pl_parser_read_ini_line_int:
1331  * @lines: a NULL-terminated array of INI lines to read
1332  * @key: the key to match
1333  *
1334  * Returns the first integer value case-insensitively matching the specified
1335  * key as an integer. The parser ignores leading whitespace on lines.
1336  *
1337  * Return value: the integer value, or -1 on error
1338  **/
1339 int
totem_pl_parser_read_ini_line_int(char ** lines,const char * key)1340 totem_pl_parser_read_ini_line_int (char **lines, const char *key)
1341 {
1342 	int retval = -1;
1343 	int i;
1344 
1345 	if (lines == NULL || key == NULL)
1346 		return -1;
1347 
1348 	for (i = 0; (lines[i] != NULL && retval == -1); i++) {
1349 		char *line = lines[i];
1350 
1351 		while (*line == '\t' || *line == ' ')
1352 			line++;
1353 
1354 		if (g_ascii_strncasecmp (line, key, strlen (key)) == 0) {
1355 			char **bits;
1356 
1357 			bits = g_strsplit (line, "=", 2);
1358 			if (bits[0] == NULL || bits [1] == NULL) {
1359 				g_strfreev (bits);
1360 				return -1;
1361 			}
1362 
1363 			retval = (gint) g_strtod (bits[1], NULL);
1364 			g_strfreev (bits);
1365 		}
1366 	}
1367 
1368 	return retval;
1369 }
1370 
1371 /**
1372  * totem_pl_parser_read_ini_line_string_with_sep:
1373  * @lines: a NULL-terminated array of INI lines to read
1374  * @key: the key to match
1375  * @sep: the key-value separator
1376  *
1377  * Returns the first string value case-insensitively matching the specified
1378  * key, where the two are separated by @sep. The parser ignores leading whitespace
1379  * on lines.
1380  *
1381  * Return value: a newly-allocated string value, or %NULL
1382  **/
1383 char*
totem_pl_parser_read_ini_line_string_with_sep(char ** lines,const char * key,const char * sep)1384 totem_pl_parser_read_ini_line_string_with_sep (char **lines, const char *key,
1385 		const char *sep)
1386 {
1387 	char *retval = NULL;
1388 	int i;
1389 
1390 	if (lines == NULL || key == NULL)
1391 		return NULL;
1392 
1393 	for (i = 0; (lines[i] != NULL && retval == NULL); i++) {
1394 		char *line = lines[i];
1395 
1396 		while (*line == '\t' || *line == ' ')
1397 			line++;
1398 
1399 		if (g_ascii_strncasecmp (line, key, strlen (key)) == 0) {
1400 			char **bits;
1401 
1402 			bits = g_strsplit (line, sep, 2);
1403 			if (bits[0] == NULL || bits [1] == NULL) {
1404 				g_strfreev (bits);
1405 				return NULL;
1406 			}
1407 
1408 			retval = g_strdup (bits[1]);
1409 			g_strfreev (bits);
1410 		}
1411 	}
1412 
1413 	return retval;
1414 }
1415 
1416 /**
1417  * totem_pl_parser_read_ini_line_string:
1418  * @lines: a NULL-terminated array of INI lines to read
1419  * @key: the key to match
1420  *
1421  * Returns the first string value case-insensitively matching the
1422  * specified key. The parser ignores leading whitespace on lines.
1423  *
1424  * Return value: a newly-allocated string value, or %NULL
1425  **/
1426 char*
totem_pl_parser_read_ini_line_string(char ** lines,const char * key)1427 totem_pl_parser_read_ini_line_string (char **lines, const char *key)
1428 {
1429 	return totem_pl_parser_read_ini_line_string_with_sep (lines, key, "=");
1430 }
1431 
1432 static void
totem_pl_parser_init(TotemPlParser * parser)1433 totem_pl_parser_init (TotemPlParser *parser)
1434 {
1435 	parser->priv = g_new0 (TotemPlParserPrivate, 1);
1436 	parser->priv->main_thread = g_thread_self ();
1437 	g_mutex_init (&parser->priv->ignore_mutex);
1438 	parser->priv->ignore_schemes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
1439 	parser->priv->ignore_mimetypes = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
1440 	parser->priv->ignore_globs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
1441 }
1442 
1443 static void
totem_pl_parser_finalize(GObject * object)1444 totem_pl_parser_finalize (GObject *object)
1445 {
1446 	TotemPlParser *parser = TOTEM_PL_PARSER (object);
1447 	TotemPlParserPrivate *priv = parser->priv;
1448 
1449 	g_clear_pointer (&priv->ignore_schemes, g_hash_table_destroy);
1450 	g_clear_pointer (&priv->ignore_mimetypes, g_hash_table_destroy);
1451 	g_clear_pointer (&priv->ignore_globs, g_hash_table_destroy);
1452 	g_mutex_clear (&priv->ignore_mutex);
1453 	g_clear_pointer (&parser->priv, g_free);
1454 
1455 	G_OBJECT_CLASS (totem_pl_parser_parent_class)->finalize (object);
1456 }
1457 
1458 typedef struct {
1459 	TotemPlParser *parser;
1460 	guint signal_id;
1461 	char *uri;
1462 	GHashTable *metadata;
1463 } EntryParsedSignalData;
1464 
1465 static gboolean
emit_entry_parsed_signal(EntryParsedSignalData * data)1466 emit_entry_parsed_signal (EntryParsedSignalData *data)
1467 {
1468 	g_signal_emit (data->parser, data->signal_id, 0, data->uri, data->metadata);
1469 
1470 	/* Free the data */
1471 	g_object_unref (data->parser);
1472 	g_free (data->uri);
1473 	g_hash_table_unref (data->metadata);
1474 	g_free (data);
1475 
1476 	return FALSE;
1477 }
1478 
1479 gboolean
totem_pl_parser_fix_string(const char * name,const char * value,char ** ret)1480 totem_pl_parser_fix_string (const char  *name,
1481 			    const char  *value,
1482 			    char       **ret)
1483 {
1484 	char *fixed = NULL;
1485 
1486 	/* Check for UTF-8 or ISO8859-1 string */
1487 	if (g_utf8_validate (value, -1, NULL) == FALSE) {
1488 		fixed = g_convert (value, -1, "UTF-8", "ISO8859-1", NULL, NULL, NULL);
1489 		if (fixed == NULL) {
1490 			g_warning ("Ignored non-UTF-8 and non-ISO8859-1 string for field '%s'", name);
1491 			return FALSE;
1492 		}
1493 	}
1494 
1495 	/* Remove trailing spaces from titles */
1496 	if (g_str_equal (name, TOTEM_PL_PARSER_FIELD_TITLE)) {
1497 		if (fixed == NULL)
1498 			fixed = g_strchomp (g_strdup (value));
1499 		else
1500 			g_strchomp (fixed);
1501 	}
1502 
1503 	*ret = fixed;
1504 
1505 	return TRUE;
1506 }
1507 
1508 void
totem_pl_parser_add_hash_table(TotemPlParser * parser,GHashTable * metadata,const char * uri,gboolean is_playlist)1509 totem_pl_parser_add_hash_table (TotemPlParser *parser,
1510 				GHashTable    *metadata,
1511 				const char    *uri,
1512 				gboolean       is_playlist)
1513 {
1514 	if (g_hash_table_size (metadata) > 0 || uri != NULL) {
1515 		EntryParsedSignalData *data;
1516 
1517 		/* Make sure to emit the signals asynchronously, as we could be in the main loop
1518 		 * *or* a worker thread at this point. */
1519 		data = g_new (EntryParsedSignalData, 1);
1520 		data->parser = g_object_ref (parser);
1521 		data->uri = g_strdup (uri);
1522 		data->metadata = g_hash_table_ref (metadata);
1523 
1524 		if (is_playlist == FALSE)
1525 			data->signal_id = totem_pl_parser_table_signals[ENTRY_PARSED];
1526 		else
1527 			data->signal_id = totem_pl_parser_table_signals[PLAYLIST_STARTED];
1528 
1529 		CALL_ASYNC (parser, emit_entry_parsed_signal, data);
1530 	}
1531 }
1532 
1533 static void
totem_pl_parser_add_uri_valist(TotemPlParser * parser,const gchar * first_property_name,va_list var_args)1534 totem_pl_parser_add_uri_valist (TotemPlParser *parser,
1535 				const gchar *first_property_name,
1536 				va_list      var_args)
1537 {
1538 	const char *name;
1539 	char *uri;
1540 	GHashTable *metadata;
1541 	gboolean is_playlist;
1542 
1543 	uri = NULL;
1544 	is_playlist = FALSE;
1545 
1546 	g_object_ref (G_OBJECT (parser));
1547 	metadata = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
1548 
1549 	name = first_property_name;
1550 
1551 	while (name) {
1552 		GValue value = { 0, };
1553 		GParamSpec *pspec;
1554 		char *error = NULL;
1555 		const char *string;
1556 
1557 		pspec = g_param_spec_pool_lookup (totem_pl_parser_pspec_pool,
1558 						  name,
1559 						  G_OBJECT_TYPE (parser),
1560 						  FALSE);
1561 
1562 		if (!pspec) {
1563 			g_warning ("Unknown property '%s'", name);
1564 			name = va_arg (var_args, char*);
1565 			continue;
1566 		}
1567 
1568 		g_value_init (&value, G_PARAM_SPEC_VALUE_TYPE (pspec));
1569 		G_VALUE_COLLECT (&value, var_args, 0, &error);
1570 		if (error != NULL) {
1571 			g_warning ("Error getting the value for property '%s'", name);
1572 			break;
1573 		}
1574 
1575 		if (strcmp (name, TOTEM_PL_PARSER_FIELD_URI) == 0) {
1576 			if (uri == NULL)
1577 				uri = g_value_dup_string (&value);
1578 		} else if (strcmp (name, TOTEM_PL_PARSER_FIELD_FILE) == 0) {
1579 			GFile *file;
1580 
1581 			file = g_value_get_object (&value);
1582 			uri = g_file_get_uri (file);
1583 
1584 			g_value_unset (&value);
1585 			name = va_arg (var_args, char*);
1586 			continue;
1587 		} else if (strcmp (name, TOTEM_PL_PARSER_FIELD_BASE_FILE) == 0) {
1588 			GFile *file;
1589 			char *base_uri;
1590 
1591 			file = g_value_get_object (&value);
1592 			base_uri = g_file_get_uri (file);
1593 
1594 			g_hash_table_insert (metadata,
1595 					     g_strdup (TOTEM_PL_PARSER_FIELD_BASE),
1596 					     base_uri);
1597 
1598 			g_value_unset (&value);
1599 			name = va_arg (var_args, char*);
1600 			continue;
1601 		} else if (strcmp (name, TOTEM_PL_PARSER_FIELD_IS_PLAYLIST) == 0) {
1602 			is_playlist = g_value_get_boolean (&value);
1603 			g_value_unset (&value);
1604 			name = va_arg (var_args, char*);
1605 			continue;
1606 		}
1607 
1608 		/* Ignore empty values */
1609 		string = g_value_get_string (&value);
1610 		if (string != NULL && string[0] != '\0') {
1611 			char *fixed = NULL;
1612 
1613 			if (!totem_pl_parser_fix_string (name, string, &fixed)) {
1614 				g_value_unset (&value);
1615 				name = va_arg (var_args, char*);
1616 				continue;
1617 			}
1618 
1619 			/* Add other values to the metadata hashtable */
1620 			g_hash_table_insert (metadata,
1621 					     g_strdup (name),
1622 					     fixed ? fixed : g_strdup (string));
1623 		}
1624 
1625 		g_value_unset (&value);
1626 		name = va_arg (var_args, char*);
1627 	}
1628 
1629 	if (parser->priv->disable_unsafe != FALSE) {
1630 		//FIXME fix this! 396710
1631 	}
1632 
1633 	totem_pl_parser_add_hash_table (parser,
1634 					metadata,
1635 					uri,
1636 					is_playlist);
1637 
1638 	g_hash_table_unref (metadata);
1639 
1640 	g_free (uri);
1641 	g_object_unref (G_OBJECT (parser));
1642 }
1643 
1644 /**
1645  * totem_pl_parser_add_uri:
1646  * @parser: a #TotemPlParser
1647  * @first_property_name: the first property name
1648  * @...: value for the first property, followed optionally by more
1649  * name/value pairs, followed by %NULL
1650  *
1651  * Adds a URI to the playlist with the properties given in @first_property_name
1652  * and @....
1653  **/
1654 void
totem_pl_parser_add_uri(TotemPlParser * parser,const char * first_property_name,...)1655 totem_pl_parser_add_uri (TotemPlParser *parser,
1656 			 const char *first_property_name,
1657 			 ...)
1658 {
1659 	va_list var_args;
1660 	va_start (var_args, first_property_name);
1661 	totem_pl_parser_add_uri_valist (parser, first_property_name, var_args);
1662 	va_end (var_args);
1663 }
1664 
1665 /**
1666  * totem_pl_parser_add_one_uri:
1667  * @parser: a #TotemPlParser
1668  * @uri: the entry's URI
1669  * @title: the entry's title
1670  *
1671  * Adds a single URI entry with only URI and title strings to the playlist.
1672  **/
1673 void
totem_pl_parser_add_one_uri(TotemPlParser * parser,const char * uri,const char * title)1674 totem_pl_parser_add_one_uri (TotemPlParser *parser, const char *uri, const char *title)
1675 {
1676 	totem_pl_parser_add_uri (parser,
1677 				 TOTEM_PL_PARSER_FIELD_URI, uri,
1678 				 TOTEM_PL_PARSER_FIELD_TITLE, title,
1679 				 NULL);
1680 }
1681 
1682 void
totem_pl_parser_add_one_file(TotemPlParser * parser,GFile * file,const char * title)1683 totem_pl_parser_add_one_file (TotemPlParser *parser, GFile *file, const char *title)
1684 {
1685 	totem_pl_parser_add_uri (parser,
1686 				 TOTEM_PL_PARSER_FIELD_FILE, file,
1687 				 TOTEM_PL_PARSER_FIELD_TITLE, title,
1688 				 NULL);
1689 }
1690 
1691 static PlaylistTypes ignore_types[] = {
1692 	PLAYLIST_TYPE3 ("image/*"),
1693 	PLAYLIST_TYPE3 ("text/plain"),
1694 	PLAYLIST_TYPE3 ("application/x-rar"),
1695 	PLAYLIST_TYPE3 ("application/zip"),
1696 	PLAYLIST_TYPE3 ("application/x-trash"),
1697 };
1698 
1699 /**
1700  * totem_pl_parser_scheme_is_ignored:
1701  * @parser: a #TotemPlParser
1702  * @uri: a URI
1703  *
1704  * Checks to see if @uri's scheme is in the @parser's list of
1705  * schemes to ignore.
1706  *
1707  * Return value: %TRUE if @uri's scheme is ignored
1708  **/
1709 gboolean
totem_pl_parser_scheme_is_ignored(TotemPlParser * parser,GFile * uri)1710 totem_pl_parser_scheme_is_ignored (TotemPlParser *parser, GFile *uri)
1711 {
1712 	char *scheme;
1713 	gboolean ret;
1714 
1715 	g_mutex_lock (&parser->priv->ignore_mutex);
1716 
1717 	scheme = g_file_get_uri_scheme (uri);
1718 	if (!scheme) {
1719 		g_mutex_unlock (&parser->priv->ignore_mutex);
1720 		return TRUE;
1721 	}
1722 	ret = GPOINTER_TO_INT (g_hash_table_lookup (parser->priv->ignore_schemes, scheme));
1723 	g_free (scheme);
1724 
1725 	g_mutex_unlock (&parser->priv->ignore_mutex);
1726 
1727 	return ret;
1728 }
1729 
1730 static gboolean
totem_pl_parser_mimetype_is_ignored(TotemPlParser * parser,const char * mimetype)1731 totem_pl_parser_mimetype_is_ignored (TotemPlParser *parser,
1732 				     const char *mimetype)
1733 {
1734 	gboolean ret;
1735 
1736 	g_mutex_lock (&parser->priv->ignore_mutex);
1737 	ret = GPOINTER_TO_INT (g_hash_table_lookup (parser->priv->ignore_mimetypes, mimetype));
1738 	g_mutex_unlock (&parser->priv->ignore_mutex);
1739 
1740 	return ret;
1741 }
1742 
1743 static gboolean
totem_pl_parser_glob_is_ignored(TotemPlParser * parser,const char * filename)1744 totem_pl_parser_glob_is_ignored (TotemPlParser *parser,
1745 				 const char *filename)
1746 {
1747 	GHashTableIter iter;
1748 	gpointer key;
1749 	int ret;
1750 
1751 	g_mutex_lock (&parser->priv->ignore_mutex);
1752 	g_hash_table_iter_init (&iter, parser->priv->ignore_globs);
1753 	while (g_hash_table_iter_next (&iter, &key, NULL)) {
1754 		const char *glob = key;
1755 
1756 		ret = fnmatch (glob, filename, 0);
1757 		if (ret == 0)
1758 			break;
1759 	}
1760 	g_mutex_unlock (&parser->priv->ignore_mutex);
1761 
1762 	return (ret == 0);
1763 }
1764 
1765 /**
1766  * totem_pl_parser_ignore:
1767  * @parser: a #TotemPlParser
1768  * @uri: a URI
1769  *
1770  * Checks if the URI should be ignored. URIs are <emphasis>not</emphasis> ignored if:
1771  * <itemizedlist>
1772  *  <listitem><para>they have an unknown mimetype,</para></listitem>
1773  *  <listitem><para>they have a special mimetype,</para></listitem>
1774  *  <listitem><para>they have a mimetype which could be a video or a playlist.</para></listitem>
1775  * </itemizedlist>
1776  *
1777  * URIs are automatically ignored if their scheme is ignored as per totem_pl_parser_scheme_is_ignored(),
1778  * and are ignored if all the other tests are inconclusive.
1779  *
1780  * Return value: %TRUE if @uri is to be ignored
1781  **/
1782 gboolean
totem_pl_parser_ignore(TotemPlParser * parser,const char * uri)1783 totem_pl_parser_ignore (TotemPlParser *parser, const char *uri)
1784 {
1785 	g_autofree char *mimetype;
1786 	g_autoptr(GFile) file;
1787 	guint i;
1788 
1789 	if (totem_pl_parser_glob_is_ignored (parser, uri) != FALSE)
1790 		return TRUE;
1791 
1792 	file = g_file_new_for_path (uri);
1793 	if (totem_pl_parser_scheme_is_ignored (parser, file) != FALSE)
1794 		return TRUE;
1795 
1796 	//FIXME wrong for win32
1797 	mimetype = g_content_type_guess (uri, NULL, 0, NULL);
1798 	if (mimetype == NULL || strcmp (mimetype, UNKNOWN_TYPE) == 0)
1799 		return FALSE;
1800 
1801 	for (i = 0; i < G_N_ELEMENTS (special_types); i++) {
1802 		if (strcmp (special_types[i].mimetype, mimetype) == 0)
1803 			return FALSE;
1804 	}
1805 
1806 	for (i = 0; i < G_N_ELEMENTS (dual_types); i++) {
1807 		if (strcmp (dual_types[i].mimetype, mimetype) == 0)
1808 			return FALSE;
1809 	}
1810 
1811 	return TRUE;
1812 }
1813 
1814 /**
1815  * totem_pl_parser_cleanup_xml:
1816  * @contents: the contents of the file
1817  *
1818  * Removes HTML comments from a string representing the contents of an XML file.
1819  * The function modifies the string in place.
1820  */
1821 static void
totem_pl_parser_cleanup_xml(char * contents)1822 totem_pl_parser_cleanup_xml (char *contents)
1823 {
1824 	char *needle;
1825 
1826 	needle = contents;
1827 	while ((needle = strstr (needle, "<!--")) != NULL) {
1828 		char *end;
1829 
1830 		/* Find end of comments */
1831 		end = strstr (needle, "-->");
1832 		/* Broken file? */
1833 		if (end == NULL)
1834 			return;
1835 		if (g_strstr_len (needle, end - needle, "]]>") != NULL) {
1836 			/* Advance 3 and skip */
1837 			needle += 3;
1838 			continue;
1839 		}
1840 		/* Empty the comment */
1841 		memset (needle, ' ', end + 3 - needle);
1842 	}
1843 }
1844 
1845 xml_node_t *
totem_pl_parser_parse_xml_relaxed(char * contents,gsize size)1846 totem_pl_parser_parse_xml_relaxed (char *contents,
1847 				   gsize size)
1848 {
1849 	xml_node_t* doc, *node;
1850 	char *encoding, *new_contents;
1851 	gsize new_size;
1852 	xml_parser_t *xml_parser;
1853 
1854 	totem_pl_parser_cleanup_xml (contents);
1855 	xml_parser = xml_parser_init_r (contents, size, XML_PARSER_CASE_INSENSITIVE);
1856 	if (xml_parser_build_tree_with_options_r (xml_parser, &doc, XML_PARSER_RELAXED | XML_PARSER_MULTI_TEXT) < 0) {
1857 		xml_parser_finalize_r (xml_parser);
1858 		return NULL;
1859 	}
1860 
1861 	xml_parser_finalize_r (xml_parser);
1862 
1863 	encoding = NULL;
1864 	for (node = doc; node != NULL; node = node->next) {
1865 		if (node->name == NULL || g_str_equal (node->name, "?XML") == FALSE)
1866 			continue;
1867 		encoding = g_strdup (xml_parser_get_property (node, "ENCODING"));
1868 		break;
1869 	}
1870 
1871 	if (encoding == NULL || g_str_equal (encoding, "UTF-8") != FALSE) {
1872 		g_free (encoding);
1873 		return doc;
1874 	}
1875 
1876 	xml_parser_free_tree (doc);
1877 
1878 	new_contents = g_convert (contents, size, "UTF-8", encoding, NULL, &new_size, NULL);
1879 	if (new_contents == NULL) {
1880 		g_warning ("Failed to convert XML data to UTF-8");
1881 		g_free (encoding);
1882 		return NULL;
1883 	}
1884 	g_free (encoding);
1885 
1886 	xml_parser = xml_parser_init_r (new_contents, new_size, XML_PARSER_CASE_INSENSITIVE);
1887 	if (xml_parser_build_tree_with_options_r (xml_parser, &doc, XML_PARSER_RELAXED | XML_PARSER_MULTI_TEXT) < 0) {
1888 		xml_parser_finalize_r (xml_parser);
1889 		g_free (new_contents);
1890 		return NULL;
1891 	}
1892 
1893 	xml_parser_finalize_r (xml_parser);
1894 	g_free (new_contents);
1895 
1896 	return doc;
1897 }
1898 
1899 static gboolean
totem_pl_parser_ignore_from_mimetype(TotemPlParser * parser,const char * mimetype)1900 totem_pl_parser_ignore_from_mimetype (TotemPlParser *parser, const char *mimetype)
1901 {
1902 	guint i;
1903 
1904 	if (mimetype == NULL)
1905 		return FALSE;
1906 
1907 	for (i = 0; i < G_N_ELEMENTS (ignore_types); i++) {
1908 		/* Up until we have a way to detect private inheritance
1909 		 * in shared-mime-info */
1910 		if (strcmp (mimetype, "application/vnd.apple.mpegurl") != 0 &&
1911 		    strcmp (mimetype, "audio/x-mpegurl") != 0 &&
1912 		    strcmp (mimetype, "video/x-mjpeg") != 0 &&
1913 		    g_content_type_is_a (mimetype, ignore_types[i].mimetype) != FALSE) {
1914 			if (parser->priv->debug)
1915 				g_print ("Ignoring %s because it's a %s\n", mimetype, ignore_types[i].mimetype);
1916 			return TRUE;
1917 		}
1918 		if (g_content_type_equals (mimetype, ignore_types[i].mimetype) != FALSE) {
1919 			if (parser->priv->debug)
1920 				g_print ("Ignoring %s because it's equal to %s\n", mimetype, ignore_types[i].mimetype);
1921 			return TRUE;
1922 		}
1923 	}
1924 
1925 	return FALSE;
1926 }
1927 
1928 static PlaylistCallback
totem_pl_parser_get_function_for_mimetype(const char * mimetype)1929 totem_pl_parser_get_function_for_mimetype (const char *mimetype)
1930 {
1931 	guint i;
1932 
1933 	if (mimetype == NULL)
1934 		return NULL;
1935 
1936 	for (i = 0; i < G_N_ELEMENTS(special_types); i++) {
1937 		if (strcmp (special_types[i].mimetype, mimetype) == 0)
1938 			return special_types[i].func;
1939 	}
1940 	for (i = 0; i < G_N_ELEMENTS(dual_types); i++) {
1941 		if (strcmp (dual_types[i].mimetype, mimetype) == 0)
1942 			return dual_types[i].func;
1943 	}
1944 	return NULL;
1945 }
1946 
1947 TotemPlParserResult
totem_pl_parser_parse_internal(TotemPlParser * parser,GFile * file,GFile * base_file,TotemPlParseData * parse_data)1948 totem_pl_parser_parse_internal (TotemPlParser *parser,
1949 				GFile *file,
1950 				GFile *base_file,
1951 				TotemPlParseData *parse_data)
1952 {
1953 	g_autofree char *mimetype = NULL;
1954 	g_autofree gpointer data = NULL;
1955 	g_autofree char *uri = NULL;
1956 	guint i;
1957 	TotemPlParserResult ret = TOTEM_PL_PARSER_RESULT_UNHANDLED;
1958 	gboolean found = FALSE;
1959 
1960 	if (parse_data->recurse_level > RECURSE_LEVEL_MAX)
1961 		return TOTEM_PL_PARSER_RESULT_ERROR;
1962 
1963 	if (g_file_has_uri_scheme (file, "mms") != FALSE
1964 			|| g_file_has_uri_scheme (file, "rtsp") != FALSE
1965 			|| g_file_has_uri_scheme (file, "rtmp") != FALSE
1966 			|| g_file_has_uri_scheme (file, "icy") != FALSE
1967 			|| g_file_has_uri_scheme (file, "pnm") != FALSE) {
1968 		DEBUG(file, g_print ("URI '%s' is MMS, RTSP, RTMP, PNM or ICY, not a playlist\n", uri));
1969 		return TOTEM_PL_PARSER_RESULT_UNHANDLED;
1970 	}
1971 
1972 	/* Fix up itpc, see http://www.apple.com/itunes/store/podcaststechspecs.html,
1973 	 * feed:// as used by Firefox 3,
1974 	 * as well as zcast:// as used by ZENCast */
1975 	if (g_file_has_uri_scheme (file, "itpc") != FALSE
1976 	    || g_file_has_uri_scheme (file, "feed") != FALSE
1977 	    || g_file_has_uri_scheme (file, "zcast") != FALSE) {
1978 		DEBUG(file, g_print ("URI '%s' is getting special cased for ITPC/FEED/ZCAST parsing\n", uri));
1979 		return totem_pl_parser_add_itpc (parser, file, base_file, parse_data, NULL);
1980 	}
1981 	if (g_file_has_uri_scheme (file, "zune") != FALSE) {
1982 		DEBUG(file, g_print ("URI '%s' is getting special cased for ZUNE parsing\n", uri));
1983 		return totem_pl_parser_add_zune (parser, file, base_file, parse_data, NULL);
1984 	}
1985 	/* Try itms Podcast references, see itunes.py in PenguinTV */
1986 	if (totem_pl_parser_is_itms_feed (file) != FALSE) {
1987 		DEBUG(file, g_print ("URI '%s' is getting special cased for ITMS parsing\n", uri));
1988 		return totem_pl_parser_add_itms (parser, file, NULL, parse_data, NULL);
1989 	}
1990 
1991 	if (!parse_data->recurse && parse_data->recurse_level > 0)
1992 		return TOTEM_PL_PARSER_RESULT_UNHANDLED;
1993 
1994 	uri = g_file_get_uri (file);
1995 
1996 	/* Should we try to parse it with quvi? */
1997 	if (g_file_has_uri_scheme (file, "http") ||
1998 	    g_file_has_uri_scheme (file, "https")) {
1999 		if (uri != NULL && totem_pl_parser_is_videosite (uri, parser->priv->debug) != FALSE) {
2000 			ret = totem_pl_parser_add_videosite (parser, file, base_file, parse_data, NULL);
2001 			if (ret == TOTEM_PL_PARSER_RESULT_SUCCESS)
2002 				return ret;
2003 		}
2004 	}
2005 
2006 	if (uri != NULL) {
2007 		if (totem_pl_parser_glob_is_ignored (parser, uri))
2008 			return TOTEM_PL_PARSER_RESULT_IGNORED;
2009 	}
2010 
2011 	/* In force mode we want to get the data */
2012 	if (parse_data->force != FALSE) {
2013 		mimetype = my_g_file_info_get_mime_type_with_data (file, &data, parser);
2014 	} else {
2015 		char *uri;
2016 
2017 		uri = g_file_get_uri (file);
2018 #ifdef G_OS_WIN32
2019 		{
2020 			char *content_type;
2021 			content_type = g_content_type_guess (uri, NULL, 0, NULL);
2022 			mimetype = g_content_type_get_mime_type (content_type);
2023 			g_free (content_type);
2024 		}
2025 #else
2026 		mimetype = g_content_type_guess (uri, NULL, 0, NULL);
2027 #endif
2028 
2029 		g_free (uri);
2030 	}
2031 
2032 	/* We're much more likely to have an MP2T file instead */
2033 	if (g_strcmp0 (mimetype, "application/x-linguist") == 0 ||
2034 	    g_strcmp0 (mimetype, "text/vnd.trolltech.linguist") == 0) {
2035 		g_free (mimetype);
2036 		mimetype = g_strdup ("video/mp2t");
2037 	}
2038 
2039 	/* Not a directory on http though */
2040 	if (g_strcmp0 (mimetype, "inode/directory") == 0 &&
2041 	    g_file_has_uri_scheme (file, "http")) {
2042 		g_clear_pointer (&mimetype, g_free);
2043 	}
2044 
2045 	DEBUG(file, g_print ("_get_mime_type_for_name for '%s' returned '%s'\n", uri, mimetype));
2046 	if (mimetype == NULL ||
2047 	    strcmp (UNKNOWN_TYPE, mimetype) == 0 ||
2048 	    g_content_type_is_a (mimetype, "text/plain") != FALSE) {
2049 		char *new_mimetype;
2050 		new_mimetype = my_g_file_info_get_mime_type_with_data (file, &data, parser);
2051 		if (new_mimetype) {
2052 			g_free (mimetype);
2053 			mimetype = new_mimetype;
2054 			DEBUG(file, g_print ("_get_mime_type_with_data for '%s' returned '%s'\n", uri, mimetype ? mimetype : "NULL"));
2055 		} else {
2056 			DEBUG(file, g_print ("_get_mime_type_with_data for '%s' returned NULL, ignoring\n", uri));
2057 		}
2058 	}
2059 
2060 	if (mimetype == NULL)
2061 		return TOTEM_PL_PARSER_RESULT_UNHANDLED;
2062 
2063 	if (strcmp (mimetype, EMPTY_FILE_TYPE) == 0)
2064 		return TOTEM_PL_PARSER_RESULT_SUCCESS;
2065 	else if (strcmp (mimetype, HLS_MIME_TYPE) == 0)
2066 		return TOTEM_PL_PARSER_RESULT_UNHANDLED;
2067 
2068 	/* If we're at the top-level of the parsing, try to get more
2069 	 * data from the playlist parser */
2070 	if (strcmp (mimetype, AUDIO_MPEG_TYPE) == 0 && parse_data->recurse_level == 0 && data == NULL) {
2071 		char *tmp;
2072 		tmp = my_g_file_info_get_mime_type_with_data (file, &data, parser);
2073 		if (tmp != NULL) {
2074 			g_free (mimetype);
2075 			mimetype = tmp;
2076 		}
2077 		DEBUG(file, g_print ("_get_mime_type_with_data for '%s' returned '%s' (was %s)\n", uri, mimetype, AUDIO_MPEG_TYPE));
2078 
2079 		if (strcmp (mimetype, AUDIO_MPEG_TYPE) == 0)
2080 			return TOTEM_PL_PARSER_RESULT_UNHANDLED;
2081 	}
2082 
2083 	if (totem_pl_parser_mimetype_is_ignored (parser, mimetype) != FALSE)
2084 		return TOTEM_PL_PARSER_RESULT_IGNORED;
2085 
2086 	if (parse_data->recurse || parse_data->recurse_level == 0) {
2087 		parse_data->recurse_level++;
2088 
2089 		for (i = 0; i < G_N_ELEMENTS(special_types); i++) {
2090 			if (strcmp (special_types[i].mimetype, mimetype) == 0) {
2091 				DEBUG(file, g_print ("URI '%s' is special type '%s'\n", uri, mimetype));
2092 				if (parse_data->disable_unsafe != FALSE && special_types[i].unsafe != FALSE) {
2093 					DEBUG(file, g_print ("URI '%s' is unsafe so was ignored\n", uri));
2094 					return TOTEM_PL_PARSER_RESULT_IGNORED;
2095 				}
2096 				if (base_file == NULL)
2097 					base_file = g_file_get_parent (file);
2098 				else
2099 					base_file = g_object_ref (base_file);
2100 
2101 				DEBUG (file, g_print ("Using %s function for '%s'\n", special_types[i].mimetype, uri));
2102 				ret = (* special_types[i].func) (parser, file, base_file, parse_data, data);
2103 
2104 				if (base_file != NULL)
2105 					g_object_unref (base_file);
2106 
2107 				found = TRUE;
2108 				break;
2109 			}
2110 		}
2111 
2112 		for (i = 0; i < G_N_ELEMENTS(dual_types) && found == FALSE; i++) {
2113 			if (strcmp (dual_types[i].mimetype, mimetype) == 0) {
2114 				PlaylistCallback func;
2115 
2116 				DEBUG(file, g_print ("URI '%s' is dual type '%s'\n", uri, mimetype));
2117 				if (data == NULL) {
2118 					g_free (mimetype);
2119 					mimetype = my_g_file_info_get_mime_type_with_data (file, &data, parser);
2120 					DEBUG(file, g_print ("URI '%s' dual type has type '%s' from data\n", uri, mimetype));
2121 				}
2122 				/* If it's _still_ a text/plain, we don't want it */
2123 				if (mimetype != NULL &&
2124 				    g_content_type_is_a (mimetype, "text/plain") &&
2125 				    g_content_type_is_a (mimetype, "application/xml") == FALSE) {
2126 					DEBUG(file, g_print ("Ignoring URI '%s' dual type because '%s' is a text/plain\n", uri, mimetype));
2127 					ret = TOTEM_PL_PARSER_RESULT_IGNORED;
2128 					g_free (mimetype);
2129 					mimetype = NULL;
2130 					break;
2131 				}
2132 				/* Now look for the proper function to use */
2133 				func = totem_pl_parser_get_function_for_mimetype (mimetype);
2134 				if ((func == NULL && mimetype != NULL) || (mimetype == NULL && dual_types[i].func == NULL)) {
2135 					DEBUG(file, g_print ("Ignoring URI '%s' because we couldn't find a playlist parser for '%s'\n", uri, mimetype));
2136 					ret = TOTEM_PL_PARSER_RESULT_UNHANDLED;
2137 					g_clear_pointer (&mimetype, g_free);
2138 					break;
2139 				} else if (func == NULL) {
2140 					func = dual_types[i].func;
2141 				}
2142 
2143 				if (base_file == NULL)
2144 					base_file = g_file_get_parent (file);
2145 				else
2146 					base_file = g_object_ref (base_file);
2147 
2148 				ret = (* func) (parser, file, base_file ? base_file : file, parse_data, data);
2149 
2150 				if (base_file != NULL)
2151 					g_object_unref (base_file);
2152 
2153 				found = TRUE;
2154 				break;
2155 			}
2156 		}
2157 
2158 		parse_data->recurse_level--;
2159 	}
2160 
2161 	if (ret == TOTEM_PL_PARSER_RESULT_SUCCESS)
2162 		return ret;
2163 
2164 	if (totem_pl_parser_ignore_from_mimetype (parser, mimetype) != FALSE)
2165 		return TOTEM_PL_PARSER_RESULT_IGNORED;
2166 
2167 	if (ret != TOTEM_PL_PARSER_RESULT_SUCCESS && parse_data->fallback) {
2168 		totem_pl_parser_add_one_file (parser, file, NULL);
2169 		return TOTEM_PL_PARSER_RESULT_SUCCESS;
2170 	}
2171 
2172 	return ret;
2173 }
2174 
2175 typedef struct {
2176 	char *uri;
2177 	char *base;
2178 	gboolean fallback;
2179 } ParseAsyncData;
2180 
2181 static void
parse_async_data_free(ParseAsyncData * data)2182 parse_async_data_free (ParseAsyncData *data)
2183 {
2184 	g_free (data->uri);
2185 	g_free (data->base);
2186 	g_slice_free (ParseAsyncData, data);
2187 }
2188 
2189 static void
parse_thread(GTask * task,gpointer source_object,gpointer task_data,GCancellable * cancellable)2190 parse_thread (GTask *task, gpointer source_object, gpointer task_data, GCancellable *cancellable)
2191 {
2192 	TotemPlParser *parser = TOTEM_PL_PARSER (source_object);
2193 	TotemPlParserResult parse_result;
2194 	GError *error = NULL;
2195 	ParseAsyncData *data = task_data;
2196 
2197 	/* Check to see if it's been cancelled already */
2198 	if (g_cancellable_set_error_if_cancelled (cancellable, &error) == TRUE) {
2199 		g_task_return_error (task, error);
2200 		return;
2201 	}
2202 
2203 	/* Parse and return */
2204 	parse_result = totem_pl_parser_parse_with_base (parser, data->uri, data->base, data->fallback);
2205 	g_task_return_int (task, parse_result);
2206 }
2207 
2208 /**
2209  * totem_pl_parser_parse_with_base_async:
2210  * @parser: a #TotemPlParser
2211  * @uri: the URI of the playlist to parse
2212  * @base: (allow-none): the base path for relative filenames, or %NULL
2213  * @fallback: %TRUE if the parser should add the playlist URI to the
2214  * end of the playlist on parse failure
2215  * @cancellable: (allow-none): optional #GCancellable object, or %NULL
2216  * @callback: (allow-none): a #GAsyncReadyCallback to call when parsing is finished
2217  * @user_data: data to pass to the @callback function
2218  *
2219  * Starts asynchronous parsing of a playlist given by the absolute URI @uri, using @base to resolve relative paths where appropriate.
2220  * @parser and @uri are both reffed/copied when this function is called, so can safely be freed after this function returns.
2221  *
2222  * For more details, see totem_pl_parser_parse_with_base(), which is the synchronous version of this function.
2223  *
2224  * When the operation is finished, @callback will be called. You can then call totem_pl_parser_parse_finish()
2225  * to get the results of the operation.
2226  **/
2227 void
totem_pl_parser_parse_with_base_async(TotemPlParser * parser,const char * uri,const char * base,gboolean fallback,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)2228 totem_pl_parser_parse_with_base_async (TotemPlParser *parser, const char *uri, const char *base, gboolean fallback,
2229 				       GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data)
2230 {
2231 	GTask *task;
2232 	ParseAsyncData *data;
2233 
2234 	g_return_if_fail (TOTEM_PL_IS_PARSER (parser));
2235 	g_return_if_fail (uri != NULL);
2236 	g_return_if_fail (strstr (uri, "://") != NULL);
2237 
2238 	data = g_slice_new (ParseAsyncData);
2239 	data->uri = g_strdup (uri);
2240 	data->base = g_strdup (base);
2241 	data->fallback = fallback;
2242 
2243 	task = g_task_new (parser, cancellable, callback, user_data);
2244 	g_task_set_task_data (task, data, (GDestroyNotify) parse_async_data_free);
2245 	g_task_run_in_thread (task, parse_thread);
2246 	g_object_unref (task);
2247 }
2248 
2249 /**
2250  * totem_pl_parser_parse_with_base:
2251  * @parser: a #TotemPlParser
2252  * @uri: the URI of the playlist to parse
2253  * @base: (allow-none): the base path for relative filenames, or %NULL
2254  * @fallback: %TRUE if the parser should add the playlist URI to the
2255  * end of the playlist on parse failure
2256  *
2257  * Parses a playlist given by the absolute URI @uri, using
2258  * @base to resolve relative paths where appropriate.
2259  *
2260  * Return value: a #TotemPlParserResult
2261  **/
2262 TotemPlParserResult
totem_pl_parser_parse_with_base(TotemPlParser * parser,const char * uri,const char * base,gboolean fallback)2263 totem_pl_parser_parse_with_base (TotemPlParser *parser, const char *uri,
2264 				 const char *base, gboolean fallback)
2265 {
2266 	GFile *file, *base_file;
2267 	TotemPlParserResult retval;
2268 	TotemPlParseData data;
2269 
2270 	g_return_val_if_fail (TOTEM_PL_IS_PARSER (parser), TOTEM_PL_PARSER_RESULT_UNHANDLED);
2271 	g_return_val_if_fail (uri != NULL, TOTEM_PL_PARSER_RESULT_UNHANDLED);
2272 	g_return_val_if_fail (strstr (uri, "://") != NULL,
2273 			TOTEM_PL_PARSER_RESULT_ERROR);
2274 
2275 	file = g_file_new_for_uri (uri);
2276 	base_file = NULL;
2277 
2278 	if (totem_pl_parser_scheme_is_ignored (parser, file) != FALSE) {
2279 		g_object_unref (file);
2280 		return TOTEM_PL_PARSER_RESULT_UNHANDLED;
2281 	}
2282 
2283 	/* Use a struct to store copies of the options as set for this parse operation */
2284 	data.recurse_level = 0;
2285 	data.fallback = fallback;
2286 	data.recurse = parser->priv->recurse;
2287 	data.force = parser->priv->force;
2288 	data.disable_unsafe = parser->priv->disable_unsafe;
2289 
2290 	if (base != NULL)
2291 		base_file = g_file_new_for_uri (base);
2292 	retval = totem_pl_parser_parse_internal (parser, file, base_file, &data);
2293 
2294 	g_object_unref (file);
2295 	if (base_file != NULL)
2296 		g_object_unref (base_file);
2297 
2298 	return retval;
2299 }
2300 
2301 /**
2302  * totem_pl_parser_parse_async:
2303  * @parser: a #TotemPlParser
2304  * @uri: the URI of the playlist to parse
2305  * @fallback: %TRUE if the parser should add the playlist URI to the
2306  * end of the playlist on parse failure
2307  * @cancellable: (allow-none): optional #GCancellable object, or %NULL
2308  * @callback: (allow-none): a #GAsyncReadyCallback to call when parsing is finished
2309  * @user_data: data to pass to the @callback function
2310  *
2311  * Starts asynchronous parsing of a playlist given by the absolute URI @uri. @parser and @uri are both reffed/copied
2312  * when this function is called, so can safely be freed after this function returns.
2313  *
2314  * For more details, see totem_pl_parser_parse(), which is the synchronous version of this function.
2315  *
2316  * When the operation is finished, @callback will be called. You can then call totem_pl_parser_parse_finish()
2317  * to get the results of the operation.
2318  **/
2319 void
totem_pl_parser_parse_async(TotemPlParser * parser,const char * uri,gboolean fallback,GCancellable * cancellable,GAsyncReadyCallback callback,gpointer user_data)2320 totem_pl_parser_parse_async (TotemPlParser *parser, const char *uri, gboolean fallback,
2321 			     GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data)
2322 {
2323 	totem_pl_parser_parse_with_base_async (parser, uri, NULL, fallback, cancellable, callback, user_data);
2324 }
2325 
2326 /**
2327  * totem_pl_parser_parse_finish:
2328  * @parser: a #TotemPlParser
2329  * @async_result: a #GAsyncResult
2330  * @error: a #GError, or %NULL
2331  *
2332  * Finishes an asynchronous playlist parsing operation started with totem_pl_parser_parse_async()
2333  * or totem_pl_parser_parse_with_base_async().
2334  *
2335  * If parsing of the playlist is cancelled part-way through, %TOTEM_PL_PARSER_RESULT_CANCELLED is returned when
2336  * this function is called.
2337  *
2338  * Return value: a #TotemPlParserResult
2339  **/
2340 TotemPlParserResult
totem_pl_parser_parse_finish(TotemPlParser * parser,GAsyncResult * async_result,GError ** error)2341 totem_pl_parser_parse_finish (TotemPlParser *parser, GAsyncResult *async_result, GError **error)
2342 {
2343 	GTask *task = G_TASK (async_result);
2344 
2345 	g_return_val_if_fail (TOTEM_PL_IS_PARSER (parser), FALSE);
2346 	g_return_val_if_fail (g_task_is_valid (async_result, parser), FALSE);
2347 
2348 	/* Propagate any errors which were caught and return the result; otherwise just return the result */
2349 	return g_task_propagate_int (task, error);
2350 }
2351 
2352 /**
2353  * totem_pl_parser_parse:
2354  * @parser: a #TotemPlParser
2355  * @uri: the URI of the playlist to parse
2356  * @fallback: %TRUE if the parser should add the playlist URI to the
2357  * end of the playlist on parse failure
2358  *
2359  * Parses a playlist given by the absolute URI @uri. This method is
2360  * synchronous, and will block on (e.g.) network requests to slow
2361  * servers. totem_pl_parser_parse_async() is recommended instead.
2362  *
2363  * Return values are as totem_pl_parser_parse_with_base().
2364  *
2365  * Return value: a #TotemPlParserResult
2366  **/
2367 TotemPlParserResult
totem_pl_parser_parse(TotemPlParser * parser,const char * uri,gboolean fallback)2368 totem_pl_parser_parse (TotemPlParser *parser, const char *uri,
2369 		       gboolean fallback)
2370 {
2371 	return totem_pl_parser_parse_with_base (parser, uri, NULL, fallback);
2372 }
2373 
2374 /**
2375  * totem_pl_parser_add_ignored_scheme:
2376  * @parser: a #TotemPlParser
2377  * @scheme: the scheme to ignore
2378  *
2379  * Adds a scheme to the list of schemes to ignore, so that
2380  * any URI using that scheme is ignored during playlist parsing.
2381  **/
2382 void
totem_pl_parser_add_ignored_scheme(TotemPlParser * parser,const char * scheme)2383 totem_pl_parser_add_ignored_scheme (TotemPlParser *parser,
2384 		const char *scheme)
2385 {
2386 	char *s;
2387 
2388 	g_return_if_fail (TOTEM_PL_IS_PARSER (parser));
2389 
2390 	g_mutex_lock (&parser->priv->ignore_mutex);
2391 
2392 	s = g_strdup (scheme);
2393 	if (s[strlen (s) - 1] == ':')
2394 		s[strlen (s) - 1] = '\0';
2395 	g_hash_table_insert (parser->priv->ignore_schemes, s, GINT_TO_POINTER (1));
2396 
2397 	g_mutex_unlock (&parser->priv->ignore_mutex);
2398 }
2399 
2400 /**
2401  * totem_pl_parser_add_ignored_mimetype:
2402  * @parser: a #TotemPlParser
2403  * @mimetype: the mimetype to ignore
2404  *
2405  * Adds a mimetype to the list of mimetypes to ignore, so that
2406  * any URI of that mimetype is ignored during playlist parsing.
2407  **/
2408 void
totem_pl_parser_add_ignored_mimetype(TotemPlParser * parser,const char * mimetype)2409 totem_pl_parser_add_ignored_mimetype (TotemPlParser *parser,
2410 		const char *mimetype)
2411 {
2412 	g_return_if_fail (TOTEM_PL_IS_PARSER (parser));
2413 
2414 	g_mutex_lock (&parser->priv->ignore_mutex);
2415 	g_hash_table_insert (parser->priv->ignore_mimetypes, g_strdup (mimetype), GINT_TO_POINTER (1));
2416 	g_mutex_unlock (&parser->priv->ignore_mutex);
2417 }
2418 
2419 /**
2420  * totem_pl_parser_add_ignored_glob:
2421  * @parser: a #TotemPlParser
2422  * @glob: a glob to ignore
2423  *
2424  * Adds a glob to the list of mimetypes to ignore, so that
2425  * any URI of that glob is ignored during playlist parsing.
2426  *
2427  * Since: 3.26.4
2428  **/
2429 void
totem_pl_parser_add_ignored_glob(TotemPlParser * parser,const char * glob)2430 totem_pl_parser_add_ignored_glob (TotemPlParser *parser,
2431 				  const char    *glob)
2432 {
2433 	g_return_if_fail (TOTEM_PL_IS_PARSER (parser));
2434 
2435 	g_mutex_lock (&parser->priv->ignore_mutex);
2436 	g_hash_table_insert (parser->priv->ignore_globs, g_strdup (glob), GINT_TO_POINTER (1));
2437 	g_mutex_unlock (&parser->priv->ignore_mutex);
2438 }
2439 
2440 /**
2441  * totem_pl_parser_parse_duration:
2442  * @duration: the duration string to parse
2443  * @debug: %TRUE if debug statements should be printed
2444  *
2445  * Parses the given duration string and returns it as a <type>gint64</type>
2446  * denoting the duration in seconds.
2447  *
2448  * Return value: the duration in seconds, or -1 on error
2449  **/
2450 gint64
totem_pl_parser_parse_duration(const char * duration,gboolean debug)2451 totem_pl_parser_parse_duration (const char *duration, gboolean debug)
2452 {
2453 	int hours, minutes, seconds, fractions;
2454 
2455 	if (duration == NULL) {
2456 		D(g_print ("No duration passed\n"));
2457 		return -1;
2458 	}
2459 
2460 	/* Formats used by both ASX and RAM files */
2461 	if (sscanf (duration, "%d:%d:%d.%d", &hours, &minutes, &seconds, &fractions) == 4) {
2462 		gint64 ret = (gint64) hours * 3600 + (gint64) minutes * 60 + seconds;
2463 		if (ret == 0 && fractions > 0) {
2464 			D(g_print ("Used 00:00:00.00 format, with fractions rounding\n"));
2465 			ret = 1;
2466 		} else {
2467 			D(g_print ("Used 00:00:00.00 format\n"));
2468 		}
2469 		return ret;
2470 	}
2471 	if (sscanf (duration, "%d:%d:%d", &hours, &minutes, &seconds) == 3) {
2472 		D(g_print ("Used 00:00:00 format\n"));
2473 		return (gint64) hours * 3600 + (gint64) minutes * 60 + seconds;
2474 	}
2475 	if (sscanf (duration, "%d:%d.%d", &minutes, &seconds, &fractions) == 3) {
2476 		gint64 ret = minutes * 60 + seconds;
2477 		if (ret == 0 && fractions > 0) {
2478 			D(g_print ("Used 00:00.00 format, with fractions rounding\n"));
2479 			ret = 1;
2480 		} else {
2481 			D(g_print ("Used 00:00.00 format\n"));
2482 		}
2483 		return ret;
2484 	}
2485 	if (sscanf (duration, "%d:%d", &minutes, &seconds) == 2) {
2486 		D(g_print ("Used 00:00 format\n"));
2487 		return (gint64) minutes * 60 + seconds;
2488 	}
2489 	if (sscanf (duration, "%d.%d", &minutes, &seconds) == 2) {
2490 		D(g_print ("Used broken float format (00.00)\n"));
2491 		return (gint64) minutes * 60 + seconds;
2492 	}
2493 	/* YouTube format */
2494 	if (sscanf (duration, "%dm%ds", &minutes, &seconds) == 2) {
2495 		D(g_print ("Used YouTube format\n"));
2496 		return (gint64) minutes * 60 + seconds;
2497 	}
2498 	/* PLS files format */
2499 	if (sscanf (duration, "%d", &seconds) == 1) {
2500 		D(g_print ("Used PLS format\n"));
2501 		return seconds;
2502 	}
2503 
2504 	D(g_message ("Couldn't parse duration '%s'\n", duration));
2505 
2506 	return -1;
2507 }
2508 
2509 
2510 /**
2511  * totem_pl_parser_parse_date:
2512  * @date_str: the date string to parse
2513  * @debug: %TRUE if debug statements should be printed
2514  *
2515  * Parses the given date string and returns it as a <type>gint64</type>
2516  * denoting the date in seconds since the UNIX Epoch.
2517  *
2518  * Return value: the date in seconds, or -1 on error
2519  **/
2520 guint64
totem_pl_parser_parse_date(const char * date_str,gboolean debug)2521 totem_pl_parser_parse_date (const char *date_str, gboolean debug)
2522 {
2523 	g_autoptr(GDateTime) date = NULL;
2524 
2525 	g_return_val_if_fail (date_str != NULL, -1);
2526 
2527 	/* Try to parse as an ISO8601/RFC3339 date */
2528 	date = g_date_time_new_from_iso8601 (date_str, NULL);
2529 	if (date != NULL) {
2530 		D(g_message ("Parsed duration '%s' using the ISO8601 parser", date_str));
2531 		return g_date_time_to_unix (date);
2532 	}
2533 	D(g_message ("Failed to parse duration '%s' using the ISO8601 parser", date_str));
2534 	/* Fall back to RFC 2822 date parsing */
2535 	date = g_mime_utils_header_decode_date (date_str);
2536 	if (!date) {
2537 		D(g_message ("Failed to parse duration '%s' using the RFC 2822 parser", date_str));
2538 		return -1;
2539 	}
2540 	return g_date_time_to_unix (date);
2541 }
2542 #endif /* !TOTEM_PL_PARSER_MINI */
2543 
2544 static char *
totem_pl_parser_mime_type_from_data(gconstpointer data,int len)2545 totem_pl_parser_mime_type_from_data (gconstpointer data, int len)
2546 {
2547 	char *mime_type;
2548 	gboolean uncertain;
2549 
2550 #ifdef G_OS_WIN32
2551 	char *content_type;
2552 
2553 	content_type = g_content_type_guess (NULL, data, len, &uncertain);
2554 	if (uncertain == FALSE) {
2555 		mime_type = g_content_type_get_mime_type (content_type);
2556 		g_free (content_type);
2557 	} else {
2558 		mime_type = NULL;
2559 	}
2560 #else
2561 	mime_type = g_content_type_guess (NULL, data, len, &uncertain);
2562 	if (uncertain != FALSE) {
2563 		g_free (mime_type);
2564 		mime_type = NULL;
2565 	}
2566 #endif
2567 
2568 	if (mime_type != NULL &&
2569 	    (strcmp (mime_type, "text/plain") == 0 ||
2570 	     strcmp (mime_type, "application/octet-stream") == 0 ||
2571 	     strcmp (mime_type, "application/xml") == 0 ||
2572 	     strcmp (mime_type, "text/html") == 0)) {
2573 		PlaylistIdenCallback func;
2574 		guint i;
2575 
2576 		func = NULL;
2577 
2578 		for (i = 0; i < G_N_ELEMENTS(dual_types); i++) {
2579 			const char *res;
2580 
2581 			if (func == dual_types[i].iden)
2582 				continue;
2583 			func = dual_types[i].iden;
2584 			if (func == NULL)
2585 				continue;
2586 			res = func (data, len);
2587 			if (res != NULL) {
2588 				g_free (mime_type);
2589 				return g_strdup (res);
2590 			}
2591 		}
2592 
2593 		return NULL;
2594 	}
2595 
2596 	return mime_type;
2597 }
2598 
2599 /**
2600  * totem_pl_parser_can_parse_from_data:
2601  * @data: the data to check for parsability
2602  * @len: the length of data to check
2603  * @debug: %TRUE if debug statements should be printed
2604  *
2605  * Checks if the first @len bytes of @data can be parsed.
2606  *
2607  * Return value: %TRUE if @data can be parsed
2608  **/
2609 gboolean
totem_pl_parser_can_parse_from_data(const char * data,gsize len,gboolean debug)2610 totem_pl_parser_can_parse_from_data (const char *data,
2611 				     gsize len,
2612 				     gboolean debug)
2613 {
2614 	char *mimetype;
2615 	guint i;
2616 
2617 	g_return_val_if_fail (data != NULL, FALSE);
2618 
2619 	/* Bad cast! */
2620 	mimetype = totem_pl_parser_mime_type_from_data ((gpointer) data, (int) len);
2621 
2622 	if (mimetype == NULL) {
2623 		D(g_message ("totem_pl_parser_can_parse_from_data couldn't get mimetype"));
2624 		return FALSE;
2625 	}
2626 
2627 	for (i = 0; i < G_N_ELEMENTS(special_types); i++) {
2628 		if (strcmp (special_types[i].mimetype, mimetype) == 0) {
2629 			D(g_message ("Is special type '%s'", mimetype));
2630 			g_free (mimetype);
2631 			return TRUE;
2632 		}
2633 	}
2634 
2635 	for (i = 0; i < G_N_ELEMENTS(dual_types); i++) {
2636 		if (strcmp (dual_types[i].mimetype, mimetype) == 0) {
2637 			D(g_message ("Should be dual type '%s', making sure now", mimetype));
2638 			if (dual_types[i].iden != NULL) {
2639 				gboolean retval = ((* dual_types[i].iden) (data, len) != NULL);
2640 				D(g_message ("%s dual type '%s'",
2641 					     retval ? "Is" : "Is not", mimetype));
2642 				g_free (mimetype);
2643 				return retval;
2644 			}
2645 			g_free (mimetype);
2646 			return FALSE;
2647 		}
2648 	}
2649 
2650 	D(g_message ("Is unsupported mime-type '%s'", mimetype));
2651 
2652 	g_free (mimetype);
2653 
2654 	return FALSE;
2655 }
2656 
2657 /**
2658  * totem_pl_parser_can_parse_from_filename:
2659  * @filename: the file to check for parsability
2660  * @debug: %TRUE if debug statements should be printed
2661  *
2662  * Checks if the file can be parsed. Files can be parsed if:
2663  * <itemizedlist>
2664  *  <listitem><para>they have a special mimetype, or</para></listitem>
2665  *  <listitem><para>they have a mimetype which could be a video or a playlist.</para></listitem>
2666  * </itemizedlist>
2667  *
2668  * Return value: %TRUE if @filename can be parsed
2669  **/
2670 gboolean
totem_pl_parser_can_parse_from_filename(const char * filename,gboolean debug)2671 totem_pl_parser_can_parse_from_filename (const char *filename, gboolean debug)
2672 {
2673 	GMappedFile *map;
2674 	GError *err = NULL;
2675 	gboolean retval;
2676 
2677 	g_return_val_if_fail (filename != NULL, FALSE);
2678 
2679 	map = g_mapped_file_new (filename, FALSE, &err);
2680 	if (map == NULL) {
2681 		D(g_message ("couldn't mmap %s: %s", filename, err->message));
2682 		g_error_free (err);
2683 		return FALSE;
2684 	}
2685 
2686 	retval = totem_pl_parser_can_parse_from_data
2687 		(g_mapped_file_get_contents (map),
2688 		 g_mapped_file_get_length (map), debug);
2689 
2690 	g_mapped_file_unref (map);
2691 
2692 	return retval;
2693 }
2694 
2695 /**
2696  * totem_pl_parser_can_parse_from_uri:
2697  * @uri: the remote URI to check for parsability
2698  * @debug: %TRUE if debug statements should be printed
2699  *
2700  * Checks if the remote URI can be parsed. Note that this does
2701  * not actually try to open the remote URI, or deduce its mime-type
2702  * from filename, as this would bring too many false positives.
2703  *
2704  * Return value: %TRUE if @uri could be parsed
2705  **/
2706 gboolean
totem_pl_parser_can_parse_from_uri(const char * uri,gboolean debug)2707 totem_pl_parser_can_parse_from_uri (const char *uri, gboolean debug)
2708 {
2709 	return totem_pl_parser_is_videosite (uri, debug);
2710 }
2711 
2712 #ifndef TOTEM_PL_PARSER_MINI
2713 GType
totem_pl_parser_metadata_get_type(void)2714 totem_pl_parser_metadata_get_type (void)
2715 {
2716 	static volatile gsize g_define_type_id__volatile = 0;
2717 	if (g_once_init_enter (&g_define_type_id__volatile))
2718 	{
2719 		/* NOTE: This is equivalent to the definition for GHashTable in gboxed.c, in that it uses the same copy/free functions.
2720 		 * This means that if we box a TotemPlParserMetadata inside a GValue, we can safely unbox it as a GHashTable (and vice-versa).
2721 		 * This means we can hide TotemPlParserMetadata from introspection, and just pretend it's actually been a GHashTable all along. */
2722 		GType g_define_type_id = g_boxed_type_register_static (
2723 		    g_intern_static_string ("TotemPlParserMetadata"),
2724 		    (GBoxedCopyFunc) g_hash_table_ref,
2725 		    (GBoxedFreeFunc) g_hash_table_unref);
2726 		g_once_init_leave (&g_define_type_id__volatile, g_define_type_id);
2727 	}
2728 	return g_define_type_id__volatile;
2729 }
2730 #endif /* !TOTEM_PL_PARSER_MINI */
2731 
2732