1067 lines · 43.2 KB · C++
RawDownload
////  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.