|
|
|
@ -1,4 +1,5 @@
|
|
|
|
|
#include "webclient.hh"
|
|
|
|
|
#include "url_parser.hh"
|
|
|
|
|
|
|
|
|
|
#include <string>
|
|
|
|
|
#include <stdexcept>
|
|
|
|
@ -24,8 +25,29 @@ using tcp = net::ip::tcp; // from <boost/asio/ip/tcp.hpp>
|
|
|
|
|
namespace
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
std::string redirect(boost::string_view url_string, int redirection_level)
|
|
|
|
|
{
|
|
|
|
|
const pEp::URL url = pEp::parse_url(url_string.data(), url_string.data()+url_string.size());
|
|
|
|
|
|
|
|
|
|
if(!url.username.empty() || !url.password.empty())
|
|
|
|
|
{
|
|
|
|
|
throw pEp::HttpError(901, "Redirect to URL with username:password is not supported.");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const bool secure = (
|
|
|
|
|
url.schema=="https" ? true :
|
|
|
|
|
(url.schema=="http" ? false :
|
|
|
|
|
throw pEp::HttpError(902, "Unknown URL schema \"" + url.schema + "\"")
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
pEp::Webclient client{url.host, secure, url.port};
|
|
|
|
|
return client.get(url.path_and_query, redirection_level);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
template<class Stream>
|
|
|
|
|
std::string fetch_and_close(Stream& stream, const std::string& server, const std::string& url)
|
|
|
|
|
std::string fetch_and_close(Stream& stream, const std::string& server, const std::string& url, int redirection_level)
|
|
|
|
|
{
|
|
|
|
|
// Set up an HTTP GET request message
|
|
|
|
|
http::request<http::string_body> req{http::verb::get, url, 11};
|
|
|
|
@ -43,9 +65,27 @@ std::string fetch_and_close(Stream& stream, const std::string& server, const std
|
|
|
|
|
|
|
|
|
|
// Receive the HTTP response
|
|
|
|
|
http::read(stream, buffer, res);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return boost::beast::buffers_to_string(res.body().data());
|
|
|
|
|
switch(res.result())
|
|
|
|
|
{
|
|
|
|
|
case http::status::ok :
|
|
|
|
|
case http::status::no_content: return boost::beast::buffers_to_string(res.body().data());
|
|
|
|
|
|
|
|
|
|
case http::status::moved_permanently:
|
|
|
|
|
case http::status::found: // was "moved temporarily"
|
|
|
|
|
case http::status::see_other:
|
|
|
|
|
case http::status::temporary_redirect:
|
|
|
|
|
case http::status::permanent_redirect:
|
|
|
|
|
if(redirection_level < pEp::MaxRedirectionLevel)
|
|
|
|
|
{
|
|
|
|
|
return redirect(res["Location"], redirection_level + 1);
|
|
|
|
|
} else {
|
|
|
|
|
throw pEp::HttpError(900, "Too many redirections!");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
default: // HTTP error or unknown/not implemented response code.
|
|
|
|
|
throw pEp::HttpError(res.result_int(), boost::beast::buffers_to_string(res.body().data() ));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // end of anonymous namespace
|
|
|
|
@ -57,7 +97,7 @@ HttpError::HttpError(unsigned error_code, const std::string& error_message)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// based on https://www.boost.org/doc/libs/1_75_0/libs/beast/doc/html/beast/quick_start/http_client.html
|
|
|
|
|
std::string Webclient::get(const std::string& url)
|
|
|
|
|
std::string Webclient::get(const std::string& url, int redirection_level)
|
|
|
|
|
{
|
|
|
|
|
std::string ret;
|
|
|
|
|
|
|
|
|
@ -95,7 +135,7 @@ std::string Webclient::get(const std::string& url)
|
|
|
|
|
|
|
|
|
|
// Perform the SSL handshake
|
|
|
|
|
stream.handshake(ssl::stream_base::client);
|
|
|
|
|
ret = fetch_and_close(stream, m_server_name, url);
|
|
|
|
|
ret = fetch_and_close(stream, m_server_name, url, redirection_level);
|
|
|
|
|
|
|
|
|
|
// Gracefully close the socket
|
|
|
|
|
beast::error_code ec;
|
|
|
|
@ -113,7 +153,7 @@ std::string Webclient::get(const std::string& url)
|
|
|
|
|
|
|
|
|
|
// Make the connection on the IP address we get from a lookup
|
|
|
|
|
stream.connect(results);
|
|
|
|
|
ret = fetch_and_close(stream, m_server_name, url);
|
|
|
|
|
ret = fetch_and_close(stream, m_server_name, url, redirection_level);
|
|
|
|
|
|
|
|
|
|
// Gracefully close the socket
|
|
|
|
|
beast::error_code ec;
|
|
|
|
|