Imported Upstream version 17.25.4
[platform/upstream/libzypp.git] / zypp / repo / RepoVariables.cc
1 /*---------------------------------------------------------------------\
2 |                          ____ _   __ __ ___                          |
3 |                         |__  / \ / / . \ . \                         |
4 |                           / / \ V /|  _/  _/                         |
5 |                          / /__ | | | | | |                           |
6 |                         /_____||_| |_| |_|                           |
7 |                                                                      |
8 \---------------------------------------------------------------------*/
9 #include <iostream>
10 #include <fstream>
11
12 #include <zypp/base/LogTools.h>
13 #include <zypp/base/String.h>
14 #include <zypp/base/Regex.h>
15
16 #include <zypp/ZYppFactory.h>
17 #include <zypp/ZConfig.h>
18 #include <zypp/Target.h>
19 #include <zypp/Arch.h>
20 #include <zypp/repo/RepoVariables.h>
21 #include <zypp/base/NonCopyable.h>
22
23 #define ZYPP_DBG_VAREXPAND 0
24 #if ( ZYPP_DBG_VAREXPAND )
25 #warning ZYPP_DBG_VAREXPAND is on
26 using std::cout;
27 #endif // ZYPP_DBG_VAREXPAND
28
29 ///////////////////////////////////////////////////////////////////
30 namespace zypp
31 {
32   namespace env
33   {
34     /** Use faked releasever (e.g. for 'zupper dup' to next distro version */
35     inline std::string ZYPP_REPO_RELEASEVER()
36     {
37       const char * env = getenv("ZYPP_REPO_RELEASEVER");
38       return( env ? env : "" );
39     }
40   }
41
42   ///////////////////////////////////////////////////////////////////
43   namespace repo
44   {
45     ///////////////////////////////////////////////////////////////////
46     // RepoVarExpand
47     ///////////////////////////////////////////////////////////////////
48     namespace
49     {
50       ///////////////////////////////////////////////////////////////////
51       /// \class FindVar
52       /// \brief Helper scanning for variable definitions in a string
53       ///////////////////////////////////////////////////////////////////
54       struct FindVar
55       {
56         bool _embedded;         ///< A (formerly) embedded string may have esacped \c $, \c closebrace and \c backslash
57         const char * _sbeg;     ///< start of string to scan
58         const char * _vbeg;     ///< [$]{variable:-word} / [$]{variable} / if embedded also on [\\]
59         const char * _nbeg;     ///< ${[v]ariable:-word} / ${[v]ariable}
60         const char * _nend;     ///< ${variable[:]-word} / ${variable[}]
61         const char * _vend;     ///< ${variable:-word}[] / ${variable}[]
62         const char * _send;     ///< end of scan (next $ or nullptr if done)
63
64         FindVar( const std::string & str_r, bool embedded_r )
65         : _embedded( embedded_r )
66         , _sbeg( str_r.c_str() )
67         , _vbeg( nullptr )
68         , _nbeg( nullptr )
69         , _nend( nullptr )
70         , _vend( nullptr )
71         , _send( findVarStart( _sbeg ) )
72         {}
73
74         /** Nullptr in _send indicates we scanned the whole string. */
75         bool done() const
76         { return !_send; }
77
78         /** Advance to first/next var if there is one */
79         bool nextVar()
80         {
81           if ( done() )
82             return false;
83
84           do {
85             if ( _vbeg && !_vend )      // loop internal: no findVarEnd at current $; skip it
86               _send = findVarStart( _vbeg+1 );
87             _vbeg = _send;              // next $ or null if string end
88             _nbeg = _nend = _vend = _send = nullptr;
89             if ( ! _vbeg )              // done!
90               return false;
91           } while( ! findVarEnd() );
92
93           return true;
94         }
95
96         /** Valid _vend indicates valid var data in scan. */
97         bool hasVar() const
98         { return _vend; }
99
100         //
101         // Methods below are only valid if hasVar() == true
102         //
103
104         /** Return the full var text */
105         std::string var() const
106         { return std::string( _vbeg, _vend ); }
107
108         /** Return the var name */
109         std::string varName() const
110         { return std::string( _nbeg, _nend ); }
111
112         /** Whether this is a conditional var (${..:[+-]...}) */
113         bool varIsConditional() const
114         { return( *(_vbeg+1) == '{' && *_nend == ':' ); }
115
116         /** The var type: \c \, \c $, \c - , \c +
117         * \li \c \ backslash escaped literal
118         * \li \c $      plain variable
119         * \li \c - conditional: default value
120         * \li \c + conditional: alternate value
121         */
122         int varType() const
123         { return( varIsConditional() ? *(_nend+1) : *_vbeg ); }
124
125         /** Return embedded value in conditional vars or empty string */
126         std::string varEmbedded() const
127         { return( varIsConditional() ? std::string( _nend+2, _vend-1 ) : std::string() ); }
128
129
130         /** Have unwritten data before var? */
131         bool hasVarPrefix() const
132         { return ( _sbeg != _vbeg ); }
133
134         /** Return unwritten data before var */
135         std::string varPrefix() const
136         { return std::string( _sbeg, _vbeg ); }
137
138         /** Indicate all data up to _vend were written */
139         void wroteVar()
140         { _sbeg = _vend; }
141
142       private:
143         /** Return next \c $ */
144         const char * findVarStart( const char * sbeg_r ) const
145         {
146           for ( ; *sbeg_r; ++sbeg_r )
147             if ( *sbeg_r == '$' || ( _embedded && *sbeg_r == '\\' ) )
148               return sbeg_r;
149           return nullptr;
150         }
151
152         /** Valid var name char */
153         bool isnamech( int ch ) const
154         { return ch == '_' || isalnum( ch ); }
155
156         /** Scan for a valid variable starting at _vbeg (storing the values) */
157         bool findVarEnd()
158         {
159           // asserted: *_vbeg == '$' || '\\'
160           if ( ! findVarEnd( _vbeg, _nbeg, _nend, _vend ) )
161             return false;
162           _send = findVarStart( _vend );
163           return true;
164         }
165
166         /** Skip over valid variable starting at vbeg (return end in \a vend). */
167         const char * findVarEnd( const char * vbeg ) const
168         {
169           // asserted: *_vbeg == '$'
170           const char * nbeg = nullptr;
171           const char * nend = nullptr;
172           const char * vend = nullptr;
173           findVarEnd( vbeg, nbeg, nend, vend );
174           return vend;
175         }
176
177         /** Scan for a valid variable starting at vbeg (const version returning the values). */
178         bool findVarEnd( const char * vbeg, const char *& nbeg, const char *& nend, const char *& vend ) const
179         {
180           // embedded only: handle backslash escaped chars
181           if ( *_vbeg == '\\' )
182           {
183             nbeg = vbeg+1;
184             if ( *nbeg == '$'
185               || *nbeg == '}'
186               || *nbeg == '\\' )
187             {
188               nend = vend = vbeg+2;
189               return true;
190             }
191             return false;
192           }
193
194           // asserted: *vbeg == '$'
195           // vbeg: [$]{variable:-word} / [$]{variable}
196           // nbeg: ${[v]ariable:-word} / ${[v]ariable}
197           bool braced = ( *(vbeg+1) == '{' ); //}
198           nbeg = vbeg+( braced ? 2 : 1 );
199           if ( !isnamech( *nbeg ) )     // don't allow empty var name
200             return false;
201           for ( nend = nbeg+1; isnamech( *nend ); ++nend )
202           {;} // skip over var name
203           // nend: ${variable[:]-word} / ${variable[}]
204
205           // vend: ${variable:-word}[] / ${variable}[]
206           // stay with ( vend == nullptr ) until you know it's valid
207           if ( braced )
208           {
209             if ( *nend == '}' )
210             {
211               vend = nend+1;
212             }
213             else if ( *nend == ':' )
214             {
215               const char * scan = nend+1;
216               if ( *scan == '+' || *scan == '-' )
217               {
218                 ++scan;
219                 // find first not escaped '}'
220                 while ( *scan )
221                 {
222                   if ( *scan == '\\' )
223                   {
224                     ++scan;     // next char is skipped
225                     if ( *scan )
226                       ++scan;
227                   }
228                   else if ( *scan == '$' )
229                   {
230                     // an embedded var?
231                     if ( ! (scan = findVarEnd( scan )) )
232                       return false;
233                   }
234                   else if ( *scan == '}' )
235                   {
236                     vend = scan+1;      // ==> unesacped '}', we're done!
237                     break;
238                   }
239                   else
240                     ++scan;     // literal
241                 }
242                 // ( ! *scan ) => end of string while looking for unesacped '}'
243               }
244               else
245                 ; // err: ':' not followed by '+' or '-'
246             }
247             else
248               ; // err: braced name must end with '}' or ':'
249           }
250           else
251           {
252             vend = nend;        // un-braced
253           }
254           return( vend != nullptr );
255         }
256       };
257
258       bool _expand( std::string &, const std::string & value_r, unsigned level_r, RepoVarExpand::VarRetriever & varRetriever_r );
259
260       inline std::string expand( const std::string & value_r, unsigned level_r, RepoVarExpand::VarRetriever & varRetriever_r )
261       {
262         std::string ret;
263         if ( ! _expand( ret, value_r, level_r, varRetriever_r ) )
264           ret = value_r;
265         return ret;
266       }
267
268       inline std::string expand( std::string && value_r, unsigned level_r, RepoVarExpand::VarRetriever & varRetriever_r )
269       {
270         std::string ret;
271         if ( ! _expand( ret, value_r, level_r, varRetriever_r ) )
272           ret = std::move(value_r);
273         return ret;
274       }
275
276       /** Expand variables in \a value_r depending on \a level-r
277       * <tt>level_r > 0</tt> may have escaped chars outside braces.
278       */
279       inline bool _expand( std::string & result_r, const std::string & value_r, unsigned level_r, RepoVarExpand::VarRetriever & varRetriever_r )
280       {
281 #if ( ZYPP_DBG_VAREXPAND )
282         cout << std::string(  2*level_r, ' ' ) << "\033[7m>>" << value_r << "<<\033[27m" << endl;
283         std::ostringstream dbg;
284         const char * dbgsbeg = value_r.c_str(); // track vars we already added to dbg
285         unsigned dbgi = 0;                      // color 1-5 var / 6 moved value_r
286         dbg << std::string(  2*level_r, ' ' ) << ">>";
287 #endif // ZYPP_DBG_VAREXPAND
288
289         bool expanded = false;
290
291         if ( ! value_r.empty() )
292         {
293           FindVar scan( value_r, level_r );     // level_r > 0 is embedded
294           while ( scan.nextVar() )
295           {
296             static const std::string _emptyValue;
297             const std::string *const knownVar = ( varRetriever_r ? varRetriever_r( scan.varName() ) : nullptr );
298             const std::string & varValue( knownVar ? *knownVar : _emptyValue );
299
300 #if ( ZYPP_DBG_VAREXPAND )
301             dbg << std::string(dbgsbeg,scan._vbeg) << "\033[3" << ((dbgi%5)+1) << "m" << scan.var() << "\033[0m";
302             cout << dbg.str() << "|<< " << scan.varName() << " " << (knownVar?"("+varValue+")":"-") << " {" << scan.varEmbedded() << "}" << endl;
303             dbgsbeg = scan._vend;
304             dbgi++;
305 #endif // ZYPP_DBG_VAREXPAND
306
307             bool mustSubstitute = false;        // keep original text per default
308             std::string substitutionValue;
309
310             int varType = scan.varType();
311             if ( varType == '$' )       // plain var
312             {
313               if ( knownVar )
314               {
315                 mustSubstitute = true;
316                 substitutionValue = varValue;
317               }
318               else
319                 ; // keep original text per default
320             }
321             else if ( varType == '-' ) // ':-' default value
322             {
323               mustSubstitute = true;
324               if ( varValue.empty() )
325                 substitutionValue = expand( scan.varEmbedded(), level_r+1, varRetriever_r );
326               else
327                 substitutionValue = varValue;
328             }
329             else if ( varType == '+' ) // ':+' alternate value
330             {
331               mustSubstitute = true;
332               if ( ! varValue.empty() )
333                 substitutionValue = expand( scan.varEmbedded(), level_r+1, varRetriever_r );
334               else
335                 ; // empty substitutionValue
336             }
337             else if ( varType == '\\' ) // backslash escaped literal (in varName)
338             {
339               mustSubstitute = true;
340               substitutionValue = scan.varName();
341             }
342             else
343               ; // keep original text per default
344
345             if ( mustSubstitute  )
346             {
347               if ( scan.hasVarPrefix() )
348                 result_r += scan.varPrefix();
349               if ( ! substitutionValue.empty() )
350                 result_r += substitutionValue;
351               scan.wroteVar(); // this moves scan._sbeg so we can later see what's already written
352             }
353           }
354
355 #if ( ZYPP_DBG_VAREXPAND )
356           dbg << std::string( dbgsbeg ) << (scan._sbeg == value_r.c_str() ? "<<\033[36m(moved)\033[0m" : "");
357 #endif // ZYPP_DBG_VAREXPAND
358
359           // handle unwritten data:
360           if ( scan._sbeg != value_r.c_str() )
361           {
362             expanded = true;
363             if ( *scan._sbeg )
364               result_r += std::string( scan._sbeg );
365           }
366           else
367             ; // no replacements at all
368         }
369
370 #if ( ZYPP_DBG_VAREXPAND )
371         dbg << "<<";
372         cout << dbg.str() << endl;
373         cout << std::string(  2*level_r, ' ' ) << "\033[36m->" << result_r << "<-\033[0m" << endl;
374 #endif // ZYPP_DBG_VAREXPAND
375         return expanded;
376       }
377     } // namespace
378     ///////////////////////////////////////////////////////////////////
379
380     std::string RepoVarExpand::operator()( const std::string & value_r, VarRetriever varRetriever_r ) const
381     { return expand( value_r, 0, varRetriever_r ); }
382
383     std::string RepoVarExpand::operator()( std::string && value_r, VarRetriever varRetriever_r ) const
384     { return expand( std::move(value_r), 0, varRetriever_r ); }
385
386     ///////////////////////////////////////////////////////////////////
387     // RepoVariables*Replace
388     ///////////////////////////////////////////////////////////////////
389     namespace
390     {
391       class RepoVarsMap : public std::map<std::string,std::string>
392       {
393       public:
394         static RepoVarsMap & instance()
395         { static RepoVarsMap _instance; return _instance; }
396
397         static const std::string * lookup( const std::string & name_r )
398         { return instance()._lookup( name_r ); }
399
400       private:
401         const std::string * _lookup( const std::string & name_r )
402         {
403           // Safe guard in case the caller does not own a zypp instance. In this case
404           // getZYpp()->getTarget() in checkOverride would create a zypp instance which
405           // would clear the variables parsed so far.
406           auto guard { getZYpp() };
407
408           if ( empty() )        // at init / after reset
409           {
410             // load user definitions from vars.d
411             filesystem::dirForEach( ZConfig::instance().repoManagerRoot() / ZConfig::instance().varsPath(),
412                                     filesystem::matchNoDots(), bind( &RepoVarsMap::parse, this, _1, _2 ) );
413             // releasever_major/_minor are per default derived from releasever.
414             // If releasever is userdefined, inject missing _major/_minor too.
415             deriveFromReleasever( "releasever", /*dont't overwrite user defined values*/false );
416
417             dumpOn( DBG );
418             // add builtin vars except for releasever{,_major,_minor} (see checkOverride)
419             {
420               const Arch & arch( ZConfig::instance().systemArchitecture() );
421               {
422                 std::string & var( operator[]( "arch" ) );
423                 if ( var.empty() ) var = arch.asString();
424               }
425               {
426                 std::string & var( operator[]( "basearch" ) );
427                 if ( var.empty() ) var = arch.baseArch().asString();
428               }
429             }
430           }
431
432           const std::string * ret = checkOverride( name_r );
433           if ( !ret )
434           {
435             // get value from map
436             iterator it = find( name_r );
437             if ( it != end() )
438               ret = &(it->second);
439           }
440
441           return ret;
442         }
443
444         std::ostream & dumpOn( std::ostream & str ) const
445         {
446           for ( auto && kv : *this )
447           {
448             str << '{' << kv.first << '=' << kv.second << '}' << endl;
449           }
450           return str;
451         }
452
453       private:
454         /** Get first line from file */
455         bool parse( const Pathname & dir_r, const std::string & str_r )
456         {
457           std::ifstream file( (dir_r/str_r).c_str() );
458           operator[]( str_r ) = str::getline( file, /*trim*/false );
459           return true;
460         }
461
462         /** Derive \c releasever_major/_minor from \c releasever, keeping or overwrititing existing values. */
463         void deriveFromReleasever( const std::string & stem_r, bool overwrite_r )
464         {
465           if ( count( stem_r ) )        // releasever is defined..
466           {
467             const std::string & stem_major( stem_r+"_major" );
468             const std::string & stem_minor( stem_r+"_minor" );
469             if ( overwrite_r )
470               splitReleaseverTo( operator[]( stem_r ), &operator[]( stem_major ), &operator[]( stem_minor ) );
471             else
472               splitReleaseverTo( operator[]( stem_r ),
473                                  count( stem_major ) ? nullptr : &operator[]( stem_major ),
474                                  count( stem_minor ) ? nullptr : &operator[]( stem_minor ) );
475           }
476         }
477
478         /** Split \c releasever at \c '.' and store major/minor parts as requested. */
479         void splitReleaseverTo( const std::string & releasever_r, std::string * major_r, std::string * minor_r ) const
480         {
481           if ( major_r || minor_r )
482           {
483             std::string::size_type pos = releasever_r.find( "." );
484             if ( pos == std::string::npos )
485             {
486               if ( major_r ) *major_r = releasever_r;
487               if ( minor_r ) minor_r->clear();
488             }
489             else
490             {
491               if ( major_r ) *major_r = releasever_r.substr( 0, pos );
492               if ( minor_r ) *minor_r = releasever_r.substr( pos+1 ) ;
493             }
494           }
495         }
496
497         /** Check for conditions overwriting the (user) defined values. */
498         const std::string * checkOverride( const std::string & name_r )
499         {
500           ///////////////////////////////////////////////////////////////////
501           // Always check for changing releasever{,_major,_minor} (bnc#943563)
502           if ( str::startsWith( name_r, "releasever" )
503             && ( name_r.size() == 10
504               || strcmp( name_r.c_str()+10, "_minor" ) == 0
505               || strcmp( name_r.c_str()+10, "_major" ) == 0 ) )
506           {
507             std::string val( env::ZYPP_REPO_RELEASEVER() );
508             if ( !val.empty() )
509             {
510               // $ZYPP_REPO_RELEASEVER always overwrites any defined value
511               if ( val != operator[]( "$releasever" ) )
512               {
513                 operator[]( "$releasever" ) = std::move(val);
514                 deriveFromReleasever( "$releasever", /*overwrite previous values*/true );
515               }
516               return &operator[]( "$"+name_r );
517             }
518             else if ( !count( name_r ) )
519             {
520               // No user defined value, so we follow the target
521               Target_Ptr trg( getZYpp()->getTarget() );
522               if ( trg )
523                 val = trg->distributionVersion();
524               else
525                 val = Target::distributionVersion( Pathname()/*guess*/ );
526
527               if ( val != operator[]( "$_releasever" ) )
528               {
529                 operator[]( "$_releasever" ) = std::move(val);
530                 deriveFromReleasever( "$_releasever", /*overwrite previous values*/true );
531               }
532               return &operator[]( "$_"+name_r );
533             }
534             // else:
535             return nullptr;     // get user value from map
536           }
537           ///////////////////////////////////////////////////////////////////
538
539           return nullptr;       // get user value from map
540         }
541       };
542     } // namespace
543     ///////////////////////////////////////////////////////////////////
544
545     std::string RepoVariablesStringReplacer::operator()( const std::string & value ) const
546     {
547       return RepoVarExpand()( value, RepoVarsMap::lookup );
548     }
549     std::string RepoVariablesStringReplacer::operator()( std::string && value ) const
550     {
551       return RepoVarExpand()( value, RepoVarsMap::lookup );
552     }
553
554     Url RepoVariablesUrlReplacer::operator()( const Url & value ) const
555     {
556       Url::ViewOptions toReplace = value.getViewOptions() - url::ViewOption::WITH_USERNAME - url::ViewOption::WITH_PASSWORD;
557       // Legacy: Not 100% correct because it substitutes inside the 'proxypass=' value,
558       // but this was done before as well. The final fix will have to keep the proxypasswd
559       // out side the url in a cedential file.
560       Url tmpurl { value };
561       tmpurl.setViewOptions( toReplace );
562       const std::string & replaced( RepoVarExpand()( hotfix1050625::asString( tmpurl ), RepoVarsMap::lookup ) );
563
564       Url newurl;
565       if ( !replaced.empty() )
566       {
567         newurl = replaced;
568         newurl.setUsername( value.getUsername( url::E_ENCODED ), url::E_ENCODED );
569         newurl.setPassword( value.getPassword( url::E_ENCODED ), url::E_ENCODED );
570         newurl.setViewOptions( value.getViewOptions() );
571       }
572       return newurl;
573     }
574   } // namespace repo
575   ///////////////////////////////////////////////////////////////////
576 } // namespace zypp
577 ///////////////////////////////////////////////////////////////////
578 ///////////////////////////////////////////////////////////////////
579 namespace zyppintern
580 {
581   using namespace zypp;
582   // internal helper called when re-acquiring the lock
583   void repoVariablesReset()
584   { repo::RepoVarsMap::instance().clear(); }
585
586 } // namespace zyppintern
587 ///////////////////////////////////////////////////////////////////