Merge pull request #26362 from ksooo/estuary-rework-pvr-info-dialog
[xbmc.git] / xbmc / filesystem / CurlFile.cpp
blob95124daecd88a5b0857848722a024a2e6df789a0
1 /*
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.
7 */
9 #include "CurlFile.h"
11 #include "File.h"
12 #include "ServiceBroker.h"
13 #include "URL.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"
23 #include <algorithm>
24 #include <cassert>
25 #include <climits>
26 #include <vector>
28 #ifdef TARGET_POSIX
29 #include <errno.h>
30 #include <inttypes.h>
31 #include "platform/posix/ConvUtils.h"
32 #endif
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)
64 return 0;
66 if (!CServiceBroker::GetLogging().CanLogComponent(LOGCURL))
67 return 0;
69 std::string strLine;
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();
75 const char *infotype;
76 switch(info)
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));
90 ++it;
92 return 0;
95 /* curl calls this routine to get more data */
96 extern "C" size_t write_callback(char *buffer,
97 size_t size,
98 size_t nitems,
99 void *userp)
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,
108 size_t size,
109 size_t nitems,
110 void *userp)
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,
126 curl_off_t dltotal,
127 curl_off_t dlnow,
128 curl_off_t ultotal,
129 curl_off_t ulnow)
131 if(dlnow > 0)
132 return 1;
133 else
134 return 0;
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)
143 free(ptr);
144 return NULL;
146 else
147 return ptr2;
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
161 else
162 inString.append(strBuf, iSize);
164 m_httpheader.Parse(inString);
166 return iSize;
169 size_t CCurlFile::CReadState::ReadCallback(char *buffer, size_t size, size_t nitems)
171 if (m_fileSize == 0)
172 return 0;
174 if (m_filePos >= m_fileSize)
176 m_isPaused = true;
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;
184 return retSize;
187 size_t CCurlFile::CReadState::WriteCallback(char *buffer, size_t size, size_t nitems)
189 unsigned int amount = size * nitems;
190 if (m_overflowSize)
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);
194 if (maxWriteable)
196 if (!m_buffer.WriteData(m_overflowBuffer, maxWriteable))
198 CLog::Log(LOGERROR, "CCurlFile::CReadState::{} - ({}) Unable to write to buffer",
199 __FUNCTION__, fmt::ptr(this));
200 return 0;
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;
210 // Shrink memory:
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);
216 if (maxWriteable)
218 if (!m_buffer.WriteData(buffer, maxWriteable))
220 CLog::Log(LOGERROR,
221 "CCurlFile::CReadState::{} - ({}) Unable to write to buffer with {} bytes",
222 __FUNCTION__, fmt::ptr(this), maxWriteable);
223 return 0;
225 else
227 amount -= maxWriteable;
228 buffer += maxWriteable;
231 if (amount)
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 "
239 "{} bytes",
240 __FUNCTION__, fmt::ptr(this), m_overflowSize, amount + m_overflowSize);
241 return 0;
243 memcpy(m_overflowBuffer + m_overflowSize, buffer, amount);
244 m_overflowSize += amount;
246 return size * nitems;
249 CCurlFile::CReadState::CReadState()
251 m_easyHandle = NULL;
252 m_multiHandle = NULL;
253 m_overflowBuffer = NULL;
254 m_overflowSize = 0;
255 m_stillRunning = 0;
256 m_filePos = 0;
257 m_fileSize = 0;
258 m_bufferSize = 0;
259 m_cancelled = false;
260 m_bFirstLoop = true;
261 m_sendRange = true;
262 m_bLastError = false;
263 m_readBuffer = 0;
264 m_isPaused = false;
265 m_bRetry = true;
266 m_curlHeaderList = NULL;
267 m_curlAliasList = NULL;
270 CCurlFile::CReadState::~CReadState()
272 Disconnect();
274 if(m_easyHandle)
275 g_curlInterface.easy_release(&m_easyHandle, &m_multiHandle);
278 bool CCurlFile::CReadState::Seek(int64_t pos)
280 if(pos == m_filePos)
281 return true;
283 if(FITS_INT(pos - m_filePos) && m_buffer.SkipBytes((int)(pos - m_filePos)))
285 m_filePos = pos;
286 return true;
289 if(pos > m_filePos && pos < m_filePos + m_bufferSize)
291 int len = m_buffer.getMaxReadSize();
292 m_filePos += len;
293 m_buffer.SkipBytes(len);
294 if (FillBuffer(m_bufferSize) != FILLBUFFER_OK)
296 if(!m_buffer.SkipBytes(-len))
297 CLog::Log(LOGERROR,
298 "CCurlFile::CReadState::{} - ({}) Failed to restore position after failed fill",
299 __FUNCTION__, fmt::ptr(this));
300 else
301 m_filePos -= len;
302 return false;
305 if(!FITS_INT(pos - m_filePos) || !m_buffer.SkipBytes((int)(pos - m_filePos)))
307 CLog::Log(
308 LOGERROR,
309 "CCurlFile::CReadState::{} - ({}) Failed to skip to position after having filled buffer",
310 __FUNCTION__, fmt::ptr(this));
311 if(!m_buffer.SkipBytes(-len))
312 CLog::Log(LOGERROR,
313 "CCurlFile::CReadState::{} - ({}) Failed to restore position after failed seek",
314 __FUNCTION__, fmt::ptr(this));
315 else
316 m_filePos -= len;
317 return false;
319 m_filePos = pos;
320 return true;
322 return false;
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-");
334 else
336 g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_RANGE, NULL);
337 m_sendRange = false;
340 g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_RESUME_FROM_LARGE, m_filePos);
343 long CCurlFile::CReadState::Connect(unsigned int size)
345 if (m_filePos != 0)
346 CLog::Log(LOGDEBUG, "CurlFile::CReadState::{} - ({}) Resume from position {}", __FUNCTION__,
347 fmt::ptr(this), m_filePos);
349 SetResume();
350 g_curlInterface.multi_add_handle(m_multiHandle, m_easyHandle);
352 m_bufferSize = size;
353 m_buffer.Destroy();
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??
359 m_stillRunning = 1;
361 // (Try to) fill buffer
362 if (FillBuffer(1) != FILLBUFFER_OK)
364 // Check response code
365 long response;
366 if (CURLE_OK == g_curlInterface.easy_getinfo(m_easyHandle, CURLINFO_RESPONSE_CODE, &response))
367 return response;
368 else
369 return -1;
372 #if LIBCURL_VERSION_NUM >= 0x073700 // CURL_AT_LEAST_VERSION(0, 7, 55)
373 curl_off_t length;
374 if (CURLE_OK ==
375 g_curlInterface.easy_getinfo(m_easyHandle, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T, &length))
376 #else
377 double length;
378 if (CURLE_OK == g_curlInterface.easy_getinfo(m_easyHandle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &length))
379 #endif
381 if (length < 0)
382 length = 0.0;
383 m_fileSize = m_filePos + (int64_t)length;
386 long response;
387 if (CURLE_OK == g_curlInterface.easy_getinfo(m_easyHandle, CURLINFO_RESPONSE_CODE, &response))
388 return response;
390 return -1;
393 void CCurlFile::CReadState::Disconnect()
395 if(m_multiHandle && m_easyHandle)
396 g_curlInterface.multi_remove_handle(m_multiHandle, m_easyHandle);
398 m_buffer.Clear();
399 free(m_overflowBuffer);
400 m_overflowBuffer = NULL;
401 m_overflowSize = 0;
402 m_filePos = 0;
403 m_fileSize = 0;
404 m_bufferSize = 0;
405 m_readBuffer = 0;
407 /* cleanup */
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()
420 Close();
421 delete m_state;
422 delete m_oldState;
425 CCurlFile::CCurlFile()
426 : m_overflowBuffer(NULL)
428 m_opened = false;
429 m_forWrite = false;
430 m_inError = false;
431 m_multisession = true;
432 m_seekable = true;
433 m_connecttimeout = 0;
434 m_redirectlimit = 5;
435 m_lowspeedtime = 0;
436 m_ftppasvip = false;
437 m_bufferSize = 32768;
438 m_postdataset = false;
439 m_state = new CReadState();
440 m_oldState = NULL;
441 m_skipshout = false;
442 m_httpresponse = -1;
443 m_acceptCharset = "UTF-8,*;q=0.8"; /* prefer UTF-8 if available */
444 m_allowRetry = true;
445 m_acceptencoding = "all"; /* Accept all supported encoding by default */
448 //Has to be called before Open()
449 void CCurlFile::SetBufferSize(unsigned int size)
451 m_bufferSize = size;
454 void CCurlFile::Close()
456 if (m_opened && m_forWrite && !m_inError)
457 Write(NULL, 0);
459 m_state->Disconnect();
460 delete m_oldState;
461 m_oldState = NULL;
463 m_url.clear();
464 m_referer.clear();
465 m_cookie.clear();
467 m_opened = false;
468 m_forWrite = false;
469 m_inError = false;
471 if (m_dnsCacheList)
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);
486 else
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);
495 // use DNS cache
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);
525 if (failOnError)
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);
538 if (!m_verifyPeer)
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)
545 if (m_postdataset)
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());
555 else
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())
578 bAuthSet = true;
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);
587 else
588 bAuthSet = false;
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());
598 if (!bAuthSet)
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());
605 else
606 g_curlInterface.easy_setopt(h, CURLOPT_FTPPORT, NULL);
608 // allow curl to not use the ip address in the returned pasv response
609 if( m_ftppasvip )
610 g_curlInterface.easy_setopt(h, CURLOPT_FTP_SKIP_PASV_IP, 0);
611 else
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);
680 else
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");
690 #endif
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();
749 if (entryPort == 0)
751 if (strProtocol == "http")
752 entryPort = 80;
753 else if (strProtocol == "https")
754 entryPort = 443;
755 else if (strProtocol == "ftp")
756 entryPort = 21;
757 else if (strProtocol == "ftps")
758 entryPort = 990;
761 std::string entryString =
762 url2.GetHostName() + ":" + std::to_string(entryPort) + ":" + resolvedHost;
763 tempCache = g_curlInterface.slist_append(m_dnsCacheList, entryString.c_str());
765 if (tempCache)
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 "
777 "option (change "
778 "'?' to '|')",
779 __FUNCTION__, url2.GetRedacted());
780 url2.SetProtocolOptions(url2.GetOptions().substr(1));
781 /* ftp has no options */
782 url2.SetOptions("");
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, "/");
799 filename.clear();
800 for (std::vector<std::string>::iterator it = array.begin(); it != array.end(); ++it)
802 if(it != array.begin())
803 filename += "/";
805 filename += CURL::Encode(*it);
808 /* make sure we keep slashes */
809 if(StringUtils::EndsWith(url2.GetFileName(), "/"))
810 filename += "/";
812 url2.SetFileName(filename);
814 m_ftpauth.clear();
815 if (url2.HasProtocolOption("auth"))
817 m_ftpauth = url2.GetProtocolOption("auth");
818 StringUtils::ToLower(m_ftpauth);
819 if(m_ftpauth.empty())
820 m_ftpauth = "any";
822 m_ftpport = "";
823 if (url2.HasProtocolOption("active"))
825 m_ftpport = url2.GetProtocolOption("active");
826 if(m_ftpport.empty())
827 m_ftpport = "-";
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();
840 if (!s)
841 return;
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())
867 // set xbmc headers
868 for (const auto& it : options)
870 std::string name = it.first;
871 StringUtils::ToLower(name);
872 const std::string& value = it.second;
874 if (name == "auth")
876 m_httpauth = value;
877 StringUtils::ToLower(m_httpauth);
878 if(m_httpauth.empty())
879 m_httpauth = "any";
881 else if (name == "referer")
882 SetReferer(value);
883 else if (name == "user-agent")
884 SetUserAgent(value);
885 else if (name == "cookie")
886 SetCookie(value);
887 else if (name == "acceptencoding" || name == "encoding")
888 SetAcceptEncoding(value);
889 else if (name == "noshout" && value == "true")
890 m_skipshout = true;
891 else if (name == "seekable" && value == "0")
892 m_seekable = false;
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;
921 else
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));
929 else
931 SetRequestHeader(it.first, value);
932 if (name == "authorization")
933 CLog::LogFC(LOGDEBUG, LOGCURL, "<{}> Adding custom header option '{}: ***********'",
934 url2.GetRedacted(), it.first);
935 else
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();
949 else
950 m_url = url2.Get();
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)
962 m_postdata = "";
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);
970 if (Open(pathToUrl))
972 if (ReadData(strHTML))
974 Close();
975 return true;
978 Close();
979 return false;
982 bool CCurlFile::ReadData(std::string& strHTML)
984 int size_read = 0;
985 strHTML = "";
986 char buffer[16384];
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)
993 return false;
994 return true;
997 bool CCurlFile::Download(const std::string& strURL, const std::string& strFileName, unsigned int* pdwSize)
999 CLog::Log(LOGINFO, "CCurlFile::{} - {}->{}", __FUNCTION__, CURL::GetRedacted(strURL),
1000 strFileName);
1002 std::string strData;
1003 if (!Get(strURL, strData))
1004 return false;
1006 XFILE::CFile file;
1007 if (!file.OpenForWrite(strFileName, true))
1009 CLog::Log(LOGERROR, "CCurlFile::{} - <{}> Unable to open file for write: {} ({})", __FUNCTION__,
1010 CURL::GetRedacted(strURL), strFileName, GetLastError());
1011 return false;
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);
1028 if (!found)
1030 // fallback
1031 Close();
1032 url.Parse("http://www.w3.org/");
1033 found = Exists(url);
1035 Close();
1037 return found;
1040 void CCurlFile::Cancel()
1042 m_state->m_cancelled = true;
1043 while (m_opened)
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;
1056 if (type == "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;
1068 else
1069 CLog::Log(LOGERROR, "CCurFile::{} - <{}> Invalid proxy type \"{}\"", __FUNCTION__,
1070 CURL::GetRedacted(m_url), type);
1071 m_proxyhost = host;
1072 m_proxyport = port;
1073 m_proxyuser = user;
1074 m_proxypassword = password;
1077 bool CCurlFile::Open(const CURL& url)
1079 m_opened = true;
1080 m_seekable = true;
1082 CURL url2(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);
1104 long hte = 0;
1106 // Allow HTTP response code 0 for file:// protocol
1107 if (url2.IsProtocol("file"))
1109 hte = -1;
1112 if (m_httpresponse <= hte || (m_failOnError && m_httpresponse >= 400))
1114 std::string error;
1115 if (m_httpresponse >= 400 && CServiceBroker::GetLogging().CanLogComponent(LOGCURL))
1117 error.resize(4096);
1118 ReadLine(&error[0], 4095);
1121 CLog::Log(LOGERROR, "CCurlFile::{} - <{}> Failed with code {}:\n{}", __FUNCTION__,
1122 redactPath, m_httpresponse, error);
1124 return false;
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
1131 // file size.
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__,
1142 redactPath);
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)
1163 m_seekable = false;
1164 if (m_seekable)
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"))
1171 m_seekable = false;
1175 std::string efurl = GetInfoString(CURLINFO_EFFECTIVE_URL);
1176 if (!efurl.empty())
1178 if (m_url != efurl)
1180 std::string redactEfpath = CURL::GetRedacted(efurl);
1181 CLog::Log(LOGDEBUG, "CCurlFile::{} - <{}> Effective URL is {}", __FUNCTION__, redactPath,
1182 redactEfpath);
1184 m_url = efurl;
1187 return true;
1190 bool CCurlFile::OpenForWrite(const CURL& url, bool bOverWrite)
1192 if(m_opened)
1193 return false;
1195 if (Exists(url) && !bOverWrite)
1196 return false;
1198 CURL url2(url);
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);
1214 if (!efurl.empty())
1215 m_url = efurl;
1217 m_opened = true;
1218 m_forWrite = true;
1219 m_inError = false;
1220 m_writeOffset = 0;
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);
1230 return true;
1233 ssize_t CCurlFile::Write(const void* lpBuf, size_t uiBufSize)
1235 if (!(m_opened && m_forWrite) || m_inError)
1236 return -1;
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;
1246 m_stillRunning = 1;
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)
1252 break;
1254 if (result != CURLM_OK)
1256 long code;
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);
1260 m_inError = true;
1261 return -1;
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)
1283 CLog::Log(
1284 LOGWARNING,
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;
1297 if (reachedEnd)
1298 break;
1300 foundNewline = buffer[bytesRead] == '\n';
1303 buffer[bytesRead] = '\0';
1304 m_filePos += bytesRead;
1305 if (bytesRead == 0)
1306 return {ReadLineResult::FAILURE, 0};
1307 else if (foundNewline)
1308 return {ReadLineResult::OK, bytesRead - 1};
1309 else if (reachedEnd)
1310 return {ReadLineResult::OK, bytesRead};
1311 else
1312 return {ReadLineResult::TRUNCATED, bytesRead};
1315 bool CCurlFile::ReOpen(const CURL& url)
1317 Close();
1318 return Open(url);
1321 bool CCurlFile::Exists(const CURL& url)
1323 // if file is already running, get info from it
1324 if( m_opened )
1326 CLog::Log(LOGWARNING, "CCurlFile::{} - <{}> Exist called on open file", __FUNCTION__,
1327 url.GetRedacted());
1328 return true;
1331 CURL url2(url);
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);
1351 else
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);
1360 return true;
1363 if (result == CURLE_HTTP_RETURNED_ERROR)
1365 long code;
1366 if(g_curlInterface.easy_getinfo(m_state->m_easyHandle, CURLINFO_RESPONSE_CODE, &code) == CURLE_OK && code != 404 )
1368 if (code == 405)
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);
1387 return true;
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);
1397 else
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);
1408 errno = ENOENT;
1409 g_curlInterface.easy_release(&m_state->m_easyHandle, NULL);
1410 return false;
1413 int64_t CCurlFile::Seek(int64_t iFilePosition, int iWhence)
1415 int64_t nextPos = m_state->m_filePos;
1417 if(!m_seekable)
1418 return -1;
1420 switch(iWhence)
1422 case SEEK_SET:
1423 nextPos = iFilePosition;
1424 break;
1425 case SEEK_CUR:
1426 nextPos += iFilePosition;
1427 break;
1428 case SEEK_END:
1429 if (m_state->m_fileSize)
1430 nextPos = m_state->m_fileSize + iFilePosition;
1431 else
1432 return -1;
1433 break;
1434 default:
1435 return -1;
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))
1442 return nextPos;
1444 if (m_multisession)
1446 if (!m_oldState)
1448 CURL url(m_url);
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 );
1457 else
1459 CReadState *tmp;
1460 tmp = m_state;
1461 m_state = m_oldState;
1462 m_oldState = tmp;
1464 if (m_state->Seek(nextPos))
1465 return nextPos;
1467 m_state->Disconnect();
1470 else
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))
1487 if(m_multisession)
1489 if (m_oldState)
1491 delete m_state;
1492 m_state = m_oldState;
1493 m_oldState = NULL;
1495 // Retry without multisession
1496 m_multisession = false;
1497 return Seek(iFilePosition, iWhence);
1499 else
1501 m_seekable = false;
1502 return -1;
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
1526 if( m_opened )
1528 CLog::Log(LOGWARNING, "CCurlFile::{} - <{}> Stat called on open file", __FUNCTION__,
1529 url.GetRedacted());
1530 if (buffer)
1532 *buffer = {};
1533 buffer->st_size = GetLength();
1534 buffer->st_mode = _S_IFREG;
1536 return 0;
1539 CURL url2(url);
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);
1558 else
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)
1566 long code;
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);
1570 errno = ENOENT;
1571 return -1;
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);
1588 #else
1589 g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_PROGRESSFUNCTION, transfer_abort_callback);
1590 #endif
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);
1600 errno = ENOENT;
1601 CLog::Log(LOGERROR, "CCurlFile::{} - <{}> Failed: {}({})", __FUNCTION__, url.GetRedacted(),
1602 g_curlInterface.easy_strerror(result), result);
1603 return -1;
1606 #if LIBCURL_VERSION_NUM >= 0x073700 // CURL_AT_LEAST_VERSION(0, 7, 55)
1607 curl_off_t length;
1608 result = g_curlInterface.easy_getinfo(m_state->m_easyHandle, CURLINFO_CONTENT_LENGTH_DOWNLOAD_T,
1609 &length);
1610 #else
1611 double length;
1612 result = g_curlInterface.easy_getinfo(m_state->m_easyHandle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &length);
1613 #endif
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);
1621 errno = ENOENT;
1622 return -1;
1624 else
1625 length = 0.0;
1628 SetCorrectHeaders(m_state);
1630 if (buffer)
1632 *buffer = {};
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;
1644 else
1645 buffer->st_mode = _S_IFREG;
1647 long filetime;
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);
1654 else
1656 if (filetime != -1)
1657 buffer->st_mtime = filetime;
1660 g_curlInterface.easy_release(&m_state->m_easyHandle, NULL);
1661 return 0;
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)
1674 return 0;
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))
1683 m_filePos += want;
1684 return 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 "
1692 "pos {}, size {}",
1693 __FUNCTION__, fmt::ptr(this), m_filePos, m_fileSize);
1694 return -1;
1697 return 0;
1700 /* use to attempt to fill the read buffer up to requested number of bytes */
1701 int8_t CCurlFile::CReadState::FillBuffer(unsigned int want)
1703 int retry = 0;
1704 fd_set fdread;
1705 fd_set fdwrite;
1706 fd_set fdexcep;
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 )
1712 if (m_cancelled)
1713 return FILLBUFFER_NO_DATA;
1715 /* if there is data in overflow buffer, try to use that first */
1716 if (m_overflowSize)
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;
1725 // Shrink memory:
1726 m_overflowBuffer = (char*)realloc_simple(m_overflowBuffer, m_overflowSize);
1727 continue;
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;
1739 // check for errors
1740 int msgs;
1741 CURLMsg* msg;
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;
1751 long httpCode = 0;
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)
1758 CLog::Log(LOGERROR,
1759 "CCurlFile::CReadState::{} - ({}) Failed: HTTP returned code {}",
1760 __FUNCTION__, fmt::ptr(this), httpCode);
1762 else
1764 CLog::Log(LOGERROR, "CCurlFile::CReadState::{} - ({}) Failed: {}({})", __FUNCTION__,
1765 fmt::ptr(this), g_curlInterface.easy_strerror(msg->data.result),
1766 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) &&
1773 !m_bFirstLoop)
1775 bRetryNow = false; // Leave it to caller whether the operation is retried
1776 bError = true;
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 */) &&
1781 m_bFirstLoop &&
1782 m_filePos == 0 &&
1783 m_sendRange)
1785 // If server returns a (possible) range error, disable range and retry (handled below)
1786 bRetryNow = true;
1787 bError = true;
1788 m_sendRange = false;
1790 else
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;
1802 // Close handle
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()
1807 m_buffer.Clear();
1808 free(m_overflowBuffer);
1809 m_overflowBuffer = NULL;
1810 m_overflowSize = 0;
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))
1816 retry++;
1818 // Connect + seek to current position (again)
1819 SetResume();
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:
1826 continue;
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;
1841 switch (result)
1843 case CURLM_OK:
1845 int maxfd = -1;
1846 FD_ZERO(&fdread);
1847 FD_ZERO(&fdwrite);
1848 FD_ZERO(&fdexcep);
1850 // get file descriptors from the transfers
1851 g_curlInterface.multi_fdset(m_multiHandle, &fdread, &fdwrite, &fdexcep, &maxfd);
1853 long timeout = 0;
1854 if (CURLM_OK != g_curlInterface.multi_timeout(m_multiHandle, &timeout) || timeout == -1 || timeout < 200)
1855 timeout = 200;
1857 XbmcThreads::EndTime<> endTime{std::chrono::milliseconds(timeout)};
1858 int rc;
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.
1868 if (maxfd == -1)
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);
1876 rc = 0;
1877 #else
1878 /* Portable sleep for platforms other than Windows. */
1879 struct timeval wait = { 0, 100 * 1000 }; /* 100ms */
1880 rc = select(0, NULL, NULL, NULL, &wait);
1881 #endif
1883 else
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);
1891 #else
1892 } while(rc == SOCKET_ERROR && errno == EINTR);
1893 #endif
1895 if(rc == SOCKET_ERROR)
1897 #ifdef TARGET_WINDOWS
1898 char buf[256];
1899 strerror_s(buf, 256, WSAGetLastError());
1900 CLog::Log(LOGERROR, "CCurlFile::CReadState::{} - ({}) Failed with socket error:{}",
1901 __FUNCTION__, fmt::ptr(this), buf);
1902 #else
1903 char const * str = strerror(errno);
1904 CLog::Log(LOGERROR, "CCurlFile::CReadState::{} - ({}) Failed with socket error:{}",
1905 __FUNCTION__, fmt::ptr(this), str);
1906 #endif
1908 return FILLBUFFER_FAIL;
1911 break;
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
1917 continue;
1919 break;
1920 default:
1922 CLog::Log(LOGERROR,
1923 "CCurlFile::CReadState::{} - ({}) Multi perform failed with code {}, aborting",
1924 __FUNCTION__, fmt::ptr(this), result);
1925 return FILLBUFFER_FAIL;
1927 break;
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;
1937 m_filePos = 0;
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)
1962 char* info{};
1963 CURLcode result = g_curlInterface.easy_getinfo(m_state->m_easyHandle, static_cast<CURLINFO> (infoType), &info);
1964 if (result != CURLE_OK)
1966 CLog::Log(LOGERROR,
1967 "CCurlFile::{} - <{}> Info string request for type {} failed with result code {}",
1968 __FUNCTION__, CURL::GetRedacted(m_url), infoType, result);
1969 return "";
1971 return (info ? info : "");
1974 /* STATIC FUNCTIONS */
1975 bool CCurlFile::GetHttpHeader(const CURL &url, CHttpHeader &headers)
1979 CCurlFile file;
1980 if(file.Stat(url, NULL) == 0)
1982 headers = file.GetHttpHeader();
1983 return true;
1985 return false;
1987 catch(...)
1989 CLog::Log(LOGERROR, "CCurlFile::{} - <{}> Exception thrown while trying to retrieve header",
1990 __FUNCTION__, url.GetRedacted());
1991 return false;
1995 bool CCurlFile::GetMimeType(const CURL &url, std::string &content, const std::string &useragent)
1997 CCurlFile file;
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";
2007 else
2008 content = file.GetProperty(XFILE::FileProperty::MIME_TYPE);
2009 CLog::Log(LOGDEBUG, "CCurlFile::{} - <{}> -> {}", __FUNCTION__, redactUrl, content);
2010 return true;
2012 CLog::Log(LOGDEBUG, "CCurlFile::{} - <{}> -> failed", __FUNCTION__, redactUrl);
2013 content.clear();
2014 return false;
2017 bool CCurlFile::GetContentType(const CURL &url, std::string &content, const std::string &useragent)
2019 CCurlFile file;
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";
2029 else
2030 content = file.GetProperty(XFILE::FileProperty::CONTENT_TYPE, "");
2031 CLog::Log(LOGDEBUG, "CCurlFile::{} - <{}> -> {}", __FUNCTION__, redactUrl, content);
2032 return true;
2034 CLog::Log(LOGDEBUG, "CCurlFile::{} - <{}> -> failed", __FUNCTION__, redactUrl);
2035 content.clear();
2036 return false;
2039 bool CCurlFile::GetCookies(const CURL &url, std::string &cookies)
2041 std::string cookiesStr;
2042 curl_slist* curlCookies;
2043 CURL_HANDLE* easyHandle;
2044 CURLM* multiHandle;
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;
2066 continue;
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())
2076 cookiesStr += "\n";
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;
2093 return true;
2097 // no cookies to return
2098 return false;
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;
2109 return 0;
2112 return -1;
2115 const std::string CCurlFile::GetProperty(XFILE::FileProperty type, const std::string &name) const
2117 switch (type)
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 : "";
2135 default:
2136 return "";
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);
2148 if (!value.empty())
2150 values.emplace_back(value);
2152 return values;
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) ==
2160 CURLE_OK)
2161 return speed;
2162 #else
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
2166 && time > 0.0)
2168 return size / time;
2170 #endif
2171 return 0.0;