Ring Daemon
Loading...
Searching...
No Matches
transfer_channel_handler.cpp
Go to the documentation of this file.
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
19
20#include <opendht/thread_pool.h>
21#include <charconv>
22
23#include "fileutils.h"
24
25namespace jami {
26
27TransferChannelHandler::TransferChannelHandler(const std::shared_ptr<JamiAccount>& account,
28 dhtnet::ConnectionManager& cm)
30 , account_(account)
31 , connectionManager_(cm)
32{
33 if (auto acc = account_.lock())
34 idPath_ = fileutils::get_data_dir() / acc->getAccountID();
35}
36
38
39void
41 const std::string& /*channelName*/,
42 ConnectCb&& /*cb*/,
43 const std::string& /*connectionType*/,
44 bool /*forceNewConnection*/)
45{
46 throw std::runtime_error("connect is not supported in TransferChannelHandler");
47}
48
49bool
50TransferChannelHandler::onRequest(const std::shared_ptr<dht::crypto::Certificate>& cert, const std::string& name)
51{
52 auto acc = account_.lock();
53 if (!acc || !cert || !cert->issuer)
54 return false;
55 auto cm = acc->convModule(true);
56 if (!cm)
57 return false;
58 auto uri = cert->issuer->getId().toString();
59 // Else, check if it's a profile or file in a conversation.
60 auto idstr = std::string_view(name).substr(DATA_TRANSFER_SCHEME.size());
61 // Remove arguments for now
62 auto sep = idstr.find_last_of('?');
63 idstr = idstr.substr(0, sep);
64 if (idstr == "profile.vcf") {
65 // If it's our profile from another device
66 return uri == acc->getUsername();
67 }
68 sep = idstr.find('/');
69 auto lastSep = idstr.find_last_of('/');
70 auto conversationId = std::string(idstr.substr(0, sep));
71 auto fileHost = idstr.substr(sep + 1, lastSep - sep - 1);
72 auto fileId = idstr.substr(lastSep + 1);
73 if (fileHost == acc->currentDeviceId())
74 return false;
75
76 // Check if peer is member of the conversation
77 if (fileId == fmt::format("{}.vcf", acc->getUsername()) || fileId == "profile.vcf") {
78 // Or a member from the conversation
79 auto members = cm->getConversationMembers(conversationId);
80 return std::find_if(members.begin(), members.end(), [&](auto m) { return m["uri"] == uri; }) != members.end();
81 } else if (fileHost == "profile") {
82 // If a profile is sent, check if it's from another device
83 return uri == acc->getUsername();
84 }
85
86 return cm->onFileChannelRequest(conversationId, uri, std::string(fileId), acc->sha3SumVerify());
87}
88
89void
90TransferChannelHandler::onReady(const std::shared_ptr<dht::crypto::Certificate>&,
91 const std::string& name,
92 std::shared_ptr<dhtnet::ChannelSocket> channel)
93{
94 auto acc = account_.lock();
95 if (!acc)
96 return;
97
98 // Remove scheme
99 auto idstr = name.substr(DATA_TRANSFER_SCHEME.size());
100 // Parse arguments
101 auto sep = idstr.find_last_of('?');
102 std::string arguments;
103 if (sep != std::string::npos) {
104 arguments = idstr.substr(sep + 1);
105 idstr = idstr.substr(0, sep);
106 }
107
108 auto start = 0u, end = 0u;
110 std::string sha3Sum;
111 for (const auto arg : split_string(arguments, '&')) {
112 auto keyVal = split_string(arg, '=');
113 if (keyVal.size() == 2) {
114 if (keyVal[0] == "start") {
115 start = to_int<unsigned>(keyVal[1]);
116 } else if (keyVal[0] == "end") {
117 end = to_int<unsigned>(keyVal[1]);
118 } else if (keyVal[0] == "sha3") {
119 sha3Sum = keyVal[1];
120 } else if (keyVal[0] == "modified") {
121 try {
123 } catch (const std::exception& e) {
124 JAMI_WARNING("TransferChannel: Unable to parse modified date: {}: {}", keyVal[1], e.what());
125 }
126 }
127 }
128 }
129
130 // Check if profile
131 if (idstr == "profile.vcf") {
132 dht::ThreadPool::io().run(
133 [wacc = acc->weak(), path = idPath_ / "profile.vcf", channel, idstr, lastModified, sha3Sum] {
134 if (auto acc = wacc.lock()) {
135 if (!channel->isInitiator()) {
136 // Only accept newest profiles
137 if (lastModified == 0 || lastModified > fileutils::lastWriteTimeInSeconds(acc->profilePath()))
138 acc->dataTransfer()->onIncomingProfile(channel, sha3Sum);
139 else
140 channel->shutdown();
141 } else {
142 // If it's a profile from sync
143 acc->dataTransfer()->transferFile(channel, idstr, "", path.string());
144 }
145 }
146 });
147 return;
148 }
149
150 auto splitted_id = split_string(idstr, '/');
151 if (splitted_id.size() < 3) {
152 JAMI_ERROR("Unsupported ID detected {}", name);
153 channel->shutdown();
154 return;
155 }
156
157 // convId/fileHost/fileId or convId/profile/fileId
158 auto conversationId = std::string(splitted_id[0]);
159 auto fileHost = std::string(splitted_id[1]);
160 auto isContactProfile = splitted_id[1] == "profile";
161 auto fileId = std::string(splitted_id[splitted_id.size() - 1]);
162 if (channel->isInitiator())
163 return;
164
165 // Profile for a member in the conversation
166 dht::ThreadPool::io().run([wacc = acc->weak(),
167 profilePath = idPath_ / "profile.vcf",
168 channel,
169 conversationId,
170 fileId,
172 idstr,
173 start,
174 end,
175 sha3Sum] {
176 if (auto acc = wacc.lock()) {
177 if (fileId == fmt::format("{}.vcf", acc->getUsername())) {
178 acc->dataTransfer()->transferFile(channel, fileId, "", profilePath.string());
179 return;
180 } else if (isContactProfile && fileId.find(".vcf") != std::string::npos) {
181 auto path = acc->dataTransfer()->profilePath(fileId.substr(0, fileId.size() - 4));
182 acc->dataTransfer()->transferFile(channel, fileId, "", path.string());
183 return;
184 } else if (fileId == "profile.vcf") {
185 acc->dataTransfer()->onIncomingProfile(channel, sha3Sum);
186 return;
187 }
188 // Check if it's a file in a conversation
189 auto dt = acc->dataTransfer(conversationId);
190 auto sep = fileId.find('_');
191 if (!dt or sep == std::string::npos) {
192 channel->shutdown();
193 return;
194 }
195 auto interactionId = fileId.substr(0, sep);
196 auto path = dt->path(fileId);
197 dt->transferFile(channel, fileId, interactionId, path.string(), start, end);
198 }
199 });
200}
201
202} // namespace jami
A Channel handler is used to make the link between JamiAccount and ConnectionManager Its role is to m...
void connect(const DeviceId &deviceId, const std::string &channelName, ConnectCb &&cb, const std::string &connectionType="", bool forceNewConnection=false) override
Ask for a new channel This replaces the connectDevice() in jamiaccount.
TransferChannelHandler(const std::shared_ptr< JamiAccount > &acc, dhtnet::ConnectionManager &cm)
void onReady(const std::shared_ptr< dht::crypto::Certificate > &peer, const std::string &name, std::shared_ptr< dhtnet::ChannelSocket > channel) override
Handle socket ready.
bool onRequest(const std::shared_ptr< dht::crypto::Certificate > &peer, const std::string &name) override
Determine if we accept or not the request.
#define JAMI_ERROR(formatstr,...)
Definition logger.h:243
#define JAMI_WARNING(formatstr,...)
Definition logger.h:242
const std::filesystem::path & get_data_dir()
std::function< void(std::shared_ptr< dhtnet::ChannelSocket >, const DeviceId &)> ConnectCb
dht::PkId DeviceId
void emitSignal(Args... args)
Definition jami_signal.h:64
static constexpr std::string_view DATA_TRANSFER_SCHEME
Definition uri.h:27
std::vector< std::string_view > split_string(std::string_view str, char delim)