Fork for poking around in the original "client implementation for p≡p update server".
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
downloadclient/HTTPSStream.cc

194 lines
5.4 KiB

// this file is under GNU General Public License 3.0
// see LICENSE.txt
#include "HTTPSStream.hh"
#include <boost/regex.hpp>
#include <boost/beast/core.hpp>
#include <boost/beast/core/file_base.hpp>
#include <cstdio>
namespace pEp {
namespace http = boost::beast::http;
using file_mode = boost::beast::file_mode;
url_split split_url(string url)
{
static auto url_pattern =
"^(?<protocol>\\w+)://"
"((?<login>\\S+)(:(?<password>\\S+))?@)?"
"(?<host>[\\w.-]+)"
"(:(?<port>\\d+))?"
"(?<path>[^#\\s]+)?"
"(?<hash>#\\w+)?$";
url_split result;
static boost::regex e(url_pattern);
boost::match_results<string::const_iterator> what;
if (boost::regex_match(url, what, e)) {
result.protocol = what["protocol"];
result.login = what["login"];
result.password = what["password"];
result.host = what["host"];
result.port = what["port"];
result.path = what["path"];
result.hash = what["hash"];
}
else {
throw runtime_error("regex does not match");
}
return result;
}
HTTPSDevice::HTTPSDevice(string url, string user_agent, int version)
: _url(url), _user_agent(user_agent), _version(version),
_stream(nullptr), notifyRead(nullptr)
{
if (version != 11)
throw logic_error("only HTTP version 1.1 implemented");
_parser.body_limit(1000000000);
}
HTTPSDevice::HTTPSDevice(const HTTPSDevice& second)
: _url(second._url), _user_agent(second._user_agent),
_version(second._version), _stream(nullptr),
notifyRead(second.notifyRead)
{
_parser.body_limit(1000000000);
}
HTTPSDevice::~HTTPSDevice()
{
close();
}
void HTTPSDevice::open(string url, notifyRead_t notifyRead)
{
if (url == "") {
if (_url != "")
url = _url;
else
throw invalid_argument("cannot open without URL");
}
else {
_url = url;
}
if (_stream)
throw logic_error("stream already open");
url_split u = split_url(url);
if (u.protocol != "https")
throw invalid_argument("only https supported");
if (u.login != "")
throw invalid_argument("login data unsupported");
if (u.hash != "")
throw invalid_argument("hash unsupported");
if (u.port =="")
u.port = "443";
_host = u.host;
boost::system::error_code ec;
tcp::resolver resolver{_ioc};
ssl::context ctx{ssl::context::sslv23_client};
_stream = new ssl::stream<tcp::socket>{_ioc, ctx};
if (!SSL_set_tlsext_host_name(_stream->native_handle(), _host.c_str()))
{
delete _stream;
_stream = nullptr;
ec = {
static_cast<int>(::ERR_get_error()),
boost::asio::error::get_ssl_category()
};
throw boost::system::system_error{ec};
}
try {
auto const results = resolver.resolve(u.host, u.port);
boost::asio::connect(_stream->next_layer(), results.begin(),
results.end());
_stream->handshake(ssl::stream_base::client);
}
catch (std::exception&) {
_stream->next_layer().shutdown(tcp::socket::shutdown_both, ec);
delete _stream;
_stream = nullptr;
throw;
}
if (u.path != "")
get(u.path, notifyRead);
}
void HTTPSDevice::get(string path, notifyRead_t notifyRead)
{
if (path == "")
throw invalid_argument("path needed for GET");
if (!_stream)
open();
_temp.close();
remove(temp_file_path());
_filename = "";
http::request<http::string_body> req{http::verb::get, path, _version};
if (_host != "")
req.set(http::field::host, _host);
req.set(http::field::user_agent, _user_agent);
http::write(*_stream, req);
boost::system::error_code ec;
_parser.get().body().open(temp_file_path(), file_mode::write, ec);
boost::beast::flat_buffer b;
http::read_header(*_stream, b, _parser);
if (header()["Content-Length"] != "0" && notifyRead)
notifyRead();
http::read(*_stream, b, _parser);
auto cl = header()["Content-Disposition"];
static auto filename_pattern =
"attachment;\\s*filename=\"(?<filename>([^\"\\/:.]+\\.?)+)\"";
static boost::regex e(filename_pattern);
boost::match_results<string::const_iterator> what;
if (cl.size() && boost::regex_match(string(cl.data(), cl.size()), what, e))
_filename = what["filename"];
_parser.get().body().close();
_temp.open(temp_file_path());
}
void HTTPSDevice::close()
{
if (!_stream)
return;
_temp.close();
remove(temp_file_path());
boost::system::error_code ec;
_stream->next_layer().shutdown(tcp::socket::shutdown_both, ec);
delete _stream;
_stream = nullptr;
}
streamsize HTTPSDevice::read(char* s, streamsize n)
{
if (_temp.eof())
return -1;
_temp.read(s, n);
return _temp.gcount();
}
}