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