gstsouphttpsrc.c 64.2 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 178 179
static void gst_soup_http_src_session_close (GstSoupHTTPSrc * src);
static void gst_soup_http_src_parse_status (SoupMessage * msg,
    GstSoupHTTPSrc * src);
Sebastian Dröge's avatar
Sebastian Dröge committed
180 181
static void gst_soup_http_src_got_headers (GstSoupHTTPSrc * src,
    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;

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

457 458 459 460 461 462 463 464 465
  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;
}

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

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

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

504
  gst_soup_http_src_reset (src);
505 506
}

507 508 509 510 511 512 513 514 515 516 517 518
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);
}

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

524 525
  GST_DEBUG_OBJECT (src, "finalize");

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

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

546 547
  g_free (src->ssl_ca_file);

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

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

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

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

  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;
      }
575
      if (!gst_soup_http_src_set_location (src, location, NULL)) {
576 577 578 579 580
        GST_WARNING ("badly formatted location");
        goto done;
      }
      break;
    }
581
    case PROP_USER_AGENT:
582
      g_free (src->user_agent);
583 584
      src->user_agent = g_value_dup_string (value);
      break;
585 586 587
    case PROP_IRADIO_MODE:
      src->iradio_mode = g_value_get_boolean (value);
      break;
588 589 590 591 592 593 594 595
    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);
596
      if (!gst_soup_http_src_set_proxy (src, proxy)) {
597 598 599 600 601
        GST_WARNING ("badly formatted proxy URI");
        goto done;
      }
      break;
    }
602 603 604 605
    case PROP_COOKIES:
      g_strfreev (src->cookies);
      src->cookies = g_strdupv (g_value_get_boxed (value));
      break;
606 607 608
    case PROP_IS_LIVE:
      gst_base_src_set_live (GST_BASE_SRC (src), g_value_get_boolean (value));
      break;
609
    case PROP_USER_ID:
610
      g_free (src->user_id);
611 612 613
      src->user_id = g_value_dup_string (value);
      break;
    case PROP_USER_PW:
614
      g_free (src->user_pw);
615 616 617
      src->user_pw = g_value_dup_string (value);
      break;
    case PROP_PROXY_ID:
618
      g_free (src->proxy_id);
619 620 621
      src->proxy_id = g_value_dup_string (value);
      break;
    case PROP_PROXY_PW:
622
      g_free (src->proxy_pw);
623 624
      src->proxy_pw = g_value_dup_string (value);
      break;
625 626 627
    case PROP_TIMEOUT:
      src->timeout = g_value_get_uint (value);
      break;
628 629 630 631 632 633 634 635 636
    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;
    }
637 638 639
    case PROP_SOUP_LOG_LEVEL:
      src->log_level = g_value_get_enum (value);
      break;
640 641 642
    case PROP_COMPRESS:
      src->compress = g_value_get_boolean (value);
      break;
643 644 645
    case PROP_KEEP_ALIVE:
      src->keep_alive = g_value_get_boolean (value);
      break;
646 647 648 649
    case PROP_SSL_STRICT:
      src->ssl_strict = g_value_get_boolean (value);
      break;
    case PROP_SSL_CA_FILE:
650
      g_free (src->ssl_ca_file);
651 652 653 654 655
      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;
656 657 658 659
    case PROP_TLS_DATABASE:
      g_clear_object (&src->tls_database);
      src->tls_database = g_value_dup_object (value);
      break;
660 661 662 663
    case PROP_TLS_INTERACTION:
      g_clear_object (&src->tls_interaction);
      src->tls_interaction = g_value_dup_object (value);
      break;
664 665 666
    case PROP_RETRIES:
      src->max_retries = g_value_get_int (value);
      break;
667
    case PROP_METHOD:
668
      g_free (src->method);
669 670
      src->method = g_value_dup_string (value);
      break;
671 672 673
    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
674 675 676 677 678 679
  }
done:
  return;
}

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

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

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

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

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

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

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

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

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

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

819 820 821 822 823 824 825 826 827
  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);
    }
828 829 830
  }

  if (field_content == NULL) {
831 832
    GST_ERROR_OBJECT (src, "extra-headers field '%s' contains no value "
        "or can't be converted to a string", field_name);
833 834 835 836 837 838 839
    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);
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 873

  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);
  }

874 875 876 877 878 879 880 881 882 883 884 885 886
  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);
}

887 888 889 890 891 892 893 894 895 896 897 898 899 900 901
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) {
902 903 904
    GST_DEBUG_OBJECT (src, "Creating session");
    if (src->proxy == NULL) {
      src->session =
Sebastian Dröge's avatar
Sebastian Dröge committed
905 906
          soup_session_new_with_options (SOUP_SESSION_USER_AGENT,
          src->user_agent, SOUP_SESSION_TIMEOUT, src->timeout,
907
          SOUP_SESSION_SSL_STRICT, src->ssl_strict,
908
          SOUP_SESSION_TLS_INTERACTION, src->tls_interaction, NULL);
909 910
    } else {
      src->session =
Sebastian Dröge's avatar
Sebastian Dröge committed
911
          soup_session_new_with_options (SOUP_SESSION_PROXY_URI, src->proxy,
912
          SOUP_SESSION_TIMEOUT, src->timeout,
913
          SOUP_SESSION_SSL_STRICT, src->ssl_strict,
914 915
          SOUP_SESSION_USER_AGENT, src->user_agent,
          SOUP_SESSION_TLS_INTERACTION, src->tls_interaction, NULL);
916 917 918 919 920 921 922
    }

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

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

927 928
    /* Set up logging */
    gst_soup_util_log_setup (src->session, src->log_level, GST_ELEMENT (src));
929 930 931
    if (src->tls_database)
      g_object_set (src->session, "tls-database", src->tls_database, NULL);
    else if (src->ssl_ca_file)
932 933 934 935
      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);
936 937 938
  } else {
    GST_DEBUG_OBJECT (src, "Re-using session");
  }
939

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

946 947 948
  return TRUE;
}

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

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

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

969 970 971 972 973 974 975
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) {
976 977
      if (src->user_id && src->user_pw)
        soup_auth_authenticate (auth, src->user_id, src->user_pw);
978
    } else if (msg->status_code == SOUP_STATUS_PROXY_AUTHENTICATION_REQUIRED) {
979 980
      if (src->proxy_id && src->proxy_pw)
        soup_auth_authenticate (auth, src->proxy_id, src->proxy_pw);
981 982 983 984
    }
  }
}

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

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

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 1021
  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);
  }
}

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

1034