LCOV - code coverage report
Current view: top level - src/jamidht - transfer_channel_handler.cpp (source / functions) Coverage Total Hit
Test: jami-coverage-filtered.info Lines: 83.1 % 124 103
Test Date: 2026-09-13 09:08:58 Functions: 90.0 % 10 9

            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              : #include "jamidht/conversationrepository.h"
      20              : #include "data_transfer.h"
      21              : 
      22              : #include <opendht/thread_pool.h>
      23              : #include <charconv>
      24              : 
      25              : #include "fileutils.h"
      26              : 
      27              : namespace jami {
      28              : 
      29              : namespace {
      30              : 
      31              : /** "<uri>.vcf" for a member's profile: the uri must be an account hash */
      32              : bool
      33           26 : isMemberProfileName(std::string_view fileId)
      34              : {
      35           26 :     constexpr std::string_view SUFFIX = ".vcf";
      36           26 :     if (fileId.size() != 40 + SUFFIX.size() || !fileId.ends_with(SUFFIX))
      37            0 :         return false;
      38           26 :     return static_cast<bool>(dht::InfoHash(fileId.substr(0, 40)));
      39              : }
      40              : 
      41              : } // namespace
      42              : 
      43          796 : TransferChannelHandler::TransferChannelHandler(const std::shared_ptr<JamiAccount>& account,
      44          796 :                                                dhtnet::ConnectionManager& cm)
      45              :     : ChannelHandlerInterface()
      46          796 :     , account_(account)
      47          796 :     , connectionManager_(cm)
      48              : {
      49          796 :     if (auto acc = account_.lock())
      50          796 :         idPath_ = fileutils::get_data_dir() / acc->getAccountID();
      51          796 : }
      52              : 
      53         1592 : TransferChannelHandler::~TransferChannelHandler() {}
      54              : 
      55              : void
      56            0 : TransferChannelHandler::connect(const DeviceId& /*deviceId*/,
      57              :                                 const std::string& /*channelName*/,
      58              :                                 ConnectCb&& /*cb*/,
      59              :                                 const std::string& /*connectionType*/,
      60              :                                 bool /*forceNewConnection*/)
      61              : {
      62            0 :     throw std::runtime_error("connect is not supported in TransferChannelHandler");
      63              : }
      64              : 
      65              : bool
      66           84 : TransferChannelHandler::onRequest(const std::shared_ptr<dht::crypto::Certificate>& cert, const std::string& name)
      67              : {
      68           84 :     auto acc = account_.lock();
      69           84 :     if (!acc || !cert || !cert->issuer)
      70            0 :         return false;
      71           84 :     auto cm = acc->convModule(true);
      72           84 :     if (!cm)
      73            0 :         return false;
      74           84 :     auto uri = cert->issuer->getId().toString();
      75              :     // Else, check if it's a profile or file in a conversation.
      76           84 :     auto idstr = std::string_view(name).substr(DATA_TRANSFER_SCHEME.size());
      77              :     // Remove arguments for now
      78           84 :     auto sep = idstr.find_last_of('?');
      79           84 :     idstr = idstr.substr(0, sep);
      80           84 :     if (idstr == "profile.vcf") {
      81              :         // If it's our profile from another device
      82            2 :         return uri == acc->getUsername();
      83              :     }
      84           82 :     sep = idstr.find('/');
      85           82 :     auto lastSep = idstr.find_last_of('/');
      86           82 :     if (sep == std::string_view::npos || lastSep == sep)
      87            0 :         return false;
      88           82 :     auto conversationId = std::string(idstr.substr(0, sep));
      89           82 :     auto fileHost = idstr.substr(sep + 1, lastSep - sep - 1);
      90           82 :     auto fileId = idstr.substr(lastSep + 1);
      91           82 :     if (fileHost == acc->currentDeviceId())
      92            0 :         return false;
      93              :     // Both ids end up in filesystem paths
      94           82 :     if (!ConversationRepository::isValidConversationId(conversationId))
      95            0 :         return false;
      96              : 
      97              :     // Check if peer is member of the conversation
      98          164 :     if (fileId == fmt::format("{}.vcf", acc->getUsername()) || fileId == "profile.vcf") {
      99              :         // Or a member from the conversation
     100           55 :         auto members = cm->getConversationMembers(conversationId);
     101          376 :         return std::find_if(members.begin(), members.end(), [&](auto m) { return m["uri"] == uri; }) != members.end();
     102           82 :     } else if (fileHost == "profile") {
     103              :         // If a profile is sent, check if it's from another device
     104           13 :         return uri == acc->getUsername() && isMemberProfileName(fileId);
     105              :     }
     106              : 
     107           14 :     if (!isValidFileId(fileId))
     108            0 :         return false;
     109           42 :     return cm->onFileChannelRequest(conversationId, uri, std::string(fileId), acc->sha3SumVerify());
     110           84 : }
     111              : 
     112              : void
     113          159 : TransferChannelHandler::onReady(const std::shared_ptr<dht::crypto::Certificate>&,
     114              :                                 const std::string& name,
     115              :                                 std::shared_ptr<dhtnet::ChannelSocket> channel)
     116              : {
     117          159 :     auto acc = account_.lock();
     118          160 :     if (!acc)
     119            0 :         return;
     120              : 
     121              :     // Remove scheme
     122          160 :     auto idstr = name.substr(DATA_TRANSFER_SCHEME.size());
     123              :     // Parse arguments
     124          160 :     auto sep = idstr.find_last_of('?');
     125          160 :     std::string arguments;
     126          159 :     if (sep != std::string::npos) {
     127           21 :         arguments = idstr.substr(sep + 1);
     128           22 :         idstr = idstr.substr(0, sep);
     129              :     }
     130              : 
     131          160 :     auto start = 0u, end = 0u;
     132          160 :     uint64_t lastModified = 0;
     133          160 :     std::string sha3Sum;
     134          203 :     for (const auto arg : split_string(arguments, '&')) {
     135           43 :         auto keyVal = split_string(arg, '=');
     136           43 :         if (keyVal.size() == 2) {
     137           43 :             if (keyVal[0] == "start") {
     138            4 :                 start = to_int<unsigned>(keyVal[1]);
     139           40 :             } else if (keyVal[0] == "end") {
     140            4 :                 end = to_int<unsigned>(keyVal[1]);
     141           36 :             } else if (keyVal[0] == "sha3") {
     142           18 :                 sha3Sum = keyVal[1];
     143           18 :             } else if (keyVal[0] == "modified") {
     144              :                 try {
     145           18 :                     lastModified = to_int<uint64_t>(keyVal[1]);
     146            0 :                 } catch (const std::exception& e) {
     147            0 :                     JAMI_WARNING("TransferChannel: Unable to parse modified date: {}: {}", keyVal[1], e.what());
     148            0 :                 }
     149              :             }
     150              :         }
     151          202 :     }
     152              : 
     153              :     // Check if profile
     154          159 :     if (idstr == "profile.vcf") {
     155            8 :         dht::ThreadPool::io().run(
     156            8 :             [wacc = acc->weak(), path = idPath_ / "profile.vcf", channel, idstr, lastModified, sha3Sum] {
     157            4 :                 if (auto acc = wacc.lock()) {
     158            4 :                     if (!channel->isInitiator()) {
     159              :                         // Only accept newest profiles
     160            2 :                         if (lastModified == 0 || lastModified > fileutils::lastWriteTimeInSeconds(acc->profilePath()))
     161            6 :                             acc->dataTransfer()->onIncomingProfile(channel, sha3Sum);
     162              :                         else
     163            0 :                             channel->shutdown();
     164              :                     } else {
     165              :                         // If it's a profile from sync
     166           10 :                         acc->dataTransfer()->transferFile(channel, idstr, "", path.string());
     167              :                     }
     168            4 :                 }
     169            4 :             });
     170            4 :         return;
     171              :     }
     172              : 
     173          155 :     auto splitted_id = split_string(idstr, '/');
     174          156 :     if (splitted_id.size() < 3) {
     175            0 :         JAMI_ERROR("Unsupported ID detected {}", name);
     176            0 :         channel->shutdown();
     177            0 :         return;
     178              :     }
     179              : 
     180              :     // convId/fileHost/fileId or convId/profile/fileId
     181          312 :     auto conversationId = std::string(splitted_id[0]);
     182          156 :     auto fileHost = std::string(splitted_id[1]);
     183          156 :     auto isContactProfile = splitted_id[1] == "profile";
     184          156 :     auto fileId = std::string(splitted_id[splitted_id.size() - 1]);
     185          156 :     if (channel->isInitiator())
     186           78 :         return;
     187              : 
     188              :     // Profile for a member in the conversation
     189          156 :     dht::ThreadPool::io().run([wacc = acc->weak(),
     190           78 :                                profilePath = idPath_ / "profile.vcf",
     191              :                                channel,
     192              :                                conversationId,
     193              :                                fileId,
     194              :                                isContactProfile,
     195              :                                idstr,
     196              :                                start,
     197              :                                end,
     198              :                                sha3Sum] {
     199           78 :         if (auto acc = wacc.lock()) {
     200          156 :             if (fileId == fmt::format("{}.vcf", acc->getUsername())) {
     201          180 :                 acc->dataTransfer()->transferFile(channel, fileId, "", profilePath.string());
     202           65 :                 return;
     203           33 :             } else if (isContactProfile && isMemberProfileName(fileId)) {
     204           26 :                 auto path = acc->dataTransfer()->profilePath(fileId.substr(0, fileId.size() - 4));
     205           52 :                 acc->dataTransfer()->transferFile(channel, fileId, "", path.string());
     206           13 :                 return;
     207           33 :             } else if (fileId == "profile.vcf") {
     208           14 :                 acc->dataTransfer()->onIncomingProfile(channel, sha3Sum);
     209            7 :                 return;
     210              :             }
     211              :             // Check if it's a file in a conversation
     212           13 :             if (!ConversationRepository::isValidConversationId(conversationId) || !isValidFileId(fileId)) {
     213            0 :                 channel->shutdown();
     214            0 :                 return;
     215              :             }
     216           13 :             auto dt = acc->dataTransfer(conversationId);
     217           13 :             if (!dt) {
     218            0 :                 channel->shutdown();
     219            0 :                 return;
     220              :             }
     221           13 :             auto interactionId = fileId.substr(0, fileId.find('_'));
     222           13 :             auto path = dt->path(fileId);
     223           13 :             dt->transferFile(channel, fileId, interactionId, path.string(), start, end);
     224           91 :         }
     225              :     });
     226          718 : }
     227              : 
     228              : } // namespace jami
        

Generated by: LCOV version 2.0-1