47ec974035cea5a13933362feff4c64f8ab9a747
[platform/upstream/libzypp.git] / zypp / RepoInfo.cc
1 /*---------------------------------------------------------------------\
2 |                          ____ _   __ __ ___                          |
3 |                         |__  / \ / / . \ . \                         |
4 |                           / / \ V /|  _/  _/                         |
5 |                          / /__ | | | | | |                           |
6 |                         /_____||_| |_| |_|                           |
7 |                                                                      |
8 \---------------------------------------------------------------------*/
9 /** \file       zypp/RepoInfo.cc
10  *
11 */
12 #include <iostream>
13 #include <vector>
14
15 #include "zypp/base/LogTools.h"
16 #include "zypp/base/DefaultIntegral.h"
17 #include "zypp/parser/xml/XmlEscape.h"
18
19 #include "zypp/RepoInfo.h"
20 #include "zypp/TriBool.h"
21 #include "zypp/Pathname.h"
22 #include "zypp/ZConfig.h"
23 #include "zypp/repo/RepoMirrorList.h"
24 #include "zypp/ExternalProgram.h"
25 #include "zypp/media/MediaAccess.h"
26
27 #include "zypp/base/IOStream.h"
28 #include "zypp/base/InputStream.h"
29 #include "zypp/parser/xml/Reader.h"
30
31 using std::endl;
32 using zypp::xml::escape;
33
34 ///////////////////////////////////////////////////////////////////
35 namespace zypp
36 { /////////////////////////////////////////////////////////////////
37
38   ///////////////////////////////////////////////////////////////////
39   //
40   //    CLASS NAME : RepoInfo::Impl
41   //
42   /** RepoInfo implementation. */
43   struct RepoInfo::Impl
44   {
45     Impl()
46       : _gpgCheck( indeterminate )
47       , _repoGpgCheck( indeterminate )
48       , _pkgGpgCheck( indeterminate )
49       , _validRepoSignature( indeterminate )
50       , keeppackages(indeterminate)
51       , _mirrorListForceMetalink(false)
52       , type(repo::RepoType::NONE_e)
53       , emptybaseurls(false)
54     {}
55
56     ~Impl()
57     {}
58
59   public:
60     static const unsigned defaultPriority = 99;
61
62     void setProbedType( const repo::RepoType & t ) const
63     {
64       if ( type == repo::RepoType::NONE
65            && t != repo::RepoType::NONE )
66       {
67         // lazy init!
68         const_cast<Impl*>(this)->type = t;
69       }
70     }
71
72   public:
73     Pathname licenseTgz() const
74     { return metadatapath.empty() ? Pathname() : metadatapath / path / "license.tar.gz"; }
75
76     const RepoVariablesReplacedUrlList & baseUrls() const
77     {
78       const Url & mlurl( _mirrorListUrl.transformed() );        // Variables replaced!
79       if ( _baseUrls.empty() && ! mlurl.asString().empty() )
80       {
81         emptybaseurls = true;
82         DBG << "MetadataPath: " << metadatapath << endl;
83         repo::RepoMirrorList rmurls( mlurl, metadatapath, _mirrorListForceMetalink );
84         _baseUrls.raw().insert( _baseUrls.raw().end(), rmurls.getUrls().begin(), rmurls.getUrls().end() );
85       }
86       return _baseUrls;
87     }
88
89     RepoVariablesReplacedUrlList & baseUrls()
90     { return _baseUrls; }
91
92     bool baseurl2dump() const
93     { return !emptybaseurls && !_baseUrls.empty(); }
94
95
96     void addContent( const std::string & keyword_r )
97     { _keywords.insert( keyword_r ); }
98
99     bool hasContent( const std::string & keyword_r ) const
100     {
101       if ( _keywords.empty() && ! metadatapath.empty() )
102       {
103         // HACK directly check master index file until RepoManager offers
104         // some content probing ans zypepr uses it.
105         /////////////////////////////////////////////////////////////////
106         MIL << "Empty keywords...." << metadatapath << endl;
107         Pathname master;
108         if ( PathInfo( (master=metadatapath/"/repodata/repomd.xml") ).isFile() )
109         {
110           //MIL << "GO repomd.." << endl;
111           xml::Reader reader( master );
112           while ( reader.seekToNode( 2, "content" ) )
113           {
114             _keywords.insert( reader.nodeText().asString() );
115             reader.seekToEndNode( 2, "content" );
116           }
117           _keywords.insert( "" );       // valid content in _keywords even if empty
118         }
119         else if ( PathInfo( (master=metadatapath/"/content") ).isFile() )
120         {
121           //MIL << "GO content.." << endl;
122           iostr::forEachLine( InputStream( master ),
123                             [this]( int num_r, std::string line_r )->bool
124                             {
125                               if ( str::startsWith( line_r, "REPOKEYWORDS" ) )
126                               {
127                                 std::vector<std::string> words;
128                                 if ( str::split( line_r, std::back_inserter(words) ) > 1
129                                   && words[0].length() == 12 /*"REPOKEYWORDS"*/ )
130                                 {
131                                   this->_keywords.insert( ++words.begin(), words.end() );
132                                 }
133                                 return true; // mult. occurrances are ok.
134                               }
135                               return( ! str::startsWith( line_r, "META " ) );   // no need to parse into META section.
136                             } );
137           _keywords.insert( "" );
138         }
139         /////////////////////////////////////////////////////////////////
140       }
141       return( _keywords.find( keyword_r ) != _keywords.end() );
142     }
143
144     /** Signature check result needs to be stored/retrieved from _metadatapath.
145      * Don't call them from outside validRepoSignature/setValidRepoSignature
146      */
147     //@{
148     TriBool internalValidRepoSignature() const
149     {
150       if ( ! indeterminate(_validRepoSignature) )               return _validRepoSignature;
151       // check metadata:
152       if ( ! metadatapath.empty() )
153       {
154         //TODO: a missing ".repo_gpgcheck" might be plaindir(no Downloader) or not yet refreshed signed repo!
155         TriBool linkval = triBoolFromPath( metadatapath / ".repo_gpgcheck" );
156         return linkval;
157       }
158       return indeterminate;
159     }
160
161     void internalSetValidRepoSignature( TriBool value_r )
162     {
163       if ( PathInfo(metadatapath).isDir() )
164       {
165         TriBool linkval( indeterminate );
166         if ( triBoolFromPath( metadatapath / ".repo_gpgcheck", linkval ) && linkval == value_r )
167           return;       // existing symlink fits value_r
168
169         filesystem::unlink( metadatapath / ".repo_gpgcheck" );
170         filesystem::symlink( asString(value_r), metadatapath / ".repo_gpgcheck" );
171       }
172       _validRepoSignature = value_r;
173     }
174
175     bool triBoolFromPath( const Pathname & path_r, TriBool & ret_r ) const
176     {
177       static const Pathname truePath( "true" );
178       static const Pathname falsePath( "false" );
179       static const Pathname indeterminatePath( "indeterminate" );
180       Pathname linkval( filesystem::readlink( path_r ) );
181       bool known = true;
182       if ( linkval == truePath )
183         ret_r = true;
184       else if ( linkval == falsePath )
185         ret_r = false;
186       else if ( linkval == indeterminatePath )
187         ret_r = indeterminate;
188       else
189         known = false;
190       return known;
191     }
192
193     TriBool triBoolFromPath( const Pathname & path_r ) const
194     { TriBool ret(indeterminate); triBoolFromPath( path_r, ret ); return ret; }
195
196     //@}
197
198   public:
199     TriBool _gpgCheck;          ///< default gpgcheck behavior: Y/N/ZConf
200     TriBool _repoGpgCheck;      ///< need to check repo sign.: Y/N/(ZConf(Y/N/gpgCheck))
201     TriBool _pkgGpgCheck;       ///< need to check pkg sign.: Y/N/(ZConf(Y/N/gpgCheck && no valid repo sign.))
202   private:
203     TriBool _validRepoSignature;///< have  signed and valid repo metadata
204   public:
205     TriBool keeppackages;
206     RepoVariablesReplacedUrl _gpgKeyUrl;
207     RepoVariablesReplacedUrl _mirrorListUrl;
208     bool                     _mirrorListForceMetalink;
209     repo::RepoType type;
210     Pathname path;
211     std::string service;
212     std::string targetDistro;
213     Pathname metadatapath;
214     Pathname packagespath;
215     DefaultIntegral<unsigned,defaultPriority> priority;
216     mutable bool emptybaseurls;
217     repo::RepoVariablesUrlReplacer replacer;
218
219   private:
220     mutable RepoVariablesReplacedUrlList _baseUrls;
221     mutable std::set<std::string> _keywords;
222
223     friend Impl * rwcowClone<Impl>( const Impl * rhs );
224     /** clone for RWCOW_pointer */
225     Impl * clone() const
226     { return new Impl( *this ); }
227   };
228   ///////////////////////////////////////////////////////////////////
229
230   /** \relates RepoInfo::Impl Stream output */
231   inline std::ostream & operator<<( std::ostream & str, const RepoInfo::Impl & obj )
232   {
233     return str << "RepoInfo::Impl";
234   }
235
236   ///////////////////////////////////////////////////////////////////
237   //
238   //    CLASS NAME : RepoInfo
239   //
240   ///////////////////////////////////////////////////////////////////
241
242   const RepoInfo RepoInfo::noRepo;
243
244   RepoInfo::RepoInfo()
245   : _pimpl( new Impl() )
246   {}
247
248   RepoInfo::~RepoInfo()
249   {}
250
251   unsigned RepoInfo::priority() const
252   { return _pimpl->priority; }
253
254   unsigned RepoInfo::defaultPriority()
255   { return Impl::defaultPriority; }
256
257   void RepoInfo::setPriority( unsigned newval_r )
258   { _pimpl->priority = newval_r ? newval_r : Impl::defaultPriority; }
259
260
261   bool RepoInfo::gpgCheck() const
262   { return indeterminate(_pimpl->_gpgCheck) ? ZConfig::instance().gpgCheck() : (bool)_pimpl->_gpgCheck; }
263
264   void RepoInfo::setGpgCheck( TriBool value_r )
265   { _pimpl->_gpgCheck = value_r; }
266
267   void RepoInfo::setGpgCheck( bool value_r ) // deprecated legacy and for squid
268   { setGpgCheck( TriBool(value_r) ); }
269
270
271   bool RepoInfo::repoGpgCheck() const
272   {
273     if ( ! indeterminate(_pimpl->_repoGpgCheck) )               return _pimpl->_repoGpgCheck;
274     if ( ! indeterminate(ZConfig::instance().repoGpgCheck()) )  return ZConfig::instance().repoGpgCheck();
275     return gpgCheck();  // no preference: follow gpgCheck
276   }
277
278   void RepoInfo::setRepoGpgCheck( TriBool value_r )
279   { _pimpl->_repoGpgCheck = value_r; }
280
281
282   bool RepoInfo::pkgGpgCheck() const
283   {
284     if ( ! indeterminate(_pimpl->_pkgGpgCheck) )                return _pimpl->_pkgGpgCheck;
285     if ( ! indeterminate(ZConfig::instance().pkgGpgCheck()) )   return ZConfig::instance().pkgGpgCheck();
286     // no preference: follow gpgCheck and check package if repo signature not available or not checked
287     return gpgCheck() && ( !repoGpgCheck() || !(bool)validRepoSignature() );    // !(bool)TriBool ==> false or indeterminate
288   }
289
290   void RepoInfo::setPkgGpgCheck( TriBool value_r )
291   { _pimpl->_pkgGpgCheck = value_r; }
292
293
294   TriBool RepoInfo::validRepoSignature() const
295   {
296     TriBool ret = _pimpl->internalValidRepoSignature();
297     // keep indeterminate(=unsigned) but invalidate any signature if !repoGpgCheck
298     if ( !indeterminate(ret) && !repoGpgCheck() )
299       ret = false;
300     return ret;
301   }
302
303   void RepoInfo::setValidRepoSignature( TriBool value_r )
304   { _pimpl->internalSetValidRepoSignature( value_r ); }
305
306
307   void RepoInfo::setMirrorListUrl( const Url & url_r )  // Raw
308   { _pimpl->_mirrorListUrl.raw() = url_r; _pimpl->_mirrorListForceMetalink = false; }
309
310   void  RepoInfo::setMetalinkUrl( const Url & url_r )   // Raw
311   { _pimpl->_mirrorListUrl.raw() = url_r; _pimpl->_mirrorListForceMetalink = true; }
312
313   void RepoInfo::setGpgKeyUrl( const Url & url_r )
314   { _pimpl->_gpgKeyUrl.raw() = url_r; }
315
316   void RepoInfo::addBaseUrl( const Url & url_r )
317   {
318     for ( const auto & url : _pimpl->baseUrls().raw() ) // Raw unique!
319       if ( url == url_r )
320         return;
321     _pimpl->baseUrls().raw().push_back( url_r );
322   }
323
324   void RepoInfo::setBaseUrl( const Url & url_r )
325   {
326     _pimpl->baseUrls().raw().clear();
327     _pimpl->baseUrls().raw().push_back( url_r );
328   }
329
330   void RepoInfo::setPath( const Pathname &path )
331   { _pimpl->path = path; }
332
333   void RepoInfo::setType( const repo::RepoType &t )
334   { _pimpl->type = t; }
335
336   void RepoInfo::setProbedType( const repo::RepoType &t ) const
337   { _pimpl->setProbedType( t ); }
338
339
340   void RepoInfo::setMetadataPath( const Pathname &path )
341   { _pimpl->metadatapath = path; }
342
343   void RepoInfo::setPackagesPath( const Pathname &path )
344   { _pimpl->packagespath = path; }
345
346   void RepoInfo::setKeepPackages( bool keep )
347   { _pimpl->keeppackages = keep; }
348
349   void RepoInfo::setService( const std::string& name )
350   { _pimpl->service = name; }
351
352   void RepoInfo::setTargetDistribution( const std::string & targetDistribution )
353   { _pimpl->targetDistro = targetDistribution; }
354
355   bool RepoInfo::keepPackages() const
356   { return indeterminate(_pimpl->keeppackages) ? false : (bool)_pimpl->keeppackages; }
357
358   Pathname RepoInfo::metadataPath() const
359   { return _pimpl->metadatapath; }
360
361   Pathname RepoInfo::packagesPath() const
362   { return _pimpl->packagespath; }
363
364   repo::RepoType RepoInfo::type() const
365   { return _pimpl->type; }
366
367   Url RepoInfo::mirrorListUrl() const                   // Variables replaced!
368   { return _pimpl->_mirrorListUrl.transformed(); }
369
370   Url RepoInfo::rawMirrorListUrl() const                // Raw
371   { return _pimpl->_mirrorListUrl.raw(); }
372
373   Url RepoInfo::gpgKeyUrl() const                       // Variables replaced!
374   { return _pimpl->_gpgKeyUrl.transformed(); }
375
376   Url RepoInfo::rawGpgKeyUrl() const                    // Raw
377   {  return _pimpl->_gpgKeyUrl.raw(); }
378
379   RepoInfo::url_set RepoInfo::baseUrls() const          // Variables replaced!
380   { return _pimpl->baseUrls().transformed(); }
381
382   RepoInfo::url_set RepoInfo::rawBaseUrls() const       // Raw
383   { return _pimpl->baseUrls().raw(); }
384
385   Pathname RepoInfo::path() const
386   { return _pimpl->path; }
387
388   std::string RepoInfo::service() const
389   { return _pimpl->service; }
390
391   std::string RepoInfo::targetDistribution() const
392   { return _pimpl->targetDistro; }
393
394   Url RepoInfo::rawUrl() const
395   { return( _pimpl->baseUrls().empty() ? Url() : *_pimpl->baseUrls().rawBegin() ); }
396
397   RepoInfo::urls_const_iterator RepoInfo::baseUrlsBegin() const
398   { return _pimpl->baseUrls().transformedBegin(); }
399
400   RepoInfo::urls_const_iterator RepoInfo::baseUrlsEnd() const
401   { return _pimpl->baseUrls().transformedEnd(); }
402
403   RepoInfo::urls_size_type RepoInfo::baseUrlsSize() const
404   { return _pimpl->baseUrls().size(); }
405
406   bool RepoInfo::baseUrlsEmpty() const
407   { return _pimpl->baseUrls().empty(); }
408
409   bool RepoInfo::baseUrlSet() const
410   { return _pimpl->baseurl2dump(); }
411
412
413   void RepoInfo::addContent( const std::string & keyword_r )
414   { _pimpl->addContent( keyword_r ); }
415
416   bool RepoInfo::hasContent( const std::string & keyword_r ) const
417   { return _pimpl->hasContent( keyword_r ); }
418
419   ///////////////////////////////////////////////////////////////////
420
421   bool RepoInfo::hasLicense() const
422   {
423     Pathname licenseTgz( _pimpl->licenseTgz() );
424     return ! licenseTgz.empty() &&  PathInfo(licenseTgz).isFile();
425   }
426
427   bool RepoInfo::needToAcceptLicense() const
428   {
429     static const std::string noAcceptanceFile = "no-acceptance-needed\n";
430     bool accept = true;
431
432     Pathname licenseTgz( _pimpl->licenseTgz() );
433     if ( licenseTgz.empty() || ! PathInfo( licenseTgz ).isFile() )
434       return false;     // no licenses at all
435
436     ExternalProgram::Arguments cmd;
437     cmd.push_back( "tar" );
438     cmd.push_back( "-t" );
439     cmd.push_back( "-z" );
440     cmd.push_back( "-f" );
441     cmd.push_back( licenseTgz.asString() );
442
443     ExternalProgram prog( cmd, ExternalProgram::Stderr_To_Stdout );
444     for ( std::string output( prog.receiveLine() ); output.length(); output = prog.receiveLine() )
445     {
446       if ( output == noAcceptanceFile )
447       {
448         accept = false;
449       }
450     }
451     MIL << "License for " << name() << " has to be accepted: " << (accept?"true":"false" ) << endl;
452     return accept;
453   }
454
455   std::string RepoInfo::getLicense( const Locale & lang_r )
456   { return const_cast<const RepoInfo *>(this)->getLicense( lang_r );  }
457
458   std::string RepoInfo::getLicense( const Locale & lang_r ) const
459   {
460     LocaleSet avlocales( getLicenseLocales() );
461     if ( avlocales.empty() )
462       return std::string();
463
464     Locale getLang( Locale::bestMatch( avlocales, lang_r ) );
465     if ( getLang == Locale::noCode
466          && avlocales.find( Locale::noCode ) == avlocales.end() )
467     {
468       WAR << "License.tar.gz contains no fallback text! " << *this << endl;
469       // Using the fist locale instead of returning no text at all.
470       // So the user might recognize that there is a license, even if he
471       // can't read it.
472       getLang = *avlocales.begin();
473     }
474
475     // now extract the license file.
476     static const std::string licenseFileFallback( "license.txt" );
477     std::string licenseFile( getLang == Locale::noCode
478                              ? licenseFileFallback
479                              : str::form( "license.%s.txt", getLang.code().c_str() ) );
480
481     ExternalProgram::Arguments cmd;
482     cmd.push_back( "tar" );
483     cmd.push_back( "-x" );
484     cmd.push_back( "-z" );
485     cmd.push_back( "-O" );
486     cmd.push_back( "-f" );
487     cmd.push_back( _pimpl->licenseTgz().asString() ); // if it not exists, avlocales was empty.
488     cmd.push_back( licenseFile );
489
490     std::string ret;
491     ExternalProgram prog( cmd, ExternalProgram::Discard_Stderr );
492     for ( std::string output( prog.receiveLine() ); output.length(); output = prog.receiveLine() )
493     {
494       ret += output;
495     }
496     prog.close();
497     return ret;
498   }
499
500   LocaleSet RepoInfo::getLicenseLocales() const
501   {
502     Pathname licenseTgz( _pimpl->licenseTgz() );
503     if ( licenseTgz.empty() || ! PathInfo( licenseTgz ).isFile() )
504       return LocaleSet();
505
506     ExternalProgram::Arguments cmd;
507     cmd.push_back( "tar" );
508     cmd.push_back( "-t" );
509     cmd.push_back( "-z" );
510     cmd.push_back( "-f" );
511     cmd.push_back( licenseTgz.asString() );
512
513     LocaleSet ret;
514     ExternalProgram prog( cmd, ExternalProgram::Stderr_To_Stdout );
515     for ( std::string output( prog.receiveLine() ); output.length(); output = prog.receiveLine() )
516     {
517       static const C_Str license( "license." );
518       static const C_Str dotTxt( ".txt\n" );
519       if ( str::hasPrefix( output, license ) && str::hasSuffix( output, dotTxt ) )
520       {
521         if ( output.size() <= license.size() +  dotTxt.size() ) // license.txt
522           ret.insert( Locale() );
523         else
524           ret.insert( Locale( std::string( output.c_str()+license.size(), output.size()- license.size() - dotTxt.size() ) ) );
525       }
526     }
527     prog.close();
528     return ret;
529   }
530
531   ///////////////////////////////////////////////////////////////////
532
533   std::ostream & RepoInfo::dumpOn( std::ostream & str ) const
534   {
535     RepoInfoBase::dumpOn(str);
536     if ( _pimpl->baseurl2dump() )
537     {
538       for ( const auto & url : _pimpl->baseUrls().raw() )
539       {
540         str << "- url         : " << url << std::endl;
541       }
542     }
543
544     // print if non empty value
545     auto strif( [&] ( const std::string & tag_r, const std::string & value_r ) {
546       if ( ! value_r.empty() )
547         str << tag_r << value_r << std::endl;
548     });
549
550     strif( (_pimpl->_mirrorListForceMetalink ? "- metalink    : " : "- mirrorlist  : "), rawMirrorListUrl().asString() );
551     strif( "- path        : ", path().asString() );
552     str << "- type        : " << type() << std::endl;
553     str << "- priority    : " << priority() << std::endl;
554
555     // Yes No Default(Y) Default(N)
556 #define OUTS(T,B) ( indeterminate(T) ? (std::string("D(")+(B?"Y":"N")+")") : ((bool)T?"Y":"N") )
557     str << "- gpgcheck    : " << OUTS(_pimpl->_gpgCheck,gpgCheck())
558                               << " repo" << OUTS(_pimpl->_repoGpgCheck,repoGpgCheck())
559                               << " sig" << asString( validRepoSignature(), "?", "Y", "N" )
560                               << " pkg" << OUTS(_pimpl->_pkgGpgCheck,pkgGpgCheck())
561                               << std::endl;
562 #undef OUTS
563
564     strif( "- gpgkey      : ", rawGpgKeyUrl().asString() );
565
566     if ( ! indeterminate(_pimpl->keeppackages) )
567       str << "- keeppackages: " << keepPackages() << std::endl;
568
569     strif( "- service     : ", service() );
570     strif( "- targetdistro: ", targetDistribution() );
571     strif( "- metadataPath: ", metadataPath().asString() );
572     strif( "- packagesPath: ", packagesPath().asString() );
573
574     return str;
575   }
576
577   std::ostream & RepoInfo::dumpAsIniOn( std::ostream & str ) const
578   {
579     RepoInfoBase::dumpAsIniOn(str);
580
581     if ( _pimpl->baseurl2dump() )
582     {
583       str << "baseurl=";
584       std::string indent;
585       for ( const auto & url : _pimpl->baseUrls().raw() )
586       {
587         str << indent << url << endl;
588         if ( indent.empty() ) indent = "        ";      // "baseurl="
589       }
590     }
591
592     if ( ! _pimpl->path.empty() )
593       str << "path="<< path() << endl;
594
595     if ( ! (rawMirrorListUrl().asString().empty()) )
596       str << (_pimpl->_mirrorListForceMetalink ? "metalink=" : "mirrorlist=") << rawMirrorListUrl() << endl;
597
598     str << "type=" << type().asString() << endl;
599
600     if ( priority() != defaultPriority() )
601       str << "priority=" << priority() << endl;
602
603     if ( ! indeterminate(_pimpl->_gpgCheck) )
604       str << "gpgcheck=" << (_pimpl->_gpgCheck ? "1" : "0") << endl;
605
606     if ( ! indeterminate(_pimpl->_repoGpgCheck) )
607       str << "repo_gpgcheck=" << (_pimpl->_repoGpgCheck ? "1" : "0") << endl;
608
609     if ( ! indeterminate(_pimpl->_pkgGpgCheck) )
610       str << "pkg_gpgcheck=" << (_pimpl->_pkgGpgCheck ? "1" : "0") << endl;
611
612     if ( ! (rawGpgKeyUrl().asString().empty()) )
613       str << "gpgkey=" << rawGpgKeyUrl() << endl;
614
615     if (!indeterminate(_pimpl->keeppackages))
616       str << "keeppackages=" << keepPackages() << endl;
617
618     if( ! service().empty() )
619       str << "service=" << service() << endl;
620
621     return str;
622   }
623
624   std::ostream & RepoInfo::dumpAsXmlOn( std::ostream & str, const std::string & content ) const
625   {
626     std::string tmpstr;
627     str
628       << "<repo"
629       << " alias=\"" << escape(alias()) << "\""
630       << " name=\"" << escape(name()) << "\"";
631     if (type() != repo::RepoType::NONE)
632       str << " type=\"" << type().asString() << "\"";
633     str
634       << " priority=\"" << priority() << "\""
635       << " enabled=\"" << enabled() << "\""
636       << " autorefresh=\"" << autorefresh() << "\""
637       << " gpgcheck=\"" << gpgCheck() << "\""
638       << " repo_gpgcheck=\"" << repoGpgCheck() << "\""
639       << " pkg_gpgcheck=\"" << pkgGpgCheck() << "\"";
640     if (!(tmpstr = gpgKeyUrl().asString()).empty())
641       str << " gpgkey=\"" << escape(tmpstr) << "\"";
642     if (!(tmpstr = mirrorListUrl().asString()).empty())
643       str << (_pimpl->_mirrorListForceMetalink ? " metalink=\"" : " mirrorlist=\"") << escape(tmpstr) << "\"";
644     str << ">" << endl;
645
646     if ( _pimpl->baseurl2dump() )
647     {
648       for_( it, baseUrlsBegin(), baseUrlsEnd() )        // !transform iterator replaces variables
649         str << "<url>" << escape((*it).asString()) << "</url>" << endl;
650     }
651
652     str << "</repo>" << endl;
653     return str;
654   }
655
656
657   std::ostream & operator<<( std::ostream & str, const RepoInfo & obj )
658   {
659     return obj.dumpOn(str);
660   }
661
662
663   /////////////////////////////////////////////////////////////////
664 } // namespace zypp
665 ///////////////////////////////////////////////////////////////////