/* * HTTPConnection.cpp ***************************************************************************** * Copyright (C) 2014-2015 - VideoLAN and VLC Authors * * This program is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. *****************************************************************************/ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include "HTTPConnection.hpp" #include "ConnectionParams.hpp" #include "AuthStorage.hpp" #include "Transport.hpp" #include "../tools/Helper.h" #include #include #include #include using namespace adaptive::http; AbstractConnection::AbstractConnection(vlc_object_t *p_object_) { p_object = p_object_; available = true; bytesRead = 0; contentLength = 0; } AbstractConnection::~AbstractConnection() { } bool AbstractConnection::prepare(const ConnectionParams ¶ms_) { if (!available) return false; params = params_; available = false; return true; } size_t AbstractConnection::getContentLength() const { return contentLength; } const std::string & AbstractConnection::getContentType() const { return contentType; } HTTPConnection::HTTPConnection(vlc_object_t *p_object_, AuthStorage *auth, Transport *socket_, const ConnectionParams &proxy) : AbstractConnection( p_object_ ) { transport = socket_; char *psz_useragent = var_InheritString(p_object_, "http-user-agent"); useragent = psz_useragent ? std::string(psz_useragent) : std::string(""); free(psz_useragent); char *psz_referer = var_InheritString(p_object_, "http-referrer"); referer = psz_referer ? std::string(psz_referer) : std::string(""); free(psz_referer); std::replace_if(useragent.begin(), useragent.end(), [](const char &c){return !::isprint(c);}, ' '); std::replace_if(referer.begin(), referer.end(), [](const char &c){return !::isprint(c);}, ' '); queryOk = false; retries = 0; authStorage = auth; connectionClose = false; chunked = false; chunked_eof = false; chunkLength = 0; proxyparams = proxy; } HTTPConnection::~HTTPConnection() { delete transport; } bool HTTPConnection::canReuse(const ConnectionParams ¶ms_) const { if( !available || params_.usesAccess() ) return false; char *psz_proxy_url = vlc_getProxyUrl(params_.getUrl().c_str()); if(psz_proxy_url) { ConnectionParams proxy(psz_proxy_url); free(psz_proxy_url); return (proxyparams.getHostname() == proxy.getHostname() && proxyparams.getScheme() == proxy.getScheme() && proxyparams.getPort() == proxy.getPort()); } else return (params.getHostname() == params_.getHostname() && params.getScheme() == params_.getScheme() && params.getPort() == params_.getPort()); } bool HTTPConnection::connect() { if(proxyparams.getHostname().empty()) return transport->connect(p_object, params.getHostname().c_str(), params.getPort()); else return transport->connect(p_object, proxyparams.getHostname().c_str(), proxyparams.getPort()); } bool HTTPConnection::connected() const { return transport->connected(); } void HTTPConnection::disconnect() { queryOk = false; bytesRead = 0; contentLength = 0; connectionClose = false; chunked = false; chunkLength = 0; bytesRange = BytesRange(); contentType = std::string(); transport->disconnect(); } RequestStatus HTTPConnection::request(const std::string &path, const BytesRange &range) { queryOk = false; chunked = false; chunked_eof = false; chunkLength = 0; /* Set new path for this query */ params.setPath(path); locationparams = ConnectionParams(); msg_Dbg(p_object, "Retrieving %s @%zu", params.getUrl().c_str(), range.isValid() ? range.getStartByte() : 0); std::string querypath; if(!proxyparams.getHostname().empty()) { msg_Dbg(p_object, "Using proxy %s", proxyparams.getUrl().c_str()); querypath = params.getUrl(); } else querypath = path; if(!connected() && ( params.getHostname().empty() || !connect() )) return RequestStatus::GenericError; bytesRange = range; if(range.isValid() && range.getEndByte() > 0) contentLength = range.getEndByte() - range.getStartByte() + 1; std::string header = buildRequestHeader(querypath); if(connectionClose) header.append("Connection: close\r\n"); header.append("\r\n"); if(!send( header )) { transport->disconnect(); if(!connectionClose) { /* server closed connection pipeline after last req. need new */ connectionClose = true; return request(path, range); } return RequestStatus::GenericError; } RequestStatus status = parseReply(); if(status == RequestStatus::Success) { queryOk = true; } else if(status == RequestStatus::Redirection) { transport->disconnect(); } else if(status == RequestStatus::GenericError) { transport->disconnect(); if(!connectionClose) { connectionClose = true; return request(path, range); } } return status; } ssize_t HTTPConnection::read(void *p_buffer, size_t len) { if( !connected() || (!queryOk && bytesRead == 0) ) return VLC_EGENERIC; if(len == 0) return VLC_SUCCESS; queryOk = false; const size_t toRead = (contentLength) ? contentLength - bytesRead : len; if (toRead == 0) return VLC_SUCCESS; if(len > toRead) len = toRead; ssize_t ret = ( chunked ) ? readChunk(p_buffer, len) : transport->read(p_buffer, len); if(ret >= 0) bytesRead += ret; if(ret < 0 || (size_t)ret < len || /* set EOF */ (contentLength == bytesRead && connectionClose)) { transport->disconnect(); return ret; } return ret; } bool HTTPConnection::send(const std::string &data) { return send(data.c_str(), data.length()); } bool HTTPConnection::send(const void *buf, size_t size) { return transport->send(buf, size); } RequestStatus HTTPConnection::parseReply() { std::string statusline = readLine(); if(statusline.empty()) return RequestStatus::GenericError; if (statusline.compare(0, 9, "HTTP/1.1 ")!=0) { if(statusline.compare(0, 9, "HTTP/1.0 ")!=0) return RequestStatus::NotFound; else connectionClose = true; } std::istringstream ss(statusline.substr(9)); ss.imbue(std::locale("C")); int replycode; ss >> replycode; std::string lines; for( ;; ) { std::string l = readLine(); if(l.empty()) break; lines.append(l); size_t split = lines.find_first_of(':'); if(split != std::string::npos) { size_t value = lines.find_first_not_of(' ', split + 1); if(value == std::string::npos) value = lines.length(); onHeader(lines.substr(0, split), lines.substr(value)); lines = std::string(); } } if((replycode == 301 || replycode == 302 || replycode == 307 || replycode == 308) && !locationparams.getUrl().empty()) { msg_Info(p_object, "%d redirection to %s", replycode, locationparams.getUrl().c_str()); if(locationparams.isLocal() && !params.isLocal()) { msg_Err(p_object, "redirection to local rejected"); return RequestStatus::GenericError; } return RequestStatus::Redirection; } else if (replycode != 200 && replycode != 206) { msg_Err(p_object, "Failed reading %s: %s", params.getUrl().c_str(), statusline.c_str()); return RequestStatus::NotFound; } return RequestStatus::Success; } ssize_t HTTPConnection::readChunk(void *p_buffer, size_t len) { size_t copied = 0; for( ; copied < len && !chunked_eof; ) { /* adapted from access/http/chunked.c */ if(chunkLength == 0) { std::string line = readLine(); int end; if (std::sscanf(line.c_str(), "%zx%n", &chunkLength, &end) < 1 || (line[end] != '\0' && line[end] != ';' /* ignore extension(s) */)) return -1; } if(chunkLength > 0) { size_t toread = len - copied; if(toread > chunkLength) toread = chunkLength; ssize_t in = transport->read(&((uint8_t*)p_buffer)[copied], toread); if(in < 0) { return (copied == 0) ? in : copied; } else if((size_t)in < toread) { return copied + in; } copied += in; chunkLength -= in; } else chunked_eof = true; if(chunkLength == 0) { char crlf[2]; ssize_t in = transport->read(&crlf, 2); if(in < 2 || memcmp(crlf, "\r\n", 2)) return (copied == 0) ? -1 : copied; } } return copied; } std::string HTTPConnection::readLine() { return transport->readline(); } void HTTPConnection::setUsed( bool b ) { available = !b; if(available) { if(!connectionClose && contentLength == bytesRead && (!chunked || chunked_eof)) { queryOk = false; bytesRead = 0; contentLength = 0; bytesRange = BytesRange(); } else /* We can't resend request if we haven't finished reading */ disconnect(); } } void HTTPConnection::onHeader(const std::string &key, const std::string &value) { if(Helper::icaseEquals(key, "Content-Length")) { std::istringstream ss(value); ss.imbue(std::locale("C")); size_t length; ss >> length; contentLength = length; } else if (Helper::icaseEquals(key, "Connection") && Helper::icaseEquals(value, "close")) { connectionClose = true; } else if (Helper::icaseEquals(key, "Transfer-Encoding") && Helper::icaseEquals(value, "chunked")) { chunked = true; } else if(Helper::icaseEquals(key, "Content-Type")) { contentType = value; } else if(Helper::icaseEquals(key, "Location")) { locationparams = ConnectionParams(); ConnectionParams loc = ConnectionParams( value ); if(loc.getScheme().empty()) { locationparams = params; locationparams.setPath(loc.getPath()); } else locationparams = loc; } else if(Helper::icaseEquals(key, "Set-Cookie") && authStorage) { authStorage->addCookie( value, params ); } } std::string HTTPConnection::buildRequestHeader(const std::string &path) const { std::stringstream req; req.imbue(std::locale("C")); req << "GET " << path << " HTTP/1.1\r\n"; if((params.getScheme() == "http" && params.getPort() != 80) || (params.getScheme() == "https" && params.getPort() != 443)) { req << "Host: " << params.getHostname() << ":" << params.getPort() << "\r\n"; } else { req << "Host: " << params.getHostname() << "\r\n"; } if(authStorage) { std::string cookie = authStorage->getCookie(params, params.getScheme() == "https" || params.getPort() == 443); if(!cookie.empty()) req << "Cookie: " << cookie << "\r\n"; } req << "Cache-Control: no-cache" << "\r\n"; if(!useragent.empty()) req << "User-Agent: " << useragent << "\r\n"; if(!referer.empty()) req << "Referer: " << referer << "\r\n"; req << extraRequestHeaders(); return req.str(); } std::string HTTPConnection::extraRequestHeaders() const { std::stringstream ss; ss.imbue(std::locale("C")); if(bytesRange.isValid()) { ss << "Range: bytes=" << bytesRange.getStartByte() << "-"; if(bytesRange.getEndByte()) ss << bytesRange.getEndByte(); ss << "\r\n"; } return ss.str(); } const ConnectionParams & HTTPConnection::getRedirection() const { return locationparams; } StreamUrlConnection::StreamUrlConnection(vlc_object_t *p_object) : AbstractConnection(p_object) { p_streamurl = nullptr; bytesRead = 0; contentLength = 0; } StreamUrlConnection::~StreamUrlConnection() { reset(); } void StreamUrlConnection::reset() { if(p_streamurl) vlc_stream_Delete(p_streamurl); p_streamurl = nullptr; bytesRead = 0; contentLength = 0; contentType = std::string(); bytesRange = BytesRange(); } bool StreamUrlConnection::canReuse(const ConnectionParams ¶ms_) const { if( !available || !params_.usesAccess() ) return false; return (params.getHostname() == params_.getHostname() && params.getScheme() == params_.getScheme() && params.getPort() == params_.getPort()); } RequestStatus StreamUrlConnection::request(const std::string &path, const BytesRange &range) { reset(); /* Set new path for this query */ params.setPath(path); msg_Dbg(p_object, "Retrieving %s @%zu", params.getUrl().c_str(), range.isValid() ? range.getStartByte() : 0); p_streamurl = vlc_stream_NewURL(p_object, params.getUrl().c_str()); if(!p_streamurl) return RequestStatus::GenericError; char *psz_type = stream_ContentType(p_streamurl); if(psz_type) { contentType = std::string(psz_type); free(psz_type); } stream_t *p_chain = vlc_stream_FilterNew( p_streamurl, "inflate" ); if( p_chain ) p_streamurl = p_chain; if(range.isValid() && range.getEndByte() > 0) { if(vlc_stream_Seek(p_streamurl, range.getStartByte()) != VLC_SUCCESS) { vlc_stream_Delete(p_streamurl); return RequestStatus::GenericError; } bytesRange = range; contentLength = range.getEndByte() - range.getStartByte() + 1; } int64_t i_size = stream_Size(p_streamurl); if(i_size > -1) { if(!range.isValid() || contentLength > (size_t) i_size) contentLength = (size_t) i_size; } return RequestStatus::Success; } ssize_t StreamUrlConnection::read(void *p_buffer, size_t len) { if( !p_streamurl ) return VLC_EGENERIC; if(len == 0) return VLC_SUCCESS; const size_t toRead = (contentLength) ? contentLength - bytesRead : len; if (toRead == 0) return VLC_SUCCESS; if(len > toRead) len = toRead; ssize_t ret = vlc_stream_Read(p_streamurl, p_buffer, len); if(ret >= 0) bytesRead += ret; if(ret < 0 || (size_t)ret < len || /* set EOF */ contentLength == bytesRead ) { reset(); return ret; } return ret; } void StreamUrlConnection::setUsed( bool b ) { available = !b; if(available && contentLength == bytesRead) reset(); } NativeConnectionFactory::NativeConnectionFactory( AuthStorage *auth ) : AbstractConnectionFactory() { authStorage = auth; } NativeConnectionFactory::~NativeConnectionFactory() { } AbstractConnection * NativeConnectionFactory::createConnection(vlc_object_t *p_object, const ConnectionParams ¶ms) { if(params.usesAccess() || (params.getScheme() != "http" && params.getScheme() != "https") || params.getHostname().empty()) return nullptr; ConnectionParams proxy; std::string scheme; char *psz_proxy_url = vlc_getProxyUrl(params.getUrl().c_str()); if(psz_proxy_url) { proxy = ConnectionParams(psz_proxy_url); free(psz_proxy_url); scheme = proxy.getScheme(); } else scheme = params.getScheme(); Transport *socket = new (std::nothrow) Transport(params.getScheme() == "https"); if(!socket) return nullptr; HTTPConnection *conn = new (std::nothrow) HTTPConnection(p_object, authStorage, socket, proxy); if(!conn) { delete socket; return nullptr; } return conn; } StreamUrlConnectionFactory::StreamUrlConnectionFactory() : AbstractConnectionFactory() { } AbstractConnection * StreamUrlConnectionFactory::createConnection(vlc_object_t *p_object, const ConnectionParams &) { return new (std::nothrow) StreamUrlConnection(p_object); }