Merge branch 'master' of git@git.opensuse.org:projects/zypp/libzypp
[platform/upstream/libzypp.git] / zypp / media / MediaCurl.cc
1 /*---------------------------------------------------------------------\
2 |                          ____ _   __ __ ___                          |
3 |                         |__  / \ / / . \ . \                         |
4 |                           / / \ V /|  _/  _/                         |
5 |                          / /__ | | | | | |                           |
6 |                         /_____||_| |_| |_|                           |
7 |                                                                      |
8 \---------------------------------------------------------------------*/
9 /** \file zypp/media/MediaCurl.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/base/String.h"
19 #include "zypp/base/Gettext.h"
20 #include "zypp/base/Sysconfig.h"
21 #include "zypp/base/Gettext.h"
22
23 #include "zypp/media/MediaCurl.h"
24 #include "zypp/media/proxyinfo/ProxyInfos.h"
25 #include "zypp/media/ProxyInfo.h"
26 #include "zypp/media/MediaUserAuth.h"
27 #include "zypp/media/CredentialManager.h"
28 #include "zypp/media/CurlConfig.h"
29 #include "zypp/thread/Once.h"
30 #include "zypp/Target.h"
31 #include "zypp/ZYppFactory.h"
32
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
52 {
53   zypp::thread::OnceFlag g_InitOnceFlag = PTHREAD_ONCE_INIT;
54   zypp::thread::OnceFlag g_FreeOnceFlag = PTHREAD_ONCE_INIT;
55
56   extern "C" void _do_free_once()
57   {
58     curl_global_cleanup();
59   }
60
61   extern "C" void globalFreeOnce()
62   {
63     zypp::thread::callOnce(g_FreeOnceFlag, _do_free_once);
64   }
65
66   extern "C" void _do_init_once()
67   {
68     CURLcode ret = curl_global_init( CURL_GLOBAL_ALL );
69     if ( ret != 0 )
70     {
71       WAR << "curl global init failed" << endl;
72     }
73
74     //
75     // register at exit handler ?
76     // this may cause trouble, because we can protect it
77     // against ourself only.
78     // if the app sets an atexit handler as well, it will
79     // cause a double free while the second of them runs.
80     //
81     //std::atexit( globalFreeOnce);
82   }
83
84   inline void globalInitOnce()
85   {
86     zypp::thread::callOnce(g_InitOnceFlag, _do_init_once);
87   }
88
89   int log_curl(CURL *curl, curl_infotype info,
90                char *ptr, size_t len, void *max_lvl)
91   {
92     std::string pfx(" ");
93     long        lvl = 0;
94     switch( info)
95     {
96       case CURLINFO_TEXT:       lvl = 1; pfx = "*"; break;
97       case CURLINFO_HEADER_IN:  lvl = 2; pfx = "<"; break;
98       case CURLINFO_HEADER_OUT: lvl = 2; pfx = ">"; break;
99       default:                                      break;
100     }
101     if( lvl > 0 && max_lvl != NULL && lvl <= *((long *)max_lvl))
102     {
103       std::string                            msg(ptr, len);
104       std::list<std::string>                 lines;
105       std::list<std::string>::const_iterator line;
106       zypp::str::split(msg, std::back_inserter(lines), "\r\n");
107       for(line = lines.begin(); line != lines.end(); ++line)
108       {
109         DBG << pfx << " " << *line << endl;
110       }
111     }
112     return 0;
113   }
114 }
115
116 namespace zypp {
117   namespace media {
118
119   namespace {
120     struct ProgressData
121     {
122       ProgressData(const long _timeout, const zypp::Url &_url = zypp::Url(),
123                    callback::SendReport<DownloadProgressReport> *_report=NULL)
124         : timeout(_timeout)
125         , reached(false)
126         , report(_report)
127         , drate_period(-1)
128         , dload_period(0)
129         , secs(0)
130         , drate_avg(-1)
131         , ltime( time(NULL))
132         , dload( 0)
133         , uload( 0)
134         , url(_url)
135       {}
136       long                                          timeout;
137       bool                                          reached;
138       callback::SendReport<DownloadProgressReport> *report;
139       // download rate of the last period (cca 1 sec)
140       double                                        drate_period;
141       // bytes downloaded at the start of the last period
142       double                                        dload_period;
143       // seconds from the start of the download
144       long                                          secs;
145       // average download rate
146       double                                        drate_avg;
147       // last time the progress was reported
148       time_t                                        ltime;
149       // bytes downloaded at the moment the progress was last reported
150       double                                        dload;
151       // bytes uploaded at the moment the progress was last reported
152       double                                        uload;
153       zypp::Url                                     url;
154     };
155
156     ///////////////////////////////////////////////////////////////////
157
158     inline void escape( string & str_r,
159                         const char char_r, const string & escaped_r ) {
160       for ( string::size_type pos = str_r.find( char_r );
161             pos != string::npos; pos = str_r.find( char_r, pos ) ) {
162               str_r.replace( pos, 1, escaped_r );
163             }
164     }
165
166     inline string escapedPath( string path_r ) {
167       escape( path_r, ' ', "%20" );
168       return path_r;
169     }
170
171     inline string unEscape( string text_r ) {
172       char * tmp = curl_unescape( text_r.c_str(), 0 );
173       string ret( tmp );
174       curl_free( tmp );
175       return ret;
176     }
177
178   }
179
180 /**
181  * Fills the settings structure using options passed on the url
182  * for example ?timeout=x&proxy=foo
183  */
184 void fillSettingsFromUrl( const Url &url, TransferSettings &s )
185 {
186     std::string param(url.getQueryParam("timeout"));
187     if( !param.empty())
188     {
189       long num = str::strtonum<long>(param);
190       if( num >= 0 && num <= TRANSFER_TIMEOUT_MAX)
191           s.setTimeout(num);
192     }
193
194     if ( ! url.getUsername().empty() )
195     {
196         s.setUsername(url.getUsername());
197         if ( url.getPassword().size() )
198             s.setPassword(url.getPassword());
199     }
200     else
201     {
202         // if there is no username, set anonymous auth
203         if ( url.getScheme() == "ftp" && s.username().empty() )
204             s.setAnonymousAuth();
205     }
206         
207     if ( url.getScheme() == "https" )
208     {
209         s.setVerifyPeerEnabled(false);
210         s.setVerifyHostEnabled(false);
211
212         std::string verify( url.getQueryParam("ssl_verify"));
213         if( verify.empty() ||
214             verify == "yes")
215         {
216             s.setVerifyPeerEnabled(true);
217             s.setVerifyHostEnabled(true);
218         }
219         else if( verify == "no")
220         {
221             s.setVerifyPeerEnabled(false);
222             s.setVerifyHostEnabled(false);
223         }
224         else
225         {
226             std::vector<std::string>                 flags;
227             std::vector<std::string>::const_iterator flag;
228             str::split( verify, std::back_inserter(flags), ",");
229             for(flag = flags.begin(); flag != flags.end(); ++flag)
230             {
231                 if( *flag == "host")
232                     s.setVerifyHostEnabled(true);
233                 else if( *flag == "peer")
234                     s.setVerifyPeerEnabled(true);
235                 else
236                     ZYPP_THROW(MediaBadUrlException(url, "Unknown ssl_verify flag"));
237             }
238         }
239     }
240     
241     Pathname ca_path = Pathname(url.getQueryParam("ssl_capath")).asString();
242     if( ! ca_path.empty())
243     {    
244         if( !PathInfo(ca_path).isDir() || !Pathname(ca_path).absolute())
245             ZYPP_THROW(MediaBadUrlException(url, "Invalid ssl_capath path"));
246         else
247             s.setCertificateAuthoritiesPath(ca_path);
248     }
249
250     string proxy = url.getQueryParam( "proxy" );
251     if ( ! proxy.empty() )
252     {
253         string proxyport( url.getQueryParam( "proxyport" ) );
254         if ( ! proxyport.empty() ) {
255             proxy += ":" + proxyport;
256         }
257         s.setProxy(proxy);
258         s.setProxyEnabled(true);
259     }
260 }    
261
262 /**
263  * Reads the system proxy configuration and fills the settings
264  * structure proxy information
265  */
266 void fillSettingsSystemProxy( const Url&url, TransferSettings &s )
267 {
268     ProxyInfo proxy_info (ProxyInfo::ImplPtr(new ProxyInfoSysconfig("proxy")));
269
270     if ( proxy_info.enabled())
271     {
272       s.setProxyEnabled(true);
273       std::list<std::string> nope = proxy_info.noProxy();
274       for (ProxyInfo::NoProxyIterator it = proxy_info.noProxyBegin();
275            it != proxy_info.noProxyEnd();
276            it++)
277       {
278         std::string host( str::toLower(url.getHost()));
279         std::string temp( str::toLower(*it));
280
281         // no proxy if it points to a suffix
282         // preceeded by a '.', that maches
283         // the trailing portion of the host.
284         if( temp.size() > 1 && temp.at(0) == '.')
285         {
286           if(host.size() > temp.size() &&
287              host.compare(host.size() - temp.size(), temp.size(), temp) == 0)
288           {
289             DBG << "NO_PROXY: '" << *it  << "' matches host '"
290                                  << host << "'" << endl;
291             s.setProxyEnabled(false);
292             break;
293           }
294         }
295         else
296         // no proxy if we have an exact match
297         if( host == temp)
298         {
299           DBG << "NO_PROXY: '" << *it  << "' matches host '"
300                                << host << "'" << endl;
301           s.setProxyEnabled(false);
302           break;
303         }
304       }
305
306       if ( s.proxyEnabled() )
307           s.setProxy(proxy_info.proxy(url.getScheme()));
308     }
309 }    
310
311 Pathname MediaCurl::_cookieFile = "/var/lib/YaST2/cookies";
312
313 /**
314  * initialized only once, this gets the anonymous id
315  * from the target, which we pass in the http header
316  */
317 static const char *const anonymousIdHeader()
318 {
319   // we need to add the release and identifier to the
320   // agent string.
321   // The target could be not initialized, and then this information
322   // is not available.
323   Target_Ptr target = zypp::getZYpp()->getTarget();
324
325   static const std::string _value(
326       str::trim( str::form(
327           "X-ZYpp-AnonymousId: %s",
328           target ? target->anonymousUniqueId().c_str() : "" ) )
329   );
330   return _value.c_str();
331 }
332
333 /**
334  * initialized only once, this gets the distribution flavor
335  * from the target, which we pass in the http header
336  */
337 static const char *const distributionFlavorHeader()
338 {
339   // we need to add the release and identifier to the
340   // agent string.
341   // The target could be not initialized, and then this information
342   // is not available.
343   Target_Ptr target = zypp::getZYpp()->getTarget();
344
345   static const std::string _value(
346       str::trim( str::form(
347           "X-ZYpp-DistributionFlavor: %s",
348           target ? target->distributionFlavor().c_str() : "" ) )
349   );
350   return _value.c_str();
351 }
352
353 /**
354  * initialized only once, this gets the agent string
355  * which also includes the curl version
356  */
357 static const char *const agentString()
358 {
359   // we need to add the release and identifier to the
360   // agent string.
361   // The target could be not initialized, and then this information
362   // is not available.
363   Target_Ptr target = zypp::getZYpp()->getTarget();
364
365   static const std::string _value(
366     str::form(
367        "ZYpp %s (curl %s) %s"
368        , VERSION
369        , curl_version_info(CURLVERSION_NOW)->version
370        , target ? target->targetDistribution().c_str() : ""
371     )
372   );
373   return _value.c_str();
374 }
375
376 // we use this define to unbloat code as this C setting option
377 // and catching exception is done frequently.
378 #define SET_OPTION(opt,val) { \
379     ret = curl_easy_setopt ( _curl, opt, val ); \
380     if ( ret != 0) { \
381       disconnectFrom(); \
382       ZYPP_THROW(MediaCurlSetOptException(_url, _curlError)); \
383     } \
384   }
385       
386 MediaCurl::MediaCurl( const Url &      url_r,
387                       const Pathname & attach_point_hint_r )
388     : MediaHandler( url_r, attach_point_hint_r,
389                     "/", // urlpath at attachpoint
390                     true ), // does_download
391       _curl( NULL ),
392       _customHeaders(0L)
393 {
394   _curlError[0] = '\0';
395   _curlDebug = 0L;
396
397   MIL << "MediaCurl::MediaCurl(" << url_r << ", " << attach_point_hint_r << ")" << endl;
398
399   globalInitOnce();
400
401   if( !attachPoint().empty())
402   {
403     PathInfo ainfo(attachPoint());
404     Pathname apath(attachPoint() + "XXXXXX");
405     char    *atemp = ::strdup( apath.asString().c_str());
406     char    *atest = NULL;
407     if( !ainfo.isDir() || !ainfo.userMayRWX() ||
408          atemp == NULL || (atest=::mkdtemp(atemp)) == NULL)
409     {
410       WAR << "attach point " << ainfo.path()
411           << " is not useable for " << url_r.getScheme() << endl;
412       setAttachPoint("", true);
413     }
414     else if( atest != NULL)
415       ::rmdir(atest);
416
417     if( atemp != NULL)
418       ::free(atemp);
419   }
420 }
421
422 void MediaCurl::setCookieFile( const Pathname &fileName )
423 {
424   _cookieFile = fileName;
425 }
426
427 ///////////////////////////////////////////////////////////////////
428
429 void MediaCurl::attachTo (bool next)
430 {
431   if ( next )
432     ZYPP_THROW(MediaNotSupportedException(_url));
433
434   if ( !_url.isValid() )
435     ZYPP_THROW(MediaBadUrlException(_url));
436
437   CurlConfig curlconf;
438   CurlConfig::parseConfig(curlconf); // parse ~/.curlrc
439
440   curl_version_info_data *curl_info = NULL;
441   curl_info = curl_version_info(CURLVERSION_NOW);
442   // curl_info does not need any free (is static)
443   if (curl_info->protocols)
444   {
445     const char * const *proto;
446     std::string        scheme( _url.getScheme());
447     bool               found = false;
448     for(proto=curl_info->protocols; !found && *proto; ++proto)
449     {
450       if( scheme == std::string((const char *)*proto))
451         found = true;
452     }
453     if( !found)
454     {
455       std::string msg("Unsupported protocol '");
456       msg += scheme;
457       msg += "'";
458       ZYPP_THROW(MediaBadUrlException(_url, msg));
459     }
460   }
461
462   if( !isUseableAttachPoint(attachPoint()))
463   {
464     std::string mountpoint = createAttachPoint().asString();
465
466     if( mountpoint.empty())
467       ZYPP_THROW( MediaBadAttachPointException(url()));
468
469     setAttachPoint( mountpoint, true);
470   }
471
472   disconnectFrom(); // clean _curl if needed
473   _curl = curl_easy_init();
474   if ( !_curl ) {
475     ZYPP_THROW(MediaCurlInitException(_url));
476   }
477
478   {
479     char *ptr = getenv("ZYPP_MEDIA_CURL_DEBUG");
480     _curlDebug = (ptr && *ptr) ? str::strtonum<long>( ptr) : 0L;
481     if( _curlDebug > 0)
482     {
483       curl_easy_setopt( _curl, CURLOPT_VERBOSE, 1);
484       curl_easy_setopt( _curl, CURLOPT_DEBUGFUNCTION, log_curl);
485       curl_easy_setopt( _curl, CURLOPT_DEBUGDATA, &_curlDebug);
486     }
487   }
488
489   CURLcode ret = curl_easy_setopt( _curl, CURLOPT_ERRORBUFFER, _curlError );
490   if ( ret != 0 ) {
491     disconnectFrom();
492     ZYPP_THROW(MediaCurlSetOptException(_url, "Error setting error buffer"));
493   }
494
495   SET_OPTION(CURLOPT_FAILONERROR,true);
496   SET_OPTION(CURLOPT_NOSIGNAL, 1);
497
498   // add custom headers
499   _settings.addHeader(anonymousIdHeader());
500   _settings.addHeader(distributionFlavorHeader());
501   _settings.addHeader("Pragma:");
502
503   _settings.setTimeout(TRANSFER_TIMEOUT);
504   _settings.setConnectTimeout(CONNECT_TIMEOUT);
505
506   _settings.setUserAgentString(agentString());
507
508   // fill some settings from url query parameters
509   try
510   {
511       fillSettingsFromUrl(_url, _settings);
512   }
513   catch ( const MediaException &e )
514   {
515       disconnectFrom();
516       ZYPP_RETHROW(e);
517   }
518   
519   // if the proxy was not set by url, then look 
520   if ( _settings.proxy().empty() )
521   {
522       // at the system proxy settings
523       fillSettingsSystemProxy(_url, _settings);
524   }
525
526   DBG << "Proxy: " << (_settings.proxy().empty() ? "-none-" : _settings.proxy()) << endl;
527
528  /**
529   * Connect timeout
530   */
531   SET_OPTION(CURLOPT_CONNECTTIMEOUT, _settings.connectTimeout());
532
533   if ( _url.getScheme() == "http" )
534   {
535     // follow any Location: header that the server sends as part of
536     // an HTTP header (#113275)
537     SET_OPTION(CURLOPT_FOLLOWLOCATION, true);
538     SET_OPTION(CURLOPT_MAXREDIRS, 3L);
539     SET_OPTION(CURLOPT_USERAGENT, _settings.userAgentString().c_str() );
540   }
541
542   if ( _url.getScheme() == "https" )
543   {
544     if( _settings.verifyPeerEnabled() || 
545         _settings.verifyHostEnabled() )
546     {
547       SET_OPTION(CURLOPT_CAPATH, _settings.certificateAuthoritiesPath().c_str());      
548     }
549
550     SET_OPTION(CURLOPT_SSL_VERIFYPEER, _settings.verifyPeerEnabled() ? 1L : 0L);
551     SET_OPTION(CURLOPT_SSL_VERIFYHOST, _settings.verifyHostEnabled() ? 2L : 0L);
552     SET_OPTION(CURLOPT_USERAGENT, _settings.userAgentString().c_str() );
553   }
554
555   /*---------------------------------------------------------------*
556    CURLOPT_USERPWD: [user name]:[password]
557
558    Url::username/password -> CURLOPT_USERPWD
559    If not provided, anonymous FTP identification
560    *---------------------------------------------------------------*/
561
562   if ( _settings.userPassword().size() )
563   {
564     SET_OPTION(CURLOPT_USERPWD, unEscape(_settings.userPassword()).c_str());
565
566     //FIXME, we leave this here for now, as it does not make sense yet
567     // to refactor it to the fill settings from url function
568
569     // HTTP authentication type
570     if(_url.getScheme() == "http" || _url.getScheme() == "https")
571     {
572       string use_auth = _url.getQueryParam("auth");
573       if( use_auth.empty())
574         use_auth = "digest,basic";
575
576       try
577       {
578         long auth = CurlAuthData::auth_type_str2long(use_auth);
579         if( auth != CURLAUTH_NONE)
580         {
581           DBG << "Enabling HTTP authentication methods: " << use_auth
582               << " (CURLOPT_HTTPAUTH=" << auth << ")" << std::endl;
583
584           SET_OPTION(CURLOPT_HTTPAUTH, auth);
585         }
586       }
587       catch (MediaException & ex_r)
588       {
589         string auth_hint = getAuthHint();
590
591         DBG << "Rethrowing as MediaUnauthorizedException. auth hint: '"
592             << auth_hint << "'" << endl;
593
594         ZYPP_THROW(MediaUnauthorizedException(
595           _url, ex_r.msg(), _curlError, auth_hint
596         ));
597       }
598     }
599   }
600
601   if ( _settings.proxyEnabled() )
602   {
603     if ( ! _settings.proxy().empty() )
604     {
605       SET_OPTION(CURLOPT_PROXY, _settings.proxy().c_str());
606       /*---------------------------------------------------------------*
607         CURLOPT_PROXYUSERPWD: [user name]:[password]
608         
609         Url::option(proxyuser and proxypassword) -> CURLOPT_PROXYUSERPWD
610         If not provided, $HOME/.curlrc is evaluated
611         *---------------------------------------------------------------*/
612       
613       string proxyuserpwd = _settings.proxyUserPassword();
614
615       if ( proxyuserpwd.empty() )
616       {
617         if (curlconf.proxyuserpwd.empty())
618           DBG << "~/.curlrc does not contain the proxy-user option" << endl;
619         else
620         {
621           proxyuserpwd = curlconf.proxyuserpwd;
622           DBG << "using proxy-user from ~/.curlrc" << endl;
623         }
624       }
625
626       proxyuserpwd = unEscape( proxyuserpwd );
627       if ( ! proxyuserpwd.empty() )
628         SET_OPTION(CURLOPT_PROXYUSERPWD, proxyuserpwd.c_str());
629     }
630   }
631   
632   /** Speed limits */
633   if ( _settings.minDownloadSpeed() != 0 )
634   {
635       SET_OPTION(CURLOPT_LOW_SPEED_LIMIT, _settings.minDownloadSpeed());
636       // default to 10 seconds at low speed
637       SET_OPTION(CURLOPT_LOW_SPEED_TIME, 10);
638   }
639   
640   if ( _settings.maxDownloadSpeed() != 0 )
641       SET_OPTION(CURLOPT_MAX_RECV_SPEED_LARGE, _settings.maxDownloadSpeed());
642
643   /*---------------------------------------------------------------*
644    *---------------------------------------------------------------*/
645
646   _currentCookieFile = _cookieFile.asString();
647   SET_OPTION(CURLOPT_COOKIEFILE, _currentCookieFile.c_str() );
648   SET_OPTION(CURLOPT_COOKIEJAR, _currentCookieFile.c_str() );
649   SET_OPTION(CURLOPT_PROGRESSFUNCTION, &progressCallback );
650   SET_OPTION(CURLOPT_NOPROGRESS, false );
651
652   // bnc #306272
653   SET_OPTION(CURLOPT_PROXY_TRANSFER_MODE, 1 );
654
655   // append settings custom headers to curl
656   for ( TransferSettings::Headers::const_iterator it = _settings.headersBegin();
657         it != _settings.headersEnd();
658         ++it )
659   {
660       
661       _customHeaders = curl_slist_append(_customHeaders, it->c_str());
662       if ( !_customHeaders )
663           ZYPP_THROW(MediaCurlInitException(_url));
664   }
665
666   SET_OPTION(CURLOPT_HTTPHEADER, _customHeaders);
667
668   // FIXME: need a derived class to propelly compare url's
669   MediaSourceRef media( new MediaSource(_url.getScheme(), _url.asString()));
670   setMediaSource(media);
671 }
672
673 bool
674 MediaCurl::checkAttachPoint(const Pathname &apoint) const
675 {
676   return MediaHandler::checkAttachPoint( apoint, true, true);
677 }
678
679 ///////////////////////////////////////////////////////////////////
680
681 void MediaCurl::disconnectFrom()
682 {
683   if ( _customHeaders )
684   {
685     curl_slist_free_all(_customHeaders);
686     _customHeaders = 0L;
687   }
688
689   if ( _curl )
690   {
691     curl_easy_cleanup( _curl );
692     _curl = NULL;
693   }
694 }
695
696 ///////////////////////////////////////////////////////////////////
697
698 void MediaCurl::releaseFrom( const std::string & ejectDev )
699 {
700   disconnect();
701 }
702
703 static Url getFileUrl(const Url & url, const Pathname & filename)
704 {
705   Url newurl(url);
706   string path = url.getPathName();
707   if ( !path.empty() && path != "/" && *path.rbegin() == '/' &&
708        filename.absolute() )
709   {
710     // If url has a path with trailing slash, remove the leading slash from
711     // the absolute file name
712     path += filename.asString().substr( 1, filename.asString().size() - 1 );
713   }
714   else if ( filename.relative() )
715   {
716     // Add trailing slash to path, if not already there
717     if (path.empty()) path = "/";
718     else if (*path.rbegin() != '/' ) path += "/";
719     // Remove "./" from begin of relative file name
720     path += filename.asString().substr( 2, filename.asString().size() - 2 );
721   }
722   else
723   {
724     path += filename.asString();
725   }
726
727   newurl.setPathName(path);
728   return newurl;
729 }
730
731 ///////////////////////////////////////////////////////////////////
732
733 void MediaCurl::getFile( const Pathname & filename ) const
734 {
735     // Use absolute file name to prevent access of files outside of the
736     // hierarchy below the attach point.
737     getFileCopy(filename, localPath(filename).absolutename());
738 }
739
740 ///////////////////////////////////////////////////////////////////
741
742 void MediaCurl::getFileCopy( const Pathname & filename , const Pathname & target) const
743 {
744   callback::SendReport<DownloadProgressReport> report;
745
746   Url fileurl(getFileUrl(_url, filename));
747
748   bool retry = false;
749
750   do
751   {
752     try
753     {
754       doGetFileCopy(filename, target, report);
755       retry = false;
756     }
757     // retry with proper authentication data
758     catch (MediaUnauthorizedException & ex_r)
759     {
760       if(authenticate(ex_r.hint(), !retry))
761         retry = true;
762       else
763       {
764         report->finish(fileurl, zypp::media::DownloadProgressReport::ACCESS_DENIED, ex_r.asUserHistory());
765         ZYPP_RETHROW(ex_r);
766       }
767     }
768     // unexpected exception
769     catch (MediaException & excpt_r)
770     {
771       // FIXME: error number fix
772       report->finish(fileurl, zypp::media::DownloadProgressReport::ERROR, excpt_r.asUserHistory());
773       ZYPP_RETHROW(excpt_r);
774     }
775   }
776   while (retry);
777
778   report->finish(fileurl, zypp::media::DownloadProgressReport::NO_ERROR, "");
779 }
780
781 ///////////////////////////////////////////////////////////////////
782
783 bool MediaCurl::getDoesFileExist( const Pathname & filename ) const
784 {
785   bool retry = false;
786
787   do
788   {
789     try
790     {
791       return doGetDoesFileExist( filename );
792     }
793     // authentication problem, retry with proper authentication data
794     catch (MediaUnauthorizedException & ex_r)
795     {
796       if(authenticate(ex_r.hint(), !retry))
797         retry = true;
798       else
799         ZYPP_RETHROW(ex_r);
800     }
801     // unexpected exception
802     catch (MediaException & excpt_r)
803     {
804       ZYPP_RETHROW(excpt_r);
805     }
806   }
807   while (retry);
808
809   return false;
810 }
811
812 ///////////////////////////////////////////////////////////////////
813
814 void MediaCurl::evaluateCurlCode( const Pathname &filename,
815                                   CURLcode code,
816                                   bool timeout_reached ) const
817 {
818   Url url(getFileUrl(_url, filename));
819   
820   if ( code != 0 )
821   {
822     std::string err;
823     try
824     {
825       switch ( code )
826       {
827       case CURLE_UNSUPPORTED_PROTOCOL:
828       case CURLE_URL_MALFORMAT:
829       case CURLE_URL_MALFORMAT_USER:
830           err = " Bad URL";
831       case CURLE_LOGIN_DENIED:
832           ZYPP_THROW(
833               MediaUnauthorizedException(url, "Login failed.", _curlError, ""));
834           break;
835       case CURLE_HTTP_RETURNED_ERROR:
836       {
837         long httpReturnCode = 0;
838         CURLcode infoRet = curl_easy_getinfo( _curl,
839                                               CURLINFO_RESPONSE_CODE,
840                                               &httpReturnCode );
841         if ( infoRet == CURLE_OK )
842         {
843           string msg = "HTTP response: " + str::numstring( httpReturnCode );
844           switch ( httpReturnCode )
845           {
846           case 401:
847           {
848             string auth_hint = getAuthHint();
849             
850             DBG << msg << " Login failed (URL: " << url.asString() << ")" << std::endl;
851             DBG << "MediaUnauthorizedException auth hint: '" << auth_hint << "'" << std::endl;
852             
853             ZYPP_THROW(MediaUnauthorizedException(
854                            url, "Login failed.", _curlError, auth_hint
855                            ));
856           }
857                     
858           case 503: // service temporarily unavailable (bnc #462545)
859             ZYPP_THROW(MediaTemporaryProblemException(url));            
860           case 504: // gateway timeout
861             ZYPP_THROW(MediaTimeoutException(url));                        
862           case 403:
863             ZYPP_THROW(MediaForbiddenException(url));            
864           case 404:
865               ZYPP_THROW(MediaFileNotFoundException(_url, filename));
866           }
867                     
868           DBG << msg << " (URL: " << url.asString() << ")" << std::endl;
869           ZYPP_THROW(MediaCurlException(url, msg, _curlError));
870         }
871         else
872         {
873           string msg = "Unable to retrieve HTTP response:";
874           DBG << msg << " (URL: " << url.asString() << ")" << std::endl;
875           ZYPP_THROW(MediaCurlException(url, msg, _curlError));
876         }
877       }
878       break;
879       case CURLE_FTP_COULDNT_RETR_FILE:
880       case CURLE_FTP_ACCESS_DENIED:
881         err = "File not found";
882         ZYPP_THROW(MediaFileNotFoundException(_url, filename));
883         break;
884       case CURLE_BAD_PASSWORD_ENTERED:
885       case CURLE_FTP_USER_PASSWORD_INCORRECT:
886           err = "Login failed";
887           break;
888       case CURLE_COULDNT_RESOLVE_PROXY:
889       case CURLE_COULDNT_RESOLVE_HOST:
890       case CURLE_COULDNT_CONNECT:
891       case CURLE_FTP_CANT_GET_HOST:
892         err = "Connection failed";
893         break;
894       case CURLE_WRITE_ERROR:
895         err = "Write error";
896         break;
897       case CURLE_ABORTED_BY_CALLBACK:
898       case CURLE_OPERATION_TIMEDOUT:
899         if( timeout_reached)
900         {
901           err  = "Timeout reached";
902           ZYPP_THROW(MediaTimeoutException(url));
903         }
904         else
905         {
906           err = "User abort";
907         }
908         break;
909       case CURLE_SSL_PEER_CERTIFICATE:
910       default:
911         err = "Unrecognized error";
912         break;
913       }
914       
915       // uhm, no 0 code but unknown curl exception
916       ZYPP_THROW(MediaCurlException(url, err, _curlError));
917     }
918     catch (const MediaException & excpt_r)
919     {
920       ZYPP_RETHROW(excpt_r);
921     }
922   }
923   else
924   {
925     // actually the code is 0, nothing happened
926   }   
927 }    
928
929 ///////////////////////////////////////////////////////////////////
930
931 bool MediaCurl::doGetDoesFileExist( const Pathname & filename ) const
932 {
933   DBG << filename.asString() << endl;
934
935   if(!_url.isValid())
936     ZYPP_THROW(MediaBadUrlException(_url));
937
938   if(_url.getHost().empty())
939     ZYPP_THROW(MediaBadUrlEmptyHostException(_url));
940
941   Url url(getFileUrl(_url, filename));
942
943   DBG << "URL: " << url.asString() << endl;
944     // Use URL without options and without username and passwd
945     // (some proxies dislike them in the URL).
946     // Curl seems to need the just scheme, hostname and a path;
947     // the rest was already passed as curl options (in attachTo).
948   Url curlUrl( url );
949
950   // Use asString + url::ViewOptions instead?
951   curlUrl.setUsername( "" );
952   curlUrl.setPassword( "" );
953   curlUrl.setPathParams( "" );
954   curlUrl.setQueryString( "" );
955   curlUrl.setFragment( "" );
956
957   //
958     // See also Bug #154197 and ftp url definition in RFC 1738:
959     // The url "ftp://user@host/foo/bar/file" contains a path,
960     // that is relative to the user's home.
961     // The url "ftp://user@host//foo/bar/file" (or also with
962     // encoded slash as %2f) "ftp://user@host/%2ffoo/bar/file"
963     // contains an absolute path.
964   //
965   string urlBuffer( curlUrl.asString());
966   CURLcode ret = curl_easy_setopt( _curl, CURLOPT_URL,
967                                    urlBuffer.c_str() );
968   if ( ret != 0 ) {
969     ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
970   }
971
972   // instead of returning no data with NOBODY, we return
973   // little data, that works with broken servers, and
974   // works for ftp as well, because retrieving only headers
975   // ftp will return always OK code ?
976   // See http://curl.haxx.se/docs/knownbugs.html #58
977   if (  _url.getScheme() == "http" ||  _url.getScheme() == "https" )
978     ret = curl_easy_setopt( _curl, CURLOPT_NOBODY, 1 );
979   else
980     ret = curl_easy_setopt( _curl, CURLOPT_RANGE, "0-1" );
981
982   if ( ret != 0 ) {
983     curl_easy_setopt( _curl, CURLOPT_NOBODY, NULL );
984     curl_easy_setopt( _curl, CURLOPT_RANGE, NULL );
985     /* yes, this is why we never got to get NOBODY working before,
986        because setting it changes this option too, and we also
987        need to reset it
988        See: http://curl.haxx.se/mail/archive-2005-07/0073.html
989     */
990     curl_easy_setopt( _curl, CURLOPT_HTTPGET, 1 );
991     ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
992   }
993
994   FILE *file = ::fopen( "/dev/null", "w" );
995   if ( !file ) {
996       ::fclose(file);
997       ERR << "fopen failed for /dev/null" << endl;
998       curl_easy_setopt( _curl, CURLOPT_NOBODY, NULL );
999       curl_easy_setopt( _curl, CURLOPT_RANGE, NULL );
1000       /* yes, this is why we never got to get NOBODY working before,
1001        because setting it changes this option too, and we also
1002        need to reset it
1003        See: http://curl.haxx.se/mail/archive-2005-07/0073.html
1004       */
1005       curl_easy_setopt( _curl, CURLOPT_HTTPGET, 1 );
1006       if ( ret != 0 ) {
1007           ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
1008       }
1009       ZYPP_THROW(MediaWriteException("/dev/null"));
1010   }
1011
1012   ret = curl_easy_setopt( _curl, CURLOPT_WRITEDATA, file );
1013   if ( ret != 0 ) {
1014       ::fclose(file);
1015       std::string err( _curlError);
1016       curl_easy_setopt( _curl, CURLOPT_RANGE, NULL );
1017       curl_easy_setopt( _curl, CURLOPT_NOBODY, NULL );
1018       /* yes, this is why we never got to get NOBODY working before,
1019        because setting it changes this option too, and we also
1020        need to reset it
1021        See: http://curl.haxx.se/mail/archive-2005-07/0073.html
1022       */
1023       curl_easy_setopt( _curl, CURLOPT_HTTPGET, 1 );
1024       if ( ret != 0 ) {
1025           ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
1026       }
1027       ZYPP_THROW(MediaCurlSetOptException(url, err));
1028   }
1029
1030   CURLcode ok = curl_easy_perform( _curl );
1031   MIL << "perform code: " << ok << " [ " << curl_easy_strerror(ok) << " ]" << endl;
1032
1033   // reset curl settings
1034   if (  _url.getScheme() == "http" ||  _url.getScheme() == "https" )
1035   {
1036     curl_easy_setopt( _curl, CURLOPT_NOBODY, NULL );
1037     if ( ret != 0 ) {
1038       ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
1039     }
1040
1041     /* yes, this is why we never got to get NOBODY working before,
1042        because setting it changes this option too, and we also
1043        need to reset it
1044        See: http://curl.haxx.se/mail/archive-2005-07/0073.html
1045     */
1046     curl_easy_setopt( _curl, CURLOPT_HTTPGET, 1);
1047     if ( ret != 0 ) {
1048       ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
1049     }
1050
1051   }
1052   else
1053   {
1054     // for FTP we set different options
1055     curl_easy_setopt( _curl, CURLOPT_RANGE, NULL);
1056     if ( ret != 0 ) {
1057       ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
1058     }
1059   }
1060   
1061   // if the code is not zero, close the file
1062   if ( ok != 0 )
1063       ::fclose(file);
1064   
1065   // as we are not having user interaction, the user can't cancel
1066   // the file existence checking, a callback or timeout return code
1067   // will be always a timeout.
1068   try {
1069       evaluateCurlCode( filename, ok, true /* timeout */);
1070   }
1071   catch ( const MediaFileNotFoundException &e ) {
1072       // if the file did not exist then we can return false
1073       return false;
1074   }
1075   catch ( const MediaException &e ) {
1076       // some error, we are not sure about file existence, rethrw
1077       ZYPP_RETHROW(e);
1078   }
1079   // exists
1080   return ( ok == CURLE_OK );
1081 }
1082
1083 ///////////////////////////////////////////////////////////////////
1084
1085 void MediaCurl::doGetFileCopy( const Pathname & filename , const Pathname & target, callback::SendReport<DownloadProgressReport> & report, RequestOptions options ) const
1086 {
1087     DBG << filename.asString() << endl;
1088
1089     if(!_url.isValid())
1090       ZYPP_THROW(MediaBadUrlException(_url));
1091
1092     if(_url.getHost().empty())
1093       ZYPP_THROW(MediaBadUrlEmptyHostException(_url));
1094
1095     Url url(getFileUrl(_url, filename));
1096
1097     Pathname dest = target.absolutename();
1098     if( assert_dir( dest.dirname() ) )
1099     {
1100       DBG << "assert_dir " << dest.dirname() << " failed" << endl;
1101       ZYPP_THROW( MediaSystemException(url, "System error on " + dest.dirname().asString()) );
1102     }
1103
1104     DBG << "URL: " << url.asString() << endl;
1105     // Use URL without options and without username and passwd
1106     // (some proxies dislike them in the URL).
1107     // Curl seems to need the just scheme, hostname and a path;
1108     // the rest was already passed as curl options (in attachTo).
1109     Url curlUrl( url );
1110
1111     // Use asString + url::ViewOptions instead?
1112     curlUrl.setUsername( "" );
1113     curlUrl.setPassword( "" );
1114     curlUrl.setPathParams( "" );
1115     curlUrl.setQueryString( "" );
1116     curlUrl.setFragment( "" );
1117
1118     //
1119     // See also Bug #154197 and ftp url definition in RFC 1738:
1120     // The url "ftp://user@host/foo/bar/file" contains a path,
1121     // that is relative to the user's home.
1122     // The url "ftp://user@host//foo/bar/file" (or also with
1123     // encoded slash as %2f) "ftp://user@host/%2ffoo/bar/file"
1124     // contains an absolute path.
1125     //
1126     string urlBuffer( curlUrl.asString());
1127     CURLcode ret = curl_easy_setopt( _curl, CURLOPT_URL,
1128                                      urlBuffer.c_str() );
1129     if ( ret != 0 ) {
1130       ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
1131     }
1132
1133     // set IFMODSINCE time condition (no download if not modified)
1134     if( PathInfo(target).isExist() )
1135     {
1136       curl_easy_setopt(_curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_IFMODSINCE);
1137       curl_easy_setopt(_curl, CURLOPT_TIMEVALUE, PathInfo(target).mtime());
1138     }
1139     else
1140     {
1141       curl_easy_setopt(_curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_NONE);
1142       curl_easy_setopt(_curl, CURLOPT_TIMEVALUE, 0);
1143     }
1144
1145     string destNew = target.asString() + ".new.zypp.XXXXXX";
1146     char *buf = ::strdup( destNew.c_str());
1147     if( !buf)
1148     {
1149       ERR << "out of memory for temp file name" << endl;
1150       ZYPP_THROW(MediaSystemException(
1151         url, "out of memory for temp file name"
1152       ));
1153     }
1154
1155     int tmp_fd = ::mkstemp( buf );
1156     if( tmp_fd == -1)
1157     {
1158       free( buf);
1159       ERR << "mkstemp failed for file '" << destNew << "'" << endl;
1160       ZYPP_THROW(MediaWriteException(destNew));
1161     }
1162     destNew = buf;
1163     free( buf);
1164
1165     FILE *file = ::fdopen( tmp_fd, "w" );
1166     if ( !file ) {
1167       ::close( tmp_fd);
1168       filesystem::unlink( destNew );
1169       ERR << "fopen failed for file '" << destNew << "'" << endl;
1170       ZYPP_THROW(MediaWriteException(destNew));
1171     }
1172
1173     DBG << "dest: " << dest << endl;
1174     DBG << "temp: " << destNew << endl;
1175
1176     ret = curl_easy_setopt( _curl, CURLOPT_WRITEDATA, file );
1177     if ( ret != 0 ) {
1178       ::fclose( file );
1179       filesystem::unlink( destNew );
1180       ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
1181     }
1182
1183     // Set callback and perform.
1184     ProgressData progressData(_settings.timeout(), url, &report);
1185     report->start(url, dest);
1186     if ( curl_easy_setopt( _curl, CURLOPT_PROGRESSDATA, &progressData ) != 0 ) {
1187       WAR << "Can't set CURLOPT_PROGRESSDATA: " << _curlError << endl;;
1188     }
1189
1190     ret = curl_easy_perform( _curl );
1191
1192     if ( curl_easy_setopt( _curl, CURLOPT_PROGRESSDATA, NULL ) != 0 ) {
1193       WAR << "Can't unset CURLOPT_PROGRESSDATA: " << _curlError << endl;;
1194     }
1195
1196     if ( ret != 0 )
1197     {
1198       ERR << "curl error: " << ret << ": " << _curlError
1199           << ", temp file size " << PathInfo(destNew).size()
1200           << " byte." << endl;
1201
1202       ::fclose( file );
1203       filesystem::unlink( destNew );
1204
1205       // the timeout is determined by the progress data object
1206       // which holds wheter the timeout was reached or not,
1207       // otherwise it would be a user cancel
1208       try {
1209         evaluateCurlCode( filename, ret, progressData.reached);
1210       }
1211       catch ( const MediaException &e ) {
1212         // some error, we are not sure about file existence, rethrw
1213         ZYPP_RETHROW(e);
1214       }
1215     }
1216     
1217 #if DETECT_DIR_INDEX
1218     else
1219     if(curlUrl.getScheme() == "http" ||
1220        curlUrl.getScheme() == "https")
1221     {
1222       //
1223       // try to check the effective url and set the not_a_file flag
1224       // if the url path ends with a "/", what usually means, that
1225       // we've received a directory index (index.html content).
1226       //
1227       // Note: This may be dangerous and break file retrieving in
1228       //       case of some server redirections ... ?
1229       //
1230       bool      not_a_file = false;
1231       char     *ptr = NULL;
1232       CURLcode  ret = curl_easy_getinfo( _curl,
1233                                          CURLINFO_EFFECTIVE_URL,
1234                                          &ptr);
1235       if ( ret == CURLE_OK && ptr != NULL)
1236       {
1237         try
1238         {
1239           Url         eurl( ptr);
1240           std::string path( eurl.getPathName());
1241           if( !path.empty() && path != "/" && *path.rbegin() == '/')
1242           {
1243             DBG << "Effective url ("
1244                 << eurl
1245                 << ") seems to provide the index of a directory"
1246                 << endl;
1247             not_a_file = true;
1248           }
1249         }
1250         catch( ... )
1251         {}
1252       }
1253
1254       if( not_a_file)
1255       {
1256         ::fclose( file );
1257         filesystem::unlink( destNew );
1258         ZYPP_THROW(MediaNotAFileException(_url, filename));
1259       }
1260     }
1261 #endif // DETECT_DIR_INDEX
1262
1263     long httpReturnCode = 0;
1264     CURLcode infoRet = curl_easy_getinfo(_curl,
1265                                          CURLINFO_RESPONSE_CODE,
1266                                          &httpReturnCode);
1267     bool modified = true;
1268     if (infoRet == CURLE_OK)
1269     {
1270       DBG << "HTTP response: " + str::numstring(httpReturnCode);
1271       if ( httpReturnCode == 304
1272            || ( httpReturnCode == 213 && _url.getScheme() == "ftp" ) ) // not modified
1273       {
1274         DBG << " Not modified.";
1275         modified = false;
1276       }
1277       DBG << endl;
1278     }
1279     else
1280     {
1281       WAR << "Could not get the reponse code." << endl;
1282     }
1283
1284     if (modified || infoRet != CURLE_OK)
1285     {
1286       // apply umask
1287       if ( ::fchmod( ::fileno(file), filesystem::applyUmaskTo( 0644 ) ) )
1288       {
1289         ERR << "Failed to chmod file " << destNew << endl;
1290       }
1291       ::fclose( file );
1292
1293       // move the temp file into dest
1294       if ( rename( destNew, dest ) != 0 ) {
1295         ERR << "Rename failed" << endl;
1296         ZYPP_THROW(MediaWriteException(dest));
1297       }
1298     }
1299     else
1300     {
1301       // close and remove the temp file
1302       ::fclose( file );
1303       filesystem::unlink( destNew );
1304     }
1305
1306     DBG << "done: " << PathInfo(dest) << endl;
1307 }
1308
1309 ///////////////////////////////////////////////////////////////////
1310
1311 void MediaCurl::getDir( const Pathname & dirname, bool recurse_r ) const
1312 {
1313   filesystem::DirContent content;
1314   getDirInfo( content, dirname, /*dots*/false );
1315
1316   for ( filesystem::DirContent::const_iterator it = content.begin(); it != content.end(); ++it ) {
1317       Pathname filename = dirname + it->name;
1318       int res = 0;
1319
1320       switch ( it->type ) {
1321       case filesystem::FT_NOT_AVAIL: // old directory.yast contains no typeinfo at all
1322       case filesystem::FT_FILE:
1323         getFile( filename );
1324         break;
1325       case filesystem::FT_DIR: // newer directory.yast contain at least directory info
1326         if ( recurse_r ) {
1327           getDir( filename, recurse_r );
1328         } else {
1329           res = assert_dir( localPath( filename ) );
1330           if ( res ) {
1331             WAR << "Ignore error (" << res <<  ") on creating local directory '" << localPath( filename ) << "'" << endl;
1332           }
1333         }
1334         break;
1335       default:
1336         // don't provide devices, sockets, etc.
1337         break;
1338       }
1339   }
1340 }
1341
1342 ///////////////////////////////////////////////////////////////////
1343
1344 void MediaCurl::getDirInfo( std::list<std::string> & retlist,
1345                                const Pathname & dirname, bool dots ) const
1346 {
1347   getDirectoryYast( retlist, dirname, dots );
1348 }
1349
1350 ///////////////////////////////////////////////////////////////////
1351
1352 void MediaCurl::getDirInfo( filesystem::DirContent & retlist,
1353                             const Pathname & dirname, bool dots ) const
1354 {
1355   getDirectoryYast( retlist, dirname, dots );
1356 }
1357
1358 ///////////////////////////////////////////////////////////////////
1359
1360 int MediaCurl::progressCallback( void *clientp,
1361                                  double dltotal, double dlnow,
1362                                  double ultotal, double ulnow)
1363 {
1364   ProgressData *pdata = reinterpret_cast<ProgressData *>(clientp);
1365   if( pdata)
1366   {
1367     time_t now   = time(NULL);
1368     if( now > 0)
1369     {
1370         // reset time of last change in case initial time()
1371         // failed or the time was adjusted (goes backward)
1372         if( pdata->ltime <= 0 || pdata->ltime > now)
1373         {
1374           pdata->ltime = now;
1375         }
1376
1377         // start time counting as soon as first data arrives
1378         // (skip the connection / redirection time at begin)
1379         time_t dif = 0;
1380         if (dlnow > 0 || ulnow > 0)
1381         {
1382           dif = (now - pdata->ltime);
1383           dif = dif > 0 ? dif : 0;
1384
1385           pdata->secs += dif;
1386         }
1387
1388         // update the drate_avg and drate_period only after a second has passed
1389         // (this callback is called much more often than a second)
1390         // otherwise the values would be far from accurate when measuring
1391         // the time in seconds
1392         //! \todo more accurate download rate computationn, e.g. compute average value from last 5 seconds, or work with milliseconds instead of seconds
1393
1394         if ( pdata->secs > 1 && (dif > 0 || dlnow == dltotal ))
1395           pdata->drate_avg = (dlnow / pdata->secs);
1396
1397         if ( dif > 0 )
1398         {
1399           pdata->drate_period = ((dlnow - pdata->dload_period) / dif);
1400           pdata->dload_period = dlnow;
1401         }
1402     }
1403
1404     // send progress report first, abort transfer if requested
1405     if( pdata->report)
1406     {
1407       if (!(*(pdata->report))->progress(int( dlnow * 100 / dltotal ),
1408                                         pdata->url,
1409                                         pdata->drate_avg,
1410                                         pdata->drate_period))
1411       {
1412         return 1; // abort transfer
1413       }
1414     }
1415
1416     // check if we there is a timeout set
1417     if( pdata->timeout > 0)
1418     {
1419       if( now > 0)
1420       {
1421         bool progress = false;
1422
1423         // update download data if changed, mark progress
1424         if( dlnow != pdata->dload)
1425         {
1426           progress     = true;
1427           pdata->dload = dlnow;
1428           pdata->ltime = now;
1429         }
1430         // update upload data if changed, mark progress
1431         if( ulnow != pdata->uload)
1432         {
1433           progress     = true;
1434           pdata->uload = ulnow;
1435           pdata->ltime = now;
1436         }
1437
1438         if( !progress && (now >= (pdata->ltime + pdata->timeout)))
1439         {
1440           pdata->reached = true;
1441           return 1; // aborts transfer
1442         }
1443       }
1444     }
1445   }
1446   return 0;
1447 }
1448
1449 ///////////////////////////////////////////////////////////////////
1450
1451 string MediaCurl::getAuthHint() const
1452 {
1453   long auth_info = CURLAUTH_NONE;
1454
1455   CURLcode infoRet =
1456     curl_easy_getinfo(_curl, CURLINFO_HTTPAUTH_AVAIL, &auth_info);
1457
1458   if(infoRet == CURLE_OK)
1459   {
1460     return CurlAuthData::auth_type_long2str(auth_info);
1461   }
1462
1463   return "";
1464 }
1465
1466 ///////////////////////////////////////////////////////////////////
1467
1468 bool MediaCurl::authenticate(const string & availAuthTypes, bool firstTry) const
1469 {
1470   //! \todo need a way to pass different CredManagerOptions here
1471   Target_Ptr target = zypp::getZYpp()->getTarget();
1472   CredentialManager cm(CredManagerOptions(target ? target->root() : ""));
1473   CurlAuthData_Ptr credentials;
1474
1475   // get stored credentials
1476   AuthData_Ptr cmcred = cm.getCred(_url);
1477
1478   if (cmcred && firstTry)
1479   {
1480     credentials.reset(new CurlAuthData(*cmcred));
1481     DBG << "got stored credentials:" << endl << *credentials << endl;
1482   }
1483   // if not found, ask user
1484   else
1485   {
1486
1487     CurlAuthData_Ptr curlcred;
1488     curlcred.reset(new CurlAuthData());
1489     callback::SendReport<AuthenticationReport> auth_report;
1490
1491     // preset the username if present in current url
1492     if (!_url.getUsername().empty() && firstTry)
1493       curlcred->setUsername(_url.getUsername());
1494     // if CM has found some credentials, preset the username from there
1495     else if (cmcred)
1496       curlcred->setUsername(cmcred->username());
1497
1498     // indicate we have no good credentials from CM
1499     cmcred.reset();
1500
1501     string prompt_msg = boost::str(boost::format(
1502       //!\todo add comma to the message for the next release
1503       _("Authentication required for '%s'")) % _url.asString());
1504
1505     // set available authentication types from the exception
1506     // might be needed in prompt
1507     curlcred->setAuthType(availAuthTypes);
1508
1509     // ask user
1510     if (auth_report->prompt(_url, prompt_msg, *curlcred))
1511     {
1512       DBG << "callback answer: retry" << endl
1513           << "CurlAuthData: " << *curlcred << endl;
1514
1515       if (curlcred->valid())
1516       {
1517         credentials = curlcred;
1518           // if (credentials->username() != _url.getUsername())
1519           //   _url.setUsername(credentials->username());
1520           /**
1521            *  \todo find a way to save the url with changed username
1522            *  back to repoinfo or dont store urls with username
1523            *  (and either forbid more repos with the same url and different
1524            *  user, or return a set of credentials from CM and try them one
1525            *  by one)
1526            */
1527       }
1528     }
1529     else
1530     {
1531       DBG << "callback answer: cancel" << endl;
1532     }
1533   }
1534
1535   // set username and password
1536   if (credentials)
1537   {
1538     // HACK, why is this const?
1539     const_cast<MediaCurl*>(this)->_settings.setUsername(credentials->username());
1540     const_cast<MediaCurl*>(this)->_settings.setPassword(credentials->password());
1541
1542     // set username and password
1543     CURLcode ret = curl_easy_setopt(_curl, CURLOPT_USERPWD, _settings.userPassword().c_str());
1544     if ( ret != 0 ) ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
1545
1546     // set available authentication types from the exception
1547     if (credentials->authType() == CURLAUTH_NONE)
1548       credentials->setAuthType(availAuthTypes);
1549
1550     // set auth type (seems this must be set _after_ setting the userpwd
1551     if (credentials->authType() != CURLAUTH_NONE)
1552     {
1553       ret = curl_easy_setopt(_curl, CURLOPT_HTTPAUTH, credentials->authType());
1554       if ( ret != 0 ) ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
1555     }
1556
1557     if (!cmcred)
1558     {
1559       credentials->setUrl(_url);
1560       cm.addCred(*credentials);
1561       cm.save();
1562     }
1563
1564     return true;
1565   }
1566
1567   return false;
1568 }
1569
1570
1571   } // namespace media
1572 } // namespace zypp
1573 //