diff --git a/doc/autoinclude/FeatureTest.doc b/doc/autoinclude/FeatureTest.doc index 61987923b7..c325581ab6 100644 --- a/doc/autoinclude/FeatureTest.doc +++ b/doc/autoinclude/FeatureTest.doc @@ -79,6 +79,8 @@ Packages requiring a feature may use the corresponding \c Requires: in their .sp
Also support user defined repo variables in /etc/zypp/vars.d.
version 1.1
Support repo variables in an URIs \c host and \c port component.
+
version 1.2
+
Support repo variables forming valid URIs after expansion - but not before.
diff --git a/doc/autoinclude/RepoVariables.doc b/doc/autoinclude/RepoVariables.doc index 89c6d71630..f44b9209c6 100644 --- a/doc/autoinclude/RepoVariables.doc +++ b/doc/autoinclude/RepoVariables.doc @@ -15,7 +15,7 @@ The variable expander also supports shell like definition of default and alterna \see \ref zypp::repo::RepoVarExpand Variable expander -\subsection zypp-repoars-builtin Builtin repository variables +\subsection zypp-repovars-builtin Builtin repository variables \li \c $arch - The system's CPU architecture. @@ -31,10 +31,11 @@ The variable expander also supports shell like definition of default and alterna \c $releasever_major will be set to the leading portion up to (but not including) the 1st dot; \c $releasever_minor to the trailing portion after the 1st dot. If there's no dot in \c $releasever, \c $releasever_major is the same as \c $releasever and \c $releasever_minor is empty. -\subsection zypp-repoars-userdefined User defined repository variables [requires 'libzypp(repovarexpand) >= 1'] +\subsection zypp-repovars-userdefined User defined repository variables [requires 'libzypp(repovarexpand) >= 1'] A custom repository variable is defined by creating a file in \c /etc/zypp/vars.d. The variable name equals the file name. The files first line (up to but not including the newline character) defines the variables value. Valid variable(file) names consist of alphanumeric chars and '_' only. Variable substitution within an URIs authority [requires 'libzypp(repovarexpand) >= 1.1'] is limited to \c host and \c port. Bash style definition of default and alternate values is not supported. No variables can be used in an URIs \c scheme, \c user and \c password. +The use of arbitrary strings which form a valid url after variable substitution - but not before - requires 'libzypp(repovarexpand) >= 1.2'. The \c zypp-rawurl: schema is used to wrap the unexpanded strings into a valid \ref Url until they get expanded (\see \ref zypp::RawUrl). */ diff --git a/libzypp.spec.cmake b/libzypp.spec.cmake index de2f7e3d9f..0471e8fb0a 100644 --- a/libzypp.spec.cmake +++ b/libzypp.spec.cmake @@ -62,7 +62,7 @@ Provides: libzypp(plugin:services) = 1 Provides: libzypp(plugin:system) = 1 Provides: libzypp(plugin:urlresolver) = 0 Provides: libzypp(plugin:repoverification) = 0 -Provides: libzypp(repovarexpand) = 1.1 +Provides: libzypp(repovarexpand) = 1.2 %if 0%{?suse_version} Recommends: logrotate diff --git a/tests/parser/RepoFileReader_test.cc b/tests/parser/RepoFileReader_test.cc index 6b861d0c8e..1192234b2e 100644 --- a/tests/parser/RepoFileReader_test.cc +++ b/tests/parser/RepoFileReader_test.cc @@ -9,6 +9,23 @@ using std::stringstream; using std::string; using namespace zypp; +// This is kind of ugly for testing: Whenever a zypp lock is +// acquired the repo variables are cleared and re-read from disk, +// which is ok (by ZYppFactory). +// The VarReplacer itself however acquires a lock to resolve the +// Target variables (like $releasever), which is ok as well. +// For testing however - if we want to manipulate the variable +// definitions - we must hold a lock ourselves. Otherwise the +// VarReplacer will acquired one and so discard our custom +// settings. +#include +auto guard { getZYpp() }; +namespace zyppintern { + std::map repoVariablesGet(); + void repoVariablesSwap( std::map & val_r ); +} +// --- + static std::string suse_repo = "[factory-oss]\n" "name=factory-oss $releasever - $basearch\n" "failovermethod=priority\n" @@ -56,3 +73,78 @@ BOOST_AUTO_TEST_CASE(read_repo_file) BOOST_CHECK_EQUAL( Url("http://serv.er/loc1"), repo.mirrorListUrl() ); } } + +BOOST_AUTO_TEST_CASE(rawurl2repoinfo) +{ + // Set up repo variables... + std::map vardef { ::zyppintern::repoVariablesGet() }; + vardef["releasever"] = "myversion"; + vardef["basearch"] = "myarch"; + vardef["OPENSUSE_DISTURL"] = "https://cdn.opensuse.org/repositories/"; + ::zyppintern::repoVariablesSwap( vardef ); + + { + std::stringstream input( + "[leap-repo]\n" + "name=leap-repo $releasever - $basearch\n" + "baseurl= ${OPENSUSE_DISTURL}leap/repo\n" + "gpgkey= ${OPENSUSE_DISTURL}leap/repo\n" + "mirrorlist= ${OPENSUSE_DISTURL}leap/repo\n" + "metalink= ${OPENSUSE_DISTURL}leap/repo\n" + ); + RepoCollector collector; + parser::RepoFileReader parser( input, bind( &RepoCollector::collect, &collector, _1 ) ); + BOOST_CHECK_EQUAL(1, collector.repos.size()); + + const RepoInfo & repo( collector.repos.front() ); + BOOST_CHECK_EQUAL( repo.name(), "leap-repo myversion - myarch" ); + + BOOST_CHECK_EQUAL( repo.url().asCompleteString(), "https://cdn.opensuse.org/repositories/leap/repo" ); + BOOST_CHECK_EQUAL( repo.gpgKeyUrl().asCompleteString(), "https://cdn.opensuse.org/repositories/leap/repo" ); + BOOST_CHECK_EQUAL( repo.mirrorListUrl().asCompleteString(), "https://cdn.opensuse.org/repositories/leap/repo" ); + + BOOST_CHECK_EQUAL( repo.rawUrl().asCompleteString(), "zypp-rawurl:#${OPENSUSE_DISTURL}leap/repo" ); + BOOST_CHECK_EQUAL( repo.rawGpgKeyUrl().asCompleteString(), "zypp-rawurl:#${OPENSUSE_DISTURL}leap/repo" ); + BOOST_CHECK_EQUAL( repo.rawMirrorListUrl().asCompleteString(), "zypp-rawurl:#${OPENSUSE_DISTURL}leap/repo" ); + } + + // RepoFileReader unfortunately encodes "proxy=" into the URLs + // query part (by now just for the baseurl). For RawUrls the + // VarReplacer needs to take this into account. + { + std::stringstream input( + "[leap-repo]\n" + "name=leap-repo $releasever - $basearch\n" + "proxy=myproxy.host:1234\n" + "baseurl=${OPENSUSE_DISTURL}leap/repo\n" + "baseurl=https://cdn.opensuse.org/repositories/leap/repo\n" + "baseurl=https://cdn.opensuse.org/repositories/leap/repo?proxy=otherproxy.host\n" + "gpgkey=${OPENSUSE_DISTURL}leap/repo\n" + "mirrorlist=${OPENSUSE_DISTURL}leap/repo\n" + "metalink=${OPENSUSE_DISTURL}leap/repo\n" + ); + RepoCollector collector; + parser::RepoFileReader parser( input, bind( &RepoCollector::collect, &collector, _1 ) ); + BOOST_CHECK_EQUAL(1, collector.repos.size()); + + const RepoInfo & repo( collector.repos.front() ); + BOOST_CHECK_EQUAL( repo.name(), "leap-repo myversion - myarch" ); + + BOOST_CHECK_EQUAL( repo.baseUrlsSize(), 3 ); + const auto & baseurls = repo.baseUrls(); + auto iter = baseurls.begin(); + BOOST_CHECK_EQUAL( (iter++)->asCompleteString(), "https://cdn.opensuse.org/repositories/leap/repo?proxy=myproxy.host&proxyport=1234" ); + BOOST_CHECK_EQUAL( (iter++)->asCompleteString(), "https://cdn.opensuse.org/repositories/leap/repo?proxy=myproxy.host&proxyport=1234" ); + BOOST_CHECK_EQUAL( (iter++)->asCompleteString(), "https://cdn.opensuse.org/repositories/leap/repo?proxy=otherproxy.host" ); + BOOST_CHECK_EQUAL( repo.gpgKeyUrl().asCompleteString(), "https://cdn.opensuse.org/repositories/leap/repo" ); + BOOST_CHECK_EQUAL( repo.mirrorListUrl().asCompleteString(), "https://cdn.opensuse.org/repositories/leap/repo" ); + + const auto & rawbaseurls = repo.rawBaseUrls(); + iter = rawbaseurls.begin(); + BOOST_CHECK_EQUAL( (iter++)->asCompleteString(), "zypp-rawurl:?proxy=myproxy.host&proxyport=1234#${OPENSUSE_DISTURL}leap/repo" ); + BOOST_CHECK_EQUAL( (iter++)->asCompleteString(), "https://cdn.opensuse.org/repositories/leap/repo?proxy=myproxy.host&proxyport=1234" ); + BOOST_CHECK_EQUAL( (iter++)->asCompleteString(), "https://cdn.opensuse.org/repositories/leap/repo?proxy=otherproxy.host" ); + BOOST_CHECK_EQUAL( repo.rawGpgKeyUrl().asCompleteString(), "zypp-rawurl:#${OPENSUSE_DISTURL}leap/repo" ); + BOOST_CHECK_EQUAL( repo.rawMirrorListUrl().asCompleteString(), "zypp-rawurl:#${OPENSUSE_DISTURL}leap/repo" ); + } +} diff --git a/tests/repo/RepoVariables_test.cc b/tests/repo/RepoVariables_test.cc index 60265a2def..9dc159a022 100644 --- a/tests/repo/RepoVariables_test.cc +++ b/tests/repo/RepoVariables_test.cc @@ -14,6 +14,23 @@ using std::endl; using namespace zypp; using namespace boost::unit_test; +// This is kind of ugly for testing: Whenever a zypp lock is +// acquired the repo variables are cleared and re-read from disk, +// which is ok (by ZYppFactory). +// The VarReplacer itself however acquires a lock to resolve the +// Target variables (like $releasever), which is ok as well. +// For testing however - if we want to manipulate the variable +// definitions - we must hold a lock ourselves. Otherwise the +// VarReplacer will acquired one and so discard our custom +// settings. +#include +auto guard { getZYpp() }; +namespace zyppintern { + std::map repoVariablesGet(); + void repoVariablesSwap( std::map & val_r ); +} +// --- + #define DATADIR (Pathname(TESTS_SRC_DIR) + "/repo/yum/data") typedef std::list ListType; @@ -73,7 +90,7 @@ void helperGenRepVarExpandResults() { // Generate test result strings for RepVarExpand: // ( STRING, REPLACED_all_vars_undef, REPLACED_all_vars_defined ) - // Crefully check whether new stings are correct before + // Carefully check whether new stings are correct before // adding them to the testccse. std::map vartable; std::map> result; @@ -125,7 +142,7 @@ void helperGenRepVarExpandResults() } } -void RepVarExpandTest( const std::string & string_r, const std::string & allUndef_r, const std::string & allDef_r ) +void RepVarExpandTest( const std::string & string_r, const std::string & allUndef_r, const std::string & allDef_r, bool hasVars=true ) { std::map vartable; bool varsoff = true; @@ -140,6 +157,7 @@ void RepVarExpandTest( const std::string & string_r, const std::string & allUnde return &val; }; + BOOST_CHECK_EQUAL( repo::hasRepoVarsEmbedded( string_r ), hasVars ); varsoff = true; BOOST_CHECK_EQUAL( repo::RepoVarExpand()( string_r, varLookup ), allUndef_r ); varsoff = false; @@ -147,23 +165,23 @@ void RepVarExpandTest( const std::string & string_r, const std::string & allUnde } BOOST_AUTO_TEST_CASE(RepVarExpand) -{ // ( STRING , REPLACED_all_vars_undef , REPLACED_all_vars_defined ) - RepVarExpandTest( "" , "" , "" ); - RepVarExpandTest( "$" , "$" , "$" ); - RepVarExpandTest( "$${}" , "$${}" , "$${}" ); +{ // ( STRING , REPLACED_all_vars_undef , REPLACED_all_vars_defined STRING has Vars) + RepVarExpandTest( "" , "" , "" , false ); + RepVarExpandTest( "$" , "$" , "$" , false ); + RepVarExpandTest( "$${}" , "$${}" , "$${}" , false ); RepVarExpandTest( "$_:" , "$_:" , "[_]:" ); RepVarExpandTest( "$_A:" , "$_A:" , "[_A]:" ); RepVarExpandTest( "$_A_:" , "$_A_:" , "[_A_]:" ); RepVarExpandTest( "$_A_B:" , "$_A_B:" , "[_A_B]:" ); RepVarExpandTest( "${C:+a$Bba\\}" , "${C:+a$Bba\\}" , "${C:+a[Bba]\\}" ); RepVarExpandTest( "${C:+a$Bba}" , "" , "a[Bba]" ); - RepVarExpandTest( "${C:+a${B\\}ba}" , "${C:+a${B\\}ba}" , "${C:+a${B\\}ba}" ); + RepVarExpandTest( "${C:+a${B\\}ba}" , "" , "a${B}ba" ); RepVarExpandTest( "${C:+a${B}ba}" , "" , "a[B]ba" ); RepVarExpandTest( "${C:+a\\$Bba}" , "" , "a$Bba" ); RepVarExpandTest( "${C:+a\\${B\\}ba}" , "" , "a${B}ba" ); RepVarExpandTest( "${C:+a\\${B}ba}" , "ba}" , "a${Bba}" ); RepVarExpandTest( "${C:-a$Bba}" , "a$Bba" , "[C]" ); - RepVarExpandTest( "${_A_B\\}" , "${_A_B\\}" , "${_A_B\\}" ); + RepVarExpandTest( "${_A_B\\}" , "${_A_B\\}" , "${_A_B\\}" , false ); RepVarExpandTest( "${_A_B}" , "${_A_B}" , "[_A_B]" ); RepVarExpandTest( "\\${_A_B}" , "\\${_A_B}" , "\\[_A_B]" ); RepVarExpandTest( "__${D:+\\$X--{${E:-==\\$X{o\\}==} }--}__\\${B}${}__", "__--}__\\${B}${}__" , "__$X--{[E] --}__\\[B]${}__" ); @@ -248,4 +266,77 @@ BOOST_AUTO_TEST_CASE(uncached) ::setenv( "ZYPP_REPO_RELEASEVER", "13.3", 1 ); BOOST_CHECK_EQUAL( replacer1("${releasever}"), "13.3" ); } + +BOOST_AUTO_TEST_CASE(replace_rawurl) +{ + std::string s; + + s = "http://$host/repositories"; // lowercase, so it works for both (Url stores host component lowercased) + { + Url u { s }; + RawUrl r { s }; + // check replacing... + repo::RepoVariablesUrlReplacer replacer; + std::map vardef { ::zyppintern::repoVariablesGet() }; + + vardef["host"] = ""; // make sure it's not defined + ::zyppintern::repoVariablesSwap( vardef ); + BOOST_CHECK_THROW( replacer( u ).asCompleteString(), zypp::url::UrlNotAllowedException ); // Url scheme requires a host component + BOOST_CHECK_THROW( replacer( r ).asCompleteString(), zypp::url::UrlNotAllowedException ); // Url scheme requires a host component + + vardef["host"] = "cdn.opensuse.org"; + ::zyppintern::repoVariablesSwap( vardef ); + BOOST_CHECK_EQUAL( replacer( u ).asCompleteString(), "http://cdn.opensuse.org/repositories" ); + BOOST_CHECK_EQUAL( replacer( r ).asCompleteString(), "http://cdn.opensuse.org/repositories" ); + + vardef["host"] = "cdn.opensuse.org/pathadded"; + ::zyppintern::repoVariablesSwap( vardef ); + BOOST_CHECK_EQUAL( replacer( u ).asCompleteString(), "http://cdn.opensuse.org/pathadded/repositories" ); + BOOST_CHECK_EQUAL( replacer( r ).asCompleteString(), "http://cdn.opensuse.org/pathadded/repositories" ); + + vardef["host"] = "cdn.opensuse.org:1234/pathadded"; + ::zyppintern::repoVariablesSwap( vardef ); + BOOST_CHECK_EQUAL( replacer( u ).asCompleteString(), "http://cdn.opensuse.org:1234/pathadded/repositories" ); + BOOST_CHECK_EQUAL( replacer( r ).asCompleteString(), "http://cdn.opensuse.org:1234/pathadded/repositories" ); + + vardef["host"] = "//something making the Url invalid//"; + ::zyppintern::repoVariablesSwap( vardef ); + BOOST_CHECK_THROW( replacer( u ).asCompleteString(), zypp::url::UrlNotAllowedException ); // Url scheme requires a host component + BOOST_CHECK_THROW( replacer( r ).asCompleteString(), zypp::url::UrlNotAllowedException ); // Url scheme requires a host component + } + + // If embedded vars do not form a valid Url, RawUrl must be used to carry them. + // But one should make sure the expanded string later forms a valid Url. + s = "${OPENSUSE_DISTURL:-http://cdn.opensuse.org/repositories/}leap/repo"; + BOOST_CHECK_THROW( Url{ s }, zypp::url::UrlBadComponentException ); + { + RawUrl r { s }; + BOOST_CHECK_EQUAL( r.getScheme(), "zypp-rawurl" ); + BOOST_CHECK_EQUAL( r.getFragment(), s ); + // Make sure a RawUrl (as Url or String) is not re-evaluated when fed + // back into a RawUrl. I.e. no zypp-rawurl as payload of a zypp-rawurl. + BOOST_CHECK_EQUAL( r, RawUrl( r ) ); + BOOST_CHECK_EQUAL( r, RawUrl( r.asCompleteString() ) ); + // Of course you can always do Url("zypp-rawurl:#zypp-rawurl:#$VAR"), + // but then you probably know what you are doing. + BOOST_CHECK_EQUAL( RawUrl( "zypp-rawurl:#zypp-rawurl:%23$VAR" ).asCompleteString(), "zypp-rawurl:#zypp-rawurl:%23$VAR" ); + BOOST_CHECK_EQUAL( Url( "zypp-rawurl:#zypp-rawurl:%23$VAR" ).asCompleteString(), "zypp-rawurl:#zypp-rawurl:%23$VAR" ); + + // check replacing... + repo::RepoVariablesUrlReplacer replacer; + std::map vardef { ::zyppintern::repoVariablesGet() }; + + vardef["OPENSUSE_DISTURL"] = ""; // make sure it's not defined + ::zyppintern::repoVariablesSwap( vardef ); + BOOST_CHECK_EQUAL( replacer( r ).asCompleteString(), "http://cdn.opensuse.org/repositories/leap/repo" ); + + vardef["OPENSUSE_DISTURL"] = "https://mymirror.org/"; // custom value + ::zyppintern::repoVariablesSwap( vardef ); + BOOST_CHECK_EQUAL( replacer( r ).asCompleteString(), "https://mymirror.org/leap/repo" ); + + vardef["OPENSUSE_DISTURL"] = "//something making the Url invalid//"; + ::zyppintern::repoVariablesSwap( vardef ); + BOOST_CHECK_THROW( replacer( r ).asCompleteString(), zypp::url::UrlBadComponentException ); // Url scheme is a required component + } +} // vim: set ts=2 sts=2 sw=2 ai et: diff --git a/tests/zypp/Url_test.cc b/tests/zypp/Url_test.cc index 00ee9eb677..08bb9221a6 100644 --- a/tests/zypp/Url_test.cc +++ b/tests/zypp/Url_test.cc @@ -11,6 +11,7 @@ #include #include +#include #include #include #include @@ -91,12 +92,14 @@ BOOST_AUTO_TEST_CASE(test_url1) // asString shouldn't print the password, asCompleteString should. // further, the "//" at the begin of the path should be keept. str = "http://user:pass@localhost//srv/ftp?proxypass=@PROXYPASS@&proxy=proxy.my&proxyuser=@PROXYUSER@&Xproxypass=NOTTHIS&proxypass=@PROXYPASS@&proxypass=@PROXYPASS@"; - one = "http://user@localhost//srv/ftp?proxy=proxy.my&proxyuser=@PROXYUSER@&Xproxypass=NOTTHIS"; - two = str; + one = "http://user@localhost//srv/ftp?proxypass=@PROXYPASS@&proxy=proxy.my&proxyuser=@PROXYUSER@&Xproxypass=NOTTHIS&proxypass=@PROXYPASS@&proxypass=@PROXYPASS@"; + two = "http://user@localhost//srv/ftp?proxy=proxy.my&proxyuser=@PROXYUSER@&Xproxypass=NOTTHIS"; url = str; - BOOST_CHECK_EQUAL( one, url.asString() ); - BOOST_CHECK_EQUAL( two, url.asCompleteString() ); + BOOST_CHECK_EQUAL( str, url.asCompleteString() ); + BOOST_CHECK_EQUAL( one, hotfix1050625::asString( url ) ); + BOOST_CHECK_EQUAL( two, url.asString() ); + // hidden proxypass in the query is available when explicitly asked for BOOST_CHECK_EQUAL( url.getQueryParam( "proxypass" ), "@PROXYPASS@" ); @@ -315,4 +318,61 @@ BOOST_AUTO_TEST_CASE(plugin_querystring_args) BOOST_CHECK_EQUAL( pm["o"], "" ); } +BOOST_AUTO_TEST_CASE(rawurls) +{ + std::string s; + // Both throw if constructed from an empty string (which is a legacy decision). + BOOST_CHECK_THROW( Url{ s }, zypp::url::UrlBadComponentException ); + BOOST_CHECK_THROW( RawUrl{ s }, zypp::url::UrlBadComponentException ); + + // Without embedded vars both forms are the same + s = "http://cdn.opensuse.org/repositories"; + BOOST_CHECK_EQUAL( Url{ s }, RawUrl{ s } ); + + // Without embedded vars both forms throw if Url is not valid + s = "no url"; + BOOST_CHECK_THROW( Url{ s }, zypp::url::UrlBadComponentException ); + BOOST_CHECK_THROW( RawUrl{ s }, zypp::url::UrlBadComponentException ); + + // Embedded vars may form a valid Url (apparently). RawUrl however always forms the + // zypp-rawurl: schema if vars are embedded because it resolves some issues. RawUrl + // is always able to restore the original unexpanded string. Url is not. + s = "http://$HOST/repositories"; + { + Url u { s }; + BOOST_CHECK_EQUAL( u.getScheme(), "http" ); + // OOps: A variable in the host part of an Url must be defined lowercased, + // because the Url ctor stores the lowercased 'hostname'. + BOOST_CHECK_EQUAL( u.asCompleteString(), "http://$host/repositories" ); // not "http://$HOST/repositories + + // RawUrl is able to restore the original unexpanded string... + RawUrl r { s }; + BOOST_CHECK_EQUAL( r.getScheme(), "zypp-rawurl" ); + BOOST_CHECK_EQUAL( r.getFragment(), s ); + + // OOps: Conditional vars in the host part don't work at all for Url. + s = "http://${HOST:-defaulthost}/repositories"; + BOOST_CHECK_THROW( Url{ s }, zypp::url::UrlBadComponentException ); // Invalid port component '-defaulthost} + r = s; + BOOST_CHECK_EQUAL( r.getScheme(), "zypp-rawurl" ); + BOOST_CHECK_EQUAL( r.getFragment(), s ); + } + + // If embedded vars do not form a valid Url, RawUrl must be used to carry them. + // But one should make sure the expanded string later forms a valid Url. + s = "${OPENSUSE_DISTURL:-http://cdn.opensuse.org/repositories/}leap/repo"; + BOOST_CHECK_THROW( Url{ s }, zypp::url::UrlBadComponentException ); + { + RawUrl r { s }; + BOOST_CHECK_EQUAL( r.getScheme(), "zypp-rawurl" ); + BOOST_CHECK_EQUAL( r.getFragment(), s ); + + // The RawUrl can then be used and shipped as Url. + // The schema however stays zypp-rawurl: until a RepoVariablesUrlReplacer + // is used to form the intended Url. + BOOST_CHECK_EQUAL( r, Url{ r } ); + BOOST_CHECK_EQUAL( r, Url{ r.asCompleteString() } ); + } +} + // vim: set ts=2 sts=2 sw=2 ai et: diff --git a/zypp-core/Url.cc b/zypp-core/Url.cc index 166789e43f..391107d050 100644 --- a/zypp-core/Url.cc +++ b/zypp-core/Url.cc @@ -27,17 +27,6 @@ namespace zypp using namespace zypp::url; - // ----------------------------------------------------------------- - /* - * url = [scheme:] [//authority] /path [?query] [#fragment] - */ - #define RX_SPLIT_URL "^([^:/?#]+:|)" \ - "(//[^/?#]*|)" \ - "([^?#]*)" \ - "([?][^#]*|)" \ - "(#.*|)" - - //////////////////////////////////////////////////////////////////// namespace { ////////////////////////////////////////////////////////////////// @@ -217,6 +206,20 @@ namespace zypp addUrlByScheme("ftp", ref); addUrlByScheme("sftp", ref); addUrlByScheme("tftp", ref); + + // ===================================== + ref.reset( new UrlBase()); + ref->setViewOptions( zypp::url::ViewOption::DEFAULTS + - zypp::url::ViewOption::EMPTY_AUTHORITY + - zypp::url::ViewOption::EMPTY_PATH_NAME + - zypp::url::ViewOption::WITH_PATH_NAME + - zypp::url::ViewOption::WITH_QUERY_STR + - zypp::url::ViewOption::WITH_FRAGMENT + ); + ref->config("safe_fragment", ref->config( "safe_fragment" ) + "{}" ); // more readable: ${VAR} + ref->config("with_authority", "n"); // disallow host,... + ref->config("require_pathname", "n"); // path not required + addUrlByScheme("zypp-rawurl", ref); // in fragment: URL with unexpanded RepoVariables } bool @@ -370,40 +373,26 @@ namespace zypp UrlRef Url::parseUrl(const std::string &encodedUrl) { - UrlRef url; - str::smatch out; - bool ret = false; - - try - { - str::regex rex(RX_SPLIT_URL); - ret = str::regex_match(encodedUrl, out, rex); - } - catch( ... ) - {} + // url = [scheme:] [//authority] /path [?query] [#fragment] + static const str::regex RX_SPLIT_URL { + //| scheme || authority || path || query || fr | + "^(([^:/?#]+):|)(//([^/?#]*)|)([^?#]*)([?]([^#]*)|)(#(.*)|)" + }; - if(ret && out.size() == 6) + UrlRef url; + str::smatch out; + if ( str::regex_match( encodedUrl, out, RX_SPLIT_URL ) ) { - std::string scheme = out[1]; - if (scheme.size() > 1) - scheme = scheme.substr(0, scheme.size()-1); - std::string authority = out[2]; - if (authority.size() >= 2) - authority = authority.substr(2); - std::string query = out[4]; - if (query.size() > 1) - query = query.substr(1); - std::string fragment = out[5]; - if (fragment.size() > 1) - fragment = fragment.substr(1); - - url = g_urlSchemeRepository().getUrlByScheme(scheme); - if( !url) - { - url.reset( new UrlBase()); + const std::string & scheme { out[2] }; + const std::string & authority { out[4] }; + const std::string & path { out[5] }; + const std::string & query { out[7] }; + const std::string & fragment { out[9] }; + url = g_urlSchemeRepository().getUrlByScheme( scheme ); + if( !url ) { + url.reset( new UrlBase() ); } - url->init(scheme, authority, out[3], - query, fragment); + url->init( scheme, authority, path, query, fragment ); } return url; } @@ -482,6 +471,12 @@ namespace zypp { return scheme_r == "plugin"; } + + bool Url::schemeIsRawUrl( const std::string & scheme_r ) + { + return scheme_r == "zypp-rawurl"; + } + /////////////////////////////////////////////////////////////////// // ----------------------------------------------------------------- @@ -519,6 +514,18 @@ namespace zypp return m_impl->asString(opts); } + // ----------------------------------------------------------------- + std::string + Url::asRawString() const + { + return schemeIsRawUrl() ? getFragment() : asString(); + } + + std::string + Url::asRawString(const ViewOptions &opts) const + { + return schemeIsRawUrl() ? getFragment() : asString(opts); + } // ----------------------------------------------------------------- std::string @@ -884,7 +891,7 @@ namespace zypp namespace hotfix1050625 { std::string asString( const Url & url_r ) - { return url_r.m_impl->asString1050625(); } + { return url_r.asRawString( url_r.getViewOptions()+ViewOptions::hotfix1050625 ); } } //////////////////////////////////////////////////////////////////// diff --git a/zypp-core/Url.h b/zypp-core/Url.h index d1eeabfd12..f1bbf20eb2 100644 --- a/zypp-core/Url.h +++ b/zypp-core/Url.h @@ -22,6 +22,8 @@ namespace zypp class Url; namespace hotfix1050625 { + // Temp. fix to keep the proxypass in the query when writing the .repo files, + // but otherwise hiding it, when WITH_PASSWORD is not set. Returns asRawString. std::string asString( const Url & url_r ); } namespace filesystem { @@ -280,6 +282,11 @@ namespace zypp /** \overload nonstatic version */ bool schemeIsPlugin() const { return schemeIsPlugin( getScheme() ); } + /** zypp-rawurl */ + static bool schemeIsRawUrl( const std::string & scheme_r ); + /** \overload nonstatic version */ + bool schemeIsRawUrl() const { return schemeIsRawUrl( getScheme() ); } + /** * \brief Verifies the Url. * @@ -326,11 +333,33 @@ namespace zypp * in the current object (see setViewOption()) and forces to * return a string with all URL components included. * + * \note: It may contain embedded passwords otherwise hidden + * by the \ref ViewOptions. + * * \return A complete string representation of the Url object. */ std::string asCompleteString() const; + /** + * Returns the original raw string e.g. to be written back to a file. + * + * This is basically \ref asString, just for the \c zypp-rawurl: + * schema the unexpanded fragment part is returned. I.e. it returns + * a valid Url as string or a string with embedded RepoVariables + * which is expected to form a valid Url after variables have been + * replaced. + * + * Although a \c zypp-rawurl: written back \ref asCompleteString to a + * file could be used to restore the Url as well, it may be desired for + * user supplied strings to restore the original string value. + **/ + std::string + asRawString() const; + /** \overload taking custom ViewOptions to render a Url (except for zypp-rawurl:). */ + std::string + asRawString(const ViewOptions &opts) const; + // ----------------- /** diff --git a/zypp-core/base/Regex.h b/zypp-core/base/Regex.h index f0d555ca9d..f5dbad1586 100644 --- a/zypp-core/base/Regex.h +++ b/zypp-core/base/Regex.h @@ -62,7 +62,7 @@ namespace zypp /// \ingroup ZYPP_STR_REGEX /// \relates regex /// Return whether a \ref regex matches a specific string. An optionally - /// passed \ref smatch object will contain the match reults. + /// passed \ref smatch object will contain the match results. ////////////////////////////////////////////////////////////////// bool regex_match( const char * s, smatch & matches, const regex & regex ); diff --git a/zypp-core/url/UrlBase.cc b/zypp-core/url/UrlBase.cc index 82f00961b7..bb554a314e 100644 --- a/zypp-core/url/UrlBase.cc +++ b/zypp-core/url/UrlBase.cc @@ -35,11 +35,11 @@ ** ** host = hostname | IPv4 | "[" IPv6-IP "]" | "[v...]" */ -#define RX_VALID_SCHEME "^[" a_zA_Z "][" a_zA_Z "0-9\\.+-]*$" +#define RX_VALID_SCHEME "^[" a_zA_Z "][" a_zA_Z "0-9.+-]*$" #define RX_VALID_PORT "^[0-9]{1,5}$" -#define RX_VALID_HOSTNAME "^[[:alnum:]${_}]+([\\.-][[:alnum:]${_}]+)*$" +#define RX_VALID_HOSTNAME "^[[:alnum:]${_}]+([.-][[:alnum:]${_}]+)*$" #define RX_VALID_HOSTIPV4 \ "^([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})$" @@ -506,13 +506,6 @@ namespace zypp return asString(getViewOptions()); } - std::string UrlBase::asString1050625() const - { - // Temp. fix to keep the proxypass in the query when writing the .repo files, - // but otherwise hiding it, when WITH_PASSWORD is not set. - return asString(getViewOptions()+ViewOptions::hotfix1050625); - } - // --------------------------------------------------------------- std::string UrlBase::asString(const zypp::url::ViewOptions &opts) const diff --git a/zypp-core/url/UrlBase.h b/zypp-core/url/UrlBase.h index dfa4d3f410..557e1d51b7 100644 --- a/zypp-core/url/UrlBase.h +++ b/zypp-core/url/UrlBase.h @@ -1000,7 +1000,6 @@ namespace zypp void setViewOptions(const ViewOptions &vopts); - std::string asString1050625() const; protected: /** * Utility method to cleanup an encoded path name. diff --git a/zypp/RepoInfo.cc b/zypp/RepoInfo.cc index d32e8b55f7..61dd151aa9 100644 --- a/zypp/RepoInfo.cc +++ b/zypp/RepoInfo.cc @@ -884,10 +884,12 @@ namespace zypp std::ostream & RepoInfo::dumpOn( std::ostream & str ) const { + // Don't dump raw Urls while the fragment is hidden in the default view + // (because it may otherwise expose embedded passwords). RepoInfoBase::dumpOn(str); if ( _pimpl->baseurl2dump() ) { - for ( const auto & url : _pimpl->baseUrls().raw() ) + for ( const auto & url : _pimpl->baseUrls().transformed() ) // not .raw()! { str << "- url : " << url << std::endl; } @@ -899,7 +901,7 @@ namespace zypp str << tag_r << value_r << std::endl; }); - strif( (_pimpl->_mirrorListForceMetalink ? "- metalink : " : "- mirrorlist : "), rawMirrorListUrl().asString() ); + strif( (_pimpl->_mirrorListForceMetalink ? "- metalink : " : "- mirrorlist : "), mirrorListUrl().asString() ); // not rawMirrorListUrl() strif( "- path : ", path().asString() ); str << "- type : " << type() << std::endl; str << "- priority : " << priority() << std::endl; @@ -913,7 +915,7 @@ namespace zypp << std::endl; #undef OUTS - for ( const auto & url : _pimpl->gpgKeyUrls().raw() ) + for ( const auto & url : _pimpl->gpgKeyUrls().transformed() ) // not .raw()! { str << "- gpgkey : " << url << std::endl; } @@ -932,6 +934,8 @@ namespace zypp std::ostream & RepoInfo::dumpAsIniOn( std::ostream & str ) const { + // NOTE: hotfix1050625::asString provides the Url asRawSring, + // which is what we want when writing a .repo file. RepoInfoBase::dumpAsIniOn(str); if ( _pimpl->baseurl2dump() ) @@ -948,8 +952,8 @@ namespace zypp if ( ! _pimpl->path.empty() ) str << "path="<< path() << endl; - if ( ! (rawMirrorListUrl().asString().empty()) ) - str << (_pimpl->_mirrorListForceMetalink ? "metalink=" : "mirrorlist=") << hotfix1050625::asString( rawMirrorListUrl() ) << endl; + if ( _pimpl->_mirrorListUrl.raw().isValid() ) + str << (_pimpl->_mirrorListForceMetalink ? "metalink=" : "mirrorlist=") << hotfix1050625::asString( _pimpl->_mirrorListUrl.raw() ) << endl; if ( type() != repo::RepoType::NONE ) str << "type=" << type().asString() << endl; @@ -970,7 +974,7 @@ namespace zypp std::string indent( "gpgkey="); for ( const auto & url : _pimpl->gpgKeyUrls().raw() ) { - str << indent << url << endl; + str << indent << hotfix1050625::asString( url ) << endl; if ( indent[0] != ' ' ) indent = " "; } diff --git a/zypp/RepoInfo.h b/zypp/RepoInfo.h index 003034ed59..fabfc41eba 100644 --- a/zypp/RepoInfo.h +++ b/zypp/RepoInfo.h @@ -65,8 +65,8 @@ namespace zypp * \note A RepoInfo is a hint about how * to create a Repository. * - * \note Name, baseUrls and mirrorUrl are subject to repo variable replacement - * (\see \ref RepoVariablesStringReplacer). + * \note Name, baseUrls, gpgKeyUrl and mirrorUrl are subject to repo variable + * replacement(\see \ref RepoVariablesStringReplacer and \ref RawUrl). */ class RepoInfo : public repo::RepoInfoBase { @@ -132,8 +132,9 @@ namespace zypp { return( baseUrlsEmpty() ? Url() : *baseUrlsBegin()); } /** * Pars pro toto: The first repository raw url (no variables replaced) + * \deprecated \see \ref RawUrl */ - Url rawUrl() const; + Url rawUrl() const ZYPP_DEPRECATED; /** * The complete set of repository urls @@ -144,8 +145,9 @@ namespace zypp url_set baseUrls() const; /** * The complete set of raw repository urls (no variables replaced) + * \deprecated \see \ref RawUrl */ - url_set rawBaseUrls() const; + url_set rawBaseUrls() const ZYPP_DEPRECATED; /** * Add a base url. \see baseUrls @@ -197,8 +199,9 @@ namespace zypp Url mirrorListUrl() const; /** * The raw mirrorListUrl (no variables replaced). + * \deprecated \see \ref RawUrl */ - Url rawMirrorListUrl() const; + Url rawMirrorListUrl() const ZYPP_DEPRECATED; /** * Set mirror list url. \see mirrorListUrl * \param url The base url for the list @@ -391,15 +394,19 @@ namespace zypp /** The list of gpgkey URLs defined for this repo */ url_set gpgKeyUrls() const; - /** The list of raw gpgkey URLs defined for this repo (no variables replaced) */ - url_set rawGpgKeyUrls() const; + /** The list of raw gpgkey URLs defined for this repo (no variables replaced) + * \deprecated \see \ref RawUrl + */ + url_set rawGpgKeyUrls() const ZYPP_DEPRECATED; /** Set a list of gpgkey URLs defined for this repo */ void setGpgKeyUrls( url_set urls ); /** (leagcy API) The 1st gpgkey URL defined for this repo */ Url gpgKeyUrl() const; - /** (leagcy API) The 1st raw gpgkey URL defined for this repo (no variables replaced) */ - Url rawGpgKeyUrl() const; + /** (leagcy API) The 1st raw gpgkey URL defined for this repo (no variables replaced) + * \deprecated \see \ref RawUrl + */ + Url rawGpgKeyUrl() const ZYPP_DEPRECATED; /** (leagcy API) Set the gpgkey URL defined for this repo */ void setGpgKeyUrl( const Url &gpgkey ); diff --git a/zypp/RepoManager.cc b/zypp/RepoManager.cc index 89a3f49202..1e32ad2ccb 100644 --- a/zypp/RepoManager.cc +++ b/zypp/RepoManager.cc @@ -87,6 +87,14 @@ namespace zypp /////////////////////////////////////////////////////////////////// namespace { + TODO: + - check locations extracting raw urls (raw.*Url) + - check locations setting urls (must be the raw one) (setBaseUrl) + - check UrlCredentialExtractor uses (must not be the raw ones) + - In general check all Urls passed from the application and which might become a + zypp-rawurl:. The repos rawurls can not be modified if the changes do not persist + the var expansion. + /////////////////////////////////////////////////////////////////// /// \class UrlCredentialExtractor /// \brief Extract credentials in \ref Url authority and store them via \ref CredentialManager. diff --git a/zypp/ServiceInfo.cc b/zypp/ServiceInfo.cc index a20e5319e2..08f66fc308 100644 --- a/zypp/ServiceInfo.cc +++ b/zypp/ServiceInfo.cc @@ -172,8 +172,10 @@ namespace zypp std::ostream & ServiceInfo::dumpAsIniOn( std::ostream & str ) const { + // NOTE: hotfix1050625::asString provides the Url asRawSring, + // which is what we want when writing a .service file. RepoInfoBase::dumpAsIniOn(str) - << "url = " << hotfix1050625::asString( rawUrl() ) << endl + << "url = " << hotfix1050625::asString( _pimpl->_url.raw() ) << endl << "type = " << type() << endl; if ( ttl() ) diff --git a/zypp/ServiceInfo.h b/zypp/ServiceInfo.h index b7f9a1b005..e8c96d9f0b 100644 --- a/zypp/ServiceInfo.h +++ b/zypp/ServiceInfo.h @@ -65,8 +65,10 @@ namespace zypp /** The service url */ Url url() const; - /** The service raw url (no variables replaced) */ - Url rawUrl() const; + /** The service raw url (no variables replaced) + * \deprecated \see \ref RawUrl + */ + Url rawUrl() const ZYPP_DEPRECATED; /** Set the service url (raw value) */ void setUrl( const Url& url ); @@ -85,16 +87,16 @@ namespace zypp * You don't want to use the setters unless you are a \ref RepoManager. */ //@{ - /** Sugested TTL between two metadata auto-refreshs. + /** Suggested TTL between two metadata auto-refreshes. * The value (in seconds) may be provided in repoindex.xml:xpath:/repoindex@ttl. * Default is \a 0 - perform each auto-refresh request. */ Date::Duration ttl() const; - /** Set sugested TTL. */ + /** Set suggested TTL. */ void setTtl( Date::Duration ttl_r ); - /** Lazy init sugested TTL. */ + /** Lazy init suggested TTL. */ void setProbedTtl( Date::Duration ttl_r ) const; /** Date of last refresh (if known). */ diff --git a/zypp/parser/RepoFileReader.cc b/zypp/parser/RepoFileReader.cc index a1e12296ff..67a64e15cc 100644 --- a/zypp/parser/RepoFileReader.cc +++ b/zypp/parser/RepoFileReader.cc @@ -115,7 +115,7 @@ namespace zypp // #285: Fedora/dnf allows WS separated urls (and an optional comma) strv::splitRx( line_r, "[,[:blank:]]*[[:blank:]][,[:blank:]]*", [&store_r]( std::string_view w ) { if ( ! w.empty() ) - store_r.push_back( Url(std::string(w)) ); + store_r.push_back( RawUrl(std::string(w)) ); // RawUrl! to support repo var replacement }); } diff --git a/zypp/parser/ServiceFileReader.cc b/zypp/parser/ServiceFileReader.cc index b71740cffd..9dc555ecc1 100644 --- a/zypp/parser/ServiceFileReader.cc +++ b/zypp/parser/ServiceFileReader.cc @@ -61,11 +61,12 @@ namespace zypp it != dict.entriesEnd(*its); ++it ) { + // MIL << (*it).first << endl; if ( it->first == "name" ) service.setName( it->second ); else if ( it->first == "url" && ! it->second.empty() ) - service.setUrl( Url (it->second) ); + service.setUrl( RawUrl(it->second) ); // RawUrl! to support repo var replacement else if ( it->first == "enabled" ) service.setEnabled( str::strToTrue( it->second ) ); else if ( it->first == "autorefresh" ) diff --git a/zypp/repo/RepoVariables.cc b/zypp/repo/RepoVariables.cc index 867cac9ea0..efaff29271 100644 --- a/zypp/repo/RepoVariables.cc +++ b/zypp/repo/RepoVariables.cc @@ -115,7 +115,7 @@ namespace zypp /** The var type: \c \, \c $, \c - , \c + * \li \c \ backslash escaped literal - * \li \c $ plain variable + * \li \c $ plain variable * \li \c - conditional: default value * \li \c + conditional: alternate value */ @@ -228,8 +228,10 @@ namespace zypp else if ( *scan == '$' ) { // an embedded var? - if ( ! (scan = findVarEnd( scan )) ) - return false; + if ( const char * scanEnd = findVarEnd( scan ) ) + scan = scanEnd; + else + ++scan; // no valid var: treated as literal } else if ( *scan == '}' ) { @@ -294,7 +296,10 @@ namespace zypp while ( scan.nextVar() ) { static const std::string _emptyValue; - const std::string *const knownVar = ( varRetriever_r ? varRetriever_r( scan.varName() ) : nullptr ); + + int varType = scan.varType(); + // VarRetriever callback is not needed for backslash escaped literals (varType '\\') + const std::string *const knownVar = ( varType != '\\' && varRetriever_r ? varRetriever_r( scan.varName() ) : nullptr ); const std::string & varValue( knownVar ? *knownVar : _emptyValue ); #if ( ZYPP_DBG_VAREXPAND ) @@ -307,7 +312,6 @@ namespace zypp bool mustSubstitute = false; // keep original text per default std::string substitutionValue; - int varType = scan.varType(); if ( varType == '$' ) // plain var { if ( knownVar ) @@ -377,6 +381,9 @@ namespace zypp } // namespace /////////////////////////////////////////////////////////////////// + bool hasRepoVarsEmbedded( const std::string & str_r ) + { return FindVar( str_r, 0 ).nextVar(); } + std::string RepoVarExpand::operator()( const std::string & value_r, VarRetriever varRetriever_r ) const { return expand( value_r, 0, varRetriever_r ); } @@ -553,26 +560,61 @@ namespace zypp Url RepoVariablesUrlReplacer::operator()( const Url & value ) const { - Url::ViewOptions toReplace = value.getViewOptions() - url::ViewOption::WITH_USERNAME - url::ViewOption::WITH_PASSWORD; - // Legacy: Not 100% correct because it substitutes inside the 'proxypass=' value, - // but this was done before as well. The final fix will have to keep the proxypasswd - // out side the url in a cedential file. - Url tmpurl { value }; - tmpurl.setViewOptions( toReplace ); - const std::string & replaced( RepoVarExpand()( hotfix1050625::asString( tmpurl ), RepoVarsMap::lookup ) ); - Url newurl; - if ( !replaced.empty() ) - { - newurl = replaced; - newurl.setUsername( value.getUsername( url::E_ENCODED ), url::E_ENCODED ); - newurl.setPassword( value.getPassword( url::E_ENCODED ), url::E_ENCODED ); - newurl.setViewOptions( value.getViewOptions() ); + if ( value.isValid() ) { + // The zypp-rawurl: schema is replaced by extracting and replacing the raw stringvalue. + // Other schmemata are replaced the traditional way. + if ( value.schemeIsRawUrl() ) { + const std::string & replaced { RepoVarExpand()( value.getFragment(), RepoVarsMap::lookup ) }; + newurl = Url( replaced ); + // Fixup some legacy issue: RepoFileReader encodes a 'proxy=' in .repo in + // the Url's Query part. We must restore it from there unless the original Url + // defined proxy in it's query part. + if ( newurl.getQueryParam( "proxy" ).empty() ) { + const std::string & p { value.getQueryParam( "proxy" ) }; + if ( not p.empty() ) { + newurl.setQueryParam( "proxy", p ); + newurl.setQueryParam( "proxyport", value.getQueryParam( "proxyport" ) ); + } + } + } else { + // The traditional + Url::ViewOptions toReplace = value.getViewOptions() - url::ViewOption::WITH_USERNAME - url::ViewOption::WITH_PASSWORD; + // Legacy: Not 100% correct because it substitutes inside the 'proxypass=' value, + // but this was done before as well. The final fix will have to keep the proxypasswd + // out side the url in a credential file. + Url tmpurl { value }; + tmpurl.setViewOptions( toReplace ); + const std::string & replaced( RepoVarExpand()( hotfix1050625::asString( tmpurl ), RepoVarsMap::lookup ) ); + + if ( !replaced.empty() ) + { + newurl = replaced; + newurl.setUsername( value.getUsername( url::E_ENCODED ), url::E_ENCODED ); + newurl.setPassword( value.getPassword( url::E_ENCODED ), url::E_ENCODED ); + newurl.setViewOptions( value.getViewOptions() ); + } + } } return newurl; } } // namespace repo /////////////////////////////////////////////////////////////////// + + //////////////////////////////////////////////////////////////////// + // class RawUrl + //////////////////////////////////////////////////////////////////// + + RawUrl::RawUrl( const std::string & encodedUrl_r ) + { + if ( str::startsWith( encodedUrl_r, "zypp-rawurl:" ) || not repo::hasRepoVarsEmbedded( encodedUrl_r ) ) { + *this = Url( encodedUrl_r ); + } else { + *this = Url( "zypp-rawurl:" ); + setFragment( encodedUrl_r ); + } + } + } // namespace zypp /////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////// @@ -582,6 +624,11 @@ namespace zyppintern // internal helper called when re-acquiring the lock void repoVariablesReset() { repo::RepoVarsMap::instance().clear(); } + // Direct inspection and manipulation of the var-set for debugging and testcases + std::map repoVariablesGet() + { return repo::RepoVarsMap::instance(); } + void repoVariablesSwap( std::map & val_r ) + { return repo::RepoVarsMap::instance().swap( val_r ); } } // namespace zyppintern /////////////////////////////////////////////////////////////////// diff --git a/zypp/repo/RepoVariables.h b/zypp/repo/RepoVariables.h index 1299471442..d155786954 100644 --- a/zypp/repo/RepoVariables.h +++ b/zypp/repo/RepoVariables.h @@ -22,6 +22,9 @@ namespace zypp /////////////////////////////////////////////////////////////////// namespace repo { + /** Return whether \a str_r has embedded variables. */ + bool hasRepoVarsEmbedded( const std::string & str_r ); + /////////////////////////////////////////////////////////////////// /// \class RepoVarExpand /// \brief Functor expanding repo variables in a string @@ -38,7 +41,7 @@ namespace zypp /// When braces are used, the matching ending brace is the first \c } not /// escaped by a backslash and not within an embedded variable expansion. /// Within braces only \c $, \c } and \c backslash are escaped by a - /// backslash. There is no escaping outside braces, to stay comaptible + /// backslash. There is no escaping outside braces, to stay compatible /// with \c YUM (which does not support braces). /// ///
    @@ -79,7 +82,7 @@ namespace zypp * * \note The $releasever value is overwritten by the environment * variable \c ZYPP_REPO_RELEASEVER. This might be handy for - * distribution upogrades like this: + * distribution upgrades like this: * \code * $ export ZYPP_REPO_RELEASEVER=13.2 * $ zypper lr -u @@ -113,8 +116,8 @@ namespace zypp /** * \short Functor replacing repository variables * - * Replaces repository variables in the URL (except for user/pass inside authority) - * \see RepoVariablesStringReplacer + * Replaces repository variables in the URL + * \see \ref RepoVariablesStringReplacer and \ref RawUrl */ struct RepoVariablesUrlReplacer { @@ -135,6 +138,59 @@ namespace zypp /** \relates RepoVariablesUrlReplacer Helper managing repo variables replaced url lists */ typedef base::ContainerTransform, repo::RepoVariablesUrlReplacer> RepoVariablesReplacedUrlList; + //////////////////////////////////////////////////////////////////// + /// \class RawUrl + /// \brief Convenience type to create \c zypp-rawurl: Urls. + /// + /// Strings containing embedded RepoVariables (\see \ref repo::RepoVarExpand) + /// may not form a valid \ref Url until the variables are actually expanded. + /// The \c zypp-rawurl: schema was introduced to allow shipping such strings + /// through legacy APIs expecting a valid \ref Url. Prior to that even the + /// unexpanded string has to form a valid \ref Url which limited the use of + /// repo variables in URLs. + /// + /// The unexpanded string is shipped as payload encoded in the Url's fragment + /// part. Later a \ref RepoVariablesStringReplacer can be used to form the + /// intended \ref Url by substituting the variables. + /// + /// The typical use case are url strings read from a .repo (or .service) file. + /// They are stored as\c zypp-rawurl: within a \ref RepoInfo (or \ref ServiceInfo) + /// in case they have to written back to the file. The application usually refers + /// to the expanded \ref Url, but those classes offer to retrieve the raw Urls + /// as well - as type \ref Url. + /// + /// If the string does not contain any variables at all, the native \u Url is + /// constructed. + /// + /// \code + /// std::string s { "$DISTURL/leap/15.6/repo/oss" } + /// Url raw { s }; // Throws because s does not form a valid Url + /// RawUrl raw { s }; // OK; creates a zypp-rawurl: with s as payload + /// + /// // with DISTURL=https://cdn.opensuse.org/distribution + /// Url url { RepoVariablesUrlReplacer()( raw ) }; + /// // forms https://cdn.opensuse.org/distribution/leap/15.6/repo/oss + /// \endcode + /// + /// \note Support for repo variables forming valid URIs after expansion only + /// requires a libzypp provinding 'libzypp(repovarexpand) >= 1.2' + /// (\see \ref feature-test). + /// + /// \note Methods returning RawUrls are often deprecated because printing them + /// may accidentally expose credentials (password,proxypassword) embedded in the + /// raw url-string. An url-string with embedded RepoVariables, forms a valid + /// \ref Url after variables have been expanded. In the expanded Url passwords + /// are protected. The unexpanded url-string however does not even need to form + /// a valid Url, so sensitive data in the string can not be detected and protected. + /// This is why the \c zypp-rawurl: schema, which is used to store the unexpanded + /// url-strings, per default prints just it's schema, but not it's content. + struct RawUrl : public Url + { + RawUrl() : Url() {} + RawUrl( const Url & url_r ) : Url( url_r ) {} + RawUrl( const std::string & encodedUrl_r ); + }; + } // namespace zypp ///////////////////////////////////////////////////////////////////