libzypp 8.13.6
|
00001 /*---------------------------------------------------------------------\ 00002 | ____ _ __ __ ___ | 00003 | |__ / \ / / . \ . \ | 00004 | / / \ V /| _/ _/ | 00005 | / /__ | | | | | | | 00006 | /_____||_| |_| |_| | 00007 | | 00008 \---------------------------------------------------------------------*/ 00013 #include <iostream> 00014 #include <list> 00015 00016 #include "zypp/base/Logger.h" 00017 #include "zypp/ExternalProgram.h" 00018 #include "zypp/base/String.h" 00019 #include "zypp/base/Gettext.h" 00020 #include "zypp/base/Sysconfig.h" 00021 #include "zypp/base/Gettext.h" 00022 00023 #include "zypp/media/MediaCurl.h" 00024 #include "zypp/media/ProxyInfo.h" 00025 #include "zypp/media/MediaUserAuth.h" 00026 #include "zypp/media/CredentialManager.h" 00027 #include "zypp/media/CurlConfig.h" 00028 #include "zypp/thread/Once.h" 00029 #include "zypp/Target.h" 00030 #include "zypp/ZYppFactory.h" 00031 00032 #include <cstdlib> 00033 #include <sys/types.h> 00034 #include <sys/stat.h> 00035 #include <sys/mount.h> 00036 #include <errno.h> 00037 #include <dirent.h> 00038 #include <unistd.h> 00039 #include <boost/format.hpp> 00040 00041 #define DETECT_DIR_INDEX 0 00042 #define CONNECT_TIMEOUT 60 00043 #define TRANSFER_TIMEOUT 60 * 3 00044 #define TRANSFER_TIMEOUT_MAX 60 * 60 00045 00046 #undef CURLVERSION_AT_LEAST 00047 #define CURLVERSION_AT_LEAST(M,N,O) LIBCURL_VERSION_NUM >= ((((M)<<8)+(N))<<8)+(O) 00048 00049 using namespace std; 00050 using namespace zypp::base; 00051 00052 namespace 00053 { 00054 zypp::thread::OnceFlag g_InitOnceFlag = PTHREAD_ONCE_INIT; 00055 zypp::thread::OnceFlag g_FreeOnceFlag = PTHREAD_ONCE_INIT; 00056 00057 extern "C" void _do_free_once() 00058 { 00059 curl_global_cleanup(); 00060 } 00061 00062 extern "C" void globalFreeOnce() 00063 { 00064 zypp::thread::callOnce(g_FreeOnceFlag, _do_free_once); 00065 } 00066 00067 extern "C" void _do_init_once() 00068 { 00069 CURLcode ret = curl_global_init( CURL_GLOBAL_ALL ); 00070 if ( ret != 0 ) 00071 { 00072 WAR << "curl global init failed" << endl; 00073 } 00074 00075 // 00076 // register at exit handler ? 00077 // this may cause trouble, because we can protect it 00078 // against ourself only. 00079 // if the app sets an atexit handler as well, it will 00080 // cause a double free while the second of them runs. 00081 // 00082 //std::atexit( globalFreeOnce); 00083 } 00084 00085 inline void globalInitOnce() 00086 { 00087 zypp::thread::callOnce(g_InitOnceFlag, _do_init_once); 00088 } 00089 00090 int log_curl(CURL *curl, curl_infotype info, 00091 char *ptr, size_t len, void *max_lvl) 00092 { 00093 std::string pfx(" "); 00094 long lvl = 0; 00095 switch( info) 00096 { 00097 case CURLINFO_TEXT: lvl = 1; pfx = "*"; break; 00098 case CURLINFO_HEADER_IN: lvl = 2; pfx = "<"; break; 00099 case CURLINFO_HEADER_OUT: lvl = 2; pfx = ">"; break; 00100 default: break; 00101 } 00102 if( lvl > 0 && max_lvl != NULL && lvl <= *((long *)max_lvl)) 00103 { 00104 std::string msg(ptr, len); 00105 std::list<std::string> lines; 00106 std::list<std::string>::const_iterator line; 00107 zypp::str::split(msg, std::back_inserter(lines), "\r\n"); 00108 for(line = lines.begin(); line != lines.end(); ++line) 00109 { 00110 DBG << pfx << " " << *line << endl; 00111 } 00112 } 00113 return 0; 00114 } 00115 00116 static size_t 00117 log_redirects_curl( 00118 void *ptr, size_t size, size_t nmemb, void *stream) 00119 { 00120 // INT << "got header: " << string((char *)ptr, ((char*)ptr) + size*nmemb) << endl; 00121 00122 char * lstart = (char *)ptr, * lend = (char *)ptr; 00123 size_t pos = 0; 00124 size_t max = size * nmemb; 00125 while (pos + 1 < max) 00126 { 00127 // get line 00128 for (lstart = lend; *lend != '\n' && pos < max; ++lend, ++pos); 00129 00130 // look for "Location" 00131 string line(lstart, lend); 00132 if (line.find("Location") != string::npos) 00133 { 00134 DBG << "redirecting to " << line << endl; 00135 return max; 00136 } 00137 00138 // continue with the next line 00139 if (pos + 1 < max) 00140 { 00141 ++lend; 00142 ++pos; 00143 } 00144 else 00145 break; 00146 } 00147 00148 return max; 00149 } 00150 } 00151 00152 namespace zypp { 00153 namespace media { 00154 00155 namespace { 00156 struct ProgressData 00157 { 00158 ProgressData(const long _timeout, const zypp::Url &_url = zypp::Url(), 00159 callback::SendReport<DownloadProgressReport> *_report=NULL) 00160 : timeout(_timeout) 00161 , reached(false) 00162 , report(_report) 00163 , drate_period(-1) 00164 , dload_period(0) 00165 , secs(0) 00166 , drate_avg(-1) 00167 , ltime( time(NULL)) 00168 , dload( 0) 00169 , uload( 0) 00170 , url(_url) 00171 {} 00172 long timeout; 00173 bool reached; 00174 callback::SendReport<DownloadProgressReport> *report; 00175 // download rate of the last period (cca 1 sec) 00176 double drate_period; 00177 // bytes downloaded at the start of the last period 00178 double dload_period; 00179 // seconds from the start of the download 00180 long secs; 00181 // average download rate 00182 double drate_avg; 00183 // last time the progress was reported 00184 time_t ltime; 00185 // bytes downloaded at the moment the progress was last reported 00186 double dload; 00187 // bytes uploaded at the moment the progress was last reported 00188 double uload; 00189 zypp::Url url; 00190 }; 00191 00193 00194 inline void escape( string & str_r, 00195 const char char_r, const string & escaped_r ) { 00196 for ( string::size_type pos = str_r.find( char_r ); 00197 pos != string::npos; pos = str_r.find( char_r, pos ) ) { 00198 str_r.replace( pos, 1, escaped_r ); 00199 } 00200 } 00201 00202 inline string escapedPath( string path_r ) { 00203 escape( path_r, ' ', "%20" ); 00204 return path_r; 00205 } 00206 00207 inline string unEscape( string text_r ) { 00208 char * tmp = curl_unescape( text_r.c_str(), 0 ); 00209 string ret( tmp ); 00210 curl_free( tmp ); 00211 return ret; 00212 } 00213 00214 } 00215 00220 void fillSettingsFromUrl( const Url &url, TransferSettings &s ) 00221 { 00222 std::string param(url.getQueryParam("timeout")); 00223 if( !param.empty()) 00224 { 00225 long num = str::strtonum<long>(param); 00226 if( num >= 0 && num <= TRANSFER_TIMEOUT_MAX) 00227 s.setTimeout(num); 00228 } 00229 00230 if ( ! url.getUsername().empty() ) 00231 { 00232 s.setUsername(url.getUsername()); 00233 if ( url.getPassword().size() ) 00234 s.setPassword(url.getPassword()); 00235 } 00236 else 00237 { 00238 // if there is no username, set anonymous auth 00239 if ( url.getScheme() == "ftp" && s.username().empty() ) 00240 s.setAnonymousAuth(); 00241 } 00242 00243 if ( url.getScheme() == "https" ) 00244 { 00245 s.setVerifyPeerEnabled(false); 00246 s.setVerifyHostEnabled(false); 00247 00248 std::string verify( url.getQueryParam("ssl_verify")); 00249 if( verify.empty() || 00250 verify == "yes") 00251 { 00252 s.setVerifyPeerEnabled(true); 00253 s.setVerifyHostEnabled(true); 00254 } 00255 else if( verify == "no") 00256 { 00257 s.setVerifyPeerEnabled(false); 00258 s.setVerifyHostEnabled(false); 00259 } 00260 else 00261 { 00262 std::vector<std::string> flags; 00263 std::vector<std::string>::const_iterator flag; 00264 str::split( verify, std::back_inserter(flags), ","); 00265 for(flag = flags.begin(); flag != flags.end(); ++flag) 00266 { 00267 if( *flag == "host") 00268 s.setVerifyHostEnabled(true); 00269 else if( *flag == "peer") 00270 s.setVerifyPeerEnabled(true); 00271 else 00272 ZYPP_THROW(MediaBadUrlException(url, "Unknown ssl_verify flag")); 00273 } 00274 } 00275 } 00276 00277 Pathname ca_path = Pathname(url.getQueryParam("ssl_capath")).asString(); 00278 if( ! ca_path.empty()) 00279 { 00280 if( !PathInfo(ca_path).isDir() || !Pathname(ca_path).absolute()) 00281 ZYPP_THROW(MediaBadUrlException(url, "Invalid ssl_capath path")); 00282 else 00283 s.setCertificateAuthoritiesPath(ca_path); 00284 } 00285 00286 string proxy = url.getQueryParam( "proxy" ); 00287 if ( ! proxy.empty() ) 00288 { 00289 if ( proxy == "_none_" ) { 00290 s.setProxyEnabled(false); 00291 } 00292 else { 00293 string proxyport( url.getQueryParam( "proxyport" ) ); 00294 if ( ! proxyport.empty() ) { 00295 proxy += ":" + proxyport; 00296 } 00297 s.setProxy(proxy); 00298 s.setProxyEnabled(true); 00299 } 00300 } 00301 00302 // HTTP authentication type 00303 string use_auth = url.getQueryParam("auth"); 00304 if (!use_auth.empty() && (url.getScheme() == "http" || url.getScheme() == "https")) 00305 { 00306 try 00307 { 00308 CurlAuthData::auth_type_str2long(use_auth); // check if we know it 00309 } 00310 catch (MediaException & ex_r) 00311 { 00312 DBG << "Rethrowing as MediaUnauthorizedException."; 00313 ZYPP_THROW(MediaUnauthorizedException(url, ex_r.msg(), "", "")); 00314 } 00315 s.setAuthType(use_auth); 00316 } 00317 00318 // workarounds 00319 std::string head_requests( url.getQueryParam("head_requests")); 00320 if( !head_requests.empty() && head_requests == "no") 00321 s.setHeadRequestsAllowed(false); 00322 } 00323 00328 void fillSettingsSystemProxy( const Url&url, TransferSettings &s ) 00329 { 00330 ProxyInfo proxy_info; 00331 if ( proxy_info.useProxyFor( url ) ) 00332 { 00333 // We must extract any 'user:pass' from the proxy url 00334 // otherwise they won't make it into curl (.curlrc wins). 00335 try { 00336 Url u( proxy_info.proxy( url ) ); 00337 s.setProxy( u.asString( url::ViewOption::WITH_SCHEME + url::ViewOption::WITH_HOST + url::ViewOption::WITH_PORT ) ); 00338 s.setProxyUsername( u.getUsername( url::E_ENCODED ) ); 00339 s.setProxyPassword( u.getPassword( url::E_ENCODED ) ); 00340 s.setProxyEnabled( true ); 00341 } 00342 catch (...) {} // no proxy if URL is malformed 00343 } 00344 } 00345 00346 Pathname MediaCurl::_cookieFile = "/var/lib/YaST2/cookies"; 00347 00352 static const char *const anonymousIdHeader() 00353 { 00354 // we need to add the release and identifier to the 00355 // agent string. 00356 // The target could be not initialized, and then this information 00357 // is guessed. 00358 static const std::string _value( 00359 str::trim( str::form( 00360 "X-ZYpp-AnonymousId: %s", 00361 Target::anonymousUniqueId( Pathname()/*guess root*/ ).c_str() ) ) 00362 ); 00363 return _value.c_str(); 00364 } 00365 00370 static const char *const distributionFlavorHeader() 00371 { 00372 // we need to add the release and identifier to the 00373 // agent string. 00374 // The target could be not initialized, and then this information 00375 // is guessed. 00376 static const std::string _value( 00377 str::trim( str::form( 00378 "X-ZYpp-DistributionFlavor: %s", 00379 Target::distributionFlavor( Pathname()/*guess root*/ ).c_str() ) ) 00380 ); 00381 return _value.c_str(); 00382 } 00383 00388 static const char *const agentString() 00389 { 00390 // we need to add the release and identifier to the 00391 // agent string. 00392 // The target could be not initialized, and then this information 00393 // is guessed. 00394 static const std::string _value( 00395 str::form( 00396 "ZYpp %s (curl %s) %s" 00397 , VERSION 00398 , curl_version_info(CURLVERSION_NOW)->version 00399 , Target::targetDistribution( Pathname()/*guess root*/ ).c_str() 00400 ) 00401 ); 00402 return _value.c_str(); 00403 } 00404 00405 // we use this define to unbloat code as this C setting option 00406 // and catching exception is done frequently. 00408 #define SET_OPTION(opt,val) do { \ 00409 ret = curl_easy_setopt ( _curl, opt, val ); \ 00410 if ( ret != 0) { \ 00411 ZYPP_THROW(MediaCurlSetOptException(_url, _curlError)); \ 00412 } \ 00413 } while ( false ) 00414 00415 #define SET_OPTION_OFFT(opt,val) SET_OPTION(opt,(curl_off_t)val) 00416 #define SET_OPTION_LONG(opt,val) SET_OPTION(opt,(long)val) 00417 #define SET_OPTION_VOID(opt,val) SET_OPTION(opt,(void*)val) 00418 00419 MediaCurl::MediaCurl( const Url & url_r, 00420 const Pathname & attach_point_hint_r ) 00421 : MediaHandler( url_r, attach_point_hint_r, 00422 "/", // urlpath at attachpoint 00423 true ), // does_download 00424 _curl( NULL ), 00425 _customHeaders(0L) 00426 { 00427 _curlError[0] = '\0'; 00428 _curlDebug = 0L; 00429 00430 MIL << "MediaCurl::MediaCurl(" << url_r << ", " << attach_point_hint_r << ")" << endl; 00431 00432 globalInitOnce(); 00433 00434 if( !attachPoint().empty()) 00435 { 00436 PathInfo ainfo(attachPoint()); 00437 Pathname apath(attachPoint() + "XXXXXX"); 00438 char *atemp = ::strdup( apath.asString().c_str()); 00439 char *atest = NULL; 00440 if( !ainfo.isDir() || !ainfo.userMayRWX() || 00441 atemp == NULL || (atest=::mkdtemp(atemp)) == NULL) 00442 { 00443 WAR << "attach point " << ainfo.path() 00444 << " is not useable for " << url_r.getScheme() << endl; 00445 setAttachPoint("", true); 00446 } 00447 else if( atest != NULL) 00448 ::rmdir(atest); 00449 00450 if( atemp != NULL) 00451 ::free(atemp); 00452 } 00453 } 00454 00455 Url MediaCurl::clearQueryString(const Url &url) const 00456 { 00457 Url curlUrl (url); 00458 curlUrl.setUsername( "" ); 00459 curlUrl.setPassword( "" ); 00460 curlUrl.setPathParams( "" ); 00461 curlUrl.setFragment( "" ); 00462 curlUrl.delQueryParam("cookies"); 00463 curlUrl.delQueryParam("proxy"); 00464 curlUrl.delQueryParam("proxyport"); 00465 curlUrl.delQueryParam("proxyuser"); 00466 curlUrl.delQueryParam("proxypass"); 00467 curlUrl.delQueryParam("ssl_capath"); 00468 curlUrl.delQueryParam("ssl_verify"); 00469 curlUrl.delQueryParam("timeout"); 00470 curlUrl.delQueryParam("auth"); 00471 curlUrl.delQueryParam("username"); 00472 curlUrl.delQueryParam("password"); 00473 curlUrl.delQueryParam("mediahandler"); 00474 return curlUrl; 00475 } 00476 00477 TransferSettings & MediaCurl::settings() 00478 { 00479 return _settings; 00480 } 00481 00482 00483 void MediaCurl::setCookieFile( const Pathname &fileName ) 00484 { 00485 _cookieFile = fileName; 00486 } 00487 00489 00490 void MediaCurl::checkProtocol(const Url &url) const 00491 { 00492 curl_version_info_data *curl_info = NULL; 00493 curl_info = curl_version_info(CURLVERSION_NOW); 00494 // curl_info does not need any free (is static) 00495 if (curl_info->protocols) 00496 { 00497 const char * const *proto; 00498 std::string scheme( url.getScheme()); 00499 bool found = false; 00500 for(proto=curl_info->protocols; !found && *proto; ++proto) 00501 { 00502 if( scheme == std::string((const char *)*proto)) 00503 found = true; 00504 } 00505 if( !found) 00506 { 00507 std::string msg("Unsupported protocol '"); 00508 msg += scheme; 00509 msg += "'"; 00510 ZYPP_THROW(MediaBadUrlException(_url, msg)); 00511 } 00512 } 00513 } 00514 00515 void MediaCurl::setupEasy() 00516 { 00517 { 00518 char *ptr = getenv("ZYPP_MEDIA_CURL_DEBUG"); 00519 _curlDebug = (ptr && *ptr) ? str::strtonum<long>( ptr) : 0L; 00520 if( _curlDebug > 0) 00521 { 00522 curl_easy_setopt( _curl, CURLOPT_VERBOSE, 1L); 00523 curl_easy_setopt( _curl, CURLOPT_DEBUGFUNCTION, log_curl); 00524 curl_easy_setopt( _curl, CURLOPT_DEBUGDATA, &_curlDebug); 00525 } 00526 } 00527 00528 curl_easy_setopt(_curl, CURLOPT_HEADERFUNCTION, log_redirects_curl); 00529 CURLcode ret = curl_easy_setopt( _curl, CURLOPT_ERRORBUFFER, _curlError ); 00530 if ( ret != 0 ) { 00531 ZYPP_THROW(MediaCurlSetOptException(_url, "Error setting error buffer")); 00532 } 00533 00534 SET_OPTION(CURLOPT_FAILONERROR, 1L); 00535 SET_OPTION(CURLOPT_NOSIGNAL, 1L); 00536 00537 // create non persistant settings 00538 // so that we don't add headers twice 00539 TransferSettings vol_settings(_settings); 00540 00541 // add custom headers 00542 vol_settings.addHeader(anonymousIdHeader()); 00543 vol_settings.addHeader(distributionFlavorHeader()); 00544 vol_settings.addHeader("Pragma:"); 00545 00546 _settings.setTimeout(TRANSFER_TIMEOUT); 00547 _settings.setConnectTimeout(CONNECT_TIMEOUT); 00548 00549 _settings.setUserAgentString(agentString()); 00550 00551 // fill some settings from url query parameters 00552 try 00553 { 00554 fillSettingsFromUrl(_url, _settings); 00555 } 00556 catch ( const MediaException &e ) 00557 { 00558 disconnectFrom(); 00559 ZYPP_RETHROW(e); 00560 } 00561 00562 // if the proxy was not set by url, then look 00563 if ( _settings.proxy().empty() ) 00564 { 00565 // at the system proxy settings 00566 fillSettingsSystemProxy(_url, _settings); 00567 } 00568 00569 DBG << "Proxy: " << (_settings.proxy().empty() ? "-none-" : _settings.proxy()) << endl; 00570 00574 SET_OPTION(CURLOPT_CONNECTTIMEOUT, _settings.connectTimeout()); 00575 00576 // follow any Location: header that the server sends as part of 00577 // an HTTP header (#113275) 00578 SET_OPTION(CURLOPT_FOLLOWLOCATION, 1L); 00579 // 3 redirects seem to be too few in some cases (bnc #465532) 00580 SET_OPTION(CURLOPT_MAXREDIRS, 6L); 00581 00582 if ( _url.getScheme() == "https" ) 00583 { 00584 #if CURLVERSION_AT_LEAST(7,19,4) 00585 // restrict following of redirections from https to https only 00586 SET_OPTION( CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS ); 00587 #endif 00588 00589 if( _settings.verifyPeerEnabled() || 00590 _settings.verifyHostEnabled() ) 00591 { 00592 SET_OPTION(CURLOPT_CAPATH, _settings.certificateAuthoritiesPath().c_str()); 00593 } 00594 00595 SET_OPTION(CURLOPT_SSL_VERIFYPEER, _settings.verifyPeerEnabled() ? 1L : 0L); 00596 SET_OPTION(CURLOPT_SSL_VERIFYHOST, _settings.verifyHostEnabled() ? 2L : 0L); 00597 } 00598 00599 SET_OPTION(CURLOPT_USERAGENT, _settings.userAgentString().c_str() ); 00600 00601 /*---------------------------------------------------------------* 00602 CURLOPT_USERPWD: [user name]:[password] 00603 00604 Url::username/password -> CURLOPT_USERPWD 00605 If not provided, anonymous FTP identification 00606 *---------------------------------------------------------------*/ 00607 00608 if ( _settings.userPassword().size() ) 00609 { 00610 SET_OPTION(CURLOPT_USERPWD, _settings.userPassword().c_str()); 00611 string use_auth = _settings.authType(); 00612 if (use_auth.empty()) 00613 use_auth = "digest,basic"; // our default 00614 long auth = CurlAuthData::auth_type_str2long(use_auth); 00615 if( auth != CURLAUTH_NONE) 00616 { 00617 DBG << "Enabling HTTP authentication methods: " << use_auth 00618 << " (CURLOPT_HTTPAUTH=" << auth << ")" << std::endl; 00619 SET_OPTION(CURLOPT_HTTPAUTH, auth); 00620 } 00621 } 00622 00623 if ( _settings.proxyEnabled() ) 00624 { 00625 if ( ! _settings.proxy().empty() ) 00626 { 00627 SET_OPTION(CURLOPT_PROXY, _settings.proxy().c_str()); 00628 /*---------------------------------------------------------------* 00629 CURLOPT_PROXYUSERPWD: [user name]:[password] 00630 00631 Url::option(proxyuser and proxypassword) -> CURLOPT_PROXYUSERPWD 00632 If not provided, $HOME/.curlrc is evaluated 00633 *---------------------------------------------------------------*/ 00634 00635 string proxyuserpwd = _settings.proxyUserPassword(); 00636 00637 if ( proxyuserpwd.empty() ) 00638 { 00639 CurlConfig curlconf; 00640 CurlConfig::parseConfig(curlconf); // parse ~/.curlrc 00641 if (curlconf.proxyuserpwd.empty()) 00642 DBG << "~/.curlrc does not contain the proxy-user option" << endl; 00643 else 00644 { 00645 proxyuserpwd = curlconf.proxyuserpwd; 00646 DBG << "using proxy-user from ~/.curlrc" << endl; 00647 } 00648 } 00649 else 00650 { 00651 DBG << "using provided proxy-user" << endl; 00652 } 00653 00654 if ( ! proxyuserpwd.empty() ) 00655 { 00656 SET_OPTION(CURLOPT_PROXYUSERPWD, unEscape( proxyuserpwd ).c_str()); 00657 } 00658 } 00659 } 00660 else 00661 { 00662 #if CURLVERSION_AT_LEAST(7,19,4) 00663 SET_OPTION(CURLOPT_NOPROXY, "*"); 00664 #endif 00665 } 00666 00668 if ( _settings.minDownloadSpeed() != 0 ) 00669 { 00670 SET_OPTION(CURLOPT_LOW_SPEED_LIMIT, _settings.minDownloadSpeed()); 00671 // default to 10 seconds at low speed 00672 SET_OPTION(CURLOPT_LOW_SPEED_TIME, 10L); 00673 } 00674 00675 #if CURLVERSION_AT_LEAST(7,15,5) 00676 if ( _settings.maxDownloadSpeed() != 0 ) 00677 SET_OPTION_OFFT(CURLOPT_MAX_RECV_SPEED_LARGE, _settings.maxDownloadSpeed()); 00678 #endif 00679 00680 /*---------------------------------------------------------------* 00681 *---------------------------------------------------------------*/ 00682 00683 _currentCookieFile = _cookieFile.asString(); 00684 if ( str::strToBool( _url.getQueryParam( "cookies" ), true ) ) 00685 SET_OPTION(CURLOPT_COOKIEFILE, _currentCookieFile.c_str() ); 00686 else 00687 MIL << "No cookies requested" << endl; 00688 SET_OPTION(CURLOPT_COOKIEJAR, _currentCookieFile.c_str() ); 00689 SET_OPTION(CURLOPT_PROGRESSFUNCTION, &progressCallback ); 00690 SET_OPTION(CURLOPT_NOPROGRESS, 0L); 00691 00692 #if CURLVERSION_AT_LEAST(7,18,0) 00693 // bnc #306272 00694 SET_OPTION(CURLOPT_PROXY_TRANSFER_MODE, 1L ); 00695 #endif 00696 // append settings custom headers to curl 00697 for ( TransferSettings::Headers::const_iterator it = vol_settings.headersBegin(); 00698 it != vol_settings.headersEnd(); 00699 ++it ) 00700 { 00701 MIL << "HEADER " << *it << std::endl; 00702 00703 _customHeaders = curl_slist_append(_customHeaders, it->c_str()); 00704 if ( !_customHeaders ) 00705 ZYPP_THROW(MediaCurlInitException(_url)); 00706 } 00707 00708 SET_OPTION(CURLOPT_HTTPHEADER, _customHeaders); 00709 } 00710 00712 00713 00714 void MediaCurl::attachTo (bool next) 00715 { 00716 if ( next ) 00717 ZYPP_THROW(MediaNotSupportedException(_url)); 00718 00719 if ( !_url.isValid() ) 00720 ZYPP_THROW(MediaBadUrlException(_url)); 00721 00722 checkProtocol(_url); 00723 if( !isUseableAttachPoint(attachPoint())) 00724 { 00725 std::string mountpoint = createAttachPoint().asString(); 00726 00727 if( mountpoint.empty()) 00728 ZYPP_THROW( MediaBadAttachPointException(url())); 00729 00730 setAttachPoint( mountpoint, true); 00731 } 00732 00733 disconnectFrom(); // clean _curl if needed 00734 _curl = curl_easy_init(); 00735 if ( !_curl ) { 00736 ZYPP_THROW(MediaCurlInitException(_url)); 00737 } 00738 try 00739 { 00740 setupEasy(); 00741 } 00742 catch (Exception & ex) 00743 { 00744 disconnectFrom(); 00745 ZYPP_RETHROW(ex); 00746 } 00747 00748 // FIXME: need a derived class to propelly compare url's 00749 MediaSourceRef media( new MediaSource(_url.getScheme(), _url.asString())); 00750 setMediaSource(media); 00751 } 00752 00753 bool 00754 MediaCurl::checkAttachPoint(const Pathname &apoint) const 00755 { 00756 return MediaHandler::checkAttachPoint( apoint, true, true); 00757 } 00758 00760 00761 void MediaCurl::disconnectFrom() 00762 { 00763 if ( _customHeaders ) 00764 { 00765 curl_slist_free_all(_customHeaders); 00766 _customHeaders = 0L; 00767 } 00768 00769 if ( _curl ) 00770 { 00771 curl_easy_cleanup( _curl ); 00772 _curl = NULL; 00773 } 00774 } 00775 00777 00778 void MediaCurl::releaseFrom( const std::string & ejectDev ) 00779 { 00780 disconnect(); 00781 } 00782 00783 Url MediaCurl::getFileUrl(const Pathname & filename) const 00784 { 00785 Url newurl(_url); 00786 string path = _url.getPathName(); 00787 if ( !path.empty() && path != "/" && *path.rbegin() == '/' && 00788 filename.absolute() ) 00789 { 00790 // If url has a path with trailing slash, remove the leading slash from 00791 // the absolute file name 00792 path += filename.asString().substr( 1, filename.asString().size() - 1 ); 00793 } 00794 else if ( filename.relative() ) 00795 { 00796 // Add trailing slash to path, if not already there 00797 if (path.empty()) path = "/"; 00798 else if (*path.rbegin() != '/' ) path += "/"; 00799 // Remove "./" from begin of relative file name 00800 path += filename.asString().substr( 2, filename.asString().size() - 2 ); 00801 } 00802 else 00803 { 00804 path += filename.asString(); 00805 } 00806 00807 newurl.setPathName(path); 00808 return newurl; 00809 } 00810 00812 00813 void MediaCurl::getFile( const Pathname & filename ) const 00814 { 00815 // Use absolute file name to prevent access of files outside of the 00816 // hierarchy below the attach point. 00817 getFileCopy(filename, localPath(filename).absolutename()); 00818 } 00819 00821 00822 void MediaCurl::getFileCopy( const Pathname & filename , const Pathname & target) const 00823 { 00824 callback::SendReport<DownloadProgressReport> report; 00825 00826 Url fileurl(getFileUrl(filename)); 00827 00828 bool retry = false; 00829 00830 do 00831 { 00832 try 00833 { 00834 doGetFileCopy(filename, target, report); 00835 retry = false; 00836 } 00837 // retry with proper authentication data 00838 catch (MediaUnauthorizedException & ex_r) 00839 { 00840 if(authenticate(ex_r.hint(), !retry)) 00841 retry = true; 00842 else 00843 { 00844 report->finish(fileurl, zypp::media::DownloadProgressReport::ACCESS_DENIED, ex_r.asUserHistory()); 00845 ZYPP_RETHROW(ex_r); 00846 } 00847 } 00848 // unexpected exception 00849 catch (MediaException & excpt_r) 00850 { 00851 // FIXME: error number fix 00852 report->finish(fileurl, zypp::media::DownloadProgressReport::ERROR, excpt_r.asUserHistory()); 00853 ZYPP_RETHROW(excpt_r); 00854 } 00855 } 00856 while (retry); 00857 00858 report->finish(fileurl, zypp::media::DownloadProgressReport::NO_ERROR, ""); 00859 } 00860 00862 00863 bool MediaCurl::getDoesFileExist( const Pathname & filename ) const 00864 { 00865 bool retry = false; 00866 00867 do 00868 { 00869 try 00870 { 00871 return doGetDoesFileExist( filename ); 00872 } 00873 // authentication problem, retry with proper authentication data 00874 catch (MediaUnauthorizedException & ex_r) 00875 { 00876 if(authenticate(ex_r.hint(), !retry)) 00877 retry = true; 00878 else 00879 ZYPP_RETHROW(ex_r); 00880 } 00881 // unexpected exception 00882 catch (MediaException & excpt_r) 00883 { 00884 ZYPP_RETHROW(excpt_r); 00885 } 00886 } 00887 while (retry); 00888 00889 return false; 00890 } 00891 00893 00894 void MediaCurl::evaluateCurlCode( const Pathname &filename, 00895 CURLcode code, 00896 bool timeout_reached ) const 00897 { 00898 if ( code != 0 ) 00899 { 00900 Url url; 00901 if (filename.empty()) 00902 url = _url; 00903 else 00904 url = getFileUrl(filename); 00905 std::string err; 00906 try 00907 { 00908 switch ( code ) 00909 { 00910 case CURLE_UNSUPPORTED_PROTOCOL: 00911 case CURLE_URL_MALFORMAT: 00912 case CURLE_URL_MALFORMAT_USER: 00913 err = " Bad URL"; 00914 break; 00915 case CURLE_LOGIN_DENIED: 00916 ZYPP_THROW( 00917 MediaUnauthorizedException(url, "Login failed.", _curlError, "")); 00918 break; 00919 case CURLE_HTTP_RETURNED_ERROR: 00920 { 00921 long httpReturnCode = 0; 00922 CURLcode infoRet = curl_easy_getinfo( _curl, 00923 CURLINFO_RESPONSE_CODE, 00924 &httpReturnCode ); 00925 if ( infoRet == CURLE_OK ) 00926 { 00927 string msg = "HTTP response: " + str::numstring( httpReturnCode ); 00928 switch ( httpReturnCode ) 00929 { 00930 case 401: 00931 { 00932 string auth_hint = getAuthHint(); 00933 00934 DBG << msg << " Login failed (URL: " << url.asString() << ")" << std::endl; 00935 DBG << "MediaUnauthorizedException auth hint: '" << auth_hint << "'" << std::endl; 00936 00937 ZYPP_THROW(MediaUnauthorizedException( 00938 url, "Login failed.", _curlError, auth_hint 00939 )); 00940 } 00941 00942 case 503: // service temporarily unavailable (bnc #462545) 00943 ZYPP_THROW(MediaTemporaryProblemException(url)); 00944 case 504: // gateway timeout 00945 ZYPP_THROW(MediaTimeoutException(url)); 00946 case 403: 00947 { 00948 string msg403; 00949 if (url.asString().find("novell.com") != string::npos) 00950 msg403 = _("Visit the Novell Customer Center to check whether your registration is valid and has not expired."); 00951 ZYPP_THROW(MediaForbiddenException(url, msg403)); 00952 } 00953 case 404: 00954 ZYPP_THROW(MediaFileNotFoundException(_url, filename)); 00955 } 00956 00957 DBG << msg << " (URL: " << url.asString() << ")" << std::endl; 00958 ZYPP_THROW(MediaCurlException(url, msg, _curlError)); 00959 } 00960 else 00961 { 00962 string msg = "Unable to retrieve HTTP response:"; 00963 DBG << msg << " (URL: " << url.asString() << ")" << std::endl; 00964 ZYPP_THROW(MediaCurlException(url, msg, _curlError)); 00965 } 00966 } 00967 break; 00968 case CURLE_FTP_COULDNT_RETR_FILE: 00969 #if CURLVERSION_AT_LEAST(7,16,0) 00970 case CURLE_REMOTE_FILE_NOT_FOUND: 00971 #endif 00972 case CURLE_FTP_ACCESS_DENIED: 00973 err = "File not found"; 00974 ZYPP_THROW(MediaFileNotFoundException(_url, filename)); 00975 break; 00976 case CURLE_BAD_PASSWORD_ENTERED: 00977 case CURLE_FTP_USER_PASSWORD_INCORRECT: 00978 err = "Login failed"; 00979 break; 00980 case CURLE_COULDNT_RESOLVE_PROXY: 00981 case CURLE_COULDNT_RESOLVE_HOST: 00982 case CURLE_COULDNT_CONNECT: 00983 case CURLE_FTP_CANT_GET_HOST: 00984 err = "Connection failed"; 00985 break; 00986 case CURLE_WRITE_ERROR: 00987 err = "Write error"; 00988 break; 00989 case CURLE_PARTIAL_FILE: 00990 case CURLE_ABORTED_BY_CALLBACK: 00991 case CURLE_OPERATION_TIMEDOUT: 00992 if( timeout_reached) 00993 { 00994 err = "Timeout reached"; 00995 ZYPP_THROW(MediaTimeoutException(url)); 00996 } 00997 else 00998 { 00999 err = "User abort"; 01000 } 01001 break; 01002 case CURLE_SSL_PEER_CERTIFICATE: 01003 default: 01004 err = "Unrecognized error"; 01005 break; 01006 } 01007 01008 // uhm, no 0 code but unknown curl exception 01009 ZYPP_THROW(MediaCurlException(url, err, _curlError)); 01010 } 01011 catch (const MediaException & excpt_r) 01012 { 01013 ZYPP_RETHROW(excpt_r); 01014 } 01015 } 01016 else 01017 { 01018 // actually the code is 0, nothing happened 01019 } 01020 } 01021 01023 01024 bool MediaCurl::doGetDoesFileExist( const Pathname & filename ) const 01025 { 01026 DBG << filename.asString() << endl; 01027 01028 if(!_url.isValid()) 01029 ZYPP_THROW(MediaBadUrlException(_url)); 01030 01031 if(_url.getHost().empty()) 01032 ZYPP_THROW(MediaBadUrlEmptyHostException(_url)); 01033 01034 Url url(getFileUrl(filename)); 01035 01036 DBG << "URL: " << url.asString() << endl; 01037 // Use URL without options and without username and passwd 01038 // (some proxies dislike them in the URL). 01039 // Curl seems to need the just scheme, hostname and a path; 01040 // the rest was already passed as curl options (in attachTo). 01041 Url curlUrl( clearQueryString(url) ); 01042 01043 // 01044 // See also Bug #154197 and ftp url definition in RFC 1738: 01045 // The url "ftp://user@host/foo/bar/file" contains a path, 01046 // that is relative to the user's home. 01047 // The url "ftp://user@host//foo/bar/file" (or also with 01048 // encoded slash as %2f) "ftp://user@host/%2ffoo/bar/file" 01049 // contains an absolute path. 01050 // 01051 string urlBuffer( curlUrl.asString()); 01052 CURLcode ret = curl_easy_setopt( _curl, CURLOPT_URL, 01053 urlBuffer.c_str() ); 01054 if ( ret != 0 ) { 01055 ZYPP_THROW(MediaCurlSetOptException(url, _curlError)); 01056 } 01057 01058 // instead of returning no data with NOBODY, we return 01059 // little data, that works with broken servers, and 01060 // works for ftp as well, because retrieving only headers 01061 // ftp will return always OK code ? 01062 // See http://curl.haxx.se/docs/knownbugs.html #58 01063 if ( (_url.getScheme() == "http" || _url.getScheme() == "https") && 01064 _settings.headRequestsAllowed() ) 01065 ret = curl_easy_setopt( _curl, CURLOPT_NOBODY, 1L ); 01066 else 01067 ret = curl_easy_setopt( _curl, CURLOPT_RANGE, "0-1" ); 01068 01069 if ( ret != 0 ) { 01070 curl_easy_setopt( _curl, CURLOPT_NOBODY, 0L); 01071 curl_easy_setopt( _curl, CURLOPT_RANGE, NULL ); 01072 /* yes, this is why we never got to get NOBODY working before, 01073 because setting it changes this option too, and we also 01074 need to reset it 01075 See: http://curl.haxx.se/mail/archive-2005-07/0073.html 01076 */ 01077 curl_easy_setopt( _curl, CURLOPT_HTTPGET, 1L ); 01078 ZYPP_THROW(MediaCurlSetOptException(url, _curlError)); 01079 } 01080 01081 FILE *file = ::fopen( "/dev/null", "w" ); 01082 if ( !file ) { 01083 ERR << "fopen failed for /dev/null" << endl; 01084 curl_easy_setopt( _curl, CURLOPT_NOBODY, 0L); 01085 curl_easy_setopt( _curl, CURLOPT_RANGE, NULL ); 01086 /* yes, this is why we never got to get NOBODY working before, 01087 because setting it changes this option too, and we also 01088 need to reset it 01089 See: http://curl.haxx.se/mail/archive-2005-07/0073.html 01090 */ 01091 curl_easy_setopt( _curl, CURLOPT_HTTPGET, 1L ); 01092 if ( ret != 0 ) { 01093 ZYPP_THROW(MediaCurlSetOptException(url, _curlError)); 01094 } 01095 ZYPP_THROW(MediaWriteException("/dev/null")); 01096 } 01097 01098 ret = curl_easy_setopt( _curl, CURLOPT_WRITEDATA, file ); 01099 if ( ret != 0 ) { 01100 ::fclose(file); 01101 std::string err( _curlError); 01102 curl_easy_setopt( _curl, CURLOPT_RANGE, NULL ); 01103 curl_easy_setopt( _curl, CURLOPT_NOBODY, 0L); 01104 /* yes, this is why we never got to get NOBODY working before, 01105 because setting it changes this option too, and we also 01106 need to reset it 01107 See: http://curl.haxx.se/mail/archive-2005-07/0073.html 01108 */ 01109 curl_easy_setopt( _curl, CURLOPT_HTTPGET, 1L ); 01110 if ( ret != 0 ) { 01111 ZYPP_THROW(MediaCurlSetOptException(url, _curlError)); 01112 } 01113 ZYPP_THROW(MediaCurlSetOptException(url, err)); 01114 } 01115 01116 CURLcode ok = curl_easy_perform( _curl ); 01117 MIL << "perform code: " << ok << " [ " << curl_easy_strerror(ok) << " ]" << endl; 01118 01119 // reset curl settings 01120 if ( _url.getScheme() == "http" || _url.getScheme() == "https" ) 01121 { 01122 curl_easy_setopt( _curl, CURLOPT_NOBODY, 0L); 01123 if ( ret != 0 ) { 01124 ZYPP_THROW(MediaCurlSetOptException(url, _curlError)); 01125 } 01126 01127 /* yes, this is why we never got to get NOBODY working before, 01128 because setting it changes this option too, and we also 01129 need to reset it 01130 See: http://curl.haxx.se/mail/archive-2005-07/0073.html 01131 */ 01132 curl_easy_setopt( _curl, CURLOPT_HTTPGET, 1L); 01133 if ( ret != 0 ) { 01134 ZYPP_THROW(MediaCurlSetOptException(url, _curlError)); 01135 } 01136 01137 } 01138 else 01139 { 01140 // for FTP we set different options 01141 curl_easy_setopt( _curl, CURLOPT_RANGE, NULL); 01142 if ( ret != 0 ) { 01143 ZYPP_THROW(MediaCurlSetOptException(url, _curlError)); 01144 } 01145 } 01146 01147 // if the code is not zero, close the file 01148 if ( ok != 0 ) 01149 ::fclose(file); 01150 01151 // as we are not having user interaction, the user can't cancel 01152 // the file existence checking, a callback or timeout return code 01153 // will be always a timeout. 01154 try { 01155 evaluateCurlCode( filename, ok, true /* timeout */); 01156 } 01157 catch ( const MediaFileNotFoundException &e ) { 01158 // if the file did not exist then we can return false 01159 return false; 01160 } 01161 catch ( const MediaException &e ) { 01162 // some error, we are not sure about file existence, rethrw 01163 ZYPP_RETHROW(e); 01164 } 01165 // exists 01166 return ( ok == CURLE_OK ); 01167 } 01168 01170 01171 01172 #if DETECT_DIR_INDEX 01173 bool MediaCurl::detectDirIndex() const 01174 { 01175 if(_url.getScheme() != "http" && _url.getScheme() != "https") 01176 return false; 01177 // 01178 // try to check the effective url and set the not_a_file flag 01179 // if the url path ends with a "/", what usually means, that 01180 // we've received a directory index (index.html content). 01181 // 01182 // Note: This may be dangerous and break file retrieving in 01183 // case of some server redirections ... ? 01184 // 01185 bool not_a_file = false; 01186 char *ptr = NULL; 01187 CURLcode ret = curl_easy_getinfo( _curl, 01188 CURLINFO_EFFECTIVE_URL, 01189 &ptr); 01190 if ( ret == CURLE_OK && ptr != NULL) 01191 { 01192 try 01193 { 01194 Url eurl( ptr); 01195 std::string path( eurl.getPathName()); 01196 if( !path.empty() && path != "/" && *path.rbegin() == '/') 01197 { 01198 DBG << "Effective url (" 01199 << eurl 01200 << ") seems to provide the index of a directory" 01201 << endl; 01202 not_a_file = true; 01203 } 01204 } 01205 catch( ... ) 01206 {} 01207 } 01208 return not_a_file; 01209 } 01210 #endif 01211 01213 01214 void MediaCurl::doGetFileCopy( const Pathname & filename , const Pathname & target, callback::SendReport<DownloadProgressReport> & report, RequestOptions options ) const 01215 { 01216 Pathname dest = target.absolutename(); 01217 if( assert_dir( dest.dirname() ) ) 01218 { 01219 DBG << "assert_dir " << dest.dirname() << " failed" << endl; 01220 Url url(getFileUrl(filename)); 01221 ZYPP_THROW( MediaSystemException(url, "System error on " + dest.dirname().asString()) ); 01222 } 01223 string destNew = target.asString() + ".new.zypp.XXXXXX"; 01224 char *buf = ::strdup( destNew.c_str()); 01225 if( !buf) 01226 { 01227 ERR << "out of memory for temp file name" << endl; 01228 Url url(getFileUrl(filename)); 01229 ZYPP_THROW(MediaSystemException(url, "out of memory for temp file name")); 01230 } 01231 01232 int tmp_fd = ::mkstemp( buf ); 01233 if( tmp_fd == -1) 01234 { 01235 free( buf); 01236 ERR << "mkstemp failed for file '" << destNew << "'" << endl; 01237 ZYPP_THROW(MediaWriteException(destNew)); 01238 } 01239 destNew = buf; 01240 free( buf); 01241 01242 FILE *file = ::fdopen( tmp_fd, "w" ); 01243 if ( !file ) { 01244 ::close( tmp_fd); 01245 filesystem::unlink( destNew ); 01246 ERR << "fopen failed for file '" << destNew << "'" << endl; 01247 ZYPP_THROW(MediaWriteException(destNew)); 01248 } 01249 01250 DBG << "dest: " << dest << endl; 01251 DBG << "temp: " << destNew << endl; 01252 01253 // set IFMODSINCE time condition (no download if not modified) 01254 if( PathInfo(target).isExist() && !(options & OPTION_NO_IFMODSINCE) ) 01255 { 01256 curl_easy_setopt(_curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_IFMODSINCE); 01257 curl_easy_setopt(_curl, CURLOPT_TIMEVALUE, (long)PathInfo(target).mtime()); 01258 } 01259 else 01260 { 01261 curl_easy_setopt(_curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_NONE); 01262 curl_easy_setopt(_curl, CURLOPT_TIMEVALUE, 0L); 01263 } 01264 try 01265 { 01266 doGetFileCopyFile(filename, dest, file, report, options); 01267 } 01268 catch (Exception &e) 01269 { 01270 ::fclose( file ); 01271 filesystem::unlink( destNew ); 01272 curl_easy_setopt(_curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_NONE); 01273 curl_easy_setopt(_curl, CURLOPT_TIMEVALUE, 0L); 01274 ZYPP_RETHROW(e); 01275 } 01276 01277 long httpReturnCode = 0; 01278 CURLcode infoRet = curl_easy_getinfo(_curl, 01279 CURLINFO_RESPONSE_CODE, 01280 &httpReturnCode); 01281 bool modified = true; 01282 if (infoRet == CURLE_OK) 01283 { 01284 DBG << "HTTP response: " + str::numstring(httpReturnCode); 01285 if ( httpReturnCode == 304 01286 || ( httpReturnCode == 213 && _url.getScheme() == "ftp" ) ) // not modified 01287 { 01288 DBG << " Not modified."; 01289 modified = false; 01290 } 01291 DBG << endl; 01292 } 01293 else 01294 { 01295 WAR << "Could not get the reponse code." << endl; 01296 } 01297 01298 if (modified || infoRet != CURLE_OK) 01299 { 01300 // apply umask 01301 if ( ::fchmod( ::fileno(file), filesystem::applyUmaskTo( 0644 ) ) ) 01302 { 01303 ERR << "Failed to chmod file " << destNew << endl; 01304 } 01305 if (::fclose( file )) 01306 { 01307 ERR << "Fclose failed for file '" << destNew << "'" << endl; 01308 ZYPP_THROW(MediaWriteException(destNew)); 01309 } 01310 // move the temp file into dest 01311 if ( rename( destNew, dest ) != 0 ) { 01312 ERR << "Rename failed" << endl; 01313 ZYPP_THROW(MediaWriteException(dest)); 01314 } 01315 } 01316 else 01317 { 01318 // close and remove the temp file 01319 ::fclose( file ); 01320 filesystem::unlink( destNew ); 01321 } 01322 01323 DBG << "done: " << PathInfo(dest) << endl; 01324 } 01325 01327 01328 void MediaCurl::doGetFileCopyFile( const Pathname & filename , const Pathname & dest, FILE *file, callback::SendReport<DownloadProgressReport> & report, RequestOptions options ) const 01329 { 01330 DBG << filename.asString() << endl; 01331 01332 if(!_url.isValid()) 01333 ZYPP_THROW(MediaBadUrlException(_url)); 01334 01335 if(_url.getHost().empty()) 01336 ZYPP_THROW(MediaBadUrlEmptyHostException(_url)); 01337 01338 Url url(getFileUrl(filename)); 01339 01340 DBG << "URL: " << url.asString() << endl; 01341 // Use URL without options and without username and passwd 01342 // (some proxies dislike them in the URL). 01343 // Curl seems to need the just scheme, hostname and a path; 01344 // the rest was already passed as curl options (in attachTo). 01345 Url curlUrl( clearQueryString(url) ); 01346 01347 // 01348 // See also Bug #154197 and ftp url definition in RFC 1738: 01349 // The url "ftp://user@host/foo/bar/file" contains a path, 01350 // that is relative to the user's home. 01351 // The url "ftp://user@host//foo/bar/file" (or also with 01352 // encoded slash as %2f) "ftp://user@host/%2ffoo/bar/file" 01353 // contains an absolute path. 01354 // 01355 string urlBuffer( curlUrl.asString()); 01356 CURLcode ret = curl_easy_setopt( _curl, CURLOPT_URL, 01357 urlBuffer.c_str() ); 01358 if ( ret != 0 ) { 01359 ZYPP_THROW(MediaCurlSetOptException(url, _curlError)); 01360 } 01361 01362 ret = curl_easy_setopt( _curl, CURLOPT_WRITEDATA, file ); 01363 if ( ret != 0 ) { 01364 ZYPP_THROW(MediaCurlSetOptException(url, _curlError)); 01365 } 01366 01367 // Set callback and perform. 01368 ProgressData progressData(_settings.timeout(), url, &report); 01369 if (!(options & OPTION_NO_REPORT_START)) 01370 report->start(url, dest); 01371 if ( curl_easy_setopt( _curl, CURLOPT_PROGRESSDATA, &progressData ) != 0 ) { 01372 WAR << "Can't set CURLOPT_PROGRESSDATA: " << _curlError << endl;; 01373 } 01374 01375 ret = curl_easy_perform( _curl ); 01376 #if CURLVERSION_AT_LEAST(7,19,4) 01377 // bnc#692260: If the client sends a request with an If-Modified-Since header 01378 // with a future date for the server, the server may respond 200 sending a 01379 // zero size file. 01380 // curl-7.19.4 introduces CURLINFO_CONDITION_UNMET to check this condition. 01381 if ( ftell(file) == 0 && ret == 0 ) 01382 { 01383 long httpReturnCode = 33; 01384 if ( curl_easy_getinfo( _curl, CURLINFO_RESPONSE_CODE, &httpReturnCode ) == CURLE_OK && httpReturnCode == 200 ) 01385 { 01386 long conditionUnmet = 33; 01387 if ( curl_easy_getinfo( _curl, CURLINFO_CONDITION_UNMET, &conditionUnmet ) == CURLE_OK && conditionUnmet ) 01388 { 01389 WAR << "TIMECONDITION unmet - retry without." << endl; 01390 curl_easy_setopt(_curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_NONE); 01391 curl_easy_setopt(_curl, CURLOPT_TIMEVALUE, 0L); 01392 ret = curl_easy_perform( _curl ); 01393 } 01394 } 01395 } 01396 #endif 01397 01398 if ( curl_easy_setopt( _curl, CURLOPT_PROGRESSDATA, NULL ) != 0 ) { 01399 WAR << "Can't unset CURLOPT_PROGRESSDATA: " << _curlError << endl;; 01400 } 01401 01402 if ( ret != 0 ) 01403 { 01404 ERR << "curl error: " << ret << ": " << _curlError 01405 << ", temp file size " << ftell(file) 01406 << " bytes." << endl; 01407 01408 // the timeout is determined by the progress data object 01409 // which holds wheter the timeout was reached or not, 01410 // otherwise it would be a user cancel 01411 try { 01412 evaluateCurlCode( filename, ret, progressData.reached); 01413 } 01414 catch ( const MediaException &e ) { 01415 // some error, we are not sure about file existence, rethrw 01416 ZYPP_RETHROW(e); 01417 } 01418 } 01419 01420 #if DETECT_DIR_INDEX 01421 if (!ret && detectDirIndex()) 01422 { 01423 ZYPP_THROW(MediaNotAFileException(_url, filename)); 01424 } 01425 #endif // DETECT_DIR_INDEX 01426 } 01427 01429 01430 void MediaCurl::getDir( const Pathname & dirname, bool recurse_r ) const 01431 { 01432 filesystem::DirContent content; 01433 getDirInfo( content, dirname, /*dots*/false ); 01434 01435 for ( filesystem::DirContent::const_iterator it = content.begin(); it != content.end(); ++it ) { 01436 Pathname filename = dirname + it->name; 01437 int res = 0; 01438 01439 switch ( it->type ) { 01440 case filesystem::FT_NOT_AVAIL: // old directory.yast contains no typeinfo at all 01441 case filesystem::FT_FILE: 01442 getFile( filename ); 01443 break; 01444 case filesystem::FT_DIR: // newer directory.yast contain at least directory info 01445 if ( recurse_r ) { 01446 getDir( filename, recurse_r ); 01447 } else { 01448 res = assert_dir( localPath( filename ) ); 01449 if ( res ) { 01450 WAR << "Ignore error (" << res << ") on creating local directory '" << localPath( filename ) << "'" << endl; 01451 } 01452 } 01453 break; 01454 default: 01455 // don't provide devices, sockets, etc. 01456 break; 01457 } 01458 } 01459 } 01460 01462 01463 void MediaCurl::getDirInfo( std::list<std::string> & retlist, 01464 const Pathname & dirname, bool dots ) const 01465 { 01466 getDirectoryYast( retlist, dirname, dots ); 01467 } 01468 01470 01471 void MediaCurl::getDirInfo( filesystem::DirContent & retlist, 01472 const Pathname & dirname, bool dots ) const 01473 { 01474 getDirectoryYast( retlist, dirname, dots ); 01475 } 01476 01478 01479 int MediaCurl::progressCallback( void *clientp, 01480 double dltotal, double dlnow, 01481 double ultotal, double ulnow) 01482 { 01483 ProgressData *pdata = reinterpret_cast<ProgressData *>(clientp); 01484 if( pdata) 01485 { 01486 time_t now = time(NULL); 01487 if( now > 0) 01488 { 01489 // reset time of last change in case initial time() 01490 // failed or the time was adjusted (goes backward) 01491 if( pdata->ltime <= 0 || pdata->ltime > now) 01492 { 01493 pdata->ltime = now; 01494 } 01495 01496 // start time counting as soon as first data arrives 01497 // (skip the connection / redirection time at begin) 01498 time_t dif = 0; 01499 if (dlnow > 0 || ulnow > 0) 01500 { 01501 dif = (now - pdata->ltime); 01502 dif = dif > 0 ? dif : 0; 01503 01504 pdata->secs += dif; 01505 } 01506 01507 // update the drate_avg and drate_period only after a second has passed 01508 // (this callback is called much more often than a second) 01509 // otherwise the values would be far from accurate when measuring 01510 // the time in seconds 01512 01513 if ( pdata->secs > 1 && (dif > 0 || dlnow == dltotal )) 01514 pdata->drate_avg = (dlnow / pdata->secs); 01515 01516 if ( dif > 0 ) 01517 { 01518 pdata->drate_period = ((dlnow - pdata->dload_period) / dif); 01519 pdata->dload_period = dlnow; 01520 } 01521 } 01522 01523 // send progress report first, abort transfer if requested 01524 if( pdata->report) 01525 { 01526 if (!(*(pdata->report))->progress(int( dltotal ? dlnow * 100 / dltotal : 0 ), 01527 pdata->url, 01528 pdata->drate_avg, 01529 pdata->drate_period)) 01530 { 01531 return 1; // abort transfer 01532 } 01533 } 01534 01535 // check if we there is a timeout set 01536 if( pdata->timeout > 0) 01537 { 01538 if( now > 0) 01539 { 01540 bool progress = false; 01541 01542 // update download data if changed, mark progress 01543 if( dlnow != pdata->dload) 01544 { 01545 progress = true; 01546 pdata->dload = dlnow; 01547 pdata->ltime = now; 01548 } 01549 // update upload data if changed, mark progress 01550 if( ulnow != pdata->uload) 01551 { 01552 progress = true; 01553 pdata->uload = ulnow; 01554 pdata->ltime = now; 01555 } 01556 01557 if( !progress && (now >= (pdata->ltime + pdata->timeout))) 01558 { 01559 pdata->reached = true; 01560 return 1; // aborts transfer 01561 } 01562 } 01563 } 01564 } 01565 return 0; 01566 } 01567 01569 01570 string MediaCurl::getAuthHint() const 01571 { 01572 long auth_info = CURLAUTH_NONE; 01573 01574 CURLcode infoRet = 01575 curl_easy_getinfo(_curl, CURLINFO_HTTPAUTH_AVAIL, &auth_info); 01576 01577 if(infoRet == CURLE_OK) 01578 { 01579 return CurlAuthData::auth_type_long2str(auth_info); 01580 } 01581 01582 return ""; 01583 } 01584 01586 01587 bool MediaCurl::authenticate(const string & availAuthTypes, bool firstTry) const 01588 { 01590 Target_Ptr target = zypp::getZYpp()->getTarget(); 01591 CredentialManager cm(CredManagerOptions(target ? target->root() : "")); 01592 CurlAuthData_Ptr credentials; 01593 01594 // get stored credentials 01595 AuthData_Ptr cmcred = cm.getCred(_url); 01596 01597 if (cmcred && firstTry) 01598 { 01599 credentials.reset(new CurlAuthData(*cmcred)); 01600 DBG << "got stored credentials:" << endl << *credentials << endl; 01601 } 01602 // if not found, ask user 01603 else 01604 { 01605 01606 CurlAuthData_Ptr curlcred; 01607 curlcred.reset(new CurlAuthData()); 01608 callback::SendReport<AuthenticationReport> auth_report; 01609 01610 // preset the username if present in current url 01611 if (!_url.getUsername().empty() && firstTry) 01612 curlcred->setUsername(_url.getUsername()); 01613 // if CM has found some credentials, preset the username from there 01614 else if (cmcred) 01615 curlcred->setUsername(cmcred->username()); 01616 01617 // indicate we have no good credentials from CM 01618 cmcred.reset(); 01619 01620 string prompt_msg = boost::str(boost::format( 01622 _("Authentication required for '%s'")) % _url.asString()); 01623 01624 // set available authentication types from the exception 01625 // might be needed in prompt 01626 curlcred->setAuthType(availAuthTypes); 01627 01628 // ask user 01629 if (auth_report->prompt(_url, prompt_msg, *curlcred)) 01630 { 01631 DBG << "callback answer: retry" << endl 01632 << "CurlAuthData: " << *curlcred << endl; 01633 01634 if (curlcred->valid()) 01635 { 01636 credentials = curlcred; 01637 // if (credentials->username() != _url.getUsername()) 01638 // _url.setUsername(credentials->username()); 01646 } 01647 } 01648 else 01649 { 01650 DBG << "callback answer: cancel" << endl; 01651 } 01652 } 01653 01654 // set username and password 01655 if (credentials) 01656 { 01657 // HACK, why is this const? 01658 const_cast<MediaCurl*>(this)->_settings.setUsername(credentials->username()); 01659 const_cast<MediaCurl*>(this)->_settings.setPassword(credentials->password()); 01660 01661 // set username and password 01662 CURLcode ret = curl_easy_setopt(_curl, CURLOPT_USERPWD, _settings.userPassword().c_str()); 01663 if ( ret != 0 ) ZYPP_THROW(MediaCurlSetOptException(_url, _curlError)); 01664 01665 // set available authentication types from the exception 01666 if (credentials->authType() == CURLAUTH_NONE) 01667 credentials->setAuthType(availAuthTypes); 01668 01669 // set auth type (seems this must be set _after_ setting the userpwd) 01670 if (credentials->authType() != CURLAUTH_NONE) 01671 { 01672 // FIXME: only overwrite if not empty? 01673 const_cast<MediaCurl*>(this)->_settings.setAuthType(credentials->authTypeAsString()); 01674 ret = curl_easy_setopt(_curl, CURLOPT_HTTPAUTH, credentials->authType()); 01675 if ( ret != 0 ) ZYPP_THROW(MediaCurlSetOptException(_url, _curlError)); 01676 } 01677 01678 if (!cmcred) 01679 { 01680 credentials->setUrl(_url); 01681 cm.addCred(*credentials); 01682 cm.save(); 01683 } 01684 01685 return true; 01686 } 01687 01688 return false; 01689 } 01690 01691 01692 } // namespace media 01693 } // namespace zypp 01694 //