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