Imported Upstream version 15.0.0
[platform/upstream/libzypp.git] / zypp / repo / RepoVariables.cc
index 415dfca..d53e534 100644 (file)
 |                         /_____||_| |_| |_|                           |
 |                                                                      |
 \---------------------------------------------------------------------*/
+#include <cstring>
 
+#define ZYPP_DBG_VAREXPAND 0
+#if ( ZYPP_DBG_VAREXPAND )
+#warning ZYPP_DBG_VAREXPAND is on
 #include <iostream>
-#include <map>
-#include <algorithm>
+#include <sstream>
+using std::cout;
+using std::endl;
+#endif // ZYPP_DBG_VAREXPAND
+
+#include "zypp/base/LogTools.h"
 #include "zypp/base/String.h"
-#include "zypp/repo/RepoException.h"
-#include "zypp/ZConfig.h"
-#include "RepoVariables.h"
+#include "zypp/base/Regex.h"
 
-using namespace std;
+#include "zypp/ZConfig.h"
+#include "zypp/Target.h"
+#include "zypp/Arch.h"
+#include "zypp/repo/RepoVariables.h"
+#include "zypp/base/NonCopyable.h"
 
+///////////////////////////////////////////////////////////////////
 namespace zypp
 {
-namespace repo
-{
+  namespace env
+  {
+    /** Use faked releasever (e.g. for 'zupper dup' to next distro version */
+    inline std::string ZYPP_REPO_RELEASEVER()
+    {
+      const char * env = getenv("ZYPP_REPO_RELEASEVER");
+      return( env ? env : "" );
+    }
+  }
+
+  ///////////////////////////////////////////////////////////////////
+  namespace repo
+  {
+    ///////////////////////////////////////////////////////////////////
+    // RepoVarExpand
+    ///////////////////////////////////////////////////////////////////
+    namespace
+    {
+      ///////////////////////////////////////////////////////////////////
+      /// \class FindVar
+      /// \brief Helper scanning for variable definitions in a string
+      ///////////////////////////////////////////////////////////////////
+      struct FindVar
+      {
+       bool _embedded;         ///< A (formerly) embedded string may have esacped \c $, \c closebrace and \c backslash
+       const char * _sbeg;     ///< start of string to scan
+       const char * _vbeg;     ///< [$]{variable:-word} / [$]{variable} / if embedded also on [\\]
+       const char * _nbeg;     ///< ${[v]ariable:-word} / ${[v]ariable}
+       const char * _nend;     ///< ${variable[:]-word} / ${variable[}]
+       const char * _vend;     ///< ${variable:-word}[] / ${variable}[]
+       const char * _send;     ///< end of scan (next $ or nullptr if done)
 
-RepoVariablesStringReplacer::RepoVariablesStringReplacer()
-{}
+       FindVar( const std::string & str_r, bool embedded_r )
+       : _embedded( embedded_r )
+       , _sbeg( str_r.c_str() )
+       , _vbeg( nullptr )
+       , _nbeg( nullptr )
+       , _nend( nullptr )
+       , _vend( nullptr )
+       , _send( findVarStart( _sbeg ) )
+       {}
 
-RepoVariablesStringReplacer::~RepoVariablesStringReplacer()
-{}
+       /** Nullptr in _send indicates we scanned the whole string. */
+       bool done() const
+       { return !_send; }
 
-std::string RepoVariablesStringReplacer::operator()( const std::string &value ) const
-{
-  string newvalue(value);
-
-  // $arch
-  newvalue = str::gsub( newvalue,
-                        "$arch",
-                        ZConfig::instance().systemArchitecture().asString() );
-  // $basearch
-
-  Arch::CompatSet cset( Arch::compatSet( ZConfig::instance().systemArchitecture() ) );
-  Arch::CompatSet::const_iterator it = cset.end();
-  --it;
-  // now at noarch
-  --it;
-
-  Arch basearch = *it;
-  if ( basearch == Arch_noarch )
-  {
-    basearch = ZConfig::instance().systemArchitecture();
-  }
+       /** Advance to first/next var if there is one */
+       bool nextVar()
+       {
+         if ( done() )
+           return false;
 
-  newvalue = str::gsub( newvalue,
-                        "$basearch",
-                        basearch.asString() );
-  return newvalue;
-}
+         do {
+           if ( _vbeg && !_vend )      // loop internal: no findVarEnd at current $; skip it
+             _send = findVarStart( _vbeg+1 );
+           _vbeg = _send;              // next $ or null if string end
+           _nbeg = _nend = _vend = _send = nullptr;
+           if ( ! _vbeg )              // done!
+             return false;
+         } while( ! findVarEnd() );
 
-//////////////////////////////////////////////////////////////////////
+         return true;
+       }
 
-RepoVariablesUrlReplacer::RepoVariablesUrlReplacer()
-{}
+       /** Valid _vend indicates valid var data in scan. */
+       bool hasVar() const
+       { return _vend; }
 
-RepoVariablesUrlReplacer::~RepoVariablesUrlReplacer()
-{}
+       //
+       // Methods below are only valid if hasVar() == true
+       //
 
-/*
- * Replaces '$arch' and '$basearch' in the path and query part of the URL
- * with the global ZYpp values. Examples:
- *
- * ftp://user:secret@site.net/$arch/ -> ftp://user:secret@site.net/i686/
- * http://site.net/?basearch=$basearch -> http://site.net/?basearch=i386
- */
-Url RepoVariablesUrlReplacer::operator()( const Url &value ) const
-{
-  Url newurl = value;
-  RepoVariablesStringReplacer replacer;
-  newurl.setPathData(replacer(value.getPathData()));
-  newurl.setQueryString(replacer(value.getQueryString()));
+       /** Return the full var text */
+       std::string var() const
+       { return std::string( _vbeg, _vend ); }
+
+       /** Return the var name */
+       std::string varName() const
+       { return std::string( _nbeg, _nend ); }
+
+       /** Whether this is a conditional var (${..:[+-]...}) */
+       bool varIsConditional() const
+       { return( *(_vbeg+1) == '{' && *_nend == ':' ); }
+
+       /** The var type: \c \, \c $, \c - , \c +
+       * \li \c \ backslash escaped literal
+       * \li \c $      plain variable
+       * \li \c - conditional: default value
+       * \li \c + conditional: alternate value
+       */
+       int varType() const
+       { return( varIsConditional() ? *(_nend+1) : *_vbeg ); }
+
+       /** Return embedded value in conditional vars or empty string */
+       std::string varEmbedded() const
+       { return( varIsConditional() ? std::string( _nend+2, _vend-1 ) : std::string() ); }
+
+
+       /** Have unwritten data before var? */
+       bool hasVarPrefix() const
+       { return ( _sbeg != _vbeg ); }
+
+       /** Return unwritten data before var */
+       std::string varPrefix() const
+       { return std::string( _sbeg, _vbeg ); }
+
+       /** Indicate all data up to _vend were written */
+       void wroteVar()
+       { _sbeg = _vend; }
+
+      private:
+       /** Return next \c $ */
+       const char * findVarStart( const char * sbeg_r ) const
+       {
+         for ( ; *sbeg_r; ++sbeg_r )
+           if ( *sbeg_r == '$' || ( _embedded && *sbeg_r == '\\' ) )
+             return sbeg_r;
+         return nullptr;
+       }
+
+       /** Valid var name char */
+       bool isnamech( int ch ) const
+       { return ch == '_' || isalpha( ch ); }
+
+       /** Scan for a valid variable starting at _vbeg (storing the values) */
+       bool findVarEnd()
+       {
+         // asserted: *_vbeg == '$' || '\\'
+         if ( ! findVarEnd( _vbeg, _nbeg, _nend, _vend ) )
+           return false;
+         _send = findVarStart( _vend );
+         return true;
+       }
+
+       /** Skip over valid variable starting at vbeg (return end in \a vend). */
+       const char * findVarEnd( const char * vbeg ) const
+       {
+         // asserted: *_vbeg == '$'
+         const char * nbeg = nullptr;
+         const char * nend = nullptr;
+         const char * vend = nullptr;
+         findVarEnd( vbeg, nbeg, nend, vend );
+         return vend;
+       }
+
+       /** Scan for a valid variable starting at vbeg (const version returning the values). */
+       bool findVarEnd( const char * vbeg, const char *& nbeg, const char *& nend, const char *& vend ) const
+       {
+         // embedded only: handle backslash escaped chars
+         if ( *_vbeg == '\\' )
+         {
+           nbeg = vbeg+1;
+           if ( *nbeg == '$'
+             || *nbeg == '}'
+             || *nbeg == '\\' )
+           {
+             nend = vend = vbeg+2;
+             return true;
+           }
+           return false;
+         }
+
+         // asserted: *vbeg == '$'
+         // vbeg: [$]{variable:-word} / [$]{variable}
+         // nbeg: ${[v]ariable:-word} / ${[v]ariable}
+         bool braced = ( *(vbeg+1) == '{' ); //}
+         nbeg = vbeg+( braced ? 2 : 1 );
+         if ( !isnamech( *nbeg ) )     // don't allow empty var name
+           return false;
+         for ( nend = nbeg+1; isnamech( *nend ); ++nend )
+         {;} // skip over var name
+         // nend: ${variable[:]-word} / ${variable[}]
+
+         // vend: ${variable:-word}[] / ${variable}[]
+         // stay with ( vend == nullptr ) until you know it's valid
+         if ( braced )
+         {
+           if ( *nend == '}' )
+           {
+             vend = nend+1;
+           }
+           else if ( *nend == ':' )
+           {
+             const char * scan = nend+1;
+             if ( *scan == '+' || *scan == '-' )
+             {
+               ++scan;
+               // find first not escaped '}'
+               while ( *scan )
+               {
+                 if ( *scan == '\\' )
+                 {
+                   ++scan;     // next char is skipped
+                   if ( *scan )
+                     ++scan;
+                 }
+                 else if ( *scan == '$' )
+                 {
+                   // an embedded var?
+                   if ( ! (scan = findVarEnd( scan )) )
+                     return false;
+                 }
+                 else if ( *scan == '}' )
+                 {
+                   vend = scan+1;      // ==> unesacped '}', we're done!
+                   break;
+                 }
+                 else
+                   ++scan;     // literal
+               }
+               // ( ! *scan ) => end of string while looking for unesacped '}'
+             }
+             else
+               ; // err: ':' not followed by '+' or '-'
+           }
+           else
+             ; // err: braced name must end with '}' or ':'
+         }
+         else
+         {
+           vend = nend;        // un-braced
+         }
+         return( vend != nullptr );
+       }
+      };
+
+      bool _expand( std::string &, const std::string & value_r, unsigned level_r, RepoVarExpand::VarRetriever & varRetriever_r );
+
+      inline std::string expand( const std::string & value_r, unsigned level_r, RepoVarExpand::VarRetriever & varRetriever_r )
+      {
+       std::string ret;
+       if ( ! _expand( ret, value_r, level_r, varRetriever_r ) )
+         ret = value_r;
+       return ret;
+      }
+
+      inline std::string expand( std::string && value_r, unsigned level_r, RepoVarExpand::VarRetriever & varRetriever_r )
+      {
+       std::string ret;
+       if ( ! _expand( ret, value_r, level_r, varRetriever_r ) )
+         ret = std::move(value_r);
+       return ret;
+      }
+
+      /** Expand variables in \a value_r depending on \a level-r
+      * <tt>level_r > 0</tt> may have escaped chars outside braces.
+      */
+      inline bool _expand( std::string & result_r, const std::string & value_r, unsigned level_r, RepoVarExpand::VarRetriever & varRetriever_r )
+      {
+#if ( ZYPP_DBG_VAREXPAND )
+       cout << std::string(  2*level_r, ' ' ) << "\033[7m>>" << value_r << "<<\033[27m" << endl;
+       std::ostringstream dbg;
+       const char * dbgsbeg = value_r.c_str(); // track vars we already added to dbg
+       unsigned dbgi = 0;                      // color 1-5 var / 6 moved value_r
+       dbg << std::string(  2*level_r, ' ' ) << ">>";
+#endif // ZYPP_DBG_VAREXPAND
+
+       bool expanded = false;
+
+       if ( ! value_r.empty() )
+       {
+         FindVar scan( value_r, level_r );     // level_r > 0 is embedded
+         while ( scan.nextVar() )
+         {
+           static const std::string _emptyValue;
+           const std::string *const knownVar = ( varRetriever_r ? varRetriever_r( scan.varName() ) : nullptr );
+           const std::string & varValue( knownVar ? *knownVar : _emptyValue );
+
+#if ( ZYPP_DBG_VAREXPAND )
+           dbg << std::string(dbgsbeg,scan._vbeg) << "\033[3" << ((dbgi%5)+1) << "m" << scan.var() << "\033[0m";
+           cout << dbg.str() << "|<< " << scan.varName() << " " << (knownVar?"("+varValue+")":"-") << " {" << scan.varEmbedded() << "}" << endl;
+           dbgsbeg = scan._vend;
+           dbgi++;
+#endif // ZYPP_DBG_VAREXPAND
+
+           bool mustSubstitute = false;        // keep original text per default
+           std::string substitutionValue;
+
+           int varType = scan.varType();
+           if ( varType == '$' )       // plain var
+           {
+             if ( knownVar )
+             {
+               mustSubstitute = true;
+               substitutionValue = varValue;
+             }
+             else
+               ; // keep original text per default
+           }
+           else if ( varType == '-' ) // ':-' default value
+           {
+             mustSubstitute = true;
+             if ( varValue.empty() )
+               substitutionValue = expand( scan.varEmbedded(), level_r+1, varRetriever_r );
+             else
+               substitutionValue = varValue;
+           }
+           else if ( varType == '+' ) // ':+' alternate value
+           {
+             mustSubstitute = true;
+             if ( ! varValue.empty() )
+               substitutionValue = expand( scan.varEmbedded(), level_r+1, varRetriever_r );
+             else
+               ; // empty substitutionValue
+           }
+           else if ( varType == '\\' ) // backslash escaped literal (in varName)
+           {
+             mustSubstitute = true;
+             substitutionValue = scan.varName();
+           }
+           else
+             ; // keep original text per default
+
+           if ( mustSubstitute  )
+           {
+             if ( scan.hasVarPrefix() )
+               result_r += scan.varPrefix();
+             if ( ! substitutionValue.empty() )
+               result_r += substitutionValue;
+             scan.wroteVar(); // this moves scan._sbeg so we can later see what's already written
+           }
+         }
+
+#if ( ZYPP_DBG_VAREXPAND )
+         dbg << std::string( dbgsbeg ) << (scan._sbeg == value_r.c_str() ? "<<\033[36m(moved)\033[0m" : "");
+#endif // ZYPP_DBG_VAREXPAND
+
+         // handle unwritten data:
+         if ( scan._sbeg != value_r.c_str() )
+         {
+           expanded = true;
+           if ( *scan._sbeg )
+             result_r += std::string( scan._sbeg );
+         }
+         else
+           ; // no replacements at all
+       }
+
+#if ( ZYPP_DBG_VAREXPAND )
+       dbg << "<<";
+       cout << dbg.str() << endl;
+       cout << std::string(  2*level_r, ' ' ) << "\033[36m->" << result_r << "<-\033[0m" << endl;
+#endif // ZYPP_DBG_VAREXPAND
+       return expanded;
+      }
+    } // namespace
+    ///////////////////////////////////////////////////////////////////
+
+    std::string RepoVarExpand::operator()( const std::string & value_r, VarRetriever varRetriever_r ) const
+    { return expand( value_r, 0, varRetriever_r ); }
+
+    std::string RepoVarExpand::operator()( std::string && value_r, VarRetriever varRetriever_r ) const
+    { return expand( std::move(value_r), 0, varRetriever_r ); }
+
+    ///////////////////////////////////////////////////////////////////
+    // RepoVariables*Replace
+    ///////////////////////////////////////////////////////////////////
+    namespace
+    {
+      /** \brief Provide lazy initialized repo variables
+       */
+      struct RepoVars : private zypp::base::NonCopyable
+      {
+       typedef const std::string & (RepoVars::*Getter)() const;
+
+       const std::string & arch() const
+       {
+         assertArchStr();
+         return _arch;
+       }
+
+       const std::string & basearch() const
+       {
+         assertArchStr();
+         return _basearch;
+       }
+
+       const std::string & releasever() const
+       {
+         assertReleaseverStr();
+         return _releasever;
+       }
+
+       const std::string & releaseverMajor() const
+       {
+         assertReleaseverStr();
+         return _releaseverMajor;
+       }
+
+       const std::string & releaseverMinor() const
+       {
+         assertReleaseverStr();
+         return _releaseverMinor;
+       }
+
+      private:
+       void assertArchStr() const
+       {
+         if ( _arch.empty() )
+         {
+           Arch arch( ZConfig::instance().systemArchitecture() );
+           _arch = arch.asString();
+           _basearch = arch.baseArch().asString();
+         }
+       }
+
+       void assertReleaseverStr() const
+       {
+         if ( _releasever.empty() )
+         {
+           _releasever = env::ZYPP_REPO_RELEASEVER();
+           if( _releasever.empty() )
+             _releasever = Target::distributionVersion( Pathname()/*guess*/ );
+           else
+             WAR << "ENV overwrites $releasever=" << _releasever << endl;
+
+           // split major/minor for SLE
+           std::string::size_type pos = _releasever.find( "." );
+           if ( pos == std::string::npos )
+           {
+             _releaseverMajor = _releasever;
+             _releaseverMinor.clear();
+           }
+           else
+           {
+             _releaseverMajor = _releasever.substr( 0, pos );
+             _releaseverMinor = _releasever.substr( pos+1 ) ;
+           }
+         }
+       }
+      private:
+       mutable std::string _arch;
+       mutable std::string _basearch;
+       mutable std::string _releasever;
+       mutable std::string _releaseverMajor;
+       mutable std::string _releaseverMinor;
+      };
+
+      /** \brief */
+      const std::string * repoVarLookup( const std::string & name_r )
+      {
+       RepoVars::Getter getter = nullptr;
+       switch ( name_r.size() )
+       {
+#define ASSIGN_IF(NAME,GETTER) if ( name_r == NAME ) getter = GETTER
+         case  4:      ASSIGN_IF( "arch",              &RepoVars::arch );              break;
+         case  8:      ASSIGN_IF( "basearch",          &RepoVars::basearch );          break;
+         case 10:      ASSIGN_IF( "releasever",        &RepoVars::releasever );        break;
+         case 16:      ASSIGN_IF( "releasever_major",  &RepoVars::releaseverMajor );
+             else      ASSIGN_IF( "releasever_minor",  &RepoVars::releaseverMinor );   break;
+#undef ASSIGN_IF
+       }
+
+       const std::string * ret = nullptr;
+       if ( getter )   // known var
+       {
+         static const RepoVars _repoVars;
+         ret = &(_repoVars.*getter)();
+       }
+       return ret;
+      }
+    } // namespace
+    ///////////////////////////////////////////////////////////////////
 
-  return newurl;
-}
+    std::string RepoVariablesStringReplacer::operator()( const std::string & value ) const
+    {
+      return RepoVarExpand()( value, repoVarLookup );
+    }
+    std::string RepoVariablesStringReplacer::operator()( std::string && value ) const
+    {
+      return RepoVarExpand()( value, repoVarLookup );
+    }
 
-} // ns repo
-} // ns zypp
+    Url RepoVariablesUrlReplacer::operator()( const Url & value ) const
+    {
+      RepoVarExpand expand;
+      Url newurl( value );
+      newurl.setPathData( expand( value.getPathData(), repoVarLookup ) );
+      newurl.setQueryString( expand( value.getQueryString(), repoVarLookup ) );
+      return newurl;
+    }
 
-// vim: set ts=2 sts=2 sw=2 et ai:
+  } // namespace repo
+  ///////////////////////////////////////////////////////////////////
+} // namespace zypp
+///////////////////////////////////////////////////////////////////