merge poeml suggested changes, make sure we have the right aria version
[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/MediaAria2c.h"
30 #include "zypp/media/proxyinfo/ProxyInfos.h"
31 #include "zypp/media/ProxyInfo.h"
32 #include "zypp/media/MediaUserAuth.h"
33 #include "zypp/thread/Once.h"
34 #include <cstdlib>
35 #include <sys/types.h>
36 #include <sys/stat.h>
37 #include <sys/mount.h>
38 #include <errno.h>
39 #include <dirent.h>
40 #include <unistd.h>
41 #include <boost/format.hpp>
42
43 #define  DETECT_DIR_INDEX       0
44 #define  CONNECT_TIMEOUT        60
45 #define  TRANSFER_TIMEOUT       60 * 3
46 #define  TRANSFER_TIMEOUT_MAX   60 * 60
47
48
49 using namespace std;
50 using namespace zypp::base;
51
52 namespace zypp
53 {
54 namespace media
55 {
56
57 Pathname MediaAria2c::_cookieFile = "/var/lib/YaST2/cookies";
58 Pathname MediaAria2c::_aria2cPath = "/usr/local/bin/aria2c";
59 std::string MediaAria2c::_aria2cVersion = "WE DON'T KNOW ARIA2C VERSION";
60
61 //check if aria2c is present in the system
62 bool
63 MediaAria2c::existsAria2cmd()
64 {
65     const char* argv[] =
66     {
67       "which",
68       "aria2c",
69       NULL
70     };
71
72     ExternalProgram aria(argv, ExternalProgram::Stderr_To_Stdout);
73     return ( aria.close() == 0 );
74 }
75
76 void fillSettingsFromUrl( const Url &url, TransferSettings &s )
77 {
78     std::string param(url.getQueryParam("timeout"));
79     if( !param.empty())
80     {
81       long num = str::strtonum<long>(param);
82       if( num >= 0 && num <= TRANSFER_TIMEOUT_MAX)
83           s.setTimeout(num);
84     }
85
86     if ( ! url.getUsername().empty() )
87     {
88         s.setUsername(url.getUsername());
89         if ( url.getPassword().size() )
90         {
91             s.setPassword(url.getPassword());
92         }
93     }
94
95     string proxy = url.getQueryParam( "proxy" );
96
97     if ( ! proxy.empty() )
98     {
99         string proxyport( url.getQueryParam( "proxyport" ) );
100         if ( ! proxyport.empty() ) {
101             proxy += ":" + proxyport;
102         }
103         s.setProxy(proxy);
104         s.setProxyEnabled(true);
105     }
106 }    
107
108 void fillSettingsSystemProxy( const Url&url, TransferSettings &s )
109 {
110     ProxyInfo proxy_info (ProxyInfo::ImplPtr(new ProxyInfoSysconfig("proxy")));
111
112     if ( proxy_info.enabled())
113     {
114       s.setProxyEnabled(true);
115       std::list<std::string> nope = proxy_info.noProxy();
116       for (ProxyInfo::NoProxyIterator it = proxy_info.noProxyBegin();
117            it != proxy_info.noProxyEnd();
118            it++)
119       {
120         std::string host( str::toLower(url.getHost()));
121         std::string temp( str::toLower(*it));
122
123         // no proxy if it points to a suffix
124         // preceeded by a '.', that maches
125         // the trailing portion of the host.
126         if( temp.size() > 1 && temp.at(0) == '.')
127         {
128           if(host.size() > temp.size() &&
129              host.compare(host.size() - temp.size(), temp.size(), temp) == 0)
130           {
131             DBG << "NO_PROXY: '" << *it  << "' matches host '"
132                                  << host << "'" << endl;
133             s.setProxyEnabled(false);
134             break;
135           }
136         }
137         else
138         // no proxy if we have an exact match
139         if( host == temp)
140         {
141           DBG << "NO_PROXY: '" << *it  << "' matches host '"
142                                << host << "'" << endl;
143           s.setProxyEnabled(false);
144           break;
145         }
146       }
147
148       if ( s.proxyEnabled() )
149           s.setProxy(proxy_info.proxy(url.getScheme()));
150     }
151
152 }    
153
154 /**
155  * comannd line for aria.
156  * The argument list gets passed as reference
157  * and it is filled.
158  */
159 void fillAriaCmdLine( const Pathname &ariapath,
160                       const string &ariaver,
161                       const TransferSettings &s,
162                       const Url &url,
163                       const Pathname &destination,
164                       ExternalProgram::Arguments &args )
165 {
166     args.push_back(ariapath.c_str());
167     args.push_back(str::form("--user-agent=%s", s.userAgentString().c_str()));
168     args.push_back("--summary-interval=1");
169     args.push_back("--follow-metalink=mem");
170     args.push_back("--check-integrity=true");
171
172     // only present in recent aria
173     if ( Edition(ariaver) >= Edition("1.20") )
174         args.push_back( "--use-head=false");
175
176     // TODO make this one configurable
177     args.push_back( "--max-concurrent-downloads=2");
178
179     // add the anonymous id.
180     for ( TransferSettings::Headers::const_iterator it = s.headersBegin();
181           it != s.headersEnd();
182           ++it )
183         args.push_back(str::form("--header=%s", it->c_str() ));
184         
185     args.push_back( str::form("--connect-timeout=%ld", s.timeout()));
186
187     if ( s.username().empty() )
188     {
189         if ( url.getScheme() == "ftp" )
190         {
191             // set anonymous ftp
192             args.push_back(str::form("--ftp-user=%s", "suseuser" ));
193             args.push_back(str::form("--ftp-passwd=%s", VERSION ));
194
195             string id = "yast2";
196             id += VERSION;
197             DBG << "Anonymous FTP identification: '" << id << "'" << endl;
198         }
199     }
200     else
201     {
202         if ( url.getScheme() == "ftp" )
203             args.push_back(str::form("--ftp-user=%s", s.username().c_str() ));
204         else if ( url.getScheme() == "http" ||
205                   url.getScheme() == "https" )
206             args.push_back(str::form("--http-user=%s", s.username().c_str() ));
207         
208         if ( s.password().size() )
209         {
210             if ( url.getScheme() == "ftp" )
211                 args.push_back(str::form("--ftp-passwd=%s", s.password().c_str() ));
212             else if ( url.getScheme() == "http" ||
213                       url.getScheme() == "https" )
214                 args.push_back(str::form("--http-passwd=%s", s.password().c_str() ));
215         }
216     }
217     
218     if ( s.proxyEnabled() )
219     {
220         args.push_back(str::form("--http-proxy=%s", s.proxy().c_str() ));
221         if ( ! s.proxyUsername().empty() )
222         {
223             args.push_back(str::form("--http-proxy-user=%s", s.proxyUsername().c_str() ));
224             if ( ! s.proxyPassword().empty() )
225                 args.push_back(str::form("--http-proxy-passwd=%s", s.proxyPassword().c_str() ));
226         }
227     }
228
229     if ( ! destination.empty() )
230         args.push_back(str::form("--dir=%s", destination.c_str()));
231
232     args.push_back(url.asString().c_str());
233 }
234
235 /**
236  * comannd line for curl.
237  * The argument list gets passed as reference
238  * and it is filled.
239  */
240 void fillCurlCmdLine( const Pathname &curlpath,
241                       const TransferSettings &s,
242                       const Url &url,
243                       ExternalProgram::Arguments &args )
244 {
245     args.push_back(curlpath.c_str());
246     // only do a head request
247     args.push_back("-I");
248     args.push_back("-A"); args.push_back(s.userAgentString());
249
250     // headers.
251     for ( TransferSettings::Headers::const_iterator it = s.headersBegin();
252           it != s.headersEnd();
253           ++it )
254     {
255         args.push_back("-H");
256         args.push_back(it->c_str());
257     }
258     
259     args.push_back("--connect-timeout");
260     args.push_back(str::numstring(s.timeout()));
261
262     if ( s.username().empty() )
263     {
264         if ( url.getScheme() == "ftp" )
265         {
266             string id = "yast2:";
267             id += VERSION;
268             args.push_back("--user");
269             args.push_back(id);
270             DBG << "Anonymous FTP identification: '" << id << "'" << endl;
271         }
272     }
273     else
274     {
275         string userpass = s.username();
276                     
277         if ( s.password().size() )
278             userpass += (":" + s.password());
279         args.push_back("--user");
280         args.push_back(userpass);
281     }
282     
283     if ( s.proxyEnabled() )
284     {
285         args.push_back("--proxy");
286         args.push_back(s.proxy());
287         if ( ! s.proxyUsername().empty() )
288         {
289             string userpass = s.proxyUsername();
290                     
291             if ( s.proxyPassword().size() )
292                 userpass += (":" + s.proxyPassword());
293             args.push_back("--proxy-user");
294             args.push_back(userpass);
295         }
296     }
297
298     args.push_back("--url");
299     args.push_back(url.asString().c_str());
300 }
301
302
303 static const char *const anonymousIdHeader()
304 {
305   // we need to add the release and identifier to the
306   // agent string.
307   // The target could be not initialized, and then this information
308   // is not available.
309   Target_Ptr target = zypp::getZYpp()->getTarget();
310
311   static const std::string _value(
312       str::form(
313           "X-Zypp-AnonymousId: %s",
314           target ? target->anonymousUniqueId().c_str() : "" )
315   );
316   return _value.c_str();
317 }
318
319 static const char *const distributionFlavorHeader()
320 {
321   // we need to add the release and identifier to the
322   // agent string.
323   // The target could be not initialized, and then this information
324   // is not available.
325   Target_Ptr target = zypp::getZYpp()->getTarget();
326
327   static const std::string _value(
328       str::trim( str::form(
329           "X-ZYpp-DistributionFlavor: %s",
330           target ? target->distributionFlavor().c_str() : "" ) )
331   );
332   return _value.c_str();
333 }
334
335 const char *const MediaAria2c::agentString()
336 {
337   // we need to add the release and identifier to the
338   // agent string.
339   // The target could be not initialized, and then this information
340   // is not available.
341   Target_Ptr target = zypp::getZYpp()->getTarget();
342
343   static const std::string _value(
344     str::form(
345        "ZYpp %s (%s) %s"
346        , VERSION
347        , MediaAria2c::_aria2cVersion.c_str()
348        , target ? target->targetDistribution().c_str() : ""
349     )
350   );
351   return _value.c_str();
352 }
353
354
355
356 MediaAria2c::MediaAria2c( const Url &      url_r,
357                       const Pathname & attach_point_hint_r )
358     : MediaHandler( url_r, attach_point_hint_r,
359                     "/", // urlpath at attachpoint
360                     true ) // does_download
361 {
362   MIL << "MediaAria2c::MediaAria2c(" << url_r << ", " << attach_point_hint_r << ")" << endl;
363
364   if( !attachPoint().empty())
365   {
366     PathInfo ainfo(attachPoint());
367     Pathname apath(attachPoint() + "XXXXXX");
368     char    *atemp = ::strdup( apath.asString().c_str());
369     char    *atest = NULL;
370     if( !ainfo.isDir() || !ainfo.userMayRWX() ||
371          atemp == NULL || (atest=::mkdtemp(atemp)) == NULL)
372     {
373       WAR << "attach point " << ainfo.path()
374           << " is not useable for " << url_r.getScheme() << endl;
375       setAttachPoint("", true);
376     }
377     else if( atest != NULL)
378       ::rmdir(atest);
379
380     if( atemp != NULL)
381       ::free(atemp);
382   }
383
384    //At this point, we initialize aria2c path
385    _aria2cPath = Pathname( whereisAria2c().asString() );
386
387    //Get aria2c version
388    _aria2cVersion = getAria2cVersion();
389 }
390
391 void MediaAria2c::attachTo (bool next)
392 {
393    // clear last arguments
394   if ( next )
395     ZYPP_THROW(MediaNotSupportedException(_url));
396
397   if ( !_url.isValid() )
398     ZYPP_THROW(MediaBadUrlException(_url));
399
400   if( !isUseableAttachPoint(attachPoint()))
401   {
402     std::string mountpoint = createAttachPoint().asString();
403
404     if( mountpoint.empty())
405       ZYPP_THROW( MediaBadAttachPointException(url()));
406
407     setAttachPoint( mountpoint, true);
408   }
409
410   disconnectFrom();
411
412   _settings.setUserAgentString(agentString());
413   _settings.addHeader(anonymousIdHeader());
414   _settings.addHeader(distributionFlavorHeader());
415
416   _settings.setTimeout(TRANSFER_TIMEOUT);
417   _settings.setConnectTimeout(CONNECT_TIMEOUT);
418
419   // fill some settings from url query parameters
420   fillSettingsFromUrl(_url, _settings);
421
422   // if the proxy was not set by url, then look 
423   if ( _settings.proxy().empty() )
424   {
425       // at the system proxy settings
426       fillSettingsSystemProxy(_url, _settings);
427   }
428
429   DBG << "Proxy: " << (_settings.proxy().empty() ? "-none-" : _settings.proxy()) << endl;
430
431   MediaSourceRef media( new MediaSource(_url.getScheme(), _url.asString()));
432   setMediaSource(media);
433
434 }
435
436 bool
437 MediaAria2c::checkAttachPoint(const Pathname &apoint) const
438 {
439   return MediaHandler::checkAttachPoint( apoint, true, true);
440 }
441
442 void MediaAria2c::disconnectFrom()
443 {
444 }
445
446 void MediaAria2c::releaseFrom( const std::string & ejectDev )
447 {
448   disconnect();
449 }
450
451 static Url getFileUrl(const Url & url, const Pathname & filename)
452 {
453   Url newurl(url);
454   string path = url.getPathName();
455   if ( !path.empty() && path != "/" && *path.rbegin() == '/' &&
456        filename.absolute() )
457   {
458     // If url has a path with trailing slash, remove the leading slash from
459     // the absolute file name
460     path += filename.asString().substr( 1, filename.asString().size() - 1 );
461   }
462   else if ( filename.relative() )
463   {
464     // Add trailing slash to path, if not already there
465     if (path.empty()) path = "/";
466     else if (*path.rbegin() != '/' ) path += "/";
467     // Remove "./" from begin of relative file name
468     path += filename.asString().substr( 2, filename.asString().size() - 2 );
469   }
470   else
471   {
472     path += filename.asString();
473   }
474
475   newurl.setPathName(path);
476   return newurl;
477 }
478
479 void MediaAria2c::getFile( const Pathname & filename ) const
480 {
481     // Use absolute file name to prevent access of files outside of the
482     // hierarchy below the attach point.
483     getFileCopy(filename, localPath(filename).absolutename());
484 }
485
486 void MediaAria2c::getFileCopy( const Pathname & filename , const Pathname & target) const
487 {
488   callback::SendReport<DownloadProgressReport> report;
489
490   Url fileurl(getFileUrl(_url, filename));
491
492   bool retry = false;
493
494   ExternalProgram::Arguments args;
495
496   fillAriaCmdLine(_aria2cPath, _aria2cVersion, _settings, fileurl, target.dirname(), args);
497   
498   do
499   {
500     try
501     {
502       report->start(_url, target.asString() );
503
504       ExternalProgram aria(args, ExternalProgram::Stderr_To_Stdout);
505       int nLine = 0;
506
507       //Process response
508       for(std::string ariaResponse( aria.receiveLine());
509           ariaResponse.length();
510           ariaResponse = aria.receiveLine())
511       {
512         //cout << ariaResponse;
513
514         if (!ariaResponse.substr(0,31).compare("Exception: Authorization failed") )
515         {
516             ZYPP_THROW(MediaUnauthorizedException(
517                   _url, "Login failed.", "Login failed", "auth hint"
518                 ));
519         }
520         if (!ariaResponse.substr(0,29).compare("Exception: Resource not found") )
521         {
522             ZYPP_THROW(MediaFileNotFoundException(_url, filename));
523         }
524
525         if (!ariaResponse.substr(0,9).compare("[#2 SIZE:"))
526         {
527           if (!nLine)
528           {
529             size_t left_bound = ariaResponse.find('(',0) + 1;
530             size_t count = ariaResponse.find('%',left_bound) - left_bound;
531             //cout << ariaResponse.substr(left_bound, count) << endl;
532             //progressData.toMax();
533             report->progress ( std::atoi(ariaResponse.substr(left_bound, count).c_str()), _url, -1, -1 );
534             nLine = 1;
535           }
536           else
537           {
538             nLine = 0;
539           }
540         }
541       }
542
543       aria.close();
544
545       report->finish( _url ,  zypp::media::DownloadProgressReport::NO_ERROR, "");
546       retry = false;
547     }
548
549     // retry with proper authentication data
550     catch (MediaUnauthorizedException & ex_r)
551     {
552       if(authenticate(ex_r.hint(), !retry))
553         retry = true;
554       else
555       {
556         report->finish(fileurl, zypp::media::DownloadProgressReport::ACCESS_DENIED, ex_r.asUserHistory());
557         ZYPP_RETHROW(ex_r);
558       }
559
560     }
561     // unexpected exception
562     catch (MediaException & excpt_r)
563     {
564       // FIXME: error number fix
565       report->finish(fileurl, zypp::media::DownloadProgressReport::ERROR, excpt_r.asUserHistory());
566       ZYPP_RETHROW(excpt_r);
567     }
568   }
569   while (retry);
570
571   report->finish(fileurl, zypp::media::DownloadProgressReport::NO_ERROR, "");
572 }
573
574 bool MediaAria2c::getDoesFileExist( const Pathname & filename ) const
575 {
576   bool retry = false;
577   AuthData auth_data;
578
579   do
580   {
581     try
582     {
583       return doGetDoesFileExist( filename );
584     }
585     // authentication problem, retry with proper authentication data
586     catch (MediaUnauthorizedException & ex_r)
587     {
588       if(authenticate(ex_r.hint(), !retry))
589         retry = true;
590       else
591         ZYPP_RETHROW(ex_r);
592     }
593     // unexpected exception
594     catch (MediaException & excpt_r)
595     {
596       ZYPP_RETHROW(excpt_r);
597     }
598   }
599   while (retry);
600
601   return false;
602 }
603
604 bool MediaAria2c::doGetDoesFileExist( const Pathname & filename ) const
605 {
606   DBG << filename.asString() << endl;
607   callback::SendReport<DownloadProgressReport> report;
608
609   Url fileurl(getFileUrl(_url, filename));
610   bool retry = false;
611
612   ExternalProgram::Arguments args;
613
614   fillCurlCmdLine("/usr/bin/curl", _settings, fileurl, args);
615   
616   do
617   {
618     try
619     {
620       report->start(_url, fileurl.asString() );
621
622       ExternalProgram curl(args, ExternalProgram::Stderr_To_Stdout);
623       //Process response
624       for(std::string curlResponse( curl.receiveLine());
625           curlResponse.length();
626           curlResponse = curl.receiveLine())
627       {
628       
629           if ( str::contains(curlResponse, "401 Authorization Required") )
630           {
631               ZYPP_THROW(MediaUnauthorizedException(
632                              _url, "Login failed.", "Login failed", "auth hint"
633                              ));
634           }
635
636           if ( str::contains(curlResponse, "404 Not Found") )
637               return false;
638
639           if ( str::contains(curlResponse, "200 OK") )
640               return true;
641       }
642
643       int code = curl.close();
644
645       switch (code)
646       {
647       case 0: break;
648           // connection problems
649           return true;
650       case 1:
651       case 2:
652       case 3:
653       case 7:
654       default:
655           ZYPP_THROW(MediaException(_url.asString()));
656       }
657       
658
659       report->finish( _url ,  zypp::media::DownloadProgressReport::NO_ERROR, "");
660       retry = false;
661     }
662     // retry with proper authentication data
663     catch (MediaUnauthorizedException & ex_r)
664     {
665       if(authenticate(ex_r.hint(), !retry))
666         retry = true;
667       else
668       {
669         report->finish(fileurl, zypp::media::DownloadProgressReport::ACCESS_DENIED, ex_r.asUserHistory());
670         ZYPP_RETHROW(ex_r);
671       }
672
673     }
674     // unexpected exception
675     catch (MediaException & excpt_r)
676     {
677       // FIXME: error number fix
678       report->finish(fileurl, zypp::media::DownloadProgressReport::ERROR, excpt_r.asUserHistory());
679       ZYPP_RETHROW(excpt_r);
680     }
681   }
682   while (retry);
683
684   report->finish(fileurl, zypp::media::DownloadProgressReport::NO_ERROR, "");
685   return true;
686 }
687
688 void MediaAria2c::getDir( const Pathname & dirname, bool recurse_r ) const
689 {
690   filesystem::DirContent content;
691   getDirInfo( content, dirname, /*dots*/false );
692
693   for ( filesystem::DirContent::const_iterator it = content.begin(); it != content.end(); ++it ) {
694       Pathname filename = dirname + it->name;
695       int res = 0;
696
697       switch ( it->type ) {
698       case filesystem::FT_NOT_AVAIL: // old directory.yast contains no typeinfo at all
699       case filesystem::FT_FILE:
700         getFile( filename );
701         break;
702       case filesystem::FT_DIR: // newer directory.yast contain at least directory info
703         if ( recurse_r ) {
704           getDir( filename, recurse_r );
705         } else {
706           res = assert_dir( localPath( filename ) );
707           if ( res ) {
708             WAR << "Ignore error (" << res <<  ") on creating local directory '" << localPath( filename ) << "'" << endl;
709           }
710         }
711         break;
712       default:
713         // don't provide devices, sockets, etc.
714         break;
715       }
716   }
717 }
718
719 bool MediaAria2c::authenticate(const std::string & availAuthTypes, bool firstTry) const
720 {
721     return false;
722 }
723
724
725 void MediaAria2c::getDirInfo( std::list<std::string> & retlist,
726                                const Pathname & dirname, bool dots ) const
727 {
728   getDirectoryYast( retlist, dirname, dots );
729 }
730
731 void MediaAria2c::getDirInfo( filesystem::DirContent & retlist,
732                             const Pathname & dirname, bool dots ) const
733 {
734   getDirectoryYast( retlist, dirname, dots );
735 }
736
737 std::string MediaAria2c::getAria2cVersion()
738 {
739     const char* argv[] =
740     {
741         _aria2cPath.c_str(),
742       "--version",
743       NULL
744     };
745
746     ExternalProgram aria(argv, ExternalProgram::Stderr_To_Stdout);
747
748     std::string vResponse = aria.receiveLine();
749     aria.close();
750     return str::trim(vResponse);
751 }
752
753 #define ARIA_DEFAULT_BINARY "/usr/bin/aria2c"
754
755 Pathname MediaAria2c::whereisAria2c()
756 {
757     Pathname aria2cPathr(ARIA_DEFAULT_BINARY);
758
759     const char* argv[] =
760     {
761       "which",
762       "aria2c",
763       NULL
764     };
765
766     ExternalProgram aria(argv, ExternalProgram::Stderr_To_Stdout);
767
768     std::string ariaResponse( aria.receiveLine());
769     int code = aria.close();
770
771     if( code == 0 )
772     {
773         aria2cPathr = str::trim(ariaResponse);
774         MIL << "We will use aria2c located here:  " << aria2cPathr << endl;
775     }
776     else
777     {
778         MIL << "We don't know were is ari2ac binary. We will use aria2c located here:  " << aria2cPathr << endl;
779     }
780
781     return aria2cPathr;
782 }
783
784 } // namespace media
785 } // namespace zypp
786 //