LibTLS+LibHTTP: Tolerate improperly closed TLS sockets

Some really cursed servers simply drop the TCP socket on the floor when
they're trying to close an HTTP connection going through a TLS socket.
This commit makes LibTLS tolerate these silly servers, and LibHTTP
accept their idea of "EOF == connection closed".

Fixes loading wpt.live/acid/acid3/test.html.

Note that this means TLSv12::on_ready_to_read can fire with an empty
buffer signifying EOF; one test refused this behaviour, and has been
changed in this commit.
This commit is contained in:
Ali Mohammad Pur 2024-04-16 17:48:31 +02:00 committed by Andreas Kling
parent 2d90317c20
commit 06386ab2b5
Notes: sideshowbarker 2024-07-19 16:49:21 +09:00
3 changed files with 32 additions and 8 deletions

View file

@ -61,11 +61,7 @@ TEST_CASE(test_TLS_hello_handshake)
auto tls = TRY_OR_FAIL(TLS::TLSv12::connect(DEFAULT_SERVER, port, move(options))); auto tls = TRY_OR_FAIL(TLS::TLSv12::connect(DEFAULT_SERVER, port, move(options)));
ByteBuffer contents; ByteBuffer contents;
tls->on_ready_to_read = [&] { tls->on_ready_to_read = [&] {
auto read_bytes = TRY_OR_FAIL(tls->read_some(contents.must_get_bytes_for_writing(4 * KiB))); (void)TRY_OR_FAIL(tls->read_some(contents.must_get_bytes_for_writing(4 * KiB)));
if (read_bytes.is_empty()) {
FAIL("No data received");
loop.quit(1);
}
loop.quit(0); loop.quit(0);
}; };

View file

@ -223,8 +223,20 @@ void Job::on_socket_connected()
} }
if (m_socket->is_eof()) { if (m_socket->is_eof()) {
dbgln_if(JOB_DEBUG, "Read failure: Actually EOF!"); // Some servers really like terminating connections by simply closing them (even TLS ones)
return deferred_invoke([this] { did_fail(Core::NetworkJob::Error::ProtocolFailed); }); // to signal end-of-data, if there's no:
// - connection
// - content-size
// - transfer-encoding: chunked
// header, simply treat EOF as a termination signal.
if (m_headers.contains("connection"sv) || m_content_length.has_value() || m_current_chunk_total_size.has_value()) {
dbgln_if(JOB_DEBUG, "Read failure: Actually EOF!");
deferred_invoke([this] { did_fail(Core::NetworkJob::Error::ProtocolFailed); });
return;
}
finish_up();
return;
} }
while (m_state == State::InStatus) { while (m_state == State::InStatus) {

View file

@ -174,6 +174,16 @@ ErrorOr<void> TLSv12::read_from_socket()
consume(read_bytes); consume(read_bytes);
} while (!read_bytes.is_empty() && !m_context.critical_error); } while (!read_bytes.is_empty() && !m_context.critical_error);
if (read_bytes.is_empty()) {
// read_some() returned an empty span, this is either an EOF (from improper closure)
// or some sort of weird even that is showing itself as an EOF.
// To guard against servers closing the connection weirdly or just improperly, make sure
// to check the connection state here and send the appropriate notifications.
stream.close();
check_connection_state(true);
}
return {}; return {};
} }
@ -204,6 +214,11 @@ bool TLSv12::check_connection_state(bool read)
m_context.connection_finished = true; m_context.connection_finished = true;
m_context.connection_status = ConnectionStatus::Disconnected; m_context.connection_status = ConnectionStatus::Disconnected;
close(); close();
m_context.has_invoked_finish_or_error_callback = true;
if (on_ready_to_read)
on_ready_to_read(); // Notify the client about the weird event.
if (on_tls_finished)
on_tls_finished();
return false; return false;
} }
@ -291,7 +306,8 @@ ErrorOr<bool> TLSv12::flush()
void TLSv12::close() void TLSv12::close()
{ {
alert(AlertLevel::FATAL, AlertDescription::CLOSE_NOTIFY); if (underlying_stream().is_open())
alert(AlertLevel::FATAL, AlertDescription::CLOSE_NOTIFY);
// bye bye. // bye bye.
m_context.connection_status = ConnectionStatus::Disconnected; m_context.connection_status = ConnectionStatus::Disconnected;
} }