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