diff options
Diffstat (limited to 'indra/llcommon')
-rw-r--r-- | indra/llcommon/llstring.h | 11 | ||||
-rw-r--r-- | indra/llcommon/lluri.cpp | 98 | ||||
-rw-r--r-- | indra/llcommon/lluri.h | 8 | ||||
-rw-r--r-- | indra/llcommon/tests/lluri_test.cpp | 35 |
4 files changed, 148 insertions, 4 deletions
diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h index 30bec3a6f8..b619a9e48c 100644 --- a/indra/llcommon/llstring.h +++ b/indra/llcommon/llstring.h @@ -1735,7 +1735,8 @@ bool LLStringUtilBase<T>::startsWith( const string_type& substr) { if(string.empty() || (substr.empty())) return false; - if(0 == string.find(substr)) return true; + if (substr.length() > string.length()) return false; + if (0 == string.compare(0, substr.length(), substr)) return true; return false; } @@ -1746,9 +1747,11 @@ bool LLStringUtilBase<T>::endsWith( const string_type& substr) { if(string.empty() || (substr.empty())) return false; - std::string::size_type idx = string.rfind(substr); - if(std::string::npos == idx) return false; - return (idx == (string.size() - substr.size())); + size_t sub_len = substr.length(); + size_t str_len = string.length(); + if (sub_len > str_len) return false; + if (0 == string.compare(str_len - sub_len, sub_len, substr)) return true; + return false; } // static diff --git a/indra/llcommon/lluri.cpp b/indra/llcommon/lluri.cpp index 758b98e143..9942bc0cf8 100644 --- a/indra/llcommon/lluri.cpp +++ b/indra/llcommon/lluri.cpp @@ -173,6 +173,19 @@ namespace "-._~"; 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 = "!$&'()*+,;="; @@ -187,6 +200,12 @@ namespace { 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 @@ -202,6 +221,85 @@ std::string LLURI::escape(const std::string& str) 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) + { + 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() { } diff --git a/indra/llcommon/lluri.h b/indra/llcommon/lluri.h index 9e44cc7da2..b8fca0ca51 100644 --- a/indra/llcommon/lluri.h +++ b/indra/llcommon/lluri.h @@ -158,6 +158,14 @@ public: bool is_allowed_sorted = false); /** + * @brief Break string into data part and path or sheme + * and escape path (if present) and data. + * Data part is not allowed to have path related symbols + * @param str The raw URI to escape. + */ + static std::string escapePathAndData(const std::string &str); + + /** * @brief unescape an escaped URI string. * * @param str The escped URI to unescape. diff --git a/indra/llcommon/tests/lluri_test.cpp b/indra/llcommon/tests/lluri_test.cpp index 4c64f15ca7..1a4c6641b9 100644 --- a/indra/llcommon/tests/lluri_test.cpp +++ b/indra/llcommon/tests/lluri_test.cpp @@ -383,6 +383,41 @@ namespace tut ensure_equals("query", u.query(), "redirect-http-hack=secondlife:///app/login?first_name=Callum&last_name=Linden&location=specify&grid=vaak®ion=/Morris/128/128&web_login_key=efaa4795-c2aa-4c58-8966-763c27931e78"); ensure_equals("query map element", u.queryMap()["redirect-http-hack"].asString(), "secondlife:///app/login?first_name=Callum&last_name=Linden&location=specify&grid=vaak®ion=/Morris/128/128&web_login_key=efaa4795-c2aa-4c58-8966-763c27931e78"); } + + template<> template<> + void URITestObject::test<20>() + { + set_test_name("escapePathAndData uri test"); + + // Basics scheme:[//authority]path[?query][#fragment] + ensure_equals(LLURI::escapePathAndData("dirname?query"), + "dirname?query"); + ensure_equals(LLURI::escapePathAndData("dirname?query=data"), + "dirname?query=data"); + ensure_equals(LLURI::escapePathAndData("host://dirname/subdir name?query#fragment"), + "host://dirname/subdir%20name?query#fragment"); + ensure_equals(LLURI::escapePathAndData("host://dirname/subdir name?query=some@>data#fragment"), + "host://dirname/subdir%20name?query=some@%3Edata#fragment"); + ensure_equals(LLURI::escapePathAndData("host://dir[name/subdir name?query=some[data#fra[gment"), + "host://dir[name/subdir%20name?query=some%5Bdata#fra[gment"); + ensure_equals(LLURI::escapePathAndData("mailto:zero@ll.com"), + "mailto:zero@ll.com"); + // pre-escaped + ensure_equals(LLURI::escapePathAndData("host://dirname/subdir%20name"), + "host://dirname/subdir%20name"); + + // data:[<mediatype>][;base64],<data> + ensure_equals(LLURI::escapePathAndData("data:,Hello, World!"), + "data:,Hello%2C%20World%21"); + ensure_equals(LLURI::escapePathAndData("data:text/html,<h1>Hello, World!</h1>"), + "data:text/html,%3Ch1%3EHello%2C%20World%21%3C%2Fh1%3E"); + // pre-escaped + ensure_equals(LLURI::escapePathAndData("data:text/html,%3Ch1%3EHello%2C%20World!</h1>"), + "data:text/html,%3Ch1%3EHello%2C%20World%21%3C%2Fh1%3E"); + // assume that base64 does not need escaping + ensure_equals(LLURI::escapePathAndData("data:image;base64,SGVs/bG8sIFd/vcmxkIQ%3D%3D!-&*?="), + "data:image;base64,SGVs/bG8sIFd/vcmxkIQ%3D%3D!-&*?="); + } } |