Imported Upstream version 17.11.1
[platform/upstream/libzypp.git] / zypp / media / MediaCurl.cc
index 6257fe2..8663f35 100644 (file)
@@ -28,6 +28,7 @@
 #include "zypp/thread/Once.h"
 #include "zypp/Target.h"
 #include "zypp/ZYppFactory.h"
+#include "zypp/ZConfig.h"
 
 #include <cstdlib>
 #include <sys/types.h>
 #include <errno.h>
 #include <dirent.h>
 #include <unistd.h>
-#include <boost/format.hpp>
 
 #define  DETECT_DIR_INDEX       0
 #define  CONNECT_TIMEOUT        60
-#define  TRANSFER_TIMEOUT       60 * 3
 #define  TRANSFER_TIMEOUT_MAX   60 * 60
 
 #define EXPLICITLY_NO_PROXY "_none_"
@@ -115,13 +114,11 @@ namespace
     return 0;
   }
 
-  static size_t
-  log_redirects_curl(
-      void *ptr, size_t size, size_t nmemb, void *stream)
+  static size_t log_redirects_curl( char *ptr, size_t size, size_t nmemb, void *userdata)
   {
-    // INT << "got header: " << string((char *)ptr, ((char*)ptr) + size*nmemb) << endl;
+    // INT << "got header: " << string(ptr, ptr + size*nmemb) << endl;
 
-    char * lstart = (char *)ptr, * lend = (char *)ptr;
+    char * lstart = ptr, * lend = ptr;
     size_t pos = 0;
     size_t max = size * nmemb;
     while (pos + 1 < max)
@@ -130,10 +127,21 @@ namespace
       for (lstart = lend; *lend != '\n' && pos < max; ++lend, ++pos);
 
       // look for "Location"
-      string line(lstart, lend);
-      if (line.find("Location") != string::npos)
+      if ( lstart[0] == 'L'
+       && lstart[1] == 'o'
+       && lstart[2] == 'c'
+       && lstart[3] == 'a'
+       && lstart[4] == 't'
+       && lstart[5] == 'i'
+       && lstart[6] == 'o'
+       && lstart[7] == 'n'
+       && lstart[8] == ':' )
       {
+       std::string line { lstart, *(lend-1)=='\r' ? lend-1 : lend };
         DBG << "redirecting to " << line << endl;
+       if ( userdata ) {
+         *reinterpret_cast<std::string *>( userdata ) = line;
+       }
         return max;
       }
 
@@ -152,30 +160,133 @@ namespace
 }
 
 namespace zypp {
+
+  ///////////////////////////////////////////////////////////////////
+  namespace env
+  {
+    namespace
+    {
+      inline int getZYPP_MEDIA_CURL_IPRESOLVE()
+      {
+       int ret = 0;
+       if ( const char * envp = getenv( "ZYPP_MEDIA_CURL_IPRESOLVE" ) )
+       {
+         WAR << "env set: $ZYPP_MEDIA_CURL_IPRESOLVE='" << envp << "'" << endl;
+         if (      strcmp( envp, "4" ) == 0 )  ret = 4;
+         else if ( strcmp( envp, "6" ) == 0 )  ret = 6;
+       }
+       return ret;
+      }
+    }
+
+    inline int ZYPP_MEDIA_CURL_IPRESOLVE()
+    {
+      static int _v = getZYPP_MEDIA_CURL_IPRESOLVE();
+      return _v;
+    }
+  } // namespace env
+  ///////////////////////////////////////////////////////////////////
+
   namespace media {
 
   namespace {
     struct ProgressData
     {
-      ProgressData(CURL *_curl, const long _timeout, const zypp::Url &_url = zypp::Url(),
-                   callback::SendReport<DownloadProgressReport> *_report=NULL)
-        : curl(_curl)
-        , timeout(_timeout)
-        , reached(false)
-        , report(_report)
-        , drate_period(-1)
-        , dload_period(0)
-        , secs(0)
-        , drate_avg(-1)
-        , ltime( time(NULL))
-        , dload( 0)
-        , uload( 0)
-        , url(_url)
+      ProgressData( CURL *_curl, time_t _timeout = 0, const Url & _url = Url(),
+                    ByteCount expectedFileSize_r = 0,
+                   callback::SendReport<DownloadProgressReport> *_report = nullptr )
+        : curl( _curl )
+       , url( _url )
+       , timeout( _timeout )
+        , reached( false )
+        , fileSizeExceeded ( false )
+        , report( _report )
+        , _expectedFileSize( expectedFileSize_r )
       {}
-      CURL                                         *curl;
-      long                                          timeout;
-      bool                                          reached;
+
+      CURL     *curl;
+      Url      url;
+      time_t   timeout;
+      bool     reached;
+      bool      fileSizeExceeded;
       callback::SendReport<DownloadProgressReport> *report;
+      ByteCount _expectedFileSize;
+
+      time_t _timeStart        = 0;    ///< Start total stats
+      time_t _timeLast = 0;    ///< Start last period(~1sec)
+      time_t _timeRcv  = 0;    ///< Start of no-data timeout
+      time_t _timeNow  = 0;    ///< Now
+
+      double _dnlTotal = 0.0;  ///< Bytes to download or 0 if unknown
+      double _dnlLast  = 0.0;  ///< Bytes downloaded at period start
+      double _dnlNow   = 0.0;  ///< Bytes downloaded now
+
+      int    _dnlPercent= 0;   ///< Percent completed or 0 if _dnlTotal is unknown
+
+      double _drateTotal= 0.0; ///< Download rate so far
+      double _drateLast        = 0.0;  ///< Download rate in last period
+
+      void updateStats( double dltotal = 0.0, double dlnow = 0.0 )
+      {
+       time_t now = _timeNow = time(0);
+
+       // If called without args (0.0), recompute based on the last values seen
+       if ( dltotal && dltotal != _dnlTotal )
+         _dnlTotal = dltotal;
+
+       if ( dlnow && dlnow != _dnlNow )
+       {
+         _timeRcv = now;
+         _dnlNow = dlnow;
+       }
+       else if ( !_dnlNow && !_dnlTotal )
+       {
+         // Start time counting as soon as first data arrives.
+         // Skip the connection / redirection time at begin.
+         return;
+       }
+
+       // init or reset if time jumps back
+       if ( !_timeStart || _timeStart > now )
+         _timeStart = _timeLast = _timeRcv = now;
+
+       // timeout condition
+       if ( timeout )
+         reached = ( (now - _timeRcv) > timeout );
+
+        // check if the downloaded data is already bigger than what we expected
+       fileSizeExceeded = _expectedFileSize > 0 && _expectedFileSize < static_cast<ByteCount::SizeType>(_dnlNow);
+
+       // percentage:
+       if ( _dnlTotal )
+         _dnlPercent = int(_dnlNow * 100 / _dnlTotal);
+
+       // download rates:
+       _drateTotal = _dnlNow / std::max( int(now - _timeStart), 1 );
+
+       if ( _timeLast < now )
+       {
+         _drateLast = (_dnlNow - _dnlLast) / int(now - _timeLast);
+         // start new period
+         _timeLast  = now;
+         _dnlLast   = _dnlNow;
+       }
+       else if ( _timeStart == _timeLast )
+         _drateLast = _drateTotal;
+      }
+
+      int reportProgress() const
+      {
+        if ( fileSizeExceeded )
+          return 1;
+       if ( reached )
+         return 1;     // no-data timeout
+       if ( report && !(*report)->progress( _dnlPercent, url, _drateTotal, _drateLast ) )
+         return 1;     // user requested abort
+       return 0;
+      }
+
+
       // download rate of the last period (cca 1 sec)
       double                                        drate_period;
       // bytes downloaded at the start of the last period
@@ -190,7 +301,6 @@ namespace zypp {
       double                                        dload;
       // bytes uploaded at the moment the progress was last reported
       double                                        uload;
-      zypp::Url                                     url;
     };
 
     ///////////////////////////////////////////////////////////////////
@@ -287,6 +397,23 @@ void fillSettingsFromUrl( const Url &url, TransferSettings &s )
             s.setCertificateAuthoritiesPath(ca_path);
     }
 
+    Pathname client_cert( url.getQueryParam("ssl_clientcert") );
+    if( ! client_cert.empty())
+    {
+        if( !PathInfo(client_cert).isFile() || !client_cert.absolute())
+            ZYPP_THROW(MediaBadUrlException(url, "Invalid ssl_clientcert file"));
+        else
+            s.setClientCertificatePath(client_cert);
+    }
+    Pathname client_key( url.getQueryParam("ssl_clientkey") );
+    if( ! client_key.empty())
+    {
+        if( !PathInfo(client_key).isFile() || !client_key.absolute())
+            ZYPP_THROW(MediaBadUrlException(url, "Invalid ssl_clientkey file"));
+        else
+            s.setClientKeyPath(client_key);
+    }
+
     param = url.getQueryParam( "proxy" );
     if ( ! param.empty() )
     {
@@ -486,11 +613,14 @@ Url MediaCurl::clearQueryString(const Url &url) const
   curlUrl.delQueryParam("proxypass");
   curlUrl.delQueryParam("ssl_capath");
   curlUrl.delQueryParam("ssl_verify");
+  curlUrl.delQueryParam("ssl_clientcert");
   curlUrl.delQueryParam("timeout");
   curlUrl.delQueryParam("auth");
   curlUrl.delQueryParam("username");
   curlUrl.delQueryParam("password");
   curlUrl.delQueryParam("mediahandler");
+  curlUrl.delQueryParam("credentials");
+  curlUrl.delQueryParam("head_requests");
   return curlUrl;
 }
 
@@ -546,6 +676,7 @@ void MediaCurl::setupEasy()
   }
 
   curl_easy_setopt(_curl, CURLOPT_HEADERFUNCTION, log_redirects_curl);
+  curl_easy_setopt(_curl, CURLOPT_HEADERDATA, &_lastRedirect);
   CURLcode ret = curl_easy_setopt( _curl, CURLOPT_ERRORBUFFER, _curlError );
   if ( ret != 0 ) {
     ZYPP_THROW(MediaCurlSetOptException(_url, "Error setting error buffer"));
@@ -558,12 +689,15 @@ void MediaCurl::setupEasy()
   // so that we don't add headers twice
   TransferSettings vol_settings(_settings);
 
-  // add custom headers
-  vol_settings.addHeader(anonymousIdHeader());
-  vol_settings.addHeader(distributionFlavorHeader());
+  // add custom headers for download.opensuse.org (bsc#955801)
+  if ( _url.getHost() == "download.opensuse.org" )
+  {
+    vol_settings.addHeader(anonymousIdHeader());
+    vol_settings.addHeader(distributionFlavorHeader());
+  }
   vol_settings.addHeader("Pragma:");
 
-  _settings.setTimeout(TRANSFER_TIMEOUT);
+  _settings.setTimeout(ZConfig::instance().download_transfer_timeout());
   _settings.setConnectTimeout(CONNECT_TIMEOUT);
 
   _settings.setUserAgentString(agentString());
@@ -585,10 +719,27 @@ void MediaCurl::setupEasy()
       fillSettingsSystemProxy(_url, _settings);
   }
 
+  /** Force IPv4/v6 */
+  if ( env::ZYPP_MEDIA_CURL_IPRESOLVE() )
+  {
+    switch ( env::ZYPP_MEDIA_CURL_IPRESOLVE() )
+    {
+      case 4: SET_OPTION(CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); break;
+      case 6: SET_OPTION(CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6); break;
+    }
+  }
+
  /**
   * Connect timeout
   */
   SET_OPTION(CURLOPT_CONNECTTIMEOUT, _settings.connectTimeout());
+  // If a transfer timeout is set, also set CURLOPT_TIMEOUT to an upper limit
+  // just in case curl does not trigger its progress callback frequently
+  // enough.
+  if ( _settings.timeout() )
+  {
+    SET_OPTION(CURLOPT_TIMEOUT, 3600L);
+  }
 
   // follow any Location: header that the server sends as part of
   // an HTTP header (#113275)
@@ -609,6 +760,15 @@ void MediaCurl::setupEasy()
       SET_OPTION(CURLOPT_CAPATH, _settings.certificateAuthoritiesPath().c_str());
     }
 
+    if( ! _settings.clientCertificatePath().empty() )
+    {
+      SET_OPTION(CURLOPT_SSLCERT, _settings.clientCertificatePath().c_str());
+    }
+    if( ! _settings.clientKeyPath().empty() )
+    {
+      SET_OPTION(CURLOPT_SSLKEY, _settings.clientKeyPath().c_str());
+    }
+
 #ifdef CURLSSLOPT_ALLOW_BEAST
     // see bnc#779177
     ret = curl_easy_setopt( _curl, CURLOPT_SSL_OPTIONS, CURLSSLOPT_ALLOW_BEAST );
@@ -619,6 +779,8 @@ void MediaCurl::setupEasy()
 #endif
     SET_OPTION(CURLOPT_SSL_VERIFYPEER, _settings.verifyPeerEnabled() ? 1L : 0L);
     SET_OPTION(CURLOPT_SSL_VERIFYHOST, _settings.verifyHostEnabled() ? 2L : 0L);
+    // bnc#903405 - POODLE: libzypp should only talk TLS
+    SET_OPTION(CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1);
   }
 
   SET_OPTION(CURLOPT_USERAGENT, _settings.userAgentString().c_str() );
@@ -692,8 +854,8 @@ void MediaCurl::setupEasy()
 #endif
   else
   {
-    // libcurl may look into the enviroanment
     DBG << "Proxy: not explicitly set" << endl;
+    DBG << "Proxy: libcurl may look into the environment" << endl;
   }
 
   /** Speed limits */
@@ -701,7 +863,7 @@ void MediaCurl::setupEasy()
   {
       SET_OPTION(CURLOPT_LOW_SPEED_LIMIT, _settings.minDownloadSpeed());
       // default to 10 seconds at low speed
-      SET_OPTION(CURLOPT_LOW_SPEED_TIME, 10L);
+      SET_OPTION(CURLOPT_LOW_SPEED_TIME, 60L);
   }
 
 #if CURLVERSION_AT_LEAST(7,15,5)
@@ -752,14 +914,9 @@ void MediaCurl::attachTo (bool next)
     ZYPP_THROW(MediaBadUrlException(_url));
 
   checkProtocol(_url);
-  if( !isUseableAttachPoint(attachPoint()))
+  if( !isUseableAttachPoint( attachPoint() ) )
   {
-    std::string mountpoint = createAttachPoint().asString();
-
-    if( mountpoint.empty())
-      ZYPP_THROW( MediaBadAttachPointException(url()));
-
-    setAttachPoint( mountpoint, true);
+    setAttachPoint( createAttachPoint(), true );
   }
 
   disconnectFrom(); // clean _curl if needed
@@ -812,46 +969,32 @@ void MediaCurl::releaseFrom( const std::string & ejectDev )
   disconnect();
 }
 
-Url MediaCurl::getFileUrl(const Pathname & filename) const
+Url MediaCurl::getFileUrl( const Pathname & filename_r ) const
 {
-  Url newurl(_url);
-  string path = _url.getPathName();
-  if ( !path.empty() && path != "/" && *path.rbegin() == '/' &&
-       filename.absolute() )
-  {
-    // If url has a path with trailing slash, remove the leading slash from
-    // the absolute file name
-    path += filename.asString().substr( 1, filename.asString().size() - 1 );
-  }
-  else if ( filename.relative() )
-  {
-    // Add trailing slash to path, if not already there
-    if (path.empty()) path = "/";
-    else if (*path.rbegin() != '/' ) path += "/";
-    // Remove "./" from begin of relative file name
-    path += filename.asString().substr( 2, filename.asString().size() - 2 );
-  }
-  else
-  {
-    path += filename.asString();
-  }
-
-  newurl.setPathName(path);
+  // Simply extend the URLs pathname. An 'absolute' URL path
+  // is achieved by encoding the leading '/' in an URL path:
+  //   URL: ftp://user@server          -> ~user
+  //   URL: ftp://user@server/         -> ~user
+  //   URL: ftp://user@server//                -> ~user
+  //   URL: ftp://user@server/%2F      -> /
+  //                         ^- this '/' is just a separator
+  Url newurl( _url );
+  newurl.setPathName( ( Pathname("./"+_url.getPathName()) / filename_r ).asString().substr(1) );
   return newurl;
 }
 
 ///////////////////////////////////////////////////////////////////
 
-void MediaCurl::getFile( const Pathname & filename ) const
+void MediaCurl::getFile(const Pathname & filename , const ByteCount &expectedFileSize_r) const
 {
     // Use absolute file name to prevent access of files outside of the
     // hierarchy below the attach point.
-    getFileCopy(filename, localPath(filename).absolutename());
+    getFileCopy(filename, localPath(filename).absolutename(), expectedFileSize_r);
 }
 
 ///////////////////////////////////////////////////////////////////
 
-void MediaCurl::getFileCopy( const Pathname & filename , const Pathname & target) const
+void MediaCurl::getFileCopy( const Pathname & filename , const Pathname & target, const ByteCount &expectedFileSize_r ) const
 {
   callback::SendReport<DownloadProgressReport> report;
 
@@ -863,7 +1006,7 @@ void MediaCurl::getFileCopy( const Pathname & filename , const Pathname & target
   {
     try
     {
-      doGetFileCopy(filename, target, report);
+      doGetFileCopy(filename, target, report, expectedFileSize_r);
       retry = false;
     }
     // retry with proper authentication data
@@ -880,8 +1023,13 @@ void MediaCurl::getFileCopy( const Pathname & filename , const Pathname & target
     // unexpected exception
     catch (MediaException & excpt_r)
     {
-      // FIXME: error number fix
-      report->finish(fileurl, zypp::media::DownloadProgressReport::ERROR, excpt_r.asUserHistory());
+      media::DownloadProgressReport::Error reason = media::DownloadProgressReport::ERROR;
+      if( typeid(excpt_r) == typeid( media::MediaFileNotFoundException )  ||
+         typeid(excpt_r) == typeid( media::MediaNotAFileException ) )
+      {
+       reason = media::DownloadProgressReport::NOT_FOUND;
+      }
+      report->finish(fileurl, reason, excpt_r.asUserHistory());
       ZYPP_RETHROW(excpt_r);
     }
   }
@@ -923,9 +1071,9 @@ bool MediaCurl::getDoesFileExist( const Pathname & filename ) const
 
 ///////////////////////////////////////////////////////////////////
 
-void MediaCurl::evaluateCurlCode( const Pathname &filename,
+void MediaCurl::evaluateCurlCode(const Pathname &filename,
                                   CURLcode code,
-                                  bool timeout_reached ) const
+                                  bool timeout_reached) const
 {
   if ( code != 0 )
   {
@@ -934,12 +1082,20 @@ void MediaCurl::evaluateCurlCode( const Pathname &filename,
       url = _url;
     else
       url = getFileUrl(filename);
+
     std::string err;
-    try
     {
       switch ( code )
       {
       case CURLE_UNSUPPORTED_PROTOCOL:
+         err = " Unsupported protocol";
+         if ( !_lastRedirect.empty() )
+         {
+           err += " or redirect (";
+           err += _lastRedirect;
+           err += ")";
+         }
+         break;
       case CURLE_URL_MALFORMAT:
       case CURLE_URL_MALFORMAT_USER:
           err = " Bad URL";
@@ -971,6 +1127,7 @@ void MediaCurl::evaluateCurlCode( const Pathname &filename,
                            ));
           }
 
+          case 502: // bad gateway (bnc #1070851)
           case 503: // service temporarily unavailable (bnc #462545)
             ZYPP_THROW(MediaTemporaryProblemException(url));
           case 504: // gateway timeout
@@ -983,6 +1140,7 @@ void MediaCurl::evaluateCurlCode( const Pathname &filename,
             ZYPP_THROW(MediaForbiddenException(url, msg403));
           }
           case 404:
+          case 410:
               ZYPP_THROW(MediaFileNotFoundException(_url, filename));
           }
 
@@ -1002,6 +1160,7 @@ void MediaCurl::evaluateCurlCode( const Pathname &filename,
       case CURLE_REMOTE_FILE_NOT_FOUND:
 #endif
       case CURLE_FTP_ACCESS_DENIED:
+      case CURLE_TFTP_NOTFOUND:
         err = "File not found";
         ZYPP_THROW(MediaFileNotFoundException(_url, filename));
         break;
@@ -1035,17 +1194,13 @@ void MediaCurl::evaluateCurlCode( const Pathname &filename,
         break;
       case CURLE_SSL_PEER_CERTIFICATE:
       default:
-        err = "Unrecognized error";
+        err = "Curl error " + str::numstring( code );
         break;
       }
 
       // uhm, no 0 code but unknown curl exception
       ZYPP_THROW(MediaCurlException(url, err, _curlError));
     }
-    catch (const MediaException & excpt_r)
-    {
-      ZYPP_RETHROW(excpt_r);
-    }
   }
   else
   {
@@ -1082,6 +1237,7 @@ bool MediaCurl::doGetDoesFileExist( const Pathname & filename ) const
     // encoded slash as %2f) "ftp://user@host/%2ffoo/bar/file"
     // contains an absolute path.
   //
+  _lastRedirect.clear();
   string urlBuffer( curlUrl.asString());
   CURLcode ret = curl_easy_setopt( _curl, CURLOPT_URL,
                                    urlBuffer.c_str() );
@@ -1245,7 +1401,7 @@ bool MediaCurl::detectDirIndex() const
 
 ///////////////////////////////////////////////////////////////////
 
-void MediaCurl::doGetFileCopy( const Pathname & filename , const Pathname & target, callback::SendReport<DownloadProgressReport> & report, RequestOptions options ) const
+void MediaCurl::doGetFileCopy(const Pathname & filename , const Pathname & target, callback::SendReport<DownloadProgressReport> & report, const ByteCount &expectedFileSize_r, RequestOptions options ) const
 {
     Pathname dest = target.absolutename();
     if( assert_dir( dest.dirname() ) )
@@ -1297,7 +1453,7 @@ void MediaCurl::doGetFileCopy( const Pathname & filename , const Pathname & targ
     }
     try
     {
-      doGetFileCopyFile(filename, dest, file, report, options);
+      doGetFileCopyFile(filename, dest, file, report, expectedFileSize_r, options);
     }
     catch (Exception &e)
     {
@@ -1359,7 +1515,7 @@ void MediaCurl::doGetFileCopy( const Pathname & filename , const Pathname & targ
 
 ///////////////////////////////////////////////////////////////////
 
-void MediaCurl::doGetFileCopyFile( const Pathname & filename , const Pathname & dest, FILE *file, callback::SendReport<DownloadProgressReport> & report, RequestOptions options ) const
+void MediaCurl::doGetFileCopyFile(const Pathname & filename , const Pathname & dest, FILE *file, callback::SendReport<DownloadProgressReport> & report, const ByteCount &expectedFileSize_r, RequestOptions options ) const
 {
     DBG << filename.asString() << endl;
 
@@ -1386,6 +1542,7 @@ void MediaCurl::doGetFileCopyFile( const Pathname & filename , const Pathname &
     // encoded slash as %2f) "ftp://user@host/%2ffoo/bar/file"
     // contains an absolute path.
     //
+    _lastRedirect.clear();
     string urlBuffer( curlUrl.asString());
     CURLcode ret = curl_easy_setopt( _curl, CURLOPT_URL,
                                      urlBuffer.c_str() );
@@ -1399,7 +1556,7 @@ void MediaCurl::doGetFileCopyFile( const Pathname & filename , const Pathname &
     }
 
     // Set callback and perform.
-    ProgressData progressData(_curl, _settings.timeout(), url, &report);
+    ProgressData progressData(_curl, _settings.timeout(), url, expectedFileSize_r, &report);
     if (!(options & OPTION_NO_REPORT_START))
       report->start(url, dest);
     if ( curl_easy_setopt( _curl, CURLOPT_PROGRESSDATA, &progressData ) != 0 ) {
@@ -1440,10 +1597,14 @@ void MediaCurl::doGetFileCopyFile( const Pathname & filename , const Pathname &
           << " bytes." << endl;
 
       // the timeout is determined by the progress data object
-      // which holds wheter the timeout was reached or not,
+      // which holds whether the timeout was reached or not,
       // otherwise it would be a user cancel
       try {
-        evaluateCurlCode( filename, ret, progressData.reached);
+
+        if ( progressData.fileSizeExceeded )
+          ZYPP_THROW(MediaFileSizeExceededException(url, progressData._expectedFileSize));
+
+        evaluateCurlCode( filename, ret, progressData.reached );
       }
       catch ( const MediaException &e ) {
         // some error, we are not sure about file existence, rethrw
@@ -1473,7 +1634,7 @@ void MediaCurl::getDir( const Pathname & dirname, bool recurse_r ) const
       switch ( it->type ) {
       case filesystem::FT_NOT_AVAIL: // old directory.yast contains no typeinfo at all
       case filesystem::FT_FILE:
-        getFile( filename );
+        getFile( filename, 0 );
         break;
       case filesystem::FT_DIR: // newer directory.yast contain at least directory info
         if ( recurse_r ) {
@@ -1509,97 +1670,33 @@ void MediaCurl::getDirInfo( filesystem::DirContent & retlist,
 }
 
 ///////////////////////////////////////////////////////////////////
+//
+int MediaCurl::aliveCallback( void *clientp, double /*dltotal*/, double dlnow, double /*ultotal*/, double /*ulnow*/ )
+{
+  ProgressData *pdata = reinterpret_cast<ProgressData *>( clientp );
+  if( pdata )
+  {
+    // Do not propagate dltotal in alive callbacks. MultiCurl uses this to
+    // prevent a percentage raise while downloading a metalink file. Download
+    // activity however is indicated by propagating the download rate (via dlnow).
+    pdata->updateStats( 0.0, dlnow );
+    return pdata->reportProgress();
+  }
+  return 0;
+}
 
-int MediaCurl::progressCallback( void *clientp,
-                                 double dltotal, double dlnow,
-                                 double ultotal, double ulnow)
+int MediaCurl::progressCallback( void *clientp, double dltotal, double dlnow, double ultotal, double ulnow )
 {
-  ProgressData *pdata = reinterpret_cast<ProgressData *>(clientp);
-  if( pdata)
+  ProgressData *pdata = reinterpret_cast<ProgressData *>( clientp );
+  if( pdata )
   {
     // work around curl bug that gives us old data
-    long httpReturnCode = 0; 
-    if (curl_easy_getinfo(pdata->curl, CURLINFO_RESPONSE_CODE, &httpReturnCode) != CURLE_OK || httpReturnCode == 0)
-      return 0;
-
-    time_t now   = time(NULL);
-    if( now > 0)
-    {
-       // reset time of last change in case initial time()
-       // failed or the time was adjusted (goes backward)
-       if( pdata->ltime <= 0 || pdata->ltime > now)
-       {
-         pdata->ltime = now;
-       }
-
-       // start time counting as soon as first data arrives
-       // (skip the connection / redirection time at begin)
-       time_t dif = 0;
-       if (dlnow > 0 || ulnow > 0)
-       {
-         dif = (now - pdata->ltime);
-         dif = dif > 0 ? dif : 0;
-
-         pdata->secs += dif;
-       }
-
-       // update the drate_avg and drate_period only after a second has passed
-       // (this callback is called much more often than a second)
-       // otherwise the values would be far from accurate when measuring
-       // the time in seconds
-       //! \todo more accurate download rate computationn, e.g. compute average value from last 5 seconds, or work with milliseconds instead of seconds
-
-        if ( pdata->secs > 1 && (dif > 0 || dlnow == dltotal ))
-          pdata->drate_avg = (dlnow / pdata->secs);
-
-       if ( dif > 0 )
-       {
-         pdata->drate_period = ((dlnow - pdata->dload_period) / dif);
-         pdata->dload_period = dlnow;
-       }
-    }
-
-    // send progress report first, abort transfer if requested
-    if( pdata->report)
-    {
-      if (!(*(pdata->report))->progress(int( dltotal ? dlnow * 100 / dltotal : 0 ),
-                                       pdata->url,
-                                       pdata->drate_avg,
-                                       pdata->drate_period))
-      {
-        return 1; // abort transfer
-      }
-    }
-
-    // check if we there is a timeout set
-    if( pdata->timeout > 0)
-    {
-      if( now > 0)
-      {
-        bool progress = false;
-
-        // update download data if changed, mark progress
-        if( dlnow != pdata->dload)
-        {
-          progress     = true;
-          pdata->dload = dlnow;
-          pdata->ltime = now;
-        }
-        // update upload data if changed, mark progress
-        if( ulnow != pdata->uload)
-        {
-          progress     = true;
-          pdata->uload = ulnow;
-          pdata->ltime = now;
-        }
+    long httpReturnCode = 0;
+    if ( curl_easy_getinfo( pdata->curl, CURLINFO_RESPONSE_CODE, &httpReturnCode ) != CURLE_OK || httpReturnCode == 0 )
+      return aliveCallback( clientp, dltotal, dlnow, ultotal, ulnow );
 
-        if( !progress && (now >= (pdata->ltime + pdata->timeout)))
-        {
-          pdata->reached = true;
-          return 1; // aborts transfer
-        }
-      }
-    }
+    pdata->updateStats( dltotal, dlnow );
+    return pdata->reportProgress();
   }
   return 0;
 }
@@ -1627,13 +1724,24 @@ string MediaCurl::getAuthHint() const
   return "";
 }
 
+/**
+ * MediaMultiCurl needs to reset the expected filesize in case a metalink file is downloaded
+ * otherwise this function should not be called
+ */
+void MediaCurl::resetExpectedFileSize(void *clientp, const ByteCount &expectedFileSize)
+{
+  ProgressData *data = reinterpret_cast<ProgressData *>(clientp);
+  if ( data ) {
+    data->_expectedFileSize = expectedFileSize;
+  }
+}
+
 ///////////////////////////////////////////////////////////////////
 
 bool MediaCurl::authenticate(const string & availAuthTypes, bool firstTry) const
 {
   //! \todo need a way to pass different CredManagerOptions here
-  Target_Ptr target = zypp::getZYpp()->getTarget();
-  CredentialManager cm(CredManagerOptions(target ? target->root() : ""));
+  CredentialManager cm(CredManagerOptions(ZConfig::instance().repoManagerRoot()));
   CurlAuthData_Ptr credentials;
 
   // get stored credentials
@@ -1662,9 +1770,7 @@ bool MediaCurl::authenticate(const string & availAuthTypes, bool firstTry) const
     // indicate we have no good credentials from CM
     cmcred.reset();
 
-    string prompt_msg = boost::str(boost::format(
-      //!\todo add comma to the message for the next release
-      _("Authentication required for '%s'")) % _url.asString());
+    string prompt_msg = str::Format(_("Authentication required for '%s'")) % _url.asString();
 
     // set available authentication types from the exception
     // might be needed in prompt