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