From cbf9457ae707ffadb8035854a51669fd95cbc68a Mon Sep 17 00:00:00 2001 From: Adrien Beraud <adrien.beraud@savoirfairelinux.com> Date: Thu, 16 Jan 2025 16:54:47 -0500 Subject: [PATCH] name directory: allow unicode names GitLab: #1097 Change-Id: I742d1306007b3ae4908ee2d46bd5275d1ce2c99a --- .../cx.ring.Ring.ConfigurationManager.xml | 2 + bin/dbus/dbusconfigurationmanager.hpp | 2 +- bin/jni/configurationmanager.i | 4 +- bin/jni/jni_interface.i | 2 +- bin/nodejs/callback.h | 6 +- bin/nodejs/configurationmanager.i | 4 +- bin/nodejs/nodejs_interface.i | 2 +- src/client/configurationmanager.cpp | 11 +- src/jami/configurationmanager_interface.h | 1 + src/jamidht/jamiaccount.cpp | 27 +++-- src/jamidht/namedirectory.cpp | 111 +++++++++--------- src/jamidht/namedirectory.h | 15 ++- src/string_utils.cpp | 29 +++++ src/string_utils.h | 8 ++ test/unitTest/namedirectory/namedirectory.cpp | 7 ++ 15 files changed, 140 insertions(+), 91 deletions(-) diff --git a/bin/dbus/cx.ring.Ring.ConfigurationManager.xml b/bin/dbus/cx.ring.Ring.ConfigurationManager.xml index ebcbfa723..ad5c740c3 100644 --- a/bin/dbus/cx.ring.Ring.ConfigurationManager.xml +++ b/bin/dbus/cx.ring.Ring.ConfigurationManager.xml @@ -367,6 +367,8 @@ </tp:docstring> <arg type="s" name="accountId"> </arg> + <arg type="s" name="requestName"> + </arg> <arg type="i" name="status"> <tp:docstring> Status code: 0 for success diff --git a/bin/dbus/dbusconfigurationmanager.hpp b/bin/dbus/dbusconfigurationmanager.hpp index 7ac259703..5eb11d178 100644 --- a/bin/dbus/dbusconfigurationmanager.hpp +++ b/bin/dbus/dbusconfigurationmanager.hpp @@ -1111,7 +1111,7 @@ private: exportable_serialized_callback<ConfigurationSignal::UserSearchEnded>( std::bind(&DBusConfigurationManager::emitUserSearchEnded, this, _1, _2, _3, _4)), exportable_serialized_callback<ConfigurationSignal::RegisteredNameFound>( - std::bind(&DBusConfigurationManager::emitRegisteredNameFound, this, _1, _2, _3, _4)), + std::bind(&DBusConfigurationManager::emitRegisteredNameFound, this, _1, _2, _3, _4, _5)), exportable_serialized_callback<ConfigurationSignal::DeviceRevocationEnded>( std::bind(&DBusConfigurationManager::emitDeviceRevocationEnded, this, _1, _2, _3)), exportable_serialized_callback<ConfigurationSignal::AccountProfileReceived>( diff --git a/bin/jni/configurationmanager.i b/bin/jni/configurationmanager.i index 87f0c21ad..c499fe52b 100644 --- a/bin/jni/configurationmanager.i +++ b/bin/jni/configurationmanager.i @@ -53,7 +53,7 @@ public: virtual void getDeviceName(std::vector<std::string>* /*name_ret*/){} virtual void nameRegistrationEnded(const std::string& /*account_id*/, int state, const std::string& /*name*/){} - virtual void registeredNameFound(const std::string& /*account_id*/, int state, const std::string& /*address*/, const std::string& /*name*/){} + virtual void registeredNameFound(const std::string& /*account_id*/, const std::string& /*query*/, int state, const std::string& /*address*/, const std::string& /*name*/){} virtual void userSearchEnded(const std::string& /*account_id*/, int state, const std::string& /*query*/, const std::vector<std::map<std::string, std::string>>& /*results*/){} virtual void migrationEnded(const std::string& /*accountId*/, const std::string& /*state*/){} @@ -275,7 +275,7 @@ public: virtual void getDeviceName(std::vector<std::string>* /*name_ret*/){} virtual void nameRegistrationEnded(const std::string& /*account_id*/, int state, const std::string& /*name*/){} - virtual void registeredNameFound(const std::string& /*account_id*/, int state, const std::string& /*address*/, const std::string& /*name*/){} + virtual void registeredNameFound(const std::string& /*account_id*/, const std::string& /*query*/, int state, const std::string& /*address*/, const std::string& /*name*/){} virtual void userSearchEnded(const std::string& /*account_id*/, int state, const std::string& /*query*/, const std::vector<std::map<std::string, std::string>>& /*results*/){} virtual void migrationEnded(const std::string& /*accountId*/, const std::string& /*state*/){} diff --git a/bin/jni/jni_interface.i b/bin/jni/jni_interface.i index 4a0df09dc..74c83c88d 100644 --- a/bin/jni/jni_interface.i +++ b/bin/jni/jni_interface.i @@ -287,7 +287,7 @@ void init(ConfigurationCallback* confM, Callback* callM, PresenceCallback* presM exportable_callback<ConfigurationSignal::GetHardwareAudioFormat>(bind(&ConfigurationCallback::getHardwareAudioFormat, confM, _1 )), exportable_callback<ConfigurationSignal::GetAppDataPath>(bind(&ConfigurationCallback::getAppDataPath, confM, _1, _2 )), exportable_callback<ConfigurationSignal::GetDeviceName>(bind(&ConfigurationCallback::getDeviceName, confM, _1 )), - exportable_callback<ConfigurationSignal::RegisteredNameFound>(bind(&ConfigurationCallback::registeredNameFound, confM, _1, _2, _3, _4 )), + exportable_callback<ConfigurationSignal::RegisteredNameFound>(bind(&ConfigurationCallback::registeredNameFound, confM, _1, _2, _3, _4, _5 )), exportable_callback<ConfigurationSignal::NameRegistrationEnded>(bind(&ConfigurationCallback::nameRegistrationEnded, confM, _1, _2, _3 )), exportable_callback<ConfigurationSignal::UserSearchEnded>(bind(&ConfigurationCallback::userSearchEnded, confM, _1, _2, _3, _4 )), exportable_callback<ConfigurationSignal::MigrationEnded>(bind(&ConfigurationCallback::migrationEnded, confM, _1, _2)), diff --git a/bin/nodejs/callback.h b/bin/nodejs/callback.h index 325cba491..0d5cdebd5 100644 --- a/bin/nodejs/callback.h +++ b/bin/nodejs/callback.h @@ -448,19 +448,21 @@ nameRegistrationEnded(const std::string& accountId, int state, const std::string void registeredNameFound(const std::string& accountId, + const std::string& requestName, int state, const std::string& address, const std::string& name) { std::lock_guard lock(pendingSignalsLock); - pendingSignals.emplace([accountId, state, address, name]() { + pendingSignals.emplace([accountId, requestName, state, address, name]() { Local<Function> func = Local<Function>::New(Isolate::GetCurrent(), registeredNameFoundCb); if (!func.IsEmpty()) { SWIGV8_VALUE callback_args[] = {V8_STRING_NEW_LOCAL(accountId), + V8_STRING_NEW_LOCAL(requestName), SWIGV8_INTEGER_NEW(state), V8_STRING_NEW_LOCAL(address), V8_STRING_NEW_LOCAL(name)}; - func->Call(SWIGV8_CURRENT_CONTEXT(), SWIGV8_NULL(), 4, callback_args); + func->Call(SWIGV8_CURRENT_CONTEXT(), SWIGV8_NULL(), 5, callback_args); } }); diff --git a/bin/nodejs/configurationmanager.i b/bin/nodejs/configurationmanager.i index a1d034f07..2d2108d21 100644 --- a/bin/nodejs/configurationmanager.i +++ b/bin/nodejs/configurationmanager.i @@ -50,7 +50,7 @@ public: virtual void errorAlert(int alert){} virtual void nameRegistrationEnded(const std::string& /*account_id*/, int state, const std::string& /*name*/){} - virtual void registeredNameFound(const std::string& /*account_id*/, int state, const std::string& /*address*/, const std::string& /*name*/){} + virtual void registeredNameFound(const std::string& /*account_id*/, const std::string& /*query*/, int state, const std::string& /*address*/, const std::string& /*name*/){} virtual void userSearchEnded(const std::string& /*account_id*/, int state, const std::string& /*query*/, const std::vector<std::map<std::string, std::string>>& /*results*/){} virtual void migrationEnded(const std::string& /*accountId*/, const std::string& /*state*/){} @@ -263,7 +263,7 @@ public: virtual void errorAlert(int alert){} virtual void nameRegistrationEnded(const std::string& /*account_id*/, int state, const std::string& /*name*/){} - virtual void registeredNameFound(const std::string& /*account_id*/, int state, const std::string& /*address*/, const std::string& /*name*/){} + virtual void registeredNameFound(const std::string& /*account_id*/, const std::string& /*query*/, int state, const std::string& /*address*/, const std::string& /*name*/){} virtual void userSearchEnded(const std::string& /*account_id*/, int state, const std::string& /*query*/, const std::vector<std::map<std::string, std::string>>& /*results*/){} virtual void migrationEnded(const std::string& /*accountId*/, const std::string& /*state*/){} diff --git a/bin/nodejs/nodejs_interface.i b/bin/nodejs/nodejs_interface.i index b486c7b1d..e9c15e224 100644 --- a/bin/nodejs/nodejs_interface.i +++ b/bin/nodejs/nodejs_interface.i @@ -133,7 +133,7 @@ void init(const SWIGV8_VALUE& funcMap){ exportable_callback<ConfigurationSignal::ContactRemoved>(bind(&contactRemoved, _1, _2, _3 )), exportable_callback<ConfigurationSignal::ExportOnRingEnded>(bind(&exportOnRingEnded, _1, _2, _3 )), exportable_callback<ConfigurationSignal::NameRegistrationEnded>(bind(&nameRegistrationEnded, _1, _2, _3 )), - exportable_callback<ConfigurationSignal::RegisteredNameFound>(bind(®isteredNameFound, _1, _2, _3, _4 )), + exportable_callback<ConfigurationSignal::RegisteredNameFound>(bind(®isteredNameFound, _1, _2, _3, _4, _5 )), exportable_callback<ConfigurationSignal::VolatileDetailsChanged>(bind(&volatileDetailsChanged, _1, _2)), exportable_callback<ConfigurationSignal::KnownDevicesChanged>(bind(&knownDevicesChanged, _1, _2 )), exportable_callback<ConfigurationSignal::IncomingAccountMessage>(bind(&incomingAccountMessage, _1, _2, _3, _4 )), diff --git a/src/client/configurationmanager.cpp b/src/client/configurationmanager.cpp index 7110b07af..a5dfa0305 100644 --- a/src/client/configurationmanager.cpp +++ b/src/client/configurationmanager.cpp @@ -1002,11 +1002,12 @@ lookupName(const std::string& account, const std::string& nameserver, const std: { #if HAVE_RINGNS if (account.empty()) { - auto cb = [name](const std::string& result, jami::NameDirectory::Response response) { + auto cb = [name](const std::string& regName, const std::string& address, jami::NameDirectory::Response response) { jami::emitSignal<libjami::ConfigurationSignal::RegisteredNameFound>("", + name, (int) response, - result, - name); + address, + regName); }; if (nameserver.empty()) jami::NameDirectory::lookupUri(name, "", cb); @@ -1028,10 +1029,10 @@ lookupAddress(const std::string& account, const std::string& nameserver, const s if (account.empty()) { jami::NameDirectory::instance(nameserver) .lookupAddress(address, - [address](const std::string& result, + [address](const std::string& regName, const std::string& addr, jami::NameDirectory::Response response) { jami::emitSignal<libjami::ConfigurationSignal::RegisteredNameFound>( - "", (int) response, address, result); + "", address, (int) response, regName, addr); }); return true; } else if (auto acc = jami::Manager::instance().getAccount<JamiAccount>(account)) { diff --git a/src/jami/configurationmanager_interface.h b/src/jami/configurationmanager_interface.h index c9542a6f3..156489812 100644 --- a/src/jami/configurationmanager_interface.h +++ b/src/jami/configurationmanager_interface.h @@ -473,6 +473,7 @@ struct LIBJAMI_PUBLIC ConfigurationSignal { constexpr static const char* name = "RegisteredNameFound"; using cb_type = void(const std::string& /*account_id*/, + const std::string& /*request_name*/, int state, const std::string& /*address*/, const std::string& /*name*/); diff --git a/src/jamidht/jamiaccount.cpp b/src/jamidht/jamiaccount.cpp index 3c30fa561..3979c72fa 100644 --- a/src/jamidht/jamiaccount.cpp +++ b/src/jamidht/jamiaccount.cpp @@ -396,18 +396,18 @@ JamiAccount::newOutgoingCallHelper(const std::shared_ptr<SIPCall>& call, const U auto suffix = stripPrefix(uri.toString()); NameDirectory::lookupUri(suffix, config().nameServer, - [wthis_ = weak(), call](const std::string& result, + [wthis_ = weak(), call](const std::string& regName, const std::string& address, NameDirectory::Response response) { // we may run inside an unknown thread, but following code must // be called in main thread - runOnMainThread([wthis_, result, response, call]() { + runOnMainThread([wthis_, regName, address, response, call]() { if (response != NameDirectory::Response::found) { call->onFailure(EINVAL); return; } if (auto sthis = wthis_.lock()) { try { - sthis->startOutgoingCall(call, result); + sthis->startOutgoingCall(call, regName); } catch (...) { call->onFailure(ENOENT); } @@ -590,11 +590,11 @@ JamiAccount::startOutgoingCall(const std::shared_ptr<SIPCall>& call, const std:: #if HAVE_RINGNS accountManager_->lookupAddress(toUri, - [wCall](const std::string& result, + [wCall](const std::string& regName, const std::string& address, const NameDirectory::Response& response) { if (response == NameDirectory::Response::found) if (auto call = wCall.lock()) { - call->setPeerRegisteredName(result); + call->setPeerRegisteredName(regName); } }); #endif @@ -1441,10 +1441,11 @@ JamiAccount::lookupName(const std::string& name) if (accountManager_) accountManager_->lookupUri(name, config().nameServer, - [acc = getAccountID(), name](const std::string& result, + [acc = getAccountID(), name](const std::string& regName, + const std::string& address, NameDirectory::Response response) { emitSignal<libjami::ConfigurationSignal::RegisteredNameFound>( - acc, (int) response, result, name); + acc, name, (int) response, address, regName); }); } @@ -1455,11 +1456,11 @@ JamiAccount::lookupAddress(const std::string& addr) auto acc = getAccountID(); if (accountManager_) accountManager_->lookupAddress( - addr, [acc, addr](const std::string& result, NameDirectory::Response response) { - emitSignal<libjami::ConfigurationSignal::RegisteredNameFound>(acc, + addr, [acc, addr](const std::string& regName, const std::string& address, NameDirectory::Response response) { + emitSignal<libjami::ConfigurationSignal::RegisteredNameFound>(acc, addr, (int) response, - addr, - result); + address, + regName); }); } @@ -1848,11 +1849,11 @@ JamiAccount::doRegister_() // Look for registered name accountManager_->lookupAddress( accountManager_->getInfo()->accountId, - [w = weak()](const std::string& result, const NameDirectory::Response& response) { + [w = weak()](const std::string& regName, const std::string& address, const NameDirectory::Response& response) { if (auto this_ = w.lock()) { if (response == NameDirectory::Response::found or response == NameDirectory::Response::notFound) { - const auto& nameResult = response == NameDirectory::Response::found ? result + const auto& nameResult = response == NameDirectory::Response::found ? regName : ""; if (this_->setRegisteredName(nameResult)) { this_->editConfig([&](JamiAccountConfig& config) { diff --git a/src/jamidht/namedirectory.cpp b/src/jamidht/namedirectory.cpp index 6d8ae56e5..94a0ab2ca 100644 --- a/src/jamidht/namedirectory.cpp +++ b/src/jamidht/namedirectory.cpp @@ -55,10 +55,20 @@ constexpr const char DEFAULT_SERVER_HOST[] = "https://ns.jami.net"; const std::string HEX_PREFIX = "0x"; constexpr std::chrono::seconds SAVE_INTERVAL {5}; -/** Parser for URIs. ( protocol ) ( username ) ( hostname ) */ +/** + Parser for URIs. ( protocol ) ( username ) ( hostname ) + - Requires "@" if a username is present (e.g., "user@domain.com"). + - Allows common URL-safe special characters in usernames and domains. + + Regex breakdown: + 1. `([a-zA-Z]+:(?://)?)?` → Optional scheme ("http://", "ftp://"). + 2. `(?:([^\s@]{1,64})@)?` → Optional username (max 64 chars, Unicode allowed). + 3. `([^\s@]+)` → Domain or standalone name (Unicode allowed, no spaces or "@"). + + */ const std::regex URI_VALIDATOR { - "^([a-zA-Z]+:(?://)?)?(?:([a-z0-9-_]{1,64})@)?([a-zA-Z0-9\\-._~%!$&'()*+,;=:\\[\\]]+)"}; -const std::regex NAME_VALIDATOR {"^[a-zA-Z0-9-_]{3,32}$"}; + R"(^([a-zA-Z]+:(?://)?)?(?:([\w\-.~%!$&'()*+,;=]{1,64}|[^\s@]{1,64})@)?([^\s@]+)$)" +}; constexpr size_t MAX_RESPONSE_SIZE {1024ul * 1024}; @@ -91,7 +101,7 @@ NameDirectory::lookupUri(std::string_view uri, const std::string& default_server } } JAMI_ERROR("Unable to parse URI: {}", uri); - cb("", Response::invalidResponse); + cb("", "", Response::invalidResponse); } NameDirectory::NameDirectory(const std::string& serverUrl, std::shared_ptr<dht::Logger> l) @@ -163,8 +173,8 @@ void NameDirectory::lookupAddress(const std::string& addr, LookupCallback cb) { auto cacheResult = nameCache(addr); - if (not cacheResult.empty()) { - cb(cacheResult, Response::found); + if (not cacheResult.first.empty()) { + cb(cacheResult.first, cacheResult.second, Response::found); return; } auto request = std::make_shared<Request>(*httpContext_, @@ -175,16 +185,16 @@ NameDirectory::lookupAddress(const std::string& addr, LookupCallback cb) setHeaderFields(*request); request->add_on_done_callback( [this, cb = std::move(cb), addr](const dht::http::Response& response) { - if (response.status_code >= 400 && response.status_code < 500) { + if (response.status_code > 400 && response.status_code < 500) { auto cacheResult = nameCache(addr); - if (not cacheResult.empty()) - cb(cacheResult, Response::found); + if (not cacheResult.first.empty()) + cb(cacheResult.first, cacheResult.second, Response::found); else - cb("", Response::notFound); + cb("", "", Response::notFound); } else if (response.status_code != 200) { JAMI_ERROR("Address lookup for {} on {} failed with code={}", addr, serverUrl_, response.status_code); - cb("", Response::error); + cb("", "", Response::error); } else { try { Json::Value json; @@ -198,25 +208,25 @@ NameDirectory::lookupAddress(const std::string& addr, LookupCallback cb) JAMI_DBG("Address lookup for %s: Unable to parse server response: %s", addr.c_str(), response.body.c_str()); - cb("", Response::error); + cb("", "", Response::error); return; } auto name = json["name"].asString(); if (name.empty()) { - cb(name, Response::notFound); + cb(name, addr, Response::notFound); return; } JAMI_DEBUG("Found name for {}: {}", addr, name); { std::lock_guard l(cacheLock_); - addrCache_.emplace(name, addr); - nameCache_.emplace(addr, name); + addrCache_.emplace(name, std::pair(name, addr)); + nameCache_.emplace(addr, std::pair(name, addr)); } - cb(name, Response::found); + cb(name, addr, Response::found); scheduleCacheSave(); } catch (const std::exception& e) { JAMI_ERROR("Error when performing address lookup: {}", e.what()); - cb("", Response::error); + cb("", "", Response::error); } } std::lock_guard lk(requestsMtx_); @@ -246,33 +256,28 @@ NameDirectory::verify(const std::string& name, } void -NameDirectory::lookupName(const std::string& n, LookupCallback cb) +NameDirectory::lookupName(const std::string& name, LookupCallback cb) { - std::string name {n}; - if (not validateName(name)) { - cb("", Response::invalidResponse); - return; - } - toLower(name); - std::string cacheResult = addrCache(name); - if (not cacheResult.empty()) { - cb(cacheResult, Response::found); + auto cacheResult = addrCache(name); + if (not cacheResult.first.empty()) { + cb(cacheResult.first, cacheResult.second, Response::found); return; } + auto encodedName = urlEncode(name); auto request = std::make_shared<Request>(*httpContext_, resolver_, - serverUrl_ + QUERY_NAME + name); + serverUrl_ + QUERY_NAME + encodedName); try { request->set_method(restinio::http_method_get()); setHeaderFields(*request); request->add_on_done_callback([this, name, cb = std::move(cb)]( const dht::http::Response& response) { - if (response.status_code >= 400 && response.status_code < 500) - cb("", Response::notFound); + if (response.status_code > 400 && response.status_code < 500) + cb("", "", Response::notFound); else if (response.status_code < 200 || response.status_code > 299) { JAMI_ERROR("Name lookup for {} on {} failed with code={}", name, serverUrl_, response.status_code); - cb("", Response::error); + cb("", "", Response::error); } else { try { Json::Value json; @@ -285,9 +290,10 @@ NameDirectory::lookupName(const std::string& n, LookupCallback cb) &err)) { JAMI_ERROR("Name lookup for {}: Unable to parse server response: {}", name, response.body); - cb("", Response::error); + cb("", "", Response::error); return; } + auto nameResult = json["name"].asString(); auto addr = json["addr"].asString(); auto publickey = json["publickey"].asString(); auto signature = json["signature"].asString(); @@ -295,32 +301,33 @@ NameDirectory::lookupName(const std::string& n, LookupCallback cb) if (!addr.compare(0, HEX_PREFIX.size(), HEX_PREFIX)) addr = addr.substr(HEX_PREFIX.size()); if (addr.empty()) { - cb("", Response::notFound); + cb("", "", Response::notFound); return; } if (not publickey.empty() and not signature.empty()) { try { auto pk = dht::crypto::PublicKey(base64::decode(publickey)); - if (pk.getId().toString() != addr or not verify(name, pk, signature)) { - cb("", Response::invalidResponse); + if (pk.getId().toString() != addr or not verify(nameResult, pk, signature)) { + cb("", "", Response::invalidResponse); return; } } catch (const std::exception& e) { - cb("", Response::invalidResponse); + cb("", "", Response::invalidResponse); return; } } JAMI_DEBUG("Found address for {}: {}", name, addr); { std::lock_guard l(cacheLock_); - addrCache_.emplace(name, addr); - nameCache_.emplace(addr, name); + addrCache_.emplace(name, std::pair(nameResult, addr)); + addrCache_.emplace(nameResult, std::pair(nameResult, addr)); + nameCache_.emplace(addr, std::pair(nameResult, addr)); } - cb(addr, Response::found); + cb(nameResult, addr, Response::found); scheduleCacheSave(); } catch (const std::exception& e) { JAMI_ERROR("Error when performing name lookup: {}", e.what()); - cb("", Response::error); + cb("", "", Response::error); } } if (auto req = response.request.lock()) @@ -339,12 +346,6 @@ NameDirectory::lookupName(const std::string& n, LookupCallback cb) } } -bool -NameDirectory::validateName(const std::string& name) const -{ - return std::regex_match(name, NAME_VALIDATOR); -} - using Blob = std::vector<uint8_t>; void NameDirectory::registerName(const std::string& addr, @@ -355,14 +356,10 @@ NameDirectory::registerName(const std::string& addr, const std::string& publickey) { std::string name {n}; - if (not validateName(name)) { - cb(RegistrationResponse::invalidName, name); - return; - } toLower(name); auto cacheResult = addrCache(name); - if (not cacheResult.empty()) { - if (cacheResult == addr) + if (not cacheResult.first.empty()) { + if (cacheResult.second == addr) cb(RegistrationResponse::success, name); else cb(RegistrationResponse::alreadyTaken, name); @@ -381,9 +378,11 @@ NameDirectory::registerName(const std::string& addr, owner, signedname, base64::encode(publickey)); + + auto encodedName = urlEncode(name); auto request = std::make_shared<Request>(*httpContext_, resolver_, - serverUrl_ + QUERY_NAME + name); + serverUrl_ + QUERY_NAME + encodedName); try { request->set_method(restinio::http_method_post()); setHeaderFields(*request); @@ -433,8 +432,8 @@ NameDirectory::registerName(const std::string& addr, name, addr, success ? "success" : "failure"); if (success) { std::lock_guard l(cacheLock_); - addrCache_.emplace(name, addr); - nameCache_.emplace(addr, name); + addrCache_.emplace(name, std::pair(name, addr)); + nameCache_.emplace(addr, std::pair(name, addr)); } cb(success ? RegistrationResponse::success : RegistrationResponse::error, name); } @@ -512,7 +511,7 @@ NameDirectory::loadCache() if (pac.next(oh)) oh.get().convert(nameCache_); for (const auto& m : nameCache_) - addrCache_.emplace(m.second, m.first); + addrCache_.emplace(m.second.second, m.second); JAMI_DEBUG("Loaded {:d} name-address mappings", nameCache_.size()); } diff --git a/src/jamidht/namedirectory.h b/src/jamidht/namedirectory.h index a7ebb18e7..fda66953a 100644 --- a/src/jamidht/namedirectory.h +++ b/src/jamidht/namedirectory.h @@ -64,7 +64,7 @@ public: unsupported }; - using LookupCallback = std::function<void(const std::string& result, Response response)>; + using LookupCallback = std::function<void(const std::string& name, const std::string& address, Response response)>; using SearchResult = std::vector<std::map<std::string, std::string>>; using SearchCallback = std::function<void(const SearchResult& result, Response response)>; using RegistrationCallback = std::function<void(RegistrationResponse response, const std::string& name)>; @@ -117,27 +117,26 @@ private: std::map<std::string, std::string> pendingRegistrations_ {}; - std::map<std::string, std::string> nameCache_ {}; - std::map<std::string, std::string> addrCache_ {}; + std::map<std::string, std::pair<std::string, std::string>> nameCache_ {}; + std::map<std::string, std::pair<std::string, std::string>> addrCache_ {}; std::weak_ptr<Task> saveTask_; void setHeaderFields(dht::http::Request& request); - std::string nameCache(const std::string& addr) + std::pair<std::string, std::string> nameCache(const std::string& addr) { std::lock_guard l(cacheLock_); auto cacheRes = nameCache_.find(addr); - return cacheRes != nameCache_.end() ? cacheRes->second : std::string {}; + return cacheRes != nameCache_.end() ? cacheRes->second : std::pair<std::string, std::string> {}; } - std::string addrCache(const std::string& name) + std::pair<std::string, std::string> addrCache(const std::string& name) { std::lock_guard l(cacheLock_); auto cacheRes = addrCache_.find(name); - return cacheRes != addrCache_.end() ? cacheRes->second : std::string {}; + return cacheRes != addrCache_.end() ? cacheRes->second : std::pair<std::string, std::string> {}; } - bool validateName(const std::string& name) const; static bool verify(const std::string& name, const dht::crypto::PublicKey& publickey, const std::string& signature); diff --git a/src/string_utils.cpp b/src/string_utils.cpp index 5a640627c..fb11b0f44 100644 --- a/src/string_utils.cpp +++ b/src/string_utils.cpp @@ -170,4 +170,33 @@ string_split_set(std::string& str, std::string_view separator) return output; } +std::string urlEncode(std::string_view input) +{ + if (input.empty()) { + return {}; + } + + auto isAsciiAlnum = [](unsigned char c) { + return (c >= '0' && c <= '9') + || (c >= 'A' && c <= 'Z') + || (c >= 'a' && c <= 'z'); + }; + + std::ostringstream encoded; + // Use uppercase for hex digits + encoded << std::uppercase << std::hex; + + for (unsigned char c : input) { + // If character is unreserved per RFC 3986, keep it as-is + if (isAsciiAlnum(c) || c == '-' || c == '_' || c == '.' || c == '~') { + encoded << c; + } else { + // Otherwise, percent-encode + encoded << '%' << std::setw(2) << std::setfill('0') << static_cast<int>(c); + } + } + + return encoded.str(); +} + } // namespace jami diff --git a/src/string_utils.h b/src/string_utils.h index b6bb7cfe2..5b74d8db9 100644 --- a/src/string_utils.h +++ b/src/string_utils.h @@ -207,6 +207,14 @@ std::string string_join(const std::set<std::string>& set, std::string_view separ std::set<std::string> string_split_set(std::string& str, std::string_view separator = "/"); +/** + * Percent-encode a string according to RFC 3986 unreserved characters. + * + * Only [0-9A-Za-z], '-' , '_' , '.' , '~' remain unencoded. + * Everything else (including non-ASCII) becomes '%XX'. + */ +std::string urlEncode(std::string_view input); + } // namespace jami // Add string operators crucially missing from standard diff --git a/test/unitTest/namedirectory/namedirectory.cpp b/test/unitTest/namedirectory/namedirectory.cpp index a87023890..45a8001f5 100644 --- a/test/unitTest/namedirectory/namedirectory.cpp +++ b/test/unitTest/namedirectory/namedirectory.cpp @@ -190,6 +190,7 @@ NameDirectoryTest::testRegisterName() // Watch signals confHandlers.insert(libjami::exportable_callback<libjami::ConfigurationSignal::NameRegistrationEnded>( [&](const std::string&, + const std::string&, int status, const std::string&) { nameRegistered = status == 0; @@ -211,6 +212,7 @@ NameDirectoryTest::testLookupName() // Watch signals confHandlers.insert(libjami::exportable_callback<libjami::ConfigurationSignal::RegisteredNameFound>( [&](const std::string&, + const std::string&, int status, const std::string&, const std::string&) { @@ -233,6 +235,7 @@ NameDirectoryTest::testLookupNameInvalid() // Watch signals confHandlers.insert(libjami::exportable_callback<libjami::ConfigurationSignal::RegisteredNameFound>( [&](const std::string&, + const std::string&, int status, const std::string&, const std::string&) { @@ -255,6 +258,7 @@ NameDirectoryTest::testLookupNameNotFound() // Watch signals confHandlers.insert(libjami::exportable_callback<libjami::ConfigurationSignal::RegisteredNameFound>( [&](const std::string&, + const std::string&, int status, const std::string&, const std::string&) { @@ -277,6 +281,7 @@ NameDirectoryTest::testLookupAddr() // Watch signals confHandlers.insert(libjami::exportable_callback<libjami::ConfigurationSignal::RegisteredNameFound>( [&](const std::string&, + const std::string&, int status, const std::string&, const std::string&) { @@ -299,6 +304,7 @@ NameDirectoryTest::testLookupAddrInvalid() // Watch signals confHandlers.insert(libjami::exportable_callback<libjami::ConfigurationSignal::RegisteredNameFound>( [&](const std::string&, + const std::string&, int status, const std::string&, const std::string&) { @@ -321,6 +327,7 @@ NameDirectoryTest::testLookupAddrNotFound() // Watch signals confHandlers.insert(libjami::exportable_callback<libjami::ConfigurationSignal::RegisteredNameFound>( [&](const std::string&, + const std::string&, int status, const std::string&, const std::string&) { -- GitLab