- include Pathname.h in Sysconfig.h
[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
15 #include "zypp/base/Logger.h"
16 #include "zypp/ExternalProgram.h"
17 #include "zypp/base/String.h"
18 #include "zypp/base/Sysconfig.h"
19
20 #include "zypp/media/MediaCurl.h"
21 #include "zypp/media/proxyinfo/ProxyInfos.h"
22 #include "zypp/media/ProxyInfo.h"
23 #include "zypp/thread/Once.h"
24 #include <cstdlib>
25 #include <sys/types.h>
26 #include <sys/stat.h>
27 #include <sys/mount.h>
28 #include <errno.h>
29 #include <dirent.h>
30 #include <unistd.h>
31
32 #include "config.h"
33
34 #define  DETECT_DIR_INDEX       0
35
36 using namespace std;
37 using namespace zypp::base;
38
39 namespace
40 {
41   zypp::thread::OnceFlag g_InitOnceFlag = PTHREAD_ONCE_INIT;
42   zypp::thread::OnceFlag g_FreeOnceFlag = PTHREAD_ONCE_INIT;
43
44   extern "C" void _do_free_once()
45   {
46     curl_global_cleanup();
47   }
48
49   extern "C" void globalFreeOnce()
50   {
51     zypp::thread::callOnce(g_FreeOnceFlag, _do_free_once);
52   }
53
54   extern "C" void _do_init_once()
55   {
56     CURLcode ret = curl_global_init( CURL_GLOBAL_ALL );
57     if ( ret != 0 )
58     {
59       WAR << "curl global init failed" << endl;
60     }
61
62     //
63     // register at exit handler ?
64     // this may cause trouble, because we can protect it
65     // against ourself only.
66     // if the app sets an atexit handler as well, it will
67     // cause a double free while the second of them runs.
68     //
69     //std::atexit( globalFreeOnce);
70   }
71
72   inline void globalInitOnce()
73   {
74     zypp::thread::callOnce(g_InitOnceFlag, _do_init_once);
75   }
76 }
77
78 namespace zypp {
79   namespace media {
80
81 Pathname    MediaCurl::_cookieFile = "/var/lib/YaST2/cookies";
82 std::string MediaCurl::_agent = "Novell ZYPP Installer";
83
84 ///////////////////////////////////////////////////////////////////
85
86 static inline void escape( string & str_r,
87                            const char char_r, const string & escaped_r ) {
88   for ( string::size_type pos = str_r.find( char_r );
89         pos != string::npos; pos = str_r.find( char_r, pos ) ) {
90     str_r.replace( pos, 1, escaped_r );
91   }
92 }
93
94 static inline string escapedPath( string path_r ) {
95   escape( path_r, ' ', "%20" );
96   return path_r;
97 }
98
99 static inline string unEscape( string text_r ) {
100   char * tmp = curl_unescape( text_r.c_str(), 0 );
101   string ret( tmp );
102   curl_free( tmp );
103   return ret;
104 }
105
106 ///////////////////////////////////////////////////////////////////
107 //
108 //      CLASS NAME : MediaCurl
109 //
110 ///////////////////////////////////////////////////////////////////
111
112 MediaCurl::MediaCurl( const Url &      url_r,
113                       const Pathname & attach_point_hint_r )
114     : MediaHandler( url_r, attach_point_hint_r,
115                     "/", // urlpath at attachpoint
116                     true ), // does_download
117       _curl( NULL )
118 {
119   _curlError[0] = '\0';
120
121   MIL << "MediaCurl::MediaCurl(" << url_r << ", " << attach_point_hint_r << ")" << endl;
122
123   globalInitOnce();
124
125   if( !attachPoint().empty())
126   {
127     PathInfo ainfo(attachPoint());
128     Pathname apath(attachPoint() + "XXXXXX");
129     char    *atemp = ::strdup( apath.asString().c_str());
130     char    *atest = NULL;
131     if( !ainfo.isDir() || !ainfo.userMayRWX() ||
132          atemp == NULL || (atest=::mkdtemp(atemp)) == NULL)
133     {
134       WAR << "attach point " << ainfo.path()
135           << " is not useable for " << url_r.getScheme() << endl;
136       setAttachPoint("", true);
137     }
138     else if( atest != NULL)
139       ::rmdir(atest);
140
141     if( atemp != NULL)
142       ::free(atemp);
143   }
144 }
145
146 void MediaCurl::setCookieFile( const Pathname &fileName )
147 {
148   _cookieFile = fileName;
149 }
150
151 ///////////////////////////////////////////////////////////////////
152 //
153 //
154 //      METHOD NAME : MediaCurl::attachTo
155 //      METHOD TYPE : PMError
156 //
157 //      DESCRIPTION : Asserted that not already attached, and attachPoint is a directory.
158 //
159 void MediaCurl::attachTo (bool next)
160 {
161   if ( next )
162     ZYPP_THROW(MediaNotSupportedException(_url));
163
164   if ( !_url.isValid() )
165     ZYPP_THROW(MediaBadUrlException(_url));
166
167   curl_version_info_data *curl_info = NULL;
168   curl_info = curl_version_info(CURLVERSION_NOW);
169   // curl_info does not need any free (is static)
170   if (curl_info->protocols)
171   {
172     const char * const *proto;
173     std::string        scheme( _url.getScheme());
174     bool               found = false;
175     for(proto=curl_info->protocols; !found && *proto; ++proto)
176     {
177       if( scheme == std::string((const char *)*proto))
178         found = true;
179     }
180     if( !found)
181     {
182       std::string msg("Unsupported protocol '");
183       msg += scheme;
184       msg += "'";
185       ZYPP_THROW(MediaBadUrlException(_url, msg));
186     }
187   }
188
189   if( !isUseableAttachPoint(attachPoint()))
190   {
191     std::string mountpoint = createAttachPoint().asString();
192
193     if( mountpoint.empty())
194       ZYPP_THROW( MediaBadAttachPointException(url()));
195
196     setAttachPoint( mountpoint, true);
197   }
198
199   disconnectFrom(); // clean _curl if needed
200   _curl = curl_easy_init();
201   if ( !_curl ) {
202     ZYPP_THROW(MediaCurlInitException(_url));
203   }
204
205   CURLcode ret = curl_easy_setopt( _curl, CURLOPT_ERRORBUFFER, _curlError );
206   if ( ret != 0 ) {
207     disconnectFrom();
208     ZYPP_THROW(MediaCurlSetOptException(_url, "Error setting error buffer"));
209   }
210
211   ret = curl_easy_setopt( _curl, CURLOPT_FAILONERROR, true );
212   if ( ret != 0 ) {
213     disconnectFrom();
214     ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
215   }
216
217   ret = curl_easy_setopt( _curl, CURLOPT_NOSIGNAL, 1 );
218   if ( ret != 0 ) {
219     disconnectFrom();
220     ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
221   }
222
223   /*
224   ** Don't block "forever" on system calls. Curl seems to
225   ** recover nicely, if the ftp server has e.g. a 30sec
226   ** timeout. If required, it closes the connection, trys
227   ** to reopen and fetch it - this works in many cases
228   ** without to report any error to us.
229   **
230   ** Disabled, because it breaks normal operations over a
231   ** slow link :(
232   **
233   ret = curl_easy_setopt( _curl, CURLOPT_TIMEOUT, 600 );
234   if ( ret != 0 ) {
235     disconnectFrom();
236     ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
237   }
238   */
239
240   /*
241   ** Connect timeout
242   */
243   ret = curl_easy_setopt( _curl, CURLOPT_CONNECTTIMEOUT, 60);
244   if ( ret != 0 ) {
245     disconnectFrom();
246     ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
247   }
248
249   if ( _url.getScheme() == "http" ) {
250     // follow any Location: header that the server sends as part of
251     // an HTTP header (#113275)
252     ret = curl_easy_setopt ( _curl, CURLOPT_FOLLOWLOCATION, true );
253     if ( ret != 0) {
254       disconnectFrom();
255       ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
256     }
257     ret = curl_easy_setopt ( _curl, CURLOPT_MAXREDIRS, 3L );
258     if ( ret != 0) {
259       disconnectFrom();
260       ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
261     }
262     ret = curl_easy_setopt ( _curl, CURLOPT_USERAGENT, _agent.c_str() );
263     if ( ret != 0) {
264       disconnectFrom();
265       ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
266     }
267   }
268
269   if ( _url.getScheme() == "https" ) {
270     ret = curl_easy_setopt( _curl, CURLOPT_SSL_VERIFYPEER, 1 );
271     if ( ret != 0 ) {
272       disconnectFrom();
273       ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
274     }
275     ret = curl_easy_setopt( _curl, CURLOPT_CAPATH, "/etc/ssl/certs/" );
276     if ( ret != 0 ) {
277       disconnectFrom();
278       ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
279     }
280     ret = curl_easy_setopt( _curl, CURLOPT_SSL_VERIFYHOST, 2 );
281     if ( ret != 0 ) {
282       disconnectFrom();
283       ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
284     }
285     ret = curl_easy_setopt ( _curl, CURLOPT_USERAGENT, _agent.c_str() );
286     if ( ret != 0) {
287       disconnectFrom();
288       ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
289     }
290   }
291
292
293   /*---------------------------------------------------------------*
294    CURLOPT_USERPWD: [user name]:[password]
295
296    Url::username/password -> CURLOPT_USERPWD
297    If not provided, anonymous FTP identification
298    *---------------------------------------------------------------*/
299
300   if ( _url.getUsername().empty() ) {
301     if ( _url.getScheme() == "ftp" ) {
302       string id = "yast2@";
303       id += VERSION;
304       DBG << "Anonymous FTP identification: '" << id << "'" << endl;
305       _userpwd = "anonymous:" + id;
306     }
307   } else {
308     _userpwd = _url.getUsername();
309     if ( _url.getPassword().size() ) {
310       _userpwd += ":" + _url.getPassword();
311     }
312   }
313
314   if ( _userpwd.size() ) {
315     _userpwd = unEscape( _userpwd );
316     ret = curl_easy_setopt( _curl, CURLOPT_USERPWD, _userpwd.c_str() );
317     if ( ret != 0 ) {
318       disconnectFrom();
319       ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
320     }
321
322     if( !_url.getQueryParam("auth").empty() &&
323         (_url.getScheme() == "http" || _url.getScheme() == "https"))
324     {
325       std::vector<std::string>                 list;
326       std::vector<std::string>::const_iterator it;
327       str::split(_url.getQueryParam("auth"), std::back_inserter(list), ",");
328
329       long auth = CURLAUTH_NONE;
330       for(it = list.begin(); it != list.end(); ++it)
331       {
332         if(*it == "basic")
333         {
334           auth |= CURLAUTH_BASIC;
335         }
336         else
337         if(*it == "digest")
338         {
339           auth |= CURLAUTH_DIGEST;
340         }
341         else
342         if((curl_info && (curl_info->features & CURL_VERSION_NTLM)) &&
343            (*it == "ntlm"))
344         {
345           auth |= CURLAUTH_NTLM;
346         }
347         else
348         if((curl_info && (curl_info->features & CURL_VERSION_SPNEGO)) &&
349            (*it == "spnego" || *it == "negotiate"))
350         {
351           // there is no separate spnego flag for auth
352           auth |= CURLAUTH_GSSNEGOTIATE;
353         }
354         else
355         if((curl_info && (curl_info->features & CURL_VERSION_GSSNEGOTIATE)) &&
356            (*it == "gssnego" || *it == "negotiate"))
357         {
358           auth |= CURLAUTH_GSSNEGOTIATE;
359         }
360         else
361         {
362           std::string msg("Unsupported HTTP authentication method '");
363           msg += *it;
364           msg += "'";
365           disconnectFrom();
366           ZYPP_THROW(MediaBadUrlException(_url, msg));
367         }
368       }
369
370       if( auth != CURLAUTH_NONE)
371       {
372         DBG << "Enabling HTTP authentication methods: "
373             << _url.getQueryParam("auth") << std::endl;
374
375         ret = curl_easy_setopt( _curl, CURLOPT_HTTPAUTH, auth);
376         if ( ret != 0 ) {
377           disconnectFrom();
378           ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
379         }
380       }
381     }
382   }
383
384   /*---------------------------------------------------------------*
385    CURLOPT_PROXY: host[:port]
386
387    Url::option(proxy and proxyport) -> CURLOPT_PROXY
388    If not provided, /etc/sysconfig/proxy is evaluated
389    *---------------------------------------------------------------*/
390
391   _proxy = _url.getQueryParam( "proxy" );
392
393   if ( ! _proxy.empty() ) {
394     string proxyport( _url.getQueryParam( "proxyport" ) );
395     if ( ! proxyport.empty() ) {
396       _proxy += ":" + proxyport;
397     }
398   } else {
399
400     ProxyInfo proxy_info (ProxyInfo::ImplPtr(new ProxyInfoSysconfig("proxy")));
401
402     if ( proxy_info.enabled())
403     {
404       bool useproxy = true;
405
406       std::list<std::string> nope = proxy_info.noProxy();
407       for (ProxyInfo::NoProxyIterator it = proxy_info.noProxyBegin();
408            it != proxy_info.noProxyEnd();
409            it++)
410       {
411         std::string host( str::toLower(_url.getHost()));
412         std::string temp( str::toLower(*it));
413
414         // no proxy if it points to a suffix
415         // preceeded by a '.', that maches
416         // the trailing portion of the host.
417         if( temp.size() > 1 && temp.at(0) == '.')
418         {
419           if(host.size() > temp.size() &&
420              host.compare(host.size() - temp.size(), temp.size(), temp) == 0)
421           {
422             DBG << "NO_PROXY: '" << *it  << "' matches host '"
423                                  << host << "'" << endl;
424             useproxy = false;
425             break;
426           }
427         }
428         else
429         // no proxy if we have an exact match
430         if( host == temp)
431         {
432           DBG << "NO_PROXY: '" << *it  << "' matches host '"
433                                << host << "'" << endl;
434           useproxy = false;
435           break;
436         }
437       }
438
439       if ( useproxy ) {
440         _proxy = proxy_info.proxy(_url.getScheme());
441       }
442     }
443   }
444
445
446   DBG << "Proxy: " << (_proxy.empty() ? "-none-" : _proxy) << endl;
447
448   if ( ! _proxy.empty() ) {
449
450     ret = curl_easy_setopt( _curl, CURLOPT_PROXY, _proxy.c_str() );
451     if ( ret != 0 ) {
452       disconnectFrom();
453       ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
454     }
455
456     /*---------------------------------------------------------------*
457      CURLOPT_PROXYUSERPWD: [user name]:[password]
458
459      Url::option(proxyuser and proxypassword) -> CURLOPT_PROXYUSERPWD
460      If not provided, $HOME/.curlrc is evaluated
461      *---------------------------------------------------------------*/
462
463     _proxyuserpwd = _url.getQueryParam( "proxyuser" );
464
465     if ( ! _proxyuserpwd.empty() ) {
466
467       string proxypassword( _url.getQueryParam( "proxypassword" ) );
468       if ( ! proxypassword.empty() ) {
469         _proxyuserpwd += ":" + proxypassword;
470       }
471
472     } else {
473       char *home = getenv("HOME");
474       if( home && *home)
475       {
476         Pathname curlrcFile = string( home ) + string( "/.curlrc" );
477
478         PathInfo h_info(string(home), PathInfo::LSTAT);
479         PathInfo c_info(curlrcFile,   PathInfo::LSTAT);
480
481         if( h_info.isDir()  && h_info.owner() == getuid() &&
482             c_info.isFile() && c_info.owner() == getuid())
483         {
484           map<string,string> rc_data = base::sysconfig::read( curlrcFile );
485
486           map<string,string>::const_iterator it = rc_data.find("proxy-user");
487           if (it != rc_data.end())
488             _proxyuserpwd = it->second;
489         }
490         else
491         {
492           WAR << "Not allowed to parse '" << curlrcFile
493               << "': bad file owner" << std::endl;
494         }
495       }
496     }
497
498     _proxyuserpwd = unEscape( _proxyuserpwd );
499     ret = curl_easy_setopt( _curl, CURLOPT_PROXYUSERPWD, _proxyuserpwd.c_str() );
500     if ( ret != 0 ) {
501       disconnectFrom();
502       ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
503     }
504   }
505
506   /*---------------------------------------------------------------*
507    *---------------------------------------------------------------*/
508
509   _currentCookieFile = _cookieFile.asString();
510
511   ret = curl_easy_setopt( _curl, CURLOPT_COOKIEFILE,
512                           _currentCookieFile.c_str() );
513   if ( ret != 0 ) {
514     disconnectFrom();
515     ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
516   }
517
518   ret = curl_easy_setopt( _curl, CURLOPT_COOKIEJAR,
519                           _currentCookieFile.c_str() );
520   if ( ret != 0 ) {
521     disconnectFrom();
522     ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
523   }
524
525   ret = curl_easy_setopt( _curl, CURLOPT_PROGRESSFUNCTION,
526                           &progressCallback );
527   if ( ret != 0 ) {
528     disconnectFrom();
529     ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
530   }
531
532   ret = curl_easy_setopt( _curl, CURLOPT_NOPROGRESS, false );
533   if ( ret != 0 ) {
534     disconnectFrom();
535     ZYPP_THROW(MediaCurlSetOptException(_url, _curlError));
536   }
537
538   // FIXME: need a derived class to propelly compare url's
539   MediaSourceRef media( new MediaSource(_url.getScheme(), _url.asString()));
540   setMediaSource(media);
541 }
542
543 bool
544 MediaCurl::checkAttachPoint(const Pathname &apoint) const
545 {
546   return MediaHandler::checkAttachPoint( apoint, true, true);
547 }
548
549 ///////////////////////////////////////////////////////////////////
550 //
551 //
552 //      METHOD NAME : MediaCurl::disconnectFrom
553 //      METHOD TYPE : PMError
554 //
555 void MediaCurl::disconnectFrom()
556 {
557   if ( _curl )
558   {
559     curl_easy_cleanup( _curl );
560     _curl = NULL;
561   }
562 }
563
564 ///////////////////////////////////////////////////////////////////
565 //
566 //
567 //      METHOD NAME : MediaCurl::releaseFrom
568 //      METHOD TYPE : PMError
569 //
570 //      DESCRIPTION : Asserted that media is attached.
571 //
572 void MediaCurl::releaseFrom( bool eject )
573 {
574   disconnect();
575 }
576
577
578 ///////////////////////////////////////////////////////////////////
579 //
580 //      METHOD NAME : MediaCurl::getFile
581 //      METHOD TYPE : PMError
582 //
583
584 void MediaCurl::getFile( const Pathname & filename ) const
585 {
586     // Use absolute file name to prevent access of files outside of the
587     // hierarchy below the attach point.
588     getFileCopy(filename, localPath(filename).absolutename());
589 }
590
591
592 void MediaCurl::getFileCopy( const Pathname & filename , const Pathname & target) const
593 {
594   callback::SendReport<DownloadProgressReport> report;
595
596   Url url( _url );
597
598   try {
599     doGetFileCopy(filename, target, report);
600   }
601   catch (MediaException & excpt_r)
602   {
603     // FIXME: this will not match the first URL
604     // FIXME: error number fix
605     report->finish(url, zypp::media::DownloadProgressReport::NOT_FOUND, excpt_r.msg());
606     ZYPP_RETHROW(excpt_r);
607   }
608   report->finish(url, zypp::media::DownloadProgressReport::NO_ERROR, "");
609 }
610
611 void MediaCurl::doGetFileCopy( const Pathname & filename , const Pathname & target, callback::SendReport<DownloadProgressReport> & report) const
612 {
613     DBG << filename.asString() << endl;
614
615     if(!_url.isValid())
616       ZYPP_THROW(MediaBadUrlException(_url));
617
618     if(_url.getHost().empty())
619       ZYPP_THROW(MediaBadUrlEmptyHostException(_url));
620
621     string path = _url.getPathName();
622     if ( !path.empty() && path != "/" && *path.rbegin() == '/' &&
623          filename.absolute() ) {
624       // If url has a path with trailing slash, remove the leading slash from
625       // the absolute file name
626       path += filename.asString().substr( 1, filename.asString().size() - 1 );
627     } else if ( filename.relative() ) {
628       // Add trailing slash to path, if not already there
629       if ( !path.empty() && *path.rbegin() != '/' ) path += "/";
630       // Remove "./" from begin of relative file name
631       path += filename.asString().substr( 2, filename.asString().size() - 2 );
632     } else {
633       path += filename.asString();
634     }
635
636     Url url( _url );
637     url.setPathName( path );
638
639     Pathname dest = target.absolutename();
640     if( assert_dir( dest.dirname() ) )
641     {
642       DBG << "assert_dir " << dest.dirname() << " failed" << endl;
643       ZYPP_THROW( MediaSystemException(url, "System error on " + dest.dirname().asString()) );
644     }
645
646     DBG << "URL: " << url.asString() << endl;
647     // Use URL without options and without username and passwd
648     // (some proxies dislike them in the URL).
649     // Curl seems to need the just scheme, hostname and a path;
650     // the rest was already passed as curl options (in attachTo).
651     Url curlUrl( url );
652
653     // Use asString + url::ViewOptions instead?
654     curlUrl.setUsername( "" );
655     curlUrl.setPassword( "" );
656     curlUrl.setPathParams( "" );
657     curlUrl.setQueryString( "" );
658     curlUrl.setFragment( "" );
659
660     //
661     // See also Bug #154197 and ftp url definition in RFC 1738:
662     // The url "ftp://user@host/foo/bar/file" contains a path,
663     // that is relative to the user's home.
664     // The url "ftp://user@host//foo/bar/file" (or also with
665     // encoded slash as %2f) "ftp://user@host/%2ffoo/bar/file"
666     // contains an absolute path.
667     //
668     string urlBuffer( curlUrl.asString());
669     CURLcode ret = curl_easy_setopt( _curl, CURLOPT_URL,
670                                      urlBuffer.c_str() );
671     if ( ret != 0 ) {
672       ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
673     }
674
675     string destNew = target.asString() + ".new.zypp.XXXXXX";
676     char *buf = ::strdup( destNew.c_str());
677     if( !buf)
678     {
679       ERR << "out of memory for temp file name" << endl;
680       ZYPP_THROW(MediaSystemException(
681         url, "out of memory for temp file name"
682       ));
683     }
684
685     int tmp_fd = ::mkstemp( buf );
686     if( tmp_fd == -1)
687     {
688       free( buf);
689       ERR << "mkstemp failed for file '" << destNew << "'" << endl;
690       ZYPP_THROW(MediaWriteException(destNew));
691     }
692     destNew = buf;
693     free( buf);
694
695     FILE *file = ::fdopen( tmp_fd, "w" );
696     if ( !file ) {
697       ::close( tmp_fd);
698       filesystem::unlink( destNew );
699       ERR << "fopen failed for file '" << destNew << "'" << endl;
700       ZYPP_THROW(MediaWriteException(destNew));
701     }
702
703     DBG << "dest: " << dest << endl;
704     DBG << "temp: " << destNew << endl;
705
706     ret = curl_easy_setopt( _curl, CURLOPT_WRITEDATA, file );
707     if ( ret != 0 ) {
708       ::fclose( file );
709       filesystem::unlink( destNew );
710       ZYPP_THROW(MediaCurlSetOptException(url, _curlError));
711     }
712
713     // Set callback and perform.
714     report->start(url, dest);
715     if ( curl_easy_setopt( _curl, CURLOPT_PROGRESSDATA, &report ) != 0 ) {
716       WAR << "Can't set CURLOPT_PROGRESSDATA: " << _curlError << endl;;
717     }
718
719     ret = curl_easy_perform( _curl );
720
721     if ( curl_easy_setopt( _curl, CURLOPT_PROGRESSDATA, NULL ) != 0 ) {
722       WAR << "Can't unset CURLOPT_PROGRESSDATA: " << _curlError << endl;;
723     }
724
725     if ( ret != 0 ) {
726       ::fclose( file );
727       filesystem::unlink( destNew );
728       ERR << "curl error: " << ret << ": " << _curlError << endl;
729       std::string err;
730       try {
731        bool err_file_not_found = false;
732        switch ( ret ) {
733         case CURLE_UNSUPPORTED_PROTOCOL:
734         case CURLE_URL_MALFORMAT:
735         case CURLE_URL_MALFORMAT_USER:
736           err = " Bad URL";
737         case CURLE_HTTP_RETURNED_ERROR:
738           {
739             long httpReturnCode = 0;
740             CURLcode infoRet = curl_easy_getinfo( _curl,
741                                                   CURLINFO_RESPONSE_CODE,
742                                                   &httpReturnCode );
743             if ( infoRet == CURLE_OK ) {
744               string msg = "HTTP response: " +
745                            str::numstring( httpReturnCode );
746               if ( httpReturnCode == 401 )
747               {
748                 err = " Login failed";
749               }
750               else
751               if ( httpReturnCode == 404)
752               {
753                 ZYPP_THROW(MediaFileNotFoundException(_url, filename));
754               }
755
756               msg += err;
757               DBG << msg << " (URL: " << url.asString() << ")" << std::endl;
758               ZYPP_THROW(MediaCurlException(url, msg, _curlError));
759             }
760             else
761             {
762               string msg = "Unable to retrieve HTTP response:";
763               msg += err;
764               DBG << msg << " (URL: " << url.asString() << ")" << std::endl;
765               ZYPP_THROW(MediaCurlException(url, msg, _curlError));
766             }
767           }
768           break;
769         case CURLE_FTP_COULDNT_RETR_FILE:
770         case CURLE_FTP_ACCESS_DENIED:
771           err = "File not found";
772           err_file_not_found = true;
773           break;
774         case CURLE_BAD_PASSWORD_ENTERED:
775         case CURLE_FTP_USER_PASSWORD_INCORRECT:
776           err = "Login failed";
777           break;
778         case CURLE_COULDNT_RESOLVE_PROXY:
779         case CURLE_COULDNT_RESOLVE_HOST:
780         case CURLE_COULDNT_CONNECT:
781         case CURLE_FTP_CANT_GET_HOST:
782           err = "Connection failed";
783           break;
784         case CURLE_WRITE_ERROR:
785           err = "Write error";
786           break;
787         case CURLE_ABORTED_BY_CALLBACK:
788           err = "User abort";
789           break;
790         case CURLE_SSL_PEER_CERTIFICATE:
791         default:
792           err = "Unrecognized error";
793           break;
794        }
795        if( err_file_not_found)
796        {
797          ZYPP_THROW(MediaFileNotFoundException(_url, filename));
798        }
799        else
800        {
801          ZYPP_THROW(MediaCurlException(url, err, _curlError));
802        }
803       }
804       catch (const MediaException & excpt_r)
805       {
806         ZYPP_RETHROW(excpt_r);
807       }
808     }
809 #if DETECT_DIR_INDEX
810     else
811     if(curlUrl.getScheme() == "http" ||
812        curlUrl.getScheme() == "https")
813     {
814       //
815       // try to check the effective url and set the not_a_file flag
816       // if the url path ends with a "/", what usually means, that
817       // we've received a directory index (index.html content).
818       //
819       // Note: This may be dangerous and break file retrieving in
820       //       case of some server redirections ... ?
821       //
822       bool      not_a_file = false;
823       char     *ptr = NULL;
824       CURLcode  ret = curl_easy_getinfo( _curl,
825                                          CURLINFO_EFFECTIVE_URL,
826                                          &ptr);
827       if ( ret == CURLE_OK && ptr != NULL)
828       {
829         try
830         {
831           Url         eurl( ptr);
832           std::string path( eurl.getPathName());
833           if( !path.empty() && path != "/" && *path.rbegin() == '/')
834           {
835             DBG << "Effective url ("
836                 << eurl
837                 << ") seems to provide the index of a directory"
838                 << endl;
839             not_a_file = true;
840           }
841         }
842         catch( ... )
843         {}
844       }
845
846       if( not_a_file)
847       {
848         ::fclose( file );
849         filesystem::unlink( destNew );
850         ZYPP_THROW(MediaNotAFileException(_url, filename));
851       }
852     }
853 #endif // DETECT_DIR_INDEX
854
855     mode_t mask;
856     // getumask() would be fine, but does not exist
857     // [ the linker can't find it in glibc :-( ].
858     mask = ::umask(0022); ::umask(mask);
859     if ( ::fchmod( ::fileno(file), 0644 & ~mask))
860     {
861       ERR << "Failed to chmod file " << destNew << endl;
862     }
863     ::fclose( file );
864
865     if ( rename( destNew, dest ) != 0 ) {
866       ERR << "Rename failed" << endl;
867       ZYPP_THROW(MediaWriteException(dest));
868     }
869 }
870
871
872 ///////////////////////////////////////////////////////////////////
873 //
874 //
875 //      METHOD NAME : MediaCurl::getDir
876 //      METHOD TYPE : PMError
877 //
878 //      DESCRIPTION : Asserted that media is attached
879 //
880 void MediaCurl::getDir( const Pathname & dirname, bool recurse_r ) const
881 {
882   filesystem::DirContent content;
883   getDirInfo( content, dirname, /*dots*/false );
884
885   for ( filesystem::DirContent::const_iterator it = content.begin(); it != content.end(); ++it ) {
886       Pathname filename = dirname + it->name;
887       int res = 0;
888
889       switch ( it->type ) {
890       case filesystem::FT_NOT_AVAIL: // old directory.yast contains no typeinfo at all
891       case filesystem::FT_FILE:
892         getFile( filename );
893         break;
894       case filesystem::FT_DIR: // newer directory.yast contain at least directory info
895         if ( recurse_r ) {
896           getDir( filename, recurse_r );
897         } else {
898           res = assert_dir( localPath( filename ) );
899           if ( res ) {
900             WAR << "Ignore error (" << res <<  ") on creating local directory '" << localPath( filename ) << "'" << endl;
901           }
902         }
903         break;
904       default:
905         // don't provide devices, sockets, etc.
906         break;
907       }
908   }
909 }
910
911 ///////////////////////////////////////////////////////////////////
912 //
913 //
914 //      METHOD NAME : MediaCurl::getDirInfo
915 //      METHOD TYPE : PMError
916 //
917 //      DESCRIPTION : Asserted that media is attached and retlist is empty.
918 //
919 void MediaCurl::getDirInfo( std::list<std::string> & retlist,
920                                const Pathname & dirname, bool dots ) const
921 {
922   getDirectoryYast( retlist, dirname, dots );
923 }
924
925 ///////////////////////////////////////////////////////////////////
926 //
927 //
928 //      METHOD NAME : MediaCurl::getDirInfo
929 //      METHOD TYPE : PMError
930 //
931 //      DESCRIPTION : Asserted that media is attached and retlist is empty.
932 //
933 void MediaCurl::getDirInfo( filesystem::DirContent & retlist,
934                             const Pathname & dirname, bool dots ) const
935 {
936   getDirectoryYast( retlist, dirname, dots );
937 }
938
939 ///////////////////////////////////////////////////////////////////
940 //
941 //
942 //      METHOD NAME : MediaCurl::progressCallback
943 //      METHOD TYPE : int
944 //
945 //      DESCRIPTION : Progress callback triggered from MediaCurl::getFile
946 //
947 int MediaCurl::progressCallback( void *clientp, double dltotal, double dlnow,
948                                  double ultotal, double ulnow )
949 {
950   callback::SendReport<DownloadProgressReport> *report
951     = reinterpret_cast<callback::SendReport<DownloadProgressReport>*>( clientp );
952   if (report)
953   {
954     // FIXME: empty URL
955     if (! (*report)->progress(int( dlnow * 100 / dltotal ), Url() ))
956       return 1;
957   }
958   return 0;
959 }
960
961
962   } // namespace media
963 } // namespace zypp