From f19735beff8af726b35c2bae37e9da352e832a48 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Fri, 14 Mar 2025 19:07:57 +0100 Subject: [PATCH] Add wxURI::SetUserAndPassword() This allows to set the user info part of the user name without escaping the username and password manually in the application. --- include/wx/uri.h | 5 +++++ interface/wx/uri.h | 20 +++++++++++++++++ src/common/uri.cpp | 55 +++++++++++++++++++++++++++++++++++++++------ tests/uris/uris.cpp | 9 ++++++++ 4 files changed, 82 insertions(+), 7 deletions(-) diff --git a/include/wx/uri.h b/include/wx/uri.h index a8048ac46c..05289f0aee 100644 --- a/include/wx/uri.h +++ b/include/wx/uri.h @@ -98,6 +98,11 @@ public: wxString GetUser() const; wxString GetPassword() const; + // Set username and password for the URI. This function is _not_ the exact + // counterpart of GetUserInfo() because it takes unescaped strings, unlike + // the latter, which returns them in the escaped form, hence it uses a + // deliberately different name. + void SetUserAndPassword(const wxString& user, const wxString& password = {}); // combine all URI components into a single string // diff --git a/interface/wx/uri.h b/interface/wx/uri.h index 487a034665..96c823cd47 100644 --- a/interface/wx/uri.h +++ b/interface/wx/uri.h @@ -286,6 +286,26 @@ public: */ void Resolve(const wxURI& base, int flags = wxURI_STRICT); + /** + Sets the user and password for the URI. + + Please note that this function is not the exact counterpart of + GetUserInfo() because it takes unescaped strings, unlike the latter, + which returns them in the escaped form, and hence it uses a slightly + different name. For example, passing `user@domain` as @a user to this + function will result in GetUser() returning `user%40domain` (which can + be turned back into the original string by passing it to Unescape()). + + @param user + User name. If empty, resets any existing user information. + @param password + Password. If empty, the password is not set (and any existing + password is removed). + + @since 3.3.0 + */ + void SetUserAndPassword(const wxString& user, const wxString& password = {}); + /** Translates all escape sequences (normal characters and returns the result. diff --git a/src/common/uri.cpp b/src/common/uri.cpp index 29118a2d9c..0b9cd8aae0 100644 --- a/src/common/uri.cpp +++ b/src/common/uri.cpp @@ -120,6 +120,15 @@ wxString wxURI::Unescape(const wxString& uri) return wxString::FromUTF8(buf); } +static void AppendEscaped(wxString& s, char c) +{ + static const char* hexDigits = "0123456789abcdef"; + + s += '%'; + s += hexDigits[(c >> 4) & 15]; + s += hexDigits[c & 15]; +} + void wxURI::AppendNextEscaped(wxString& s, const char *& p) { // check for an already encoded character: @@ -133,13 +142,7 @@ void wxURI::AppendNextEscaped(wxString& s, const char *& p) } else // really needs escaping { - static const char* hexDigits = "0123456789abcdef"; - - const char c = *p++; - - s += '%'; - s += hexDigits[(c >> 4) & 15]; - s += hexDigits[c & 15]; + AppendEscaped(s, *p++); } } @@ -166,6 +169,44 @@ wxString wxURI::GetPassword() const return m_userinfo(posColon + 1, wxString::npos); } +void wxURI::SetUserAndPassword(const wxString& user, const wxString& password) +{ + if ( user.empty() ) + { + m_userinfo.clear(); + m_fields &= ~wxURI_USERINFO; + return; + } + + m_fields |= wxURI_USERINFO; + + auto escapeAsUserInfo = [](const wxString& in) + { + wxString out; + out.reserve(in.length()); + + for ( auto c : in.utf8_string() ) + { + // We could allow unencoded colon in the password (only), but it + // doesn't seem to be worth the trouble, so always encode it in + // both the username (where it has to be done) and the password. + if ( IsUnreserved(c) || IsSubDelim(c) ) + out += c; + else + AppendEscaped(out, c); + } + + return out; + }; + + m_userinfo = escapeAsUserInfo(user); + if ( !password.empty() ) + { + m_userinfo += ':'; + m_userinfo += escapeAsUserInfo(password); + } +} + // combine all URI fields in a single string, applying funcDecode to each // component which it may make sense to decode (i.e. "unescape") wxString wxURI::DoBuildURI(wxString (*funcDecode)(const wxString&)) const diff --git a/tests/uris/uris.cpp b/tests/uris/uris.cpp index b0230d74f9..0a32a02b58 100644 --- a/tests/uris/uris.cpp +++ b/tests/uris/uris.cpp @@ -321,6 +321,15 @@ TEST_CASE("URI::UserInfo", "[uri]") CHECK( uri.Create("https://u:pass%3Dword@h/") ); CHECK( wxURI::Unescape(uri.GetPassword()) == "pass=word" ); + + // Also test that using SetUserAndPassword() works. + uri = "https://host/"; + uri.SetUserAndPassword("me@here!"); + CHECK( uri.BuildURI() == "https://me%40here!@host/" ); + + uri.SetUserAndPassword("you:", "?me"); + INFO(DumpURI(uri)); + CHECK( uri.BuildURI() == "https://you%3a:%3fme@host/" ); } //examples taken from RFC 2396.bis