As I explained in the other answer, short read ("stream_truncaed") is to be expected when servers are implementing shutdown by simply closing the connection. The regular SSL shutdown is missing in such a case, prompting a "Short Read".
The issue I linked back there describes how to treat the error in the context of HTTP response parsing - and concludes that it is safe to ignore when the response has been parsed completely.
In your case, you DO NOT explicitly parse the response, so you can't really make an informed decision. This leaves you open to potential abuse where you can be made to accept malformed requests by an attacker.
1. Tired (workaround)
Uour naive workaround would be:
Live On Coliru
std::istream response_stream(&response_);
if (std::getline(response_stream >> http_version >> status_code, status_message)
&& http_version.substr(0, 5) == "HTTP/")
{
if (status_code != 200) {
std::clog << "Response returned with status code: " << status_code << "\n";
}
boost::system::error_code error;
boost::asio::read(socket, response_, boost::asio::transfer_all(), error);
if (error != boost::asio::error::eof &&
error != boost::asio::ssl::error::stream_truncated)
{
std::clog << "Error : " << error.message() << std::endl;
return 1;
}
else
{
//parse the original resposne
}
} else {
std::clog << "Invalid response\n" << std::endl;
}
Prints
Payload:
----
<!DOCTYPE html>
<html>
<title>Coliru Viewer</title>
<head>
....
....
</body>
</html>
----
2. Wired: correct implementation
However, I suggest you up your game and use Beast to parse the response. This has the benefit that you can trust the response. It parses the expected protocol, rather than blindly waiting for EOF
(which might never come?) and scratching your head why that results in short reads.
Since we now stop reading once the response is complete, we don't encounter EOF nor the Short Read.
Note: this method also makes a lot of other things less error-prone and way less insecure. Things like encoding header values (which you don't), proper use of CRLF (which was an error in your previous question), correct calculation of content length etc.
On the response parsing you - randomly - check that HTTP version starts with "HTTP/". However, using Beast you will actually check input sanity and change behaviour accordingly. This means that if your server does something you didn't expect (like, you know, actually using HTTP/1.1 features like chunked encoding) you will still read the response, as if you totally knew about that.
Conversely, if someone attacks your server by sending invalid responses, you have a better chance of not crashing or worse.
Live On Coliru
#include <boost/asio.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/core/flat_buffer.hpp>
#include <boost/asio/ssl.hpp>
#include <iostream>
#include <iomanip>
namespace http = boost::beast::http;
using boost::asio::ip::tcp;
// https://postman-echo.com/post see https://docs.postman-echo.com/?version=latest
static std::string
verb = "POST",
server_endpoint = "/post",
hostname = "postman-echo.com",
port_no = "443",
authorization_token = "c3RhdGljIGNvbnN0IHN0ZDo6c3RyaW5nIGF1dGhvcml6YXRpb"
"25fdG9rZW4gPSAiQXV0aDogIj"
"sK",
client_name = "demo program 0.01",
req_str = R"(name=blabla&password=bloblo)";
int main() {
boost::asio::io_service io_service;
boost::asio::ssl::context ctx(boost::asio::ssl::context::sslv23);
ctx.set_verify_mode(boost::asio::ssl::verify_peer);
//ctx.load_verify_file("ca.pem");
ctx.add_verify_path("/etc/ssl/certs");
ctx.set_options(boost::asio::ssl::context::default_workarounds |
boost::asio::ssl::context::no_sslv2 |
boost::asio::ssl::context::no_sslv3);
boost::asio::ssl::stream<boost::asio::ip::tcp::socket>
socket(io_service, ctx);
{
#ifdef COLIRU
verb = "GET";
server_endpoint = "/a/cf2748285fa3343a";
hostname = "coliru.stacked-crooked.com";
socket.set_verify_mode(boost::asio::ssl::verify_none);
socket.lowest_layer().connect({ boost::asio::ip::address::from_string("173.203.57.63"), 443 });
#else
tcp::resolver resolver(io_service);
tcp::resolver::query query(hostname, port_no);
tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
tcp::resolver::iterator end;
boost::system::error_code error = boost::asio::error::host_not_found;
boost::asio::connect(socket.lowest_layer(), endpoint_iterator, error);
#endif
}
{
socket.handshake(boost::asio::ssl::stream_base::client);
using http::field;
http::request<http::string_body> request;
request.method_string(::verb);
request.target(server_endpoint);
request.version(11);
request.set(field::host, hostname);
request.set(field::accept, "*/*");
request.set(field::authorization, authorization_token);
request.set(field::user_agent, client_name);
request.set(field::content_type, "application/x-www-form-urlencoded");
request.set(field::connection, field::close);
request.body() = req_str;
request.prepare_payload();
write(socket, request);
// std::clog << request << "\n"; return 1;
}
{
boost::system::error_code ec;
using http::field;
http::response<http::string_body> response_;
boost::beast::flat_buffer buf;
read(socket, buf, response_, ec);
{
//std::clog << "Response: " << response_ << std::endl;
}
// Check that response is OK.
if (!ec && response_.version() == 11) {
if (response_.result() != http::status::ok) {
std::clog << "Response returned with status code: " << response_.result_int() << "\n";
}
std::clog << "Payload:\n----\n" << response_.body() << "\n----" << std::endl;
} else {
std::clog << "Error: " << ec.message() << "\n";
}
}
}
Summary
Lessons learned:
- Don't underestimate the complexity of HTTP or SSL
- Don't reinvent the wheel - use libraries to your advantage!
Happy learning!