souphttpsrc: Add properties for selecting SSL/TLS certificate checking
[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-1.0 -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-1.0 -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-1.0 -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 #include <libsoup/soup.h>
81 #include "gstsouphttpsrc.h"
82 #include "gstsouputils.h"
83
84 #include <gst/tag/tag.h>
85
86 GST_DEBUG_CATEGORY_STATIC (souphttpsrc_debug);
87 #define GST_CAT_DEFAULT souphttpsrc_debug
88
89 static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
90     GST_PAD_SRC,
91     GST_PAD_ALWAYS,
92     GST_STATIC_CAPS_ANY);
93
94 enum
95 {
96   PROP_0,
97   PROP_LOCATION,
98   PROP_IS_LIVE,
99   PROP_USER_AGENT,
100   PROP_AUTOMATIC_REDIRECT,
101   PROP_PROXY,
102   PROP_USER_ID,
103   PROP_USER_PW,
104   PROP_PROXY_ID,
105   PROP_PROXY_PW,
106   PROP_COOKIES,
107   PROP_IRADIO_MODE,
108   PROP_TIMEOUT,
109   PROP_EXTRA_HEADERS,
110   PROP_SOUP_LOG_LEVEL,
111   PROP_COMPRESS,
112   PROP_KEEP_ALIVE,
113   PROP_SSL_STRICT,
114   PROP_SSL_CA_FILE,
115   PROP_SSL_USE_SYSTEM_CA_FILE
116 };
117
118 #define DEFAULT_USER_AGENT           "GStreamer souphttpsrc "
119 #define DEFAULT_IRADIO_MODE          TRUE
120 #define DEFAULT_SOUP_LOG_LEVEL       SOUP_LOGGER_LOG_HEADERS
121 #define DEFAULT_COMPRESS             FALSE
122 #define DEFAULT_KEEP_ALIVE           FALSE
123 #define DEFAULT_SSL_STRICT           TRUE
124 #define DEFAULT_SSL_CA_FILE          NULL
125 #define DEFAULT_SSL_USE_SYSTEM_CA_FILE TRUE
126
127 static void gst_soup_http_src_uri_handler_init (gpointer g_iface,
128     gpointer iface_data);
129 static void gst_soup_http_src_finalize (GObject * gobject);
130 static void gst_soup_http_src_dispose (GObject * gobject);
131
132 static void gst_soup_http_src_set_property (GObject * object, guint prop_id,
133     const GValue * value, GParamSpec * pspec);
134 static void gst_soup_http_src_get_property (GObject * object, guint prop_id,
135     GValue * value, GParamSpec * pspec);
136
137 static GstStateChangeReturn gst_soup_http_src_change_state (GstElement *
138     element, GstStateChange transition);
139 static GstFlowReturn gst_soup_http_src_create (GstPushSrc * psrc,
140     GstBuffer ** outbuf);
141 static gboolean gst_soup_http_src_start (GstBaseSrc * bsrc);
142 static gboolean gst_soup_http_src_stop (GstBaseSrc * bsrc);
143 static gboolean gst_soup_http_src_get_size (GstBaseSrc * bsrc, guint64 * size);
144 static gboolean gst_soup_http_src_is_seekable (GstBaseSrc * bsrc);
145 static gboolean gst_soup_http_src_do_seek (GstBaseSrc * bsrc,
146     GstSegment * segment);
147 static gboolean gst_soup_http_src_query (GstBaseSrc * bsrc, GstQuery * query);
148 static gboolean gst_soup_http_src_unlock (GstBaseSrc * bsrc);
149 static gboolean gst_soup_http_src_unlock_stop (GstBaseSrc * bsrc);
150 static gboolean gst_soup_http_src_set_location (GstSoupHTTPSrc * src,
151     const gchar * uri, GError ** error);
152 static gboolean gst_soup_http_src_set_proxy (GstSoupHTTPSrc * src,
153     const gchar * uri);
154 static char *gst_soup_http_src_unicodify (const char *str);
155 static gboolean gst_soup_http_src_build_message (GstSoupHTTPSrc * src,
156     const gchar * method);
157 static void gst_soup_http_src_cancel_message (GstSoupHTTPSrc * src);
158 static void gst_soup_http_src_queue_message (GstSoupHTTPSrc * src);
159 static gboolean gst_soup_http_src_add_range_header (GstSoupHTTPSrc * src,
160     guint64 offset, guint64 stop_offset);
161 static void gst_soup_http_src_session_unpause_message (GstSoupHTTPSrc * src);
162 static void gst_soup_http_src_session_pause_message (GstSoupHTTPSrc * src);
163 static gboolean gst_soup_http_src_session_open (GstSoupHTTPSrc * src);
164 static void gst_soup_http_src_session_close (GstSoupHTTPSrc * src);
165 static void gst_soup_http_src_parse_status (SoupMessage * msg,
166     GstSoupHTTPSrc * src);
167 static void gst_soup_http_src_chunk_free (gpointer gstbuf);
168 static SoupBuffer *gst_soup_http_src_chunk_allocator (SoupMessage * msg,
169     gsize max_len, gpointer user_data);
170 static void gst_soup_http_src_got_chunk_cb (SoupMessage * msg,
171     SoupBuffer * chunk, GstSoupHTTPSrc * src);
172 static void gst_soup_http_src_response_cb (SoupSession * session,
173     SoupMessage * msg, GstSoupHTTPSrc * src);
174 static void gst_soup_http_src_got_headers_cb (SoupMessage * msg,
175     GstSoupHTTPSrc * src);
176 static void gst_soup_http_src_got_body_cb (SoupMessage * msg,
177     GstSoupHTTPSrc * src);
178 static void gst_soup_http_src_finished_cb (SoupMessage * msg,
179     GstSoupHTTPSrc * src);
180 static void gst_soup_http_src_authenticate_cb (SoupSession * session,
181     SoupMessage * msg, SoupAuth * auth, gboolean retrying,
182     GstSoupHTTPSrc * src);
183
184 #define gst_soup_http_src_parent_class parent_class
185 G_DEFINE_TYPE_WITH_CODE (GstSoupHTTPSrc, gst_soup_http_src, GST_TYPE_PUSH_SRC,
186     G_IMPLEMENT_INTERFACE (GST_TYPE_URI_HANDLER,
187         gst_soup_http_src_uri_handler_init));
188
189 static void
190 gst_soup_http_src_class_init (GstSoupHTTPSrcClass * klass)
191 {
192   GObjectClass *gobject_class;
193   GstElementClass *gstelement_class;
194   GstBaseSrcClass *gstbasesrc_class;
195   GstPushSrcClass *gstpushsrc_class;
196
197   gobject_class = (GObjectClass *) klass;
198   gstelement_class = (GstElementClass *) klass;
199   gstbasesrc_class = (GstBaseSrcClass *) klass;
200   gstpushsrc_class = (GstPushSrcClass *) klass;
201
202   gobject_class->set_property = gst_soup_http_src_set_property;
203   gobject_class->get_property = gst_soup_http_src_get_property;
204   gobject_class->finalize = gst_soup_http_src_finalize;
205   gobject_class->dispose = gst_soup_http_src_dispose;
206
207   g_object_class_install_property (gobject_class,
208       PROP_LOCATION,
209       g_param_spec_string ("location", "Location",
210           "Location to read from", "",
211           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
212   g_object_class_install_property (gobject_class,
213       PROP_USER_AGENT,
214       g_param_spec_string ("user-agent", "User-Agent",
215           "Value of the User-Agent HTTP request header field",
216           DEFAULT_USER_AGENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
217   g_object_class_install_property (gobject_class,
218       PROP_AUTOMATIC_REDIRECT,
219       g_param_spec_boolean ("automatic-redirect", "automatic-redirect",
220           "Automatically follow HTTP redirects (HTTP Status Code 3xx)",
221           TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
222   g_object_class_install_property (gobject_class,
223       PROP_PROXY,
224       g_param_spec_string ("proxy", "Proxy",
225           "HTTP proxy server URI", "",
226           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
227   g_object_class_install_property (gobject_class,
228       PROP_USER_ID,
229       g_param_spec_string ("user-id", "user-id",
230           "HTTP location URI user id for authentication", "",
231           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
232   g_object_class_install_property (gobject_class, PROP_USER_PW,
233       g_param_spec_string ("user-pw", "user-pw",
234           "HTTP location URI user password for authentication", "",
235           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
236   g_object_class_install_property (gobject_class, PROP_PROXY_ID,
237       g_param_spec_string ("proxy-id", "proxy-id",
238           "HTTP proxy URI user id for authentication", "",
239           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
240   g_object_class_install_property (gobject_class, PROP_PROXY_PW,
241       g_param_spec_string ("proxy-pw", "proxy-pw",
242           "HTTP proxy URI user password for authentication", "",
243           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
244   g_object_class_install_property (gobject_class, PROP_COOKIES,
245       g_param_spec_boxed ("cookies", "Cookies", "HTTP request cookies",
246           G_TYPE_STRV, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
247   g_object_class_install_property (gobject_class, PROP_IS_LIVE,
248       g_param_spec_boolean ("is-live", "is-live", "Act like a live source",
249           FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
250   g_object_class_install_property (gobject_class, PROP_TIMEOUT,
251       g_param_spec_uint ("timeout", "timeout",
252           "Value in seconds to timeout a blocking I/O (0 = No timeout).", 0,
253           3600, 0, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
254   g_object_class_install_property (gobject_class, PROP_EXTRA_HEADERS,
255       g_param_spec_boxed ("extra-headers", "Extra Headers",
256           "Extra headers to append to the HTTP request",
257           GST_TYPE_STRUCTURE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
258   g_object_class_install_property (gobject_class, PROP_IRADIO_MODE,
259       g_param_spec_boolean ("iradio-mode", "iradio-mode",
260           "Enable internet radio mode (ask server to send shoutcast/icecast "
261           "metadata interleaved with the actual stream data)",
262           DEFAULT_IRADIO_MODE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
263
264  /**
265    * GstSoupHTTPSrc::http-log-level:
266    *
267    * If set and > 0, captures and dumps HTTP session data as
268    * log messages if log level >= GST_LEVEL_TRACE
269    *
270    * Since: 1.4
271    */
272   g_object_class_install_property (gobject_class, PROP_SOUP_LOG_LEVEL,
273       g_param_spec_enum ("http-log-level", "HTTP log level",
274           "Set log level for soup's HTTP session log",
275           SOUP_TYPE_LOGGER_LOG_LEVEL, DEFAULT_SOUP_LOG_LEVEL,
276           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
277
278  /**
279    * GstSoupHTTPSrc::compress:
280    *
281    * If set to %TRUE, souphttpsrc will automatically handle gzip
282    * and deflate Content-Encodings. This does not make much difference
283    * and causes more load for normal media files, but makes a real
284    * difference in size for plaintext files.
285    *
286    * Since: 1.4
287    */
288   g_object_class_install_property (gobject_class, PROP_COMPRESS,
289       g_param_spec_boolean ("compress", "Compress",
290           "Allow compressed content encodings",
291           DEFAULT_COMPRESS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
292
293  /**
294    * GstSoupHTTPSrc::keep-alive:
295    *
296    * If set to %TRUE, souphttpsrc will keep alive connections when being
297    * set to READY state and only will close connections when connecting
298    * to a different server or when going to NULL state..
299    *
300    * Since: 1.4
301    */
302   g_object_class_install_property (gobject_class, PROP_KEEP_ALIVE,
303       g_param_spec_boolean ("keep-alive", "keep-alive",
304           "Use HTTP persistent connections", DEFAULT_KEEP_ALIVE,
305           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
306
307  /**
308    * GstSoupHTTPSrc::ssl-strict:
309    *
310    * If set to %TRUE, souphttpsrc will reject all SSL certificates that
311    * are considered invalid.
312    *
313    * Since: 1.4
314    */
315   g_object_class_install_property (gobject_class, PROP_SSL_STRICT,
316       g_param_spec_boolean ("ssl-strict", "SSL Strict",
317           "Strict SSL certificate checking", DEFAULT_SSL_STRICT,
318           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
319
320  /**
321    * GstSoupHTTPSrc::ssl-ca-file:
322    *
323    * A SSL anchor CA file that should be used for checking certificates
324    * instead of the system CA file.
325    *
326    * Since: 1.4
327    */
328   g_object_class_install_property (gobject_class, PROP_SSL_CA_FILE,
329       g_param_spec_string ("ssl-ca-file", "SSL CA File",
330           "Location of a SSL anchor CA file to use", DEFAULT_SSL_CA_FILE,
331           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
332
333  /**
334    * GstSoupHTTPSrc::ssl-use-system-ca-file:
335    *
336    * If set to %TRUE, souphttpsrc will use the system's CA file for
337    * checking certificates.
338    *
339    * Since: 1.4
340    */
341   g_object_class_install_property (gobject_class, PROP_SSL_USE_SYSTEM_CA_FILE,
342       g_param_spec_boolean ("ssl-use-system-ca-file", "Use System CA File",
343           "Use system CA file", DEFAULT_SSL_USE_SYSTEM_CA_FILE,
344           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
345
346   gst_element_class_add_pad_template (gstelement_class,
347       gst_static_pad_template_get (&srctemplate));
348
349   gst_element_class_set_static_metadata (gstelement_class, "HTTP client source",
350       "Source/Network",
351       "Receive data as a client over the network via HTTP using SOUP",
352       "Wouter Cloetens <wouter@mind.be>");
353   gstelement_class->change_state =
354       GST_DEBUG_FUNCPTR (gst_soup_http_src_change_state);
355
356   gstbasesrc_class->start = GST_DEBUG_FUNCPTR (gst_soup_http_src_start);
357   gstbasesrc_class->stop = GST_DEBUG_FUNCPTR (gst_soup_http_src_stop);
358   gstbasesrc_class->unlock = GST_DEBUG_FUNCPTR (gst_soup_http_src_unlock);
359   gstbasesrc_class->unlock_stop =
360       GST_DEBUG_FUNCPTR (gst_soup_http_src_unlock_stop);
361   gstbasesrc_class->get_size = GST_DEBUG_FUNCPTR (gst_soup_http_src_get_size);
362   gstbasesrc_class->is_seekable =
363       GST_DEBUG_FUNCPTR (gst_soup_http_src_is_seekable);
364   gstbasesrc_class->do_seek = GST_DEBUG_FUNCPTR (gst_soup_http_src_do_seek);
365   gstbasesrc_class->query = GST_DEBUG_FUNCPTR (gst_soup_http_src_query);
366
367   gstpushsrc_class->create = GST_DEBUG_FUNCPTR (gst_soup_http_src_create);
368
369   GST_DEBUG_CATEGORY_INIT (souphttpsrc_debug, "souphttpsrc", 0,
370       "SOUP HTTP src");
371 }
372
373 static void
374 gst_soup_http_src_reset (GstSoupHTTPSrc * src)
375 {
376   src->interrupted = FALSE;
377   src->retry = FALSE;
378   src->have_size = FALSE;
379   src->got_headers = FALSE;
380   src->seekable = FALSE;
381   src->read_position = 0;
382   src->request_position = 0;
383   src->stop_position = -1;
384   src->content_size = 0;
385   src->have_body = FALSE;
386
387   src->ret = GST_FLOW_OK;
388
389   gst_caps_replace (&src->src_caps, NULL);
390   g_free (src->iradio_name);
391   src->iradio_name = NULL;
392   g_free (src->iradio_genre);
393   src->iradio_genre = NULL;
394   g_free (src->iradio_url);
395   src->iradio_url = NULL;
396 }
397
398 static void
399 gst_soup_http_src_init (GstSoupHTTPSrc * src)
400 {
401   const gchar *proxy;
402
403   g_mutex_init (&src->mutex);
404   g_cond_init (&src->request_finished_cond);
405   src->location = NULL;
406   src->redirection_uri = NULL;
407   src->automatic_redirect = TRUE;
408   src->user_agent = g_strdup (DEFAULT_USER_AGENT);
409   src->user_id = NULL;
410   src->user_pw = NULL;
411   src->proxy_id = NULL;
412   src->proxy_pw = NULL;
413   src->cookies = NULL;
414   src->iradio_mode = DEFAULT_IRADIO_MODE;
415   src->loop = NULL;
416   src->context = NULL;
417   src->session = NULL;
418   src->msg = NULL;
419   src->log_level = DEFAULT_SOUP_LOG_LEVEL;
420   src->ssl_strict = DEFAULT_SSL_STRICT;
421   src->ssl_use_system_ca_file = DEFAULT_SSL_USE_SYSTEM_CA_FILE;
422   proxy = g_getenv ("http_proxy");
423   if (proxy && !gst_soup_http_src_set_proxy (src, proxy)) {
424     GST_WARNING_OBJECT (src,
425         "The proxy in the http_proxy env var (\"%s\") cannot be parsed.",
426         proxy);
427   }
428
429   gst_base_src_set_automatic_eos (GST_BASE_SRC (src), FALSE);
430
431   gst_soup_http_src_reset (src);
432 }
433
434 static void
435 gst_soup_http_src_dispose (GObject * gobject)
436 {
437   GstSoupHTTPSrc *src = GST_SOUP_HTTP_SRC (gobject);
438
439   GST_DEBUG_OBJECT (src, "dispose");
440
441   gst_soup_http_src_session_close (src);
442
443   G_OBJECT_CLASS (parent_class)->dispose (gobject);
444 }
445
446 static void
447 gst_soup_http_src_finalize (GObject * gobject)
448 {
449   GstSoupHTTPSrc *src = GST_SOUP_HTTP_SRC (gobject);
450
451   GST_DEBUG_OBJECT (src, "finalize");
452
453   g_mutex_clear (&src->mutex);
454   g_cond_clear (&src->request_finished_cond);
455   g_free (src->location);
456   if (src->redirection_uri) {
457     g_free (src->redirection_uri);
458   }
459   g_free (src->user_agent);
460   if (src->proxy != NULL) {
461     soup_uri_free (src->proxy);
462   }
463   g_free (src->user_id);
464   g_free (src->user_pw);
465   g_free (src->proxy_id);
466   g_free (src->proxy_pw);
467   g_strfreev (src->cookies);
468
469   if (src->extra_headers) {
470     gst_structure_free (src->extra_headers);
471     src->extra_headers = NULL;
472   }
473
474   g_free (src->ssl_ca_file);
475
476   G_OBJECT_CLASS (parent_class)->finalize (gobject);
477 }
478
479 static void
480 gst_soup_http_src_set_property (GObject * object, guint prop_id,
481     const GValue * value, GParamSpec * pspec)
482 {
483   GstSoupHTTPSrc *src = GST_SOUP_HTTP_SRC (object);
484
485   switch (prop_id) {
486     case PROP_LOCATION:
487     {
488       const gchar *location;
489
490       location = g_value_get_string (value);
491
492       if (location == NULL) {
493         GST_WARNING ("location property cannot be NULL");
494         goto done;
495       }
496       if (!gst_soup_http_src_set_location (src, location, NULL)) {
497         GST_WARNING ("badly formatted location");
498         goto done;
499       }
500       break;
501     }
502     case PROP_USER_AGENT:
503       if (src->user_agent)
504         g_free (src->user_agent);
505       src->user_agent = g_value_dup_string (value);
506       break;
507     case PROP_IRADIO_MODE:
508       src->iradio_mode = g_value_get_boolean (value);
509       break;
510     case PROP_AUTOMATIC_REDIRECT:
511       src->automatic_redirect = g_value_get_boolean (value);
512       break;
513     case PROP_PROXY:
514     {
515       const gchar *proxy;
516
517       proxy = g_value_get_string (value);
518
519       if (proxy == NULL) {
520         GST_WARNING ("proxy property cannot be NULL");
521         goto done;
522       }
523       if (!gst_soup_http_src_set_proxy (src, proxy)) {
524         GST_WARNING ("badly formatted proxy URI");
525         goto done;
526       }
527       break;
528     }
529     case PROP_COOKIES:
530       g_strfreev (src->cookies);
531       src->cookies = g_strdupv (g_value_get_boxed (value));
532       break;
533     case PROP_IS_LIVE:
534       gst_base_src_set_live (GST_BASE_SRC (src), g_value_get_boolean (value));
535       break;
536     case PROP_USER_ID:
537       if (src->user_id)
538         g_free (src->user_id);
539       src->user_id = g_value_dup_string (value);
540       break;
541     case PROP_USER_PW:
542       if (src->user_pw)
543         g_free (src->user_pw);
544       src->user_pw = g_value_dup_string (value);
545       break;
546     case PROP_PROXY_ID:
547       if (src->proxy_id)
548         g_free (src->proxy_id);
549       src->proxy_id = g_value_dup_string (value);
550       break;
551     case PROP_PROXY_PW:
552       if (src->proxy_pw)
553         g_free (src->proxy_pw);
554       src->proxy_pw = g_value_dup_string (value);
555       break;
556     case PROP_TIMEOUT:
557       src->timeout = g_value_get_uint (value);
558       break;
559     case PROP_EXTRA_HEADERS:{
560       const GstStructure *s = gst_value_get_structure (value);
561
562       if (src->extra_headers)
563         gst_structure_free (src->extra_headers);
564
565       src->extra_headers = s ? gst_structure_copy (s) : NULL;
566       break;
567     }
568     case PROP_SOUP_LOG_LEVEL:
569       src->log_level = g_value_get_enum (value);
570       break;
571     case PROP_COMPRESS:
572       src->compress = g_value_get_boolean (value);
573       break;
574     case PROP_KEEP_ALIVE:
575       src->keep_alive = g_value_get_boolean (value);
576       break;
577     case PROP_SSL_STRICT:
578       src->ssl_strict = g_value_get_boolean (value);
579       break;
580     case PROP_SSL_CA_FILE:
581       if (src->ssl_ca_file)
582         g_free (src->ssl_ca_file);
583       src->ssl_ca_file = g_value_dup_string (value);
584       break;
585     case PROP_SSL_USE_SYSTEM_CA_FILE:
586       src->ssl_use_system_ca_file = g_value_get_boolean (value);
587       break;
588     default:
589       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
590       break;
591   }
592 done:
593   return;
594 }
595
596 static void
597 gst_soup_http_src_get_property (GObject * object, guint prop_id,
598     GValue * value, GParamSpec * pspec)
599 {
600   GstSoupHTTPSrc *src = GST_SOUP_HTTP_SRC (object);
601
602   switch (prop_id) {
603     case PROP_LOCATION:
604       g_value_set_string (value, src->location);
605       break;
606     case PROP_USER_AGENT:
607       g_value_set_string (value, src->user_agent);
608       break;
609     case PROP_AUTOMATIC_REDIRECT:
610       g_value_set_boolean (value, src->automatic_redirect);
611       break;
612     case PROP_PROXY:
613       if (src->proxy == NULL)
614         g_value_set_static_string (value, "");
615       else {
616         char *proxy = soup_uri_to_string (src->proxy, FALSE);
617
618         g_value_set_string (value, proxy);
619         g_free (proxy);
620       }
621       break;
622     case PROP_COOKIES:
623       g_value_set_boxed (value, g_strdupv (src->cookies));
624       break;
625     case PROP_IS_LIVE:
626       g_value_set_boolean (value, gst_base_src_is_live (GST_BASE_SRC (src)));
627       break;
628     case PROP_IRADIO_MODE:
629       g_value_set_boolean (value, src->iradio_mode);
630       break;
631     case PROP_USER_ID:
632       g_value_set_string (value, src->user_id);
633       break;
634     case PROP_USER_PW:
635       g_value_set_string (value, src->user_pw);
636       break;
637     case PROP_PROXY_ID:
638       g_value_set_string (value, src->proxy_id);
639       break;
640     case PROP_PROXY_PW:
641       g_value_set_string (value, src->proxy_pw);
642       break;
643     case PROP_TIMEOUT:
644       g_value_set_uint (value, src->timeout);
645       break;
646     case PROP_EXTRA_HEADERS:
647       gst_value_set_structure (value, src->extra_headers);
648       break;
649     case PROP_SOUP_LOG_LEVEL:
650       g_value_set_enum (value, src->log_level);
651       break;
652     case PROP_COMPRESS:
653       g_value_set_boolean (value, src->compress);
654       break;
655     case PROP_KEEP_ALIVE:
656       g_value_set_boolean (value, src->keep_alive);
657       break;
658     case PROP_SSL_STRICT:
659       g_value_set_boolean (value, src->ssl_strict);
660       break;
661     case PROP_SSL_CA_FILE:
662       g_value_set_string (value, src->ssl_ca_file);
663       break;
664     case PROP_SSL_USE_SYSTEM_CA_FILE:
665       g_value_set_boolean (value, src->ssl_strict);
666       break;
667     default:
668       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
669       break;
670   }
671 }
672
673 static gchar *
674 gst_soup_http_src_unicodify (const gchar * str)
675 {
676   const gchar *env_vars[] = { "GST_ICY_TAG_ENCODING",
677     "GST_TAG_ENCODING", NULL
678   };
679
680   return gst_tag_freeform_string_to_utf8 (str, -1, env_vars);
681 }
682
683 static void
684 gst_soup_http_src_cancel_message (GstSoupHTTPSrc * src)
685 {
686   if (src->msg != NULL) {
687     src->session_io_status = GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_CANCELLED;
688     soup_session_cancel_message (src->session, src->msg, SOUP_STATUS_CANCELLED);
689   }
690   src->session_io_status = GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_IDLE;
691   src->msg = NULL;
692 }
693
694 static void
695 gst_soup_http_src_queue_message (GstSoupHTTPSrc * src)
696 {
697   soup_session_queue_message (src->session, src->msg,
698       (SoupSessionCallback) gst_soup_http_src_response_cb, src);
699   src->session_io_status = GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_QUEUED;
700 }
701
702 static gboolean
703 gst_soup_http_src_add_range_header (GstSoupHTTPSrc * src, guint64 offset,
704     guint64 stop_offset)
705 {
706   gchar buf[64];
707
708   gint rc;
709
710   soup_message_headers_remove (src->msg->request_headers, "Range");
711   if (offset || stop_offset != -1) {
712     if (stop_offset != -1) {
713       rc = g_snprintf (buf, sizeof (buf), "bytes=%" G_GUINT64_FORMAT "-%"
714           G_GUINT64_FORMAT, offset, stop_offset);
715     } else {
716       rc = g_snprintf (buf, sizeof (buf), "bytes=%" G_GUINT64_FORMAT "-",
717           offset);
718     }
719     if (rc > sizeof (buf) || rc < 0)
720       return FALSE;
721     soup_message_headers_append (src->msg->request_headers, "Range", buf);
722   }
723   src->read_position = offset;
724   return TRUE;
725 }
726
727 static gboolean
728 _append_extra_header (GQuark field_id, const GValue * value, gpointer user_data)
729 {
730   GstSoupHTTPSrc *src = GST_SOUP_HTTP_SRC (user_data);
731   const gchar *field_name = g_quark_to_string (field_id);
732   gchar *field_content = NULL;
733
734   if (G_VALUE_TYPE (value) == G_TYPE_STRING) {
735     field_content = g_value_dup_string (value);
736   } else {
737     GValue dest = { 0, };
738
739     g_value_init (&dest, G_TYPE_STRING);
740     if (g_value_transform (value, &dest)) {
741       field_content = g_value_dup_string (&dest);
742     }
743   }
744
745   if (field_content == NULL) {
746     GST_ERROR_OBJECT (src, "extra-headers field '%s' contains no value "
747         "or can't be converted to a string", field_name);
748     return FALSE;
749   }
750
751   GST_DEBUG_OBJECT (src, "Appending extra header: \"%s: %s\"", field_name,
752       field_content);
753   soup_message_headers_append (src->msg->request_headers, field_name,
754       field_content);
755
756   g_free (field_content);
757
758   return TRUE;
759 }
760
761 static gboolean
762 _append_extra_headers (GQuark field_id, const GValue * value,
763     gpointer user_data)
764 {
765   if (G_VALUE_TYPE (value) == GST_TYPE_ARRAY) {
766     guint n = gst_value_array_get_size (value);
767     guint i;
768
769     for (i = 0; i < n; i++) {
770       const GValue *v = gst_value_array_get_value (value, i);
771
772       if (!_append_extra_header (field_id, v, user_data))
773         return FALSE;
774     }
775   } else if (G_VALUE_TYPE (value) == GST_TYPE_LIST) {
776     guint n = gst_value_list_get_size (value);
777     guint i;
778
779     for (i = 0; i < n; i++) {
780       const GValue *v = gst_value_list_get_value (value, i);
781
782       if (!_append_extra_header (field_id, v, user_data))
783         return FALSE;
784     }
785   } else {
786     return _append_extra_header (field_id, value, user_data);
787   }
788
789   return TRUE;
790 }
791
792
793 static gboolean
794 gst_soup_http_src_add_extra_headers (GstSoupHTTPSrc * src)
795 {
796   if (!src->extra_headers)
797     return TRUE;
798
799   return gst_structure_foreach (src->extra_headers, _append_extra_headers, src);
800 }
801
802
803 static void
804 gst_soup_http_src_session_unpause_message (GstSoupHTTPSrc * src)
805 {
806   soup_session_unpause_message (src->session, src->msg);
807 }
808
809 static void
810 gst_soup_http_src_session_pause_message (GstSoupHTTPSrc * src)
811 {
812   soup_session_pause_message (src->session, src->msg);
813 }
814
815 static gboolean
816 gst_soup_http_src_session_open (GstSoupHTTPSrc * src)
817 {
818   if (src->session) {
819     GST_DEBUG_OBJECT (src, "Session is already open");
820     return TRUE;
821   }
822
823   if (!src->location) {
824     GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, (_("No URL set.")),
825         ("Missing location property"));
826     return FALSE;
827   }
828
829   if (!src->context)
830     src->context = g_main_context_new ();
831
832   if (!src->loop)
833     src->loop = g_main_loop_new (src->context, TRUE);
834   if (!src->loop) {
835     GST_ELEMENT_ERROR (src, LIBRARY, INIT,
836         (NULL), ("Failed to start GMainLoop"));
837     g_main_context_unref (src->context);
838     return FALSE;
839   }
840
841   if (!src->session) {
842     GST_DEBUG_OBJECT (src, "Creating session");
843     if (src->proxy == NULL) {
844       src->session =
845           soup_session_async_new_with_options (SOUP_SESSION_ASYNC_CONTEXT,
846           src->context, SOUP_SESSION_USER_AGENT, src->user_agent,
847           SOUP_SESSION_TIMEOUT, src->timeout,
848           SOUP_SESSION_SSL_STRICT, src->ssl_strict,
849           SOUP_SESSION_ADD_FEATURE_BY_TYPE, SOUP_TYPE_PROXY_RESOLVER_DEFAULT,
850           NULL);
851     } else {
852       src->session =
853           soup_session_async_new_with_options (SOUP_SESSION_ASYNC_CONTEXT,
854           src->context, SOUP_SESSION_PROXY_URI, src->proxy,
855           SOUP_SESSION_TIMEOUT, src->timeout,
856           SOUP_SESSION_SSL_STRICT, src->ssl_strict,
857           SOUP_SESSION_USER_AGENT, src->user_agent, NULL);
858     }
859
860     if (!src->session) {
861       GST_ELEMENT_ERROR (src, LIBRARY, INIT,
862           (NULL), ("Failed to create async session"));
863       return FALSE;
864     }
865
866     g_signal_connect (src->session, "authenticate",
867         G_CALLBACK (gst_soup_http_src_authenticate_cb), src);
868
869     /* Set up logging */
870     gst_soup_util_log_setup (src->session, src->log_level, GST_ELEMENT (src));
871     if (src->ssl_ca_file)
872       g_object_set (src->session, "ssl-ca-file", src->ssl_ca_file, NULL);
873     else
874       g_object_set (src->session, "ssl-use-system-ca-file",
875           src->ssl_use_system_ca_file, NULL);
876   } else {
877     GST_DEBUG_OBJECT (src, "Re-using session");
878   }
879
880   if (src->compress)
881     soup_session_add_feature_by_type (src->session, SOUP_TYPE_CONTENT_DECODER);
882   else
883     soup_session_remove_feature_by_type (src->session,
884         SOUP_TYPE_CONTENT_DECODER);
885
886   return TRUE;
887 }
888
889 static void
890 gst_soup_http_src_session_close (GstSoupHTTPSrc * src)
891 {
892   if (src->session) {
893     soup_session_abort (src->session);  /* This unrefs the message. */
894     g_object_unref (src->session);
895     src->session = NULL;
896     src->msg = NULL;
897   }
898   if (src->loop) {
899     g_main_loop_unref (src->loop);
900     g_main_context_unref (src->context);
901     src->loop = NULL;
902     src->context = NULL;
903   }
904 }
905
906 static void
907 gst_soup_http_src_authenticate_cb (SoupSession * session, SoupMessage * msg,
908     SoupAuth * auth, gboolean retrying, GstSoupHTTPSrc * src)
909 {
910   if (!retrying) {
911     /* First time authentication only, if we fail and are called again with retry true fall through */
912     if (msg->status_code == SOUP_STATUS_UNAUTHORIZED) {
913       if (src->user_id && src->user_pw)
914         soup_auth_authenticate (auth, src->user_id, src->user_pw);
915     } else if (msg->status_code == SOUP_STATUS_PROXY_AUTHENTICATION_REQUIRED) {
916       if (src->proxy_id && src->proxy_pw)
917         soup_auth_authenticate (auth, src->proxy_id, src->proxy_pw);
918     }
919   }
920 }
921
922 static void
923 gst_soup_http_src_got_headers_cb (SoupMessage * msg, GstSoupHTTPSrc * src)
924 {
925   const char *value;
926   GstTagList *tag_list;
927   GstBaseSrc *basesrc;
928   guint64 newsize;
929   GHashTable *params = NULL;
930
931   GST_INFO_OBJECT (src, "got headers");
932
933   if (msg->status_code == SOUP_STATUS_PROXY_AUTHENTICATION_REQUIRED &&
934       src->proxy_id && src->proxy_pw)
935     return;
936
937   if (src->automatic_redirect && SOUP_STATUS_IS_REDIRECTION (msg->status_code)) {
938     src->redirection_uri = g_strdup (soup_message_headers_get_one
939         (msg->response_headers, "Location"));
940     GST_DEBUG_OBJECT (src, "%u redirect to \"%s\"", msg->status_code,
941         src->redirection_uri);
942     return;
943   }
944
945   if (msg->status_code == SOUP_STATUS_UNAUTHORIZED)
946     return;
947
948   src->session_io_status = GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_RUNNING;
949   src->got_headers = TRUE;
950
951   /* Parse Content-Length. */
952   if (soup_message_headers_get_encoding (msg->response_headers) ==
953       SOUP_ENCODING_CONTENT_LENGTH) {
954     newsize = src->request_position +
955         soup_message_headers_get_content_length (msg->response_headers);
956     if (!src->have_size || (src->content_size != newsize)) {
957       src->content_size = newsize;
958       src->have_size = TRUE;
959       src->seekable = TRUE;
960       GST_DEBUG_OBJECT (src, "size = %" G_GUINT64_FORMAT, src->content_size);
961
962       basesrc = GST_BASE_SRC_CAST (src);
963       basesrc->segment.duration = src->content_size;
964       gst_element_post_message (GST_ELEMENT (src),
965           gst_message_new_duration_changed (GST_OBJECT (src)));
966     }
967   }
968
969   /* Icecast stuff */
970   tag_list = gst_tag_list_new_empty ();
971
972   if ((value =
973           soup_message_headers_get_one (msg->response_headers,
974               "icy-metaint")) != NULL) {
975     gint icy_metaint = atoi (value);
976
977     GST_DEBUG_OBJECT (src, "icy-metaint: %s (parsed: %d)", value, icy_metaint);
978     if (icy_metaint > 0) {
979       if (src->src_caps)
980         gst_caps_unref (src->src_caps);
981
982       src->src_caps = gst_caps_new_simple ("application/x-icy",
983           "metadata-interval", G_TYPE_INT, icy_metaint, NULL);
984
985       gst_base_src_set_caps (GST_BASE_SRC (src), src->src_caps);
986     }
987   }
988   if ((value =
989           soup_message_headers_get_content_type (msg->response_headers,
990               &params)) != NULL) {
991     GST_DEBUG_OBJECT (src, "Content-Type: %s", value);
992     if (g_ascii_strcasecmp (value, "audio/L16") == 0) {
993       gint channels = 2;
994       gint rate = 44100;
995       char *param;
996
997       if (src->src_caps)
998         gst_caps_unref (src->src_caps);
999
1000       param = g_hash_table_lookup (params, "channels");
1001       if (param != NULL)
1002         channels = atol (param);
1003
1004       param = g_hash_table_lookup (params, "rate");
1005       if (param != NULL)
1006         rate = atol (param);
1007
1008       src->src_caps = gst_caps_new_simple ("audio/x-raw",
1009           "format", G_TYPE_STRING, "S16BE",
1010           "layout", G_TYPE_STRING, "interleaved",
1011           "channels", G_TYPE_INT, channels, "rate", G_TYPE_INT, rate, NULL);
1012
1013       gst_base_src_set_caps (GST_BASE_SRC (src), src->src_caps);
1014     } else {
1015       /* Set the Content-Type field on the caps */
1016       if (src->src_caps) {
1017         src->src_caps = gst_caps_make_writable (src->src_caps);
1018         gst_caps_set_simple (src->src_caps, "content-type", G_TYPE_STRING,
1019             value, NULL);
1020         gst_base_src_set_caps (GST_BASE_SRC (src), src->src_caps);
1021       }
1022     }
1023   }
1024
1025   if (params != NULL)
1026     g_hash_table_destroy (params);
1027
1028   if ((value =
1029           soup_message_headers_get_one (msg->response_headers,
1030               "icy-name")) != NULL) {
1031     g_free (src->iradio_name);
1032     src->iradio_name = gst_soup_http_src_unicodify (value);
1033     if (src->iradio_name) {
1034       gst_tag_list_add (tag_list, GST_TAG_MERGE_REPLACE, GST_TAG_ORGANIZATION,
1035           src->iradio_name, NULL);
1036     }
1037   }
1038   if ((value =
1039           soup_message_headers_get_one (msg->response_headers,
1040               "icy-genre")) != NULL) {
1041     g_free (src->iradio_genre);
1042     src->iradio_genre = gst_soup_http_src_unicodify (value);
1043     if (src->iradio_genre) {
1044       gst_tag_list_add (tag_list, GST_TAG_MERGE_REPLACE, GST_TAG_GENRE,
1045           src->iradio_genre, NULL);
1046     }
1047   }
1048   if ((value = soup_message_headers_get_one (msg->response_headers, "icy-url"))
1049       != NULL) {
1050     g_free (src->iradio_url);
1051     src->iradio_url = gst_soup_http_src_unicodify (value);
1052     if (src->iradio_url) {
1053       gst_tag_list_add (tag_list, GST_TAG_MERGE_REPLACE, GST_TAG_LOCATION,
1054           src->iradio_url, NULL);
1055     }
1056   }
1057   if (!gst_tag_list_is_empty (tag_list)) {
1058     GST_DEBUG_OBJECT (src,
1059         "calling gst_element_found_tags with %" GST_PTR_FORMAT, tag_list);
1060     gst_pad_push_event (GST_BASE_SRC_PAD (src), gst_event_new_tag (tag_list));
1061   } else {
1062     gst_tag_list_unref (tag_list);
1063   }
1064
1065   /* Handle HTTP errors. */
1066   gst_soup_http_src_parse_status (msg, src);
1067
1068   /* Check if Range header was respected. */
1069   if (src->ret == GST_FLOW_CUSTOM_ERROR &&
1070       src->read_position && msg->status_code != SOUP_STATUS_PARTIAL_CONTENT) {
1071     src->seekable = FALSE;
1072     GST_ELEMENT_ERROR (src, RESOURCE, SEEK,
1073         (_("Server does not support seeking.")),
1074         ("Server does not accept Range HTTP header, URL: %s", src->location));
1075     src->ret = GST_FLOW_ERROR;
1076   }
1077
1078   /* If we are going to error out, stop all processing right here, so we
1079    * don't output any data (such as an error html page), and return
1080    * GST_FLOW_ERROR from the create function instead of having
1081    * got_chunk_cb overwrite src->ret with FLOW_OK again. */
1082   if (src->ret == GST_FLOW_ERROR || src->ret == GST_FLOW_EOS) {
1083     gst_soup_http_src_session_pause_message (src);
1084
1085     if (src->loop)
1086       g_main_loop_quit (src->loop);
1087   }
1088   g_cond_signal (&src->request_finished_cond);
1089 }
1090
1091 /* Have body. Signal EOS. */
1092 static void
1093 gst_soup_http_src_got_body_cb (SoupMessage * msg, GstSoupHTTPSrc * src)
1094 {
1095   if (G_UNLIKELY (msg != src->msg)) {
1096     GST_DEBUG_OBJECT (src, "got body, but not for current message");
1097     return;
1098   }
1099   if (G_UNLIKELY (src->session_io_status !=
1100           GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_RUNNING)) {
1101     /* Probably a redirect. */
1102     return;
1103   }
1104   GST_DEBUG_OBJECT (src, "got body");
1105   src->ret = GST_FLOW_EOS;
1106   src->have_body = TRUE;
1107
1108   /* no need to interrupt the message here, we do it on the
1109    * finished_cb anyway if needed. And getting the body might mean
1110    * that the connection was hang up before finished. This happens when
1111    * the pipeline is stalled for too long (long pauses during playback).
1112    * Best to let it continue from here and pause because it reached the
1113    * final bytes based on content_size or received an out of range error */
1114 }
1115
1116 /* Finished. Signal EOS. */
1117 static void
1118 gst_soup_http_src_finished_cb (SoupMessage * msg, GstSoupHTTPSrc * src)
1119 {
1120   if (G_UNLIKELY (msg != src->msg)) {
1121     GST_DEBUG_OBJECT (src, "finished, but not for current message");
1122     return;
1123   }
1124   GST_DEBUG_OBJECT (src, "finished");
1125   src->ret = GST_FLOW_EOS;
1126   if (src->session_io_status == GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_CANCELLED) {
1127     /* gst_soup_http_src_cancel_message() triggered this; probably a seek
1128      * that occurred in the QUEUEING state; i.e. before the connection setup
1129      * was complete. Do nothing */
1130   } else if (src->session_io_status ==
1131       GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_RUNNING && src->read_position > 0 &&
1132       (src->have_size && src->read_position < src->content_size)) {
1133     /* The server disconnected while streaming. Reconnect and seeking to the
1134      * last location. */
1135     src->retry = TRUE;
1136     src->ret = GST_FLOW_CUSTOM_ERROR;
1137   } else if (G_UNLIKELY (src->session_io_status !=
1138           GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_RUNNING)) {
1139     if (msg->method == SOUP_METHOD_HEAD) {
1140       GST_DEBUG_OBJECT (src, "Ignoring error %d:%s during HEAD request",
1141           msg->status_code, msg->reason_phrase);
1142     } else {
1143       gst_soup_http_src_parse_status (msg, src);
1144     }
1145   }
1146   if (src->loop)
1147     g_main_loop_quit (src->loop);
1148   g_cond_signal (&src->request_finished_cond);
1149 }
1150
1151 /* Buffer lifecycle management.
1152  *
1153  * gst_soup_http_src_create() runs the GMainLoop for this element, to let
1154  * Soup take control.
1155  * A GstBuffer is allocated in gst_soup_http_src_chunk_allocator() and
1156  * associated with a SoupBuffer.
1157  * Soup reads HTTP data in the GstBuffer's data buffer.
1158  * The gst_soup_http_src_got_chunk_cb() is then called with the SoupBuffer.
1159  * That sets gst_soup_http_src_create()'s return argument to the GstBuffer,
1160  * increments its refcount (to 2), pauses the flow of data from the HTTP
1161  * source to prevent gst_soup_http_src_got_chunk_cb() from being called
1162  * again and breaks out of the GMainLoop.
1163  * Because the SOUP_MESSAGE_OVERWRITE_CHUNKS flag is set, Soup frees the
1164  * SoupBuffer and calls gst_soup_http_src_chunk_free(), which decrements the
1165  * refcount (to 1).
1166  * gst_soup_http_src_create() returns the GstBuffer. It will be freed by a
1167  * downstream element.
1168  * If Soup fails to read HTTP data, it does not call
1169  * gst_soup_http_src_got_chunk_cb(), but still frees the SoupBuffer and
1170  * calls gst_soup_http_src_chunk_free(), which decrements the GstBuffer's
1171  * refcount to 0, freeing it.
1172  */
1173
1174 typedef struct
1175 {
1176   GstBuffer *buffer;
1177   GstMapInfo map;
1178 } SoupGstChunk;
1179
1180 static void
1181 gst_soup_http_src_chunk_free (gpointer user_data)
1182 {
1183   SoupGstChunk *chunk = (SoupGstChunk *) user_data;
1184
1185   gst_buffer_unmap (chunk->buffer, &chunk->map);
1186   gst_buffer_unref (chunk->buffer);
1187   g_slice_free (SoupGstChunk, chunk);
1188 }
1189
1190 static SoupBuffer *
1191 gst_soup_http_src_chunk_allocator (SoupMessage * msg, gsize max_len,
1192     gpointer user_data)
1193 {
1194   GstSoupHTTPSrc *src = (GstSoupHTTPSrc *) user_data;
1195   GstBaseSrc *basesrc = GST_BASE_SRC_CAST (src);
1196   GstBuffer *gstbuf;
1197   SoupBuffer *soupbuf;
1198   gsize length;
1199   GstFlowReturn rc;
1200   SoupGstChunk *chunk;
1201
1202   if (max_len)
1203     length = MIN (basesrc->blocksize, max_len);
1204   else
1205     length = basesrc->blocksize;
1206   GST_DEBUG_OBJECT (src, "alloc %" G_GSIZE_FORMAT " bytes <= %" G_GSIZE_FORMAT,
1207       length, max_len);
1208
1209   rc = GST_BASE_SRC_CLASS (parent_class)->alloc (basesrc, -1, length, &gstbuf);
1210   if (G_UNLIKELY (rc != GST_FLOW_OK)) {
1211     /* Failed to allocate buffer. Stall SoupSession and return error code
1212      * to create(). */
1213     src->ret = rc;
1214     g_main_loop_quit (src->loop);
1215     return NULL;
1216   }
1217
1218   chunk = g_slice_new0 (SoupGstChunk);
1219   chunk->buffer = gstbuf;
1220   gst_buffer_map (gstbuf, &chunk->map, GST_MAP_READWRITE);
1221
1222   soupbuf = soup_buffer_new_with_owner (chunk->map.data, chunk->map.size,
1223       chunk, gst_soup_http_src_chunk_free);
1224
1225   return soupbuf;
1226 }
1227
1228 static void
1229 gst_soup_http_src_got_chunk_cb (SoupMessage * msg, SoupBuffer * chunk,
1230     GstSoupHTTPSrc * src)
1231 {
1232   GstBaseSrc *basesrc;
1233   guint64 new_position;
1234   SoupGstChunk *gchunk;
1235
1236   if (G_UNLIKELY (msg != src->msg)) {
1237     GST_DEBUG_OBJECT (src, "got chunk, but not for current message");
1238     return;
1239   }
1240   if (G_UNLIKELY (!src->outbuf)) {
1241     GST_DEBUG_OBJECT (src, "got chunk but we're not expecting one");
1242     src->ret = GST_FLOW_OK;
1243     gst_soup_http_src_cancel_message (src);
1244     g_main_loop_quit (src->loop);
1245     return;
1246   }
1247
1248   src->have_body = FALSE;
1249   if (G_UNLIKELY (src->session_io_status !=
1250           GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_RUNNING)) {
1251     /* Probably a redirect. */
1252     return;
1253   }
1254   basesrc = GST_BASE_SRC_CAST (src);
1255   GST_DEBUG_OBJECT (src, "got chunk of %" G_GSIZE_FORMAT " bytes",
1256       chunk->length);
1257
1258   /* Extract the GstBuffer from the SoupBuffer and set its fields. */
1259   gchunk = (SoupGstChunk *) soup_buffer_get_owner (chunk);
1260   *src->outbuf = gchunk->buffer;
1261
1262   gst_buffer_resize (*src->outbuf, 0, chunk->length);
1263   GST_BUFFER_OFFSET (*src->outbuf) = basesrc->segment.position;
1264
1265   gst_buffer_ref (*src->outbuf);
1266
1267   new_position = src->read_position + chunk->length;
1268   if (G_LIKELY (src->request_position == src->read_position))
1269     src->request_position = new_position;
1270   src->read_position = new_position;
1271
1272   if (src->have_size) {
1273     if (new_position > src->content_size) {
1274       GST_DEBUG_OBJECT (src, "Got position previous estimated content size "
1275           "(%" G_GINT64_FORMAT " > %" G_GINT64_FORMAT ")", new_position,
1276           src->content_size);
1277       src->content_size = new_position;
1278       basesrc->segment.duration = src->content_size;
1279       gst_element_post_message (GST_ELEMENT (src),
1280           gst_message_new_duration_changed (GST_OBJECT (src)));
1281     } else if (new_position == src->content_size) {
1282       GST_DEBUG_OBJECT (src, "We're EOS now");
1283     }
1284   }
1285
1286   src->ret = GST_FLOW_OK;
1287   g_main_loop_quit (src->loop);
1288   gst_soup_http_src_session_pause_message (src);
1289 }
1290
1291 static void
1292 gst_soup_http_src_response_cb (SoupSession * session, SoupMessage * msg,
1293     GstSoupHTTPSrc * src)
1294 {
1295   if (G_UNLIKELY (msg != src->msg)) {
1296     GST_DEBUG_OBJECT (src, "got response %d: %s, but not for current message",
1297         msg->status_code, msg->reason_phrase);
1298     return;
1299   }
1300   if (G_UNLIKELY (src->session_io_status !=
1301           GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_RUNNING)
1302       && SOUP_STATUS_IS_REDIRECTION (msg->status_code)) {
1303     /* Ignore redirections. */
1304     return;
1305   }
1306   GST_DEBUG_OBJECT (src, "got response %d: %s", msg->status_code,
1307       msg->reason_phrase);
1308   if (src->session_io_status == GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_RUNNING &&
1309       src->read_position > 0 && (src->have_size
1310           && src->read_position < src->content_size)) {
1311     /* The server disconnected while streaming. Reconnect and seeking to the
1312      * last location. */
1313     src->retry = TRUE;
1314   } else
1315     gst_soup_http_src_parse_status (msg, src);
1316   /* The session's SoupMessage object expires after this callback returns. */
1317   src->msg = NULL;
1318   g_main_loop_quit (src->loop);
1319 }
1320
1321 #define SOUP_HTTP_SRC_ERROR(src,soup_msg,cat,code,error_message)     \
1322   GST_ELEMENT_ERROR ((src), cat, code, ("%s", error_message),        \
1323       ("%s (%d), URL: %s", (soup_msg)->reason_phrase,                \
1324           (soup_msg)->status_code, (src)->location));
1325
1326 static void
1327 gst_soup_http_src_parse_status (SoupMessage * msg, GstSoupHTTPSrc * src)
1328 {
1329   if (msg->method == SOUP_METHOD_HEAD) {
1330     if (!SOUP_STATUS_IS_SUCCESSFUL (msg->status_code))
1331       GST_DEBUG_OBJECT (src, "Ignoring error %d during HEAD request",
1332           msg->status_code);
1333   } else if (SOUP_STATUS_IS_TRANSPORT_ERROR (msg->status_code)) {
1334     switch (msg->status_code) {
1335       case SOUP_STATUS_CANT_RESOLVE:
1336       case SOUP_STATUS_CANT_RESOLVE_PROXY:
1337         SOUP_HTTP_SRC_ERROR (src, msg, RESOURCE, NOT_FOUND,
1338             _("Could not resolve server name."));
1339         src->ret = GST_FLOW_ERROR;
1340         break;
1341       case SOUP_STATUS_CANT_CONNECT:
1342       case SOUP_STATUS_CANT_CONNECT_PROXY:
1343         SOUP_HTTP_SRC_ERROR (src, msg, RESOURCE, OPEN_READ,
1344             _("Could not establish connection to server."));
1345         src->ret = GST_FLOW_ERROR;
1346         break;
1347       case SOUP_STATUS_SSL_FAILED:
1348         SOUP_HTTP_SRC_ERROR (src, msg, RESOURCE, OPEN_READ,
1349             _("Secure connection setup failed."));
1350         src->ret = GST_FLOW_ERROR;
1351         break;
1352       case SOUP_STATUS_IO_ERROR:
1353         SOUP_HTTP_SRC_ERROR (src, msg, RESOURCE, READ,
1354             _("A network error occured, or the server closed the connection "
1355                 "unexpectedly."));
1356         src->ret = GST_FLOW_ERROR;
1357         break;
1358       case SOUP_STATUS_MALFORMED:
1359         SOUP_HTTP_SRC_ERROR (src, msg, RESOURCE, READ,
1360             _("Server sent bad data."));
1361         src->ret = GST_FLOW_ERROR;
1362         break;
1363       case SOUP_STATUS_CANCELLED:
1364         /* No error message when interrupted by program. */
1365         break;
1366     }
1367   } else if (SOUP_STATUS_IS_CLIENT_ERROR (msg->status_code) ||
1368       SOUP_STATUS_IS_REDIRECTION (msg->status_code) ||
1369       SOUP_STATUS_IS_SERVER_ERROR (msg->status_code)) {
1370     /* Report HTTP error. */
1371
1372     /* when content_size is unknown and we have just finished receiving
1373      * a body message, requests that go beyond the content limits will result
1374      * in an error. Here we convert those to EOS */
1375     if (msg->status_code == SOUP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE &&
1376         src->have_body && !src->have_size) {
1377       GST_DEBUG_OBJECT (src, "Requested range out of limits and received full "
1378           "body, returning EOS");
1379       src->ret = GST_FLOW_EOS;
1380       return;
1381     }
1382
1383     /* FIXME: reason_phrase is not translated and not suitable for user
1384      * error dialog according to libsoup documentation.
1385      */
1386     if (msg->status_code == SOUP_STATUS_NOT_FOUND) {
1387       GST_ELEMENT_ERROR (src, RESOURCE, NOT_FOUND,
1388           ("%s", msg->reason_phrase),
1389           ("%s (%d), URL: %s", msg->reason_phrase, msg->status_code,
1390               src->location));
1391     } else if (msg->status_code == SOUP_STATUS_UNAUTHORIZED ||
1392         msg->status_code == SOUP_STATUS_PAYMENT_REQUIRED ||
1393         msg->status_code == SOUP_STATUS_FORBIDDEN ||
1394         msg->status_code == SOUP_STATUS_PROXY_AUTHENTICATION_REQUIRED) {
1395       GST_ELEMENT_ERROR (src, RESOURCE, NOT_AUTHORIZED,
1396           ("%s", msg->reason_phrase),
1397           ("%s (%d), URL: %s", msg->reason_phrase, msg->status_code,
1398               src->location));
1399     } else {
1400       GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ,
1401           ("%s", msg->reason_phrase),
1402           ("%s (%d), URL: %s", msg->reason_phrase, msg->status_code,
1403               src->location));
1404     }
1405     src->ret = GST_FLOW_ERROR;
1406   }
1407 }
1408
1409 static gboolean
1410 gst_soup_http_src_build_message (GstSoupHTTPSrc * src, const gchar * method)
1411 {
1412   src->msg = soup_message_new (method, src->location);
1413   if (!src->msg) {
1414     GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ,
1415         ("Error parsing URL."), ("URL: %s", src->location));
1416     return FALSE;
1417   }
1418   src->session_io_status = GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_IDLE;
1419   if (!src->keep_alive) {
1420     soup_message_headers_append (src->msg->request_headers, "Connection",
1421         "close");
1422   }
1423   if (src->iradio_mode) {
1424     soup_message_headers_append (src->msg->request_headers, "icy-metadata",
1425         "1");
1426   }
1427   if (src->cookies) {
1428     gchar **cookie;
1429
1430     for (cookie = src->cookies; *cookie != NULL; cookie++) {
1431       soup_message_headers_append (src->msg->request_headers, "Cookie",
1432           *cookie);
1433     }
1434   }
1435   src->retry = FALSE;
1436
1437   g_signal_connect (src->msg, "got_headers",
1438       G_CALLBACK (gst_soup_http_src_got_headers_cb), src);
1439   g_signal_connect (src->msg, "got_body",
1440       G_CALLBACK (gst_soup_http_src_got_body_cb), src);
1441   g_signal_connect (src->msg, "finished",
1442       G_CALLBACK (gst_soup_http_src_finished_cb), src);
1443   g_signal_connect (src->msg, "got_chunk",
1444       G_CALLBACK (gst_soup_http_src_got_chunk_cb), src);
1445   soup_message_set_flags (src->msg, SOUP_MESSAGE_OVERWRITE_CHUNKS |
1446       (src->automatic_redirect ? 0 : SOUP_MESSAGE_NO_REDIRECT));
1447   soup_message_set_chunk_allocator (src->msg,
1448       gst_soup_http_src_chunk_allocator, src, NULL);
1449   gst_soup_http_src_add_range_header (src, src->request_position,
1450       src->stop_position);
1451
1452   gst_soup_http_src_add_extra_headers (src);
1453
1454   return TRUE;
1455 }
1456
1457 static GstFlowReturn
1458 gst_soup_http_src_do_request (GstSoupHTTPSrc * src, const gchar * method,
1459     GstBuffer ** outbuf)
1460 {
1461   /* If we're not OK, just go out of here */
1462   if (src->ret != GST_FLOW_OK) {
1463     GST_DEBUG_OBJECT (src, "Previous flow return not OK: %s",
1464         gst_flow_get_name (src->ret));
1465     return src->ret;
1466   }
1467
1468   GST_LOG_OBJECT (src, "Running request for method: %s", method);
1469   if (src->msg && (src->request_position != src->read_position)) {
1470     if (src->session_io_status == GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_IDLE) {
1471       gst_soup_http_src_add_range_header (src, src->request_position,
1472           src->stop_position);
1473     } else {
1474       GST_DEBUG_OBJECT (src, "Seek from position %" G_GUINT64_FORMAT
1475           " to %" G_GUINT64_FORMAT ": requeueing connection request",
1476           src->read_position, src->request_position);
1477       gst_soup_http_src_cancel_message (src);
1478     }
1479   }
1480   if (!src->msg)
1481     if (!gst_soup_http_src_build_message (src, method)) {
1482       return GST_FLOW_ERROR;
1483     }
1484
1485   src->ret = GST_FLOW_CUSTOM_ERROR;
1486   src->outbuf = outbuf;
1487   do {
1488     if (src->interrupted) {
1489       GST_DEBUG_OBJECT (src, "interrupted");
1490       break;
1491     }
1492     if (src->retry) {
1493       GST_DEBUG_OBJECT (src, "Reconnecting");
1494       if (!gst_soup_http_src_build_message (src, method)) {
1495         return GST_FLOW_ERROR;
1496       }
1497       src->retry = FALSE;
1498       continue;
1499     }
1500     if (!src->msg) {
1501       GST_DEBUG_OBJECT (src, "EOS reached");
1502       break;
1503     }
1504
1505     switch (src->session_io_status) {
1506       case GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_IDLE:
1507         GST_DEBUG_OBJECT (src, "Queueing connection request");
1508         gst_soup_http_src_queue_message (src);
1509         break;
1510       case GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_QUEUED:
1511         break;
1512       case GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_RUNNING:
1513         gst_soup_http_src_session_unpause_message (src);
1514         break;
1515       case GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_CANCELLED:
1516         /* Impossible. */
1517         break;
1518     }
1519
1520     if (src->ret == GST_FLOW_CUSTOM_ERROR)
1521       g_main_loop_run (src->loop);
1522
1523   } while (src->ret == GST_FLOW_CUSTOM_ERROR);
1524
1525   /* Let the request finish if we had a stop position and are there */
1526   if (src->ret == GST_FLOW_OK && src->stop_position != -1
1527       && src->read_position >= src->stop_position) {
1528     src->outbuf = NULL;
1529     gst_soup_http_src_session_unpause_message (src);
1530     g_main_loop_run (src->loop);
1531
1532     g_cond_signal (&src->request_finished_cond);
1533     /* Return OK unconditionally here, src->ret will
1534      * be most likely be EOS now but we want to
1535      * consume the buffer we got above */
1536     return GST_FLOW_OK;
1537   }
1538
1539   if (src->ret == GST_FLOW_CUSTOM_ERROR)
1540     src->ret = GST_FLOW_EOS;
1541   g_cond_signal (&src->request_finished_cond);
1542
1543   return src->ret;
1544 }
1545
1546 static GstFlowReturn
1547 gst_soup_http_src_create (GstPushSrc * psrc, GstBuffer ** outbuf)
1548 {
1549   GstSoupHTTPSrc *src;
1550   GstFlowReturn ret;
1551
1552   src = GST_SOUP_HTTP_SRC (psrc);
1553
1554   g_mutex_lock (&src->mutex);
1555   *outbuf = NULL;
1556   ret = gst_soup_http_src_do_request (src, SOUP_METHOD_GET, outbuf);
1557   g_mutex_unlock (&src->mutex);
1558   return ret;
1559 }
1560
1561 static gboolean
1562 gst_soup_http_src_start (GstBaseSrc * bsrc)
1563 {
1564   GstSoupHTTPSrc *src = GST_SOUP_HTTP_SRC (bsrc);
1565
1566   GST_DEBUG_OBJECT (src, "start(\"%s\")", src->location);
1567
1568   return gst_soup_http_src_session_open (src);
1569 }
1570
1571 static gboolean
1572 gst_soup_http_src_stop (GstBaseSrc * bsrc)
1573 {
1574   GstSoupHTTPSrc *src;
1575
1576   src = GST_SOUP_HTTP_SRC (bsrc);
1577   GST_DEBUG_OBJECT (src, "stop()");
1578   if (src->keep_alive)
1579     gst_soup_http_src_cancel_message (src);
1580   else
1581     gst_soup_http_src_session_close (src);
1582
1583   gst_soup_http_src_reset (src);
1584   return TRUE;
1585 }
1586
1587 static GstStateChangeReturn
1588 gst_soup_http_src_change_state (GstElement * element, GstStateChange transition)
1589 {
1590   GstStateChangeReturn ret;
1591   GstSoupHTTPSrc *src;
1592
1593   src = GST_SOUP_HTTP_SRC (element);
1594
1595   switch (transition) {
1596     case GST_STATE_CHANGE_READY_TO_NULL:
1597       gst_soup_http_src_session_close (src);
1598       break;
1599     default:
1600       break;
1601   }
1602
1603   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
1604
1605   return ret;
1606 }
1607
1608 /* Interrupt a blocking request. */
1609 static gboolean
1610 gst_soup_http_src_unlock (GstBaseSrc * bsrc)
1611 {
1612   GstSoupHTTPSrc *src;
1613
1614   src = GST_SOUP_HTTP_SRC (bsrc);
1615   GST_DEBUG_OBJECT (src, "unlock()");
1616
1617   src->interrupted = TRUE;
1618   if (src->loop)
1619     g_main_loop_quit (src->loop);
1620   g_cond_signal (&src->request_finished_cond);
1621   return TRUE;
1622 }
1623
1624 /* Interrupt interrupt. */
1625 static gboolean
1626 gst_soup_http_src_unlock_stop (GstBaseSrc * bsrc)
1627 {
1628   GstSoupHTTPSrc *src;
1629
1630   src = GST_SOUP_HTTP_SRC (bsrc);
1631   GST_DEBUG_OBJECT (src, "unlock_stop()");
1632
1633   src->interrupted = FALSE;
1634   return TRUE;
1635 }
1636
1637 static gboolean
1638 gst_soup_http_src_get_size (GstBaseSrc * bsrc, guint64 * size)
1639 {
1640   GstSoupHTTPSrc *src;
1641
1642   src = GST_SOUP_HTTP_SRC (bsrc);
1643
1644   if (src->have_size) {
1645     GST_DEBUG_OBJECT (src, "get_size() = %" G_GUINT64_FORMAT,
1646         src->content_size);
1647     *size = src->content_size;
1648     return TRUE;
1649   }
1650   GST_DEBUG_OBJECT (src, "get_size() = FALSE");
1651   return FALSE;
1652 }
1653
1654 static void
1655 gst_soup_http_src_check_seekable (GstSoupHTTPSrc * src)
1656 {
1657   GstFlowReturn ret = GST_FLOW_OK;
1658
1659   /* Special case to check if the server allows range requests
1660    * before really starting to get data in the buffer creation
1661    * loops.
1662    */
1663   if (!src->got_headers && GST_STATE (src) >= GST_STATE_PAUSED) {
1664     g_mutex_lock (&src->mutex);
1665     while (!src->got_headers && !src->interrupted && ret == GST_FLOW_OK) {
1666       if ((src->msg && src->msg->method != SOUP_METHOD_HEAD) &&
1667           src->session_io_status != GST_SOUP_HTTP_SRC_SESSION_IO_STATUS_IDLE) {
1668         /* wait for the current request to finish */
1669         g_cond_wait (&src->request_finished_cond, &src->mutex);
1670       } else {
1671         if (gst_soup_http_src_session_open (src)) {
1672           ret = gst_soup_http_src_do_request (src, SOUP_METHOD_HEAD, NULL);
1673         }
1674       }
1675     }
1676     if (src->ret == GST_FLOW_EOS) {
1677       /* A HEAD request shouldn't lead to EOS */
1678       src->ret = GST_FLOW_OK;
1679     }
1680     /* resets status to idle */
1681     gst_soup_http_src_cancel_message (src);
1682     g_mutex_unlock (&src->mutex);
1683   }
1684
1685 }
1686
1687 static gboolean
1688 gst_soup_http_src_is_seekable (GstBaseSrc * bsrc)
1689 {
1690   GstSoupHTTPSrc *src = GST_SOUP_HTTP_SRC (bsrc);
1691
1692   gst_soup_http_src_check_seekable (src);
1693
1694   return src->seekable;
1695 }
1696
1697 static gboolean
1698 gst_soup_http_src_do_seek (GstBaseSrc * bsrc, GstSegment * segment)
1699 {
1700   GstSoupHTTPSrc *src = GST_SOUP_HTTP_SRC (bsrc);
1701
1702   GST_DEBUG_OBJECT (src, "do_seek(%" G_GUINT64_FORMAT "-%" G_GUINT64_FORMAT
1703       ")", segment->start, segment->stop);
1704   if (src->read_position == segment->start &&
1705       src->request_position == src->read_position &&
1706       src->stop_position == segment->stop) {
1707     GST_DEBUG_OBJECT (src,
1708         "Seek to current read/end position and no seek pending");
1709     return TRUE;
1710   }
1711
1712   gst_soup_http_src_check_seekable (src);
1713
1714   /* If we have no headers we don't know yet if it is seekable or not.
1715    * Store the start position and error out later if it isn't */
1716   if (src->got_headers && !src->seekable) {
1717     GST_WARNING_OBJECT (src, "Not seekable");
1718     return FALSE;
1719   }
1720
1721   if (segment->rate < 0.0 || segment->format != GST_FORMAT_BYTES) {
1722     GST_WARNING_OBJECT (src, "Invalid seek segment");
1723     return FALSE;
1724   }
1725
1726   if (src->have_size && segment->start >= src->content_size) {
1727     GST_WARNING_OBJECT (src,
1728         "Potentially seeking behind end of file, might EOS immediately");
1729   }
1730
1731   /* Wait for create() to handle the jump in offset. */
1732   src->request_position = segment->start;
1733   src->stop_position = segment->stop;
1734
1735   return TRUE;
1736 }
1737
1738 static gboolean
1739 gst_soup_http_src_query (GstBaseSrc * bsrc, GstQuery * query)
1740 {
1741   GstSoupHTTPSrc *src = GST_SOUP_HTTP_SRC (bsrc);
1742   gboolean ret;
1743   GstSchedulingFlags flags;
1744   gint minsize, maxsize, align;
1745
1746   switch (GST_QUERY_TYPE (query)) {
1747     case GST_QUERY_URI:
1748       gst_query_set_uri (query, src->location);
1749       if (src->redirection_uri != NULL)
1750         gst_query_set_uri_redirection (query, src->redirection_uri);
1751       ret = TRUE;
1752       break;
1753     default:
1754       ret = FALSE;
1755       break;
1756   }
1757
1758   if (!ret)
1759     ret = GST_BASE_SRC_CLASS (parent_class)->query (bsrc, query);
1760
1761   switch (GST_QUERY_TYPE (query)) {
1762     case GST_QUERY_SCHEDULING:
1763       gst_query_parse_scheduling (query, &flags, &minsize, &maxsize, &align);
1764       flags |= GST_SCHEDULING_FLAG_BANDWIDTH_LIMITED;
1765       gst_query_set_scheduling (query, flags, minsize, maxsize, align);
1766       break;
1767     default:
1768       break;
1769   }
1770
1771   return ret;
1772 }
1773
1774 static gboolean
1775 gst_soup_http_src_set_location (GstSoupHTTPSrc * src, const gchar * uri,
1776     GError ** error)
1777 {
1778   const char *alt_schemes[] = { "icy://", "icyx://" };
1779   guint i;
1780
1781   if (src->location) {
1782     g_free (src->location);
1783     src->location = NULL;
1784   }
1785
1786   if (uri == NULL)
1787     return FALSE;
1788
1789   for (i = 0; i < G_N_ELEMENTS (alt_schemes); i++) {
1790     if (g_str_has_prefix (uri, alt_schemes[i])) {
1791       src->location =
1792           g_strdup_printf ("http://%s", uri + strlen (alt_schemes[i]));
1793       return TRUE;
1794     }
1795   }
1796
1797   if (src->redirection_uri) {
1798     g_free (src->redirection_uri);
1799     src->redirection_uri = NULL;
1800   }
1801
1802   src->location = g_strdup (uri);
1803
1804   return TRUE;
1805 }
1806
1807 static gboolean
1808 gst_soup_http_src_set_proxy (GstSoupHTTPSrc * src, const gchar * uri)
1809 {
1810   if (src->proxy) {
1811     soup_uri_free (src->proxy);
1812     src->proxy = NULL;
1813   }
1814   if (g_str_has_prefix (uri, "http://")) {
1815     src->proxy = soup_uri_new (uri);
1816   } else {
1817     gchar *new_uri = g_strconcat ("http://", uri, NULL);
1818
1819     src->proxy = soup_uri_new (new_uri);
1820     g_free (new_uri);
1821   }
1822
1823   return TRUE;
1824 }
1825
1826 static guint
1827 gst_soup_http_src_uri_get_type (GType type)
1828 {
1829   return GST_URI_SRC;
1830 }
1831
1832 static const gchar *const *
1833 gst_soup_http_src_uri_get_protocols (GType type)
1834 {
1835   static const gchar *protocols[] = { "http", "https", "icy", "icyx", NULL };
1836
1837   return protocols;
1838 }
1839
1840 static gchar *
1841 gst_soup_http_src_uri_get_uri (GstURIHandler * handler)
1842 {
1843   GstSoupHTTPSrc *src = GST_SOUP_HTTP_SRC (handler);
1844
1845   /* FIXME: make thread-safe */
1846   return g_strdup (src->location);
1847 }
1848
1849 static gboolean
1850 gst_soup_http_src_uri_set_uri (GstURIHandler * handler, const gchar * uri,
1851     GError ** error)
1852 {
1853   GstSoupHTTPSrc *src = GST_SOUP_HTTP_SRC (handler);
1854
1855   return gst_soup_http_src_set_location (src, uri, error);
1856 }
1857
1858 static void
1859 gst_soup_http_src_uri_handler_init (gpointer g_iface, gpointer iface_data)
1860 {
1861   GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface;
1862
1863   iface->get_type = gst_soup_http_src_uri_get_type;
1864   iface->get_protocols = gst_soup_http_src_uri_get_protocols;
1865   iface->get_uri = gst_soup_http_src_uri_get_uri;
1866   iface->set_uri = gst_soup_http_src_uri_set_uri;
1867 }