add distrbutionFlavor that does not require the target to be loaded, and it as a...
[platform/upstream/libzypp.git] / zypp / media / MediaAria2c.cc
1 /*---------------------------------------------------------------------\
2 |                          ____ _   __ __ ___                          |
3 |                         |__  / \ / / . \ . \                         |
4 |                           / / \ V /|  _/  _/                         |
5 |                          / /__ | | | | | |                           |
6 |                         /_____||_| |_| |_|                           |
7 |                                                                      |
8 \---------------------------------------------------------------------*/
9 /** \file zypp/media/MediaAria2c.cc
10  *
11 */
12
13 #include <iostream>
14 #include <list>
15
16 #include "zypp/base/Logger.h"
17 #include "zypp/ExternalProgram.h"
18 #include "zypp/ProgressData.h"
19 #include "zypp/base/String.h"
20 #include "zypp/base/Gettext.h"
21 #include "zypp/base/Sysconfig.h"
22 #include "zypp/base/Gettext.h"
23 #include "zypp/ZYppCallbacks.h"
24
25 #include "zypp/Target.h"
26 #include "zypp/ZYppFactory.h"
27
28 #include "zypp/media/MediaAria2c.h"
29 #include "zypp/media/proxyinfo/ProxyInfos.h"
30 #include "zypp/media/ProxyInfo.h"
31 #include "zypp/media/MediaUserAuth.h"
32 #include "zypp/thread/Once.h"
33 #include <cstdlib>
34 #include <sys/types.h>
35 #include <sys/stat.h>
36 #include <sys/mount.h>
37 #include <errno.h>
38 #include <dirent.h>
39 #include <unistd.h>
40 #include <boost/format.hpp>
41
42 #define  DETECT_DIR_INDEX       0
43 #define  CONNECT_TIMEOUT        60
44 #define  TRANSFER_TIMEOUT       60 * 3
45 #define  TRANSFER_TIMEOUT_MAX   60 * 60
46
47
48 using namespace std;
49 using namespace zypp::base;
50
51 namespace zypp 
52 {
53 namespace media 
54 {
55
56 Pathname MediaAria2c::_cookieFile = "/var/lib/YaST2/cookies";
57 Pathname MediaAria2c::_aria2cPath = "/usr/local/bin/aria2c";
58 std::string MediaAria2c::_aria2cVersion = "WE DON'T KNOW ARIA2C VERSION";
59
60 //check if aria2c is present in the system
61 bool
62 MediaAria2c::existsAria2cmd()
63 {
64     const char* argv[] =
65     {
66       "whereis",
67       "-b",
68       "aria2c",
69       NULL
70     };
71
72     ExternalProgram aria(argv, ExternalProgram::Stderr_To_Stdout);
73            
74     std::string ariaResponse( aria.receiveLine());
75     string::size_type pos = ariaResponse.find('/', 0 );
76     if( pos != string::npos )
77         return true;
78     else
79         return false;
80 }
81
82 static const char *const anonymousIdHeader()
83 {
84   // we need to add the release and identifier to the
85   // agent string.
86   // The target could be not initialized, and then this information
87   // is not available.
88   Target_Ptr target;
89   // FIXME this has to go away as soon as the target
90   // does not throw when not initialized.
91   try {
92       target = zypp::getZYpp()->target();
93   }
94   catch ( const Exception &e )
95   {
96       // nothing to do
97   }
98
99   static const std::string _value(
100       str::form(
101           "X-Zypp-AnonymousId: %s",
102           target ? target->anonymousUniqueId().c_str() : "" )
103   );
104   return _value.c_str();
105 }
106
107 static const char *const distributionFlavorHeader()
108 {
109   // we need to add the release and identifier to the
110   // agent string.
111   // The target could be not initialized, and then this information
112   // is not available.
113   Target_Ptr target;
114   // FIXME this has to go away as soon as the target
115   // does not throw when not initialized.
116   try {
117       target = zypp::getZYpp()->target();
118   }
119   catch ( const Exception &e )
120   {
121       // nothing to do
122   }
123
124   static const std::string _value(
125       str::trim( str::form(
126           "X-ZYpp-DistributionFlavor: %s",
127           target ? target->distributionFlavor().c_str() : "" ) )
128   );
129   return _value.c_str();
130 }
131
132 const char *const MediaAria2c::agentString()
133 {
134   // we need to add the release and identifier to the
135   // agent string.
136   // The target could be not initialized, and then this information
137   // is not available.
138   Target_Ptr target;
139   // FIXME this has to go away as soon as the target
140   // does not throw when not initialized.
141   try {
142       target = zypp::getZYpp()->target();
143   }
144   catch ( const Exception &e )
145   {
146       // nothing to do
147   }
148
149   static const std::string _value(
150     str::form(
151        "ZYpp %s (%s) %s"
152        , VERSION
153        , MediaAria2c::_aria2cVersion.c_str()
154        , target ? target->targetDistribution().c_str() : ""
155     )
156   );
157   return _value.c_str();
158 }
159
160
161 MediaAria2c::MediaAria2c( const Url &      url_r,
162                       const Pathname & attach_point_hint_r )
163     : MediaHandler( url_r, attach_point_hint_r,
164                     "/", // urlpath at attachpoint
165                     true ) // does_download
166 {
167   MIL << "MediaAria2c::MediaAria2c(" << url_r << ", " << attach_point_hint_r << ")" << endl;
168       
169   if( !attachPoint().empty())
170   {
171     PathInfo ainfo(attachPoint());
172     Pathname apath(attachPoint() + "XXXXXX");
173     char    *atemp = ::strdup( apath.asString().c_str());
174     char    *atest = NULL;
175     if( !ainfo.isDir() || !ainfo.userMayRWX() ||
176          atemp == NULL || (atest=::mkdtemp(atemp)) == NULL)
177     {
178       WAR << "attach point " << ainfo.path()
179           << " is not useable for " << url_r.getScheme() << endl;
180       setAttachPoint("", true);
181     }
182     else if( atest != NULL)
183       ::rmdir(atest);
184
185     if( atemp != NULL)
186       ::free(atemp);
187   }
188
189    //At this point, we initialize aria2c path
190    _aria2cPath = Pathname( whereisAria2c().asString() );
191
192    //Get aria2c version
193    _aria2cVersion = getAria2cVersion();
194 }
195
196 void MediaAria2c::attachTo (bool next)
197 {
198    // clear last arguments
199    _args.clear();   
200
201   if ( next )
202     ZYPP_THROW(MediaNotSupportedException(_url));
203
204   if ( !_url.isValid() )
205     ZYPP_THROW(MediaBadUrlException(_url));
206
207   if( !isUseableAttachPoint(attachPoint()))
208   {
209     std::string mountpoint = createAttachPoint().asString();
210
211     if( mountpoint.empty())
212       ZYPP_THROW( MediaBadAttachPointException(url()));
213
214     setAttachPoint( mountpoint, true);
215   }
216
217   disconnectFrom(); 
218
219   // Build the aria command.
220   _args.push_back(_aria2cPath.asString());
221   _args.push_back(str::form("--user-agent=%s", agentString()));
222   _args.push_back("--summary-interval=1");
223   _args.push_back("--follow-metalink=mem");
224   _args.push_back( "--check-integrity=true");
225   
226    // add the anonymous id.
227    _args.push_back(str::form("--header=%s", anonymousIdHeader() ));
228    _args.push_back(str::form("--header=%s", distributionFlavorHeader() ));
229   // TODO add debug option
230    
231   // Transfer timeout
232   {
233     _xfer_timeout = TRANSFER_TIMEOUT;
234
235     std::string param(_url.getQueryParam("timeout"));
236     if( !param.empty())
237     {
238       long num = str::strtonum<long>( param);
239       if( num >= 0 && num <= TRANSFER_TIMEOUT_MAX)
240         _xfer_timeout = num;
241     }
242   }
243
244   _args.push_back( str::form("--connect-timeout=%d", CONNECT_TIMEOUT));
245
246   // TODO limit redirections
247   // TODO Implement certificate validation
248
249   // FTP defaults to anonymous
250
251
252   if ( _url.getUsername().empty() )
253   {
254     if ( _url.getScheme() == "ftp" )
255     {
256       string id = "yast2@";
257       id += VERSION;
258       DBG << "Anonymous FTP identification: '" << id << "'" << endl;
259       _userpwd = "anonymous:" + id;
260     }
261   } 
262   else 
263   {
264      if ( _url.getScheme() == "ftp" )
265      { 
266          _args.push_back(str::form("--ftp-user=%s", _url.getUsername().c_str() ));
267      }
268      else if ( _url.getScheme() == "http" ||
269                _url.getScheme() == "https" )
270     {
271         _args.push_back(str::form("--http-user=%s", _url.getUsername().c_str() ));
272     }
273      
274     if ( _url.getPassword().size() )
275     {
276       if ( _url.getScheme() == "ftp" )
277       { 
278           _args.push_back(str::form("--ftp-passwd=%s", _url.getPassword().c_str() ));
279       }
280       else if ( _url.getScheme() == "http" ||
281                _url.getScheme() == "https" )
282       {
283           _args.push_back(str::form("--http-passwd=%s", _url.getPassword().c_str() ));
284       }
285     }
286   }
287
288   // note, aria2c does not support setting the auth type with
289   // (basic, digest yet)
290   
291
292   /*---------------------------------------------------------------*
293    CURLOPT_PROXY: host[:port]
294
295    Url::option(proxy and proxyport)
296    If not provided, /etc/sysconfig/proxy is evaluated
297    *---------------------------------------------------------------*/
298
299   _proxy = _url.getQueryParam( "proxy" );
300
301   if ( ! _proxy.empty() )
302   {
303     string proxyport( _url.getQueryParam( "proxyport" ) );
304     if ( ! proxyport.empty() ) {
305       _proxy += ":" + proxyport;
306     }
307   }
308   else
309   {
310
311     ProxyInfo proxy_info (ProxyInfo::ImplPtr(new ProxyInfoSysconfig("proxy")));
312
313     if ( proxy_info.enabled())
314     {
315       bool useproxy = true;
316
317       std::list<std::string> nope = proxy_info.noProxy();
318       for (ProxyInfo::NoProxyIterator it = proxy_info.noProxyBegin();
319            it != proxy_info.noProxyEnd();
320            it++)
321       {
322         std::string host( str::toLower(_url.getHost()));
323         std::string temp( str::toLower(*it));
324
325         // no proxy if it points to a suffix
326         // preceeded by a '.', that maches
327         // the trailing portion of the host.
328         if( temp.size() > 1 && temp.at(0) == '.')
329         {
330           if(host.size() > temp.size() &&
331              host.compare(host.size() - temp.size(), temp.size(), temp) == 0)
332           {
333             DBG << "NO_PROXY: '" << *it  << "' matches host '"
334                                  << host << "'" << endl;
335             useproxy = false;
336             break;
337           }
338         }
339         else
340         // no proxy if we have an exact match
341         if( host == temp)
342         {
343           DBG << "NO_PROXY: '" << *it  << "' matches host '"
344                                << host << "'" << endl;
345           useproxy = false;
346           break;
347         }
348       }
349
350       if ( useproxy ) {
351         _proxy = proxy_info.proxy(_url.getScheme());
352       }
353     }
354   }
355
356   DBG << "Proxy: " << (_proxy.empty() ? "-none-" : _proxy) << endl;
357
358   if ( ! _proxy.empty() )
359   {
360       _args.push_back(str::form("--http-proxy=%s", _proxy.c_str() ));
361
362      /*---------------------------------------------------------------*
363      CURLOPT_PROXYUSERPWD: [user name]:[password]
364
365      Url::option(proxyuser and proxypassword) -> CURLOPT_PROXYUSERPWD
366      If not provided, $HOME/.curlrc is evaluated
367      *---------------------------------------------------------------*/
368
369     _proxyuserpwd = _url.getQueryParam( "proxyuser" );
370
371     if ( ! _proxyuserpwd.empty() ) {
372         _args.push_back(str::form("--http-proxy-user=%s", _proxyuserpwd.c_str() ));
373       
374       string proxypassword( _url.getQueryParam( "proxypassword" ) );
375       if ( ! proxypassword.empty() ) {
376           _args.push_back(str::form("--http-proxy-passwd=%s", proxypassword.c_str() ));
377       }
378     }
379   }
380
381   //_currentCookieFile = _cookieFile.asString();
382   //_args.push_back(str::form("--load-cookies=%s", _currentCookieFile.c_str()));
383   //NOTE cookie jar?
384
385   // FIXME: need a derived class to propelly compare url's
386   MediaSourceRef media( new MediaSource(_url.getScheme(), _url.asString()));
387   setMediaSource(media);
388         
389 }
390
391 bool
392 MediaAria2c::checkAttachPoint(const Pathname &apoint) const
393 {
394   return MediaHandler::checkAttachPoint( apoint, true, true);
395 }
396
397 void MediaAria2c::disconnectFrom()
398 {
399 }
400
401 void MediaAria2c::releaseFrom( const std::string & ejectDev )
402 {
403   disconnect();
404 }
405
406 static Url getFileUrl(const Url & url, const Pathname & filename)
407 {
408   Url newurl(url);
409   string path = url.getPathName();
410   if ( !path.empty() && path != "/" && *path.rbegin() == '/' &&
411        filename.absolute() )
412   {
413     // If url has a path with trailing slash, remove the leading slash from
414     // the absolute file name
415     path += filename.asString().substr( 1, filename.asString().size() - 1 );
416   }
417   else if ( filename.relative() )
418   {
419     // Add trailing slash to path, if not already there
420     if (path.empty()) path = "/";
421     else if (*path.rbegin() != '/' ) path += "/";
422     // Remove "./" from begin of relative file name
423     path += filename.asString().substr( 2, filename.asString().size() - 2 );
424   }
425   else
426   {
427     path += filename.asString();
428   }
429
430   newurl.setPathName(path);
431   return newurl;
432 }
433
434 void MediaAria2c::getFile( const Pathname & filename ) const
435 {
436     // Use absolute file name to prevent access of files outside of the
437     // hierarchy below the attach point.    
438     getFileCopy(filename, localPath(filename).absolutename());
439 }
440
441 void MediaAria2c::getFileCopy( const Pathname & filename , const Pathname & target) const
442 {
443   callback::SendReport<DownloadProgressReport> report;
444
445   Url fileurl(getFileUrl(_url, filename));  
446
447   bool retry = false;
448
449   ExternalProgram::Arguments args = _args;
450   args.push_back(str::form("--dir=%s", target.dirname().c_str()));
451   args.push_back(fileurl.asString());
452   
453   do
454   {
455     try
456     {   
457       report->start(_url, target.asString() );  
458         
459       ExternalProgram aria(args, ExternalProgram::Stderr_To_Stdout);       
460       int nLine = 0;   
461
462       //Process response
463       for(std::string ariaResponse( aria.receiveLine());
464           ariaResponse.length(); 
465           ariaResponse = aria.receiveLine())
466       { 
467         //cout << ariaResponse;
468
469         if (!ariaResponse.substr(0,31).compare("Exception: Authorization failed") )
470         {
471             ZYPP_THROW(MediaUnauthorizedException(
472                   _url, "Login failed.", "Login failed", "auth hint"
473                 ));
474         }
475         if (!ariaResponse.substr(0,29).compare("Exception: Resource not found") )
476         {
477             ZYPP_THROW(MediaFileNotFoundException(_url, filename));
478         }        
479
480         if (!ariaResponse.substr(0,9).compare("[#2 SIZE:")) {
481                 
482           if (!nLine) 
483           {
484             size_t left_bound = ariaResponse.find('(',0) + 1;
485             size_t count = ariaResponse.find('%',left_bound) - left_bound;
486             //cout << ariaResponse.substr(left_bound, count) << endl;
487             //progressData.toMax();
488             report->progress ( std::atoi(ariaResponse.substr(left_bound, count).c_str()), _url, -1, -1 );
489             nLine = 1;
490           } 
491           else
492           {
493             nLine = 0;
494           }                  
495         } 
496       }
497       aria.close();
498         
499       report->finish( _url ,  zypp::media::DownloadProgressReport::NO_ERROR, "");
500       retry = false;
501     }
502  
503     // retry with proper authentication data
504     catch (MediaUnauthorizedException & ex_r)
505     {
506       if(authenticate(ex_r.hint(), !retry))
507         retry = true;
508       else
509       {
510         report->finish(fileurl, zypp::media::DownloadProgressReport::ACCESS_DENIED, ex_r.asUserString());
511         ZYPP_RETHROW(ex_r);
512       }
513
514     }
515     // unexpected exception
516     catch (MediaException & excpt_r)
517     {
518       // FIXME: error number fix
519       report->finish(fileurl, zypp::media::DownloadProgressReport::ERROR, excpt_r.asUserString());
520       ZYPP_RETHROW(excpt_r);
521     }
522   }
523   while (retry);
524
525   report->finish(fileurl, zypp::media::DownloadProgressReport::NO_ERROR, "");
526 }
527
528 bool MediaAria2c::getDoesFileExist( const Pathname & filename ) const
529 {
530   bool retry = false;
531   AuthData auth_data;
532
533   do
534   {
535     try
536     {
537       return doGetDoesFileExist( filename );
538     }
539     // authentication problem, retry with proper authentication data
540     catch (MediaUnauthorizedException & ex_r)
541     {
542       if(authenticate(ex_r.hint(), !retry))
543         retry = true;
544       else
545         ZYPP_RETHROW(ex_r);
546     }
547     // unexpected exception
548     catch (MediaException & excpt_r)
549     {
550       ZYPP_RETHROW(excpt_r);
551     }
552   }
553   while (retry);
554
555   return false;
556 }
557
558 bool MediaAria2c::doGetDoesFileExist( const Pathname & filename ) const
559 {
560         
561   DBG << filename.asString() << endl;
562   return true;
563 }
564
565 void MediaAria2c::getDir( const Pathname & dirname, bool recurse_r ) const
566 {
567   filesystem::DirContent content;
568   getDirInfo( content, dirname, /*dots*/false );
569
570   for ( filesystem::DirContent::const_iterator it = content.begin(); it != content.end(); ++it ) {
571       Pathname filename = dirname + it->name;
572       int res = 0;
573
574       switch ( it->type ) {
575       case filesystem::FT_NOT_AVAIL: // old directory.yast contains no typeinfo at all
576       case filesystem::FT_FILE:
577         getFile( filename );
578         break;
579       case filesystem::FT_DIR: // newer directory.yast contain at least directory info
580         if ( recurse_r ) {
581           getDir( filename, recurse_r );
582         } else {
583           res = assert_dir( localPath( filename ) );
584           if ( res ) {
585             WAR << "Ignore error (" << res <<  ") on creating local directory '" << localPath( filename ) << "'" << endl;
586           }
587         }
588         break;
589       default:
590         // don't provide devices, sockets, etc.
591         break;
592       }
593   }
594 }
595
596 bool MediaAria2c::authenticate(const std::string & availAuthTypes, bool firstTry) const
597 {
598     return false;
599 }
600
601
602 void MediaAria2c::getDirInfo( std::list<std::string> & retlist,
603                                const Pathname & dirname, bool dots ) const
604 {
605   getDirectoryYast( retlist, dirname, dots );
606 }
607
608 void MediaAria2c::getDirInfo( filesystem::DirContent & retlist,
609                             const Pathname & dirname, bool dots ) const
610 {
611   getDirectoryYast( retlist, dirname, dots );
612 }
613
614 std::string MediaAria2c::getAria2cVersion() 
615 {
616     const char* argv[] =
617     {
618         _aria2cPath.c_str(),
619       "--version",
620       NULL
621     };
622
623     ExternalProgram aria(argv, ExternalProgram::Stderr_To_Stdout);
624
625     std::string vResponse = aria.receiveLine();
626     aria.close();
627     return str::trim(vResponse);
628 }
629
630 #define ARIA_DEFAULT_BINARY "/usr/bin/aria2c"
631
632 Pathname MediaAria2c::whereisAria2c()
633 {
634     Pathname aria2cPathr(ARIA_DEFAULT_BINARY);
635     
636     const char* argv[] =
637     {
638       "whereis",
639       "-b",
640       "aria2c",
641       NULL
642     };
643
644     ExternalProgram aria(argv, ExternalProgram::Stderr_To_Stdout);
645            
646     std::string ariaResponse( aria.receiveLine());
647     aria.close();
648     
649     string::size_type pos = ariaResponse.find('/', 0 );
650     if( pos != string::npos ) 
651     {
652         aria2cPathr = ariaResponse;
653         string::size_type pose = ariaResponse.find(' ', pos + 1 );
654         aria2cPathr = ariaResponse.substr( pos , pose - pos );
655         MIL << "We will use aria2c located here:  " << ariaResponse.substr( pos , pose - pos) << endl;
656     }
657     else 
658     {
659         MIL << "We don't know were is ari2ac binary. We will use aria2c located here:  " << aria2cPathr << endl;
660     }
661     
662     return aria2cPathr;
663 }
664
665 } // namespace media
666 } // namespace zypp
667 //