Line data Source code
1 : /*
2 : * Copyright (C) 2004-2024 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 <charconv>
21 :
22 : #include "fileutils.h"
23 :
24 : namespace jami {
25 :
26 678 : TransferChannelHandler::TransferChannelHandler(const std::shared_ptr<JamiAccount>& account,
27 678 : dhtnet::ConnectionManager& cm)
28 : : ChannelHandlerInterface()
29 678 : , account_(account)
30 678 : , connectionManager_(cm)
31 : {
32 678 : if (auto acc = account_.lock())
33 678 : idPath_ = fileutils::get_data_dir() / acc->getAccountID();
34 678 : }
35 :
36 1356 : TransferChannelHandler::~TransferChannelHandler() {}
37 :
38 : void
39 0 : TransferChannelHandler::connect(const DeviceId& deviceId,
40 : const std::string& channelName,
41 : ConnectCb&& cb,
42 : const std::string& connectionType,
43 : bool forceNewConnection)
44 0 : {}
45 :
46 : bool
47 67 : TransferChannelHandler::onRequest(const std::shared_ptr<dht::crypto::Certificate>& cert,
48 : const std::string& name)
49 : {
50 67 : auto acc = account_.lock();
51 67 : if (!acc || !cert || !cert->issuer)
52 0 : return false;
53 67 : auto cm = acc->convModule(true);
54 67 : if (!cm)
55 0 : return false;
56 67 : auto uri = cert->issuer->getId().toString();
57 : // Else, check if it's a profile or file in a conversation.
58 67 : auto idstr = std::string_view(name).substr(DATA_TRANSFER_SCHEME.size());
59 : // Remove arguments for now
60 67 : auto sep = idstr.find_last_of('?');
61 67 : idstr = idstr.substr(0, sep);
62 67 : if (idstr == "profile.vcf") {
63 : // If it's our profile from another device
64 2 : return uri == acc->getUsername();
65 : }
66 65 : sep = idstr.find('/');
67 65 : auto lastSep = idstr.find_last_of('/');
68 65 : auto conversationId = std::string(idstr.substr(0, sep));
69 65 : auto fileHost = idstr.substr(sep + 1, lastSep - sep - 1);
70 65 : auto fileId = idstr.substr(lastSep + 1);
71 65 : if (fileHost == acc->currentDeviceId())
72 0 : return false;
73 :
74 : // Check if peer is member of the conversation
75 130 : if (fileId == fmt::format("{}.vcf", acc->getUsername()) || fileId == "profile.vcf") {
76 : // Or a member from the conversation
77 42 : auto members = cm->getConversationMembers(conversationId);
78 120 : return std::find_if(members.begin(), members.end(), [&](auto m) { return m["uri"] == uri; })
79 84 : != members.end();
80 65 : } else if (fileHost == "profile") {
81 : // If a profile is sent, check if it's from another device
82 10 : return uri == acc->getUsername();
83 : }
84 :
85 13 : return cm->onFileChannelRequest(conversationId, uri, std::string(fileId), acc->sha3SumVerify());
86 67 : }
87 :
88 : void
89 126 : TransferChannelHandler::onReady(const std::shared_ptr<dht::crypto::Certificate>&,
90 : const std::string& name,
91 : std::shared_ptr<dhtnet::ChannelSocket> channel)
92 : {
93 126 : auto acc = account_.lock();
94 126 : if (!acc)
95 0 : return;
96 :
97 : // Remove scheme
98 126 : auto idstr = name.substr(DATA_TRANSFER_SCHEME.size());
99 : // Parse arguments
100 126 : auto sep = idstr.find_last_of('?');
101 126 : std::string arguments;
102 126 : if (sep != std::string::npos) {
103 14 : arguments = idstr.substr(sep + 1);
104 14 : idstr = idstr.substr(0, sep);
105 : }
106 :
107 126 : auto start = 0u, end = 0u;
108 126 : uint64_t lastModified = 0;
109 126 : std::string sha3Sum;
110 154 : for (const auto arg : split_string(arguments, '&')) {
111 28 : auto keyVal = split_string(arg, '=');
112 28 : if (keyVal.size() == 2) {
113 28 : if (keyVal[0] == "start") {
114 0 : start = to_int<unsigned>(keyVal[1]);
115 28 : } else if (keyVal[0] == "end") {
116 0 : end = to_int<unsigned>(keyVal[1]);
117 28 : } else if (keyVal[0] == "sha3") {
118 14 : sha3Sum = keyVal[1];
119 14 : } else if (keyVal[0] == "modified") {
120 : try {
121 14 : lastModified = jami::to_int<uint64_t>(keyVal[1]);
122 0 : } catch (const std::exception& e) {
123 0 : JAMI_WARNING("TransferChannel: Unable to parse modified date: {}: {}",
124 : keyVal[1],
125 : e.what());
126 0 : }
127 : }
128 : }
129 154 : }
130 :
131 : // Check if profile
132 126 : if (idstr == "profile.vcf") {
133 4 : if (!channel->isInitiator()) {
134 : // Only accept newest profiles
135 0 : if (lastModified == 0
136 4 : || lastModified > fileutils::lastWriteTimeInSeconds(acc->profilePath()))
137 0 : acc->dataTransfer()->onIncomingProfile(channel, sha3Sum);
138 : else
139 0 : channel->shutdown();
140 : } else {
141 : // If it's a profile from sync
142 2 : auto path = idPath_ / "profile.vcf";
143 2 : acc->dataTransfer()->transferFile(channel, idstr, "", path.string());
144 2 : }
145 2 : return;
146 : }
147 :
148 122 : auto splitted_id = split_string(idstr, '/');
149 122 : if (splitted_id.size() < 3) {
150 0 : JAMI_ERR() << "Unsupported ID detected " << name;
151 0 : channel->shutdown();
152 0 : return;
153 : }
154 :
155 : // convId/fileHost/fileId or convId/profile/fileId
156 122 : auto conversationId = std::string(splitted_id[0]);
157 122 : auto fileHost = std::string(splitted_id[1]);
158 122 : auto isContactProfile = splitted_id[1] == "profile";
159 122 : auto fileId = std::string(splitted_id[splitted_id.size() - 1]);
160 122 : if (channel->isInitiator())
161 61 : return;
162 :
163 : // Profile for a member in the conversation
164 122 : if (fileId == fmt::format("{}.vcf", acc->getUsername())) {
165 36 : auto path = idPath_ / "profile.vcf";
166 36 : acc->dataTransfer()->transferFile(channel, fileId, "", path.string());
167 36 : return;
168 61 : } else if (isContactProfile && fileId.find(".vcf") != std::string::npos) {
169 18 : auto path = acc->dataTransfer()->profilePath(fileId.substr(0, fileId.size() - 4));
170 9 : acc->dataTransfer()->transferFile(channel, fileId, "", path.string());
171 9 : return;
172 25 : } else if (fileId == "profile.vcf") {
173 5 : acc->dataTransfer()->onIncomingProfile(channel, sha3Sum);
174 5 : return;
175 : }
176 : // Check if it's a file in a conversation
177 11 : auto dt = acc->dataTransfer(conversationId);
178 11 : sep = fileId.find('_');
179 11 : if (!dt or sep == std::string::npos) {
180 0 : channel->shutdown();
181 0 : return;
182 : }
183 11 : auto interactionId = fileId.substr(0, sep);
184 11 : auto path = dt->path(fileId);
185 11 : dt->transferFile(channel, fileId, interactionId, path.string(), start, end);
186 915 : }
187 :
188 : } // namespace jami
|