From 2f36bcc3e6c0cf514004fa9499437a383566d040 Mon Sep 17 00:00:00 2001 From: Michael Schroeder Date: Tue, 27 Jul 2010 10:49:38 +0200 Subject: [PATCH] - some refactoring for MediaMultiCurl: * split off checkProtocol(), getFileUrl() functions, make them protected * make setupEasy() protected * move dirIndex hack into detectDirIndex() to improve readability * add OPTION_NO_IFMODSINCE and OPTION_NO_REPORT_START options * fix argument type of many curl_easy_setopt() calls * move authType setting into fillSettingsFromUrl where it belongs * split doGetFileCopyFile from doGetFileCopy so that we can call it from MediaMultiCurl --- zypp/media/MediaCurl.cc | 489 +++++++++++++++++++++------------------- zypp/media/MediaCurl.h | 49 +++- 2 files changed, 297 insertions(+), 241 deletions(-) diff --git a/zypp/media/MediaCurl.cc b/zypp/media/MediaCurl.cc index 5ae492db5..71d729a35 100644 --- a/zypp/media/MediaCurl.cc +++ b/zypp/media/MediaCurl.cc @@ -292,6 +292,22 @@ void fillSettingsFromUrl( const Url &url, TransferSettings &s ) s.setProxy(proxy); s.setProxyEnabled(true); } + + // HTTP authentication type + string use_auth = url.getQueryParam("auth"); + if (!use_auth.empty() && (url.getScheme() == "http" || url.getScheme() == "https")) + { + try + { + CurlAuthData::auth_type_str2long(use_auth); // check if we know it + } + catch (MediaException & ex_r) + { + DBG << "Rethrowing as MediaUnauthorizedException."; + ZYPP_THROW(MediaUnauthorizedException(url, ex_r.msg(), "", "")); + } + s.setAuthType(use_auth); + } } /** @@ -370,7 +386,6 @@ static const char *const agentString() #define SET_OPTION(opt,val) do { \ ret = curl_easy_setopt ( _curl, opt, val ); \ if ( ret != 0) { \ - disconnectFrom(); \ ZYPP_THROW(MediaCurlSetOptException(_url, _curlError)); \ } \ } while ( false ) @@ -418,76 +433,52 @@ void MediaCurl::setCookieFile( const Pathname &fileName ) /////////////////////////////////////////////////////////////////// -void MediaCurl::attachTo (bool next) +void MediaCurl::checkProtocol(const Url &url) const { - if ( next ) - ZYPP_THROW(MediaNotSupportedException(_url)); - - if ( !_url.isValid() ) - ZYPP_THROW(MediaBadUrlException(_url)); - - CurlConfig curlconf; - CurlConfig::parseConfig(curlconf); // parse ~/.curlrc - curl_version_info_data *curl_info = NULL; curl_info = curl_version_info(CURLVERSION_NOW); // curl_info does not need any free (is static) if (curl_info->protocols) { const char * const *proto; - std::string scheme( _url.getScheme()); + std::string scheme( url.getScheme()); bool found = false; for(proto=curl_info->protocols; !found && *proto; ++proto) - { + { if( scheme == std::string((const char *)*proto)) found = true; - } + } if( !found) - { + { std::string msg("Unsupported protocol '"); msg += scheme; - msg += "'"; + msg += "'"; ZYPP_THROW(MediaBadUrlException(_url, msg)); - } - } - - if( !isUseableAttachPoint(attachPoint())) - { - std::string mountpoint = createAttachPoint().asString(); - - if( mountpoint.empty()) - ZYPP_THROW( MediaBadAttachPointException(url())); - - setAttachPoint( mountpoint, true); - } - - disconnectFrom(); // clean _curl if needed - _curl = curl_easy_init(); - if ( !_curl ) { - ZYPP_THROW(MediaCurlInitException(_url)); + } } +} +void MediaCurl::setupEasy() +{ { char *ptr = getenv("ZYPP_MEDIA_CURL_DEBUG"); _curlDebug = (ptr && *ptr) ? str::strtonum( ptr) : 0L; if( _curlDebug > 0) { - curl_easy_setopt( _curl, CURLOPT_VERBOSE, 1); + curl_easy_setopt( _curl, CURLOPT_VERBOSE, 1L); curl_easy_setopt( _curl, CURLOPT_DEBUGFUNCTION, log_curl); curl_easy_setopt( _curl, CURLOPT_DEBUGDATA, &_curlDebug); } } curl_easy_setopt(_curl, CURLOPT_HEADERFUNCTION, log_redirects_curl); - CURLcode ret = curl_easy_setopt( _curl, CURLOPT_ERRORBUFFER, _curlError ); if ( ret != 0 ) { - disconnectFrom(); ZYPP_THROW(MediaCurlSetOptException(_url, "Error setting error buffer")); } - SET_OPTION(CURLOPT_FAILONERROR,true); - SET_OPTION(CURLOPT_NOSIGNAL, 1); + SET_OPTION(CURLOPT_FAILONERROR, 1L); + SET_OPTION(CURLOPT_NOSIGNAL, 1L); // reset settings in case we are re-attaching _settings.reset(); @@ -529,7 +520,7 @@ void MediaCurl::attachTo (bool next) // follow any Location: header that the server sends as part of // an HTTP header (#113275) - SET_OPTION(CURLOPT_FOLLOWLOCATION, true); + SET_OPTION(CURLOPT_FOLLOWLOCATION, 1L); // 3 redirects seem to be too few in some cases (bnc #465532) SET_OPTION(CURLOPT_MAXREDIRS, 6L); @@ -559,40 +550,16 @@ void MediaCurl::attachTo (bool next) if ( _settings.userPassword().size() ) { - SET_OPTION(CURLOPT_USERPWD, unEscape(_settings.userPassword()).c_str()); - - //FIXME, we leave this here for now, as it does not make sense yet - // to refactor it to the fill settings from url function - - // HTTP authentication type - if(_url.getScheme() == "http" || _url.getScheme() == "https") + SET_OPTION(CURLOPT_USERPWD, _settings.userPassword().c_str()); + string use_auth = _settings.authType(); + if (use_auth.empty()) + use_auth = "digest,basic"; // our default + long auth = CurlAuthData::auth_type_str2long(use_auth); + if( auth != CURLAUTH_NONE) { - string use_auth = _url.getQueryParam("auth"); - if( use_auth.empty()) - use_auth = "digest,basic"; - - try - { - long auth = CurlAuthData::auth_type_str2long(use_auth); - if( auth != CURLAUTH_NONE) - { - DBG << "Enabling HTTP authentication methods: " << use_auth - << " (CURLOPT_HTTPAUTH=" << auth << ")" << std::endl; - - SET_OPTION(CURLOPT_HTTPAUTH, auth); - } - } - catch (MediaException & ex_r) - { - string auth_hint = getAuthHint(); - - DBG << "Rethrowing as MediaUnauthorizedException. auth hint: '" - << auth_hint << "'" << endl; - - ZYPP_THROW(MediaUnauthorizedException( - _url, ex_r.msg(), _curlError, auth_hint - )); - } + DBG << "Enabling HTTP authentication methods: " << use_auth + << " (CURLOPT_HTTPAUTH=" << auth << ")" << std::endl; + SET_OPTION(CURLOPT_HTTPAUTH, auth); } } @@ -612,6 +579,8 @@ void MediaCurl::attachTo (bool next) if ( proxyuserpwd.empty() ) { + CurlConfig curlconf; + CurlConfig::parseConfig(curlconf); // parse ~/.curlrc if (curlconf.proxyuserpwd.empty()) DBG << "~/.curlrc does not contain the proxy-user option" << endl; else @@ -632,11 +601,11 @@ void MediaCurl::attachTo (bool next) { SET_OPTION(CURLOPT_LOW_SPEED_LIMIT, _settings.minDownloadSpeed()); // default to 10 seconds at low speed - SET_OPTION(CURLOPT_LOW_SPEED_TIME, 10); + SET_OPTION(CURLOPT_LOW_SPEED_TIME, 10L); } if ( _settings.maxDownloadSpeed() != 0 ) - SET_OPTION(CURLOPT_MAX_RECV_SPEED_LARGE, _settings.maxDownloadSpeed()); + SET_OPTION(CURLOPT_MAX_RECV_SPEED_LARGE, (curl_off_t)_settings.maxDownloadSpeed()); /*---------------------------------------------------------------* *---------------------------------------------------------------*/ @@ -648,10 +617,10 @@ void MediaCurl::attachTo (bool next) MIL << "No cookies requested" << endl; SET_OPTION(CURLOPT_COOKIEJAR, _currentCookieFile.c_str() ); SET_OPTION(CURLOPT_PROGRESSFUNCTION, &progressCallback ); - SET_OPTION(CURLOPT_NOPROGRESS, false ); + SET_OPTION(CURLOPT_NOPROGRESS, 0L); // bnc #306272 - SET_OPTION(CURLOPT_PROXY_TRANSFER_MODE, 1 ); + SET_OPTION(CURLOPT_PROXY_TRANSFER_MODE, 1L ); // append settings custom headers to curl for ( TransferSettings::Headers::const_iterator it = _settings.headersBegin(); @@ -665,6 +634,44 @@ void MediaCurl::attachTo (bool next) } SET_OPTION(CURLOPT_HTTPHEADER, _customHeaders); +} + +/////////////////////////////////////////////////////////////////// + + +void MediaCurl::attachTo (bool next) +{ + if ( next ) + ZYPP_THROW(MediaNotSupportedException(_url)); + + if ( !_url.isValid() ) + ZYPP_THROW(MediaBadUrlException(_url)); + + checkProtocol(_url); + if( !isUseableAttachPoint(attachPoint())) + { + std::string mountpoint = createAttachPoint().asString(); + + if( mountpoint.empty()) + ZYPP_THROW( MediaBadAttachPointException(url())); + + setAttachPoint( mountpoint, true); + } + + disconnectFrom(); // clean _curl if needed + _curl = curl_easy_init(); + if ( !_curl ) { + ZYPP_THROW(MediaCurlInitException(_url)); + } + try + { + setupEasy(); + } + catch (Exception & ex) + { + disconnectFrom(); + ZYPP_RETHROW(ex); + } // FIXME: need a derived class to propelly compare url's MediaSourceRef media( new MediaSource(_url.getScheme(), _url.asString())); @@ -701,10 +708,10 @@ void MediaCurl::releaseFrom( const std::string & ejectDev ) disconnect(); } -static Url getFileUrl(const Url & url, const Pathname & filename) +Url MediaCurl::getFileUrl(const Pathname & filename) const { - Url newurl(url); - string path = url.getPathName(); + Url newurl(_url); + string path = _url.getPathName(); if ( !path.empty() && path != "/" && *path.rbegin() == '/' && filename.absolute() ) { @@ -744,7 +751,7 @@ void MediaCurl::getFileCopy( const Pathname & filename , const Pathname & target { callback::SendReport report; - Url fileurl(getFileUrl(_url, filename)); + Url fileurl(getFileUrl(filename)); bool retry = false; @@ -816,10 +823,13 @@ void MediaCurl::evaluateCurlCode( const Pathname &filename, CURLcode code, bool timeout_reached ) const { - Url url(getFileUrl(_url, filename)); - if ( code != 0 ) { + Url url; + if (filename.empty()) + url = _url; + else + url = getFileUrl(filename); std::string err; try { @@ -945,7 +955,7 @@ bool MediaCurl::doGetDoesFileExist( const Pathname & filename ) const if(_url.getHost().empty()) ZYPP_THROW(MediaBadUrlEmptyHostException(_url)); - Url url(getFileUrl(_url, filename)); + Url url(getFileUrl(filename)); DBG << "URL: " << url.asString() << endl; // Use URL without options and without username and passwd @@ -982,19 +992,19 @@ bool MediaCurl::doGetDoesFileExist( const Pathname & filename ) const // ftp will return always OK code ? // See http://curl.haxx.se/docs/knownbugs.html #58 if ( _url.getScheme() == "http" || _url.getScheme() == "https" ) - ret = curl_easy_setopt( _curl, CURLOPT_NOBODY, 1 ); + ret = curl_easy_setopt( _curl, CURLOPT_NOBODY, 1L ); else ret = curl_easy_setopt( _curl, CURLOPT_RANGE, "0-1" ); if ( ret != 0 ) { - curl_easy_setopt( _curl, CURLOPT_NOBODY, NULL ); + curl_easy_setopt( _curl, CURLOPT_NOBODY, 0L); curl_easy_setopt( _curl, CURLOPT_RANGE, NULL ); /* yes, this is why we never got to get NOBODY working before, because setting it changes this option too, and we also need to reset it See: http://curl.haxx.se/mail/archive-2005-07/0073.html */ - curl_easy_setopt( _curl, CURLOPT_HTTPGET, 1 ); + curl_easy_setopt( _curl, CURLOPT_HTTPGET, 1L ); ZYPP_THROW(MediaCurlSetOptException(url, _curlError)); } @@ -1002,14 +1012,14 @@ bool MediaCurl::doGetDoesFileExist( const Pathname & filename ) const if ( !file ) { ::fclose(file); ERR << "fopen failed for /dev/null" << endl; - curl_easy_setopt( _curl, CURLOPT_NOBODY, NULL ); + curl_easy_setopt( _curl, CURLOPT_NOBODY, 0L); curl_easy_setopt( _curl, CURLOPT_RANGE, NULL ); /* yes, this is why we never got to get NOBODY working before, because setting it changes this option too, and we also need to reset it See: http://curl.haxx.se/mail/archive-2005-07/0073.html */ - curl_easy_setopt( _curl, CURLOPT_HTTPGET, 1 ); + curl_easy_setopt( _curl, CURLOPT_HTTPGET, 1L ); if ( ret != 0 ) { ZYPP_THROW(MediaCurlSetOptException(url, _curlError)); } @@ -1021,13 +1031,13 @@ bool MediaCurl::doGetDoesFileExist( const Pathname & filename ) const ::fclose(file); std::string err( _curlError); curl_easy_setopt( _curl, CURLOPT_RANGE, NULL ); - curl_easy_setopt( _curl, CURLOPT_NOBODY, NULL ); + curl_easy_setopt( _curl, CURLOPT_NOBODY, 0L); /* yes, this is why we never got to get NOBODY working before, because setting it changes this option too, and we also need to reset it See: http://curl.haxx.se/mail/archive-2005-07/0073.html */ - curl_easy_setopt( _curl, CURLOPT_HTTPGET, 1 ); + curl_easy_setopt( _curl, CURLOPT_HTTPGET, 1L ); if ( ret != 0 ) { ZYPP_THROW(MediaCurlSetOptException(url, _curlError)); } @@ -1040,7 +1050,7 @@ bool MediaCurl::doGetDoesFileExist( const Pathname & filename ) const // reset curl settings if ( _url.getScheme() == "http" || _url.getScheme() == "https" ) { - curl_easy_setopt( _curl, CURLOPT_NOBODY, NULL ); + curl_easy_setopt( _curl, CURLOPT_NOBODY, 0L); if ( ret != 0 ) { ZYPP_THROW(MediaCurlSetOptException(url, _curlError)); } @@ -1050,7 +1060,7 @@ bool MediaCurl::doGetDoesFileExist( const Pathname & filename ) const need to reset it See: http://curl.haxx.se/mail/archive-2005-07/0073.html */ - curl_easy_setopt( _curl, CURLOPT_HTTPGET, 1); + curl_easy_setopt( _curl, CURLOPT_HTTPGET, 1L); if ( ret != 0 ) { ZYPP_THROW(MediaCurlSetOptException(url, _curlError)); } @@ -1089,74 +1099,65 @@ bool MediaCurl::doGetDoesFileExist( const Pathname & filename ) const /////////////////////////////////////////////////////////////////// -void MediaCurl::doGetFileCopy( const Pathname & filename , const Pathname & target, callback::SendReport & report, RequestOptions options ) const -{ - DBG << filename.asString() << endl; - if(!_url.isValid()) - ZYPP_THROW(MediaBadUrlException(_url)); - - if(_url.getHost().empty()) - ZYPP_THROW(MediaBadUrlEmptyHostException(_url)); +#if DETECT_DIR_INDEX +bool MediaCurl::detectDirIndex() const +{ + if(_url.getScheme() != "http" && _url.getScheme() != "https") + return false; + // + // try to check the effective url and set the not_a_file flag + // if the url path ends with a "/", what usually means, that + // we've received a directory index (index.html content). + // + // Note: This may be dangerous and break file retrieving in + // case of some server redirections ... ? + // + bool not_a_file = false; + char *ptr = NULL; + CURLcode ret = curl_easy_getinfo( _curl, + CURLINFO_EFFECTIVE_URL, + &ptr); + if ( ret == CURLE_OK && ptr != NULL) + { + try + { + Url eurl( ptr); + std::string path( eurl.getPathName()); + if( !path.empty() && path != "/" && *path.rbegin() == '/') + { + DBG << "Effective url (" + << eurl + << ") seems to provide the index of a directory" + << endl; + not_a_file = true; + } + } + catch( ... ) + {} + } + return not_a_file; +} +#endif - Url url(getFileUrl(_url, filename)); +/////////////////////////////////////////////////////////////////// +void MediaCurl::doGetFileCopy( const Pathname & filename , const Pathname & target, callback::SendReport & report, RequestOptions options ) const +{ Pathname dest = target.absolutename(); if( assert_dir( dest.dirname() ) ) { DBG << "assert_dir " << dest.dirname() << " failed" << endl; + Url url(getFileUrl(filename)); ZYPP_THROW( MediaSystemException(url, "System error on " + dest.dirname().asString()) ); } - - DBG << "URL: " << url.asString() << endl; - // Use URL without options and without username and passwd - // (some proxies dislike them in the URL). - // Curl seems to need the just scheme, hostname and a path; - // the rest was already passed as curl options (in attachTo). - Url curlUrl( url ); - - // Use asString + url::ViewOptions instead? - curlUrl.setUsername( "" ); - curlUrl.setPassword( "" ); - curlUrl.setPathParams( "" ); - curlUrl.setQueryString( "" ); - curlUrl.setFragment( "" ); - - // - // See also Bug #154197 and ftp url definition in RFC 1738: - // The url "ftp://user@host/foo/bar/file" contains a path, - // that is relative to the user's home. - // The url "ftp://user@host//foo/bar/file" (or also with - // encoded slash as %2f) "ftp://user@host/%2ffoo/bar/file" - // contains an absolute path. - // - string urlBuffer( curlUrl.asString()); - CURLcode ret = curl_easy_setopt( _curl, CURLOPT_URL, - urlBuffer.c_str() ); - if ( ret != 0 ) { - ZYPP_THROW(MediaCurlSetOptException(url, _curlError)); - } - - // set IFMODSINCE time condition (no download if not modified) - if( PathInfo(target).isExist() ) - { - curl_easy_setopt(_curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_IFMODSINCE); - curl_easy_setopt(_curl, CURLOPT_TIMEVALUE, PathInfo(target).mtime()); - } - else - { - curl_easy_setopt(_curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_NONE); - curl_easy_setopt(_curl, CURLOPT_TIMEVALUE, 0); - } - string destNew = target.asString() + ".new.zypp.XXXXXX"; char *buf = ::strdup( destNew.c_str()); if( !buf) { ERR << "out of memory for temp file name" << endl; - ZYPP_THROW(MediaSystemException( - url, "out of memory for temp file name" - )); + Url url(getFileUrl(filename)); + ZYPP_THROW(MediaSystemException(url, "out of memory for temp file name")); } int tmp_fd = ::mkstemp( buf ); @@ -1180,92 +1181,29 @@ void MediaCurl::doGetFileCopy( const Pathname & filename , const Pathname & targ DBG << "dest: " << dest << endl; DBG << "temp: " << destNew << endl; - ret = curl_easy_setopt( _curl, CURLOPT_WRITEDATA, file ); - if ( ret != 0 ) { - ::fclose( file ); - filesystem::unlink( destNew ); - ZYPP_THROW(MediaCurlSetOptException(url, _curlError)); + // set IFMODSINCE time condition (no download if not modified) + if( PathInfo(target).isExist() && !(options & OPTION_NO_IFMODSINCE) ) + { + curl_easy_setopt(_curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_IFMODSINCE); + curl_easy_setopt(_curl, CURLOPT_TIMEVALUE, (long)PathInfo(target).mtime()); } - - // Set callback and perform. - ProgressData progressData(_settings.timeout(), url, &report); - report->start(url, dest); - if ( curl_easy_setopt( _curl, CURLOPT_PROGRESSDATA, &progressData ) != 0 ) { - WAR << "Can't set CURLOPT_PROGRESSDATA: " << _curlError << endl;; + else + { + curl_easy_setopt(_curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_NONE); + curl_easy_setopt(_curl, CURLOPT_TIMEVALUE, 0L); } - - ret = curl_easy_perform( _curl ); - - if ( curl_easy_setopt( _curl, CURLOPT_PROGRESSDATA, NULL ) != 0 ) { - WAR << "Can't unset CURLOPT_PROGRESSDATA: " << _curlError << endl;; + try + { + doGetFileCopyFile(filename, dest, file, report, options); } - - if ( ret != 0 ) + catch (Exception &e) { - ERR << "curl error: " << ret << ": " << _curlError - << ", temp file size " << PathInfo(destNew).size() - << " byte." << endl; - ::fclose( file ); filesystem::unlink( destNew ); - - // the timeout is determined by the progress data object - // which holds wheter the timeout was reached or not, - // otherwise it would be a user cancel - try { - evaluateCurlCode( filename, ret, progressData.reached); - } - catch ( const MediaException &e ) { - // some error, we are not sure about file existence, rethrw - ZYPP_RETHROW(e); - } - } - -#if DETECT_DIR_INDEX - else - if(curlUrl.getScheme() == "http" || - curlUrl.getScheme() == "https") - { - // - // try to check the effective url and set the not_a_file flag - // if the url path ends with a "/", what usually means, that - // we've received a directory index (index.html content). - // - // Note: This may be dangerous and break file retrieving in - // case of some server redirections ... ? - // - bool not_a_file = false; - char *ptr = NULL; - CURLcode ret = curl_easy_getinfo( _curl, - CURLINFO_EFFECTIVE_URL, - &ptr); - if ( ret == CURLE_OK && ptr != NULL) - { - try - { - Url eurl( ptr); - std::string path( eurl.getPathName()); - if( !path.empty() && path != "/" && *path.rbegin() == '/') - { - DBG << "Effective url (" - << eurl - << ") seems to provide the index of a directory" - << endl; - not_a_file = true; - } - } - catch( ... ) - {} - } - - if( not_a_file) - { - ::fclose( file ); - filesystem::unlink( destNew ); - ZYPP_THROW(MediaNotAFileException(_url, filename)); - } + curl_easy_setopt(_curl, CURLOPT_TIMECONDITION, CURL_TIMECOND_NONE); + curl_easy_setopt(_curl, CURLOPT_TIMEVALUE, 0L); + ZYPP_RETHROW(e); } -#endif // DETECT_DIR_INDEX long httpReturnCode = 0; CURLcode infoRet = curl_easy_getinfo(_curl, @@ -1295,8 +1233,11 @@ void MediaCurl::doGetFileCopy( const Pathname & filename , const Pathname & targ { ERR << "Failed to chmod file " << destNew << endl; } - ::fclose( file ); - + if (::fclose( file )) + { + ERR << "Fclose failed for file '" << destNew << "'" << endl; + ZYPP_THROW(MediaWriteException(destNew)); + } // move the temp file into dest if ( rename( destNew, dest ) != 0 ) { ERR << "Rename failed" << endl; @@ -1315,6 +1256,94 @@ void MediaCurl::doGetFileCopy( const Pathname & filename , const Pathname & targ /////////////////////////////////////////////////////////////////// +void MediaCurl::doGetFileCopyFile( const Pathname & filename , const Pathname & dest, FILE *file, callback::SendReport & report, RequestOptions options ) const +{ + DBG << filename.asString() << endl; + + if(!_url.isValid()) + ZYPP_THROW(MediaBadUrlException(_url)); + + if(_url.getHost().empty()) + ZYPP_THROW(MediaBadUrlEmptyHostException(_url)); + + Url url(getFileUrl(filename)); + + DBG << "URL: " << url.asString() << endl; + // Use URL without options and without username and passwd + // (some proxies dislike them in the URL). + // Curl seems to need the just scheme, hostname and a path; + // the rest was already passed as curl options (in attachTo). + Url curlUrl( url ); + + // Use asString + url::ViewOptions instead? + curlUrl.setUsername( "" ); + curlUrl.setPassword( "" ); + curlUrl.setPathParams( "" ); + curlUrl.setQueryString( "" ); + curlUrl.setFragment( "" ); + + // + // See also Bug #154197 and ftp url definition in RFC 1738: + // The url "ftp://user@host/foo/bar/file" contains a path, + // that is relative to the user's home. + // The url "ftp://user@host//foo/bar/file" (or also with + // encoded slash as %2f) "ftp://user@host/%2ffoo/bar/file" + // contains an absolute path. + // + string urlBuffer( curlUrl.asString()); + CURLcode ret = curl_easy_setopt( _curl, CURLOPT_URL, + urlBuffer.c_str() ); + if ( ret != 0 ) { + ZYPP_THROW(MediaCurlSetOptException(url, _curlError)); + } + + ret = curl_easy_setopt( _curl, CURLOPT_WRITEDATA, file ); + if ( ret != 0 ) { + ZYPP_THROW(MediaCurlSetOptException(url, _curlError)); + } + + // Set callback and perform. + ProgressData progressData(_settings.timeout(), url, &report); + if (!(options & OPTION_NO_REPORT_START)) + report->start(url, dest); + if ( curl_easy_setopt( _curl, CURLOPT_PROGRESSDATA, &progressData ) != 0 ) { + WAR << "Can't set CURLOPT_PROGRESSDATA: " << _curlError << endl;; + } + + ret = curl_easy_perform( _curl ); + + if ( curl_easy_setopt( _curl, CURLOPT_PROGRESSDATA, NULL ) != 0 ) { + WAR << "Can't unset CURLOPT_PROGRESSDATA: " << _curlError << endl;; + } + + if ( ret != 0 ) + { + ERR << "curl error: " << ret << ": " << _curlError + << ", temp file size " << ftell(file) + << " bytes." << endl; + + // the timeout is determined by the progress data object + // which holds wheter the timeout was reached or not, + // otherwise it would be a user cancel + try { + evaluateCurlCode( filename, ret, progressData.reached); + } + catch ( const MediaException &e ) { + // some error, we are not sure about file existence, rethrw + ZYPP_RETHROW(e); + } + } + +#if DETECT_DIR_INDEX + if (!ret && detectDirIndex()) + { + ZYPP_THROW(MediaNotAFileException(_url, filename)); + } +#endif // DETECT_DIR_INDEX +} + +/////////////////////////////////////////////////////////////////// + void MediaCurl::getDir( const Pathname & dirname, bool recurse_r ) const { filesystem::DirContent content; @@ -1411,7 +1440,7 @@ int MediaCurl::progressCallback( void *clientp, // send progress report first, abort transfer if requested if( pdata->report) { - if (!(*(pdata->report))->progress(int( dlnow * 100 / dltotal ), + if (!(*(pdata->report))->progress(int( dltotal ? dlnow * 100 / dltotal : 0 ), pdata->url, pdata->drate_avg, pdata->drate_period)) @@ -1554,9 +1583,11 @@ bool MediaCurl::authenticate(const string & availAuthTypes, bool firstTry) const if (credentials->authType() == CURLAUTH_NONE) credentials->setAuthType(availAuthTypes); - // set auth type (seems this must be set _after_ setting the userpwd + // set auth type (seems this must be set _after_ setting the userpwd) if (credentials->authType() != CURLAUTH_NONE) { + // FIXME: only overwrite if not empty? + const_cast(this)->_settings.setAuthType(credentials->authTypeAsString()); ret = curl_easy_setopt(_curl, CURLOPT_HTTPAUTH, credentials->authType()); if ( ret != 0 ) ZYPP_THROW(MediaCurlSetOptException(_url, _curlError)); } diff --git a/zypp/media/MediaCurl.h b/zypp/media/MediaCurl.h index a11a012e5..bdd439639 100644 --- a/zypp/media/MediaCurl.h +++ b/zypp/media/MediaCurl.h @@ -40,6 +40,10 @@ class MediaCurl : public MediaHandler OPTION_RANGE = 0x1, /** only issue a HEAD (or equivalent) request */ OPTION_HEAD = 0x02, + /** to not add a IFMODSINCE header if target exists */ + OPTION_NO_IFMODSINCE = 0x04, + /** do not send a start ProgressReport */ + OPTION_NO_REPORT_START = 0x08, }; ZYPP_DECLARE_FLAGS(RequestOptions,RequestOption); @@ -108,16 +112,22 @@ class MediaCurl : public MediaHandler static int progressCallback( void *clientp, double dltotal, double dlnow, double ultotal, double ulnow ); - private: /** - * Return a comma separated list of available authentication methods - * supported by server. - */ - std::string getAuthHint() const; - - bool authenticate(const std::string & availAuthTypes, bool firstTry) const; + * check the url is supported by the curl library + * \throws MediaBadUrlException if there is a problem + **/ + void checkProtocol(const Url &url) const; - private: + /** + * initializes the curl easy handle with the data from the url + * \throws MediaCurlSetOptException if there is a problem + **/ + virtual void setupEasy(); + /** + * concatenate the attach url and the filename to a complete + * download url + **/ + Url getFileUrl(const Pathname & filename) const; /** * Evaluates a curl return code and throws the right MediaException @@ -133,14 +143,29 @@ class MediaCurl : public MediaHandler */ void evaluateCurlCode( const zypp::Pathname &filename, CURLcode code, bool timeout ) const; - CURL *_curl; - char _curlError[ CURL_ERROR_SIZE ]; + void doGetFileCopyFile( const Pathname & srcFilename, const Pathname & dest, FILE *file, callback::SendReport & _report, RequestOptions options = OPTION_NONE ) const; + + private: + /** + * Return a comma separated list of available authentication methods + * supported by server. + */ + std::string getAuthHint() const; + + bool authenticate(const std::string & availAuthTypes, bool firstTry) const; + + bool detectDirIndex() const; + + private: long _curlDebug; - curl_slist *_customHeaders; std::string _currentCookieFile; static Pathname _cookieFile; -protected: + + protected: + CURL *_curl; + char _curlError[ CURL_ERROR_SIZE ]; + curl_slist *_customHeaders; TransferSettings _settings; }; ZYPP_DECLARE_OPERATORS_FOR_FLAGS(MediaCurl::RequestOptions); -- 2.34.1