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