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
))
368 #if LIBCURL_VERSION_NUM >= 0x073700 // CURL_AT_LEAST_VERSION(0, 7, 55)
371 g_curlInterface
.easy_getinfo(m_easyHandle
, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T
, &length
))
374 if (CURLE_OK
== g_curlInterface
.easy_getinfo(m_easyHandle
, CURLINFO_CONTENT_LENGTH_DOWNLOAD
, &length
))
379 m_fileSize
= m_filePos
+ (int64_t)length
;
383 if (CURLE_OK
== g_curlInterface
.easy_getinfo(m_easyHandle
, CURLINFO_RESPONSE_CODE
, &response
))
389 void CCurlFile::CReadState::Disconnect()
391 if(m_multiHandle
&& m_easyHandle
)
392 g_curlInterface
.multi_remove_handle(m_multiHandle
, m_easyHandle
);
395 free(m_overflowBuffer
);
396 m_overflowBuffer
= NULL
;
404 if( m_curlHeaderList
)
405 g_curlInterface
.slist_free_all(m_curlHeaderList
);
406 m_curlHeaderList
= NULL
;
408 if( m_curlAliasList
)
409 g_curlInterface
.slist_free_all(m_curlAliasList
);
410 m_curlAliasList
= NULL
;
414 CCurlFile::~CCurlFile()
421 CCurlFile::CCurlFile()
422 : m_overflowBuffer(NULL
)
427 m_multisession
= true;
429 m_connecttimeout
= 0;
433 m_bufferSize
= 32768;
434 m_postdataset
= false;
435 m_state
= new CReadState();
439 m_acceptCharset
= "UTF-8,*;q=0.8"; /* prefer UTF-8 if available */
441 m_acceptencoding
= "all"; /* Accept all supported encoding by default */
444 //Has to be called before Open()
445 void CCurlFile::SetBufferSize(unsigned int size
)
450 void CCurlFile::Close()
452 if (m_opened
&& m_forWrite
&& !m_inError
)
455 m_state
->Disconnect();
468 g_curlInterface
.slist_free_all(m_dnsCacheList
);
469 m_dnsCacheList
= nullptr;
472 void CCurlFile::SetCommonOptions(CReadState
* state
, bool failOnError
/* = true */)
474 CURL_HANDLE
* h
= state
->m_easyHandle
;
476 g_curlInterface
.easy_reset(h
);
478 g_curlInterface
.easy_setopt(h
, CURLOPT_DEBUGFUNCTION
, debug_callback
);
480 if( CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_logLevel
>= LOG_LEVEL_DEBUG
)
481 g_curlInterface
.easy_setopt(h
, CURLOPT_VERBOSE
, CURL_ON
);
483 g_curlInterface
.easy_setopt(h
, CURLOPT_VERBOSE
, CURL_OFF
);
485 g_curlInterface
.easy_setopt(h
, CURLOPT_WRITEDATA
, state
);
486 g_curlInterface
.easy_setopt(h
, CURLOPT_WRITEFUNCTION
, write_callback
);
488 g_curlInterface
.easy_setopt(h
, CURLOPT_READDATA
, state
);
489 g_curlInterface
.easy_setopt(h
, CURLOPT_READFUNCTION
, read_callback
);
492 g_curlInterface
.easy_setopt(h
, CURLOPT_RESOLVE
, m_dnsCacheList
);
494 // make sure headers are separated from the data stream
495 g_curlInterface
.easy_setopt(h
, CURLOPT_WRITEHEADER
, state
);
496 g_curlInterface
.easy_setopt(h
, CURLOPT_HEADERFUNCTION
, header_callback
);
497 g_curlInterface
.easy_setopt(h
, CURLOPT_HEADER
, CURL_OFF
);
499 g_curlInterface
.easy_setopt(h
, CURLOPT_FTP_USE_EPSV
, 0); // turn off epsv
501 // Allow us to follow redirects
502 g_curlInterface
.easy_setopt(h
, CURLOPT_FOLLOWLOCATION
, m_redirectlimit
!= 0);
503 g_curlInterface
.easy_setopt(h
, CURLOPT_MAXREDIRS
, m_redirectlimit
);
505 // Enable cookie engine for current handle
506 g_curlInterface
.easy_setopt(h
, CURLOPT_COOKIEFILE
, "");
508 // Set custom cookie if requested
509 if (!m_cookie
.empty())
510 g_curlInterface
.easy_setopt(h
, CURLOPT_COOKIE
, m_cookie
.c_str());
512 g_curlInterface
.easy_setopt(h
, CURLOPT_COOKIELIST
, "FLUSH");
514 // When using multiple threads you should set the CURLOPT_NOSIGNAL option to
515 // TRUE for all handles. Everything will work fine except that timeouts are not
516 // honored during the DNS lookup - which you can work around by building libcurl
517 // with c-ares support. c-ares is a library that provides asynchronous name
518 // resolves. Unfortunately, c-ares does not yet support IPv6.
519 g_curlInterface
.easy_setopt(h
, CURLOPT_NOSIGNAL
, CURL_ON
);
523 // not interested in failed requests
524 g_curlInterface
.easy_setopt(h
, CURLOPT_FAILONERROR
, 1);
527 // enable support for icecast / shoutcast streams
528 if ( NULL
== state
->m_curlAliasList
)
529 // m_curlAliasList is used only by this one place, but SetCommonOptions can
530 // be called multiple times, only append to list if it's empty.
531 state
->m_curlAliasList
= g_curlInterface
.slist_append(state
->m_curlAliasList
, "ICY 200 OK");
532 g_curlInterface
.easy_setopt(h
, CURLOPT_HTTP200ALIASES
, state
->m_curlAliasList
);
535 g_curlInterface
.easy_setopt(h
, CURLOPT_SSL_VERIFYPEER
, 0);
537 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_URL
, m_url
.c_str());
538 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_TRANSFERTEXT
, CURL_OFF
);
540 // setup POST data if it is set (and it may be empty)
543 g_curlInterface
.easy_setopt(h
, CURLOPT_POST
, 1 );
544 g_curlInterface
.easy_setopt(h
, CURLOPT_POSTFIELDSIZE
, m_postdata
.length());
545 g_curlInterface
.easy_setopt(h
, CURLOPT_POSTFIELDS
, m_postdata
.c_str());
548 // setup Referer header if needed
549 if (!m_referer
.empty())
550 g_curlInterface
.easy_setopt(h
, CURLOPT_REFERER
, m_referer
.c_str());
553 g_curlInterface
.easy_setopt(h
, CURLOPT_REFERER
, NULL
);
554 // Do not send referer header on redirects (same behaviour as ffmpeg and browsers)
555 g_curlInterface
.easy_setopt(h
, CURLOPT_AUTOREFERER
, CURL_OFF
);
558 // setup any requested authentication
559 if( !m_ftpauth
.empty() )
561 g_curlInterface
.easy_setopt(h
, CURLOPT_FTP_SSL
, CURLFTPSSL_TRY
);
562 if( m_ftpauth
== "any" )
563 g_curlInterface
.easy_setopt(h
, CURLOPT_FTPSSLAUTH
, CURLFTPAUTH_DEFAULT
);
564 else if( m_ftpauth
== "ssl" )
565 g_curlInterface
.easy_setopt(h
, CURLOPT_FTPSSLAUTH
, CURLFTPAUTH_SSL
);
566 else if( m_ftpauth
== "tls" )
567 g_curlInterface
.easy_setopt(h
, CURLOPT_FTPSSLAUTH
, CURLFTPAUTH_TLS
);
570 // setup requested http authentication method
571 bool bAuthSet
= false;
572 if(!m_httpauth
.empty())
575 if( m_httpauth
== "any" )
576 g_curlInterface
.easy_setopt(h
, CURLOPT_HTTPAUTH
, CURLAUTH_ANY
);
577 else if( m_httpauth
== "anysafe" )
578 g_curlInterface
.easy_setopt(h
, CURLOPT_HTTPAUTH
, CURLAUTH_ANYSAFE
);
579 else if( m_httpauth
== "digest" )
580 g_curlInterface
.easy_setopt(h
, CURLOPT_HTTPAUTH
, CURLAUTH_DIGEST
);
581 else if( m_httpauth
== "ntlm" )
582 g_curlInterface
.easy_setopt(h
, CURLOPT_HTTPAUTH
, CURLAUTH_NTLM
);
587 // set username and password for current handle
588 if (!m_username
.empty())
590 g_curlInterface
.easy_setopt(h
, CURLOPT_USERNAME
, m_username
.c_str());
591 if (!m_password
.empty())
592 g_curlInterface
.easy_setopt(h
, CURLOPT_PASSWORD
, m_password
.c_str());
595 g_curlInterface
.easy_setopt(h
, CURLOPT_HTTPAUTH
, CURLAUTH_ANY
);
598 // allow passive mode for ftp
599 if( m_ftpport
.length() > 0 )
600 g_curlInterface
.easy_setopt(h
, CURLOPT_FTPPORT
, m_ftpport
.c_str());
602 g_curlInterface
.easy_setopt(h
, CURLOPT_FTPPORT
, NULL
);
604 // allow curl to not use the ip address in the returned pasv response
606 g_curlInterface
.easy_setopt(h
, CURLOPT_FTP_SKIP_PASV_IP
, 0);
608 g_curlInterface
.easy_setopt(h
, CURLOPT_FTP_SKIP_PASV_IP
, 1);
610 // setup Accept-Encoding if requested
611 if (m_acceptencoding
.length() > 0)
612 g_curlInterface
.easy_setopt(h
, CURLOPT_ACCEPT_ENCODING
, m_acceptencoding
== "all" ? "" : m_acceptencoding
.c_str());
614 if (!m_acceptCharset
.empty())
615 SetRequestHeader("Accept-Charset", m_acceptCharset
);
617 if (m_userAgent
.length() > 0)
618 g_curlInterface
.easy_setopt(h
, CURLOPT_USERAGENT
, m_userAgent
.c_str());
619 else /* set some default agent as shoutcast doesn't return proper stuff otherwise */
620 g_curlInterface
.easy_setopt(h
, CURLOPT_USERAGENT
, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_userAgent
.c_str());
622 if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_curlDisableIPV6
)
623 g_curlInterface
.easy_setopt(h
, CURLOPT_IPRESOLVE
, CURL_IPRESOLVE_V4
);
625 if (!m_proxyhost
.empty())
627 g_curlInterface
.easy_setopt(h
, CURLOPT_PROXYTYPE
, proxyType2CUrlProxyType
[m_proxytype
]);
629 const std::string hostport
= m_proxyhost
+ StringUtils::Format(":{}", m_proxyport
);
630 g_curlInterface
.easy_setopt(h
, CURLOPT_PROXY
, hostport
.c_str());
632 std::string userpass
;
634 if (!m_proxyuser
.empty() && !m_proxypassword
.empty())
635 userpass
= CURL::Encode(m_proxyuser
) + ":" + CURL::Encode(m_proxypassword
);
637 if (!userpass
.empty())
638 g_curlInterface
.easy_setopt(h
, CURLOPT_PROXYUSERPWD
, userpass
.c_str());
640 if (m_customrequest
.length() > 0)
641 g_curlInterface
.easy_setopt(h
, CURLOPT_CUSTOMREQUEST
, m_customrequest
.c_str());
643 if (m_connecttimeout
== 0)
644 m_connecttimeout
= CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_curlconnecttimeout
;
646 // set our timeouts, we abort connection after m_timeout, and reads after no data for m_timeout seconds
647 g_curlInterface
.easy_setopt(h
, CURLOPT_CONNECTTIMEOUT
, m_connecttimeout
);
649 // We abort in case we transfer less than 1byte/second
650 g_curlInterface
.easy_setopt(h
, CURLOPT_LOW_SPEED_LIMIT
, 1);
652 if (m_lowspeedtime
== 0)
653 m_lowspeedtime
= CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_curllowspeedtime
;
655 // Set the lowspeed time very low as it seems Curl takes much longer to detect a lowspeed condition
656 g_curlInterface
.easy_setopt(h
, CURLOPT_LOW_SPEED_TIME
, m_lowspeedtime
);
658 // enable tcp keepalive
659 if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_curlKeepAliveInterval
> 0)
661 g_curlInterface
.easy_setopt(h
, CURLOPT_TCP_KEEPALIVE
, 1L);
662 g_curlInterface
.easy_setopt(
663 h
, CURLOPT_TCP_KEEPIDLE
,
664 CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_curlKeepAliveInterval
/ 2);
665 g_curlInterface
.easy_setopt(
666 h
, CURLOPT_TCP_KEEPINTVL
,
667 CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_curlKeepAliveInterval
);
670 // Setup allowed TLS/SSL ciphers. New versions of cURL may deprecate things that are still in use.
671 if (!m_cipherlist
.empty())
672 g_curlInterface
.easy_setopt(h
, CURLOPT_SSL_CIPHER_LIST
, m_cipherlist
.c_str());
674 if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_curlDisableHTTP2
)
675 g_curlInterface
.easy_setopt(h
, CURLOPT_HTTP_VERSION
, CURL_HTTP_VERSION_1_1
);
677 // enable HTTP2 support. default: CURL_HTTP_VERSION_1_1. Curl >= 7.62.0 defaults to CURL_HTTP_VERSION_2TLS
678 g_curlInterface
.easy_setopt(h
, CURLOPT_HTTP_VERSION
, CURL_HTTP_VERSION_2TLS
);
680 // set CA bundle file
681 std::string caCert
= CSpecialProtocol::TranslatePath(
682 CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_caTrustFile
);
683 #ifdef TARGET_WINDOWS_STORE
684 // UWP Curl - Setting CURLOPT_CAINFO with a valid cacert file path is required for UWP
685 g_curlInterface
.easy_setopt(h
, CURLOPT_CAINFO
, "system\\certs\\cacert.pem");
687 if (!caCert
.empty() && XFILE::CFile::Exists(caCert
))
688 g_curlInterface
.easy_setopt(h
, CURLOPT_CAINFO
, caCert
.c_str());
691 void CCurlFile::SetRequestHeaders(CReadState
* state
)
693 if(state
->m_curlHeaderList
)
695 g_curlInterface
.slist_free_all(state
->m_curlHeaderList
);
696 state
->m_curlHeaderList
= NULL
;
699 for (const auto& it
: m_requestheaders
)
701 std::string buffer
= it
.first
+ ": " + it
.second
;
702 state
->m_curlHeaderList
= g_curlInterface
.slist_append(state
->m_curlHeaderList
, buffer
.c_str());
705 // add user defined headers
706 if (state
->m_easyHandle
)
707 g_curlInterface
.easy_setopt(state
->m_easyHandle
, CURLOPT_HTTPHEADER
, state
->m_curlHeaderList
);
710 void CCurlFile::SetCorrectHeaders(CReadState
* state
)
712 CHttpHeader
& h
= state
->m_httpheader
;
713 /* workaround for shoutcast server which doesn't set content type on standard mp3 */
714 if( h
.GetMimeType().empty() )
716 if( !h
.GetValue("icy-notice1").empty()
717 || !h
.GetValue("icy-name").empty()
718 || !h
.GetValue("icy-br").empty() )
719 h
.AddParam("Content-Type", "audio/mpeg");
722 /* hack for google video */
723 if (StringUtils::EqualsNoCase(h
.GetMimeType(),"text/html")
724 && !h
.GetValue("Content-Disposition").empty() )
726 std::string strValue
= h
.GetValue("Content-Disposition");
727 if (strValue
.find("filename=") != std::string::npos
&&
728 strValue
.find(".flv") != std::string::npos
)
729 h
.AddParam("Content-Type", "video/flv");
733 void CCurlFile::ParseAndCorrectUrl(CURL
&url2
)
735 std::string strProtocol
= url2
.GetTranslatedProtocol();
736 url2
.SetProtocol(strProtocol
);
738 // lookup host in DNS cache
739 std::string resolvedHost
;
740 if (CDNSNameCache::GetCached(url2
.GetHostName(), resolvedHost
))
742 struct curl_slist
* tempCache
;
743 int entryPort
= url2
.GetPort();
747 if (strProtocol
== "http")
749 else if (strProtocol
== "https")
751 else if (strProtocol
== "ftp")
753 else if (strProtocol
== "ftps")
757 std::string entryString
=
758 url2
.GetHostName() + ":" + std::to_string(entryPort
) + ":" + resolvedHost
;
759 tempCache
= g_curlInterface
.slist_append(m_dnsCacheList
, entryString
.c_str());
762 m_dnsCacheList
= tempCache
;
765 if( url2
.IsProtocol("ftp")
766 || url2
.IsProtocol("ftps") )
768 // we was using url options for urls, keep the old code work and warning
769 if (!url2
.GetOptions().empty())
771 CLog::Log(LOGWARNING
,
772 "CCurlFile::{} - <{}> FTP url option is deprecated, please switch to use protocol "
775 __FUNCTION__
, url2
.GetRedacted());
776 url2
.SetProtocolOptions(url2
.GetOptions().substr(1));
777 /* ftp has no options */
781 /* this is ugly, depending on where we get */
782 /* the link from, it may or may not be */
783 /* url encoded. if handed from ftpdirectory */
784 /* it won't be so let's handle that case */
786 std::string
filename(url2
.GetFileName());
787 std::vector
<std::string
> array
;
789 // if server sent us the filename in non-utf8, we need send back with same encoding.
790 if (url2
.GetProtocolOption("utf8") == "0")
791 g_charsetConverter
.utf8ToStringCharset(filename
);
793 //! @todo create a tokenizer that doesn't skip empty's
794 StringUtils::Tokenize(filename
, array
, "/");
796 for (std::vector
<std::string
>::iterator it
= array
.begin(); it
!= array
.end(); ++it
)
798 if(it
!= array
.begin())
801 filename
+= CURL::Encode(*it
);
804 /* make sure we keep slashes */
805 if(StringUtils::EndsWith(url2
.GetFileName(), "/"))
808 url2
.SetFileName(filename
);
811 if (url2
.HasProtocolOption("auth"))
813 m_ftpauth
= url2
.GetProtocolOption("auth");
814 StringUtils::ToLower(m_ftpauth
);
815 if(m_ftpauth
.empty())
819 if (url2
.HasProtocolOption("active"))
821 m_ftpport
= url2
.GetProtocolOption("active");
822 if(m_ftpport
.empty())
825 if (url2
.HasProtocolOption("verifypeer"))
827 if (url2
.GetProtocolOption("verifypeer") == "false")
828 m_verifyPeer
= false;
830 m_ftppasvip
= url2
.HasProtocolOption("pasvip") && url2
.GetProtocolOption("pasvip") != "0";
832 else if(url2
.IsProtocol("http") ||
833 url2
.IsProtocol("https"))
835 std::shared_ptr
<CSettings
> s
= CServiceBroker::GetSettingsComponent()->GetSettings();
839 if (!url2
.IsLocalHost() &&
840 m_proxyhost
.empty() &&
841 s
->GetBool(CSettings::SETTING_NETWORK_USEHTTPPROXY
) &&
842 !s
->GetString(CSettings::SETTING_NETWORK_HTTPPROXYSERVER
).empty() &&
843 s
->GetInt(CSettings::SETTING_NETWORK_HTTPPROXYPORT
) > 0)
845 m_proxytype
= (ProxyType
)s
->GetInt(CSettings::SETTING_NETWORK_HTTPPROXYTYPE
);
846 m_proxyhost
= s
->GetString(CSettings::SETTING_NETWORK_HTTPPROXYSERVER
);
847 m_proxyport
= s
->GetInt(CSettings::SETTING_NETWORK_HTTPPROXYPORT
);
848 m_proxyuser
= s
->GetString(CSettings::SETTING_NETWORK_HTTPPROXYUSERNAME
);
849 m_proxypassword
= s
->GetString(CSettings::SETTING_NETWORK_HTTPPROXYPASSWORD
);
850 CLog::LogFC(LOGDEBUG
, LOGCURL
, "<{}> Using proxy {}, type {}", url2
.GetRedacted(),
851 m_proxyhost
, proxyType2CUrlProxyType
[m_proxytype
]);
854 // get username and password
855 m_username
= url2
.GetUserName();
856 m_password
= url2
.GetPassWord();
858 // handle any protocol options
859 std::map
<std::string
, std::string
> options
;
860 url2
.GetProtocolOptions(options
);
861 if (!options
.empty())
864 for (const auto& it
: options
)
866 std::string name
= it
.first
;
867 StringUtils::ToLower(name
);
868 const std::string
& value
= it
.second
;
873 StringUtils::ToLower(m_httpauth
);
874 if(m_httpauth
.empty())
877 else if (name
== "referer")
879 else if (name
== "user-agent")
881 else if (name
== "cookie")
883 else if (name
== "acceptencoding" || name
== "encoding")
884 SetAcceptEncoding(value
);
885 else if (name
== "noshout" && value
== "true")
887 else if (name
== "seekable" && value
== "0")
889 else if (name
== "accept-charset")
890 SetAcceptCharset(value
);
891 else if (name
== "sslcipherlist")
892 m_cipherlist
= value
;
893 else if (name
== "connection-timeout")
894 m_connecttimeout
= strtol(value
.c_str(), NULL
, 10);
895 else if (name
== "failonerror")
896 m_failOnError
= value
== "true";
897 else if (name
== "redirect-limit")
898 m_redirectlimit
= strtol(value
.c_str(), NULL
, 10);
899 else if (name
== "postdata")
901 m_postdata
= Base64::Decode(value
);
902 m_postdataset
= true;
904 else if (name
== "active-remote")// needed for DACP!
906 SetRequestHeader(it
.first
, value
);
908 else if (name
== "customrequest")
910 SetCustomRequest(value
);
912 else if (name
== "verifypeer")
914 if (value
== "false")
915 m_verifyPeer
= false;
919 if (name
.length() > 0 && name
[0] == '!')
921 SetRequestHeader(it
.first
.substr(1), value
);
922 CLog::LogFC(LOGDEBUG
, LOGCURL
, "<{}> Adding custom header option '{}: ***********'",
923 url2
.GetRedacted(), it
.first
.substr(1));
927 SetRequestHeader(it
.first
, value
);
928 if (name
== "authorization")
929 CLog::LogFC(LOGDEBUG
, LOGCURL
, "<{}> Adding custom header option '{}: ***********'",
930 url2
.GetRedacted(), it
.first
);
932 CLog::LogFC(LOGDEBUG
, LOGCURL
, "<{}> Adding custom header option '{}: {}'",
933 url2
.GetRedacted(), it
.first
, value
);
940 // Unset the protocol options to have an url without protocol options
941 url2
.SetProtocolOptions("");
943 if (m_username
.length() > 0 && m_password
.length() > 0)
944 m_url
= url2
.GetWithoutUserDetails();
949 bool CCurlFile::Post(const std::string
& strURL
, const std::string
& strPostData
, std::string
& strHTML
)
951 m_postdata
= strPostData
;
952 m_postdataset
= true;
953 return Service(strURL
, strHTML
);
956 bool CCurlFile::Get(const std::string
& strURL
, std::string
& strHTML
)
959 m_postdataset
= false;
960 return Service(strURL
, strHTML
);
963 bool CCurlFile::Service(const std::string
& strURL
, std::string
& strHTML
)
965 const CURL
pathToUrl(strURL
);
968 if (ReadData(strHTML
))
978 bool CCurlFile::ReadData(std::string
& strHTML
)
983 while( (size_read
= Read(buffer
, sizeof(buffer
)-1) ) > 0 )
985 buffer
[size_read
] = 0;
986 strHTML
.append(buffer
, size_read
);
988 if (m_state
->m_cancelled
)
993 bool CCurlFile::Download(const std::string
& strURL
, const std::string
& strFileName
, unsigned int* pdwSize
)
995 CLog::Log(LOGINFO
, "CCurlFile::{} - {}->{}", __FUNCTION__
, CURL::GetRedacted(strURL
),
999 if (!Get(strURL
, strData
))
1003 if (!file
.OpenForWrite(strFileName
, true))
1005 CLog::Log(LOGERROR
, "CCurlFile::{} - <{}> Unable to open file for write: {} ({})", __FUNCTION__
,
1006 CURL::GetRedacted(strURL
), strFileName
, GetLastError());
1009 ssize_t written
= 0;
1010 if (!strData
.empty())
1011 written
= file
.Write(strData
.c_str(), strData
.size());
1013 if (pdwSize
!= NULL
)
1014 *pdwSize
= written
> 0 ? written
: 0;
1016 return written
== static_cast<ssize_t
>(strData
.size());
1019 // Detect whether we are "online" or not! Very simple and dirty!
1020 bool CCurlFile::IsInternet()
1022 CURL
url("http://www.msftconnecttest.com/connecttest.txt");
1023 bool found
= Exists(url
);
1028 url
.Parse("http://www.w3.org/");
1029 found
= Exists(url
);
1036 void CCurlFile::Cancel()
1038 m_state
->m_cancelled
= true;
1040 KODI::TIME::Sleep(1ms
);
1043 void CCurlFile::Reset()
1045 m_state
->m_cancelled
= false;
1048 void CCurlFile::SetProxy(const std::string
&type
, const std::string
&host
,
1049 uint16_t port
, const std::string
&user
, const std::string
&password
)
1051 m_proxytype
= CCurlFile::PROXY_HTTP
;
1053 m_proxytype
= CCurlFile::PROXY_HTTP
;
1054 else if (type
== "https")
1055 m_proxytype
= CCurlFile::PROXY_HTTPS
;
1056 else if (type
== "socks4")
1057 m_proxytype
= CCurlFile::PROXY_SOCKS4
;
1058 else if (type
== "socks4a")
1059 m_proxytype
= CCurlFile::PROXY_SOCKS4A
;
1060 else if (type
== "socks5")
1061 m_proxytype
= CCurlFile::PROXY_SOCKS5
;
1062 else if (type
== "socks5-remote")
1063 m_proxytype
= CCurlFile::PROXY_SOCKS5_REMOTE
;
1065 CLog::Log(LOGERROR
, "CCurFile::{} - <{}> Invalid proxy type \"{}\"", __FUNCTION__
,
1066 CURL::GetRedacted(m_url
), type
);
1070 m_proxypassword
= password
;
1073 bool CCurlFile::Open(const CURL
& url
)
1079 ParseAndCorrectUrl(url2
);
1081 std::string redactPath
= CURL::GetRedacted(m_url
);
1082 CLog::Log(LOGDEBUG
, "CurlFile::{} - <{}>", __FUNCTION__
, redactPath
);
1084 assert(!(!m_state
->m_easyHandle
^ !m_state
->m_multiHandle
));
1085 if( m_state
->m_easyHandle
== NULL
)
1086 g_curlInterface
.easy_acquire(url2
.GetProtocol().c_str(),
1087 url2
.GetHostName().c_str(),
1088 &m_state
->m_easyHandle
,
1089 &m_state
->m_multiHandle
);
1091 // setup common curl options
1092 SetCommonOptions(m_state
,
1093 m_failOnError
&& !CServiceBroker::GetLogging().CanLogComponent(LOGCURL
));
1094 SetRequestHeaders(m_state
);
1095 m_state
->m_sendRange
= m_seekable
;
1096 m_state
->m_bRetry
= m_allowRetry
;
1098 m_httpresponse
= m_state
->Connect(m_bufferSize
);
1102 // Allow HTTP response code 0 for file:// protocol
1103 if (url2
.IsProtocol("file"))
1108 if (m_httpresponse
<= hte
|| (m_failOnError
&& m_httpresponse
>= 400))
1111 if (m_httpresponse
>= 400 && CServiceBroker::GetLogging().CanLogComponent(LOGCURL
))
1114 ReadString(&error
[0], 4095);
1117 CLog::Log(LOGERROR
, "CCurlFile::{} - <{}> Failed with code {}:\n{}", __FUNCTION__
,
1118 redactPath
, m_httpresponse
, error
);
1123 SetCorrectHeaders(m_state
);
1125 // since we can't know the stream size up front if we're gzipped/deflated
1126 // flag the stream with an unknown file size rather than the compressed
1128 if (!m_state
->m_httpheader
.GetValue("Content-Encoding").empty() && !StringUtils::EqualsNoCase(m_state
->m_httpheader
.GetValue("Content-Encoding"), "identity"))
1129 m_state
->m_fileSize
= 0;
1131 // check if this stream is a shoutcast stream. sometimes checking the protocol line is not enough so examine other headers as well.
1132 // shoutcast streams should be handled by FileShoutcast.
1133 if ((m_state
->m_httpheader
.GetProtoLine().substr(0, 3) == "ICY" || !m_state
->m_httpheader
.GetValue("icy-notice1").empty()
1134 || !m_state
->m_httpheader
.GetValue("icy-name").empty()
1135 || !m_state
->m_httpheader
.GetValue("icy-br").empty()) && !m_skipshout
)
1137 CLog::Log(LOGDEBUG
, "CCurlFile::{} - <{}> File is a shoutcast stream. Re-opening", __FUNCTION__
,
1139 throw new CRedirectException(new CShoutcastFile
);
1142 m_multisession
= false;
1143 if(url2
.IsProtocol("http") || url2
.IsProtocol("https"))
1145 m_multisession
= true;
1146 if(m_state
->m_httpheader
.GetValue("Server").find("Portable SDK for UPnP devices") != std::string::npos
)
1148 CLog::Log(LOGWARNING
,
1149 "CCurlFile::{} - <{}> Disabling multi session due to broken libupnp server",
1150 __FUNCTION__
, redactPath
);
1151 m_multisession
= false;
1155 if(StringUtils::EqualsNoCase(m_state
->m_httpheader
.GetValue("Transfer-Encoding"), "chunked"))
1156 m_state
->m_fileSize
= 0;
1158 if(m_state
->m_fileSize
<= 0)
1162 if(url2
.IsProtocol("http")
1163 || url2
.IsProtocol("https"))
1165 // if server says explicitly it can't seek, respect that
1166 if(StringUtils::EqualsNoCase(m_state
->m_httpheader
.GetValue("Accept-Ranges"),"none"))
1171 std::string efurl
= GetInfoString(CURLINFO_EFFECTIVE_URL
);
1176 std::string redactEfpath
= CURL::GetRedacted(efurl
);
1177 CLog::Log(LOGDEBUG
, "CCurlFile::{} - <{}> Effective URL is {}", __FUNCTION__
, redactPath
,
1186 bool CCurlFile::OpenForWrite(const CURL
& url
, bool bOverWrite
)
1191 if (Exists(url
) && !bOverWrite
)
1195 ParseAndCorrectUrl(url2
);
1197 CLog::Log(LOGDEBUG
, "CCurlFile::{} - Opening {}", __FUNCTION__
, CURL::GetRedacted(m_url
));
1199 assert(m_state
->m_easyHandle
== NULL
);
1200 g_curlInterface
.easy_acquire(url2
.GetProtocol().c_str(),
1201 url2
.GetHostName().c_str(),
1202 &m_state
->m_easyHandle
,
1203 &m_state
->m_multiHandle
);
1205 // setup common curl options
1206 SetCommonOptions(m_state
);
1207 SetRequestHeaders(m_state
);
1209 std::string efurl
= GetInfoString(CURLINFO_EFFECTIVE_URL
);
1218 assert(m_state
->m_multiHandle
);
1220 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_UPLOAD
, 1);
1222 g_curlInterface
.multi_add_handle(m_state
->m_multiHandle
, m_state
->m_easyHandle
);
1224 m_state
->SetReadBuffer(NULL
, 0);
1229 ssize_t
CCurlFile::Write(const void* lpBuf
, size_t uiBufSize
)
1231 if (!(m_opened
&& m_forWrite
) || m_inError
)
1234 assert(m_state
->m_multiHandle
);
1236 m_state
->SetReadBuffer(lpBuf
, uiBufSize
);
1237 m_state
->m_isPaused
= false;
1238 g_curlInterface
.easy_pause(m_state
->m_easyHandle
, CURLPAUSE_CONT
);
1240 CURLMcode result
= CURLM_OK
;
1243 while (m_stillRunning
&& !m_state
->m_isPaused
)
1245 while ((result
= g_curlInterface
.multi_perform(m_state
->m_multiHandle
, &m_stillRunning
)) == CURLM_CALL_MULTI_PERFORM
);
1247 if (!m_stillRunning
)
1250 if (result
!= CURLM_OK
)
1253 if(g_curlInterface
.easy_getinfo(m_state
->m_easyHandle
, CURLINFO_RESPONSE_CODE
, &code
) == CURLE_OK
)
1254 CLog::Log(LOGERROR
, "CCurlFile::{} - <{}> Unable to write curl resource with code {}",
1255 __FUNCTION__
, CURL::GetRedacted(m_url
), code
);
1261 m_writeOffset
+= m_state
->m_filePos
;
1262 return m_state
->m_filePos
;
1265 bool CCurlFile::CReadState::ReadString(char *szLine
, int iLineLength
)
1267 unsigned int want
= (unsigned int)iLineLength
;
1269 if((m_fileSize
== 0 || m_filePos
< m_fileSize
) && FillBuffer(want
) != FILLBUFFER_OK
)
1272 // ensure only available data is considered
1273 want
= std::min(m_buffer
.getMaxReadSize(), want
);
1275 /* check if we finished prematurely */
1276 if (!m_stillRunning
&& (m_fileSize
== 0 || m_filePos
!= m_fileSize
) && !want
)
1278 if (m_fileSize
!= 0)
1281 "CCurlFile::{} - ({}) Transfer ended before entire file was retrieved pos {}, size {}",
1282 __FUNCTION__
, fmt::ptr(this), m_filePos
, m_fileSize
);
1287 char* pLine
= szLine
;
1290 if (!m_buffer
.ReadData(pLine
, 1))
1294 } while (((pLine
- 1)[0] != '\n') && ((unsigned int)(pLine
- szLine
) < want
));
1296 m_filePos
+= (pLine
- szLine
);
1297 return (pLine
- szLine
) > 0;
1300 bool CCurlFile::ReOpen(const CURL
& url
)
1306 bool CCurlFile::Exists(const CURL
& url
)
1308 // if file is already running, get info from it
1311 CLog::Log(LOGWARNING
, "CCurlFile::{} - <{}> Exist called on open file", __FUNCTION__
,
1317 ParseAndCorrectUrl(url2
);
1319 assert(m_state
->m_easyHandle
== NULL
);
1320 g_curlInterface
.easy_acquire(url2
.GetProtocol().c_str(),
1321 url2
.GetHostName().c_str(),
1322 &m_state
->m_easyHandle
, NULL
);
1324 SetCommonOptions(m_state
);
1325 SetRequestHeaders(m_state
);
1326 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_TIMEOUT
, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_curlconnecttimeout
);
1327 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_NOBODY
, 1);
1328 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_WRITEDATA
, NULL
); /* will cause write failure*/
1330 if(url2
.IsProtocol("ftp") || url2
.IsProtocol("ftps"))
1332 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_FILETIME
, 1);
1333 // nocwd is less standard, will return empty list for non-existed remote dir on some ftp server, avoid it.
1334 if (StringUtils::EndsWith(url2
.GetFileName(), "/"))
1335 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_FTP_FILEMETHOD
, CURLFTPMETHOD_SINGLECWD
);
1337 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_FTP_FILEMETHOD
, CURLFTPMETHOD_NOCWD
);
1340 CURLcode result
= g_curlInterface
.easy_perform(m_state
->m_easyHandle
);
1342 if (result
== CURLE_WRITE_ERROR
|| result
== CURLE_OK
)
1344 g_curlInterface
.easy_release(&m_state
->m_easyHandle
, NULL
);
1348 if (result
== CURLE_HTTP_RETURNED_ERROR
)
1351 if(g_curlInterface
.easy_getinfo(m_state
->m_easyHandle
, CURLINFO_RESPONSE_CODE
, &code
) == CURLE_OK
&& code
!= 404 )
1355 // If we get a Method Not Allowed response, retry with a GET Request
1356 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_NOBODY
, 0);
1358 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_FILETIME
, 1);
1359 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_XFERINFOFUNCTION
, transfer_abort_callback
);
1360 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_NOPROGRESS
, 0);
1362 curl_slist
*list
= NULL
;
1363 list
= g_curlInterface
.slist_append(list
, "Range: bytes=0-1"); /* try to only request 1 byte */
1364 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_HTTPHEADER
, list
);
1366 CURLcode result
= g_curlInterface
.easy_perform(m_state
->m_easyHandle
);
1367 g_curlInterface
.slist_free_all(list
);
1369 if (result
== CURLE_WRITE_ERROR
|| result
== CURLE_OK
)
1371 g_curlInterface
.easy_release(&m_state
->m_easyHandle
, NULL
);
1375 if (result
== CURLE_HTTP_RETURNED_ERROR
)
1377 if (g_curlInterface
.easy_getinfo(m_state
->m_easyHandle
, CURLINFO_RESPONSE_CODE
, &code
) == CURLE_OK
&& code
!= 404 )
1378 CLog::Log(LOGERROR
, "CCurlFile::{} - <{}> Failed: HTTP returned error {}", __FUNCTION__
,
1379 url
.GetRedacted(), code
);
1383 CLog::Log(LOGERROR
, "CCurlFile::{} - <{}> Failed: HTTP returned error {}", __FUNCTION__
,
1384 url
.GetRedacted(), code
);
1387 else if (result
!= CURLE_REMOTE_FILE_NOT_FOUND
&& result
!= CURLE_FTP_COULDNT_RETR_FILE
)
1389 CLog::Log(LOGERROR
, "CCurlFile::{} - <{}> Failed: {}({})", __FUNCTION__
, url
.GetRedacted(),
1390 g_curlInterface
.easy_strerror(result
), result
);
1394 g_curlInterface
.easy_release(&m_state
->m_easyHandle
, NULL
);
1398 int64_t CCurlFile::Seek(int64_t iFilePosition
, int iWhence
)
1400 int64_t nextPos
= m_state
->m_filePos
;
1408 nextPos
= iFilePosition
;
1411 nextPos
+= iFilePosition
;
1414 if (m_state
->m_fileSize
)
1415 nextPos
= m_state
->m_fileSize
+ iFilePosition
;
1423 // We can't seek beyond EOF
1424 if (m_state
->m_fileSize
&& nextPos
> m_state
->m_fileSize
) return -1;
1426 if(m_state
->Seek(nextPos
))
1434 m_oldState
= m_state
;
1435 m_state
= new CReadState();
1436 m_state
->m_fileSize
= m_oldState
->m_fileSize
;
1437 g_curlInterface
.easy_acquire(url
.GetProtocol().c_str(),
1438 url
.GetHostName().c_str(),
1439 &m_state
->m_easyHandle
,
1440 &m_state
->m_multiHandle
);
1446 m_state
= m_oldState
;
1449 if (m_state
->Seek(nextPos
))
1452 m_state
->Disconnect();
1456 m_state
->Disconnect();
1458 // re-setup common curl options
1459 SetCommonOptions(m_state
);
1461 /* caller might have changed some headers (needed for daap)*/
1462 //! @todo daap is gone. is this needed for something else?
1463 SetRequestHeaders(m_state
);
1465 m_state
->m_filePos
= nextPos
;
1466 m_state
->m_sendRange
= true;
1467 m_state
->m_bRetry
= m_allowRetry
;
1469 long response
= m_state
->Connect(m_bufferSize
);
1470 if(response
< 0 && (m_state
->m_fileSize
== 0 || m_state
->m_fileSize
!= m_state
->m_filePos
))
1477 m_state
= m_oldState
;
1480 // Retry without multisession
1481 m_multisession
= false;
1482 return Seek(iFilePosition
, iWhence
);
1491 SetCorrectHeaders(m_state
);
1493 return m_state
->m_filePos
;
1496 int64_t CCurlFile::GetLength()
1498 if (!m_opened
) return 0;
1499 return m_state
->m_fileSize
;
1502 int64_t CCurlFile::GetPosition()
1504 if (!m_opened
) return 0;
1505 return m_state
->m_filePos
;
1508 int CCurlFile::Stat(const CURL
& url
, struct __stat64
* buffer
)
1510 // if file is already running, get info from it
1513 CLog::Log(LOGWARNING
, "CCurlFile::{} - <{}> Stat called on open file", __FUNCTION__
,
1518 buffer
->st_size
= GetLength();
1519 buffer
->st_mode
= _S_IFREG
;
1525 ParseAndCorrectUrl(url2
);
1527 assert(m_state
->m_easyHandle
== NULL
);
1528 g_curlInterface
.easy_acquire(url2
.GetProtocol().c_str(),
1529 url2
.GetHostName().c_str(),
1530 &m_state
->m_easyHandle
, NULL
);
1532 SetCommonOptions(m_state
);
1533 SetRequestHeaders(m_state
);
1534 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_TIMEOUT
, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_curlconnecttimeout
);
1535 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_NOBODY
, 1);
1536 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_FILETIME
, 1);
1538 if(url2
.IsProtocol("ftp"))
1540 // nocwd is less standard, will return empty list for non-existed remote dir on some ftp server, avoid it.
1541 if (StringUtils::EndsWith(url2
.GetFileName(), "/"))
1542 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_FTP_FILEMETHOD
, CURLFTPMETHOD_SINGLECWD
);
1544 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_FTP_FILEMETHOD
, CURLFTPMETHOD_NOCWD
);
1547 CURLcode result
= g_curlInterface
.easy_perform(m_state
->m_easyHandle
);
1549 if(result
== CURLE_HTTP_RETURNED_ERROR
)
1552 if(g_curlInterface
.easy_getinfo(m_state
->m_easyHandle
, CURLINFO_RESPONSE_CODE
, &code
) == CURLE_OK
&& code
== 404 )
1554 g_curlInterface
.easy_release(&m_state
->m_easyHandle
, NULL
);
1560 if(result
== CURLE_GOT_NOTHING
1561 || result
== CURLE_HTTP_RETURNED_ERROR
1562 || result
== CURLE_RECV_ERROR
/* some silly shoutcast servers */ )
1564 /* some http servers and shoutcast servers don't give us any data on a head request */
1565 /* request normal and just bail out via progress meter callback after we received data */
1566 /* somehow curl doesn't reset CURLOPT_NOBODY properly so reset everything */
1567 SetCommonOptions(m_state
);
1568 SetRequestHeaders(m_state
);
1569 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_TIMEOUT
, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_curlconnecttimeout
);
1570 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_FILETIME
, 1);
1571 #if LIBCURL_VERSION_NUM >= 0x072000 // 0.7.32
1572 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_XFERINFOFUNCTION
, transfer_abort_callback
);
1574 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_PROGRESSFUNCTION
, transfer_abort_callback
);
1576 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_NOPROGRESS
, 0);
1578 result
= g_curlInterface
.easy_perform(m_state
->m_easyHandle
);
1582 if( result
!= CURLE_ABORTED_BY_CALLBACK
&& result
!= CURLE_OK
)
1584 g_curlInterface
.easy_release(&m_state
->m_easyHandle
, NULL
);
1586 CLog::Log(LOGERROR
, "CCurlFile::{} - <{}> Failed: {}({})", __FUNCTION__
, url
.GetRedacted(),
1587 g_curlInterface
.easy_strerror(result
), result
);
1591 #if LIBCURL_VERSION_NUM >= 0x073700 // CURL_AT_LEAST_VERSION(0, 7, 55)
1593 result
= g_curlInterface
.easy_getinfo(m_state
->m_easyHandle
, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T
,
1597 result
= g_curlInterface
.easy_getinfo(m_state
->m_easyHandle
, CURLINFO_CONTENT_LENGTH_DOWNLOAD
, &length
);
1599 if (result
!= CURLE_OK
|| length
< 0.0)
1601 if (url
.IsProtocol("ftp"))
1603 g_curlInterface
.easy_release(&m_state
->m_easyHandle
, NULL
);
1604 CLog::Log(LOGINFO
, "CCurlFile::{} - <{}> Content length failed: {}({})", __FUNCTION__
,
1605 url
.GetRedacted(), g_curlInterface
.easy_strerror(result
), result
);
1613 SetCorrectHeaders(m_state
);
1618 buffer
->st_size
= static_cast<int64_t>(length
);
1620 // Note: CURLINFO_CONTENT_TYPE returns the last received content-type response header value.
1621 // In case there is authentication required there might be multiple requests involved and if
1622 // the last request which actually returns the data does not return a content-type header, but
1623 // one of the preceding requests, CURLINFO_CONTENT_TYPE returns not the content type of the
1624 // actual resource requested! m_state contains only the values of the last request, which is
1625 // what we want here.
1626 const std::string mimeType
= m_state
->m_httpheader
.GetMimeType();
1627 if (mimeType
.find("text/html") != std::string::npos
) // consider html files directories
1628 buffer
->st_mode
= _S_IFDIR
;
1630 buffer
->st_mode
= _S_IFREG
;
1633 result
= g_curlInterface
.easy_getinfo(m_state
->m_easyHandle
, CURLINFO_FILETIME
, &filetime
);
1634 if (result
!= CURLE_OK
)
1636 CLog::Log(LOGINFO
, "CCurlFile::{} - <{}> Filetime failed: {}({})", __FUNCTION__
,
1637 url
.GetRedacted(), g_curlInterface
.easy_strerror(result
), result
);
1642 buffer
->st_mtime
= filetime
;
1645 g_curlInterface
.easy_release(&m_state
->m_easyHandle
, NULL
);
1649 ssize_t
CCurlFile::CReadState::Read(void* lpBuf
, size_t uiBufSize
)
1651 /* only request 1 byte, for truncated reads (only if not eof) */
1652 if (m_fileSize
== 0 || m_filePos
< m_fileSize
)
1654 int8_t result
= FillBuffer(1);
1655 if (result
== FILLBUFFER_FAIL
)
1656 return -1; // Fatal error
1658 if (result
== FILLBUFFER_NO_DATA
)
1662 /* ensure only available data is considered */
1663 unsigned int want
= std::min
<unsigned int>(m_buffer
.getMaxReadSize(), uiBufSize
);
1665 /* xfer data to caller */
1666 if (m_buffer
.ReadData((char *)lpBuf
, want
))
1672 /* check if we finished prematurely */
1673 if (!m_stillRunning
&& (m_fileSize
== 0 || m_filePos
!= m_fileSize
))
1675 CLog::Log(LOGWARNING
,
1676 "CCurlFile::CReadState::{} - ({}) Transfer ended before entire file was retrieved "
1678 __FUNCTION__
, fmt::ptr(this), m_filePos
, m_fileSize
);
1685 /* use to attempt to fill the read buffer up to requested number of bytes */
1686 int8_t CCurlFile::CReadState::FillBuffer(unsigned int want
)
1693 // only attempt to fill buffer if transactions still running and buffer
1694 // doesn't exceed required size already
1695 while (m_buffer
.getMaxReadSize() < want
&& m_buffer
.getMaxWriteSize() > 0 )
1698 return FILLBUFFER_NO_DATA
;
1700 /* if there is data in overflow buffer, try to use that first */
1703 unsigned amount
= std::min(m_buffer
.getMaxWriteSize(), m_overflowSize
);
1704 m_buffer
.WriteData(m_overflowBuffer
, amount
);
1706 if (amount
< m_overflowSize
)
1707 memmove(m_overflowBuffer
, m_overflowBuffer
+ amount
, m_overflowSize
- amount
);
1709 m_overflowSize
-= amount
;
1711 m_overflowBuffer
= (char*)realloc_simple(m_overflowBuffer
, m_overflowSize
);
1715 CURLMcode result
= g_curlInterface
.multi_perform(m_multiHandle
, &m_stillRunning
);
1716 if (!m_stillRunning
)
1718 if (result
== CURLM_OK
)
1720 /* if we still have stuff in buffer, we are fine */
1721 if (m_buffer
.getMaxReadSize())
1722 return FILLBUFFER_OK
;
1727 bool bRetryNow
= true;
1728 bool bError
= false;
1729 while ((msg
= g_curlInterface
.multi_info_read(m_multiHandle
, &msgs
)))
1731 if (msg
->msg
== CURLMSG_DONE
)
1733 if (msg
->data
.result
== CURLE_OK
)
1734 return FILLBUFFER_OK
;
1737 if (msg
->data
.result
== CURLE_HTTP_RETURNED_ERROR
)
1739 g_curlInterface
.easy_getinfo(msg
->easy_handle
, CURLINFO_RESPONSE_CODE
, &httpCode
);
1741 // Don't log 404 not-found errors to prevent log-spam
1742 if (httpCode
!= 404)
1744 "CCurlFile::CReadState::{} - ({}) Failed: HTTP returned code {}",
1745 __FUNCTION__
, fmt::ptr(this), httpCode
);
1749 CLog::Log(LOGERROR
, "CCurlFile::CReadState::{} - ({}) Failed: {}({})", __FUNCTION__
,
1750 fmt::ptr(this), g_curlInterface
.easy_strerror(msg
->data
.result
),
1754 if ( (msg
->data
.result
== CURLE_OPERATION_TIMEDOUT
||
1755 msg
->data
.result
== CURLE_PARTIAL_FILE
||
1756 msg
->data
.result
== CURLE_COULDNT_CONNECT
||
1757 msg
->data
.result
== CURLE_RECV_ERROR
) &&
1760 bRetryNow
= false; // Leave it to caller whether the operation is retried
1763 else if ( (msg
->data
.result
== CURLE_HTTP_RANGE_ERROR
||
1764 httpCode
== 416 /* = Requested Range Not Satisfiable */ ||
1765 httpCode
== 406 /* = Not Acceptable (fixes issues with non compliant HDHomerun servers */) &&
1770 // If server returns a (possible) range error, disable range and retry (handled below)
1773 m_sendRange
= false;
1777 // For all other errors, abort the operation
1778 return FILLBUFFER_FAIL
;
1783 // Check for an actual error, if not, just return no-data
1784 if (!bError
&& !m_bLastError
)
1785 return FILLBUFFER_NO_DATA
;
1788 if (m_multiHandle
&& m_easyHandle
)
1789 g_curlInterface
.multi_remove_handle(m_multiHandle
, m_easyHandle
);
1791 // Reset all the stuff like we would in Disconnect()
1793 free(m_overflowBuffer
);
1794 m_overflowBuffer
= NULL
;
1796 m_bLastError
= true; // Flag error for the next run
1798 // Retry immediately or leave it up to the caller?
1799 if ((m_bRetry
&& retry
< CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_curlretries
) || (bRetryNow
&& retry
== 0))
1803 // Connect + seek to current position (again)
1805 g_curlInterface
.multi_add_handle(m_multiHandle
, m_easyHandle
);
1807 CLog::Log(LOGWARNING
, "CCurlFile::CReadState::{} - ({}) Reconnect, (re)try {}",
1808 __FUNCTION__
, fmt::ptr(this), retry
);
1810 // Return to the beginning of the loop:
1814 return FILLBUFFER_NO_DATA
; // We failed but flag no data to caller, so it can retry the operation
1816 return FILLBUFFER_FAIL
;
1819 // We've finished out first loop
1820 if(m_bFirstLoop
&& m_buffer
.getMaxReadSize() > 0)
1821 m_bFirstLoop
= false;
1823 // No error this run
1824 m_bLastError
= false;
1835 // get file descriptors from the transfers
1836 g_curlInterface
.multi_fdset(m_multiHandle
, &fdread
, &fdwrite
, &fdexcep
, &maxfd
);
1839 if (CURLM_OK
!= g_curlInterface
.multi_timeout(m_multiHandle
, &timeout
) || timeout
== -1 || timeout
< 200)
1842 XbmcThreads::EndTime
<> endTime
{std::chrono::milliseconds(timeout
)};
1847 /* On success the value of maxfd is guaranteed to be >= -1. We call
1848 * select(maxfd + 1, ...); specially in case of (maxfd == -1) there are
1849 * no fds ready yet so we call select(0, ...) --or Sleep() on Windows--
1850 * to sleep 100ms, which is the minimum suggested value in the
1851 * curl_multi_fdset() doc.
1855 #ifdef TARGET_WINDOWS
1856 /* Windows does not support using select() for sleeping without a dummy
1857 * socket. Instead use Windows' Sleep() and sleep for 100ms which is the
1858 * minimum suggested value in the curl_multi_fdset() doc.
1860 KODI::TIME::Sleep(100ms
);
1863 /* Portable sleep for platforms other than Windows. */
1864 struct timeval wait
= { 0, 100 * 1000 }; /* 100ms */
1865 rc
= select(0, NULL
, NULL
, NULL
, &wait
);
1870 unsigned int time_left
= endTime
.GetTimeLeft().count();
1871 struct timeval wait
= { (int)time_left
/ 1000, ((int)time_left
% 1000) * 1000 };
1872 rc
= select(maxfd
+ 1, &fdread
, &fdwrite
, &fdexcep
, &wait
);
1874 #ifdef TARGET_WINDOWS
1875 } while(rc
== SOCKET_ERROR
&& WSAGetLastError() == WSAEINTR
);
1877 } while(rc
== SOCKET_ERROR
&& errno
== EINTR
);
1880 if(rc
== SOCKET_ERROR
)
1882 #ifdef TARGET_WINDOWS
1884 strerror_s(buf
, 256, WSAGetLastError());
1885 CLog::Log(LOGERROR
, "CCurlFile::CReadState::{} - ({}) Failed with socket error:{}",
1886 __FUNCTION__
, fmt::ptr(this), buf
);
1888 char const * str
= strerror(errno
);
1889 CLog::Log(LOGERROR
, "CCurlFile::CReadState::{} - ({}) Failed with socket error:{}",
1890 __FUNCTION__
, fmt::ptr(this), str
);
1893 return FILLBUFFER_FAIL
;
1897 case CURLM_CALL_MULTI_PERFORM
:
1899 // we don't keep calling here as that can easily overwrite our buffer which we want to avoid
1900 // docs says we should call it soon after, but as long as we are reading data somewhere
1901 // this aught to be soon enough. should stay in socket otherwise
1908 "CCurlFile::CReadState::{} - ({}) Multi perform failed with code {}, aborting",
1909 __FUNCTION__
, fmt::ptr(this), result
);
1910 return FILLBUFFER_FAIL
;
1915 return FILLBUFFER_OK
;
1918 void CCurlFile::CReadState::SetReadBuffer(const void* lpBuf
, int64_t uiBufSize
)
1920 m_readBuffer
= const_cast<char*>((const char*)lpBuf
);
1921 m_fileSize
= uiBufSize
;
1925 void CCurlFile::ClearRequestHeaders()
1927 m_requestheaders
.clear();
1930 void CCurlFile::SetRequestHeader(const std::string
& header
, const std::string
& value
)
1932 m_requestheaders
[header
] = value
;
1935 void CCurlFile::SetRequestHeader(const std::string
& header
, long value
)
1937 m_requestheaders
[header
] = std::to_string(value
);
1940 std::string
CCurlFile::GetRedirectURL()
1942 return GetInfoString(CURLINFO_REDIRECT_URL
);
1945 std::string
CCurlFile::GetInfoString(int infoType
)
1948 CURLcode result
= g_curlInterface
.easy_getinfo(m_state
->m_easyHandle
, static_cast<CURLINFO
> (infoType
), &info
);
1949 if (result
!= CURLE_OK
)
1952 "CCurlFile::{} - <{}> Info string request for type {} failed with result code {}",
1953 __FUNCTION__
, CURL::GetRedacted(m_url
), infoType
, result
);
1956 return (info
? info
: "");
1959 /* STATIC FUNCTIONS */
1960 bool CCurlFile::GetHttpHeader(const CURL
&url
, CHttpHeader
&headers
)
1965 if(file
.Stat(url
, NULL
) == 0)
1967 headers
= file
.GetHttpHeader();
1974 CLog::Log(LOGERROR
, "CCurlFile::{} - <{}> Exception thrown while trying to retrieve header",
1975 __FUNCTION__
, url
.GetRedacted());
1980 bool CCurlFile::GetMimeType(const CURL
&url
, std::string
&content
, const std::string
&useragent
)
1983 if (!useragent
.empty())
1984 file
.SetUserAgent(useragent
);
1986 struct __stat64 buffer
;
1987 std::string redactUrl
= url
.GetRedacted();
1988 if( file
.Stat(url
, &buffer
) == 0 )
1990 if (buffer
.st_mode
== _S_IFDIR
)
1991 content
= "x-directory/normal";
1993 content
= file
.GetProperty(XFILE::FILE_PROPERTY_MIME_TYPE
);
1994 CLog::Log(LOGDEBUG
, "CCurlFile::{} - <{}> -> {}", __FUNCTION__
, redactUrl
, content
);
1997 CLog::Log(LOGDEBUG
, "CCurlFile::{} - <{}> -> failed", __FUNCTION__
, redactUrl
);
2002 bool CCurlFile::GetContentType(const CURL
&url
, std::string
&content
, const std::string
&useragent
)
2005 if (!useragent
.empty())
2006 file
.SetUserAgent(useragent
);
2008 struct __stat64 buffer
;
2009 std::string redactUrl
= url
.GetRedacted();
2010 if (file
.Stat(url
, &buffer
) == 0)
2012 if (buffer
.st_mode
== _S_IFDIR
)
2013 content
= "x-directory/normal";
2015 content
= file
.GetProperty(XFILE::FILE_PROPERTY_CONTENT_TYPE
, "");
2016 CLog::Log(LOGDEBUG
, "CCurlFile::{} - <{}> -> {}", __FUNCTION__
, redactUrl
, content
);
2019 CLog::Log(LOGDEBUG
, "CCurlFile::{} - <{}> -> failed", __FUNCTION__
, redactUrl
);
2024 bool CCurlFile::GetCookies(const CURL
&url
, std::string
&cookies
)
2026 std::string cookiesStr
;
2027 curl_slist
* curlCookies
;
2028 CURL_HANDLE
* easyHandle
;
2031 // get the cookies list
2032 g_curlInterface
.easy_acquire(url
.GetProtocol().c_str(),
2033 url
.GetHostName().c_str(),
2034 &easyHandle
, &multiHandle
);
2035 if (CURLE_OK
== g_curlInterface
.easy_getinfo(easyHandle
, CURLINFO_COOKIELIST
, &curlCookies
))
2037 // iterate over each cookie and format it into an RFC 2109 formatted Set-Cookie string
2038 curl_slist
* curlCookieIter
= curlCookies
;
2039 while(curlCookieIter
)
2041 // tokenize the CURL cookie string
2042 std::vector
<std::string
> valuesVec
;
2043 StringUtils::Tokenize(curlCookieIter
->data
, valuesVec
, "\t");
2045 // ensure the length is valid
2046 if (valuesVec
.size() < 7)
2048 CLog::Log(LOGERROR
, "CCurlFile::{} - <{}> Invalid cookie: '{}'", __FUNCTION__
,
2049 url
.GetRedacted(), curlCookieIter
->data
);
2050 curlCookieIter
= curlCookieIter
->next
;
2054 // create a http-header formatted cookie string
2055 std::string cookieStr
= valuesVec
[5] + "=" + valuesVec
[6] +
2056 "; path=" + valuesVec
[2] +
2057 "; domain=" + valuesVec
[0];
2059 // append this cookie to the string containing all cookies
2060 if (!cookiesStr
.empty())
2062 cookiesStr
+= cookieStr
;
2064 // move on to the next cookie
2065 curlCookieIter
= curlCookieIter
->next
;
2068 // free the curl cookies
2069 g_curlInterface
.slist_free_all(curlCookies
);
2071 // release our handles
2072 g_curlInterface
.easy_release(&easyHandle
, &multiHandle
);
2074 // if we have a non-empty cookie string, return it
2075 if (!cookiesStr
.empty())
2077 cookies
= cookiesStr
;
2082 // no cookies to return
2086 int CCurlFile::IoControl(EIoControl request
, void* param
)
2088 if (request
== IOCTRL_SEEK_POSSIBLE
)
2089 return m_seekable
? 1 : 0;
2091 if (request
== IOCTRL_SET_RETRY
)
2093 m_allowRetry
= *(bool*) param
;
2100 const std::string
CCurlFile::GetProperty(XFILE::FileProperty type
, const std::string
&name
) const
2104 case FILE_PROPERTY_RESPONSE_PROTOCOL
:
2105 return m_state
->m_httpheader
.GetProtoLine();
2106 case FILE_PROPERTY_RESPONSE_HEADER
:
2107 return m_state
->m_httpheader
.GetValue(name
);
2108 case FILE_PROPERTY_CONTENT_TYPE
:
2109 return m_state
->m_httpheader
.GetValue("content-type");
2110 case FILE_PROPERTY_CONTENT_CHARSET
:
2111 return m_state
->m_httpheader
.GetCharset();
2112 case FILE_PROPERTY_MIME_TYPE
:
2113 return m_state
->m_httpheader
.GetMimeType();
2114 case FILE_PROPERTY_EFFECTIVE_URL
:
2116 char *url
= nullptr;
2117 g_curlInterface
.easy_getinfo(m_state
->m_easyHandle
, CURLINFO_EFFECTIVE_URL
, &url
);
2118 return url
? url
: "";
2125 const std::vector
<std::string
> CCurlFile::GetPropertyValues(XFILE::FileProperty type
, const std::string
&name
) const
2127 if (type
== FILE_PROPERTY_RESPONSE_HEADER
)
2129 return m_state
->m_httpheader
.GetValues(name
);
2131 std::vector
<std::string
> values
;
2132 std::string value
= GetProperty(type
, name
);
2135 values
.emplace_back(value
);
2140 double CCurlFile::GetDownloadSpeed()
2142 #if LIBCURL_VERSION_NUM >= 0x073a00 // 0.7.58.0
2143 curl_off_t speed
= 0;
2144 if (g_curlInterface
.easy_getinfo(m_state
->m_easyHandle
, CURLINFO_SPEED_DOWNLOAD_T
, &speed
) ==
2148 double time
= 0.0, size
= 0.0;
2149 if (g_curlInterface
.easy_getinfo(m_state
->m_easyHandle
, CURLINFO_TOTAL_TIME
, &time
) == CURLE_OK
2150 && g_curlInterface
.easy_getinfo(m_state
->m_easyHandle
, CURLINFO_SIZE_DOWNLOAD
, &size
) == CURLE_OK