summaryrefslogtreecommitdiff
path: root/indra/llcommon/lluri.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'indra/llcommon/lluri.cpp')
-rw-r--r--indra/llcommon/lluri.cpp1516
1 files changed, 758 insertions, 758 deletions
diff --git a/indra/llcommon/lluri.cpp b/indra/llcommon/lluri.cpp
index 50ff0f9de0..b4664318cc 100644
--- a/indra/llcommon/lluri.cpp
+++ b/indra/llcommon/lluri.cpp
@@ -1,758 +1,758 @@
-/**
- * @file lluri.cpp
- * @author Phoenix
- * @date 2006-02-08
- * @brief Implementation of the LLURI class.
- *
- * $LicenseInfo:firstyear=2006&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 "linden_common.h"
-
-#include "llapp.h"
-#include "lluri.h"
-#include "llsd.h"
-#include <iomanip>
-
-#include "lluuid.h"
-
-// system includes
-#include <boost/tokenizer.hpp>
-#include <boost/algorithm/string/find_iterator.hpp>
-#include <boost/algorithm/string/finder.hpp>
-
-// static
-void LLURI::encodeCharacter(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<S32>(static_cast<U8>(val))
-
- // reset stream state
- << std::nouppercase
- << std::dec
- << std::setfill(' ');
-}
-
-// 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
- {
- encodeCharacter(ostr, c);
- }
- }
- }
- else
- {
- for(; it != end; ++it)
- {
- c = *it;
- if(allowed.find(c) == std::string::npos)
- {
- encodeCharacter(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;
-
- if(is_char_hex(*it))
- {
- U8 c = hex_as_nybble(*it++);
-
- c = c << 4;
- if (it == end) break;
-
- if(is_char_hex(*it))
- {
- c |= hex_as_nybble(*it);
- ostr.put((char)c);
- }
- else
- {
- ostr.put((char)c);
- ostr.put(*it);
- }
- }
- else
- {
- ostr.put('%');
- ostr.put(*it);
- }
- }
- else
- {
- ostr.put(*it);
- }
- }
- return ostr.str();
-}
-
-namespace
-{
- const std::string unreserved()
- {
- static const std::string s =
- "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
- "0123456789"
- "-._~";
- return s;
- }
- const std::string path()
- {
- static const std::string s =
- "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
- "abcdefghijklmnopqrstuvwxyz"
- "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 - "&;" + ":@"
- std::string escapeUriQuery(const std::string& s)
- { return LLURI::escape(s, unreserved() + ":@?&$;*+=%/"); }
- std::string escapeUriData(const std::string& s)
- { return LLURI::escape(s, unreserved() + "%"); }
- std::string escapeUriPath(const std::string& s)
- { return LLURI::escape(s, path()); }
-}
-
-//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);
-}
-
-//static
-std::string LLURI::escapePathAndData(const std::string &str)
-{
- std::string result;
-
- const std::string data_marker = "data:";
- if (str.compare(0, data_marker.length(), data_marker) == 0)
- {
- // This is not url, but data, data part needs to be properly escaped
- // data part is separated by ',' from header. Minimal data uri is "data:,"
- // See "data URI scheme"
- size_t separator = str.find(',');
- if (separator != std::string::npos)
- {
- size_t header_size = separator + 1;
- std::string header = str.substr(0, header_size);
- // base64 is url-safe
- if (header.find("base64") != std::string::npos)
- {
- // assume url-safe data
- result = str;
- }
- else
- {
- std::string data = str.substr(header_size, str.length() - header_size);
-
- // Notes: File can be partially pre-escaped, that's why escaping ignores '%'
- // It somewhat limits user from displaying strings like "%20" in text
- // but that's how viewer worked for a while and user can double-escape it
-
-
- // Header doesn't need escaping
- result = header + escapeUriData(data);
- }
- }
- }
- else
- {
- // try processing it as path with query separator
- // The query component is indicated by the first question
- // mark("?") character and terminated by a number sign("#")
- size_t delim_pos = str.find('?');
- if (delim_pos == std::string::npos)
- {
- // alternate separator
- delim_pos = str.find(';');
- }
-
- if (delim_pos != std::string::npos)
- {
- size_t path_size = delim_pos + 1;
- std::string query;
- std::string fragment;
-
- size_t fragment_pos = str.find('#');
- if ((fragment_pos != std::string::npos) && (fragment_pos > delim_pos))
- {
- query = str.substr(path_size, fragment_pos - path_size);
- fragment = str.substr(fragment_pos);
- }
- else
- {
- query = str.substr(path_size);
- }
-
- std::string path = str.substr(0, path_size);
-
- result = escapeUriPath(path) + escapeUriQuery(query) + escapeUriPath(fragment);
- }
- }
-
- if (result.empty())
- {
- // Not a known scheme or no data part, try just escaping as Uri path
- result = escapeUriPath(str);
- }
- return result;
-}
-
-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" ||
- mScheme == "x-grid-location-info")
- {
- 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)
- {
- LL_DEBUGS() << "PATH: inserting " << it->asString() << LL_ENDL;
- result.mEscapedPath += "/" + escapePathComponent(it->asString());
- }
- }
- else if (path.isString())
- {
- std::string pathstr(path);
- // Trailing slash is significant in HTTP land. If caller specified,
- // make a point of preserving.
- std::string last_slash;
- std::string::size_type len(pathstr.length());
- if (len && pathstr[len-1] == '/')
- {
- last_slash = "/";
- }
-
- // Escape every individual path component, recombining with slashes.
- for (boost::split_iterator<std::string::const_iterator>
- ti(pathstr, boost::first_finder("/")), tend;
- ti != tend; ++ti)
- {
- // Eliminate a leading slash or duplicate slashes anywhere. (Extra
- // slashes show up here as empty components.) This test also
- // eliminates a trailing slash, hence last_slash above.
- if (! ti->empty())
- {
- result.mEscapedPath
- += "/" + escapePathComponent(std::string(ti->begin(), ti->end()));
- }
- }
-
- // Reinstate trailing slash, if any.
- result.mEscapedPath += last_slash;
- }
- else if(path.isUndefined())
- {
- // do nothing
- }
- else
- {
- LL_WARNS() << "Valid path arguments to buildHTTP are array, string, or undef, you passed type"
- << path.type() << LL_ENDL;
- }
- 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<boost::char_separator<char> > tokenizer;
- boost::char_separator<char> sep("/", "", boost::drop_empty_tokens);
- tokenizer tokens(mEscapedPath, sep);
- tokenizer::iterator it = tokens.begin();
- tokenizer::iterator end = tokens.end();
-
- LLSD params;
- for (const std::string& str : tokens)
- {
- params.append(str);
- }
- 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)
-{
- LL_DEBUGS() << "LLURI::queryMap query params: " << escaped_query_string << LL_ENDL;
-
- 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));
- LL_DEBUGS() << "inserting key " << key << " value " << value << LL_ENDL;
- result[key] = value;
- }
- else
- {
- LL_DEBUGS() << "inserting key " << unescape(tuple) << " value true" << LL_ENDL;
- 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;
-}
-
-bool operator!=(const LLURI& first, const LLURI& second)
-{
- return (first.asString() != second.asString());
-}
+/**
+ * @file lluri.cpp
+ * @author Phoenix
+ * @date 2006-02-08
+ * @brief Implementation of the LLURI class.
+ *
+ * $LicenseInfo:firstyear=2006&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 "linden_common.h"
+
+#include "llapp.h"
+#include "lluri.h"
+#include "llsd.h"
+#include <iomanip>
+
+#include "lluuid.h"
+
+// system includes
+#include <boost/tokenizer.hpp>
+#include <boost/algorithm/string/find_iterator.hpp>
+#include <boost/algorithm/string/finder.hpp>
+
+// static
+void LLURI::encodeCharacter(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<S32>(static_cast<U8>(val))
+
+ // reset stream state
+ << std::nouppercase
+ << std::dec
+ << std::setfill(' ');
+}
+
+// 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
+ {
+ encodeCharacter(ostr, c);
+ }
+ }
+ }
+ else
+ {
+ for(; it != end; ++it)
+ {
+ c = *it;
+ if(allowed.find(c) == std::string::npos)
+ {
+ encodeCharacter(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;
+
+ if(is_char_hex(*it))
+ {
+ U8 c = hex_as_nybble(*it++);
+
+ c = c << 4;
+ if (it == end) break;
+
+ if(is_char_hex(*it))
+ {
+ c |= hex_as_nybble(*it);
+ ostr.put((char)c);
+ }
+ else
+ {
+ ostr.put((char)c);
+ ostr.put(*it);
+ }
+ }
+ else
+ {
+ ostr.put('%');
+ ostr.put(*it);
+ }
+ }
+ else
+ {
+ ostr.put(*it);
+ }
+ }
+ return ostr.str();
+}
+
+namespace
+{
+ const std::string unreserved()
+ {
+ static const std::string s =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
+ "0123456789"
+ "-._~";
+ return s;
+ }
+ const std::string path()
+ {
+ static const std::string s =
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz"
+ "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 - "&;" + ":@"
+ std::string escapeUriQuery(const std::string& s)
+ { return LLURI::escape(s, unreserved() + ":@?&$;*+=%/"); }
+ std::string escapeUriData(const std::string& s)
+ { return LLURI::escape(s, unreserved() + "%"); }
+ std::string escapeUriPath(const std::string& s)
+ { return LLURI::escape(s, path()); }
+}
+
+//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);
+}
+
+//static
+std::string LLURI::escapePathAndData(const std::string &str)
+{
+ std::string result;
+
+ const std::string data_marker = "data:";
+ if (str.compare(0, data_marker.length(), data_marker) == 0)
+ {
+ // This is not url, but data, data part needs to be properly escaped
+ // data part is separated by ',' from header. Minimal data uri is "data:,"
+ // See "data URI scheme"
+ size_t separator = str.find(',');
+ if (separator != std::string::npos)
+ {
+ size_t header_size = separator + 1;
+ std::string header = str.substr(0, header_size);
+ // base64 is url-safe
+ if (header.find("base64") != std::string::npos)
+ {
+ // assume url-safe data
+ result = str;
+ }
+ else
+ {
+ std::string data = str.substr(header_size, str.length() - header_size);
+
+ // Notes: File can be partially pre-escaped, that's why escaping ignores '%'
+ // It somewhat limits user from displaying strings like "%20" in text
+ // but that's how viewer worked for a while and user can double-escape it
+
+
+ // Header doesn't need escaping
+ result = header + escapeUriData(data);
+ }
+ }
+ }
+ else
+ {
+ // try processing it as path with query separator
+ // The query component is indicated by the first question
+ // mark("?") character and terminated by a number sign("#")
+ size_t delim_pos = str.find('?');
+ if (delim_pos == std::string::npos)
+ {
+ // alternate separator
+ delim_pos = str.find(';');
+ }
+
+ if (delim_pos != std::string::npos)
+ {
+ size_t path_size = delim_pos + 1;
+ std::string query;
+ std::string fragment;
+
+ size_t fragment_pos = str.find('#');
+ if ((fragment_pos != std::string::npos) && (fragment_pos > delim_pos))
+ {
+ query = str.substr(path_size, fragment_pos - path_size);
+ fragment = str.substr(fragment_pos);
+ }
+ else
+ {
+ query = str.substr(path_size);
+ }
+
+ std::string path = str.substr(0, path_size);
+
+ result = escapeUriPath(path) + escapeUriQuery(query) + escapeUriPath(fragment);
+ }
+ }
+
+ if (result.empty())
+ {
+ // Not a known scheme or no data part, try just escaping as Uri path
+ result = escapeUriPath(str);
+ }
+ return result;
+}
+
+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" ||
+ mScheme == "x-grid-location-info")
+ {
+ 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)
+ {
+ LL_DEBUGS() << "PATH: inserting " << it->asString() << LL_ENDL;
+ result.mEscapedPath += "/" + escapePathComponent(it->asString());
+ }
+ }
+ else if (path.isString())
+ {
+ std::string pathstr(path);
+ // Trailing slash is significant in HTTP land. If caller specified,
+ // make a point of preserving.
+ std::string last_slash;
+ std::string::size_type len(pathstr.length());
+ if (len && pathstr[len-1] == '/')
+ {
+ last_slash = "/";
+ }
+
+ // Escape every individual path component, recombining with slashes.
+ for (boost::split_iterator<std::string::const_iterator>
+ ti(pathstr, boost::first_finder("/")), tend;
+ ti != tend; ++ti)
+ {
+ // Eliminate a leading slash or duplicate slashes anywhere. (Extra
+ // slashes show up here as empty components.) This test also
+ // eliminates a trailing slash, hence last_slash above.
+ if (! ti->empty())
+ {
+ result.mEscapedPath
+ += "/" + escapePathComponent(std::string(ti->begin(), ti->end()));
+ }
+ }
+
+ // Reinstate trailing slash, if any.
+ result.mEscapedPath += last_slash;
+ }
+ else if(path.isUndefined())
+ {
+ // do nothing
+ }
+ else
+ {
+ LL_WARNS() << "Valid path arguments to buildHTTP are array, string, or undef, you passed type"
+ << path.type() << LL_ENDL;
+ }
+ 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<boost::char_separator<char> > tokenizer;
+ boost::char_separator<char> sep("/", "", boost::drop_empty_tokens);
+ tokenizer tokens(mEscapedPath, sep);
+ tokenizer::iterator it = tokens.begin();
+ tokenizer::iterator end = tokens.end();
+
+ LLSD params;
+ for (const std::string& str : tokens)
+ {
+ params.append(str);
+ }
+ 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)
+{
+ LL_DEBUGS() << "LLURI::queryMap query params: " << escaped_query_string << LL_ENDL;
+
+ 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));
+ LL_DEBUGS() << "inserting key " << key << " value " << value << LL_ENDL;
+ result[key] = value;
+ }
+ else
+ {
+ LL_DEBUGS() << "inserting key " << unescape(tuple) << " value true" << LL_ENDL;
+ 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;
+}
+
+bool operator!=(const LLURI& first, const LLURI& second)
+{
+ return (first.asString() != second.asString());
+}