|
|
|
@ -26,11 +26,23 @@ |
|
|
|
#include "AuthStorage.hpp" |
|
|
|
#include "Transport.hpp" |
|
|
|
#include "../tools/Helper.h" |
|
|
|
#include "../AbstractSource.hpp" |
|
|
|
#include "../plumbing/SourceStream.hpp" |
|
|
|
|
|
|
|
#include <cstdio> |
|
|
|
#include <sstream> |
|
|
|
#include <algorithm> |
|
|
|
#include <vlc_stream.h> |
|
|
|
#include <vlc_tls.h> |
|
|
|
#include <vlc_keystore.h> |
|
|
|
|
|
|
|
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<LibVLCHTTPSource **>(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<LibVLCHTTPSource **>(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() |
|
|
|
{ |
|
|
|
|