From f5c3393339ebfafe35c4ff43fe59bcee71d93692 Mon Sep 17 00:00:00 2001 From: Francois Cartegnie Date: Wed, 1 Sep 2021 16:05:19 +0200 Subject: [PATCH] demux: adaptive: add libvlchttp based connection --- modules/demux/Makefile.am | 4 + modules/demux/adaptive/http/AuthStorage.cpp | 5 + modules/demux/adaptive/http/AuthStorage.hpp | 1 + .../demux/adaptive/http/HTTPConnection.cpp | 363 ++++++++++++++++++ .../demux/adaptive/http/HTTPConnection.hpp | 34 ++ 5 files changed, 407 insertions(+) diff --git a/modules/demux/Makefile.am b/modules/demux/Makefile.am index 419b0aa050..83ccd63b99 100644 --- a/modules/demux/Makefile.am +++ b/modules/demux/Makefile.am @@ -491,6 +491,10 @@ libvlc_adaptive_la_SOURCES += \ libvlc_adaptive_la_CXXFLAGS = $(AM_CXXFLAGS) -I$(srcdir)/demux/adaptive libvlc_adaptive_la_LIBADD = $(SOCKET_LIBS) $(LIBM) libvlc_adaptive_la_LDFLAGS = -static +if !HAVE_WIN32 +libvlc_adaptive_la_LDFLAGS += -lpthread +endif +libvlc_adaptive_la_LIBADD += libvlc_http.la if HAVE_ZLIB libvlc_adaptive_la_LIBADD += -lz endif diff --git a/modules/demux/adaptive/http/AuthStorage.cpp b/modules/demux/adaptive/http/AuthStorage.cpp index f5f658d692..0e4458b580 100644 --- a/modules/demux/adaptive/http/AuthStorage.cpp +++ b/modules/demux/adaptive/http/AuthStorage.cpp @@ -62,3 +62,8 @@ std::string AuthStorage::getCookie( const ConnectionParams ¶ms, bool secure } return ret; } + +vlc_http_cookie_jar_t *AuthStorage::getJar() const +{ + return p_cookies_jar; +} diff --git a/modules/demux/adaptive/http/AuthStorage.hpp b/modules/demux/adaptive/http/AuthStorage.hpp index 528c6e8b49..20c791dec0 100644 --- a/modules/demux/adaptive/http/AuthStorage.hpp +++ b/modules/demux/adaptive/http/AuthStorage.hpp @@ -38,6 +38,7 @@ namespace adaptive ~AuthStorage(); void addCookie( const std::string &cookie, const ConnectionParams & ); std::string getCookie( const ConnectionParams &, bool secure ); + vlc_http_cookie_jar_t *getJar() const; private: vlc_http_cookie_jar_t *p_cookies_jar; diff --git a/modules/demux/adaptive/http/HTTPConnection.cpp b/modules/demux/adaptive/http/HTTPConnection.cpp index f53f3d6e0c..0b0f0063f1 100644 --- a/modules/demux/adaptive/http/HTTPConnection.cpp +++ b/modules/demux/adaptive/http/HTTPConnection.cpp @@ -26,11 +26,23 @@ #include "AuthStorage.hpp" #include "Transport.hpp" #include "../tools/Helper.h" +#include "../AbstractSource.hpp" +#include "../plumbing/SourceStream.hpp" #include #include #include #include +#include +#include + +extern "C" +{ + #include "../access/http/resource.h" + #include "../access/http/connmgr.h" + #include "../access/http/conn.h" + #include "../access/http/message.h" +} using namespace adaptive::http; @@ -480,6 +492,342 @@ std::string HTTPConnection::extraRequestHeaders() const return ss.str(); } +class adaptive::http::LibVLCHTTPSource : public adaptive::AbstractSource +{ + friend class LibVLCHTTPConnection; + + public: + LibVLCHTTPSource(vlc_object_t *p_object, struct vlc_http_cookie_jar_t *jar) + { + http_mgr = vlc_http_mgr_create(p_object, jar); + http_res = nullptr; + totalRead = 0; + } + virtual ~LibVLCHTTPSource() + { + if(http_mgr) + vlc_http_mgr_destroy(http_mgr); + } + virtual block_t *readNextBlock() override + { + if(http_res == nullptr) + return nullptr; + block_t *b = vlc_http_res_read(http_res); + if(b == vlc_http_error) + return nullptr; + if(b) + totalRead += b->i_buffer; + return b; + } + void reset() + { + if(http_res) + { + vlc_http_res_destroy(http_res); + http_res = nullptr; + totalRead = 0; + } + } + + private: + struct restuple + { + struct vlc_http_resource resource; + LibVLCHTTPSource *source; + }; + + int formatRequest(const struct vlc_http_resource *, + struct vlc_http_msg *req) + { + vlc_http_msg_add_header(req, "Cache-Control", "no-cache"); + if(range.isValid()) + { + if(range.getEndByte() > 0) + { + if (vlc_http_msg_add_header(req, "Range", "bytes=%" PRIuMAX "-%" PRIuMAX, + range.getStartByte(), range.getEndByte())) + return -1; + } + else + { + if (vlc_http_msg_add_header(req, "Range", "bytes=%" PRIuMAX "-", + range.getStartByte())) + return -1; + } + } + return 0; + } + + int validateResponse(const struct vlc_http_resource *, const struct vlc_http_msg *resp) + { + if (vlc_http_msg_get_status(resp) == 206) + { + const char *str = vlc_http_msg_get_header(resp, "Content-Range"); + if (str == NULL) + /* A multipart/byteranges response. This is not what we asked for + * and we do not support it. */ + return -1; + + uintmax_t start, end; + if (sscanf(str, "bytes %" SCNuMAX "-%" SCNuMAX, &start, &end) != 2 + || start != range.getStartByte() || start > end || + (range.getEndByte() > range.getStartByte() && range.getEndByte() != end) ) + /* A single range response is what we asked for, but not at that + * start offset. */ + return -1; + } + return 0; + } + + static int formatrequest_handler(const struct vlc_http_resource *res, + struct vlc_http_msg *req, void *opaque) + { + return (*static_cast(opaque))->formatRequest(res, req); + } + + static int validateresponse_handler(const struct vlc_http_resource *res, + const struct vlc_http_msg *resp, void *opaque) + { + return (*static_cast(opaque))->validateResponse(res, resp); + } + + static const struct vlc_http_resource_cbs callbacks; + size_t totalRead; + struct vlc_http_mgr *http_mgr; + BytesRange range; + + public: + struct vlc_http_resource *http_res; + int create(const char *uri,const std::string &ua, + const std::string &ref, const BytesRange &range) + { + struct restuple *tpl = new struct restuple; + tpl->source = this; + this->range = range; + if (vlc_http_res_init(&tpl->resource, &this->callbacks, http_mgr, uri, + ua.empty() ? nullptr : ua.c_str(), + ref.empty() ? nullptr : ref.c_str())) + { + delete tpl; + return -1; + } + http_res = &tpl->resource; + return 0; + } + + int abortandlogin(const char *user, const char *pass) + { + if(http_res == nullptr) + return -1; + + free(http_res->username); + http_res->username = user ? strdup(user) : nullptr; + free(http_res->password); + http_res->password = pass ? strdup(pass) : nullptr; + + struct vlc_http_msg *resp = vlc_http_res_open(http_res, &http_res[1]); + if (resp == nullptr) + return -1; + + if (http_res->response != nullptr) + vlc_http_msg_destroy(http_res->response); + + http_res->response = resp; + return 0; + } +}; + +const struct vlc_http_resource_cbs LibVLCHTTPSource::callbacks = +{ + LibVLCHTTPSource::formatrequest_handler, + LibVLCHTTPSource::validateresponse_handler, +}; + +LibVLCHTTPConnection::LibVLCHTTPConnection(vlc_object_t *p_object_, AuthStorage *auth) + : AbstractConnection( p_object_ ) +{ + source = new adaptive::http::LibVLCHTTPSource(p_object_, auth->getJar()); + sourceStream = new ChunksSourceStream(p_object, source); + stream = nullptr; + char *psz_useragent = var_InheritString(p_object_, "http-user-agent"); + if(psz_useragent) + { + useragent = std::string(psz_useragent); + free(psz_useragent); + } + char *psz_referer = var_InheritString(p_object_, "http-referrer"); + if(psz_referer) + { + referer = std::string(psz_referer); + free(psz_referer); + } +} + +LibVLCHTTPConnection::~LibVLCHTTPConnection() +{ + reset(); + delete sourceStream; + delete source; +} + +void LibVLCHTTPConnection::reset() +{ + source->reset(); + sourceStream->Reset(); + if(stream) + { + vlc_stream_Delete(stream); + stream = nullptr; + } + bytesRange = BytesRange(); + contentType = std::string(); + bytesRead = 0; + contentLength = 0; +} + +bool LibVLCHTTPConnection::canReuse(const ConnectionParams ¶ms_) const +{ + if(!available) + return false; + return (params.getHostname() == params_.getHostname() && + params.getScheme() == params_.getScheme() && + params.getPort() == params_.getPort()); +} + +RequestStatus LibVLCHTTPConnection::request(const std::string &path, + const BytesRange &range) +{ + if(source->http_mgr == nullptr) + return RequestStatus::GenericError; + + reset(); + + /* Set new path for this query */ + params.setPath(path); + + if(range.isValid()) + msg_Dbg(p_object, "Retrieving %s @%zu-%zu", params.getUrl().c_str(), + range.getStartByte(), range.getEndByte()); + else + msg_Dbg(p_object, "Retrieving %s", params.getUrl().c_str()); + + if(source->create(params.getUrl().c_str(), useragent,referer, range)) + return RequestStatus::GenericError; + + struct vlc_credential crd; + struct vlc_url_t crd_url; + vlc_UrlParse(&crd_url, params.getUrl().c_str()); + + vlc_credential_init(&crd, &crd_url); + if (vlc_credential_get(&crd, p_object, NULL, NULL, NULL, NULL)) + { + vlc_http_res_set_login(source->http_res, + crd.psz_username, crd.psz_password); + } + + int status = vlc_http_res_get_status(source->http_res); + if (status < 0) + { + vlc_credential_clean(&crd); + vlc_UrlClean(&crd_url); + return RequestStatus::GenericError; + } + + char *psz_realm = nullptr; + if (status == 401) /* authentication */ + { + psz_realm = vlc_http_res_get_basic_realm(source->http_res); + if (psz_realm) + { + vlc_credential_init(&crd, &crd_url); + crd.psz_authtype = "Basic"; + crd.psz_realm = psz_realm; + if (vlc_credential_get(&crd, p_object, NULL, NULL, + _("HTTP authentication"), + _("Please enter a valid login name and a " + "password for realm %s."), psz_realm)) + { + if(source->abortandlogin(crd.psz_username, crd.psz_password)) + { + vlc_credential_clean(&crd); + vlc_UrlClean(&crd_url); + free(psz_realm); + return RequestStatus::Unauthorized; + } + status = vlc_http_res_get_status(source->http_res); + } + } + } + + if(status > 0 && status < 400 && crd.psz_realm && + crd.i_get_order > decltype(crd.i_get_order)::GET_FROM_MEMORY_KEYSTORE) + { + /* Force caching into memory keystore */ + crd.b_from_keystore = false; + crd.b_store = false; + vlc_credential_store(&crd, p_object); + } + + vlc_credential_clean(&crd); + vlc_UrlClean(&crd_url); + free(psz_realm); + + if (status >= 400) + return RequestStatus::GenericError; + + char *psz_redir = vlc_http_res_get_redirect(source->http_res); + if(psz_redir) + { + ConnectionParams loc = ConnectionParams(psz_redir); + free(psz_redir); + if(loc.getScheme().empty()) + { + locationparams = params; + locationparams.setPath(loc.getPath()); + } + else locationparams = loc; + return RequestStatus::Redirection; + } + + sourceStream->Reset(); + stream = sourceStream->makeStream(); + if(stream == nullptr) + return RequestStatus::GenericError; + + contentLength = vlc_http_msg_get_size(source->http_res->response); + + const char *s = vlc_http_msg_get_header(source->http_res->response, "Content-Type"); + if(s) + contentType = std::string(s); + + s = vlc_http_msg_get_header(source->http_res->response, "Content-Encoding"); + if(s && stream && (strstr(s, "deflate") || strstr(s, "gzip"))) + { + stream_t *decomp = vlc_stream_FilterNew(stream, "inflate"); + if(decomp) + { + stream = decomp; + contentLength = 0; + } + } + + return RequestStatus::Success; +} + +ssize_t LibVLCHTTPConnection::read(void *p_buffer, size_t len) +{ + ssize_t read = vlc_stream_Read(stream, p_buffer, len); + bytesRead = source->totalRead; + return read; +} + +void LibVLCHTTPConnection::setUsed( bool b ) +{ + available = !b; + if(available) + reset(); +} + StreamUrlConnection::StreamUrlConnection(vlc_object_t *p_object) : AbstractConnection(p_object) { @@ -640,6 +988,21 @@ AbstractConnection * NativeConnectionFactory::createConnection(vlc_object_t *p_o return conn; } +LibVLCHTTPConnectionFactory::LibVLCHTTPConnectionFactory( AuthStorage *auth ) + : AbstractConnectionFactory() +{ + authStorage = auth; +} + +AbstractConnection * LibVLCHTTPConnectionFactory::createConnection(vlc_object_t *p_object, + const ConnectionParams ¶ms) +{ + if((params.getScheme() != "http" && params.getScheme() != "https") || + params.getHostname().empty()) + return nullptr; + return new LibVLCHTTPConnection(p_object, authStorage); +} + StreamUrlConnectionFactory::StreamUrlConnectionFactory() : AbstractConnectionFactory() { diff --git a/modules/demux/adaptive/http/HTTPConnection.hpp b/modules/demux/adaptive/http/HTTPConnection.hpp index c246021eca..65e5a11b71 100644 --- a/modules/demux/adaptive/http/HTTPConnection.hpp +++ b/modules/demux/adaptive/http/HTTPConnection.hpp @@ -32,6 +32,8 @@ namespace adaptive { + class ChunksSourceStream; + namespace http { class Transport; @@ -114,6 +116,28 @@ namespace adaptive Transport *transport; }; + class LibVLCHTTPSource; + + class LibVLCHTTPConnection : public AbstractConnection + { + public: + LibVLCHTTPConnection(vlc_object_t *, AuthStorage *); + virtual ~LibVLCHTTPConnection(); + virtual bool canReuse (const ConnectionParams &) const override; + virtual RequestStatus request(const std::string& path, + const BytesRange & = BytesRange()) override; + virtual ssize_t read (void *p_buffer, size_t len) override; + virtual void setUsed ( bool ) override; + + private: + void reset(); + std::string useragent; + std::string referer; + LibVLCHTTPSource *source; + ChunksSourceStream *sourceStream; + stream_t *stream; + }; + class StreamUrlConnection : public AbstractConnection { public: @@ -151,6 +175,16 @@ namespace adaptive AuthStorage *authStorage; }; + class LibVLCHTTPConnectionFactory : public AbstractConnectionFactory + { + public: + LibVLCHTTPConnectionFactory( AuthStorage * ); + virtual ~LibVLCHTTPConnectionFactory() = default; + virtual AbstractConnection * createConnection(vlc_object_t *, const ConnectionParams &) override; + private: + AuthStorage *authStorage; + }; + class StreamUrlConnectionFactory : public AbstractConnectionFactory { public: