/** * @file llurlsimstring.cpp (was llsimurlstring.cpp) * @brief Handles "SLURL fragments" like Ahern/123/45 for * startup processing, login screen, prefs, etc. * * $LicenseInfo:firstyear=2010&license=viewerlgpl$ * Second Life Viewer Source Code * Copyright (C) 2010, Linden Research, Inc. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License only. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ #include "llviewerprecompiledheaders.h" #include "llslurl.h" #include "llpanellogin.h" #include "llviewercontrol.h" #include "llviewernetwork.h" #include "llfiltersd2xmlrpc.h" #include "curl/curl.h" const char* LLSLURL::SLURL_HTTP_SCHEME = "http"; const char* LLSLURL::SLURL_HTTPS_SCHEME = "https"; const char* LLSLURL::SLURL_SECONDLIFE_SCHEME = "secondlife"; const char* LLSLURL::SLURL_SECONDLIFE_PATH = "secondlife"; const char* LLSLURL::SLURL_COM = "slurl.com"; // For DnD - even though www.slurl.com redirects to slurl.com in a browser, you can copy and drag // text with www.slurl.com or a link explicitly pointing at www.slurl.com so testing for this // version is required also. const char* LLSLURL::WWW_SLURL_COM = "www.slurl.com"; const char* LLSLURL::MAPS_SECONDLIFE_COM = "maps.secondlife.com"; const char* LLSLURL::SLURL_X_GRID_LOCATION_INFO_SCHEME = "x-grid-location-info"; const char* LLSLURL::SLURL_APP_PATH = "app"; const char* LLSLURL::SLURL_REGION_PATH = "region"; const char* LLSLURL::SIM_LOCATION_HOME = "home"; const char* LLSLURL::SIM_LOCATION_LAST = "last"; // resolve a simstring from a slurl LLSLURL::LLSLURL(const std::string& slurl) { // by default we go to agni. mType = INVALID; if (slurl.empty() || (slurl == SIM_LOCATION_LAST)) { mType = LAST_LOCATION; } else if (slurl == SIM_LOCATION_HOME) { mType = HOME_LOCATION; } else { LLURI slurl_uri; // parse the slurl as a uri if (slurl.find(':') == std::string::npos) { // There may be no scheme ('secondlife:' etc.) passed in. In that case // we want to normalize the slurl by putting the appropriate scheme // in front of the slurl. So, we grab the appropriate slurl base // from the grid manager which may be http://slurl.com/secondlife/ for maingrid, or // https://<hostname>/region/ for Standalone grid (the word region, not the region name) // these slurls are typically passed in from the 'starting location' box on the login panel, // where the user can type in <regionname>/<x>/<y>/<z> std::string fixed_slurl = LLGridManager::getInstance()->getSLURLBase(); // the slurl that was passed in might have a prepended /, or not. So, // we strip off the prepended '/' so we don't end up with http://slurl.com/secondlife/<region>/<x>/<y>/<z> // or some such. if (slurl[0] == '/') { fixed_slurl += slurl.substr(1); } else { fixed_slurl += slurl; } // We then load the slurl into a LLURI form slurl_uri = LLURI(fixed_slurl); } else { // as we did have a scheme, implying a URI style slurl, we // simply parse it as a URI slurl_uri = LLURI(slurl); } LLSD path_array = slurl_uri.pathArray(); // determine whether it's a maingrid URI or an Standalone/open style URI // by looking at the scheme. If it's a 'secondlife:' slurl scheme or // 'sl:' scheme, we know it's maingrid // At the end of this if/else block, we'll have determined the grid, // and the slurl type (APP or LOCATION) if (slurl_uri.scheme() == LLSLURL::SLURL_SECONDLIFE_SCHEME) { if (path_array.size() == 0 && slurl_uri.authority().empty() && slurl_uri.escapedQuery().empty()) { mType = EMPTY; // um, we need a path... return; } // parse a maingrid style slurl. We know the grid is maingrid // so grab it. // A location slurl for maingrid (with the special schemes) can be in the form // secondlife://<regionname>/<x>/<y>/<z> // or // secondlife://<Grid>/secondlife/<region>/<x>/<y>/<z> // where if grid is empty, it specifies Agni // An app style slurl for maingrid can be // secondlife://<Grid>/app/<app parameters> // where an empty grid implies Agni // we'll start by checking the top of the 'path' which will be // either 'app', 'secondlife', or <x>. // default to maingrid mGrid = MAINGRID; if ((path_array[0].asString() == LLSLURL::SLURL_SECONDLIFE_PATH) || (path_array[0].asString() == LLSLURL::SLURL_APP_PATH)) { // it's in the form secondlife://<grid>/(app|secondlife) // so parse the grid name to derive the grid ID if (!slurl_uri.hostName().empty()) { mGrid = LLGridManager::getInstance()->getGridId(slurl_uri.hostName()); } else if(path_array[0].asString() == LLSLURL::SLURL_SECONDLIFE_PATH) { // If the slurl is in the form secondlife:///secondlife/<region> form, // then we are in fact on maingrid. mGrid = MAINGRID; } else if(path_array[0].asString() == LLSLURL::SLURL_APP_PATH) { // for app style slurls, where no grid name is specified, assume the currently // selected or logged in grid. mGrid = LLGridManager::getInstance()->getGridId(); } if (mGrid.empty()) { // we couldn't find the grid in the grid manager, so bail LL_WARNS("AppInit")<<"unable to find grid"<<LL_ENDL; return; } // set the type as appropriate. if (path_array[0].asString() == LLSLURL::SLURL_SECONDLIFE_PATH) { mType = LOCATION; } else { mType = APP; } path_array.erase(0); } else { if (slurl_uri.hostName() == LLSLURL::SLURL_APP_PATH) { mType = APP; } else { // it wasn't a /secondlife/<region> or /app/<params>, so it must be secondlife://<region> // therefore the hostname will be the region name, and it's a location type mType = LOCATION; // 'normalize' it so the region name is in fact the head of the path_array path_array.insert(0, slurl_uri.hostName()); } } } else if ((slurl_uri.scheme() == LLSLURL::SLURL_HTTP_SCHEME) || (slurl_uri.scheme() == LLSLURL::SLURL_HTTPS_SCHEME) || (slurl_uri.scheme() == LLSLURL::SLURL_X_GRID_LOCATION_INFO_SCHEME)) { // We're dealing with either a Standalone style slurl or slurl.com slurl if ((slurl_uri.hostName() == LLSLURL::SLURL_COM) || (slurl_uri.hostName() == LLSLURL::WWW_SLURL_COM) || (slurl_uri.hostName() == LLSLURL::MAPS_SECONDLIFE_COM)) { // slurl.com implies maingrid mGrid = MAINGRID; } else { // Don't try to match any old http://<host>/ URL as a SLurl. // SLE SLurls will have the grid hostname in the URL, so only // match http URLs if the hostname matches the grid hostname // (or its a slurl.com or maps.secondlife.com URL). if ((slurl_uri.scheme() == LLSLURL::SLURL_HTTP_SCHEME || slurl_uri.scheme() == LLSLURL::SLURL_HTTPS_SCHEME) && slurl_uri.hostName() != LLGridManager::getInstance()->getGrid()) { return; } // As it's a Standalone grid/open, we will always have a hostname, as Standalone/open style // urls are properly formed, unlike the stinky maingrid style mGrid = slurl_uri.hostName(); } if (path_array.size() == 0) { // um, we need a path... return; } // we need to normalize the urls so // the path portion starts with the 'command' that we want to do // it can either be region or app. if ((path_array[0].asString() == LLSLURL::SLURL_REGION_PATH) || (path_array[0].asString() == LLSLURL::SLURL_SECONDLIFE_PATH)) { // strip off 'region' or 'secondlife' path_array.erase(0); // it's a location mType = LOCATION; } else if (path_array[0].asString() == LLSLURL::SLURL_APP_PATH) { mType = APP; path_array.erase(0); // leave app appended. } else { // not a valid https/http/x-grid-location-info slurl, so it'll likely just be a URL return; } } else { // invalid scheme, so bail return; } if (path_array.size() == 0) { // we gotta have some stuff after the specifier as to whether it's a region or command return; } // now that we know whether it's an app slurl or a location slurl, // parse the slurl into the proper data structures. if (mType == APP) { // grab the app command type and strip it (could be a command to jump somewhere, // or whatever ) mAppCmd = path_array[0].asString(); path_array.erase(0); // Grab the parameters mAppPath = path_array; // and the query mAppQuery = slurl_uri.query(); mAppQueryMap = slurl_uri.queryMap(); return; } else if (mType == LOCATION) { // at this point, head of the path array should be [ <region>, <x>, <y>, <z> ] where x, y and z // are collectively optional // are optional mRegion = LLURI::unescape(path_array[0].asString()); if (LLStringUtil::containsNonprintable(mRegion)) { LLStringUtil::stripNonprintable(mRegion); } path_array.erase(0); // parse the x, y, and optionally z if (path_array.size() >= 2) { mPosition = LLVector3(path_array); // this construction handles LLSD without all components (values default to 0.f) if ((F32(mPosition[VX]) < 0.f) || (mPosition[VX] > REGION_WIDTH_METERS) || (F32(mPosition[VY]) < 0.f) || (mPosition[VY] > REGION_WIDTH_METERS) || (F32(mPosition[VZ]) < 0.f) || (mPosition[VZ] > REGION_HEIGHT_METERS)) { mType = INVALID; return; } } else { // if x, y and z were not fully passed in, go to the middle of the region. // teleport will adjust the actual location to make sure you're on the ground // and such mPosition = LLVector3(REGION_WIDTH_METERS / 2, REGION_WIDTH_METERS / 2, 0); } } } } // Create a slurl for the middle of the region LLSLURL::LLSLURL(const std::string& grid, const std::string& region) { mGrid = grid; mRegion = region; mType = LOCATION; mPosition = LLVector3((F64)REGION_WIDTH_METERS / 2, (F64)REGION_WIDTH_METERS / 2, 0); } // create a slurl given the position. The position will be modded with the region // width handling global positions as well LLSLURL::LLSLURL(const std::string& grid, const std::string& region, const LLVector3& position) { mGrid = grid; mRegion = region; S32 x = ll_round((F32)fmod(position[VX], (F32)REGION_WIDTH_METERS)); S32 y = ll_round((F32)fmod(position[VY], (F32)REGION_WIDTH_METERS)); S32 z = ll_round((F32)position[VZ]); mType = LOCATION; mPosition = LLVector3(x, y, z); } // create a simstring LLSLURL::LLSLURL(const std::string& region, const LLVector3& position) { *this = LLSLURL(LLGridManager::getInstance()->getGridId(), region, position); } // create a slurl from a global position LLSLURL::LLSLURL(const std::string& grid, const std::string& region, const LLVector3d& global_position) { *this = LLSLURL(LLGridManager::getInstance()->getGridId(grid), region, LLVector3(global_position.mdV[VX], global_position.mdV[VY], global_position.mdV[VZ])); } // create a slurl from a global position LLSLURL::LLSLURL(const std::string& region, const LLVector3d& global_position) { *this = LLSLURL(LLGridManager::getInstance()->getGridId(), region, global_position); } LLSLURL::LLSLURL(const std::string& command, const LLUUID&id, const std::string& verb) { mType = APP; mAppCmd = command; mAppPath = LLSD::emptyArray(); mAppPath.append(LLSD(id)); mAppPath.append(LLSD(verb)); } std::string LLSLURL::getSLURLString() const { switch (mType) { case HOME_LOCATION: return SIM_LOCATION_HOME; case LAST_LOCATION: return SIM_LOCATION_LAST; case LOCATION: { // lookup the grid S32 x = ll_round((F32)mPosition[VX]); S32 y = ll_round((F32)mPosition[VY]); S32 z = ll_round((F32)mPosition[VZ]); return LLGridManager::getInstance()->getSLURLBase(mGrid) + LLURI::escape(mRegion) + llformat("/%d/%d/%d", x, y, z); } case APP: { std::ostringstream app_url; app_url << LLGridManager::getInstance()->getAppSLURLBase() << "/" << mAppCmd; for (LLSD::array_const_iterator i = mAppPath.beginArray(); i != mAppPath.endArray(); i++) { app_url << "/" << i->asString(); } if (mAppQuery.length() > 0) { app_url << "?" << mAppQuery; } return app_url.str(); } default: LL_WARNS("AppInit") << "Unexpected SLURL type for SLURL string" << (int)mType << LL_ENDL; return std::string(); } } std::string LLSLURL::getLoginString() const { std::stringstream unescaped_start; switch (mType) { case LOCATION: unescaped_start << "uri:" << mRegion << "&" << ll_round(mPosition[0]) << "&" << ll_round(mPosition[1]) << "&" << ll_round(mPosition[2]); break; case HOME_LOCATION: unescaped_start << "home"; break; case LAST_LOCATION: unescaped_start << "last"; break; default: LL_WARNS("AppInit") << "Unexpected SLURL type (" << (int)mType << ")for login string" << LL_ENDL; break; } return xml_escape_string(unescaped_start.str()); } bool LLSLURL::operator ==(const LLSLURL& rhs) { if (rhs.mType != mType) return false; switch (mType) { case LOCATION: return (mGrid == rhs.mGrid) && (mRegion == rhs.mRegion) && (mPosition == rhs.mPosition); case APP: return getSLURLString() == rhs.getSLURLString(); case HOME_LOCATION: case LAST_LOCATION: return true; default: return false; } } bool LLSLURL::operator !=(const LLSLURL& rhs) { return !(*this == rhs); } std::string LLSLURL::getLocationString() const { return llformat("%s/%d/%d/%d", mRegion.c_str(), (int)ll_round(mPosition[0]), (int)ll_round(mPosition[1]), (int)ll_round(mPosition[2])); } // static const std::string LLSLURL::typeName[NUM_SLURL_TYPES] = { "INVALID", "LOCATION", "HOME_LOCATION", "LAST_LOCATION", "APP", "HELP", "EMPTY" }; std::string LLSLURL::getTypeString(SLURL_TYPE type) { std::string name; if (type >= INVALID && type < NUM_SLURL_TYPES) { name = LLSLURL::typeName[type]; } else { name = llformat("Out of Range (%d)", type); } return name; } std::string LLSLURL::asString() const { std::ostringstream result; result << " mType: " << LLSLURL::getTypeString(mType) << " mGrid: " + getGrid() << " mRegion: " + getRegion() << " mPosition: " << mPosition << " mAppCmd:" << getAppCmd() << " mAppPath:" + getAppPath().asString() << " mAppQueryMap:" + getAppQueryMap().asString() << " mAppQuery: " + getAppQuery() ; return result.str(); }