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 static const auto proxyType2CUrlProxyType
= std::unordered_map
<XFILE::CCurlFile::ProxyType
, int>{
48 {CCurlFile::ProxyType::HTTP
, CURLPROXY_HTTP
},
49 {CCurlFile::ProxyType::SOCKS4
, CURLPROXY_SOCKS4
},
50 {CCurlFile::ProxyType::SOCKS4A
, CURLPROXY_SOCKS4A
},
51 {CCurlFile::ProxyType::SOCKS5
, CURLPROXY_SOCKS5
},
52 {CCurlFile::ProxyType::SOCKS5_REMOTE
, CURLPROXY_SOCKS5_HOSTNAME
},
53 {CCurlFile::ProxyType::HTTPS
, CURLPROXY_HTTPS
},
56 #define FILLBUFFER_OK 0
57 #define FILLBUFFER_NO_DATA 1
58 #define FILLBUFFER_FAIL 2
60 // curl calls this routine to debug
61 extern "C" int debug_callback(CURL_HANDLE
*handle
, curl_infotype info
, char *output
, size_t size
, void *data
)
63 if (info
== CURLINFO_DATA_IN
|| info
== CURLINFO_DATA_OUT
)
66 if (!CServiceBroker::GetLogging().CanLogComponent(LOGCURL
))
70 strLine
.append(output
, size
);
71 std::vector
<std::string
> vecLines
;
72 StringUtils::Tokenize(strLine
, vecLines
, "\r\n");
73 std::vector
<std::string
>::const_iterator it
= vecLines
.begin();
78 case CURLINFO_TEXT
: infotype
= "TEXT: "; break;
79 case CURLINFO_HEADER_IN
: infotype
= "HEADER_IN: "; break;
80 case CURLINFO_HEADER_OUT
: infotype
= "HEADER_OUT: "; break;
81 case CURLINFO_SSL_DATA_IN
: infotype
= "SSL_DATA_IN: "; break;
82 case CURLINFO_SSL_DATA_OUT
: infotype
= "SSL_DATA_OUT: "; break;
83 case CURLINFO_END
: infotype
= "END: "; break;
84 default : infotype
= ""; break;
87 while (it
!= vecLines
.end())
89 CLog::Log(LOGDEBUG
, "Curl::Debug - {}{}", infotype
, (*it
));
95 /* curl calls this routine to get more data */
96 extern "C" size_t write_callback(char *buffer
,
101 if(userp
== NULL
) return 0;
103 CCurlFile::CReadState
*state
= (CCurlFile::CReadState
*)userp
;
104 return state
->WriteCallback(buffer
, size
, nitems
);
107 extern "C" size_t read_callback(char *buffer
,
112 if(userp
== NULL
) return 0;
114 CCurlFile::CReadState
*state
= (CCurlFile::CReadState
*)userp
;
115 return state
->ReadCallback(buffer
, size
, nitems
);
118 extern "C" size_t header_callback(void *ptr
, size_t size
, size_t nmemb
, void *stream
)
120 CCurlFile::CReadState
*state
= (CCurlFile::CReadState
*)stream
;
121 return state
->HeaderCallback(ptr
, size
, nmemb
);
124 /* used only by CCurlFile::Stat to bail out of unwanted transfers */
125 extern "C" int transfer_abort_callback(void *clientp
,
137 /* fix for silly behavior of realloc */
138 static inline void* realloc_simple(void *ptr
, size_t size
)
140 void *ptr2
= realloc(ptr
, size
);
141 if(ptr
&& !ptr2
&& size
> 0)
150 static constexpr int CURL_OFF
= 0L;
151 static constexpr int CURL_ON
= 1L;
153 size_t CCurlFile::CReadState::HeaderCallback(void *ptr
, size_t size
, size_t nmemb
)
155 std::string inString
;
156 // libcurl doc says that this info is not always \0 terminated
157 const char* strBuf
= (const char*)ptr
;
158 const size_t iSize
= size
* nmemb
;
159 if (strBuf
[iSize
- 1] == 0)
160 inString
.assign(strBuf
, iSize
- 1); // skip last char if it's zero
162 inString
.append(strBuf
, iSize
);
164 m_httpheader
.Parse(inString
);
169 size_t CCurlFile::CReadState::ReadCallback(char *buffer
, size_t size
, size_t nitems
)
174 if (m_filePos
>= m_fileSize
)
177 return CURL_READFUNC_PAUSE
;
180 int64_t retSize
= std::min(m_fileSize
- m_filePos
, int64_t(nitems
* size
));
181 memcpy(buffer
, m_readBuffer
+ m_filePos
, retSize
);
182 m_filePos
+= retSize
;
187 size_t CCurlFile::CReadState::WriteCallback(char *buffer
, size_t size
, size_t nitems
)
189 unsigned int amount
= size
* nitems
;
192 // we have our overflow buffer - first get rid of as much as we can
193 unsigned int maxWriteable
= std::min(m_buffer
.getMaxWriteSize(), m_overflowSize
);
196 if (!m_buffer
.WriteData(m_overflowBuffer
, maxWriteable
))
198 CLog::Log(LOGERROR
, "CCurlFile::CReadState::{} - ({}) Unable to write to buffer",
199 __FUNCTION__
, fmt::ptr(this));
203 if (maxWriteable
< m_overflowSize
)
205 // still have some more - copy it down
206 memmove(m_overflowBuffer
, m_overflowBuffer
+ maxWriteable
, m_overflowSize
- maxWriteable
);
208 m_overflowSize
-= maxWriteable
;
211 m_overflowBuffer
= (char*)realloc_simple(m_overflowBuffer
, m_overflowSize
);
214 // ok, now copy the data into our ring buffer
215 unsigned int maxWriteable
= std::min(m_buffer
.getMaxWriteSize(), amount
);
218 if (!m_buffer
.WriteData(buffer
, maxWriteable
))
221 "CCurlFile::CReadState::{} - ({}) Unable to write to buffer with {} bytes",
222 __FUNCTION__
, fmt::ptr(this), maxWriteable
);
227 amount
-= maxWriteable
;
228 buffer
+= maxWriteable
;
233 //! @todo Limit max. amount of the overflowbuffer
234 m_overflowBuffer
= (char*)realloc_simple(m_overflowBuffer
, amount
+ m_overflowSize
);
235 if(m_overflowBuffer
== NULL
)
237 CLog::Log(LOGWARNING
,
238 "CCurlFile::CReadState::{} - ({}) Failed to grow overflow buffer from {} bytes to "
240 __FUNCTION__
, fmt::ptr(this), m_overflowSize
, amount
+ m_overflowSize
);
243 memcpy(m_overflowBuffer
+ m_overflowSize
, buffer
, amount
);
244 m_overflowSize
+= amount
;
246 return size
* nitems
;
249 CCurlFile::CReadState::CReadState()
252 m_multiHandle
= NULL
;
253 m_overflowBuffer
= NULL
;
262 m_bLastError
= false;
266 m_curlHeaderList
= NULL
;
267 m_curlAliasList
= NULL
;
270 CCurlFile::CReadState::~CReadState()
275 g_curlInterface
.easy_release(&m_easyHandle
, &m_multiHandle
);
278 bool CCurlFile::CReadState::Seek(int64_t pos
)
283 if(FITS_INT(pos
- m_filePos
) && m_buffer
.SkipBytes((int)(pos
- m_filePos
)))
289 if(pos
> m_filePos
&& pos
< m_filePos
+ m_bufferSize
)
291 int len
= m_buffer
.getMaxReadSize();
293 m_buffer
.SkipBytes(len
);
294 if (FillBuffer(m_bufferSize
) != FILLBUFFER_OK
)
296 if(!m_buffer
.SkipBytes(-len
))
298 "CCurlFile::CReadState::{} - ({}) Failed to restore position after failed fill",
299 __FUNCTION__
, fmt::ptr(this));
305 if(!FITS_INT(pos
- m_filePos
) || !m_buffer
.SkipBytes((int)(pos
- m_filePos
)))
309 "CCurlFile::CReadState::{} - ({}) Failed to skip to position after having filled buffer",
310 __FUNCTION__
, fmt::ptr(this));
311 if(!m_buffer
.SkipBytes(-len
))
313 "CCurlFile::CReadState::{} - ({}) Failed to restore position after failed seek",
314 __FUNCTION__
, fmt::ptr(this));
325 void CCurlFile::CReadState::SetResume(void)
328 * Explicitly set RANGE header when filepos=0 as some http servers require us to always send the range
329 * request header. If we don't the server may provide different content causing seeking to fail.
330 * This only affects HTTP-like items, for FTP it's a null operation.
332 if (m_sendRange
&& m_filePos
== 0)
333 g_curlInterface
.easy_setopt(m_easyHandle
, CURLOPT_RANGE
, "0-");
336 g_curlInterface
.easy_setopt(m_easyHandle
, CURLOPT_RANGE
, NULL
);
340 g_curlInterface
.easy_setopt(m_easyHandle
, CURLOPT_RESUME_FROM_LARGE
, m_filePos
);
343 long CCurlFile::CReadState::Connect(unsigned int size
)
346 CLog::Log(LOGDEBUG
, "CurlFile::CReadState::{} - ({}) Resume from position {}", __FUNCTION__
,
347 fmt::ptr(this), m_filePos
);
350 g_curlInterface
.multi_add_handle(m_multiHandle
, m_easyHandle
);
354 m_buffer
.Create(size
* 3);
355 m_httpheader
.Clear();
357 // read some data in to try and obtain the length
358 // maybe there's a better way to get this info??
361 // (Try to) fill buffer
362 if (FillBuffer(1) != FILLBUFFER_OK
)
364 // Check response code
366 if (CURLE_OK
== g_curlInterface
.easy_getinfo(m_easyHandle
, CURLINFO_RESPONSE_CODE
, &response
))
372 #if LIBCURL_VERSION_NUM >= 0x073700 // CURL_AT_LEAST_VERSION(0, 7, 55)
375 g_curlInterface
.easy_getinfo(m_easyHandle
, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T
, &length
))
378 if (CURLE_OK
== g_curlInterface
.easy_getinfo(m_easyHandle
, CURLINFO_CONTENT_LENGTH_DOWNLOAD
, &length
))
383 m_fileSize
= m_filePos
+ (int64_t)length
;
387 if (CURLE_OK
== g_curlInterface
.easy_getinfo(m_easyHandle
, CURLINFO_RESPONSE_CODE
, &response
))
393 void CCurlFile::CReadState::Disconnect()
395 if(m_multiHandle
&& m_easyHandle
)
396 g_curlInterface
.multi_remove_handle(m_multiHandle
, m_easyHandle
);
399 free(m_overflowBuffer
);
400 m_overflowBuffer
= NULL
;
408 if( m_curlHeaderList
)
409 g_curlInterface
.slist_free_all(m_curlHeaderList
);
410 m_curlHeaderList
= NULL
;
412 if( m_curlAliasList
)
413 g_curlInterface
.slist_free_all(m_curlAliasList
);
414 m_curlAliasList
= NULL
;
418 CCurlFile::~CCurlFile()
425 CCurlFile::CCurlFile()
426 : m_overflowBuffer(NULL
)
431 m_multisession
= true;
433 m_connecttimeout
= 0;
437 m_bufferSize
= 32768;
438 m_postdataset
= false;
439 m_state
= new CReadState();
443 m_acceptCharset
= "UTF-8,*;q=0.8"; /* prefer UTF-8 if available */
445 m_acceptencoding
= "all"; /* Accept all supported encoding by default */
448 //Has to be called before Open()
449 void CCurlFile::SetBufferSize(unsigned int size
)
454 void CCurlFile::Close()
456 if (m_opened
&& m_forWrite
&& !m_inError
)
459 m_state
->Disconnect();
472 g_curlInterface
.slist_free_all(m_dnsCacheList
);
473 m_dnsCacheList
= nullptr;
476 void CCurlFile::SetCommonOptions(CReadState
* state
, bool failOnError
/* = true */)
478 CURL_HANDLE
* h
= state
->m_easyHandle
;
480 g_curlInterface
.easy_reset(h
);
482 g_curlInterface
.easy_setopt(h
, CURLOPT_DEBUGFUNCTION
, debug_callback
);
484 if( CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_logLevel
>= LOG_LEVEL_DEBUG
)
485 g_curlInterface
.easy_setopt(h
, CURLOPT_VERBOSE
, CURL_ON
);
487 g_curlInterface
.easy_setopt(h
, CURLOPT_VERBOSE
, CURL_OFF
);
489 g_curlInterface
.easy_setopt(h
, CURLOPT_WRITEDATA
, state
);
490 g_curlInterface
.easy_setopt(h
, CURLOPT_WRITEFUNCTION
, write_callback
);
492 g_curlInterface
.easy_setopt(h
, CURLOPT_READDATA
, state
);
493 g_curlInterface
.easy_setopt(h
, CURLOPT_READFUNCTION
, read_callback
);
496 g_curlInterface
.easy_setopt(h
, CURLOPT_RESOLVE
, m_dnsCacheList
);
498 // make sure headers are separated from the data stream
499 g_curlInterface
.easy_setopt(h
, CURLOPT_WRITEHEADER
, state
);
500 g_curlInterface
.easy_setopt(h
, CURLOPT_HEADERFUNCTION
, header_callback
);
501 g_curlInterface
.easy_setopt(h
, CURLOPT_HEADER
, CURL_OFF
);
503 g_curlInterface
.easy_setopt(h
, CURLOPT_FTP_USE_EPSV
, 0); // turn off epsv
505 // Allow us to follow redirects
506 g_curlInterface
.easy_setopt(h
, CURLOPT_FOLLOWLOCATION
, m_redirectlimit
!= 0);
507 g_curlInterface
.easy_setopt(h
, CURLOPT_MAXREDIRS
, m_redirectlimit
);
509 // Enable cookie engine for current handle
510 g_curlInterface
.easy_setopt(h
, CURLOPT_COOKIEFILE
, "");
512 // Set custom cookie if requested
513 if (!m_cookie
.empty())
514 g_curlInterface
.easy_setopt(h
, CURLOPT_COOKIE
, m_cookie
.c_str());
516 g_curlInterface
.easy_setopt(h
, CURLOPT_COOKIELIST
, "FLUSH");
518 // When using multiple threads you should set the CURLOPT_NOSIGNAL option to
519 // TRUE for all handles. Everything will work fine except that timeouts are not
520 // honored during the DNS lookup - which you can work around by building libcurl
521 // with c-ares support. c-ares is a library that provides asynchronous name
522 // resolves. Unfortunately, c-ares does not yet support IPv6.
523 g_curlInterface
.easy_setopt(h
, CURLOPT_NOSIGNAL
, CURL_ON
);
527 // not interested in failed requests
528 g_curlInterface
.easy_setopt(h
, CURLOPT_FAILONERROR
, 1);
531 // enable support for icecast / shoutcast streams
532 if ( NULL
== state
->m_curlAliasList
)
533 // m_curlAliasList is used only by this one place, but SetCommonOptions can
534 // be called multiple times, only append to list if it's empty.
535 state
->m_curlAliasList
= g_curlInterface
.slist_append(state
->m_curlAliasList
, "ICY 200 OK");
536 g_curlInterface
.easy_setopt(h
, CURLOPT_HTTP200ALIASES
, state
->m_curlAliasList
);
539 g_curlInterface
.easy_setopt(h
, CURLOPT_SSL_VERIFYPEER
, 0);
541 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_URL
, m_url
.c_str());
542 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_TRANSFERTEXT
, CURL_OFF
);
544 // setup POST data if it is set (and it may be empty)
547 g_curlInterface
.easy_setopt(h
, CURLOPT_POST
, 1 );
548 g_curlInterface
.easy_setopt(h
, CURLOPT_POSTFIELDSIZE
, m_postdata
.length());
549 g_curlInterface
.easy_setopt(h
, CURLOPT_POSTFIELDS
, m_postdata
.c_str());
552 // setup Referer header if needed
553 if (!m_referer
.empty())
554 g_curlInterface
.easy_setopt(h
, CURLOPT_REFERER
, m_referer
.c_str());
557 g_curlInterface
.easy_setopt(h
, CURLOPT_REFERER
, NULL
);
558 // Do not send referer header on redirects (same behaviour as ffmpeg and browsers)
559 g_curlInterface
.easy_setopt(h
, CURLOPT_AUTOREFERER
, CURL_OFF
);
562 // setup any requested authentication
563 if( !m_ftpauth
.empty() )
565 g_curlInterface
.easy_setopt(h
, CURLOPT_FTP_SSL
, CURLFTPSSL_TRY
);
566 if( m_ftpauth
== "any" )
567 g_curlInterface
.easy_setopt(h
, CURLOPT_FTPSSLAUTH
, CURLFTPAUTH_DEFAULT
);
568 else if( m_ftpauth
== "ssl" )
569 g_curlInterface
.easy_setopt(h
, CURLOPT_FTPSSLAUTH
, CURLFTPAUTH_SSL
);
570 else if( m_ftpauth
== "tls" )
571 g_curlInterface
.easy_setopt(h
, CURLOPT_FTPSSLAUTH
, CURLFTPAUTH_TLS
);
574 // setup requested http authentication method
575 bool bAuthSet
= false;
576 if(!m_httpauth
.empty())
579 if( m_httpauth
== "any" )
580 g_curlInterface
.easy_setopt(h
, CURLOPT_HTTPAUTH
, CURLAUTH_ANY
);
581 else if( m_httpauth
== "anysafe" )
582 g_curlInterface
.easy_setopt(h
, CURLOPT_HTTPAUTH
, CURLAUTH_ANYSAFE
);
583 else if( m_httpauth
== "digest" )
584 g_curlInterface
.easy_setopt(h
, CURLOPT_HTTPAUTH
, CURLAUTH_DIGEST
);
585 else if( m_httpauth
== "ntlm" )
586 g_curlInterface
.easy_setopt(h
, CURLOPT_HTTPAUTH
, CURLAUTH_NTLM
);
591 // set username and password for current handle
592 if (!m_username
.empty())
594 g_curlInterface
.easy_setopt(h
, CURLOPT_USERNAME
, m_username
.c_str());
595 if (!m_password
.empty())
596 g_curlInterface
.easy_setopt(h
, CURLOPT_PASSWORD
, m_password
.c_str());
599 g_curlInterface
.easy_setopt(h
, CURLOPT_HTTPAUTH
, CURLAUTH_ANY
);
602 // allow passive mode for ftp
603 if( m_ftpport
.length() > 0 )
604 g_curlInterface
.easy_setopt(h
, CURLOPT_FTPPORT
, m_ftpport
.c_str());
606 g_curlInterface
.easy_setopt(h
, CURLOPT_FTPPORT
, NULL
);
608 // allow curl to not use the ip address in the returned pasv response
610 g_curlInterface
.easy_setopt(h
, CURLOPT_FTP_SKIP_PASV_IP
, 0);
612 g_curlInterface
.easy_setopt(h
, CURLOPT_FTP_SKIP_PASV_IP
, 1);
614 // setup Accept-Encoding if requested
615 if (m_acceptencoding
.length() > 0)
616 g_curlInterface
.easy_setopt(h
, CURLOPT_ACCEPT_ENCODING
, m_acceptencoding
== "all" ? "" : m_acceptencoding
.c_str());
618 if (!m_acceptCharset
.empty())
619 SetRequestHeader("Accept-Charset", m_acceptCharset
);
621 if (m_userAgent
.length() > 0)
622 g_curlInterface
.easy_setopt(h
, CURLOPT_USERAGENT
, m_userAgent
.c_str());
623 else /* set some default agent as shoutcast doesn't return proper stuff otherwise */
624 g_curlInterface
.easy_setopt(h
, CURLOPT_USERAGENT
, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_userAgent
.c_str());
626 if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_curlDisableIPV6
)
627 g_curlInterface
.easy_setopt(h
, CURLOPT_IPRESOLVE
, CURL_IPRESOLVE_V4
);
629 if (!m_proxyhost
.empty())
631 g_curlInterface
.easy_setopt(h
, CURLOPT_PROXYTYPE
, proxyType2CUrlProxyType
.at(m_proxytype
));
633 const std::string hostport
= m_proxyhost
+ StringUtils::Format(":{}", m_proxyport
);
634 g_curlInterface
.easy_setopt(h
, CURLOPT_PROXY
, hostport
.c_str());
636 std::string userpass
;
638 if (!m_proxyuser
.empty() && !m_proxypassword
.empty())
639 userpass
= CURL::Encode(m_proxyuser
) + ":" + CURL::Encode(m_proxypassword
);
641 if (!userpass
.empty())
642 g_curlInterface
.easy_setopt(h
, CURLOPT_PROXYUSERPWD
, userpass
.c_str());
644 if (m_customrequest
.length() > 0)
645 g_curlInterface
.easy_setopt(h
, CURLOPT_CUSTOMREQUEST
, m_customrequest
.c_str());
647 if (m_connecttimeout
== 0)
648 m_connecttimeout
= CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_curlconnecttimeout
;
650 // set our timeouts, we abort connection after m_timeout, and reads after no data for m_timeout seconds
651 g_curlInterface
.easy_setopt(h
, CURLOPT_CONNECTTIMEOUT
, m_connecttimeout
);
653 // We abort in case we transfer less than 1byte/second
654 g_curlInterface
.easy_setopt(h
, CURLOPT_LOW_SPEED_LIMIT
, 1);
656 if (m_lowspeedtime
== 0)
657 m_lowspeedtime
= CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_curllowspeedtime
;
659 // Set the lowspeed time very low as it seems Curl takes much longer to detect a lowspeed condition
660 g_curlInterface
.easy_setopt(h
, CURLOPT_LOW_SPEED_TIME
, m_lowspeedtime
);
662 // enable tcp keepalive
663 if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_curlKeepAliveInterval
> 0)
665 g_curlInterface
.easy_setopt(h
, CURLOPT_TCP_KEEPALIVE
, 1L);
666 g_curlInterface
.easy_setopt(
667 h
, CURLOPT_TCP_KEEPIDLE
,
668 CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_curlKeepAliveInterval
/ 2);
669 g_curlInterface
.easy_setopt(
670 h
, CURLOPT_TCP_KEEPINTVL
,
671 CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_curlKeepAliveInterval
);
674 // Setup allowed TLS/SSL ciphers. New versions of cURL may deprecate things that are still in use.
675 if (!m_cipherlist
.empty())
676 g_curlInterface
.easy_setopt(h
, CURLOPT_SSL_CIPHER_LIST
, m_cipherlist
.c_str());
678 if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_curlDisableHTTP2
)
679 g_curlInterface
.easy_setopt(h
, CURLOPT_HTTP_VERSION
, CURL_HTTP_VERSION_1_1
);
681 // enable HTTP2 support. default: CURL_HTTP_VERSION_1_1. Curl >= 7.62.0 defaults to CURL_HTTP_VERSION_2TLS
682 g_curlInterface
.easy_setopt(h
, CURLOPT_HTTP_VERSION
, CURL_HTTP_VERSION_2TLS
);
684 // set CA bundle file
685 std::string caCert
= CSpecialProtocol::TranslatePath(
686 CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_caTrustFile
);
687 #ifdef TARGET_WINDOWS_STORE
688 // UWP Curl - Setting CURLOPT_CAINFO with a valid cacert file path is required for UWP
689 g_curlInterface
.easy_setopt(h
, CURLOPT_CAINFO
, "system\\certs\\cacert.pem");
691 if (!caCert
.empty() && XFILE::CFile::Exists(caCert
))
692 g_curlInterface
.easy_setopt(h
, CURLOPT_CAINFO
, caCert
.c_str());
695 void CCurlFile::SetRequestHeaders(CReadState
* state
)
697 if(state
->m_curlHeaderList
)
699 g_curlInterface
.slist_free_all(state
->m_curlHeaderList
);
700 state
->m_curlHeaderList
= NULL
;
703 for (const auto& it
: m_requestheaders
)
705 std::string buffer
= it
.first
+ ": " + it
.second
;
706 state
->m_curlHeaderList
= g_curlInterface
.slist_append(state
->m_curlHeaderList
, buffer
.c_str());
709 // add user defined headers
710 if (state
->m_easyHandle
)
711 g_curlInterface
.easy_setopt(state
->m_easyHandle
, CURLOPT_HTTPHEADER
, state
->m_curlHeaderList
);
714 void CCurlFile::SetCorrectHeaders(CReadState
* state
)
716 CHttpHeader
& h
= state
->m_httpheader
;
717 /* workaround for shoutcast server which doesn't set content type on standard mp3 */
718 if( h
.GetMimeType().empty() )
720 if( !h
.GetValue("icy-notice1").empty()
721 || !h
.GetValue("icy-name").empty()
722 || !h
.GetValue("icy-br").empty() )
723 h
.AddParam("Content-Type", "audio/mpeg");
726 /* hack for google video */
727 if (StringUtils::EqualsNoCase(h
.GetMimeType(),"text/html")
728 && !h
.GetValue("Content-Disposition").empty() )
730 std::string strValue
= h
.GetValue("Content-Disposition");
731 if (strValue
.find("filename=") != std::string::npos
&&
732 strValue
.find(".flv") != std::string::npos
)
733 h
.AddParam("Content-Type", "video/flv");
737 void CCurlFile::ParseAndCorrectUrl(CURL
&url2
)
739 std::string strProtocol
= url2
.GetTranslatedProtocol();
740 url2
.SetProtocol(strProtocol
);
742 // lookup host in DNS cache
743 std::string resolvedHost
;
744 if (CDNSNameCache::GetCached(url2
.GetHostName(), resolvedHost
))
746 struct curl_slist
* tempCache
;
747 int entryPort
= url2
.GetPort();
751 if (strProtocol
== "http")
753 else if (strProtocol
== "https")
755 else if (strProtocol
== "ftp")
757 else if (strProtocol
== "ftps")
761 std::string entryString
=
762 url2
.GetHostName() + ":" + std::to_string(entryPort
) + ":" + resolvedHost
;
763 tempCache
= g_curlInterface
.slist_append(m_dnsCacheList
, entryString
.c_str());
766 m_dnsCacheList
= tempCache
;
769 if( url2
.IsProtocol("ftp")
770 || url2
.IsProtocol("ftps") )
772 // we was using url options for urls, keep the old code work and warning
773 if (!url2
.GetOptions().empty())
775 CLog::Log(LOGWARNING
,
776 "CCurlFile::{} - <{}> FTP url option is deprecated, please switch to use protocol "
779 __FUNCTION__
, url2
.GetRedacted());
780 url2
.SetProtocolOptions(url2
.GetOptions().substr(1));
781 /* ftp has no options */
785 /* this is ugly, depending on where we get */
786 /* the link from, it may or may not be */
787 /* url encoded. if handed from ftpdirectory */
788 /* it won't be so let's handle that case */
790 std::string
filename(url2
.GetFileName());
791 std::vector
<std::string
> array
;
793 // if server sent us the filename in non-utf8, we need send back with same encoding.
794 if (url2
.GetProtocolOption("utf8") == "0")
795 g_charsetConverter
.utf8ToStringCharset(filename
);
797 //! @todo create a tokenizer that doesn't skip empty's
798 StringUtils::Tokenize(filename
, array
, "/");
800 for (std::vector
<std::string
>::iterator it
= array
.begin(); it
!= array
.end(); ++it
)
802 if(it
!= array
.begin())
805 filename
+= CURL::Encode(*it
);
808 /* make sure we keep slashes */
809 if(StringUtils::EndsWith(url2
.GetFileName(), "/"))
812 url2
.SetFileName(filename
);
815 if (url2
.HasProtocolOption("auth"))
817 m_ftpauth
= url2
.GetProtocolOption("auth");
818 StringUtils::ToLower(m_ftpauth
);
819 if(m_ftpauth
.empty())
823 if (url2
.HasProtocolOption("active"))
825 m_ftpport
= url2
.GetProtocolOption("active");
826 if(m_ftpport
.empty())
829 if (url2
.HasProtocolOption("verifypeer"))
831 if (url2
.GetProtocolOption("verifypeer") == "false")
832 m_verifyPeer
= false;
834 m_ftppasvip
= url2
.HasProtocolOption("pasvip") && url2
.GetProtocolOption("pasvip") != "0";
836 else if(url2
.IsProtocol("http") ||
837 url2
.IsProtocol("https"))
839 std::shared_ptr
<CSettings
> s
= CServiceBroker::GetSettingsComponent()->GetSettings();
843 if (!url2
.IsLocalHost() &&
844 m_proxyhost
.empty() &&
845 s
->GetBool(CSettings::SETTING_NETWORK_USEHTTPPROXY
) &&
846 !s
->GetString(CSettings::SETTING_NETWORK_HTTPPROXYSERVER
).empty() &&
847 s
->GetInt(CSettings::SETTING_NETWORK_HTTPPROXYPORT
) > 0)
849 m_proxytype
= (ProxyType
)s
->GetInt(CSettings::SETTING_NETWORK_HTTPPROXYTYPE
);
850 m_proxyhost
= s
->GetString(CSettings::SETTING_NETWORK_HTTPPROXYSERVER
);
851 m_proxyport
= s
->GetInt(CSettings::SETTING_NETWORK_HTTPPROXYPORT
);
852 m_proxyuser
= s
->GetString(CSettings::SETTING_NETWORK_HTTPPROXYUSERNAME
);
853 m_proxypassword
= s
->GetString(CSettings::SETTING_NETWORK_HTTPPROXYPASSWORD
);
854 CLog::LogFC(LOGDEBUG
, LOGCURL
, "<{}> Using proxy {}, type {}", url2
.GetRedacted(),
855 m_proxyhost
, proxyType2CUrlProxyType
.at(m_proxytype
));
858 // get username and password
859 m_username
= url2
.GetUserName();
860 m_password
= url2
.GetPassWord();
862 // handle any protocol options
863 std::map
<std::string
, std::string
> options
;
864 url2
.GetProtocolOptions(options
);
865 if (!options
.empty())
868 for (const auto& it
: options
)
870 std::string name
= it
.first
;
871 StringUtils::ToLower(name
);
872 const std::string
& value
= it
.second
;
877 StringUtils::ToLower(m_httpauth
);
878 if(m_httpauth
.empty())
881 else if (name
== "referer")
883 else if (name
== "user-agent")
885 else if (name
== "cookie")
887 else if (name
== "acceptencoding" || name
== "encoding")
888 SetAcceptEncoding(value
);
889 else if (name
== "noshout" && value
== "true")
891 else if (name
== "seekable" && value
== "0")
893 else if (name
== "accept-charset")
894 SetAcceptCharset(value
);
895 else if (name
== "sslcipherlist")
896 m_cipherlist
= value
;
897 else if (name
== "connection-timeout")
898 m_connecttimeout
= strtol(value
.c_str(), NULL
, 10);
899 else if (name
== "failonerror")
900 m_failOnError
= value
== "true";
901 else if (name
== "redirect-limit")
902 m_redirectlimit
= strtol(value
.c_str(), NULL
, 10);
903 else if (name
== "postdata")
905 m_postdata
= Base64::Decode(value
);
906 m_postdataset
= true;
908 else if (name
== "active-remote")// needed for DACP!
910 SetRequestHeader(it
.first
, value
);
912 else if (name
== "customrequest")
914 SetCustomRequest(value
);
916 else if (name
== "verifypeer")
918 if (value
== "false")
919 m_verifyPeer
= false;
923 if (name
.length() > 0 && name
[0] == '!')
925 SetRequestHeader(it
.first
.substr(1), value
);
926 CLog::LogFC(LOGDEBUG
, LOGCURL
, "<{}> Adding custom header option '{}: ***********'",
927 url2
.GetRedacted(), it
.first
.substr(1));
931 SetRequestHeader(it
.first
, value
);
932 if (name
== "authorization")
933 CLog::LogFC(LOGDEBUG
, LOGCURL
, "<{}> Adding custom header option '{}: ***********'",
934 url2
.GetRedacted(), it
.first
);
936 CLog::LogFC(LOGDEBUG
, LOGCURL
, "<{}> Adding custom header option '{}: {}'",
937 url2
.GetRedacted(), it
.first
, value
);
944 // Unset the protocol options to have an url without protocol options
945 url2
.SetProtocolOptions("");
947 if (m_username
.length() > 0 && m_password
.length() > 0)
948 m_url
= url2
.GetWithoutUserDetails();
953 bool CCurlFile::Post(const std::string
& strURL
, const std::string
& strPostData
, std::string
& strHTML
)
955 m_postdata
= strPostData
;
956 m_postdataset
= true;
957 return Service(strURL
, strHTML
);
960 bool CCurlFile::Get(const std::string
& strURL
, std::string
& strHTML
)
963 m_postdataset
= false;
964 return Service(strURL
, strHTML
);
967 bool CCurlFile::Service(const std::string
& strURL
, std::string
& strHTML
)
969 const CURL
pathToUrl(strURL
);
972 if (ReadData(strHTML
))
982 bool CCurlFile::ReadData(std::string
& strHTML
)
987 while( (size_read
= Read(buffer
, sizeof(buffer
)-1) ) > 0 )
989 buffer
[size_read
] = 0;
990 strHTML
.append(buffer
, size_read
);
992 if (m_state
->m_cancelled
)
997 bool CCurlFile::Download(const std::string
& strURL
, const std::string
& strFileName
, unsigned int* pdwSize
)
999 CLog::Log(LOGINFO
, "CCurlFile::{} - {}->{}", __FUNCTION__
, CURL::GetRedacted(strURL
),
1002 std::string strData
;
1003 if (!Get(strURL
, strData
))
1007 if (!file
.OpenForWrite(strFileName
, true))
1009 CLog::Log(LOGERROR
, "CCurlFile::{} - <{}> Unable to open file for write: {} ({})", __FUNCTION__
,
1010 CURL::GetRedacted(strURL
), strFileName
, GetLastError());
1013 ssize_t written
= 0;
1014 if (!strData
.empty())
1015 written
= file
.Write(strData
.c_str(), strData
.size());
1017 if (pdwSize
!= NULL
)
1018 *pdwSize
= written
> 0 ? written
: 0;
1020 return written
== static_cast<ssize_t
>(strData
.size());
1023 // Detect whether we are "online" or not! Very simple and dirty!
1024 bool CCurlFile::IsInternet()
1026 CURL
url("http://www.msftconnecttest.com/connecttest.txt");
1027 bool found
= Exists(url
);
1032 url
.Parse("http://www.w3.org/");
1033 found
= Exists(url
);
1040 void CCurlFile::Cancel()
1042 m_state
->m_cancelled
= true;
1044 KODI::TIME::Sleep(1ms
);
1047 void CCurlFile::Reset()
1049 m_state
->m_cancelled
= false;
1052 void CCurlFile::SetProxy(const std::string
&type
, const std::string
&host
,
1053 uint16_t port
, const std::string
&user
, const std::string
&password
)
1055 m_proxytype
= CCurlFile::ProxyType::HTTP
;
1057 m_proxytype
= CCurlFile::ProxyType::HTTP
;
1058 else if (type
== "https")
1059 m_proxytype
= CCurlFile::ProxyType::HTTPS
;
1060 else if (type
== "socks4")
1061 m_proxytype
= CCurlFile::ProxyType::SOCKS4
;
1062 else if (type
== "socks4a")
1063 m_proxytype
= CCurlFile::ProxyType::SOCKS4A
;
1064 else if (type
== "socks5")
1065 m_proxytype
= CCurlFile::ProxyType::SOCKS5
;
1066 else if (type
== "socks5-remote")
1067 m_proxytype
= CCurlFile::ProxyType::SOCKS5_REMOTE
;
1069 CLog::Log(LOGERROR
, "CCurFile::{} - <{}> Invalid proxy type \"{}\"", __FUNCTION__
,
1070 CURL::GetRedacted(m_url
), type
);
1074 m_proxypassword
= password
;
1077 bool CCurlFile::Open(const CURL
& url
)
1083 ParseAndCorrectUrl(url2
);
1085 std::string redactPath
= CURL::GetRedacted(m_url
);
1086 CLog::Log(LOGDEBUG
, "CurlFile::{} - <{}>", __FUNCTION__
, redactPath
);
1088 assert(!(!m_state
->m_easyHandle
^ !m_state
->m_multiHandle
));
1089 if( m_state
->m_easyHandle
== NULL
)
1090 g_curlInterface
.easy_acquire(url2
.GetProtocol().c_str(),
1091 url2
.GetHostName().c_str(),
1092 &m_state
->m_easyHandle
,
1093 &m_state
->m_multiHandle
);
1095 // setup common curl options
1096 SetCommonOptions(m_state
,
1097 m_failOnError
&& !CServiceBroker::GetLogging().CanLogComponent(LOGCURL
));
1098 SetRequestHeaders(m_state
);
1099 m_state
->m_sendRange
= m_seekable
;
1100 m_state
->m_bRetry
= m_allowRetry
;
1102 m_httpresponse
= m_state
->Connect(m_bufferSize
);
1106 // Allow HTTP response code 0 for file:// protocol
1107 if (url2
.IsProtocol("file"))
1112 if (m_httpresponse
<= hte
|| (m_failOnError
&& m_httpresponse
>= 400))
1115 if (m_httpresponse
>= 400 && CServiceBroker::GetLogging().CanLogComponent(LOGCURL
))
1118 ReadLine(&error
[0], 4095);
1121 CLog::Log(LOGERROR
, "CCurlFile::{} - <{}> Failed with code {}:\n{}", __FUNCTION__
,
1122 redactPath
, m_httpresponse
, error
);
1127 SetCorrectHeaders(m_state
);
1129 // since we can't know the stream size up front if we're gzipped/deflated
1130 // flag the stream with an unknown file size rather than the compressed
1132 if (!m_state
->m_httpheader
.GetValue("Content-Encoding").empty() && !StringUtils::EqualsNoCase(m_state
->m_httpheader
.GetValue("Content-Encoding"), "identity"))
1133 m_state
->m_fileSize
= 0;
1135 // check if this stream is a shoutcast stream. sometimes checking the protocol line is not enough so examine other headers as well.
1136 // shoutcast streams should be handled by FileShoutcast.
1137 if ((m_state
->m_httpheader
.GetProtoLine().substr(0, 3) == "ICY" || !m_state
->m_httpheader
.GetValue("icy-notice1").empty()
1138 || !m_state
->m_httpheader
.GetValue("icy-name").empty()
1139 || !m_state
->m_httpheader
.GetValue("icy-br").empty()) && !m_skipshout
)
1141 CLog::Log(LOGDEBUG
, "CCurlFile::{} - <{}> File is a shoutcast stream. Re-opening", __FUNCTION__
,
1143 throw new CRedirectException(new CShoutcastFile
);
1146 m_multisession
= false;
1147 if(url2
.IsProtocol("http") || url2
.IsProtocol("https"))
1149 m_multisession
= true;
1150 if(m_state
->m_httpheader
.GetValue("Server").find("Portable SDK for UPnP devices") != std::string::npos
)
1152 CLog::Log(LOGWARNING
,
1153 "CCurlFile::{} - <{}> Disabling multi session due to broken libupnp server",
1154 __FUNCTION__
, redactPath
);
1155 m_multisession
= false;
1159 if(StringUtils::EqualsNoCase(m_state
->m_httpheader
.GetValue("Transfer-Encoding"), "chunked"))
1160 m_state
->m_fileSize
= 0;
1162 if(m_state
->m_fileSize
<= 0)
1166 if(url2
.IsProtocol("http")
1167 || url2
.IsProtocol("https"))
1169 // if server says explicitly it can't seek, respect that
1170 if(StringUtils::EqualsNoCase(m_state
->m_httpheader
.GetValue("Accept-Ranges"),"none"))
1175 std::string efurl
= GetInfoString(CURLINFO_EFFECTIVE_URL
);
1180 std::string redactEfpath
= CURL::GetRedacted(efurl
);
1181 CLog::Log(LOGDEBUG
, "CCurlFile::{} - <{}> Effective URL is {}", __FUNCTION__
, redactPath
,
1190 bool CCurlFile::OpenForWrite(const CURL
& url
, bool bOverWrite
)
1195 if (Exists(url
) && !bOverWrite
)
1199 ParseAndCorrectUrl(url2
);
1201 CLog::Log(LOGDEBUG
, "CCurlFile::{} - Opening {}", __FUNCTION__
, CURL::GetRedacted(m_url
));
1203 assert(m_state
->m_easyHandle
== NULL
);
1204 g_curlInterface
.easy_acquire(url2
.GetProtocol().c_str(),
1205 url2
.GetHostName().c_str(),
1206 &m_state
->m_easyHandle
,
1207 &m_state
->m_multiHandle
);
1209 // setup common curl options
1210 SetCommonOptions(m_state
);
1211 SetRequestHeaders(m_state
);
1213 std::string efurl
= GetInfoString(CURLINFO_EFFECTIVE_URL
);
1222 assert(m_state
->m_multiHandle
);
1224 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_UPLOAD
, 1);
1226 g_curlInterface
.multi_add_handle(m_state
->m_multiHandle
, m_state
->m_easyHandle
);
1228 m_state
->SetReadBuffer(NULL
, 0);
1233 ssize_t
CCurlFile::Write(const void* lpBuf
, size_t uiBufSize
)
1235 if (!(m_opened
&& m_forWrite
) || m_inError
)
1238 assert(m_state
->m_multiHandle
);
1240 m_state
->SetReadBuffer(lpBuf
, uiBufSize
);
1241 m_state
->m_isPaused
= false;
1242 g_curlInterface
.easy_pause(m_state
->m_easyHandle
, CURLPAUSE_CONT
);
1244 CURLMcode result
= CURLM_OK
;
1247 while (m_stillRunning
&& !m_state
->m_isPaused
)
1249 while ((result
= g_curlInterface
.multi_perform(m_state
->m_multiHandle
, &m_stillRunning
)) == CURLM_CALL_MULTI_PERFORM
);
1251 if (!m_stillRunning
)
1254 if (result
!= CURLM_OK
)
1257 if(g_curlInterface
.easy_getinfo(m_state
->m_easyHandle
, CURLINFO_RESPONSE_CODE
, &code
) == CURLE_OK
)
1258 CLog::Log(LOGERROR
, "CCurlFile::{} - <{}> Unable to write curl resource with code {}",
1259 __FUNCTION__
, CURL::GetRedacted(m_url
), code
);
1265 m_writeOffset
+= m_state
->m_filePos
;
1266 return m_state
->m_filePos
;
1269 CCurlFile::ReadLineResult
CCurlFile::CReadState::ReadLine(char* buffer
, std::size_t bufferSize
)
1271 unsigned int want
= (unsigned int)bufferSize
;
1273 if((m_fileSize
== 0 || m_filePos
< m_fileSize
) && FillBuffer(want
) != FILLBUFFER_OK
)
1274 return {ReadLineResult::FAILURE
, 0};
1276 // ensure only available data is considered
1277 want
= std::min(m_buffer
.getMaxReadSize(), want
);
1279 /* check if we finished prematurely */
1280 if (!m_stillRunning
&& (m_fileSize
== 0 || m_filePos
!= m_fileSize
) && !want
)
1282 if (m_fileSize
!= 0)
1285 "CCurlFile::{} - ({}) Transfer ended before entire file was retrieved pos {}, size {}",
1286 __FUNCTION__
, fmt::ptr(this), m_filePos
, m_fileSize
);
1288 return {ReadLineResult::FAILURE
, 0};
1291 std::size_t bytesRead
= 0;
1292 bool foundNewline
= false;
1293 bool reachedEnd
= false;
1294 for (; bytesRead
< want
- 1 && !foundNewline
; ++bytesRead
)
1296 reachedEnd
= m_buffer
.ReadData(buffer
+ bytesRead
, 1) == 0;
1300 foundNewline
= buffer
[bytesRead
] == '\n';
1303 buffer
[bytesRead
] = '\0';
1304 m_filePos
+= bytesRead
;
1306 return {ReadLineResult::FAILURE
, 0};
1307 else if (foundNewline
)
1308 return {ReadLineResult::OK
, bytesRead
- 1};
1309 else if (reachedEnd
)
1310 return {ReadLineResult::OK
, bytesRead
};
1312 return {ReadLineResult::TRUNCATED
, bytesRead
};
1315 bool CCurlFile::ReOpen(const CURL
& url
)
1321 bool CCurlFile::Exists(const CURL
& url
)
1323 // if file is already running, get info from it
1326 CLog::Log(LOGWARNING
, "CCurlFile::{} - <{}> Exist called on open file", __FUNCTION__
,
1332 ParseAndCorrectUrl(url2
);
1334 assert(m_state
->m_easyHandle
== NULL
);
1335 g_curlInterface
.easy_acquire(url2
.GetProtocol().c_str(),
1336 url2
.GetHostName().c_str(),
1337 &m_state
->m_easyHandle
, NULL
);
1339 SetCommonOptions(m_state
);
1340 SetRequestHeaders(m_state
);
1341 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_TIMEOUT
, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_curlconnecttimeout
);
1342 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_NOBODY
, 1);
1343 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_WRITEDATA
, NULL
); /* will cause write failure*/
1345 if(url2
.IsProtocol("ftp") || url2
.IsProtocol("ftps"))
1347 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_FILETIME
, 1);
1348 // nocwd is less standard, will return empty list for non-existed remote dir on some ftp server, avoid it.
1349 if (StringUtils::EndsWith(url2
.GetFileName(), "/"))
1350 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_FTP_FILEMETHOD
, CURLFTPMETHOD_SINGLECWD
);
1352 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_FTP_FILEMETHOD
, CURLFTPMETHOD_NOCWD
);
1355 CURLcode result
= g_curlInterface
.easy_perform(m_state
->m_easyHandle
);
1357 if (result
== CURLE_WRITE_ERROR
|| result
== CURLE_OK
)
1359 g_curlInterface
.easy_release(&m_state
->m_easyHandle
, NULL
);
1363 if (result
== CURLE_HTTP_RETURNED_ERROR
)
1366 if(g_curlInterface
.easy_getinfo(m_state
->m_easyHandle
, CURLINFO_RESPONSE_CODE
, &code
) == CURLE_OK
&& code
!= 404 )
1370 // If we get a Method Not Allowed response, retry with a GET Request
1371 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_NOBODY
, 0);
1373 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_FILETIME
, 1);
1374 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_XFERINFOFUNCTION
, transfer_abort_callback
);
1375 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_NOPROGRESS
, 0);
1377 curl_slist
*list
= NULL
;
1378 list
= g_curlInterface
.slist_append(list
, "Range: bytes=0-1"); /* try to only request 1 byte */
1379 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_HTTPHEADER
, list
);
1381 CURLcode result
= g_curlInterface
.easy_perform(m_state
->m_easyHandle
);
1382 g_curlInterface
.slist_free_all(list
);
1384 if (result
== CURLE_WRITE_ERROR
|| result
== CURLE_OK
)
1386 g_curlInterface
.easy_release(&m_state
->m_easyHandle
, NULL
);
1390 if (result
== CURLE_HTTP_RETURNED_ERROR
)
1392 if (g_curlInterface
.easy_getinfo(m_state
->m_easyHandle
, CURLINFO_RESPONSE_CODE
, &code
) == CURLE_OK
&& code
!= 404 )
1393 CLog::Log(LOGERROR
, "CCurlFile::{} - <{}> Failed: HTTP returned error {}", __FUNCTION__
,
1394 url
.GetRedacted(), code
);
1398 CLog::Log(LOGERROR
, "CCurlFile::{} - <{}> Failed: HTTP returned error {}", __FUNCTION__
,
1399 url
.GetRedacted(), code
);
1402 else if (result
!= CURLE_REMOTE_FILE_NOT_FOUND
&& result
!= CURLE_FTP_COULDNT_RETR_FILE
)
1404 CLog::Log(LOGERROR
, "CCurlFile::{} - <{}> Failed: {}({})", __FUNCTION__
, url
.GetRedacted(),
1405 g_curlInterface
.easy_strerror(result
), result
);
1409 g_curlInterface
.easy_release(&m_state
->m_easyHandle
, NULL
);
1413 int64_t CCurlFile::Seek(int64_t iFilePosition
, int iWhence
)
1415 int64_t nextPos
= m_state
->m_filePos
;
1423 nextPos
= iFilePosition
;
1426 nextPos
+= iFilePosition
;
1429 if (m_state
->m_fileSize
)
1430 nextPos
= m_state
->m_fileSize
+ iFilePosition
;
1438 // We can't seek beyond EOF
1439 if (m_state
->m_fileSize
&& nextPos
> m_state
->m_fileSize
) return -1;
1441 if(m_state
->Seek(nextPos
))
1449 m_oldState
= m_state
;
1450 m_state
= new CReadState();
1451 m_state
->m_fileSize
= m_oldState
->m_fileSize
;
1452 g_curlInterface
.easy_acquire(url
.GetProtocol().c_str(),
1453 url
.GetHostName().c_str(),
1454 &m_state
->m_easyHandle
,
1455 &m_state
->m_multiHandle
);
1461 m_state
= m_oldState
;
1464 if (m_state
->Seek(nextPos
))
1467 m_state
->Disconnect();
1471 m_state
->Disconnect();
1473 // re-setup common curl options
1474 SetCommonOptions(m_state
);
1476 /* caller might have changed some headers (needed for daap)*/
1477 //! @todo daap is gone. is this needed for something else?
1478 SetRequestHeaders(m_state
);
1480 m_state
->m_filePos
= nextPos
;
1481 m_state
->m_sendRange
= true;
1482 m_state
->m_bRetry
= m_allowRetry
;
1484 long response
= m_state
->Connect(m_bufferSize
);
1485 if(response
< 0 && (m_state
->m_fileSize
== 0 || m_state
->m_fileSize
!= m_state
->m_filePos
))
1492 m_state
= m_oldState
;
1495 // Retry without multisession
1496 m_multisession
= false;
1497 return Seek(iFilePosition
, iWhence
);
1506 SetCorrectHeaders(m_state
);
1508 return m_state
->m_filePos
;
1511 int64_t CCurlFile::GetLength()
1513 if (!m_opened
) return 0;
1514 return m_state
->m_fileSize
;
1517 int64_t CCurlFile::GetPosition()
1519 if (!m_opened
) return 0;
1520 return m_state
->m_filePos
;
1523 int CCurlFile::Stat(const CURL
& url
, struct __stat64
* buffer
)
1525 // if file is already running, get info from it
1528 CLog::Log(LOGWARNING
, "CCurlFile::{} - <{}> Stat called on open file", __FUNCTION__
,
1533 buffer
->st_size
= GetLength();
1534 buffer
->st_mode
= _S_IFREG
;
1540 ParseAndCorrectUrl(url2
);
1542 assert(m_state
->m_easyHandle
== NULL
);
1543 g_curlInterface
.easy_acquire(url2
.GetProtocol().c_str(),
1544 url2
.GetHostName().c_str(),
1545 &m_state
->m_easyHandle
, NULL
);
1547 SetCommonOptions(m_state
);
1548 SetRequestHeaders(m_state
);
1549 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_TIMEOUT
, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_curlconnecttimeout
);
1550 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_NOBODY
, 1);
1551 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_FILETIME
, 1);
1553 if(url2
.IsProtocol("ftp"))
1555 // nocwd is less standard, will return empty list for non-existed remote dir on some ftp server, avoid it.
1556 if (StringUtils::EndsWith(url2
.GetFileName(), "/"))
1557 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_FTP_FILEMETHOD
, CURLFTPMETHOD_SINGLECWD
);
1559 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_FTP_FILEMETHOD
, CURLFTPMETHOD_NOCWD
);
1562 CURLcode result
= g_curlInterface
.easy_perform(m_state
->m_easyHandle
);
1564 if(result
== CURLE_HTTP_RETURNED_ERROR
)
1567 if(g_curlInterface
.easy_getinfo(m_state
->m_easyHandle
, CURLINFO_RESPONSE_CODE
, &code
) == CURLE_OK
&& code
== 404 )
1569 g_curlInterface
.easy_release(&m_state
->m_easyHandle
, NULL
);
1575 if(result
== CURLE_GOT_NOTHING
1576 || result
== CURLE_HTTP_RETURNED_ERROR
1577 || result
== CURLE_RECV_ERROR
/* some silly shoutcast servers */ )
1579 /* some http servers and shoutcast servers don't give us any data on a head request */
1580 /* request normal and just bail out via progress meter callback after we received data */
1581 /* somehow curl doesn't reset CURLOPT_NOBODY properly so reset everything */
1582 SetCommonOptions(m_state
);
1583 SetRequestHeaders(m_state
);
1584 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_TIMEOUT
, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_curlconnecttimeout
);
1585 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_FILETIME
, 1);
1586 #if LIBCURL_VERSION_NUM >= 0x072000 // 0.7.32
1587 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_XFERINFOFUNCTION
, transfer_abort_callback
);
1589 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_PROGRESSFUNCTION
, transfer_abort_callback
);
1591 g_curlInterface
.easy_setopt(m_state
->m_easyHandle
, CURLOPT_NOPROGRESS
, 0);
1593 result
= g_curlInterface
.easy_perform(m_state
->m_easyHandle
);
1597 if( result
!= CURLE_ABORTED_BY_CALLBACK
&& result
!= CURLE_OK
)
1599 g_curlInterface
.easy_release(&m_state
->m_easyHandle
, NULL
);
1601 CLog::Log(LOGERROR
, "CCurlFile::{} - <{}> Failed: {}({})", __FUNCTION__
, url
.GetRedacted(),
1602 g_curlInterface
.easy_strerror(result
), result
);
1606 #if LIBCURL_VERSION_NUM >= 0x073700 // CURL_AT_LEAST_VERSION(0, 7, 55)
1608 result
= g_curlInterface
.easy_getinfo(m_state
->m_easyHandle
, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T
,
1612 result
= g_curlInterface
.easy_getinfo(m_state
->m_easyHandle
, CURLINFO_CONTENT_LENGTH_DOWNLOAD
, &length
);
1614 if (result
!= CURLE_OK
|| length
< 0.0)
1616 if (url
.IsProtocol("ftp"))
1618 g_curlInterface
.easy_release(&m_state
->m_easyHandle
, NULL
);
1619 CLog::Log(LOGINFO
, "CCurlFile::{} - <{}> Content length failed: {}({})", __FUNCTION__
,
1620 url
.GetRedacted(), g_curlInterface
.easy_strerror(result
), result
);
1628 SetCorrectHeaders(m_state
);
1633 buffer
->st_size
= static_cast<int64_t>(length
);
1635 // Note: CURLINFO_CONTENT_TYPE returns the last received content-type response header value.
1636 // In case there is authentication required there might be multiple requests involved and if
1637 // the last request which actually returns the data does not return a content-type header, but
1638 // one of the preceding requests, CURLINFO_CONTENT_TYPE returns not the content type of the
1639 // actual resource requested! m_state contains only the values of the last request, which is
1640 // what we want here.
1641 const std::string mimeType
= m_state
->m_httpheader
.GetMimeType();
1642 if (mimeType
.find("text/html") != std::string::npos
) // consider html files directories
1643 buffer
->st_mode
= _S_IFDIR
;
1645 buffer
->st_mode
= _S_IFREG
;
1648 result
= g_curlInterface
.easy_getinfo(m_state
->m_easyHandle
, CURLINFO_FILETIME
, &filetime
);
1649 if (result
!= CURLE_OK
)
1651 CLog::Log(LOGINFO
, "CCurlFile::{} - <{}> Filetime failed: {}({})", __FUNCTION__
,
1652 url
.GetRedacted(), g_curlInterface
.easy_strerror(result
), result
);
1657 buffer
->st_mtime
= filetime
;
1660 g_curlInterface
.easy_release(&m_state
->m_easyHandle
, NULL
);
1664 ssize_t
CCurlFile::CReadState::Read(void* lpBuf
, size_t uiBufSize
)
1666 /* only request 1 byte, for truncated reads (only if not eof) */
1667 if (m_fileSize
== 0 || m_filePos
< m_fileSize
)
1669 int8_t result
= FillBuffer(1);
1670 if (result
== FILLBUFFER_FAIL
)
1671 return -1; // Fatal error
1673 if (result
== FILLBUFFER_NO_DATA
)
1677 /* ensure only available data is considered */
1678 unsigned int want
= std::min
<unsigned int>(m_buffer
.getMaxReadSize(), uiBufSize
);
1680 /* xfer data to caller */
1681 if (m_buffer
.ReadData((char *)lpBuf
, want
))
1687 /* check if we finished prematurely */
1688 if (!m_stillRunning
&& (m_fileSize
== 0 || m_filePos
!= m_fileSize
))
1690 CLog::Log(LOGWARNING
,
1691 "CCurlFile::CReadState::{} - ({}) Transfer ended before entire file was retrieved "
1693 __FUNCTION__
, fmt::ptr(this), m_filePos
, m_fileSize
);
1700 /* use to attempt to fill the read buffer up to requested number of bytes */
1701 int8_t CCurlFile::CReadState::FillBuffer(unsigned int want
)
1708 // only attempt to fill buffer if transactions still running and buffer
1709 // doesn't exceed required size already
1710 while (m_buffer
.getMaxReadSize() < want
&& m_buffer
.getMaxWriteSize() > 0 )
1713 return FILLBUFFER_NO_DATA
;
1715 /* if there is data in overflow buffer, try to use that first */
1718 unsigned amount
= std::min(m_buffer
.getMaxWriteSize(), m_overflowSize
);
1719 m_buffer
.WriteData(m_overflowBuffer
, amount
);
1721 if (amount
< m_overflowSize
)
1722 memmove(m_overflowBuffer
, m_overflowBuffer
+ amount
, m_overflowSize
- amount
);
1724 m_overflowSize
-= amount
;
1726 m_overflowBuffer
= (char*)realloc_simple(m_overflowBuffer
, m_overflowSize
);
1730 CURLMcode result
= g_curlInterface
.multi_perform(m_multiHandle
, &m_stillRunning
);
1731 if (!m_stillRunning
)
1733 if (result
== CURLM_OK
)
1735 /* if we still have stuff in buffer, we are fine */
1736 if (m_buffer
.getMaxReadSize())
1737 return FILLBUFFER_OK
;
1742 bool bRetryNow
= true;
1743 bool bError
= false;
1744 while ((msg
= g_curlInterface
.multi_info_read(m_multiHandle
, &msgs
)))
1746 if (msg
->msg
== CURLMSG_DONE
)
1748 if (msg
->data
.result
== CURLE_OK
)
1749 return FILLBUFFER_OK
;
1752 if (msg
->data
.result
== CURLE_HTTP_RETURNED_ERROR
)
1754 g_curlInterface
.easy_getinfo(msg
->easy_handle
, CURLINFO_RESPONSE_CODE
, &httpCode
);
1756 // Don't log 404 not-found errors to prevent log-spam
1757 if (httpCode
!= 404)
1759 "CCurlFile::CReadState::{} - ({}) Failed: HTTP returned code {}",
1760 __FUNCTION__
, fmt::ptr(this), httpCode
);
1764 CLog::Log(LOGERROR
, "CCurlFile::CReadState::{} - ({}) Failed: {}({})", __FUNCTION__
,
1765 fmt::ptr(this), g_curlInterface
.easy_strerror(msg
->data
.result
),
1769 if ( (msg
->data
.result
== CURLE_OPERATION_TIMEDOUT
||
1770 msg
->data
.result
== CURLE_PARTIAL_FILE
||
1771 msg
->data
.result
== CURLE_COULDNT_CONNECT
||
1772 msg
->data
.result
== CURLE_RECV_ERROR
) &&
1775 bRetryNow
= false; // Leave it to caller whether the operation is retried
1778 else if ( (msg
->data
.result
== CURLE_HTTP_RANGE_ERROR
||
1779 httpCode
== 416 /* = Requested Range Not Satisfiable */ ||
1780 httpCode
== 406 /* = Not Acceptable (fixes issues with non compliant HDHomerun servers */) &&
1785 // If server returns a (possible) range error, disable range and retry (handled below)
1788 m_sendRange
= false;
1792 // For all other errors, abort the operation
1793 return FILLBUFFER_FAIL
;
1798 // Check for an actual error, if not, just return no-data
1799 if (!bError
&& !m_bLastError
)
1800 return FILLBUFFER_NO_DATA
;
1803 if (m_multiHandle
&& m_easyHandle
)
1804 g_curlInterface
.multi_remove_handle(m_multiHandle
, m_easyHandle
);
1806 // Reset all the stuff like we would in Disconnect()
1808 free(m_overflowBuffer
);
1809 m_overflowBuffer
= NULL
;
1811 m_bLastError
= true; // Flag error for the next run
1813 // Retry immediately or leave it up to the caller?
1814 if ((m_bRetry
&& retry
< CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_curlretries
) || (bRetryNow
&& retry
== 0))
1818 // Connect + seek to current position (again)
1820 g_curlInterface
.multi_add_handle(m_multiHandle
, m_easyHandle
);
1822 CLog::Log(LOGWARNING
, "CCurlFile::CReadState::{} - ({}) Reconnect, (re)try {}",
1823 __FUNCTION__
, fmt::ptr(this), retry
);
1825 // Return to the beginning of the loop:
1829 return FILLBUFFER_NO_DATA
; // We failed but flag no data to caller, so it can retry the operation
1831 return FILLBUFFER_FAIL
;
1834 // We've finished out first loop
1835 if(m_bFirstLoop
&& m_buffer
.getMaxReadSize() > 0)
1836 m_bFirstLoop
= false;
1838 // No error this run
1839 m_bLastError
= false;
1850 // get file descriptors from the transfers
1851 g_curlInterface
.multi_fdset(m_multiHandle
, &fdread
, &fdwrite
, &fdexcep
, &maxfd
);
1854 if (CURLM_OK
!= g_curlInterface
.multi_timeout(m_multiHandle
, &timeout
) || timeout
== -1 || timeout
< 200)
1857 XbmcThreads::EndTime
<> endTime
{std::chrono::milliseconds(timeout
)};
1862 /* On success the value of maxfd is guaranteed to be >= -1. We call
1863 * select(maxfd + 1, ...); specially in case of (maxfd == -1) there are
1864 * no fds ready yet so we call select(0, ...) --or Sleep() on Windows--
1865 * to sleep 100ms, which is the minimum suggested value in the
1866 * curl_multi_fdset() doc.
1870 #ifdef TARGET_WINDOWS
1871 /* Windows does not support using select() for sleeping without a dummy
1872 * socket. Instead use Windows' Sleep() and sleep for 100ms which is the
1873 * minimum suggested value in the curl_multi_fdset() doc.
1875 KODI::TIME::Sleep(100ms
);
1878 /* Portable sleep for platforms other than Windows. */
1879 struct timeval wait
= { 0, 100 * 1000 }; /* 100ms */
1880 rc
= select(0, NULL
, NULL
, NULL
, &wait
);
1885 unsigned int time_left
= endTime
.GetTimeLeft().count();
1886 struct timeval wait
= { (int)time_left
/ 1000, ((int)time_left
% 1000) * 1000 };
1887 rc
= select(maxfd
+ 1, &fdread
, &fdwrite
, &fdexcep
, &wait
);
1889 #ifdef TARGET_WINDOWS
1890 } while(rc
== SOCKET_ERROR
&& WSAGetLastError() == WSAEINTR
);
1892 } while(rc
== SOCKET_ERROR
&& errno
== EINTR
);
1895 if(rc
== SOCKET_ERROR
)
1897 #ifdef TARGET_WINDOWS
1899 strerror_s(buf
, 256, WSAGetLastError());
1900 CLog::Log(LOGERROR
, "CCurlFile::CReadState::{} - ({}) Failed with socket error:{}",
1901 __FUNCTION__
, fmt::ptr(this), buf
);
1903 char const * str
= strerror(errno
);
1904 CLog::Log(LOGERROR
, "CCurlFile::CReadState::{} - ({}) Failed with socket error:{}",
1905 __FUNCTION__
, fmt::ptr(this), str
);
1908 return FILLBUFFER_FAIL
;
1912 case CURLM_CALL_MULTI_PERFORM
:
1914 // we don't keep calling here as that can easily overwrite our buffer which we want to avoid
1915 // docs says we should call it soon after, but as long as we are reading data somewhere
1916 // this aught to be soon enough. should stay in socket otherwise
1923 "CCurlFile::CReadState::{} - ({}) Multi perform failed with code {}, aborting",
1924 __FUNCTION__
, fmt::ptr(this), result
);
1925 return FILLBUFFER_FAIL
;
1930 return FILLBUFFER_OK
;
1933 void CCurlFile::CReadState::SetReadBuffer(const void* lpBuf
, int64_t uiBufSize
)
1935 m_readBuffer
= const_cast<char*>((const char*)lpBuf
);
1936 m_fileSize
= uiBufSize
;
1940 void CCurlFile::ClearRequestHeaders()
1942 m_requestheaders
.clear();
1945 void CCurlFile::SetRequestHeader(const std::string
& header
, const std::string
& value
)
1947 m_requestheaders
[header
] = value
;
1950 void CCurlFile::SetRequestHeader(const std::string
& header
, long value
)
1952 m_requestheaders
[header
] = std::to_string(value
);
1955 std::string
CCurlFile::GetRedirectURL()
1957 return GetInfoString(CURLINFO_REDIRECT_URL
);
1960 std::string
CCurlFile::GetInfoString(int infoType
)
1963 CURLcode result
= g_curlInterface
.easy_getinfo(m_state
->m_easyHandle
, static_cast<CURLINFO
> (infoType
), &info
);
1964 if (result
!= CURLE_OK
)
1967 "CCurlFile::{} - <{}> Info string request for type {} failed with result code {}",
1968 __FUNCTION__
, CURL::GetRedacted(m_url
), infoType
, result
);
1971 return (info
? info
: "");
1974 /* STATIC FUNCTIONS */
1975 bool CCurlFile::GetHttpHeader(const CURL
&url
, CHttpHeader
&headers
)
1980 if(file
.Stat(url
, NULL
) == 0)
1982 headers
= file
.GetHttpHeader();
1989 CLog::Log(LOGERROR
, "CCurlFile::{} - <{}> Exception thrown while trying to retrieve header",
1990 __FUNCTION__
, url
.GetRedacted());
1995 bool CCurlFile::GetMimeType(const CURL
&url
, std::string
&content
, const std::string
&useragent
)
1998 if (!useragent
.empty())
1999 file
.SetUserAgent(useragent
);
2001 struct __stat64 buffer
;
2002 std::string redactUrl
= url
.GetRedacted();
2003 if( file
.Stat(url
, &buffer
) == 0 )
2005 if (buffer
.st_mode
== _S_IFDIR
)
2006 content
= "x-directory/normal";
2008 content
= file
.GetProperty(XFILE::FileProperty::MIME_TYPE
);
2009 CLog::Log(LOGDEBUG
, "CCurlFile::{} - <{}> -> {}", __FUNCTION__
, redactUrl
, content
);
2012 CLog::Log(LOGDEBUG
, "CCurlFile::{} - <{}> -> failed", __FUNCTION__
, redactUrl
);
2017 bool CCurlFile::GetContentType(const CURL
&url
, std::string
&content
, const std::string
&useragent
)
2020 if (!useragent
.empty())
2021 file
.SetUserAgent(useragent
);
2023 struct __stat64 buffer
;
2024 std::string redactUrl
= url
.GetRedacted();
2025 if (file
.Stat(url
, &buffer
) == 0)
2027 if (buffer
.st_mode
== _S_IFDIR
)
2028 content
= "x-directory/normal";
2030 content
= file
.GetProperty(XFILE::FileProperty::CONTENT_TYPE
, "");
2031 CLog::Log(LOGDEBUG
, "CCurlFile::{} - <{}> -> {}", __FUNCTION__
, redactUrl
, content
);
2034 CLog::Log(LOGDEBUG
, "CCurlFile::{} - <{}> -> failed", __FUNCTION__
, redactUrl
);
2039 bool CCurlFile::GetCookies(const CURL
&url
, std::string
&cookies
)
2041 std::string cookiesStr
;
2042 curl_slist
* curlCookies
;
2043 CURL_HANDLE
* easyHandle
;
2046 // get the cookies list
2047 g_curlInterface
.easy_acquire(url
.GetProtocol().c_str(),
2048 url
.GetHostName().c_str(),
2049 &easyHandle
, &multiHandle
);
2050 if (CURLE_OK
== g_curlInterface
.easy_getinfo(easyHandle
, CURLINFO_COOKIELIST
, &curlCookies
))
2052 // iterate over each cookie and format it into an RFC 2109 formatted Set-Cookie string
2053 curl_slist
* curlCookieIter
= curlCookies
;
2054 while(curlCookieIter
)
2056 // tokenize the CURL cookie string
2057 std::vector
<std::string
> valuesVec
;
2058 StringUtils::Tokenize(curlCookieIter
->data
, valuesVec
, "\t");
2060 // ensure the length is valid
2061 if (valuesVec
.size() < 7)
2063 CLog::Log(LOGERROR
, "CCurlFile::{} - <{}> Invalid cookie: '{}'", __FUNCTION__
,
2064 url
.GetRedacted(), curlCookieIter
->data
);
2065 curlCookieIter
= curlCookieIter
->next
;
2069 // create a http-header formatted cookie string
2070 std::string cookieStr
= valuesVec
[5] + "=" + valuesVec
[6] +
2071 "; path=" + valuesVec
[2] +
2072 "; domain=" + valuesVec
[0];
2074 // append this cookie to the string containing all cookies
2075 if (!cookiesStr
.empty())
2077 cookiesStr
+= cookieStr
;
2079 // move on to the next cookie
2080 curlCookieIter
= curlCookieIter
->next
;
2083 // free the curl cookies
2084 g_curlInterface
.slist_free_all(curlCookies
);
2086 // release our handles
2087 g_curlInterface
.easy_release(&easyHandle
, &multiHandle
);
2089 // if we have a non-empty cookie string, return it
2090 if (!cookiesStr
.empty())
2092 cookies
= cookiesStr
;
2097 // no cookies to return
2101 int CCurlFile::IoControl(IOControl request
, void* param
)
2103 if (request
== IOControl::SEEK_POSSIBLE
)
2104 return m_seekable
? 1 : 0;
2106 if (request
== IOControl::SET_RETRY
)
2108 m_allowRetry
= *(bool*) param
;
2115 const std::string
CCurlFile::GetProperty(XFILE::FileProperty type
, const std::string
&name
) const
2119 case FileProperty::RESPONSE_PROTOCOL
:
2120 return m_state
->m_httpheader
.GetProtoLine();
2121 case FileProperty::RESPONSE_HEADER
:
2122 return m_state
->m_httpheader
.GetValue(name
);
2123 case FileProperty::CONTENT_TYPE
:
2124 return m_state
->m_httpheader
.GetValue("content-type");
2125 case FileProperty::CONTENT_CHARSET
:
2126 return m_state
->m_httpheader
.GetCharset();
2127 case FileProperty::MIME_TYPE
:
2128 return m_state
->m_httpheader
.GetMimeType();
2129 case FileProperty::EFFECTIVE_URL
:
2131 char* url
= nullptr;
2132 g_curlInterface
.easy_getinfo(m_state
->m_easyHandle
, CURLINFO_EFFECTIVE_URL
, &url
);
2133 return url
? url
: "";
2140 const std::vector
<std::string
> CCurlFile::GetPropertyValues(XFILE::FileProperty type
, const std::string
&name
) const
2142 if (type
== FileProperty::RESPONSE_HEADER
)
2144 return m_state
->m_httpheader
.GetValues(name
);
2146 std::vector
<std::string
> values
;
2147 std::string value
= GetProperty(type
, name
);
2150 values
.emplace_back(value
);
2155 double CCurlFile::GetDownloadSpeed()
2157 #if LIBCURL_VERSION_NUM >= 0x073a00 // 0.7.58.0
2158 curl_off_t speed
= 0;
2159 if (g_curlInterface
.easy_getinfo(m_state
->m_easyHandle
, CURLINFO_SPEED_DOWNLOAD_T
, &speed
) ==
2163 double time
= 0.0, size
= 0.0;
2164 if (g_curlInterface
.easy_getinfo(m_state
->m_easyHandle
, CURLINFO_TOTAL_TIME
, &time
) == CURLE_OK
2165 && g_curlInterface
.easy_getinfo(m_state
->m_easyHandle
, CURLINFO_SIZE_DOWNLOAD
, &size
) == CURLE_OK