2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
12 #include "ServiceBroker.h"
14 #include "filesystem/SpecialProtocol.h"
15 #include "network/DNSNameCache.h"
16 #include "settings/AdvancedSettings.h"
17 #include "settings/Settings.h"
18 #include "settings/SettingsComponent.h"
19 #include "threads/SystemClock.h"
20 #include "utils/Base64.h"
21 #include "utils/XTimeUtils.h"
31 #include "platform/posix/ConvUtils.h"
34 #include "DllLibCurl.h"
35 #include "ShoutcastFile.h"
36 #include "utils/CharsetConverter.h"
37 #include "utils/log.h"
38 #include "utils/StringUtils.h"
40 using namespace XFILE
;
41 using namespace XCURL
;
43 using namespace std::chrono_literals
;
45 #define FITS_INT(a) (((a) <= INT_MAX) && ((a) >= INT_MIN))
47 curl_proxytype proxyType2CUrlProxyType
[] = {
48 CURLPROXY_HTTP
, CURLPROXY_SOCKS4
, CURLPROXY_SOCKS4A
,
49 CURLPROXY_SOCKS5
, CURLPROXY_SOCKS5_HOSTNAME
, CURLPROXY_HTTPS
,
52 #define FILLBUFFER_OK 0
53 #define FILLBUFFER_NO_DATA 1
54 #define FILLBUFFER_FAIL 2
56 // curl calls this routine to debug
57 extern "C" int debug_callback(CURL_HANDLE
*handle
, curl_infotype info
, char *output
, size_t size
, void *data
)
59 if (info
== CURLINFO_DATA_IN
|| info
== CURLINFO_DATA_OUT
)
62 if (!CServiceBroker::GetLogging().CanLogComponent(LOGCURL
))
66 strLine
.append(output
, size
);
67 std::vector
<std::string
> vecLines
;
68 StringUtils::Tokenize(strLine
, vecLines
, "\r\n");
69 std::vector
<std::string
>::const_iterator it
= vecLines
.begin();
74 case CURLINFO_TEXT
: infotype
= "TEXT: "; break;
75 case CURLINFO_HEADER_IN
: infotype
= "HEADER_IN: "; break;
76 case CURLINFO_HEADER_OUT
: infotype
= "HEADER_OUT: "; break;
77 case CURLINFO_SSL_DATA_IN
: infotype
= "SSL_DATA_IN: "; break;
78 case CURLINFO_SSL_DATA_OUT
: infotype
= "SSL_DATA_OUT: "; break;
79 case CURLINFO_END
: infotype
= "END: "; break;
80 default : infotype
= ""; break;
83 while (it
!= vecLines
.end())
85 CLog::Log(LOGDEBUG
, "Curl::Debug - {}{}", infotype
, (*it
));
91 /* curl calls this routine to get more data */
92 extern "C" size_t write_callback(char *buffer
,
97 if(userp
== NULL
) return 0;
99 CCurlFile::CReadState
*state
= (CCurlFile::CReadState
*)userp
;
100 return state
->WriteCallback(buffer
, size
, nitems
);
103 extern "C" size_t read_callback(char *buffer
,
108 if(userp
== NULL
) return 0;
110 CCurlFile::CReadState
*state
= (CCurlFile::CReadState
*)userp
;
111 return state
->ReadCallback(buffer
, size
, nitems
);
114 extern "C" size_t header_callback(void *ptr
, size_t size
, size_t nmemb
, void *stream
)
116 CCurlFile::CReadState
*state
= (CCurlFile::CReadState
*)stream
;
117 return state
->HeaderCallback(ptr
, size
, nmemb
);
120 /* used only by CCurlFile::Stat to bail out of unwanted transfers */
121 extern "C" int transfer_abort_callback(void *clientp
,
133 /* fix for silly behavior of realloc */
134 static inline void* realloc_simple(void *ptr
, size_t size
)
136 void *ptr2
= realloc(ptr
, size
);
137 if(ptr
&& !ptr2
&& size
> 0)
146 static constexpr int CURL_OFF
= 0L;
147 static constexpr int CURL_ON
= 1L;
149 size_t CCurlFile::CReadState::HeaderCallback(void *ptr
, size_t size
, size_t nmemb
)
151 std::string inString
;
152 // libcurl doc says that this info is not always \0 terminated
153 const char* strBuf
= (const char*)ptr
;
154 const size_t iSize
= size
* nmemb
;
155 if (strBuf
[iSize
- 1] == 0)
156 inString
.assign(strBuf
, iSize
- 1); // skip last char if it's zero
158 inString
.append(strBuf
, iSize
);
160 m_httpheader
.Parse(inString
);
165 size_t CCurlFile::CReadState::ReadCallback(char *buffer
, size_t size
, size_t nitems
)
170 if (m_filePos
>= m_fileSize
)
173 return CURL_READFUNC_PAUSE
;
176 int64_t retSize
= std::min(m_fileSize
- m_filePos
, int64_t(nitems
* size
));
177 memcpy(buffer
, m_readBuffer
+ m_filePos
, retSize
);
178 m_filePos
+= retSize
;
183 size_t CCurlFile::CReadState::WriteCallback(char *buffer
, size_t size
, size_t nitems
)
185 unsigned int amount
= size
* nitems
;
188 // we have our overflow buffer - first get rid of as much as we can
189 unsigned int maxWriteable
= std::min(m_buffer
.getMaxWriteSize(), m_overflowSize
);
192 if (!m_buffer
.WriteData(m_overflowBuffer
, maxWriteable
))
194 CLog::Log(LOGERROR
, "CCurlFile::CReadState::{} - ({}) Unable to write to buffer",
195 __FUNCTION__
, fmt::ptr(this));
199 if (maxWriteable
< m_overflowSize
)
201 // still have some more - copy it down
202 memmove(m_overflowBuffer
, m_overflowBuffer
+ maxWriteable
, m_overflowSize
- maxWriteable
);
204 m_overflowSize
-= maxWriteable
;
207 m_overflowBuffer
= (char*)realloc_simple(m_overflowBuffer
, m_overflowSize
);
210 // ok, now copy the data into our ring buffer
211 unsigned int maxWriteable
= std::min(m_buffer
.getMaxWriteSize(), amount
);
214 if (!m_buffer
.WriteData(buffer
, maxWriteable
))
217 "CCurlFile::CReadState::{} - ({}) Unable to write to buffer with {} bytes",
218 __FUNCTION__
, fmt::ptr(this), maxWriteable
);
223 amount
-= maxWriteable
;
224 buffer
+= maxWriteable
;
229 //! @todo Limit max. amount of the overflowbuffer
230 m_overflowBuffer
= (char*)realloc_simple(m_overflowBuffer
, amount
+ m_overflowSize
);
231 if(m_overflowBuffer
== NULL
)
233 CLog::Log(LOGWARNING
,
234 "CCurlFile::CReadState::{} - ({}) Failed to grow overflow buffer from {} bytes to "
236 __FUNCTION__
, fmt::ptr(this), m_overflowSize
, amount
+ m_overflowSize
);
239 memcpy(m_overflowBuffer
+ m_overflowSize
, buffer
, amount
);
240 m_overflowSize
+= amount
;
242 return size
* nitems
;
245 CCurlFile::CReadState::CReadState()
248 m_multiHandle
= NULL
;
249 m_overflowBuffer
= NULL
;
258 m_bLastError
= false;
262 m_curlHeaderList
= NULL
;
263 m_curlAliasList
= NULL
;
266 CCurlFile::CReadState::~CReadState()
271 g_curlInterface
.easy_release(&m_easyHandle
, &m_multiHandle
);
274 bool CCurlFile::CReadState::Seek(int64_t pos
)
279 if(FITS_INT(pos
- m_filePos
) && m_buffer
.SkipBytes((int)(pos
- m_filePos
)))
285 if(pos
> m_filePos
&& pos
< m_filePos
+ m_bufferSize
)
287 int len
= m_buffer
.getMaxReadSize();
289 m_buffer
.SkipBytes(len
);
290 if (FillBuffer(m_bufferSize
) != FILLBUFFER_OK
)
292 if(!m_buffer
.SkipBytes(-len
))
294 "CCurlFile::CReadState::{} - ({}) Failed to restore position after failed fill",
295 __FUNCTION__
, fmt::ptr(this));
301 if(!FITS_INT(pos
- m_filePos
) || !m_buffer
.SkipBytes((int)(pos
- m_filePos
)))
305 "CCurlFile::CReadState::{} - ({}) Failed to skip to position after having filled buffer",
306 __FUNCTION__
, fmt::ptr(this));
307 if(!m_buffer
.SkipBytes(-len
))
309 "CCurlFile::CReadState::{} - ({}) Failed to restore position after failed seek",
310 __FUNCTION__
, fmt::ptr(this));
321 void CCurlFile::CReadState::SetResume(void)
324 * Explicitly set RANGE header when filepos=0 as some http servers require us to always send the range
325 * request header. If we don't the server may provide different content causing seeking to fail.
326 * This only affects HTTP-like items, for FTP it's a null operation.
328 if (m_sendRange
&& m_filePos
== 0)
329 g_curlInterface
.easy_setopt(m_easyHandle
, CURLOPT_RANGE
, "0-");
332 g_curlInterface
.easy_setopt(m_easyHandle
, CURLOPT_RANGE
, NULL
);
336 g_curlInterface
.easy_setopt(m_easyHandle
, CURLOPT_RESUME_FROM_LARGE
, m_filePos
);
339 long CCurlFile::CReadState::Connect(unsigned int size
)
342 CLog::Log(LOGDEBUG
, "CurlFile::CReadState::{} - ({}) Resume from position {}", __FUNCTION__
,
343 fmt::ptr(this), m_filePos
);
346 g_curlInterface
.multi_add_handle(m_multiHandle
, m_easyHandle
);
350 m_buffer
.Create(size
* 3);
351 m_httpheader
.Clear();
353 // read some data in to try and obtain the length
354 // maybe there's a better way to get this info??
357 // (Try to) fill buffer
358 if (FillBuffer(1) != FILLBUFFER_OK
)
360 // Check response code
362 if (CURLE_OK
== g_curlInterface
.easy_getinfo(m_easyHandle
, CURLINFO_RESPONSE_CODE
, &response
))
369 if (CURLE_OK
== g_curlInterface
.easy_getinfo(m_easyHandle
, CURLINFO_CONTENT_LENGTH_DOWNLOAD
, &length
))
373 m_fileSize
= m_filePos
+ (int64_t)length
;
377 if (CURLE_OK
== g_curlInterface
.easy_getinfo(m_easyHandle
, CURLINFO_RESPONSE_CODE
, &response
))
383 void CCurlFile::CReadState::Disconnect()
385 if(m_multiHandle
&& m_easyHandle
)
386 g_curlInterface
.multi_remove_handle(m_multiHandle
, m_easyHandle
);
389 free(m_overflowBuffer
);
390 m_overflowBuffer
= NULL
;
398 if( m_curlHeaderList
)
399 g_curlInterface
.slist_free_all(m_curlHeaderList
);
400 m_curlHeaderList
= NULL
;
402 if( m_curlAliasList
)
403 g_curlInterface
.slist_free_all(m_curlAliasList
);
404 m_curlAliasList
= NULL
;
408 CCurlFile::~CCurlFile()
415 CCurlFile::CCurlFile()
416 : m_overflowBuffer(NULL
)
421 m_multisession
= true;
423 m_connecttimeout
= 0;
427 m_bufferSize
= 32768;
428 m_postdataset
= false;
429 m_state
= new CReadState();
433 m_acceptCharset
= "UTF-8,*;q=0.8"; /* prefer UTF-8 if available */
435 m_acceptencoding
= "all"; /* Accept all supported encoding by default */
438 //Has to be called before Open()
439 void CCurlFile::SetBufferSize(unsigned int size
)
444 void CCurlFile::Close()
446 if (m_opened
&& m_forWrite
&& !m_inError
)
449 m_state
->Disconnect();
462 g_curlInterface
.slist_free_all(m_dnsCacheList
);
463 m_dnsCacheList
= nullptr;
466 void CCurlFile::SetCommonOptions(CReadState
* state
, bool failOnError
/* = true */)
468 CURL_HANDLE
* h
= state
->m_easyHandle
;
470 g_curlInterface
.easy_reset(h
);
472 g_curlInterface
.easy_setopt(h
, CURLOPT_DEBUGFUNCTION
, debug_callback
);
474 if( CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_logLevel
>= LOG_LEVEL_DEBUG
)
475 g_curlInterface
.easy_setopt(h
, CURLOPT_VERBOSE
, CURL_ON
);
477 g_curlInterface
.easy_setopt(h
, CURLOPT_VERBOSE
, CURL_OFF
);
479 g_curlInterface
.easy_setopt(h
, CURLOPT_WRITEDATA
, state
);
480 g_curlInterface
.easy_setopt(h
, CURLOPT_WRITEFUNCTION
, write_callback
);
482 g_curlInterface
.easy_setopt(h
, CURLOPT_READDATA
, state
);
483 g_curlInterface
.easy_setopt(h
, CURLOPT_READFUNCTION
, read_callback
);
486 g_curlInterface
.easy_setopt(h
, CURLOPT_RESOLVE
, m_dnsCacheList
);
488 // make sure headers are separated from the data stream
489 g_curlInterface
.easy_setopt(h
, CURLOPT_WRITEHEADER
, state
);
490 g_curlInterface
.easy_setopt(h
, CURLOPT_HEADERFUNCTION
, header_callback
);
491 g_curlInterface
.easy_setopt(h
, CURLOPT_HEADER
, CURL_OFF
);
493 g_curlInterface
.easy_setopt(h
, CURLOPT_FTP_USE_EPSV
, 0); // turn off epsv
495 // Allow us to follow redirects
496 g_curlInterface
.easy_setopt(h
, CURLOPT_FOLLOWLOCATION
, m_redirectlimit
!= 0);
497 g_curlInterface
.easy_setopt(h
, CURLOPT_MAXREDIRS
, m_redirectlimit
);
499 // Enable cookie engine for current handle
500 g_curlInterface
.easy_setopt(h
, CURLOPT_COOKIEFILE
, "");
502 // Set custom cookie if requested
503 if (!m_cookie
.empty())
504 g_curlInterface
.easy_setopt(h
, CURLOPT_COOKIE
, m_cookie
.c_str());
506 g_curlInterface
.easy_setopt(h
, CURLOPT_COOKIELIST
, "FLUSH");
508 // When using multiple threads you should set the CURLOPT_NOSIGNAL option to
509 // TRUE for all handles. Everything will work fine except that timeouts are not
510 // honored during the DNS lookup - which you can work around by building libcurl
511 // with c-ares support. c-ares is a library that provides asynchronous name
512 // resolves. Unfortunately, c-ares does not yet support IPv6.
513 g_curlInterface
.easy_setopt(h
, CURLOPT_NOSIGNAL
, CURL_ON
);
517 // not interested in failed requests
518 g_curlInterface
.easy_setopt(h
, CURLOPT_FAILONERROR
, 1);
521 // enable support for icecast / shoutcast streams
522 if ( NULL
== state
->m_curlAliasList
)
523 // m_curlAliasList is used only by this one place, but SetCommonOptions can
524 // be called multiple times, only append to list if it's empty.
525 state
->m_curlAliasList
= g_curlInterface
.slist_append(state
->m_curlAliasList
, "ICY 200 OK");
526 g_curlInterface
.easy_setopt(h
, CURLOPT_HTTP200ALIASES
, state
->m_curlAliasList
);
529 g_curlInterface
.easy_setopt(h
, CURLOPT_SSL_VERIFYPEER
, 0);
531 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_URL
, m_url
.c_str());
532 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_TRANSFERTEXT
, CURL_OFF
);
534 // setup POST data if it is set (and it may be empty)
537 g_curlInterface
.easy_setopt(h
, CURLOPT_POST
, 1 );
538 g_curlInterface
.easy_setopt(h
, CURLOPT_POSTFIELDSIZE
, m_postdata
.length());
539 g_curlInterface
.easy_setopt(h
, CURLOPT_POSTFIELDS
, m_postdata
.c_str());
542 // setup Referer header if needed
543 if (!m_referer
.empty())
544 g_curlInterface
.easy_setopt(h
, CURLOPT_REFERER
, m_referer
.c_str());
547 g_curlInterface
.easy_setopt(h
, CURLOPT_REFERER
, NULL
);
548 // Do not send referer header on redirects (same behaviour as ffmpeg and browsers)
549 g_curlInterface
.easy_setopt(h
, CURLOPT_AUTOREFERER
, CURL_OFF
);
552 // setup any requested authentication
553 if( !m_ftpauth
.empty() )
555 g_curlInterface
.easy_setopt(h
, CURLOPT_FTP_SSL
, CURLFTPSSL_TRY
);
556 if( m_ftpauth
== "any" )
557 g_curlInterface
.easy_setopt(h
, CURLOPT_FTPSSLAUTH
, CURLFTPAUTH_DEFAULT
);
558 else if( m_ftpauth
== "ssl" )
559 g_curlInterface
.easy_setopt(h
, CURLOPT_FTPSSLAUTH
, CURLFTPAUTH_SSL
);
560 else if( m_ftpauth
== "tls" )
561 g_curlInterface
.easy_setopt(h
, CURLOPT_FTPSSLAUTH
, CURLFTPAUTH_TLS
);
564 // setup requested http authentication method
565 bool bAuthSet
= false;
566 if(!m_httpauth
.empty())
569 if( m_httpauth
== "any" )
570 g_curlInterface
.easy_setopt(h
, CURLOPT_HTTPAUTH
, CURLAUTH_ANY
);
571 else if( m_httpauth
== "anysafe" )
572 g_curlInterface
.easy_setopt(h
, CURLOPT_HTTPAUTH
, CURLAUTH_ANYSAFE
);
573 else if( m_httpauth
== "digest" )
574 g_curlInterface
.easy_setopt(h
, CURLOPT_HTTPAUTH
, CURLAUTH_DIGEST
);
575 else if( m_httpauth
== "ntlm" )
576 g_curlInterface
.easy_setopt(h
, CURLOPT_HTTPAUTH
, CURLAUTH_NTLM
);
581 // set username and password for current handle
582 if (!m_username
.empty())
584 g_curlInterface
.easy_setopt(h
, CURLOPT_USERNAME
, m_username
.c_str());
585 if (!m_password
.empty())
586 g_curlInterface
.easy_setopt(h
, CURLOPT_PASSWORD
, m_password
.c_str());
589 g_curlInterface
.easy_setopt(h
, CURLOPT_HTTPAUTH
, CURLAUTH_ANY
);
592 // allow passive mode for ftp
593 if( m_ftpport
.length() > 0 )
594 g_curlInterface
.easy_setopt(h
, CURLOPT_FTPPORT
, m_ftpport
.c_str());
596 g_curlInterface
.easy_setopt(h
, CURLOPT_FTPPORT
, NULL
);
598 // allow curl to not use the ip address in the returned pasv response
600 g_curlInterface
.easy_setopt(h
, CURLOPT_FTP_SKIP_PASV_IP
, 0);
602 g_curlInterface
.easy_setopt(h
, CURLOPT_FTP_SKIP_PASV_IP
, 1);
604 // setup Accept-Encoding if requested
605 if (m_acceptencoding
.length() > 0)
606 g_curlInterface
.easy_setopt(h
, CURLOPT_ACCEPT_ENCODING
, m_acceptencoding
== "all" ? "" : m_acceptencoding
.c_str());
608 if (!m_acceptCharset
.empty())
609 SetRequestHeader("Accept-Charset", m_acceptCharset
);
611 if (m_userAgent
.length() > 0)
612 g_curlInterface
.easy_setopt(h
, CURLOPT_USERAGENT
, m_userAgent
.c_str());
613 else /* set some default agent as shoutcast doesn't return proper stuff otherwise */
614 g_curlInterface
.easy_setopt(h
, CURLOPT_USERAGENT
, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_userAgent
.c_str());
616 if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_curlDisableIPV6
)
617 g_curlInterface
.easy_setopt(h
, CURLOPT_IPRESOLVE
, CURL_IPRESOLVE_V4
);
619 if (!m_proxyhost
.empty())
621 g_curlInterface
.easy_setopt(h
, CURLOPT_PROXYTYPE
, proxyType2CUrlProxyType
[m_proxytype
]);
623 const std::string hostport
= m_proxyhost
+ StringUtils::Format(":{}", m_proxyport
);
624 g_curlInterface
.easy_setopt(h
, CURLOPT_PROXY
, hostport
.c_str());
626 std::string userpass
;
628 if (!m_proxyuser
.empty() && !m_proxypassword
.empty())
629 userpass
= CURL::Encode(m_proxyuser
) + ":" + CURL::Encode(m_proxypassword
);
631 if (!userpass
.empty())
632 g_curlInterface
.easy_setopt(h
, CURLOPT_PROXYUSERPWD
, userpass
.c_str());
634 if (m_customrequest
.length() > 0)
635 g_curlInterface
.easy_setopt(h
, CURLOPT_CUSTOMREQUEST
, m_customrequest
.c_str());
637 if (m_connecttimeout
== 0)
638 m_connecttimeout
= CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_curlconnecttimeout
;
640 // set our timeouts, we abort connection after m_timeout, and reads after no data for m_timeout seconds
641 g_curlInterface
.easy_setopt(h
, CURLOPT_CONNECTTIMEOUT
, m_connecttimeout
);
643 // We abort in case we transfer less than 1byte/second
644 g_curlInterface
.easy_setopt(h
, CURLOPT_LOW_SPEED_LIMIT
, 1);
646 if (m_lowspeedtime
== 0)
647 m_lowspeedtime
= CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_curllowspeedtime
;
649 // Set the lowspeed time very low as it seems Curl takes much longer to detect a lowspeed condition
650 g_curlInterface
.easy_setopt(h
, CURLOPT_LOW_SPEED_TIME
, m_lowspeedtime
);
652 // enable tcp keepalive
653 if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_curlKeepAliveInterval
> 0)
655 g_curlInterface
.easy_setopt(h
, CURLOPT_TCP_KEEPALIVE
, 1L);
656 g_curlInterface
.easy_setopt(
657 h
, CURLOPT_TCP_KEEPIDLE
,
658 CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_curlKeepAliveInterval
/ 2);
659 g_curlInterface
.easy_setopt(
660 h
, CURLOPT_TCP_KEEPINTVL
,
661 CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_curlKeepAliveInterval
);
664 // Setup allowed TLS/SSL ciphers. New versions of cURL may deprecate things that are still in use.
665 if (!m_cipherlist
.empty())
666 g_curlInterface
.easy_setopt(h
, CURLOPT_SSL_CIPHER_LIST
, m_cipherlist
.c_str());
668 if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_curlDisableHTTP2
)
669 g_curlInterface
.easy_setopt(h
, CURLOPT_HTTP_VERSION
, CURL_HTTP_VERSION_1_1
);
671 // enable HTTP2 support. default: CURL_HTTP_VERSION_1_1. Curl >= 7.62.0 defaults to CURL_HTTP_VERSION_2TLS
672 g_curlInterface
.easy_setopt(h
, CURLOPT_HTTP_VERSION
, CURL_HTTP_VERSION_2TLS
);
674 // set CA bundle file
675 std::string caCert
= CSpecialProtocol::TranslatePath(
676 CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_caTrustFile
);
677 #ifdef TARGET_WINDOWS_STORE
678 // UWP Curl - Setting CURLOPT_CAINFO with a valid cacert file path is required for UWP
679 g_curlInterface
.easy_setopt(h
, CURLOPT_CAINFO
, "system\\certs\\cacert.pem");
681 if (!caCert
.empty() && XFILE::CFile::Exists(caCert
))
682 g_curlInterface
.easy_setopt(h
, CURLOPT_CAINFO
, caCert
.c_str());
685 void CCurlFile::SetRequestHeaders(CReadState
* state
)
687 if(state
->m_curlHeaderList
)
689 g_curlInterface
.slist_free_all(state
->m_curlHeaderList
);
690 state
->m_curlHeaderList
= NULL
;
693 for (const auto& it
: m_requestheaders
)
695 std::string buffer
= it
.first
+ ": " + it
.second
;
696 state
->m_curlHeaderList
= g_curlInterface
.slist_append(state
->m_curlHeaderList
, buffer
.c_str());
699 // add user defined headers
700 if (state
->m_easyHandle
)
701 g_curlInterface
.easy_setopt(state
->m_easyHandle
, CURLOPT_HTTPHEADER
, state
->m_curlHeaderList
);
704 void CCurlFile::SetCorrectHeaders(CReadState
* state
)
706 CHttpHeader
& h
= state
->m_httpheader
;
707 /* workaround for shoutcast server which doesn't set content type on standard mp3 */
708 if( h
.GetMimeType().empty() )
710 if( !h
.GetValue("icy-notice1").empty()
711 || !h
.GetValue("icy-name").empty()
712 || !h
.GetValue("icy-br").empty() )
713 h
.AddParam("Content-Type", "audio/mpeg");
716 /* hack for google video */
717 if (StringUtils::EqualsNoCase(h
.GetMimeType(),"text/html")
718 && !h
.GetValue("Content-Disposition").empty() )
720 std::string strValue
= h
.GetValue("Content-Disposition");
721 if (strValue
.find("filename=") != std::string::npos
&&
722 strValue
.find(".flv") != std::string::npos
)
723 h
.AddParam("Content-Type", "video/flv");
727 void CCurlFile::ParseAndCorrectUrl(CURL
&url2
)
729 std::string strProtocol
= url2
.GetTranslatedProtocol();
730 url2
.SetProtocol(strProtocol
);
732 // lookup host in DNS cache
733 std::string resolvedHost
;
734 if (CDNSNameCache::GetCached(url2
.GetHostName(), resolvedHost
))
736 struct curl_slist
* tempCache
;
737 int entryPort
= url2
.GetPort();
741 if (strProtocol
== "http")
743 else if (strProtocol
== "https")
745 else if (strProtocol
== "ftp")
747 else if (strProtocol
== "ftps")
751 std::string entryString
=
752 url2
.GetHostName() + ":" + std::to_string(entryPort
) + ":" + resolvedHost
;
753 tempCache
= g_curlInterface
.slist_append(m_dnsCacheList
, entryString
.c_str());
756 m_dnsCacheList
= tempCache
;
759 if( url2
.IsProtocol("ftp")
760 || url2
.IsProtocol("ftps") )
762 // we was using url options for urls, keep the old code work and warning
763 if (!url2
.GetOptions().empty())
765 CLog::Log(LOGWARNING
,
766 "CCurlFile::{} - <{}> FTP url option is deprecated, please switch to use protocol "
769 __FUNCTION__
, url2
.GetRedacted());
770 url2
.SetProtocolOptions(url2
.GetOptions().substr(1));
771 /* ftp has no options */
775 /* this is ugly, depending on where we get */
776 /* the link from, it may or may not be */
777 /* url encoded. if handed from ftpdirectory */
778 /* it won't be so let's handle that case */
780 std::string
filename(url2
.GetFileName());
781 std::vector
<std::string
> array
;
783 // if server sent us the filename in non-utf8, we need send back with same encoding.
784 if (url2
.GetProtocolOption("utf8") == "0")
785 g_charsetConverter
.utf8ToStringCharset(filename
);
787 //! @todo create a tokenizer that doesn't skip empty's
788 StringUtils::Tokenize(filename
, array
, "/");
790 for (std::vector
<std::string
>::iterator it
= array
.begin(); it
!= array
.end(); ++it
)
792 if(it
!= array
.begin())
795 filename
+= CURL::Encode(*it
);
798 /* make sure we keep slashes */
799 if(StringUtils::EndsWith(url2
.GetFileName(), "/"))
802 url2
.SetFileName(filename
);
805 if (url2
.HasProtocolOption("auth"))
807 m_ftpauth
= url2
.GetProtocolOption("auth");
808 StringUtils::ToLower(m_ftpauth
);
809 if(m_ftpauth
.empty())
813 if (url2
.HasProtocolOption("active"))
815 m_ftpport
= url2
.GetProtocolOption("active");
816 if(m_ftpport
.empty())
819 if (url2
.HasProtocolOption("verifypeer"))
821 if (url2
.GetProtocolOption("verifypeer") == "false")
822 m_verifyPeer
= false;
824 m_ftppasvip
= url2
.HasProtocolOption("pasvip") && url2
.GetProtocolOption("pasvip") != "0";
826 else if(url2
.IsProtocol("http") ||
827 url2
.IsProtocol("https"))
829 std::shared_ptr
<CSettings
> s
= CServiceBroker::GetSettingsComponent()->GetSettings();
833 if (!url2
.IsLocalHost() &&
834 m_proxyhost
.empty() &&
835 s
->GetBool(CSettings::SETTING_NETWORK_USEHTTPPROXY
) &&
836 !s
->GetString(CSettings::SETTING_NETWORK_HTTPPROXYSERVER
).empty() &&
837 s
->GetInt(CSettings::SETTING_NETWORK_HTTPPROXYPORT
) > 0)
839 m_proxytype
= (ProxyType
)s
->GetInt(CSettings::SETTING_NETWORK_HTTPPROXYTYPE
);
840 m_proxyhost
= s
->GetString(CSettings::SETTING_NETWORK_HTTPPROXYSERVER
);
841 m_proxyport
= s
->GetInt(CSettings::SETTING_NETWORK_HTTPPROXYPORT
);
842 m_proxyuser
= s
->GetString(CSettings::SETTING_NETWORK_HTTPPROXYUSERNAME
);
843 m_proxypassword
= s
->GetString(CSettings::SETTING_NETWORK_HTTPPROXYPASSWORD
);
844 CLog::LogFC(LOGDEBUG
, LOGCURL
, "<{}> Using proxy {}, type {}", url2
.GetRedacted(),
845 m_proxyhost
, proxyType2CUrlProxyType
[m_proxytype
]);
848 // get username and password
849 m_username
= url2
.GetUserName();
850 m_password
= url2
.GetPassWord();
852 // handle any protocol options
853 std::map
<std::string
, std::string
> options
;
854 url2
.GetProtocolOptions(options
);
855 if (!options
.empty())
858 for (const auto& it
: options
)
860 std::string name
= it
.first
;
861 StringUtils::ToLower(name
);
862 const std::string
& value
= it
.second
;
867 StringUtils::ToLower(m_httpauth
);
868 if(m_httpauth
.empty())
871 else if (name
== "referer")
873 else if (name
== "user-agent")
875 else if (name
== "cookie")
877 else if (name
== "acceptencoding" || name
== "encoding")
878 SetAcceptEncoding(value
);
879 else if (name
== "noshout" && value
== "true")
881 else if (name
== "seekable" && value
== "0")
883 else if (name
== "accept-charset")
884 SetAcceptCharset(value
);
885 else if (name
== "sslcipherlist")
886 m_cipherlist
= value
;
887 else if (name
== "connection-timeout")
888 m_connecttimeout
= strtol(value
.c_str(), NULL
, 10);
889 else if (name
== "failonerror")
890 m_failOnError
= value
== "true";
891 else if (name
== "redirect-limit")
892 m_redirectlimit
= strtol(value
.c_str(), NULL
, 10);
893 else if (name
== "postdata")
895 m_postdata
= Base64::Decode(value
);
896 m_postdataset
= true;
898 else if (name
== "active-remote")// needed for DACP!
900 SetRequestHeader(it
.first
, value
);
902 else if (name
== "customrequest")
904 SetCustomRequest(value
);
906 else if (name
== "verifypeer")
908 if (value
== "false")
909 m_verifyPeer
= false;
913 if (name
.length() > 0 && name
[0] == '!')
915 SetRequestHeader(it
.first
.substr(1), value
);
916 CLog::LogFC(LOGDEBUG
, LOGCURL
, "<{}> Adding custom header option '{}: ***********'",
917 url2
.GetRedacted(), it
.first
.substr(1));
921 SetRequestHeader(it
.first
, value
);
922 if (name
== "authorization")
923 CLog::LogFC(LOGDEBUG
, LOGCURL
, "<{}> Adding custom header option '{}: ***********'",
924 url2
.GetRedacted(), it
.first
);
926 CLog::LogFC(LOGDEBUG
, LOGCURL
, "<{}> Adding custom header option '{}: {}'",
927 url2
.GetRedacted(), it
.first
, value
);
934 // Unset the protocol options to have an url without protocol options
935 url2
.SetProtocolOptions("");
937 if (m_username
.length() > 0 && m_password
.length() > 0)
938 m_url
= url2
.GetWithoutUserDetails();
943 bool CCurlFile::Post(const std::string
& strURL
, const std::string
& strPostData
, std::string
& strHTML
)
945 m_postdata
= strPostData
;
946 m_postdataset
= true;
947 return Service(strURL
, strHTML
);
950 bool CCurlFile::Get(const std::string
& strURL
, std::string
& strHTML
)
953 m_postdataset
= false;
954 return Service(strURL
, strHTML
);
957 bool CCurlFile::Service(const std::string
& strURL
, std::string
& strHTML
)
959 const CURL
pathToUrl(strURL
);
962 if (ReadData(strHTML
))
972 bool CCurlFile::ReadData(std::string
& strHTML
)
977 while( (size_read
= Read(buffer
, sizeof(buffer
)-1) ) > 0 )
979 buffer
[size_read
] = 0;
980 strHTML
.append(buffer
, size_read
);
982 if (m_state
->m_cancelled
)
987 bool CCurlFile::Download(const std::string
& strURL
, const std::string
& strFileName
, unsigned int* pdwSize
)
989 CLog::Log(LOGINFO
, "CCurlFile::{} - {}->{}", __FUNCTION__
, CURL::GetRedacted(strURL
),
993 if (!Get(strURL
, strData
))
997 if (!file
.OpenForWrite(strFileName
, true))
999 CLog::Log(LOGERROR
, "CCurlFile::{} - <{}> Unable to open file for write: {} ({})", __FUNCTION__
,
1000 CURL::GetRedacted(strURL
), strFileName
, GetLastError());
1003 ssize_t written
= 0;
1004 if (!strData
.empty())
1005 written
= file
.Write(strData
.c_str(), strData
.size());
1007 if (pdwSize
!= NULL
)
1008 *pdwSize
= written
> 0 ? written
: 0;
1010 return written
== static_cast<ssize_t
>(strData
.size());
1013 // Detect whether we are "online" or not! Very simple and dirty!
1014 bool CCurlFile::IsInternet()
1016 CURL
url("http://www.msftconnecttest.com/connecttest.txt");
1017 bool found
= Exists(url
);
1022 url
.Parse("http://www.w3.org/");
1023 found
= Exists(url
);
1030 void CCurlFile::Cancel()
1032 m_state
->m_cancelled
= true;
1034 KODI::TIME::Sleep(1ms
);
1037 void CCurlFile::Reset()
1039 m_state
->m_cancelled
= false;
1042 void CCurlFile::SetProxy(const std::string
&type
, const std::string
&host
,
1043 uint16_t port
, const std::string
&user
, const std::string
&password
)
1045 m_proxytype
= CCurlFile::PROXY_HTTP
;
1047 m_proxytype
= CCurlFile::PROXY_HTTP
;
1048 else if (type
== "https")
1049 m_proxytype
= CCurlFile::PROXY_HTTPS
;
1050 else if (type
== "socks4")
1051 m_proxytype
= CCurlFile::PROXY_SOCKS4
;
1052 else if (type
== "socks4a")
1053 m_proxytype
= CCurlFile::PROXY_SOCKS4A
;
1054 else if (type
== "socks5")
1055 m_proxytype
= CCurlFile::PROXY_SOCKS5
;
1056 else if (type
== "socks5-remote")
1057 m_proxytype
= CCurlFile::PROXY_SOCKS5_REMOTE
;
1059 CLog::Log(LOGERROR
, "CCurFile::{} - <{}> Invalid proxy type \"{}\"", __FUNCTION__
,
1060 CURL::GetRedacted(m_url
), type
);
1064 m_proxypassword
= password
;
1067 bool CCurlFile::Open(const CURL
& url
)
1073 ParseAndCorrectUrl(url2
);
1075 std::string redactPath
= CURL::GetRedacted(m_url
);
1076 CLog::Log(LOGDEBUG
, "CurlFile::{} - <{}>", __FUNCTION__
, redactPath
);
1078 assert(!(!m_state
->m_easyHandle
^ !m_state
->m_multiHandle
));
1079 if( m_state
->m_easyHandle
== NULL
)
1080 g_curlInterface
.easy_acquire(url2
.GetProtocol().c_str(),
1081 url2
.GetHostName().c_str(),
1082 &m_state
->m_easyHandle
,
1083 &m_state
->m_multiHandle
);
1085 // setup common curl options
1086 SetCommonOptions(m_state
,
1087 m_failOnError
&& !CServiceBroker::GetLogging().CanLogComponent(LOGCURL
));
1088 SetRequestHeaders(m_state
);
1089 m_state
->m_sendRange
= m_seekable
;
1090 m_state
->m_bRetry
= m_allowRetry
;
1092 m_httpresponse
= m_state
->Connect(m_bufferSize
);
1094 if (m_httpresponse
<= 0 || (m_failOnError
&& m_httpresponse
>= 400))
1097 if (m_httpresponse
>= 400 && CServiceBroker::GetLogging().CanLogComponent(LOGCURL
))
1100 ReadString(&error
[0], 4095);
1103 CLog::Log(LOGERROR
, "CCurlFile::{} - <{}> Failed with code {}:\n{}", __FUNCTION__
,
1104 redactPath
, m_httpresponse
, error
);
1109 SetCorrectHeaders(m_state
);
1111 // since we can't know the stream size up front if we're gzipped/deflated
1112 // flag the stream with an unknown file size rather than the compressed
1114 if (!m_state
->m_httpheader
.GetValue("Content-Encoding").empty() && !StringUtils::EqualsNoCase(m_state
->m_httpheader
.GetValue("Content-Encoding"), "identity"))
1115 m_state
->m_fileSize
= 0;
1117 // check if this stream is a shoutcast stream. sometimes checking the protocol line is not enough so examine other headers as well.
1118 // shoutcast streams should be handled by FileShoutcast.
1119 if ((m_state
->m_httpheader
.GetProtoLine().substr(0, 3) == "ICY" || !m_state
->m_httpheader
.GetValue("icy-notice1").empty()
1120 || !m_state
->m_httpheader
.GetValue("icy-name").empty()
1121 || !m_state
->m_httpheader
.GetValue("icy-br").empty()) && !m_skipshout
)
1123 CLog::Log(LOGDEBUG
, "CCurlFile::{} - <{}> File is a shoutcast stream. Re-opening", __FUNCTION__
,
1125 throw new CRedirectException(new CShoutcastFile
);
1128 m_multisession
= false;
1129 if(url2
.IsProtocol("http") || url2
.IsProtocol("https"))
1131 m_multisession
= true;
1132 if(m_state
->m_httpheader
.GetValue("Server").find("Portable SDK for UPnP devices") != std::string::npos
)
1134 CLog::Log(LOGWARNING
,
1135 "CCurlFile::{} - <{}> Disabling multi session due to broken libupnp server",
1136 __FUNCTION__
, redactPath
);
1137 m_multisession
= false;
1141 if(StringUtils::EqualsNoCase(m_state
->m_httpheader
.GetValue("Transfer-Encoding"), "chunked"))
1142 m_state
->m_fileSize
= 0;
1144 if(m_state
->m_fileSize
<= 0)
1148 if(url2
.IsProtocol("http")
1149 || url2
.IsProtocol("https"))
1151 // if server says explicitly it can't seek, respect that
1152 if(StringUtils::EqualsNoCase(m_state
->m_httpheader
.GetValue("Accept-Ranges"),"none"))
1157 std::string efurl
= GetInfoString(CURLINFO_EFFECTIVE_URL
);
1162 std::string redactEfpath
= CURL::GetRedacted(efurl
);
1163 CLog::Log(LOGDEBUG
, "CCurlFile::{} - <{}> Effective URL is {}", __FUNCTION__
, redactPath
,
1172 bool CCurlFile::OpenForWrite(const CURL
& url
, bool bOverWrite
)
1177 if (Exists(url
) && !bOverWrite
)
1181 ParseAndCorrectUrl(url2
);
1183 CLog::Log(LOGDEBUG
, "CCurlFile::{} - Opening {}", __FUNCTION__
, CURL::GetRedacted(m_url
));
1185 assert(m_state
->m_easyHandle
== NULL
);
1186 g_curlInterface
.easy_acquire(url2
.GetProtocol().c_str(),
1187 url2
.GetHostName().c_str(),
1188 &m_state
->m_easyHandle
,
1189 &m_state
->m_multiHandle
);
1191 // setup common curl options
1192 SetCommonOptions(m_state
);
1193 SetRequestHeaders(m_state
);
1195 std::string efurl
= GetInfoString(CURLINFO_EFFECTIVE_URL
);
1204 assert(m_state
->m_multiHandle
);
1206 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_UPLOAD
, 1);
1208 g_curlInterface
.multi_add_handle(m_state
->m_multiHandle
, m_state
->m_easyHandle
);
1210 m_state
->SetReadBuffer(NULL
, 0);
1215 ssize_t
CCurlFile::Write(const void* lpBuf
, size_t uiBufSize
)
1217 if (!(m_opened
&& m_forWrite
) || m_inError
)
1220 assert(m_state
->m_multiHandle
);
1222 m_state
->SetReadBuffer(lpBuf
, uiBufSize
);
1223 m_state
->m_isPaused
= false;
1224 g_curlInterface
.easy_pause(m_state
->m_easyHandle
, CURLPAUSE_CONT
);
1226 CURLMcode result
= CURLM_OK
;
1229 while (m_stillRunning
&& !m_state
->m_isPaused
)
1231 while ((result
= g_curlInterface
.multi_perform(m_state
->m_multiHandle
, &m_stillRunning
)) == CURLM_CALL_MULTI_PERFORM
);
1233 if (!m_stillRunning
)
1236 if (result
!= CURLM_OK
)
1239 if(g_curlInterface
.easy_getinfo(m_state
->m_easyHandle
, CURLINFO_RESPONSE_CODE
, &code
) == CURLE_OK
)
1240 CLog::Log(LOGERROR
, "CCurlFile::{} - <{}> Unable to write curl resource with code {}",
1241 __FUNCTION__
, CURL::GetRedacted(m_url
), code
);
1247 m_writeOffset
+= m_state
->m_filePos
;
1248 return m_state
->m_filePos
;
1251 bool CCurlFile::CReadState::ReadString(char *szLine
, int iLineLength
)
1253 unsigned int want
= (unsigned int)iLineLength
;
1255 if((m_fileSize
== 0 || m_filePos
< m_fileSize
) && FillBuffer(want
) != FILLBUFFER_OK
)
1258 // ensure only available data is considered
1259 want
= std::min(m_buffer
.getMaxReadSize(), want
);
1261 /* check if we finished prematurely */
1262 if (!m_stillRunning
&& (m_fileSize
== 0 || m_filePos
!= m_fileSize
) && !want
)
1264 if (m_fileSize
!= 0)
1267 "CCurlFile::{} - ({}) Transfer ended before entire file was retrieved pos {}, size {}",
1268 __FUNCTION__
, fmt::ptr(this), m_filePos
, m_fileSize
);
1273 char* pLine
= szLine
;
1276 if (!m_buffer
.ReadData(pLine
, 1))
1280 } while (((pLine
- 1)[0] != '\n') && ((unsigned int)(pLine
- szLine
) < want
));
1282 m_filePos
+= (pLine
- szLine
);
1283 return (pLine
- szLine
) > 0;
1286 bool CCurlFile::ReOpen(const CURL
& url
)
1292 bool CCurlFile::Exists(const CURL
& url
)
1294 // if file is already running, get info from it
1297 CLog::Log(LOGWARNING
, "CCurlFile::{} - <{}> Exist called on open file", __FUNCTION__
,
1303 ParseAndCorrectUrl(url2
);
1305 assert(m_state
->m_easyHandle
== NULL
);
1306 g_curlInterface
.easy_acquire(url2
.GetProtocol().c_str(),
1307 url2
.GetHostName().c_str(),
1308 &m_state
->m_easyHandle
, NULL
);
1310 SetCommonOptions(m_state
);
1311 SetRequestHeaders(m_state
);
1312 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_TIMEOUT
, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_curlconnecttimeout
);
1313 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_NOBODY
, 1);
1314 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_WRITEDATA
, NULL
); /* will cause write failure*/
1316 if(url2
.IsProtocol("ftp") || url2
.IsProtocol("ftps"))
1318 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_FILETIME
, 1);
1319 // nocwd is less standard, will return empty list for non-existed remote dir on some ftp server, avoid it.
1320 if (StringUtils::EndsWith(url2
.GetFileName(), "/"))
1321 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_FTP_FILEMETHOD
, CURLFTPMETHOD_SINGLECWD
);
1323 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_FTP_FILEMETHOD
, CURLFTPMETHOD_NOCWD
);
1326 CURLcode result
= g_curlInterface
.easy_perform(m_state
->m_easyHandle
);
1328 if (result
== CURLE_WRITE_ERROR
|| result
== CURLE_OK
)
1330 g_curlInterface
.easy_release(&m_state
->m_easyHandle
, NULL
);
1334 if (result
== CURLE_HTTP_RETURNED_ERROR
)
1337 if(g_curlInterface
.easy_getinfo(m_state
->m_easyHandle
, CURLINFO_RESPONSE_CODE
, &code
) == CURLE_OK
&& code
!= 404 )
1341 // If we get a Method Not Allowed response, retry with a GET Request
1342 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_NOBODY
, 0);
1344 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_FILETIME
, 1);
1345 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_XFERINFOFUNCTION
, transfer_abort_callback
);
1346 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_NOPROGRESS
, 0);
1348 curl_slist
*list
= NULL
;
1349 list
= g_curlInterface
.slist_append(list
, "Range: bytes=0-1"); /* try to only request 1 byte */
1350 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_HTTPHEADER
, list
);
1352 CURLcode result
= g_curlInterface
.easy_perform(m_state
->m_easyHandle
);
1353 g_curlInterface
.slist_free_all(list
);
1355 if (result
== CURLE_WRITE_ERROR
|| result
== CURLE_OK
)
1357 g_curlInterface
.easy_release(&m_state
->m_easyHandle
, NULL
);
1361 if (result
== CURLE_HTTP_RETURNED_ERROR
)
1363 if (g_curlInterface
.easy_getinfo(m_state
->m_easyHandle
, CURLINFO_RESPONSE_CODE
, &code
) == CURLE_OK
&& code
!= 404 )
1364 CLog::Log(LOGERROR
, "CCurlFile::{} - <{}> Failed: HTTP returned error {}", __FUNCTION__
,
1365 url
.GetRedacted(), code
);
1369 CLog::Log(LOGERROR
, "CCurlFile::{} - <{}> Failed: HTTP returned error {}", __FUNCTION__
,
1370 url
.GetRedacted(), code
);
1373 else if (result
!= CURLE_REMOTE_FILE_NOT_FOUND
&& result
!= CURLE_FTP_COULDNT_RETR_FILE
)
1375 CLog::Log(LOGERROR
, "CCurlFile::{} - <{}> Failed: {}({})", __FUNCTION__
, url
.GetRedacted(),
1376 g_curlInterface
.easy_strerror(result
), result
);
1380 g_curlInterface
.easy_release(&m_state
->m_easyHandle
, NULL
);
1384 int64_t CCurlFile::Seek(int64_t iFilePosition
, int iWhence
)
1386 int64_t nextPos
= m_state
->m_filePos
;
1394 nextPos
= iFilePosition
;
1397 nextPos
+= iFilePosition
;
1400 if (m_state
->m_fileSize
)
1401 nextPos
= m_state
->m_fileSize
+ iFilePosition
;
1409 // We can't seek beyond EOF
1410 if (m_state
->m_fileSize
&& nextPos
> m_state
->m_fileSize
) return -1;
1412 if(m_state
->Seek(nextPos
))
1420 m_oldState
= m_state
;
1421 m_state
= new CReadState();
1422 m_state
->m_fileSize
= m_oldState
->m_fileSize
;
1423 g_curlInterface
.easy_acquire(url
.GetProtocol().c_str(),
1424 url
.GetHostName().c_str(),
1425 &m_state
->m_easyHandle
,
1426 &m_state
->m_multiHandle
);
1432 m_state
= m_oldState
;
1435 if (m_state
->Seek(nextPos
))
1438 m_state
->Disconnect();
1442 m_state
->Disconnect();
1444 // re-setup common curl options
1445 SetCommonOptions(m_state
);
1447 /* caller might have changed some headers (needed for daap)*/
1448 //! @todo daap is gone. is this needed for something else?
1449 SetRequestHeaders(m_state
);
1451 m_state
->m_filePos
= nextPos
;
1452 m_state
->m_sendRange
= true;
1453 m_state
->m_bRetry
= m_allowRetry
;
1455 long response
= m_state
->Connect(m_bufferSize
);
1456 if(response
< 0 && (m_state
->m_fileSize
== 0 || m_state
->m_fileSize
!= m_state
->m_filePos
))
1463 m_state
= m_oldState
;
1466 // Retry without multisession
1467 m_multisession
= false;
1468 return Seek(iFilePosition
, iWhence
);
1477 SetCorrectHeaders(m_state
);
1479 return m_state
->m_filePos
;
1482 int64_t CCurlFile::GetLength()
1484 if (!m_opened
) return 0;
1485 return m_state
->m_fileSize
;
1488 int64_t CCurlFile::GetPosition()
1490 if (!m_opened
) return 0;
1491 return m_state
->m_filePos
;
1494 int CCurlFile::Stat(const CURL
& url
, struct __stat64
* buffer
)
1496 // if file is already running, get info from it
1499 CLog::Log(LOGWARNING
, "CCurlFile::{} - <{}> Stat called on open file", __FUNCTION__
,
1504 buffer
->st_size
= GetLength();
1505 buffer
->st_mode
= _S_IFREG
;
1511 ParseAndCorrectUrl(url2
);
1513 assert(m_state
->m_easyHandle
== NULL
);
1514 g_curlInterface
.easy_acquire(url2
.GetProtocol().c_str(),
1515 url2
.GetHostName().c_str(),
1516 &m_state
->m_easyHandle
, NULL
);
1518 SetCommonOptions(m_state
);
1519 SetRequestHeaders(m_state
);
1520 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_TIMEOUT
, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_curlconnecttimeout
);
1521 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_NOBODY
, 1);
1522 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_FILETIME
, 1);
1524 if(url2
.IsProtocol("ftp"))
1526 // nocwd is less standard, will return empty list for non-existed remote dir on some ftp server, avoid it.
1527 if (StringUtils::EndsWith(url2
.GetFileName(), "/"))
1528 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_FTP_FILEMETHOD
, CURLFTPMETHOD_SINGLECWD
);
1530 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_FTP_FILEMETHOD
, CURLFTPMETHOD_NOCWD
);
1533 CURLcode result
= g_curlInterface
.easy_perform(m_state
->m_easyHandle
);
1535 if(result
== CURLE_HTTP_RETURNED_ERROR
)
1538 if(g_curlInterface
.easy_getinfo(m_state
->m_easyHandle
, CURLINFO_RESPONSE_CODE
, &code
) == CURLE_OK
&& code
== 404 )
1540 g_curlInterface
.easy_release(&m_state
->m_easyHandle
, NULL
);
1546 if(result
== CURLE_GOT_NOTHING
1547 || result
== CURLE_HTTP_RETURNED_ERROR
1548 || result
== CURLE_RECV_ERROR
/* some silly shoutcast servers */ )
1550 /* some http servers and shoutcast servers don't give us any data on a head request */
1551 /* request normal and just bail out via progress meter callback after we received data */
1552 /* somehow curl doesn't reset CURLOPT_NOBODY properly so reset everything */
1553 SetCommonOptions(m_state
);
1554 SetRequestHeaders(m_state
);
1555 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_TIMEOUT
, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_curlconnecttimeout
);
1556 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_FILETIME
, 1);
1557 #if LIBCURL_VERSION_NUM >= 0x072000 // 0.7.32
1558 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_XFERINFOFUNCTION
, transfer_abort_callback
);
1560 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_PROGRESSFUNCTION
, transfer_abort_callback
);
1562 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_NOPROGRESS
, 0);
1564 result
= g_curlInterface
.easy_perform(m_state
->m_easyHandle
);
1568 if( result
!= CURLE_ABORTED_BY_CALLBACK
&& result
!= CURLE_OK
)
1570 g_curlInterface
.easy_release(&m_state
->m_easyHandle
, NULL
);
1572 CLog::Log(LOGERROR
, "CCurlFile::{} - <{}> Failed: {}({})", __FUNCTION__
, url
.GetRedacted(),
1573 g_curlInterface
.easy_strerror(result
), result
);
1578 result
= g_curlInterface
.easy_getinfo(m_state
->m_easyHandle
, CURLINFO_CONTENT_LENGTH_DOWNLOAD
, &length
);
1579 if (result
!= CURLE_OK
|| length
< 0.0)
1581 if (url
.IsProtocol("ftp"))
1583 g_curlInterface
.easy_release(&m_state
->m_easyHandle
, NULL
);
1584 CLog::Log(LOGINFO
, "CCurlFile::{} - <{}> Content length failed: {}({})", __FUNCTION__
,
1585 url
.GetRedacted(), g_curlInterface
.easy_strerror(result
), result
);
1593 SetCorrectHeaders(m_state
);
1598 buffer
->st_size
= static_cast<int64_t>(length
);
1600 // Note: CURLINFO_CONTENT_TYPE returns the last received content-type response header value.
1601 // In case there is authentication required there might be multiple requests involved and if
1602 // the last request which actually returns the data does not return a content-type header, but
1603 // one of the preceding requests, CURLINFO_CONTENT_TYPE returns not the content type of the
1604 // actual resource requested! m_state contains only the values of the last request, which is
1605 // what we want here.
1606 const std::string mimeType
= m_state
->m_httpheader
.GetMimeType();
1607 if (mimeType
.find("text/html") != std::string::npos
) // consider html files directories
1608 buffer
->st_mode
= _S_IFDIR
;
1610 buffer
->st_mode
= _S_IFREG
;
1613 result
= g_curlInterface
.easy_getinfo(m_state
->m_easyHandle
, CURLINFO_FILETIME
, &filetime
);
1614 if (result
!= CURLE_OK
)
1616 CLog::Log(LOGINFO
, "CCurlFile::{} - <{}> Filetime failed: {}({})", __FUNCTION__
,
1617 url
.GetRedacted(), g_curlInterface
.easy_strerror(result
), result
);
1622 buffer
->st_mtime
= filetime
;
1625 g_curlInterface
.easy_release(&m_state
->m_easyHandle
, NULL
);
1629 ssize_t
CCurlFile::CReadState::Read(void* lpBuf
, size_t uiBufSize
)
1631 /* only request 1 byte, for truncated reads (only if not eof) */
1632 if (m_fileSize
== 0 || m_filePos
< m_fileSize
)
1634 int8_t result
= FillBuffer(1);
1635 if (result
== FILLBUFFER_FAIL
)
1636 return -1; // Fatal error
1638 if (result
== FILLBUFFER_NO_DATA
)
1642 /* ensure only available data is considered */
1643 unsigned int want
= std::min
<unsigned int>(m_buffer
.getMaxReadSize(), uiBufSize
);
1645 /* xfer data to caller */
1646 if (m_buffer
.ReadData((char *)lpBuf
, want
))
1652 /* check if we finished prematurely */
1653 if (!m_stillRunning
&& (m_fileSize
== 0 || m_filePos
!= m_fileSize
))
1655 CLog::Log(LOGWARNING
,
1656 "CCurlFile::CReadState::{} - ({}) Transfer ended before entire file was retrieved "
1658 __FUNCTION__
, fmt::ptr(this), m_filePos
, m_fileSize
);
1665 /* use to attempt to fill the read buffer up to requested number of bytes */
1666 int8_t CCurlFile::CReadState::FillBuffer(unsigned int want
)
1673 // only attempt to fill buffer if transactions still running and buffer
1674 // doesn't exceed required size already
1675 while (m_buffer
.getMaxReadSize() < want
&& m_buffer
.getMaxWriteSize() > 0 )
1678 return FILLBUFFER_NO_DATA
;
1680 /* if there is data in overflow buffer, try to use that first */
1683 unsigned amount
= std::min(m_buffer
.getMaxWriteSize(), m_overflowSize
);
1684 m_buffer
.WriteData(m_overflowBuffer
, amount
);
1686 if (amount
< m_overflowSize
)
1687 memmove(m_overflowBuffer
, m_overflowBuffer
+ amount
, m_overflowSize
- amount
);
1689 m_overflowSize
-= amount
;
1691 m_overflowBuffer
= (char*)realloc_simple(m_overflowBuffer
, m_overflowSize
);
1695 CURLMcode result
= g_curlInterface
.multi_perform(m_multiHandle
, &m_stillRunning
);
1696 if (!m_stillRunning
)
1698 if (result
== CURLM_OK
)
1700 /* if we still have stuff in buffer, we are fine */
1701 if (m_buffer
.getMaxReadSize())
1702 return FILLBUFFER_OK
;
1707 bool bRetryNow
= true;
1708 bool bError
= false;
1709 while ((msg
= g_curlInterface
.multi_info_read(m_multiHandle
, &msgs
)))
1711 if (msg
->msg
== CURLMSG_DONE
)
1713 if (msg
->data
.result
== CURLE_OK
)
1714 return FILLBUFFER_OK
;
1717 if (msg
->data
.result
== CURLE_HTTP_RETURNED_ERROR
)
1719 g_curlInterface
.easy_getinfo(msg
->easy_handle
, CURLINFO_RESPONSE_CODE
, &httpCode
);
1721 // Don't log 404 not-found errors to prevent log-spam
1722 if (httpCode
!= 404)
1724 "CCurlFile::CReadState::{} - ({}) Failed: HTTP returned code {}",
1725 __FUNCTION__
, fmt::ptr(this), httpCode
);
1729 CLog::Log(LOGERROR
, "CCurlFile::CReadState::{} - ({}) Failed: {}({})", __FUNCTION__
,
1730 fmt::ptr(this), g_curlInterface
.easy_strerror(msg
->data
.result
),
1734 if ( (msg
->data
.result
== CURLE_OPERATION_TIMEDOUT
||
1735 msg
->data
.result
== CURLE_PARTIAL_FILE
||
1736 msg
->data
.result
== CURLE_COULDNT_CONNECT
||
1737 msg
->data
.result
== CURLE_RECV_ERROR
) &&
1740 bRetryNow
= false; // Leave it to caller whether the operation is retried
1743 else if ( (msg
->data
.result
== CURLE_HTTP_RANGE_ERROR
||
1744 httpCode
== 416 /* = Requested Range Not Satisfiable */ ||
1745 httpCode
== 406 /* = Not Acceptable (fixes issues with non compliant HDHomerun servers */) &&
1750 // If server returns a (possible) range error, disable range and retry (handled below)
1753 m_sendRange
= false;
1757 // For all other errors, abort the operation
1758 return FILLBUFFER_FAIL
;
1763 // Check for an actual error, if not, just return no-data
1764 if (!bError
&& !m_bLastError
)
1765 return FILLBUFFER_NO_DATA
;
1768 if (m_multiHandle
&& m_easyHandle
)
1769 g_curlInterface
.multi_remove_handle(m_multiHandle
, m_easyHandle
);
1771 // Reset all the stuff like we would in Disconnect()
1773 free(m_overflowBuffer
);
1774 m_overflowBuffer
= NULL
;
1776 m_bLastError
= true; // Flag error for the next run
1778 // Retry immediately or leave it up to the caller?
1779 if ((m_bRetry
&& retry
< CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_curlretries
) || (bRetryNow
&& retry
== 0))
1783 // Connect + seek to current position (again)
1785 g_curlInterface
.multi_add_handle(m_multiHandle
, m_easyHandle
);
1787 CLog::Log(LOGWARNING
, "CCurlFile::CReadState::{} - ({}) Reconnect, (re)try {}",
1788 __FUNCTION__
, fmt::ptr(this), retry
);
1790 // Return to the beginning of the loop:
1794 return FILLBUFFER_NO_DATA
; // We failed but flag no data to caller, so it can retry the operation
1796 return FILLBUFFER_FAIL
;
1799 // We've finished out first loop
1800 if(m_bFirstLoop
&& m_buffer
.getMaxReadSize() > 0)
1801 m_bFirstLoop
= false;
1803 // No error this run
1804 m_bLastError
= false;
1815 // get file descriptors from the transfers
1816 g_curlInterface
.multi_fdset(m_multiHandle
, &fdread
, &fdwrite
, &fdexcep
, &maxfd
);
1819 if (CURLM_OK
!= g_curlInterface
.multi_timeout(m_multiHandle
, &timeout
) || timeout
== -1 || timeout
< 200)
1822 XbmcThreads::EndTime
<> endTime
{std::chrono::milliseconds(timeout
)};
1827 /* On success the value of maxfd is guaranteed to be >= -1. We call
1828 * select(maxfd + 1, ...); specially in case of (maxfd == -1) there are
1829 * no fds ready yet so we call select(0, ...) --or Sleep() on Windows--
1830 * to sleep 100ms, which is the minimum suggested value in the
1831 * curl_multi_fdset() doc.
1835 #ifdef TARGET_WINDOWS
1836 /* Windows does not support using select() for sleeping without a dummy
1837 * socket. Instead use Windows' Sleep() and sleep for 100ms which is the
1838 * minimum suggested value in the curl_multi_fdset() doc.
1840 KODI::TIME::Sleep(100ms
);
1843 /* Portable sleep for platforms other than Windows. */
1844 struct timeval wait
= { 0, 100 * 1000 }; /* 100ms */
1845 rc
= select(0, NULL
, NULL
, NULL
, &wait
);
1850 unsigned int time_left
= endTime
.GetTimeLeft().count();
1851 struct timeval wait
= { (int)time_left
/ 1000, ((int)time_left
% 1000) * 1000 };
1852 rc
= select(maxfd
+ 1, &fdread
, &fdwrite
, &fdexcep
, &wait
);
1854 #ifdef TARGET_WINDOWS
1855 } while(rc
== SOCKET_ERROR
&& WSAGetLastError() == WSAEINTR
);
1857 } while(rc
== SOCKET_ERROR
&& errno
== EINTR
);
1860 if(rc
== SOCKET_ERROR
)
1862 #ifdef TARGET_WINDOWS
1864 strerror_s(buf
, 256, WSAGetLastError());
1865 CLog::Log(LOGERROR
, "CCurlFile::CReadState::{} - ({}) Failed with socket error:{}",
1866 __FUNCTION__
, fmt::ptr(this), buf
);
1868 char const * str
= strerror(errno
);
1869 CLog::Log(LOGERROR
, "CCurlFile::CReadState::{} - ({}) Failed with socket error:{}",
1870 __FUNCTION__
, fmt::ptr(this), str
);
1873 return FILLBUFFER_FAIL
;
1877 case CURLM_CALL_MULTI_PERFORM
:
1879 // we don't keep calling here as that can easily overwrite our buffer which we want to avoid
1880 // docs says we should call it soon after, but as long as we are reading data somewhere
1881 // this aught to be soon enough. should stay in socket otherwise
1888 "CCurlFile::CReadState::{} - ({}) Multi perform failed with code {}, aborting",
1889 __FUNCTION__
, fmt::ptr(this), result
);
1890 return FILLBUFFER_FAIL
;
1895 return FILLBUFFER_OK
;
1898 void CCurlFile::CReadState::SetReadBuffer(const void* lpBuf
, int64_t uiBufSize
)
1900 m_readBuffer
= const_cast<char*>((const char*)lpBuf
);
1901 m_fileSize
= uiBufSize
;
1905 void CCurlFile::ClearRequestHeaders()
1907 m_requestheaders
.clear();
1910 void CCurlFile::SetRequestHeader(const std::string
& header
, const std::string
& value
)
1912 m_requestheaders
[header
] = value
;
1915 void CCurlFile::SetRequestHeader(const std::string
& header
, long value
)
1917 m_requestheaders
[header
] = std::to_string(value
);
1920 std::string
CCurlFile::GetURL(void)
1925 std::string
CCurlFile::GetRedirectURL()
1927 return GetInfoString(CURLINFO_REDIRECT_URL
);
1930 std::string
CCurlFile::GetInfoString(int infoType
)
1933 CURLcode result
= g_curlInterface
.easy_getinfo(m_state
->m_easyHandle
, static_cast<CURLINFO
> (infoType
), &info
);
1934 if (result
!= CURLE_OK
)
1937 "CCurlFile::{} - <{}> Info string request for type {} failed with result code {}",
1938 __FUNCTION__
, CURL::GetRedacted(m_url
), infoType
, result
);
1941 return (info
? info
: "");
1944 /* STATIC FUNCTIONS */
1945 bool CCurlFile::GetHttpHeader(const CURL
&url
, CHttpHeader
&headers
)
1950 if(file
.Stat(url
, NULL
) == 0)
1952 headers
= file
.GetHttpHeader();
1959 CLog::Log(LOGERROR
, "CCurlFile::{} - <{}> Exception thrown while trying to retrieve header",
1960 __FUNCTION__
, url
.GetRedacted());
1965 bool CCurlFile::GetMimeType(const CURL
&url
, std::string
&content
, const std::string
&useragent
)
1968 if (!useragent
.empty())
1969 file
.SetUserAgent(useragent
);
1971 struct __stat64 buffer
;
1972 std::string redactUrl
= url
.GetRedacted();
1973 if( file
.Stat(url
, &buffer
) == 0 )
1975 if (buffer
.st_mode
== _S_IFDIR
)
1976 content
= "x-directory/normal";
1978 content
= file
.GetProperty(XFILE::FILE_PROPERTY_MIME_TYPE
);
1979 CLog::Log(LOGDEBUG
, "CCurlFile::{} - <{}> -> {}", __FUNCTION__
, redactUrl
, content
);
1982 CLog::Log(LOGDEBUG
, "CCurlFile::{} - <{}> -> failed", __FUNCTION__
, redactUrl
);
1987 bool CCurlFile::GetContentType(const CURL
&url
, std::string
&content
, const std::string
&useragent
)
1990 if (!useragent
.empty())
1991 file
.SetUserAgent(useragent
);
1993 struct __stat64 buffer
;
1994 std::string redactUrl
= url
.GetRedacted();
1995 if (file
.Stat(url
, &buffer
) == 0)
1997 if (buffer
.st_mode
== _S_IFDIR
)
1998 content
= "x-directory/normal";
2000 content
= file
.GetProperty(XFILE::FILE_PROPERTY_CONTENT_TYPE
, "");
2001 CLog::Log(LOGDEBUG
, "CCurlFile::{} - <{}> -> {}", __FUNCTION__
, redactUrl
, content
);
2004 CLog::Log(LOGDEBUG
, "CCurlFile::{} - <{}> -> failed", __FUNCTION__
, redactUrl
);
2009 bool CCurlFile::GetCookies(const CURL
&url
, std::string
&cookies
)
2011 std::string cookiesStr
;
2012 curl_slist
* curlCookies
;
2013 CURL_HANDLE
* easyHandle
;
2016 // get the cookies list
2017 g_curlInterface
.easy_acquire(url
.GetProtocol().c_str(),
2018 url
.GetHostName().c_str(),
2019 &easyHandle
, &multiHandle
);
2020 if (CURLE_OK
== g_curlInterface
.easy_getinfo(easyHandle
, CURLINFO_COOKIELIST
, &curlCookies
))
2022 // iterate over each cookie and format it into an RFC 2109 formatted Set-Cookie string
2023 curl_slist
* curlCookieIter
= curlCookies
;
2024 while(curlCookieIter
)
2026 // tokenize the CURL cookie string
2027 std::vector
<std::string
> valuesVec
;
2028 StringUtils::Tokenize(curlCookieIter
->data
, valuesVec
, "\t");
2030 // ensure the length is valid
2031 if (valuesVec
.size() < 7)
2033 CLog::Log(LOGERROR
, "CCurlFile::{} - <{}> Invalid cookie: '{}'", __FUNCTION__
,
2034 url
.GetRedacted(), curlCookieIter
->data
);
2035 curlCookieIter
= curlCookieIter
->next
;
2039 // create a http-header formatted cookie string
2040 std::string cookieStr
= valuesVec
[5] + "=" + valuesVec
[6] +
2041 "; path=" + valuesVec
[2] +
2042 "; domain=" + valuesVec
[0];
2044 // append this cookie to the string containing all cookies
2045 if (!cookiesStr
.empty())
2047 cookiesStr
+= cookieStr
;
2049 // move on to the next cookie
2050 curlCookieIter
= curlCookieIter
->next
;
2053 // free the curl cookies
2054 g_curlInterface
.slist_free_all(curlCookies
);
2056 // release our handles
2057 g_curlInterface
.easy_release(&easyHandle
, &multiHandle
);
2059 // if we have a non-empty cookie string, return it
2060 if (!cookiesStr
.empty())
2062 cookies
= cookiesStr
;
2067 // no cookies to return
2071 int CCurlFile::IoControl(EIoControl request
, void* param
)
2073 if (request
== IOCTRL_SEEK_POSSIBLE
)
2074 return m_seekable
? 1 : 0;
2076 if (request
== IOCTRL_SET_RETRY
)
2078 m_allowRetry
= *(bool*) param
;
2085 const std::string
CCurlFile::GetProperty(XFILE::FileProperty type
, const std::string
&name
) const
2089 case FILE_PROPERTY_RESPONSE_PROTOCOL
:
2090 return m_state
->m_httpheader
.GetProtoLine();
2091 case FILE_PROPERTY_RESPONSE_HEADER
:
2092 return m_state
->m_httpheader
.GetValue(name
);
2093 case FILE_PROPERTY_CONTENT_TYPE
:
2094 return m_state
->m_httpheader
.GetValue("content-type");
2095 case FILE_PROPERTY_CONTENT_CHARSET
:
2096 return m_state
->m_httpheader
.GetCharset();
2097 case FILE_PROPERTY_MIME_TYPE
:
2098 return m_state
->m_httpheader
.GetMimeType();
2099 case FILE_PROPERTY_EFFECTIVE_URL
:
2101 char *url
= nullptr;
2102 g_curlInterface
.easy_getinfo(m_state
->m_easyHandle
, CURLINFO_EFFECTIVE_URL
, &url
);
2103 return url
? url
: "";
2110 const std::vector
<std::string
> CCurlFile::GetPropertyValues(XFILE::FileProperty type
, const std::string
&name
) const
2112 if (type
== FILE_PROPERTY_RESPONSE_HEADER
)
2114 return m_state
->m_httpheader
.GetValues(name
);
2116 std::vector
<std::string
> values
;
2117 std::string value
= GetProperty(type
, name
);
2120 values
.emplace_back(value
);
2125 double CCurlFile::GetDownloadSpeed()
2127 #if LIBCURL_VERSION_NUM >= 0x073a00 // 0.7.58.0
2129 if (g_curlInterface
.easy_getinfo(m_state
->m_easyHandle
, CURLINFO_SPEED_DOWNLOAD
, &speed
) == CURLE_OK
)
2132 double time
= 0.0, size
= 0.0;
2133 if (g_curlInterface
.easy_getinfo(m_state
->m_easyHandle
, CURLINFO_TOTAL_TIME
, &time
) == CURLE_OK
2134 && g_curlInterface
.easy_getinfo(m_state
->m_easyHandle
, CURLINFO_SIZE_DOWNLOAD
, &size
) == CURLE_OK