Imported Upstream version 15.0.0
[platform/upstream/libzypp.git] / zypp / repo / RepoVariables.cc
1 /*---------------------------------------------------------------------\
2 |                          ____ _   __ __ ___                          |
3 |                         |__  / \ / / . \ . \                         |
4 |                           / / \ V /|  _/  _/                         |
5 |                          / /__ | | | | | |                           |
6 |                         /_____||_| |_| |_|                           |
7 |                                                                      |
8 \---------------------------------------------------------------------*/
9 #include <cstring>
10
11 #define ZYPP_DBG_VAREXPAND 0
12 #if ( ZYPP_DBG_VAREXPAND )
13 #warning ZYPP_DBG_VAREXPAND is on
14 #include <iostream>
15 #include <sstream>
16 using std::cout;
17 using std::endl;
18 #endif // ZYPP_DBG_VAREXPAND
19
20 #include "zypp/base/LogTools.h"
21 #include "zypp/base/String.h"
22 #include "zypp/base/Regex.h"
23
24 #include "zypp/ZConfig.h"
25 #include "zypp/Target.h"
26 #include "zypp/Arch.h"
27 #include "zypp/repo/RepoVariables.h"
28 #include "zypp/base/NonCopyable.h"
29
30 ///////////////////////////////////////////////////////////////////
31 namespace zypp
32 {
33   namespace env
34   {
35     /** Use faked releasever (e.g. for 'zupper dup' to next distro version */
36     inline std::string ZYPP_REPO_RELEASEVER()
37     {
38       const char * env = getenv("ZYPP_REPO_RELEASEVER");
39       return( env ? env : "" );
40     }
41   }
42
43   ///////////////////////////////////////////////////////////////////
44   namespace repo
45   {
46     ///////////////////////////////////////////////////////////////////
47     // RepoVarExpand
48     ///////////////////////////////////////////////////////////////////
49     namespace
50     {
51       ///////////////////////////////////////////////////////////////////
52       /// \class FindVar
53       /// \brief Helper scanning for variable definitions in a string
54       ///////////////////////////////////////////////////////////////////
55       struct FindVar
56       {
57         bool _embedded;         ///< A (formerly) embedded string may have esacped \c $, \c closebrace and \c backslash
58         const char * _sbeg;     ///< start of string to scan
59         const char * _vbeg;     ///< [$]{variable:-word} / [$]{variable} / if embedded also on [\\]
60         const char * _nbeg;     ///< ${[v]ariable:-word} / ${[v]ariable}
61         const char * _nend;     ///< ${variable[:]-word} / ${variable[}]
62         const char * _vend;     ///< ${variable:-word}[] / ${variable}[]
63         const char * _send;     ///< end of scan (next $ or nullptr if done)
64
65         FindVar( const std::string & str_r, bool embedded_r )
66         : _embedded( embedded_r )
67         , _sbeg( str_r.c_str() )
68         , _vbeg( nullptr )
69         , _nbeg( nullptr )
70         , _nend( nullptr )
71         , _vend( nullptr )
72         , _send( findVarStart( _sbeg ) )
73         {}
74
75         /** Nullptr in _send indicates we scanned the whole string. */
76         bool done() const
77         { return !_send; }
78
79         /** Advance to first/next var if there is one */
80         bool nextVar()
81         {
82           if ( done() )
83             return false;
84
85           do {
86             if ( _vbeg && !_vend )      // loop internal: no findVarEnd at current $; skip it
87               _send = findVarStart( _vbeg+1 );
88             _vbeg = _send;              // next $ or null if string end
89             _nbeg = _nend = _vend = _send = nullptr;
90             if ( ! _vbeg )              // done!
91               return false;
92           } while( ! findVarEnd() );
93
94           return true;
95         }
96
97         /** Valid _vend indicates valid var data in scan. */
98         bool hasVar() const
99         { return _vend; }
100
101         //
102         // Methods below are only valid if hasVar() == true
103         //
104
105         /** Return the full var text */
106         std::string var() const
107         { return std::string( _vbeg, _vend ); }
108
109         /** Return the var name */
110         std::string varName() const
111         { return std::string( _nbeg, _nend ); }
112
113         /** Whether this is a conditional var (${..:[+-]...}) */
114         bool varIsConditional() const
115         { return( *(_vbeg+1) == '{' && *_nend == ':' ); }
116
117         /** The var type: \c \, \c $, \c - , \c +
118         * \li \c \ backslash escaped literal
119         * \li \c $      plain variable
120         * \li \c - conditional: default value
121         * \li \c + conditional: alternate value
122         */
123         int varType() const
124         { return( varIsConditional() ? *(_nend+1) : *_vbeg ); }
125
126         /** Return embedded value in conditional vars or empty string */
127         std::string varEmbedded() const
128         { return( varIsConditional() ? std::string( _nend+2, _vend-1 ) : std::string() ); }
129
130
131         /** Have unwritten data before var? */
132         bool hasVarPrefix() const
133         { return ( _sbeg != _vbeg ); }
134
135         /** Return unwritten data before var */
136         std::string varPrefix() const
137         { return std::string( _sbeg, _vbeg ); }
138
139         /** Indicate all data up to _vend were written */
140         void wroteVar()
141         { _sbeg = _vend; }
142
143       private:
144         /** Return next \c $ */
145         const char * findVarStart( const char * sbeg_r ) const
146         {
147           for ( ; *sbeg_r; ++sbeg_r )
148             if ( *sbeg_r == '$' || ( _embedded && *sbeg_r == '\\' ) )
149               return sbeg_r;
150           return nullptr;
151         }
152
153         /** Valid var name char */
154         bool isnamech( int ch ) const
155         { return ch == '_' || isalpha( ch ); }
156
157         /** Scan for a valid variable starting at _vbeg (storing the values) */
158         bool findVarEnd()
159         {
160           // asserted: *_vbeg == '$' || '\\'
161           if ( ! findVarEnd( _vbeg, _nbeg, _nend, _vend ) )
162             return false;
163           _send = findVarStart( _vend );
164           return true;
165         }
166
167         /** Skip over valid variable starting at vbeg (return end in \a vend). */
168         const char * findVarEnd( const char * vbeg ) const
169         {
170           // asserted: *_vbeg == '$'
171           const char * nbeg = nullptr;
172           const char * nend = nullptr;
173           const char * vend = nullptr;
174           findVarEnd( vbeg, nbeg, nend, vend );
175           return vend;
176         }
177
178         /** Scan for a valid variable starting at vbeg (const version returning the values). */
179         bool findVarEnd( const char * vbeg, const char *& nbeg, const char *& nend, const char *& vend ) const
180         {
181           // embedded only: handle backslash escaped chars
182           if ( *_vbeg == '\\' )
183           {
184             nbeg = vbeg+1;
185             if ( *nbeg == '$'
186               || *nbeg == '}'
187               || *nbeg == '\\' )
188             {
189               nend = vend = vbeg+2;
190               return true;
191             }
192             return false;
193           }
194
195           // asserted: *vbeg == '$'
196           // vbeg: [$]{variable:-word} / [$]{variable}
197           // nbeg: ${[v]ariable:-word} / ${[v]ariable}
198           bool braced = ( *(vbeg+1) == '{' ); //}
199           nbeg = vbeg+( braced ? 2 : 1 );
200           if ( !isnamech( *nbeg ) )     // don't allow empty var name
201             return false;
202           for ( nend = nbeg+1; isnamech( *nend ); ++nend )
203           {;} // skip over var name
204           // nend: ${variable[:]-word} / ${variable[}]
205
206           // vend: ${variable:-word}[] / ${variable}[]
207           // stay with ( vend == nullptr ) until you know it's valid
208           if ( braced )
209           {
210             if ( *nend == '}' )
211             {
212               vend = nend+1;
213             }
214             else if ( *nend == ':' )
215             {
216               const char * scan = nend+1;
217               if ( *scan == '+' || *scan == '-' )
218               {
219                 ++scan;
220                 // find first not escaped '}'
221                 while ( *scan )
222                 {
223                   if ( *scan == '\\' )
224                   {
225                     ++scan;     // next char is skipped
226                     if ( *scan )
227                       ++scan;
228                   }
229                   else if ( *scan == '$' )
230                   {
231                     // an embedded var?
232                     if ( ! (scan = findVarEnd( scan )) )
233                       return false;
234                   }
235                   else if ( *scan == '}' )
236                   {
237                     vend = scan+1;      // ==> unesacped '}', we're done!
238                     break;
239                   }
240                   else
241                     ++scan;     // literal
242                 }
243                 // ( ! *scan ) => end of string while looking for unesacped '}'
244               }
245               else
246                 ; // err: ':' not followed by '+' or '-'
247             }
248             else
249               ; // err: braced name must end with '}' or ':'
250           }
251           else
252           {
253             vend = nend;        // un-braced
254           }
255           return( vend != nullptr );
256         }
257       };
258
259       bool _expand( std::string &, const std::string & value_r, unsigned level_r, RepoVarExpand::VarRetriever & varRetriever_r );
260
261       inline std::string expand( const std::string & value_r, unsigned level_r, RepoVarExpand::VarRetriever & varRetriever_r )
262       {
263         std::string ret;
264         if ( ! _expand( ret, value_r, level_r, varRetriever_r ) )
265           ret = value_r;
266         return ret;
267       }
268
269       inline std::string expand( std::string && value_r, unsigned level_r, RepoVarExpand::VarRetriever & varRetriever_r )
270       {
271         std::string ret;
272         if ( ! _expand( ret, value_r, level_r, varRetriever_r ) )
273           ret = std::move(value_r);
274         return ret;
275       }
276
277       /** Expand variables in \a value_r depending on \a level-r
278       * <tt>level_r > 0</tt> may have escaped chars outside braces.
279       */
280       inline bool _expand( std::string & result_r, const std::string & value_r, unsigned level_r, RepoVarExpand::VarRetriever & varRetriever_r )
281       {
282 #if ( ZYPP_DBG_VAREXPAND )
283         cout << std::string(  2*level_r, ' ' ) << "\033[7m>>" << value_r << "<<\033[27m" << endl;
284         std::ostringstream dbg;
285         const char * dbgsbeg = value_r.c_str(); // track vars we already added to dbg
286         unsigned dbgi = 0;                      // color 1-5 var / 6 moved value_r
287         dbg << std::string(  2*level_r, ' ' ) << ">>";
288 #endif // ZYPP_DBG_VAREXPAND
289
290         bool expanded = false;
291
292         if ( ! value_r.empty() )
293         {
294           FindVar scan( value_r, level_r );     // level_r > 0 is embedded
295           while ( scan.nextVar() )
296           {
297             static const std::string _emptyValue;
298             const std::string *const knownVar = ( varRetriever_r ? varRetriever_r( scan.varName() ) : nullptr );
299             const std::string & varValue( knownVar ? *knownVar : _emptyValue );
300
301 #if ( ZYPP_DBG_VAREXPAND )
302             dbg << std::string(dbgsbeg,scan._vbeg) << "\033[3" << ((dbgi%5)+1) << "m" << scan.var() << "\033[0m";
303             cout << dbg.str() << "|<< " << scan.varName() << " " << (knownVar?"("+varValue+")":"-") << " {" << scan.varEmbedded() << "}" << endl;
304             dbgsbeg = scan._vend;
305             dbgi++;
306 #endif // ZYPP_DBG_VAREXPAND
307
308             bool mustSubstitute = false;        // keep original text per default
309             std::string substitutionValue;
310
311             int varType = scan.varType();
312             if ( varType == '$' )       // plain var
313             {
314               if ( knownVar )
315               {
316                 mustSubstitute = true;
317                 substitutionValue = varValue;
318               }
319               else
320                 ; // keep original text per default
321             }
322             else if ( varType == '-' ) // ':-' default value
323             {
324               mustSubstitute = true;
325               if ( varValue.empty() )
326                 substitutionValue = expand( scan.varEmbedded(), level_r+1, varRetriever_r );
327               else
328                 substitutionValue = varValue;
329             }
330             else if ( varType == '+' ) // ':+' alternate value
331             {
332               mustSubstitute = true;
333               if ( ! varValue.empty() )
334                 substitutionValue = expand( scan.varEmbedded(), level_r+1, varRetriever_r );
335               else
336                 ; // empty substitutionValue
337             }
338             else if ( varType == '\\' ) // backslash escaped literal (in varName)
339             {
340               mustSubstitute = true;
341               substitutionValue = scan.varName();
342             }
343             else
344               ; // keep original text per default
345
346             if ( mustSubstitute  )
347             {
348               if ( scan.hasVarPrefix() )
349                 result_r += scan.varPrefix();
350               if ( ! substitutionValue.empty() )
351                 result_r += substitutionValue;
352               scan.wroteVar(); // this moves scan._sbeg so we can later see what's already written
353             }
354           }
355
356 #if ( ZYPP_DBG_VAREXPAND )
357           dbg << std::string( dbgsbeg ) << (scan._sbeg == value_r.c_str() ? "<<\033[36m(moved)\033[0m" : "");
358 #endif // ZYPP_DBG_VAREXPAND
359
360           // handle unwritten data:
361           if ( scan._sbeg != value_r.c_str() )
362           {
363             expanded = true;
364             if ( *scan._sbeg )
365               result_r += std::string( scan._sbeg );
366           }
367           else
368             ; // no replacements at all
369         }
370
371 #if ( ZYPP_DBG_VAREXPAND )
372         dbg << "<<";
373         cout << dbg.str() << endl;
374         cout << std::string(  2*level_r, ' ' ) << "\033[36m->" << result_r << "<-\033[0m" << endl;
375 #endif // ZYPP_DBG_VAREXPAND
376         return expanded;
377       }
378     } // namespace
379     ///////////////////////////////////////////////////////////////////
380
381     std::string RepoVarExpand::operator()( const std::string & value_r, VarRetriever varRetriever_r ) const
382     { return expand( value_r, 0, varRetriever_r ); }
383
384     std::string RepoVarExpand::operator()( std::string && value_r, VarRetriever varRetriever_r ) const
385     { return expand( std::move(value_r), 0, varRetriever_r ); }
386
387     ///////////////////////////////////////////////////////////////////
388     // RepoVariables*Replace
389     ///////////////////////////////////////////////////////////////////
390     namespace
391     {
392       /** \brief Provide lazy initialized repo variables
393        */
394       struct RepoVars : private zypp::base::NonCopyable
395       {
396         typedef const std::string & (RepoVars::*Getter)() const;
397
398         const std::string & arch() const
399         {
400           assertArchStr();
401           return _arch;
402         }
403
404         const std::string & basearch() const
405         {
406           assertArchStr();
407           return _basearch;
408         }
409
410         const std::string & releasever() const
411         {
412           assertReleaseverStr();
413           return _releasever;
414         }
415
416         const std::string & releaseverMajor() const
417         {
418           assertReleaseverStr();
419           return _releaseverMajor;
420         }
421
422         const std::string & releaseverMinor() const
423         {
424           assertReleaseverStr();
425           return _releaseverMinor;
426         }
427
428       private:
429         void assertArchStr() const
430         {
431           if ( _arch.empty() )
432           {
433             Arch arch( ZConfig::instance().systemArchitecture() );
434             _arch = arch.asString();
435             _basearch = arch.baseArch().asString();
436           }
437         }
438
439         void assertReleaseverStr() const
440         {
441           if ( _releasever.empty() )
442           {
443             _releasever = env::ZYPP_REPO_RELEASEVER();
444             if( _releasever.empty() )
445               _releasever = Target::distributionVersion( Pathname()/*guess*/ );
446             else
447               WAR << "ENV overwrites $releasever=" << _releasever << endl;
448
449             // split major/minor for SLE
450             std::string::size_type pos = _releasever.find( "." );
451             if ( pos == std::string::npos )
452             {
453               _releaseverMajor = _releasever;
454               _releaseverMinor.clear();
455             }
456             else
457             {
458               _releaseverMajor = _releasever.substr( 0, pos );
459               _releaseverMinor = _releasever.substr( pos+1 ) ;
460             }
461           }
462         }
463       private:
464         mutable std::string _arch;
465         mutable std::string _basearch;
466         mutable std::string _releasever;
467         mutable std::string _releaseverMajor;
468         mutable std::string _releaseverMinor;
469       };
470
471       /** \brief */
472       const std::string * repoVarLookup( const std::string & name_r )
473       {
474         RepoVars::Getter getter = nullptr;
475         switch ( name_r.size() )
476         {
477 #define ASSIGN_IF(NAME,GETTER) if ( name_r == NAME ) getter = GETTER
478           case  4:      ASSIGN_IF( "arch",              &RepoVars::arch );              break;
479           case  8:      ASSIGN_IF( "basearch",          &RepoVars::basearch );          break;
480           case 10:      ASSIGN_IF( "releasever",        &RepoVars::releasever );        break;
481           case 16:      ASSIGN_IF( "releasever_major",  &RepoVars::releaseverMajor );
482               else      ASSIGN_IF( "releasever_minor",  &RepoVars::releaseverMinor );   break;
483 #undef ASSIGN_IF
484         }
485
486         const std::string * ret = nullptr;
487         if ( getter )   // known var
488         {
489           static const RepoVars _repoVars;
490           ret = &(_repoVars.*getter)();
491         }
492         return ret;
493       }
494     } // namespace
495     ///////////////////////////////////////////////////////////////////
496
497     std::string RepoVariablesStringReplacer::operator()( const std::string & value ) const
498     {
499       return RepoVarExpand()( value, repoVarLookup );
500     }
501     std::string RepoVariablesStringReplacer::operator()( std::string && value ) const
502     {
503       return RepoVarExpand()( value, repoVarLookup );
504     }
505
506     Url RepoVariablesUrlReplacer::operator()( const Url & value ) const
507     {
508       RepoVarExpand expand;
509       Url newurl( value );
510       newurl.setPathData( expand( value.getPathData(), repoVarLookup ) );
511       newurl.setQueryString( expand( value.getQueryString(), repoVarLookup ) );
512       return newurl;
513     }
514
515   } // namespace repo
516   ///////////////////////////////////////////////////////////////////
517 } // namespace zypp
518 ///////////////////////////////////////////////////////////////////