//// AddressManager.cpp// libraries/networking/src//// Created by Stephen Birarda on 2014-09-10.// Copyright 2014 High Fidelity, Inc.// Copyright 2020 Vircadia contributors.// Copyright 2023 Overte e.V.//// Distributed under the Apache License, Version 2.0.// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html// SPDX-License-Identifier: Apache-2.0// #include "AddressManager.h" #include <QGuiApplication>#include <QClipboard>#include <QDebug>#include <QJsonDocument>#include <QRegExp>#include <QStringList>#include <QThread> #include <BuildInfo.h>#include <GLMHelpers.h>#include <NumericalConstants.h>#include <SettingHandle.h>#include <UUID.h> #include "NodeList.h"#include "NetworkLogging.h"#include "NetworkingConstants.h"#include "UserActivityLogger.h"#include "udt/PacketHeaders.h" const QString REDIRECT_HIFI_ADDRESS = NetworkingConstants::REDIRECT_HIFI_ADDRESS;const QString ADDRESS_MANAGER_SETTINGS_GROUP = "AddressManager";const QString SETTINGS_CURRENT_ADDRESS_KEY = "address"; const QString DEFAULT_OVERTE_ADDRESS = (!std::string_view(BuildInfo::PRELOADED_STARTUP_LOCATION).empty()) ? BuildInfo::PRELOADED_STARTUP_LOCATION : NetworkingConstants::DEFAULT_OVERTE_ADDRESS;const QString DEFAULT_HOME_ADDRESS = (!std::string_view(BuildInfo::PRELOADED_STARTUP_LOCATION).empty()) ? BuildInfo::PRELOADED_STARTUP_LOCATION : NetworkingConstants::DEFAULT_OVERTE_ADDRESS; Setting::Handle<QUrl> currentAddressHandle(QStringList() << ADDRESS_MANAGER_SETTINGS_GROUP << "address", DEFAULT_OVERTE_ADDRESS); bool AddressManager::isConnected() { return DependencyManager::get<NodeList>()->getDomainHandler().isConnected();} QString AddressManager::getProtocol() const { return _domainURL.scheme();} QUrl AddressManager::currentAddress(bool domainOnly) const { QUrl hifiURL = _domainURL; if (!domainOnly && hifiURL.scheme() == URL_SCHEME_OVERTE) { hifiURL.setPath(currentPath()); } return hifiURL;} QUrl AddressManager::currentFacingAddress() const { auto hifiURL = currentAddress(); if (hifiURL.scheme() == URL_SCHEME_OVERTE) { hifiURL.setPath(currentFacingPath()); } return hifiURL;} QUrl AddressManager::currentShareableAddress(bool domainOnly) const { if (!_shareablePlaceName.isEmpty()) { // if we have a shareable place name use that instead of whatever the current host is QUrl hifiURL; hifiURL.setScheme(URL_SCHEME_OVERTE); hifiURL.setHost(_shareablePlaceName); if (!domainOnly) { hifiURL.setPath(currentPath()); } return hifiURL; } else { return currentAddress(domainOnly); }} QUrl AddressManager::currentPublicAddress(bool domainOnly) const { // return an address that can be used by others to visit this client's current location. If // in a serverless domain (which can't be visited) return an empty URL. QUrl shareableAddress = currentShareableAddress(domainOnly); if (shareableAddress.scheme() != URL_SCHEME_OVERTE) { return QUrl(); // file: urls aren't public } return shareableAddress;} QUrl AddressManager::currentFacingShareableAddress() const { auto hifiURL = currentShareableAddress(); if (hifiURL.scheme() == URL_SCHEME_OVERTE) { hifiURL.setPath(currentFacingPath()); } return hifiURL;} QUrl AddressManager::currentFacingPublicAddress() const { // return an address that can be used by others to visit this client's current location. If // in a serverless domain (which can't be visited) return an empty URL. QUrl shareableAddress = currentFacingShareableAddress(); if (shareableAddress.scheme() != URL_SCHEME_OVERTE) { return QUrl(); // file: urls aren't public } return shareableAddress;} QUrl AddressManager::lastAddress() const { return _lastVisitedURL;} void AddressManager::loadSettings(const QString& lookupString) { if (lookupString.isEmpty()) { handleUrl(currentAddressHandle.get(), LookupTrigger::StartupFromSettings); } else { handleUrl(lookupString, LookupTrigger::StartupFromSettings); }} void AddressManager::goBack() { if (_backStack.size() > 0) { // go to that address handleUrl(_backStack.pop(), LookupTrigger::Back); if (_backStack.size() == 0) { // the back stack is now empty so it is no longer possible to go back - emit that signal emit goBackPossible(false); } }} void AddressManager::goForward() { if (_forwardStack.size() > 0) { // pop a URL from the forwardStack and go to that address handleUrl(_forwardStack.pop(), LookupTrigger::Forward); if (_forwardStack.size() == 0) { // the forward stack is empty so it is no longer possible to go forwards - emit that signal emit goForwardPossible(false); } }} void AddressManager::storeCurrentAddress() { auto url = currentAddress(); if (url.scheme() == HIFI_URL_SCHEME_FILE || url.scheme() == HIFI_URL_SCHEME_HTTP || url.scheme() == HIFI_URL_SCHEME_HTTPS || (url.scheme() == URL_SCHEME_OVERTE && !url.host().isEmpty())) { // TODO -- once Octree::readFromURL no-longer takes over the main event-loop, serverless-domain urls can // be loaded over http(s) // url.scheme() == HIFI_URL_SCHEME_HTTP || // url.scheme() == HIFI_URL_SCHEME_HTTPS || bool isInErrorState = DependencyManager::get<NodeList>()->getDomainHandler().isInErrorState(); if (isConnected()) { if (isInErrorState) { // save the last address visited before the problem url. currentAddressHandle.set(lastAddress()); } else { currentAddressHandle.set(url); } } else { qCWarning(networking) << "Ignoring attempt to save current address because not connected to domain:" << url; } } else { qCWarning(networking) << "Ignoring attempt to save current address with an invalid url:" << url; }} QString AddressManager::currentPath(bool withOrientation) const { if (_positionGetter) { QString pathString = "/" + createByteArray(_positionGetter()); if (withOrientation) { if (_orientationGetter) { QString orientationString = createByteArray(_orientationGetter()); pathString += "/" + orientationString; } else { qCDebug(networking) << "Cannot add orientation to path without a getter for position." << "Call AddressManager::setOrientationGetter to pass a function that will return a glm::quat"; } } return pathString; } else { qCDebug(networking) << "Cannot create address path without a getter for position." << "Call AddressManager::setPositionGetter to pass a function that will return a const glm::vec3&"; return QString(); }} QString AddressManager::currentFacingPath() const { if (_positionGetter && _orientationGetter) { auto position = _positionGetter(); auto orientation = _orientationGetter(); // move the user a couple units away const float DISTANCE_TO_USER = 2.0f; position += orientation * Vectors::FRONT * DISTANCE_TO_USER; // rotate the user by 180 degrees orientation = orientation * glm::angleAxis(PI, Vectors::UP); return "/" + createByteArray(position) + "/" + createByteArray(orientation); } else { qCDebug(networking) << "Cannot create address path without a getter for position/orientation."; return QString(); }} const JSONCallbackParameters& AddressManager::apiCallbackParameters() { static bool hasSetupParameters = false; static JSONCallbackParameters callbackParams; if (!hasSetupParameters) { callbackParams.callbackReceiver = this; callbackParams.jsonCallbackMethod = "handleAPIResponse"; callbackParams.errorCallbackMethod = "handleAPIError"; } return callbackParams;} bool AddressManager::handleUrl(const QUrl& lookupUrlIn, LookupTrigger trigger, const QString& lookupUrlInString) { static QString URL_TYPE_USER = "user"; static QString URL_TYPE_DOMAIN_ID = "domain_id"; static QString URL_TYPE_PLACE = "place"; static QString URL_TYPE_NETWORK_ADDRESS = "network_address"; QUrl lookupUrl = lookupUrlIn; if (!lookupUrl.host().isEmpty() && !lookupUrl.path().isEmpty()) { // Assignment clients ping for empty url until assigned. Don't spam. qCDebug(networking) << "Trying to go to URL" << lookupUrl.toString(); } if (lookupUrl.scheme().isEmpty() && !lookupUrl.path().startsWith("/")) { // 'urls' without schemes are taken as domain names, as opposed to // simply a path portion of a url, so we need to set the scheme lookupUrl.setScheme(URL_SCHEME_OVERTE); } static const QRegExp PORT_REGEX = QRegExp("\\d{1,5}(\\/.*)?"); if(!lookupUrl.scheme().isEmpty() && lookupUrl.host().isEmpty() && PORT_REGEX.exactMatch(lookupUrl.path())) { // this is in the form somewhere:<port>, convert it to hifi://somewhere:<port> lookupUrl = QUrl(URL_SCHEME_OVERTE + "://" + lookupUrl.toString()); } // it should be noted that url's in the form // somewhere:<port> are not valid, as that // would indicate that the scheme is 'somewhere' // use hifi://somewhere:<port> instead if (lookupUrl.scheme() == URL_SCHEME_OVERTE || lookupUrlInString.startsWith(URL_SCHEME_OVERTE + "://")) { QString lookupUrlString; if (lookupUrlInString.startsWith(URL_SCHEME_OVERTE + "://")) { lookupUrlString = lookupUrlInString; } else { lookupUrlString = lookupUrl.toString(QUrl::FullyEncoded); } if (lookupUrl.host().isEmpty()) { // this was in the form hifi:/somewhere or hifi:somewhere. Fix it by making it hifi://somewhere static const QRegExp HIFI_SCHEME_REGEX = QRegExp(URL_SCHEME_OVERTE + ":\\/{0,2}", Qt::CaseInsensitive); lookupUrl = QUrl(lookupUrl.toString().replace(HIFI_SCHEME_REGEX, URL_SCHEME_OVERTE + "://")); } DependencyManager::get<NodeList>()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::LookupAddress); // there are 4 possible lookup strings // 1. global place name (name of domain or place) - example: sanfrancisco // 2. user name (prepended with @) - example: @philip // 3. location string (posX,posY,posZ/eulerX,eulerY,eulerZ) // 4. domain network address (IP or dns resolvable hostname) // use our regex'ed helpers to figure out what we're supposed to do with this if (handleUsername(lookupUrl.authority())) { // handled a username for lookup UserActivityLogger::getInstance().wentTo(trigger, URL_TYPE_USER, lookupUrl.toString()); // save the last visited domain URL. _lastVisitedURL = lookupUrl; // in case we're failing to connect to where we thought this user was // store their username as previous lookup so we can refresh their location via API _previousAPILookup = lookupUrl; } else { // we're assuming this is either a network address or global place name // check if it is a network address first bool hostChanged; if (handleNetworkAddress(lookupUrl.host() + (lookupUrl.port() == -1 ? "" : ":" + QString::number(lookupUrl.port())), trigger, hostChanged)) { UserActivityLogger::getInstance().wentTo(trigger, URL_TYPE_NETWORK_ADDRESS, lookupUrl.toString()); // save the last visited domain URL. _lastVisitedURL = lookupUrl; // a network address lookup clears the previous lookup since we don't expect to re-attempt it _previousAPILookup.clear(); // If the host changed then we have already saved to history if (hostChanged) { trigger = Internal; } // if we were not passed a path, use the index path auto path = lookupUrl.path(); if (path.isEmpty()) { path = INDEX_PATH; } // we may have a path that defines a relative viewpoint - if so we should jump to that now handlePath(path, trigger); } else if (handleDomainID(lookupUrl.host())){ UserActivityLogger::getInstance().wentTo(trigger, URL_TYPE_DOMAIN_ID, lookupUrl.toString()); // save the last visited domain URL. _lastVisitedURL = lookupUrl; // store this domain ID as the previous lookup in case we're failing to connect and want to refresh API info _previousAPILookup = lookupUrl; // no place name - this is probably a domain ID // try to look up the domain ID on the metaverse API attemptDomainIDLookup(lookupUrl.host(), lookupUrl.path(), trigger); } else { // wasn't an address - lookup the place name // we may have a path that defines a relative viewpoint - pass that through the lookup so we can go to it after UserActivityLogger::getInstance().wentTo(trigger, URL_TYPE_PLACE, lookupUrl.toString()); // save the last visited domain URL. _lastVisitedURL = lookupUrl; // store this place name as the previous lookup in case we fail to connect and want to refresh API info _previousAPILookup = lookupUrl; // Let's convert this to a QString for processing in case there are spaces in it. if (lookupUrlString.contains(URL_SCHEME_OVERTE + "://", Qt::CaseInsensitive)) { lookupUrlString = lookupUrlString.replace((URL_SCHEME_OVERTE + "://"), ""); } else if (lookupUrlString.contains(URL_SCHEME_OVERTE + ":/", Qt::CaseInsensitive)) { lookupUrlString = lookupUrlString.replace((URL_SCHEME_OVERTE + ":/"), ""); } else if (lookupUrlString.contains(URL_SCHEME_OVERTE + ":", Qt::CaseInsensitive)) { lookupUrlString = lookupUrlString.replace((URL_SCHEME_OVERTE + ":"), ""); } // Get the path and then strip it out. QString lookupUrlStringPath; int index = lookupUrlString.indexOf('/'); if (index != -1) { lookupUrlStringPath = lookupUrlString.mid(index); lookupUrlString.replace(lookupUrlStringPath, ""); } if (!lookupUrlString.isEmpty()) { attemptPlaceNameLookup(lookupUrlString, lookupUrlStringPath, trigger); } } } return true; } else if (lookupUrl.toString().startsWith('/')) { qCDebug(networking) << "Going to relative path" << lookupUrl.path(); // a path lookup clears the previous lookup since we don't expect to re-attempt it _previousAPILookup.clear(); // if this is a relative path then handle it as a relative viewpoint handlePath(lookupUrl.path(), trigger, true); emit lookupResultsFinished(); return true; } else if (lookupUrl.scheme() == HIFI_URL_SCHEME_FILE || lookupUrl.scheme() == HIFI_URL_SCHEME_HTTPS || lookupUrl.scheme() == HIFI_URL_SCHEME_HTTP) { // Save the last visited domain URL. _lastVisitedURL = lookupUrl; _previousAPILookup.clear(); _shareablePlaceName.clear(); setDomainInfo(lookupUrl, trigger); emit lookupResultsFinished(); QString path = DOMAIN_SPAWNING_POINT; QUrlQuery queryArgs(lookupUrl); const QString LOCATION_QUERY_KEY = "location"; if (queryArgs.hasQueryItem(LOCATION_QUERY_KEY)) { path = queryArgs.queryItemValue(LOCATION_QUERY_KEY); } else { path = DEFAULT_NAMED_PATH; } handlePath(path, LookupTrigger::Internal, false); return true; } return false;} static const QString LOCALHOST = "localhost"; bool isPossiblePlaceName(QString possiblePlaceName) { bool result { false }; int length = possiblePlaceName.length(); static const int MINIMUM_PLACENAME_LENGTH = 1; static const int MAXIMUM_PLACENAME_LENGTH = 64; if (possiblePlaceName.toLower() != LOCALHOST && length >= MINIMUM_PLACENAME_LENGTH && length <= MAXIMUM_PLACENAME_LENGTH) { const QRegExp PLACE_NAME_REGEX = QRegExp("^[0-9A-Za-z](([0-9A-Za-z]|[-_](?![-_]))*[^\\W_]$|$)"); result = PLACE_NAME_REGEX.indexIn(possiblePlaceName) == 0; } return result;} void AddressManager::handleLookupString(const QString& lookupString, bool fromSuggestions) { QString trimmedString = lookupString.trimmed(); if (!trimmedString.isEmpty()) { resetConfirmConnectWithoutAvatarEntities(); // make this a valid hifi URL and handle it off to handleUrl handleUrl(trimmedString, fromSuggestions ? Suggestions : UserInput, trimmedString); }} const QString DATA_OBJECT_DOMAIN_KEY = "domain"; void AddressManager::handleAPIResponse(QNetworkReply* requestReply) { QJsonObject responseObject = QJsonDocument::fromJson(requestReply->readAll()).object(); QJsonObject dataObject = responseObject["data"].toObject(); // Lookup succeeded, don't keep re-trying it (especially on server restarts) _previousAPILookup.clear(); if (!dataObject.isEmpty()) { goToAddressFromObject(dataObject.toVariantMap(), requestReply); } else if (responseObject.contains(DATA_OBJECT_DOMAIN_KEY)) { goToAddressFromObject(responseObject.toVariantMap(), requestReply); } emit lookupResultsFinished();} const char OVERRIDE_PATH_KEY[] = "override_path";const char LOOKUP_TRIGGER_KEY[] = "lookup_trigger"; void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const QNetworkReply* reply) { const QString DATA_OBJECT_PLACE_KEY = "place"; const QString DATA_OBJECT_USER_LOCATION_KEY = "location"; QVariantMap locationMap; if (dataObject.contains(DATA_OBJECT_PLACE_KEY)) { locationMap = dataObject[DATA_OBJECT_PLACE_KEY].toMap(); } else if (dataObject.contains(DATA_OBJECT_DOMAIN_KEY)) { locationMap = dataObject; } else { locationMap = dataObject[DATA_OBJECT_USER_LOCATION_KEY].toMap(); } if (!locationMap.isEmpty()) { const QString LOCATION_API_ROOT_KEY = "root"; const QString LOCATION_API_DOMAIN_KEY = "domain"; const QString LOCATION_API_ONLINE_KEY = "online"; if (!locationMap.contains(LOCATION_API_ONLINE_KEY) || locationMap[LOCATION_API_ONLINE_KEY].toBool()) { QVariantMap rootMap = locationMap[LOCATION_API_ROOT_KEY].toMap(); if (rootMap.isEmpty()) { rootMap = locationMap; } QVariantMap domainObject = rootMap[LOCATION_API_DOMAIN_KEY].toMap(); if (!domainObject.isEmpty()) {Lines 1–500 of 1067. Load more
// // AddressManager.cpp // libraries/networking/src // // Created by Stephen Birarda on 2014-09-10. // Copyright 2014 High Fidelity, Inc. // Copyright 2020 Vircadia contributors. // Copyright 2023 Overte e.V. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // SPDX-License-Identifier: Apache-2.0 // #include "AddressManager.h" #include <QGuiApplication> #include <QClipboard> #include <QDebug> #include <QJsonDocument> #include <QRegExp> #include <QStringList> #include <QThread> #include <BuildInfo.h> #include <GLMHelpers.h> #include <NumericalConstants.h> #include <SettingHandle.h> #include <UUID.h> #include "NodeList.h" #include "NetworkLogging.h" #include "NetworkingConstants.h" #include "UserActivityLogger.h" #include "udt/PacketHeaders.h" const QString REDIRECT_HIFI_ADDRESS = NetworkingConstants::REDIRECT_HIFI_ADDRESS; const QString ADDRESS_MANAGER_SETTINGS_GROUP = "AddressManager"; const QString SETTINGS_CURRENT_ADDRESS_KEY = "address"; const QString DEFAULT_OVERTE_ADDRESS = (!std::string_view(BuildInfo::PRELOADED_STARTUP_LOCATION).empty()) ? BuildInfo::PRELOADED_STARTUP_LOCATION : NetworkingConstants::DEFAULT_OVERTE_ADDRESS; const QString DEFAULT_HOME_ADDRESS = (!std::string_view(BuildInfo::PRELOADED_STARTUP_LOCATION).empty()) ? BuildInfo::PRELOADED_STARTUP_LOCATION : NetworkingConstants::DEFAULT_OVERTE_ADDRESS; Setting::Handle<QUrl> currentAddressHandle(QStringList() << ADDRESS_MANAGER_SETTINGS_GROUP << "address", DEFAULT_OVERTE_ADDRESS); bool AddressManager::isConnected() { return DependencyManager::get<NodeList>()->getDomainHandler().isConnected(); } QString AddressManager::getProtocol() const { return _domainURL.scheme(); } QUrl AddressManager::currentAddress(bool domainOnly) const { QUrl hifiURL = _domainURL; if (!domainOnly && hifiURL.scheme() == URL_SCHEME_OVERTE) { hifiURL.setPath(currentPath()); } return hifiURL; } QUrl AddressManager::currentFacingAddress() const { auto hifiURL = currentAddress(); if (hifiURL.scheme() == URL_SCHEME_OVERTE) { hifiURL.setPath(currentFacingPath()); } return hifiURL; } QUrl AddressManager::currentShareableAddress(bool domainOnly) const { if (!_shareablePlaceName.isEmpty()) { // if we have a shareable place name use that instead of whatever the current host is QUrl hifiURL; hifiURL.setScheme(URL_SCHEME_OVERTE); hifiURL.setHost(_shareablePlaceName); if (!domainOnly) { hifiURL.setPath(currentPath()); } return hifiURL; } else { return currentAddress(domainOnly); } } QUrl AddressManager::currentPublicAddress(bool domainOnly) const { // return an address that can be used by others to visit this client's current location. If // in a serverless domain (which can't be visited) return an empty URL. QUrl shareableAddress = currentShareableAddress(domainOnly); if (shareableAddress.scheme() != URL_SCHEME_OVERTE) { return QUrl(); // file: urls aren't public } return shareableAddress; } QUrl AddressManager::currentFacingShareableAddress() const { auto hifiURL = currentShareableAddress(); if (hifiURL.scheme() == URL_SCHEME_OVERTE) { hifiURL.setPath(currentFacingPath()); } return hifiURL; } QUrl AddressManager::currentFacingPublicAddress() const { // return an address that can be used by others to visit this client's current location. If // in a serverless domain (which can't be visited) return an empty URL. QUrl shareableAddress = currentFacingShareableAddress(); if (shareableAddress.scheme() != URL_SCHEME_OVERTE) { return QUrl(); // file: urls aren't public } return shareableAddress; } QUrl AddressManager::lastAddress() const { return _lastVisitedURL; } void AddressManager::loadSettings(const QString& lookupString) { if (lookupString.isEmpty()) { handleUrl(currentAddressHandle.get(), LookupTrigger::StartupFromSettings); } else { handleUrl(lookupString, LookupTrigger::StartupFromSettings); } } void AddressManager::goBack() { if (_backStack.size() > 0) { // go to that address handleUrl(_backStack.pop(), LookupTrigger::Back); if (_backStack.size() == 0) { // the back stack is now empty so it is no longer possible to go back - emit that signal emit goBackPossible(false); } } } void AddressManager::goForward() { if (_forwardStack.size() > 0) { // pop a URL from the forwardStack and go to that address handleUrl(_forwardStack.pop(), LookupTrigger::Forward); if (_forwardStack.size() == 0) { // the forward stack is empty so it is no longer possible to go forwards - emit that signal emit goForwardPossible(false); } } } void AddressManager::storeCurrentAddress() { auto url = currentAddress(); if (url.scheme() == HIFI_URL_SCHEME_FILE || url.scheme() == HIFI_URL_SCHEME_HTTP || url.scheme() == HIFI_URL_SCHEME_HTTPS || (url.scheme() == URL_SCHEME_OVERTE && !url.host().isEmpty())) { // TODO -- once Octree::readFromURL no-longer takes over the main event-loop, serverless-domain urls can // be loaded over http(s) // url.scheme() == HIFI_URL_SCHEME_HTTP || // url.scheme() == HIFI_URL_SCHEME_HTTPS || bool isInErrorState = DependencyManager::get<NodeList>()->getDomainHandler().isInErrorState(); if (isConnected()) { if (isInErrorState) { // save the last address visited before the problem url. currentAddressHandle.set(lastAddress()); } else { currentAddressHandle.set(url); } } else { qCWarning(networking) << "Ignoring attempt to save current address because not connected to domain:" << url; } } else { qCWarning(networking) << "Ignoring attempt to save current address with an invalid url:" << url; } } QString AddressManager::currentPath(bool withOrientation) const { if (_positionGetter) { QString pathString = "/" + createByteArray(_positionGetter()); if (withOrientation) { if (_orientationGetter) { QString orientationString = createByteArray(_orientationGetter()); pathString += "/" + orientationString; } else { qCDebug(networking) << "Cannot add orientation to path without a getter for position." << "Call AddressManager::setOrientationGetter to pass a function that will return a glm::quat"; } } return pathString; } else { qCDebug(networking) << "Cannot create address path without a getter for position." << "Call AddressManager::setPositionGetter to pass a function that will return a const glm::vec3&"; return QString(); } } QString AddressManager::currentFacingPath() const { if (_positionGetter && _orientationGetter) { auto position = _positionGetter(); auto orientation = _orientationGetter(); // move the user a couple units away const float DISTANCE_TO_USER = 2.0f; position += orientation * Vectors::FRONT * DISTANCE_TO_USER; // rotate the user by 180 degrees orientation = orientation * glm::angleAxis(PI, Vectors::UP); return "/" + createByteArray(position) + "/" + createByteArray(orientation); } else { qCDebug(networking) << "Cannot create address path without a getter for position/orientation."; return QString(); } } const JSONCallbackParameters& AddressManager::apiCallbackParameters() { static bool hasSetupParameters = false; static JSONCallbackParameters callbackParams; if (!hasSetupParameters) { callbackParams.callbackReceiver = this; callbackParams.jsonCallbackMethod = "handleAPIResponse"; callbackParams.errorCallbackMethod = "handleAPIError"; } return callbackParams; } bool AddressManager::handleUrl(const QUrl& lookupUrlIn, LookupTrigger trigger, const QString& lookupUrlInString) { static QString URL_TYPE_USER = "user"; static QString URL_TYPE_DOMAIN_ID = "domain_id"; static QString URL_TYPE_PLACE = "place"; static QString URL_TYPE_NETWORK_ADDRESS = "network_address"; QUrl lookupUrl = lookupUrlIn; if (!lookupUrl.host().isEmpty() && !lookupUrl.path().isEmpty()) { // Assignment clients ping for empty url until assigned. Don't spam. qCDebug(networking) << "Trying to go to URL" << lookupUrl.toString(); } if (lookupUrl.scheme().isEmpty() && !lookupUrl.path().startsWith("/")) { // 'urls' without schemes are taken as domain names, as opposed to // simply a path portion of a url, so we need to set the scheme lookupUrl.setScheme(URL_SCHEME_OVERTE); } static const QRegExp PORT_REGEX = QRegExp("\\d{1,5}(\\/.*)?"); if(!lookupUrl.scheme().isEmpty() && lookupUrl.host().isEmpty() && PORT_REGEX.exactMatch(lookupUrl.path())) { // this is in the form somewhere:<port>, convert it to hifi://somewhere:<port> lookupUrl = QUrl(URL_SCHEME_OVERTE + "://" + lookupUrl.toString()); } // it should be noted that url's in the form // somewhere:<port> are not valid, as that // would indicate that the scheme is 'somewhere' // use hifi://somewhere:<port> instead if (lookupUrl.scheme() == URL_SCHEME_OVERTE || lookupUrlInString.startsWith(URL_SCHEME_OVERTE + "://")) { QString lookupUrlString; if (lookupUrlInString.startsWith(URL_SCHEME_OVERTE + "://")) { lookupUrlString = lookupUrlInString; } else { lookupUrlString = lookupUrl.toString(QUrl::FullyEncoded); } if (lookupUrl.host().isEmpty()) { // this was in the form hifi:/somewhere or hifi:somewhere. Fix it by making it hifi://somewhere static const QRegExp HIFI_SCHEME_REGEX = QRegExp(URL_SCHEME_OVERTE + ":\\/{0,2}", Qt::CaseInsensitive); lookupUrl = QUrl(lookupUrl.toString().replace(HIFI_SCHEME_REGEX, URL_SCHEME_OVERTE + "://")); } DependencyManager::get<NodeList>()->flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::LookupAddress); // there are 4 possible lookup strings // 1. global place name (name of domain or place) - example: sanfrancisco // 2. user name (prepended with @) - example: @philip // 3. location string (posX,posY,posZ/eulerX,eulerY,eulerZ) // 4. domain network address (IP or dns resolvable hostname) // use our regex'ed helpers to figure out what we're supposed to do with this if (handleUsername(lookupUrl.authority())) { // handled a username for lookup UserActivityLogger::getInstance().wentTo(trigger, URL_TYPE_USER, lookupUrl.toString()); // save the last visited domain URL. _lastVisitedURL = lookupUrl; // in case we're failing to connect to where we thought this user was // store their username as previous lookup so we can refresh their location via API _previousAPILookup = lookupUrl; } else { // we're assuming this is either a network address or global place name // check if it is a network address first bool hostChanged; if (handleNetworkAddress(lookupUrl.host() + (lookupUrl.port() == -1 ? "" : ":" + QString::number(lookupUrl.port())), trigger, hostChanged)) { UserActivityLogger::getInstance().wentTo(trigger, URL_TYPE_NETWORK_ADDRESS, lookupUrl.toString()); // save the last visited domain URL. _lastVisitedURL = lookupUrl; // a network address lookup clears the previous lookup since we don't expect to re-attempt it _previousAPILookup.clear(); // If the host changed then we have already saved to history if (hostChanged) { trigger = Internal; } // if we were not passed a path, use the index path auto path = lookupUrl.path(); if (path.isEmpty()) { path = INDEX_PATH; } // we may have a path that defines a relative viewpoint - if so we should jump to that now handlePath(path, trigger); } else if (handleDomainID(lookupUrl.host())){ UserActivityLogger::getInstance().wentTo(trigger, URL_TYPE_DOMAIN_ID, lookupUrl.toString()); // save the last visited domain URL. _lastVisitedURL = lookupUrl; // store this domain ID as the previous lookup in case we're failing to connect and want to refresh API info _previousAPILookup = lookupUrl; // no place name - this is probably a domain ID // try to look up the domain ID on the metaverse API attemptDomainIDLookup(lookupUrl.host(), lookupUrl.path(), trigger); } else { // wasn't an address - lookup the place name // we may have a path that defines a relative viewpoint - pass that through the lookup so we can go to it after UserActivityLogger::getInstance().wentTo(trigger, URL_TYPE_PLACE, lookupUrl.toString()); // save the last visited domain URL. _lastVisitedURL = lookupUrl; // store this place name as the previous lookup in case we fail to connect and want to refresh API info _previousAPILookup = lookupUrl; // Let's convert this to a QString for processing in case there are spaces in it. if (lookupUrlString.contains(URL_SCHEME_OVERTE + "://", Qt::CaseInsensitive)) { lookupUrlString = lookupUrlString.replace((URL_SCHEME_OVERTE + "://"), ""); } else if (lookupUrlString.contains(URL_SCHEME_OVERTE + ":/", Qt::CaseInsensitive)) { lookupUrlString = lookupUrlString.replace((URL_SCHEME_OVERTE + ":/"), ""); } else if (lookupUrlString.contains(URL_SCHEME_OVERTE + ":", Qt::CaseInsensitive)) { lookupUrlString = lookupUrlString.replace((URL_SCHEME_OVERTE + ":"), ""); } // Get the path and then strip it out. QString lookupUrlStringPath; int index = lookupUrlString.indexOf('/'); if (index != -1) { lookupUrlStringPath = lookupUrlString.mid(index); lookupUrlString.replace(lookupUrlStringPath, ""); } if (!lookupUrlString.isEmpty()) { attemptPlaceNameLookup(lookupUrlString, lookupUrlStringPath, trigger); } } } return true; } else if (lookupUrl.toString().startsWith('/')) { qCDebug(networking) << "Going to relative path" << lookupUrl.path(); // a path lookup clears the previous lookup since we don't expect to re-attempt it _previousAPILookup.clear(); // if this is a relative path then handle it as a relative viewpoint handlePath(lookupUrl.path(), trigger, true); emit lookupResultsFinished(); return true; } else if (lookupUrl.scheme() == HIFI_URL_SCHEME_FILE || lookupUrl.scheme() == HIFI_URL_SCHEME_HTTPS || lookupUrl.scheme() == HIFI_URL_SCHEME_HTTP) { // Save the last visited domain URL. _lastVisitedURL = lookupUrl; _previousAPILookup.clear(); _shareablePlaceName.clear(); setDomainInfo(lookupUrl, trigger); emit lookupResultsFinished(); QString path = DOMAIN_SPAWNING_POINT; QUrlQuery queryArgs(lookupUrl); const QString LOCATION_QUERY_KEY = "location"; if (queryArgs.hasQueryItem(LOCATION_QUERY_KEY)) { path = queryArgs.queryItemValue(LOCATION_QUERY_KEY); } else { path = DEFAULT_NAMED_PATH; } handlePath(path, LookupTrigger::Internal, false); return true; } return false; } static const QString LOCALHOST = "localhost"; bool isPossiblePlaceName(QString possiblePlaceName) { bool result { false }; int length = possiblePlaceName.length(); static const int MINIMUM_PLACENAME_LENGTH = 1; static const int MAXIMUM_PLACENAME_LENGTH = 64; if (possiblePlaceName.toLower() != LOCALHOST && length >= MINIMUM_PLACENAME_LENGTH && length <= MAXIMUM_PLACENAME_LENGTH) { const QRegExp PLACE_NAME_REGEX = QRegExp("^[0-9A-Za-z](([0-9A-Za-z]|[-_](?![-_]))*[^\\W_]$|$)"); result = PLACE_NAME_REGEX.indexIn(possiblePlaceName) == 0; } return result; } void AddressManager::handleLookupString(const QString& lookupString, bool fromSuggestions) { QString trimmedString = lookupString.trimmed(); if (!trimmedString.isEmpty()) { resetConfirmConnectWithoutAvatarEntities(); // make this a valid hifi URL and handle it off to handleUrl handleUrl(trimmedString, fromSuggestions ? Suggestions : UserInput, trimmedString); } } const QString DATA_OBJECT_DOMAIN_KEY = "domain"; void AddressManager::handleAPIResponse(QNetworkReply* requestReply) { QJsonObject responseObject = QJsonDocument::fromJson(requestReply->readAll()).object(); QJsonObject dataObject = responseObject["data"].toObject(); // Lookup succeeded, don't keep re-trying it (especially on server restarts) _previousAPILookup.clear(); if (!dataObject.isEmpty()) { goToAddressFromObject(dataObject.toVariantMap(), requestReply); } else if (responseObject.contains(DATA_OBJECT_DOMAIN_KEY)) { goToAddressFromObject(responseObject.toVariantMap(), requestReply); } emit lookupResultsFinished(); } const char OVERRIDE_PATH_KEY[] = "override_path"; const char LOOKUP_TRIGGER_KEY[] = "lookup_trigger"; void AddressManager::goToAddressFromObject(const QVariantMap& dataObject, const QNetworkReply* reply) { const QString DATA_OBJECT_PLACE_KEY = "place"; const QString DATA_OBJECT_USER_LOCATION_KEY = "location"; QVariantMap locationMap; if (dataObject.contains(DATA_OBJECT_PLACE_KEY)) { locationMap = dataObject[DATA_OBJECT_PLACE_KEY].toMap(); } else if (dataObject.contains(DATA_OBJECT_DOMAIN_KEY)) { locationMap = dataObject; } else { locationMap = dataObject[DATA_OBJECT_USER_LOCATION_KEY].toMap(); } if (!locationMap.isEmpty()) { const QString LOCATION_API_ROOT_KEY = "root"; const QString LOCATION_API_DOMAIN_KEY = "domain"; const QString LOCATION_API_ONLINE_KEY = "online"; if (!locationMap.contains(LOCATION_API_ONLINE_KEY) || locationMap[LOCATION_API_ONLINE_KEY].toBool()) { QVariantMap rootMap = locationMap[LOCATION_API_ROOT_KEY].toMap(); if (rootMap.isEmpty()) { rootMap = locationMap; } QVariantMap domainObject = rootMap[LOCATION_API_DOMAIN_KEY].toMap(); if (!domainObject.isEmpty()) {