gstsouphttpsrc.c 63.4 KB
Newer Older
1
/* GStreamer
2
 * Copyright (C) 2007-2008 Wouter Cloetens <wouter@mind.be>
3 4 5 6 7 8 9 10 11 12 13 14
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more
 */

15 16 17 18
/**
 * SECTION:element-souphttpsrc
 *
 * This plugin reads data from a remote location specified by a URI.
19
 * Supported protocols are 'http', 'https'.
20
 *
21 22
 * An HTTP proxy must be specified by its URL.
 * If the "http_proxy" environment variable is set, its value is used.
23 24
 * If built with libsoup's GNOME integration features, the GNOME proxy
 * configuration will be used, or failing that, proxy autodetection.
25 26 27 28 29 30 31 32 33 34 35 36 37 38
 * The #GstSoupHTTPSrc:proxy property can be used to override the default.
 *
 * In case the #GstSoupHTTPSrc:iradio-mode property is set and the location is
 * an HTTP resource, souphttpsrc will send special Icecast HTTP headers to the
 * server to request additional Icecast meta-information.
 * If the server is not an Icecast server, it will behave as if the
 * #GstSoupHTTPSrc:iradio-mode property were not set. If it is, souphttpsrc will
 * output data with a media type of application/x-icy, in which case you will
 * need to use the #ICYDemux element as follow-up element to extract the Icecast
 * metadata and to determine the underlying media type.
 *
 * <refsect2>
 * <title>Example launch line</title>
 * |[
39
 * gst-launch-1.0 -v souphttpsrc location=https://some.server.org/index.html
40
 *     ! filesink location=/home/joe/server.html
41
 * ]| The above pipeline reads a web page from a server using the HTTPS protocol
42
 * and writes it to a local file.
43
 * |[
44
 * gst-launch-1.0 -v souphttpsrc user-agent="FooPlayer 0.99 beta"
45 46 47
 *     automatic-redirect=false proxy=http://proxy.intranet.local:8080
 *     location=http://music.foobar.com/demo.mp3 ! mad ! audioconvert
 *     ! audioresample ! alsasink
48
 * ]| The above pipeline will read and decode and play an mp3 file from a
49 50 51 52
 * web server using the HTTP protocol. If the server sends redirects,
 * the request fails instead of following the redirect. The specified
 * HTTP proxy server is used. The User-Agent HTTP request header
 * is set to a custom string instead of "GStreamer souphttpsrc."
53
 * |[
54
 * gst-launch-1.0 -v souphttpsrc location=http://10.11.12.13/mjpeg
55
 *     do-timestamp=true ! multipartdemux
56 57
 *     ! image/jpeg,width=640,height=480 ! matroskamux
 *     ! filesink location=mjpeg.mkv
58
 * ]| The above pipeline reads a motion JPEG stream from an IP camera
59 60 61 62 63 64 65 66 67 68 69
 * using the HTTP protocol, encoded as mime/multipart image/jpeg
 * parts, and writes a Matroska motion JPEG file. The width and
 * height properties are set in the caps to provide the Matroska
 * multiplexer with the information to set this in the header.
 * Timestamps are set on the buffers as they arrive from the camera.
 * These are used by the mime/multipart demultiplexer to emit timestamps
 * on the JPEG-encoded video frame buffers. This allows the Matroska
 * multiplexer to timestamp the frames in the resulting file.
 * </refsect2>
 */

70 71 72 73 74
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <string.h>
75 76 77
#ifdef HAVE_STDLIB_H
#include <stdlib.h>             /* atoi() */
#endif
78 79
#include <gst/gstelement.h>
#include <gst/gst-i18n-plugin.h>
80 81
#include <libsoup/soup.h>
#include "gstsouphttpsrc.h"
82
#include "gstsouputils.h"
83

84 85
#include <gst/tag/tag.h>

86 87 88 89 90 91 92 93 94 95 96 97
GST_DEBUG_CATEGORY_STATIC (souphttpsrc_debug);
#define GST_CAT_DEFAULT souphttpsrc_debug

static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src",
    GST_PAD_SRC,
    GST_PAD_ALWAYS,
    GST_STATIC_CAPS_ANY);

enum
{
  PROP_0,
  PROP_LOCATION,
98
  PROP_IS_LIVE,
99
  PROP_USER_AGENT,
100 101
  PROP_AUTOMATIC_REDIRECT,
  PROP_PROXY,
102 103 104 105
  PROP_USER_ID,
  PROP_USER_PW,
  PROP_PROXY_ID,
  PROP_PROXY_PW,
106
  PROP_COOKIES,
107
  PROP_IRADIO_MODE,
108
  PROP_TIMEOUT,
109
  PROP_EXTRA_HEADERS,
110
  PROP_SOUP_LOG_LEVEL,
111
  PROP_COMPRESS,
112 113 114
  PROP_KEEP_ALIVE,
  PROP_SSL_STRICT,
  PROP_SSL_CA_FILE,
115
  PROP_SSL_USE_SYSTEM_CA_FILE,
116
  PROP_TLS_DATABASE,
117
  PROP_RETRIES,
118 119
  PROP_METHOD,
  PROP_TLS_INTERACTION,
120 121
};

122
#define DEFAULT_USER_AGENT           "GStreamer souphttpsrc "
123
#define DEFAULT_IRADIO_MODE          TRUE
124
#define DEFAULT_SOUP_LOG_LEVEL       SOUP_LOGGER_LOG_HEADERS
125
#define DEFAULT_COMPRESS             FALSE
126
#define DEFAULT_KEEP_ALIVE           FALSE
127 128 129
#define DEFAULT_SSL_STRICT           TRUE
#define DEFAULT_SSL_CA_FILE          NULL
#define DEFAULT_SSL_USE_SYSTEM_CA_FILE TRUE
130
#define DEFAULT_TLS_DATABASE         NULL
131
#define DEFAULT_TLS_INTERACTION      NULL
132
#define DEFAULT_TIMEOUT              15
133
#define DEFAULT_RETRIES              3
134
#define DEFAULT_SOUP_METHOD          NULL
135

136 137 138 139 140 141 142
#define GROW_BLOCKSIZE_LIMIT 1
#define GROW_BLOCKSIZE_COUNT 1
#define GROW_BLOCKSIZE_FACTOR 2
#define REDUCE_BLOCKSIZE_LIMIT 0.20
#define REDUCE_BLOCKSIZE_COUNT 2
#define REDUCE_BLOCKSIZE_FACTOR 0.5

143
static void gst_soup_http_src_uri_handler_init (gpointer g_iface,
144
    gpointer iface_data);
145
static void gst_soup_http_src_finalize (GObject * gobject);
146
static void gst_soup_http_src_dispose (GObject * gobject);
147

148
static void gst_soup_http_src_set_property (GObject * object, guint prop_id,
149
    const GValue * value, GParamSpec * pspec);
150
static void gst_soup_http_src_get_property (GObject * object, guint prop_id,
151 152
    GValue * value, GParamSpec * pspec);

153 154
static GstStateChangeReturn gst_soup_http_src_change_state (GstElement *
    element, GstStateChange transition);
155
static GstFlowReturn gst_soup_http_src_create (GstPushSrc * psrc,
156
    GstBuffer ** outbuf);
157 158 159 160 161
static gboolean gst_soup_http_src_start (GstBaseSrc * bsrc);
static gboolean gst_soup_http_src_stop (GstBaseSrc * bsrc);
static gboolean gst_soup_http_src_get_size (GstBaseSrc * bsrc, guint64 * size);
static gboolean gst_soup_http_src_is_seekable (GstBaseSrc * bsrc);
static gboolean gst_soup_http_src_do_seek (GstBaseSrc * bsrc,
162
    GstSegment * segment);
163
static gboolean gst_soup_http_src_query (GstBaseSrc * bsrc, GstQuery * query);
164 165 166
static gboolean gst_soup_http_src_unlock (GstBaseSrc * bsrc);
static gboolean gst_soup_http_src_unlock_stop (GstBaseSrc * bsrc);
static gboolean gst_soup_http_src_set_location (GstSoupHTTPSrc * src,
167
    const gchar * uri, GError ** error);
168
static gboolean gst_soup_http_src_set_proxy (GstSoupHTTPSrc * src,
169
    const gchar * uri);
170
static char *gst_soup_http_src_unicodify (const char *str);
171 172
static gboolean gst_soup_http_src_build_message (GstSoupHTTPSrc * src,
    const gchar * method);
173 174
static void gst_soup_http_src_cancel_message (GstSoupHTTPSrc * src);
static gboolean gst_soup_http_src_add_range_header (GstSoupHTTPSrc * src,
175
    guint64 offset, guint64 stop_offset);
176
static gboolean gst_soup_http_src_session_open (GstSoupHTTPSrc * src);
177
static void gst_soup_http_src_session_close (GstSoupHTTPSrc * src);
178
static GstFlowReturn gst_soup_http_src_parse_status (SoupMessage * msg,
179
    GstSoupHTTPSrc * src);
180
static GstFlowReturn gst_soup_http_src_got_headers (GstSoupHTTPSrc * src,
Sebastian Dröge's avatar
Sebastian Dröge committed
181
    SoupMessage * msg);
182 183 184
static void gst_soup_http_src_authenticate_cb (SoupSession * session,
    SoupMessage * msg, SoupAuth * auth, gboolean retrying,
    GstSoupHTTPSrc * src);
185

Wim Taymans's avatar
Wim Taymans committed
186 187 188 189
#define gst_soup_http_src_parent_class parent_class
G_DEFINE_TYPE_WITH_CODE (GstSoupHTTPSrc, gst_soup_http_src, GST_TYPE_PUSH_SRC,
    G_IMPLEMENT_INTERFACE (GST_TYPE_URI_HANDLER,
        gst_soup_http_src_uri_handler_init));
190 191

static void
192
gst_soup_http_src_class_init (GstSoupHTTPSrcClass * klass)
193 194
{
  GObjectClass *gobject_class;
Wim Taymans's avatar
Wim Taymans committed
195
  GstElementClass *gstelement_class;
196 197 198 199
  GstBaseSrcClass *gstbasesrc_class;
  GstPushSrcClass *gstpushsrc_class;

  gobject_class = (GObjectClass *) klass;
Wim Taymans's avatar
Wim Taymans committed
200
  gstelement_class = (GstElementClass *) klass;
201 202 203
  gstbasesrc_class = (GstBaseSrcClass *) klass;
  gstpushsrc_class = (GstPushSrcClass *) klass;

204 205
  gobject_class->set_property = gst_soup_http_src_set_property;
  gobject_class->get_property = gst_soup_http_src_get_property;
206
  gobject_class->finalize = gst_soup_http_src_finalize;
207
  gobject_class->dispose = gst_soup_http_src_dispose;
208

209 210
  g_object_class_install_property (gobject_class,
      PROP_LOCATION,
211
      g_param_spec_string ("location", "Location",
212 213
          "Location to read from", "",
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
214 215 216 217
  g_object_class_install_property (gobject_class,
      PROP_USER_AGENT,
      g_param_spec_string ("user-agent", "User-Agent",
          "Value of the User-Agent HTTP request header field",
218
          DEFAULT_USER_AGENT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
219 220 221 222
  g_object_class_install_property (gobject_class,
      PROP_AUTOMATIC_REDIRECT,
      g_param_spec_boolean ("automatic-redirect", "automatic-redirect",
          "Automatically follow HTTP redirects (HTTP Status Code 3xx)",
223
          TRUE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
224 225 226
  g_object_class_install_property (gobject_class,
      PROP_PROXY,
      g_param_spec_string ("proxy", "Proxy",
227 228
          "HTTP proxy server URI", "",
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
229
  g_object_class_install_property (gobject_class,
230 231 232
      PROP_USER_ID,
      g_param_spec_string ("user-id", "user-id",
          "HTTP location URI user id for authentication", "",
233
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
234 235 236
  g_object_class_install_property (gobject_class, PROP_USER_PW,
      g_param_spec_string ("user-pw", "user-pw",
          "HTTP location URI user password for authentication", "",
237
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
238 239
  g_object_class_install_property (gobject_class, PROP_PROXY_ID,
      g_param_spec_string ("proxy-id", "proxy-id",
240 241
          "HTTP proxy URI user id for authentication", "",
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
242 243 244
  g_object_class_install_property (gobject_class, PROP_PROXY_PW,
      g_param_spec_string ("proxy-pw", "proxy-pw",
          "HTTP proxy URI user password for authentication", "",
245
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
246 247
  g_object_class_install_property (gobject_class, PROP_COOKIES,
      g_param_spec_boxed ("cookies", "Cookies", "HTTP request cookies",
248
          G_TYPE_STRV, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
249 250
  g_object_class_install_property (gobject_class, PROP_IS_LIVE,
      g_param_spec_boolean ("is-live", "is-live", "Act like a live source",
251
          FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
252 253 254
  g_object_class_install_property (gobject_class, PROP_TIMEOUT,
      g_param_spec_uint ("timeout", "timeout",
          "Value in seconds to timeout a blocking I/O (0 = No timeout).", 0,
255
          3600, DEFAULT_TIMEOUT, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
256 257 258
  g_object_class_install_property (gobject_class, PROP_EXTRA_HEADERS,
      g_param_spec_boxed ("extra-headers", "Extra Headers",
          "Extra headers to append to the HTTP request",
259
          GST_TYPE_STRUCTURE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
260 261 262 263 264
  g_object_class_install_property (gobject_class, PROP_IRADIO_MODE,
      g_param_spec_boolean ("iradio-mode", "iradio-mode",
          "Enable internet radio mode (ask server to send shoutcast/icecast "
          "metadata interleaved with the actual stream data)",
          DEFAULT_IRADIO_MODE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
265

266 267 268 269 270 271 272 273 274 275 276 277 278
 /**
   * GstSoupHTTPSrc::http-log-level:
   *
   * If set and > 0, captures and dumps HTTP session data as
   * log messages if log level >= GST_LEVEL_TRACE
   *
   * Since: 1.4
   */
  g_object_class_install_property (gobject_class, PROP_SOUP_LOG_LEVEL,
      g_param_spec_enum ("http-log-level", "HTTP log level",
          "Set log level for soup's HTTP session log",
          SOUP_TYPE_LOGGER_LOG_LEVEL, DEFAULT_SOUP_LOG_LEVEL,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
279

280 281 282 283 284 285 286 287 288 289 290 291 292 293 294
 /**
   * GstSoupHTTPSrc::compress:
   *
   * If set to %TRUE, souphttpsrc will automatically handle gzip
   * and deflate Content-Encodings. This does not make much difference
   * and causes more load for normal media files, but makes a real
   * difference in size for plaintext files.
   *
   * Since: 1.4
   */
  g_object_class_install_property (gobject_class, PROP_COMPRESS,
      g_param_spec_boolean ("compress", "Compress",
          "Allow compressed content encodings",
          DEFAULT_COMPRESS, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

295 296 297 298 299 300 301 302 303 304 305 306 307 308
 /**
   * GstSoupHTTPSrc::keep-alive:
   *
   * If set to %TRUE, souphttpsrc will keep alive connections when being
   * set to READY state and only will close connections when connecting
   * to a different server or when going to NULL state..
   *
   * Since: 1.4
   */
  g_object_class_install_property (gobject_class, PROP_KEEP_ALIVE,
      g_param_spec_boolean ("keep-alive", "keep-alive",
          "Use HTTP persistent connections", DEFAULT_KEEP_ALIVE,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327
 /**
   * GstSoupHTTPSrc::ssl-strict:
   *
   * If set to %TRUE, souphttpsrc will reject all SSL certificates that
   * are considered invalid.
   *
   * Since: 1.4
   */
  g_object_class_install_property (gobject_class, PROP_SSL_STRICT,
      g_param_spec_boolean ("ssl-strict", "SSL Strict",
          "Strict SSL certificate checking", DEFAULT_SSL_STRICT,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

 /**
   * GstSoupHTTPSrc::ssl-ca-file:
   *
   * A SSL anchor CA file that should be used for checking certificates
   * instead of the system CA file.
   *
328 329 330 331
   * If this property is non-%NULL, #GstSoupHTTPSrc::ssl-use-system-ca-file
   * value will be ignored.
   *
   * Deprecated: Use #GstSoupHTTPSrc::tls-database property instead.
332 333 334 335 336 337 338 339 340 341 342
   * Since: 1.4
   */
  g_object_class_install_property (gobject_class, PROP_SSL_CA_FILE,
      g_param_spec_string ("ssl-ca-file", "SSL CA File",
          "Location of a SSL anchor CA file to use", DEFAULT_SSL_CA_FILE,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

 /**
   * GstSoupHTTPSrc::ssl-use-system-ca-file:
   *
   * If set to %TRUE, souphttpsrc will use the system's CA file for
343 344
   * checking certificates, unless #GstSoupHTTPSrc::ssl-ca-file or
   * #GstSoupHTTPSrc::tls-database are non-%NULL.
345 346 347 348 349 350 351 352
   *
   * Since: 1.4
   */
  g_object_class_install_property (gobject_class, PROP_SSL_USE_SYSTEM_CA_FILE,
      g_param_spec_boolean ("ssl-use-system-ca-file", "Use System CA File",
          "Use system CA file", DEFAULT_SSL_USE_SYSTEM_CA_FILE,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368
  /**
   * GstSoupHTTPSrc::tls-database:
   *
   * TLS database with anchor certificate authorities used to validate
   * the server certificate.
   *
   * If this property is non-%NULL, #GstSoupHTTPSrc::ssl-use-system-ca-file
   * and #GstSoupHTTPSrc::ssl-ca-file values will be ignored.
   *
   * Since: 1.6
   */
  g_object_class_install_property (gobject_class, PROP_TLS_DATABASE,
      g_param_spec_object ("tls-database", "TLS database",
          "TLS database with anchor certificate authorities used to validate the server certificate",
          G_TYPE_TLS_DATABASE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

369 370 371 372 373 374 375 376 377 378 379 380 381 382
  /**
   * GstSoupHTTPSrc::tls-interaction:
   *
   * A #GTlsInteraction object to be used when the connection or certificate
   * database need to interact with the user. This will be used to prompt the
   * user for passwords or certificate where necessary.
   *
   * Since: 1.8
   */
  g_object_class_install_property (gobject_class, PROP_TLS_INTERACTION,
      g_param_spec_object ("tls-interaction", "TLS interaction",
          "A GTlsInteraction object to be used when the connection or certificate database need to interact with the user.",
          G_TYPE_TLS_INTERACTION, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

383 384 385 386 387 388 389 390 391 392 393 394 395
 /**
   * GstSoupHTTPSrc::retries:
   *
   * Maximum number of retries until giving up.
   *
   * Since: 1.4
   */
  g_object_class_install_property (gobject_class, PROP_RETRIES,
      g_param_spec_int ("retries", "Retries",
          "Maximum number of retries until giving up (-1=infinite)", -1,
          G_MAXINT, DEFAULT_RETRIES,
          G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

396 397 398 399 400 401 402 403 404 405 406 407
 /**
   * GstSoupHTTPSrc::method
   *
   * The HTTP method to use when making a request
   *
   * Since: 1.6
   */
  g_object_class_install_property (gobject_class, PROP_METHOD,
      g_param_spec_string ("method", "HTTP method",
          "The HTTP method to use (GET, HEAD, OPTIONS, etc)",
          DEFAULT_SOUP_METHOD, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));

408
  gst_element_class_add_static_pad_template (gstelement_class, &srctemplate);
Wim Taymans's avatar
Wim Taymans committed
409

410
  gst_element_class_set_static_metadata (gstelement_class, "HTTP client source",
Wim Taymans's avatar
Wim Taymans committed
411 412 413
      "Source/Network",
      "Receive data as a client over the network via HTTP using SOUP",
      "Wouter Cloetens <wouter@mind.be>");
414 415
  gstelement_class->change_state =
      GST_DEBUG_FUNCPTR (gst_soup_http_src_change_state);
Wim Taymans's avatar
Wim Taymans committed
416

417 418 419
  gstbasesrc_class->start = GST_DEBUG_FUNCPTR (gst_soup_http_src_start);
  gstbasesrc_class->stop = GST_DEBUG_FUNCPTR (gst_soup_http_src_stop);
  gstbasesrc_class->unlock = GST_DEBUG_FUNCPTR (gst_soup_http_src_unlock);
420
  gstbasesrc_class->unlock_stop =
421 422
      GST_DEBUG_FUNCPTR (gst_soup_http_src_unlock_stop);
  gstbasesrc_class->get_size = GST_DEBUG_FUNCPTR (gst_soup_http_src_get_size);
423
  gstbasesrc_class->is_seekable =
424 425
      GST_DEBUG_FUNCPTR (gst_soup_http_src_is_seekable);
  gstbasesrc_class->do_seek = GST_DEBUG_FUNCPTR (gst_soup_http_src_do_seek);
426
  gstbasesrc_class->query = GST_DEBUG_FUNCPTR (gst_soup_http_src_query);
427

428
  gstpushsrc_class->create = GST_DEBUG_FUNCPTR (gst_soup_http_src_create);
Wim Taymans's avatar
Wim Taymans committed
429 430 431

  GST_DEBUG_CATEGORY_INIT (souphttpsrc_debug, "souphttpsrc", 0,
      "SOUP HTTP src");
432 433
}

434 435 436
static void
gst_soup_http_src_reset (GstSoupHTTPSrc * src)
{
437
  src->retry_count = 0;
438
  src->have_size = FALSE;
439
  src->got_headers = FALSE;
440
  src->seekable = FALSE;
441 442
  src->read_position = 0;
  src->request_position = 0;
443
  src->stop_position = -1;
444
  src->content_size = 0;
445
  src->have_body = FALSE;
446

447 448 449
  src->reduce_blocksize_count = 0;
  src->increase_blocksize_count = 0;

Sebastian Dröge's avatar
Sebastian Dröge committed
450
  g_cancellable_reset (src->cancellable);
451 452 453 454
  if (src->input_stream) {
    g_object_unref (src->input_stream);
    src->input_stream = NULL;
  }
455

456 457 458 459 460 461 462 463 464
  gst_caps_replace (&src->src_caps, NULL);
  g_free (src->iradio_name);
  src->iradio_name = NULL;
  g_free (src->iradio_genre);
  src->iradio_genre = NULL;
  g_free (src->iradio_url);
  src->iradio_url = NULL;
}

465
static void
Wim Taymans's avatar
Wim Taymans committed
466
gst_soup_http_src_init (GstSoupHTTPSrc * src)
467
{
468 469
  const gchar *proxy;

470
  g_mutex_init (&src->mutex);
Sebastian Dröge's avatar
Sebastian Dröge committed
471 472
  g_cond_init (&src->have_headers_cond);
  src->cancellable = g_cancellable_new ();
473
  src->location = NULL;
474
  src->redirection_uri = NULL;
475
  src->automatic_redirect = TRUE;
476
  src->user_agent = g_strdup (DEFAULT_USER_AGENT);
477 478 479 480
  src->user_id = NULL;
  src->user_pw = NULL;
  src->proxy_id = NULL;
  src->proxy_pw = NULL;
481
  src->cookies = NULL;
482
  src->iradio_mode = DEFAULT_IRADIO_MODE;
483 484
  src->session = NULL;
  src->msg = NULL;
485
  src->timeout = DEFAULT_TIMEOUT;
486
  src->log_level = DEFAULT_SOUP_LOG_LEVEL;
487 488
  src->ssl_strict = DEFAULT_SSL_STRICT;
  src->ssl_use_system_ca_file = DEFAULT_SSL_USE_SYSTEM_CA_FILE;
489
  src->tls_database = DEFAULT_TLS_DATABASE;
490
  src->tls_interaction = DEFAULT_TLS_INTERACTION;
491
  src->max_retries = DEFAULT_RETRIES;
492
  src->method = DEFAULT_SOUP_METHOD;
493
  src->minimum_blocksize = gst_base_src_get_blocksize (GST_BASE_SRC_CAST (src));
494
  proxy = g_getenv ("http_proxy");
495
  if (!gst_soup_http_src_set_proxy (src, proxy)) {
496 497 498 499
    GST_WARNING_OBJECT (src,
        "The proxy in the http_proxy env var (\"%s\") cannot be parsed.",
        proxy);
  }
500

501 502
  gst_base_src_set_automatic_eos (GST_BASE_SRC (src), FALSE);

503
  gst_soup_http_src_reset (src);
504 505
}

506 507 508 509 510 511 512 513 514 515 516 517
static void
gst_soup_http_src_dispose (GObject * gobject)
{
  GstSoupHTTPSrc *src = GST_SOUP_HTTP_SRC (gobject);

  GST_DEBUG_OBJECT (src, "dispose");

  gst_soup_http_src_session_close (src);

  G_OBJECT_CLASS (parent_class)->dispose (gobject);
}

518
static void
519
gst_soup_http_src_finalize (GObject * gobject)
520
{
521
  GstSoupHTTPSrc *src = GST_SOUP_HTTP_SRC (gobject);
522

523 524
  GST_DEBUG_OBJECT (src, "finalize");

525
  g_mutex_clear (&src->mutex);
Sebastian Dröge's avatar
Sebastian Dröge committed
526 527
  g_cond_clear (&src->have_headers_cond);
  g_object_unref (src->cancellable);
528
  g_free (src->location);
529
  g_free (src->redirection_uri);
530
  g_free (src->user_agent);
531 532 533
  if (src->proxy != NULL) {
    soup_uri_free (src->proxy);
  }
534 535 536 537
  g_free (src->user_id);
  g_free (src->user_pw);
  g_free (src->proxy_id);
  g_free (src->proxy_pw);
538
  g_strfreev (src->cookies);
539

540 541 542 543 544
  if (src->extra_headers) {
    gst_structure_free (src->extra_headers);
    src->extra_headers = NULL;
  }

545 546
  g_free (src->ssl_ca_file);

547 548
  if (src->tls_database)
    g_object_unref (src->tls_database);
549
  g_free (src->method);
550

551 552 553
  if (src->tls_interaction)
    g_object_unref (src->tls_interaction);

554
  G_OBJECT_CLASS (parent_class)->finalize (gobject);
555 556 557
}

static void
558
gst_soup_http_src_set_property (GObject * object, guint prop_id,
559 560
    const GValue * value, GParamSpec * pspec)
{
561
  GstSoupHTTPSrc *src = GST_SOUP_HTTP_SRC (object);
562 563 564 565 566 567 568 569 570 571 572 573

  switch (prop_id) {
    case PROP_LOCATION:
    {
      const gchar *location;

      location = g_value_get_string (value);

      if (location == NULL) {
        GST_WARNING ("location property cannot be NULL");
        goto done;
      }
574
      if (!gst_soup_http_src_set_location (src, location, NULL)) {
575 576 577 578 579
        GST_WARNING ("badly formatted location");
        goto done;
      }
      break;
    }
580
    case PROP_USER_AGENT:
581
      g_free (src->user_agent);
582 583
      src->user_agent = g_value_dup_string (value);
      break;
584 585 586
    case PROP_IRADIO_MODE:
      src->iradio_mode = g_value_get_boolean (value);
      break;
587 588 589 590 591 592 593 594
    case PROP_AUTOMATIC_REDIRECT:
      src->automatic_redirect = g_value_get_boolean (value);
      break;
    case PROP_PROXY:
    {
      const gchar *proxy;

      proxy = g_value_get_string (value);
595
      if (!gst_soup_http_src_set_proxy (src, proxy)) {
596 597 598 599 600
        GST_WARNING ("badly formatted proxy URI");
        goto done;
      }
      break;
    }
601 602 603 604
    case PROP_COOKIES:
      g_strfreev (src->cookies);
      src->cookies = g_strdupv (g_value_get_boxed (value));
      break;
605 606 607
    case PROP_IS_LIVE:
      gst_base_src_set_live (GST_BASE_SRC (src), g_value_get_boolean (value));
      break;
608
    case PROP_USER_ID:
609
      g_free (src->user_id);
610 611 612
      src->user_id = g_value_dup_string (value);
      break;
    case PROP_USER_PW:
613
      g_free (src->user_pw);
614 615 616
      src->user_pw = g_value_dup_string (value);
      break;
    case PROP_PROXY_ID:
617
      g_free (src->proxy_id);
618 619 620
      src->proxy_id = g_value_dup_string (value);
      break;
    case PROP_PROXY_PW:
621
      g_free (src->proxy_pw);
622 623
      src->proxy_pw = g_value_dup_string (value);
      break;
624 625 626
    case PROP_TIMEOUT:
      src->timeout = g_value_get_uint (value);
      break;
627 628 629 630 631 632 633 634 635
    case PROP_EXTRA_HEADERS:{
      const GstStructure *s = gst_value_get_structure (value);

      if (src->extra_headers)
        gst_structure_free (src->extra_headers);

      src->extra_headers = s ? gst_structure_copy (s) : NULL;
      break;
    }
636 637 638
    case PROP_SOUP_LOG_LEVEL:
      src->log_level = g_value_get_enum (value);
      break;
639 640 641
    case PROP_COMPRESS:
      src->compress = g_value_get_boolean (value);
      break;
642 643 644
    case PROP_KEEP_ALIVE:
      src->keep_alive = g_value_get_boolean (value);
      break;
645 646 647 648
    case PROP_SSL_STRICT:
      src->ssl_strict = g_value_get_boolean (value);
      break;
    case PROP_SSL_CA_FILE:
649
      g_free (src->ssl_ca_file);
650 651 652 653 654
      src->ssl_ca_file = g_value_dup_string (value);
      break;
    case PROP_SSL_USE_SYSTEM_CA_FILE:
      src->ssl_use_system_ca_file = g_value_get_boolean (value);
      break;
655 656 657 658
    case PROP_TLS_DATABASE:
      g_clear_object (&src->tls_database);
      src->tls_database = g_value_dup_object (value);
      break;
659 660 661 662
    case PROP_TLS_INTERACTION:
      g_clear_object (&src->tls_interaction);
      src->tls_interaction = g_value_dup_object (value);
      break;
663 664 665
    case PROP_RETRIES:
      src->max_retries = g_value_get_int (value);
      break;
666
    case PROP_METHOD:
667
      g_free (src->method);
668 669
      src->method = g_value_dup_string (value);
      break;
670 671 672
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
673 674 675 676 677 678
  }
done:
  return;
}

static void
679
gst_soup_http_src_get_property (GObject * object, guint prop_id,
680 681
    GValue * value, GParamSpec * pspec)
{
682
  GstSoupHTTPSrc *src = GST_SOUP_HTTP_SRC (object);
683 684 685

  switch (prop_id) {
    case PROP_LOCATION:
686 687 688 689 690
      g_value_set_string (value, src->location);
      break;
    case PROP_USER_AGENT:
      g_value_set_string (value, src->user_agent);
      break;
691 692 693 694 695
    case PROP_AUTOMATIC_REDIRECT:
      g_value_set_boolean (value, src->automatic_redirect);
      break;
    case PROP_PROXY:
      if (src->proxy == NULL)
696
        g_value_set_static_string (value, "");
697 698 699 700
      else {
        char *proxy = soup_uri_to_string (src->proxy, FALSE);

        g_value_set_string (value, proxy);
701
        g_free (proxy);
702 703
      }
      break;
704 705 706
    case PROP_COOKIES:
      g_value_set_boxed (value, g_strdupv (src->cookies));
      break;
707 708 709
    case PROP_IS_LIVE:
      g_value_set_boolean (value, gst_base_src_is_live (GST_BASE_SRC (src)));
      break;
710 711 712
    case PROP_IRADIO_MODE:
      g_value_set_boolean (value, src->iradio_mode);
      break;
713 714 715 716 717 718 719 720 721 722 723 724
    case PROP_USER_ID:
      g_value_set_string (value, src->user_id);
      break;
    case PROP_USER_PW:
      g_value_set_string (value, src->user_pw);
      break;
    case PROP_PROXY_ID:
      g_value_set_string (value, src->proxy_id);
      break;
    case PROP_PROXY_PW:
      g_value_set_string (value, src->proxy_pw);
      break;
725 726 727
    case PROP_TIMEOUT:
      g_value_set_uint (value, src->timeout);
      break;
728 729 730
    case PROP_EXTRA_HEADERS:
      gst_value_set_structure (value, src->extra_headers);
      break;
731 732 733
    case PROP_SOUP_LOG_LEVEL:
      g_value_set_enum (value, src->log_level);
      break;
734 735 736
    case PROP_COMPRESS:
      g_value_set_boolean (value, src->compress);
      break;
737 738 739
    case PROP_KEEP_ALIVE:
      g_value_set_boolean (value, src->keep_alive);
      break;
740 741 742 743 744 745 746
    case PROP_SSL_STRICT:
      g_value_set_boolean (value, src->ssl_strict);
      break;
    case PROP_SSL_CA_FILE:
      g_value_set_string (value, src->ssl_ca_file);
      break;
    case PROP_SSL_USE_SYSTEM_CA_FILE:
747
      g_value_set_boolean (value, src->ssl_use_system_ca_file);
748
      break;
749 750 751
    case PROP_TLS_DATABASE:
      g_value_set_object (value, src->tls_database);
      break;
752 753 754
    case PROP_TLS_INTERACTION:
      g_value_set_object (value, src->tls_interaction);
      break;
755 756 757
    case PROP_RETRIES:
      g_value_set_int (value, src->max_retries);
      break;
758 759 760
    case PROP_METHOD:
      g_value_set_string (value, src->method);
      break;
761 762 763 764 765 766
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
  }
}

767
static gchar *
768
gst_soup_http_src_unicodify (const gchar * str)
769
{
770 771 772
  const gchar *env_vars[] = { "GST_ICY_TAG_ENCODING",
    "GST_TAG_ENCODING", NULL
  };
773

774
  return gst_tag_freeform_string_to_utf8 (str, -1, env_vars);
775 776
}

777
static void
Sebastian Dröge's avatar
Sebastian Dröge committed
778
gst_soup_http_src_cancel_message (GstSoupHTTPSrc * src)
779
{
Sebastian Dröge's avatar
Sebastian Dröge committed
780 781
  g_cancellable_cancel (src->cancellable);
  g_cond_signal (&src->have_headers_cond);
782 783 784
}

static gboolean
785 786
gst_soup_http_src_add_range_header (GstSoupHTTPSrc * src, guint64 offset,
    guint64 stop_offset)
787 788 789 790
{
  gchar buf[64];
  gint rc;

791
  soup_message_headers_remove (src->msg->request_headers, "Range");
792 793
  if (offset || stop_offset != -1) {
    if (stop_offset != -1) {
794 795
      g_assert (offset != stop_offset);

796
      rc = g_snprintf (buf, sizeof (buf), "bytes=%" G_GUINT64_FORMAT "-%"
797 798
          G_GUINT64_FORMAT, offset, (stop_offset > 0) ? stop_offset - 1 :
          stop_offset);
799 800 801 802
    } else {
      rc = g_snprintf (buf, sizeof (buf), "bytes=%" G_GUINT64_FORMAT "-",
          offset);
    }
803 804
    if (rc > sizeof (buf) || rc < 0)
      return FALSE;
805
    soup_message_headers_append (src->msg->request_headers, "Range", buf);
806 807 808
  }
  src->read_position = offset;
  return TRUE;
809 810
}

811
static gboolean
812
_append_extra_header (GQuark field_id, const GValue * value, gpointer user_data)
813 814 815
{
  GstSoupHTTPSrc *src = GST_SOUP_HTTP_SRC (user_data);
  const gchar *field_name = g_quark_to_string (field_id);
816
  gchar *field_content = NULL;
817

818 819 820 821 822 823 824 825 826
  if (G_VALUE_TYPE (value) == G_TYPE_STRING) {
    field_content = g_value_dup_string (value);
  } else {
    GValue dest = { 0, };

    g_value_init (&dest, G_TYPE_STRING);
    if (g_value_transform (value, &dest)) {
      field_content = g_value_dup_string (&dest);
    }
827 828 829
  }

  if (field_content == NULL) {
830 831
    GST_ERROR_OBJECT (src, "extra-headers field '%s' contains no value "
        "or can't be converted to a string", field_name);
832 833 834 835 836 837 838
    return FALSE;
  }

  GST_DEBUG_OBJECT (src, "Appending extra header: \"%s: %s\"", field_name,
      field_content);
  soup_message_headers_append (src->msg->request_headers, field_name,
      field_content);
839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872

  g_free (field_content);

  return TRUE;
}

static gboolean
_append_extra_headers (GQuark field_id, const GValue * value,
    gpointer user_data)
{
  if (G_VALUE_TYPE (value) == GST_TYPE_ARRAY) {
    guint n = gst_value_array_get_size (value);
    guint i;

    for (i = 0; i < n; i++) {
      const GValue *v = gst_value_array_get_value (value, i);

      if (!_append_extra_header (field_id, v, user_data))
        return FALSE;
    }
  } else if (G_VALUE_TYPE (value) == GST_TYPE_LIST) {
    guint n = gst_value_list_get_size (value);
    guint i;

    for (i = 0; i < n; i++) {
      const GValue *v = gst_value_list_get_value (value, i);

      if (!_append_extra_header (field_id, v, user_data))
        return FALSE;
    }
  } else {
    return _append_extra_header (field_id, value, user_data);
  }

873 874 875 876 877 878 879 880 881 882 883 884 885
  return TRUE;
}


static gboolean
gst_soup_http_src_add_extra_headers (GstSoupHTTPSrc * src)
{
  if (!src->extra_headers)
    return TRUE;

  return gst_structure_foreach (src->extra_headers, _append_extra_headers, src);
}

886 887 888 889 890 891 892 893 894 895 896 897 898 899 900
static gboolean
gst_soup_http_src_session_open (GstSoupHTTPSrc * src)
{
  if (src->session) {
    GST_DEBUG_OBJECT (src, "Session is already open");
    return TRUE;
  }

  if (!src->location) {
    GST_ELEMENT_ERROR (src, RESOURCE, OPEN_READ, (_("No URL set.")),
        ("Missing location property"));
    return FALSE;
  }

  if (!src->session) {
901 902 903
    GST_DEBUG_OBJECT (src, "Creating session");
    if (src->proxy == NULL) {
      src->session =
Sebastian Dröge's avatar
Sebastian Dröge committed
904 905
          soup_session_new_with_options (SOUP_SESSION_USER_AGENT,
          src->user_agent, SOUP_SESSION_TIMEOUT, src->timeout,
906
          SOUP_SESSION_SSL_STRICT, src->ssl_strict,
907
          SOUP_SESSION_TLS_INTERACTION, src->tls_interaction, NULL);
908 909
    } else {
      src->session =
Sebastian Dröge's avatar
Sebastian Dröge committed
910
          soup_session_new_with_options (SOUP_SESSION_PROXY_URI, src->proxy,
911
          SOUP_SESSION_TIMEOUT, src->timeout,
912
          SOUP_SESSION_SSL_STRICT, src->ssl_strict,
913 914
          SOUP_SESSION_USER_AGENT, src->user_agent,
          SOUP_SESSION_TLS_INTERACTION, src->tls_interaction, NULL);
915 916 917 918 919 920 921
    }

    if (!src->session) {
      GST_ELEMENT_ERROR (src, LIBRARY, INIT,
          (NULL), ("Failed to create async session"));
      return FALSE;
    }
922

923 924
    g_signal_connect (src->session, "authenticate",
        G_CALLBACK (gst_soup_http_src_authenticate_cb), src);
925

926 927
    /* Set up logging */
    gst_soup_util_log_setup (src->session, src->log_level, GST_ELEMENT (src));
928 929 930
    if (src->tls_database)
      g_object_set (src->session, "tls-database", src->tls_database, NULL);
    else if (src->ssl_ca_file)
931 932 933 934
      g_object_set (src->session, "ssl-ca-file", src->ssl_ca_file, NULL);
    else
      g_object_set (src->session, "ssl-use-system-ca-file",
          src->ssl_use_system_ca_file, NULL);
935 936 937
  } else {
    GST_DEBUG_OBJECT (src, "Re-using session");
  }
938

939 940
  if (src->compress)
    soup_session_add_feature_by_type (src->session, SOUP_TYPE_CONTENT_DECODER);
941 942 943
  else
    soup_session_remove_feature_by_type (src->session,
        SOUP_TYPE_CONTENT_DECODER);
944

945 946 947
  return TRUE;
}

948
static void
949
gst_soup_http_src_session_close (GstSoupHTTPSrc * src)
950
{
951 952
  GST_DEBUG_OBJECT (src, "Closing session");

953
  g_mutex_lock (&src->mutex);
954 955 956 957 958 959
  if (src->msg) {
    soup_session_cancel_message (src->session, src->msg, SOUP_STATUS_CANCELLED);
    g_object_unref (src->msg);
    src->msg = NULL;
  }

960
  if (src->session) {
961
    soup_session_abort (src->session);
962 963 964
    g_object_unref (src->session);
    src->session = NULL;
  }
965
  g_mutex_unlock (&src->mutex);
966 967
}

968 969 970 971 972 973 974
static void
gst_soup_http_src_authenticate_cb (SoupSession * session, SoupMessage * msg,
    SoupAuth * auth, gboolean retrying, GstSoupHTTPSrc * src)
{
  if (!retrying) {
    /* First time authentication only, if we fail and are called again with retry true fall through */
    if (msg->status_code == SOUP_STATUS_UNAUTHORIZED) {
975 976
      if (src->user_id && src->user_pw)
        soup_auth_authenticate (auth, src->user_id, src->user_pw);
977
    } else if (msg->status_code == SOUP_STATUS_PROXY_AUTHENTICATION_REQUIRED) {
978 979
      if (src->proxy_id && src->proxy_pw)
        soup_auth_authenticate (auth, src->proxy_id, src->proxy_pw);
980 981 982 983
    }
  }
}

984 985 986 987 988 989
static void
insert_http_header (const gchar * name, const gchar * value, gpointer user_data)
{
  GstStructure *headers = user_data;
  const GValue *gv;

990 991 992
  if (!g_utf8_validate (name, -1, NULL) || !g_utf8_validate (value, -1, NULL))
    return;

993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020
  gv = gst_structure_get_value (headers, name);
  if (gv && GST_VALUE_HOLDS_ARRAY (gv)) {
    GValue v = G_VALUE_INIT;

    g_value_init (&v, G_TYPE_STRING);
    g_value_set_string (&v, value);
    gst_value_array_append_value ((GValue *) gv, &v);
    g_value_unset (&v);
  } else if (gv && G_VALUE_HOLDS_STRING (gv)) {
    GValue arr = G_VALUE_INIT;
    GValue v = G_VALUE_INIT;
    const gchar *old_value = g_value_get_string (gv);

    g_value_init (&arr, GST_TYPE_ARRAY);
    g_value_init (&v, G_TYPE_STRING);
    g_value_set_string (&v, old_value);
    gst_value_array_append_value (&arr, &v);
    g_value_set_string (&v, value);
    gst_value_array_append_value (&arr, &v);

    gst_structure_set_value (headers, name, &arr);
    g_value_unset (&v);
    g_value_unset (&arr);
  } else {
    gst_structure_set (headers, name, G_TYPE_STRING, value, NULL);
  }
}

1021
static GstFlowReturn
Sebastian Dröge's avatar
Sebastian Dröge committed
1022
gst_soup_http_src_got_headers (GstSoupHTTPSrc * src, SoupMessage * msg)
1023 1024
{
  const char *value;
1025
  GstTagList *tag_list;
1026
  GstBaseSrc *basesrc;
1027
  guint64 newsize;
1028
  GHashTable *params = NULL;
1029 1030
  GstEvent *http_headers_event;
  GstStructure *http_headers, *headers;
1031
  const gchar *accept_ranges;