| /* |
| * Copyright (C) 2021-2022 Savoir-faire Linux Inc. |
| * |
| * Author: Kateryna Kostiuk <kateryna.kostiuk@savoirfairelinux.com> |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 3 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| */ |
| |
| #import "Adapter.h" |
| #import "Utils.h" |
| #import "jamiNotificationExtension-Swift.h" |
| |
| #import "jami/jami.h" |
| #import "jami/configurationmanager_interface.h" |
| #import "jami/callmanager_interface.h" |
| #import "jami/conversation_interface.h" |
| #import "jami/datatransfer_interface.h" |
| |
| #define MSGPACK_DISABLE_LEGACY_NIL |
| #import "opendht/crypto.h" |
| #import "opendht/default_types.h" |
| #import "yaml-cpp/yaml.h" |
| |
| #import "json/json.h" |
| #import "fstream" |
| #import "charconv" |
| |
| @implementation Adapter |
| |
| static id<AdapterDelegate> _delegate; |
| |
| using namespace libjami; |
| |
| struct PeerConnectionRequest : public dht::EncryptedValue<PeerConnectionRequest> |
| { |
| static const constexpr dht::ValueType& TYPE = dht::ValueType::USER_DATA; |
| static constexpr const char* key_prefix = "peer:"; |
| dht::Value::Id id = dht::Value::INVALID_ID; |
| std::string ice_msg {}; |
| bool isAnswer {false}; |
| std::string connType {}; |
| MSGPACK_DEFINE_MAP(id, ice_msg, isAnswer, connType) |
| }; |
| |
| typedef NS_ENUM(NSInteger, NotificationType) { videoCall, audioCall, gitMessage, unknown }; |
| |
| // Constants |
| const std::string fileSeparator = "/"; |
| NSString* const certificates = @"certificates"; |
| NSString* const crls = @"crls"; |
| NSString* const ocsp = @"ocsp"; |
| NSString* const nameCache = @"namecache"; |
| NSString* const defaultNameServer = @"ns.jami.net"; |
| std::string const nameServerConfiguration = "RingNS.uri"; |
| NSString* const accountConfig = @"config.yml"; |
| |
| std::map<std::string, std::shared_ptr<CallbackWrapperBase>> confHandlers; |
| std::map<std::string, std::string> cachedNames; |
| std::map<std::string, std::string> nameServers; |
| |
| #pragma mark Callbacks registration |
| - (void)registerSignals |
| { |
| confHandlers.insert(exportable_callback<ConfigurationSignal::GetAppDataPath>( |
| [&](const std::string& name, std::vector<std::string>* ret) { |
| if (name == "cache") { |
| auto path = [Constants cachesPath]; |
| ret->push_back(std::string([path.path UTF8String])); |
| } else { |
| auto path = [Constants documentsPath]; |
| ret->push_back(std::string([path.path UTF8String])); |
| } |
| })); |
| |
| confHandlers.insert(exportable_callback<ConversationSignal::MessageReceived>( |
| [&](const std::string& accountId, |
| const std::string& conversationId, |
| std::map<std::string, std::string> message) { |
| if (Adapter.delegate) { |
| NSString* convId = [NSString stringWithUTF8String:conversationId.c_str()]; |
| NSString* account = [NSString stringWithUTF8String:accountId.c_str()]; |
| NSMutableDictionary* interaction = [Utils mapToDictionnary:message]; |
| [Adapter.delegate newInteractionWithConversationId:convId |
| accountId:account |
| message:interaction]; |
| } |
| })); |
| |
| confHandlers.insert(exportable_callback<DataTransferSignal::DataTransferEvent>( |
| [&](const std::string& account_id, |
| const std::string& conversation_id, |
| const std::string& interaction_id, |
| const std::string& file_id, |
| int eventCode) { |
| if (Adapter.delegate) { |
| NSString* accountId = [NSString stringWithUTF8String:account_id.c_str()]; |
| NSString* conversationId = [NSString stringWithUTF8String:conversation_id.c_str()]; |
| NSString* fileId = [NSString stringWithUTF8String:file_id.c_str()]; |
| NSString* interactionId = [NSString stringWithUTF8String:interaction_id.c_str()]; |
| [Adapter.delegate dataTransferEventWithFileId:fileId |
| withEventCode:eventCode |
| accountId:accountId |
| conversationId:conversationId |
| interactionId:interactionId]; |
| } |
| })); |
| |
| confHandlers.insert(exportable_callback<ConversationSignal::ConversationSyncFinished>( |
| [&](const std::string& account_id) { |
| if (Adapter.delegate) { |
| NSString* accountId = [NSString stringWithUTF8String:account_id.c_str()]; |
| [Adapter.delegate conversationSyncCompletedWithAccountId:accountId]; |
| } |
| })); |
| |
| confHandlers.insert(exportable_callback<ConversationSignal::CallConnectionRequest>( |
| [&](const std::string& account_id, const std::string& peer_id, bool hasVideo) { |
| if (Adapter.delegate) { |
| NSString* accountId = [NSString stringWithUTF8String:account_id.c_str()]; |
| NSString* peerId = [NSString stringWithUTF8String:peer_id.c_str()]; |
| [Adapter.delegate receivedCallConnectionRequestWithAccountId:accountId |
| peerId:peerId |
| hasVideo:hasVideo]; |
| } |
| })); |
| //Contact added signal |
| confHandlers.insert(exportable_callback<ConfigurationSignal::ContactAdded>([&](const std::string& accountId, |
| const std::string& uri, |
| bool confirmed) { |
| if(Adapter.delegate) { |
| NSString* accountIdStr = [NSString stringWithUTF8String:accountId.c_str()]; |
| NSString* uriStr = [NSString stringWithUTF8String:uri.c_str()]; |
| [Adapter.delegate receivedContactRequestWithAccountId: accountIdStr peerId: uriStr]; |
| } |
| })); |
| |
| confHandlers.insert(exportable_callback<ConversationSignal::ConversationRequestReceived>([&](const std::string& accountId, const std::string& conversationId, std::map<std::string, std::string> metadata) { |
| if(Adapter.delegate) { |
| NSString* accountIdStr = [NSString stringWithUTF8String:accountId.c_str()]; |
| NSString* convIdStr = [NSString stringWithUTF8String:conversationId.c_str()]; |
| NSMutableDictionary* info = [Utils mapToDictionnary: metadata]; |
| [Adapter.delegate receivedConversationRequestWithAccountId: accountIdStr conversationId: convIdStr metadata:info]; |
| } |
| })); |
| registerSignalHandlers(confHandlers); |
| } |
| |
| #pragma mark AdapterDelegate |
| + (id<AdapterDelegate>)delegate |
| { |
| return _delegate; |
| } |
| |
| + (void)setDelegate:(id<AdapterDelegate>)delegate |
| { |
| _delegate = delegate; |
| } |
| |
| - (bool)downloadFileWithFileId:(NSString*)fileId |
| accountId:(NSString*)accountId |
| conversationId:(NSString*)conversationId |
| interactionId:(NSString*)interactionId |
| withFilePath:(NSString*)filePath |
| { |
| return downloadFile(std::string([accountId UTF8String]), |
| std::string([conversationId UTF8String]), |
| std::string([interactionId UTF8String]), |
| std::string([fileId UTF8String]), |
| std::string([filePath UTF8String])); |
| } |
| |
| - (BOOL)start:(NSString*)accountId |
| { |
| [self registerSignals]; |
| if (initialized() == true) { |
| setAccountActive(std::string([accountId UTF8String]), true); |
| return true; |
| } |
| #if DEBUG |
| int flag = LIBJAMI_FLAG_CONSOLE_LOG | LIBJAMI_FLAG_DEBUG | LIBJAMI_FLAG_IOS_EXTENSION; |
| #else |
| int flag = LIBJAMI_FLAG_IOS_EXTENSION;; |
| #endif |
| if (![[NSThread currentThread] isMainThread]) { |
| __block bool success; |
| dispatch_sync(dispatch_get_main_queue(), ^{ |
| if (init(static_cast<InitFlag>(flag))) { |
| success = start({}); |
| } else { |
| success = false; |
| } |
| }); |
| return success; |
| } else { |
| if (init(static_cast<InitFlag>(flag))) { |
| return start({}); |
| } |
| return false; |
| } |
| } |
| |
| - (void)stop |
| { |
| unregisterSignalHandlers(); |
| confHandlers.clear(); |
| [self setAccountsActive:false]; |
| } |
| |
| - (void)setAccountsActive:(BOOL)active |
| { |
| auto accounts = getAccountList(); |
| for (auto account : accounts) { |
| setAccountActive(account, active, true); |
| } |
| } |
| |
| - (NSDictionary<NSString*, NSString*>*)decrypt:(NSString*)keyPath |
| treated:(NSString*)treatedMessagesPath |
| value:(NSDictionary*)value |
| { |
| if (![[NSFileManager defaultManager] fileExistsAtPath:keyPath]) { |
| return {}; |
| } |
| |
| NSData* data = [[NSFileManager defaultManager] contentsAtPath:keyPath]; |
| const uint8_t* bytes = (const uint8_t*) [data bytes]; |
| dht::crypto::PrivateKey dhtKey(bytes, [data length], ""); |
| |
| Json::Value jsonValue = toJson(value); |
| dht::Value dhtValue(jsonValue); |
| |
| if (!dhtValue.isEncrypted()) { |
| return {}; |
| } |
| try { |
| dht::Sp<dht::Value> decrypted = dhtValue.decrypt(dhtKey); |
| auto unpacked = msgpack::unpack((const char*) decrypted->data.data(), decrypted->data.size()); |
| auto peerCR = unpacked.get().as<PeerConnectionRequest>(); |
| if (peerCR.connType.empty()) { |
| // this value is not a PeerConnectionRequest |
| // check if it a TrustRequest |
| auto conversationRequest = unpacked.get().as<dht::TrustRequest>(); |
| if (!conversationRequest.conversationId.empty()) { |
| if (conversationRequest.service == "cx.ring") { |
| // return git message type to start daemon |
| return @{@"": @"application/im-gitmessage-id"}; |
| } |
| } |
| return {}; |
| } |
| if (isMessageTreated(peerCR.id, [treatedMessagesPath UTF8String])) { |
| return {}; |
| } |
| auto certPath = [[Constants documentsPath] URLByAppendingPathComponent:certificates].path.UTF8String; |
| auto crlPath = [[Constants documentsPath] URLByAppendingPathComponent:crls].path.UTF8String; |
| auto ocspPath = [[Constants documentsPath] URLByAppendingPathComponent:ocsp].path.UTF8String; |
| std::string peerId = getPeerId(decrypted->owner->getId().toString(), |
| certPath, |
| crlPath, |
| ocspPath); |
| return @{@(peerId.c_str()): @(peerCR.connType.c_str())}; |
| } catch (std::runtime_error error) { |
| } |
| return {}; |
| } |
| |
| -(NSString*)getNameFor:(NSString*)address accountId:(NSString*)accountId { |
| return @(getName(std::string([address UTF8String]), std::string([accountId UTF8String])).c_str()); |
| } |
| |
| -(NSString*)nameServerForAccountId:(NSString*)accountId; { |
| auto nameServer = getNameServer(std::string([accountId UTF8String])); |
| return nameServer.empty() ? defaultNameServer : @(nameServer.c_str()); |
| } |
| |
| Json::Value |
| toJson(NSDictionary* value) |
| { |
| Json::Value val; |
| for (NSString* key in value.allKeys) { |
| if ([[value objectForKey:key] isKindOfClass:[NSString class]]) { |
| NSString* stringValue = [value objectForKey:key]; |
| val[key.UTF8String] = stringValue.UTF8String; |
| } else if ([[value objectForKey:key] isKindOfClass:[NSNumber class]]) { |
| NSNumber* number = [value objectForKey:key]; |
| if ([key isEqualToString:@"id"]) { |
| unsigned long long int intValue = [number unsignedLongLongValue]; |
| val[key.UTF8String] = intValue; |
| } else { |
| int intValue = [number intValue]; |
| val[key.UTF8String] = intValue; |
| } |
| } |
| } |
| return val; |
| } |
| |
| std::string getName(std::string addres, std::string accountId) |
| { |
| auto name = cachedNames.find(addres); |
| if (name != cachedNames.end()) { |
| return name->second; |
| } |
| |
| auto ns = getNameServer(accountId); |
| NSURL *url = [NSURL URLWithString: @(ns.c_str())]; |
| NSString* host = [url host]; |
| NSString* nameServer = host.length == 0 ? defaultNameServer : host; |
| std::string namesPath = [[[Constants cachesPath] URLByAppendingPathComponent: nameCache] URLByAppendingPathComponent: nameServer].path.UTF8String; |
| |
| msgpack::unpacker pac; |
| // read file |
| std::ifstream file = std::ifstream(namesPath, std::ios_base::in); |
| if (!file.is_open()) { |
| return ""; |
| } |
| std::string line; |
| while (std::getline(file, line)) { |
| pac.reserve_buffer(line.size()); |
| memcpy(pac.buffer(), line.data(), line.size()); |
| pac.buffer_consumed(line.size()); |
| } |
| |
| // load values |
| msgpack::object_handle oh; |
| if (pac.next(oh)) |
| oh.get().convert(cachedNames); |
| auto cacheRes = cachedNames.find(addres); |
| return cacheRes != cachedNames.end() ? cacheRes->second : std::string {}; |
| } |
| |
| std::string getNameServer(std::string accountId) { |
| auto it = nameServers.find(accountId); |
| if (it != nameServers.end()) { |
| return it->second; |
| } |
| std::string nameServer {}; |
| auto accountConfigPath = [[[Constants documentsPath] URLByAppendingPathComponent: @(accountId.c_str())] URLByAppendingPathComponent: accountConfig].path.UTF8String; |
| try { |
| std::ifstream file = std::ifstream(accountConfigPath, std::ios_base::in); |
| YAML::Node node = YAML::Load(file); |
| file.close(); |
| nameServer = node[nameServerConfiguration].as<std::string>(); |
| if (!nameServer.empty()) { |
| nameServers.insert(std::pair<std::string, std::string>(accountId, nameServer)); |
| } |
| } catch (const std::exception& e) {} |
| return nameServer; |
| } |
| |
| #pragma mark functions copied from the daemon |
| |
| #define LIKELY(expr) (expr) |
| #define UNLIKELY(expr) (expr) |
| |
| /* |
| * Check whether a Unicode (5.2) char is in a valid range. |
| * |
| * The first check comes from the Unicode guarantee to never encode |
| * a point above 0x0010ffff, since UTF-16 couldn't represent it. |
| * |
| * The second check covers surrogate pairs (category Cs). |
| * |
| * @param Char the character |
| */ |
| #define UNICODE_VALID(Char) ((Char) < 0x110000 && (((Char) &0xFFFFF800) != 0xD800)) |
| |
| #define CONTINUATION_CHAR \ |
| if ((*(unsigned char*) p & 0xc0) != 0x80) /* 10xxxxxx */ \ |
| goto error; \ |
| val <<= 6; \ |
| val |= (*(unsigned char*) p) & 0x3f; |
| |
| template<typename ID = dht::Value::Id> |
| bool |
| isMessageTreated(ID messageId, const std::string& path) |
| { |
| std::ifstream file = std::ifstream(path, std::ios_base::in); |
| if (!file.is_open()) { |
| return false; |
| } |
| std::set<ID, std::less<>> treatedMessages; |
| std::string line; |
| while (std::getline(file, line)) { |
| if constexpr (std::is_same<ID, std::string>::value) { |
| treatedMessages.emplace(std::move(line)); |
| } else if constexpr (std::is_integral<ID>::value) { |
| ID vid; |
| if (auto [p, ec] = std::from_chars(line.data(), line.data() + line.size(), vid, 16); |
| ec == std::errc()) { |
| treatedMessages.emplace(vid); |
| } |
| } |
| } |
| return treatedMessages.find(messageId) != treatedMessages.end(); |
| } |
| |
| std::string |
| getPeerId(const std::string& key, |
| const std::string& certPath, |
| const std::string& crlPath, |
| const std::string& ocspPath) |
| { |
| std::map<std::string, std::shared_ptr<dht::crypto::Certificate>> certs; |
| auto dir_content = readDirectory(certPath); |
| unsigned n = 0; |
| for (const auto& f : dir_content) { |
| try { |
| auto crt = std::make_shared<dht::crypto::Certificate>( |
| loadFile(certPath + fileSeparator + f)); |
| auto id = crt->getId().toString(); |
| auto longId = crt->getLongId().toString(); |
| if (id != f && longId != f) |
| throw std::logic_error("Certificate id mismatch"); |
| while (crt) { |
| id = crt->getId().toString(); |
| longId = crt->getLongId().toString(); |
| certs.emplace(std::move(id), crt); |
| certs.emplace(std::move(longId), crt); |
| loadRevocations(*crt, crlPath, ocspPath); |
| crt = crt->issuer; |
| ++n; |
| } |
| } catch (const std::exception& e) { |
| } |
| } |
| auto cit = certs.find(key); |
| if (cit == certs.cend()) { |
| return {}; |
| } |
| dht::InfoHash peer_account_id; |
| if (not foundPeerDevice(cit->second, peer_account_id)) { |
| return {}; |
| } |
| return peer_account_id.toString(); |
| } |
| |
| void |
| loadRevocations(dht::crypto::Certificate& crt, |
| const std::string& crlPath, |
| const std::string& ocspPath) |
| { |
| auto dir = crlPath + fileSeparator + crt.getId().toString(); |
| for (const auto& crl : readDirectory(dir)) { |
| try { |
| crt.addRevocationList( |
| std::make_shared<dht::crypto::RevocationList>(loadFile(dir + fileSeparator + crl))); |
| } catch (const std::exception& e) { |
| } |
| } |
| auto ocsp_dir = ocspPath + fileSeparator + crt.getId().toString(); |
| for (const auto& ocsp : readDirectory(ocsp_dir)) { |
| try { |
| std::string ocsp_filepath = ocsp_dir + fileSeparator + ocsp; |
| auto serial = crt.getSerialNumber(); |
| if (dht::toHex(serial.data(), serial.size()) != ocsp) |
| continue; |
| dht::Blob ocspBlob = loadFile(ocsp_filepath); |
| crt.ocspResponse = std::make_shared<dht::crypto::OcspResponse>(ocspBlob.data(), |
| ocspBlob.size()); |
| } catch (const std::exception& e) { |
| } |
| } |
| } |
| |
| bool |
| foundPeerDevice(const std::shared_ptr<dht::crypto::Certificate>& crt, dht::InfoHash& account_id) |
| { |
| if (not crt) |
| return false; |
| |
| auto top_issuer = crt; |
| while (top_issuer->issuer) |
| top_issuer = top_issuer->issuer; |
| |
| if (top_issuer == crt) { |
| return false; |
| } |
| dht::crypto::TrustList peer_trust; |
| peer_trust.add(*top_issuer); |
| if (not peer_trust.verify(*crt)) { |
| return false; |
| } |
| if (crt->ocspResponse and crt->ocspResponse->getCertificateStatus() != GNUTLS_OCSP_CERT_GOOD) { |
| return false; |
| } |
| account_id = crt->issuer->getId(); |
| return true; |
| } |
| |
| std::vector<std::string> |
| readDirectory(const std::string& dir) |
| { |
| NSError* error; |
| NSFileManager* fileMgr = [NSFileManager defaultManager]; |
| NSArray* files = [fileMgr contentsOfDirectoryAtPath:@(dir.c_str()) error:&error]; |
| |
| std::vector<std::string> vector; |
| for (NSString* fileName in files) { |
| vector.push_back([fileName UTF8String]); |
| } |
| return vector; |
| } |
| |
| std::vector<uint8_t> |
| loadFile(const std::string& path) |
| { |
| if (![[NSFileManager defaultManager] fileExistsAtPath:@(path.c_str())]) { |
| return {}; |
| } |
| NSData* data = [[NSFileManager defaultManager] contentsAtPath:@(path.c_str())]; |
| return [Utils vectorOfUInt8FromData:data]; |
| } |
| |
| std::string |
| utf8_make_valid(const std::string& name) |
| { |
| ssize_t remaining_bytes = name.size(); |
| ssize_t valid_bytes; |
| const char* remainder = name.c_str(); |
| const char* invalid; |
| char* str = NULL; |
| char* pos = nullptr; |
| |
| while (remaining_bytes != 0) { |
| if (utf8_validate_c_str(remainder, remaining_bytes, &invalid)) |
| break; |
| |
| valid_bytes = invalid - remainder; |
| |
| if (str == NULL) |
| // If every byte is replaced by U+FFFD, max(strlen(string)) == 3 * name.size() |
| str = new char[3 * remaining_bytes]; |
| |
| pos = str; |
| |
| strncpy(pos, remainder, valid_bytes); |
| pos += valid_bytes; |
| |
| /* append U+FFFD REPLACEMENT CHARACTER */ |
| pos[0] = '\357'; |
| pos[1] = '\277'; |
| pos[2] = '\275'; |
| |
| pos += 3; |
| |
| remaining_bytes -= valid_bytes + 1; |
| remainder = invalid + 1; |
| } |
| |
| if (str == NULL) |
| return std::string(name); |
| |
| strncpy(pos, remainder, remaining_bytes); |
| pos += remaining_bytes; |
| |
| std::string answer(str, pos - str); |
| assert(utf8_validate_c_str(answer.c_str(), -1, NULL)); |
| |
| delete[] str; |
| |
| return answer; |
| } |
| |
| bool |
| utf8_validate_c_str(const char* str, ssize_t max_len, const char** end) |
| { |
| const char* p; |
| |
| if (max_len < 0) |
| p = fast_validate(str); |
| else |
| p = fast_validate_len(str, max_len); |
| |
| if (end) |
| *end = p; |
| |
| if ((max_len >= 0 && p != str + max_len) || (max_len < 0 && *p != '\0')) |
| return false; |
| else |
| return true; |
| } |
| |
| static const char* |
| fast_validate(const char* str) |
| { |
| char32_t val = 0; |
| char32_t min = 0; |
| const char* p; |
| |
| for (p = str; *p; p++) { |
| if (*(unsigned char*) p < 128) |
| /* done */; |
| else { |
| const char* last; |
| |
| last = p; |
| |
| if ((*(unsigned char*) p & 0xe0) == 0xc0) { /* 110xxxxx */ |
| if (UNLIKELY((*(unsigned char*) p & 0x1e) == 0)) |
| goto error; |
| |
| p++; |
| |
| if (UNLIKELY((*(unsigned char*) p & 0xc0) != 0x80)) /* 10xxxxxx */ |
| goto error; |
| } else { |
| if ((*(unsigned char*) p & 0xf0) == 0xe0) { /* 1110xxxx */ |
| min = (1 << 11); |
| val = *(unsigned char*) p & 0x0f; |
| goto TWO_REMAINING; |
| } else if ((*(unsigned char*) p & 0xf8) == 0xf0) { /* 11110xxx */ |
| min = (1 << 16); |
| val = *(unsigned char*) p & 0x07; |
| } else |
| goto error; |
| |
| p++; |
| CONTINUATION_CHAR; |
| TWO_REMAINING: |
| p++; |
| CONTINUATION_CHAR; |
| p++; |
| CONTINUATION_CHAR; |
| |
| if (UNLIKELY(val < min)) |
| goto error; |
| |
| if (UNLIKELY(!UNICODE_VALID(val))) |
| goto error; |
| } |
| |
| continue; |
| |
| error: |
| return last; |
| } |
| } |
| |
| return p; |
| } |
| |
| static const char* |
| fast_validate_len(const char* str, ssize_t max_len) |
| { |
| char32_t val = 0; |
| char32_t min = 0; |
| const char* p; |
| |
| assert(max_len >= 0); |
| |
| for (p = str; ((p - str) < max_len) && *p; p++) { |
| if (*(unsigned char*) p < 128) |
| /* done */; |
| else { |
| const char* last; |
| |
| last = p; |
| |
| if ((*(unsigned char*) p & 0xe0) == 0xc0) { /* 110xxxxx */ |
| if (UNLIKELY(max_len - (p - str) < 2)) |
| goto error; |
| |
| if (UNLIKELY((*(unsigned char*) p & 0x1e) == 0)) |
| goto error; |
| |
| p++; |
| |
| if (UNLIKELY((*(unsigned char*) p & 0xc0) != 0x80)) /* 10xxxxxx */ |
| goto error; |
| } else { |
| if ((*(unsigned char*) p & 0xf0) == 0xe0) { /* 1110xxxx */ |
| if (UNLIKELY(max_len - (p - str) < 3)) |
| goto error; |
| |
| min = (1 << 11); |
| val = *(unsigned char*) p & 0x0f; |
| goto TWO_REMAINING; |
| } else if ((*(unsigned char*) p & 0xf8) == 0xf0) { /* 11110xxx */ |
| if (UNLIKELY(max_len - (p - str) < 4)) |
| goto error; |
| |
| min = (1 << 16); |
| val = *(unsigned char*) p & 0x07; |
| } else |
| goto error; |
| |
| p++; |
| CONTINUATION_CHAR; |
| TWO_REMAINING: |
| p++; |
| CONTINUATION_CHAR; |
| p++; |
| CONTINUATION_CHAR; |
| |
| if (UNLIKELY(val < min)) |
| goto error; |
| |
| if (UNLIKELY(!UNICODE_VALID(val))) |
| goto error; |
| } |
| |
| continue; |
| |
| error: |
| return last; |
| } |
| } |
| |
| return p; |
| } |
| |
| @end |