soup: hook up i18n bits for plugin
[platform/upstream/gst-plugins-good.git] / ext / soup / gstsouphttpsrc.c
1 /* GStreamer
2  * Copyright (C) 2007-2008 Wouter Cloetens <wouter@mind.be>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more
13  */
14
15 /**
16  * SECTION:element-souphttpsrc
17  *
18  * This plugin reads data from a remote location specified by a URI.
19  * Supported protocols are 'http', 'https'.
20  * 
21  * An HTTP proxy must be specified by its URL.
22  * If the "http_proxy" environment variable is set, its value is used.
23  * If built with libsoup's GNOME integration features, the GNOME proxy
24  * configuration will be used, or failing that, proxy autodetection.
25  * The #GstSoupHTTPSrc:proxy property can be used to override the default.
26  *
27  * In case the #GstSoupHTTPSrc:iradio-mode property is set and the location is
28  * an HTTP resource, souphttpsrc will send special Icecast HTTP headers to the
29  * server to request additional Icecast meta-information.
30  * If the server is not an Icecast server, it will behave as if the
31  * #GstSoupHTTPSrc:iradio-mode property were not set. If it is, souphttpsrc will
32  * output data with a media type of application/x-icy, in which case you will
33  * need to use the #ICYDemux element as follow-up element to extract the Icecast
34  * metadata and to determine the underlying media type.
35  *
36  * <refsect2>
37  * <title>Example launch line</title>
38  * |[
39  * gst-launch -v souphttpsrc location=https://some.server.org/index.html
40  *     ! filesink location=/home/joe/server.html
41  * ]| The above pipeline reads a web page from a server using the HTTPS protocol
42  * and writes it to a local file.
43  * |[
44  * gst-launch -v souphttpsrc user-agent="FooPlayer 0.99 beta"
45  *     automatic-redirect=false proxy=http://proxy.intranet.local:8080
46  *     location=http://music.foobar.com/demo.mp3 ! mad ! audioconvert
47  *     ! audioresample ! alsasink
48  * ]| The above pipeline will read and decode and play an mp3 file from a
49  * web server using the HTTP protocol. If the server sends redirects,
50  * the request fails instead of following the redirect. The specified
51  * HTTP proxy server is used. The User-Agent HTTP request header
52  * is set to a custom string instead of "GStreamer souphttpsrc."
53  * |[
54  * gst-launch -v souphttpsrc location=http://10.11.12.13/mjpeg
55  *     do-timestamp=true ! multipartdemux
56  *     ! image/jpeg,width=640,height=480 ! matroskamux
57  *     ! filesink location=mjpeg.mkv
58  * ]| The above pipeline reads a motion JPEG stream from an IP camera
59  * using the HTTP protocol, encoded as mime/multipart image/jpeg
60  * parts, and writes a Matroska motion JPEG file. The width and
61  * height properties are set in the caps to provide the Matroska
62  * multiplexer with the information to set this in the header.
63  * Timestamps are set on the buffers as they arrive from the camera.
64  * These are used by the mime/multipart demultiplexer to emit timestamps
65  * on the JPEG-encoded video frame buffers. This allows the Matroska
66  * multiplexer to timestamp the frames in the resulting file.
67  * </refsect2>
68  */
69
70 #ifdef HAVE_CONFIG_H
71 #include "config.h"
72 #endif
73
74 #include <string.h>
75 #ifdef HAVE_STDLIB_H
76 #include <stdlib.h>             /* atoi() */
77 #endif
78 #include <gst/gstelement.h>
79 #include <gst/gst-i18n-plugin.h>
80 #ifdef HAVE_LIBSOUP_GNOME
81 #include <libsoup/soup-gnome.h>
82 #else
83 #include <libsoup/soup.h>
84 #endif
85 #include "gstsouphttpsrc.h"
86
87 #include <gst/tag/tag.h>
88
89 GST_DEBUG_CATEGORY_STATIC (souphttpsrc_debug);
90 #define GST_CAT_DEFAULT souphttpsrc_debug
91
92 static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
93     GST_PAD_SRC,
94     GST_PAD_ALWAYS,
95     GST_STATIC_CAPS_ANY);
96
97 enum
98 {
99   PROP_0,
100   PROP_LOCATION,
101   PROP_IS_LIVE,
102   PROP_USER_AGENT,
103   PROP_AUTOMATIC_REDIRECT,
104   PROP_PROXY,
105   PROP_USER_ID,
106   PROP_USER_PW,
107   PROP_PROXY_ID,
108   PROP_PROXY_PW,
109   PROP_COOKIES,
110   PROP_IRADIO_MODE,
111   PROP_IRADIO_NAME,
112   PROP_IRADIO_GENRE,
113   PROP_IRADIO_URL,
114   PROP_IRADIO_TITLE,
115   PROP_TIMEOUT,
116   PROP_EXTRA_HEADERS
117 };
118
119 #define DEFAULT_USER_AGENT           "GStreamer souphttpsrc "
120
121 static void gst_soup_http_src_uri_handler_init (gpointer g_iface,
122     gpointer iface_data);
123 static void gst_soup_http_src_finalize (GObject * gobject);
124
125 static void gst_soup_http_src_set_property (GObject * object, guint prop_id,
126     const GValue * value, GParamSpec * pspec);
127 static void gst_soup_http_src_get_property (GObject * object, guint prop_id,
128     GValue * value, GParamSpec * pspec);
129
130 static GstFlowReturn gst_soup_http_src_create (GstPushSrc * psrc,
131     GstBuffer ** outbuf);
132 static gboolean gst_soup_http_src_start (GstBaseSrc * bsrc);
133 static gboolean gst_soup_http_src_stop (GstBaseSrc * bsrc);
134 static gboolean gst_soup_http_src_get_size (GstBaseSrc * bsrc, guint64 * size);
135 static gboolean gst_soup_http_src_is_seekable (GstBaseSrc * bsrc);
136 static gboolean gst_soup_http_src_do_seek (GstBaseSrc * bsrc,
137     GstSegment * segment);
138 static gboolean gst_soup_http_src_unlock (GstBaseSrc * bsrc);
139 static gboolean gst_soup_http_src_unlock_stop (GstBaseSrc * bsrc);
140 static gboolean gst_soup_http_src_set_location (GstSoupHTTPSrc * src,
141     const gchar * uri);
142 static gboolean gst_soup_http_src_set_proxy (GstSoupHTTPSrc * src,
143     const gchar * uri);
144 static char *gst_soup_http_src_unicodify (const char *str);
145 static gboolean gst_soup_http_src_build_message (GstSoupHTTPSrc * src);
146 static void gst_soup_http_src_cancel_message (GstSoupHTTPSrc * src);
147 static void gst_soup_http_src_queue_message (GstSoupHTTPSrc * src);
148 static gboolean gst_soup_http_src_add_range_header (GstSoupHTTPSrc * src,
149     guint64 offset);
150 static void gst_soup_http_src_session_unpause_message (GstSoupHTTPSrc * src);
151 static void gst_soup_http_src_session_pause_message (GstSoupHTTPSrc * src);
152 static void gst_soup_http_src_session_close (GstSoupHTTPSrc * src);
153 static void gst_soup_http_src_parse_status (SoupMessage * msg,
154     GstSoupHTTPSrc * src);
155 static void gst_soup_http_src_chunk_free (gpointer gstbuf);
156 static SoupBuffer *gst_soup_http_src_chunk_allocator (SoupMessage * msg,
157     gsize max_len, gpointer user_data);
158 static void gst_soup_http_src_got_chunk_cb (SoupMessage * msg,
159     SoupBuffer * chunk, GstSoupHTTPSrc * src);
160 static void gst_soup_http_src_response_cb (SoupSession * session,
161     SoupMessage * msg, GstSoupHTTPSrc * src);
162 static void gst_soup_http_src_got_headers_cb (SoupMessage * msg,
163     GstSoupHTTPSrc * src);
164 static void gst_soup_http_src_got_body_cb (SoupMessage * msg,
165     GstSoupHTTPSrc * src);
166 static void gst_soup_http_src_finished_cb (SoupMessage * msg,
167     GstSoupHTTPSrc * src);
168 static void gst_soup_http_src_authenticate_cb (SoupSession * session,
169     SoupMessage * msg, SoupAuth * auth, gboolean retrying,
170     GstSoupHTTPSrc * src);
171
172 static void
173 _do_init (GType type)
174 {
175   static const GInterfaceInfo urihandler_info = {
176     gst_soup_http_src_uri_handler_init,
177     NULL,
178     NULL
179   };
180
181   g_type_add_interface_static (type, GST_TYPE_URI_HANDLER, &urihandler_info);
182
183   GST_DEBUG_CATEGORY_INIT (souphttpsrc_debug, "souphttpsrc", 0,
184       "SOUP HTTP src");
185 }
186
187 GST_BOILERPLATE_FULL (GstSoupHTTPSrc, gst_soup_http_src, GstPushSrc,
188     GST_TYPE_PUSH_SRC, _do_init);
189
190 static void
191 gst_soup_http_src_base_init (gpointer g_class)
192 {
193   GstElementClass *element_class = GST_ELEMENT_CLASS (g_class);
194
195   gst_element_class_add_pad_template (element_class,
196       gst_static_pad_template_get (&srctemplate));
197
198   gst_element_class_set_details_simple (element_class, "HTTP client source",
199       "Source/Network",
200       "Receive data as a client over the network via HTTP using SOUP",
201       "Wouter Cloetens <wouter@mind.be>");
202 }
203
204 static void
205 gst_soup_http_src_class_init (GstSoupHTTPSrcClass * klass)
206 {
207   GObjectClass *gobject_class;
208   GstBaseSrcClass *gstbasesrc_class;
209   GstPushSrcClass *gstpushsrc_class;
210
211   gobject_class = (GObjectClass *) klass;
212   gstbasesrc_class = (GstBaseSrcClass *) klass;
213   gstpushsrc_class = (GstPushSrcClass *) klass;
214
215   gobject_class->set_property = gst_soup_http_src_set_property;
216   gobject_class->get_property = gst_soup_http_src_get_property;
217   gobject_class->finalize = gst_soup_http_src_finalize;
218
219   g_object_class_install_property (gobject_class,
220       PROP_LOCATION,
221       g_param_spec_string ("location", "Location",
222           "Location to read from", "", G_PARAM_READWRITE));
223   g_object_class_install_property (gobject_class,
224       PROP_USER_AGENT,
225       g_param_spec_string ("user-agent", "User-Agent",
226           "Value of the User-Agent HTTP request header field",
227           DEFAULT_USER_AGENT, G_PARAM_READWRITE));
228   g_object_class_install_property (gobject_class,
229       PROP_AUTOMATIC_REDIRECT,
230       g_param_spec_boolean ("automatic-redirect", "automatic-redirect",
231           "Automatically follow HTTP redirects (HTTP Status Code 3xx)",
232           TRUE, G_PARAM_READWRITE));
233   g_object_class_install_property (gobject_class,
234       PROP_PROXY,
235       g_param_spec_string ("proxy", "Proxy",
236           "HTTP proxy server URI", "", G_PARAM_READWRITE));
237   g_object_class_install_property (gobject_class,
238       PROP_USER_ID,
239       g_param_spec_string ("user-id", "user-id",
240           "HTTP location URI user id for authentication", "",
241           G_PARAM_READWRITE));
242   g_object_class_install_property (gobject_class, PROP_USER_PW,
243       g_param_spec_string ("user-pw", "user-pw",
244           "HTTP location URI user password for authentication", "",
245           G_PARAM_READWRITE));
246   g_object_class_install_property (gobject_class, PROP_PROXY_ID,
247       g_param_spec_string ("proxy-id", "proxy-id",
248           "HTTP proxy URI user id for authentication", "", G_PARAM_READWRITE));
249   g_object_class_install_property (gobject_class, PROP_PROXY_PW,
250       g_param_spec_string ("proxy-pw", "proxy-pw",
251           "HTTP proxy URI user password for authentication", "",
252           G_PARAM_READWRITE));
253   g_object_class_install_property (gobject_class, PROP_COOKIES,
254       g_param_spec_boxed ("cookies", "Cookies", "HTTP request cookies",
255           G_TYPE_STRV, G_PARAM_READWRITE));
256   g_object_class_install_property (gobject_class, PROP_IS_LIVE,
257       g_param_spec_boolean ("is-live", "is-live", "Act like a live source",
258           FALSE, G_PARAM_READWRITE));
259   g_object_class_install_property (gobject_class, PROP_TIMEOUT,
260       g_param_spec_uint ("timeout", "timeout",
261           "Value in seconds to timeout a blocking I/O (0 = No timeout).", 0,
262           3600, 0, G_PARAM_READWRITE));
263   g_object_class_install_property (gobject_class, PROP_EXTRA_HEADERS,
264       g_param_spec_boxed ("extra-headers", "Extra Headers",
265           "Extra headers to append to the HTTP request",
266           GST_TYPE_STRUCTURE, G_PARAM_READWRITE));
267
268   /* icecast stuff */
269   g_object_class_install_property (gobject_class,
270       PROP_IRADIO_MODE,
271       g_param_spec_boolean ("iradio-mode",
272           "iradio-mode",
273           "Enable internet radio mode (extraction of shoutcast/icecast metadata)",
274           FALSE, G_PARAM_READWRITE));
275   g_object_class_install_property (gobject_class,
276       PROP_IRADIO_NAME,
277       g_param_spec_string ("iradio-name",
278           "iradio-name", "Name of the stream", NULL, G_PARAM_READABLE));
279   g_object_class_install_property (gobject_class,
280       PROP_IRADIO_GENRE,
281       g_param_spec_string ("iradio-genre",
282           "iradio-genre", "Genre of the stream", NULL, G_PARAM_READABLE));
283   g_object_class_install_property (gobject_class,
284       PROP_IRADIO_URL,
285       g_param_spec_string ("iradio-url",
286           "iradio-url",
287           "Homepage URL for radio stream", NULL, G_PARAM_READABLE));
288   g_object_class_install_property (gobject_class,
289       PROP_IRADIO_TITLE,
290       g_param_spec_string ("iradio-title",
291           "iradio-title",
292           "Name of currently playing song", NULL, G_PARAM_READABLE));
293
294   gstbasesrc_class->start = GST_DEBUG_FUNCPTR (gst_soup_http_src_start);
295   gstbasesrc_class->stop = GST_DEBUG_FUNCPTR (gst_soup_http_src_stop);
296   gstbasesrc_class->unlock = GST_DEBUG_FUNCPTR (gst_soup_http_src_unlock);
297   gstbasesrc_class->unlock_stop =
298       GST_DEBUG_FUNCPTR (gst_soup_http_src_unlock_stop);
299   gstbasesrc_class->get_size = GST_DEBUG_FUNCPTR (gst_soup_http_src_get_size);
300   gstbasesrc_class->is_seekable =
301       GST_DEBUG_FUNCPTR (gst_soup_http_src_is_seekable);
302   gstbasesrc_class->do_seek = GST_DEBUG_FUNCPTR (gst_soup_http_src_do_seek);
303
304   gstpushsrc_class->create = GST_DEBUG_FUNCPTR (gst_soup_http_src_create);
305
306   GST_DEBUG_CATEGORY_INIT (souphttpsrc_debug, "souphttpsrc", 0,
307       "SOUP HTTP Client Source");
308 }
309
310 static void
311 gst_soup_http_src_reset (GstSoupHTTPSrc * src)
312 {
313   src->interrupted = FALSE;
314   src->retry = FALSE;
315   src->have_size = FALSE;
316   src->seekable = FALSE;
317   src->read_position = 0;
318   src->request_position = 0;
319
320   gst_caps_replace (&src->src_caps, NULL);
321   g_free (src->iradio_name);
322   src->iradio_name = NULL;
323   g_free (src->iradio_genre);
324   src->iradio_genre = NULL;
325   g_free (src->iradio_url);
326   src->iradio_url = NULL;
327   g_free (src->iradio_title);
328   src->iradio_title = NULL;
329 }
330
331 static void
332 gst_soup_http_src_init (GstSoupHTTPSrc * src, GstSoupHTTPSrcClass * g_class)
333 {
334   const gchar *proxy;
335
336   src->location = NULL;
337   src->automatic_redirect = TRUE;
338   src->user_agent = g_strdup (DEFAULT_USER_AGENT);
339   src->user_id = NULL;
340   src->user_pw = NULL;
341   src->proxy_id = NULL;
342   src->proxy_pw = NULL;
343   src->cookies = NULL;
344   src->iradio_mode = FALSE;
345   src->loop = NULL;
346   src->context = NULL;
347   src->session = NULL;
348   src->msg = NULL;
349   proxy = g_getenv ("http_proxy");
350   if (proxy && !gst_soup_http_src_set_proxy (src, proxy)) {
351     GST_WARNING_OBJECT (src,
352         "The proxy in the http_proxy env var (\"%s\") cannot be parsed.",
353         proxy);
354   }
355
356   gst_soup_http_src_reset (src);
357 }
358
359 static void
360 gst_soup_http_src_finalize (GObject * gobject)
361 {
362   GstSoupHTTPSrc *src = GST_SOUP_HTTP_SRC (gobject);
363
364   GST_DEBUG_OBJECT (src, "finalize");
365
366   g_free (src->location);
367   g_free (src->user_agent);
368   if (src->proxy != NULL) {
369     soup_uri_free (src->proxy);
370   }
371   g_free (src->user_id);
372   g_free (src->user_pw);
373   g_free (src->proxy_id);
374   g_free (src->proxy_pw);
375   g_strfreev (src->cookies);
376
377   G_OBJECT_CLASS (parent_class)->finalize (gobject);
378 }
379
380 static void
381 gst_soup_http_src_set_property (GObject * object, guint prop_id,
382     const GValue * value, GParamSpec * pspec)
383 {
384   GstSoupHTTPSrc *src = GST_SOUP_HTTP_SRC (object);
385
386   switch (prop_id) {
387     case PROP_LOCATION:
388     {
389       const gchar *location;
390
391       location = g_value_get_string (value);
392
393       if (location == NULL) {
394         GST_WARNING ("location property cannot be NULL");
395         goto done;
396       }
397       if (!gst_soup_http_src_set_location (src, location)) {
398         GST_WARNING ("badly formatted location");
399         goto done;
400       }
401       break;
402     }
403     case PROP_USER_AGENT:
404       if (src->user_agent)
405         g_free (src->user_agent);
406       src->user_agent = g_value_dup_string (value);
407       break;
408     case PROP_IRADIO_MODE:
409       src->iradio_mode = g_value_get_boolean (value);
410       break;
411     case PROP_AUTOMATIC_REDIRECT:
412       src->automatic_redirect = g_value_get_boolean (value);
413       break;
414     case PROP_PROXY:
415     {
416       const gchar *proxy;
417
418       proxy = g_value_get_string (value);
419
420       if (proxy == NULL) {
421         GST_WARNING ("proxy property cannot be NULL");
422         goto done;
423       }
424       if (!gst_soup_http_src_set_proxy (src, proxy)) {
425         GST_WARNING ("badly formatted proxy URI");
426         goto done;
427       }
428       break;
429     }
430     case PROP_COOKIES:
431       g_strfreev (src->cookies);
432       src->cookies = g_strdupv (g_value_get_boxed (value));
433       break;
434     case PROP_IS_LIVE:
435       gst_base_src_set_live (GST_BASE_SRC (src), g_value_get_boolean (value));
436       break;
437     case PROP_USER_ID:
438       if (src->user_id)
439         g_free (src->user_id);
440       src->user_id = g_value_dup_string (value);
441       break;
442     case PROP_USER_PW:
443       if (src->user_pw)
444         g_free (src->user_pw);
445       src->user_pw = g_value_dup_string (value);
446       break;
447     case PROP_PROXY_ID:
448       if (src->proxy_id)
449         g_free (src->proxy_id);
450       src->proxy_id = g_value_dup_string (value);
451       break;
452     case PROP_PROXY_PW:
453       if (src->proxy_pw)
454         g_free (src->proxy_pw);
455       src->proxy_pw = g_value_dup_string (value);
456       break;
457     case PROP_TIMEOUT:
458       src->timeout = g_value_get_uint (value);
459       break;
460     case PROP_EXTRA_HEADERS:{
461       const GstStructure *s = gst_value_get_structure (value);
462
463       if (src->extra_headers)
464         gst_structure_free (src->extra_headers);
465
466       src->extra_headers = s ? gst_structure_copy (s) : NULL;
467       break;
468     }
469     default:
470       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
471       break;
472   }
473 done:
474   return;
475 }
476
477 static void
478 gst_soup_http_src_get_property (GObject * object, guint prop_id,
479     GValue * value, GParamSpec * pspec)
480 {
481   GstSoupHTTPSrc *src = GST_SOUP_HTTP_SRC (object);
482
483   switch (prop_id) {
484     case PROP_LOCATION:
485       g_value_set_string (value, src->location);
486       break;
487     case PROP_USER_AGENT:
488       g_value_set_string (value, src->user_agent);
489       break;
490     case PROP_AUTOMATIC_REDIRECT:
491       g_value_set_boolean (value, src->automatic_redirect);
492       break;
493     case PROP_PROXY:
494       if (src->proxy == NULL)
495         g_value_set_static_string (value, "");
496       else {
497         char *proxy = soup_uri_to_string (src->proxy, FALSE);
498
499         g_value_set_string (value, proxy);
500         g_free (proxy);
501       }
502       break;
503     case PROP_COOKIES:
504       g_value_set_boxed (value, g_strdupv (src->cookies));
505       break;
506     case PROP_IS_LIVE:
507       g_value_set_boolean (value, gst_base_src_is_live (GST_BASE_SRC (src)));
508       break;
509     case PROP_IRADIO_MODE:
510       g_value_set_boolean (value, src->iradio_mode);
511       break;
512     case PROP_IRADIO_NAME:
513       g_value_set_string (value, src->iradio_name);
514       break;
515     case PROP_IRADIO_GENRE:
516       g_value_set_string (value, src->iradio_genre);
517       break;
518     case PROP_IRADIO_URL:
519       g_value_set_string (value, src->iradio_url);
520       break;
521     case PROP_IRADIO_TITLE:
522       g_value_set_string (value, src->iradio_title);
523       break;
524     case PROP_USER_ID:
525       g_value_set_string (value, src->user_id);
526       break;
527     case PROP_USER_PW:
528       g_value_set_string (value, src->user_pw);
529       break;
530     case PROP_PROXY_ID:
531       g_value_set_string (value, src->proxy_id);
532       break;
533     case PROP_PROXY_PW:
534       g_value_set_string (value, src->proxy_pw);
535       break;
536     case PROP_TIMEOUT:
537       g_value_set_uint (value, src->timeout);
538       break;
539     case PROP_EXTRA_HEADERS:
540       gst_value_set_structure (value, src->extra_headers);
541       break;
542     default:
543       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
544       break;
545   }
546 }
547
548 static gchar *
549 gst_soup_http_src_unicodify (const gchar * str)
550 {
551   const gchar *env_vars[] = { "GST_ICY_TAG_ENCODING",
552     "GST_TAG_ENCODING", NULL
553   };
554
555   return gst_tag_freeform_string_to_utf8 (str, -1, env_vars);
556 }
557
558 static void
559 gst_soup_http_src_cancel_message (GstSoupHTTPSrc * src)
560 {
561   if (src->msg != NULL) {
562     src->session_io_status = GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_CANCELLED;
563     soup_session_cancel_message (src->session, src->msg, SOUP_STATUS_CANCELLED);
564   }
565   src->session_io_status = GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_IDLE;
566   src->msg = NULL;
567 }
568
569 static void
570 gst_soup_http_src_queue_message (GstSoupHTTPSrc * src)
571 {
572   soup_session_queue_message (src->session, src->msg,
573       (SoupSessionCallback) gst_soup_http_src_response_cb, src);
574   src->session_io_status = GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_QUEUED;
575 }
576
577 static gboolean
578 gst_soup_http_src_add_range_header (GstSoupHTTPSrc * src, guint64 offset)
579 {
580   gchar buf[64];
581
582   gint rc;
583
584   soup_message_headers_remove (src->msg->request_headers, "Range");
585   if (offset) {
586     rc = g_snprintf (buf, sizeof (buf), "bytes=%" G_GUINT64_FORMAT "-", offset);
587     if (rc > sizeof (buf) || rc < 0)
588       return FALSE;
589     soup_message_headers_append (src->msg->request_headers, "Range", buf);
590   }
591   src->read_position = offset;
592   return TRUE;
593 }
594
595 static gboolean
596 _append_extra_header (GQuark field_id, const GValue * value, gpointer user_data)
597 {
598   GstSoupHTTPSrc *src = GST_SOUP_HTTP_SRC (user_data);
599   const gchar *field_name = g_quark_to_string (field_id);
600   gchar *field_content = NULL;
601
602   if (G_VALUE_TYPE (value) == G_TYPE_STRING) {
603     field_content = g_value_dup_string (value);
604   } else {
605     GValue dest = { 0, };
606
607     g_value_init (&dest, G_TYPE_STRING);
608     if (g_value_transform (value, &dest)) {
609       field_content = g_value_dup_string (&dest);
610     }
611   }
612
613   if (field_content == NULL) {
614     GST_ERROR_OBJECT (src, "extra-headers field '%s' contains no value "
615         "or can't be converted to a string", field_name);
616     return FALSE;
617   }
618
619   GST_DEBUG_OBJECT (src, "Appending extra header: \"%s: %s\"", field_name,
620       field_content);
621   soup_message_headers_append (src->msg->request_headers, field_name,
622       field_content);
623
624   g_free (field_content);
625
626   return TRUE;
627 }
628
629 static gboolean
630 _append_extra_headers (GQuark field_id, const GValue * value,
631     gpointer user_data)
632 {
633   if (G_VALUE_TYPE (value) == GST_TYPE_ARRAY) {
634     guint n = gst_value_array_get_size (value);
635     guint i;
636
637     for (i = 0; i < n; i++) {
638       const GValue *v = gst_value_array_get_value (value, i);
639
640       if (!_append_extra_header (field_id, v, user_data))
641         return FALSE;
642     }
643   } else if (G_VALUE_TYPE (value) == GST_TYPE_LIST) {
644     guint n = gst_value_list_get_size (value);
645     guint i;
646
647     for (i = 0; i < n; i++) {
648       const GValue *v = gst_value_list_get_value (value, i);
649
650       if (!_append_extra_header (field_id, v, user_data))
651         return FALSE;
652     }
653   } else {
654     return _append_extra_header (field_id, value, user_data);
655   }
656
657   return TRUE;
658 }
659
660
661 static gboolean
662 gst_soup_http_src_add_extra_headers (GstSoupHTTPSrc * src)
663 {
664   if (!src->extra_headers)
665     return TRUE;
666
667   return gst_structure_foreach (src->extra_headers, _append_extra_headers, src);
668 }
669
670
671 static void
672 gst_soup_http_src_session_unpause_message (GstSoupHTTPSrc * src)
673 {
674   soup_session_unpause_message (src->session, src->msg);
675 }
676
677 static void
678 gst_soup_http_src_session_pause_message (GstSoupHTTPSrc * src)
679 {
680   soup_session_pause_message (src->session, src->msg);
681 }
682
683 static void
684 gst_soup_http_src_session_close (GstSoupHTTPSrc * src)
685 {
686   if (src->session) {
687     soup_session_abort (src->session);  /* This unrefs the message. */
688     g_object_unref (src->session);
689     src->session = NULL;
690     src->msg = NULL;
691   }
692 }
693
694 static void
695 gst_soup_http_src_authenticate_cb (SoupSession * session, SoupMessage * msg,
696     SoupAuth * auth, gboolean retrying, GstSoupHTTPSrc * src)
697 {
698   if (!retrying) {
699     /* First time authentication only, if we fail and are called again with retry true fall through */
700     if (msg->status_code == SOUP_STATUS_UNAUTHORIZED) {
701       if (src->user_id && src->user_pw)
702         soup_auth_authenticate (auth, src->user_id, src->user_pw);
703     } else if (msg->status_code == SOUP_STATUS_PROXY_AUTHENTICATION_REQUIRED) {
704       if (src->proxy_id && src->proxy_pw)
705         soup_auth_authenticate (auth, src->proxy_id, src->proxy_pw);
706     }
707   }
708 }
709
710 static void
711 gst_soup_http_src_headers_foreach (const gchar * name, const gchar * val,
712     gpointer src)
713 {
714   GST_DEBUG_OBJECT (src, " %s: %s", name, val);
715 }
716
717 static void
718 gst_soup_http_src_got_headers_cb (SoupMessage * msg, GstSoupHTTPSrc * src)
719 {
720   const char *value;
721   GstTagList *tag_list;
722   GstBaseSrc *basesrc;
723   guint64 newsize;
724   GHashTable *params = NULL;
725
726   GST_DEBUG_OBJECT (src, "got headers:");
727   soup_message_headers_foreach (msg->response_headers,
728       gst_soup_http_src_headers_foreach, src);
729
730   if (src->automatic_redirect && SOUP_STATUS_IS_REDIRECTION (msg->status_code)) {
731     GST_DEBUG_OBJECT (src, "%u redirect to \"%s\"", msg->status_code,
732         soup_message_headers_get (msg->response_headers, "Location"));
733     return;
734   }
735
736   if (msg->status_code == SOUP_STATUS_UNAUTHORIZED)
737     return;
738
739   src->session_io_status = GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_RUNNING;
740
741   /* Parse Content-Length. */
742   if (soup_message_headers_get_encoding (msg->response_headers) ==
743       SOUP_ENCODING_CONTENT_LENGTH) {
744     newsize = src->request_position +
745         soup_message_headers_get_content_length (msg->response_headers);
746     if (!src->have_size || (src->content_size != newsize)) {
747       src->content_size = newsize;
748       src->have_size = TRUE;
749       src->seekable = TRUE;
750       GST_DEBUG_OBJECT (src, "size = %" G_GUINT64_FORMAT, src->content_size);
751
752       basesrc = GST_BASE_SRC_CAST (src);
753       gst_segment_set_duration (&basesrc->segment, GST_FORMAT_BYTES,
754           src->content_size);
755       gst_element_post_message (GST_ELEMENT (src),
756           gst_message_new_duration (GST_OBJECT (src), GST_FORMAT_BYTES,
757               src->content_size));
758     }
759   }
760
761   /* Icecast stuff */
762   tag_list = gst_tag_list_new ();
763
764   if ((value =
765           soup_message_headers_get (msg->response_headers,
766               "icy-metaint")) != NULL) {
767     gint icy_metaint = atoi (value);
768
769     GST_DEBUG_OBJECT (src, "icy-metaint: %s (parsed: %d)", value, icy_metaint);
770     if (icy_metaint > 0) {
771       if (src->src_caps)
772         gst_caps_unref (src->src_caps);
773
774       src->src_caps = gst_caps_new_simple ("application/x-icy",
775           "metadata-interval", G_TYPE_INT, icy_metaint, NULL);
776     }
777   }
778   if ((value =
779           soup_message_headers_get_content_type (msg->response_headers,
780               &params)) != NULL) {
781     GST_DEBUG_OBJECT (src, "Content-Type: %s", value);
782     if (g_ascii_strcasecmp (value, "audio/L16") == 0) {
783       gint channels = 2;
784       gint rate = 44100;
785       char *param;
786
787       if (src->src_caps)
788         gst_caps_unref (src->src_caps);
789
790       param = g_hash_table_lookup (params, "channels");
791       if (param != NULL)
792         channels = atol (param);
793
794       param = g_hash_table_lookup (params, "rate");
795       if (param != NULL)
796         rate = atol (param);
797
798       src->src_caps = gst_caps_new_simple ("audio/x-raw-int",
799           "channels", G_TYPE_INT, channels,
800           "rate", G_TYPE_INT, rate,
801           "width", G_TYPE_INT, 16,
802           "depth", G_TYPE_INT, 16,
803           "signed", G_TYPE_BOOLEAN, TRUE,
804           "endianness", G_TYPE_INT, G_BIG_ENDIAN, NULL);
805     } else {
806       /* Set the Content-Type field on the caps */
807       if (src->src_caps)
808         gst_caps_set_simple (src->src_caps, "content-type", G_TYPE_STRING,
809             value, NULL);
810     }
811   }
812
813   if (params != NULL)
814     g_hash_table_destroy (params);
815
816   if ((value =
817           soup_message_headers_get (msg->response_headers,
818               "icy-name")) != NULL) {
819     g_free (src->iradio_name);
820     src->iradio_name = gst_soup_http_src_unicodify (value);
821     if (src->iradio_name) {
822       g_object_notify (G_OBJECT (src), "iradio-name");
823       gst_tag_list_add (tag_list, GST_TAG_MERGE_REPLACE, GST_TAG_ORGANIZATION,
824           src->iradio_name, NULL);
825     }
826   }
827   if ((value =
828           soup_message_headers_get (msg->response_headers,
829               "icy-genre")) != NULL) {
830     g_free (src->iradio_genre);
831     src->iradio_genre = gst_soup_http_src_unicodify (value);
832     if (src->iradio_genre) {
833       g_object_notify (G_OBJECT (src), "iradio-genre");
834       gst_tag_list_add (tag_list, GST_TAG_MERGE_REPLACE, GST_TAG_GENRE,
835           src->iradio_genre, NULL);
836     }
837   }
838   if ((value = soup_message_headers_get (msg->response_headers, "icy-url"))
839       != NULL) {
840     g_free (src->iradio_url);
841     src->iradio_url = gst_soup_http_src_unicodify (value);
842     if (src->iradio_url) {
843       g_object_notify (G_OBJECT (src), "iradio-url");
844       gst_tag_list_add (tag_list, GST_TAG_MERGE_REPLACE, GST_TAG_LOCATION,
845           src->iradio_url, NULL);
846     }
847   }
848   if (!gst_tag_list_is_empty (tag_list)) {
849     GST_DEBUG_OBJECT (src,
850         "calling gst_element_found_tags with %" GST_PTR_FORMAT, tag_list);
851     gst_element_found_tags (GST_ELEMENT_CAST (src), tag_list);
852   } else {
853     gst_tag_list_free (tag_list);
854   }
855
856   /* Handle HTTP errors. */
857   gst_soup_http_src_parse_status (msg, src);
858
859   /* Check if Range header was respected. */
860   if (src->ret == GST_FLOW_CUSTOM_ERROR &&
861       src->read_position && msg->status_code != SOUP_STATUS_PARTIAL_CONTENT) {
862     src->seekable = FALSE;
863     GST_ELEMENT_ERROR (src, RESOURCE, SEEK,
864         (_("Server does not support seeking.")),
865         ("Server does not accept Range HTTP header, URL: ", src->location));
866     src->ret = GST_FLOW_ERROR;
867   }
868 }
869
870 /* Have body. Signal EOS. */
871 static void
872 gst_soup_http_src_got_body_cb (SoupMessage * msg, GstSoupHTTPSrc * src)
873 {
874   if (G_UNLIKELY (msg != src->msg)) {
875     GST_DEBUG_OBJECT (src, "got body, but not for current message");
876     return;
877   }
878   if (G_UNLIKELY (src->session_io_status !=
879           GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_RUNNING)) {
880     /* Probably a redirect. */
881     return;
882   }
883   GST_DEBUG_OBJECT (src, "got body");
884   src->ret = GST_FLOW_UNEXPECTED;
885   if (src->loop)
886     g_main_loop_quit (src->loop);
887   gst_soup_http_src_session_pause_message (src);
888 }
889
890 /* Finished. Signal EOS. */
891 static void
892 gst_soup_http_src_finished_cb (SoupMessage * msg, GstSoupHTTPSrc * src)
893 {
894   if (G_UNLIKELY (msg != src->msg)) {
895     GST_DEBUG_OBJECT (src, "finished, but not for current message");
896     return;
897   }
898   GST_DEBUG_OBJECT (src, "finished");
899   src->ret = GST_FLOW_UNEXPECTED;
900   if (src->session_io_status == GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_CANCELLED) {
901     /* gst_soup_http_src_cancel_message() triggered this; probably a seek
902      * that occurred in the QUEUEING state; i.e. before the connection setup
903      * was complete. Do nothing */
904   } else if (src->session_io_status ==
905       GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_RUNNING && src->read_position > 0) {
906     /* The server disconnected while streaming. Reconnect and seeking to the
907      * last location. */
908     src->retry = TRUE;
909     src->ret = GST_FLOW_CUSTOM_ERROR;
910   } else if (G_UNLIKELY (src->session_io_status !=
911           GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_RUNNING)) {
912     /* FIXME: reason_phrase is not translated, add proper error message */
913     GST_ELEMENT_ERROR (src, RESOURCE, NOT_FOUND,
914         ("%s", msg->reason_phrase),
915         ("libsoup status code %d", msg->status_code));
916   }
917   if (src->loop)
918     g_main_loop_quit (src->loop);
919 }
920
921 /* Buffer lifecycle management.
922  *
923  * gst_soup_http_src_create() runs the GMainLoop for this element, to let
924  * Soup take control.
925  * A GstBuffer is allocated in gst_soup_http_src_chunk_allocator() and
926  * associated with a SoupBuffer.
927  * Soup reads HTTP data in the GstBuffer's data buffer.
928  * The gst_soup_http_src_got_chunk_cb() is then called with the SoupBuffer.
929  * That sets gst_soup_http_src_create()'s return argument to the GstBuffer,
930  * increments its refcount (to 2), pauses the flow of data from the HTTP
931  * source to prevent gst_soup_http_src_got_chunk_cb() from being called
932  * again and breaks out of the GMainLoop.
933  * Because the SOUP_MESSAGE_OVERWRITE_CHUNKS flag is set, Soup frees the
934  * SoupBuffer and calls gst_soup_http_src_chunk_free(), which decrements the
935  * refcount (to 1).
936  * gst_soup_http_src_create() returns the GstBuffer. It will be freed by a
937  * downstream element.
938  * If Soup fails to read HTTP data, it does not call
939  * gst_soup_http_src_got_chunk_cb(), but still frees the SoupBuffer and
940  * calls gst_soup_http_src_chunk_free(), which decrements the GstBuffer's
941  * refcount to 0, freeing it.
942  */
943
944 static void
945 gst_soup_http_src_chunk_free (gpointer gstbuf)
946 {
947   gst_buffer_unref (GST_BUFFER_CAST (gstbuf));
948 }
949
950 static SoupBuffer *
951 gst_soup_http_src_chunk_allocator (SoupMessage * msg, gsize max_len,
952     gpointer user_data)
953 {
954   GstSoupHTTPSrc *src = (GstSoupHTTPSrc *) user_data;
955   GstBaseSrc *basesrc = GST_BASE_SRC_CAST (src);
956   GstBuffer *gstbuf;
957   SoupBuffer *soupbuf;
958   gsize length;
959   GstFlowReturn rc;
960
961   if (max_len)
962     length = MIN (basesrc->blocksize, max_len);
963   else
964     length = basesrc->blocksize;
965   GST_DEBUG_OBJECT (src, "alloc %" G_GSIZE_FORMAT " bytes <= %" G_GSIZE_FORMAT,
966       length, max_len);
967
968
969   rc = gst_pad_alloc_buffer (GST_BASE_SRC_PAD (basesrc),
970       GST_BUFFER_OFFSET_NONE, length,
971       src->src_caps ? src->src_caps :
972       GST_PAD_CAPS (GST_BASE_SRC_PAD (basesrc)), &gstbuf);
973   if (G_UNLIKELY (rc != GST_FLOW_OK)) {
974     /* Failed to allocate buffer. Stall SoupSession and return error code
975      * to create(). */
976     src->ret = rc;
977     g_main_loop_quit (src->loop);
978     return NULL;
979   }
980
981   soupbuf = soup_buffer_new_with_owner (GST_BUFFER_DATA (gstbuf), length,
982       gstbuf, gst_soup_http_src_chunk_free);
983
984   return soupbuf;
985 }
986
987 static void
988 gst_soup_http_src_got_chunk_cb (SoupMessage * msg, SoupBuffer * chunk,
989     GstSoupHTTPSrc * src)
990 {
991   GstBaseSrc *basesrc;
992   guint64 new_position;
993
994   if (G_UNLIKELY (msg != src->msg)) {
995     GST_DEBUG_OBJECT (src, "got chunk, but not for current message");
996     return;
997   }
998   if (G_UNLIKELY (src->session_io_status !=
999           GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_RUNNING)) {
1000     /* Probably a redirect. */
1001     return;
1002   }
1003   basesrc = GST_BASE_SRC_CAST (src);
1004   GST_DEBUG_OBJECT (src, "got chunk of %" G_GSIZE_FORMAT " bytes",
1005       chunk->length);
1006
1007   /* Extract the GstBuffer from the SoupBuffer and set its fields. */
1008   *src->outbuf = GST_BUFFER_CAST (soup_buffer_get_owner (chunk));
1009
1010   GST_BUFFER_SIZE (*src->outbuf) = chunk->length;
1011   GST_BUFFER_OFFSET (*src->outbuf) = basesrc->segment.last_stop;
1012
1013   gst_buffer_set_caps (*src->outbuf,
1014       (src->src_caps) ? src->src_caps :
1015       GST_PAD_CAPS (GST_BASE_SRC_PAD (basesrc)));
1016
1017   gst_buffer_ref (*src->outbuf);
1018
1019   new_position = src->read_position + chunk->length;
1020   if (G_LIKELY (src->request_position == src->read_position))
1021     src->request_position = new_position;
1022   src->read_position = new_position;
1023
1024   src->ret = GST_FLOW_OK;
1025   g_main_loop_quit (src->loop);
1026   gst_soup_http_src_session_pause_message (src);
1027 }
1028
1029 static void
1030 gst_soup_http_src_response_cb (SoupSession * session, SoupMessage * msg,
1031     GstSoupHTTPSrc * src)
1032 {
1033   if (G_UNLIKELY (msg != src->msg)) {
1034     GST_DEBUG_OBJECT (src, "got response %d: %s, but not for current message",
1035         msg->status_code, msg->reason_phrase);
1036     return;
1037   }
1038   if (G_UNLIKELY (src->session_io_status !=
1039           GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_RUNNING)
1040       && SOUP_STATUS_IS_REDIRECTION (msg->status_code)) {
1041     /* Ignore redirections. */
1042     return;
1043   }
1044   GST_DEBUG_OBJECT (src, "got response %d: %s", msg->status_code,
1045       msg->reason_phrase);
1046   if (src->session_io_status == GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_RUNNING &&
1047       src->read_position > 0) {
1048     /* The server disconnected while streaming. Reconnect and seeking to the
1049      * last location. */
1050     src->retry = TRUE;
1051   } else
1052     gst_soup_http_src_parse_status (msg, src);
1053   /* The session's SoupMessage object expires after this callback returns. */
1054   src->msg = NULL;
1055   g_main_loop_quit (src->loop);
1056 }
1057
1058 #define SOUP_HTTP_SRC_ERROR(src,soup_msg,cat,code,error_message)     \
1059   GST_ELEMENT_ERROR ((src), cat, code, ("%s", error_message),        \
1060       ("%s (%d), URL: %s", (soup_msg)->reason_phrase,                \
1061           (soup_msg)->status_code, (src)->location));
1062
1063 static void
1064 gst_soup_http_src_parse_status (SoupMessage * msg, GstSoupHTTPSrc * src)
1065 {
1066   if (SOUP_STATUS_IS_TRANSPORT_ERROR (msg->status_code)) {
1067     switch (msg->status_code) {
1068       case SOUP_STATUS_CANT_RESOLVE:
1069       case SOUP_STATUS_CANT_RESOLVE_PROXY:
1070         SOUP_HTTP_SRC_ERROR (src, msg, RESOURCE, NOT_FOUND,
1071             _("Could not resolve server name."));
1072         src->ret = GST_FLOW_ERROR;
1073         break;
1074       case SOUP_STATUS_CANT_CONNECT:
1075       case SOUP_STATUS_CANT_CONNECT_PROXY:
1076         SOUP_HTTP_SRC_ERROR (src, msg, RESOURCE, OPEN_READ,
1077             _("Could not establish connection to server."));
1078         src->ret = GST_FLOW_ERROR;
1079         break;
1080       case SOUP_STATUS_SSL_FAILED:
1081         SOUP_HTTP_SRC_ERROR (src, msg, RESOURCE, OPEN_READ,
1082             _("Secure connection setup failed."));
1083         src->ret = GST_FLOW_ERROR;
1084         break;
1085       case SOUP_STATUS_IO_ERROR:
1086         SOUP_HTTP_SRC_ERROR (src, msg, RESOURCE, READ,
1087             _("A network error occured, or the server closed the connection "
1088                 "unexpectedly."));
1089         src->ret = GST_FLOW_ERROR;
1090         break;
1091       case SOUP_STATUS_MALFORMED:
1092         SOUP_HTTP_SRC_ERROR (src, msg, RESOURCE, READ,
1093             _("Server sent bad data."));
1094         src->ret = GST_FLOW_ERROR;
1095         break;
1096       case SOUP_STATUS_CANCELLED:
1097         /* No error message when interrupted by program. */
1098         break;
1099     }
1100   } else if (SOUP_STATUS_IS_CLIENT_ERROR (msg->status_code) ||
1101       SOUP_STATUS_IS_REDIRECTION (msg->status_code) ||
1102       SOUP_STATUS_IS_SERVER_ERROR (msg->status_code)) {
1103     /* Report HTTP error. */
1104     /* FIXME: reason_phrase is not translated and not suitable for user
1105      * error dialog according to libsoup documentation.
1106      * FIXME: error code (OPEN_READ vs. READ) should depend on http status? */
1107     GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ,
1108         ("%s", msg->reason_phrase),
1109         ("%s (%d), URL: %s", msg->reason_phrase, msg->status_code,
1110             src->location));
1111     src->ret = GST_FLOW_ERROR;
1112   }
1113 }
1114
1115 static gboolean
1116 gst_soup_http_src_build_message (GstSoupHTTPSrc * src)
1117 {
1118   src->msg = soup_message_new (SOUP_METHOD_GET, src->location);
1119   if (!src->msg) {
1120     GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ,
1121         ("Error parsing URL."), ("URL: %s", src->location));
1122     return FALSE;
1123   }
1124   src->session_io_status = GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_IDLE;
1125   soup_message_headers_append (src->msg->request_headers, "Connection",
1126       "close");
1127   if (src->iradio_mode) {
1128     soup_message_headers_append (src->msg->request_headers, "icy-metadata",
1129         "1");
1130   }
1131   if (src->cookies) {
1132     gchar **cookie;
1133
1134     for (cookie = src->cookies; *cookie != NULL; cookie++) {
1135       soup_message_headers_append (src->msg->request_headers, "Cookie",
1136           *cookie);
1137     }
1138   }
1139   soup_message_headers_append (src->msg->request_headers,
1140       "transferMode.dlna.org", "Streaming");
1141   src->retry = FALSE;
1142
1143   g_signal_connect (src->msg, "got_headers",
1144       G_CALLBACK (gst_soup_http_src_got_headers_cb), src);
1145   g_signal_connect (src->msg, "got_body",
1146       G_CALLBACK (gst_soup_http_src_got_body_cb), src);
1147   g_signal_connect (src->msg, "finished",
1148       G_CALLBACK (gst_soup_http_src_finished_cb), src);
1149   g_signal_connect (src->msg, "got_chunk",
1150       G_CALLBACK (gst_soup_http_src_got_chunk_cb), src);
1151   soup_message_set_flags (src->msg, SOUP_MESSAGE_OVERWRITE_CHUNKS |
1152       (src->automatic_redirect ? 0 : SOUP_MESSAGE_NO_REDIRECT));
1153   soup_message_set_chunk_allocator (src->msg,
1154       gst_soup_http_src_chunk_allocator, src, NULL);
1155   gst_soup_http_src_add_range_header (src, src->request_position);
1156
1157   gst_soup_http_src_add_extra_headers (src);
1158
1159   GST_DEBUG_OBJECT (src, "request headers:");
1160   soup_message_headers_foreach (src->msg->request_headers,
1161       gst_soup_http_src_headers_foreach, src);
1162
1163   return TRUE;
1164 }
1165
1166 static GstFlowReturn
1167 gst_soup_http_src_create (GstPushSrc * psrc, GstBuffer ** outbuf)
1168 {
1169   GstSoupHTTPSrc *src;
1170
1171   src = GST_SOUP_HTTP_SRC (psrc);
1172
1173   if (src->msg && (src->request_position != src->read_position)) {
1174     if (src->session_io_status == GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_IDLE) {
1175       gst_soup_http_src_add_range_header (src, src->request_position);
1176     } else {
1177       GST_DEBUG_OBJECT (src, "Seek from position %" G_GUINT64_FORMAT
1178           " to %" G_GUINT64_FORMAT ": requeueing connection request",
1179           src->read_position, src->request_position);
1180       gst_soup_http_src_cancel_message (src);
1181     }
1182   }
1183   if (!src->msg)
1184     if (!gst_soup_http_src_build_message (src))
1185       return GST_FLOW_ERROR;
1186
1187   src->ret = GST_FLOW_CUSTOM_ERROR;
1188   src->outbuf = outbuf;
1189   do {
1190     if (src->interrupted) {
1191       GST_DEBUG_OBJECT (src, "interrupted");
1192       break;
1193     }
1194     if (src->retry) {
1195       GST_DEBUG_OBJECT (src, "Reconnecting");
1196       if (!gst_soup_http_src_build_message (src))
1197         return GST_FLOW_ERROR;
1198       src->retry = FALSE;
1199       continue;
1200     }
1201     if (!src->msg) {
1202       GST_DEBUG_OBJECT (src, "EOS reached");
1203       break;
1204     }
1205
1206     switch (src->session_io_status) {
1207       case GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_IDLE:
1208         GST_DEBUG_OBJECT (src, "Queueing connection request");
1209         gst_soup_http_src_queue_message (src);
1210         break;
1211       case GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_QUEUED:
1212         break;
1213       case GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_RUNNING:
1214         gst_soup_http_src_session_unpause_message (src);
1215         break;
1216       case GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_CANCELLED:
1217         /* Impossible. */
1218         break;
1219     }
1220
1221     if (src->ret == GST_FLOW_CUSTOM_ERROR)
1222       g_main_loop_run (src->loop);
1223   } while (src->ret == GST_FLOW_CUSTOM_ERROR);
1224
1225   if (src->ret == GST_FLOW_CUSTOM_ERROR)
1226     src->ret = GST_FLOW_UNEXPECTED;
1227   return src->ret;
1228 }
1229
1230 static gboolean
1231 gst_soup_http_src_start (GstBaseSrc * bsrc)
1232 {
1233   GstSoupHTTPSrc *src = GST_SOUP_HTTP_SRC (bsrc);
1234
1235   GST_DEBUG_OBJECT (src, "start(\"%s\")", src->location);
1236
1237   if (!src->location) {
1238     GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, (_("No URL set.")),
1239         ("Missing location property"));
1240     return FALSE;
1241   }
1242
1243   src->context = g_main_context_new ();
1244
1245   src->loop = g_main_loop_new (src->context, TRUE);
1246   if (!src->loop) {
1247     GST_ELEMENT_ERROR (src, LIBRARY, INIT,
1248         (NULL), ("Failed to start GMainLoop"));
1249     g_main_context_unref (src->context);
1250     return FALSE;
1251   }
1252
1253   if (src->proxy == NULL) {
1254     src->session =
1255         soup_session_async_new_with_options (SOUP_SESSION_ASYNC_CONTEXT,
1256         src->context, SOUP_SESSION_USER_AGENT, src->user_agent,
1257         SOUP_SESSION_TIMEOUT, src->timeout,
1258 #ifdef HAVE_LIBSOUP_GNOME
1259         SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_PROXY_RESOLVER_GNOME,
1260 #endif
1261         NULL);
1262   } else {
1263     src->session =
1264         soup_session_async_new_with_options (SOUP_SESSION_ASYNC_CONTEXT,
1265         src->context, SOUP_SESSION_PROXY_URI, src->proxy,
1266         SOUP_SESSION_TIMEOUT, src->timeout,
1267         SOUP_SESSION_USER_AGENT, src->user_agent, NULL);
1268   }
1269
1270   if (!src->session) {
1271     GST_ELEMENT_ERROR (src, LIBRARY, INIT,
1272         (NULL), ("Failed to create async session"));
1273     return FALSE;
1274   }
1275
1276   g_signal_connect (src->session, "authenticate",
1277       G_CALLBACK (gst_soup_http_src_authenticate_cb), src);
1278   return TRUE;
1279 }
1280
1281 static gboolean
1282 gst_soup_http_src_stop (GstBaseSrc * bsrc)
1283 {
1284   GstSoupHTTPSrc *src;
1285
1286   src = GST_SOUP_HTTP_SRC (bsrc);
1287   GST_DEBUG_OBJECT (src, "stop()");
1288   gst_soup_http_src_session_close (src);
1289   if (src->loop) {
1290     g_main_loop_unref (src->loop);
1291     g_main_context_unref (src->context);
1292     src->loop = NULL;
1293     src->context = NULL;
1294   }
1295   if (src->extra_headers) {
1296     gst_structure_free (src->extra_headers);
1297     src->extra_headers = NULL;
1298   }
1299
1300   gst_soup_http_src_reset (src);
1301   return TRUE;
1302 }
1303
1304 /* Interrupt a blocking request. */
1305 static gboolean
1306 gst_soup_http_src_unlock (GstBaseSrc * bsrc)
1307 {
1308   GstSoupHTTPSrc *src;
1309
1310   src = GST_SOUP_HTTP_SRC (bsrc);
1311   GST_DEBUG_OBJECT (src, "unlock()");
1312
1313   src->interrupted = TRUE;
1314   if (src->loop)
1315     g_main_loop_quit (src->loop);
1316   return TRUE;
1317 }
1318
1319 /* Interrupt interrupt. */
1320 static gboolean
1321 gst_soup_http_src_unlock_stop (GstBaseSrc * bsrc)
1322 {
1323   GstSoupHTTPSrc *src;
1324
1325   src = GST_SOUP_HTTP_SRC (bsrc);
1326   GST_DEBUG_OBJECT (src, "unlock_stop()");
1327
1328   src->interrupted = FALSE;
1329   return TRUE;
1330 }
1331
1332 static gboolean
1333 gst_soup_http_src_get_size (GstBaseSrc * bsrc, guint64 * size)
1334 {
1335   GstSoupHTTPSrc *src;
1336
1337   src = GST_SOUP_HTTP_SRC (bsrc);
1338
1339   if (src->have_size) {
1340     GST_DEBUG_OBJECT (src, "get_size() = %" G_GUINT64_FORMAT,
1341         src->content_size);
1342     *size = src->content_size;
1343     return TRUE;
1344   }
1345   GST_DEBUG_OBJECT (src, "get_size() = FALSE");
1346   return FALSE;
1347 }
1348
1349 static gboolean
1350 gst_soup_http_src_is_seekable (GstBaseSrc * bsrc)
1351 {
1352   GstSoupHTTPSrc *src = GST_SOUP_HTTP_SRC (bsrc);
1353
1354   return src->seekable;
1355 }
1356
1357 static gboolean
1358 gst_soup_http_src_do_seek (GstBaseSrc * bsrc, GstSegment * segment)
1359 {
1360   GstSoupHTTPSrc *src = GST_SOUP_HTTP_SRC (bsrc);
1361
1362   GST_DEBUG_OBJECT (src, "do_seek(%" G_GUINT64_FORMAT ")", segment->start);
1363
1364   if (src->read_position == segment->start)
1365     return TRUE;
1366
1367   if (!src->seekable)
1368     return FALSE;
1369
1370   /* Wait for create() to handle the jump in offset. */
1371   src->request_position = segment->start;
1372   return TRUE;
1373 }
1374
1375 static gboolean
1376 gst_soup_http_src_set_location (GstSoupHTTPSrc * src, const gchar * uri)
1377 {
1378   if (src->location) {
1379     g_free (src->location);
1380     src->location = NULL;
1381   }
1382   src->location = g_strdup (uri);
1383
1384   return TRUE;
1385 }
1386
1387 static gboolean
1388 gst_soup_http_src_set_proxy (GstSoupHTTPSrc * src, const gchar * uri)
1389 {
1390   if (src->proxy) {
1391     soup_uri_free (src->proxy);
1392     src->proxy = NULL;
1393   }
1394   if (g_str_has_prefix (uri, "http://")) {
1395     src->proxy = soup_uri_new (uri);
1396   } else {
1397     gchar *new_uri = g_strconcat ("http://", uri, NULL);
1398
1399     src->proxy = soup_uri_new (new_uri);
1400     g_free (new_uri);
1401   }
1402
1403   return TRUE;
1404 }
1405
1406 static guint
1407 gst_soup_http_src_uri_get_type (void)
1408 {
1409   return GST_URI_SRC;
1410 }
1411
1412 static gchar **
1413 gst_soup_http_src_uri_get_protocols (void)
1414 {
1415   static const gchar *protocols[] = { "http", "https", NULL };
1416   return (gchar **) protocols;
1417 }
1418
1419 static const gchar *
1420 gst_soup_http_src_uri_get_uri (GstURIHandler * handler)
1421 {
1422   GstSoupHTTPSrc *src = GST_SOUP_HTTP_SRC (handler);
1423
1424   return src->location;
1425 }
1426
1427 static gboolean
1428 gst_soup_http_src_uri_set_uri (GstURIHandler * handler, const gchar * uri)
1429 {
1430   GstSoupHTTPSrc *src = GST_SOUP_HTTP_SRC (handler);
1431
1432   return gst_soup_http_src_set_location (src, uri);
1433 }
1434
1435 static void
1436 gst_soup_http_src_uri_handler_init (gpointer g_iface, gpointer iface_data)
1437 {
1438   GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface;
1439
1440   iface->get_type = gst_soup_http_src_uri_get_type;
1441   iface->get_protocols = gst_soup_http_src_uri_get_protocols;
1442   iface->get_uri = gst_soup_http_src_uri_get_uri;
1443   iface->set_uri = gst_soup_http_src_uri_set_uri;
1444 }
1445
1446 static gboolean
1447 plugin_init (GstPlugin * plugin)
1448 {
1449 #ifdef ENABLE_NLS
1450   GST_DEBUG ("binding text domain %s to locale dir %s", GETTEXT_PACKAGE,
1451       LOCALEDIR);
1452   bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR);
1453   bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
1454 #endif
1455
1456   return gst_element_register (plugin, "souphttpsrc", GST_RANK_PRIMARY,
1457       GST_TYPE_SOUP_HTTP_SRC);
1458 }
1459
1460 GST_PLUGIN_DEFINE (GST_VERSION_MAJOR,
1461     GST_VERSION_MINOR,
1462     "soup",
1463     "libsoup HTTP client src",
1464     plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, GST_PACKAGE_ORIGIN)