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