blob: 4e310d8ac6731c8d583c333e369209afc23b1750 [file] [log] [blame]
kkostiuk74d1ae42021-06-17 11:10:15 -04001/*
2 * Copyright (C) 2021-2022 Savoir-faire Linux Inc.
3 *
4 * Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 */
20
21#import "Adapter.h"
22#import "Utils.h"
23#import "jamiNotificationExtension-Swift.h"
24
25#import "jami/jami.h"
26#import "jami/configurationmanager_interface.h"
27#import "jami/callmanager_interface.h"
28#import "jami/conversation_interface.h"
29#import "jami/datatransfer_interface.h"
30
31#define MSGPACK_DISABLE_LEGACY_NIL
32#import "opendht/crypto.h"
33#import "opendht/default_types.h"
kkostiuke10e6572022-07-26 15:41:12 -040034#import "yaml-cpp/yaml.h"
kkostiuk74d1ae42021-06-17 11:10:15 -040035
36#import "json/json.h"
37#import "fstream"
38#import "charconv"
39
40@implementation Adapter
41
42static id<AdapterDelegate> _delegate;
43
Kateryna Kostiukc3c00162022-11-10 11:21:14 -050044using namespace libjami;
kkostiuk74d1ae42021-06-17 11:10:15 -040045
46struct PeerConnectionRequest : public dht::EncryptedValue<PeerConnectionRequest>
47{
48 static const constexpr dht::ValueType& TYPE = dht::ValueType::USER_DATA;
49 static constexpr const char* key_prefix = "peer:";
50 dht::Value::Id id = dht::Value::INVALID_ID;
51 std::string ice_msg {};
52 bool isAnswer {false};
53 std::string connType {};
54 MSGPACK_DEFINE_MAP(id, ice_msg, isAnswer, connType)
55};
56
kkostiuk74d1ae42021-06-17 11:10:15 -040057// Constants
58const std::string fileSeparator = "/";
59NSString* const certificates = @"certificates";
60NSString* const crls = @"crls";
61NSString* const ocsp = @"ocsp";
kkostiuke10e6572022-07-26 15:41:12 -040062NSString* const nameCache = @"namecache";
63NSString* const defaultNameServer = @"ns.jami.net";
64std::string const nameServerConfiguration = "RingNS.uri";
65NSString* const accountConfig = @"config.yml";
kkostiuk74d1ae42021-06-17 11:10:15 -040066
67std::map<std::string, std::shared_ptr<CallbackWrapperBase>> confHandlers;
kkostiuke10e6572022-07-26 15:41:12 -040068std::map<std::string, std::string> cachedNames;
69std::map<std::string, std::string> nameServers;
kkostiuk74d1ae42021-06-17 11:10:15 -040070
71#pragma mark Callbacks registration
72- (void)registerSignals
73{
74 confHandlers.insert(exportable_callback<ConfigurationSignal::GetAppDataPath>(
Kateryna Kostiuk84834422023-04-11 10:53:56 -040075 [](const std::string& name, std::vector<std::string>* ret) {
kkostiuk74d1ae42021-06-17 11:10:15 -040076 if (name == "cache") {
77 auto path = [Constants cachesPath];
78 ret->push_back(std::string([path.path UTF8String]));
79 } else {
80 auto path = [Constants documentsPath];
81 ret->push_back(std::string([path.path UTF8String]));
82 }
83 }));
84
85 confHandlers.insert(exportable_callback<ConversationSignal::MessageReceived>(
Kateryna Kostiuk84834422023-04-11 10:53:56 -040086 [weakDelegate = Adapter.delegate](const std::string& accountId,
kkostiuk74d1ae42021-06-17 11:10:15 -040087 const std::string& conversationId,
88 std::map<std::string, std::string> message) {
Kateryna Kostiuk84834422023-04-11 10:53:56 -040089 id<AdapterDelegate> delegate = weakDelegate;
90 if (delegate) {
kkostiuk74d1ae42021-06-17 11:10:15 -040091 NSString* convId = [NSString stringWithUTF8String:conversationId.c_str()];
92 NSString* account = [NSString stringWithUTF8String:accountId.c_str()];
93 NSMutableDictionary* interaction = [Utils mapToDictionnary:message];
Kateryna Kostiuk84834422023-04-11 10:53:56 -040094 [delegate newInteractionWithConversationId:convId
kkostiuk74d1ae42021-06-17 11:10:15 -040095 accountId:account
96 message:interaction];
97 }
98 }));
99
100 confHandlers.insert(exportable_callback<DataTransferSignal::DataTransferEvent>(
Kateryna Kostiuk84834422023-04-11 10:53:56 -0400101 [weakDelegate = Adapter.delegate](const std::string& account_id,
kkostiuk74d1ae42021-06-17 11:10:15 -0400102 const std::string& conversation_id,
103 const std::string& interaction_id,
104 const std::string& file_id,
105 int eventCode) {
Kateryna Kostiuk84834422023-04-11 10:53:56 -0400106 id<AdapterDelegate> delegate = weakDelegate;
107 if (delegate) {
kkostiuk74d1ae42021-06-17 11:10:15 -0400108 NSString* accountId = [NSString stringWithUTF8String:account_id.c_str()];
109 NSString* conversationId = [NSString stringWithUTF8String:conversation_id.c_str()];
110 NSString* fileId = [NSString stringWithUTF8String:file_id.c_str()];
111 NSString* interactionId = [NSString stringWithUTF8String:interaction_id.c_str()];
Kateryna Kostiuk84834422023-04-11 10:53:56 -0400112 [delegate dataTransferEventWithFileId:fileId
kkostiuk74d1ae42021-06-17 11:10:15 -0400113 withEventCode:eventCode
114 accountId:accountId
115 conversationId:conversationId
116 interactionId:interactionId];
117 }
118 }));
119
120 confHandlers.insert(exportable_callback<ConversationSignal::ConversationSyncFinished>(
Kateryna Kostiuk84834422023-04-11 10:53:56 -0400121 [weakDelegate = Adapter.delegate](const std::string& account_id) {
122 id<AdapterDelegate> delegate = weakDelegate;
123 if (delegate) {
kkostiuk74d1ae42021-06-17 11:10:15 -0400124 NSString* accountId = [NSString stringWithUTF8String:account_id.c_str()];
Kateryna Kostiuk84834422023-04-11 10:53:56 -0400125 [delegate conversationSyncCompletedWithAccountId:accountId];
kkostiuk74d1ae42021-06-17 11:10:15 -0400126 }
127 }));
128
Kateryna Kostiuk84834422023-04-11 10:53:56 -0400129 confHandlers.insert(exportable_callback<ConversationSignal::ConversationRequestReceived>([weakDelegate = Adapter.delegate](const std::string& accountId, const std::string& conversationId, std::map<std::string, std::string> metadata) {
130 id<AdapterDelegate> delegate = weakDelegate;
131 if (delegate) {
Kateryna Kostiuk2648aab2022-08-30 14:24:03 -0400132 NSString* accountIdStr = [NSString stringWithUTF8String:accountId.c_str()];
133 NSString* convIdStr = [NSString stringWithUTF8String:conversationId.c_str()];
134 NSMutableDictionary* info = [Utils mapToDictionnary: metadata];
Kateryna Kostiuk84834422023-04-11 10:53:56 -0400135 [delegate receivedConversationRequestWithAccountId: accountIdStr conversationId: convIdStr metadata:info];
Kateryna Kostiuk2648aab2022-08-30 14:24:03 -0400136 }
137 }));
kkostiuk74d1ae42021-06-17 11:10:15 -0400138 registerSignalHandlers(confHandlers);
139}
140
141#pragma mark AdapterDelegate
142+ (id<AdapterDelegate>)delegate
143{
144 return _delegate;
145}
146
147+ (void)setDelegate:(id<AdapterDelegate>)delegate
148{
149 _delegate = delegate;
150}
151
152- (bool)downloadFileWithFileId:(NSString*)fileId
153 accountId:(NSString*)accountId
154 conversationId:(NSString*)conversationId
155 interactionId:(NSString*)interactionId
156 withFilePath:(NSString*)filePath
157{
158 return downloadFile(std::string([accountId UTF8String]),
159 std::string([conversationId UTF8String]),
160 std::string([interactionId UTF8String]),
161 std::string([fileId UTF8String]),
162 std::string([filePath UTF8String]));
163}
164
Kateryna Kostiuk19437652022-08-02 13:02:21 -0400165- (BOOL)start:(NSString*)accountId
kkostiuk74d1ae42021-06-17 11:10:15 -0400166{
167 [self registerSignals];
Kateryna Kostiukc3c00162022-11-10 11:21:14 -0500168 if (initialized() == true) {
Kateryna Kostiukdfee1c92023-03-29 10:22:52 -0400169 reloadConversationsAndRequests(std::string([accountId UTF8String]));
Kateryna Kostiuk19437652022-08-02 13:02:21 -0400170 setAccountActive(std::string([accountId UTF8String]), true);
kkostiuk74d1ae42021-06-17 11:10:15 -0400171 return true;
172 }
173#if DEBUG
Kateryna Kostiukd500b5d2023-05-17 16:20:00 -0400174 int flag = LIBJAMI_FLAG_CONSOLE_LOG | LIBJAMI_FLAG_DEBUG | LIBJAMI_FLAG_IOS_EXTENSION | LIBJAMI_FLAG_NO_AUTOSYNC | LIBJAMI_FLAG_NO_LOCAL_AUDIO;
kkostiuk74d1ae42021-06-17 11:10:15 -0400175#else
Kateryna Kostiukd500b5d2023-05-17 16:20:00 -0400176 int flag = LIBJAMI_FLAG_IOS_EXTENSION | LIBJAMI_FLAG_NO_AUTOSYNC | LIBJAMI_FLAG_NO_LOCAL_AUDIO;
kkostiuk74d1ae42021-06-17 11:10:15 -0400177#endif
178 if (![[NSThread currentThread] isMainThread]) {
179 __block bool success;
180 dispatch_sync(dispatch_get_main_queue(), ^{
Kateryna Kostiukc3c00162022-11-10 11:21:14 -0500181 if (init(static_cast<InitFlag>(flag))) {
kkostiuk74d1ae42021-06-17 11:10:15 -0400182 success = start({});
183 } else {
184 success = false;
185 }
186 });
187 return success;
188 } else {
Kateryna Kostiukc3c00162022-11-10 11:21:14 -0500189 if (init(static_cast<InitFlag>(flag))) {
kkostiuk74d1ae42021-06-17 11:10:15 -0400190 return start({});
191 }
192 return false;
193 }
194}
195
196- (void)stop
197{
198 unregisterSignalHandlers();
199 confHandlers.clear();
200 [self setAccountsActive:false];
201}
202
203- (void)setAccountsActive:(BOOL)active
204{
205 auto accounts = getAccountList();
206 for (auto account : accounts) {
Kateryna Kostiukdfee1c92023-03-29 10:22:52 -0400207 if (active) {
208 reloadConversationsAndRequests(account);
209 }
kkostiuk39672932022-07-12 11:36:30 -0400210 setAccountActive(account, active, true);
kkostiuk74d1ae42021-06-17 11:10:15 -0400211 }
212}
213
214- (NSDictionary<NSString*, NSString*>*)decrypt:(NSString*)keyPath
Kateryna Kostiukba9a7b12023-05-02 09:33:52 -0400215 accountId:(NSString*)accountId
kkostiuk74d1ae42021-06-17 11:10:15 -0400216 treated:(NSString*)treatedMessagesPath
217 value:(NSDictionary*)value
218{
219 if (![[NSFileManager defaultManager] fileExistsAtPath:keyPath]) {
220 return {};
221 }
222
223 NSData* data = [[NSFileManager defaultManager] contentsAtPath:keyPath];
224 const uint8_t* bytes = (const uint8_t*) [data bytes];
225 dht::crypto::PrivateKey dhtKey(bytes, [data length], "");
226
227 Json::Value jsonValue = toJson(value);
228 dht::Value dhtValue(jsonValue);
229
230 if (!dhtValue.isEncrypted()) {
231 return {};
232 }
233 try {
kkostiuk03fa94c2022-07-14 16:30:35 -0400234 dht::Sp<dht::Value> decrypted = dhtValue.decrypt(dhtKey);
235 auto unpacked = msgpack::unpack((const char*) decrypted->data.data(), decrypted->data.size());
kkostiuk74d1ae42021-06-17 11:10:15 -0400236 auto peerCR = unpacked.get().as<PeerConnectionRequest>();
Kateryna Kostiuk2648aab2022-08-30 14:24:03 -0400237 if (peerCR.connType.empty()) {
238 // this value is not a PeerConnectionRequest
239 // check if it a TrustRequest
240 auto conversationRequest = unpacked.get().as<dht::TrustRequest>();
241 if (!conversationRequest.conversationId.empty()) {
242 if (conversationRequest.service == "cx.ring") {
243 // return git message type to start daemon
244 return @{@"": @"application/im-gitmessage-id"};
245 }
246 }
247 return {};
248 }
kkostiuk74d1ae42021-06-17 11:10:15 -0400249 if (isMessageTreated(peerCR.id, [treatedMessagesPath UTF8String])) {
250 return {};
251 }
Kateryna Kostiukf099b5c2023-05-02 09:11:49 -0400252
253 std::string peerId = "";
254 if (peerCR.connType == "videoCall" || peerCR.connType == "audioCall") {
Kateryna Kostiukba9a7b12023-05-02 09:33:52 -0400255 auto certPath = [[[Constants documentsPath] URLByAppendingPathComponent:accountId] URLByAppendingPathComponent:certificates].path.UTF8String;
256 auto crlPath = [[[Constants documentsPath] URLByAppendingPathComponent:accountId] URLByAppendingPathComponent:crls].path.UTF8String;
257 auto ocspPath = [[[Constants documentsPath] URLByAppendingPathComponent:accountId] URLByAppendingPathComponent:ocsp].path.UTF8String;
Kateryna Kostiukf099b5c2023-05-02 09:11:49 -0400258 peerId = getPeerId(decrypted->owner->getId().toString(),
259 certPath,
260 crlPath,
261 ocspPath);
262 }
kkostiuk74d1ae42021-06-17 11:10:15 -0400263 return @{@(peerId.c_str()): @(peerCR.connType.c_str())};
264 } catch (std::runtime_error error) {
265 }
266 return {};
267}
268
kkostiuke10e6572022-07-26 15:41:12 -0400269-(NSString*)getNameFor:(NSString*)address accountId:(NSString*)accountId {
270 return @(getName(std::string([address UTF8String]), std::string([accountId UTF8String])).c_str());
271}
272
273-(NSString*)nameServerForAccountId:(NSString*)accountId; {
274 auto nameServer = getNameServer(std::string([accountId UTF8String]));
275 return nameServer.empty() ? defaultNameServer : @(nameServer.c_str());
276}
277
kkostiuk74d1ae42021-06-17 11:10:15 -0400278Json::Value
279toJson(NSDictionary* value)
280{
281 Json::Value val;
282 for (NSString* key in value.allKeys) {
283 if ([[value objectForKey:key] isKindOfClass:[NSString class]]) {
284 NSString* stringValue = [value objectForKey:key];
285 val[key.UTF8String] = stringValue.UTF8String;
286 } else if ([[value objectForKey:key] isKindOfClass:[NSNumber class]]) {
287 NSNumber* number = [value objectForKey:key];
288 if ([key isEqualToString:@"id"]) {
289 unsigned long long int intValue = [number unsignedLongLongValue];
290 val[key.UTF8String] = intValue;
291 } else {
292 int intValue = [number intValue];
293 val[key.UTF8String] = intValue;
294 }
295 }
296 }
297 return val;
298}
299
kkostiuke10e6572022-07-26 15:41:12 -0400300std::string getName(std::string addres, std::string accountId)
301{
302 auto name = cachedNames.find(addres);
303 if (name != cachedNames.end()) {
304 return name->second;
305 }
306
307 auto ns = getNameServer(accountId);
Kateryna Kostiuk1c0e7562022-08-23 15:17:44 -0400308 NSURL *url = [NSURL URLWithString: @(ns.c_str())];
309 NSString* host = [url host];
310 NSString* nameServer = host.length == 0 ? defaultNameServer : host;
kkostiuke10e6572022-07-26 15:41:12 -0400311 std::string namesPath = [[[Constants cachesPath] URLByAppendingPathComponent: nameCache] URLByAppendingPathComponent: nameServer].path.UTF8String;
312
313 msgpack::unpacker pac;
314 // read file
315 std::ifstream file = std::ifstream(namesPath, std::ios_base::in);
316 if (!file.is_open()) {
317 return "";
318 }
319 std::string line;
320 while (std::getline(file, line)) {
321 pac.reserve_buffer(line.size());
322 memcpy(pac.buffer(), line.data(), line.size());
323 pac.buffer_consumed(line.size());
324 }
325
326 // load values
327 msgpack::object_handle oh;
328 if (pac.next(oh))
329 oh.get().convert(cachedNames);
330 auto cacheRes = cachedNames.find(addres);
331 return cacheRes != cachedNames.end() ? cacheRes->second : std::string {};
332}
333
334std::string getNameServer(std::string accountId) {
335 auto it = nameServers.find(accountId);
336 if (it != nameServers.end()) {
337 return it->second;
338 }
339 std::string nameServer {};
340 auto accountConfigPath = [[[Constants documentsPath] URLByAppendingPathComponent: @(accountId.c_str())] URLByAppendingPathComponent: accountConfig].path.UTF8String;
341 try {
342 std::ifstream file = std::ifstream(accountConfigPath, std::ios_base::in);
343 YAML::Node node = YAML::Load(file);
344 file.close();
345 nameServer = node[nameServerConfiguration].as<std::string>();
346 if (!nameServer.empty()) {
347 nameServers.insert(std::pair<std::string, std::string>(accountId, nameServer));
348 }
349 } catch (const std::exception& e) {}
350 return nameServer;
351}
352
kkostiuk74d1ae42021-06-17 11:10:15 -0400353#pragma mark functions copied from the daemon
354
355#define LIKELY(expr) (expr)
356#define UNLIKELY(expr) (expr)
357
358/*
359 * Check whether a Unicode (5.2) char is in a valid range.
360 *
361 * The first check comes from the Unicode guarantee to never encode
362 * a point above 0x0010ffff, since UTF-16 couldn't represent it.
363 *
364 * The second check covers surrogate pairs (category Cs).
365 *
366 * @param Char the character
367 */
368#define UNICODE_VALID(Char) ((Char) < 0x110000 && (((Char) &0xFFFFF800) != 0xD800))
369
370#define CONTINUATION_CHAR \
371 if ((*(unsigned char*) p & 0xc0) != 0x80) /* 10xxxxxx */ \
372 goto error; \
373 val <<= 6; \
374 val |= (*(unsigned char*) p) & 0x3f;
375
kkostiuk74d1ae42021-06-17 11:10:15 -0400376template<typename ID = dht::Value::Id>
377bool
378isMessageTreated(ID messageId, const std::string& path)
379{
380 std::ifstream file = std::ifstream(path, std::ios_base::in);
381 if (!file.is_open()) {
382 return false;
383 }
384 std::set<ID, std::less<>> treatedMessages;
385 std::string line;
386 while (std::getline(file, line)) {
387 if constexpr (std::is_same<ID, std::string>::value) {
388 treatedMessages.emplace(std::move(line));
389 } else if constexpr (std::is_integral<ID>::value) {
390 ID vid;
391 if (auto [p, ec] = std::from_chars(line.data(), line.data() + line.size(), vid, 16);
392 ec == std::errc()) {
393 treatedMessages.emplace(vid);
394 }
395 }
396 }
397 return treatedMessages.find(messageId) != treatedMessages.end();
398}
399
400std::string
401getPeerId(const std::string& key,
402 const std::string& certPath,
403 const std::string& crlPath,
404 const std::string& ocspPath)
405{
406 std::map<std::string, std::shared_ptr<dht::crypto::Certificate>> certs;
407 auto dir_content = readDirectory(certPath);
408 unsigned n = 0;
409 for (const auto& f : dir_content) {
410 try {
411 auto crt = std::make_shared<dht::crypto::Certificate>(
412 loadFile(certPath + fileSeparator + f));
413 auto id = crt->getId().toString();
414 auto longId = crt->getLongId().toString();
415 if (id != f && longId != f)
416 throw std::logic_error("Certificate id mismatch");
417 while (crt) {
418 id = crt->getId().toString();
419 longId = crt->getLongId().toString();
420 certs.emplace(std::move(id), crt);
421 certs.emplace(std::move(longId), crt);
422 loadRevocations(*crt, crlPath, ocspPath);
423 crt = crt->issuer;
424 ++n;
425 }
426 } catch (const std::exception& e) {
427 }
428 }
429 auto cit = certs.find(key);
430 if (cit == certs.cend()) {
431 return {};
432 }
433 dht::InfoHash peer_account_id;
434 if (not foundPeerDevice(cit->second, peer_account_id)) {
435 return {};
436 }
437 return peer_account_id.toString();
438}
439
440void
441loadRevocations(dht::crypto::Certificate& crt,
442 const std::string& crlPath,
443 const std::string& ocspPath)
444{
445 auto dir = crlPath + fileSeparator + crt.getId().toString();
446 for (const auto& crl : readDirectory(dir)) {
447 try {
448 crt.addRevocationList(
449 std::make_shared<dht::crypto::RevocationList>(loadFile(dir + fileSeparator + crl)));
450 } catch (const std::exception& e) {
451 }
452 }
453 auto ocsp_dir = ocspPath + fileSeparator + crt.getId().toString();
454 for (const auto& ocsp : readDirectory(ocsp_dir)) {
455 try {
456 std::string ocsp_filepath = ocsp_dir + fileSeparator + ocsp;
457 auto serial = crt.getSerialNumber();
458 if (dht::toHex(serial.data(), serial.size()) != ocsp)
459 continue;
460 dht::Blob ocspBlob = loadFile(ocsp_filepath);
461 crt.ocspResponse = std::make_shared<dht::crypto::OcspResponse>(ocspBlob.data(),
462 ocspBlob.size());
463 } catch (const std::exception& e) {
464 }
465 }
466}
467
468bool
469foundPeerDevice(const std::shared_ptr<dht::crypto::Certificate>& crt, dht::InfoHash& account_id)
470{
471 if (not crt)
472 return false;
473
474 auto top_issuer = crt;
475 while (top_issuer->issuer)
476 top_issuer = top_issuer->issuer;
477
478 if (top_issuer == crt) {
479 return false;
480 }
481 dht::crypto::TrustList peer_trust;
482 peer_trust.add(*top_issuer);
483 if (not peer_trust.verify(*crt)) {
484 return false;
485 }
486 if (crt->ocspResponse and crt->ocspResponse->getCertificateStatus() != GNUTLS_OCSP_CERT_GOOD) {
487 return false;
488 }
489 account_id = crt->issuer->getId();
490 return true;
491}
492
493std::vector<std::string>
494readDirectory(const std::string& dir)
495{
496 NSError* error;
497 NSFileManager* fileMgr = [NSFileManager defaultManager];
498 NSArray* files = [fileMgr contentsOfDirectoryAtPath:@(dir.c_str()) error:&error];
499
500 std::vector<std::string> vector;
501 for (NSString* fileName in files) {
502 vector.push_back([fileName UTF8String]);
503 }
504 return vector;
505}
506
507std::vector<uint8_t>
508loadFile(const std::string& path)
509{
510 if (![[NSFileManager defaultManager] fileExistsAtPath:@(path.c_str())]) {
511 return {};
512 }
513 NSData* data = [[NSFileManager defaultManager] contentsAtPath:@(path.c_str())];
514 return [Utils vectorOfUInt8FromData:data];
515}
516
517std::string
518utf8_make_valid(const std::string& name)
519{
520 ssize_t remaining_bytes = name.size();
521 ssize_t valid_bytes;
522 const char* remainder = name.c_str();
523 const char* invalid;
524 char* str = NULL;
525 char* pos = nullptr;
526
527 while (remaining_bytes != 0) {
528 if (utf8_validate_c_str(remainder, remaining_bytes, &invalid))
529 break;
530
531 valid_bytes = invalid - remainder;
532
533 if (str == NULL)
534 // If every byte is replaced by U+FFFD, max(strlen(string)) == 3 * name.size()
535 str = new char[3 * remaining_bytes];
536
537 pos = str;
538
539 strncpy(pos, remainder, valid_bytes);
540 pos += valid_bytes;
541
542 /* append U+FFFD REPLACEMENT CHARACTER */
543 pos[0] = '\357';
544 pos[1] = '\277';
545 pos[2] = '\275';
546
547 pos += 3;
548
549 remaining_bytes -= valid_bytes + 1;
550 remainder = invalid + 1;
551 }
552
553 if (str == NULL)
554 return std::string(name);
555
556 strncpy(pos, remainder, remaining_bytes);
557 pos += remaining_bytes;
558
559 std::string answer(str, pos - str);
560 assert(utf8_validate_c_str(answer.c_str(), -1, NULL));
561
562 delete[] str;
563
564 return answer;
565}
566
567bool
568utf8_validate_c_str(const char* str, ssize_t max_len, const char** end)
569{
570 const char* p;
571
572 if (max_len < 0)
573 p = fast_validate(str);
574 else
575 p = fast_validate_len(str, max_len);
576
577 if (end)
578 *end = p;
579
580 if ((max_len >= 0 && p != str + max_len) || (max_len < 0 && *p != '\0'))
581 return false;
582 else
583 return true;
584}
585
586static const char*
587fast_validate(const char* str)
588{
589 char32_t val = 0;
590 char32_t min = 0;
591 const char* p;
592
593 for (p = str; *p; p++) {
594 if (*(unsigned char*) p < 128)
595 /* done */;
596 else {
597 const char* last;
598
599 last = p;
600
601 if ((*(unsigned char*) p & 0xe0) == 0xc0) { /* 110xxxxx */
602 if (UNLIKELY((*(unsigned char*) p & 0x1e) == 0))
603 goto error;
604
605 p++;
606
607 if (UNLIKELY((*(unsigned char*) p & 0xc0) != 0x80)) /* 10xxxxxx */
608 goto error;
609 } else {
610 if ((*(unsigned char*) p & 0xf0) == 0xe0) { /* 1110xxxx */
611 min = (1 << 11);
612 val = *(unsigned char*) p & 0x0f;
613 goto TWO_REMAINING;
614 } else if ((*(unsigned char*) p & 0xf8) == 0xf0) { /* 11110xxx */
615 min = (1 << 16);
616 val = *(unsigned char*) p & 0x07;
617 } else
618 goto error;
619
620 p++;
621 CONTINUATION_CHAR;
622 TWO_REMAINING:
623 p++;
624 CONTINUATION_CHAR;
625 p++;
626 CONTINUATION_CHAR;
627
628 if (UNLIKELY(val < min))
629 goto error;
630
631 if (UNLIKELY(!UNICODE_VALID(val)))
632 goto error;
633 }
634
635 continue;
636
637 error:
638 return last;
639 }
640 }
641
642 return p;
643}
644
645static const char*
646fast_validate_len(const char* str, ssize_t max_len)
647{
648 char32_t val = 0;
649 char32_t min = 0;
650 const char* p;
651
652 assert(max_len >= 0);
653
654 for (p = str; ((p - str) < max_len) && *p; p++) {
655 if (*(unsigned char*) p < 128)
656 /* done */;
657 else {
658 const char* last;
659
660 last = p;
661
662 if ((*(unsigned char*) p & 0xe0) == 0xc0) { /* 110xxxxx */
663 if (UNLIKELY(max_len - (p - str) < 2))
664 goto error;
665
666 if (UNLIKELY((*(unsigned char*) p & 0x1e) == 0))
667 goto error;
668
669 p++;
670
671 if (UNLIKELY((*(unsigned char*) p & 0xc0) != 0x80)) /* 10xxxxxx */
672 goto error;
673 } else {
674 if ((*(unsigned char*) p & 0xf0) == 0xe0) { /* 1110xxxx */
675 if (UNLIKELY(max_len - (p - str) < 3))
676 goto error;
677
678 min = (1 << 11);
679 val = *(unsigned char*) p & 0x0f;
680 goto TWO_REMAINING;
681 } else if ((*(unsigned char*) p & 0xf8) == 0xf0) { /* 11110xxx */
682 if (UNLIKELY(max_len - (p - str) < 4))
683 goto error;
684
685 min = (1 << 16);
686 val = *(unsigned char*) p & 0x07;
687 } else
688 goto error;
689
690 p++;
691 CONTINUATION_CHAR;
692 TWO_REMAINING:
693 p++;
694 CONTINUATION_CHAR;
695 p++;
696 CONTINUATION_CHAR;
697
698 if (UNLIKELY(val < min))
699 goto error;
700
701 if (UNLIKELY(!UNICODE_VALID(val)))
702 goto error;
703 }
704
705 continue;
706
707 error:
708 return last;
709 }
710 }
711
712 return p;
713}
714
715@end