dataurisrc: Add data: URI source element
[platform/upstream/gstreamer.git] / plugins / elements / gstdataurisrc.c
1 /* GStreamer
2  *
3  * Copyright (C) 2009 Igalia S.L
4  * Copyright (C) 2009 Sebastian Dröge <sebastian.droege@collabora.co.uk>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
10  *
11  * This library is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
19  * Boston, MA 02111-1307, USA.
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #include "config.h"
24 #endif
25
26 #include "gstdataurisrc.h"
27
28 #include <string.h>
29 #include <gst/base/gsttypefindhelper.h>
30
31 GST_DEBUG_CATEGORY (data_uri_src_debug);
32 #define GST_CAT_DEFAULT (data_uri_src_debug)
33
34 static GstStaticPadTemplate src_template = GST_STATIC_PAD_TEMPLATE ("src",
35     GST_PAD_SRC,
36     GST_PAD_ALWAYS,
37     GST_STATIC_CAPS_ANY);
38
39 enum
40 {
41   PROP_0,
42   PROP_URI,
43 };
44
45 static void gst_data_uri_src_finalize (GObject * object);
46 static void gst_data_uri_src_set_property (GObject * object,
47     guint prop_id, const GValue * value, GParamSpec * pspec);
48 static void gst_data_uri_src_get_property (GObject * object,
49     guint prop_id, GValue * value, GParamSpec * pspec);
50
51 static GstCaps *gst_data_uri_src_get_caps (GstBaseSrc * src);
52 static gboolean gst_data_uri_src_get_size (GstBaseSrc * src, guint64 * size);
53 static gboolean gst_data_uri_src_is_seekable (GstBaseSrc * src);
54 static GstFlowReturn gst_data_uri_src_create (GstBaseSrc * src, guint64 offset,
55     guint size, GstBuffer ** buf);
56 static gboolean gst_data_uri_src_check_get_range (GstBaseSrc * src);
57
58 static void gst_data_uri_src_handler_init (gpointer g_iface,
59     gpointer iface_data);
60 static GstURIType gst_data_uri_src_get_uri_type (void);
61 static gchar **gst_data_uri_src_get_protocols (void);
62 static const gchar *gst_data_uri_src_get_uri (GstURIHandler * handler);
63 static gboolean gst_data_uri_src_set_uri (GstURIHandler * handler,
64     const gchar * uri);
65
66 static void
67 _do_init (GType gtype)
68 {
69   static const GInterfaceInfo urihandler_info = {
70     gst_data_uri_src_handler_init,
71     0, 0
72   };
73
74   GST_DEBUG_CATEGORY_INIT (data_uri_src_debug, "dataurisrc", 0,
75       "data: URI source");
76   g_type_add_interface_static (gtype, GST_TYPE_URI_HANDLER, &urihandler_info);
77 }
78
79 GST_BOILERPLATE_FULL (GstDataURISrc, gst_data_uri_src, GstBaseSrc,
80     GST_TYPE_BASE_SRC, _do_init);
81
82 static void
83 gst_data_uri_src_base_init (gpointer klass)
84 {
85   GstElementClass *element_class = (GstElementClass *) (klass);
86
87   gst_element_class_add_pad_template (element_class,
88       gst_static_pad_template_get (&src_template));
89   gst_element_class_set_details_simple (element_class,
90       "data: URI source element", "Source", "Handles data: uris",
91       "Philippe Normand <pnormand@igalia.com>, "
92       "Sebastian Dröge <sebastian.droege@collabora.co.uk>");
93
94 }
95
96 static void
97 gst_data_uri_src_class_init (GstDataURISrcClass * klass)
98 {
99   GObjectClass *gobject_class = (GObjectClass *) klass;
100   GstBaseSrcClass *basesrc_class = (GstBaseSrcClass *) klass;
101
102   gobject_class->finalize = gst_data_uri_src_finalize;
103   gobject_class->set_property = gst_data_uri_src_set_property;
104   gobject_class->get_property = gst_data_uri_src_get_property;
105
106   g_object_class_install_property (gobject_class, PROP_URI,
107       g_param_spec_string ("uri",
108           "URI",
109           "URI that should be used",
110           NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
111
112   basesrc_class->get_caps = GST_DEBUG_FUNCPTR (gst_data_uri_src_get_caps);
113   basesrc_class->get_size = GST_DEBUG_FUNCPTR (gst_data_uri_src_get_size);
114   basesrc_class->is_seekable = GST_DEBUG_FUNCPTR (gst_data_uri_src_is_seekable);
115   basesrc_class->create = GST_DEBUG_FUNCPTR (gst_data_uri_src_create);
116   basesrc_class->check_get_range =
117       GST_DEBUG_FUNCPTR (gst_data_uri_src_check_get_range);
118 }
119
120 static void
121 gst_data_uri_src_init (GstDataURISrc * src, GstDataURISrcClass * g_class)
122 {
123   gst_base_src_set_format (GST_BASE_SRC (src), GST_FORMAT_BYTES);
124 }
125
126 static void
127 gst_data_uri_src_finalize (GObject * object)
128 {
129   GstDataURISrc *src = GST_DATA_URI_SRC (object);
130
131   g_free (src->uri);
132   src->uri = NULL;
133
134   if (src->buffer)
135     gst_buffer_unref (src->buffer);
136   src->buffer = NULL;
137
138   G_OBJECT_CLASS (parent_class)->finalize (object);
139 }
140
141 static void
142 gst_data_uri_src_set_property (GObject * object, guint prop_id,
143     const GValue * value, GParamSpec * pspec)
144 {
145   GstDataURISrc *src = GST_DATA_URI_SRC (object);
146
147   switch (prop_id) {
148     case PROP_URI:
149       gst_data_uri_src_set_uri (GST_URI_HANDLER (src),
150           g_value_get_string (value));
151       break;
152     default:
153       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
154       break;
155   }
156 }
157
158 static void
159 gst_data_uri_src_get_property (GObject * object,
160     guint prop_id, GValue * value, GParamSpec * pspec)
161 {
162   GstDataURISrc *src = GST_DATA_URI_SRC (object);
163
164   switch (prop_id) {
165     case PROP_URI:
166       g_value_set_string (value,
167           gst_data_uri_src_get_uri (GST_URI_HANDLER (src)));
168       break;
169     default:
170       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
171       break;
172   }
173 }
174
175 static GstCaps *
176 gst_data_uri_src_get_caps (GstBaseSrc * basesrc)
177 {
178   GstDataURISrc *src = GST_DATA_URI_SRC (basesrc);
179   GstCaps *caps;
180
181   GST_OBJECT_LOCK (src);
182   if (!src->buffer || !GST_BUFFER_CAPS (src->buffer))
183     caps = gst_caps_new_empty ();
184   else
185     caps = gst_buffer_get_caps (src->buffer);
186   GST_OBJECT_UNLOCK (src);
187
188   return caps;
189 }
190
191 static gboolean
192 gst_data_uri_src_get_size (GstBaseSrc * basesrc, guint64 * size)
193 {
194   GstDataURISrc *src = GST_DATA_URI_SRC (basesrc);
195   gboolean ret;
196
197   GST_OBJECT_LOCK (src);
198   if (!src->buffer) {
199     ret = FALSE;
200     *size = -1;
201   } else {
202     ret = TRUE;
203     *size = GST_BUFFER_SIZE (src->buffer);
204   }
205   GST_OBJECT_UNLOCK (src);
206
207   return ret;
208 }
209
210 static gboolean
211 gst_data_uri_src_is_seekable (GstBaseSrc * basesrc)
212 {
213   return TRUE;
214 }
215
216 static GstFlowReturn
217 gst_data_uri_src_create (GstBaseSrc * basesrc, guint64 offset, guint size,
218     GstBuffer ** buf)
219 {
220   GstDataURISrc *src = GST_DATA_URI_SRC (basesrc);
221   GstFlowReturn ret;
222
223   GST_OBJECT_LOCK (src);
224   if (!src->buffer) {
225     ret = GST_FLOW_NOT_NEGOTIATED;
226     GST_ELEMENT_ERROR (src, RESOURCE, NOT_FOUND, (NULL), (NULL));
227   } else if (offset + size > GST_BUFFER_SIZE (src->buffer)) {
228     ret = GST_FLOW_UNEXPECTED;
229   } else {
230     ret = GST_FLOW_OK;
231     *buf = gst_buffer_create_sub (src->buffer, offset, size);
232     gst_buffer_set_caps (*buf, GST_BUFFER_CAPS (src->buffer));
233   }
234   GST_OBJECT_UNLOCK (src);
235
236   return ret;
237 }
238
239 static gboolean
240 gst_data_uri_src_check_get_range (GstBaseSrc * basesrc)
241 {
242   return TRUE;
243 }
244
245 static void
246 gst_data_uri_src_handler_init (gpointer g_iface, gpointer iface_data)
247 {
248   GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface;
249
250   iface->get_type = gst_data_uri_src_get_uri_type;
251   iface->get_protocols = gst_data_uri_src_get_protocols;
252   iface->get_uri = gst_data_uri_src_get_uri;
253   iface->set_uri = gst_data_uri_src_set_uri;
254 }
255
256 static GstURIType
257 gst_data_uri_src_get_uri_type (void)
258 {
259   return GST_URI_SRC;
260 }
261
262 static gchar **
263 gst_data_uri_src_get_protocols (void)
264 {
265   static gchar *protocols[] = { "data", 0 };
266
267   return protocols;
268 }
269
270 static const gchar *
271 gst_data_uri_src_get_uri (GstURIHandler * handler)
272 {
273   GstDataURISrc *src = GST_DATA_URI_SRC (handler);
274
275   return src->uri;
276 }
277
278 static gboolean
279 gst_data_uri_src_set_uri (GstURIHandler * handler, const gchar * uri)
280 {
281   GstDataURISrc *src = GST_DATA_URI_SRC (handler);
282   gboolean ret = FALSE;
283   gchar *mimetype = NULL;
284   const gchar *parameters_start;
285   const gchar *data_start;
286   GstCaps *caps;
287   gboolean base64 = FALSE;
288   gchar *charset = NULL;
289
290   GST_OBJECT_LOCK (src);
291   if (GST_STATE (src) >= GST_STATE_PAUSED)
292     goto wrong_state;
293
294   /* uri must be an URI as defined in RFC 2397
295    * data:[<mediatype>][;base64],<data>
296    */
297   if (strncmp ("data:", uri, 5) != 0)
298     goto invalid_uri;
299
300   uri += 5;
301
302   parameters_start = strchr (uri, ';');
303   data_start = strchr (uri, ',');
304   if (data_start == NULL)
305     goto invalid_uri;
306
307   if (data_start != uri && parameters_start != uri)
308     mimetype =
309         g_strndup (uri,
310         (parameters_start ? parameters_start : data_start) - uri);
311   else
312     mimetype = g_strdup ("text/plain");
313
314   GST_DEBUG_OBJECT (src, "Mimetype: %s", mimetype);
315
316   if (parameters_start != NULL) {
317     gchar **walk;
318     gchar *parameters =
319         g_strndup (parameters_start + 1, data_start - parameters_start - 1);
320     gchar **parameters_strv;
321
322     parameters_strv = g_strsplit (parameters, ";", -1);
323
324     GST_DEBUG_OBJECT (src, "Parameters: ");
325     walk = parameters_strv;
326     while (*walk) {
327       GST_DEBUG_OBJECT (src, "\t %s", *walk);
328       if (strcmp ("base64", *walk) == 0) {
329         base64 = TRUE;
330       } else if (strncmp ("charset=", *walk, 8) == 0) {
331         charset = g_strdup (*walk + 8);
332       }
333       walk++;
334     }
335     g_free (parameters);
336     g_strfreev (parameters_strv);
337   }
338
339   /* Skip comma */
340   data_start += 1;
341   if (base64) {
342     gsize bsize;
343
344     src->buffer = gst_buffer_new ();
345     GST_BUFFER_DATA (src->buffer) =
346         (guint8 *) g_base64_decode (data_start, &bsize);
347     GST_BUFFER_MALLOCDATA (src->buffer) = GST_BUFFER_DATA (src->buffer);
348     GST_BUFFER_SIZE (src->buffer) = bsize;
349   } else {
350     gchar *data;
351
352     /* URI encoded, i.e. "percent" encoding */
353     data = g_uri_unescape_string (data_start, NULL);
354     if (data == NULL)
355       goto invalid_uri_encoded_data;
356
357     src->buffer = gst_buffer_new ();
358     GST_BUFFER_DATA (src->buffer) = (guint8 *) data;
359     GST_BUFFER_MALLOCDATA (src->buffer) = GST_BUFFER_DATA (src->buffer);
360     GST_BUFFER_SIZE (src->buffer) = strlen (data) + 1;
361   }
362
363   /* Convert to UTF8 */
364   if (strcmp ("text/plain", mimetype) == 0 &&
365       charset && strcasecmp ("US-ASCII", charset) != 0
366       && strcasecmp ("UTF-8", charset) != 0) {
367     gsize read;
368     gsize written;
369     gchar *old_data = (gchar *) GST_BUFFER_DATA (src->buffer);
370     gchar *data;
371
372     data =
373         g_convert_with_fallback (old_data, -1, "UTF-8", charset, "*", &read,
374         &written, NULL);
375     g_free (old_data);
376     GST_BUFFER_DATA (src->buffer) = GST_BUFFER_MALLOCDATA (src->buffer) =
377         (guint8 *) data;
378     GST_BUFFER_SIZE (src->buffer) = written;
379   }
380
381   caps = gst_type_find_helper_for_buffer (GST_OBJECT (src), src->buffer, NULL);
382   if (!caps)
383     caps = gst_caps_new_simple (mimetype, NULL);
384   gst_buffer_set_caps (src->buffer, caps);
385   gst_caps_unref (caps);
386
387   ret = TRUE;
388   GST_OBJECT_UNLOCK (src);
389 out:
390   g_free (mimetype);
391   g_free (charset);
392
393
394   return ret;
395
396 invalid_uri:
397   {
398     GST_OBJECT_UNLOCK (src);
399     GST_ELEMENT_ERROR (src, STREAM, FORMAT, (NULL), (NULL));
400   }
401   goto out;
402 wrong_state:
403   {
404     GST_OBJECT_UNLOCK (src);
405     GST_ELEMENT_ERROR (src, CORE, FAILED, (NULL), (NULL));
406   }
407   goto out;
408 invalid_uri_encoded_data:
409   {
410     GST_OBJECT_UNLOCK (src);
411     GST_ELEMENT_ERROR (src, STREAM, FORMAT, (NULL), (NULL));
412   }
413   goto out;
414 }
415
416 static gboolean
417 plugin_init (GstPlugin * plugin)
418 {
419   return gst_element_register (plugin, "dataurisrc",
420       GST_RANK_PRIMARY, GST_TYPE_DATA_URI_SRC);
421 }
422
423 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
424     GST_VERSION_MINOR,
425     "dataurisrc",
426     "data: URI source",
427     plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN);