/** * @file lluri.cpp * @author Phoenix * @date 2006-02-08 * @brief Implementation of the LLURI class. * * $LicenseInfo:firstyear=2006&license=viewergpl$ * * Copyright (c) 2006-2008, Linden Research, Inc. * * Second Life Viewer Source Code * The source code in this file ("Source Code") is provided by Linden Lab * to you under the terms of the GNU General Public License, version 2.0 * ("GPL"), unless you have obtained a separate licensing agreement * ("Other License"), formally executed by you and Linden Lab. Terms of * the GPL can be found in doc/GPL-license.txt in this distribution, or * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2 * * There are special exceptions to the terms and conditions of the GPL as * it is applied to this Source Code. View the full text of the exception * in the file doc/FLOSS-exception.txt in this software distribution, or * online at http://secondlifegrid.net/programs/open_source/licensing/flossexception * * By copying, modifying or distributing this software, you acknowledge * that you have read and understood your obligations described above, * and agree to abide by those obligations. * * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, * COMPLETENESS OR PERFORMANCE. * $/LicenseInfo$ */ #include "linden_common.h" #include "llapp.h" #include "lluri.h" #include "llsd.h" #include #include "../llmath/lluuid.h" // system includes #include void encode_character(std::ostream& ostr, std::string::value_type val) { ostr << "%" << std::uppercase << std::hex << std::setw(2) << std::setfill('0') // VWR-4010 Cannot cast to U32 because sign-extension on // chars > 128 will result in FFFFFFC3 instead of F3. << static_cast(static_cast(val)); } // static std::string LLURI::escape( const std::string& str, const std::string& allowed, bool is_allowed_sorted) { // *NOTE: This size determination feels like a good value to // me. If someone wante to come up with a more precise heuristic // with some data to back up the assertion that 'sort is good' // then feel free to change this test a bit. if(!is_allowed_sorted && (str.size() > 2 * allowed.size())) { // if it's already sorted, or if the url is quite long, we // want to optimize this process. std::string sorted_allowed(allowed); std::sort(sorted_allowed.begin(), sorted_allowed.end()); return escape(str, sorted_allowed, true); } std::ostringstream ostr; std::string::const_iterator it = str.begin(); std::string::const_iterator end = str.end(); std::string::value_type c; if(is_allowed_sorted) { std::string::const_iterator allowed_begin(allowed.begin()); std::string::const_iterator allowed_end(allowed.end()); for(; it != end; ++it) { c = *it; if(std::binary_search(allowed_begin, allowed_end, c)) { ostr << c; } else { encode_character(ostr, c); } } } else { for(; it != end; ++it) { c = *it; if(allowed.find(c) == std::string::npos) { encode_character(ostr, c); } else { ostr << c; } } } return ostr.str(); } // static std::string LLURI::unescape(const std::string& str) { std::ostringstream ostr; std::string::const_iterator it = str.begin(); std::string::const_iterator end = str.end(); for(; it != end; ++it) { if((*it) == '%') { ++it; if(it == end) break; U8 c = hex_as_nybble(*it++); c = c << 4; if (it == end) break; c |= hex_as_nybble(*it); ostr.put((char)c); } else { ostr.put(*it); } } return ostr.str(); } namespace { const std::string unreserved() { static const std::string s = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" "0123456789" "-._~"; return s; } const std::string sub_delims() { static const std::string s = "!$&'()*+,;="; return s; } std::string escapeHostAndPort(const std::string& s) { return LLURI::escape(s, unreserved() + sub_delims() +":"); } std::string escapePathComponent(const std::string& s) { return LLURI::escape(s, unreserved() + sub_delims() + ":@"); } std::string escapeQueryVariable(const std::string& s) { return LLURI::escape(s, unreserved() + ":@!$'()*+,"); } // sub_delims - "&;=" + ":@" std::string escapeQueryValue(const std::string& s) { return LLURI::escape(s, unreserved() + ":@!$'()*+,="); } // sub_delims - "&;" + ":@" } // *TODO: Consider using curl. After http textures gets merged everywhere. // static std::string LLURI::escape(const std::string& str) { static std::string default_allowed(unreserved() + ":@!$'()*+,=/?&#;"); static bool initialized = false; if(!initialized) { std::sort(default_allowed.begin(), default_allowed.end()); initialized = true; } return escape(str, default_allowed, true); } LLURI::LLURI() { } LLURI::LLURI(const std::string& escaped_str) { std::string::size_type delim_pos; delim_pos = escaped_str.find(':'); std::string temp; if (delim_pos == std::string::npos) { mScheme = ""; mEscapedOpaque = escaped_str; } else { mScheme = escaped_str.substr(0, delim_pos); mEscapedOpaque = escaped_str.substr(delim_pos+1); } parseAuthorityAndPathUsingOpaque(); delim_pos = mEscapedPath.find('?'); if (delim_pos != std::string::npos) { mEscapedQuery = mEscapedPath.substr(delim_pos+1); mEscapedPath = mEscapedPath.substr(0,delim_pos); } } static BOOL isDefault(const std::string& scheme, U16 port) { if (scheme == "http") return port == 80; if (scheme == "https") return port == 443; if (scheme == "ftp") return port == 21; return FALSE; } void LLURI::parseAuthorityAndPathUsingOpaque() { if (mScheme == "http" || mScheme == "https" || mScheme == "ftp" || mScheme == "secondlife" ) { if (mEscapedOpaque.substr(0,2) != "//") { return; } std::string::size_type delim_pos, delim_pos2; delim_pos = mEscapedOpaque.find('/', 2); delim_pos2 = mEscapedOpaque.find('?', 2); // no path, no query if (delim_pos == std::string::npos && delim_pos2 == std::string::npos) { mEscapedAuthority = mEscapedOpaque.substr(2); mEscapedPath = ""; } // path exist, no query else if (delim_pos2 == std::string::npos) { mEscapedAuthority = mEscapedOpaque.substr(2,delim_pos-2); mEscapedPath = mEscapedOpaque.substr(delim_pos); } // no path, only query else if (delim_pos == std::string::npos || delim_pos2 < delim_pos) { mEscapedAuthority = mEscapedOpaque.substr(2,delim_pos2-2); // query part will be broken out later mEscapedPath = mEscapedOpaque.substr(delim_pos2); } // path and query else { mEscapedAuthority = mEscapedOpaque.substr(2,delim_pos-2); // query part will be broken out later mEscapedPath = mEscapedOpaque.substr(delim_pos); } } else if (mScheme == "about") { mEscapedPath = mEscapedOpaque; } } LLURI::LLURI(const std::string& scheme, const std::string& userName, const std::string& password, const std::string& hostName, U16 port, const std::string& escapedPath, const std::string& escapedQuery) : mScheme(scheme), mEscapedPath(escapedPath), mEscapedQuery(escapedQuery) { std::ostringstream auth; std::ostringstream opaque; opaque << "//"; if (!userName.empty()) { auth << escape(userName); if (!password.empty()) { auth << ':' << escape(password); } auth << '@'; } auth << hostName; if (!isDefault(scheme, port)) { auth << ':' << port; } mEscapedAuthority = auth.str(); opaque << mEscapedAuthority << escapedPath << escapedQuery; mEscapedOpaque = opaque.str(); } LLURI::~LLURI() { } // static LLURI LLURI::buildHTTP(const std::string& prefix, const LLSD& path) { LLURI result; // TODO: deal with '/' '?' '#' in host_port if (prefix.find("://") != prefix.npos) { // it is a prefix result = LLURI(prefix); } else { // it is just a host and optional port result.mScheme = "http"; result.mEscapedAuthority = escapeHostAndPort(prefix); } if (path.isArray()) { // break out and escape each path component for (LLSD::array_const_iterator it = path.beginArray(); it != path.endArray(); ++it) { lldebugs << "PATH: inserting " << it->asString() << llendl; result.mEscapedPath += "/" + escapePathComponent(it->asString()); } } else if(path.isString()) { result.mEscapedPath += "/" + escapePathComponent(path.asString()); } else if(path.isUndefined()) { // do nothing } else { llwarns << "Valid path arguments to buildHTTP are array, string, or undef, you passed type" << path.type() << llendl; } result.mEscapedOpaque = "//" + result.mEscapedAuthority + result.mEscapedPath; return result; } // static LLURI LLURI::buildHTTP(const std::string& prefix, const LLSD& path, const LLSD& query) { LLURI uri = buildHTTP(prefix, path); // break out and escape each query component uri.mEscapedQuery = mapToQueryString(query); uri.mEscapedOpaque += uri.mEscapedQuery ; uri.mEscapedQuery.erase(0,1); // trim the leading '?' return uri; } // static LLURI LLURI::buildHTTP(const std::string& host, const U32& port, const LLSD& path) { return LLURI::buildHTTP(llformat("%s:%u", host.c_str(), port), path); } // static LLURI LLURI::buildHTTP(const std::string& host, const U32& port, const LLSD& path, const LLSD& query) { return LLURI::buildHTTP(llformat("%s:%u", host.c_str(), port), path, query); } std::string LLURI::asString() const { if (mScheme.empty()) { return mEscapedOpaque; } else { return mScheme + ":" + mEscapedOpaque; } } std::string LLURI::scheme() const { return mScheme; } std::string LLURI::opaque() const { return unescape(mEscapedOpaque); } std::string LLURI::authority() const { return unescape(mEscapedAuthority); } namespace { void findAuthorityParts(const std::string& authority, std::string& user, std::string& host, std::string& port) { std::string::size_type start_pos = authority.find('@'); if (start_pos == std::string::npos) { user = ""; start_pos = 0; } else { user = authority.substr(0, start_pos); start_pos += 1; } std::string::size_type end_pos = authority.find(':', start_pos); if (end_pos == std::string::npos) { host = authority.substr(start_pos); port = ""; } else { host = authority.substr(start_pos, end_pos - start_pos); port = authority.substr(end_pos + 1); } } } std::string LLURI::hostName() const { std::string user, host, port; findAuthorityParts(mEscapedAuthority, user, host, port); return unescape(host); } std::string LLURI::userName() const { std::string user, userPass, host, port; findAuthorityParts(mEscapedAuthority, userPass, host, port); std::string::size_type pos = userPass.find(':'); if (pos != std::string::npos) { user = userPass.substr(0, pos); } return unescape(user); } std::string LLURI::password() const { std::string pass, userPass, host, port; findAuthorityParts(mEscapedAuthority, userPass, host, port); std::string::size_type pos = userPass.find(':'); if (pos != std::string::npos) { pass = userPass.substr(pos + 1); } return unescape(pass); } BOOL LLURI::defaultPort() const { return isDefault(mScheme, hostPort()); } U16 LLURI::hostPort() const { std::string user, host, port; findAuthorityParts(mEscapedAuthority, user, host, port); if (port.empty()) { if (mScheme == "http") return 80; if (mScheme == "https") return 443; if (mScheme == "ftp") return 21; return 0; } return atoi(port.c_str()); } std::string LLURI::path() const { return unescape(mEscapedPath); } LLSD LLURI::pathArray() const { typedef boost::tokenizer > tokenizer; boost::char_separator sep("/", "", boost::drop_empty_tokens); tokenizer tokens(mEscapedPath, sep); tokenizer::iterator it = tokens.begin(); tokenizer::iterator end = tokens.end(); LLSD params; for ( ; it != end; ++it) { params.append(*it); } return params; } std::string LLURI::query() const { return unescape(mEscapedQuery); } LLSD LLURI::queryMap() const { return queryMap(mEscapedQuery); } // static LLSD LLURI::queryMap(std::string escaped_query_string) { lldebugs << "LLURI::queryMap query params: " << escaped_query_string << llendl; LLSD result = LLSD::emptyArray(); while(!escaped_query_string.empty()) { // get tuple first std::string tuple; std::string::size_type tuple_begin = escaped_query_string.find('&'); if (tuple_begin != std::string::npos) { tuple = escaped_query_string.substr(0, tuple_begin); escaped_query_string = escaped_query_string.substr(tuple_begin+1); } else { tuple = escaped_query_string; escaped_query_string = ""; } if (tuple.empty()) continue; // parse tuple std::string::size_type key_end = tuple.find('='); if (key_end != std::string::npos) { std::string key = unescape(tuple.substr(0,key_end)); std::string value = unescape(tuple.substr(key_end+1)); lldebugs << "inserting key " << key << " value " << value << llendl; result[key] = value; } else { lldebugs << "inserting key " << unescape(tuple) << " value true" << llendl; result[unescape(tuple)] = true; } } return result; } std::string LLURI::mapToQueryString(const LLSD& queryMap) { std::string query_string; if (queryMap.isMap()) { bool first_element = true; LLSD::map_const_iterator iter = queryMap.beginMap(); LLSD::map_const_iterator end = queryMap.endMap(); std::ostringstream ostr; for (; iter != end; ++iter) { if(first_element) { ostr << "?"; first_element = false; } else { ostr << "&"; } ostr << escapeQueryVariable(iter->first); if(iter->second.isDefined()) { ostr << "=" << escapeQueryValue(iter->second.asString()); } } query_string = ostr.str(); } return query_string; }