diff --git a/Libraries/CMakeLists.txt b/Libraries/CMakeLists.txt index 8f297d21ab7..3954f99c43f 100644 --- a/Libraries/CMakeLists.txt +++ b/Libraries/CMakeLists.txt @@ -4,6 +4,7 @@ add_subdirectory(LibCore) add_subdirectory(LibCrypto) add_subdirectory(LibDebug) add_subdirectory(LibDesktop) +add_subdirectory(LibGemini) add_subdirectory(LibGfx) add_subdirectory(LibGUI) add_subdirectory(LibHTTP) diff --git a/Libraries/LibGemini/CMakeLists.txt b/Libraries/LibGemini/CMakeLists.txt new file mode 100644 index 00000000000..d7a1eb0256f --- /dev/null +++ b/Libraries/LibGemini/CMakeLists.txt @@ -0,0 +1,9 @@ +set(SOURCES + GeminiJob.cpp + GeminiRequest.cpp + GeminiResponse.cpp + Job.cpp +) + +serenity_lib(LibGemini gemini) +target_link_libraries(LibGemini LibCore LibTLS) diff --git a/Libraries/LibGemini/Forward.h b/Libraries/LibGemini/Forward.h new file mode 100644 index 00000000000..1ad21897e41 --- /dev/null +++ b/Libraries/LibGemini/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 Gemini { + +class GeminiRequest; +class GeminiResponse; +class GeminiJob; +class Job; + +} diff --git a/Libraries/LibGemini/GeminiJob.cpp b/Libraries/LibGemini/GeminiJob.cpp new file mode 100644 index 00000000000..dac0237155f --- /dev/null +++ b/Libraries/LibGemini/GeminiJob.cpp @@ -0,0 +1,136 @@ +/* + * 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 + +//#define GEMINIJOB_DEBUG + +namespace Gemini { + +void GeminiJob::start() +{ + ASSERT(!m_socket); + m_socket = TLS::TLSv12::construct(this); + m_socket->on_tls_connected = [this] { +#ifdef GEMINIJOB_DEBUG + dbg() << "GeminiJob: on_connected callback"; +#endif + on_socket_connected(); + }; + m_socket->on_tls_error = [this](TLS::AlertDescription error) { + if (error == TLS::AlertDescription::HandshakeFailure) { + deferred_invoke([this](auto&) { + return did_fail(Core::NetworkJob::Error::ProtocolFailed); + }); + } else if (error == TLS::AlertDescription::DecryptError) { + deferred_invoke([this](auto&) { + return did_fail(Core::NetworkJob::Error::ConnectionFailed); + }); + } else { + deferred_invoke([this](auto&) { + return did_fail(Core::NetworkJob::Error::TransmissionFailed); + }); + } + }; + m_socket->on_tls_finished = [this] { + 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 GeminiJob::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; +} + +void GeminiJob::read_while_data_available(Function read) +{ + while (m_socket->can_read()) { + if (read() == IterationDecision::Break) + break; + } +} + +void GeminiJob::register_on_ready_to_read(Function callback) +{ + m_socket->on_tls_ready_to_read = [callback = move(callback)](auto&) { + callback(); + }; +} + +void GeminiJob::register_on_ready_to_write(Function callback) +{ + m_socket->on_tls_ready_to_write = [callback = move(callback)](auto&) { + callback(); + }; +} + +bool GeminiJob::can_read_line() const +{ + return m_socket->can_read_line(); +} + +ByteBuffer GeminiJob::read_line(size_t size) +{ + return m_socket->read_line(size); +} + +ByteBuffer GeminiJob::receive(size_t size) +{ + return m_socket->read(size); +} + +bool GeminiJob::can_read() const +{ + return m_socket->can_read(); +} + +bool GeminiJob::eof() const +{ + return m_socket->eof(); +} + +bool GeminiJob::write(const ByteBuffer& data) +{ + return m_socket->write(data); +} + +} diff --git a/Libraries/LibGemini/GeminiJob.h b/Libraries/LibGemini/GeminiJob.h new file mode 100644 index 00000000000..2a67557199f --- /dev/null +++ b/Libraries/LibGemini/GeminiJob.h @@ -0,0 +1,70 @@ +/* + * 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 Gemini { + +class GeminiJob final : public Job { + C_OBJECT(GeminiJob) +public: + explicit GeminiJob(const GeminiRequest& request) + : Job(request) + { + } + + virtual ~GeminiJob() override + { + } + + virtual void start() override; + virtual void shutdown() override; + +protected: + virtual void register_on_ready_to_read(Function) override; + virtual void register_on_ready_to_write(Function) override; + virtual bool can_read_line() const override; + virtual ByteBuffer read_line(size_t) override; + virtual bool can_read() const override; + virtual ByteBuffer receive(size_t) override; + virtual bool eof() const override; + virtual bool write(const ByteBuffer&) override; + virtual bool is_established() const override { return m_socket->is_established(); } + virtual bool should_fail_on_empty_payload() const override { return false; } + virtual void read_while_data_available(Function) override; + +private: + RefPtr m_socket; + bool m_queued_finish { false }; +}; + +} diff --git a/Libraries/LibGemini/GeminiRequest.cpp b/Libraries/LibGemini/GeminiRequest.cpp new file mode 100644 index 00000000000..3735bbb1e96 --- /dev/null +++ b/Libraries/LibGemini/GeminiRequest.cpp @@ -0,0 +1,67 @@ +/* + * 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 + +namespace Gemini { + +GeminiRequest::GeminiRequest() +{ +} + +GeminiRequest::~GeminiRequest() +{ +} + +RefPtr GeminiRequest::schedule() +{ + auto job = GeminiJob::construct(*this); + job->start(); + return job; +} + +ByteBuffer GeminiRequest::to_raw_request() const +{ + StringBuilder builder; + builder.append(m_url.to_string()); + builder.append("\r\n"); + return builder.to_byte_buffer(); +} + +Optional GeminiRequest::from_raw_request(const ByteBuffer& raw_request) +{ + URL url = StringView(raw_request); + if (!url.is_valid()) + return {}; + GeminiRequest request; + request.m_url = url; + return request; +} + +} diff --git a/Libraries/LibGemini/GeminiRequest.h b/Libraries/LibGemini/GeminiRequest.h new file mode 100644 index 00000000000..a49cff3c716 --- /dev/null +++ b/Libraries/LibGemini/GeminiRequest.h @@ -0,0 +1,54 @@ +/* + * 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 + +namespace Gemini { + +class GeminiRequest { +public: + GeminiRequest(); + ~GeminiRequest(); + + const URL& url() const { return m_url; } + void set_url(const URL& url) { m_url = url; } + + ByteBuffer to_raw_request() const; + + RefPtr schedule(); + + static Optional from_raw_request(const ByteBuffer&); + +private: + URL m_url; +}; + +} diff --git a/Libraries/LibGemini/GeminiResponse.cpp b/Libraries/LibGemini/GeminiResponse.cpp new file mode 100644 index 00000000000..8aeecb34a52 --- /dev/null +++ b/Libraries/LibGemini/GeminiResponse.cpp @@ -0,0 +1,42 @@ +/* + * 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 + +namespace Gemini { + +GeminiResponse::GeminiResponse(int status, String meta, ByteBuffer&& payload) + : Core::NetworkResponse(move(payload)) + , m_status(status) + , m_meta(meta) +{ +} + +GeminiResponse::~GeminiResponse() +{ +} + +} diff --git a/Libraries/LibGemini/GeminiResponse.h b/Libraries/LibGemini/GeminiResponse.h new file mode 100644 index 00000000000..ff3254a5877 --- /dev/null +++ b/Libraries/LibGemini/GeminiResponse.h @@ -0,0 +1,52 @@ +/* + * 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 + +namespace Gemini { + +class GeminiResponse : public Core::NetworkResponse { +public: + virtual ~GeminiResponse() override; + static NonnullRefPtr create(int status, String meta, ByteBuffer&& payload) + { + return adopt(*new GeminiResponse(status, meta, move(payload))); + } + + int status() const { return m_status; } + String meta() const { return m_meta; } + +private: + GeminiResponse(int status, String, ByteBuffer&&); + + int m_status { 0 }; + String m_meta; +}; + +} diff --git a/Libraries/LibGemini/Job.cpp b/Libraries/LibGemini/Job.cpp new file mode 100644 index 00000000000..5d22e2427fc --- /dev/null +++ b/Libraries/LibGemini/Job.cpp @@ -0,0 +1,160 @@ +/* + * 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 + +//#define JOB_DEBUG + +namespace Gemini { + +Job::Job(const GeminiRequest& request) + : m_request(request) +{ +} + +Job::~Job() +{ +} + +void Job::on_socket_connected() +{ + register_on_ready_to_write([this] { + if (m_sent_data) + return; + m_sent_data = true; + auto raw_request = m_request.to_raw_request(); +#ifdef JOB_DEBUG + dbg() << "Job: raw_request:"; + dbg() << String::copy(raw_request).characters(); +#endif + bool success = write(raw_request); + if (!success) + deferred_invoke([this](auto&) { did_fail(Core::NetworkJob::Error::TransmissionFailed); }); + }); + register_on_ready_to_read([this] { + if (is_cancelled()) + return; + + if (m_state == State::InStatus) { + if (!can_read_line()) + return; + + auto line = read_line(PAGE_SIZE); + if (line.is_null()) { + fprintf(stderr, "Job: Expected status line\n"); + return deferred_invoke([this](auto&) { did_fail(Core::NetworkJob::Error::TransmissionFailed); }); + } + + auto parts = String::copy(line, Chomp).split_limit(' ', 2); + if (parts.size() != 2) { + fprintf(stderr, "Job: Expected 2-part status line, got '%s'\n", line.data()); + return deferred_invoke([this](auto&) { did_fail(Core::NetworkJob::Error::ProtocolFailed); }); + } + + bool ok; + m_status = parts[0].to_uint(ok); + if (!ok) { + fprintf(stderr, "Job: Expected numeric status code\n"); + return deferred_invoke([this](auto&) { did_fail(Core::NetworkJob::Error::ProtocolFailed); }); + } + + m_meta = parts[1]; + + if (m_status >= 10 && m_status < 20) { + m_state = State::Finished; + } else if (m_status >= 20 && m_status < 30) { + m_state = State::InBody; + } else if (m_status >= 30 && m_status < 40) { + m_state = State::Finished; + } else if (m_status >= 40 && m_status < 50) { + m_state = State::Finished; + } else if (m_status >= 50 && m_status < 60) { + m_state = State::Finished; + } else if (m_status >= 60 && m_status < 70) { + m_state = State::InBody; + } else { + fprintf(stderr, "Job: Expected status between 10 and 69; instead got %d\n", m_status); + return deferred_invoke([this](auto&) { did_fail(Core::NetworkJob::Error::ProtocolFailed); }); + } + + return; + } + + ASSERT(m_state == State::InBody || m_state == State::Finished); + + read_while_data_available([&] { + auto read_size = 64 * KB; + + auto payload = receive(read_size); + if (!payload) { + if (eof()) { + finish_up(); + return IterationDecision::Break; + } + + if (should_fail_on_empty_payload()) { + deferred_invoke([this](auto&) { did_fail(Core::NetworkJob::Error::ProtocolFailed); }); + return IterationDecision::Break; + } + } + + m_received_buffers.append(payload); + m_received_size += payload.size(); + + did_progress({}, m_received_size); + + return IterationDecision::Continue; + }); + + if (!is_established()) { +#ifdef JOB_DEBUG + dbg() << "Connection appears to have closed, finishing up"; +#endif + finish_up(); + } + }); +} + +void Job::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 response = GeminiResponse::create(m_status, m_meta, move(flattened_buffer)); + deferred_invoke([this, response](auto&) { + did_finish(move(response)); + }); +} +} diff --git a/Libraries/LibGemini/Job.h b/Libraries/LibGemini/Job.h new file mode 100644 index 00000000000..e308166ffd9 --- /dev/null +++ b/Libraries/LibGemini/Job.h @@ -0,0 +1,79 @@ +/* + * 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 Gemini { + +class Job : public Core::NetworkJob { +public: + explicit Job(const GeminiRequest&); + virtual ~Job() override; + + virtual void start() override = 0; + virtual void shutdown() override = 0; + + GeminiResponse* response() { return static_cast(Core::NetworkJob::response()); } + const GeminiResponse* response() const { return static_cast(Core::NetworkJob::response()); } + +protected: + void finish_up(); + void on_socket_connected(); + virtual void register_on_ready_to_read(Function) = 0; + virtual void register_on_ready_to_write(Function) = 0; + virtual bool can_read_line() const = 0; + virtual ByteBuffer read_line(size_t) = 0; + virtual bool can_read() const = 0; + virtual ByteBuffer receive(size_t) = 0; + virtual bool eof() const = 0; + virtual bool write(const ByteBuffer&) = 0; + virtual bool is_established() const = 0; + virtual bool should_fail_on_empty_payload() const { return false; } + virtual void read_while_data_available(Function read) { read(); }; + + enum class State { + InStatus, + InBody, + Finished, + }; + + GeminiRequest m_request; + State m_state { State::InStatus }; + int m_status { -1 }; + String m_meta; + Vector m_received_buffers; + size_t m_received_size { 0 }; + bool m_sent_data { false }; + bool m_should_have_payload { false }; +}; + +}