blob: 4a8186301e9942dcf3ce12305cb16800b2f1f1f7 [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
57typedef NS_ENUM(NSInteger, NotificationType) { videoCall, audioCall, gitMessage, unknown };
58
59// Constants
60const std::string fileSeparator = "/";
61NSString* const certificates = @"certificates";
62NSString* const crls = @"crls";
63NSString* const ocsp = @"ocsp";
kkostiuke10e6572022-07-26 15:41:12 -040064NSString* const nameCache = @"namecache";
65NSString* const defaultNameServer = @"ns.jami.net";
66std::string const nameServerConfiguration = "RingNS.uri";
67NSString* const accountConfig = @"config.yml";
kkostiuk74d1ae42021-06-17 11:10:15 -040068
69std::map<std::string, std::shared_ptr<CallbackWrapperBase>> confHandlers;
kkostiuke10e6572022-07-26 15:41:12 -040070std::map<std::string, std::string> cachedNames;
71std::map<std::string, std::string> nameServers;
kkostiuk74d1ae42021-06-17 11:10:15 -040072
73#pragma mark Callbacks registration
74- (void)registerSignals
75{
76 confHandlers.insert(exportable_callback<ConfigurationSignal::GetAppDataPath>(
77 [&](const std::string& name, std::vector<std::string>* ret) {
78 if (name == "cache") {
79 auto path = [Constants cachesPath];
80 ret->push_back(std::string([path.path UTF8String]));
81 } else {
82 auto path = [Constants documentsPath];
83 ret->push_back(std::string([path.path UTF8String]));
84 }
85 }));
86
87 confHandlers.insert(exportable_callback<ConversationSignal::MessageReceived>(
88 [&](const std::string& accountId,
89 const std::string& conversationId,
90 std::map<std::string, std::string> message) {
91 if (Adapter.delegate) {
92 NSString* convId = [NSString stringWithUTF8String:conversationId.c_str()];
93 NSString* account = [NSString stringWithUTF8String:accountId.c_str()];
94 NSMutableDictionary* interaction = [Utils mapToDictionnary:message];
95 [Adapter.delegate newInteractionWithConversationId:convId
96 accountId:account
97 message:interaction];
98 }
99 }));
100
101 confHandlers.insert(exportable_callback<DataTransferSignal::DataTransferEvent>(
102 [&](const std::string& account_id,
103 const std::string& conversation_id,
104 const std::string& interaction_id,
105 const std::string& file_id,
106 int eventCode) {
107 if (Adapter.delegate) {
108 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()];
112 [Adapter.delegate dataTransferEventWithFileId:fileId
113 withEventCode:eventCode
114 accountId:accountId
115 conversationId:conversationId
116 interactionId:interactionId];
117 }
118 }));
119
120 confHandlers.insert(exportable_callback<ConversationSignal::ConversationSyncFinished>(
121 [&](const std::string& account_id) {
122 if (Adapter.delegate) {
123 NSString* accountId = [NSString stringWithUTF8String:account_id.c_str()];
124 [Adapter.delegate conversationSyncCompletedWithAccountId:accountId];
125 }
126 }));
127
128 confHandlers.insert(exportable_callback<ConversationSignal::CallConnectionRequest>(
129 [&](const std::string& account_id, const std::string& peer_id, bool hasVideo) {
130 if (Adapter.delegate) {
131 NSString* accountId = [NSString stringWithUTF8String:account_id.c_str()];
132 NSString* peerId = [NSString stringWithUTF8String:peer_id.c_str()];
133 [Adapter.delegate receivedCallConnectionRequestWithAccountId:accountId
134 peerId:peerId
135 hasVideo:hasVideo];
136 }
137 }));
Kateryna Kostiuk2648aab2022-08-30 14:24:03 -0400138 //Contact added signal
139 confHandlers.insert(exportable_callback<ConfigurationSignal::ContactAdded>([&](const std::string& accountId,
140 const std::string& uri,
141 bool confirmed) {
142 if(Adapter.delegate) {
143 NSString* accountIdStr = [NSString stringWithUTF8String:accountId.c_str()];
144 NSString* uriStr = [NSString stringWithUTF8String:uri.c_str()];
145 [Adapter.delegate receivedContactRequestWithAccountId: accountIdStr peerId: uriStr];
146 }
147 }));
148
149 confHandlers.insert(exportable_callback<ConversationSignal::ConversationRequestReceived>([&](const std::string& accountId, const std::string& conversationId, std::map<std::string, std::string> metadata) {
150 if(Adapter.delegate) {
151 NSString* accountIdStr = [NSString stringWithUTF8String:accountId.c_str()];
152 NSString* convIdStr = [NSString stringWithUTF8String:conversationId.c_str()];
153 NSMutableDictionary* info = [Utils mapToDictionnary: metadata];
154 [Adapter.delegate receivedConversationRequestWithAccountId: accountIdStr conversationId: convIdStr metadata:info];
155 }
156 }));
kkostiuk74d1ae42021-06-17 11:10:15 -0400157 registerSignalHandlers(confHandlers);
158}
159
160#pragma mark AdapterDelegate
161+ (id<AdapterDelegate>)delegate
162{
163 return _delegate;
164}
165
166+ (void)setDelegate:(id<AdapterDelegate>)delegate
167{
168 _delegate = delegate;
169}
170
171- (bool)downloadFileWithFileId:(NSString*)fileId
172 accountId:(NSString*)accountId
173 conversationId:(NSString*)conversationId
174 interactionId:(NSString*)interactionId
175 withFilePath:(NSString*)filePath
176{
177 return downloadFile(std::string([accountId UTF8String]),
178 std::string([conversationId UTF8String]),
179 std::string([interactionId UTF8String]),
180 std::string([fileId UTF8String]),
181 std::string([filePath UTF8String]));
182}
183
Kateryna Kostiuk19437652022-08-02 13:02:21 -0400184- (BOOL)start:(NSString*)accountId
kkostiuk74d1ae42021-06-17 11:10:15 -0400185{
186 [self registerSignals];
Kateryna Kostiukc3c00162022-11-10 11:21:14 -0500187 if (initialized() == true) {
Kateryna Kostiuk19437652022-08-02 13:02:21 -0400188 setAccountActive(std::string([accountId UTF8String]), true);
kkostiuk74d1ae42021-06-17 11:10:15 -0400189 return true;
190 }
191#if DEBUG
Kateryna Kostiukc3c00162022-11-10 11:21:14 -0500192 int flag = LIBJAMI_FLAG_CONSOLE_LOG | LIBJAMI_FLAG_DEBUG | LIBJAMI_FLAG_IOS_EXTENSION;
kkostiuk74d1ae42021-06-17 11:10:15 -0400193#else
Kateryna Kostiukc3c00162022-11-10 11:21:14 -0500194 int flag = LIBJAMI_FLAG_IOS_EXTENSION;;
kkostiuk74d1ae42021-06-17 11:10:15 -0400195#endif
196 if (![[NSThread currentThread] isMainThread]) {
197 __block bool success;
198 dispatch_sync(dispatch_get_main_queue(), ^{
Kateryna Kostiukc3c00162022-11-10 11:21:14 -0500199 if (init(static_cast<InitFlag>(flag))) {
kkostiuk74d1ae42021-06-17 11:10:15 -0400200 success = start({});
201 } else {
202 success = false;
203 }
204 });
205 return success;
206 } else {
Kateryna Kostiukc3c00162022-11-10 11:21:14 -0500207 if (init(static_cast<InitFlag>(flag))) {
kkostiuk74d1ae42021-06-17 11:10:15 -0400208 return start({});
209 }
210 return false;
211 }
212}
213
214- (void)stop
215{
216 unregisterSignalHandlers();
217 confHandlers.clear();
218 [self setAccountsActive:false];
219}
220
221- (void)setAccountsActive:(BOOL)active
222{
223 auto accounts = getAccountList();
224 for (auto account : accounts) {
kkostiuk39672932022-07-12 11:36:30 -0400225 setAccountActive(account, active, true);
kkostiuk74d1ae42021-06-17 11:10:15 -0400226 }
227}
228
229- (NSDictionary<NSString*, NSString*>*)decrypt:(NSString*)keyPath
230 treated:(NSString*)treatedMessagesPath
231 value:(NSDictionary*)value
232{
233 if (![[NSFileManager defaultManager] fileExistsAtPath:keyPath]) {
234 return {};
235 }
236
237 NSData* data = [[NSFileManager defaultManager] contentsAtPath:keyPath];
238 const uint8_t* bytes = (const uint8_t*) [data bytes];
239 dht::crypto::PrivateKey dhtKey(bytes, [data length], "");
240
241 Json::Value jsonValue = toJson(value);
242 dht::Value dhtValue(jsonValue);
243
244 if (!dhtValue.isEncrypted()) {
245 return {};
246 }
247 try {
kkostiuk03fa94c2022-07-14 16:30:35 -0400248 dht::Sp<dht::Value> decrypted = dhtValue.decrypt(dhtKey);
249 auto unpacked = msgpack::unpack((const char*) decrypted->data.data(), decrypted->data.size());
kkostiuk74d1ae42021-06-17 11:10:15 -0400250 auto peerCR = unpacked.get().as<PeerConnectionRequest>();
Kateryna Kostiuk2648aab2022-08-30 14:24:03 -0400251 if (peerCR.connType.empty()) {
252 // this value is not a PeerConnectionRequest
253 // check if it a TrustRequest
254 auto conversationRequest = unpacked.get().as<dht::TrustRequest>();
255 if (!conversationRequest.conversationId.empty()) {
256 if (conversationRequest.service == "cx.ring") {
257 // return git message type to start daemon
258 return @{@"": @"application/im-gitmessage-id"};
259 }
260 }
261 return {};
262 }
kkostiuk74d1ae42021-06-17 11:10:15 -0400263 if (isMessageTreated(peerCR.id, [treatedMessagesPath UTF8String])) {
264 return {};
265 }
kkostiuk03fa94c2022-07-14 16:30:35 -0400266 auto certPath = [[Constants documentsPath] URLByAppendingPathComponent:certificates].path.UTF8String;
kkostiuk74d1ae42021-06-17 11:10:15 -0400267 auto crlPath = [[Constants documentsPath] URLByAppendingPathComponent:crls].path.UTF8String;
268 auto ocspPath = [[Constants documentsPath] URLByAppendingPathComponent:ocsp].path.UTF8String;
kkostiuk03fa94c2022-07-14 16:30:35 -0400269 std::string peerId = getPeerId(decrypted->owner->getId().toString(),
kkostiuk74d1ae42021-06-17 11:10:15 -0400270 certPath,
271 crlPath,
272 ocspPath);
273 return @{@(peerId.c_str()): @(peerCR.connType.c_str())};
274 } catch (std::runtime_error error) {
275 }
276 return {};
277}
278
kkostiuke10e6572022-07-26 15:41:12 -0400279-(NSString*)getNameFor:(NSString*)address accountId:(NSString*)accountId {
280 return @(getName(std::string([address UTF8String]), std::string([accountId UTF8String])).c_str());
281}
282
283-(NSString*)nameServerForAccountId:(NSString*)accountId; {
284 auto nameServer = getNameServer(std::string([accountId UTF8String]));
285 return nameServer.empty() ? defaultNameServer : @(nameServer.c_str());
286}
287
kkostiuk74d1ae42021-06-17 11:10:15 -0400288Json::Value
289toJson(NSDictionary* value)
290{
291 Json::Value val;
292 for (NSString* key in value.allKeys) {
293 if ([[value objectForKey:key] isKindOfClass:[NSString class]]) {
294 NSString* stringValue = [value objectForKey:key];
295 val[key.UTF8String] = stringValue.UTF8String;
296 } else if ([[value objectForKey:key] isKindOfClass:[NSNumber class]]) {
297 NSNumber* number = [value objectForKey:key];
298 if ([key isEqualToString:@"id"]) {
299 unsigned long long int intValue = [number unsignedLongLongValue];
300 val[key.UTF8String] = intValue;
301 } else {
302 int intValue = [number intValue];
303 val[key.UTF8String] = intValue;
304 }
305 }
306 }
307 return val;
308}
309
kkostiuke10e6572022-07-26 15:41:12 -0400310std::string getName(std::string addres, std::string accountId)
311{
312 auto name = cachedNames.find(addres);
313 if (name != cachedNames.end()) {
314 return name->second;
315 }
316
317 auto ns = getNameServer(accountId);
Kateryna Kostiuk1c0e7562022-08-23 15:17:44 -0400318 NSURL *url = [NSURL URLWithString: @(ns.c_str())];
319 NSString* host = [url host];
320 NSString* nameServer = host.length == 0 ? defaultNameServer : host;
kkostiuke10e6572022-07-26 15:41:12 -0400321 std::string namesPath = [[[Constants cachesPath] URLByAppendingPathComponent: nameCache] URLByAppendingPathComponent: nameServer].path.UTF8String;
322
323 msgpack::unpacker pac;
324 // read file
325 std::ifstream file = std::ifstream(namesPath, std::ios_base::in);
326 if (!file.is_open()) {
327 return "";
328 }
329 std::string line;
330 while (std::getline(file, line)) {
331 pac.reserve_buffer(line.size());
332 memcpy(pac.buffer(), line.data(), line.size());
333 pac.buffer_consumed(line.size());
334 }
335
336 // load values
337 msgpack::object_handle oh;
338 if (pac.next(oh))
339 oh.get().convert(cachedNames);
340 auto cacheRes = cachedNames.find(addres);
341 return cacheRes != cachedNames.end() ? cacheRes->second : std::string {};
342}
343
344std::string getNameServer(std::string accountId) {
345 auto it = nameServers.find(accountId);
346 if (it != nameServers.end()) {
347 return it->second;
348 }
349 std::string nameServer {};
350 auto accountConfigPath = [[[Constants documentsPath] URLByAppendingPathComponent: @(accountId.c_str())] URLByAppendingPathComponent: accountConfig].path.UTF8String;
351 try {
352 std::ifstream file = std::ifstream(accountConfigPath, std::ios_base::in);
353 YAML::Node node = YAML::Load(file);
354 file.close();
355 nameServer = node[nameServerConfiguration].as<std::string>();
356 if (!nameServer.empty()) {
357 nameServers.insert(std::pair<std::string, std::string>(accountId, nameServer));
358 }
359 } catch (const std::exception& e) {}
360 return nameServer;
361}
362
kkostiuk74d1ae42021-06-17 11:10:15 -0400363#pragma mark functions copied from the daemon
364
365#define LIKELY(expr) (expr)
366#define UNLIKELY(expr) (expr)
367
368/*
369 * Check whether a Unicode (5.2) char is in a valid range.
370 *
371 * The first check comes from the Unicode guarantee to never encode
372 * a point above 0x0010ffff, since UTF-16 couldn't represent it.
373 *
374 * The second check covers surrogate pairs (category Cs).
375 *
376 * @param Char the character
377 */
378#define UNICODE_VALID(Char) ((Char) < 0x110000 && (((Char) &0xFFFFF800) != 0xD800))
379
380#define CONTINUATION_CHAR \
381 if ((*(unsigned char*) p & 0xc0) != 0x80) /* 10xxxxxx */ \
382 goto error; \
383 val <<= 6; \
384 val |= (*(unsigned char*) p) & 0x3f;
385
kkostiuk74d1ae42021-06-17 11:10:15 -0400386template<typename ID = dht::Value::Id>
387bool
388isMessageTreated(ID messageId, const std::string& path)
389{
390 std::ifstream file = std::ifstream(path, std::ios_base::in);
391 if (!file.is_open()) {
392 return false;
393 }
394 std::set<ID, std::less<>> treatedMessages;
395 std::string line;
396 while (std::getline(file, line)) {
397 if constexpr (std::is_same<ID, std::string>::value) {
398 treatedMessages.emplace(std::move(line));
399 } else if constexpr (std::is_integral<ID>::value) {
400 ID vid;
401 if (auto [p, ec] = std::from_chars(line.data(), line.data() + line.size(), vid, 16);
402 ec == std::errc()) {
403 treatedMessages.emplace(vid);
404 }
405 }
406 }
407 return treatedMessages.find(messageId) != treatedMessages.end();
408}
409
410std::string
411getPeerId(const std::string& key,
412 const std::string& certPath,
413 const std::string& crlPath,
414 const std::string& ocspPath)
415{
416 std::map<std::string, std::shared_ptr<dht::crypto::Certificate>> certs;
417 auto dir_content = readDirectory(certPath);
418 unsigned n = 0;
419 for (const auto& f : dir_content) {
420 try {
421 auto crt = std::make_shared<dht::crypto::Certificate>(
422 loadFile(certPath + fileSeparator + f));
423 auto id = crt->getId().toString();
424 auto longId = crt->getLongId().toString();
425 if (id != f && longId != f)
426 throw std::logic_error("Certificate id mismatch");
427 while (crt) {
428 id = crt->getId().toString();
429 longId = crt->getLongId().toString();
430 certs.emplace(std::move(id), crt);
431 certs.emplace(std::move(longId), crt);
432 loadRevocations(*crt, crlPath, ocspPath);
433 crt = crt->issuer;
434 ++n;
435 }
436 } catch (const std::exception& e) {
437 }
438 }
439 auto cit = certs.find(key);
440 if (cit == certs.cend()) {
441 return {};
442 }
443 dht::InfoHash peer_account_id;
444 if (not foundPeerDevice(cit->second, peer_account_id)) {
445 return {};
446 }
447 return peer_account_id.toString();
448}
449
450void
451loadRevocations(dht::crypto::Certificate& crt,
452 const std::string& crlPath,
453 const std::string& ocspPath)
454{
455 auto dir = crlPath + fileSeparator + crt.getId().toString();
456 for (const auto& crl : readDirectory(dir)) {
457 try {
458 crt.addRevocationList(
459 std::make_shared<dht::crypto::RevocationList>(loadFile(dir + fileSeparator + crl)));
460 } catch (const std::exception& e) {
461 }
462 }
463 auto ocsp_dir = ocspPath + fileSeparator + crt.getId().toString();
464 for (const auto& ocsp : readDirectory(ocsp_dir)) {
465 try {
466 std::string ocsp_filepath = ocsp_dir + fileSeparator + ocsp;
467 auto serial = crt.getSerialNumber();
468 if (dht::toHex(serial.data(), serial.size()) != ocsp)
469 continue;
470 dht::Blob ocspBlob = loadFile(ocsp_filepath);
471 crt.ocspResponse = std::make_shared<dht::crypto::OcspResponse>(ocspBlob.data(),
472 ocspBlob.size());
473 } catch (const std::exception& e) {
474 }
475 }
476}
477
478bool
479foundPeerDevice(const std::shared_ptr<dht::crypto::Certificate>& crt, dht::InfoHash& account_id)
480{
481 if (not crt)
482 return false;
483
484 auto top_issuer = crt;
485 while (top_issuer->issuer)
486 top_issuer = top_issuer->issuer;
487
488 if (top_issuer == crt) {
489 return false;
490 }
491 dht::crypto::TrustList peer_trust;
492 peer_trust.add(*top_issuer);
493 if (not peer_trust.verify(*crt)) {
494 return false;
495 }
496 if (crt->ocspResponse and crt->ocspResponse->getCertificateStatus() != GNUTLS_OCSP_CERT_GOOD) {
497 return false;
498 }
499 account_id = crt->issuer->getId();
500 return true;
501}
502
503std::vector<std::string>
504readDirectory(const std::string& dir)
505{
506 NSError* error;
507 NSFileManager* fileMgr = [NSFileManager defaultManager];
508 NSArray* files = [fileMgr contentsOfDirectoryAtPath:@(dir.c_str()) error:&error];
509
510 std::vector<std::string> vector;
511 for (NSString* fileName in files) {
512 vector.push_back([fileName UTF8String]);
513 }
514 return vector;
515}
516
517std::vector<uint8_t>
518loadFile(const std::string& path)
519{
520 if (![[NSFileManager defaultManager] fileExistsAtPath:@(path.c_str())]) {
521 return {};
522 }
523 NSData* data = [[NSFileManager defaultManager] contentsAtPath:@(path.c_str())];
524 return [Utils vectorOfUInt8FromData:data];
525}
526
527std::string
528utf8_make_valid(const std::string& name)
529{
530 ssize_t remaining_bytes = name.size();
531 ssize_t valid_bytes;
532 const char* remainder = name.c_str();
533 const char* invalid;
534 char* str = NULL;
535 char* pos = nullptr;
536
537 while (remaining_bytes != 0) {
538 if (utf8_validate_c_str(remainder, remaining_bytes, &invalid))
539 break;
540
541 valid_bytes = invalid - remainder;
542
543 if (str == NULL)
544 // If every byte is replaced by U+FFFD, max(strlen(string)) == 3 * name.size()
545 str = new char[3 * remaining_bytes];
546
547 pos = str;
548
549 strncpy(pos, remainder, valid_bytes);
550 pos += valid_bytes;
551
552 /* append U+FFFD REPLACEMENT CHARACTER */
553 pos[0] = '\357';
554 pos[1] = '\277';
555 pos[2] = '\275';
556
557 pos += 3;
558
559 remaining_bytes -= valid_bytes + 1;
560 remainder = invalid + 1;
561 }
562
563 if (str == NULL)
564 return std::string(name);
565
566 strncpy(pos, remainder, remaining_bytes);
567 pos += remaining_bytes;
568
569 std::string answer(str, pos - str);
570 assert(utf8_validate_c_str(answer.c_str(), -1, NULL));
571
572 delete[] str;
573
574 return answer;
575}
576
577bool
578utf8_validate_c_str(const char* str, ssize_t max_len, const char** end)
579{
580 const char* p;
581
582 if (max_len < 0)
583 p = fast_validate(str);
584 else
585 p = fast_validate_len(str, max_len);
586
587 if (end)
588 *end = p;
589
590 if ((max_len >= 0 && p != str + max_len) || (max_len < 0 && *p != '\0'))
591 return false;
592 else
593 return true;
594}
595
596static const char*
597fast_validate(const char* str)
598{
599 char32_t val = 0;
600 char32_t min = 0;
601 const char* p;
602
603 for (p = str; *p; p++) {
604 if (*(unsigned char*) p < 128)
605 /* done */;
606 else {
607 const char* last;
608
609 last = p;
610
611 if ((*(unsigned char*) p & 0xe0) == 0xc0) { /* 110xxxxx */
612 if (UNLIKELY((*(unsigned char*) p & 0x1e) == 0))
613 goto error;
614
615 p++;
616
617 if (UNLIKELY((*(unsigned char*) p & 0xc0) != 0x80)) /* 10xxxxxx */
618 goto error;
619 } else {
620 if ((*(unsigned char*) p & 0xf0) == 0xe0) { /* 1110xxxx */
621 min = (1 << 11);
622 val = *(unsigned char*) p & 0x0f;
623 goto TWO_REMAINING;
624 } else if ((*(unsigned char*) p & 0xf8) == 0xf0) { /* 11110xxx */
625 min = (1 << 16);
626 val = *(unsigned char*) p & 0x07;
627 } else
628 goto error;
629
630 p++;
631 CONTINUATION_CHAR;
632 TWO_REMAINING:
633 p++;
634 CONTINUATION_CHAR;
635 p++;
636 CONTINUATION_CHAR;
637
638 if (UNLIKELY(val < min))
639 goto error;
640
641 if (UNLIKELY(!UNICODE_VALID(val)))
642 goto error;
643 }
644
645 continue;
646
647 error:
648 return last;
649 }
650 }
651
652 return p;
653}
654
655static const char*
656fast_validate_len(const char* str, ssize_t max_len)
657{
658 char32_t val = 0;
659 char32_t min = 0;
660 const char* p;
661
662 assert(max_len >= 0);
663
664 for (p = str; ((p - str) < max_len) && *p; p++) {
665 if (*(unsigned char*) p < 128)
666 /* done */;
667 else {
668 const char* last;
669
670 last = p;
671
672 if ((*(unsigned char*) p & 0xe0) == 0xc0) { /* 110xxxxx */
673 if (UNLIKELY(max_len - (p - str) < 2))
674 goto error;
675
676 if (UNLIKELY((*(unsigned char*) p & 0x1e) == 0))
677 goto error;
678
679 p++;
680
681 if (UNLIKELY((*(unsigned char*) p & 0xc0) != 0x80)) /* 10xxxxxx */
682 goto error;
683 } else {
684 if ((*(unsigned char*) p & 0xf0) == 0xe0) { /* 1110xxxx */
685 if (UNLIKELY(max_len - (p - str) < 3))
686 goto error;
687
688 min = (1 << 11);
689 val = *(unsigned char*) p & 0x0f;
690 goto TWO_REMAINING;
691 } else if ((*(unsigned char*) p & 0xf8) == 0xf0) { /* 11110xxx */
692 if (UNLIKELY(max_len - (p - str) < 4))
693 goto error;
694
695 min = (1 << 16);
696 val = *(unsigned char*) p & 0x07;
697 } else
698 goto error;
699
700 p++;
701 CONTINUATION_CHAR;
702 TWO_REMAINING:
703 p++;
704 CONTINUATION_CHAR;
705 p++;
706 CONTINUATION_CHAR;
707
708 if (UNLIKELY(val < min))
709 goto error;
710
711 if (UNLIKELY(!UNICODE_VALID(val)))
712 goto error;
713 }
714
715 continue;
716
717 error:
718 return last;
719 }
720 }
721
722 return p;
723}
724
725@end