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