Browse Source

implement HTTP redirects. That's what the URL parser is necessary at all. :-)

master
roker 10 months ago
parent
commit
057362ec9d
2 changed files with 50 additions and 7 deletions
  1. +46
    -6
      webclient.cc
  2. +4
    -1
      webclient.hh

+ 46
- 6
webclient.cc View File

@ -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;


+ 4
- 1
webclient.hh View File

@ -5,6 +5,9 @@
namespace pEp
{
// to avoid infinite redirection loops
enum { MaxRedirectionLevel = 23 };
class HttpError : public std::runtime_error
{
public:
@ -21,7 +24,7 @@ namespace pEp
, m_port{port==0 ? (secure?443:80) : port}
{}
std::string get(const std::string& url);
std::string get(const std::string& url, int redirection_level = 0);
private:
const std::string& m_server_name;


Loading…
Cancel
Save