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