04271910a7f28cfa0537f5afdcf2a0a9fbb02b54
[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.h"
25 #include "zypp/media/MediaUserAuth.h"
26 #include "zypp/media/CredentialManager.h"
27 #include "zypp/media/CurlConfig.h"
28 #include "zypp/thread/Once.h"
29 #include "zypp/Target.h"
30 #include "zypp/ZYppFactory.h"
31 #include "zypp/ZConfig.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
41 #define  DETECT_DIR_INDEX       0
42 #define  CONNECT_TIMEOUT        60
43 #define  TRANSFER_TIMEOUT_MAX   60 * 60
44
45 #define EXPLICITLY_NO_PROXY "_none_"
46
47 #undef CURLVERSION_AT_LEAST
48 #define CURLVERSION_AT_LEAST(M,N,O) LIBCURL_VERSION_NUM >= ((((M)<<8)+(N))<<8)+(O)
49
50 using namespace std;
51 using namespace zypp::base;
52
53 namespace
54 {
55   zypp::thread::OnceFlag g_InitOnceFlag = PTHREAD_ONCE_INIT;
56   zypp::thread::OnceFlag g_FreeOnceFlag = PTHREAD_ONCE_INIT;
57
58   extern "C" void _do_free_once()
59   {
60     curl_global_cleanup();
61   }
62
63   extern "C" void globalFreeOnce()
64   {
65     zypp::thread::callOnce(g_FreeOnceFlag, _do_free_once);
66   }
67
68   extern "C" void _do_init_once()
69   {
70     CURLcode ret = curl_global_init( CURL_GLOBAL_ALL );
71     if ( ret != 0 )
72     {
73       WAR << "curl global init failed" << endl;
74     }
75
76     //
77     // register at exit handler ?
78     // this may cause trouble, because we can protect it
79     // against ourself only.
80     // if the app sets an atexit handler as well, it will
81     // cause a double free while the second of them runs.
82     //
83     //std::atexit( globalFreeOnce);
84   }
85
86   inline void globalInitOnce()
87   {
88     zypp::thread::callOnce(g_InitOnceFlag, _do_init_once);
89   }
90
91   int log_curl(CURL *curl, curl_infotype info,
92                char *ptr, size_t len, void *max_lvl)
93   {
94     std::string pfx(" ");
95     long        lvl = 0;
96     switch( info)
97     {
98       case CURLINFO_TEXT:       lvl = 1; pfx = "*"; break;
99       case CURLINFO_HEADER_IN:  lvl = 2; pfx = "<"; break;
100       case CURLINFO_HEADER_OUT: lvl = 2; pfx = ">"; break;
101       default:                                      break;
102     }
103     if( lvl > 0 && max_lvl != NULL && lvl <= *((long *)max_lvl))
104     {
105       std::string                            msg(ptr, len);
106       std::list<std::string>                 lines;
107       std::list<std::string>::const_iterator line;
108       zypp::str::split(msg, std::back_inserter(lines), "\r\n");
109       for(line = lines.begin(); line != lines.end(); ++line)
110       {
111         DBG << pfx << " " << *line << endl;
112       }
113     }
114     return 0;
115   }
116
117   static size_t log_redirects_curl( char *ptr, size_t size, size_t nmemb, void *userdata)
118   {
119     // INT << "got header: " << string(ptr, ptr + size*nmemb) << endl;
120
121     char * lstart = ptr, * lend = 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       if ( lstart[0] == 'L'
131         && lstart[1] == 'o'
132         && lstart[2] == 'c'
133         && lstart[3] == 'a'
134         && lstart[4] == 't'
135         && lstart[5] == 'i'
136         && lstart[6] == 'o'
137         && lstart[7] == 'n'
138         && lstart[8] == ':' )
139       {
140         std::string line { lstart, *(lend-1)=='\r' ? lend-1 : lend };
141         DBG << "redirecting to " << line << endl;
142         if ( userdata ) {
143           *reinterpret_cast<std::string *>( userdata ) = line;
144         }
145         return max;
146       }
147
148       // continue with the next line
149       if (pos + 1 < max)
150       {
151         ++lend;
152         ++pos;
153       }
154       else
155         break;
156     }
157
158     return max;
159   }
160 }
161
162 namespace zypp {
163
164   ///////////////////////////////////////////////////////////////////
165   namespace env
166   {
167     namespace
168     {
169       inline int getZYPP_MEDIA_CURL_IPRESOLVE()
170       {
171         int ret = 0;
172         if ( const char * envp = getenv( "ZYPP_MEDIA_CURL_IPRESOLVE" ) )
173         {
174           WAR << "env set: $ZYPP_MEDIA_CURL_IPRESOLVE='" << envp << "'" << endl;
175           if (      strcmp( envp, "4" ) == 0 )  ret = 4;
176           else if ( strcmp( envp, "6" ) == 0 )  ret = 6;
177         }
178         return ret;
179       }
180     }
181
182     inline int ZYPP_MEDIA_CURL_IPRESOLVE()
183     {
184       static int _v = getZYPP_MEDIA_CURL_IPRESOLVE();
185       return _v;
186     }
187   } // namespace env
188   ///////////////////////////////////////////////////////////////////
189
190   namespace media {
191
192   namespace {
193     struct ProgressData
194     {
195       ProgressData( CURL *_curl, time_t _timeout = 0, const Url & _url = Url(),
196                     ByteCount expectedFileSize_r = 0,
197                     callback::SendReport<DownloadProgressReport> *_report = nullptr )
198         : curl( _curl )
199         , url( _url )
200         , timeout( _timeout )
201         , reached( false )
202         , fileSizeExceeded ( false )
203         , report( _report )
204         , _expectedFileSize( expectedFileSize_r )
205       {}
206
207       CURL      *curl;
208       Url       url;
209       time_t    timeout;
210       bool      reached;
211       bool      fileSizeExceeded;
212       callback::SendReport<DownloadProgressReport> *report;
213       ByteCount _expectedFileSize;
214
215       time_t _timeStart = 0;    ///< Start total stats
216       time_t _timeLast  = 0;    ///< Start last period(~1sec)
217       time_t _timeRcv   = 0;    ///< Start of no-data timeout
218       time_t _timeNow   = 0;    ///< Now
219
220       double _dnlTotal  = 0.0;  ///< Bytes to download or 0 if unknown
221       double _dnlLast   = 0.0;  ///< Bytes downloaded at period start
222       double _dnlNow    = 0.0;  ///< Bytes downloaded now
223
224       int    _dnlPercent= 0;    ///< Percent completed or 0 if _dnlTotal is unknown
225
226       double _drateTotal= 0.0;  ///< Download rate so far
227       double _drateLast = 0.0;  ///< Download rate in last period
228
229       void updateStats( double dltotal = 0.0, double dlnow = 0.0 )
230       {
231         time_t now = _timeNow = time(0);
232
233         // If called without args (0.0), recompute based on the last values seen
234         if ( dltotal && dltotal != _dnlTotal )
235           _dnlTotal = dltotal;
236
237         if ( dlnow && dlnow != _dnlNow )
238         {
239           _timeRcv = now;
240           _dnlNow = dlnow;
241         }
242         else if ( !_dnlNow && !_dnlTotal )
243         {
244           // Start time counting as soon as first data arrives.
245           // Skip the connection / redirection time at begin.
246           return;
247         }
248
249         // init or reset if time jumps back
250         if ( !_timeStart || _timeStart > now )
251           _timeStart = _timeLast = _timeRcv = now;
252
253         // timeout condition
254         if ( timeout )
255           reached = ( (now - _timeRcv) > timeout );
256
257         // check if the downloaded data is already bigger than what we expected
258         fileSizeExceeded = _expectedFileSize > 0 && _expectedFileSize < static_cast<ByteCount::SizeType>(_dnlNow);
259
260         // percentage:
261         if ( _dnlTotal )
262           _dnlPercent = int(_dnlNow * 100 / _dnlTotal);
263
264         // download rates:
265         _drateTotal = _dnlNow / std::max( int(now - _timeStart), 1 );
266
267         if ( _timeLast < now )
268         {
269           _drateLast = (_dnlNow - _dnlLast) / int(now - _timeLast);
270           // start new period
271           _timeLast  = now;
272           _dnlLast   = _dnlNow;
273         }
274         else if ( _timeStart == _timeLast )
275           _drateLast = _drateTotal;
276       }
277
278       int reportProgress() const
279       {
280         if ( fileSizeExceeded )
281           return 1;
282         if ( reached )
283           return 1;     // no-data timeout
284         if ( report && !(*report)->progress( _dnlPercent, url, _drateTotal, _drateLast ) )
285           return 1;     // user requested abort
286         return 0;
287       }
288
289
290       // download rate of the last period (cca 1 sec)
291       double                                        drate_period;
292       // bytes downloaded at the start of the last period
293       double                                        dload_period;
294       // seconds from the start of the download
295       long                                          secs;
296       // average download rate
297       double                                        drate_avg;
298       // last time the progress was reported
299       time_t                                        ltime;
300       // bytes downloaded at the moment the progress was last reported
301       double                                        dload;
302       // bytes uploaded at the moment the progress was last reported
303       double                                        uload;
304     };
305
306     ///////////////////////////////////////////////////////////////////
307
308     inline void escape( string & str_r,
309                         const char char_r, const string & escaped_r ) {
310       for ( string::size_type pos = str_r.find( char_r );
311             pos != string::npos; pos = str_r.find( char_r, pos ) ) {
312               str_r.replace( pos, 1, escaped_r );
313             }
314     }
315
316     inline string escapedPath( string path_r ) {
317       escape( path_r, ' ', "%20" );
318       return path_r;
319     }
320
321     inline string unEscape( string text_r ) {
322       char * tmp = curl_unescape( text_r.c_str(), 0 );
323       string ret( tmp );
324       curl_free( tmp );
325       return ret;
326     }
327
328   }
329
330 /**
331  * Fills the settings structure using options passed on the url
332  * for example ?timeout=x&proxy=foo
333  */
334 void fillSettingsFromUrl( const Url &url, TransferSettings &s )
335 {
336     std::string param(url.getQueryParam("timeout"));
337     if( !param.empty())
338     {
339       long num = str::strtonum<long>(param);
340       if( num >= 0 && num <= TRANSFER_TIMEOUT_MAX)
341           s.setTimeout(num);
342     }
343
344     if ( ! url.getUsername().empty() )
345     {
346         s.setUsername(url.getUsername());
347         if ( url.getPassword().size() )
348             s.setPassword(url.getPassword());
349     }
350     else
351     {
352         // if there is no username, set anonymous auth
353         if ( ( url.getScheme() == "ftp" || url.getScheme() == "tftp" ) && s.username().empty() )
354             s.setAnonymousAuth();
355     }
356
357     if ( url.getScheme() == "https" )
358     {
359         s.setVerifyPeerEnabled(false);
360         s.setVerifyHostEnabled(false);
361
362         std::string verify( url.getQueryParam("ssl_verify"));
363         if( verify.empty() ||
364             verify == "yes")
365         {
366             s.setVerifyPeerEnabled(true);
367             s.setVerifyHostEnabled(true);
368         }
369         else if( verify == "no")
370         {
371             s.setVerifyPeerEnabled(false);
372             s.setVerifyHostEnabled(false);
373         }
374         else
375         {
376             std::vector<std::string>                 flags;
377             std::vector<std::string>::const_iterator flag;
378             str::split( verify, std::back_inserter(flags), ",");
379             for(flag = flags.begin(); flag != flags.end(); ++flag)
380             {
381                 if( *flag == "host")
382                     s.setVerifyHostEnabled(true);
383                 else if( *flag == "peer")
384                     s.setVerifyPeerEnabled(true);
385                 else
386                     ZYPP_THROW(MediaBadUrlException(url, "Unknown ssl_verify flag"));
387             }
388         }
389     }
390
391     Pathname ca_path( url.getQueryParam("ssl_capath") );
392     if( ! ca_path.empty())
393     {
394         if( !PathInfo(ca_path).isDir() || ! ca_path.absolute())
395             ZYPP_THROW(MediaBadUrlException(url, "Invalid ssl_capath path"));
396         else
397             s.setCertificateAuthoritiesPath(ca_path);
398     }
399
400     Pathname client_cert( url.getQueryParam("ssl_clientcert") );
401     if( ! client_cert.empty())
402     {
403         if( !PathInfo(client_cert).isFile() || !client_cert.absolute())
404             ZYPP_THROW(MediaBadUrlException(url, "Invalid ssl_clientcert file"));
405         else
406             s.setClientCertificatePath(client_cert);
407     }
408     Pathname client_key( url.getQueryParam("ssl_clientkey") );
409     if( ! client_key.empty())
410     {
411         if( !PathInfo(client_key).isFile() || !client_key.absolute())
412             ZYPP_THROW(MediaBadUrlException(url, "Invalid ssl_clientkey file"));
413         else
414             s.setClientKeyPath(client_key);
415     }
416
417     param = url.getQueryParam( "proxy" );
418     if ( ! param.empty() )
419     {
420         if ( param == EXPLICITLY_NO_PROXY ) {
421             // Workaround TransferSettings shortcoming: With an
422             // empty proxy string, code will continue to look for
423             // valid proxy settings. So set proxy to some non-empty
424             // string, to indicate it has been explicitly disabled.
425             s.setProxy(EXPLICITLY_NO_PROXY);
426             s.setProxyEnabled(false);
427         }
428         else {
429             string proxyport( url.getQueryParam( "proxyport" ) );
430             if ( ! proxyport.empty() ) {
431                 param += ":" + proxyport;
432             }
433             s.setProxy(param);
434             s.setProxyEnabled(true);
435         }
436     }
437
438     param = url.getQueryParam( "proxyuser" );
439     if ( ! param.empty() )
440     {
441       s.setProxyUsername(param);
442       s.setProxyPassword(url.getQueryParam( "proxypass" ));
443     }
444
445     // HTTP authentication type
446     param = url.getQueryParam("auth");
447     if (!param.empty() && (url.getScheme() == "http" || url.getScheme() == "https"))
448     {
449         try
450         {
451             CurlAuthData::auth_type_str2long(param);    // check if we know it
452         }
453         catch (MediaException & ex_r)
454         {
455             DBG << "Rethrowing as MediaUnauthorizedException.";
456             ZYPP_THROW(MediaUnauthorizedException(url, ex_r.msg(), "", ""));
457         }
458         s.setAuthType(param);
459     }
460
461     // workarounds
462     param = url.getQueryParam("head_requests");
463     if( !param.empty() && param == "no" )
464         s.setHeadRequestsAllowed(false);
465 }
466
467 /**
468  * Reads the system proxy configuration and fills the settings
469  * structure proxy information
470  */
471 void fillSettingsSystemProxy( const Url&url, TransferSettings &s )
472 {
473     ProxyInfo proxy_info;
474     if ( proxy_info.useProxyFor( url ) )
475     {
476       // We must extract any 'user:pass' from the proxy url
477       // otherwise they won't make it into curl (.curlrc wins).
478       try {
479         Url u( proxy_info.proxy( url ) );
480         s.setProxy( u.asString( url::ViewOption::WITH_SCHEME + url::ViewOption::WITH_HOST + url::ViewOption::WITH_PORT ) );
481         // don't overwrite explicit auth settings
482         if ( s.proxyUsername().empty() )
483         {
484           s.setProxyUsername( u.getUsername( url::E_ENCODED ) );
485           s.setProxyPassword( u.getPassword( url::E_ENCODED ) );
486         }
487         s.setProxyEnabled( true );
488       }
489       catch (...) {}    // no proxy if URL is malformed
490     }
491 }
492
493 Pathname MediaCurl::_cookieFile = "/var/lib/YaST2/cookies";
494
495 /**
496  * initialized only once, this gets the anonymous id
497  * from the target, which we pass in the http header
498  */
499 static const char *const anonymousIdHeader()
500 {
501   // we need to add the release and identifier to the
502   // agent string.
503   // The target could be not initialized, and then this information
504   // is guessed.
505   static const std::string _value(
506       str::trim( str::form(
507           "X-ZYpp-AnonymousId: %s",
508           Target::anonymousUniqueId( Pathname()/*guess root*/ ).c_str() ) )
509   );
510   return _value.c_str();
511 }
512
513 /**
514  * initialized only once, this gets the distribution flavor
515  * from the target, which we pass in the http header
516  */
517 static const char *const distributionFlavorHeader()
518 {
519   // we need to add the release and identifier to the
520   // agent string.
521   // The target could be not initialized, and then this information
522   // is guessed.
523   static const std::string _value(
524       str::trim( str::form(
525           "X-ZYpp-DistributionFlavor: %s",
526           Target::distributionFlavor( Pathname()/*guess root*/ ).c_str() ) )
527   );
528   return _value.c_str();
529 }
530
531 /**
532  * initialized only once, this gets the agent string
533  * which also includes the curl version
534  */
535 static const char *const agentString()
536 {
537   // we need to add the release and identifier to the
538   // agent string.
539   // The target could be not initialized, and then this information
540   // is guessed.
541   static const std::string _value(
542     str::form(
543        "ZYpp %s (curl %s) %s"
544        , VERSION
545        , curl_version_info(CURLVERSION_NOW)->version
546        , Target::targetDistribution( Pathname()/*guess root*/ ).c_str()
547     )
548   );
549   return _value.c_str();
550 }
551
552 // we use this define to unbloat code as this C setting option
553 // and catching exception is done frequently.
554 /** \todo deprecate SET_OPTION and use the typed versions below. */
555 #define SET_OPTION(opt,val) do { \
556     ret = curl_easy_setopt ( _curl, opt, val ); \
557     if ( ret != 0) { \
558       ZYPP_THROW(MediaCurlSetOptException(_url, _curlError)); \
559     } \
560   } while ( false )
561
562 #define SET_OPTION_OFFT(opt,val) SET_OPTION(opt,(curl_off_t)val)
563 #define SET_OPTION_LONG(opt,val) SET_OPTION(opt,(long)val)
564 #define SET_OPTION_VOID(opt,val) SET_OPTION(opt,(void*)val)
565
566 MediaCurl::MediaCurl( const Url &      url_r,
567                       const Pathname & attach_point_hint_r )
568     : MediaHandler( url_r, attach_point_hint_r,
569                     "/", // urlpath at attachpoint
570                     true ), // does_download
571       _curl( NULL ),
572       _customHeaders(0L)
573 {
574   _curlError[0] = '\0';
575   _curlDebug = 0L;
576
577   MIL << "MediaCurl::MediaCurl(" << url_r << ", " << attach_point_hint_r << ")" << endl;
578
579   globalInitOnce();
580
581   if( !attachPoint().empty())
582   {
583     PathInfo ainfo(attachPoint());
584     Pathname apath(attachPoint() + "XXXXXX");
585     char    *atemp = ::strdup( apath.asString().c_str());
586     char    *atest = NULL;
587     if( !ainfo.isDir() || !ainfo.userMayRWX() ||
588          atemp == NULL || (atest=::mkdtemp(atemp)) == NULL)
589     {
590       WAR << "attach point " << ainfo.path()
591           << " is not useable for " << url_r.getScheme() << endl;
592       setAttachPoint("", true);
593     }
594     else if( atest != NULL)
595       ::rmdir(atest);
596
597     if( atemp != NULL)
598       ::free(atemp);
599   }
600 }
601
602 Url MediaCurl::clearQueryString(const Url &url) const
603 {
604   Url curlUrl (url);
605   curlUrl.setUsername( "" );
606   curlUrl.setPassword( "" );
607   curlUrl.setPathParams( "" );
608   curlUrl.setFragment( "" );
609   curlUrl.delQueryParam("cookies");
610   curlUrl.delQueryParam("proxy");
611   curlUrl.delQueryParam("proxyport");
612   curlUrl.delQueryParam("proxyuser");
613   curlUrl.delQueryParam("proxypass");
614   curlUrl.delQueryParam("ssl_capath");
615   curlUrl.delQueryParam("ssl_verify");
616   curlUrl.delQueryParam("ssl_clientcert");
617   curlUrl.delQueryParam("timeout");
618   curlUrl.delQueryParam("auth");
619   curlUrl.delQueryParam("username");
620   curlUrl.delQueryParam("password");
621   curlUrl.delQueryParam("mediahandler");
622   curlUrl.delQueryParam("credentials");
623   curlUrl.delQueryParam("head_requests");
624   return curlUrl;
625 }
626
627 TransferSettings & MediaCurl::settings()
628 {
629     return _settings;
630 }
631
632
633 void MediaCurl::setCookieFile( const Pathname &fileName )
634 {
635   _cookieFile = fileName;
636 }
637
638 ///////////////////////////////////////////////////////////////////
639
640 void MediaCurl::checkProtocol(const Url &url) const
641 {
642   curl_version_info_data *curl_info = NULL;
643   curl_info = curl_version_info(CURLVERSION_NOW);
644   // curl_info does not need any free (is static)
645   if (curl_info->protocols)
646   {
647     const char * const *proto;
648     std::string        scheme( url.getScheme());
649     bool               found = false;
650     for(proto=curl_info->protocols; !found && *proto; ++proto)
651     {
652       if( scheme == std::string((const char *)*proto))
653         found = true;
654     }
655     if( !found)
656     {
657       std::string msg("Unsupported protocol '");
658       msg += scheme;
659       msg += "'";
660       ZYPP_THROW(MediaBadUrlException(_url, msg));
661     }
662   }
663 }
664
665 void MediaCurl::setupEasy()
666 {
667   {
668     char *ptr = getenv("ZYPP_MEDIA_CURL_DEBUG");
669     _curlDebug = (ptr && *ptr) ? str::strtonum<long>( ptr) : 0L;
670     if( _curlDebug > 0)
671     {
672       curl_easy_setopt( _curl, CURLOPT_VERBOSE, 1L);
673       curl_easy_setopt( _curl, CURLOPT_DEBUGFUNCTION, log_curl);
674       curl_easy_setopt( _curl, CURLOPT_DEBUGDATA, &_curlDebug);
675     }
676   }
677
678   curl_easy_setopt(_curl, CURLOPT_HEADERFUNCTION, log_redirects_curl);
679   curl_easy_setopt(_curl, CURLOPT_HEADERDATA, &_lastRedirect);
680   CURLcode ret = curl_easy_setopt( _curl, CURLOPT_ERRORBUFFER, _curlError );
681   if ( ret != 0 ) {
682     ZYPP_THROW(MediaCurlSetOptException(_url, "Error setting error buffer"));
683   }
684
685   SET_OPTION(CURLOPT_FAILONERROR, 1L);
686   SET_OPTION(CURLOPT_NOSIGNAL, 1L);
687
688   // create non persistant settings
689   // so that we don't add headers twice
690   TransferSettings vol_settings(_settings);
691
692   // add custom headers for download.opensuse.org (bsc#955801)
693   if ( _url.getHost() == "download.opensuse.org" )
694   {
695     vol_settings.addHeader(anonymousIdHeader());
696     vol_settings.addHeader(distributionFlavorHeader());
697   }
698   vol_settings.addHeader("Pragma:");
699
700   _settings.setTimeout(ZConfig::instance().download_transfer_timeout());
701   _settings.setConnectTimeout(CONNECT_TIMEOUT);
702
703   _settings.setUserAgentString(agentString());
704
705   // fill some settings from url query parameters
706   try
707   {
708       fillSettingsFromUrl(_url, _settings);
709   }
710   catch ( const MediaException &e )
711   {
712       disconnectFrom();
713       ZYPP_RETHROW(e);
714   }
715   // if the proxy was not set (or explicitly unset) by url, then look...
716   if ( _settings.proxy().empty() )
717   {
718       // ...at the system proxy settings
719       fillSettingsSystemProxy(_url, _settings);
720   }
721
722   /** Force IPv4/v6 */
723   if ( env::ZYPP_MEDIA_CURL_IPRESOLVE() )
724   {
725     switch ( env::ZYPP_MEDIA_CURL_IPRESOLVE() )
726     {
727       case 4: SET_OPTION(CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); break;
728       case 6: SET_OPTION(CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6); break;
729     }
730   }
731
732  /**
733   * Connect timeout
734   */
735   SET_OPTION(CURLOPT_CONNECTTIMEOUT, _settings.connectTimeout());
736   // If a transfer timeout is set, also set CURLOPT_TIMEOUT to an upper limit
737   // just in case curl does not trigger its progress callback frequently
738   // enough.
739   if ( _settings.timeout() )
740   {
741     SET_OPTION(CURLOPT_TIMEOUT, 3600L);
742   }
743
744   // follow any Location: header that the server sends as part of
745   // an HTTP header (#113275)
746   SET_OPTION(CURLOPT_FOLLOWLOCATION, 1L);
747   // 3 redirects seem to be too few in some cases (bnc #465532)
748   SET_OPTION(CURLOPT_MAXREDIRS, 6L);
749
750   if ( _url.getScheme() == "https" )
751   {
752 #if CURLVERSION_AT_LEAST(7,19,4)
753     // restrict following of redirections from https to https only
754     SET_OPTION( CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS );
755 #endif
756
757     if( _settings.verifyPeerEnabled() ||
758         _settings.verifyHostEnabled() )
759     {
760       SET_OPTION(CURLOPT_CAPATH, _settings.certificateAuthoritiesPath().c_str());
761     }
762
763     if( ! _settings.clientCertificatePath().empty() )
764     {
765       SET_OPTION(CURLOPT_SSLCERT, _settings.clientCertificatePath().c_str());
766     }
767     if( ! _settings.clientKeyPath().empty() )
768     {
769       SET_OPTION(CURLOPT_SSLKEY, _settings.clientKeyPath().c_str());
770     }
771
772 #ifdef CURLSSLOPT_ALLOW_BEAST
773     // see bnc#779177
774     ret = curl_easy_setopt( _curl, CURLOPT_SSL_OPTIONS, CURLSSLOPT_ALLOW_BEAST );
775     if ( ret != 0 ) {
776       disconnectFrom();
777       ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
778     }
779 #endif
780     SET_OPTION(CURLOPT_SSL_VERIFYPEER, _settings.verifyPeerEnabled() ? 1L : 0L);
781     SET_OPTION(CURLOPT_SSL_VERIFYHOST, _settings.verifyHostEnabled() ? 2L : 0L);
782     // bnc#903405 - POODLE: libzypp should only talk TLS
783     SET_OPTION(CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1);
784   }
785
786   SET_OPTION(CURLOPT_USERAGENT, _settings.userAgentString().c_str() );
787
788   /*---------------------------------------------------------------*
789    CURLOPT_USERPWD: [user name]:[password]
790
791    Url::username/password -> CURLOPT_USERPWD
792    If not provided, anonymous FTP identification
793    *---------------------------------------------------------------*/
794
795   if ( _settings.userPassword().size() )
796   {
797     SET_OPTION(CURLOPT_USERPWD, _settings.userPassword().c_str());
798     string use_auth = _settings.authType();
799     if (use_auth.empty())
800       use_auth = "digest,basic";        // our default
801     long auth = CurlAuthData::auth_type_str2long(use_auth);
802     if( auth != CURLAUTH_NONE)
803     {
804       DBG << "Enabling HTTP authentication methods: " << use_auth
805           << " (CURLOPT_HTTPAUTH=" << auth << ")" << std::endl;
806       SET_OPTION(CURLOPT_HTTPAUTH, auth);
807     }
808   }
809
810   if ( _settings.proxyEnabled() && ! _settings.proxy().empty() )
811   {
812     DBG << "Proxy: '" << _settings.proxy() << "'" << endl;
813     SET_OPTION(CURLOPT_PROXY, _settings.proxy().c_str());
814     SET_OPTION(CURLOPT_PROXYAUTH, CURLAUTH_BASIC|CURLAUTH_DIGEST|CURLAUTH_NTLM );
815     /*---------------------------------------------------------------*
816      *    CURLOPT_PROXYUSERPWD: [user name]:[password]
817      *
818      * Url::option(proxyuser and proxypassword) -> CURLOPT_PROXYUSERPWD
819      *  If not provided, $HOME/.curlrc is evaluated
820      *---------------------------------------------------------------*/
821
822     string proxyuserpwd = _settings.proxyUserPassword();
823
824     if ( proxyuserpwd.empty() )
825     {
826       CurlConfig curlconf;
827       CurlConfig::parseConfig(curlconf); // parse ~/.curlrc
828       if ( curlconf.proxyuserpwd.empty() )
829         DBG << "Proxy: ~/.curlrc does not contain the proxy-user option" << endl;
830       else
831       {
832         proxyuserpwd = curlconf.proxyuserpwd;
833         DBG << "Proxy: using proxy-user from ~/.curlrc" << endl;
834       }
835     }
836     else
837     {
838       DBG << "Proxy: using provided proxy-user '" << _settings.proxyUsername() << "'" << endl;
839     }
840
841     if ( ! proxyuserpwd.empty() )
842     {
843       SET_OPTION(CURLOPT_PROXYUSERPWD, unEscape( proxyuserpwd ).c_str());
844     }
845   }
846 #if CURLVERSION_AT_LEAST(7,19,4)
847   else if ( _settings.proxy() == EXPLICITLY_NO_PROXY )
848   {
849     // Explicitly disabled in URL (see fillSettingsFromUrl()).
850     // This should also prevent libcurl from looking into the environment.
851     DBG << "Proxy: explicitly NOPROXY" << endl;
852     SET_OPTION(CURLOPT_NOPROXY, "*");
853   }
854 #endif
855   else
856   {
857     DBG << "Proxy: not explicitly set" << endl;
858     DBG << "Proxy: libcurl may look into the environment" << endl;
859   }
860
861   /** Speed limits */
862   if ( _settings.minDownloadSpeed() != 0 )
863   {
864       SET_OPTION(CURLOPT_LOW_SPEED_LIMIT, _settings.minDownloadSpeed());
865       // default to 10 seconds at low speed
866       SET_OPTION(CURLOPT_LOW_SPEED_TIME, 60L);
867   }
868
869 #if CURLVERSION_AT_LEAST(7,15,5)
870   if ( _settings.maxDownloadSpeed() != 0 )
871       SET_OPTION_OFFT(CURLOPT_MAX_RECV_SPEED_LARGE, _settings.maxDownloadSpeed());
872 #endif
873
874   /*---------------------------------------------------------------*
875    *---------------------------------------------------------------*/
876
877   _currentCookieFile = _cookieFile.asString();
878   if ( str::strToBool( _url.getQueryParam( "cookies" ), true ) )
879     SET_OPTION(CURLOPT_COOKIEFILE, _currentCookieFile.c_str() );
880   else
881     MIL << "No cookies requested" << endl;
882   SET_OPTION(CURLOPT_COOKIEJAR, _currentCookieFile.c_str() );
883   SET_OPTION(CURLOPT_PROGRESSFUNCTION, &progressCallback );
884   SET_OPTION(CURLOPT_NOPROGRESS, 0L);
885
886 #if CURLVERSION_AT_LEAST(7,18,0)
887   // bnc #306272
888     SET_OPTION(CURLOPT_PROXY_TRANSFER_MODE, 1L );
889 #endif
890   // append settings custom headers to curl
891   for ( TransferSettings::Headers::const_iterator it = vol_settings.headersBegin();
892         it != vol_settings.headersEnd();
893         ++it )
894   {
895     // MIL << "HEADER " << *it << std::endl;
896
897       _customHeaders = curl_slist_append(_customHeaders, it->c_str());
898       if ( !_customHeaders )
899           ZYPP_THROW(MediaCurlInitException(_url));
900   }
901
902   SET_OPTION(CURLOPT_HTTPHEADER, _customHeaders);
903 }
904
905 ///////////////////////////////////////////////////////////////////
906
907
908 void MediaCurl::attachTo (bool next)
909 {
910   if ( next )
911     ZYPP_THROW(MediaNotSupportedException(_url));
912
913   if ( !_url.isValid() )
914     ZYPP_THROW(MediaBadUrlException(_url));
915
916   checkProtocol(_url);
917   if( !isUseableAttachPoint( attachPoint() ) )
918   {
919     setAttachPoint( createAttachPoint(), true );
920   }
921
922   disconnectFrom(); // clean _curl if needed
923   _curl = curl_easy_init();
924   if ( !_curl ) {
925     ZYPP_THROW(MediaCurlInitException(_url));
926   }
927   try
928     {
929       setupEasy();
930     }
931   catch (Exception & ex)
932     {
933       disconnectFrom();
934       ZYPP_RETHROW(ex);
935     }
936
937   // FIXME: need a derived class to propelly compare url's
938   MediaSourceRef media( new MediaSource(_url.getScheme(), _url.asString()));
939   setMediaSource(media);
940 }
941
942 bool
943 MediaCurl::checkAttachPoint(const Pathname &apoint) const
944 {
945   return MediaHandler::checkAttachPoint( apoint, true, true);
946 }
947
948 ///////////////////////////////////////////////////////////////////
949
950 void MediaCurl::disconnectFrom()
951 {
952   if ( _customHeaders )
953   {
954     curl_slist_free_all(_customHeaders);
955     _customHeaders = 0L;
956   }
957
958   if ( _curl )
959   {
960     curl_easy_cleanup( _curl );
961     _curl = NULL;
962   }
963 }
964
965 ///////////////////////////////////////////////////////////////////
966
967 void MediaCurl::releaseFrom( const std::string & ejectDev )
968 {
969   disconnect();
970 }
971
972 Url MediaCurl::getFileUrl( const Pathname & filename_r ) const
973 {
974   // Simply extend the URLs pathname. An 'absolute' URL path
975   // is achieved by encoding the leading '/' in an URL path:
976   //   URL: ftp://user@server           -> ~user
977   //   URL: ftp://user@server/          -> ~user
978   //   URL: ftp://user@server//         -> ~user
979   //   URL: ftp://user@server/%2F       -> /
980   //                         ^- this '/' is just a separator
981   Url newurl( _url );
982   newurl.setPathName( ( Pathname("./"+_url.getPathName()) / filename_r ).asString().substr(1) );
983   return newurl;
984 }
985
986 ///////////////////////////////////////////////////////////////////
987
988 void MediaCurl::getFile(const Pathname & filename , const ByteCount &expectedFileSize_r) const
989 {
990     // Use absolute file name to prevent access of files outside of the
991     // hierarchy below the attach point.
992     getFileCopy(filename, localPath(filename).absolutename(), expectedFileSize_r);
993 }
994
995 ///////////////////////////////////////////////////////////////////
996
997 void MediaCurl::getFileCopy( const Pathname & filename , const Pathname & target, const ByteCount &expectedFileSize_r ) const
998 {
999   callback::SendReport<DownloadProgressReport> report;
1000
1001   Url fileurl(getFileUrl(filename));
1002
1003   bool retry = false;
1004
1005   do
1006   {
1007     try
1008     {
1009       doGetFileCopy(filename, target, report, expectedFileSize_r);
1010       retry = false;
1011     }
1012     // retry with proper authentication data
1013     catch (MediaUnauthorizedException & ex_r)
1014     {
1015       if(authenticate(ex_r.hint(), !retry))
1016         retry = true;
1017       else
1018       {
1019         report->finish(fileurl, zypp::media::DownloadProgressReport::ACCESS_DENIED, ex_r.asUserHistory());
1020         ZYPP_RETHROW(ex_r);
1021       }
1022     }
1023     // unexpected exception
1024     catch (MediaException & excpt_r)
1025     {
1026       media::DownloadProgressReport::Error reason = media::DownloadProgressReport::ERROR;
1027       if( typeid(excpt_r) == typeid( media::MediaFileNotFoundException )  ||
1028           typeid(excpt_r) == typeid( media::MediaNotAFileException ) )
1029       {
1030         reason = media::DownloadProgressReport::NOT_FOUND;
1031       }
1032       report->finish(fileurl, reason, excpt_r.asUserHistory());
1033       ZYPP_RETHROW(excpt_r);
1034     }
1035   }
1036   while (retry);
1037
1038   report->finish(fileurl, zypp::media::DownloadProgressReport::NO_ERROR, "");
1039 }
1040
1041 ///////////////////////////////////////////////////////////////////
1042
1043 bool MediaCurl::getDoesFileExist( const Pathname & filename ) const
1044 {
1045   bool retry = false;
1046
1047   do
1048   {
1049     try
1050     {
1051       return doGetDoesFileExist( filename );
1052     }
1053     // authentication problem, retry with proper authentication data
1054     catch (MediaUnauthorizedException & ex_r)
1055     {
1056       if(authenticate(ex_r.hint(), !retry))
1057         retry = true;
1058       else
1059         ZYPP_RETHROW(ex_r);
1060     }
1061     // unexpected exception
1062     catch (MediaException & excpt_r)
1063     {
1064       ZYPP_RETHROW(excpt_r);
1065     }
1066   }
1067   while (retry);
1068
1069   return false;
1070 }
1071
1072 ///////////////////////////////////////////////////////////////////
1073
1074 void MediaCurl::evaluateCurlCode(const Pathname &filename,
1075                                   CURLcode code,
1076                                   bool timeout_reached) const
1077 {
1078   if ( code != 0 )
1079   {
1080     Url url;
1081     if (filename.empty())
1082       url = _url;
1083     else
1084       url = getFileUrl(filename);
1085
1086     std::string err;
1087     {
1088       switch ( code )
1089       {
1090       case CURLE_UNSUPPORTED_PROTOCOL:
1091           err = " Unsupported protocol";
1092           if ( !_lastRedirect.empty() )
1093           {
1094             err += " or redirect (";
1095             err += _lastRedirect;
1096             err += ")";
1097           }
1098           break;
1099       case CURLE_URL_MALFORMAT:
1100       case CURLE_URL_MALFORMAT_USER:
1101           err = " Bad URL";
1102           break;
1103       case CURLE_LOGIN_DENIED:
1104           ZYPP_THROW(
1105               MediaUnauthorizedException(url, "Login failed.", _curlError, ""));
1106           break;
1107       case CURLE_HTTP_RETURNED_ERROR:
1108       {
1109         long httpReturnCode = 0;
1110         CURLcode infoRet = curl_easy_getinfo( _curl,
1111                                               CURLINFO_RESPONSE_CODE,
1112                                               &httpReturnCode );
1113         if ( infoRet == CURLE_OK )
1114         {
1115           string msg = "HTTP response: " + str::numstring( httpReturnCode );
1116           switch ( httpReturnCode )
1117           {
1118           case 401:
1119           {
1120             string auth_hint = getAuthHint();
1121
1122             DBG << msg << " Login failed (URL: " << url.asString() << ")" << std::endl;
1123             DBG << "MediaUnauthorizedException auth hint: '" << auth_hint << "'" << std::endl;
1124
1125             ZYPP_THROW(MediaUnauthorizedException(
1126                            url, "Login failed.", _curlError, auth_hint
1127                            ));
1128           }
1129
1130           case 502: // bad gateway (bnc #1070851)
1131           case 503: // service temporarily unavailable (bnc #462545)
1132             ZYPP_THROW(MediaTemporaryProblemException(url));
1133           case 504: // gateway timeout
1134             ZYPP_THROW(MediaTimeoutException(url));
1135           case 403:
1136           {
1137             string msg403;
1138             if ( url.getHost().find(".suse.com") != string::npos )
1139               msg403 = _("Visit the SUSE Customer Center to check whether your registration is valid and has not expired.");
1140             else if (url.asString().find("novell.com") != string::npos)
1141               msg403 = _("Visit the Novell Customer Center to check whether your registration is valid and has not expired.");
1142             ZYPP_THROW(MediaForbiddenException(url, msg403));
1143           }
1144           case 404:
1145           case 410:
1146               ZYPP_THROW(MediaFileNotFoundException(_url, filename));
1147           }
1148
1149           DBG << msg << " (URL: " << url.asString() << ")" << std::endl;
1150           ZYPP_THROW(MediaCurlException(url, msg, _curlError));
1151         }
1152         else
1153         {
1154           string msg = "Unable to retrieve HTTP response:";
1155           DBG << msg << " (URL: " << url.asString() << ")" << std::endl;
1156           ZYPP_THROW(MediaCurlException(url, msg, _curlError));
1157         }
1158       }
1159       break;
1160       case CURLE_FTP_COULDNT_RETR_FILE:
1161 #if CURLVERSION_AT_LEAST(7,16,0)
1162       case CURLE_REMOTE_FILE_NOT_FOUND:
1163 #endif
1164       case CURLE_FTP_ACCESS_DENIED:
1165       case CURLE_TFTP_NOTFOUND:
1166         err = "File not found";
1167         ZYPP_THROW(MediaFileNotFoundException(_url, filename));
1168         break;
1169       case CURLE_BAD_PASSWORD_ENTERED:
1170       case CURLE_FTP_USER_PASSWORD_INCORRECT:
1171           err = "Login failed";
1172           break;
1173       case CURLE_COULDNT_RESOLVE_PROXY:
1174       case CURLE_COULDNT_RESOLVE_HOST:
1175       case CURLE_COULDNT_CONNECT:
1176       case CURLE_FTP_CANT_GET_HOST:
1177         err = "Connection failed";
1178         break;
1179       case CURLE_WRITE_ERROR:
1180         err = "Write error";
1181         break;
1182       case CURLE_PARTIAL_FILE:
1183       case CURLE_OPERATION_TIMEDOUT:
1184         timeout_reached = true; // fall though to TimeoutException
1185         // fall though...
1186       case CURLE_ABORTED_BY_CALLBACK:
1187          if( timeout_reached )
1188         {
1189           err  = "Timeout reached";
1190           ZYPP_THROW(MediaTimeoutException(url));
1191         }
1192         else
1193         {
1194           err = "User abort";
1195         }
1196         break;
1197       case CURLE_SSL_PEER_CERTIFICATE:
1198       default:
1199         err = "Curl error " + str::numstring( code );
1200         break;
1201       }
1202
1203       // uhm, no 0 code but unknown curl exception
1204       ZYPP_THROW(MediaCurlException(url, err, _curlError));
1205     }
1206   }
1207   else
1208   {
1209     // actually the code is 0, nothing happened
1210   }
1211 }
1212
1213 ///////////////////////////////////////////////////////////////////
1214
1215 bool MediaCurl::doGetDoesFileExist( const Pathname & filename ) const
1216 {
1217   DBG << filename.asString() << endl;
1218
1219   if(!_url.isValid())
1220     ZYPP_THROW(MediaBadUrlException(_url));
1221
1222   if(_url.getHost().empty())
1223     ZYPP_THROW(MediaBadUrlEmptyHostException(_url));
1224
1225   Url url(getFileUrl(filename));
1226
1227   DBG << "URL: " << url.asString() << endl;
1228     // Use URL without options and without username and passwd
1229     // (some proxies dislike them in the URL).
1230     // Curl seems to need the just scheme, hostname and a path;
1231     // the rest was already passed as curl options (in attachTo).
1232   Url curlUrl( clearQueryString(url) );
1233
1234   //
1235     // See also Bug #154197 and ftp url definition in RFC 1738:
1236     // The url "ftp://user@host/foo/bar/file" contains a path,
1237     // that is relative to the user's home.
1238     // The url "ftp://user@host//foo/bar/file" (or also with
1239     // encoded slash as %2f) "ftp://user@host/%2ffoo/bar/file"
1240     // contains an absolute path.
1241   //
1242   _lastRedirect.clear();
1243   string urlBuffer( curlUrl.asString());
1244   CURLcode ret = curl_easy_setopt( _curl, CURLOPT_URL,
1245                                    urlBuffer.c_str() );
1246   if ( ret != 0 ) {
1247     ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
1248   }
1249
1250   // instead of returning no data with NOBODY, we return
1251   // little data, that works with broken servers, and
1252   // works for ftp as well, because retrieving only headers
1253   // ftp will return always OK code ?
1254   // See http://curl.haxx.se/docs/knownbugs.html #58
1255   if (  (_url.getScheme() == "http" ||  _url.getScheme() == "https") &&
1256         _settings.headRequestsAllowed() )
1257     ret = curl_easy_setopt( _curl, CURLOPT_NOBODY, 1L );
1258   else
1259     ret = curl_easy_setopt( _curl, CURLOPT_RANGE, "0-1" );
1260
1261   if ( ret != 0 ) {
1262     curl_easy_setopt( _curl, CURLOPT_NOBODY, 0L);
1263     curl_easy_setopt( _curl, CURLOPT_RANGE, NULL );
1264     /* yes, this is why we never got to get NOBODY working before,
1265        because setting it changes this option too, and we also
1266        need to reset it
1267        See: http://curl.haxx.se/mail/archive-2005-07/0073.html
1268     */
1269     curl_easy_setopt( _curl, CURLOPT_HTTPGET, 1L );
1270     ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
1271   }
1272
1273   FILE *file = ::fopen( "/dev/null", "w" );
1274   if ( !file ) {
1275       ERR << "fopen failed for /dev/null" << endl;
1276       curl_easy_setopt( _curl, CURLOPT_NOBODY, 0L);
1277       curl_easy_setopt( _curl, CURLOPT_RANGE, NULL );
1278       /* yes, this is why we never got to get NOBODY working before,
1279        because setting it changes this option too, and we also
1280        need to reset it
1281        See: http://curl.haxx.se/mail/archive-2005-07/0073.html
1282       */
1283       curl_easy_setopt( _curl, CURLOPT_HTTPGET, 1L );
1284       if ( ret != 0 ) {
1285           ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
1286       }
1287       ZYPP_THROW(MediaWriteException("/dev/null"));
1288   }
1289
1290   ret = curl_easy_setopt( _curl, CURLOPT_WRITEDATA, file );
1291   if ( ret != 0 ) {
1292       ::fclose(file);
1293       std::string err( _curlError);
1294       curl_easy_setopt( _curl, CURLOPT_RANGE, NULL );
1295       curl_easy_setopt( _curl, CURLOPT_NOBODY, 0L);
1296       /* yes, this is why we never got to get NOBODY working before,
1297        because setting it changes this option too, and we also
1298        need to reset it
1299        See: http://curl.haxx.se/mail/archive-2005-07/0073.html
1300       */
1301       curl_easy_setopt( _curl, CURLOPT_HTTPGET, 1L );
1302       if ( ret != 0 ) {
1303           ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
1304       }
1305       ZYPP_THROW(MediaCurlSetOptException(url, err));
1306   }
1307
1308   CURLcode ok = curl_easy_perform( _curl );
1309   MIL << "perform code: " << ok << " [ " << curl_easy_strerror(ok) << " ]" << endl;
1310
1311   // reset curl settings
1312   if (  _url.getScheme() == "http" ||  _url.getScheme() == "https" )
1313   {
1314     curl_easy_setopt( _curl, CURLOPT_NOBODY, 0L);
1315     if ( ret != 0 ) {
1316       ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
1317     }
1318
1319     /* yes, this is why we never got to get NOBODY working before,
1320        because setting it changes this option too, and we also
1321        need to reset it
1322        See: http://curl.haxx.se/mail/archive-2005-07/0073.html
1323     */
1324     curl_easy_setopt( _curl, CURLOPT_HTTPGET, 1L);
1325     if ( ret != 0 ) {
1326       ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
1327     }
1328
1329   }
1330   else
1331   {
1332     // for FTP we set different options
1333     curl_easy_setopt( _curl, CURLOPT_RANGE, NULL);
1334     if ( ret != 0 ) {
1335       ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
1336     }
1337   }
1338
1339   // if the code is not zero, close the file
1340   if ( ok != 0 )
1341       ::fclose(file);
1342
1343   // as we are not having user interaction, the user can't cancel
1344   // the file existence checking, a callback or timeout return code
1345   // will be always a timeout.
1346   try {
1347       evaluateCurlCode( filename, ok, true /* timeout */);
1348   }
1349   catch ( const MediaFileNotFoundException &e ) {
1350       // if the file did not exist then we can return false
1351       return false;
1352   }
1353   catch ( const MediaException &e ) {
1354       // some error, we are not sure about file existence, rethrw
1355       ZYPP_RETHROW(e);
1356   }
1357   // exists
1358   return ( ok == CURLE_OK );
1359 }
1360
1361 ///////////////////////////////////////////////////////////////////
1362
1363
1364 #if DETECT_DIR_INDEX
1365 bool MediaCurl::detectDirIndex() const
1366 {
1367   if(_url.getScheme() != "http" && _url.getScheme() != "https")
1368     return false;
1369   //
1370   // try to check the effective url and set the not_a_file flag
1371   // if the url path ends with a "/", what usually means, that
1372   // we've received a directory index (index.html content).
1373   //
1374   // Note: This may be dangerous and break file retrieving in
1375   //       case of some server redirections ... ?
1376   //
1377   bool      not_a_file = false;
1378   char     *ptr = NULL;
1379   CURLcode  ret = curl_easy_getinfo( _curl,
1380                                      CURLINFO_EFFECTIVE_URL,
1381                                      &ptr);
1382   if ( ret == CURLE_OK && ptr != NULL)
1383   {
1384     try
1385     {
1386       Url         eurl( ptr);
1387       std::string path( eurl.getPathName());
1388       if( !path.empty() && path != "/" && *path.rbegin() == '/')
1389       {
1390         DBG << "Effective url ("
1391             << eurl
1392             << ") seems to provide the index of a directory"
1393             << endl;
1394         not_a_file = true;
1395       }
1396     }
1397     catch( ... )
1398     {}
1399   }
1400   return not_a_file;
1401 }
1402 #endif
1403
1404 ///////////////////////////////////////////////////////////////////
1405
1406 void MediaCurl::doGetFileCopy(const Pathname & filename , const Pathname & target, callback::SendReport<DownloadProgressReport> & report, const ByteCount &expectedFileSize_r, RequestOptions options ) const
1407 {
1408     Pathname dest = target.absolutename();
1409     if( assert_dir( dest.dirname() ) )
1410     {
1411       DBG << "assert_dir " << dest.dirname() << " failed" << endl;
1412       Url url(getFileUrl(filename));
1413       ZYPP_THROW( MediaSystemException(url, "System error on " + dest.dirname().asString()) );
1414     }
1415     string destNew = target.asString() + ".new.zypp.XXXXXX";
1416     char *buf = ::strdup( destNew.c_str());
1417     if( !buf)
1418     {
1419       ERR << "out of memory for temp file name" << endl;
1420       Url url(getFileUrl(filename));
1421       ZYPP_THROW(MediaSystemException(url, "out of memory for temp file name"));
1422     }
1423
1424     int tmp_fd = ::mkostemp( buf, O_CLOEXEC );
1425     if( tmp_fd == -1)
1426     {
1427       free( buf);
1428       ERR << "mkstemp failed for file '" << destNew << "'" << endl;
1429       ZYPP_THROW(MediaWriteException(destNew));
1430     }
1431     destNew = buf;
1432     free( buf);
1433
1434     FILE *file = ::fdopen( tmp_fd, "we" );
1435     if ( !file ) {
1436       ::close( tmp_fd);
1437       filesystem::unlink( destNew );
1438       ERR << "fopen failed for file '" << destNew << "'" << endl;
1439       ZYPP_THROW(MediaWriteException(destNew));
1440     }
1441
1442     DBG << "dest: " << dest << endl;
1443     DBG << "temp: " << destNew << endl;
1444
1445     // set IFMODSINCE time condition (no download if not modified)
1446     if( PathInfo(target).isExist() && !(options & OPTION_NO_IFMODSINCE) )
1447     {
1448       curl_easy_setopt(_curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_IFMODSINCE);
1449       curl_easy_setopt(_curl, CURLOPT_TIMEVALUE, (long)PathInfo(target).mtime());
1450     }
1451     else
1452     {
1453       curl_easy_setopt(_curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_NONE);
1454       curl_easy_setopt(_curl, CURLOPT_TIMEVALUE, 0L);
1455     }
1456     try
1457     {
1458       doGetFileCopyFile(filename, dest, file, report, expectedFileSize_r, options);
1459     }
1460     catch (Exception &e)
1461     {
1462       ::fclose( file );
1463       filesystem::unlink( destNew );
1464       curl_easy_setopt(_curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_NONE);
1465       curl_easy_setopt(_curl, CURLOPT_TIMEVALUE, 0L);
1466       ZYPP_RETHROW(e);
1467     }
1468
1469     long httpReturnCode = 0;
1470     CURLcode infoRet = curl_easy_getinfo(_curl,
1471                                          CURLINFO_RESPONSE_CODE,
1472                                          &httpReturnCode);
1473     bool modified = true;
1474     if (infoRet == CURLE_OK)
1475     {
1476       DBG << "HTTP response: " + str::numstring(httpReturnCode);
1477       if ( httpReturnCode == 304
1478            || ( httpReturnCode == 213 && (_url.getScheme() == "ftp" || _url.getScheme() == "tftp") ) ) // not modified
1479       {
1480         DBG << " Not modified.";
1481         modified = false;
1482       }
1483       DBG << endl;
1484     }
1485     else
1486     {
1487       WAR << "Could not get the reponse code." << endl;
1488     }
1489
1490     if (modified || infoRet != CURLE_OK)
1491     {
1492       // apply umask
1493       if ( ::fchmod( ::fileno(file), filesystem::applyUmaskTo( 0644 ) ) )
1494       {
1495         ERR << "Failed to chmod file " << destNew << endl;
1496       }
1497       if (::fclose( file ))
1498       {
1499         ERR << "Fclose failed for file '" << destNew << "'" << endl;
1500         ZYPP_THROW(MediaWriteException(destNew));
1501       }
1502       // move the temp file into dest
1503       if ( rename( destNew, dest ) != 0 ) {
1504         ERR << "Rename failed" << endl;
1505         ZYPP_THROW(MediaWriteException(dest));
1506       }
1507     }
1508     else
1509     {
1510       // close and remove the temp file
1511       ::fclose( file );
1512       filesystem::unlink( destNew );
1513     }
1514
1515     DBG << "done: " << PathInfo(dest) << endl;
1516 }
1517
1518 ///////////////////////////////////////////////////////////////////
1519
1520 void MediaCurl::doGetFileCopyFile(const Pathname & filename , const Pathname & dest, FILE *file, callback::SendReport<DownloadProgressReport> & report, const ByteCount &expectedFileSize_r, RequestOptions options ) const
1521 {
1522     DBG << filename.asString() << endl;
1523
1524     if(!_url.isValid())
1525       ZYPP_THROW(MediaBadUrlException(_url));
1526
1527     if(_url.getHost().empty())
1528       ZYPP_THROW(MediaBadUrlEmptyHostException(_url));
1529
1530     Url url(getFileUrl(filename));
1531
1532     DBG << "URL: " << url.asString() << endl;
1533     // Use URL without options and without username and passwd
1534     // (some proxies dislike them in the URL).
1535     // Curl seems to need the just scheme, hostname and a path;
1536     // the rest was already passed as curl options (in attachTo).
1537     Url curlUrl( clearQueryString(url) );
1538
1539     //
1540     // See also Bug #154197 and ftp url definition in RFC 1738:
1541     // The url "ftp://user@host/foo/bar/file" contains a path,
1542     // that is relative to the user's home.
1543     // The url "ftp://user@host//foo/bar/file" (or also with
1544     // encoded slash as %2f) "ftp://user@host/%2ffoo/bar/file"
1545     // contains an absolute path.
1546     //
1547     _lastRedirect.clear();
1548     string urlBuffer( curlUrl.asString());
1549     CURLcode ret = curl_easy_setopt( _curl, CURLOPT_URL,
1550                                      urlBuffer.c_str() );
1551     if ( ret != 0 ) {
1552       ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
1553     }
1554
1555     ret = curl_easy_setopt( _curl, CURLOPT_WRITEDATA, file );
1556     if ( ret != 0 ) {
1557       ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
1558     }
1559
1560     // Set callback and perform.
1561     ProgressData progressData(_curl, _settings.timeout(), url, expectedFileSize_r, &report);
1562     if (!(options & OPTION_NO_REPORT_START))
1563       report->start(url, dest);
1564     if ( curl_easy_setopt( _curl, CURLOPT_PROGRESSDATA, &progressData ) != 0 ) {
1565       WAR << "Can't set CURLOPT_PROGRESSDATA: " << _curlError << endl;;
1566     }
1567
1568     ret = curl_easy_perform( _curl );
1569 #if CURLVERSION_AT_LEAST(7,19,4)
1570     // bnc#692260: If the client sends a request with an If-Modified-Since header
1571     // with a future date for the server, the server may respond 200 sending a
1572     // zero size file.
1573     // curl-7.19.4 introduces CURLINFO_CONDITION_UNMET to check this condition.
1574     if ( ftell(file) == 0 && ret == 0 )
1575     {
1576       long httpReturnCode = 33;
1577       if ( curl_easy_getinfo( _curl, CURLINFO_RESPONSE_CODE, &httpReturnCode ) == CURLE_OK && httpReturnCode == 200 )
1578       {
1579         long conditionUnmet = 33;
1580         if ( curl_easy_getinfo( _curl, CURLINFO_CONDITION_UNMET, &conditionUnmet ) == CURLE_OK && conditionUnmet )
1581         {
1582           WAR << "TIMECONDITION unmet - retry without." << endl;
1583           curl_easy_setopt(_curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_NONE);
1584           curl_easy_setopt(_curl, CURLOPT_TIMEVALUE, 0L);
1585           ret = curl_easy_perform( _curl );
1586         }
1587       }
1588     }
1589 #endif
1590
1591     if ( curl_easy_setopt( _curl, CURLOPT_PROGRESSDATA, NULL ) != 0 ) {
1592       WAR << "Can't unset CURLOPT_PROGRESSDATA: " << _curlError << endl;;
1593     }
1594
1595     if ( ret != 0 )
1596     {
1597       ERR << "curl error: " << ret << ": " << _curlError
1598           << ", temp file size " << ftell(file)
1599           << " bytes." << endl;
1600
1601       // the timeout is determined by the progress data object
1602       // which holds whether the timeout was reached or not,
1603       // otherwise it would be a user cancel
1604       try {
1605
1606         if ( progressData.fileSizeExceeded )
1607           ZYPP_THROW(MediaFileSizeExceededException(url, progressData._expectedFileSize));
1608
1609         evaluateCurlCode( filename, ret, progressData.reached );
1610       }
1611       catch ( const MediaException &e ) {
1612         // some error, we are not sure about file existence, rethrw
1613         ZYPP_RETHROW(e);
1614       }
1615     }
1616
1617 #if DETECT_DIR_INDEX
1618     if (!ret && detectDirIndex())
1619       {
1620         ZYPP_THROW(MediaNotAFileException(_url, filename));
1621       }
1622 #endif // DETECT_DIR_INDEX
1623 }
1624
1625 ///////////////////////////////////////////////////////////////////
1626
1627 void MediaCurl::getDir( const Pathname & dirname, bool recurse_r ) const
1628 {
1629   filesystem::DirContent content;
1630   getDirInfo( content, dirname, /*dots*/false );
1631
1632   for ( filesystem::DirContent::const_iterator it = content.begin(); it != content.end(); ++it ) {
1633       Pathname filename = dirname + it->name;
1634       int res = 0;
1635
1636       switch ( it->type ) {
1637       case filesystem::FT_NOT_AVAIL: // old directory.yast contains no typeinfo at all
1638       case filesystem::FT_FILE:
1639         getFile( filename, 0 );
1640         break;
1641       case filesystem::FT_DIR: // newer directory.yast contain at least directory info
1642         if ( recurse_r ) {
1643           getDir( filename, recurse_r );
1644         } else {
1645           res = assert_dir( localPath( filename ) );
1646           if ( res ) {
1647             WAR << "Ignore error (" << res <<  ") on creating local directory '" << localPath( filename ) << "'" << endl;
1648           }
1649         }
1650         break;
1651       default:
1652         // don't provide devices, sockets, etc.
1653         break;
1654       }
1655   }
1656 }
1657
1658 ///////////////////////////////////////////////////////////////////
1659
1660 void MediaCurl::getDirInfo( std::list<std::string> & retlist,
1661                                const Pathname & dirname, bool dots ) const
1662 {
1663   getDirectoryYast( retlist, dirname, dots );
1664 }
1665
1666 ///////////////////////////////////////////////////////////////////
1667
1668 void MediaCurl::getDirInfo( filesystem::DirContent & retlist,
1669                             const Pathname & dirname, bool dots ) const
1670 {
1671   getDirectoryYast( retlist, dirname, dots );
1672 }
1673
1674 ///////////////////////////////////////////////////////////////////
1675 //
1676 int MediaCurl::aliveCallback( void *clientp, double /*dltotal*/, double dlnow, double /*ultotal*/, double /*ulnow*/ )
1677 {
1678   ProgressData *pdata = reinterpret_cast<ProgressData *>( clientp );
1679   if( pdata )
1680   {
1681     // Do not propagate dltotal in alive callbacks. MultiCurl uses this to
1682     // prevent a percentage raise while downloading a metalink file. Download
1683     // activity however is indicated by propagating the download rate (via dlnow).
1684     pdata->updateStats( 0.0, dlnow );
1685     return pdata->reportProgress();
1686   }
1687   return 0;
1688 }
1689
1690 int MediaCurl::progressCallback( void *clientp, double dltotal, double dlnow, double ultotal, double ulnow )
1691 {
1692   ProgressData *pdata = reinterpret_cast<ProgressData *>( clientp );
1693   if( pdata )
1694   {
1695     // work around curl bug that gives us old data
1696     long httpReturnCode = 0;
1697     if ( curl_easy_getinfo( pdata->curl, CURLINFO_RESPONSE_CODE, &httpReturnCode ) != CURLE_OK || httpReturnCode == 0 )
1698       return aliveCallback( clientp, dltotal, dlnow, ultotal, ulnow );
1699
1700     pdata->updateStats( dltotal, dlnow );
1701     return pdata->reportProgress();
1702   }
1703   return 0;
1704 }
1705
1706 CURL *MediaCurl::progressCallback_getcurl( void *clientp )
1707 {
1708   ProgressData *pdata = reinterpret_cast<ProgressData *>(clientp);
1709   return pdata ? pdata->curl : 0;
1710 }
1711
1712 ///////////////////////////////////////////////////////////////////
1713
1714 string MediaCurl::getAuthHint() const
1715 {
1716   long auth_info = CURLAUTH_NONE;
1717
1718   CURLcode infoRet =
1719     curl_easy_getinfo(_curl, CURLINFO_HTTPAUTH_AVAIL, &auth_info);
1720
1721   if(infoRet == CURLE_OK)
1722   {
1723     return CurlAuthData::auth_type_long2str(auth_info);
1724   }
1725
1726   return "";
1727 }
1728
1729 /**
1730  * MediaMultiCurl needs to reset the expected filesize in case a metalink file is downloaded
1731  * otherwise this function should not be called
1732  */
1733 void MediaCurl::resetExpectedFileSize(void *clientp, const ByteCount &expectedFileSize)
1734 {
1735   ProgressData *data = reinterpret_cast<ProgressData *>(clientp);
1736   if ( data ) {
1737     data->_expectedFileSize = expectedFileSize;
1738   }
1739 }
1740
1741 ///////////////////////////////////////////////////////////////////
1742
1743 bool MediaCurl::authenticate(const string & availAuthTypes, bool firstTry) const
1744 {
1745   //! \todo need a way to pass different CredManagerOptions here
1746   CredentialManager cm(CredManagerOptions(ZConfig::instance().repoManagerRoot()));
1747   CurlAuthData_Ptr credentials;
1748
1749   // get stored credentials
1750   AuthData_Ptr cmcred = cm.getCred(_url);
1751
1752   if (cmcred && firstTry)
1753   {
1754     credentials.reset(new CurlAuthData(*cmcred));
1755     DBG << "got stored credentials:" << endl << *credentials << endl;
1756   }
1757   // if not found, ask user
1758   else
1759   {
1760
1761     CurlAuthData_Ptr curlcred;
1762     curlcred.reset(new CurlAuthData());
1763     callback::SendReport<AuthenticationReport> auth_report;
1764
1765     // preset the username if present in current url
1766     if (!_url.getUsername().empty() && firstTry)
1767       curlcred->setUsername(_url.getUsername());
1768     // if CM has found some credentials, preset the username from there
1769     else if (cmcred)
1770       curlcred->setUsername(cmcred->username());
1771
1772     // indicate we have no good credentials from CM
1773     cmcred.reset();
1774
1775     string prompt_msg = str::Format(_("Authentication required for '%s'")) % _url.asString();
1776
1777     // set available authentication types from the exception
1778     // might be needed in prompt
1779     curlcred->setAuthType(availAuthTypes);
1780
1781     // ask user
1782     if (auth_report->prompt(_url, prompt_msg, *curlcred))
1783     {
1784       DBG << "callback answer: retry" << endl
1785           << "CurlAuthData: " << *curlcred << endl;
1786
1787       if (curlcred->valid())
1788       {
1789         credentials = curlcred;
1790           // if (credentials->username() != _url.getUsername())
1791           //   _url.setUsername(credentials->username());
1792           /**
1793            *  \todo find a way to save the url with changed username
1794            *  back to repoinfo or dont store urls with username
1795            *  (and either forbid more repos with the same url and different
1796            *  user, or return a set of credentials from CM and try them one
1797            *  by one)
1798            */
1799       }
1800     }
1801     else
1802     {
1803       DBG << "callback answer: cancel" << endl;
1804     }
1805   }
1806
1807   // set username and password
1808   if (credentials)
1809   {
1810     // HACK, why is this const?
1811     const_cast<MediaCurl*>(this)->_settings.setUsername(credentials->username());
1812     const_cast<MediaCurl*>(this)->_settings.setPassword(credentials->password());
1813
1814     // set username and password
1815     CURLcode ret = curl_easy_setopt(_curl, CURLOPT_USERPWD, _settings.userPassword().c_str());
1816     if ( ret != 0 ) ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
1817
1818     // set available authentication types from the exception
1819     if (credentials->authType() == CURLAUTH_NONE)
1820       credentials->setAuthType(availAuthTypes);
1821
1822     // set auth type (seems this must be set _after_ setting the userpwd)
1823     if (credentials->authType() != CURLAUTH_NONE)
1824     {
1825       // FIXME: only overwrite if not empty?
1826       const_cast<MediaCurl*>(this)->_settings.setAuthType(credentials->authTypeAsString());
1827       ret = curl_easy_setopt(_curl, CURLOPT_HTTPAUTH, credentials->authType());
1828       if ( ret != 0 ) ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
1829     }
1830
1831     if (!cmcred)
1832     {
1833       credentials->setUrl(_url);
1834       cm.addCred(*credentials);
1835       cm.save();
1836     }
1837
1838     return true;
1839   }
1840
1841   return false;
1842 }
1843
1844
1845   } // namespace media
1846 } // namespace zypp
1847 //