41ad5001c575dc281fb22c8808944a223dd4adcd
[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 #if CURLVERSION_AT_LEAST(7,60,0)        // SLE15+
757     SET_OPTION( CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2TLS );
758 #endif
759
760     if( _settings.verifyPeerEnabled() ||
761         _settings.verifyHostEnabled() )
762     {
763       SET_OPTION(CURLOPT_CAPATH, _settings.certificateAuthoritiesPath().c_str());
764     }
765
766     if( ! _settings.clientCertificatePath().empty() )
767     {
768       SET_OPTION(CURLOPT_SSLCERT, _settings.clientCertificatePath().c_str());
769     }
770     if( ! _settings.clientKeyPath().empty() )
771     {
772       SET_OPTION(CURLOPT_SSLKEY, _settings.clientKeyPath().c_str());
773     }
774
775 #ifdef CURLSSLOPT_ALLOW_BEAST
776     // see bnc#779177
777     ret = curl_easy_setopt( _curl, CURLOPT_SSL_OPTIONS, CURLSSLOPT_ALLOW_BEAST );
778     if ( ret != 0 ) {
779       disconnectFrom();
780       ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
781     }
782 #endif
783     SET_OPTION(CURLOPT_SSL_VERIFYPEER, _settings.verifyPeerEnabled() ? 1L : 0L);
784     SET_OPTION(CURLOPT_SSL_VERIFYHOST, _settings.verifyHostEnabled() ? 2L : 0L);
785     // bnc#903405 - POODLE: libzypp should only talk TLS
786     SET_OPTION(CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1);
787   }
788
789   SET_OPTION(CURLOPT_USERAGENT, _settings.userAgentString().c_str() );
790
791   /*---------------------------------------------------------------*
792    CURLOPT_USERPWD: [user name]:[password]
793
794    Url::username/password -> CURLOPT_USERPWD
795    If not provided, anonymous FTP identification
796    *---------------------------------------------------------------*/
797
798   if ( _settings.userPassword().size() )
799   {
800     SET_OPTION(CURLOPT_USERPWD, _settings.userPassword().c_str());
801     string use_auth = _settings.authType();
802     if (use_auth.empty())
803       use_auth = "digest,basic";        // our default
804     long auth = CurlAuthData::auth_type_str2long(use_auth);
805     if( auth != CURLAUTH_NONE)
806     {
807       DBG << "Enabling HTTP authentication methods: " << use_auth
808           << " (CURLOPT_HTTPAUTH=" << auth << ")" << std::endl;
809       SET_OPTION(CURLOPT_HTTPAUTH, auth);
810     }
811   }
812
813   if ( _settings.proxyEnabled() && ! _settings.proxy().empty() )
814   {
815     DBG << "Proxy: '" << _settings.proxy() << "'" << endl;
816     SET_OPTION(CURLOPT_PROXY, _settings.proxy().c_str());
817     SET_OPTION(CURLOPT_PROXYAUTH, CURLAUTH_BASIC|CURLAUTH_DIGEST|CURLAUTH_NTLM );
818     /*---------------------------------------------------------------*
819      *    CURLOPT_PROXYUSERPWD: [user name]:[password]
820      *
821      * Url::option(proxyuser and proxypassword) -> CURLOPT_PROXYUSERPWD
822      *  If not provided, $HOME/.curlrc is evaluated
823      *---------------------------------------------------------------*/
824
825     string proxyuserpwd = _settings.proxyUserPassword();
826
827     if ( proxyuserpwd.empty() )
828     {
829       CurlConfig curlconf;
830       CurlConfig::parseConfig(curlconf); // parse ~/.curlrc
831       if ( curlconf.proxyuserpwd.empty() )
832         DBG << "Proxy: ~/.curlrc does not contain the proxy-user option" << endl;
833       else
834       {
835         proxyuserpwd = curlconf.proxyuserpwd;
836         DBG << "Proxy: using proxy-user from ~/.curlrc" << endl;
837       }
838     }
839     else
840     {
841       DBG << "Proxy: using provided proxy-user '" << _settings.proxyUsername() << "'" << endl;
842     }
843
844     if ( ! proxyuserpwd.empty() )
845     {
846       SET_OPTION(CURLOPT_PROXYUSERPWD, unEscape( proxyuserpwd ).c_str());
847     }
848   }
849 #if CURLVERSION_AT_LEAST(7,19,4)
850   else if ( _settings.proxy() == EXPLICITLY_NO_PROXY )
851   {
852     // Explicitly disabled in URL (see fillSettingsFromUrl()).
853     // This should also prevent libcurl from looking into the environment.
854     DBG << "Proxy: explicitly NOPROXY" << endl;
855     SET_OPTION(CURLOPT_NOPROXY, "*");
856   }
857 #endif
858   else
859   {
860     DBG << "Proxy: not explicitly set" << endl;
861     DBG << "Proxy: libcurl may look into the environment" << endl;
862   }
863
864   /** Speed limits */
865   if ( _settings.minDownloadSpeed() != 0 )
866   {
867       SET_OPTION(CURLOPT_LOW_SPEED_LIMIT, _settings.minDownloadSpeed());
868       // default to 10 seconds at low speed
869       SET_OPTION(CURLOPT_LOW_SPEED_TIME, 60L);
870   }
871
872 #if CURLVERSION_AT_LEAST(7,15,5)
873   if ( _settings.maxDownloadSpeed() != 0 )
874       SET_OPTION_OFFT(CURLOPT_MAX_RECV_SPEED_LARGE, _settings.maxDownloadSpeed());
875 #endif
876
877   /*---------------------------------------------------------------*
878    *---------------------------------------------------------------*/
879
880   _currentCookieFile = _cookieFile.asString();
881   if ( str::strToBool( _url.getQueryParam( "cookies" ), true ) )
882     SET_OPTION(CURLOPT_COOKIEFILE, _currentCookieFile.c_str() );
883   else
884     MIL << "No cookies requested" << endl;
885   SET_OPTION(CURLOPT_COOKIEJAR, _currentCookieFile.c_str() );
886   SET_OPTION(CURLOPT_PROGRESSFUNCTION, &progressCallback );
887   SET_OPTION(CURLOPT_NOPROGRESS, 0L);
888
889 #if CURLVERSION_AT_LEAST(7,18,0)
890   // bnc #306272
891     SET_OPTION(CURLOPT_PROXY_TRANSFER_MODE, 1L );
892 #endif
893   // append settings custom headers to curl
894   for ( TransferSettings::Headers::const_iterator it = vol_settings.headersBegin();
895         it != vol_settings.headersEnd();
896         ++it )
897   {
898     // MIL << "HEADER " << *it << std::endl;
899
900       _customHeaders = curl_slist_append(_customHeaders, it->c_str());
901       if ( !_customHeaders )
902           ZYPP_THROW(MediaCurlInitException(_url));
903   }
904
905   SET_OPTION(CURLOPT_HTTPHEADER, _customHeaders);
906 }
907
908 ///////////////////////////////////////////////////////////////////
909
910
911 void MediaCurl::attachTo (bool next)
912 {
913   if ( next )
914     ZYPP_THROW(MediaNotSupportedException(_url));
915
916   if ( !_url.isValid() )
917     ZYPP_THROW(MediaBadUrlException(_url));
918
919   checkProtocol(_url);
920   if( !isUseableAttachPoint( attachPoint() ) )
921   {
922     setAttachPoint( createAttachPoint(), true );
923   }
924
925   disconnectFrom(); // clean _curl if needed
926   _curl = curl_easy_init();
927   if ( !_curl ) {
928     ZYPP_THROW(MediaCurlInitException(_url));
929   }
930   try
931     {
932       setupEasy();
933     }
934   catch (Exception & ex)
935     {
936       disconnectFrom();
937       ZYPP_RETHROW(ex);
938     }
939
940   // FIXME: need a derived class to propelly compare url's
941   MediaSourceRef media( new MediaSource(_url.getScheme(), _url.asString()));
942   setMediaSource(media);
943 }
944
945 bool
946 MediaCurl::checkAttachPoint(const Pathname &apoint) const
947 {
948   return MediaHandler::checkAttachPoint( apoint, true, true);
949 }
950
951 ///////////////////////////////////////////////////////////////////
952
953 void MediaCurl::disconnectFrom()
954 {
955   if ( _customHeaders )
956   {
957     curl_slist_free_all(_customHeaders);
958     _customHeaders = 0L;
959   }
960
961   if ( _curl )
962   {
963     curl_easy_cleanup( _curl );
964     _curl = NULL;
965   }
966 }
967
968 ///////////////////////////////////////////////////////////////////
969
970 void MediaCurl::releaseFrom( const std::string & ejectDev )
971 {
972   disconnect();
973 }
974
975 Url MediaCurl::getFileUrl( const Pathname & filename_r ) const
976 {
977   // Simply extend the URLs pathname. An 'absolute' URL path
978   // is achieved by encoding the leading '/' in an URL path:
979   //   URL: ftp://user@server           -> ~user
980   //   URL: ftp://user@server/          -> ~user
981   //   URL: ftp://user@server//         -> ~user
982   //   URL: ftp://user@server/%2F       -> /
983   //                         ^- this '/' is just a separator
984   Url newurl( _url );
985   newurl.setPathName( ( Pathname("./"+_url.getPathName()) / filename_r ).asString().substr(1) );
986   return newurl;
987 }
988
989 ///////////////////////////////////////////////////////////////////
990
991 void MediaCurl::getFile(const Pathname & filename , const ByteCount &expectedFileSize_r) const
992 {
993     // Use absolute file name to prevent access of files outside of the
994     // hierarchy below the attach point.
995     getFileCopy(filename, localPath(filename).absolutename(), expectedFileSize_r);
996 }
997
998 ///////////////////////////////////////////////////////////////////
999
1000 void MediaCurl::getFileCopy( const Pathname & filename , const Pathname & target, const ByteCount &expectedFileSize_r ) const
1001 {
1002   callback::SendReport<DownloadProgressReport> report;
1003
1004   Url fileurl(getFileUrl(filename));
1005
1006   bool retry = false;
1007
1008   do
1009   {
1010     try
1011     {
1012       doGetFileCopy(filename, target, report, expectedFileSize_r);
1013       retry = false;
1014     }
1015     // retry with proper authentication data
1016     catch (MediaUnauthorizedException & ex_r)
1017     {
1018       if(authenticate(ex_r.hint(), !retry))
1019         retry = true;
1020       else
1021       {
1022         report->finish(fileurl, zypp::media::DownloadProgressReport::ACCESS_DENIED, ex_r.asUserHistory());
1023         ZYPP_RETHROW(ex_r);
1024       }
1025     }
1026     // unexpected exception
1027     catch (MediaException & excpt_r)
1028     {
1029       media::DownloadProgressReport::Error reason = media::DownloadProgressReport::ERROR;
1030       if( typeid(excpt_r) == typeid( media::MediaFileNotFoundException )  ||
1031           typeid(excpt_r) == typeid( media::MediaNotAFileException ) )
1032       {
1033         reason = media::DownloadProgressReport::NOT_FOUND;
1034       }
1035       report->finish(fileurl, reason, excpt_r.asUserHistory());
1036       ZYPP_RETHROW(excpt_r);
1037     }
1038   }
1039   while (retry);
1040
1041   report->finish(fileurl, zypp::media::DownloadProgressReport::NO_ERROR, "");
1042 }
1043
1044 ///////////////////////////////////////////////////////////////////
1045
1046 bool MediaCurl::getDoesFileExist( const Pathname & filename ) const
1047 {
1048   bool retry = false;
1049
1050   do
1051   {
1052     try
1053     {
1054       return doGetDoesFileExist( filename );
1055     }
1056     // authentication problem, retry with proper authentication data
1057     catch (MediaUnauthorizedException & ex_r)
1058     {
1059       if(authenticate(ex_r.hint(), !retry))
1060         retry = true;
1061       else
1062         ZYPP_RETHROW(ex_r);
1063     }
1064     // unexpected exception
1065     catch (MediaException & excpt_r)
1066     {
1067       ZYPP_RETHROW(excpt_r);
1068     }
1069   }
1070   while (retry);
1071
1072   return false;
1073 }
1074
1075 ///////////////////////////////////////////////////////////////////
1076
1077 void MediaCurl::evaluateCurlCode(const Pathname &filename,
1078                                   CURLcode code,
1079                                   bool timeout_reached) const
1080 {
1081   if ( code != 0 )
1082   {
1083     Url url;
1084     if (filename.empty())
1085       url = _url;
1086     else
1087       url = getFileUrl(filename);
1088
1089     std::string err;
1090     {
1091       switch ( code )
1092       {
1093       case CURLE_UNSUPPORTED_PROTOCOL:
1094           err = " Unsupported protocol";
1095           if ( !_lastRedirect.empty() )
1096           {
1097             err += " or redirect (";
1098             err += _lastRedirect;
1099             err += ")";
1100           }
1101           break;
1102       case CURLE_URL_MALFORMAT:
1103       case CURLE_URL_MALFORMAT_USER:
1104           err = " Bad URL";
1105           break;
1106       case CURLE_LOGIN_DENIED:
1107           ZYPP_THROW(
1108               MediaUnauthorizedException(url, "Login failed.", _curlError, ""));
1109           break;
1110       case CURLE_HTTP_RETURNED_ERROR:
1111       {
1112         long httpReturnCode = 0;
1113         CURLcode infoRet = curl_easy_getinfo( _curl,
1114                                               CURLINFO_RESPONSE_CODE,
1115                                               &httpReturnCode );
1116         if ( infoRet == CURLE_OK )
1117         {
1118           string msg = "HTTP response: " + str::numstring( httpReturnCode );
1119           switch ( httpReturnCode )
1120           {
1121           case 401:
1122           {
1123             string auth_hint = getAuthHint();
1124
1125             DBG << msg << " Login failed (URL: " << url.asString() << ")" << std::endl;
1126             DBG << "MediaUnauthorizedException auth hint: '" << auth_hint << "'" << std::endl;
1127
1128             ZYPP_THROW(MediaUnauthorizedException(
1129                            url, "Login failed.", _curlError, auth_hint
1130                            ));
1131           }
1132
1133           case 502: // bad gateway (bnc #1070851)
1134           case 503: // service temporarily unavailable (bnc #462545)
1135             ZYPP_THROW(MediaTemporaryProblemException(url));
1136           case 504: // gateway timeout
1137             ZYPP_THROW(MediaTimeoutException(url));
1138           case 403:
1139           {
1140             string msg403;
1141             if ( url.getHost().find(".suse.com") != string::npos )
1142               msg403 = _("Visit the SUSE Customer Center to check whether your registration is valid and has not expired.");
1143             else if (url.asString().find("novell.com") != string::npos)
1144               msg403 = _("Visit the Novell Customer Center to check whether your registration is valid and has not expired.");
1145             ZYPP_THROW(MediaForbiddenException(url, msg403));
1146           }
1147           case 404:
1148           case 410:
1149               ZYPP_THROW(MediaFileNotFoundException(_url, filename));
1150           }
1151
1152           DBG << msg << " (URL: " << url.asString() << ")" << std::endl;
1153           ZYPP_THROW(MediaCurlException(url, msg, _curlError));
1154         }
1155         else
1156         {
1157           string msg = "Unable to retrieve HTTP response:";
1158           DBG << msg << " (URL: " << url.asString() << ")" << std::endl;
1159           ZYPP_THROW(MediaCurlException(url, msg, _curlError));
1160         }
1161       }
1162       break;
1163       case CURLE_FTP_COULDNT_RETR_FILE:
1164 #if CURLVERSION_AT_LEAST(7,16,0)
1165       case CURLE_REMOTE_FILE_NOT_FOUND:
1166 #endif
1167       case CURLE_FTP_ACCESS_DENIED:
1168       case CURLE_TFTP_NOTFOUND:
1169         err = "File not found";
1170         ZYPP_THROW(MediaFileNotFoundException(_url, filename));
1171         break;
1172       case CURLE_BAD_PASSWORD_ENTERED:
1173       case CURLE_FTP_USER_PASSWORD_INCORRECT:
1174           err = "Login failed";
1175           break;
1176       case CURLE_COULDNT_RESOLVE_PROXY:
1177       case CURLE_COULDNT_RESOLVE_HOST:
1178       case CURLE_COULDNT_CONNECT:
1179       case CURLE_FTP_CANT_GET_HOST:
1180         err = "Connection failed";
1181         break;
1182       case CURLE_WRITE_ERROR:
1183         err = "Write error";
1184         break;
1185       case CURLE_PARTIAL_FILE:
1186       case CURLE_OPERATION_TIMEDOUT:
1187         timeout_reached = true; // fall though to TimeoutException
1188         // fall though...
1189       case CURLE_ABORTED_BY_CALLBACK:
1190          if( timeout_reached )
1191         {
1192           err  = "Timeout reached";
1193           ZYPP_THROW(MediaTimeoutException(url));
1194         }
1195         else
1196         {
1197           err = "User abort";
1198         }
1199         break;
1200       case CURLE_SSL_PEER_CERTIFICATE:
1201       default:
1202         err = "Curl error " + str::numstring( code );
1203         break;
1204       }
1205
1206       // uhm, no 0 code but unknown curl exception
1207       ZYPP_THROW(MediaCurlException(url, err, _curlError));
1208     }
1209   }
1210   else
1211   {
1212     // actually the code is 0, nothing happened
1213   }
1214 }
1215
1216 ///////////////////////////////////////////////////////////////////
1217
1218 bool MediaCurl::doGetDoesFileExist( const Pathname & filename ) const
1219 {
1220   DBG << filename.asString() << endl;
1221
1222   if(!_url.isValid())
1223     ZYPP_THROW(MediaBadUrlException(_url));
1224
1225   if(_url.getHost().empty())
1226     ZYPP_THROW(MediaBadUrlEmptyHostException(_url));
1227
1228   Url url(getFileUrl(filename));
1229
1230   DBG << "URL: " << url.asString() << endl;
1231     // Use URL without options and without username and passwd
1232     // (some proxies dislike them in the URL).
1233     // Curl seems to need the just scheme, hostname and a path;
1234     // the rest was already passed as curl options (in attachTo).
1235   Url curlUrl( clearQueryString(url) );
1236
1237   //
1238     // See also Bug #154197 and ftp url definition in RFC 1738:
1239     // The url "ftp://user@host/foo/bar/file" contains a path,
1240     // that is relative to the user's home.
1241     // The url "ftp://user@host//foo/bar/file" (or also with
1242     // encoded slash as %2f) "ftp://user@host/%2ffoo/bar/file"
1243     // contains an absolute path.
1244   //
1245   _lastRedirect.clear();
1246   string urlBuffer( curlUrl.asString());
1247   CURLcode ret = curl_easy_setopt( _curl, CURLOPT_URL,
1248                                    urlBuffer.c_str() );
1249   if ( ret != 0 ) {
1250     ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
1251   }
1252
1253   // instead of returning no data with NOBODY, we return
1254   // little data, that works with broken servers, and
1255   // works for ftp as well, because retrieving only headers
1256   // ftp will return always OK code ?
1257   // See http://curl.haxx.se/docs/knownbugs.html #58
1258   if (  (_url.getScheme() == "http" ||  _url.getScheme() == "https") &&
1259         _settings.headRequestsAllowed() )
1260     ret = curl_easy_setopt( _curl, CURLOPT_NOBODY, 1L );
1261   else
1262     ret = curl_easy_setopt( _curl, CURLOPT_RANGE, "0-1" );
1263
1264   if ( ret != 0 ) {
1265     curl_easy_setopt( _curl, CURLOPT_NOBODY, 0L);
1266     curl_easy_setopt( _curl, CURLOPT_RANGE, NULL );
1267     /* yes, this is why we never got to get NOBODY working before,
1268        because setting it changes this option too, and we also
1269        need to reset it
1270        See: http://curl.haxx.se/mail/archive-2005-07/0073.html
1271     */
1272     curl_easy_setopt( _curl, CURLOPT_HTTPGET, 1L );
1273     ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
1274   }
1275
1276   FILE *file = ::fopen( "/dev/null", "w" );
1277   if ( !file ) {
1278       ERR << "fopen failed for /dev/null" << endl;
1279       curl_easy_setopt( _curl, CURLOPT_NOBODY, 0L);
1280       curl_easy_setopt( _curl, CURLOPT_RANGE, NULL );
1281       /* yes, this is why we never got to get NOBODY working before,
1282        because setting it changes this option too, and we also
1283        need to reset it
1284        See: http://curl.haxx.se/mail/archive-2005-07/0073.html
1285       */
1286       curl_easy_setopt( _curl, CURLOPT_HTTPGET, 1L );
1287       if ( ret != 0 ) {
1288           ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
1289       }
1290       ZYPP_THROW(MediaWriteException("/dev/null"));
1291   }
1292
1293   ret = curl_easy_setopt( _curl, CURLOPT_WRITEDATA, file );
1294   if ( ret != 0 ) {
1295       ::fclose(file);
1296       std::string err( _curlError);
1297       curl_easy_setopt( _curl, CURLOPT_RANGE, NULL );
1298       curl_easy_setopt( _curl, CURLOPT_NOBODY, 0L);
1299       /* yes, this is why we never got to get NOBODY working before,
1300        because setting it changes this option too, and we also
1301        need to reset it
1302        See: http://curl.haxx.se/mail/archive-2005-07/0073.html
1303       */
1304       curl_easy_setopt( _curl, CURLOPT_HTTPGET, 1L );
1305       if ( ret != 0 ) {
1306           ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
1307       }
1308       ZYPP_THROW(MediaCurlSetOptException(url, err));
1309   }
1310
1311   CURLcode ok = curl_easy_perform( _curl );
1312   MIL << "perform code: " << ok << " [ " << curl_easy_strerror(ok) << " ]" << endl;
1313
1314   // reset curl settings
1315   if (  _url.getScheme() == "http" ||  _url.getScheme() == "https" )
1316   {
1317     curl_easy_setopt( _curl, CURLOPT_NOBODY, 0L);
1318     if ( ret != 0 ) {
1319       ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
1320     }
1321
1322     /* yes, this is why we never got to get NOBODY working before,
1323        because setting it changes this option too, and we also
1324        need to reset it
1325        See: http://curl.haxx.se/mail/archive-2005-07/0073.html
1326     */
1327     curl_easy_setopt( _curl, CURLOPT_HTTPGET, 1L);
1328     if ( ret != 0 ) {
1329       ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
1330     }
1331
1332   }
1333   else
1334   {
1335     // for FTP we set different options
1336     curl_easy_setopt( _curl, CURLOPT_RANGE, NULL);
1337     if ( ret != 0 ) {
1338       ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
1339     }
1340   }
1341
1342   // if the code is not zero, close the file
1343   if ( ok != 0 )
1344       ::fclose(file);
1345
1346   // as we are not having user interaction, the user can't cancel
1347   // the file existence checking, a callback or timeout return code
1348   // will be always a timeout.
1349   try {
1350       evaluateCurlCode( filename, ok, true /* timeout */);
1351   }
1352   catch ( const MediaFileNotFoundException &e ) {
1353       // if the file did not exist then we can return false
1354       return false;
1355   }
1356   catch ( const MediaException &e ) {
1357       // some error, we are not sure about file existence, rethrw
1358       ZYPP_RETHROW(e);
1359   }
1360   // exists
1361   return ( ok == CURLE_OK );
1362 }
1363
1364 ///////////////////////////////////////////////////////////////////
1365
1366
1367 #if DETECT_DIR_INDEX
1368 bool MediaCurl::detectDirIndex() const
1369 {
1370   if(_url.getScheme() != "http" && _url.getScheme() != "https")
1371     return false;
1372   //
1373   // try to check the effective url and set the not_a_file flag
1374   // if the url path ends with a "/", what usually means, that
1375   // we've received a directory index (index.html content).
1376   //
1377   // Note: This may be dangerous and break file retrieving in
1378   //       case of some server redirections ... ?
1379   //
1380   bool      not_a_file = false;
1381   char     *ptr = NULL;
1382   CURLcode  ret = curl_easy_getinfo( _curl,
1383                                      CURLINFO_EFFECTIVE_URL,
1384                                      &ptr);
1385   if ( ret == CURLE_OK && ptr != NULL)
1386   {
1387     try
1388     {
1389       Url         eurl( ptr);
1390       std::string path( eurl.getPathName());
1391       if( !path.empty() && path != "/" && *path.rbegin() == '/')
1392       {
1393         DBG << "Effective url ("
1394             << eurl
1395             << ") seems to provide the index of a directory"
1396             << endl;
1397         not_a_file = true;
1398       }
1399     }
1400     catch( ... )
1401     {}
1402   }
1403   return not_a_file;
1404 }
1405 #endif
1406
1407 ///////////////////////////////////////////////////////////////////
1408
1409 void MediaCurl::doGetFileCopy(const Pathname & filename , const Pathname & target, callback::SendReport<DownloadProgressReport> & report, const ByteCount &expectedFileSize_r, RequestOptions options ) const
1410 {
1411     Pathname dest = target.absolutename();
1412     if( assert_dir( dest.dirname() ) )
1413     {
1414       DBG << "assert_dir " << dest.dirname() << " failed" << endl;
1415       Url url(getFileUrl(filename));
1416       ZYPP_THROW( MediaSystemException(url, "System error on " + dest.dirname().asString()) );
1417     }
1418     string destNew = target.asString() + ".new.zypp.XXXXXX";
1419     char *buf = ::strdup( destNew.c_str());
1420     if( !buf)
1421     {
1422       ERR << "out of memory for temp file name" << endl;
1423       Url url(getFileUrl(filename));
1424       ZYPP_THROW(MediaSystemException(url, "out of memory for temp file name"));
1425     }
1426
1427     int tmp_fd = ::mkostemp( buf, O_CLOEXEC );
1428     if( tmp_fd == -1)
1429     {
1430       free( buf);
1431       ERR << "mkstemp failed for file '" << destNew << "'" << endl;
1432       ZYPP_THROW(MediaWriteException(destNew));
1433     }
1434     destNew = buf;
1435     free( buf);
1436
1437     FILE *file = ::fdopen( tmp_fd, "we" );
1438     if ( !file ) {
1439       ::close( tmp_fd);
1440       filesystem::unlink( destNew );
1441       ERR << "fopen failed for file '" << destNew << "'" << endl;
1442       ZYPP_THROW(MediaWriteException(destNew));
1443     }
1444
1445     DBG << "dest: " << dest << endl;
1446     DBG << "temp: " << destNew << endl;
1447
1448     // set IFMODSINCE time condition (no download if not modified)
1449     if( PathInfo(target).isExist() && !(options & OPTION_NO_IFMODSINCE) )
1450     {
1451       curl_easy_setopt(_curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_IFMODSINCE);
1452       curl_easy_setopt(_curl, CURLOPT_TIMEVALUE, (long)PathInfo(target).mtime());
1453     }
1454     else
1455     {
1456       curl_easy_setopt(_curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_NONE);
1457       curl_easy_setopt(_curl, CURLOPT_TIMEVALUE, 0L);
1458     }
1459     try
1460     {
1461       doGetFileCopyFile(filename, dest, file, report, expectedFileSize_r, options);
1462     }
1463     catch (Exception &e)
1464     {
1465       ::fclose( file );
1466       filesystem::unlink( destNew );
1467       curl_easy_setopt(_curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_NONE);
1468       curl_easy_setopt(_curl, CURLOPT_TIMEVALUE, 0L);
1469       ZYPP_RETHROW(e);
1470     }
1471
1472     long httpReturnCode = 0;
1473     CURLcode infoRet = curl_easy_getinfo(_curl,
1474                                          CURLINFO_RESPONSE_CODE,
1475                                          &httpReturnCode);
1476     bool modified = true;
1477     if (infoRet == CURLE_OK)
1478     {
1479       DBG << "HTTP response: " + str::numstring(httpReturnCode);
1480       if ( httpReturnCode == 304
1481            || ( httpReturnCode == 213 && (_url.getScheme() == "ftp" || _url.getScheme() == "tftp") ) ) // not modified
1482       {
1483         DBG << " Not modified.";
1484         modified = false;
1485       }
1486       DBG << endl;
1487     }
1488     else
1489     {
1490       WAR << "Could not get the reponse code." << endl;
1491     }
1492
1493     if (modified || infoRet != CURLE_OK)
1494     {
1495       // apply umask
1496       if ( ::fchmod( ::fileno(file), filesystem::applyUmaskTo( 0644 ) ) )
1497       {
1498         ERR << "Failed to chmod file " << destNew << endl;
1499       }
1500       if (::fclose( file ))
1501       {
1502         ERR << "Fclose failed for file '" << destNew << "'" << endl;
1503         ZYPP_THROW(MediaWriteException(destNew));
1504       }
1505       // move the temp file into dest
1506       if ( rename( destNew, dest ) != 0 ) {
1507         ERR << "Rename failed" << endl;
1508         ZYPP_THROW(MediaWriteException(dest));
1509       }
1510     }
1511     else
1512     {
1513       // close and remove the temp file
1514       ::fclose( file );
1515       filesystem::unlink( destNew );
1516     }
1517
1518     DBG << "done: " << PathInfo(dest) << endl;
1519 }
1520
1521 ///////////////////////////////////////////////////////////////////
1522
1523 void MediaCurl::doGetFileCopyFile(const Pathname & filename , const Pathname & dest, FILE *file, callback::SendReport<DownloadProgressReport> & report, const ByteCount &expectedFileSize_r, RequestOptions options ) const
1524 {
1525     DBG << filename.asString() << endl;
1526
1527     if(!_url.isValid())
1528       ZYPP_THROW(MediaBadUrlException(_url));
1529
1530     if(_url.getHost().empty())
1531       ZYPP_THROW(MediaBadUrlEmptyHostException(_url));
1532
1533     Url url(getFileUrl(filename));
1534
1535     DBG << "URL: " << url.asString() << endl;
1536     // Use URL without options and without username and passwd
1537     // (some proxies dislike them in the URL).
1538     // Curl seems to need the just scheme, hostname and a path;
1539     // the rest was already passed as curl options (in attachTo).
1540     Url curlUrl( clearQueryString(url) );
1541
1542     //
1543     // See also Bug #154197 and ftp url definition in RFC 1738:
1544     // The url "ftp://user@host/foo/bar/file" contains a path,
1545     // that is relative to the user's home.
1546     // The url "ftp://user@host//foo/bar/file" (or also with
1547     // encoded slash as %2f) "ftp://user@host/%2ffoo/bar/file"
1548     // contains an absolute path.
1549     //
1550     _lastRedirect.clear();
1551     string urlBuffer( curlUrl.asString());
1552     CURLcode ret = curl_easy_setopt( _curl, CURLOPT_URL,
1553                                      urlBuffer.c_str() );
1554     if ( ret != 0 ) {
1555       ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
1556     }
1557
1558     ret = curl_easy_setopt( _curl, CURLOPT_WRITEDATA, file );
1559     if ( ret != 0 ) {
1560       ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
1561     }
1562
1563     // Set callback and perform.
1564     ProgressData progressData(_curl, _settings.timeout(), url, expectedFileSize_r, &report);
1565     if (!(options & OPTION_NO_REPORT_START))
1566       report->start(url, dest);
1567     if ( curl_easy_setopt( _curl, CURLOPT_PROGRESSDATA, &progressData ) != 0 ) {
1568       WAR << "Can't set CURLOPT_PROGRESSDATA: " << _curlError << endl;;
1569     }
1570
1571     ret = curl_easy_perform( _curl );
1572 #if CURLVERSION_AT_LEAST(7,19,4)
1573     // bnc#692260: If the client sends a request with an If-Modified-Since header
1574     // with a future date for the server, the server may respond 200 sending a
1575     // zero size file.
1576     // curl-7.19.4 introduces CURLINFO_CONDITION_UNMET to check this condition.
1577     if ( ftell(file) == 0 && ret == 0 )
1578     {
1579       long httpReturnCode = 33;
1580       if ( curl_easy_getinfo( _curl, CURLINFO_RESPONSE_CODE, &httpReturnCode ) == CURLE_OK && httpReturnCode == 200 )
1581       {
1582         long conditionUnmet = 33;
1583         if ( curl_easy_getinfo( _curl, CURLINFO_CONDITION_UNMET, &conditionUnmet ) == CURLE_OK && conditionUnmet )
1584         {
1585           WAR << "TIMECONDITION unmet - retry without." << endl;
1586           curl_easy_setopt(_curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_NONE);
1587           curl_easy_setopt(_curl, CURLOPT_TIMEVALUE, 0L);
1588           ret = curl_easy_perform( _curl );
1589         }
1590       }
1591     }
1592 #endif
1593
1594     if ( curl_easy_setopt( _curl, CURLOPT_PROGRESSDATA, NULL ) != 0 ) {
1595       WAR << "Can't unset CURLOPT_PROGRESSDATA: " << _curlError << endl;;
1596     }
1597
1598     if ( ret != 0 )
1599     {
1600       ERR << "curl error: " << ret << ": " << _curlError
1601           << ", temp file size " << ftell(file)
1602           << " bytes." << endl;
1603
1604       // the timeout is determined by the progress data object
1605       // which holds whether the timeout was reached or not,
1606       // otherwise it would be a user cancel
1607       try {
1608
1609         if ( progressData.fileSizeExceeded )
1610           ZYPP_THROW(MediaFileSizeExceededException(url, progressData._expectedFileSize));
1611
1612         evaluateCurlCode( filename, ret, progressData.reached );
1613       }
1614       catch ( const MediaException &e ) {
1615         // some error, we are not sure about file existence, rethrw
1616         ZYPP_RETHROW(e);
1617       }
1618     }
1619
1620 #if DETECT_DIR_INDEX
1621     if (!ret && detectDirIndex())
1622       {
1623         ZYPP_THROW(MediaNotAFileException(_url, filename));
1624       }
1625 #endif // DETECT_DIR_INDEX
1626 }
1627
1628 ///////////////////////////////////////////////////////////////////
1629
1630 void MediaCurl::getDir( const Pathname & dirname, bool recurse_r ) const
1631 {
1632   filesystem::DirContent content;
1633   getDirInfo( content, dirname, /*dots*/false );
1634
1635   for ( filesystem::DirContent::const_iterator it = content.begin(); it != content.end(); ++it ) {
1636       Pathname filename = dirname + it->name;
1637       int res = 0;
1638
1639       switch ( it->type ) {
1640       case filesystem::FT_NOT_AVAIL: // old directory.yast contains no typeinfo at all
1641       case filesystem::FT_FILE:
1642         getFile( filename, 0 );
1643         break;
1644       case filesystem::FT_DIR: // newer directory.yast contain at least directory info
1645         if ( recurse_r ) {
1646           getDir( filename, recurse_r );
1647         } else {
1648           res = assert_dir( localPath( filename ) );
1649           if ( res ) {
1650             WAR << "Ignore error (" << res <<  ") on creating local directory '" << localPath( filename ) << "'" << endl;
1651           }
1652         }
1653         break;
1654       default:
1655         // don't provide devices, sockets, etc.
1656         break;
1657       }
1658   }
1659 }
1660
1661 ///////////////////////////////////////////////////////////////////
1662
1663 void MediaCurl::getDirInfo( std::list<std::string> & retlist,
1664                                const Pathname & dirname, bool dots ) const
1665 {
1666   getDirectoryYast( retlist, dirname, dots );
1667 }
1668
1669 ///////////////////////////////////////////////////////////////////
1670
1671 void MediaCurl::getDirInfo( filesystem::DirContent & retlist,
1672                             const Pathname & dirname, bool dots ) const
1673 {
1674   getDirectoryYast( retlist, dirname, dots );
1675 }
1676
1677 ///////////////////////////////////////////////////////////////////
1678 //
1679 int MediaCurl::aliveCallback( void *clientp, double /*dltotal*/, double dlnow, double /*ultotal*/, double /*ulnow*/ )
1680 {
1681   ProgressData *pdata = reinterpret_cast<ProgressData *>( clientp );
1682   if( pdata )
1683   {
1684     // Do not propagate dltotal in alive callbacks. MultiCurl uses this to
1685     // prevent a percentage raise while downloading a metalink file. Download
1686     // activity however is indicated by propagating the download rate (via dlnow).
1687     pdata->updateStats( 0.0, dlnow );
1688     return pdata->reportProgress();
1689   }
1690   return 0;
1691 }
1692
1693 int MediaCurl::progressCallback( void *clientp, double dltotal, double dlnow, double ultotal, double ulnow )
1694 {
1695   ProgressData *pdata = reinterpret_cast<ProgressData *>( clientp );
1696   if( pdata )
1697   {
1698     // work around curl bug that gives us old data
1699     long httpReturnCode = 0;
1700     if ( curl_easy_getinfo( pdata->curl, CURLINFO_RESPONSE_CODE, &httpReturnCode ) != CURLE_OK || httpReturnCode == 0 )
1701       return aliveCallback( clientp, dltotal, dlnow, ultotal, ulnow );
1702
1703     pdata->updateStats( dltotal, dlnow );
1704     return pdata->reportProgress();
1705   }
1706   return 0;
1707 }
1708
1709 CURL *MediaCurl::progressCallback_getcurl( void *clientp )
1710 {
1711   ProgressData *pdata = reinterpret_cast<ProgressData *>(clientp);
1712   return pdata ? pdata->curl : 0;
1713 }
1714
1715 ///////////////////////////////////////////////////////////////////
1716
1717 string MediaCurl::getAuthHint() const
1718 {
1719   long auth_info = CURLAUTH_NONE;
1720
1721   CURLcode infoRet =
1722     curl_easy_getinfo(_curl, CURLINFO_HTTPAUTH_AVAIL, &auth_info);
1723
1724   if(infoRet == CURLE_OK)
1725   {
1726     return CurlAuthData::auth_type_long2str(auth_info);
1727   }
1728
1729   return "";
1730 }
1731
1732 /**
1733  * MediaMultiCurl needs to reset the expected filesize in case a metalink file is downloaded
1734  * otherwise this function should not be called
1735  */
1736 void MediaCurl::resetExpectedFileSize(void *clientp, const ByteCount &expectedFileSize)
1737 {
1738   ProgressData *data = reinterpret_cast<ProgressData *>(clientp);
1739   if ( data ) {
1740     data->_expectedFileSize = expectedFileSize;
1741   }
1742 }
1743
1744 ///////////////////////////////////////////////////////////////////
1745
1746 bool MediaCurl::authenticate(const string & availAuthTypes, bool firstTry) const
1747 {
1748   //! \todo need a way to pass different CredManagerOptions here
1749   CredentialManager cm(CredManagerOptions(ZConfig::instance().repoManagerRoot()));
1750   CurlAuthData_Ptr credentials;
1751
1752   // get stored credentials
1753   AuthData_Ptr cmcred = cm.getCred(_url);
1754
1755   if (cmcred && firstTry)
1756   {
1757     credentials.reset(new CurlAuthData(*cmcred));
1758     DBG << "got stored credentials:" << endl << *credentials << endl;
1759   }
1760   // if not found, ask user
1761   else
1762   {
1763
1764     CurlAuthData_Ptr curlcred;
1765     curlcred.reset(new CurlAuthData());
1766     callback::SendReport<AuthenticationReport> auth_report;
1767
1768     // preset the username if present in current url
1769     if (!_url.getUsername().empty() && firstTry)
1770       curlcred->setUsername(_url.getUsername());
1771     // if CM has found some credentials, preset the username from there
1772     else if (cmcred)
1773       curlcred->setUsername(cmcred->username());
1774
1775     // indicate we have no good credentials from CM
1776     cmcred.reset();
1777
1778     string prompt_msg = str::Format(_("Authentication required for '%s'")) % _url.asString();
1779
1780     // set available authentication types from the exception
1781     // might be needed in prompt
1782     curlcred->setAuthType(availAuthTypes);
1783
1784     // ask user
1785     if (auth_report->prompt(_url, prompt_msg, *curlcred))
1786     {
1787       DBG << "callback answer: retry" << endl
1788           << "CurlAuthData: " << *curlcred << endl;
1789
1790       if (curlcred->valid())
1791       {
1792         credentials = curlcred;
1793           // if (credentials->username() != _url.getUsername())
1794           //   _url.setUsername(credentials->username());
1795           /**
1796            *  \todo find a way to save the url with changed username
1797            *  back to repoinfo or dont store urls with username
1798            *  (and either forbid more repos with the same url and different
1799            *  user, or return a set of credentials from CM and try them one
1800            *  by one)
1801            */
1802       }
1803     }
1804     else
1805     {
1806       DBG << "callback answer: cancel" << endl;
1807     }
1808   }
1809
1810   // set username and password
1811   if (credentials)
1812   {
1813     // HACK, why is this const?
1814     const_cast<MediaCurl*>(this)->_settings.setUsername(credentials->username());
1815     const_cast<MediaCurl*>(this)->_settings.setPassword(credentials->password());
1816
1817     // set username and password
1818     CURLcode ret = curl_easy_setopt(_curl, CURLOPT_USERPWD, _settings.userPassword().c_str());
1819     if ( ret != 0 ) ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
1820
1821     // set available authentication types from the exception
1822     if (credentials->authType() == CURLAUTH_NONE)
1823       credentials->setAuthType(availAuthTypes);
1824
1825     // set auth type (seems this must be set _after_ setting the userpwd)
1826     if (credentials->authType() != CURLAUTH_NONE)
1827     {
1828       // FIXME: only overwrite if not empty?
1829       const_cast<MediaCurl*>(this)->_settings.setAuthType(credentials->authTypeAsString());
1830       ret = curl_easy_setopt(_curl, CURLOPT_HTTPAUTH, credentials->authType());
1831       if ( ret != 0 ) ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
1832     }
1833
1834     if (!cmcred)
1835     {
1836       credentials->setUrl(_url);
1837       cm.addCred(*credentials);
1838       cm.save();
1839     }
1840
1841     return true;
1842   }
1843
1844   return false;
1845 }
1846
1847
1848   } // namespace media
1849 } // namespace zypp
1850 //