From 7112e163e6e4c7a9ed9bf71f7b54a2486014f361 Mon Sep 17 00:00:00 2001 From: Lirusaito Date: Sun, 20 Jan 2019 09:13:05 -0500 Subject: [PATCH] Update our url stuffs! Adds support for JIRA link labels from Alchemy. (Made less of a mess by me) Adds support for x-grid-info, the future of x-grid-info-location from Alchemy. Updates uriparser latest from Alchemy. Updates llstring to be more in line with upstream Alchemy. Fixes our LLURI Implementation Updates LLURLAction to modern C++ stuffies~ Adds Email protocol support from alchemy Sync LLSLURL with Alchemy, adding x-grid-info support. Also keep NoProtocol Support because yaaassss~ (also we won't suffer from MAINT-5019 because we're not dumb.) --- autobuild.xml | 10 +- indra/cmake/CMakeLists.txt | 2 + indra/cmake/FindURIPARSER.cmake | 46 ++ indra/cmake/URIPARSER.cmake | 20 + indra/llcommon/CMakeLists.txt | 4 + indra/llcommon/llstring.cpp | 77 +++- indra/llcommon/llstring.h | 125 ++++-- indra/llcommon/lluri.cpp | 61 ++- indra/llcommon/lluri.h | 95 +++-- indra/llcommon/lluriparser.cpp | 266 ++++++++++++ indra/llcommon/lluriparser.h | 87 ++++ indra/llui/lltexteditor.cpp | 19 +- indra/llui/llurlaction.cpp | 96 ++++- indra/llui/llurlaction.h | 22 +- indra/llui/llurlentry.cpp | 392 ++++++++++++++++-- indra/llui/llurlentry.h | 200 ++++++--- indra/llui/llurlmatch.cpp | 13 +- indra/llui/llurlmatch.h | 16 +- indra/llui/llurlregistry.cpp | 116 +++++- indra/llui/llurlregistry.h | 11 +- indra/llwindow/llwindow.cpp | 4 +- indra/newview/CMakeLists.txt | 2 + indra/newview/chatbar_as_cmdline.cpp | 2 +- .../installers/windows/installer_template.nsi | 26 ++ indra/newview/llfloaterobjectiminfo.cpp | 2 +- indra/newview/llslurl.cpp | 31 +- indra/newview/llslurl.h | 1 + indra/newview/llurldispatcher.cpp | 30 +- indra/newview/llurldispatcher.h | 41 +- .../newview/skins/default/xui/de/strings.xml | 3 + .../skins/default/xui/en-us/strings.xml | 1 + .../newview/skins/default/xui/es/strings.xml | 3 + .../newview/skins/default/xui/pt/strings.xml | 6 + 33 files changed, 1529 insertions(+), 301 deletions(-) create mode 100644 indra/cmake/FindURIPARSER.cmake create mode 100644 indra/cmake/URIPARSER.cmake create mode 100644 indra/llcommon/lluriparser.cpp create mode 100644 indra/llcommon/lluriparser.h diff --git a/autobuild.xml b/autobuild.xml index 274dde845..3108aac0d 100644 --- a/autobuild.xml +++ b/autobuild.xml @@ -2476,7 +2476,7 @@ hash_algorithm md5 url - https://bitbucket.org/alchemyviewer/publiclibs-darwin/downloads/uriparser-0.8.4-darwin-201511221948.tar.bz2 + https://depot.alchemyviewer.org/pub/darwin/lib/uriparser-0.8.4-darwin-201511221948.tar.bz2 name darwin @@ -2498,11 +2498,11 @@ archive hash - 69224d9285e5a26fc7a9aca5d3da7543 + cf21d9f7ed7949d2230413e8a391fb73 hash_algorithm md5 url - http://depot.alchemyviewer.org/pub/linux64/lib-trusty/uriparser-0.8.4-linux64-201603240044.tar.bz2 + https://depot.alchemyviewer.org/pub/linux64/lib-xenial/uriparser-0.8.4-linux64-201609241428.tar.bz2 name linux64 @@ -2512,11 +2512,11 @@ archive hash - f9f82fbd29751a969e9ab55a1f33ac7b + 89f3fc7f3fe396439d306f099d1a12d3 hash_algorithm md5 url - http://depot.alchemyviewer.org/pub/windows/lib-vc14/uriparser-0.8.4-windows-201601151009.tar.bz2 + https://depot.alchemyviewer.org/pub/windows/lib-vc141/uriparser-0.8.4-windows-201703090606.tar.bz2 name windows diff --git a/indra/cmake/CMakeLists.txt b/indra/cmake/CMakeLists.txt index 7c8e616d6..7531bd335 100644 --- a/indra/cmake/CMakeLists.txt +++ b/indra/cmake/CMakeLists.txt @@ -40,6 +40,7 @@ set(cmake_SOURCE_FILES FindNDOF.cmake FindOpenJPEG.cmake FindTut.cmake + FindURIPARSER.cmake FindXmlRpcEpi.cmake FreeType.cmake GLOD.cmake @@ -93,6 +94,7 @@ set(cmake_SOURCE_FILES Tut.cmake UI.cmake UnixInstall.cmake + URIPARSER.cmake Variables.cmake ViewerMiscLibs.cmake WinManifest.cmake diff --git a/indra/cmake/FindURIPARSER.cmake b/indra/cmake/FindURIPARSER.cmake new file mode 100644 index 000000000..05f2d1737 --- /dev/null +++ b/indra/cmake/FindURIPARSER.cmake @@ -0,0 +1,46 @@ +# -*- cmake -*- + +# - Find uriparser +# Find the URIPARSER includes and library +# This module defines +# URIPARSER_INCLUDE_DIRS, where to find uriparser.h, etc. +# URIPARSER_LIBRARY, the libraries needed to use uriparser. +# URIPARSER_FOUND, If false, do not try to use uriparser. +# +# This FindURIPARSER is about 43 times as fast the one provided with cmake (2.8.x), +# because it doesn't look up the version of uriparser, resulting in a dramatic +# speed up for configure (from 4 minutes 22 seconds to 6 seconds). +# +# Note: Since this file is only used for standalone, the windows +# specific parts were left out. + +FIND_PATH(URIPARSER_INCLUDE_DIR uriparser/Uri.h + NO_SYSTEM_ENVIRONMENT_PATH + ) + +FIND_LIBRARY(URIPARSER_LIBRARY uriparser) + +if (URIPARSER_LIBRARY AND URIPARSER_INCLUDE_DIR) + SET(URIPARSER_INCLUDE_DIRS ${URIPARSER_INCLUDE_DIR}) + SET(URIPARSER_LIBRARY ${URIPARSER_LIBRARY}) + SET(URIPARSER_FOUND "YES") +else (URIPARSER_LIBRARY AND URIPARSER_INCLUDE_DIR) + SET(URIPARSER_FOUND "NO") +endif (URIPARSER_LIBRARY AND URIPARSER_INCLUDE_DIR) + +if (URIPARSER_FOUND) + if (NOT URIPARSER_FIND_QUIETLY) + message(STATUS "Found URIPARSER: ${URIPARSER_LIBRARY}") + SET(URIPARSER_FIND_QUIETLY TRUE) + endif (NOT URIPARSER_FIND_QUIETLY) +else (URIPARSER_FOUND) + if (URIPARSER_FIND_REQUIRED) + message(FATAL_ERROR "Could not find URIPARSER library") + endif (URIPARSER_FIND_REQUIRED) +endif (URIPARSER_FOUND) + +mark_as_advanced( + URIPARSER_LIBRARY + URIPARSER_INCLUDE_DIR + ) + diff --git a/indra/cmake/URIPARSER.cmake b/indra/cmake/URIPARSER.cmake new file mode 100644 index 000000000..47c3ccd37 --- /dev/null +++ b/indra/cmake/URIPARSER.cmake @@ -0,0 +1,20 @@ +# -*- cmake -*- + +set(URIPARSER_FIND_QUIETLY ON) +set(URIPARSER_FIND_REQUIRED ON) + +include(Prebuilt) + +if (USESYSTEMLIBS) + include(FindURIPARSER) +else (USESYSTEMLIBS) + use_prebuilt_binary(uriparser) + if (WINDOWS) + set(URIPARSER_LIBRARY + debug uriparserd + optimized uriparser) + elseif (DARWIN OR LINUX) + set(URIPARSER_LIBRARY uriparser) + endif (WINDOWS) + set(URIPARSER_INCLUDE_DIRS ${LIBS_PREBUILT_DIR}/include/uriparser) +endif (USESYSTEMLIBS) diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index 80eeab0d4..8d0e54aa7 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -13,6 +13,7 @@ include(LLSharedLibs) include(GoogleBreakpad) include(Copy3rdPartyLibs) include(ZLIB) +include(URIPARSER) include_directories( ${EXPAT_INCLUDE_DIRS} @@ -101,6 +102,7 @@ set(llcommon_SOURCE_FILES llthreadsafequeue.cpp lltimer.cpp lluri.cpp + lluriparser.cpp lluuid.cpp llworkerthread.cpp metaclass.cpp @@ -238,6 +240,7 @@ set(llcommon_HEADER_FILES llunittype.h lltypeinfolookup.h lluri.h + lluriparser.h lluuid.h llwin32headers.h llwin32headerslean.h @@ -296,6 +299,7 @@ target_link_libraries( ${Boost_THREAD_LIBRARY} ${Boost_SYSTEM_LIBRARY} ${CORESERVICES_LIBRARY} + ${URIPARSER_LIBRARY} ) if (DARWIN) diff --git a/indra/llcommon/llstring.cpp b/indra/llcommon/llstring.cpp index 3562c044f..30b4266b7 100644 --- a/indra/llcommon/llstring.cpp +++ b/indra/llcommon/llstring.cpp @@ -1,3 +1,5 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check it. +// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com /** * @file llstring.cpp * @brief String utility functions and the std::string class. @@ -28,12 +30,17 @@ #include "llstring.h" #include "llerror.h" +#include "llfasttimer.h" +#include "llsd.h" #if LL_WINDOWS #include "llwin32headerslean.h" #include // for WideCharToMultiByte #endif +LLTrace::BlockTimerStatHandle FT_STRING_FORMAT("String Format"); + + std::string ll_safe_string(const char* in) { if(in) return std::string(in); @@ -47,6 +54,23 @@ std::string ll_safe_string(const char* in, S32 maxlen) return std::string(); } +bool is_char_hex(char hex) +{ + if((hex >= '0') && (hex <= '9')) + { + return true; + } + else if((hex >= 'a') && (hex <='f')) + { + return true; + } + else if((hex >= 'A') && (hex <='F')) + { + return true; + } + return false; // uh - oh, not hex any more... +} + U8 hex_as_nybble(char hex) { if((hex >= '0') && (hex <= '9')) @@ -85,7 +109,7 @@ bool iswindividual(llwchar elem) bool _read_file_into_string(std::string& str, const std::string& filename) { - llifstream ifs(filename, llifstream::binary); + llifstream ifs(filename.c_str(), llifstream::binary); if (!ifs.is_open()) { LL_INFOS() << "Unable to open file " << filename << LL_ENDL; @@ -552,6 +576,33 @@ std::string utf8str_truncate(const std::string& utf8str, const S32 max_len) } } +std::string utf8str_symbol_truncate(const std::string& utf8str, const S32 symbol_len) +{ + if (0 == symbol_len) + { + return std::string(); + } + if ((S32)utf8str.length() <= symbol_len) + { + return utf8str; + } + else + { + int len = 0, byteIndex = 0; + const char* aStr = utf8str.c_str(); + size_t origSize = utf8str.size(); + + for (byteIndex = 0; len < symbol_len && byteIndex < origSize; byteIndex++) + { + if ((aStr[byteIndex] & 0xc0) != 0x80) + { + len += 1; + } + } + return utf8str.substr(0, byteIndex); + } +} + std::string utf8str_substChar( const std::string& utf8str, const llwchar target_char, @@ -628,10 +679,10 @@ std::string ll_convert_wide_to_string(const wchar_t* in, unsigned int code_page) 0, in, len_in, - NULL, + nullptr, 0, - 0, - 0); + nullptr, + nullptr); // We will need two more bytes for the double NULL ending // created in WideCharToMultiByte(). char* pout = new char [len_out + 2]; @@ -645,8 +696,8 @@ std::string ll_convert_wide_to_string(const wchar_t* in, unsigned int code_page) len_in, pout, len_out, - 0, - 0); + nullptr, + nullptr); out.assign(pout); delete[] pout; } @@ -667,8 +718,8 @@ wchar_t* ll_convert_string_to_wide(const std::string& in, unsigned int code_page // reserve place to NULL terminator int output_str_len = in.length(); wchar_t* w_out = new wchar_t[output_str_len + 1]; - memset(w_out, 0, output_str_len + 1); + int real_output_str_len = MultiByteToWideChar (code_page, 0, in.c_str(), in.length(), w_out, output_str_len); //looks like MultiByteToWideChar didn't add null terminator to converted string, see EXT-4858. @@ -689,7 +740,7 @@ std::string ll_convert_string_to_utf8_string(const std::string& in) long LLStringOps::sPacificTimeOffset = 0; long LLStringOps::sLocalTimeOffset = 0; -bool LLStringOps::sPacificDaylightTime = 0; +bool LLStringOps::sPacificDaylightTime = false; std::map LLStringOps::datetimeToCodes; std::vector LLStringOps::sWeekDayList; @@ -708,7 +759,7 @@ S32 LLStringOps::collate(const llwchar* a, const llwchar* b) #if LL_WINDOWS // in Windows, wide string functions operator on 16-bit strings, // not the proper 32 bit wide string - return strcmp(wstring_to_utf8str(LLWString(a)).c_str(), wstring_to_utf8str(LLWString(b)).c_str()); + return strcoll(wstring_to_utf8str(LLWString(a)).c_str(), wstring_to_utf8str(LLWString(b)).c_str()); #else return wcscoll(a, b); #endif @@ -719,7 +770,7 @@ void LLStringOps::setupDatetimeInfo (bool daylight) time_t nowT, localT, gmtT; struct tm * tmpT; - nowT = time (NULL); + nowT = time (nullptr); tmpT = gmtime (&nowT); gmtT = mktime (tmpT); @@ -799,7 +850,7 @@ void LLStringOps::setupDayFormat(const std::string& data) } -std::string LLStringOps::getDatetimeCode (std::string key) +std::string LLStringOps::getDatetimeCode(const std::string& key) { std::map::iterator iter; @@ -1167,6 +1218,7 @@ bool LLStringUtil::formatDatetime(std::string& replacement, std::string token, template<> S32 LLStringUtil::format(std::string& s, const format_map_t& substitutions) { + LL_RECORD_BLOCK_TIME(FT_STRING_FORMAT); S32 res = 0; std::string output; @@ -1239,6 +1291,7 @@ S32 LLStringUtil::format(std::string& s, const format_map_t& substitutions) template<> S32 LLStringUtil::format(std::string& s, const LLSD& substitutions) { + LL_RECORD_BLOCK_TIME(FT_STRING_FORMAT); S32 res = 0; if (!substitutions.isMap()) @@ -1367,7 +1420,7 @@ void LLStringUtilBase::testHarness() s2.erase( 4, 1 ); llassert( s2 == "hell"); - s2.insert( 0, 1, 'y' ); + s2.insert( s2.begin(), 'y' ); llassert( s2 == "yhell"); s2.erase( 1, 3 ); llassert( s2 == "yl"); diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h index 41c38fe13..959833dd9 100644 --- a/indra/llcommon/llstring.h +++ b/indra/llcommon/llstring.h @@ -29,8 +29,11 @@ #include #include -#include +//#include #include +#include +#include +#include #include "llsd.h" #include "llfasttimer.h" @@ -49,6 +52,7 @@ #endif const char LL_UNKNOWN_CHAR = '?'; +class LLSD; #if LL_DARWIN || LL_LINUX || LL_SOLARIS // Template specialization of char_traits for U16s. Only necessary on Mac and Linux (exists on Windows already) @@ -209,7 +213,7 @@ public: // currently in daylight savings time? static bool getPacificDaylightTime(void) { return sPacificDaylightTime;} - static std::string getDatetimeCode (std::string key); + static std::string getDatetimeCode (const std::string& key); }; /** @@ -298,7 +302,7 @@ public: static bool isValidIndex(const string_type& string, size_type i) { - return !string.empty() && (0 <= i) && (i <= string.size()); + return !string.empty() && (i <= string.size()); } static bool contains(const string_type& string, T c, size_type i=0) @@ -308,6 +312,7 @@ public: static void trimHead(string_type& string); static void trimTail(string_type& string); + static void trimTail(string_type& string, const string_type& tokens); static void trim(string_type& string) { trimHead(string); trimTail(string); } static void truncate(string_type& string, size_type count); @@ -337,6 +342,7 @@ public: static void addCRLF(string_type& string); static void removeCRLF(string_type& string); + static void removeWindowsCR(string_type& string); static void replaceTabsWithSpaces( string_type& string, size_type spaces_per_tab ); static void replaceNonstandardASCII( string_type& string, T replacement ); @@ -445,7 +451,7 @@ public: struct LLDictionaryLess { public: - bool operator()(const std::string& a, const std::string& b) + bool operator()(const std::string& a, const std::string& b) const { return (LLStringUtil::precedesDict(a, b) ? true : false); } @@ -475,6 +481,7 @@ inline std::string chop_tail_copy( * @brief This translates a nybble stored as a hex value from 0-f back * to a nybble in the low order bits of the return byte. */ +LL_COMMON_API bool is_char_hex(char hex); LL_COMMON_API U8 hex_as_nybble(char hex); /** @@ -529,10 +536,13 @@ LL_COMMON_API S32 wchar_to_utf8chars(llwchar inchar, char* outchars); LL_COMMON_API std::string wstring_to_utf8str(const LLWString &utf32str, S32 len); LL_COMMON_API std::string wstring_to_utf8str(const LLWString &utf32str); - LL_COMMON_API std::string utf16str_to_utf8str(const llutf16string &utf16str, S32 len); LL_COMMON_API std::string utf16str_to_utf8str(const llutf16string &utf16str); +#if LL_WINDOWS +inline std::string wstring_to_utf8str(const llutf16string &utf16str) { return utf16str_to_utf8str(utf16str);} +#endif + // Length of this UTF32 string in bytes when transformed to UTF8 LL_COMMON_API S32 wstring_utf8_length(const LLWString& wstr); @@ -548,7 +558,7 @@ LL_COMMON_API S32 utf16str_wstring_length(const llutf16string &utf16str, S32 len LL_COMMON_API S32 wstring_utf16_length(const LLWString & wstr, S32 woffset, S32 wlen); // Length in wstring (i.e., llwchar count) of a part of a wstring specified by utf16 length (i.e., utf16 units.) -LL_COMMON_API S32 wstring_wstring_length_from_utf16_length(const LLWString & wstr, S32 woffset, S32 utf16_length, BOOL *unaligned = NULL); +LL_COMMON_API S32 wstring_wstring_length_from_utf16_length(const LLWString & wstr, S32 woffset, S32 utf16_length, BOOL *unaligned = nullptr); /** * @brief Properly truncate a utf8 string to a maximum byte count. @@ -568,6 +578,17 @@ LL_COMMON_API S32 utf8str_compare_insensitive( const std::string& lhs, const std::string& rhs); +/** +* @brief Properly truncate a utf8 string to a maximum character count. +* +* If symbol_len is longer than the string passed in, the return +* value == utf8str. +* @param utf8str A valid utf8 string to truncate. +* @param symbol_len The maximum number of symbols in the return value. +* @return Returns a valid utf8 string with symbol count <= max_len. +*/ +LL_COMMON_API std::string utf8str_symbol_truncate(const std::string& utf8str, const S32 symbol_len); + /** * @brief Replace all occurences of target_char with replace_char * @@ -810,8 +831,9 @@ public: } /// This implementation uses the answer cached by setiter(). - virtual bool escaped() const { return mIsEsc; } - virtual T next() + bool escaped() const override { return mIsEsc; } + + T next() override { // If we're looking at the escape character of an escape sequence, // skip that character. This is the one time we can modify 'mIter' @@ -827,21 +849,21 @@ public: return result; } - virtual bool is(T ch) const + bool is(T ch) const override { // Like base-class is(), except that an escaped character matches // nothing. return (! done()) && (! mIsEsc) && *mIter == ch; } - virtual bool oneof(const string_type& delims) const + bool oneof(const string_type& delims) const override { // Like base-class oneof(), except that an escaped character matches // nothing. return (! done()) && (! mIsEsc) && LLStringUtilBase::contains(delims, *mIter); } - virtual bool collect_until(string_type& into, const_iterator from, T delim) + bool collect_until(string_type& into, const_iterator from, T delim) override { // Deal with escapes in the characters we collect; that is, an escaped // character must become just that character without the preceding @@ -1155,7 +1177,7 @@ BOOL LLStringUtilBase::precedesDict( const string_type& a, const string_type& { if( a.size() && b.size() ) { - return (LLStringUtilBase::compareDict(a.c_str(), b.c_str()) < 0); + return (LLStringUtilBase::compareDict(a, b) < 0); } else { @@ -1210,7 +1232,7 @@ void LLStringUtilBase::trimHead(string_type& string) template void LLStringUtilBase::trimTail(string_type& string) { - if( string.size() ) + if(!string.empty()) { size_type len = string.length(); size_type i = len; @@ -1223,12 +1245,31 @@ void LLStringUtilBase::trimTail(string_type& string) } } +template +void LLStringUtilBase::trimTail(string_type& string, const string_type& tokens) +{ + if(!string.empty()) + { + size_type len = string.length(); + size_type i = len; + while( i > 0 && (tokens.find_first_of(string[i-1]) != string_type::npos) ) + { + i--; + } + + string.erase( i, len - i ); + } +} + // Replace line feeds with carriage return-line feed pairs. //static template void LLStringUtilBase::addCRLF(string_type& string) { + if (string.empty()) + return; + const T LF = 10; const T CR = 13; @@ -1271,6 +1312,9 @@ void LLStringUtilBase::addCRLF(string_type& string) template void LLStringUtilBase::removeCRLF(string_type& string) { + if (string.empty()) + return; + const T CR = 13; size_type cr_count = 0; @@ -1290,6 +1334,32 @@ void LLStringUtilBase::removeCRLF(string_type& string) //static template +void LLStringUtilBase::removeWindowsCR(string_type& string) +{ + if (string.empty()) + { + return; + } + const T LF = 10; + const T CR = 13; + + size_type cr_count = 0; + size_type len = string.size(); + size_type i; + for( i = 0; i < len - cr_count - 1; i++ ) + { + if( string[i+cr_count] == CR && string[i+cr_count+1] == LF) + { + cr_count++; + } + + string[i] = string[i+cr_count]; + } + string.erase(i, cr_count); +} + +//static +template void LLStringUtilBase::replaceChar( string_type& string, T target, T replacement ) { size_type found_pos = 0; @@ -1373,6 +1443,7 @@ BOOL LLStringUtilBase::containsNonprintable(const string_type& string) return rv; } +// *TODO: reimplement in terms of algorithm //static template void LLStringUtilBase::stripNonprintable(string_type& string) @@ -1383,33 +1454,23 @@ void LLStringUtilBase::stripNonprintable(string_type& string) { return; } - size_t src_size = string.size(); - char* c_string = nullptr; - try - { - c_string = new char[src_size + 1]; - } - catch (const std::bad_alloc&) - { - return; - } - copy(c_string, string.c_str(), src_size+1); - char* write_head = &c_string[0]; + const size_t src_size = string.size(); + auto c_string = std::make_unique(src_size + 1); + + copy(c_string.get(), string.c_str(), src_size+1); for (size_type i = 0; i < src_size; i++) { - char* read_head = &string[i]; - write_head = &c_string[j]; - if(!(*read_head < MIN)) + if(string[i] >= MIN) { - *write_head = *read_head; + c_string[j] = string[i]; ++j; } } c_string[j]= '\0'; - string = c_string; - delete []c_string; + string.assign(c_string.get()); } +// *TODO: reimplement in terms of algorithm template std::basic_string LLStringUtilBase::quote(const string_type& str, const string_type& triggers, @@ -1431,7 +1492,9 @@ std::basic_string LLStringUtilBase::quote(const string_type& str, } // For whatever reason, we must quote this string. + auto needed_escapes = std::count(str.begin(), str.end(), '"'); string_type result; + result.reserve(len + (needed_escapes * escape.length())); result.push_back('"'); for (typename string_type::const_iterator ci(str.begin()), cend(str.end()); ci != cend; ++ci) { diff --git a/indra/llcommon/lluri.cpp b/indra/llcommon/lluri.cpp index b990dfe2a..5f5a67a88 100644 --- a/indra/llcommon/lluri.cpp +++ b/indra/llcommon/lluri.cpp @@ -1,3 +1,5 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check it. +// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com /** * @file lluri.cpp * @author Phoenix @@ -40,7 +42,8 @@ #include #include -void encode_character(std::ostream& ostr, std::string::value_type val) +// static +void LLURI::encodeCharacter(std::ostream& ostr, std::string::value_type val) { ostr << "%" @@ -95,7 +98,7 @@ std::string LLURI::escape( } else { - encode_character(ostr, c); + encodeCharacter(ostr, c); } } } @@ -106,7 +109,7 @@ std::string LLURI::escape( c = *it; if(allowed.find(c) == std::string::npos) { - encode_character(ostr, c); + encodeCharacter(ostr, c); } else { @@ -129,11 +132,30 @@ std::string LLURI::unescape(const std::string& str) { ++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); + + 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 { @@ -169,11 +191,10 @@ namespace { 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 std::string default_allowed = unreserved(); static bool initialized = false; if(!initialized) { @@ -228,7 +249,8 @@ void LLURI::parseAuthorityAndPathUsingOpaque() { if (mScheme == "http" || mScheme == "https" || mScheme == "ftp" || mScheme == "secondlife" || - mScheme == "x-grid-location-info") + mScheme == "x-grid-info" || + mScheme == "x-grid-location-info") // legacy { if (mEscapedOpaque.substr(0,2) != "//") { @@ -401,6 +423,15 @@ LLURI LLURI::buildHTTP(const std::string& prefix, return uri; } +// static +LLURI LLURI::buildHTTP(const std::string& scheme, + const std::string& prefix, + const LLSD& path, + const LLSD& query) +{ + return buildHTTP(llformat("%s://%s", scheme.c_str(), prefix.c_str()), path, query); +} + // static LLURI LLURI::buildHTTP(const std::string& host, const U32& port, @@ -485,6 +516,14 @@ std::string LLURI::hostName() const return unescape(host); } +std::string LLURI::hostNameAndPort() const +{ + std::string user, host, port; + findAuthorityParts(mEscapedAuthority, user, host, port); + return port.empty() ? unescape(host) : unescape(host + ":" + port); +} + + std::string LLURI::userName() const { std::string user, userPass, host, port; diff --git a/indra/llcommon/lluri.h b/indra/llcommon/lluri.h index 57bbedf42..faa5f3a75 100644 --- a/indra/llcommon/lluri.h +++ b/indra/llcommon/lluri.h @@ -4,31 +4,25 @@ * @date 2006-02-05 * @brief Declaration of the URI class. * - * $LicenseInfo:firstyear=2006&license=viewergpl$ - * - * Copyright (c) 2006-2009, Linden Research, Inc. - * + * $LicenseInfo:firstyear=2006&license=viewerlgpl$ * 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 + * Copyright (C) 2010, Linden Research, Inc. * - * 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 + * 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. * - * 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. + * 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. * - * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO - * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, - * COMPLETENESS OR PERFORMANCE. + * 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$ */ @@ -72,6 +66,13 @@ public: const std::string& prefix, const LLSD& path, const LLSD& query); + + static LLURI buildHTTP( + const std::string& scheme, + const std::string& prefix, + const LLSD& path, + const LLSD& query); + ///< prefix is either a full URL prefix of the form /// "http://example.com:8080", or it can be simply a host and /// optional port like "example.com" or "example.com:8080", in @@ -99,20 +100,21 @@ public: std::string scheme() const; ///< ex.: "http", note lack of colon std::string opaque() const; ///< everything after the colon - // for schemes that follow path like syntax (http, https, ftp) - std::string authority() const; // ex.: "host.com:80" - std::string hostName() const; // ex.: "host.com" - std::string userName() const; - std::string password() const; - U16 hostPort() const; // ex.: 80, will include implicit port - BOOL defaultPort() const; // true if port is default for scheme - const std::string& escapedPath() const { return mEscapedPath; } - std::string path() const; // ex.: "/abc/def", includes leading slash - LLSD pathArray() const; // above decoded into an array of strings - std::string query() const; // ex.: "x=34", section after "?" - const std::string& escapedQuery() const { return mEscapedQuery; } - LLSD queryMap() const; // above decoded into a map - static LLSD queryMap(std::string escaped_query_string); + // for schemes that follow path like syntax (http, https, ftp) + std::string authority() const; // ex.: "user:pass@host.com:80" + std::string hostName() const; // ex.: "host.com" + std::string hostNameAndPort() const; // ex.: "host.com:80" + std::string userName() const; + std::string password() const; + U16 hostPort() const; // ex.: 80, will include implicit port + BOOL defaultPort() const; // true if port is default for scheme + const std::string& escapedPath() const { return mEscapedPath; } + std::string path() const; // ex.: "/abc/def", includes leading slash + LLSD pathArray() const; // above decoded into an array of strings + std::string query() const; // ex.: "x=34", section after "?" + const std::string& escapedQuery() const { return mEscapedQuery; } + LLSD queryMap() const; // above decoded into a map + static LLSD queryMap(std::string escaped_query_string); /** * @brief given a name value map, return a serialized query string. @@ -127,27 +129,24 @@ public: /** @name Escaping Utilities */ //@{ /** - * @brief Escape a raw url with a reasonable set of allowed characters. + * @brief 'Escape' symbol into stream * - * The default set was chosen to match HTTP urls and general - * guidelines for naming resources. Passing in a raw url does not - * produce well defined results because you really need to know - * which segments are path parts because path parts are supposed - * to be escaped individually. The default set chosen is: + * @param ostr Output stream. + * @param val Symbol to encode. + */ + static void encodeCharacter(std::ostream& ostr, std::string::value_type val); + + /** + * @brief Escape the string passed except for unreserved * * ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz * 0123456789 * -._~ - * :@!$'()*+,=/?&#; * - * *NOTE: This API is basically broken because it does not - * allow you to specify significant path characters. For example, - * if the filename actually contained a /, then you cannot use - * this function to generate the serialized url for that - * resource. + * @see http://www.ietf.org/rfc/rfc1738.txt * * @param str The raw URI to escape. - * @return Returns the escaped uri or an empty string. + * @return Returns the rfc 1738 escaped uri or an empty string. */ static std::string escape(const std::string& str); diff --git a/indra/llcommon/lluriparser.cpp b/indra/llcommon/lluriparser.cpp new file mode 100644 index 000000000..e32d9f5c0 --- /dev/null +++ b/indra/llcommon/lluriparser.cpp @@ -0,0 +1,266 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check it. +// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com +/** + * @file lluriparser.cpp + * @author Protey + * @date 2014-10-07 + * @brief Implementation of the LLUriParser class. + * + * $LicenseInfo:firstyear=2014&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2014, 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 "lluriparser.h" + +LLUriParser::LLUriParser(const std::string& u) +: mRes(0) +, mTmpScheme(false) +, mNormalizedTmp(false) +{ + mState.uri = &mUri; + + if (u.find("://") == std::string::npos) + { + mNormalizedUri = "http://"; + mTmpScheme = true; + } + + mNormalizedUri += u; + + mRes = parse(); +} + +LLUriParser::~LLUriParser() +{ + uriFreeUriMembersA(&mUri); +} + +S32 LLUriParser::parse() +{ + mRes = uriParseUriA(&mState, mNormalizedUri.c_str()); + return mRes; +} + +const char * LLUriParser::scheme() const +{ + return mScheme.c_str(); +} + +void LLUriParser::scheme(const std::string& s) +{ + mTmpScheme = !s.size(); + mScheme = s; +} + +const char * LLUriParser::port() const +{ + return mPort.c_str(); +} + +void LLUriParser::port(const std::string& s) +{ + mPort = s; +} + +const char * LLUriParser::host() const +{ + return mHost.c_str(); +} + +void LLUriParser::host(const std::string& s) +{ + mHost = s; +} + +const char * LLUriParser::path() const +{ + return mPath.c_str(); +} + +void LLUriParser::path(const std::string& s) +{ + mPath = s; +} + +const char * LLUriParser::query() const +{ + return mQuery.c_str(); +} + +void LLUriParser::query(const std::string& s) +{ + mQuery = s; +} + +const char * LLUriParser::fragment() const +{ + return mFragment.c_str(); +} + +void LLUriParser::fragment(const std::string& s) +{ + mFragment = s; +} + +void LLUriParser::textRangeToString(UriTextRangeA& textRange, std::string& str) +{ + if (textRange.first != nullptr && textRange.afterLast != nullptr && textRange.first < textRange.afterLast) + { + const ptrdiff_t len = textRange.afterLast - textRange.first; + str.assign(textRange.first, static_cast(len)); + } + else + { + str = LLStringUtil::null; + } +} + +void LLUriParser::extractParts() +{ + if (mTmpScheme || mNormalizedTmp) + { + mScheme.clear(); + } + else + { + textRangeToString(mUri.scheme, mScheme); + } + + textRangeToString(mUri.hostText, mHost); + textRangeToString(mUri.portText, mPort); + textRangeToString(mUri.query, mQuery); + textRangeToString(mUri.fragment, mFragment); + + UriPathSegmentA * pathHead = mUri.pathHead; + while (pathHead) + { + std::string partOfPath; + textRangeToString(pathHead->text, partOfPath); + + mPath += '/'; + mPath += partOfPath; + + pathHead = pathHead->next; + } +} + +S32 LLUriParser::normalize() +{ + mNormalizedTmp = mTmpScheme; + if (!mRes) + { + mRes = uriNormalizeSyntaxExA(&mUri, URI_NORMALIZE_SCHEME | URI_NORMALIZE_HOST); + + if (!mRes) + { + S32 chars_required; + mRes = uriToStringCharsRequiredA(&mUri, &chars_required); + + if (!mRes) + { + chars_required++; + std::vector label_buf(chars_required); + mRes = uriToStringA(&label_buf[0], &mUri, chars_required, nullptr); + + if (!mRes) + { + mNormalizedUri = &label_buf[mTmpScheme ? 7 : 0]; + mTmpScheme = false; + } + } + } + } + + if(mTmpScheme) + { + mNormalizedUri = mNormalizedUri.substr(7); + mTmpScheme = false; + } + + return mRes; +} + +void LLUriParser::glue(std::string& uri) const +{ + std::string first_part; + glueFirst(first_part); + + std::string second_part; + glueSecond(second_part); + + uri = first_part + second_part; +} + +void LLUriParser::glueFirst(std::string& uri, bool use_scheme) const +{ + if (use_scheme && mScheme.size()) + { + uri = mScheme; + uri += "://"; + } + else + { + uri.clear(); + } + + uri += mHost; +} + +void LLUriParser::glueSecond(std::string& uri) const +{ + if (mPort.size()) + { + uri = ':'; + uri += mPort; + } + else + { + uri.clear(); + } + + uri += mPath; + + if (mQuery.size()) + { + uri += '?'; + uri += mQuery; + } + + if (mFragment.size()) + { + uri += '#'; + uri += mFragment; + } +} + +bool LLUriParser::test() const +{ + std::string uri; + glue(uri); + + return uri == mNormalizedUri; +} + +const char * LLUriParser::normalizedUri() const +{ + return mNormalizedUri.c_str(); +} diff --git a/indra/llcommon/lluriparser.h b/indra/llcommon/lluriparser.h new file mode 100644 index 000000000..773d4189f --- /dev/null +++ b/indra/llcommon/lluriparser.h @@ -0,0 +1,87 @@ +/** + * @file lluriparser.h + * @author Protey + * @date 20146-10-07 + * @brief Declaration of the UriParser class. + * + * $LicenseInfo:firstyear=2014&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2014, 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$ + */ + +#ifndef LL_LLURIPARSER_H +#define LL_LLURIPARSER_H + +#include +#include + +class LL_COMMON_API LLUriParser +{ +public: + LLUriParser(const std::string& u); + ~LLUriParser(); + + const char * scheme() const; + void scheme (const std::string& s); + + const char * port() const; + void port (const std::string& s); + + const char * host() const; + void host (const std::string& s); + + const char * path() const; + void path (const std::string& s); + + const char * query() const; + void query (const std::string& s); + + const char * fragment() const; + void fragment (const std::string& s); + + const char * normalizedUri() const; + + void extractParts(); + void glue(std::string& uri) const; + void glueFirst(std::string& uri, bool use_scheme = true) const; + void glueSecond(std::string& uri) const; + bool test() const; + S32 normalize(); + +private: + S32 parse(); + void textRangeToString(UriTextRangeA& textRange, std::string& str); + std::string mScheme; + std::string mHost; + std::string mPort; + std::string mPath; + std::string mQuery; + std::string mFragment; + std::string mNormalizedUri; + + UriParserStateA mState; + UriUriA mUri; + + S32 mRes; + bool mTmpScheme; + bool mNormalizedTmp; +}; + +#endif // LL_LLURIPARSER_H diff --git a/indra/llui/lltexteditor.cpp b/indra/llui/lltexteditor.cpp index 7f2327294..f1d99d0e7 100644 --- a/indra/llui/lltexteditor.cpp +++ b/indra/llui/lltexteditor.cpp @@ -4238,6 +4238,23 @@ void LLTextEditor::appendTextImpl(const std::string &new_text, const LLStyleSP s { setLastSegmentToolTip(match.getTooltip()); } + + // show query part of url with gray color only for LLUrlEntryHTTP and LLUrlEntryHTTPNoProtocol url entries + std::string label = match.getQuery(); + if (!label.empty()) + { + /* Singu Note: Upstream uses hardcoded Grey here, they have no care for skins, this could be awful! For now just make it a normal link + link_params.color = LLColor4::grey; + link_params.readonly_color = LLColor4::grey; + appendAndHighlightTextImpl(label, part, link_params, match.underlineOnHoverOnly());*/ + append_link(label); + + // set the tooltip for the query part of url + if (tooltip_required) + { + setLastSegmentToolTip(match.getTooltip()); + } + } } else if (!replace_links) // Still link the link itself { @@ -4804,7 +4821,7 @@ BOOL LLTextEditor::handleMouseUpOverSegment(S32 x, S32 y, MASK mask) if (auto style = segment->getStyle()) { if (style->isLink()) - LLUrlAction::clickAction(style->getLinkHREF()); + LLUrlAction::clickAction(style->getLinkHREF(), true); } } } diff --git a/indra/llui/llurlaction.cpp b/indra/llui/llurlaction.cpp index fd9b3d9a6..74ce67e88 100644 --- a/indra/llui/llurlaction.cpp +++ b/indra/llui/llurlaction.cpp @@ -1,3 +1,5 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check it. +// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com /** * @file llurlaction.cpp * @author Martin Reddy @@ -24,7 +26,6 @@ * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA * $/LicenseInfo$ */ - #include "linden_common.h" #include "llurlaction.h" @@ -32,6 +33,7 @@ #include "llwindow.h" #include "llurlregistry.h" + // global state for the callback functions LLUrlAction::url_callback_t LLUrlAction::sOpenURLCallback; LLUrlAction::url_callback_t LLUrlAction::sOpenURLInternalCallback; @@ -83,18 +85,19 @@ void LLUrlAction::openURLExternal(std::string url) } } -void LLUrlAction::executeSLURL(std::string url) +bool LLUrlAction::executeSLURL(std::string url, bool trusted_content) { if (sExecuteSLURLCallback) { - sExecuteSLURLCallback(url); + return sExecuteSLURLCallback(url, trusted_content); } + return false; } -void LLUrlAction::clickAction(std::string url) +void LLUrlAction::clickAction(std::string url, bool trusted_content) { // Try to handle as SLURL first, then http Url - if ( (sExecuteSLURLCallback) && !sExecuteSLURLCallback(url) ) + if ( (sExecuteSLURLCallback) && !sExecuteSLURLCallback(url, trusted_content) ) { if (sOpenURLCallback) { @@ -157,3 +160,86 @@ void LLUrlAction::showProfile(std::string url) } } } + +std::string LLUrlAction::getUserID(std::string url) +{ + LLURI uri(url); + LLSD path_array = uri.pathArray(); + std::string id_str; + if (path_array.size() == 4) + { + id_str = path_array.get(2).asString(); + } + return id_str; +} + +std::string LLUrlAction::getObjectId(std::string url) +{ + LLURI uri(url); + LLSD path_array = uri.pathArray(); + std::string id_str; + if (path_array.size() >= 3) + { + id_str = path_array.get(2).asString(); + } + return id_str; +} + +std::string LLUrlAction::getObjectName(std::string url) +{ + LLURI uri(url); + LLSD query_map = uri.queryMap(); + std::string name; + if (query_map.has("name")) + { + name = query_map["name"].asString(); + } + return name; +} + +void LLUrlAction::sendIM(std::string url) +{ + std::string id_str = getUserID(url); + if (LLUUID::validate(id_str)) + { + executeSLURL("secondlife:///app/agent/" + id_str + "/im"); + } +} + +void LLUrlAction::addFriend(std::string url) +{ + std::string id_str = getUserID(url); + if (LLUUID::validate(id_str)) + { + executeSLURL("secondlife:///app/agent/" + id_str + "/requestfriend"); + } +} + +void LLUrlAction::removeFriend(std::string url) +{ + std::string id_str = getUserID(url); + if (LLUUID::validate(id_str)) + { + executeSLURL("secondlife:///app/agent/" + id_str + "/removefriend"); + } +} + +void LLUrlAction::blockObject(std::string url) +{ + std::string object_id = getObjectId(url); + std::string object_name = getObjectName(url); + if (LLUUID::validate(object_id)) + { + executeSLURL("secondlife:///app/agent/" + object_id + "/block/" + LLURI::escape(object_name)); + } +} + +void LLUrlAction::unblockObject(std::string url) +{ + std::string object_id = getObjectId(url); + std::string object_name = getObjectName(url); + if (LLUUID::validate(object_id)) + { + executeSLURL("secondlife:///app/agent/" + object_id + "/unblock/" + object_name); + } +} diff --git a/indra/llui/llurlaction.h b/indra/llui/llurlaction.h index 0747829b7..51ab8261b 100644 --- a/indra/llui/llurlaction.h +++ b/indra/llui/llurlaction.h @@ -28,11 +28,7 @@ #ifndef LL_LLURLACTION_H #define LL_LLURLACTION_H -#include -#ifndef BOOST_FUNCTION_HPP_INCLUDED -#include -#define BOOST_FUNCTION_HPP_INCLUDED -#endif +#include /// /// The LLUrlAction class provides a number of static functions that @@ -60,7 +56,7 @@ public: static void openURLExternal(std::string url); /// execute the given secondlife: SLURL - static void executeSLURL(std::string url); + static bool executeSLURL(std::string url, bool trusted_content = true); /// if the Url specifies an SL location, teleport there static void teleportToLocation(std::string url); @@ -69,7 +65,7 @@ public: static void showLocationOnMap(std::string url); /// perform the appropriate action for left-clicking on a Url - static void clickAction(std::string url); + static void clickAction(std::string url, bool trusted_content); /// copy the label for a Url to the clipboard static void copyLabelToClipboard(std::string url); @@ -79,10 +75,18 @@ public: /// if the Url specifies an SL command in the form like 'app/{cmd}/{id}/*', show its profile static void showProfile(std::string url); + static std::string getUserID(std::string url); + static std::string getObjectName(std::string url); + static std::string getObjectId(std::string url); + static void sendIM(std::string url); + static void addFriend(std::string url); + static void removeFriend(std::string url); + static void blockObject(std::string url); + static void unblockObject(std::string url); /// specify the callbacks to enable this class's functionality - typedef boost::function url_callback_t; - typedef boost::function execute_url_callback_t; + typedef std::function url_callback_t; + typedef std::function execute_url_callback_t; static void setOpenURLCallback(url_callback_t cb); static void setOpenURLInternalCallback(url_callback_t cb); diff --git a/indra/llui/llurlentry.cpp b/indra/llui/llurlentry.cpp index 54dc1819c..0282ae626 100644 --- a/indra/llui/llurlentry.cpp +++ b/indra/llui/llurlentry.cpp @@ -1,3 +1,5 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check it. +// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com /** * @file llurlentry.cpp * @author Martin Reddy @@ -31,6 +33,7 @@ #include "lluri.h" #include "llurlmatch.h" #include "llurlregistry.h" +#include "lluriparser.h" #include "llavatarnamecache.h" #include "llcachename.h" @@ -38,14 +41,18 @@ //#include "lluicolortable.h" #include "message.h" -#define APP_HEADER_REGEX "((x-grid-location-info://[-\\w\\.]+/app)|(secondlife:///app))" +#include // + +#define APP_HEADER_REGEX "((((x-grid-info://)|(x-grid-location-info://))[-\\w\\.]+(:\\d+)?/app)|(secondlife:///app))" +#define X_GRID_OR_SECONDLIFE_HEADER_REGEX "((((x-grid-info://)|(x-grid-location-info://))[-\\w\\.]+(:\\d+)?/)|(secondlife://))" // Utility functions std::string localize_slapp_label(const std::string& url, const std::string& full_name); LLUrlEntryBase::LLUrlEntryBase() -{} +{ +} LLUrlEntryBase::~LLUrlEntryBase() { @@ -111,7 +118,7 @@ std::string LLUrlEntryBase::getLabelFromWikiLink(const std::string &url) const { // return the label part from [http://www.example.org Label] const char *text = url.c_str(); - S32 start = 0; + size_t start = 0; while (! isspace(text[start])) { start++; @@ -127,7 +134,7 @@ std::string LLUrlEntryBase::getUrlFromWikiLink(const std::string &string) const { // return the url part from [http://www.example.org Label] const char *text = string.c_str(); - S32 end = 0; + size_t end = 0; while (! isspace(text[end])) { end++; @@ -142,11 +149,14 @@ void LLUrlEntryBase::addObserver(const std::string &id, // add a callback to be notified when we have a label for the uuid LLUrlEntryObserver observer; observer.url = url; - observer.signal = new LLUrlLabelSignal(); - if (observer.signal) + try { + observer.signal = new LLUrlLabelSignal(); observer.signal->connect(cb); - mObservers.insert(std::pair(id, observer)); + mObservers.emplace(id, observer); + } + catch (...) + { } } @@ -178,12 +188,49 @@ bool LLUrlEntryBase::isLinkDisabled() const return globally_disabled; } -static std::string getStringAfterToken(const std::string str, const std::string token) +bool LLUrlEntryBase::isWikiLinkCorrect(const std::string& url) +{ + LLWString label = utf8str_to_wstring(getLabelFromWikiLink(url)); + label.erase(std::remove(label.begin(), label.end(), L'\u200B'), label.end()); + return (LLUrlRegistry::instance().hasUrl(wstring_to_utf8str(label))) ? false : true; +} + +std::string LLUrlEntryBase::urlToLabelWithGreyQuery(const std::string &url) const +{ + LLUriParser up(unescapeUrl(url)); + up.normalize(); + + std::string label; + up.extractParts(); + up.glueFirst(label); + + return label; +} + +std::string LLUrlEntryBase::urlToGreyQuery(const std::string &url) const +{ + LLUriParser up(unescapeUrl(url)); + + std::string label; + up.extractParts(); + up.glueFirst(label, false); + + size_t pos = url.find(label); + if (pos == std::string::npos) + { + return ""; + } + pos += label.size(); + return url.substr(pos); +} + + +static std::string getStringAfterToken(const std::string& str, const std::string& token) { size_t pos = str.find(token); if (pos == std::string::npos) { - return ""; + return std::string(); } pos += token.size(); @@ -194,8 +241,9 @@ static std::string getStringAfterToken(const std::string str, const std::string // LLUrlEntryHTTP Describes generic http: and https: Urls // LLUrlEntryHTTP::LLUrlEntryHTTP() + : LLUrlEntryBase() { - mPattern = boost::regex("https?://([-\\w\\.]+)+(:\\d+)?(:\\w+)?(@\\d+)?(@\\w+)?/?\\S*", + mPattern = boost::regex("https?://([^\\s/?\\.#]+\\.?)+\\.\\w+(:\\d+)?(/\\S*)?", boost::regex::perl|boost::regex::icase); mMenuName = "menu_url_http.xml"; mTooltip = LLTrans::getString("TooltipHttpUrl"); @@ -206,6 +254,20 @@ std::string LLUrlEntryHTTP::getLabel(const std::string &url, const LLUrlLabelCal return unescapeUrl(url); } +std::string LLUrlEntryHTTP::getUrl(const std::string &string) const +{ + if (string.find("://") == std::string::npos) + { + return "http://" + escapeUrl(string); + } + return escapeUrl(string); +} + +std::string LLUrlEntryHTTP::getTooltip(const std::string &url) const +{ + return mTooltip;; +} + // // LLUrlEntryHTTP Describes generic http: and https: Urls with custom label // We use the wikipedia syntax of [http://www.example.org Text] @@ -238,20 +300,22 @@ std::string LLUrlEntryHTTPLabel::getUrl(const std::string &string) const // LLUrlEntryHTTPNoProtocol Describes generic Urls like www.google.com // LLUrlEntryHTTPNoProtocol::LLUrlEntryHTTPNoProtocol() + : LLUrlEntryBase() { - mPattern = boost::regex("(" - "\\bwww\\.\\S+\\.\\S+" // i.e. www.FOO.BAR - "|" // or - "(?]+\\.(?:com|net|edu|org)([/:][^[:space:]<]*)?\\b" // i.e. FOO.net - ")", - boost::regex::perl|boost::regex::icase); + mPattern = boost::regex("\\bwww\\.\\S+\\.([^\\s<]*)?\\b", // i.e. www.FOO.BAR + boost::regex::perl | boost::regex::icase); mMenuName = "menu_url_http.xml"; mTooltip = LLTrans::getString("TooltipHttpUrl"); } std::string LLUrlEntryHTTPNoProtocol::getLabel(const std::string &url, const LLUrlLabelCallback &cb) { - return unescapeUrl(url); + return urlToLabelWithGreyQuery(url); +} + +std::string LLUrlEntryHTTPNoProtocol::getQuery(const std::string &url) const +{ + return urlToGreyQuery(url); } std::string LLUrlEntryHTTPNoProtocol::getUrl(const std::string &string) const @@ -263,6 +327,95 @@ std::string LLUrlEntryHTTPNoProtocol::getUrl(const std::string &string) const return escapeUrl(string); } +std::string LLUrlEntryHTTPNoProtocol::getTooltip(const std::string &url) const +{ + return unescapeUrl(url); +} + +LLUrlEntryInvalidSLURL::LLUrlEntryInvalidSLURL() + : LLUrlEntryBase() +{ + mPattern = boost::regex("(http://(maps.secondlife.com|slurl.com)/secondlife/|secondlife://(/app/(worldmap|teleport)/)?)[^ /]+(/-?[0-9]+){1,3}(/?(\\?title|\\?img|\\?msg)=\\S*)?/?", + boost::regex::perl|boost::regex::icase); + mMenuName = "menu_url_http.xml"; + mTooltip = LLTrans::getString("TooltipHttpUrl"); +} + +std::string LLUrlEntryInvalidSLURL::getLabel(const std::string &url, const LLUrlLabelCallback &cb) +{ + + return escapeUrl(url); +} + +std::string LLUrlEntryInvalidSLURL::getUrl(const std::string &string) const +{ + return escapeUrl(string); +} + +std::string LLUrlEntryInvalidSLURL::getTooltip(const std::string &url) const +{ + return unescapeUrl(url); +} + +bool LLUrlEntryInvalidSLURL::isSLURLvalid(const std::string &url) const +{ + S32 actual_parts; + + if(url.find(".com/secondlife/") != std::string::npos) + { + actual_parts = 5; + } + else if(url.find("/app/") != std::string::npos) + { + actual_parts = 6; + } + else + { + actual_parts = 3; + } + + LLURI uri(url); + LLSD path_array = uri.pathArray(); + S32 path_parts = path_array.size(); + S32 x,y,z; + + if (path_parts == actual_parts) + { + // handle slurl with (X,Y,Z) coordinates + LLStringUtil::convertToS32(path_array[path_parts-3],x); + LLStringUtil::convertToS32(path_array[path_parts-2],y); + LLStringUtil::convertToS32(path_array[path_parts-1],z); + + if((x>= 0 && x<= 256) && (y>= 0 && y<= 256) && (z>= 0)) + { + return TRUE; + } + } + else if (path_parts == (actual_parts-1)) + { + // handle slurl with (X,Y) coordinates + + LLStringUtil::convertToS32(path_array[path_parts-2],x); + LLStringUtil::convertToS32(path_array[path_parts-1],y); + ; + if((x>= 0 && x<= 256) && (y>= 0 && y<= 256)) + { + return TRUE; + } + } + else if (path_parts == (actual_parts-2)) + { + // handle slurl with (X) coordinate + LLStringUtil::convertToS32(path_array[path_parts-1],x); + if(x>= 0 && x<= 256) + { + return TRUE; + } + } + + return FALSE; +} + // // LLUrlEntrySLURL Describes generic http: and https: Urls // @@ -284,6 +437,7 @@ std::string LLUrlEntrySLURL::getLabel(const std::string &url, const LLUrlLabelCa // - http://slurl.com/secondlife/Place/X // - http://slurl.com/secondlife/Place // + LLURI uri(url); LLSD path_array = uri.pathArray(); S32 path_parts = path_array.size(); @@ -335,13 +489,62 @@ std::string LLUrlEntrySLURL::getLocation(const std::string &url) const return url.substr(pos, url.size() - pos); } +// +// LLUrlEntrySeconlifeURL Describes *secondlife.com/ and *lindenlab.com/ urls to substitute icon 'hand.png' before link +// +LLUrlEntrySecondlifeURL::LLUrlEntrySecondlifeURL() +{ + mPattern = boost::regex("\\b(https?://)?([-\\w\\.]*\\.)?(secondlife|lindenlab)\\.com(:\\d{1,5})?(/\\S*)?\\b", + boost::regex::perl|boost::regex::icase); + + mIcon = "Hand"; + mMenuName = "menu_url_http.xml"; + mTooltip = LLTrans::getString("TooltipHttpUrl"); +} + +/// Return the url from a string that matched the regex +std::string LLUrlEntrySecondlifeURL::getUrl(const std::string &string) const +{ + if (string.find("://") == std::string::npos) + { + return "https://" + escapeUrl(string); + } + return escapeUrl(string); +} + +std::string LLUrlEntrySecondlifeURL::getLabel(const std::string &url, const LLUrlLabelCallback &cb) +{ + return urlToLabelWithGreyQuery(url); +} + +std::string LLUrlEntrySecondlifeURL::getQuery(const std::string &url) const +{ + return urlToGreyQuery(url); +} + +std::string LLUrlEntrySecondlifeURL::getTooltip(const std::string &url) const +{ + return url; +} + +// +// LLUrlEntrySimpleSecondlifeURL Describes *secondlife.com and *lindenlab.com urls to substitute icon 'hand.png' before link +// +LLUrlEntrySimpleSecondlifeURL::LLUrlEntrySimpleSecondlifeURL() + { + mPattern = boost::regex("(https?://)?([-\\w\\.]*\\.)?(secondlife|lindenlab)\\.com(?!\\S)", + boost::regex::perl|boost::regex::icase); + + mIcon = "Hand"; + mMenuName = "menu_url_http.xml"; +} + // // LLUrlEntryAgent Describes a Second Life agent Url, e.g., // secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about -// x-grid-location-info://lincoln.lindenlab.com/app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about +// x-grid-info://lincoln.lindenlab.com/app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/about // -LLUrlEntryAgent::LLUrlEntryAgent() : - mAvatarNameCacheConnection() +LLUrlEntryAgent::LLUrlEntryAgent() { mPattern = boost::regex(APP_HEADER_REGEX "/agent/[\\da-f-]+/\\w+", boost::regex::perl|boost::regex::icase); @@ -372,7 +575,15 @@ void LLUrlEntryAgent::callObservers(const std::string &id, void LLUrlEntryAgent::onAvatarNameCache(const LLUUID& id, const LLAvatarName& av_name) { - mAvatarNameCacheConnection.disconnect(); + auto range = mAvatarNameCacheConnections.equal_range(id); + for (auto it = range.first; it != range.second; ++it) + { + if (it->second.connected()) + { + it->second.disconnect(); + } + } + mAvatarNameCacheConnections.erase(range.first, range.second); std::string label = av_name.getCompleteName(); @@ -459,11 +670,8 @@ std::string LLUrlEntryAgent::getLabel(const std::string &url, const LLUrlLabelCa } else { - if (mAvatarNameCacheConnection.connected()) - { - mAvatarNameCacheConnection.disconnect(); - } - mAvatarNameCacheConnection = LLAvatarNameCache::get(agent_id, boost::bind(&LLUrlEntryAgent::onAvatarNameCache, this, _1, _2)); + auto connection = LLAvatarNameCache::get(agent_id, boost::bind(&LLUrlEntryAgent::onAvatarNameCache, this, _1, _2)); + mAvatarNameCacheConnections.emplace(agent_id, connection); addObserver(agent_id_string, url, cb); return LLTrans::getString("LoadingData"); } @@ -522,16 +730,23 @@ std::string LLUrlEntryAgent::getIcon(const std::string &url) // // LLUrlEntryAgentName describes a Second Life agent name Url, e.g., // secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/(completename|displayname|username) -// x-grid-location-info://lincoln.lindenlab.com/app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/(completename|displayname|username) +// x-grid-info://lincoln.lindenlab.com/app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/(completename|displayname|username) // -LLUrlEntryAgentName::LLUrlEntryAgentName() : - mAvatarNameCacheConnection() +LLUrlEntryAgentName::LLUrlEntryAgentName() {} void LLUrlEntryAgentName::onAvatarNameCache(const LLUUID& id, const LLAvatarName& av_name) { - mAvatarNameCacheConnection.disconnect(); + auto range = mAvatarNameCacheConnections.equal_range(id); + for (auto it = range.first; it != range.second; ++it) + { + if (it->second.connected()) + { + it->second.disconnect(); + } + } + mAvatarNameCacheConnections.erase(range.first, range.second); std::string label = getName(av_name); // received the agent name from the server - tell our observers @@ -566,11 +781,8 @@ std::string LLUrlEntryAgentName::getLabel(const std::string &url, const LLUrlLab } else { - if (mAvatarNameCacheConnection.connected()) - { - mAvatarNameCacheConnection.disconnect(); - } - mAvatarNameCacheConnection = LLAvatarNameCache::get(agent_id, boost::bind(&LLUrlEntryAgentName::onAvatarNameCache, this, _1, _2)); + auto connection = LLAvatarNameCache::get(agent_id, boost::bind(&LLUrlEntryAgentName::onAvatarNameCache, this, _1, _2)); + mAvatarNameCacheConnections.emplace(agent_id, connection); addObserver(agent_id_string, url, cb); return LLTrans::getString("LoadingData"); } @@ -585,7 +797,7 @@ std::string LLUrlEntryAgentName::getLabel(const std::string &url, const LLUrlLab // // LLUrlEntryAgentCompleteName describes a Second Life agent complete name Url, e.g., // secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/completename -// x-grid-location-info://lincoln.lindenlab.com/app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/completename +// x-grid-info://lincoln.lindenlab.com/app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/completename // LLUrlEntryAgentCompleteName::LLUrlEntryAgentCompleteName() { @@ -598,10 +810,26 @@ std::string LLUrlEntryAgentCompleteName::getName(const LLAvatarName& avatar_name return avatar_name.getCompleteName(); } +// +// LLUrlEntryAgentLegacyName describes a Second Life agent legacy name Url, e.g., +// secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/legacyname +// x-grid-info://lincoln.lindenlab.com/app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/legacyname +// +LLUrlEntryAgentLegacyName::LLUrlEntryAgentLegacyName() +{ + mPattern = boost::regex(APP_HEADER_REGEX "/agent/[\\da-f-]+/legacyname", + boost::regex::perl|boost::regex::icase); +} + +std::string LLUrlEntryAgentLegacyName::getName(const LLAvatarName& avatar_name) +{ + return avatar_name.getLegacyName(); +} + // // LLUrlEntryAgentDisplayName describes a Second Life agent display name Url, e.g., // secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/displayname -// x-grid-location-info://lincoln.lindenlab.com/app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/displayname +// x-grid-info://lincoln.lindenlab.com/app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/displayname // LLUrlEntryAgentDisplayName::LLUrlEntryAgentDisplayName() { @@ -617,7 +845,7 @@ std::string LLUrlEntryAgentDisplayName::getName(const LLAvatarName& avatar_name) // // LLUrlEntryAgentUserName describes a Second Life agent user name Url, e.g., // secondlife:///app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/username -// x-grid-location-info://lincoln.lindenlab.com/app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/username +// x-grid-info://lincoln.lindenlab.com/app/agent/0e346d8b-4433-4d66-a6b0-fd37083abc4c/username // LLUrlEntryAgentUserName::LLUrlEntryAgentUserName() { @@ -634,7 +862,7 @@ std::string LLUrlEntryAgentUserName::getName(const LLAvatarName& avatar_name) // LLUrlEntryGroup Describes a Second Life group Url, e.g., // secondlife:///app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/about // secondlife:///app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/inspect -// x-grid-location-info://lincoln.lindenlab.com/app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/inspect +// x-grid-info://lincoln.lindenlab.com/app/group/00005ff3-4044-c79f-9de8-fb28ae0df991/inspect // LLUrlEntryGroup::LLUrlEntryGroup() { @@ -714,7 +942,7 @@ LLUrlEntryInventory::LLUrlEntryInventory() //*TODO: add supporting of inventory item names with whitespaces //this pattern cann't parse for example //secondlife:///app/inventory/0e346d8b-4433-4d66-a6b0-fd37083abc4c/select?name=name with spaces¶m2=value - //x-grid-location-info://lincoln.lindenlab.com/app/inventory/0e346d8b-4433-4d66-a6b0-fd37083abc4c/select?name=name with spaces¶m2=value + //x-grid-info://lincoln.lindenlab.com/app/inventory/0e346d8b-4433-4d66-a6b0-fd37083abc4c/select?name=name with spaces¶m2=value mPattern = boost::regex(APP_HEADER_REGEX "/inventory/[\\da-f-]+/\\w+\\S*", boost::regex::perl|boost::regex::icase); mMenuName = "menu_url_inventory.xml"; @@ -732,7 +960,7 @@ std::string LLUrlEntryInventory::getLabel(const std::string &url, const LLUrlLab // LLUrlEntryObjectIM::LLUrlEntryObjectIM() { - mPattern = boost::regex("secondlife:///app/objectim/[\\da-f-]+\?.*", + mPattern = boost::regex(APP_HEADER_REGEX "/objectim/[\\da-f-]+\?\\S*\\w", boost::regex::perl|boost::regex::icase); mMenuName = "menu_url_objectim.xml"; } @@ -765,7 +993,7 @@ std::set LLUrlEntryParcel::sParcelInfoObservers; /// /// LLUrlEntryParcel Describes a Second Life parcel Url, e.g., /// secondlife:///app/parcel/0000060e-4b39-e00b-d0c3-d98b1934e3a8/about -/// x-grid-location-info://lincoln.lindenlab.com/app/parcel/0000060e-4b39-e00b-d0c3-d98b1934e3a8/about +/// x-grid-info://lincoln.lindenlab.com/app/parcel/0000060e-4b39-e00b-d0c3-d98b1934e3a8/about /// LLUrlEntryParcel::LLUrlEntryParcel() { @@ -860,7 +1088,7 @@ void LLUrlEntryParcel::processParcelInfo(const LLParcelData& parcel_data) // LLUrlEntryPlace::LLUrlEntryPlace() { - mPattern = boost::regex("((x-grid-location-info://[-\\w\\.]+/region/)|(secondlife://))\\S+/?(\\d+/\\d+/\\d+|\\d+/\\d+)/?", + mPattern = boost::regex("((((x-grid-info://)|(x-grid-location-info://))[-\\w\\.]+(:\\d+)?/region/)|(secondlife://))\\S+/?(\\d+/\\d+/\\d+|\\d+/\\d+)/?", boost::regex::perl|boost::regex::icase); mMenuName = "menu_url_slurl.xml"; mTooltip = LLTrans::getString("TooltipSLURL"); @@ -968,7 +1196,7 @@ std::string LLUrlEntryRegion::getLocation(const std::string &url) const // // LLUrlEntryTeleport Describes a Second Life teleport Url, e.g., // secondlife:///app/teleport/Ahern/50/50/50/ -// x-grid-location-info://lincoln.lindenlab.com/app/teleport/Ahern/50/50/50/ +// x-grid-info://lincoln.lindenlab.com/app/teleport/Ahern/50/50/50/ // LLUrlEntryTeleport::LLUrlEntryTeleport() { @@ -1042,7 +1270,7 @@ std::string LLUrlEntryTeleport::getLocation(const std::string &url) const // LLUrlEntrySL::LLUrlEntrySL() { - mPattern = boost::regex("secondlife://(\\w+)?(:\\d+)?/\\S+", + mPattern = boost::regex(X_GRID_OR_SECONDLIFE_HEADER_REGEX "(\\w+)?(:\\d+)?/\\S+", boost::regex::perl|boost::regex::icase); mMenuName = "menu_url_slapp.xml"; mTooltip = LLTrans::getString("TooltipSLAPP"); @@ -1059,7 +1287,7 @@ std::string LLUrlEntrySL::getLabel(const std::string &url, const LLUrlLabelCallb // LLUrlEntrySLLabel::LLUrlEntrySLLabel() { - mPattern = boost::regex("\\[secondlife://\\S+[ \t]+[^\\]]+\\]", + mPattern = boost::regex("\\[" X_GRID_OR_SECONDLIFE_HEADER_REGEX "\\S+[ \t]+[^\\]]+\\]", boost::regex::perl|boost::regex::icase); mMenuName = "menu_url_slapp.xml"; mTooltip = LLTrans::getString("TooltipSLAPP"); @@ -1201,3 +1429,75 @@ std::string LLUrlEntryIcon::getIcon(const std::string &url) LLStringUtil::trim(mIcon); return mIcon; } + +// +// LLUrlEntryEmail Describes a generic mailto: Urls +// +LLUrlEntryEmail::LLUrlEntryEmail() + : LLUrlEntryBase() +{ + mPattern = boost::regex("(mailto:)?[\\w\\.\\-]+@[\\w\\.\\-]+\\.[a-z]{2,63}", + boost::regex::perl | boost::regex::icase); + mMenuName = "menu_url_email.xml"; + mTooltip = LLTrans::getString("TooltipEmail"); +} + +std::string LLUrlEntryEmail::getLabel(const std::string &url, const LLUrlLabelCallback &cb) +{ + size_t pos = url.find("mailto:"); + + if (pos == std::string::npos) + { + return escapeUrl(url); + } + + std::string ret = escapeUrl(url.substr(pos + 7, url.length() - pos + 8)); + return ret; +} + +std::string LLUrlEntryEmail::getUrl(const std::string &string) const +{ + if (string.find("mailto:") == std::string::npos) + { + return "mailto:" + escapeUrl(string); + } + return escapeUrl(string); +} + +// +// +// LLUrlEntryJIRA describes a Jira Issue Tracker entry +// +LLUrlEntryJira::LLUrlEntryJira() +{ + mPattern = boost::regex("((?:ALCH|SV|BUG|CHOP|FIRE|MAINT|OPEN|SCR|STORM|SVC|VWR|WEB)-\\d+)", + boost::regex::perl); + mMenuName = "menu_url_http.xml"; + mTooltip = LLTrans::getString("TooltipHttpUrl"); +} + +std::string LLUrlEntryJira::getLabel(const std::string &url, const LLUrlLabelCallback &cb) +{ + return unescapeUrl(url); +} + +std::string LLUrlEntryJira::getTooltip(const std::string &string) const +{ + return getUrl(string); +} + +std::string LLUrlEntryJira::getUrl(const std::string &url) const +{ + return (boost::format( + (url.find("ALCH") != std::string::npos) ? + "http://alchemy.atlassian.net/browse/%1%" : + (url.find("SV") != std::string::npos) ? + "https://singularityviewer.atlassian.net/browse/%1%" : + (url.find("FIRE") != std::string::npos) ? + "http://jira.phoenixviewer.com/browse/%1%" : + "http://jira.secondlife.com/browse/%1%" + ) % url).str(); +} +// + + diff --git a/indra/llui/llurlentry.h b/indra/llui/llurlentry.h index e52fe8040..a772cf8c9 100644 --- a/indra/llui/llurlentry.h +++ b/indra/llui/llurlentry.h @@ -35,10 +35,7 @@ #include "llavatarname.h" #include "llhost.h" // for resolving parcel name by parcel id -#include #include -#include -#include class LLAvatarName; @@ -78,6 +75,9 @@ public: /// Given a matched Url, return a label for the Url virtual std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb) { return url; } + /// Return port, query and fragment parts for the Url + virtual std::string getQuery(const std::string &url) const { return ""; } + /// Return an icon that can be displayed next to Urls of this type virtual std::string getIcon(const std::string &url); @@ -96,10 +96,16 @@ public: /// Should this link text be underlined only when mouse is hovered over it? virtual bool underlineOnHoverOnly(const std::string &string) const { return true; } // + virtual bool isTrusted() const { return false; } + virtual LLUUID getID(const std::string &string) const { return LLUUID::null; } bool isLinkDisabled() const; + bool isWikiLinkCorrect(const std::string& url); + + virtual bool isSLURLvalid(const std::string &url) const { return TRUE; }; + protected: std::string getIDStringFromUrl(const std::string &url) const; std::string escapeUrl(const std::string &url) const; @@ -107,6 +113,8 @@ protected: std::string getLabelFromWikiLink(const std::string &url) const; std::string getUrlFromWikiLink(const std::string &string) const; void addObserver(const std::string &id, const std::string &url, const LLUrlLabelCallback &cb); + std::string urlToLabelWithGreyQuery(const std::string &url) const; + std::string urlToGreyQuery(const std::string &url) const; virtual void callObservers(const std::string &id, const std::string &label, const std::string& icon); typedef struct { @@ -128,7 +136,9 @@ class LLUrlEntryHTTP : public LLUrlEntryBase { public: LLUrlEntryHTTP(); - /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb) override; + /*virtual*/ std::string getUrl(const std::string &string) const override; + /*virtual*/ std::string getTooltip(const std::string &url) const override; }; /// @@ -138,9 +148,9 @@ class LLUrlEntryHTTPLabel : public LLUrlEntryBase { public: LLUrlEntryHTTPLabel(); - /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); - /*virtual*/ std::string getTooltip(const std::string &string) const; - /*virtual*/ std::string getUrl(const std::string &string) const; + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb) override; + /*virtual*/ std::string getTooltip(const std::string &string) const override; + /*virtual*/ std::string getUrl(const std::string &string) const override; }; /// @@ -150,8 +160,21 @@ class LLUrlEntryHTTPNoProtocol : public LLUrlEntryBase { public: LLUrlEntryHTTPNoProtocol(); - /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); - /*virtual*/ std::string getUrl(const std::string &string) const; + std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb) override; + std::string getQuery(const std::string &url) const override; + std::string getUrl(const std::string &string) const override; + std::string getTooltip(const std::string &url) const override; +}; + +class LLUrlEntryInvalidSLURL : public LLUrlEntryBase +{ +public: + LLUrlEntryInvalidSLURL(); + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb) override; + /*virtual*/ std::string getUrl(const std::string &string) const override; + /*virtual*/ std::string getTooltip(const std::string &url) const override; + + bool isSLURLvalid(const std::string &url) const override; }; /// @@ -161,8 +184,31 @@ class LLUrlEntrySLURL : public LLUrlEntryBase { public: LLUrlEntrySLURL(); - /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); - /*virtual*/ std::string getLocation(const std::string &url) const; + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb) override; + /*virtual*/ std::string getLocation(const std::string &url) const override; +}; + +/// +/// LLUrlEntrySeconlifeURLs Describes *secondlife.com and *lindenlab.com Urls +/// +class LLUrlEntrySecondlifeURL : public LLUrlEntryBase +{ +public: + LLUrlEntrySecondlifeURL(); + /*virtual*/ bool isTrusted() const override { return true; } + /*virtual*/ std::string getUrl(const std::string &string) const override; + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb) override; + /*virtual*/ std::string getQuery(const std::string &url) const override; + /*virtual*/ std::string getTooltip(const std::string &url) const override; +}; + +/// +/// LLUrlEntrySeconlifeURLs Describes *secondlife.com and *lindenlab.com Urls +/// +class LLUrlEntrySimpleSecondlifeURL : public LLUrlEntrySecondlifeURL +{ +public: + LLUrlEntrySimpleSecondlifeURL(); }; /// @@ -174,22 +220,27 @@ public: LLUrlEntryAgent(); ~LLUrlEntryAgent() { - if (mAvatarNameCacheConnection.connected()) + for(const auto& conn_pair : mAvatarNameCacheConnections) { - mAvatarNameCacheConnection.disconnect(); + if (conn_pair.second.connected()) + { + conn_pair.second.disconnect(); + } } + mAvatarNameCacheConnections.clear(); } - /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); - /*virtual*/ std::string getIcon(const std::string &url); - /*virtual*/ std::string getTooltip(const std::string &string) const; - //*virtual*/ LLStyle::Params getStyle() const; - /*virtual*/ LLUUID getID(const std::string &string) const; - /*virtual*/ bool underlineOnHoverOnly(const std::string &string) const; + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb) override; + /*virtual*/ std::string getIcon(const std::string &url) override; + /*virtual*/ std::string getTooltip(const std::string &string) const override; + ///*virtual*/ LLStyle::Params getStyle() const override; + /*virtual*/ LLUUID getID(const std::string &string) const override; + /*virtual*/ bool underlineOnHoverOnly(const std::string &string) const override; protected: - /*virtual*/ void callObservers(const std::string &id, const std::string &label, const std::string& icon); + /*virtual*/ void callObservers(const std::string &id, const std::string &label, const std::string& icon) override; private: void onAvatarNameCache(const LLUUID& id, const LLAvatarName& av_name); - boost::signals2::connection mAvatarNameCacheConnection; + using avatar_name_cache_connection_map_t = std::multimap; + avatar_name_cache_connection_map_t mAvatarNameCacheConnections; }; /// @@ -203,19 +254,25 @@ public: LLUrlEntryAgentName(); ~LLUrlEntryAgentName() { - if (mAvatarNameCacheConnection.connected()) + for (const auto& conn_pair : mAvatarNameCacheConnections) { - mAvatarNameCacheConnection.disconnect(); + if (conn_pair.second.connected()) + { + conn_pair.second.disconnect(); + } } + mAvatarNameCacheConnections.clear(); + } - /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); - //*virtual*/ LLStyle::Params getStyle() const; + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb) override; + ///*virtual*/ LLStyle::Params getStyle() const override; protected: // override this to pull out relevant name fields virtual std::string getName(const LLAvatarName& avatar_name) = 0; private: void onAvatarNameCache(const LLUUID& id, const LLAvatarName& av_name); - boost::signals2::connection mAvatarNameCacheConnection; + using avatar_name_cache_connection_map_t = std::multimap; + avatar_name_cache_connection_map_t mAvatarNameCacheConnections; }; @@ -229,7 +286,15 @@ class LLUrlEntryAgentCompleteName : public LLUrlEntryAgentName public: LLUrlEntryAgentCompleteName(); private: - /*virtual*/ std::string getName(const LLAvatarName& avatar_name); + /*virtual*/ std::string getName(const LLAvatarName& avatar_name) override; +}; + +class LLUrlEntryAgentLegacyName : public LLUrlEntryAgentName +{ +public: + LLUrlEntryAgentLegacyName(); +private: + /*virtual*/ std::string getName(const LLAvatarName& avatar_name) override; }; /// @@ -242,7 +307,7 @@ class LLUrlEntryAgentDisplayName : public LLUrlEntryAgentName public: LLUrlEntryAgentDisplayName(); private: - /*virtual*/ std::string getName(const LLAvatarName& avatar_name); + /*virtual*/ std::string getName(const LLAvatarName& avatar_name) override; }; /// @@ -255,7 +320,7 @@ class LLUrlEntryAgentUserName : public LLUrlEntryAgentName public: LLUrlEntryAgentUserName(); private: - /*virtual*/ std::string getName(const LLAvatarName& avatar_name); + /*virtual*/ std::string getName(const LLAvatarName& avatar_name) override; }; /// @@ -266,9 +331,9 @@ class LLUrlEntryGroup : public LLUrlEntryBase { public: LLUrlEntryGroup(); - /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); - //*virtual*/ LLStyle::Params getStyle() const; - /*virtual*/ LLUUID getID(const std::string &string) const; + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb) override; + ///*virtual*/ LLStyle::Params getStyle() const override; + /*virtual*/ LLUUID getID(const std::string &string) const override; private: void onGroupNameReceived(const LLUUID& id, const std::string& name, bool is_group); }; @@ -281,7 +346,7 @@ class LLUrlEntryInventory : public LLUrlEntryBase { public: LLUrlEntryInventory(); - /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb) override; private: }; @@ -293,8 +358,8 @@ class LLUrlEntryObjectIM : public LLUrlEntryBase { public: LLUrlEntryObjectIM(); - /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); - /*virtual*/ std::string getLocation(const std::string &url) const; + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb) override; + /*virtual*/ std::string getLocation(const std::string &url) const override; private: }; @@ -317,7 +382,7 @@ public: LLUrlEntryParcel(); ~LLUrlEntryParcel(); - /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb) override; // Sends a parcel info request to sim. void sendParcelInfoRequest(const LLUUID& parcel_id); @@ -353,8 +418,8 @@ class LLUrlEntryPlace : public LLUrlEntryBase { public: LLUrlEntryPlace(); - /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); - /*virtual*/ std::string getLocation(const std::string &url) const; + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb) override; + /*virtual*/ std::string getLocation(const std::string &url) const override; }; /// @@ -365,8 +430,8 @@ class LLUrlEntryRegion : public LLUrlEntryBase { public: LLUrlEntryRegion(); - /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); - /*virtual*/ std::string getLocation(const std::string &url) const; + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb) override; + /*virtual*/ std::string getLocation(const std::string &url) const override; }; /// @@ -377,8 +442,8 @@ class LLUrlEntryTeleport : public LLUrlEntryBase { public: LLUrlEntryTeleport(); - /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); - /*virtual*/ std::string getLocation(const std::string &url) const; + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb) override; + /*virtual*/ std::string getLocation(const std::string &url) const override; }; /// @@ -389,7 +454,7 @@ class LLUrlEntrySL : public LLUrlEntryBase { public: LLUrlEntrySL(); - /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb) override; }; /// @@ -400,10 +465,10 @@ class LLUrlEntrySLLabel : public LLUrlEntryBase { public: LLUrlEntrySLLabel(); - /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); - /*virtual*/ std::string getUrl(const std::string &string) const; - /*virtual*/ std::string getTooltip(const std::string &string) const; - /*virtual*/ bool underlineOnHoverOnly(const std::string &string) const; + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb) override; + /*virtual*/ std::string getUrl(const std::string &string) const override; + /*virtual*/ std::string getTooltip(const std::string &string) const override; + /*virtual*/ bool underlineOnHoverOnly(const std::string &string) const override; }; /// @@ -414,8 +479,8 @@ class LLUrlEntryWorldMap : public LLUrlEntryBase { public: LLUrlEntryWorldMap(); - /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); - /*virtual*/ std::string getLocation(const std::string &url) const; + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb) override; + /*virtual*/ std::string getLocation(const std::string &url) const override; }; /// @@ -425,9 +490,9 @@ class LLUrlEntryNoLink : public LLUrlEntryBase { public: LLUrlEntryNoLink(); - /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); - /*virtual*/ std::string getUrl(const std::string &string) const; - //*virtual*/ LLStyle::Params getStyle() const; + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb) override; + /*virtual*/ std::string getUrl(const std::string &string) const override; + ///*virtual*/ LLStyle::Params getStyle() const override; }; /// @@ -437,10 +502,35 @@ class LLUrlEntryIcon : public LLUrlEntryBase { public: LLUrlEntryIcon(); - /*virtual*/ std::string getUrl(const std::string &string) const; - /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb); - /*virtual*/ std::string getIcon(const std::string &url); + /*virtual*/ std::string getUrl(const std::string &string) const override; + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb) override; + /*virtual*/ std::string getIcon(const std::string &url) override; }; +/// +/// LLUrlEntryEmail Describes a generic mailto: Urls +/// +class LLUrlEntryEmail : public LLUrlEntryBase +{ +public: + LLUrlEntryEmail(); + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb) override; + /*virtual*/ std::string getUrl(const std::string &string) const override; +}; + +// +/// +/// LLUrlEntryJira describes a Jira Issue +/// +class LLUrlEntryJira : public LLUrlEntryBase +{ +public: + LLUrlEntryJira(); + /*virtual*/ std::string getLabel(const std::string &url, const LLUrlLabelCallback &cb) override; + /*virtual*/ std::string getTooltip(const std::string &string) const override; + /*virtual*/ std::string getUrl(const std::string &string) const override; + +}; +// #endif diff --git a/indra/llui/llurlmatch.cpp b/indra/llui/llurlmatch.cpp index af4955ac4..3d4e808ec 100644 --- a/indra/llui/llurlmatch.cpp +++ b/indra/llui/llurlmatch.cpp @@ -1,3 +1,5 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check it. +// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com /** * @file llurlmatch.cpp * @author Martin Reddy @@ -37,20 +39,22 @@ LLUrlMatch::LLUrlMatch() : mIcon(""), mMenuName(""), mLocation(""), - mUnderlineOnHoverOnly(false) + mUnderlineOnHoverOnly(false), + mTrusted(false) { } -void LLUrlMatch::setValues(U32 start, U32 end, const std::string &url, - const std::string &label, const std::string &tooltip, +void LLUrlMatch::setValues(U32 start, U32 end, const std::string &url, const std::string &label, + const std::string& query, const std::string &tooltip, const std::string &icon, /*const LLStyle::Params& style,*/ const std::string &menu, const std::string &location, - const LLUUID& id, bool underline_on_hover_only) + const LLUUID& id, bool underline_on_hover_only, bool trusted) { mStart = start; mEnd = end; mUrl = url; mLabel = label; + mQuery = query; mTooltip = tooltip; mIcon = icon; //mStyle = style; @@ -59,4 +63,5 @@ void LLUrlMatch::setValues(U32 start, U32 end, const std::string &url, mLocation = location; mID = id; mUnderlineOnHoverOnly = underline_on_hover_only; + mTrusted = trusted; } diff --git a/indra/llui/llurlmatch.h b/indra/llui/llurlmatch.h index af36e6925..67255d3ae 100644 --- a/indra/llui/llurlmatch.h +++ b/indra/llui/llurlmatch.h @@ -30,8 +30,6 @@ //#include "linden_common.h" -#include -#include #include "llstyle.h" /// @@ -62,6 +60,9 @@ public: /// return a label that can be used for the display of this Url std::string getLabel() const { return mLabel; } + /// return a right part of url which should be drawn in grey + std::string getQuery() const { return mQuery; } + /// return a message that could be displayed in a tooltip or status bar std::string getTooltip() const { return mTooltip; } @@ -80,12 +81,15 @@ public: /// Should this link text be underlined only when mouse is hovered over it? bool underlineOnHoverOnly() const { return mUnderlineOnHoverOnly; } + /// Return true if Url is trusted. + bool isTrusted() const { return mTrusted; } + /// Change the contents of this match object (used by LLUrlRegistry) void setValues(U32 start, U32 end, const std::string &url, const std::string &label, - const std::string &tooltip, const std::string &icon, - /*const LLStyle::Params& style, */const std::string &menu, + const std::string& query, const std::string &tooltip, const std::string &icon, + /*const LLStyle::Params& style,*/ const std::string &menu, const std::string &location, const LLUUID& id, - bool underline_on_hover_only = false ); + bool underline_on_hover_only = false, bool trusted = false); const LLUUID& getID() const { return mID; } private: @@ -93,6 +97,7 @@ private: U32 mEnd; std::string mUrl; std::string mLabel; + std::string mQuery; std::string mTooltip; std::string mIcon; std::string mMenuName; @@ -100,6 +105,7 @@ private: LLUUID mID; //LLStyle::Params mStyle; bool mUnderlineOnHoverOnly; + bool mTrusted; }; #endif diff --git a/indra/llui/llurlregistry.cpp b/indra/llui/llurlregistry.cpp index 235b002d0..92d85ff91 100644 --- a/indra/llui/llurlregistry.cpp +++ b/indra/llui/llurlregistry.cpp @@ -1,3 +1,5 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check it. +// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com /** * @file llurlregistry.cpp * @author Martin Reddy @@ -27,6 +29,7 @@ #include "linden_common.h" #include "llurlregistry.h" +#include "lluriparser.h" #include @@ -37,15 +40,26 @@ void LLUrlRegistryNullCallback(const std::string &url, const std::string &label, LLUrlRegistry::LLUrlRegistry() { - mUrlEntry.reserve(20); + mUrlEntry.reserve(23); // // Urls are matched in the order that they were registered registerUrl(new LLUrlEntryNoLink()); - registerUrl(new LLUrlEntryIcon()); + mUrlEntryIcon = new LLUrlEntryIcon(); + registerUrl(mUrlEntryIcon); + mLLUrlEntryInvalidSLURL = new LLUrlEntryInvalidSLURL(); + registerUrl(mLLUrlEntryInvalidSLURL); registerUrl(new LLUrlEntrySLURL()); + + // decorated links for host names like: secondlife.com and lindenlab.com + mUrlEntryTrusted = new LLUrlEntrySecondlifeURL(); + registerUrl(mUrlEntryTrusted); + registerUrl(new LLUrlEntrySimpleSecondlifeURL()); + registerUrl(new LLUrlEntryHTTP()); - registerUrl(new LLUrlEntryHTTPLabel()); + mUrlEntryHTTPLabel = new LLUrlEntryHTTPLabel(); + registerUrl(mUrlEntryHTTPLabel); registerUrl(new LLUrlEntryAgentCompleteName()); + registerUrl(new LLUrlEntryAgentLegacyName()); registerUrl(new LLUrlEntryAgentDisplayName()); registerUrl(new LLUrlEntryAgentUserName()); // LLUrlEntryAgent*Name must appear before LLUrlEntryAgent since @@ -59,14 +73,17 @@ LLUrlRegistry::LLUrlRegistry() registerUrl(new LLUrlEntryObjectIM()); registerUrl(new LLUrlEntryPlace()); registerUrl(new LLUrlEntryInventory()); - registerUrl(new LLUrlEntryObjectIM()); //LLUrlEntrySL and LLUrlEntrySLLabel have more common pattern, //so it should be registered in the end of list registerUrl(new LLUrlEntrySL()); - registerUrl(new LLUrlEntrySLLabel()); - // most common pattern is a URL without any protocol, - // e.g., "secondlife.com" - registerUrl(new LLUrlEntryHTTPNoProtocol()); + mUrlEntrySLLabel = new LLUrlEntrySLLabel(); + registerUrl(mUrlEntrySLLabel); + registerUrl(new LLUrlEntryEmail()); + // Parse teh jiras! + registerUrl(new LLUrlEntryJira()); // + // most common pattern is a URL without any protocol starting with "www", + // e.g., "www.secondlife.com" + registerUrl(new LLUrlEntryHTTPNoProtocol()); } LLUrlRegistry::~LLUrlRegistry() @@ -90,7 +107,7 @@ void LLUrlRegistry::registerUrl(LLUrlEntryBase *url, bool force_front) } } -static bool matchRegex(const char *text, boost::regex regex, U32 &start, U32 &end) +static bool matchRegex(const char *text, const boost::regex& regex, U32 &start, U32 &end) { boost::cmatch result; bool found; @@ -100,7 +117,7 @@ static bool matchRegex(const char *text, boost::regex regex, U32 &start, U32 &en { found = boost::regex_search(text, result, regex); } - catch (std::runtime_error &) + catch (const std::runtime_error &) { return false; } @@ -127,6 +144,11 @@ static bool matchRegex(const char *text, boost::regex regex, U32 &start, U32 &en end--; } + else if (text[end] == ']' && std::string(text+start, end-start).find('[') == std::string::npos) + { + end--; + } + return true; } @@ -142,32 +164,77 @@ static bool stringHasUrl(const std::string &text) text.find(".edu") != std::string::npos || text.find(".org") != std::string::npos || text.find("") != std::string::npos || - text.find(" +static bool stringHasJira(const std::string &text) +{ + // fast heuristic test for a URL in a string. This is used + // to avoid lots of costly regex calls, BUT it needs to be + // kept in sync with the LLUrlEntry regexes we support. + return (text.find("ALCH") != std::string::npos || + text.find("SV") != std::string::npos || + text.find("BUG") != std::string::npos || + text.find("CHOP") != std::string::npos || + text.find("FIRE") != std::string::npos || + text.find("MAINT") != std::string::npos || + text.find("OPEN") != std::string::npos || + text.find("SCR") != std::string::npos || + text.find("STORM") != std::string::npos || + text.find("SVC") != std::string::npos || + text.find("VWR") != std::string::npos || + text.find("WEB") != std::string::npos); +} +// + +bool LLUrlRegistry::findUrl(const std::string &text, LLUrlMatch &match, const LLUrlLabelCallback &cb, bool is_content_trusted) { // avoid costly regexes if there is clearly no URL in the text - if (! stringHasUrl(text)) + if (!(stringHasUrl(text) || stringHasJira(text))) // { return false; } // find the first matching regex from all url entries in the registry U32 match_start = 0, match_end = 0; - LLUrlEntryBase *match_entry = NULL; + LLUrlEntryBase *match_entry = nullptr; std::vector::iterator it; for (it = mUrlEntry.begin(); it != mUrlEntry.end(); ++it) { + //Skip for url entry icon if content is not trusted + if(!is_content_trusted && (mUrlEntryIcon == *it)) + { + continue; + } + LLUrlEntryBase *url_entry = *it; U32 start = 0, end = 0; if (matchRegex(text.c_str(), url_entry->getPattern(), start, end)) { // does this match occur in the string before any other match - if (start < match_start || match_entry == NULL) + if (start < match_start || match_entry == nullptr) { + + if (mLLUrlEntryInvalidSLURL == *it) + { + if(url_entry && url_entry->isSLURLvalid(text.substr(start, end - start + 1))) + { + continue; + } + } + + if((mUrlEntryHTTPLabel == *it) || (mUrlEntrySLLabel == *it)) + { + if(url_entry && !url_entry->isWikiLinkCorrect(text.substr(start, end - start + 1))) + { + continue; + } + } + match_start = start; match_end = end; match_entry = url_entry; @@ -178,18 +245,32 @@ bool LLUrlRegistry::findUrl(const std::string &text, LLUrlMatch &match, const LL // did we find a match? if so, return its details in the match object if (match_entry) { + // Skip if link is an email with an empty username (starting with @). See MAINT-5371. + if (match_start > 0 && text.substr(match_start - 1, 1) == "@") + return false; + // fill in the LLUrlMatch object and return it std::string url = text.substr(match_start, match_end - match_start + 1); + + if (match_entry == mUrlEntryTrusted) + { + LLUriParser up(url); + up.normalize(); + url = up.normalizedUri(); + } + match.setValues(match_start, match_end, match_entry->getUrl(url), match_entry->getLabel(url, cb), + match_entry->getQuery(url), match_entry->getTooltip(url), match_entry->getIcon(url), //match_entry->getStyle(), match_entry->getMenuName(), match_entry->getLocation(url), match_entry->getID(url), - match_entry->underlineOnHoverOnly(url)); + match_entry->underlineOnHoverOnly(url), + match_entry->isTrusted()); return true; } @@ -209,7 +290,7 @@ bool LLUrlRegistry::findUrl(const LLWString &text, LLUrlMatch &match, const LLUr // character encoding, so we need to update the start // and end values to be correct for the wide string. LLWString wurl = utf8str_to_wstring(match.getUrl()); - S32 start = text.find(wurl); + size_t start = text.find(wurl); if (start == std::string::npos) { return false; @@ -218,6 +299,7 @@ bool LLUrlRegistry::findUrl(const LLWString &text, LLUrlMatch &match, const LLUr match.setValues(start, end, match.getUrl(), match.getLabel(), + match.getQuery(), match.getTooltip(), match.getIcon(), //match.getStyle(), diff --git a/indra/llui/llurlregistry.h b/indra/llui/llurlregistry.h index da16171a9..90099c6d1 100644 --- a/indra/llui/llurlregistry.h +++ b/indra/llui/llurlregistry.h @@ -33,9 +33,6 @@ #include "llsingleton.h" #include "llstring.h" -#include -#include - /// This default callback for findUrl() simply ignores any label updates void LLUrlRegistryNullCallback(const std::string &url, const std::string &label, @@ -73,7 +70,8 @@ public: /// get the next Url in an input string, starting at a given character offset /// your callback is invoked if the matched Url's label changes in the future bool findUrl(const std::string &text, LLUrlMatch &match, - const LLUrlLabelCallback &cb = &LLUrlRegistryNullCallback); + const LLUrlLabelCallback &cb = &LLUrlRegistryNullCallback, + bool is_content_trusted = false); /// a slightly less efficient version of findUrl for wide strings bool findUrl(const LLWString &text, LLUrlMatch &match, @@ -92,6 +90,11 @@ private: friend class LLSingleton; std::vector mUrlEntry; + LLUrlEntryBase* mUrlEntryTrusted; + LLUrlEntryBase* mUrlEntryIcon; + LLUrlEntryBase* mLLUrlEntryInvalidSLURL; + LLUrlEntryBase* mUrlEntryHTTPLabel; + LLUrlEntryBase* mUrlEntrySLLabel; }; #endif diff --git a/indra/llwindow/llwindow.cpp b/indra/llwindow/llwindow.cpp index 762155d8f..f64b48667 100644 --- a/indra/llwindow/llwindow.cpp +++ b/indra/llwindow/llwindow.cpp @@ -49,8 +49,8 @@ LLSplashScreen *gSplashScreenp = NULL; BOOL gDebugClicks = FALSE; BOOL gDebugWindowProc = FALSE; -const S32 gURLProtocolWhitelistCount = 4; -const std::string gURLProtocolWhitelist[] = { "secondlife:", "http:", "https:", "data:"/*, "file:"*/ }; +const S32 gURLProtocolWhitelistCount = 5; +const std::string gURLProtocolWhitelist[] = { "secondlife:", "http:", "https:", "data:", "mailto:" }; // CP: added a handler list - this is what's used to open the protocol and is based on registry entry // only meaningful difference currently is that file: protocols are opened using http: diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index d49eb8a29..258d44e95 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -43,6 +43,7 @@ include(UI) include(ViewerMiscLibs) include(WinManifest) include(ZLIB) +include(URIPARSER) if (MSVC) use_prebuilt_binary(vcredist) @@ -1635,6 +1636,7 @@ target_link_libraries(${VIEWER_BINARY_NAME} ${LLCOMMON_LIBRARIES} ${NDOF_LIBRARY} ${NVAPI_LIBRARY} + ${URIPARSER_LIBRARY} ${viewer_LIBRARIES} ${Boost_CONTEXT_LIBRARY} ${Boost_FILESYSTEM_LIBRARY} diff --git a/indra/newview/chatbar_as_cmdline.cpp b/indra/newview/chatbar_as_cmdline.cpp index 7021ace43..ee069ecbd 100644 --- a/indra/newview/chatbar_as_cmdline.cpp +++ b/indra/newview/chatbar_as_cmdline.cpp @@ -535,7 +535,7 @@ bool cmd_line_chat(std::string data, EChatType type) LLAvatarActions::showProfile(id); return false; } - LLUrlAction::clickAction(sub); + LLUrlAction::clickAction(sub, true); } return false; } diff --git a/indra/newview/installers/windows/installer_template.nsi b/indra/newview/installers/windows/installer_template.nsi index f2730839b..f6b27eb7a 100644 --- a/indra/newview/installers/windows/installer_template.nsi +++ b/indra/newview/installers/windows/installer_template.nsi @@ -507,14 +507,40 @@ Section "Viewer" WriteRegStr HKEY_CLASSES_ROOT "${URLNAME}\DefaultIcon" "" "$INSTDIR\$INSTEXE" ;; URL param must be last item passed to viewer, it ignores subsequent params ;; to avoid parameter injection attacks. + WriteRegStr HKEY_CLASSES_ROOT "${URLNAME}\shell" "" "open" +!ifdef WIN64_BIN_BUILD + WriteRegStr HKEY_CLASSES_ROOT "${URLNAME}\shell\open" "FriendlyAppName" "$INSTSHORTCUT (64 bit) Viewer" +!else + WriteRegStr HKEY_CLASSES_ROOT "${URLNAME}\shell\open" "FriendlyAppName" "$INSTSHORTCUT Viewer" +!endif WriteRegExpandStr HKEY_CLASSES_ROOT "${URLNAME}\shell\open\command" "" "$\"$INSTDIR\$INSTEXE$\" -url $\"%1$\"" + DeleteRegKey HKEY_CLASSES_ROOT "x-grid-info" + WriteRegStr HKEY_CLASSES_ROOT "x-grid-info" "" "URL:Hypergrid" + WriteRegStr HKEY_CLASSES_ROOT "x-grid-info" "URL Protocol" "" + WriteRegStr HKEY_CLASSES_ROOT "x-grid-info\DefaultIcon" "" "$INSTDIR\$INSTEXE" + ;; URL param must be last item passed to viewer, it ignores subsequent params + ;; to avoid parameter injection attacks. + WriteRegStr HKEY_CLASSES_ROOT "x-grid-info\shell" "" "open" +!ifdef WIN64_BIN_BUILD + WriteRegStr HKEY_CLASSES_ROOT "x-grid-info\shell\open" "FriendlyAppName" "$INSTSHORTCUT (64 bit) Viewer" +!else + WriteRegStr HKEY_CLASSES_ROOT "x-grid-info\shell\open" "FriendlyAppName" "$INSTSHORTCUT Viewer" +!endif + WriteRegExpandStr HKEY_CLASSES_ROOT "x-grid-info\shell\open\command" "" "$\"$INSTDIR\$INSTEXE$\" -url $\"%1$\"" + DeleteRegKey HKEY_CLASSES_ROOT "x-grid-location-info" WriteRegStr HKEY_CLASSES_ROOT "x-grid-location-info" "" "URL:Hypergrid legacy" WriteRegStr HKEY_CLASSES_ROOT "x-grid-location-info" "URL Protocol" "" WriteRegStr HKEY_CLASSES_ROOT "x-grid-location-info\DefaultIcon" "" "$\"$INSTDIR\$INSTEXE$\"" ;; URL param must be last item passed to viewer, it ignores subsequent params ;; to avoid parameter injection attacks. + WriteRegStr HKEY_CLASSES_ROOT "x-grid-location-info\shell" "" "open" +!ifdef WIN64_BIN_BUILD + WriteRegStr HKEY_CLASSES_ROOT "x-grid-location-info\shell\open" "FriendlyAppName" "$INSTSHORTCUT (64 bit) Viewer" +!else + WriteRegStr HKEY_CLASSES_ROOT "x-grid-location-info\shell\open" "FriendlyAppName" "$INSTSHORTCUT Viewer" +!endif WriteRegExpandStr HKEY_CLASSES_ROOT "x-grid-location-info\shell\open\command" "" "$\"$INSTDIR\$INSTEXE$\" -url $\"%1$\"" ;Create uninstaller diff --git a/indra/newview/llfloaterobjectiminfo.cpp b/indra/newview/llfloaterobjectiminfo.cpp index e1a0806f8..3610cab14 100644 --- a/indra/newview/llfloaterobjectiminfo.cpp +++ b/indra/newview/llfloaterobjectiminfo.cpp @@ -79,7 +79,7 @@ BOOL LLFloaterObjectIMInfo::postBuild() { getChild("Mute")->setCommitCallback(boost::bind(&LLFloaterObjectIMInfo::onClickMute, this)); getChild("OwnerName")->setClickedCallback(boost::bind(boost::ref(mGroupOwned) ? boost::bind(LLGroupActions::show, boost::ref(mOwnerID)) : boost::bind(show_avatar_profile, boost::ref(mOwnerID)))); - getChild("Slurl")->setClickedCallback(boost::bind(LLUrlAction::executeSLURL, boost::bind(std::plus(), "secondlife:///app/worldmap/", boost::ref(mSLurl)))); + getChild("Slurl")->setClickedCallback(boost::bind(LLUrlAction::executeSLURL, boost::bind(std::plus(), "secondlife:///app/worldmap/", boost::ref(mSLurl)), true)); return true; } diff --git a/indra/newview/llslurl.cpp b/indra/newview/llslurl.cpp index d22178377..e4523889a 100644 --- a/indra/newview/llslurl.cpp +++ b/indra/newview/llslurl.cpp @@ -1,5 +1,7 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check it. +// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com /** - * @file llurlsimstring.cpp (was llsimurlstring.cpp) + * @file llslurl.cpp (was llsimurlstring.cpp) * @brief Handles "SLURL fragments" like Ahern/123/45 for * startup processing, login screen, prefs, etc. * @@ -48,7 +50,8 @@ const char* LLSLURL::SLURL_COM = "slurl.com"; 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_X_GRID_INFO_SCHEME = "x-grid-info"; +const char* LLSLURL::SLURL_X_GRID_LOCATION_INFO_SCHEME = "x-grid-location-info"; // <- deprecated! const char* LLSLURL::SLURL_APP_PATH = "app"; const char* LLSLURL::SLURL_REGION_PATH = "region"; const char* LLSLURL::SIM_LOCATION_HOME = "home"; @@ -214,7 +217,8 @@ LLSLURL::LLSLURL(const std::string& slurl) } 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)) + (slurl_uri.scheme() == LLSLURL::SLURL_X_GRID_INFO_SCHEME) || + (slurl_uri.scheme() == LLSLURL::SLURL_X_GRID_LOCATION_INFO_SCHEME)) // deprecated legacy { // We're dealing with either a Standalone style slurl or slurl.com slurl if ((slurl_uri.hostName() == LLSLURL::SLURL_COM) || @@ -239,7 +243,7 @@ LLSLURL::LLSLURL(const std::string& slurl) // 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(); + mGrid = slurl_uri.hostNameAndPort(); } if (path_array.size() == 0) { @@ -266,7 +270,7 @@ LLSLURL::LLSLURL(const std::string& slurl) } else { - // not a valid https/http/x-grid-location-info slurl, so it'll likely just be a URL + // not a valid https/http/x-grid-info slurl, so it'll likely just be a URL return; } } @@ -304,7 +308,14 @@ LLSLURL::LLSLURL(const std::string& slurl) // at this point, head of the path array should be [ , , , ] 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 @@ -313,11 +324,11 @@ LLSLURL::LLSLURL(const std::string& slurl) mPosition = LLVector3(path_array); // this construction handles LLSD without all components (values default to 0.f) if((F32(mPosition[VX]) < 0.f) || - (mPosition[VX] > 8192.f/*REGION_WIDTH_METERS*/) || + (mPosition[VX] > 8192.f) || (F32(mPosition[VY]) < 0.f) || - (mPosition[VY] > 8192.f/*REGION_WIDTH_METERS*/) || + (mPosition[VY] > 8192.f) || (F32(mPosition[VZ]) < 0.f) || - (mPosition[VZ] > 8192.f/*REGION_HEIGHT_METERS*/)) + (mPosition[VZ] > 8192.f)) { mType = INVALID; return; @@ -444,9 +455,9 @@ std::string LLSLURL::getSLURLString() const app_url << SYSTEM_GRID_APP_SLURL_BASE << "/" << mAppCmd; else app_url << llformat(DEFAULT_APP_SLURL_BASE, gHippoGridManager->getCurrentGridNick().c_str()) << "/" << mAppCmd; - for(LLSD::array_const_iterator i = mAppPath.beginArray(); + for(auto i = mAppPath.beginArray(); i != mAppPath.endArray(); - i++) + ++i) { app_url << "/" << i->asString(); } diff --git a/indra/newview/llslurl.h b/indra/newview/llslurl.h index b86cf7949..3beff26c7 100644 --- a/indra/newview/llslurl.h +++ b/indra/newview/llslurl.h @@ -44,6 +44,7 @@ public: static const char* WWW_SLURL_COM; static const char* SECONDLIFE_COM; static const char* MAPS_SECONDLIFE_COM; + static const char* SLURL_X_GRID_INFO_SCHEME; static const char* SLURL_X_GRID_LOCATION_INFO_SCHEME; static LLSLURL START_LOCATION; static const char* SIM_LOCATION_HOME; diff --git a/indra/newview/llurldispatcher.cpp b/indra/newview/llurldispatcher.cpp index b9535d36a..eb976171d 100644 --- a/indra/newview/llurldispatcher.cpp +++ b/indra/newview/llurldispatcher.cpp @@ -144,7 +144,7 @@ bool LLURLDispatcherImpl::dispatch(const LLSLURL& slurl, bool LLURLDispatcherImpl::dispatchRightClick(const LLSLURL& slurl) { const bool right_click = true; - LLMediaCtrl* web = NULL; + LLMediaCtrl* web = nullptr; const bool trusted_browser = false; return dispatchCore(slurl, "clicked", right_click, web, trusted_browser); } @@ -187,11 +187,18 @@ bool LLURLDispatcherImpl::dispatchRegion(const LLSLURL& slurl, const std::string LLPanelLogin::setLocation(slurl); return true; } + LLSLURL _slurl = slurl; + const std::string& grid = slurl.getGrid(); + const std::string& current_grid = gHippoGridManager->getCurrentGrid()->getGridName(); + if (grid != current_grid) + { + _slurl = LLSLURL(llformat("%s:%s", grid.c_str(), slurl.getRegion().c_str()), slurl.getPosition()); + } // Request a region handle by name LLWorldMapMessage::getInstance()->sendNamedRegionRequest(slurl.getRegion(), LLURLDispatcherImpl::regionNameCallback, - slurl.getSLURLString(), + _slurl.getSLURLString(), LLUI::sConfigGroup->getBOOL("SLURLTeleportDirectly")); // don't teleport return true; } @@ -263,7 +270,8 @@ void LLURLDispatcherImpl::regionHandleCallback(U64 region_handle, const LLSLURL& //--------------------------------------------------------------------------- // Teleportation links are handled here because they are tightly coupled -// to URL parsing and sim-fragment parsing +// to SLURL parsing and sim-fragment parsing + class LLTeleportHandler : public LLCommandHandler { public: @@ -272,8 +280,9 @@ public: // cause a constant teleport loop. JC LLTeleportHandler() : LLCommandHandler("teleport", UNTRUSTED_THROTTLE) { } + bool handle(const LLSD& tokens, const LLSD& query_map, - LLMediaCtrl* web) + LLMediaCtrl* web) override { // construct a "normal" SLURL, resolve the region to // a global position, and teleport to it @@ -287,12 +296,11 @@ public: tokens[3].asReal()); } - LLSD args; - args["LOCATION"] = tokens[0]; - // Region names may be %20 escaped. std::string region_name = LLURI::unescape(tokens[0]); + LLSD args; + args["LOCATION"] = region_name; LLSD payload; payload["region_name"] = region_name; @@ -351,7 +359,7 @@ bool LLURLDispatcher::dispatchRightClick(const std::string& slurl) } // static -bool LLURLDispatcher::dispatchFromTextEditor(const std::string& slurl) +bool LLURLDispatcher::dispatchFromTextEditor(const std::string& slurl, bool trusted_content) { // *NOTE: Text editors are considered sources of trusted URLs // in order to make avatar profile links in chat history work. @@ -359,9 +367,9 @@ bool LLURLDispatcher::dispatchFromTextEditor(const std::string& slurl) // receiving resident will see it and must affirmatively // click on it. // *TODO: Make this trust model more refined. JC - const bool trusted_browser = true; - LLMediaCtrl* web = NULL; - return LLURLDispatcherImpl::dispatch(LLSLURL(slurl), "clicked", web, trusted_browser); + + LLMediaCtrl* web = nullptr; + return LLURLDispatcherImpl::dispatch(LLSLURL(slurl), "clicked", web, trusted_content); } diff --git a/indra/newview/llurldispatcher.h b/indra/newview/llurldispatcher.h index 82e1d3152..9b05260af 100644 --- a/indra/newview/llurldispatcher.h +++ b/indra/newview/llurldispatcher.h @@ -2,36 +2,29 @@ * @file llurldispatcher.h * @brief Central registry for all SL URL handlers * - * $LicenseInfo:firstyear=2007&license=viewergpl$ - * - * Copyright (c) 2007-2009, Linden Research, Inc. - * + * $LicenseInfo:firstyear=2010&license=viewerlgpl$ * 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 + * Copyright (C) 2010, Linden Research, Inc. * - * 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 + * 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. * - * 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. + * 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. * - * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO - * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY, - * COMPLETENESS OR PERFORMANCE. + * 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$ */ #ifndef LLURLDISPATCHER_H #define LLURLDISPATCHER_H - class LLMediaCtrl; @@ -49,6 +42,8 @@ public: // secondlife://RegionName/123/45/67/ // secondlife:///app/agent/3d6181b0-6a4b-97ef-18d8-722652995cf1/show // sl://app/foo/bar + // @param nav_type + // type of navigation type (see LLQtWebKit::LLWebPage::acceptNavigationRequest) // @param web // Pointer to LLMediaCtrl sending URL, can be NULL // @param trusted_browser @@ -58,7 +53,7 @@ public: static bool dispatchRightClick(const std::string& slurl); - static bool dispatchFromTextEditor(const std::string& slurl); + static bool dispatchFromTextEditor(const std::string& slurl, bool trusted_content); }; #endif diff --git a/indra/newview/skins/default/xui/de/strings.xml b/indra/newview/skins/default/xui/de/strings.xml index a3e2c9f63..76d75d7c4 100644 --- a/indra/newview/skins/default/xui/de/strings.xml +++ b/indra/newview/skins/default/xui/de/strings.xml @@ -125,6 +125,9 @@ Klicken, um diese Position auf der Karte anzuzeigen Anklicken, um Befehl secondlife:// auszuführen + + Klicken, um eine E-Mail zu verfassen + Teleportieren zu diff --git a/indra/newview/skins/default/xui/en-us/strings.xml b/indra/newview/skins/default/xui/en-us/strings.xml index bc261fdcb..66e72db88 100644 --- a/indra/newview/skins/default/xui/en-us/strings.xml +++ b/indra/newview/skins/default/xui/en-us/strings.xml @@ -141,6 +141,7 @@ Make sure you entered the correct Login URI. An example of a Login URI is: \"htt Click to view this location on a map Click to run the secondlife:// command + Click to compose an email Teleport to diff --git a/indra/newview/skins/default/xui/es/strings.xml b/indra/newview/skins/default/xui/es/strings.xml index 03b29be1e..ac9282a11 100644 --- a/indra/newview/skins/default/xui/es/strings.xml +++ b/indra/newview/skins/default/xui/es/strings.xml @@ -133,6 +133,9 @@ Asegurate que has ingresado el URI de inicio de sesión correcto. Un ejemplo de Pulsa para ver esta ubicación en el mapa Pulsa para ejecutar el comando secondlife:// + + Haz clic para redactar un correo electrónico + Teleportar a diff --git a/indra/newview/skins/default/xui/pt/strings.xml b/indra/newview/skins/default/xui/pt/strings.xml index 143c4038b..ee9dc7f60 100644 --- a/indra/newview/skins/default/xui/pt/strings.xml +++ b/indra/newview/skins/default/xui/pt/strings.xml @@ -403,6 +403,9 @@ Pessoas com contas gratuitas não poderão acessar o Second Life no momento para Clique para ativar no secondlife:// comando + + Clique para escrever um email + Teletransportar para @@ -427,6 +430,9 @@ Pessoas com contas gratuitas não poderão acessar o Second Life no momento para Pedido de amizade + + Remoção de amigo + Fechar (⌘W)