Ring Daemon 16.0.0
Loading...
Searching...
No Matches
transfer_channel_handler.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2004-2025 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,
45{}
46
47bool
48TransferChannelHandler::onRequest(const std::shared_ptr<dht::crypto::Certificate>& cert,
49 const std::string& name)
50{
51 auto acc = account_.lock();
52 if (!acc || !cert || !cert->issuer)
53 return false;
54 auto cm = acc->convModule(true);
55 if (!cm)
56 return false;
57 auto uri = cert->issuer->getId().toString();
58 // Else, check if it's a profile or file in a conversation.
59 auto idstr = std::string_view(name).substr(DATA_TRANSFER_SCHEME.size());
60 // Remove arguments for now
61 auto sep = idstr.find_last_of('?');
62 idstr = idstr.substr(0, sep);
63 if (idstr == "profile.vcf") {
64 // If it's our profile from another device
65 return uri == acc->getUsername();
66 }
67 sep = idstr.find('/');
68 auto lastSep = idstr.find_last_of('/');
69 auto conversationId = std::string(idstr.substr(0, sep));
70 auto fileHost = idstr.substr(sep + 1, lastSep - sep - 1);
71 auto fileId = idstr.substr(lastSep + 1);
72 if (fileHost == acc->currentDeviceId())
73 return false;
74
75 // Check if peer is member of the conversation
76 if (fileId == fmt::format("{}.vcf", acc->getUsername()) || fileId == "profile.vcf") {
77 // Or a member from the conversation
78 auto members = cm->getConversationMembers(conversationId);
79 return std::find_if(members.begin(), members.end(), [&](auto m) { return m["uri"] == uri; })
80 != 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: {}: {}",
125 keyVal[1],
126 e.what());
127 }
128 }
129 }
130 }
131
132 // Check if profile
133 if (idstr == "profile.vcf") {
134 dht::ThreadPool::io().run([wacc = acc->weak(), path = idPath_ / "profile.vcf", channel, idstr, lastModified, sha3Sum] {
135 if (auto acc = wacc.lock()) {
136 if (!channel->isInitiator()) {
137 // Only accept newest profiles
138 if (lastModified == 0
139 || lastModified > fileutils::lastWriteTimeInSeconds(acc->profilePath()))
140 acc->dataTransfer()->onIncomingProfile(channel, sha3Sum);
141 else
142 channel->shutdown();
143 } else {
144 // If it's a profile from sync
145 acc->dataTransfer()->transferFile(channel, idstr, "", path.string());
146 }
147 }
148 });
149 return;
150 }
151
152 auto splitted_id = split_string(idstr, '/');
153 if (splitted_id.size() < 3) {
154 JAMI_ERROR("Unsupported ID detected {}", name);
155 channel->shutdown();
156 return;
157 }
158
159 // convId/fileHost/fileId or convId/profile/fileId
160 auto conversationId = std::string(splitted_id[0]);
161 auto fileHost = std::string(splitted_id[1]);
162 auto isContactProfile = splitted_id[1] == "profile";
163 auto fileId = std::string(splitted_id[splitted_id.size() - 1]);
164 if (channel->isInitiator())
165 return;
166
167 // Profile for a member in the conversation
168 dht::ThreadPool::io().run([wacc = acc->weak(), profilePath = idPath_ / "profile.vcf", channel, conversationId, fileId, isContactProfile, idstr, start, end, sha3Sum] {
169 if (auto acc = wacc.lock()) {
170 if (fileId == fmt::format("{}.vcf", acc->getUsername())) {
171 acc->dataTransfer()->transferFile(channel, fileId, "", profilePath.string());
172 return;
173 } else if (isContactProfile && fileId.find(".vcf") != std::string::npos) {
174 auto path = acc->dataTransfer()->profilePath(fileId.substr(0, fileId.size() - 4));
175 acc->dataTransfer()->transferFile(channel, fileId, "", path.string());
176 return;
177 } else if (fileId == "profile.vcf") {
178 acc->dataTransfer()->onIncomingProfile(channel, sha3Sum);
179 return;
180 }
181 // Check if it's a file in a conversation
182 auto dt = acc->dataTransfer(conversationId);
183 auto sep = fileId.find('_');
184 if (!dt or sep == std::string::npos) {
185 channel->shutdown();
186 return;
187 }
188 auto interactionId = fileId.substr(0, sep);
189 auto path = dt->path(fileId);
190 dt->transferFile(channel, fileId, interactionId, path.string(), start, end);
191 }
192 });
193}
194
195} // 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:228
#define JAMI_WARNING(formatstr,...)
Definition logger.h:227
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 ring_signal.h:64
static constexpr std::string_view DATA_TRANSFER_SCHEME
Definition uri.h:26
std::vector< std::string_view > split_string(std::string_view str, char delim)