30d2f79703834a0b8ed3711647df23cea27b1159
[platform/upstream/gstreamer.git] / subprojects / gst-plugins-good / ext / soup / gstsouphttpsrc.c
1 /* GStreamer
2  * Copyright (C) 2007-2008 Wouter Cloetens <wouter@mind.be>
3  * Copyright (C) 2021 Igalia S.L.
4  *
5  * This library is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU Library General Public
7  * License as published by the Free Software Foundation; either
8  * version 2 of the License, or (at your option) any later version.
9  *
10  * This library is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * Library General Public License for more
14  */
15
16 /**
17  * SECTION:element-souphttpsrc
18  * @title: souphttpsrc
19  *
20  * This plugin reads data from a remote location specified by a URI.
21  * Supported protocols are 'http', 'https'.
22  *
23  * An HTTP proxy must be specified by its URL.
24  * If the "http_proxy" environment variable is set, its value is used.
25  * If built with libsoup's GNOME integration features, the GNOME proxy
26  * configuration will be used, or failing that, proxy autodetection.
27  * The #GstSoupHTTPSrc:proxy property can be used to override the default.
28  *
29  * In case the #GstSoupHTTPSrc:iradio-mode property is set and the location is
30  * an HTTP resource, souphttpsrc will send special Icecast HTTP headers to the
31  * server to request additional Icecast meta-information.
32  * If the server is not an Icecast server, it will behave as if the
33  * #GstSoupHTTPSrc:iradio-mode property were not set. If it is, souphttpsrc will
34  * output data with a media type of application/x-icy, in which case you will
35  * need to use the #GstICYDemux element as follow-up element to extract the Icecast
36  * metadata and to determine the underlying media type.
37  *
38  * ## Example launch line
39  * |[
40  * gst-launch-1.0 -v souphttpsrc location=https://some.server.org/index.html
41  *     ! filesink location=/home/joe/server.html
42  * ]| The above pipeline reads a web page from a server using the HTTPS protocol
43  * and writes it to a local file.
44  * |[
45  * gst-launch-1.0 -v souphttpsrc user-agent="FooPlayer 0.99 beta"
46  *     automatic-redirect=false proxy=http://proxy.intranet.local:8080
47  *     location=http://music.foobar.com/demo.mp3 ! mpgaudioparse
48  *     ! mpg123audiodec ! audioconvert ! audioresample ! autoaudiosink
49  * ]| The above pipeline will read and decode and play an mp3 file from a
50  * web server using the HTTP protocol. If the server sends redirects,
51  * the request fails instead of following the redirect. The specified
52  * HTTP proxy server is used. The User-Agent HTTP request header
53  * is set to a custom string instead of "GStreamer souphttpsrc."
54  * |[
55  * gst-launch-1.0 -v souphttpsrc location=http://10.11.12.13/mjpeg
56  *     do-timestamp=true ! multipartdemux
57  *     ! image/jpeg,width=640,height=480 ! matroskamux
58  *     ! filesink location=mjpeg.mkv
59  * ]| The above pipeline reads a motion JPEG stream from an IP camera
60  * using the HTTP protocol, encoded as mime/multipart image/jpeg
61  * parts, and writes a Matroska motion JPEG file. The width and
62  * height properties are set in the caps to provide the Matroska
63  * multiplexer with the information to set this in the header.
64  * Timestamps are set on the buffers as they arrive from the camera.
65  * These are used by the mime/multipart demultiplexer to emit timestamps
66  * on the JPEG-encoded video frame buffers. This allows the Matroska
67  * multiplexer to timestamp the frames in the resulting file.
68  *
69  */
70
71 #ifdef HAVE_CONFIG_H
72 #include "config.h"
73 #endif
74
75 #include <string.h>
76 #ifdef HAVE_STDLIB_H
77 #include <stdlib.h>             /* atoi() */
78 #endif
79 #include <gst/gstelement.h>
80 #include <gst/gst-i18n-plugin.h>
81 #include "gstsoupelements.h"
82 #include "gstsouphttpsrc.h"
83 #include "gstsouputils.h"
84
85 #include <gst/tag/tag.h>
86
87 /* this is a simple wrapper class around SoupSession; it exists in order to
88  * have a refcountable owner for the actual SoupSession + the thread it runs
89  * in and its main loop (we cannot inverse the ownership hierarchy, because
90  * the thread + loop are actually longer lived than the session)
91  *
92  * it is entirely private to this implementation
93  */
94
95 #define GST_TYPE_SOUP_SESSION (gst_soup_session_get_type())
96 #define GST_SOUP_SESSION(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_TYPE_SOUP_SESSION, GstSoupSession))
97
98 GType gst_soup_session_get_type (void);
99
100 typedef struct _GstSoupSessionClass GstSoupSessionClass;
101
102 struct _GstSoupSession
103 {
104   GObject parent_instance;
105
106   SoupSession *session;
107   GThread *thread;
108   GMainLoop *loop;
109 };
110
111 struct _GstSoupSessionClass
112 {
113   GObjectClass parent_class;
114 };
115
116 G_DEFINE_TYPE (GstSoupSession, gst_soup_session, G_TYPE_OBJECT);
117
118 static void
119 gst_soup_session_init (GstSoupSession * sess)
120 {
121 }
122
123 static gboolean
124 _soup_session_finalize_cb (gpointer user_data)
125 {
126   GstSoupSession *sess = user_data;
127
128   g_main_loop_quit (sess->loop);
129
130   return FALSE;
131 }
132
133 static void
134 gst_soup_session_finalize (GObject * obj)
135 {
136   GstSoupSession *sess = GST_SOUP_SESSION (obj);
137   GSource *src;
138
139   /* handle disposing of failure cases */
140   if (!sess->loop)
141     return;
142
143   src = g_idle_source_new ();
144
145   g_source_set_callback (src, _soup_session_finalize_cb, sess, NULL);
146   g_source_attach (src, g_main_loop_get_context (sess->loop));
147   g_source_unref (src);
148
149   /* finish off thread and the loop; ensure it's not from the thread */
150   g_assert (!g_main_context_is_owner (g_main_loop_get_context (sess->loop)));
151   g_thread_join (sess->thread);
152   g_main_loop_unref (sess->loop);
153 }
154
155 static void
156 gst_soup_session_class_init (GstSoupSessionClass * klass)
157 {
158   GObjectClass *gclass = G_OBJECT_CLASS (klass);
159
160   gclass->finalize = gst_soup_session_finalize;
161 }
162
163 GST_DEBUG_CATEGORY_STATIC (souphttpsrc_debug);
164 #define GST_CAT_DEFAULT souphttpsrc_debug
165
166 #define GST_SOUP_SESSION_CONTEXT "gst.soup.session"
167
168 static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
169     GST_PAD_SRC,
170     GST_PAD_ALWAYS,
171     GST_STATIC_CAPS_ANY);
172
173 enum
174 {
175   PROP_0,
176   PROP_LOCATION,
177   PROP_IS_LIVE,
178   PROP_USER_AGENT,
179   PROP_AUTOMATIC_REDIRECT,
180   PROP_PROXY,
181   PROP_USER_ID,
182   PROP_USER_PW,
183   PROP_PROXY_ID,
184   PROP_PROXY_PW,
185   PROP_COOKIES,
186   PROP_IRADIO_MODE,
187   PROP_TIMEOUT,
188   PROP_EXTRA_HEADERS,
189   PROP_SOUP_LOG_LEVEL,
190   PROP_COMPRESS,
191   PROP_KEEP_ALIVE,
192   PROP_SSL_STRICT,
193   PROP_SSL_CA_FILE,
194   PROP_SSL_USE_SYSTEM_CA_FILE,
195   PROP_TLS_DATABASE,
196   PROP_RETRIES,
197   PROP_METHOD,
198   PROP_TLS_INTERACTION,
199 };
200
201 #define DEFAULT_USER_AGENT           "GStreamer souphttpsrc " PACKAGE_VERSION " "
202 #define DEFAULT_IRADIO_MODE          TRUE
203 #define DEFAULT_SOUP_LOG_LEVEL       SOUP_LOGGER_LOG_HEADERS
204 #define DEFAULT_COMPRESS             FALSE
205 #define DEFAULT_KEEP_ALIVE           TRUE
206 #define DEFAULT_SSL_STRICT           TRUE
207 #define DEFAULT_SSL_CA_FILE          NULL
208 #define DEFAULT_SSL_USE_SYSTEM_CA_FILE TRUE
209 #define DEFAULT_TLS_DATABASE         NULL
210 #define DEFAULT_TLS_INTERACTION      NULL
211 #define DEFAULT_TIMEOUT              15
212 #define DEFAULT_RETRIES              3
213 #define DEFAULT_SOUP_METHOD          NULL
214
215 #define GROW_BLOCKSIZE_LIMIT 1
216 #define GROW_BLOCKSIZE_COUNT 1
217 #define GROW_BLOCKSIZE_FACTOR 2
218 #define REDUCE_BLOCKSIZE_LIMIT 0.20
219 #define REDUCE_BLOCKSIZE_COUNT 2
220 #define REDUCE_BLOCKSIZE_FACTOR 0.5
221 #define GROW_TIME_LIMIT (1 * GST_SECOND)
222
223 static void gst_soup_http_src_uri_handler_init (gpointer g_iface,
224     gpointer iface_data);
225 static void gst_soup_http_src_finalize (GObject * gobject);
226 static void gst_soup_http_src_dispose (GObject * gobject);
227
228 static void gst_soup_http_src_set_property (GObject * object, guint prop_id,
229     const GValue * value, GParamSpec * pspec);
230 static void gst_soup_http_src_get_property (GObject * object, guint prop_id,
231     GValue * value, GParamSpec * pspec);
232
233 static GstStateChangeReturn gst_soup_http_src_change_state (GstElement *
234     element, GstStateChange transition);
235 static void gst_soup_http_src_set_context (GstElement * element,
236     GstContext * context);
237 static GstFlowReturn gst_soup_http_src_create (GstPushSrc * psrc,
238     GstBuffer ** outbuf);
239 static gboolean gst_soup_http_src_start (GstBaseSrc * bsrc);
240 static gboolean gst_soup_http_src_stop (GstBaseSrc * bsrc);
241 static gboolean gst_soup_http_src_get_size (GstBaseSrc * bsrc, guint64 * size);
242 static gboolean gst_soup_http_src_is_seekable (GstBaseSrc * bsrc);
243 static gboolean gst_soup_http_src_do_seek (GstBaseSrc * bsrc,
244     GstSegment * segment);
245 static gboolean gst_soup_http_src_query (GstBaseSrc * bsrc, GstQuery * query);
246 static gboolean gst_soup_http_src_unlock (GstBaseSrc * bsrc);
247 static gboolean gst_soup_http_src_unlock_stop (GstBaseSrc * bsrc);
248 static gboolean gst_soup_http_src_set_location (GstSoupHTTPSrc * src,
249     const gchar * uri, GError ** error);
250 static gboolean gst_soup_http_src_set_proxy (GstSoupHTTPSrc * src,
251     const gchar * uri);
252 static char *gst_soup_http_src_unicodify (const char *str);
253 static gboolean gst_soup_http_src_build_message (GstSoupHTTPSrc * src,
254     const gchar * method);
255 static gboolean gst_soup_http_src_add_range_header (GstSoupHTTPSrc * src,
256     guint64 offset, guint64 stop_offset);
257 static gboolean gst_soup_http_src_session_open (GstSoupHTTPSrc * src);
258 static void gst_soup_http_src_session_close (GstSoupHTTPSrc * src);
259 static GstFlowReturn gst_soup_http_src_parse_status (SoupMessage * msg,
260     GstSoupHTTPSrc * src);
261 static GstFlowReturn gst_soup_http_src_got_headers (GstSoupHTTPSrc * src,
262     SoupMessage * msg);
263 static void gst_soup_http_src_authenticate_cb_2 (SoupSession *,
264     SoupMessage * msg, SoupAuth * auth, gboolean retrying, gpointer);
265 static gboolean gst_soup_http_src_authenticate_cb (SoupMessage * msg,
266     SoupAuth * auth, gboolean retrying, gpointer);
267 static gboolean gst_soup_http_src_accept_certificate_cb (SoupMessage * msg,
268     GTlsCertificate * tls_certificate, GTlsCertificateFlags tls_errors,
269     gpointer user_data);
270
271 #define gst_soup_http_src_parent_class parent_class
272 G_DEFINE_TYPE_WITH_CODE (GstSoupHTTPSrc, gst_soup_http_src, GST_TYPE_PUSH_SRC,
273     G_IMPLEMENT_INTERFACE (GST_TYPE_URI_HANDLER,
274         gst_soup_http_src_uri_handler_init));
275 GST_ELEMENT_REGISTER_DEFINE_WITH_CODE (souphttpsrc, "souphttpsrc",
276     GST_RANK_PRIMARY, GST_TYPE_SOUP_HTTP_SRC, soup_element_init (plugin));
277
278 static void
279 gst_soup_http_src_class_init (GstSoupHTTPSrcClass * klass)
280 {
281   GObjectClass *gobject_class;
282   GstElementClass *gstelement_class;
283   GstBaseSrcClass *gstbasesrc_class;
284   GstPushSrcClass *gstpushsrc_class;
285
286   gobject_class = (GObjectClass *) klass;
287   gstelement_class = (GstElementClass *) klass;
288   gstbasesrc_class = (GstBaseSrcClass *) klass;
289   gstpushsrc_class = (GstPushSrcClass *) klass;
290
291   gobject_class->set_property = gst_soup_http_src_set_property;
292   gobject_class->get_property = gst_soup_http_src_get_property;
293   gobject_class->finalize = gst_soup_http_src_finalize;
294   gobject_class->dispose = gst_soup_http_src_dispose;
295
296   g_object_class_install_property (gobject_class,
297       PROP_LOCATION,
298       g_param_spec_string ("location", "Location",
299           "Location to read from", "",
300           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
301   g_object_class_install_property (gobject_class,
302       PROP_USER_AGENT,
303       g_param_spec_string ("user-agent", "User-Agent",
304           "Value of the User-Agent HTTP request header field",
305           DEFAULT_USER_AGENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
306   g_object_class_install_property (gobject_class,
307       PROP_AUTOMATIC_REDIRECT,
308       g_param_spec_boolean ("automatic-redirect", "automatic-redirect",
309           "Automatically follow HTTP redirects (HTTP Status Code 3xx)",
310           TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
311   g_object_class_install_property (gobject_class,
312       PROP_PROXY,
313       g_param_spec_string ("proxy", "Proxy",
314           "HTTP proxy server URI", "",
315           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
316   g_object_class_install_property (gobject_class,
317       PROP_USER_ID,
318       g_param_spec_string ("user-id", "user-id",
319           "HTTP location URI user id for authentication", "",
320           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
321   g_object_class_install_property (gobject_class, PROP_USER_PW,
322       g_param_spec_string ("user-pw", "user-pw",
323           "HTTP location URI user password for authentication", "",
324           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
325   g_object_class_install_property (gobject_class, PROP_PROXY_ID,
326       g_param_spec_string ("proxy-id", "proxy-id",
327           "HTTP proxy URI user id for authentication", "",
328           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
329   g_object_class_install_property (gobject_class, PROP_PROXY_PW,
330       g_param_spec_string ("proxy-pw", "proxy-pw",
331           "HTTP proxy URI user password for authentication", "",
332           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
333   g_object_class_install_property (gobject_class, PROP_COOKIES,
334       g_param_spec_boxed ("cookies", "Cookies", "HTTP request cookies",
335           G_TYPE_STRV, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
336   g_object_class_install_property (gobject_class, PROP_IS_LIVE,
337       g_param_spec_boolean ("is-live", "is-live", "Act like a live source",
338           FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
339   g_object_class_install_property (gobject_class, PROP_TIMEOUT,
340       g_param_spec_uint ("timeout", "timeout",
341           "Value in seconds to timeout a blocking I/O (0 = No timeout).", 0,
342           3600, DEFAULT_TIMEOUT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
343   g_object_class_install_property (gobject_class, PROP_EXTRA_HEADERS,
344       g_param_spec_boxed ("extra-headers", "Extra Headers",
345           "Extra headers to append to the HTTP request",
346           GST_TYPE_STRUCTURE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
347   g_object_class_install_property (gobject_class, PROP_IRADIO_MODE,
348       g_param_spec_boolean ("iradio-mode", "iradio-mode",
349           "Enable internet radio mode (ask server to send shoutcast/icecast "
350           "metadata interleaved with the actual stream data)",
351           DEFAULT_IRADIO_MODE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
352
353  /**
354    * GstSoupHTTPSrc::http-log-level:
355    *
356    * If set and > 0, captures and dumps HTTP session data as
357    * log messages if log level >= GST_LEVEL_TRACE
358    *
359    * Since: 1.4
360    */
361   g_object_class_install_property (gobject_class, PROP_SOUP_LOG_LEVEL,
362       g_param_spec_enum ("http-log-level", "HTTP log level",
363           "Set log level for soup's HTTP session log",
364           _soup_logger_log_level_get_type (),
365           DEFAULT_SOUP_LOG_LEVEL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
366
367   /**
368    * GstSoupHTTPSrc::compress:
369    *
370    * If set to %TRUE, souphttpsrc will automatically handle gzip
371    * and deflate Content-Encodings. This does not make much difference
372    * and causes more load for normal media files, but makes a real
373    * difference in size for plaintext files.
374    *
375    * Since: 1.4
376    */
377   g_object_class_install_property (gobject_class, PROP_COMPRESS,
378       g_param_spec_boolean ("compress", "Compress",
379           "Allow compressed content encodings",
380           DEFAULT_COMPRESS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
381
382  /**
383    * GstSoupHTTPSrc::keep-alive:
384    *
385    * If set to %TRUE, souphttpsrc will keep alive connections when being
386    * set to READY state and only will close connections when connecting
387    * to a different server or when going to NULL state..
388    *
389    * Since: 1.4
390    */
391   g_object_class_install_property (gobject_class, PROP_KEEP_ALIVE,
392       g_param_spec_boolean ("keep-alive", "keep-alive",
393           "Use HTTP persistent connections", DEFAULT_KEEP_ALIVE,
394           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
395
396  /**
397    * GstSoupHTTPSrc::ssl-strict:
398    *
399    * If set to %TRUE, souphttpsrc will reject all SSL certificates that
400    * are considered invalid.
401    *
402    * Since: 1.4
403    */
404   g_object_class_install_property (gobject_class, PROP_SSL_STRICT,
405       g_param_spec_boolean ("ssl-strict", "SSL Strict",
406           "Strict SSL certificate checking", DEFAULT_SSL_STRICT,
407           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
408
409  /**
410    * GstSoupHTTPSrc::ssl-ca-file:
411    *
412    * A SSL anchor CA file that should be used for checking certificates
413    * instead of the system CA file.
414    *
415    * If this property is non-%NULL, #GstSoupHTTPSrc::ssl-use-system-ca-file
416    * value will be ignored.
417    *
418    * Deprecated: Use #GstSoupHTTPSrc::tls-database property instead. This
419    * property is no-op when libsoup3 is being used at runtime.
420    *
421    * Since: 1.4
422    */
423   g_object_class_install_property (gobject_class, PROP_SSL_CA_FILE,
424       g_param_spec_string ("ssl-ca-file", "SSL CA File",
425           "Location of a SSL anchor CA file to use", DEFAULT_SSL_CA_FILE,
426           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
427
428   /**
429    * GstSoupHTTPSrc::ssl-use-system-ca-file:
430    *
431    * If set to %TRUE, souphttpsrc will use the system's CA file for
432    * checking certificates, unless #GstSoupHTTPSrc::ssl-ca-file or
433    * #GstSoupHTTPSrc::tls-database are non-%NULL.
434    *
435    * Deprecated: This property is no-op when libsoup3 is being used at runtime.
436    *
437    * Since: 1.4
438    */
439   g_object_class_install_property (gobject_class, PROP_SSL_USE_SYSTEM_CA_FILE,
440       g_param_spec_boolean ("ssl-use-system-ca-file", "Use System CA File",
441           "Use system CA file", DEFAULT_SSL_USE_SYSTEM_CA_FILE,
442           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
443
444   /**
445    * GstSoupHTTPSrc::tls-database:
446    *
447    * TLS database with anchor certificate authorities used to validate
448    * the server certificate.
449    *
450    * If this property is non-%NULL, #GstSoupHTTPSrc::ssl-use-system-ca-file
451    * and #GstSoupHTTPSrc::ssl-ca-file values will be ignored.
452    *
453    * Since: 1.6
454    */
455   g_object_class_install_property (gobject_class, PROP_TLS_DATABASE,
456       g_param_spec_object ("tls-database", "TLS database",
457           "TLS database with anchor certificate authorities used to validate the server certificate",
458           G_TYPE_TLS_DATABASE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
459
460   /**
461    * GstSoupHTTPSrc::tls-interaction:
462    *
463    * A #GTlsInteraction object to be used when the connection or certificate
464    * database need to interact with the user. This will be used to prompt the
465    * user for passwords or certificate where necessary.
466    *
467    * Since: 1.8
468    */
469   g_object_class_install_property (gobject_class, PROP_TLS_INTERACTION,
470       g_param_spec_object ("tls-interaction", "TLS interaction",
471           "A GTlsInteraction object to be used when the connection or certificate database need to interact with the user.",
472           G_TYPE_TLS_INTERACTION, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
473
474  /**
475    * GstSoupHTTPSrc::retries:
476    *
477    * Maximum number of retries until giving up.
478    *
479    * Since: 1.4
480    */
481   g_object_class_install_property (gobject_class, PROP_RETRIES,
482       g_param_spec_int ("retries", "Retries",
483           "Maximum number of retries until giving up (-1=infinite)", -1,
484           G_MAXINT, DEFAULT_RETRIES,
485           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
486
487  /**
488    * GstSoupHTTPSrc::method
489    *
490    * The HTTP method to use when making a request
491    *
492    * Since: 1.6
493    */
494   g_object_class_install_property (gobject_class, PROP_METHOD,
495       g_param_spec_string ("method", "HTTP method",
496           "The HTTP method to use (GET, HEAD, OPTIONS, etc)",
497           DEFAULT_SOUP_METHOD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
498
499   gst_element_class_add_static_pad_template (gstelement_class, &srctemplate);
500
501   gst_element_class_set_static_metadata (gstelement_class, "HTTP client source",
502       "Source/Network",
503       "Receive data as a client over the network via HTTP using SOUP",
504       "Wouter Cloetens <wouter@mind.be>");
505   gstelement_class->change_state =
506       GST_DEBUG_FUNCPTR (gst_soup_http_src_change_state);
507   gstelement_class->set_context =
508       GST_DEBUG_FUNCPTR (gst_soup_http_src_set_context);
509
510   gstbasesrc_class->start = GST_DEBUG_FUNCPTR (gst_soup_http_src_start);
511   gstbasesrc_class->stop = GST_DEBUG_FUNCPTR (gst_soup_http_src_stop);
512   gstbasesrc_class->unlock = GST_DEBUG_FUNCPTR (gst_soup_http_src_unlock);
513   gstbasesrc_class->unlock_stop =
514       GST_DEBUG_FUNCPTR (gst_soup_http_src_unlock_stop);
515   gstbasesrc_class->get_size = GST_DEBUG_FUNCPTR (gst_soup_http_src_get_size);
516   gstbasesrc_class->is_seekable =
517       GST_DEBUG_FUNCPTR (gst_soup_http_src_is_seekable);
518   gstbasesrc_class->do_seek = GST_DEBUG_FUNCPTR (gst_soup_http_src_do_seek);
519   gstbasesrc_class->query = GST_DEBUG_FUNCPTR (gst_soup_http_src_query);
520
521   gstpushsrc_class->create = GST_DEBUG_FUNCPTR (gst_soup_http_src_create);
522
523   GST_DEBUG_CATEGORY_INIT (souphttpsrc_debug, "souphttpsrc", 0,
524       "SOUP HTTP src");
525 }
526
527 static void
528 gst_soup_http_src_reset (GstSoupHTTPSrc * src)
529 {
530   src->retry_count = 0;
531   src->have_size = FALSE;
532   src->got_headers = FALSE;
533   src->headers_ret = GST_FLOW_OK;
534   src->seekable = FALSE;
535   src->read_position = 0;
536   src->request_position = 0;
537   src->stop_position = -1;
538   src->content_size = 0;
539   src->have_body = FALSE;
540
541   src->reduce_blocksize_count = 0;
542   src->increase_blocksize_count = 0;
543   src->last_socket_read_time = 0;
544
545   g_cancellable_reset (src->cancellable);
546
547   gst_caps_replace (&src->src_caps, NULL);
548   g_free (src->iradio_name);
549   src->iradio_name = NULL;
550   g_free (src->iradio_genre);
551   src->iradio_genre = NULL;
552   g_free (src->iradio_url);
553   src->iradio_url = NULL;
554 }
555
556 static void
557 gst_soup_http_src_init (GstSoupHTTPSrc * src)
558 {
559   const gchar *proxy;
560
561   g_mutex_init (&src->session_mutex);
562   g_cond_init (&src->session_cond);
563   src->cancellable = g_cancellable_new ();
564   src->location = NULL;
565   src->redirection_uri = NULL;
566   src->automatic_redirect = TRUE;
567   src->user_agent = g_strdup (DEFAULT_USER_AGENT);
568   src->user_id = NULL;
569   src->user_pw = NULL;
570   src->proxy_id = NULL;
571   src->proxy_pw = NULL;
572   src->cookies = NULL;
573   src->iradio_mode = DEFAULT_IRADIO_MODE;
574   src->session = NULL;
575   src->external_session = NULL;
576   src->msg = NULL;
577   src->timeout = DEFAULT_TIMEOUT;
578   src->log_level = DEFAULT_SOUP_LOG_LEVEL;
579   src->compress = DEFAULT_COMPRESS;
580   src->keep_alive = DEFAULT_KEEP_ALIVE;
581   src->ssl_strict = DEFAULT_SSL_STRICT;
582   src->ssl_use_system_ca_file = DEFAULT_SSL_USE_SYSTEM_CA_FILE;
583   src->tls_database = DEFAULT_TLS_DATABASE;
584   src->tls_interaction = DEFAULT_TLS_INTERACTION;
585   src->max_retries = DEFAULT_RETRIES;
586   src->method = DEFAULT_SOUP_METHOD;
587   src->minimum_blocksize = gst_base_src_get_blocksize (GST_BASE_SRC_CAST (src));
588   proxy = g_getenv ("http_proxy");
589   if (!gst_soup_http_src_set_proxy (src, proxy)) {
590     GST_WARNING_OBJECT (src,
591         "The proxy in the http_proxy env var (\"%s\") cannot be parsed.",
592         proxy);
593   }
594
595   gst_base_src_set_automatic_eos (GST_BASE_SRC (src), FALSE);
596
597   gst_soup_http_src_reset (src);
598 }
599
600 static void
601 gst_soup_http_src_dispose (GObject * gobject)
602 {
603   GstSoupHTTPSrc *src = GST_SOUP_HTTP_SRC (gobject);
604
605   GST_DEBUG_OBJECT (src, "dispose");
606
607   gst_soup_http_src_session_close (src);
608
609   g_clear_object (&src->external_session);
610
611   G_OBJECT_CLASS (parent_class)->dispose (gobject);
612 }
613
614 static void
615 gst_soup_http_src_finalize (GObject * gobject)
616 {
617   GstSoupHTTPSrc *src = GST_SOUP_HTTP_SRC (gobject);
618
619   GST_DEBUG_OBJECT (src, "finalize");
620
621   g_mutex_clear (&src->session_mutex);
622   g_cond_clear (&src->session_cond);
623   g_object_unref (src->cancellable);
624   g_free (src->location);
625   g_free (src->redirection_uri);
626   g_free (src->user_agent);
627   if (src->proxy != NULL) {
628     gst_soup_uri_free (src->proxy);
629   }
630   g_free (src->user_id);
631   g_free (src->user_pw);
632   g_free (src->proxy_id);
633   g_free (src->proxy_pw);
634   g_strfreev (src->cookies);
635
636   if (src->extra_headers) {
637     gst_structure_free (src->extra_headers);
638     src->extra_headers = NULL;
639   }
640
641   g_free (src->ssl_ca_file);
642
643   if (src->tls_database)
644     g_object_unref (src->tls_database);
645   g_free (src->method);
646
647   if (src->tls_interaction)
648     g_object_unref (src->tls_interaction);
649
650   G_OBJECT_CLASS (parent_class)->finalize (gobject);
651 }
652
653 static void
654 gst_soup_http_src_set_property (GObject * object, guint prop_id,
655     const GValue * value, GParamSpec * pspec)
656 {
657   GstSoupHTTPSrc *src = GST_SOUP_HTTP_SRC (object);
658
659   switch (prop_id) {
660     case PROP_LOCATION:
661     {
662       const gchar *location;
663
664       location = g_value_get_string (value);
665
666       if (location == NULL) {
667         GST_WARNING ("location property cannot be NULL");
668         goto done;
669       }
670       if (!gst_soup_http_src_set_location (src, location, NULL)) {
671         GST_WARNING ("badly formatted location");
672         goto done;
673       }
674       break;
675     }
676     case PROP_USER_AGENT:
677       g_free (src->user_agent);
678       src->user_agent = g_value_dup_string (value);
679       break;
680     case PROP_IRADIO_MODE:
681       src->iradio_mode = g_value_get_boolean (value);
682       break;
683     case PROP_AUTOMATIC_REDIRECT:
684       src->automatic_redirect = g_value_get_boolean (value);
685       break;
686     case PROP_PROXY:
687     {
688       const gchar *proxy;
689
690       proxy = g_value_get_string (value);
691       if (!gst_soup_http_src_set_proxy (src, proxy)) {
692         GST_WARNING ("badly formatted proxy URI");
693         goto done;
694       }
695       break;
696     }
697     case PROP_COOKIES:
698       g_strfreev (src->cookies);
699       src->cookies = g_strdupv (g_value_get_boxed (value));
700       break;
701     case PROP_IS_LIVE:
702       gst_base_src_set_live (GST_BASE_SRC (src), g_value_get_boolean (value));
703       break;
704     case PROP_USER_ID:
705       g_free (src->user_id);
706       src->user_id = g_value_dup_string (value);
707       break;
708     case PROP_USER_PW:
709       g_free (src->user_pw);
710       src->user_pw = g_value_dup_string (value);
711       break;
712     case PROP_PROXY_ID:
713       g_free (src->proxy_id);
714       src->proxy_id = g_value_dup_string (value);
715       break;
716     case PROP_PROXY_PW:
717       g_free (src->proxy_pw);
718       src->proxy_pw = g_value_dup_string (value);
719       break;
720     case PROP_TIMEOUT:
721       src->timeout = g_value_get_uint (value);
722       break;
723     case PROP_EXTRA_HEADERS:{
724       const GstStructure *s = gst_value_get_structure (value);
725
726       if (src->extra_headers)
727         gst_structure_free (src->extra_headers);
728
729       src->extra_headers = s ? gst_structure_copy (s) : NULL;
730       break;
731     }
732     case PROP_SOUP_LOG_LEVEL:
733       src->log_level = g_value_get_enum (value);
734       break;
735     case PROP_COMPRESS:
736       src->compress = g_value_get_boolean (value);
737       break;
738     case PROP_KEEP_ALIVE:
739       src->keep_alive = g_value_get_boolean (value);
740       break;
741     case PROP_SSL_STRICT:
742       src->ssl_strict = g_value_get_boolean (value);
743       break;
744     case PROP_TLS_DATABASE:
745       g_clear_object (&src->tls_database);
746       src->tls_database = g_value_dup_object (value);
747       break;
748     case PROP_TLS_INTERACTION:
749       g_clear_object (&src->tls_interaction);
750       src->tls_interaction = g_value_dup_object (value);
751       break;
752     case PROP_RETRIES:
753       src->max_retries = g_value_get_int (value);
754       break;
755     case PROP_METHOD:
756       g_free (src->method);
757       src->method = g_value_dup_string (value);
758       break;
759     case PROP_SSL_CA_FILE:
760       if (gst_soup_loader_get_api_version () == 2) {
761         g_free (src->ssl_ca_file);
762         src->ssl_ca_file = g_value_dup_string (value);
763       }
764       break;
765     case PROP_SSL_USE_SYSTEM_CA_FILE:
766       if (gst_soup_loader_get_api_version () == 2) {
767         src->ssl_use_system_ca_file = g_value_get_boolean (value);
768       }
769       break;
770     default:
771       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
772       break;
773   }
774 done:
775   return;
776 }
777
778 static void
779 gst_soup_http_src_get_property (GObject * object, guint prop_id,
780     GValue * value, GParamSpec * pspec)
781 {
782   GstSoupHTTPSrc *src = GST_SOUP_HTTP_SRC (object);
783
784   switch (prop_id) {
785     case PROP_LOCATION:
786       g_value_set_string (value, src->location);
787       break;
788     case PROP_USER_AGENT:
789       g_value_set_string (value, src->user_agent);
790       break;
791     case PROP_AUTOMATIC_REDIRECT:
792       g_value_set_boolean (value, src->automatic_redirect);
793       break;
794     case PROP_PROXY:
795       if (src->proxy == NULL)
796         g_value_set_static_string (value, "");
797       else {
798         char *proxy = gst_soup_uri_to_string (src->proxy);
799         g_value_set_string (value, proxy);
800         g_free (proxy);
801       }
802       break;
803     case PROP_COOKIES:
804       g_value_set_boxed (value, g_strdupv (src->cookies));
805       break;
806     case PROP_IS_LIVE:
807       g_value_set_boolean (value, gst_base_src_is_live (GST_BASE_SRC (src)));
808       break;
809     case PROP_IRADIO_MODE:
810       g_value_set_boolean (value, src->iradio_mode);
811       break;
812     case PROP_USER_ID:
813       g_value_set_string (value, src->user_id);
814       break;
815     case PROP_USER_PW:
816       g_value_set_string (value, src->user_pw);
817       break;
818     case PROP_PROXY_ID:
819       g_value_set_string (value, src->proxy_id);
820       break;
821     case PROP_PROXY_PW:
822       g_value_set_string (value, src->proxy_pw);
823       break;
824     case PROP_TIMEOUT:
825       g_value_set_uint (value, src->timeout);
826       break;
827     case PROP_EXTRA_HEADERS:
828       gst_value_set_structure (value, src->extra_headers);
829       break;
830     case PROP_SOUP_LOG_LEVEL:
831       g_value_set_enum (value, src->log_level);
832       break;
833     case PROP_COMPRESS:
834       g_value_set_boolean (value, src->compress);
835       break;
836     case PROP_KEEP_ALIVE:
837       g_value_set_boolean (value, src->keep_alive);
838       break;
839     case PROP_SSL_STRICT:
840       g_value_set_boolean (value, src->ssl_strict);
841       break;
842     case PROP_TLS_DATABASE:
843       g_value_set_object (value, src->tls_database);
844       break;
845     case PROP_TLS_INTERACTION:
846       g_value_set_object (value, src->tls_interaction);
847       break;
848     case PROP_RETRIES:
849       g_value_set_int (value, src->max_retries);
850       break;
851     case PROP_METHOD:
852       g_value_set_string (value, src->method);
853       break;
854     case PROP_SSL_CA_FILE:
855       if (gst_soup_loader_get_api_version () == 2)
856         g_value_set_string (value, src->ssl_ca_file);
857       break;
858     case PROP_SSL_USE_SYSTEM_CA_FILE:
859       if (gst_soup_loader_get_api_version () == 2)
860         g_value_set_boolean (value, src->ssl_use_system_ca_file);
861       break;
862     default:
863       G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
864       break;
865   }
866 }
867
868 static gchar *
869 gst_soup_http_src_unicodify (const gchar * str)
870 {
871   const gchar *env_vars[] = { "GST_ICY_TAG_ENCODING",
872     "GST_TAG_ENCODING", NULL
873   };
874
875   return gst_tag_freeform_string_to_utf8 (str, -1, env_vars);
876 }
877
878 static gboolean
879 gst_soup_http_src_add_range_header (GstSoupHTTPSrc * src, guint64 offset,
880     guint64 stop_offset)
881 {
882   gchar buf[64];
883   gint rc;
884   SoupMessageHeaders *request_headers =
885       _soup_message_get_request_headers (src->msg);
886
887   _soup_message_headers_remove (request_headers, "Range");
888   if (offset || stop_offset != -1) {
889     if (stop_offset != -1) {
890       g_assert (offset != stop_offset);
891
892       rc = g_snprintf (buf, sizeof (buf), "bytes=%" G_GUINT64_FORMAT "-%"
893           G_GUINT64_FORMAT, offset, (stop_offset > 0) ? stop_offset - 1 :
894           stop_offset);
895     } else {
896       rc = g_snprintf (buf, sizeof (buf), "bytes=%" G_GUINT64_FORMAT "-",
897           offset);
898     }
899     if (rc > sizeof (buf) || rc < 0)
900       return FALSE;
901     _soup_message_headers_append (request_headers, "Range", buf);
902   }
903   src->read_position = offset;
904   return TRUE;
905 }
906
907 static gboolean
908 _append_extra_header (GQuark field_id, const GValue * value, gpointer user_data)
909 {
910   GstSoupHTTPSrc *src = GST_SOUP_HTTP_SRC (user_data);
911   const gchar *field_name = g_quark_to_string (field_id);
912   gchar *field_content = NULL;
913   SoupMessageHeaders *request_headers =
914       _soup_message_get_request_headers (src->msg);
915
916   if (G_VALUE_TYPE (value) == G_TYPE_STRING) {
917     field_content = g_value_dup_string (value);
918   } else {
919     GValue dest = { 0, };
920
921     g_value_init (&dest, G_TYPE_STRING);
922     if (g_value_transform (value, &dest)) {
923       field_content = g_value_dup_string (&dest);
924     }
925   }
926
927   if (field_content == NULL) {
928     GST_ERROR_OBJECT (src, "extra-headers field '%s' contains no value "
929         "or can't be converted to a string", field_name);
930     return FALSE;
931   }
932
933   GST_DEBUG_OBJECT (src, "Appending extra header: \"%s: %s\"", field_name,
934       field_content);
935   _soup_message_headers_append (request_headers, field_name, field_content);
936
937   g_free (field_content);
938
939   return TRUE;
940 }
941
942 static gboolean
943 _append_extra_headers (GQuark field_id, const GValue * value,
944     gpointer user_data)
945 {
946   if (G_VALUE_TYPE (value) == GST_TYPE_ARRAY) {
947     guint n = gst_value_array_get_size (value);
948     guint i;
949
950     for (i = 0; i < n; i++) {
951       const GValue *v = gst_value_array_get_value (value, i);
952
953       if (!_append_extra_header (field_id, v, user_data))
954         return FALSE;
955     }
956   } else if (G_VALUE_TYPE (value) == GST_TYPE_LIST) {
957     guint n = gst_value_list_get_size (value);
958     guint i;
959
960     for (i = 0; i < n; i++) {
961       const GValue *v = gst_value_list_get_value (value, i);
962
963       if (!_append_extra_header (field_id, v, user_data))
964         return FALSE;
965     }
966   } else {
967     return _append_extra_header (field_id, value, user_data);
968   }
969
970   return TRUE;
971 }
972
973
974 static gboolean
975 gst_soup_http_src_add_extra_headers (GstSoupHTTPSrc * src)
976 {
977   if (!src->extra_headers)
978     return TRUE;
979
980   return gst_structure_foreach (src->extra_headers, _append_extra_headers, src);
981 }
982
983 static gpointer
984 thread_func (gpointer user_data)
985 {
986   GstSoupHTTPSrc *src = user_data;
987   GstSoupSession *session = src->session;
988   GMainContext *ctx;
989
990   GST_DEBUG_OBJECT (src, "thread start");
991
992   ctx = g_main_loop_get_context (session->loop);
993
994   g_main_context_push_thread_default (ctx);
995
996   /* We explicitly set User-Agent to NULL here and overwrite it per message
997    * to be able to have the same session with different User-Agents per
998    * source */
999   session->session =
1000       _soup_session_new_with_options ("user-agent", NULL,
1001       "timeout", src->timeout, "tls-interaction", src->tls_interaction,
1002       /* Unset the limit the number of maximum allowed connections */
1003       "max-conns", src->session_is_shared ? G_MAXINT : 10,
1004       "max-conns-per-host", src->session_is_shared ? G_MAXINT : 2, NULL);
1005   g_assert (session->session);
1006
1007   if (gst_soup_loader_get_api_version () == 3) {
1008     if (src->proxy != NULL) {
1009       GProxyResolver *proxy_resolver;
1010       char *proxy_string = gst_soup_uri_to_string (src->proxy);
1011       proxy_resolver = g_simple_proxy_resolver_new (proxy_string, NULL);
1012       g_free (proxy_string);
1013       g_object_set (src->session->session, "proxy-resolver", proxy_resolver,
1014           NULL);
1015       g_object_unref (proxy_resolver);
1016     }
1017 #if !defined(STATIC_SOUP) || STATIC_SOUP == 2
1018   } else {
1019     g_object_set (session->session, "ssl-strict", src->ssl_strict, NULL);
1020     if (src->proxy != NULL) {
1021       /* Need #if because there's no proxy->soup_uri when STATIC_SOUP == 3 */
1022       g_object_set (session->session, "proxy-uri", src->proxy->soup_uri, NULL);
1023     }
1024 #endif
1025   }
1026
1027   gst_soup_util_log_setup (session->session, src->log_level,
1028       G_OBJECT (session));
1029   if (gst_soup_loader_get_api_version () < 3) {
1030     _soup_session_add_feature_by_type (session->session,
1031         _soup_content_decoder_get_type ());
1032   }
1033   _soup_session_add_feature_by_type (session->session,
1034       _soup_cookie_jar_get_type ());
1035
1036   /* soup2: connect the authenticate handler for the src that spawned the
1037    * session (i.e. the first owner); other users of this session will connect
1038    * their own after fetching the external session; the callback will handle
1039    * this correctly (it checks if the message belongs to the current src
1040    * and exits early if it does not)
1041    */
1042   if (gst_soup_loader_get_api_version () < 3) {
1043     g_signal_connect (session->session, "authenticate",
1044         G_CALLBACK (gst_soup_http_src_authenticate_cb_2), src);
1045   }
1046
1047   if (!src->session_is_shared) {
1048     if (src->tls_database)
1049       g_object_set (src->session->session, "tls-database", src->tls_database,
1050           NULL);
1051     else if (gst_soup_loader_get_api_version () == 2) {
1052       if (src->ssl_ca_file)
1053         g_object_set (src->session->session, "ssl-ca-file", src->ssl_ca_file,
1054             NULL);
1055       else
1056         g_object_set (src->session->session, "ssl-use-system-ca-file",
1057             src->ssl_use_system_ca_file, NULL);
1058     }
1059   }
1060
1061   /* Once the main loop is running, the source element that created this
1062    * session might disappear if the session is shared with other source
1063    * elements.
1064    */
1065   src = NULL;
1066
1067   g_main_loop_run (session->loop);
1068
1069   /* Abort any pending operations on the session ... */
1070   _soup_session_abort (session->session);
1071   g_clear_object (&session->session);
1072
1073   /* ... and iterate the main context until nothing is pending anymore */
1074   while (g_main_context_iteration (ctx, FALSE));
1075
1076   g_main_context_pop_thread_default (ctx);
1077
1078   GST_DEBUG_OBJECT (session, "thread stop");
1079
1080   return NULL;
1081 }
1082
1083 static gboolean
1084 _session_ready_cb (gpointer user_data)
1085 {
1086   GstSoupHTTPSrc *src = user_data;
1087
1088   GST_DEBUG_OBJECT (src, "thread ready");
1089
1090   g_mutex_lock (&src->session_mutex);
1091   g_cond_signal (&src->session_cond);
1092   g_mutex_unlock (&src->session_mutex);
1093
1094   return FALSE;
1095 }
1096
1097 /* called with session_mutex taken */
1098 static gboolean
1099 gst_soup_http_src_session_open (GstSoupHTTPSrc * src)
1100 {
1101   GstQuery *query;
1102   gboolean can_share;
1103
1104   if (src->session) {
1105     GST_DEBUG_OBJECT (src, "Session is already open");
1106     return TRUE;
1107   }
1108
1109   if (!src->location) {
1110     GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, (_("No URL set.")),
1111         ("Missing location property"));
1112     return FALSE;
1113   }
1114
1115   can_share = (src->timeout == DEFAULT_TIMEOUT)
1116       && (src->cookies == NULL)
1117       && (src->ssl_strict == DEFAULT_SSL_STRICT)
1118       && (src->tls_interaction == NULL) && (src->proxy == NULL)
1119       && (src->tls_database == DEFAULT_TLS_DATABASE);
1120
1121   if (gst_soup_loader_get_api_version () == 2)
1122     can_share = can_share && (src->ssl_ca_file == DEFAULT_SSL_CA_FILE) &&
1123         (src->ssl_use_system_ca_file == DEFAULT_SSL_USE_SYSTEM_CA_FILE);
1124
1125   query = gst_query_new_context (GST_SOUP_SESSION_CONTEXT);
1126   if (gst_pad_peer_query (GST_BASE_SRC_PAD (src), query)) {
1127     GstContext *context;
1128
1129     gst_query_parse_context (query, &context);
1130     gst_element_set_context (GST_ELEMENT_CAST (src), context);
1131   } else {
1132     GstMessage *message;
1133
1134     message =
1135         gst_message_new_need_context (GST_OBJECT_CAST (src),
1136         GST_SOUP_SESSION_CONTEXT);
1137     gst_element_post_message (GST_ELEMENT_CAST (src), message);
1138   }
1139   gst_query_unref (query);
1140
1141   GST_OBJECT_LOCK (src);
1142
1143   src->session_is_shared = can_share;
1144
1145   if (src->external_session && can_share) {
1146     GST_DEBUG_OBJECT (src, "Using external session %p", src->external_session);
1147     src->session = g_object_ref (src->external_session);
1148     /* for soup2, connect another authenticate handler; see thread_func */
1149     if (gst_soup_loader_get_api_version () < 3) {
1150       g_signal_connect (src->session->session, "authenticate",
1151           G_CALLBACK (gst_soup_http_src_authenticate_cb_2), src);
1152     }
1153   } else {
1154     GMainContext *ctx;
1155     GSource *source;
1156
1157     GST_DEBUG_OBJECT (src, "Creating session (can share %d)", can_share);
1158
1159     src->session =
1160         GST_SOUP_SESSION (g_object_new (GST_TYPE_SOUP_SESSION, NULL));
1161
1162     GST_DEBUG_OBJECT (src, "Created session %p", src->session);
1163
1164     ctx = g_main_context_new ();
1165
1166     src->session->loop = g_main_loop_new (ctx, FALSE);
1167     /* now owned by the loop */
1168     g_main_context_unref (ctx);
1169
1170     src->session->thread = g_thread_try_new ("souphttpsrc-thread",
1171         thread_func, src, NULL);
1172
1173     if (!src->session->thread) {
1174       goto err;
1175     }
1176
1177     source = g_idle_source_new ();
1178     g_source_set_callback (source, _session_ready_cb, src, NULL);
1179     g_source_attach (source, ctx);
1180     g_source_unref (source);
1181
1182     GST_DEBUG_OBJECT (src, "Waiting for thread to start...");
1183     while (!g_main_loop_is_running (src->session->loop))
1184       g_cond_wait (&src->session_cond, &src->session_mutex);
1185     GST_DEBUG_OBJECT (src, "Soup thread started");
1186   }
1187
1188   GST_OBJECT_UNLOCK (src);
1189
1190   if (src->session_is_shared) {
1191     GstContext *context;
1192     GstMessage *message;
1193     GstStructure *s;
1194
1195     GST_DEBUG_OBJECT (src->session, "Sharing session %p", src->session);
1196
1197     context = gst_context_new (GST_SOUP_SESSION_CONTEXT, TRUE);
1198     s = gst_context_writable_structure (context);
1199     gst_structure_set (s, "session", GST_TYPE_SOUP_SESSION, src->session, NULL);
1200
1201     gst_element_set_context (GST_ELEMENT_CAST (src), context);
1202     message = gst_message_new_have_context (GST_OBJECT_CAST (src), context);
1203     gst_element_post_message (GST_ELEMENT_CAST (src), message);
1204   }
1205
1206   return TRUE;
1207
1208 err:
1209   g_clear_object (&src->session);
1210   GST_ELEMENT_ERROR (src, LIBRARY, INIT, (NULL), ("Failed to create session"));
1211   GST_OBJECT_UNLOCK (src);
1212
1213   return FALSE;
1214 }
1215
1216 static gboolean
1217 _session_close_cb (gpointer user_data)
1218 {
1219   GstSoupHTTPSrc *src = user_data;
1220
1221   if (src->msg) {
1222     gst_soup_session_cancel_message (src->session->session, src->msg,
1223         src->cancellable);
1224     g_clear_object (&src->msg);
1225   }
1226
1227   /* there may be multiple of this callback attached to the session,
1228    * each with different data pointer; disconnect the one we are closing
1229    * the session for, leave the others alone
1230    */
1231   g_signal_handlers_disconnect_by_func (src->session->session,
1232       G_CALLBACK (gst_soup_http_src_authenticate_cb_2), src);
1233
1234   g_mutex_lock (&src->session_mutex);
1235   g_clear_object (&src->session);
1236   g_cond_signal (&src->session_cond);
1237   g_mutex_unlock (&src->session_mutex);
1238
1239   return FALSE;
1240 }
1241
1242 static void
1243 gst_soup_http_src_session_close (GstSoupHTTPSrc * src)
1244 {
1245   GSource *source;
1246   GstSoupSession *sess;
1247
1248   GST_DEBUG_OBJECT (src, "Closing session");
1249
1250   if (!src->session) {
1251     return;
1252   }
1253
1254   /* ensure _session_close_cb does not deadlock us */
1255   sess = g_object_ref (src->session);
1256
1257   source = g_idle_source_new ();
1258
1259   g_mutex_lock (&src->session_mutex);
1260
1261   g_source_set_callback (source, _session_close_cb, src, NULL);
1262   g_source_attach (source, g_main_loop_get_context (src->session->loop));
1263   g_source_unref (source);
1264
1265   while (src->session)
1266     g_cond_wait (&src->session_cond, &src->session_mutex);
1267
1268   g_mutex_unlock (&src->session_mutex);
1269
1270   /* finally dispose of our reference from the gst thread */
1271   g_object_unref (sess);
1272 }
1273
1274 static void
1275 gst_soup_http_src_authenticate_cb_2 (SoupSession * session, SoupMessage * msg,
1276     SoupAuth * auth, gboolean retrying, gpointer data)
1277 {
1278   gst_soup_http_src_authenticate_cb (msg, auth, retrying, data);
1279 }
1280
1281 static gboolean
1282 gst_soup_http_src_authenticate_cb (SoupMessage * msg, SoupAuth * auth,
1283     gboolean retrying, gpointer data)
1284 {
1285   GstSoupHTTPSrc *src = GST_SOUP_HTTP_SRC (data);
1286   SoupStatus status_code;
1287
1288   /* Might be from another user of the shared session */
1289   if (!GST_IS_SOUP_HTTP_SRC (src) || msg != src->msg)
1290     return FALSE;
1291
1292   status_code = _soup_message_get_status (msg);
1293
1294   if (!retrying) {
1295     /* First time authentication only, if we fail and are called again with
1296      * retry true fall through */
1297     if (status_code == SOUP_STATUS_UNAUTHORIZED) {
1298       if (src->user_id && src->user_pw) {
1299         _soup_auth_authenticate (auth, src->user_id, src->user_pw);
1300       }
1301     } else if (status_code == SOUP_STATUS_PROXY_AUTHENTICATION_REQUIRED) {
1302       if (src->proxy_id && src->proxy_pw) {
1303         _soup_auth_authenticate (auth, src->proxy_id, src->proxy_pw);
1304       }
1305     }
1306   }
1307
1308   return FALSE;
1309 }
1310
1311 static gboolean
1312 gst_soup_http_src_accept_certificate_cb (SoupMessage * msg,
1313     GTlsCertificate * tls_certificate, GTlsCertificateFlags tls_errors,
1314     gpointer user_data)
1315 {
1316   GstSoupHTTPSrc *src = user_data;
1317
1318   /* Might be from another user of the shared session */
1319   if (!GST_IS_SOUP_HTTP_SRC (src) || msg != src->msg)
1320     return FALSE;
1321
1322   /* Accept invalid certificates */
1323   if (!src->ssl_strict)
1324     return TRUE;
1325
1326   return FALSE;
1327 }
1328
1329 static void
1330 insert_http_header (const gchar * name, const gchar * value, gpointer user_data)
1331 {
1332   GstStructure *headers = user_data;
1333   const GValue *gv;
1334
1335   if (!g_utf8_validate (name, -1, NULL) || !g_utf8_validate (value, -1, NULL))
1336     return;
1337
1338   gv = gst_structure_get_value (headers, name);
1339   if (gv && GST_VALUE_HOLDS_ARRAY (gv)) {
1340     GValue v = G_VALUE_INIT;
1341
1342     g_value_init (&v, G_TYPE_STRING);
1343     g_value_set_string (&v, value);
1344     gst_value_array_append_value ((GValue *) gv, &v);
1345     g_value_unset (&v);
1346   } else if (gv && G_VALUE_HOLDS_STRING (gv)) {
1347     GValue arr = G_VALUE_INIT;
1348     GValue v = G_VALUE_INIT;
1349     const gchar *old_value = g_value_get_string (gv);
1350
1351     g_value_init (&arr, GST_TYPE_ARRAY);
1352     g_value_init (&v, G_TYPE_STRING);
1353     g_value_set_string (&v, old_value);
1354     gst_value_array_append_value (&arr, &v);
1355     g_value_set_string (&v, value);
1356     gst_value_array_append_value (&arr, &v);
1357
1358     gst_structure_set_value (headers, name, &arr);
1359     g_value_unset (&v);
1360     g_value_unset (&arr);
1361   } else {
1362     gst_structure_set (headers, name, G_TYPE_STRING, value, NULL);
1363   }
1364 }
1365
1366 static GstFlowReturn
1367 gst_soup_http_src_got_headers (GstSoupHTTPSrc * src, SoupMessage * msg)
1368 {
1369   const char *value;
1370   GstTagList *tag_list;
1371   GstBaseSrc *basesrc;
1372   guint64 newsize;
1373   GHashTable *params = NULL;
1374   GstEvent *http_headers_event;
1375   GstStructure *http_headers, *headers;
1376   const gchar *accept_ranges;
1377   SoupMessageHeaders *request_headers = _soup_message_get_request_headers (msg);
1378   SoupMessageHeaders *response_headers =
1379       _soup_message_get_response_headers (msg);
1380   SoupStatus status_code = _soup_message_get_status (msg);
1381
1382   GST_INFO_OBJECT (src, "got headers");
1383
1384   if (status_code == SOUP_STATUS_PROXY_AUTHENTICATION_REQUIRED &&
1385       src->proxy_id && src->proxy_pw) {
1386     /* wait for authenticate callback */
1387     return GST_FLOW_OK;
1388   }
1389
1390   http_headers = gst_structure_new_empty ("http-headers");
1391   gst_structure_set (http_headers, "uri", G_TYPE_STRING, src->location,
1392       "http-status-code", G_TYPE_UINT, status_code, NULL);
1393   if (src->redirection_uri)
1394     gst_structure_set (http_headers, "redirection-uri", G_TYPE_STRING,
1395         src->redirection_uri, NULL);
1396   headers = gst_structure_new_empty ("request-headers");
1397   _soup_message_headers_foreach (request_headers, insert_http_header, headers);
1398   gst_structure_set (http_headers, "request-headers", GST_TYPE_STRUCTURE,
1399       headers, NULL);
1400   gst_structure_free (headers);
1401   headers = gst_structure_new_empty ("response-headers");
1402   _soup_message_headers_foreach (response_headers, insert_http_header, headers);
1403   gst_structure_set (http_headers, "response-headers", GST_TYPE_STRUCTURE,
1404       headers, NULL);
1405   gst_structure_free (headers);
1406
1407   gst_element_post_message (GST_ELEMENT_CAST (src),
1408       gst_message_new_element (GST_OBJECT_CAST (src),
1409           gst_structure_copy (http_headers)));
1410
1411   if (status_code == SOUP_STATUS_UNAUTHORIZED) {
1412     /* force an error */
1413     gst_structure_free (http_headers);
1414     return gst_soup_http_src_parse_status (msg, src);
1415   }
1416
1417   src->got_headers = TRUE;
1418
1419   http_headers_event =
1420       gst_event_new_custom (GST_EVENT_CUSTOM_DOWNSTREAM_STICKY, http_headers);
1421   gst_event_replace (&src->http_headers_event, http_headers_event);
1422   gst_event_unref (http_headers_event);
1423
1424   /* Parse Content-Length. */
1425   if (SOUP_STATUS_IS_SUCCESSFUL (status_code) &&
1426       (_soup_message_headers_get_encoding (response_headers) ==
1427           SOUP_ENCODING_CONTENT_LENGTH)) {
1428     newsize = src->request_position +
1429         _soup_message_headers_get_content_length (response_headers);
1430     if (!src->have_size || (src->content_size != newsize)) {
1431       src->content_size = newsize;
1432       src->have_size = TRUE;
1433       src->seekable = TRUE;
1434       GST_DEBUG_OBJECT (src, "size = %" G_GUINT64_FORMAT, src->content_size);
1435
1436       basesrc = GST_BASE_SRC_CAST (src);
1437       basesrc->segment.duration = src->content_size;
1438       gst_element_post_message (GST_ELEMENT (src),
1439           gst_message_new_duration_changed (GST_OBJECT (src)));
1440     }
1441   }
1442
1443   /* If the server reports Accept-Ranges: none we don't have to try
1444    * doing range requests at all
1445    */
1446   if ((accept_ranges =
1447           _soup_message_headers_get_one (response_headers, "Accept-Ranges"))) {
1448     if (g_ascii_strcasecmp (accept_ranges, "none") == 0)
1449       src->seekable = FALSE;
1450   }
1451
1452   /* Icecast stuff */
1453   tag_list = gst_tag_list_new_empty ();
1454
1455   if ((value =
1456           _soup_message_headers_get_one (response_headers,
1457               "icy-metaint")) != NULL) {
1458     gint icy_metaint;
1459
1460     if (g_utf8_validate (value, -1, NULL)) {
1461       icy_metaint = atoi (value);
1462
1463       GST_DEBUG_OBJECT (src, "icy-metaint: %s (parsed: %d)", value,
1464           icy_metaint);
1465       if (icy_metaint > 0) {
1466         if (src->src_caps)
1467           gst_caps_unref (src->src_caps);
1468
1469         src->src_caps = gst_caps_new_simple ("application/x-icy",
1470             "metadata-interval", G_TYPE_INT, icy_metaint, NULL);
1471
1472         gst_base_src_set_caps (GST_BASE_SRC (src), src->src_caps);
1473       }
1474     }
1475   }
1476   if ((value =
1477           _soup_message_headers_get_content_type (response_headers,
1478               &params)) != NULL) {
1479     if (!g_utf8_validate (value, -1, NULL)) {
1480       GST_WARNING_OBJECT (src, "Content-Type is invalid UTF-8");
1481     } else if (g_ascii_strcasecmp (value, "audio/L16") == 0) {
1482       gint channels = 2;
1483       gint rate = 44100;
1484       char *param;
1485
1486       GST_DEBUG_OBJECT (src, "Content-Type: %s", value);
1487
1488       if (src->src_caps) {
1489         gst_caps_unref (src->src_caps);
1490         src->src_caps = NULL;
1491       }
1492
1493       param = g_hash_table_lookup (params, "channels");
1494       if (param != NULL) {
1495         guint64 val = g_ascii_strtoull (param, NULL, 10);
1496         if (val < 64)
1497           channels = val;
1498         else
1499           channels = 0;
1500       }
1501
1502       param = g_hash_table_lookup (params, "rate");
1503       if (param != NULL) {
1504         guint64 val = g_ascii_strtoull (param, NULL, 10);
1505         if (val < G_MAXINT)
1506           rate = val;
1507         else
1508           rate = 0;
1509       }
1510
1511       if (rate > 0 && channels > 0) {
1512         src->src_caps = gst_caps_new_simple ("audio/x-unaligned-raw",
1513             "format", G_TYPE_STRING, "S16BE",
1514             "layout", G_TYPE_STRING, "interleaved",
1515             "channels", G_TYPE_INT, channels, "rate", G_TYPE_INT, rate, NULL);
1516
1517         gst_base_src_set_caps (GST_BASE_SRC (src), src->src_caps);
1518       }
1519     } else {
1520       GST_DEBUG_OBJECT (src, "Content-Type: %s", value);
1521
1522       /* Set the Content-Type field on the caps */
1523       if (src->src_caps) {
1524         src->src_caps = gst_caps_make_writable (src->src_caps);
1525         gst_caps_set_simple (src->src_caps, "content-type", G_TYPE_STRING,
1526             value, NULL);
1527         gst_base_src_set_caps (GST_BASE_SRC (src), src->src_caps);
1528       }
1529     }
1530   }
1531
1532   if (params != NULL)
1533     g_hash_table_destroy (params);
1534
1535   if ((value =
1536           _soup_message_headers_get_one (response_headers,
1537               "icy-name")) != NULL) {
1538     if (g_utf8_validate (value, -1, NULL)) {
1539       g_free (src->iradio_name);
1540       src->iradio_name = gst_soup_http_src_unicodify (value);
1541       if (src->iradio_name) {
1542         gst_tag_list_add (tag_list, GST_TAG_MERGE_REPLACE, GST_TAG_ORGANIZATION,
1543             src->iradio_name, NULL);
1544       }
1545     }
1546   }
1547   if ((value =
1548           _soup_message_headers_get_one (response_headers,
1549               "icy-genre")) != NULL) {
1550     if (g_utf8_validate (value, -1, NULL)) {
1551       g_free (src->iradio_genre);
1552       src->iradio_genre = gst_soup_http_src_unicodify (value);
1553       if (src->iradio_genre) {
1554         gst_tag_list_add (tag_list, GST_TAG_MERGE_REPLACE, GST_TAG_GENRE,
1555             src->iradio_genre, NULL);
1556       }
1557     }
1558   }
1559   if ((value = _soup_message_headers_get_one (response_headers, "icy-url"))
1560       != NULL) {
1561     if (g_utf8_validate (value, -1, NULL)) {
1562       g_free (src->iradio_url);
1563       src->iradio_url = gst_soup_http_src_unicodify (value);
1564       if (src->iradio_url) {
1565         gst_tag_list_add (tag_list, GST_TAG_MERGE_REPLACE, GST_TAG_LOCATION,
1566             src->iradio_url, NULL);
1567       }
1568     }
1569   }
1570   if (!gst_tag_list_is_empty (tag_list)) {
1571     GST_DEBUG_OBJECT (src,
1572         "calling gst_element_found_tags with %" GST_PTR_FORMAT, tag_list);
1573     gst_pad_push_event (GST_BASE_SRC_PAD (src), gst_event_new_tag (tag_list));
1574   } else {
1575     gst_tag_list_unref (tag_list);
1576   }
1577
1578   /* Handle HTTP errors. */
1579   return gst_soup_http_src_parse_status (msg, src);
1580 }
1581
1582 static GstBuffer *
1583 gst_soup_http_src_alloc_buffer (GstSoupHTTPSrc * src)
1584 {
1585   GstBaseSrc *basesrc = GST_BASE_SRC_CAST (src);
1586   GstFlowReturn rc;
1587   GstBuffer *gstbuf;
1588
1589   rc = GST_BASE_SRC_CLASS (parent_class)->alloc (basesrc, -1,
1590       basesrc->blocksize, &gstbuf);
1591   if (G_UNLIKELY (rc != GST_FLOW_OK)) {
1592     return NULL;
1593   }
1594
1595   return gstbuf;
1596 }
1597
1598 #define SOUP_HTTP_SRC_ERROR(src,soup_msg,cat,code,error_message)     \
1599   do { \
1600     GST_ELEMENT_ERROR_WITH_DETAILS ((src), cat, code, ("%s", error_message), \
1601         ("%s (%d), URL: %s, Redirect to: %s", _soup_message_get_reason_phrase (soup_msg), \
1602             _soup_message_get_status (soup_msg), (src)->location, GST_STR_NULL ((src)->redirection_uri)), \
1603             ("http-status-code", G_TYPE_UINT, _soup_message_get_status (soup_msg), \
1604              "http-redirect-uri", G_TYPE_STRING, GST_STR_NULL ((src)->redirection_uri), NULL)); \
1605   } while(0)
1606
1607 static GstFlowReturn
1608 gst_soup_http_src_parse_status (SoupMessage * msg, GstSoupHTTPSrc * src)
1609 {
1610   SoupStatus status_code = _soup_message_get_status (msg);
1611   if (_soup_message_get_method (msg) == SOUP_METHOD_HEAD) {
1612     if (!SOUP_STATUS_IS_SUCCESSFUL (status_code))
1613       GST_DEBUG_OBJECT (src, "Ignoring error %d during HEAD request",
1614           status_code);
1615     return GST_FLOW_OK;
1616   }
1617
1618   /* SOUP_STATUS_IS_TRANSPORT_ERROR was replaced with GError in libsoup-3.0 */
1619 #if !defined(STATIC_SOUP) || STATIC_SOUP == 2
1620   if (SOUP_STATUS_IS_TRANSPORT_ERROR (status_code)) {
1621     switch (status_code) {
1622       case SOUP_STATUS_CANT_RESOLVE:
1623       case SOUP_STATUS_CANT_RESOLVE_PROXY:
1624         SOUP_HTTP_SRC_ERROR (src, msg, RESOURCE, NOT_FOUND,
1625             _("Could not resolve server name."));
1626         return GST_FLOW_ERROR;
1627       case SOUP_STATUS_CANT_CONNECT:
1628       case SOUP_STATUS_CANT_CONNECT_PROXY:
1629         SOUP_HTTP_SRC_ERROR (src, msg, RESOURCE, OPEN_READ,
1630             _("Could not establish connection to server."));
1631         return GST_FLOW_ERROR;
1632       case SOUP_STATUS_SSL_FAILED:
1633         SOUP_HTTP_SRC_ERROR (src, msg, RESOURCE, OPEN_READ,
1634             _("Secure connection setup failed."));
1635         return GST_FLOW_ERROR;
1636       case SOUP_STATUS_IO_ERROR:
1637         if (src->max_retries == -1 || src->retry_count < src->max_retries)
1638           return GST_FLOW_CUSTOM_ERROR;
1639         SOUP_HTTP_SRC_ERROR (src, msg, RESOURCE, READ,
1640             _("A network error occurred, or the server closed the connection "
1641                 "unexpectedly."));
1642         return GST_FLOW_ERROR;
1643       case SOUP_STATUS_MALFORMED:
1644         SOUP_HTTP_SRC_ERROR (src, msg, RESOURCE, READ,
1645             _("Server sent bad data."));
1646         return GST_FLOW_ERROR;
1647       case SOUP_STATUS_CANCELLED:
1648         /* No error message when interrupted by program. */
1649         break;
1650       default:
1651         break;
1652     }
1653     return GST_FLOW_OK;
1654   }
1655 #endif
1656
1657   if (SOUP_STATUS_IS_CLIENT_ERROR (status_code) ||
1658       SOUP_STATUS_IS_REDIRECTION (status_code) ||
1659       SOUP_STATUS_IS_SERVER_ERROR (status_code)) {
1660     const gchar *reason_phrase;
1661
1662     reason_phrase = _soup_message_get_reason_phrase (msg);
1663     if (reason_phrase && !g_utf8_validate (reason_phrase, -1, NULL)) {
1664       GST_ERROR_OBJECT (src, "Invalid UTF-8 in reason");
1665       reason_phrase = "(invalid)";
1666     }
1667
1668     /* Report HTTP error. */
1669
1670     /* when content_size is unknown and we have just finished receiving
1671      * a body message, requests that go beyond the content limits will result
1672      * in an error. Here we convert those to EOS */
1673     if (status_code == SOUP_STATUS_REQUESTED_RANGE_NOT_SATISFIABLE &&
1674         src->have_body && (!src->have_size ||
1675             (src->request_position >= src->content_size))) {
1676       GST_DEBUG_OBJECT (src, "Requested range out of limits and received full "
1677           "body, returning EOS");
1678       return GST_FLOW_EOS;
1679     }
1680
1681     /* FIXME: reason_phrase is not translated and not suitable for user
1682      * error dialog according to libsoup documentation.
1683      */
1684     if (status_code == SOUP_STATUS_NOT_FOUND) {
1685       SOUP_HTTP_SRC_ERROR (src, msg, RESOURCE, NOT_FOUND, (reason_phrase));
1686     } else if (status_code == SOUP_STATUS_UNAUTHORIZED
1687         || status_code == SOUP_STATUS_PAYMENT_REQUIRED
1688         || status_code == SOUP_STATUS_FORBIDDEN
1689         || status_code == SOUP_STATUS_PROXY_AUTHENTICATION_REQUIRED) {
1690       SOUP_HTTP_SRC_ERROR (src, msg, RESOURCE, NOT_AUTHORIZED, (reason_phrase));
1691     } else {
1692       SOUP_HTTP_SRC_ERROR (src, msg, RESOURCE, OPEN_READ, (reason_phrase));
1693     }
1694     return GST_FLOW_ERROR;
1695   }
1696
1697   return GST_FLOW_OK;
1698 }
1699
1700 static void
1701 gst_soup_http_src_restarted_cb (SoupMessage * msg, GstSoupHTTPSrc * src)
1702 {
1703   SoupStatus status = _soup_message_get_status (msg);
1704
1705   if (!SOUP_STATUS_IS_REDIRECTION (status))
1706     return;
1707
1708   src->redirection_uri = gst_soup_message_uri_to_string (msg);
1709   src->redirection_permanent = (status == SOUP_STATUS_MOVED_PERMANENTLY);
1710
1711   GST_DEBUG_OBJECT (src, "%u redirect to \"%s\" (permanent %d)",
1712       status, src->redirection_uri, src->redirection_permanent);
1713 }
1714
1715 static gboolean
1716 gst_soup_http_src_build_message (GstSoupHTTPSrc * src, const gchar * method)
1717 {
1718   SoupMessageHeaders *request_headers;
1719
1720   g_return_val_if_fail (src->msg == NULL, FALSE);
1721
1722   src->msg = _soup_message_new (method, src->location);
1723   if (!src->msg) {
1724     GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ,
1725         ("Error parsing URL."), ("URL: %s", src->location));
1726     return FALSE;
1727   }
1728
1729   request_headers = _soup_message_get_request_headers (src->msg);
1730
1731   /* Duplicating the defaults of libsoup here. We don't want to set a
1732    * User-Agent in the session as each source might have its own User-Agent
1733    * set */
1734   if (!src->user_agent || !*src->user_agent) {
1735     gchar *user_agent =
1736         g_strdup_printf ("libsoup/%u.%u.%u", _soup_get_major_version (),
1737         _soup_get_minor_version (), _soup_get_micro_version ());
1738     _soup_message_headers_append (request_headers, "User-Agent", user_agent);
1739     g_free (user_agent);
1740   } else if (g_str_has_suffix (src->user_agent, " ")) {
1741     gchar *user_agent = g_strdup_printf ("%slibsoup/%u.%u.%u", src->user_agent,
1742         _soup_get_major_version (),
1743         _soup_get_minor_version (), _soup_get_micro_version ());
1744     _soup_message_headers_append (request_headers, "User-Agent", user_agent);
1745     g_free (user_agent);
1746   } else {
1747     _soup_message_headers_append (request_headers, "User-Agent",
1748         src->user_agent);
1749   }
1750
1751   if (!src->keep_alive) {
1752     _soup_message_headers_append (request_headers, "Connection", "close");
1753   }
1754   if (src->iradio_mode) {
1755     _soup_message_headers_append (request_headers, "icy-metadata", "1");
1756   }
1757   if (src->cookies) {
1758     gchar **cookie;
1759
1760     for (cookie = src->cookies; *cookie != NULL; cookie++) {
1761       _soup_message_headers_append (request_headers, "Cookie", *cookie);
1762     }
1763
1764     _soup_message_disable_feature (src->msg, _soup_cookie_jar_get_type ());
1765   }
1766
1767   if (!src->compress) {
1768     _soup_message_headers_append (_soup_message_get_request_headers (src->msg),
1769         "Accept-Encoding", "identity");
1770   }
1771
1772   if (gst_soup_loader_get_api_version () == 3) {
1773     g_signal_connect (src->msg, "accept-certificate",
1774         G_CALLBACK (gst_soup_http_src_accept_certificate_cb), src);
1775     g_signal_connect (src->msg, "authenticate",
1776         G_CALLBACK (gst_soup_http_src_authenticate_cb), src);
1777   }
1778
1779   {
1780     SoupMessageFlags flags =
1781         src->automatic_redirect ? 0 : SOUP_MESSAGE_NO_REDIRECT;
1782
1783     /* SOUP_MESSAGE_OVERWRITE_CHUNKS is gone in libsoup-3.0, and
1784      * soup_message_body_set_accumulate() requires SoupMessageBody, which
1785      * can only be fetched from SoupServerMessage, not SoupMessage */
1786 #if !defined(STATIC_SOUP) || STATIC_SOUP == 2
1787     if (gst_soup_loader_get_api_version () == 2)
1788       flags |= SOUP_MESSAGE_OVERWRITE_CHUNKS;
1789 #endif
1790
1791     _soup_message_set_flags (src->msg, flags);
1792   }
1793
1794   if (src->automatic_redirect) {
1795     g_signal_connect (src->msg, "restarted",
1796         G_CALLBACK (gst_soup_http_src_restarted_cb), src);
1797   }
1798
1799   gst_soup_http_src_add_range_header (src, src->request_position,
1800       src->stop_position);
1801
1802   gst_soup_http_src_add_extra_headers (src);
1803
1804   return TRUE;
1805 }
1806
1807 struct GstSoupSendSrc
1808 {
1809   GstSoupHTTPSrc *src;
1810   GError *error;
1811 };
1812
1813 static void
1814 _session_send_cb (GObject * source, GAsyncResult * res, gpointer user_data)
1815 {
1816   struct GstSoupSendSrc *msrc = user_data;
1817   GstSoupHTTPSrc *src = msrc->src;
1818   GError *error = NULL;
1819
1820   g_mutex_lock (&src->session_mutex);
1821
1822   src->input_stream = _soup_session_send_finish (src->session->session,
1823       res, &error);
1824
1825   if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
1826     src->headers_ret = GST_FLOW_FLUSHING;
1827   } else {
1828     src->headers_ret = gst_soup_http_src_got_headers (src, src->msg);
1829   }
1830
1831   if (!src->input_stream) {
1832     GST_DEBUG_OBJECT (src, "Sending message failed: %s", error->message);
1833     msrc->error = error;
1834   }
1835
1836   g_cond_broadcast (&src->session_cond);
1837   g_mutex_unlock (&src->session_mutex);
1838 }
1839
1840 static gboolean
1841 _session_send_idle_cb (gpointer user_data)
1842 {
1843   struct GstSoupSendSrc *msrc = user_data;
1844   GstSoupHTTPSrc *src = msrc->src;
1845
1846   _soup_session_send_async (src->session->session, src->msg, src->cancellable,
1847       _session_send_cb, msrc);
1848
1849   return FALSE;
1850 }
1851
1852 /* called with session lock taken */
1853 static GstFlowReturn
1854 gst_soup_http_src_send_message (GstSoupHTTPSrc * src)
1855 {
1856   GstFlowReturn ret;
1857   GSource *source;
1858   struct GstSoupSendSrc msrc;
1859
1860   g_return_val_if_fail (src->msg != NULL, GST_FLOW_ERROR);
1861   g_assert (src->input_stream == NULL);
1862
1863   msrc.src = src;
1864   msrc.error = NULL;
1865
1866   source = g_idle_source_new ();
1867
1868   src->headers_ret = GST_FLOW_OK;
1869
1870   g_source_set_callback (source, _session_send_idle_cb, &msrc, NULL);
1871   g_source_attach (source, g_main_loop_get_context (src->session->loop));
1872   g_source_unref (source);
1873
1874   while (!src->input_stream && !msrc.error)
1875     g_cond_wait (&src->session_cond, &src->session_mutex);
1876
1877   ret = src->headers_ret;
1878
1879   if (ret != GST_FLOW_OK) {
1880     goto done;
1881   }
1882
1883   if (!src->input_stream) {
1884     GST_DEBUG_OBJECT (src, "Didn't get an input stream: %s",
1885         msrc.error->message);
1886     ret = GST_FLOW_ERROR;
1887     goto done;
1888   }
1889
1890   /* if an input stream exists, it was always successful */
1891   GST_DEBUG_OBJECT (src, "Successfully got a reply");
1892
1893 done:
1894   g_clear_error (&msrc.error);
1895   return ret;
1896 }
1897
1898 /* called with session lock taken */
1899 static GstFlowReturn
1900 gst_soup_http_src_do_request (GstSoupHTTPSrc * src, const gchar * method)
1901 {
1902   GstFlowReturn ret;
1903   SoupMessageHeaders *request_headers;
1904
1905   if (src->max_retries != -1 && src->retry_count > src->max_retries) {
1906     GST_DEBUG_OBJECT (src, "Max retries reached");
1907     return GST_FLOW_ERROR;
1908   }
1909
1910   src->retry_count++;
1911   /* EOS immediately if we have an empty segment */
1912   if (src->request_position == src->stop_position)
1913     return GST_FLOW_EOS;
1914
1915   GST_LOG_OBJECT (src, "Running request for method: %s", method);
1916
1917   if (src->msg)
1918     request_headers = _soup_message_get_request_headers (src->msg);
1919
1920   /* Update the position if we are retrying */
1921   if (src->msg && src->request_position > 0) {
1922     gst_soup_http_src_add_range_header (src, src->request_position,
1923         src->stop_position);
1924   } else if (src->msg && src->request_position == 0)
1925     _soup_message_headers_remove (request_headers, "Range");
1926
1927   /* add_range_header() has the side effect of setting read_position to
1928    * the requested position. This *needs* to be set regardless of having
1929    * a message or not. Failure to do so would result in calculation being
1930    * done with stale/wrong read position */
1931   src->read_position = src->request_position;
1932
1933   if (!src->msg) {
1934     if (!gst_soup_http_src_build_message (src, method)) {
1935       return GST_FLOW_ERROR;
1936     }
1937   }
1938
1939   if (g_cancellable_is_cancelled (src->cancellable)) {
1940     GST_INFO_OBJECT (src, "interrupted");
1941     return GST_FLOW_FLUSHING;
1942   }
1943
1944   ret = gst_soup_http_src_send_message (src);
1945
1946   /* Check if Range header was respected. */
1947   if (ret == GST_FLOW_OK && src->request_position > 0 &&
1948       _soup_message_get_status (src->msg) != SOUP_STATUS_PARTIAL_CONTENT) {
1949     src->seekable = FALSE;
1950     GST_ELEMENT_ERROR_WITH_DETAILS (src, RESOURCE, SEEK,
1951         (_("Server does not support seeking.")),
1952         ("Server does not accept Range HTTP header, URL: %s, Redirect to: %s",
1953             src->location, GST_STR_NULL (src->redirection_uri)),
1954         ("http-status-code", G_TYPE_UINT, _soup_message_get_status (src->msg),
1955             "http-redirection-uri", G_TYPE_STRING,
1956             GST_STR_NULL (src->redirection_uri), NULL));
1957     ret = GST_FLOW_ERROR;
1958   }
1959
1960   return ret;
1961 }
1962
1963 /*
1964  * Check if the bytes_read is above a certain threshold of the blocksize, if
1965  * that happens a few times in a row, increase the blocksize; Do the same in
1966  * the opposite direction to reduce the blocksize.
1967  */
1968 static void
1969 gst_soup_http_src_check_update_blocksize (GstSoupHTTPSrc * src,
1970     gint64 bytes_read)
1971 {
1972   guint blocksize = gst_base_src_get_blocksize (GST_BASE_SRC_CAST (src));
1973
1974   gint64 time_since_last_read =
1975       g_get_monotonic_time () * GST_USECOND - src->last_socket_read_time;
1976
1977   GST_LOG_OBJECT (src, "Checking to update blocksize. Read: %" G_GINT64_FORMAT
1978       " bytes, blocksize: %u bytes, time since last read: %" GST_TIME_FORMAT,
1979       bytes_read, blocksize, GST_TIME_ARGS (time_since_last_read));
1980
1981   if (bytes_read >= blocksize * GROW_BLOCKSIZE_LIMIT
1982       && time_since_last_read <= GROW_TIME_LIMIT) {
1983     src->reduce_blocksize_count = 0;
1984     src->increase_blocksize_count++;
1985
1986     if (src->increase_blocksize_count >= GROW_BLOCKSIZE_COUNT) {
1987       blocksize *= GROW_BLOCKSIZE_FACTOR;
1988       GST_DEBUG_OBJECT (src, "Increased blocksize to %u", blocksize);
1989       gst_base_src_set_blocksize (GST_BASE_SRC_CAST (src), blocksize);
1990       src->increase_blocksize_count = 0;
1991     }
1992   } else if (bytes_read < blocksize * REDUCE_BLOCKSIZE_LIMIT
1993       || time_since_last_read > GROW_TIME_LIMIT) {
1994     src->reduce_blocksize_count++;
1995     src->increase_blocksize_count = 0;
1996
1997     if (src->reduce_blocksize_count >= REDUCE_BLOCKSIZE_COUNT) {
1998       blocksize *= REDUCE_BLOCKSIZE_FACTOR;
1999       blocksize = MAX (blocksize, src->minimum_blocksize);
2000       GST_DEBUG_OBJECT (src, "Decreased blocksize to %u", blocksize);
2001       gst_base_src_set_blocksize (GST_BASE_SRC_CAST (src), blocksize);
2002       src->reduce_blocksize_count = 0;
2003     }
2004   } else {
2005     src->reduce_blocksize_count = src->increase_blocksize_count = 0;
2006   }
2007 }
2008
2009 static void
2010 gst_soup_http_src_update_position (GstSoupHTTPSrc * src, gint64 bytes_read)
2011 {
2012   GstBaseSrc *basesrc = GST_BASE_SRC_CAST (src);
2013   guint64 new_position;
2014
2015   new_position = src->read_position + bytes_read;
2016   if (G_LIKELY (src->request_position == src->read_position))
2017     src->request_position = new_position;
2018   src->read_position = new_position;
2019
2020   if (src->have_size) {
2021     if (new_position > src->content_size) {
2022       GST_DEBUG_OBJECT (src, "Got position previous estimated content size "
2023           "(%" G_GINT64_FORMAT " > %" G_GINT64_FORMAT ")", new_position,
2024           src->content_size);
2025       src->content_size = new_position;
2026       basesrc->segment.duration = src->content_size;
2027       gst_element_post_message (GST_ELEMENT (src),
2028           gst_message_new_duration_changed (GST_OBJECT (src)));
2029     } else if (new_position == src->content_size) {
2030       GST_DEBUG_OBJECT (src, "We're EOS now");
2031     }
2032   }
2033 }
2034
2035 struct GstSoupReadResult
2036 {
2037   GstSoupHTTPSrc *src;
2038   GError *error;
2039   void *buffer;
2040   gsize bufsize;
2041   gssize nbytes;
2042 };
2043
2044 static void
2045 _session_read_cb (GObject * source, GAsyncResult * ret, gpointer user_data)
2046 {
2047   struct GstSoupReadResult *res = user_data;
2048
2049   g_mutex_lock (&res->src->session_mutex);
2050
2051   res->nbytes = g_input_stream_read_finish (G_INPUT_STREAM (source),
2052       ret, &res->error);
2053
2054   g_cond_signal (&res->src->session_cond);
2055   g_mutex_unlock (&res->src->session_mutex);
2056 }
2057
2058 static gboolean
2059 _session_read_idle_cb (gpointer user_data)
2060 {
2061   struct GstSoupReadResult *res = user_data;
2062
2063   g_input_stream_read_async (res->src->input_stream, res->buffer,
2064       res->bufsize, G_PRIORITY_DEFAULT, res->src->cancellable,
2065       _session_read_cb, res);
2066
2067   return FALSE;
2068 }
2069
2070 static GstFlowReturn
2071 gst_soup_http_src_read_buffer (GstSoupHTTPSrc * src, GstBuffer ** outbuf)
2072 {
2073   struct GstSoupReadResult res;
2074   GstMapInfo mapinfo;
2075   GstBaseSrc *bsrc;
2076   GstFlowReturn ret;
2077   GSource *source;
2078
2079   bsrc = GST_BASE_SRC_CAST (src);
2080
2081   *outbuf = gst_soup_http_src_alloc_buffer (src);
2082   if (!*outbuf) {
2083     GST_WARNING_OBJECT (src, "Failed to allocate buffer");
2084     return GST_FLOW_ERROR;
2085   }
2086
2087   if (!gst_buffer_map (*outbuf, &mapinfo, GST_MAP_WRITE)) {
2088     GST_WARNING_OBJECT (src, "Failed to map buffer");
2089     return GST_FLOW_ERROR;
2090   }
2091
2092   res.src = src;
2093   res.buffer = mapinfo.data;
2094   res.bufsize = mapinfo.size;
2095   res.error = NULL;
2096   res.nbytes = -1;
2097
2098   source = g_idle_source_new ();
2099
2100   g_mutex_lock (&src->session_mutex);
2101
2102   g_source_set_callback (source, _session_read_idle_cb, &res, NULL);
2103   /* invoke on libsoup thread */
2104   g_source_attach (source, g_main_loop_get_context (src->session->loop));
2105   g_source_unref (source);
2106
2107   /* wait for it */
2108   while (!res.error && res.nbytes < 0)
2109     g_cond_wait (&src->session_cond, &src->session_mutex);
2110   g_mutex_unlock (&src->session_mutex);
2111
2112   GST_DEBUG_OBJECT (src, "Read %" G_GSSIZE_FORMAT " bytes from http input",
2113       res.nbytes);
2114
2115   if (res.error) {
2116     /* retry by default */
2117     GstFlowReturn ret = GST_FLOW_CUSTOM_ERROR;
2118     if (g_error_matches (res.error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
2119       ret = GST_FLOW_FLUSHING;
2120     } else {
2121       GST_ERROR_OBJECT (src, "Got error from libsoup: %s", res.error->message);
2122     }
2123     g_error_free (res.error);
2124     gst_buffer_unmap (*outbuf, &mapinfo);
2125     gst_buffer_unref (*outbuf);
2126     return ret;
2127   }
2128
2129   gst_buffer_unmap (*outbuf, &mapinfo);
2130   if (res.nbytes > 0) {
2131     gst_buffer_set_size (*outbuf, res.nbytes);
2132     GST_BUFFER_OFFSET (*outbuf) = bsrc->segment.position;
2133     ret = GST_FLOW_OK;
2134     gst_soup_http_src_update_position (src, res.nbytes);
2135
2136     /* Got some data, reset retry counter */
2137     src->retry_count = 0;
2138
2139     gst_soup_http_src_check_update_blocksize (src, res.nbytes);
2140
2141     src->last_socket_read_time = g_get_monotonic_time () * GST_USECOND;
2142
2143     /* If we're at the end of a range request, read again to let libsoup
2144      * finalize the request. This allows to reuse the connection again later,
2145      * otherwise we would have to cancel the message and close the connection
2146      */
2147     if (bsrc->segment.stop != -1
2148         && bsrc->segment.position + res.nbytes >= bsrc->segment.stop) {
2149       SoupMessage *msg = src->msg;
2150       guint8 tmp[128];
2151
2152       res.buffer = tmp;
2153       res.bufsize = sizeof (tmp);
2154       res.nbytes = -1;
2155
2156       src->msg = NULL;
2157       src->have_body = TRUE;
2158
2159       g_mutex_lock (&src->session_mutex);
2160
2161       source = g_idle_source_new ();
2162
2163       g_source_set_callback (source, _session_read_idle_cb, &res, NULL);
2164       /* This should return immediately as we're at the end of the range */
2165       g_source_attach (source, g_main_loop_get_context (src->session->loop));
2166       g_source_unref (source);
2167
2168       while (!res.error && res.nbytes < 0)
2169         g_cond_wait (&src->session_cond, &src->session_mutex);
2170       g_mutex_unlock (&src->session_mutex);
2171
2172       g_clear_error (&res.error);
2173       g_object_unref (msg);
2174
2175       if (res.nbytes > 0)
2176         GST_ERROR_OBJECT (src,
2177             "Read %" G_GSIZE_FORMAT " bytes after end of range", res.nbytes);
2178     }
2179   } else {
2180     gst_buffer_unref (*outbuf);
2181     if (src->have_size && src->read_position < src->content_size) {
2182       /* Maybe the server disconnected, retry */
2183       ret = GST_FLOW_CUSTOM_ERROR;
2184     } else {
2185       g_clear_object (&src->msg);
2186       src->msg = NULL;
2187       ret = GST_FLOW_EOS;
2188       src->have_body = TRUE;
2189     }
2190   }
2191
2192   g_clear_error (&res.error);
2193
2194   return ret;
2195 }
2196
2197 static gboolean
2198 _session_stream_clear_cb (gpointer user_data)
2199 {
2200   GstSoupHTTPSrc *src = user_data;
2201
2202   g_mutex_lock (&src->session_mutex);
2203
2204   g_clear_object (&src->input_stream);
2205
2206   g_cond_signal (&src->session_cond);
2207   g_mutex_unlock (&src->session_mutex);
2208
2209   return FALSE;
2210 }
2211
2212 static void
2213 gst_soup_http_src_stream_clear (GstSoupHTTPSrc * src)
2214 {
2215   GSource *source;
2216
2217   if (!src->input_stream)
2218     return;
2219
2220   g_mutex_lock (&src->session_mutex);
2221
2222   source = g_idle_source_new ();
2223
2224   g_source_set_callback (source, _session_stream_clear_cb, src, NULL);
2225   g_source_attach (source, g_main_loop_get_context (src->session->loop));
2226   g_source_unref (source);
2227
2228   while (src->input_stream)
2229     g_cond_wait (&src->session_cond, &src->session_mutex);
2230
2231   g_mutex_unlock (&src->session_mutex);
2232 }
2233
2234 static GstFlowReturn
2235 gst_soup_http_src_create (GstPushSrc * psrc, GstBuffer ** outbuf)
2236 {
2237   GstSoupHTTPSrc *src;
2238   GstFlowReturn ret = GST_FLOW_OK;
2239   GstEvent *http_headers_event = NULL;
2240
2241   src = GST_SOUP_HTTP_SRC (psrc);
2242
2243 retry:
2244
2245   /* Check for pending position change */
2246   if (src->request_position != src->read_position && src->input_stream) {
2247     gst_soup_http_src_stream_clear (src);
2248   }
2249
2250   if (g_cancellable_is_cancelled (src->cancellable)) {
2251     ret = GST_FLOW_FLUSHING;
2252     goto done;
2253   }
2254
2255   /* If we have no open connection to the server, start one */
2256   if (!src->input_stream) {
2257     *outbuf = NULL;
2258     g_mutex_lock (&src->session_mutex);
2259     ret =
2260         gst_soup_http_src_do_request (src,
2261         src->method ? src->method : SOUP_METHOD_GET);
2262     http_headers_event = src->http_headers_event;
2263     src->http_headers_event = NULL;
2264     g_mutex_unlock (&src->session_mutex);
2265   }
2266
2267   if (ret == GST_FLOW_OK || ret == GST_FLOW_CUSTOM_ERROR) {
2268     if (http_headers_event) {
2269       gst_pad_push_event (GST_BASE_SRC_PAD (src), http_headers_event);
2270       http_headers_event = NULL;
2271     }
2272   }
2273
2274   if (ret == GST_FLOW_OK)
2275     ret = gst_soup_http_src_read_buffer (src, outbuf);
2276
2277 done:
2278   GST_DEBUG_OBJECT (src, "Returning %d %s", ret, gst_flow_get_name (ret));
2279   if (ret != GST_FLOW_OK) {
2280     if (http_headers_event)
2281       gst_event_unref (http_headers_event);
2282
2283     if (src->input_stream) {
2284       gst_soup_http_src_stream_clear (src);
2285     }
2286     if (ret == GST_FLOW_CUSTOM_ERROR) {
2287       ret = GST_FLOW_OK;
2288       goto retry;
2289     }
2290   }
2291
2292   if (ret == GST_FLOW_FLUSHING) {
2293     src->retry_count = 0;
2294   }
2295
2296   return ret;
2297 }
2298
2299 static gboolean
2300 gst_soup_http_src_start (GstBaseSrc * bsrc)
2301 {
2302   GstSoupHTTPSrc *src = GST_SOUP_HTTP_SRC (bsrc);
2303   gboolean ret;
2304
2305   GST_DEBUG_OBJECT (src, "start(\"%s\")", src->location);
2306
2307   g_mutex_lock (&src->session_mutex);
2308   ret = gst_soup_http_src_session_open (src);
2309   g_mutex_unlock (&src->session_mutex);
2310   return ret;
2311 }
2312
2313 static gboolean
2314 gst_soup_http_src_stop (GstBaseSrc * bsrc)
2315 {
2316   GstSoupHTTPSrc *src;
2317
2318   src = GST_SOUP_HTTP_SRC (bsrc);
2319   GST_DEBUG_OBJECT (src, "stop()");
2320
2321   gst_soup_http_src_stream_clear (src);
2322
2323   if (src->keep_alive && !src->msg && !src->session_is_shared)
2324     g_cancellable_cancel (src->cancellable);
2325   else
2326     gst_soup_http_src_session_close (src);
2327
2328   gst_soup_http_src_reset (src);
2329   return TRUE;
2330 }
2331
2332 static GstStateChangeReturn
2333 gst_soup_http_src_change_state (GstElement * element, GstStateChange transition)
2334 {
2335   GstStateChangeReturn ret;
2336   GstSoupHTTPSrc *src;
2337
2338   src = GST_SOUP_HTTP_SRC (element);
2339
2340   switch (transition) {
2341     case GST_STATE_CHANGE_READY_TO_NULL:
2342       gst_soup_http_src_session_close (src);
2343       break;
2344     default:
2345       break;
2346   }
2347
2348   ret = GST_ELEMENT_CLASS (parent_class)->change_state (element, transition);
2349
2350   return ret;
2351 }
2352
2353 static void
2354 gst_soup_http_src_set_context (GstElement * element, GstContext * context)
2355 {
2356   GstSoupHTTPSrc *src = GST_SOUP_HTTP_SRC (element);
2357
2358   if (g_strcmp0 (gst_context_get_context_type (context),
2359           GST_SOUP_SESSION_CONTEXT) == 0) {
2360     const GstStructure *s = gst_context_get_structure (context);
2361
2362     GST_OBJECT_LOCK (src);
2363
2364     g_clear_object (&src->external_session);
2365     gst_structure_get (s, "session", GST_TYPE_SOUP_SESSION,
2366         &src->external_session, NULL);
2367
2368     GST_DEBUG_OBJECT (src, "Setting external session %p",
2369         src->external_session);
2370     GST_OBJECT_UNLOCK (src);
2371   }
2372
2373   GST_ELEMENT_CLASS (parent_class)->set_context (element, context);
2374 }
2375
2376 /* Interrupt a blocking request. */
2377 static gboolean
2378 gst_soup_http_src_unlock (GstBaseSrc * bsrc)
2379 {
2380   GstSoupHTTPSrc *src;
2381
2382   src = GST_SOUP_HTTP_SRC (bsrc);
2383   GST_DEBUG_OBJECT (src, "unlock()");
2384
2385   g_cancellable_cancel (src->cancellable);
2386   return TRUE;
2387 }
2388
2389 /* Interrupt interrupt. */
2390 static gboolean
2391 gst_soup_http_src_unlock_stop (GstBaseSrc * bsrc)
2392 {
2393   GstSoupHTTPSrc *src;
2394
2395   src = GST_SOUP_HTTP_SRC (bsrc);
2396   GST_DEBUG_OBJECT (src, "unlock_stop()");
2397
2398   g_cancellable_reset (src->cancellable);
2399   return TRUE;
2400 }
2401
2402 static gboolean
2403 gst_soup_http_src_get_size (GstBaseSrc * bsrc, guint64 * size)
2404 {
2405   GstSoupHTTPSrc *src;
2406
2407   src = GST_SOUP_HTTP_SRC (bsrc);
2408
2409   if (src->have_size) {
2410     GST_DEBUG_OBJECT (src, "get_size() = %" G_GUINT64_FORMAT,
2411         src->content_size);
2412     *size = src->content_size;
2413     return TRUE;
2414   }
2415   GST_DEBUG_OBJECT (src, "get_size() = FALSE");
2416   return FALSE;
2417 }
2418
2419 static void
2420 gst_soup_http_src_check_seekable (GstSoupHTTPSrc * src)
2421 {
2422   GstFlowReturn ret = GST_FLOW_OK;
2423
2424   /* Special case to check if the server allows range requests
2425    * before really starting to get data in the buffer creation
2426    * loops.
2427    */
2428   if (!src->got_headers && GST_STATE (src) >= GST_STATE_PAUSED) {
2429     g_mutex_lock (&src->session_mutex);
2430     while (!src->got_headers && !g_cancellable_is_cancelled (src->cancellable)
2431         && ret == GST_FLOW_OK) {
2432       if ((src->msg && _soup_message_get_method (src->msg) != SOUP_METHOD_HEAD)) {
2433         /* wait for the current request to finish */
2434         g_cond_wait (&src->session_cond, &src->session_mutex);
2435         ret = src->headers_ret;
2436       } else {
2437         if (gst_soup_http_src_session_open (src)) {
2438           ret = gst_soup_http_src_do_request (src, SOUP_METHOD_HEAD);
2439         }
2440       }
2441     }
2442     g_mutex_unlock (&src->session_mutex);
2443   }
2444 }
2445
2446 static gboolean
2447 gst_soup_http_src_is_seekable (GstBaseSrc * bsrc)
2448 {
2449   GstSoupHTTPSrc *src = GST_SOUP_HTTP_SRC (bsrc);
2450
2451   gst_soup_http_src_check_seekable (src);
2452
2453   return src->seekable;
2454 }
2455
2456 static gboolean
2457 gst_soup_http_src_do_seek (GstBaseSrc * bsrc, GstSegment * segment)
2458 {
2459   GstSoupHTTPSrc *src = GST_SOUP_HTTP_SRC (bsrc);
2460
2461   GST_DEBUG_OBJECT (src, "do_seek(%" G_GUINT64_FORMAT "-%" G_GUINT64_FORMAT
2462       ")", segment->start, segment->stop);
2463   if (src->read_position == segment->start &&
2464       src->request_position == src->read_position &&
2465       src->stop_position == segment->stop) {
2466     GST_DEBUG_OBJECT (src,
2467         "Seek to current read/end position and no seek pending");
2468     return TRUE;
2469   }
2470
2471   gst_soup_http_src_check_seekable (src);
2472
2473   /* If we have no headers we don't know yet if it is seekable or not.
2474    * Store the start position and error out later if it isn't */
2475   if (src->got_headers && !src->seekable) {
2476     GST_WARNING_OBJECT (src, "Not seekable");
2477     return FALSE;
2478   }
2479
2480   if (segment->rate < 0.0 || segment->format != GST_FORMAT_BYTES) {
2481     GST_WARNING_OBJECT (src, "Invalid seek segment");
2482     return FALSE;
2483   }
2484
2485   if (src->have_size && segment->start >= src->content_size) {
2486     GST_WARNING_OBJECT (src,
2487         "Potentially seeking behind end of file, might EOS immediately");
2488   }
2489
2490   /* Wait for create() to handle the jump in offset. */
2491   src->request_position = segment->start;
2492   src->stop_position = segment->stop;
2493
2494   return TRUE;
2495 }
2496
2497 static gboolean
2498 gst_soup_http_src_query (GstBaseSrc * bsrc, GstQuery * query)
2499 {
2500   GstSoupHTTPSrc *src = GST_SOUP_HTTP_SRC (bsrc);
2501   gboolean ret;
2502   GstSchedulingFlags flags;
2503   gint minsize, maxsize, align;
2504
2505   switch (GST_QUERY_TYPE (query)) {
2506     case GST_QUERY_URI:
2507       gst_query_set_uri (query, src->location);
2508       if (src->redirection_uri != NULL) {
2509         gst_query_set_uri_redirection (query, src->redirection_uri);
2510         gst_query_set_uri_redirection_permanent (query,
2511             src->redirection_permanent);
2512       }
2513       ret = TRUE;
2514       break;
2515     default:
2516       ret = FALSE;
2517       break;
2518   }
2519
2520   if (!ret)
2521     ret = GST_BASE_SRC_CLASS (parent_class)->query (bsrc, query);
2522
2523   switch (GST_QUERY_TYPE (query)) {
2524     case GST_QUERY_SCHEDULING:
2525       gst_query_parse_scheduling (query, &flags, &minsize, &maxsize, &align);
2526       flags |= GST_SCHEDULING_FLAG_BANDWIDTH_LIMITED;
2527       gst_query_set_scheduling (query, flags, minsize, maxsize, align);
2528       break;
2529     default:
2530       break;
2531   }
2532
2533   return ret;
2534 }
2535
2536 static gboolean
2537 gst_soup_http_src_set_location (GstSoupHTTPSrc * src, const gchar * uri,
2538     GError ** error)
2539 {
2540   const char *alt_schemes[] = { "icy://", "icyx://" };
2541   guint i;
2542
2543   if (src->location) {
2544     g_free (src->location);
2545     src->location = NULL;
2546   }
2547
2548   if (uri == NULL)
2549     return FALSE;
2550
2551   for (i = 0; i < G_N_ELEMENTS (alt_schemes); i++) {
2552     if (g_str_has_prefix (uri, alt_schemes[i])) {
2553       src->location =
2554           g_strdup_printf ("http://%s", uri + strlen (alt_schemes[i]));
2555       return TRUE;
2556     }
2557   }
2558
2559   if (src->redirection_uri) {
2560     g_free (src->redirection_uri);
2561     src->redirection_uri = NULL;
2562   }
2563
2564   src->location = g_strdup (uri);
2565
2566   return TRUE;
2567 }
2568
2569 static gboolean
2570 gst_soup_http_src_set_proxy (GstSoupHTTPSrc * src, const gchar * uri)
2571 {
2572   if (src->proxy) {
2573     gst_soup_uri_free (src->proxy);
2574     src->proxy = NULL;
2575   }
2576
2577   if (uri == NULL || *uri == '\0')
2578     return TRUE;
2579
2580   if (g_strstr_len (uri, -1, "://")) {
2581     src->proxy = gst_soup_uri_new (uri);
2582   } else {
2583     gchar *new_uri = g_strconcat ("http://", uri, NULL);
2584
2585     src->proxy = gst_soup_uri_new (new_uri);
2586     g_free (new_uri);
2587   }
2588
2589   return (src->proxy != NULL);
2590 }
2591
2592 static GstURIType
2593 gst_soup_http_src_uri_get_type (GType type)
2594 {
2595   return GST_URI_SRC;
2596 }
2597
2598 static const gchar *const *
2599 gst_soup_http_src_uri_get_protocols (GType type)
2600 {
2601   static const gchar *protocols[] = { "http", "https", "icy", "icyx", NULL };
2602
2603   return protocols;
2604 }
2605
2606 static gchar *
2607 gst_soup_http_src_uri_get_uri (GstURIHandler * handler)
2608 {
2609   GstSoupHTTPSrc *src = GST_SOUP_HTTP_SRC (handler);
2610
2611   /* FIXME: make thread-safe */
2612   return g_strdup (src->location);
2613 }
2614
2615 static gboolean
2616 gst_soup_http_src_uri_set_uri (GstURIHandler * handler, const gchar * uri,
2617     GError ** error)
2618 {
2619   GstSoupHTTPSrc *src = GST_SOUP_HTTP_SRC (handler);
2620
2621   return gst_soup_http_src_set_location (src, uri, error);
2622 }
2623
2624 static void
2625 gst_soup_http_src_uri_handler_init (gpointer g_iface, gpointer iface_data)
2626 {
2627   GstURIHandlerInterface *iface = (GstURIHandlerInterface *) g_iface;
2628
2629   iface->get_type = gst_soup_http_src_uri_get_type;
2630   iface->get_protocols = gst_soup_http_src_uri_get_protocols;
2631   iface->get_uri = gst_soup_http_src_uri_get_uri;
2632   iface->set_uri = gst_soup_http_src_uri_set_uri;
2633 }