Imported Upstream version 17.23.5
[platform/upstream/libzypp.git] / zypp / zyppng / media / network / request.cc
1 #include <zypp/zyppng/media/network/private/request_p.h>
2 #include <zypp/zyppng/media/network/private/networkrequesterror_p.h>
3 #include <zypp/zyppng/media/network/networkrequestdispatcher.h>
4 #include <zypp/media/CurlHelper.h>
5 #include <zypp/media/CurlConfig.h>
6 #include <zypp/media/MediaUserAuth.h>
7 #include <zypp/ZConfig.h>
8 #include <zypp/base/Logger.h>
9 #include <zypp/base/String.h>
10 #include <zypp/Pathname.h>
11 #include <stdio.h>
12 #include <fcntl.h>
13 #include <sstream>
14
15
16 namespace zyppng {
17
18   std::vector<char> peek_data_fd( FILE *fd, off_t offset, size_t count )
19   {
20     if ( !fd )
21       return {};
22
23     fflush( fd );
24
25     std::vector<char> data( count + 1 , '\0' );
26
27     ssize_t l = -1;
28     while ((l = pread( fileno( fd ), data.data(), count, offset ) ) == -1 && errno == EINTR)
29       ;
30     if (l == -1)
31       return {};
32
33     return data;
34   }
35
36   NetworkRequestPrivate::NetworkRequestPrivate(Url &&url, zypp::Pathname &&targetFile, off_t &&start, off_t &&len, NetworkRequest::FileMode fMode )
37     : _url ( std::move(url) )
38     , _targetFile ( std::move( targetFile) )
39     , _start ( std::move(start) )
40     , _len ( std::move(len) )
41     , _fMode ( std::move(fMode) )
42     , _activityTimer ( Timer::create() )
43     , _headers( std::unique_ptr< curl_slist, decltype (&curl_slist_free_all) >( nullptr, &curl_slist_free_all ) )
44   {
45     _activityTimer->sigExpired().connect( sigc::mem_fun( this, &NetworkRequestPrivate::onActivityTimeout ));
46   }
47
48   NetworkRequestPrivate::~NetworkRequestPrivate()
49   {
50     if ( _easyHandle ) {
51       //clean up for now, later we might reuse handles
52       curl_easy_cleanup( _easyHandle );
53       //reset in request but make sure the request was not enqueued again and got a new handle
54       _easyHandle = nullptr;
55     }
56   }
57
58   bool NetworkRequestPrivate::initialize( std::string &errBuf )
59   {
60     reset();
61
62     if ( _easyHandle )
63     //will reset to defaults but keep live connections, session ID and DNS caches
64       curl_easy_reset( _easyHandle );
65     else
66       _easyHandle = curl_easy_init();
67
68     _errorBuf.fill( '\0' );
69     curl_easy_setopt( _easyHandle, CURLOPT_ERRORBUFFER, this->_errorBuf.data() );
70
71     try {
72
73       setCurlOption( CURLOPT_PRIVATE, this );
74       setCurlOption( CURLOPT_XFERINFOFUNCTION, NetworkRequestPrivate::curlProgressCallback );
75       setCurlOption( CURLOPT_XFERINFODATA, this  );
76       setCurlOption( CURLOPT_NOPROGRESS, 0L);
77       setCurlOption( CURLOPT_FAILONERROR, 1L);
78       setCurlOption( CURLOPT_NOSIGNAL, 1L);
79
80       std::string urlBuffer( _url.asString() );
81       setCurlOption( CURLOPT_URL, urlBuffer.c_str() );
82
83       setCurlOption( CURLOPT_WRITEFUNCTION, NetworkRequestPrivate::writeCallback );
84       setCurlOption( CURLOPT_WRITEDATA, this );
85
86       if ( _options & NetworkRequest::HeadRequest ) {
87         // instead of returning no data with NOBODY, we return
88         // little data, that works with broken servers, and
89         // works for ftp as well, because retrieving only headers
90         // ftp will return always OK code ?
91         // See http://curl.haxx.se/docs/knownbugs.html #58
92         if (  (_url.getScheme() == "http" ||  _url.getScheme() == "https") && _settings.headRequestsAllowed() )
93           setCurlOption( CURLOPT_NOBODY, 1L );
94         else
95           setCurlOption( CURLOPT_RANGE, "0-1" );
96       } else {
97         std::string rangeDesc;
98         if ( _start >= 0) {
99           _expectRangeStatus = true;
100           rangeDesc = zypp::str::form("%llu-", static_cast<unsigned long long>( _start ));
101           if( _len > 0 ) {
102             rangeDesc.append( zypp::str::form( "%llu", static_cast<unsigned long long>(_start + _len - 1) ) );
103           }
104           if ( setCurlOption( CURLOPT_RANGE, rangeDesc.c_str() ), CURLE_OK ) {
105             strncpy( _errorBuf.data(), "curl_easy_setopt range failed", CURL_ERROR_SIZE);
106             return false;
107           }
108         } else {
109           _expectRangeStatus = false;
110         }
111
112       }
113
114       //make a local copy of the settings, so headers are not added multiple times
115       TransferSettings locSet = _settings;
116
117       // add custom headers for download.opensuse.org (bsc#955801)
118       if ( _url.getHost() == "download.opensuse.org" )
119       {
120         locSet.addHeader( internal::anonymousIdHeader() );
121         locSet.addHeader( internal::distributionFlavorHeader() );
122       }
123
124       locSet.addHeader("Pragma:");
125
126       locSet.setTimeout( zypp::ZConfig::instance().download_transfer_timeout() );
127       locSet.setConnectTimeout( CONNECT_TIMEOUT );
128
129       locSet.setUserAgentString( internal::agentString() );
130
131       {
132         char *ptr = getenv("ZYPP_MEDIA_CURL_DEBUG");
133         _curlDebug = (ptr && *ptr) ? zypp::str::strtonum<long>( ptr) : 0L;
134         if( _curlDebug > 0)
135         {
136           setCurlOption( CURLOPT_VERBOSE, 1L);
137           setCurlOption( CURLOPT_DEBUGFUNCTION, internal::log_curl);
138           setCurlOption( CURLOPT_DEBUGDATA, &_curlDebug);
139         }
140       }
141
142       /** Force IPv4/v6 */
143       switch ( internal::env::ZYPP_MEDIA_CURL_IPRESOLVE() )
144       {
145         case 4: setCurlOption( CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4 ); break;
146         case 6: setCurlOption( CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6 ); break;
147         default: break;
148       }
149
150       setCurlOption( CURLOPT_HEADERFUNCTION, internal::log_redirects_curl );
151       setCurlOption( CURLOPT_HEADERDATA, &_lastRedirect );
152
153       /**
154         * Connect timeout
155         */
156       setCurlOption( CURLOPT_CONNECTTIMEOUT, locSet.connectTimeout() );
157       // If a transfer timeout is set, also set CURLOPT_TIMEOUT to an upper limit
158       // just in case curl does not trigger its progress callback frequently
159       // enough.
160       if ( locSet.timeout() )
161       {
162         setCurlOption( CURLOPT_TIMEOUT, 3600L );
163       }
164
165       if ( _url.getScheme() == "https" )
166       {
167 #if CURLVERSION_AT_LEAST(7,19,4)
168         // restrict following of redirections from https to https only
169         setCurlOption( CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTPS );
170 #endif
171
172 #if CURLVERSION_AT_LEAST(7,60,0)        // SLE15+
173         setCurlOption( CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2TLS );
174 #endif
175
176         if( locSet.verifyPeerEnabled() ||
177              locSet.verifyHostEnabled() )
178         {
179           setCurlOption(CURLOPT_CAPATH, locSet.certificateAuthoritiesPath().c_str());
180         }
181
182         if( ! locSet.clientCertificatePath().empty() )
183         {
184           setCurlOption(CURLOPT_SSLCERT, locSet.clientCertificatePath().c_str());
185         }
186         if( ! locSet.clientKeyPath().empty() )
187         {
188           setCurlOption(CURLOPT_SSLKEY, locSet.clientKeyPath().c_str());
189         }
190
191 #ifdef CURLSSLOPT_ALLOW_BEAST
192         // see bnc#779177
193         setCurlOption( CURLOPT_SSL_OPTIONS, CURLSSLOPT_ALLOW_BEAST );
194 #endif
195         setCurlOption(CURLOPT_SSL_VERIFYPEER, locSet.verifyPeerEnabled() ? 1L : 0L);
196         setCurlOption(CURLOPT_SSL_VERIFYHOST, locSet.verifyHostEnabled() ? 2L : 0L);
197         // bnc#903405 - POODLE: libzypp should only talk TLS
198         setCurlOption(CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1);
199       }
200
201       // follow any Location: header that the server sends as part of
202       // an HTTP header (#113275)
203       setCurlOption( CURLOPT_FOLLOWLOCATION, 1L);
204       // 3 redirects seem to be too few in some cases (bnc #465532)
205       setCurlOption( CURLOPT_MAXREDIRS, 6L );
206
207       //set the user agent
208       setCurlOption(CURLOPT_USERAGENT, locSet.userAgentString().c_str() );
209
210
211       /*---------------------------------------------------------------*
212         CURLOPT_USERPWD: [user name]:[password]
213         Url::username/password -> CURLOPT_USERPWD
214         If not provided, anonymous FTP identification
215       *---------------------------------------------------------------*/
216       if ( locSet.userPassword().size() )
217       {
218         setCurlOption(CURLOPT_USERPWD, locSet.userPassword().c_str());
219         std::string use_auth = _settings.authType();
220         if (use_auth.empty())
221           use_auth = "digest,basic";    // our default
222         long auth = zypp::media::CurlAuthData::auth_type_str2long(use_auth);
223         if( auth != CURLAUTH_NONE)
224         {
225           DBG << "Enabling HTTP authentication methods: " << use_auth
226               << " (CURLOPT_HTTPAUTH=" << auth << ")" << std::endl;
227           setCurlOption(CURLOPT_HTTPAUTH, auth);
228         }
229       }
230
231       if ( locSet.proxyEnabled() && ! locSet.proxy().empty() )
232       {
233         DBG << "Proxy: '" << locSet.proxy() << "'" << std::endl;
234         setCurlOption(CURLOPT_PROXY, locSet.proxy().c_str());
235         setCurlOption(CURLOPT_PROXYAUTH, CURLAUTH_BASIC|CURLAUTH_DIGEST|CURLAUTH_NTLM );
236
237         /*---------------------------------------------------------------*
238          *    CURLOPT_PROXYUSERPWD: [user name]:[password]
239          *
240          * Url::option(proxyuser and proxypassword) -> CURLOPT_PROXYUSERPWD
241          *  If not provided, $HOME/.curlrc is evaluated
242          *---------------------------------------------------------------*/
243
244         std::string proxyuserpwd = locSet.proxyUserPassword();
245
246         if ( proxyuserpwd.empty() )
247         {
248           zypp::media::CurlConfig curlconf;
249           zypp::media::CurlConfig::parseConfig(curlconf); // parse ~/.curlrc
250           if ( curlconf.proxyuserpwd.empty() )
251             DBG << "Proxy: ~/.curlrc does not contain the proxy-user option" << std::endl;
252           else
253           {
254             proxyuserpwd = curlconf.proxyuserpwd;
255             DBG << "Proxy: using proxy-user from ~/.curlrc" << std::endl;
256           }
257         }
258         else
259         {
260           DBG << "Proxy: using provided proxy-user '" << _settings.proxyUsername() << "'" << std::endl;
261         }
262
263         if ( ! proxyuserpwd.empty() )
264         {
265           setCurlOption(CURLOPT_PROXYUSERPWD, internal::curlUnEscape( proxyuserpwd ).c_str());
266         }
267       }
268 #if CURLVERSION_AT_LEAST(7,19,4)
269       else if ( locSet.proxy() == EXPLICITLY_NO_PROXY )
270       {
271         // Explicitly disabled in URL (see fillSettingsFromUrl()).
272         // This should also prevent libcurl from looking into the environment.
273         DBG << "Proxy: explicitly NOPROXY" << std::endl;
274         setCurlOption(CURLOPT_NOPROXY, "*");
275       }
276
277 #endif
278       else
279       {
280         DBG << "Proxy: not explicitly set" << std::endl;
281         DBG << "Proxy: libcurl may look into the environment" << std::endl;
282       }
283
284       /** Speed limits */
285       if ( locSet.minDownloadSpeed() != 0 )
286       {
287         setCurlOption(CURLOPT_LOW_SPEED_LIMIT, locSet.minDownloadSpeed());
288         // default to 10 seconds at low speed
289         setCurlOption(CURLOPT_LOW_SPEED_TIME, 60L);
290       }
291
292 #if CURLVERSION_AT_LEAST(7,15,5)
293       if ( locSet.maxDownloadSpeed() != 0 )
294         setCurlOption(CURLOPT_MAX_RECV_SPEED_LARGE, locSet.maxDownloadSpeed());
295 #endif
296
297       if ( zypp::str::strToBool( _url.getQueryParam( "cookies" ), true ) )
298         setCurlOption( CURLOPT_COOKIEFILE, _currentCookieFile.c_str() );
299       else
300         MIL << "No cookies requested" << std::endl;
301       setCurlOption(CURLOPT_COOKIEJAR, _currentCookieFile.c_str() );
302
303 #if CURLVERSION_AT_LEAST(7,18,0)
304       // bnc #306272
305       setCurlOption(CURLOPT_PROXY_TRANSFER_MODE, 1L );
306 #endif
307
308       // append settings custom headers to curl
309       for ( TransferSettings::Headers::const_iterator it = locSet.headersBegin();
310             it != locSet.headersEnd();
311             ++it ) {
312         if ( !z_func()->addRequestHeader( it->c_str() ) )
313           ZYPP_THROW(zypp::media::MediaCurlInitException(_url));
314       }
315
316       if ( _headers )
317         setCurlOption( CURLOPT_HTTPHEADER, _headers.get() );
318
319       return true;
320
321     } catch ( const zypp::Exception &excp ) {
322       ZYPP_CAUGHT(excp);
323       errBuf = excp.asString();
324     }
325     return false;
326   }
327
328   void NetworkRequestPrivate::aboutToStart()
329   {
330     if ( _activityTimer )
331       _activityTimer->start( static_cast<uint64_t>( _settings.timeout() ) * 1000 );
332
333     _state = NetworkRequest::Running;
334     _sigStarted.emit( *z_func() );
335   }
336
337   void NetworkRequestPrivate::setResult( NetworkRequestError &&err )
338   {
339     if ( _outFile )
340       fclose( _outFile );
341     _outFile = nullptr;
342
343     _result = std::move(err);
344
345     if ( _activityTimer )
346       _activityTimer->stop();
347
348     if ( _result.type() == NetworkRequestError::NoError ) {
349       //we have a successful download, lets see if the checksum is fine IF we have one
350       _state = NetworkRequest::Finished;
351       if ( _expectedChecksum.size() && _digest ) {
352         if ( _digest->digestVector() != _expectedChecksum ) {
353           _state = NetworkRequest::Error;
354
355           auto hexToStr = []( const std::vector<u_char> &hex ) {
356             std::string res;
357             for (std::vector<u_char>::size_type j = 0; j < hex.size(); j++)
358               res += zypp::str::form("%02hhx", hex[j]);
359             return res;
360           };
361
362           _result = NetworkRequestErrorPrivate::customError( NetworkRequestError::InvalidChecksum, (zypp::str::Format("Invalid checksum %1%, expected checksum %2%") % _digest->digest() % hexToStr( _expectedChecksum ) ) );
363         }
364       }
365     } else
366       _state = NetworkRequest::Error;
367
368     _sigFinished.emit( *z_func(), _result );
369   }
370
371   void NetworkRequestPrivate::reset()
372   {
373     if ( _outFile )
374       fclose( _outFile );
375
376     if ( _digest )
377       _digest->reset();
378
379     _outFile = nullptr;
380     _easyHandle = nullptr;
381     _result = NetworkRequestError();
382     _state = NetworkRequest::Pending;
383     _downloaded = -1;
384     _reportedSize = 0;
385     _errorBuf.fill( 0 );
386     _headers.reset( nullptr );
387   }
388
389   void NetworkRequestPrivate::onActivityTimeout( Timer & )
390   {
391     std::map<std::string, boost::any> extraInfo;
392     extraInfo.insert( {"requestUrl", _url } );
393     extraInfo.insert( {"filepath", _targetFile } );
394     _dispatcher->cancel( *z_func(), NetworkRequestErrorPrivate::customError( NetworkRequestError::Timeout, "Download timed out", std::move(extraInfo) ) );
395   }
396
397   int NetworkRequestPrivate::curlProgressCallback( void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow )
398   {
399     if ( !clientp )
400       return 0;
401     NetworkRequestPrivate *that = reinterpret_cast<NetworkRequestPrivate *>( clientp );
402
403     //reset the timer
404     if ( that->_activityTimer && that->_activityTimer->isRunning() )
405       that->_activityTimer->start();
406
407     //keep signals to a minimum
408     if ( that->_downloaded == dlnow )
409       return 0;
410
411     that->_downloaded   = dlnow;
412     that->_reportedSize = dltotal;
413
414     if ( that->_len > 0 && that->_len < dlnow ) {
415       that->_dispatcher->cancel( *that->z_func(), NetworkRequestErrorPrivate::customError( NetworkRequestError::ExceededMaxLen ) );
416       return 0;
417     }
418
419     that->_sigProgress.emit( *that->z_func(), dltotal, dlnow, ultotal, ulnow );
420     return 0;
421   }
422
423   size_t NetworkRequestPrivate::writeCallback( char *ptr, size_t size, size_t nmemb, void *userdata )
424   {
425     if ( !userdata )
426       return 0;
427
428     //it is valid to call this function with no data to write, just return OK
429     if ( size * nmemb == 0)
430       return 0;
431
432     NetworkRequestPrivate *that = reinterpret_cast<NetworkRequestPrivate *>( userdata );
433
434     //in case of a HEAD request, we do not write anything
435     if ( that->_options & NetworkRequest::HeadRequest ) {
436       return ( size * nmemb );
437     }
438
439     //If we expect a file range we better double check that we got the status code for it
440     if ( that->_expectRangeStatus ) {
441       char *effurl;
442       (void)curl_easy_getinfo( that->_easyHandle, CURLINFO_EFFECTIVE_URL, &effurl);
443       if (effurl && !strncasecmp(effurl, "http", 4))
444       {
445         long statuscode = 0;
446         (void)curl_easy_getinfo( that->_easyHandle, CURLINFO_RESPONSE_CODE, &statuscode);
447         if (statuscode != 206) {
448           strncpy( that->_errorBuf.data(), "Expected range status code 206, but got none.", CURL_ERROR_SIZE);
449           return 0;
450         }
451       }
452     }
453
454     if ( !that->_outFile ) {
455       std::string openMode = "w+b";
456       if ( that->_fMode == NetworkRequest::WriteShared )
457         openMode = "r+b";
458
459       that->_outFile = fopen( that->_targetFile.asString().c_str() , openMode.c_str() );
460
461       //if the file does not exist create a new one
462       if ( !that->_outFile && that->_fMode == NetworkRequest::WriteShared ) {
463         that->_outFile = fopen( that->_targetFile.asString().c_str() , "w+b" );
464       }
465
466       if ( !that->_outFile ) {
467         strncpy( that->_errorBuf.data(), "Unable to open target file.", CURL_ERROR_SIZE);
468         return 0;
469       }
470
471       if ( that->_start > 0 )
472         if ( fseek( that->_outFile, that->_start, SEEK_SET ) != 0 ) {
473           strncpy( that->_errorBuf.data(), "Unable to set output file pointer.", CURL_ERROR_SIZE);
474           return 0;
475         }
476     }
477
478      size_t written = fwrite( ptr, size, nmemb, that->_outFile );
479      if ( that->_digest ) {
480        that->_digest->update( ptr, written );
481      }
482
483      return written;
484   }
485
486   NetworkRequest::NetworkRequest(zyppng::Url url, zypp::filesystem::Pathname targetFile, off_t start, off_t len, zyppng::NetworkRequest::FileMode fMode)
487     : Base ( *new NetworkRequestPrivate( std::move(url), std::move(targetFile), std::move(start), std::move(len), std::move(fMode) ) )
488   {
489   }
490
491   NetworkRequest::~NetworkRequest()
492   {
493     Z_D();
494
495     if ( d->_dispatcher )
496       d->_dispatcher->cancel( *this, "Request destroyed while still running" );
497
498     if ( d->_outFile )
499       fclose( d->_outFile );
500   }
501
502   void NetworkRequest::setPriority(NetworkRequest::Priority prio)
503   {
504     d_func()->_priority = prio;
505   }
506
507   NetworkRequest::Priority NetworkRequest::priority() const
508   {
509     return d_func()->_priority;
510   }
511
512   void NetworkRequest::setOptions( Options opt )
513   {
514     d_func()->_options = opt;
515   }
516
517   NetworkRequest::Options NetworkRequest::options() const
518   {
519     return d_func()->_options;
520   }
521
522   void NetworkRequest::setRequestRange(off_t start, off_t len)
523   {
524     Z_D();
525     if ( d->_state == Running )
526       return;
527     d->_start = start;
528     d->_len = len;
529   }
530
531   const std::string &NetworkRequest::lastRedirectInfo() const
532   {
533     return d_func()->_lastRedirect;
534   }
535
536   void *NetworkRequest::nativeHandle() const
537   {
538     return d_func()->_easyHandle;
539   }
540
541   std::vector<char> NetworkRequest::peekData( off_t offset, size_t count ) const
542   {
543     Z_D();
544     return peek_data_fd( d->_outFile, offset, count );
545   }
546
547   Url NetworkRequest::url() const
548   {
549     return d_func()->_url;
550   }
551
552   void NetworkRequest::setUrl(const Url &url)
553   {
554     Z_D();
555     if ( d->_state == NetworkRequest::Running )
556       return;
557
558     d->_url = url;
559   }
560
561   const zypp::filesystem::Pathname &NetworkRequest::targetFilePath() const
562   {
563     return d_func()->_targetFile;
564   }
565
566   std::string NetworkRequest::contentType() const
567   {
568     char *ptr = NULL;
569     if ( curl_easy_getinfo( d_func()->_easyHandle, CURLINFO_CONTENT_TYPE, &ptr ) == CURLE_OK && ptr )
570       return std::string(ptr);
571     return std::string();
572   }
573
574   off_t NetworkRequest::downloadOffset() const
575   {
576     return d_func()->_start;
577   }
578
579   off_t NetworkRequest::reportedByteCount() const
580   {
581     return d_func()->_reportedSize;
582   }
583
584   off_t NetworkRequest::expectedByteCount() const
585   {
586     return d_func()->_len;
587   }
588
589   off_t NetworkRequest::downloadedByteCount() const
590   {
591     Z_D();
592     if ( d->_downloaded == -1 )
593       return 0;
594     return d->_downloaded;
595   }
596
597   void NetworkRequest::setDigest( std::shared_ptr<zypp::Digest> dig )
598   {
599     d_func()->_digest = dig;
600   }
601
602   void NetworkRequest::setExpectedChecksum(std::vector<unsigned char> checksum )
603   {
604     d_func()->_expectedChecksum = std::move(checksum);
605   }
606
607   TransferSettings &NetworkRequest::transferSettings()
608   {
609     return d_func()->_settings;
610   }
611
612   std::shared_ptr<zypp::Digest> NetworkRequest::digest( ) const
613   {
614     return d_func()->_digest;
615   }
616
617   NetworkRequest::State NetworkRequest::state() const
618   {
619     return d_func()->_state;
620   }
621
622   const NetworkRequestError &NetworkRequest::error() const
623   {
624     return d_func()->_result;
625   }
626
627   std::string NetworkRequest::extendedErrorString() const
628   {
629     Z_D();
630     if ( !hasError() )
631       return std::string();
632
633     return d->_result.nativeErrorString();
634   }
635
636   bool NetworkRequest::hasError() const
637   {
638     return error().isError();
639   }
640
641   bool NetworkRequest::addRequestHeader( const std::string &header )
642   {
643     Z_D();
644
645     curl_slist *res = curl_slist_append( d->_headers ? d->_headers.get() : nullptr, header.c_str() );
646     if ( !res )
647       return false;
648
649     if ( !d->_headers )
650       d->_headers = std::unique_ptr< curl_slist, decltype (&curl_slist_free_all) >( res, &curl_slist_free_all );
651
652     return true;
653   }
654
655   SignalProxy<void (NetworkRequest &req)> NetworkRequest::sigStarted()
656   {
657     return d_func()->_sigStarted;
658   }
659
660   SignalProxy<void (NetworkRequest &req, off_t dltotal, off_t dlnow, off_t ultotal, off_t ulnow)> NetworkRequest::sigProgress()
661   {
662     return d_func()->_sigProgress;
663   }
664
665   SignalProxy<void (zyppng::NetworkRequest &req, const zyppng::NetworkRequestError &err)> NetworkRequest::sigFinished()
666   {
667     return d_func()->_sigFinished;
668   }
669
670
671 }