diff --git a/Applications/Browser/Tab.h b/Applications/Browser/Tab.h index 5ba5c212fa..78bd04328e 100644 --- a/Applications/Browser/Tab.h +++ b/Applications/Browser/Tab.h @@ -30,6 +30,7 @@ #include #include #include +#include namespace Browser { diff --git a/Libraries/LibCore/Forward.h b/Libraries/LibCore/Forward.h index 9d15634050..8725e91d5c 100644 --- a/Libraries/LibCore/Forward.h +++ b/Libraries/LibCore/Forward.h @@ -36,9 +36,6 @@ class ElapsedTime; class Event; class EventLoop; class File; -class HttpJob; -class HttpRequest; -class HttpResponse; class IODevice; class LocalServer; class LocalSocket; diff --git a/Libraries/LibCore/Makefile b/Libraries/LibCore/Makefile index 90f00d7488..763760e97c 100644 --- a/Libraries/LibCore/Makefile +++ b/Libraries/LibCore/Makefile @@ -9,9 +9,6 @@ OBJS = \ EventLoop.o \ File.o \ Gzip.o \ - HttpJob.o \ - HttpRequest.o \ - HttpResponse.o \ IODevice.o \ LocalServer.o \ LocalSocket.o \ diff --git a/Libraries/LibGUI/ToolBarContainer.h b/Libraries/LibGUI/ToolBarContainer.h index 40ffddc4eb..fb8a432a0c 100644 --- a/Libraries/LibGUI/ToolBarContainer.h +++ b/Libraries/LibGUI/ToolBarContainer.h @@ -28,6 +28,7 @@ #include #include +#include namespace GUI { diff --git a/Libraries/LibHTTP/Forward.h b/Libraries/LibHTTP/Forward.h new file mode 100644 index 0000000000..91ad283579 --- /dev/null +++ b/Libraries/LibHTTP/Forward.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2020, The SerenityOS developers. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +namespace HTTP { + +class HttpRequest; +class HttpResponse; +class HttpJob; +class HttpsJob; + +} diff --git a/Libraries/LibCore/HttpJob.cpp b/Libraries/LibHTTP/HttpJob.cpp similarity index 89% rename from Libraries/LibCore/HttpJob.cpp rename to Libraries/LibHTTP/HttpJob.cpp index 579ae6e58f..ae1165756b 100644 --- a/Libraries/LibCore/HttpJob.cpp +++ b/Libraries/LibHTTP/HttpJob.cpp @@ -25,15 +25,15 @@ */ #include -#include -#include #include +#include +#include #include #include -//#define CHTTPJOB_DEBUG +//#define HTTPJOB_DEBUG -namespace Core { +namespace HTTP { static ByteBuffer handle_content_encoding(const ByteBuffer& buf, const String& content_encoding) { @@ -42,7 +42,7 @@ static ByteBuffer handle_content_encoding(const ByteBuffer& buf, const String& c #endif if (content_encoding == "gzip") { - if (!Gzip::is_compressed(buf)) { + if (!Core::Gzip::is_compressed(buf)) { dbg() << "HttpJob::handle_content_encoding: buf is not gzip compressed!"; } @@ -50,7 +50,7 @@ static ByteBuffer handle_content_encoding(const ByteBuffer& buf, const String& c dbg() << "HttpJob::handle_content_encoding: buf is gzip compressed!"; #endif - auto uncompressed = Gzip::decompress(buf); + auto uncompressed = Core::Gzip::decompress(buf); if (!uncompressed.has_value()) { dbg() << "HttpJob::handle_content_encoding: Gzip::decompress() failed. Returning original buffer."; return buf; @@ -87,7 +87,7 @@ void HttpJob::on_socket_connected() bool success = m_socket->send(raw_request); if (!success) - return deferred_invoke([this](auto&) { did_fail(NetworkJob::Error::TransmissionFailed); }); + return deferred_invoke([this](auto&) { did_fail(Core::NetworkJob::Error::TransmissionFailed); }); m_socket->on_ready_to_read = [&] { if (is_cancelled()) @@ -98,18 +98,18 @@ void HttpJob::on_socket_connected() auto line = m_socket->read_line(PAGE_SIZE); if (line.is_null()) { fprintf(stderr, "HttpJob: Expected HTTP status\n"); - return deferred_invoke([this](auto&) { did_fail(NetworkJob::Error::TransmissionFailed); }); + return deferred_invoke([this](auto&) { did_fail(Core::NetworkJob::Error::TransmissionFailed); }); } auto parts = String::copy(line, Chomp).split(' '); if (parts.size() < 3) { fprintf(stderr, "HttpJob: Expected 3-part HTTP status, got '%s'\n", line.data()); - return deferred_invoke([this](auto&) { did_fail(NetworkJob::Error::ProtocolFailed); }); + return deferred_invoke([this](auto&) { did_fail(Core::NetworkJob::Error::ProtocolFailed); }); } bool ok; m_code = parts[1].to_uint(ok); if (!ok) { fprintf(stderr, "HttpJob: Expected numeric HTTP status\n"); - return deferred_invoke([this](auto&) { did_fail(NetworkJob::Error::ProtocolFailed); }); + return deferred_invoke([this](auto&) { did_fail(Core::NetworkJob::Error::ProtocolFailed); }); } m_state = State::InHeaders; return; @@ -120,7 +120,7 @@ void HttpJob::on_socket_connected() auto line = m_socket->read_line(PAGE_SIZE); if (line.is_null()) { fprintf(stderr, "HttpJob: Expected HTTP header\n"); - return did_fail(NetworkJob::Error::ProtocolFailed); + return did_fail(Core::NetworkJob::Error::ProtocolFailed); } auto chomped_line = String::copy(line, Chomp); if (chomped_line.is_empty()) { @@ -130,12 +130,12 @@ void HttpJob::on_socket_connected() auto parts = chomped_line.split(':'); if (parts.is_empty()) { fprintf(stderr, "HttpJob: Expected HTTP header with key/value\n"); - return deferred_invoke([this](auto&) { did_fail(NetworkJob::Error::ProtocolFailed); }); + return deferred_invoke([this](auto&) { did_fail(Core::NetworkJob::Error::ProtocolFailed); }); } auto name = parts[0]; if (chomped_line.length() < name.length() + 2) { fprintf(stderr, "HttpJob: Malformed HTTP header: '%s' (%zu)\n", chomped_line.characters(), chomped_line.length()); - return deferred_invoke([this](auto&) { did_fail(NetworkJob::Error::ProtocolFailed); }); + return deferred_invoke([this](auto&) { did_fail(Core::NetworkJob::Error::ProtocolFailed); }); } auto value = chomped_line.substring(name.length() + 2, chomped_line.length() - name.length() - 2); m_headers.set(name, value); @@ -150,7 +150,7 @@ void HttpJob::on_socket_connected() if (!payload) { if (m_socket->eof()) return finish_up(); - return deferred_invoke([this](auto&) { did_fail(NetworkJob::Error::ProtocolFailed); }); + return deferred_invoke([this](auto&) { did_fail(Core::NetworkJob::Error::ProtocolFailed); }); } m_received_buffers.append(payload); m_received_size += payload.size(); @@ -189,7 +189,7 @@ void HttpJob::finish_up() void HttpJob::start() { ASSERT(!m_socket); - m_socket = TCPSocket::construct(this); + m_socket = Core::TCPSocket::construct(this); m_socket->on_connected = [this] { #ifdef CHTTPJOB_DEBUG dbg() << "HttpJob: on_connected callback"; @@ -199,7 +199,7 @@ void HttpJob::start() bool success = m_socket->connect(m_request.url().host(), m_request.url().port()); if (!success) { deferred_invoke([this](auto&) { - return did_fail(NetworkJob::Error::ConnectionFailed); + return did_fail(Core::NetworkJob::Error::ConnectionFailed); }); } } diff --git a/Libraries/LibCore/HttpJob.h b/Libraries/LibHTTP/HttpJob.h similarity index 88% rename from Libraries/LibCore/HttpJob.h rename to Libraries/LibHTTP/HttpJob.h index 2c78386dc9..0816b3159d 100644 --- a/Libraries/LibCore/HttpJob.h +++ b/Libraries/LibHTTP/HttpJob.h @@ -27,13 +27,14 @@ #pragma once #include -#include -#include #include +#include +#include +#include -namespace Core { +namespace HTTP { -class HttpJob final : public NetworkJob { +class HttpJob final : public Core::NetworkJob { C_OBJECT(HttpJob) public: explicit HttpJob(const HttpRequest&); @@ -42,8 +43,8 @@ public: virtual void start() override; virtual void shutdown() override; - HttpResponse* response() { return static_cast(NetworkJob::response()); } - const HttpResponse* response() const { return static_cast(NetworkJob::response()); } + HttpResponse* response() { return static_cast(Core::NetworkJob::response()); } + const HttpResponse* response() const { return static_cast(Core::NetworkJob::response()); } private: void on_socket_connected(); @@ -57,7 +58,7 @@ private: }; HttpRequest m_request; - RefPtr m_socket; + RefPtr m_socket; State m_state { State::InStatus }; int m_code { -1 }; HashMap m_headers; diff --git a/Libraries/LibCore/HttpRequest.cpp b/Libraries/LibHTTP/HttpRequest.cpp similarity index 95% rename from Libraries/LibCore/HttpRequest.cpp rename to Libraries/LibHTTP/HttpRequest.cpp index 14ea2ff816..1f02e6106d 100644 --- a/Libraries/LibCore/HttpRequest.cpp +++ b/Libraries/LibHTTP/HttpRequest.cpp @@ -25,10 +25,10 @@ */ #include -#include -#include +#include +#include -namespace Core { +namespace HTTP { HttpRequest::HttpRequest() { @@ -38,7 +38,7 @@ HttpRequest::~HttpRequest() { } -RefPtr HttpRequest::schedule() +RefPtr HttpRequest::schedule() { auto job = HttpJob::construct(*this); job->start(); @@ -65,9 +65,9 @@ ByteBuffer HttpRequest::to_raw_request() const builder.append(method_name()); builder.append(' '); builder.append(m_url.path()); - builder.append(" HTTP/1.0\r\nHost: "); + builder.append(" HTTP/1.1\r\nHost: "); builder.append(m_url.host()); - builder.append("\r\n\r\n"); + builder.append("\r\nConnection: close\r\n\r\n"); return builder.to_byte_buffer(); } diff --git a/Libraries/LibCore/HttpRequest.h b/Libraries/LibHTTP/HttpRequest.h similarity index 97% rename from Libraries/LibCore/HttpRequest.h rename to Libraries/LibHTTP/HttpRequest.h index f3420964cf..44f30b5c01 100644 --- a/Libraries/LibCore/HttpRequest.h +++ b/Libraries/LibHTTP/HttpRequest.h @@ -32,7 +32,7 @@ #include #include -namespace Core { +namespace HTTP { class HttpRequest { public: @@ -63,7 +63,7 @@ public: String method_name() const; ByteBuffer to_raw_request() const; - RefPtr schedule(); + RefPtr schedule(); static Optional from_raw_request(const ByteBuffer&); diff --git a/Libraries/LibCore/HttpResponse.cpp b/Libraries/LibHTTP/HttpResponse.cpp similarity index 94% rename from Libraries/LibCore/HttpResponse.cpp rename to Libraries/LibHTTP/HttpResponse.cpp index aae66e6df8..dc8f6468f4 100644 --- a/Libraries/LibCore/HttpResponse.cpp +++ b/Libraries/LibHTTP/HttpResponse.cpp @@ -24,12 +24,12 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include +#include -namespace Core { +namespace HTTP { HttpResponse::HttpResponse(int code, HashMap&& headers, ByteBuffer&& payload) - : NetworkResponse(move(payload)) + : Core::NetworkResponse(move(payload)) , m_code(code) , m_headers(move(headers)) { diff --git a/Libraries/LibCore/HttpResponse.h b/Libraries/LibHTTP/HttpResponse.h similarity index 96% rename from Libraries/LibCore/HttpResponse.h rename to Libraries/LibHTTP/HttpResponse.h index 6af92dec31..4f86cbe9e9 100644 --- a/Libraries/LibCore/HttpResponse.h +++ b/Libraries/LibHTTP/HttpResponse.h @@ -30,9 +30,9 @@ #include #include -namespace Core { +namespace HTTP { -class HttpResponse : public NetworkResponse { +class HttpResponse : public Core::NetworkResponse { public: virtual ~HttpResponse() override; static NonnullRefPtr create(int code, HashMap&& headers, ByteBuffer&& payload) diff --git a/Libraries/LibHTTP/HttpsJob.cpp b/Libraries/LibHTTP/HttpsJob.cpp new file mode 100644 index 0000000000..e783b815cd --- /dev/null +++ b/Libraries/LibHTTP/HttpsJob.cpp @@ -0,0 +1,237 @@ +/* + * Copyright (c) 2020, The SerenityOS developers. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include +#include +#include + +//#define HTTPJOB_DEBUG + +namespace HTTP { + +static ByteBuffer handle_content_encoding(const ByteBuffer& buf, const String& content_encoding) +{ +#ifdef CHTTPJOB_DEBUG + dbg() << "HttpsJob::handle_content_encoding: buf has content_encoding = " << content_encoding; +#endif + + if (content_encoding == "gzip") { + if (!Core::Gzip::is_compressed(buf)) { + dbg() << "HttpsJob::handle_content_encoding: buf is not gzip compressed!"; + } + +#ifdef CHTTPJOB_DEBUG + dbg() << "HttpsJob::handle_content_encoding: buf is gzip compressed!"; +#endif + + auto uncompressed = Core::Gzip::decompress(buf); + if (!uncompressed.has_value()) { + dbg() << "HttpsJob::handle_content_encoding: Gzip::decompress() failed. Returning original buffer."; + return buf; + } + +#ifdef CHTTPJOB_DEBUG + dbg() << "HttpsJob::handle_content_encoding: Gzip::decompress() successful.\n" + << " Input size = " << buf.size() << "\n" + << " Output size = " << uncompressed.value().size(); +#endif + + return uncompressed.value(); + } + + return buf; +} + +HttpsJob::HttpsJob(const HttpRequest& request) + : m_request(request) +{ +} + +HttpsJob::~HttpsJob() +{ + m_socket = nullptr; +} + +void HttpsJob::on_socket_connected() +{ + + m_socket->on_tls_ready_to_write = [&](TLS::TLSv12& tls) { + if (m_sent_data) + return; + m_sent_data = true; + auto raw_request = m_request.to_raw_request(); +#if 0 + dbg() << "HttpsJob: raw_request:"; + dbg() << String::copy(raw_request).characters(); +#endif + bool success = tls.write(raw_request); + if (!success) + deferred_invoke([this](auto&) { did_fail(Core::NetworkJob::Error::TransmissionFailed); }); + }; + + m_socket->on_tls_ready_to_read = [&](TLS::TLSv12& tls) { + dbg() << " ON TLS READY TO READ: " << (u16)m_state; + if (is_cancelled()) + return; + if (m_state == State::InStatus) { + if (!tls.can_read_line()) { + dbg() << " cannot read line"; + return; + } + auto line = tls.read_line(PAGE_SIZE); + if (line.is_null()) { + fprintf(stderr, "HttpsJob: Expected HTTP status\n"); + return deferred_invoke([this](auto&) { did_fail(Core::NetworkJob::Error::TransmissionFailed); }); + } + auto parts = String::copy(line, Chomp).split(' '); + if (parts.size() < 3) { + fprintf(stderr, "HttpsJob: Expected 3-part HTTP status, got '%s'\n", line.data()); + return deferred_invoke([this](auto&) { did_fail(Core::NetworkJob::Error::ProtocolFailed); }); + } + bool ok; + m_code = parts[1].to_uint(ok); + if (!ok) { + fprintf(stderr, "HttpsJob: Expected numeric HTTP status\n"); + return deferred_invoke([this](auto&) { did_fail(Core::NetworkJob::Error::ProtocolFailed); }); + } + m_state = State::InHeaders; + return; + } + if (m_state == State::InHeaders) { + if (!tls.can_read_line()) + return; + auto line = tls.read_line(PAGE_SIZE); + if (line.is_null()) { + fprintf(stderr, "HttpsJob: Expected HTTP header\n"); + return did_fail(Core::NetworkJob::Error::ProtocolFailed); + } + auto chomped_line = String::copy(line, Chomp); + if (chomped_line.is_empty()) { + m_state = State::InBody; + return; + } + auto parts = chomped_line.split(':'); + if (parts.is_empty()) { + fprintf(stderr, "HttpsJob: Expected HTTP header with key/value\n"); + return deferred_invoke([this](auto&) { did_fail(Core::NetworkJob::Error::ProtocolFailed); }); + } + auto name = parts[0]; + if (chomped_line.length() < name.length() + 2) { + fprintf(stderr, "HttpsJob: Malformed HTTP header: '%s' (%zu)\n", chomped_line.characters(), chomped_line.length()); + return deferred_invoke([this](auto&) { did_fail(Core::NetworkJob::Error::ProtocolFailed); }); + } + auto value = chomped_line.substring(name.length() + 2, chomped_line.length() - name.length() - 2); + m_headers.set(name, value); +#ifdef CHTTPJOB_DEBUG + dbg() << "HttpsJob: [" << name << "] = '" << value << "'"; +#endif + return; + } + ASSERT(m_state == State::InBody); + ASSERT(tls.can_read()); + auto payload = tls.read(64 * KB); + if (!payload) { + if (tls.eof()) + return finish_up(); + return deferred_invoke([this](auto&) { did_fail(Core::NetworkJob::Error::ProtocolFailed); }); + } + dbg() << "Read payload, " << payload.size() << " bytes"; + m_received_buffers.append(payload); + m_received_size += payload.size(); + + auto content_length_header = m_headers.get("Content-Length"); + if (content_length_header.has_value()) { + dbg() << "content length is " << content_length_header.value() << ", we have " << m_received_size; + bool ok; + if (m_received_size >= content_length_header.value().to_uint(ok) && ok) + finish_up(); + } else { + // no content-length, assume closed connection + finish_up(); + } + }; +} + +void HttpsJob::finish_up() +{ + m_state = State::Finished; + auto flattened_buffer = ByteBuffer::create_uninitialized(m_received_size); + u8* flat_ptr = flattened_buffer.data(); + for (auto& received_buffer : m_received_buffers) { + memcpy(flat_ptr, received_buffer.data(), received_buffer.size()); + flat_ptr += received_buffer.size(); + } + m_received_buffers.clear(); + + auto content_encoding = m_headers.get("Content-Encoding"); + if (content_encoding.has_value()) { + flattened_buffer = handle_content_encoding(flattened_buffer, content_encoding.value()); + } + + auto response = HttpResponse::create(m_code, move(m_headers), move(flattened_buffer)); + deferred_invoke([this, response](auto&) { + did_finish(move(response)); + }); +} + +void HttpsJob::start() +{ + ASSERT(!m_socket); + m_socket = TLS::TLSv12::construct(this); + m_socket->on_tls_connected = [this] { +#ifdef CHTTPJOB_DEBUG + dbg() << "HttpsJob: on_connected callback"; +#endif + on_socket_connected(); + }; + m_socket->on_tls_error = [&](auto) { + finish_up(); + }; + m_socket->on_tls_finished = [&] { + finish_up(); + }; + bool success = ((TLS::TLSv12&)*m_socket).connect(m_request.url().host(), m_request.url().port()); + if (!success) { + deferred_invoke([this](auto&) { + return did_fail(Core::NetworkJob::Error::ConnectionFailed); + }); + } +} + +void HttpsJob::shutdown() +{ + if (!m_socket) + return; + m_socket->on_tls_ready_to_read = nullptr; + m_socket->on_tls_connected = nullptr; + remove_child(*m_socket); + m_socket = nullptr; +} +} diff --git a/Libraries/LibHTTP/HttpsJob.h b/Libraries/LibHTTP/HttpsJob.h new file mode 100644 index 0000000000..b7a9ba3f38 --- /dev/null +++ b/Libraries/LibHTTP/HttpsJob.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2020, The SerenityOS developers. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace HTTP { + +class HttpsJob final : public Core::NetworkJob { + C_OBJECT(HttpsJob) +public: + explicit HttpsJob(const HttpRequest&); + virtual ~HttpsJob() override; + + virtual void start() override; + virtual void shutdown() override; + + HttpResponse* response() { return static_cast(Core::NetworkJob::response()); } + const HttpResponse* response() const { return static_cast(Core::NetworkJob::response()); } + +private: + RefPtr construct_socket() { return TLS::TLSv12::construct(this); } + void on_socket_connected(); + void finish_up(); + + enum class State { + InStatus, + InHeaders, + InBody, + Finished, + }; + + HttpRequest m_request; + RefPtr m_socket; + State m_state { State::InStatus }; + int m_code { -1 }; + HashMap m_headers; + Vector m_received_buffers; + size_t m_received_size { 0 }; + bool m_sent_data { false }; +}; + +} diff --git a/Libraries/LibHTTP/Makefile b/Libraries/LibHTTP/Makefile new file mode 100644 index 0000000000..db085876fc --- /dev/null +++ b/Libraries/LibHTTP/Makefile @@ -0,0 +1,18 @@ +OBJS = HttpResponse.o \ + HttpRequest.o \ + HttpJob.o \ + HttpsJob.o + +LIBRARY = libhttp.a + +LIB_DEPS = Core + +POST_LIBRARY_BUILD = $(QUIET) $(MAKE) install + +install: + mkdir -p $(SERENITY_BASE_DIR)/Root/usr/include/sys/ + mkdir -p $(SERENITY_BASE_DIR)/Root/usr/lib/ + cp *.h $(SERENITY_BASE_DIR)/Root/usr/include/ + cp $(LIBRARY) $(SERENITY_BASE_DIR)/Root/usr/lib/ + +include ../../Makefile.common diff --git a/Libraries/LibWeb/ResourceLoader.cpp b/Libraries/LibWeb/ResourceLoader.cpp index a26ab27746..558359019b 100644 --- a/Libraries/LibWeb/ResourceLoader.cpp +++ b/Libraries/LibWeb/ResourceLoader.cpp @@ -105,7 +105,7 @@ void ResourceLoader::load(const URL& url, Function succ return; } - if (url.protocol() == "http") { + if (url.protocol() == "http" || url.protocol() == "https") { auto download = protocol_client().start_download(url.to_string()); if (!download) { if (error_callback) diff --git a/Servers/ProtocolServer/HttpDownload.cpp b/Servers/ProtocolServer/HttpDownload.cpp index af83dfa625..f49908fd25 100644 --- a/Servers/ProtocolServer/HttpDownload.cpp +++ b/Servers/ProtocolServer/HttpDownload.cpp @@ -24,11 +24,11 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include -#include +#include +#include #include -HttpDownload::HttpDownload(PSClientConnection& client, NonnullRefPtr&& job) +HttpDownload::HttpDownload(PSClientConnection& client, NonnullRefPtr&& job) : Download(client) , m_job(job) { @@ -43,7 +43,7 @@ HttpDownload::~HttpDownload() { } -NonnullRefPtr HttpDownload::create_with_job(Badge, PSClientConnection& client, NonnullRefPtr&& job) +NonnullRefPtr HttpDownload::create_with_job(Badge, PSClientConnection& client, NonnullRefPtr&& job) { return adopt(*new HttpDownload(client, move(job))); } diff --git a/Servers/ProtocolServer/HttpDownload.h b/Servers/ProtocolServer/HttpDownload.h index 8ec1809e97..364fe6aef2 100644 --- a/Servers/ProtocolServer/HttpDownload.h +++ b/Servers/ProtocolServer/HttpDownload.h @@ -28,6 +28,7 @@ #include #include +#include #include class HttpProtocol; @@ -35,10 +36,10 @@ class HttpProtocol; class HttpDownload final : public Download { public: virtual ~HttpDownload() override; - static NonnullRefPtr create_with_job(Badge, PSClientConnection&, NonnullRefPtr&&); + static NonnullRefPtr create_with_job(Badge, PSClientConnection&, NonnullRefPtr&&); private: - explicit HttpDownload(PSClientConnection&, NonnullRefPtr&&); + explicit HttpDownload(PSClientConnection&, NonnullRefPtr&&); - NonnullRefPtr m_job; + NonnullRefPtr m_job; }; diff --git a/Servers/ProtocolServer/HttpProtocol.cpp b/Servers/ProtocolServer/HttpProtocol.cpp index 25bbd19f86..1513a32e66 100644 --- a/Servers/ProtocolServer/HttpProtocol.cpp +++ b/Servers/ProtocolServer/HttpProtocol.cpp @@ -24,8 +24,8 @@ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -#include -#include +#include +#include #include #include @@ -40,11 +40,11 @@ HttpProtocol::~HttpProtocol() RefPtr HttpProtocol::start_download(PSClientConnection& client, const URL& url) { - Core::HttpRequest request; - request.set_method(Core::HttpRequest::Method::GET); + HTTP::HttpRequest request; + request.set_method(HTTP::HttpRequest::Method::GET); request.set_url(url); auto job = request.schedule(); if (!job) return nullptr; - return HttpDownload::create_with_job({}, client, (Core::HttpJob&)*job); + return HttpDownload::create_with_job({}, client, (HTTP::HttpJob&)*job); } diff --git a/Servers/ProtocolServer/HttpsDownload.cpp b/Servers/ProtocolServer/HttpsDownload.cpp new file mode 100644 index 0000000000..d7114cd7d9 --- /dev/null +++ b/Servers/ProtocolServer/HttpsDownload.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2020, The SerenityOS developers. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +HttpsDownload::HttpsDownload(PSClientConnection& client, NonnullRefPtr&& job) + : Download(client) + , m_job(job) +{ + m_job->on_finish = [this](bool success) { + if (m_job->response()) + set_payload(m_job->response()->payload()); + did_finish(success); + }; +} + +HttpsDownload::~HttpsDownload() +{ +} + +NonnullRefPtr HttpsDownload::create_with_job(Badge, PSClientConnection& client, NonnullRefPtr&& job) +{ + return adopt(*new HttpsDownload(client, move(job))); +} diff --git a/Servers/ProtocolServer/HttpsDownload.h b/Servers/ProtocolServer/HttpsDownload.h new file mode 100644 index 0000000000..a6c75bc4d7 --- /dev/null +++ b/Servers/ProtocolServer/HttpsDownload.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2020, The SerenityOS developers. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include +#include +#include +#include + +class HttpsProtocol; + +class HttpsDownload final : public Download { +public: + virtual ~HttpsDownload() override; + static NonnullRefPtr create_with_job(Badge, PSClientConnection&, NonnullRefPtr&&); + +private: + explicit HttpsDownload(PSClientConnection&, NonnullRefPtr&&); + + NonnullRefPtr m_job; +}; diff --git a/Servers/ProtocolServer/HttpsProtocol.cpp b/Servers/ProtocolServer/HttpsProtocol.cpp new file mode 100644 index 0000000000..1271d559f9 --- /dev/null +++ b/Servers/ProtocolServer/HttpsProtocol.cpp @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2018-2020, The SerenityOS developers. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include + +HttpsProtocol::HttpsProtocol() + : Protocol("https") +{ +} + +HttpsProtocol::~HttpsProtocol() +{ +} + +RefPtr HttpsProtocol::start_download(PSClientConnection& client, const URL& url) +{ + HTTP::HttpRequest request; + request.set_method(HTTP::HttpRequest::Method::GET); + request.set_url(url); + auto job = HTTP::HttpsJob::construct(request); + job->start(); + return HttpsDownload::create_with_job({}, client, (HTTP::HttpsJob&)*job); +} diff --git a/Servers/ProtocolServer/HttpsProtocol.h b/Servers/ProtocolServer/HttpsProtocol.h new file mode 100644 index 0000000000..e446178751 --- /dev/null +++ b/Servers/ProtocolServer/HttpsProtocol.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2018-2020, The SerenityOS developers. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#pragma once + +#include + +class HttpsProtocol final : public Protocol { +public: + HttpsProtocol(); + virtual ~HttpsProtocol() override; + + virtual RefPtr start_download(PSClientConnection&, const URL&) override; +}; diff --git a/Servers/ProtocolServer/Makefile b/Servers/ProtocolServer/Makefile index 7437d33c91..fa39255c53 100644 --- a/Servers/ProtocolServer/Makefile +++ b/Servers/ProtocolServer/Makefile @@ -4,11 +4,13 @@ OBJS = \ Download.o \ HttpProtocol.o \ HttpDownload.o \ + HttpsProtocol.o \ + HttpsDownload.o \ main.o PROGRAM = ProtocolServer -LIB_DEPS = Core IPC +LIB_DEPS = HTTP TLS Crypto Core IPC EXTRA_CLEAN = ProtocolServerEndpoint.h ProtocolClientEndpoint.h diff --git a/Servers/ProtocolServer/main.cpp b/Servers/ProtocolServer/main.cpp index e2cd971b52..100fe42cd2 100644 --- a/Servers/ProtocolServer/main.cpp +++ b/Servers/ProtocolServer/main.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include int main(int, char**) @@ -43,6 +44,7 @@ int main(int, char**) return 1; } (void)*new HttpProtocol; + (void)*new HttpsProtocol; auto server = Core::LocalServer::construct(); bool ok = server->take_over_from_system_server(); ASSERT(ok); diff --git a/Servers/WebServer/Client.cpp b/Servers/WebServer/Client.cpp index 3ad486eeaf..ae9aedc907 100644 --- a/Servers/WebServer/Client.cpp +++ b/Servers/WebServer/Client.cpp @@ -30,7 +30,7 @@ #include #include #include -#include +#include #include #include #include @@ -67,7 +67,7 @@ void Client::start() void Client::handle_request(ByteBuffer raw_request) { - auto request_or_error = Core::HttpRequest::from_raw_request(raw_request); + auto request_or_error = HTTP::HttpRequest::from_raw_request(raw_request); if (!request_or_error.has_value()) return; auto& request = request_or_error.value(); @@ -77,7 +77,7 @@ void Client::handle_request(ByteBuffer raw_request) dbg() << " " << header.name << " => " << header.value; } - if (request.method() != Core::HttpRequest::Method::GET) { + if (request.method() != HTTP::HttpRequest::Method::GET) { send_error_response(403, "Forbidden, bro!", request); return; } @@ -122,7 +122,7 @@ void Client::handle_request(ByteBuffer raw_request) send_response(file->read_all(), request); } -void Client::send_response(StringView response, const Core::HttpRequest& request) +void Client::send_response(StringView response, const HTTP::HttpRequest& request) { StringBuilder builder; builder.append("HTTP/1.0 200 OK\r\n"); @@ -136,7 +136,7 @@ void Client::send_response(StringView response, const Core::HttpRequest& request log_response(200, request); } -void Client::send_redirect(StringView redirect_path, const Core::HttpRequest& request) +void Client::send_redirect(StringView redirect_path, const HTTP::HttpRequest& request) { StringBuilder builder; builder.append("HTTP/1.0 301 Moved Permanently\r\n"); @@ -150,7 +150,7 @@ void Client::send_redirect(StringView redirect_path, const Core::HttpRequest& re log_response(301, request); } -void Client::handle_directory_listing(const String& requested_path, const String& real_path, const Core::HttpRequest& request) +void Client::handle_directory_listing(const String& requested_path, const String& real_path, const HTTP::HttpRequest& request) { StringBuilder builder; @@ -203,7 +203,7 @@ void Client::handle_directory_listing(const String& requested_path, const String send_response(builder.to_string(), request); } -void Client::send_error_response(unsigned code, const StringView& message, const Core::HttpRequest& request) +void Client::send_error_response(unsigned code, const StringView& message, const HTTP::HttpRequest& request) { StringBuilder builder; builder.appendf("HTTP/1.0 %u ", code); @@ -218,7 +218,7 @@ void Client::send_error_response(unsigned code, const StringView& message, const log_response(code, request); } -void Client::log_response(unsigned code, const Core::HttpRequest& request) +void Client::log_response(unsigned code, const HTTP::HttpRequest& request) { printf("%s :: %03u :: %s %s\n", Core::DateTime::now().to_string().characters(), diff --git a/Servers/WebServer/Client.h b/Servers/WebServer/Client.h index db2f727057..35cdb4ab1c 100644 --- a/Servers/WebServer/Client.h +++ b/Servers/WebServer/Client.h @@ -28,6 +28,7 @@ #include #include +#include namespace WebServer { @@ -40,12 +41,12 @@ private: Client(NonnullRefPtr, Core::Object* parent); void handle_request(ByteBuffer); - void send_response(StringView, const Core::HttpRequest&); - void send_redirect(StringView redirect, const Core::HttpRequest& request); - void send_error_response(unsigned code, const StringView& message, const Core::HttpRequest&); + void send_response(StringView, const HTTP::HttpRequest&); + void send_redirect(StringView redirect, const HTTP::HttpRequest& request); + void send_error_response(unsigned code, const StringView& message, const HTTP::HttpRequest&); void die(); - void log_response(unsigned code, const Core::HttpRequest&); - void handle_directory_listing(const String& requested_path, const String& real_path, const Core::HttpRequest&); + void log_response(unsigned code, const HTTP::HttpRequest&); + void handle_directory_listing(const String& requested_path, const String& real_path, const HTTP::HttpRequest&); NonnullRefPtr m_socket; }; diff --git a/Servers/WebServer/Makefile b/Servers/WebServer/Makefile index db88ad0679..ddc99a85ae 100644 --- a/Servers/WebServer/Makefile +++ b/Servers/WebServer/Makefile @@ -4,6 +4,6 @@ OBJS = \ PROGRAM = WebServer -LIB_DEPS = Core +LIB_DEPS = HTTP Core include ../../Makefile.common