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