From 476774d68113275c4658ae47214f6a4bb2000848 Mon Sep 17 00:00:00 2001 From: Caoimhe Date: Sat, 13 May 2023 19:47:59 +0100 Subject: [PATCH] SpiceAgent: Implement `FileTransferStart` messages --- Userland/Services/SpiceAgent/Message.cpp | 55 +++++++++++++++++++++ Userland/Services/SpiceAgent/Message.h | 27 ++++++++++ Userland/Services/SpiceAgent/SpiceAgent.cpp | 7 +++ 3 files changed, 89 insertions(+) diff --git a/Userland/Services/SpiceAgent/Message.cpp b/Userland/Services/SpiceAgent/Message.cpp index ca0e06fe77..c6e58027a1 100644 --- a/Userland/Services/SpiceAgent/Message.cpp +++ b/Userland/Services/SpiceAgent/Message.cpp @@ -186,4 +186,59 @@ ErrorOr ClipboardMessage::debug_description() return builder.to_string(); } +ErrorOr FileTransferStartMessage::read_from_stream(AK::Stream& stream) +{ + auto id = TRY(stream.read_value()); + + auto metadata_bytes = TRY(stream.read_until_eof()); + auto metadata_content = TRY(String::from_utf8(metadata_bytes)); + + // TODO: We need some sort of INIParser, or we need to make Core::ConfigFile not depend on having an actual Core::File + // The first line in the file should always be `[vdagent-file-xfer]` + auto lines = TRY(metadata_content.split('\n')); + if (lines.is_empty() || lines.at(0) != "[vdagent-file-xfer]") { + return Error::from_string_literal("Failed to parse file transfer metadata"); + } + + Optional name = {}; + Optional size = {}; + + for (auto const& line : lines) { + // Ignore the header, we already assume that it is [vdagent-file-xfer] + if (line.starts_with('[')) + continue; + + if (line.starts_with_bytes("name="sv)) { + auto parsed_name = TRY(line.replace("name="sv, ""sv, ReplaceMode::FirstOnly)); + if (parsed_name.is_empty()) { + return Error::from_string_literal("Failed to parse file name!"); + } + + name = parsed_name; + } + + if (line.starts_with_bytes("size="sv)) { + auto size_string = TRY(line.replace("size="sv, ""sv, ReplaceMode::FirstOnly)); + size = size_string.to_number(TrimWhitespace::Yes); + } + } + + // Verify that we actually parsed any data + if (!name.has_value() || !size.has_value()) { + return Error::from_string_literal("Invalid transfer start message received!"); + } + + return FileTransferStartMessage(id, Metadata { name.release_value(), size.release_value() }); +} + +ErrorOr FileTransferStartMessage::debug_description() +{ + StringBuilder builder; + TRY(builder.try_append("FileTransferStart { "sv)); + TRY(builder.try_appendff("id = {}, ", id())); + TRY(builder.try_appendff("metadata = Metadata {{ name = {}, size = {} }}", metadata().name, metadata().size)); + TRY(builder.try_append(" }"sv)); + return builder.to_string(); +} + } diff --git a/Userland/Services/SpiceAgent/Message.h b/Userland/Services/SpiceAgent/Message.h index 29456cc307..01da14b7e7 100644 --- a/Userland/Services/SpiceAgent/Message.h +++ b/Userland/Services/SpiceAgent/Message.h @@ -179,6 +179,33 @@ private: ByteBuffer m_contents; }; +// Sent to the agent to indicate that a file transfer has been requested. +class FileTransferStartMessage : public Message { +public: + struct Metadata { + String name; + u32 size; + }; + + static ErrorOr read_from_stream(AK::Stream& stream); + + ErrorOr debug_description() override; + + u32 id() const { return m_id; } + Metadata const& metadata() { return m_metadata; } + +private: + FileTransferStartMessage(u32 id, FileTransferStartMessage::Metadata const& metadata) + : Message(Type::FileTransferStart) + , m_id(id) + , m_metadata(metadata) + { + } + + u32 m_id { 0 }; + Metadata m_metadata; +}; + } namespace AK { diff --git a/Userland/Services/SpiceAgent/SpiceAgent.cpp b/Userland/Services/SpiceAgent/SpiceAgent.cpp index 1cf46a1d45..788ce57a02 100644 --- a/Userland/Services/SpiceAgent/SpiceAgent.cpp +++ b/Userland/Services/SpiceAgent/SpiceAgent.cpp @@ -154,6 +154,13 @@ ErrorOr SpiceAgent::on_message_received() break; } + case Message::Type::FileTransferStart: { + auto message = TRY(FileTransferStartMessage::read_from_stream(stream)); + dbgln("File transfer request received: {}", TRY(message.debug_description())); + + break; + } + // We ignore certain messages to prevent it from clogging up the logs. case Message::Type::MonitorsConfig: dbgln_if(SPICE_AGENT_DEBUG, "Ignored message: {}", header);