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