mirror of
https://github.com/RGBCube/serenity
synced 2025-07-26 03:37:43 +00:00
LibIMAP: Add a new IMAP client and support NOOP
A large commit, but sets up the framework for how the IMAP library will work. Right now only the NOOP command and response is supported.
This commit is contained in:
parent
904322e754
commit
8c6061fc4a
8 changed files with 644 additions and 0 deletions
|
@ -19,6 +19,7 @@ add_subdirectory(LibGfx)
|
||||||
add_subdirectory(LibGL)
|
add_subdirectory(LibGL)
|
||||||
add_subdirectory(LibGUI)
|
add_subdirectory(LibGUI)
|
||||||
add_subdirectory(LibHTTP)
|
add_subdirectory(LibHTTP)
|
||||||
|
add_subdirectory(LibIMAP)
|
||||||
add_subdirectory(LibImageDecoderClient)
|
add_subdirectory(LibImageDecoderClient)
|
||||||
add_subdirectory(LibIPC)
|
add_subdirectory(LibIPC)
|
||||||
add_subdirectory(LibJS)
|
add_subdirectory(LibJS)
|
||||||
|
|
6
Userland/Libraries/LibIMAP/CMakeLists.txt
Normal file
6
Userland/Libraries/LibIMAP/CMakeLists.txt
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
set(SOURCES Objects.cpp Client.cpp Parser.cpp)
|
||||||
|
|
||||||
|
set(GENERATED_SOURCES)
|
||||||
|
|
||||||
|
serenity_lib(LibIMAP imap)
|
||||||
|
target_link_libraries(LibIMAP LibCore LibTLS)
|
204
Userland/Libraries/LibIMAP/Client.cpp
Normal file
204
Userland/Libraries/LibIMAP/Client.cpp
Normal file
|
@ -0,0 +1,204 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021, Kyle Pereira <hey@xylepereira.me>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <LibIMAP/Client.h>
|
||||||
|
|
||||||
|
namespace IMAP {
|
||||||
|
Client::Client(StringView host, unsigned int port, bool start_with_tls)
|
||||||
|
: m_host(host)
|
||||||
|
, m_port(port)
|
||||||
|
, m_tls(start_with_tls)
|
||||||
|
, m_parser(Parser())
|
||||||
|
{
|
||||||
|
if (start_with_tls) {
|
||||||
|
m_tls_socket = TLS::TLSv12::construct(nullptr);
|
||||||
|
m_tls_socket->set_root_certificates(DefaultRootCACertificates::the().certificates());
|
||||||
|
} else {
|
||||||
|
m_socket = Core::TCPSocket::construct();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Optional<RefPtr<Promise<Empty>>> Client::connect()
|
||||||
|
{
|
||||||
|
bool success;
|
||||||
|
if (m_tls) {
|
||||||
|
success = connect_tls();
|
||||||
|
} else {
|
||||||
|
success = connect_plaintext();
|
||||||
|
}
|
||||||
|
if (!success)
|
||||||
|
return {};
|
||||||
|
m_connect_pending = new Promise<bool> {};
|
||||||
|
return m_connect_pending;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Client::connect_tls()
|
||||||
|
{
|
||||||
|
m_tls_socket->on_tls_ready_to_read = [&](TLS::TLSv12&) {
|
||||||
|
on_tls_ready_to_receive();
|
||||||
|
};
|
||||||
|
m_tls_socket->on_tls_error = [&](TLS::AlertDescription alert) {
|
||||||
|
dbgln("failed: {}", alert_name(alert));
|
||||||
|
};
|
||||||
|
m_tls_socket->on_tls_connected = [&] {
|
||||||
|
dbgln("connected");
|
||||||
|
};
|
||||||
|
auto success = m_tls_socket->connect(m_host, m_port);
|
||||||
|
dbgln("connecting to {}:{} {}", m_host, m_port, success);
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Client::connect_plaintext()
|
||||||
|
{
|
||||||
|
m_socket->on_ready_to_read = [&] {
|
||||||
|
on_ready_to_receive();
|
||||||
|
};
|
||||||
|
auto success = m_socket->connect(m_host, m_port);
|
||||||
|
dbgln("connecting to {}:{} {}", m_host, m_port, success);
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::on_tls_ready_to_receive()
|
||||||
|
{
|
||||||
|
if (!m_tls_socket->can_read())
|
||||||
|
return;
|
||||||
|
auto data = m_tls_socket->read();
|
||||||
|
if (!data.has_value())
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Once we get server hello we can start sending
|
||||||
|
if (m_connect_pending) {
|
||||||
|
m_connect_pending->resolve({});
|
||||||
|
m_connect_pending.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_buffer += data.value();
|
||||||
|
if (m_buffer[m_buffer.size() - 1] == '\n') {
|
||||||
|
// Don't try parsing until we have a complete line.
|
||||||
|
auto response = m_parser.parse(move(m_buffer), m_expecting_response);
|
||||||
|
handle_parsed_response(move(response));
|
||||||
|
m_buffer.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::on_ready_to_receive()
|
||||||
|
{
|
||||||
|
if (!m_socket->can_read())
|
||||||
|
return;
|
||||||
|
m_buffer += m_socket->read_all();
|
||||||
|
|
||||||
|
// Once we get server hello we can start sending.
|
||||||
|
if (m_connect_pending) {
|
||||||
|
m_connect_pending->resolve({});
|
||||||
|
m_connect_pending.clear();
|
||||||
|
m_buffer.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_buffer[m_buffer.size() - 1] == '\n') {
|
||||||
|
// Don't try parsing until we have a complete line.
|
||||||
|
auto response = m_parser.parse(move(m_buffer), m_expecting_response);
|
||||||
|
handle_parsed_response(move(response));
|
||||||
|
m_buffer.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static ReadonlyBytes command_byte_buffer(CommandType command)
|
||||||
|
{
|
||||||
|
switch (command) {
|
||||||
|
case CommandType::Noop:
|
||||||
|
return "NOOP"sv.bytes();
|
||||||
|
}
|
||||||
|
VERIFY_NOT_REACHED();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::send_raw(StringView data)
|
||||||
|
{
|
||||||
|
if (m_tls) {
|
||||||
|
m_tls_socket->write(data.bytes());
|
||||||
|
m_tls_socket->write("\r\n"sv.bytes());
|
||||||
|
} else {
|
||||||
|
m_socket->write(data.bytes());
|
||||||
|
m_socket->write("\r\n"sv.bytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RefPtr<Promise<Optional<Response>>> Client::send_command(Command&& command)
|
||||||
|
{
|
||||||
|
m_command_queue.append(move(command));
|
||||||
|
m_current_command++;
|
||||||
|
|
||||||
|
auto promise = Promise<Optional<Response>>::construct();
|
||||||
|
m_pending_promises.append(promise);
|
||||||
|
|
||||||
|
if (m_pending_promises.size() == 1)
|
||||||
|
send_next_command();
|
||||||
|
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
RefPtr<Promise<Optional<Response>>> Client::send_simple_command(CommandType type)
|
||||||
|
{
|
||||||
|
auto command = Command { type, m_current_command, {} };
|
||||||
|
return send_command(move(command));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::handle_parsed_response(ParseStatus&& parse_status)
|
||||||
|
{
|
||||||
|
if (!m_expecting_response) {
|
||||||
|
if (!parse_status.successful) {
|
||||||
|
dbgln("Parsing failed on unrequested data!");
|
||||||
|
} else if (parse_status.response.has_value()) {
|
||||||
|
unrequested_response_callback(move(parse_status.response.value().get<SolidResponse>().data()));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
bool should_send_next = false;
|
||||||
|
if (!parse_status.successful) {
|
||||||
|
m_expecting_response = false;
|
||||||
|
m_pending_promises.first()->resolve({});
|
||||||
|
m_pending_promises.remove(0);
|
||||||
|
}
|
||||||
|
if (parse_status.response.has_value()) {
|
||||||
|
m_expecting_response = false;
|
||||||
|
should_send_next = parse_status.response->has<SolidResponse>();
|
||||||
|
m_pending_promises.first()->resolve(move(parse_status.response));
|
||||||
|
m_pending_promises.remove(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (should_send_next && !m_command_queue.is_empty()) {
|
||||||
|
send_next_command();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::send_next_command()
|
||||||
|
{
|
||||||
|
auto command = m_command_queue.take_first();
|
||||||
|
ByteBuffer buffer;
|
||||||
|
auto tag = AK::String::formatted("A{} ", m_current_command);
|
||||||
|
buffer += tag.to_byte_buffer();
|
||||||
|
auto command_type = command_byte_buffer(command.type);
|
||||||
|
buffer.append(command_type.data(), command_type.size());
|
||||||
|
|
||||||
|
for (auto& arg : command.args) {
|
||||||
|
buffer.append(" ", 1);
|
||||||
|
buffer.append(arg.bytes().data(), arg.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
send_raw(buffer);
|
||||||
|
m_expecting_response = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Client::close()
|
||||||
|
{
|
||||||
|
if (m_tls) {
|
||||||
|
m_tls_socket->close();
|
||||||
|
} else {
|
||||||
|
m_socket->close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
57
Userland/Libraries/LibIMAP/Client.h
Normal file
57
Userland/Libraries/LibIMAP/Client.h
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021, Kyle Pereira <hey@xylepereira.me>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <AK/Function.h>
|
||||||
|
#include <LibIMAP/Parser.h>
|
||||||
|
#include <LibTLS/TLSv12.h>
|
||||||
|
|
||||||
|
namespace IMAP {
|
||||||
|
class Client {
|
||||||
|
friend class Parser;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Client(StringView host, unsigned port, bool start_with_tls);
|
||||||
|
|
||||||
|
Optional<RefPtr<Promise<Empty>>> connect();
|
||||||
|
RefPtr<Promise<Optional<Response>>> send_command(Command&&);
|
||||||
|
RefPtr<Promise<Optional<Response>>> send_simple_command(CommandType);
|
||||||
|
void send_raw(StringView data);
|
||||||
|
void close();
|
||||||
|
|
||||||
|
Function<void(ResponseData&&)> unrequested_response_callback;
|
||||||
|
|
||||||
|
private:
|
||||||
|
StringView m_host;
|
||||||
|
unsigned m_port;
|
||||||
|
RefPtr<Core::Socket> m_socket;
|
||||||
|
RefPtr<TLS::TLSv12> m_tls_socket;
|
||||||
|
|
||||||
|
void on_ready_to_receive();
|
||||||
|
void on_tls_ready_to_receive();
|
||||||
|
|
||||||
|
bool m_tls;
|
||||||
|
int m_current_command = 1;
|
||||||
|
|
||||||
|
bool connect_tls();
|
||||||
|
bool connect_plaintext();
|
||||||
|
|
||||||
|
// Sent but response not received
|
||||||
|
Vector<RefPtr<Promise<Optional<Response>>>> m_pending_promises;
|
||||||
|
// Not yet sent
|
||||||
|
Vector<Command> m_command_queue {};
|
||||||
|
|
||||||
|
RefPtr<Promise<bool>> m_connect_pending {};
|
||||||
|
|
||||||
|
ByteBuffer m_buffer;
|
||||||
|
Parser m_parser;
|
||||||
|
|
||||||
|
bool m_expecting_response { false };
|
||||||
|
void handle_parsed_response(ParseStatus&& parse_status);
|
||||||
|
void send_next_command();
|
||||||
|
};
|
||||||
|
}
|
11
Userland/Libraries/LibIMAP/Objects.cpp
Normal file
11
Userland/Libraries/LibIMAP/Objects.cpp
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021, Kyle Pereira <hey@xylepereira.me>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <LibIMAP/Objects.h>
|
||||||
|
|
||||||
|
namespace IMAP {
|
||||||
|
|
||||||
|
}
|
158
Userland/Libraries/LibIMAP/Objects.h
Normal file
158
Userland/Libraries/LibIMAP/Objects.h
Normal file
|
@ -0,0 +1,158 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021, Kyle Pereira <hey@xylepereira.me>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <AK/Format.h>
|
||||||
|
#include <AK/Function.h>
|
||||||
|
#include <AK/Tuple.h>
|
||||||
|
#include <AK/Variant.h>
|
||||||
|
#include <LibCore/DateTime.h>
|
||||||
|
#include <LibCore/EventLoop.h>
|
||||||
|
#include <LibCore/Object.h>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace IMAP {
|
||||||
|
enum class CommandType {
|
||||||
|
Noop,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class ResponseType : unsigned {
|
||||||
|
};
|
||||||
|
|
||||||
|
class Parser;
|
||||||
|
|
||||||
|
struct Command {
|
||||||
|
public:
|
||||||
|
CommandType type;
|
||||||
|
int tag;
|
||||||
|
Vector<String> args;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class ResponseStatus {
|
||||||
|
Bad,
|
||||||
|
No,
|
||||||
|
OK,
|
||||||
|
};
|
||||||
|
|
||||||
|
class ResponseData {
|
||||||
|
public:
|
||||||
|
[[nodiscard]] unsigned response_type() const
|
||||||
|
{
|
||||||
|
return m_response_type;
|
||||||
|
}
|
||||||
|
|
||||||
|
ResponseData()
|
||||||
|
: m_response_type(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
ResponseData(ResponseData&) = delete;
|
||||||
|
ResponseData(ResponseData&&) = default;
|
||||||
|
ResponseData& operator=(const ResponseData&) = delete;
|
||||||
|
ResponseData& operator=(ResponseData&&) = default;
|
||||||
|
|
||||||
|
[[nodiscard]] bool contains_response_type(ResponseType response_type) const
|
||||||
|
{
|
||||||
|
return (static_cast<unsigned>(response_type) & m_response_type) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void add_response_type(ResponseType response_type)
|
||||||
|
{
|
||||||
|
m_response_type = m_response_type | static_cast<unsigned>(response_type);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
unsigned m_response_type;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SolidResponse {
|
||||||
|
// Parser is allowed to set up fields
|
||||||
|
friend class Parser;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ResponseStatus status() { return m_status; }
|
||||||
|
|
||||||
|
int tag() const { return m_tag; }
|
||||||
|
|
||||||
|
ResponseData& data() { return m_data; }
|
||||||
|
|
||||||
|
String response_text() { return m_response_text; };
|
||||||
|
|
||||||
|
SolidResponse()
|
||||||
|
: SolidResponse(ResponseStatus::Bad, -1)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
SolidResponse(ResponseStatus status, int tag)
|
||||||
|
: m_status(status)
|
||||||
|
, m_tag(tag)
|
||||||
|
, m_data(ResponseData())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
ResponseStatus m_status;
|
||||||
|
String m_response_text;
|
||||||
|
unsigned m_tag;
|
||||||
|
|
||||||
|
ResponseData m_data;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ContinueRequest {
|
||||||
|
String data;
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename Result>
|
||||||
|
class Promise : public Core::Object {
|
||||||
|
C_OBJECT(Promise);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Optional<Result> m_pending;
|
||||||
|
|
||||||
|
public:
|
||||||
|
Function<void(Result&)> on_resolved;
|
||||||
|
|
||||||
|
void resolve(Result&& result)
|
||||||
|
{
|
||||||
|
m_pending = move(result);
|
||||||
|
if (on_resolved)
|
||||||
|
on_resolved(m_pending.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_resolved()
|
||||||
|
{
|
||||||
|
return m_pending.has_value();
|
||||||
|
};
|
||||||
|
|
||||||
|
Result await()
|
||||||
|
{
|
||||||
|
while (!is_resolved()) {
|
||||||
|
Core::EventLoop::current().pump();
|
||||||
|
}
|
||||||
|
return m_pending.release_value();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Converts a Promise<A> to a Promise<B> using a function func: A -> B
|
||||||
|
template<typename T>
|
||||||
|
RefPtr<Promise<T>> map(Function<T(Result&)> func)
|
||||||
|
{
|
||||||
|
RefPtr<Promise<T>> new_promise = Promise<T>::construct();
|
||||||
|
on_resolved = [new_promise, func](Result& result) mutable {
|
||||||
|
auto t = func(result);
|
||||||
|
new_promise->resolve(move(t));
|
||||||
|
};
|
||||||
|
return new_promise;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
using Response = Variant<SolidResponse, ContinueRequest>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// An RFC 2822 message
|
||||||
|
// https://datatracker.ietf.org/doc/html/rfc2822
|
||||||
|
struct Message {
|
||||||
|
String data;
|
||||||
|
};
|
163
Userland/Libraries/LibIMAP/Parser.cpp
Normal file
163
Userland/Libraries/LibIMAP/Parser.cpp
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021, Kyle Pereira <hey@xylepereira.me>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <AK/CharacterTypes.h>
|
||||||
|
#include <LibIMAP/Parser.h>
|
||||||
|
|
||||||
|
namespace IMAP {
|
||||||
|
|
||||||
|
ParseStatus Parser::parse(ByteBuffer&& buffer, bool expecting_tag)
|
||||||
|
{
|
||||||
|
if (m_incomplete) {
|
||||||
|
m_buffer += buffer;
|
||||||
|
m_incomplete = false;
|
||||||
|
} else {
|
||||||
|
m_buffer = move(buffer);
|
||||||
|
position = 0;
|
||||||
|
m_response = SolidResponse();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (try_consume("+")) {
|
||||||
|
consume(" ");
|
||||||
|
auto data = parse_while([](u8 x) { return x != '\r'; });
|
||||||
|
consume("\r\n");
|
||||||
|
return { true, { ContinueRequest { data } } };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (expecting_tag) {
|
||||||
|
if (at_end()) {
|
||||||
|
m_incomplete = true;
|
||||||
|
return { true, {} };
|
||||||
|
}
|
||||||
|
parse_response_done();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_parsing_failed) {
|
||||||
|
return { false, {} };
|
||||||
|
} else {
|
||||||
|
return { true, { { move(m_response) } } };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Parser::try_consume(StringView x)
|
||||||
|
{
|
||||||
|
size_t i = 0;
|
||||||
|
auto previous_position = position;
|
||||||
|
while (i < x.length() && !at_end() && to_ascii_lowercase(x[i]) == to_ascii_lowercase(m_buffer[position])) {
|
||||||
|
i++;
|
||||||
|
position++;
|
||||||
|
}
|
||||||
|
if (i != x.length()) {
|
||||||
|
// We didn't match the full string.
|
||||||
|
position = previous_position;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Parser::parse_response_done()
|
||||||
|
{
|
||||||
|
consume("A");
|
||||||
|
auto tag = parse_number();
|
||||||
|
consume(" ");
|
||||||
|
|
||||||
|
ResponseStatus status = parse_status();
|
||||||
|
consume(" ");
|
||||||
|
|
||||||
|
m_response.m_tag = tag;
|
||||||
|
m_response.m_status = status;
|
||||||
|
|
||||||
|
StringBuilder response_data;
|
||||||
|
|
||||||
|
while (!at_end() && m_buffer[position] != '\r') {
|
||||||
|
response_data.append((char)m_buffer[position]);
|
||||||
|
position += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
consume("\r\n");
|
||||||
|
m_response.m_response_text = response_data.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Parser::consume(StringView x)
|
||||||
|
{
|
||||||
|
if (!try_consume(x)) {
|
||||||
|
dbgln("{} not matched at {}, buffer: {}", x, position, StringView(m_buffer.data(), m_buffer.size()));
|
||||||
|
|
||||||
|
m_parsing_failed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Optional<unsigned> Parser::try_parse_number()
|
||||||
|
{
|
||||||
|
auto number_matched = 0;
|
||||||
|
while (!at_end() && 0 <= m_buffer[position] - '0' && m_buffer[position] - '0' <= 9) {
|
||||||
|
number_matched++;
|
||||||
|
position++;
|
||||||
|
}
|
||||||
|
if (number_matched == 0)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
auto number = StringView(m_buffer.data() + position - number_matched, number_matched);
|
||||||
|
|
||||||
|
return number.to_uint();
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned Parser::parse_number()
|
||||||
|
{
|
||||||
|
auto number = try_parse_number();
|
||||||
|
if (!number.has_value()) {
|
||||||
|
m_parsing_failed = true;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return number.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
StringView Parser::parse_atom()
|
||||||
|
{
|
||||||
|
auto is_non_atom_char = [](u8 x) {
|
||||||
|
auto non_atom_chars = { '(', ')', '{', ' ', '%', '*', '"', '\\', ']' };
|
||||||
|
return AK::find(non_atom_chars.begin(), non_atom_chars.end(), x) != non_atom_chars.end();
|
||||||
|
};
|
||||||
|
|
||||||
|
auto start = position;
|
||||||
|
auto count = 0;
|
||||||
|
while (!at_end() && !is_ascii_control(m_buffer[position]) && !is_non_atom_char(m_buffer[position])) {
|
||||||
|
count++;
|
||||||
|
position++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return StringView(m_buffer.data() + start, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
ResponseStatus Parser::parse_status()
|
||||||
|
{
|
||||||
|
auto atom = parse_atom();
|
||||||
|
|
||||||
|
if (atom.matches("OK")) {
|
||||||
|
return ResponseStatus::OK;
|
||||||
|
} else if (atom.matches("BAD")) {
|
||||||
|
return ResponseStatus::Bad;
|
||||||
|
} else if (atom.matches("NO")) {
|
||||||
|
return ResponseStatus::No;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_parsing_failed = true;
|
||||||
|
return ResponseStatus::Bad;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringView Parser::parse_while(Function<bool(u8)> should_consume)
|
||||||
|
{
|
||||||
|
int chars = 0;
|
||||||
|
while (!at_end() && should_consume(m_buffer[position])) {
|
||||||
|
position++;
|
||||||
|
chars++;
|
||||||
|
}
|
||||||
|
return StringView(m_buffer.data() + position - chars, chars);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
44
Userland/Libraries/LibIMAP/Parser.h
Normal file
44
Userland/Libraries/LibIMAP/Parser.h
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2021, Kyle Pereira <hey@xylepereira.me>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <AK/ByteBuffer.h>
|
||||||
|
#include <AK/Result.h>
|
||||||
|
#include <LibIMAP/Objects.h>
|
||||||
|
|
||||||
|
namespace IMAP {
|
||||||
|
class Client;
|
||||||
|
|
||||||
|
struct ParseStatus {
|
||||||
|
bool successful;
|
||||||
|
Optional<Response> response;
|
||||||
|
};
|
||||||
|
|
||||||
|
class Parser {
|
||||||
|
public:
|
||||||
|
ParseStatus parse(ByteBuffer&& buffer, bool expecting_tag);
|
||||||
|
|
||||||
|
private:
|
||||||
|
// To retain state if parsing is not finished
|
||||||
|
ByteBuffer m_buffer;
|
||||||
|
SolidResponse m_response;
|
||||||
|
unsigned position { 0 };
|
||||||
|
bool m_incomplete { false };
|
||||||
|
bool m_parsing_failed { false };
|
||||||
|
|
||||||
|
bool try_consume(StringView);
|
||||||
|
bool at_end() { return position >= m_buffer.size(); };
|
||||||
|
void parse_response_done();
|
||||||
|
void consume(StringView x);
|
||||||
|
unsigned parse_number();
|
||||||
|
Optional<unsigned> try_parse_number();
|
||||||
|
void parse_untagged();
|
||||||
|
StringView parse_atom();
|
||||||
|
ResponseStatus parse_status();
|
||||||
|
StringView parse_while(Function<bool(u8)> should_consume);
|
||||||
|
};
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue