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