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