/* * Copyright (c) 2026, The PurpleI2P Project * * This file is part of Purple i2pd project and licensed under BSD3 * * See full license text in LICENSE file at top of project tree */ #include #if BOOST_VERSION >= 107800 // boost::json since 1.75, we allow since 1.78 #include #define JSON_SUPPORTED #endif #include "Log.h" #include "Torrents.h" #include "TorrentsRPC.h" namespace i2p { namespace torrents { TorrentsRPCSession::TorrentsRPCSession (TorrentsRPCServer& server, boost::asio::ip::tcp::socket&& s): m_Server (server), m_Socket (std::move (s)) { } void TorrentsRPCSession::ReceiveRequest () { boost::beast::http::async_read (m_Socket, m_ReceiveBuffer, m_Request, boost::beast::bind_front_handler (&TorrentsRPCSession::HandleRequest, shared_from_this())); } void TorrentsRPCSession::HandleRequest (const boost::system::error_code& ecode, size_t bytes_transferred) { if (!ecode) { if (m_Request.method() == boost::beast::http::verb::post) { auto path = m_Request.target (); auto tunnel = m_Server.GetTunnel ( {path.data (), path.size ()}); // boost::beast::string_view to std::string_view if (tunnel) { if (m_Request[boost::beast::http::field::content_type] == "application/json") { #ifdef JSON_SUPPORTED HandleJSONRequest (m_Request.body (), tunnel); #else SendResponse (boost::beast::http::status::not_implemented, std::string ("Your boost version ") + BOOST_LIB_VERSION + " is too old"); #endif } else SendResponse (boost::beast::http::status::no_content); } else SendResponse (boost::beast::http::status::not_found); } else SendResponse (boost::beast::http::status::method_not_allowed); } } void TorrentsRPCSession::SendResponse (boost::beast::http::status result, std::string_view data) { m_Response.version (11); // HTTP/1.1 m_Response.result (result); m_Response.set (boost::beast::http::field::server, "i2pd torents RPC"); m_Response.set(boost::beast::http::field::content_type, (result == boost::beast::http::status::ok) ? "application/json" : "text/plain"); m_Response.body () = data; m_Response.prepare_payload (); boost::beast::http::async_write (m_Socket, m_Response, [s = shared_from_this ()](const boost::system::error_code& ecode, size_t bytes_transferred) { if (ecode) LogPrint (eLogWarning, "TorrentsRPC: Failed to send response: ", ecode.message ()); }); } void TorrentsRPCSession::HandleJSONRequest (std::string_view request, std::shared_ptr tunnel) { #ifdef JSON_SUPPORTED try { auto jsonRequest = boost::json::parse (request).as_object (); auto method = jsonRequest.at ("method").as_string (); if (method == "torrent-add") { auto arguments = jsonRequest.at ("arguments").as_object (); auto b64torrent = arguments.at ("metainfo").as_string (); std::string torrentFileContent; torrentFileContent.resize (boost::beast::detail::base64::decoded_size (b64torrent.size ())); boost::beast::detail::base64::decode (torrentFileContent.data (), b64torrent.data (), b64torrent.size ()); tunnel->AddTorrent (torrentFileContent); } } catch (const std::exception& ex) { LogPrint (eLogInfo, "TorrentsRPC: Failed to parse JSON: ", ex.what ()); SendResponse (boost::beast::http::status::bad_request); } #endif } TorrentsRPCServer::TorrentsRPCServer (uint16_t port): RunnableServiceWithWork ("TRPC"), m_Acceptor (GetService (), boost::asio::ip::tcp::endpoint (boost::asio::ip::make_address ("127.0.0.1"), port)) { } void TorrentsRPCServer::Start () { if (!IsRunning ()) { StartIOService (); Accept (); } } void TorrentsRPCServer::Stop () { if (IsRunning ()) { m_Acceptor.cancel (); StopIOService (); } } void TorrentsRPCServer::AddTunnel (std::string_view path, std::shared_ptr tunnel) { m_Tunnels.emplace (path, tunnel); } std::shared_ptr TorrentsRPCServer::GetTunnel (std::string_view path) const { auto it = m_Tunnels.find (path); if (it != m_Tunnels.end ()) return it->second.lock (); return nullptr; } void TorrentsRPCServer::Accept () { m_Acceptor.async_accept (boost::asio::make_strand (GetService ()), [this](const boost::system::error_code& ecode, boost::asio::ip::tcp::socket socket) { if (!ecode) { LogPrint (eLogDebug, "TorrentsRPC: Accepted incoming request"); auto session = std::make_shared(*this, std::move(socket)); session->ReceiveRequest (); Accept (); } }); } } }