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