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