finally I saw the light, MediaAria2 now inherits MediaCurl, therefore
[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/Edition.h"
26 #include "zypp/Target.h"
27 #include "zypp/ZYppFactory.h"
28
29 #include "zypp/media/TransferProgram.h"
30 #include "zypp/media/MediaAria2c.h"
31 #include "zypp/media/proxyinfo/ProxyInfos.h"
32 #include "zypp/media/ProxyInfo.h"
33 #include "zypp/media/MediaUserAuth.h"
34 #include "zypp/media/MediaCurl.h"
35 #include "zypp/thread/Once.h"
36 #include <cstdlib>
37 #include <sys/types.h>
38 #include <sys/stat.h>
39 #include <sys/mount.h>
40 #include <errno.h>
41 #include <dirent.h>
42 #include <unistd.h>
43 #include <boost/format.hpp>
44
45 #define  DETECT_DIR_INDEX       0
46 #define  CONNECT_TIMEOUT        60
47 #define  TRANSFER_TIMEOUT       60 * 3
48 #define  TRANSFER_TIMEOUT_MAX   60 * 60
49
50
51 using namespace std;
52 using namespace zypp::base;
53
54 namespace zypp
55 {
56 namespace media
57 {
58
59 Pathname MediaAria2c::_cookieFile = "/var/lib/YaST2/cookies";
60 Pathname MediaAria2c::_aria2cPath = "/usr/local/bin/aria2c";
61 std::string MediaAria2c::_aria2cVersion = "WE DON'T KNOW ARIA2C VERSION";
62
63 //check if aria2c is present in the system
64 bool
65 MediaAria2c::existsAria2cmd()
66 {
67     const char* argv[] =
68     {
69       "which",
70       "aria2c",
71       NULL
72     };
73
74     ExternalProgram aria(argv, ExternalProgram::Stderr_To_Stdout);
75     return ( aria.close() == 0 );
76 }
77
78 void fillSettingsFromUrl( const Url &url, TransferSettings &s )
79 {
80     std::string param(url.getQueryParam("timeout"));
81     if( !param.empty())
82     {
83       long num = str::strtonum<long>(param);
84       if( num >= 0 && num <= TRANSFER_TIMEOUT_MAX)
85           s.setTimeout(num);
86     }
87
88     if ( ! url.getUsername().empty() )
89     {
90         s.setUsername(url.getUsername());
91         if ( url.getPassword().size() )
92         {
93             s.setPassword(url.getPassword());
94         }
95     }
96
97     string proxy = url.getQueryParam( "proxy" );
98
99     if ( ! proxy.empty() )
100     {
101         string proxyport( url.getQueryParam( "proxyport" ) );
102         if ( ! proxyport.empty() ) {
103             proxy += ":" + proxyport;
104         }
105         s.setProxy(proxy);
106         s.setProxyEnabled(true);
107     }
108 }    
109
110 void fillSettingsSystemProxy( const Url&url, TransferSettings &s )
111 {
112     ProxyInfo proxy_info (ProxyInfo::ImplPtr(new ProxyInfoSysconfig("proxy")));
113
114     if ( proxy_info.enabled())
115     {
116       s.setProxyEnabled(true);
117       std::list<std::string> nope = proxy_info.noProxy();
118       for (ProxyInfo::NoProxyIterator it = proxy_info.noProxyBegin();
119            it != proxy_info.noProxyEnd();
120            it++)
121       {
122         std::string host( str::toLower(url.getHost()));
123         std::string temp( str::toLower(*it));
124
125         // no proxy if it points to a suffix
126         // preceeded by a '.', that maches
127         // the trailing portion of the host.
128         if( temp.size() > 1 && temp.at(0) == '.')
129         {
130           if(host.size() > temp.size() &&
131              host.compare(host.size() - temp.size(), temp.size(), temp) == 0)
132           {
133             DBG << "NO_PROXY: '" << *it  << "' matches host '"
134                                  << host << "'" << endl;
135             s.setProxyEnabled(false);
136             break;
137           }
138         }
139         else
140         // no proxy if we have an exact match
141         if( host == temp)
142         {
143           DBG << "NO_PROXY: '" << *it  << "' matches host '"
144                                << host << "'" << endl;
145           s.setProxyEnabled(false);
146           break;
147         }
148       }
149
150       if ( s.proxyEnabled() )
151           s.setProxy(proxy_info.proxy(url.getScheme()));
152     }
153
154 }    
155
156 /**
157  * comannd line for aria.
158  * The argument list gets passed as reference
159  * and it is filled.
160  */
161 void fillAriaCmdLine( const Pathname &ariapath,
162                       const string &ariaver,
163                       const TransferSettings &s,
164                       const Url &url,
165                       const Pathname &destination,
166                       ExternalProgram::Arguments &args )
167 {
168     args.push_back(ariapath.c_str());
169     args.push_back(str::form("--user-agent=%s", s.userAgentString().c_str()));
170     args.push_back("--summary-interval=1");
171     args.push_back("--follow-metalink=mem");
172     args.push_back("--check-integrity=true");
173
174     // only present in recent aria lets find out the aria version
175     vector<string> fields;    
176     // "aria2c version x.x"
177     str::split( ariaver, std::back_inserter(fields));
178     if ( fields.size() == 3 )
179     {
180         if ( Edition(fields[2]) >= Edition("1.1.2") )
181             args.push_back( "--use-head=false");
182     }
183     
184     if ( s.maxDownloadSpeed() > 0 )
185         args.push_back(str::form("--max-download-limit=%ld", s.maxDownloadSpeed()));
186     if ( s.minDownloadSpeed() > 0 )
187         args.push_back(str::form("--lowest-speed-limit=%ld", s.minDownloadSpeed()));
188
189     args.push_back(str::form("--max-tries=%ld", s.maxSilentTries()));
190
191     if ( Edition(fields[2]) < Edition("1.2.0") )
192         WAR << "aria2c is older than 1.2.0, some features may be disabled" << endl;
193     
194     // TODO make this one configurable
195     args.push_back(str::form("--max-concurrent-downloads=%ld", s.maxConcurrentConnections()));
196
197     // add the anonymous id.
198     for ( TransferSettings::Headers::const_iterator it = s.headersBegin();
199           it != s.headersEnd();
200           ++it )
201         args.push_back(str::form("--header=%s", it->c_str() ));
202         
203     args.push_back( str::form("--connect-timeout=%ld", s.timeout()));
204
205     if ( s.username().empty() )
206     {
207         if ( url.getScheme() == "ftp" )
208         {
209             // set anonymous ftp
210             args.push_back(str::form("--ftp-user=%s", "suseuser" ));
211             args.push_back(str::form("--ftp-passwd=%s", VERSION ));
212
213             string id = "yast2";
214             id += VERSION;
215             DBG << "Anonymous FTP identification: '" << id << "'" << endl;
216         }
217     }
218     else
219     {
220         if ( url.getScheme() == "ftp" )
221             args.push_back(str::form("--ftp-user=%s", s.username().c_str() ));
222         else if ( url.getScheme() == "http" ||
223                   url.getScheme() == "https" )
224             args.push_back(str::form("--http-user=%s", s.username().c_str() ));
225         
226         if ( s.password().size() )
227         {
228             if ( url.getScheme() == "ftp" )
229                 args.push_back(str::form("--ftp-passwd=%s", s.password().c_str() ));
230             else if ( url.getScheme() == "http" ||
231                       url.getScheme() == "https" )
232                 args.push_back(str::form("--http-passwd=%s", s.password().c_str() ));
233         }
234     }
235     
236     if ( s.proxyEnabled() )
237     {
238         args.push_back(str::form("--http-proxy=%s", s.proxy().c_str() ));
239         if ( ! s.proxyUsername().empty() )
240         {
241             args.push_back(str::form("--http-proxy-user=%s", s.proxyUsername().c_str() ));
242             if ( ! s.proxyPassword().empty() )
243                 args.push_back(str::form("--http-proxy-passwd=%s", s.proxyPassword().c_str() ));
244         }
245     }
246
247     if ( ! destination.empty() )
248         args.push_back(str::form("--dir=%s", destination.c_str()));
249
250     args.push_back(url.asString().c_str());
251 }
252
253 static const char *const anonymousIdHeader()
254 {
255   // we need to add the release and identifier to the
256   // agent string.
257   // The target could be not initialized, and then this information
258   // is not available.
259   Target_Ptr target = zypp::getZYpp()->getTarget();
260
261   static const std::string _value(
262       str::form(
263           "X-Zypp-AnonymousId: %s",
264           target ? target->anonymousUniqueId().c_str() : "" )
265   );
266   return _value.c_str();
267 }
268
269 static const char *const distributionFlavorHeader()
270 {
271   // we need to add the release and identifier to the
272   // agent string.
273   // The target could be not initialized, and then this information
274   // is not available.
275   Target_Ptr target = zypp::getZYpp()->getTarget();
276
277   static const std::string _value(
278       str::trim( str::form(
279           "X-ZYpp-DistributionFlavor: %s",
280           target ? target->distributionFlavor().c_str() : "" ) )
281   );
282   return _value.c_str();
283 }
284
285 const char *const MediaAria2c::agentString()
286 {
287   // we need to add the release and identifier to the
288   // agent string.
289   // The target could be not initialized, and then this information
290   // is not available.
291   Target_Ptr target = zypp::getZYpp()->getTarget();
292
293   static const std::string _value(
294     str::form(
295        "ZYpp %s (%s) %s"
296        , VERSION
297        , MediaAria2c::_aria2cVersion.c_str()
298        , target ? target->targetDistribution().c_str() : ""
299     )
300   );
301   return _value.c_str();
302 }
303
304
305
306 MediaAria2c::MediaAria2c( const Url &      url_r,
307                       const Pathname & attach_point_hint_r )
308     : MediaCurl( url_r, attach_point_hint_r )
309 {
310   MIL << "MediaAria2c::MediaAria2c(" << url_r << ", " << attach_point_hint_r << ")" << endl;
311
312   /*
313   if( !attachPoint().empty())
314   {
315     PathInfo ainfo(attachPoint());
316     Pathname apath(attachPoint() + "XXXXXX");
317     char    *atemp = ::strdup( apath.asString().c_str());
318     char    *atest = NULL;
319     if( !ainfo.isDir() || !ainfo.userMayRWX() ||
320          atemp == NULL || (atest=::mkdtemp(atemp)) == NULL)
321     {
322       WAR << "attach point " << ainfo.path()
323           << " is not useable for " << url_r.getScheme() << endl;
324       setAttachPoint("", true);
325     }
326     else if( atest != NULL)
327       ::rmdir(atest);
328
329     if( atemp != NULL)
330       ::free(atemp);
331   }
332   */
333
334    //At this point, we initialize aria2c path
335    _aria2cPath = Pathname( whereisAria2c().asString() );
336
337    //Get aria2c version
338    _aria2cVersion = getAria2cVersion();
339 }
340
341 void MediaAria2c::attachTo (bool next)
342 {
343   MediaCurl::attachTo(next);
344     
345   _settings.setUserAgentString(agentString());
346   _settings.addHeader(anonymousIdHeader());
347   _settings.addHeader(distributionFlavorHeader());
348
349   _settings.setTimeout(TRANSFER_TIMEOUT);
350   _settings.setConnectTimeout(CONNECT_TIMEOUT);
351
352   // fill some settings from url query parameters
353   fillSettingsFromUrl(_url, _settings);
354
355   // if the proxy was not set by url, then look 
356   if ( _settings.proxy().empty() )
357   {
358       // at the system proxy settings
359       fillSettingsSystemProxy(_url, _settings);
360   }
361
362   DBG << "Proxy: " << (_settings.proxy().empty() ? "-none-" : _settings.proxy()) << endl;
363 }
364
365 bool
366 MediaAria2c::checkAttachPoint(const Pathname &apoint) const
367 {
368     return MediaCurl::checkAttachPoint( apoint );
369 }
370
371 void MediaAria2c::disconnectFrom()
372 {
373     MediaCurl::disconnectFrom();
374     
375 }
376
377 void MediaAria2c::releaseFrom( const std::string & ejectDev )
378 {
379   MediaCurl::releaseFrom(ejectDev);
380 }
381
382 static Url getFileUrl(const Url & url, const Pathname & filename)
383 {
384   Url newurl(url);
385   string path = url.getPathName();
386   if ( !path.empty() && path != "/" && *path.rbegin() == '/' &&
387        filename.absolute() )
388   {
389     // If url has a path with trailing slash, remove the leading slash from
390     // the absolute file name
391     path += filename.asString().substr( 1, filename.asString().size() - 1 );
392   }
393   else if ( filename.relative() )
394   {
395     // Add trailing slash to path, if not already there
396     if (path.empty()) path = "/";
397     else if (*path.rbegin() != '/' ) path += "/";
398     // Remove "./" from begin of relative file name
399     path += filename.asString().substr( 2, filename.asString().size() - 2 );
400   }
401   else
402   {
403     path += filename.asString();
404   }
405
406   newurl.setPathName(path);
407   return newurl;
408 }
409
410 void MediaAria2c::getFile( const Pathname & filename ) const
411 {
412     // Use absolute file name to prevent access of files outside of the
413     // hierarchy below the attach point.
414     getFileCopy(filename, localPath(filename).absolutename());
415 }
416
417 void MediaAria2c::getFileCopy( const Pathname & filename , const Pathname & target) const
418 {
419   callback::SendReport<DownloadProgressReport> report;
420
421   Url fileurl(getFileUrl(_url, filename));
422
423   bool retry = false;
424
425   ExternalProgram::Arguments args;
426
427   fillAriaCmdLine(_aria2cPath, _aria2cVersion, _settings, fileurl, target.dirname(), args);
428   
429   do
430   {
431     try
432     {
433       report->start(_url, target.asString() );
434
435       ExternalProgram aria(args, ExternalProgram::Stderr_To_Stdout);
436       int nLine = 0;
437
438       //Process response
439       for(std::string ariaResponse( aria.receiveLine());
440           ariaResponse.length();
441           ariaResponse = aria.receiveLine())
442       {
443         //cout << ariaResponse;
444
445         if (!ariaResponse.substr(0,31).compare("Exception: Authorization failed") )
446         {
447             ZYPP_THROW(MediaUnauthorizedException(
448                   _url, "Login failed.", "Login failed", "auth hint"
449                 ));
450         }
451         if (!ariaResponse.substr(0,29).compare("Exception: Resource not found") )
452         {
453             ZYPP_THROW(MediaFileNotFoundException(_url, filename));
454         }
455
456         if (!ariaResponse.substr(0,9).compare("[#2 SIZE:"))
457         {
458           if (!nLine)
459           {
460             size_t left_bound = ariaResponse.find('(',0) + 1;
461             size_t count = ariaResponse.find('%',left_bound) - left_bound;
462             //cout << ariaResponse.substr(left_bound, count) << endl;
463             //progressData.toMax();
464             report->progress ( std::atoi(ariaResponse.substr(left_bound, count).c_str()), _url, -1, -1 );
465             nLine = 1;
466           }
467           else
468           {
469             nLine = 0;
470           }
471         }
472       }
473
474       aria.close();
475
476       report->finish( _url ,  zypp::media::DownloadProgressReport::NO_ERROR, "");
477       retry = false;
478     }
479
480     // retry with proper authentication data
481     catch (MediaUnauthorizedException & ex_r)
482     {
483       if(authenticate(ex_r.hint(), !retry))
484         retry = true;
485       else
486       {
487         report->finish(fileurl, zypp::media::DownloadProgressReport::ACCESS_DENIED, ex_r.asUserHistory());
488         ZYPP_RETHROW(ex_r);
489       }
490
491     }
492     // unexpected exception
493     catch (MediaException & excpt_r)
494     {
495       // FIXME: error number fix
496       report->finish(fileurl, zypp::media::DownloadProgressReport::ERROR, excpt_r.asUserHistory());
497       ZYPP_RETHROW(excpt_r);
498     }
499   }
500   while (retry);
501
502   report->finish(fileurl, zypp::media::DownloadProgressReport::NO_ERROR, "");
503 }
504
505 bool MediaAria2c::getDoesFileExist( const Pathname & filename ) const
506 {
507     return MediaCurl::getDoesFileExist(filename);
508 }
509
510 bool MediaAria2c::doGetDoesFileExist( const Pathname & filename ) const
511 {
512     return MediaCurl::doGetDoesFileExist(filename);
513 }
514     
515 void MediaAria2c::getDir( const Pathname & dirname, bool recurse_r ) const
516 {
517     MediaCurl::getDir(dirname, recurse_r);
518 }
519
520 bool MediaAria2c::authenticate(const std::string & availAuthTypes, bool firstTry) const
521 {
522     return false;
523 }
524
525
526 void MediaAria2c::getDirInfo( std::list<std::string> & retlist,
527                                const Pathname & dirname, bool dots ) const
528 {
529   getDirectoryYast( retlist, dirname, dots );
530 }
531
532 void MediaAria2c::getDirInfo( filesystem::DirContent & retlist,
533                             const Pathname & dirname, bool dots ) const
534 {
535   getDirectoryYast( retlist, dirname, dots );
536 }
537
538 std::string MediaAria2c::getAria2cVersion()
539 {
540     const char* argv[] =
541     {
542         _aria2cPath.c_str(),
543       "--version",
544       NULL
545     };
546
547     ExternalProgram aria(argv, ExternalProgram::Stderr_To_Stdout);
548
549     std::string vResponse = aria.receiveLine();
550     aria.close();
551     return str::trim(vResponse);
552 }
553
554 #define ARIA_DEFAULT_BINARY "/usr/bin/aria2c"
555
556 Pathname MediaAria2c::whereisAria2c()
557 {
558     Pathname aria2cPathr(ARIA_DEFAULT_BINARY);
559
560     const char* argv[] =
561     {
562       "which",
563       "aria2c",
564       NULL
565     };
566
567     ExternalProgram aria(argv, ExternalProgram::Stderr_To_Stdout);
568
569     std::string ariaResponse( aria.receiveLine());
570     int code = aria.close();
571
572     if( code == 0 )
573     {
574         aria2cPathr = str::trim(ariaResponse);
575         MIL << "We will use aria2c located here:  " << aria2cPathr << endl;
576     }
577     else
578     {
579         MIL << "We don't know were is ari2ac binary. We will use aria2c located here:  " << aria2cPathr << endl;
580     }
581
582     return aria2cPathr;
583 }
584
585 } // namespace media
586 } // namespace zypp
587 //