LCOV - code coverage report
Current view: top level - foo/src/jamidht - transfer_channel_handler.cpp (source / functions) Hit Total Coverage
Test: jami-coverage-filtered.info Lines: 97 112 86.6 %
Date: 2026-02-28 10:41:24 Functions: 8 13 61.5 %

          Line data    Source code
       1             : /*
       2             :  *  Copyright (C) 2004-2026 Savoir-faire Linux Inc.
       3             :  *
       4             :  *  This program is free software: you can redistribute it and/or modify
       5             :  *  it under the terms of the GNU General Public License as published by
       6             :  *  the Free Software Foundation, either version 3 of the License, or
       7             :  *  (at your option) any later version.
       8             :  *
       9             :  *  This program is distributed in the hope that it will be useful,
      10             :  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
      12             :  *  GNU General Public License for more details.
      13             :  *
      14             :  *  You should have received a copy of the GNU General Public License
      15             :  *  along with this program. If not, see <https://www.gnu.org/licenses/>.
      16             :  */
      17             : 
      18             : #include "jamidht/transfer_channel_handler.h"
      19             : 
      20             : #include <opendht/thread_pool.h>
      21             : #include <charconv>
      22             : 
      23             : #include "fileutils.h"
      24             : 
      25             : namespace jami {
      26             : 
      27         668 : TransferChannelHandler::TransferChannelHandler(const std::shared_ptr<JamiAccount>& account,
      28         668 :                                                dhtnet::ConnectionManager& cm)
      29             :     : ChannelHandlerInterface()
      30         668 :     , account_(account)
      31         668 :     , connectionManager_(cm)
      32             : {
      33         668 :     if (auto acc = account_.lock())
      34         668 :         idPath_ = fileutils::get_data_dir() / acc->getAccountID();
      35         668 : }
      36             : 
      37        1336 : TransferChannelHandler::~TransferChannelHandler() {}
      38             : 
      39             : void
      40           0 : TransferChannelHandler::connect(const DeviceId& /*deviceId*/,
      41             :                                 const std::string& /*channelName*/,
      42             :                                 ConnectCb&& /*cb*/,
      43             :                                 const std::string& /*connectionType*/,
      44             :                                 bool /*forceNewConnection*/)
      45             : {
      46           0 :     throw std::runtime_error("connect is not supported in TransferChannelHandler");
      47             : }
      48             : 
      49             : bool
      50          69 : TransferChannelHandler::onRequest(const std::shared_ptr<dht::crypto::Certificate>& cert, const std::string& name)
      51             : {
      52          69 :     auto acc = account_.lock();
      53          69 :     if (!acc || !cert || !cert->issuer)
      54           0 :         return false;
      55          69 :     auto cm = acc->convModule(true);
      56          69 :     if (!cm)
      57           0 :         return false;
      58          69 :     auto uri = cert->issuer->getId().toString();
      59             :     // Else, check if it's a profile or file in a conversation.
      60          69 :     auto idstr = std::string_view(name).substr(DATA_TRANSFER_SCHEME.size());
      61             :     // Remove arguments for now
      62          69 :     auto sep = idstr.find_last_of('?');
      63          69 :     idstr = idstr.substr(0, sep);
      64          69 :     if (idstr == "profile.vcf") {
      65             :         // If it's our profile from another device
      66           2 :         return uri == acc->getUsername();
      67             :     }
      68          67 :     sep = idstr.find('/');
      69          67 :     auto lastSep = idstr.find_last_of('/');
      70          67 :     auto conversationId = std::string(idstr.substr(0, sep));
      71          67 :     auto fileHost = idstr.substr(sep + 1, lastSep - sep - 1);
      72          67 :     auto fileId = idstr.substr(lastSep + 1);
      73          67 :     if (fileHost == acc->currentDeviceId())
      74           0 :         return false;
      75             : 
      76             :     // Check if peer is member of the conversation
      77         134 :     if (fileId == fmt::format("{}.vcf", acc->getUsername()) || fileId == "profile.vcf") {
      78             :         // Or a member from the conversation
      79          42 :         auto members = cm->getConversationMembers(conversationId);
      80         123 :         return std::find_if(members.begin(), members.end(), [&](auto m) { return m["uri"] == uri; }) != members.end();
      81          67 :     } else if (fileHost == "profile") {
      82             :         // If a profile is sent, check if it's from another device
      83          12 :         return uri == acc->getUsername();
      84             :     }
      85             : 
      86          13 :     return cm->onFileChannelRequest(conversationId, uri, std::string(fileId), acc->sha3SumVerify());
      87          69 : }
      88             : 
      89             : void
      90         130 : TransferChannelHandler::onReady(const std::shared_ptr<dht::crypto::Certificate>&,
      91             :                                 const std::string& name,
      92             :                                 std::shared_ptr<dhtnet::ChannelSocket> channel)
      93             : {
      94         130 :     auto acc = account_.lock();
      95         131 :     if (!acc)
      96           0 :         return;
      97             : 
      98             :     // Remove scheme
      99         131 :     auto idstr = name.substr(DATA_TRANSFER_SCHEME.size());
     100             :     // Parse arguments
     101         131 :     auto sep = idstr.find_last_of('?');
     102         131 :     std::string arguments;
     103         131 :     if (sep != std::string::npos) {
     104          16 :         arguments = idstr.substr(sep + 1);
     105          16 :         idstr = idstr.substr(0, sep);
     106             :     }
     107             : 
     108         131 :     auto start = 0u, end = 0u;
     109         131 :     uint64_t lastModified = 0;
     110         131 :     std::string sha3Sum;
     111         163 :     for (const auto arg : split_string(arguments, '&')) {
     112          32 :         auto keyVal = split_string(arg, '=');
     113          32 :         if (keyVal.size() == 2) {
     114          32 :             if (keyVal[0] == "start") {
     115           4 :                 start = to_int<unsigned>(keyVal[1]);
     116          28 :             } else if (keyVal[0] == "end") {
     117           4 :                 end = to_int<unsigned>(keyVal[1]);
     118          24 :             } else if (keyVal[0] == "sha3") {
     119          12 :                 sha3Sum = keyVal[1];
     120          12 :             } else if (keyVal[0] == "modified") {
     121             :                 try {
     122          12 :                     lastModified = to_int<uint64_t>(keyVal[1]);
     123           0 :                 } catch (const std::exception& e) {
     124           0 :                     JAMI_WARNING("TransferChannel: Unable to parse modified date: {}: {}", keyVal[1], e.what());
     125           0 :                 }
     126             :             }
     127             :         }
     128         161 :     }
     129             : 
     130             :     // Check if profile
     131         129 :     if (idstr == "profile.vcf") {
     132           8 :         dht::ThreadPool::io().run(
     133           4 :             [wacc = acc->weak(), path = idPath_ / "profile.vcf", channel, idstr, lastModified, sha3Sum] {
     134           4 :                 if (auto acc = wacc.lock()) {
     135           4 :                     if (!channel->isInitiator()) {
     136             :                         // Only accept newest profiles
     137           2 :                         if (lastModified == 0 || lastModified > fileutils::lastWriteTimeInSeconds(acc->profilePath()))
     138           2 :                             acc->dataTransfer()->onIncomingProfile(channel, sha3Sum);
     139             :                         else
     140           0 :                             channel->shutdown();
     141             :                     } else {
     142             :                         // If it's a profile from sync
     143           2 :                         acc->dataTransfer()->transferFile(channel, idstr, "", path.string());
     144             :                     }
     145           4 :                 }
     146           4 :             });
     147           4 :         return;
     148             :     }
     149             : 
     150         127 :     auto splitted_id = split_string(idstr, '/');
     151         127 :     if (splitted_id.size() < 3) {
     152           0 :         JAMI_ERROR("Unsupported ID detected {}", name);
     153           0 :         channel->shutdown();
     154           0 :         return;
     155             :     }
     156             : 
     157             :     // convId/fileHost/fileId or convId/profile/fileId
     158         127 :     auto conversationId = std::string(splitted_id[0]);
     159         127 :     auto fileHost = std::string(splitted_id[1]);
     160         126 :     auto isContactProfile = splitted_id[1] == "profile";
     161         125 :     auto fileId = std::string(splitted_id[splitted_id.size() - 1]);
     162         126 :     if (channel->isInitiator())
     163          62 :         return;
     164             : 
     165             :     // Profile for a member in the conversation
     166         130 :     dht::ThreadPool::io().run([wacc = acc->weak(),
     167          65 :                                profilePath = idPath_ / "profile.vcf",
     168             :                                channel,
     169             :                                conversationId,
     170             :                                fileId,
     171             :                                isContactProfile,
     172             :                                idstr,
     173             :                                start,
     174             :                                end,
     175          40 :                                sha3Sum] {
     176          65 :         if (auto acc = wacc.lock()) {
     177         130 :             if (fileId == fmt::format("{}.vcf", acc->getUsername())) {
     178          37 :                 acc->dataTransfer()->transferFile(channel, fileId, "", profilePath.string());
     179          53 :                 return;
     180          28 :             } else if (isContactProfile && fileId.find(".vcf") != std::string::npos) {
     181          24 :                 auto path = acc->dataTransfer()->profilePath(fileId.substr(0, fileId.size() - 4));
     182          12 :                 acc->dataTransfer()->transferFile(channel, fileId, "", path.string());
     183          12 :                 return;
     184          28 :             } else if (fileId == "profile.vcf") {
     185           4 :                 acc->dataTransfer()->onIncomingProfile(channel, sha3Sum);
     186           4 :                 return;
     187             :             }
     188             :             // Check if it's a file in a conversation
     189          12 :             auto dt = acc->dataTransfer(conversationId);
     190          12 :             auto sep = fileId.find('_');
     191          12 :             if (!dt or sep == std::string::npos) {
     192           0 :                 channel->shutdown();
     193           0 :                 return;
     194             :             }
     195          12 :             auto interactionId = fileId.substr(0, sep);
     196          12 :             auto path = dt->path(fileId);
     197          12 :             dt->transferFile(channel, fileId, interactionId, path.string(), start, end);
     198          77 :         }
     199             :     });
     200         575 : }
     201             : 
     202             : } // namespace jami

Generated by: LCOV version 1.14