1 /*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9 #include "CurlFile.h"
10
11 #include "File.h"
12 #include "ServiceBroker.h"
13 #include "URL.h"
14 #include "Util.h"
15 #include "filesystem/SpecialProtocol.h"
16 #include "network/DNSNameCache.h"
17 #include "settings/AdvancedSettings.h"
18 #include "settings/Settings.h"
19 #include "settings/SettingsComponent.h"
20 #include "threads/SystemClock.h"
21 #include "utils/Base64.h"
22 #include "utils/XTimeUtils.h"
23
24 #include <algorithm>
25 #include <cassert>
26 #include <climits>
27 #include <vector>
28
29 #ifdef TARGET_POSIX
30 #include <errno.h>
31 #include <inttypes.h>
32 #include "platform/posix/ConvUtils.h"
33 #endif
34
35 #include "DllLibCurl.h"
36 #include "ShoutcastFile.h"
37 #include "utils/CharsetConverter.h"
38 #include "utils/log.h"
39 #include "utils/StringUtils.h"
40
41 using namespace XFILE;
42 using namespace XCURL;
43
44 #define FITS_INT(a) (((a) <= INT_MAX) && ((a) >= INT_MIN))
45
46 curl_proxytype proxyType2CUrlProxyType[] = {
47 CURLPROXY_HTTP,
48 CURLPROXY_SOCKS4,
49 CURLPROXY_SOCKS4A,
50 CURLPROXY_SOCKS5,
51 CURLPROXY_SOCKS5_HOSTNAME,
52 };
53
54 #define FILLBUFFER_OK 0
55 #define FILLBUFFER_NO_DATA 1
56 #define FILLBUFFER_FAIL 2
57
58 // curl calls this routine to debug
debug_callback(CURL_HANDLE * handle,curl_infotype info,char * output,size_t size,void * data)59 extern "C" int debug_callback(CURL_HANDLE *handle, curl_infotype info, char *output, size_t size, void *data)
60 {
61 if (info == CURLINFO_DATA_IN || info == CURLINFO_DATA_OUT)
62 return 0;
63
64 if (!CServiceBroker::GetLogging().CanLogComponent(LOGCURL))
65 return 0;
66
67 std::string strLine;
68 strLine.append(output, size);
69 std::vector<std::string> vecLines;
70 StringUtils::Tokenize(strLine, vecLines, "\r\n");
71 std::vector<std::string>::const_iterator it = vecLines.begin();
72
73 const char *infotype;
74 switch(info)
75 {
76 case CURLINFO_TEXT : infotype = "TEXT: "; break;
77 case CURLINFO_HEADER_IN : infotype = "HEADER_IN: "; break;
78 case CURLINFO_HEADER_OUT : infotype = "HEADER_OUT: "; break;
79 case CURLINFO_SSL_DATA_IN : infotype = "SSL_DATA_IN: "; break;
80 case CURLINFO_SSL_DATA_OUT : infotype = "SSL_DATA_OUT: "; break;
81 case CURLINFO_END : infotype = "END: "; break;
82 default : infotype = ""; break;
83 }
84
85 while (it != vecLines.end())
86 {
87 CLog::Log(LOGDEBUG, "Curl::Debug - %s%s", infotype, (*it).c_str());
88 it++;
89 }
90 return 0;
91 }
92
93 /* curl calls this routine to get more data */
write_callback(char * buffer,size_t size,size_t nitems,void * userp)94 extern "C" size_t write_callback(char *buffer,
95 size_t size,
96 size_t nitems,
97 void *userp)
98 {
99 if(userp == NULL) return 0;
100
101 CCurlFile::CReadState *state = (CCurlFile::CReadState *)userp;
102 return state->WriteCallback(buffer, size, nitems);
103 }
104
read_callback(char * buffer,size_t size,size_t nitems,void * userp)105 extern "C" size_t read_callback(char *buffer,
106 size_t size,
107 size_t nitems,
108 void *userp)
109 {
110 if(userp == NULL) return 0;
111
112 CCurlFile::CReadState *state = (CCurlFile::CReadState *)userp;
113 return state->ReadCallback(buffer, size, nitems);
114 }
115
header_callback(void * ptr,size_t size,size_t nmemb,void * stream)116 extern "C" size_t header_callback(void *ptr, size_t size, size_t nmemb, void *stream)
117 {
118 CCurlFile::CReadState *state = (CCurlFile::CReadState *)stream;
119 return state->HeaderCallback(ptr, size, nmemb);
120 }
121
122 /* used only by CCurlFile::Stat to bail out of unwanted transfers */
transfer_abort_callback(void * clientp,curl_off_t dltotal,curl_off_t dlnow,curl_off_t ultotal,curl_off_t ulnow)123 extern "C" int transfer_abort_callback(void *clientp,
124 curl_off_t dltotal,
125 curl_off_t dlnow,
126 curl_off_t ultotal,
127 curl_off_t ulnow)
128 {
129 if(dlnow > 0)
130 return 1;
131 else
132 return 0;
133 }
134
135 /* fix for silly behavior of realloc */
realloc_simple(void * ptr,size_t size)136 static inline void* realloc_simple(void *ptr, size_t size)
137 {
138 void *ptr2 = realloc(ptr, size);
139 if(ptr && !ptr2 && size > 0)
140 {
141 free(ptr);
142 return NULL;
143 }
144 else
145 return ptr2;
146 }
147
148 static constexpr int CURL_OFF = 0L;
149 static constexpr int CURL_ON = 1L;
150
HeaderCallback(void * ptr,size_t size,size_t nmemb)151 size_t CCurlFile::CReadState::HeaderCallback(void *ptr, size_t size, size_t nmemb)
152 {
153 std::string inString;
154 // libcurl doc says that this info is not always \0 terminated
155 const char* strBuf = (const char*)ptr;
156 const size_t iSize = size * nmemb;
157 if (strBuf[iSize - 1] == 0)
158 inString.assign(strBuf, iSize - 1); // skip last char if it's zero
159 else
160 inString.append(strBuf, iSize);
161
162 m_httpheader.Parse(inString);
163
164 return iSize;
165 }
166
ReadCallback(char * buffer,size_t size,size_t nitems)167 size_t CCurlFile::CReadState::ReadCallback(char *buffer, size_t size, size_t nitems)
168 {
169 if (m_fileSize == 0)
170 return 0;
171
172 if (m_filePos >= m_fileSize)
173 {
174 m_isPaused = true;
175 return CURL_READFUNC_PAUSE;
176 }
177
178 int64_t retSize = std::min(m_fileSize - m_filePos, int64_t(nitems * size));
179 memcpy(buffer, m_readBuffer + m_filePos, retSize);
180 m_filePos += retSize;
181
182 return retSize;
183 }
184
WriteCallback(char * buffer,size_t size,size_t nitems)185 size_t CCurlFile::CReadState::WriteCallback(char *buffer, size_t size, size_t nitems)
186 {
187 unsigned int amount = size * nitems;
188 if (m_overflowSize)
189 {
190 // we have our overflow buffer - first get rid of as much as we can
191 unsigned int maxWriteable = std::min(m_buffer.getMaxWriteSize(), m_overflowSize);
192 if (maxWriteable)
193 {
194 if (!m_buffer.WriteData(m_overflowBuffer, maxWriteable))
195 {
196 CLog::Log(LOGERROR, "CCurlFile::WriteCallback - Unable to write to buffer - what's up?");
197 return 0;
198 }
199
200 if (maxWriteable < m_overflowSize)
201 {
202 // still have some more - copy it down
203 memmove(m_overflowBuffer, m_overflowBuffer + maxWriteable, m_overflowSize - maxWriteable);
204 }
205 m_overflowSize -= maxWriteable;
206
207 // Shrink memory:
208 m_overflowBuffer = (char*)realloc_simple(m_overflowBuffer, m_overflowSize);
209 }
210 }
211 // ok, now copy the data into our ring buffer
212 unsigned int maxWriteable = std::min(m_buffer.getMaxWriteSize(), amount);
213 if (maxWriteable)
214 {
215 if (!m_buffer.WriteData(buffer, maxWriteable))
216 {
217 CLog::Log(LOGERROR, "CCurlFile::WriteCallback - Unable to write to buffer with %i bytes - what's up?", maxWriteable);
218 return 0;
219 }
220 else
221 {
222 amount -= maxWriteable;
223 buffer += maxWriteable;
224 }
225 }
226 if (amount)
227 {
228 //! @todo Limit max. amount of the overflowbuffer
229 m_overflowBuffer = (char*)realloc_simple(m_overflowBuffer, amount + m_overflowSize);
230 if(m_overflowBuffer == NULL)
231 {
232 CLog::Log(LOGWARNING, "CCurlFile::WriteCallback - Failed to grow overflow buffer from %i bytes to %i bytes", m_overflowSize, amount + m_overflowSize);
233 return 0;
234 }
235 memcpy(m_overflowBuffer + m_overflowSize, buffer, amount);
236 m_overflowSize += amount;
237 }
238 return size * nitems;
239 }
240
CReadState()241 CCurlFile::CReadState::CReadState()
242 {
243 m_easyHandle = NULL;
244 m_multiHandle = NULL;
245 m_overflowBuffer = NULL;
246 m_overflowSize = 0;
247 m_stillRunning = 0;
248 m_filePos = 0;
249 m_fileSize = 0;
250 m_bufferSize = 0;
251 m_cancelled = false;
252 m_bFirstLoop = true;
253 m_sendRange = true;
254 m_bLastError = false;
255 m_readBuffer = 0;
256 m_isPaused = false;
257 m_bRetry = true;
258 m_curlHeaderList = NULL;
259 m_curlAliasList = NULL;
260 }
261
~CReadState()262 CCurlFile::CReadState::~CReadState()
263 {
264 Disconnect();
265
266 if(m_easyHandle)
267 g_curlInterface.easy_release(&m_easyHandle, &m_multiHandle);
268 }
269
Seek(int64_t pos)270 bool CCurlFile::CReadState::Seek(int64_t pos)
271 {
272 if(pos == m_filePos)
273 return true;
274
275 if(FITS_INT(pos - m_filePos) && m_buffer.SkipBytes((int)(pos - m_filePos)))
276 {
277 m_filePos = pos;
278 return true;
279 }
280
281 if(pos > m_filePos && pos < m_filePos + m_bufferSize)
282 {
283 int len = m_buffer.getMaxReadSize();
284 m_filePos += len;
285 m_buffer.SkipBytes(len);
286 if (FillBuffer(m_bufferSize) != FILLBUFFER_OK)
287 {
288 if(!m_buffer.SkipBytes(-len))
289 CLog::Log(LOGERROR, "%s - Failed to restore position after failed fill", __FUNCTION__);
290 else
291 m_filePos -= len;
292 return false;
293 }
294
295 if(!FITS_INT(pos - m_filePos) || !m_buffer.SkipBytes((int)(pos - m_filePos)))
296 {
297 CLog::Log(LOGERROR, "%s - Failed to skip to position after having filled buffer", __FUNCTION__);
298 if(!m_buffer.SkipBytes(-len))
299 CLog::Log(LOGERROR, "%s - Failed to restore position after failed seek", __FUNCTION__);
300 else
301 m_filePos -= len;
302 return false;
303 }
304 m_filePos = pos;
305 return true;
306 }
307 return false;
308 }
309
SetResume(void)310 void CCurlFile::CReadState::SetResume(void)
311 {
312 /*
313 * Explicitly set RANGE header when filepos=0 as some http servers require us to always send the range
314 * request header. If we don't the server may provide different content causing seeking to fail.
315 * This only affects HTTP-like items, for FTP it's a null operation.
316 */
317 if (m_sendRange && m_filePos == 0)
318 g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_RANGE, "0-");
319 else
320 {
321 g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_RANGE, NULL);
322 m_sendRange = false;
323 }
324
325 g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_RESUME_FROM_LARGE, m_filePos);
326 }
327
Connect(unsigned int size)328 long CCurlFile::CReadState::Connect(unsigned int size)
329 {
330 if (m_filePos != 0)
331 CLog::Log(LOGDEBUG,"CurlFile::CReadState::Connect - Resume from position %" PRId64, m_filePos);
332
333 SetResume();
334 g_curlInterface.multi_add_handle(m_multiHandle, m_easyHandle);
335
336 m_bufferSize = size;
337 m_buffer.Destroy();
338 m_buffer.Create(size * 3);
339 m_httpheader.Clear();
340
341 // read some data in to try and obtain the length
342 // maybe there's a better way to get this info??
343 m_stillRunning = 1;
344
345 // (Try to) fill buffer
346 if (FillBuffer(1) != FILLBUFFER_OK)
347 {
348 // Check response code
349 long response;
350 if (CURLE_OK == g_curlInterface.easy_getinfo(m_easyHandle, CURLINFO_RESPONSE_CODE, &response))
351 return response;
352 else
353 return -1;
354 }
355
356 double length;
357 if (CURLE_OK == g_curlInterface.easy_getinfo(m_easyHandle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &length))
358 {
359 if (length < 0)
360 length = 0.0;
361 m_fileSize = m_filePos + (int64_t)length;
362 }
363
364 long response;
365 if (CURLE_OK == g_curlInterface.easy_getinfo(m_easyHandle, CURLINFO_RESPONSE_CODE, &response))
366 return response;
367
368 return -1;
369 }
370
Disconnect()371 void CCurlFile::CReadState::Disconnect()
372 {
373 if(m_multiHandle && m_easyHandle)
374 g_curlInterface.multi_remove_handle(m_multiHandle, m_easyHandle);
375
376 m_buffer.Clear();
377 free(m_overflowBuffer);
378 m_overflowBuffer = NULL;
379 m_overflowSize = 0;
380 m_filePos = 0;
381 m_fileSize = 0;
382 m_bufferSize = 0;
383 m_readBuffer = 0;
384
385 /* cleanup */
386 if( m_curlHeaderList )
387 g_curlInterface.slist_free_all(m_curlHeaderList);
388 m_curlHeaderList = NULL;
389
390 if( m_curlAliasList )
391 g_curlInterface.slist_free_all(m_curlAliasList);
392 m_curlAliasList = NULL;
393 }
394
395
~CCurlFile()396 CCurlFile::~CCurlFile()
397 {
398 Close();
399 delete m_state;
400 delete m_oldState;
401 }
402
CCurlFile()403 CCurlFile::CCurlFile()
404 : m_overflowBuffer(NULL)
405 {
406 m_opened = false;
407 m_forWrite = false;
408 m_inError = false;
409 m_multisession = true;
410 m_seekable = true;
411 m_connecttimeout = 0;
412 m_redirectlimit = 5;
413 m_lowspeedtime = 0;
414 m_ftpauth = "";
415 m_ftpport = "";
416 m_ftppasvip = false;
417 m_bufferSize = 32768;
418 m_postdata = "";
419 m_postdataset = false;
420 m_username = "";
421 m_password = "";
422 m_httpauth = "";
423 m_cipherlist = "";
424 m_state = new CReadState();
425 m_oldState = NULL;
426 m_skipshout = false;
427 m_httpresponse = -1;
428 m_acceptCharset = "UTF-8,*;q=0.8"; /* prefer UTF-8 if available */
429 m_allowRetry = true;
430 m_acceptencoding = "all"; /* Accept all supported encoding by default */
431 }
432
433 //Has to be called before Open()
SetBufferSize(unsigned int size)434 void CCurlFile::SetBufferSize(unsigned int size)
435 {
436 m_bufferSize = size;
437 }
438
Close()439 void CCurlFile::Close()
440 {
441 if (m_opened && m_forWrite && !m_inError)
442 Write(NULL, 0);
443
444 m_state->Disconnect();
445 delete m_oldState;
446 m_oldState = NULL;
447
448 m_url.clear();
449 m_referer.clear();
450 m_cookie.clear();
451
452 m_opened = false;
453 m_forWrite = false;
454 m_inError = false;
455
456 if (m_dnsCacheList)
457 g_curlInterface.slist_free_all(m_dnsCacheList);
458 m_dnsCacheList = nullptr;
459 }
460
SetCommonOptions(CReadState * state,bool failOnError)461 void CCurlFile::SetCommonOptions(CReadState* state, bool failOnError /* = true */)
462 {
463 CURL_HANDLE* h = state->m_easyHandle;
464
465 g_curlInterface.easy_reset(h);
466
467 g_curlInterface.easy_setopt(h, CURLOPT_DEBUGFUNCTION, debug_callback);
468
469 if( CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_logLevel >= LOG_LEVEL_DEBUG )
470 g_curlInterface.easy_setopt(h, CURLOPT_VERBOSE, CURL_ON);
471 else
472 g_curlInterface.easy_setopt(h, CURLOPT_VERBOSE, CURL_OFF);
473
474 g_curlInterface.easy_setopt(h, CURLOPT_WRITEDATA, state);
475 g_curlInterface.easy_setopt(h, CURLOPT_WRITEFUNCTION, write_callback);
476
477 g_curlInterface.easy_setopt(h, CURLOPT_READDATA, state);
478 g_curlInterface.easy_setopt(h, CURLOPT_READFUNCTION, read_callback);
479
480 // use DNS cache
481 g_curlInterface.easy_setopt(h, CURLOPT_RESOLVE, m_dnsCacheList);
482
483 // make sure headers are separated from the data stream
484 g_curlInterface.easy_setopt(h, CURLOPT_WRITEHEADER, state);
485 g_curlInterface.easy_setopt(h, CURLOPT_HEADERFUNCTION, header_callback);
486 g_curlInterface.easy_setopt(h, CURLOPT_HEADER, CURL_OFF);
487
488 g_curlInterface.easy_setopt(h, CURLOPT_FTP_USE_EPSV, 0); // turn off epsv
489
490 // Allow us to follow redirects
491 g_curlInterface.easy_setopt(h, CURLOPT_FOLLOWLOCATION, m_redirectlimit != 0);
492 g_curlInterface.easy_setopt(h, CURLOPT_MAXREDIRS, m_redirectlimit);
493
494 // Enable cookie engine for current handle
495 g_curlInterface.easy_setopt(h, CURLOPT_COOKIEFILE, "");
496
497 // Set custom cookie if requested
498 if (!m_cookie.empty())
499 g_curlInterface.easy_setopt(h, CURLOPT_COOKIE, m_cookie.c_str());
500
501 g_curlInterface.easy_setopt(h, CURLOPT_COOKIELIST, "FLUSH");
502
503 // When using multiple threads you should set the CURLOPT_NOSIGNAL option to
504 // TRUE for all handles. Everything will work fine except that timeouts are not
505 // honored during the DNS lookup - which you can work around by building libcurl
506 // with c-ares support. c-ares is a library that provides asynchronous name
507 // resolves. Unfortunately, c-ares does not yet support IPv6.
508 g_curlInterface.easy_setopt(h, CURLOPT_NOSIGNAL, CURL_ON);
509
510 if (failOnError)
511 {
512 // not interested in failed requests
513 g_curlInterface.easy_setopt(h, CURLOPT_FAILONERROR, 1);
514 }
515
516 // enable support for icecast / shoutcast streams
517 if ( NULL == state->m_curlAliasList )
518 // m_curlAliasList is used only by this one place, but SetCommonOptions can
519 // be called multiple times, only append to list if it's empty.
520 state->m_curlAliasList = g_curlInterface.slist_append(state->m_curlAliasList, "ICY 200 OK");
521 g_curlInterface.easy_setopt(h, CURLOPT_HTTP200ALIASES, state->m_curlAliasList);
522
523 if (!m_verifyPeer)
524 g_curlInterface.easy_setopt(h, CURLOPT_SSL_VERIFYPEER, 0);
525
526 g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_URL, m_url.c_str());
527 g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_TRANSFERTEXT, CURL_OFF);
528
529 // setup POST data if it is set (and it may be empty)
530 if (m_postdataset)
531 {
532 g_curlInterface.easy_setopt(h, CURLOPT_POST, 1 );
533 g_curlInterface.easy_setopt(h, CURLOPT_POSTFIELDSIZE, m_postdata.length());
534 g_curlInterface.easy_setopt(h, CURLOPT_POSTFIELDS, m_postdata.c_str());
535 }
536
537 // setup Referer header if needed
538 if (!m_referer.empty())
539 g_curlInterface.easy_setopt(h, CURLOPT_REFERER, m_referer.c_str());
540 else
541 {
542 g_curlInterface.easy_setopt(h, CURLOPT_REFERER, NULL);
543 // Do not send referer header on redirects (same behaviour as ffmpeg and browsers)
544 g_curlInterface.easy_setopt(h, CURLOPT_AUTOREFERER, CURL_OFF);
545 }
546
547 // setup any requested authentication
548 if( !m_ftpauth.empty() )
549 {
550 g_curlInterface.easy_setopt(h, CURLOPT_FTP_SSL, CURLFTPSSL_TRY);
551 if( m_ftpauth == "any" )
552 g_curlInterface.easy_setopt(h, CURLOPT_FTPSSLAUTH, CURLFTPAUTH_DEFAULT);
553 else if( m_ftpauth == "ssl" )
554 g_curlInterface.easy_setopt(h, CURLOPT_FTPSSLAUTH, CURLFTPAUTH_SSL);
555 else if( m_ftpauth == "tls" )
556 g_curlInterface.easy_setopt(h, CURLOPT_FTPSSLAUTH, CURLFTPAUTH_TLS);
557 }
558
559 // setup requested http authentication method
560 bool bAuthSet = false;
561 if(!m_httpauth.empty())
562 {
563 bAuthSet = true;
564 if( m_httpauth == "any" )
565 g_curlInterface.easy_setopt(h, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
566 else if( m_httpauth == "anysafe" )
567 g_curlInterface.easy_setopt(h, CURLOPT_HTTPAUTH, CURLAUTH_ANYSAFE);
568 else if( m_httpauth == "digest" )
569 g_curlInterface.easy_setopt(h, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);
570 else if( m_httpauth == "ntlm" )
571 g_curlInterface.easy_setopt(h, CURLOPT_HTTPAUTH, CURLAUTH_NTLM);
572 else
573 bAuthSet = false;
574 }
575
576 // set username and password for current handle
577 if (!m_username.empty())
578 {
579 g_curlInterface.easy_setopt(h, CURLOPT_USERNAME, m_username.c_str());
580 if (!m_password.empty())
581 g_curlInterface.easy_setopt(h, CURLOPT_PASSWORD, m_password.c_str());
582
583 if (!bAuthSet)
584 g_curlInterface.easy_setopt(h, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
585 }
586
587 // allow passive mode for ftp
588 if( m_ftpport.length() > 0 )
589 g_curlInterface.easy_setopt(h, CURLOPT_FTPPORT, m_ftpport.c_str());
590 else
591 g_curlInterface.easy_setopt(h, CURLOPT_FTPPORT, NULL);
592
593 // allow curl to not use the ip address in the returned pasv response
594 if( m_ftppasvip )
595 g_curlInterface.easy_setopt(h, CURLOPT_FTP_SKIP_PASV_IP, 0);
596 else
597 g_curlInterface.easy_setopt(h, CURLOPT_FTP_SKIP_PASV_IP, 1);
598
599 // setup Accept-Encoding if requested
600 if (m_acceptencoding.length() > 0)
601 g_curlInterface.easy_setopt(h, CURLOPT_ACCEPT_ENCODING, m_acceptencoding == "all" ? "" : m_acceptencoding.c_str());
602
603 if (!m_acceptCharset.empty())
604 SetRequestHeader("Accept-Charset", m_acceptCharset);
605
606 if (m_userAgent.length() > 0)
607 g_curlInterface.easy_setopt(h, CURLOPT_USERAGENT, m_userAgent.c_str());
608 else /* set some default agent as shoutcast doesn't return proper stuff otherwise */
609 g_curlInterface.easy_setopt(h, CURLOPT_USERAGENT, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_userAgent.c_str());
610
611 if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_curlDisableIPV6)
612 g_curlInterface.easy_setopt(h, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
613
614 if (!m_proxyhost.empty())
615 {
616 g_curlInterface.easy_setopt(h, CURLOPT_PROXYTYPE, proxyType2CUrlProxyType[m_proxytype]);
617
618 const std::string hostport = m_proxyhost +
619 StringUtils::Format(":%d", m_proxyport);
620 g_curlInterface.easy_setopt(h, CURLOPT_PROXY, hostport.c_str());
621
622 std::string userpass;
623
624 if (!m_proxyuser.empty() && !m_proxypassword.empty())
625 userpass = CURL::Encode(m_proxyuser) + ":" + CURL::Encode(m_proxypassword);
626
627 if (!userpass.empty())
628 g_curlInterface.easy_setopt(h, CURLOPT_PROXYUSERPWD, userpass.c_str());
629 }
630 if (m_customrequest.length() > 0)
631 g_curlInterface.easy_setopt(h, CURLOPT_CUSTOMREQUEST, m_customrequest.c_str());
632
633 if (m_connecttimeout == 0)
634 m_connecttimeout = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_curlconnecttimeout;
635
636 // set our timeouts, we abort connection after m_timeout, and reads after no data for m_timeout seconds
637 g_curlInterface.easy_setopt(h, CURLOPT_CONNECTTIMEOUT, m_connecttimeout);
638
639 // We abort in case we transfer less than 1byte/second
640 g_curlInterface.easy_setopt(h, CURLOPT_LOW_SPEED_LIMIT, 1);
641
642 if (m_lowspeedtime == 0)
643 m_lowspeedtime = CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_curllowspeedtime;
644
645 // Set the lowspeed time very low as it seems Curl takes much longer to detect a lowspeed condition
646 g_curlInterface.easy_setopt(h, CURLOPT_LOW_SPEED_TIME, m_lowspeedtime);
647
648 // enable tcp keepalive
649 if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_curlKeepAliveInterval > 0)
650 {
651 g_curlInterface.easy_setopt(h, CURLOPT_TCP_KEEPALIVE, 1L);
652 g_curlInterface.easy_setopt(
653 h, CURLOPT_TCP_KEEPIDLE,
654 CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_curlKeepAliveInterval / 2);
655 g_curlInterface.easy_setopt(
656 h, CURLOPT_TCP_KEEPINTVL,
657 CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_curlKeepAliveInterval);
658 }
659
660 // Setup allowed TLS/SSL ciphers. New versions of cURL may deprecate things that are still in use.
661 if (!m_cipherlist.empty())
662 g_curlInterface.easy_setopt(h, CURLOPT_SSL_CIPHER_LIST, m_cipherlist.c_str());
663
664 if (CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_curlDisableHTTP2)
665 g_curlInterface.easy_setopt(h, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
666 else
667 // enable HTTP2 support. default: CURL_HTTP_VERSION_1_1. Curl >= 7.62.0 defaults to CURL_HTTP_VERSION_2TLS
668 g_curlInterface.easy_setopt(h, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2TLS);
669
670 // set CA bundle file
671 std::string caCert = CSpecialProtocol::TranslatePath(
672 CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_caTrustFile);
673 #ifdef TARGET_WINDOWS_STORE
674 // UWP Curl - Setting CURLOPT_CAINFO with a valid cacert file path is required for UWP
675 g_curlInterface.easy_setopt(h, CURLOPT_CAINFO, "system\\certs\\cacert.pem");
676 #endif
677 if (!caCert.empty() && XFILE::CFile::Exists(caCert))
678 g_curlInterface.easy_setopt(h, CURLOPT_CAINFO, caCert.c_str());
679 }
680
SetRequestHeaders(CReadState * state)681 void CCurlFile::SetRequestHeaders(CReadState* state)
682 {
683 if(state->m_curlHeaderList)
684 {
685 g_curlInterface.slist_free_all(state->m_curlHeaderList);
686 state->m_curlHeaderList = NULL;
687 }
688
689 for (const auto& it : m_requestheaders)
690 {
691 std::string buffer = it.first + ": " + it.second;
692 state->m_curlHeaderList = g_curlInterface.slist_append(state->m_curlHeaderList, buffer.c_str());
693 }
694
695 // add user defined headers
696 if (state->m_easyHandle)
697 g_curlInterface.easy_setopt(state->m_easyHandle, CURLOPT_HTTPHEADER, state->m_curlHeaderList);
698 }
699
SetCorrectHeaders(CReadState * state)700 void CCurlFile::SetCorrectHeaders(CReadState* state)
701 {
702 CHttpHeader& h = state->m_httpheader;
703 /* workaround for shoutcast server wich doesn't set content type on standard mp3 */
704 if( h.GetMimeType().empty() )
705 {
706 if( !h.GetValue("icy-notice1").empty()
707 || !h.GetValue("icy-name").empty()
708 || !h.GetValue("icy-br").empty() )
709 h.AddParam("Content-Type", "audio/mpeg");
710 }
711
712 /* hack for google video */
713 if (StringUtils::EqualsNoCase(h.GetMimeType(),"text/html")
714 && !h.GetValue("Content-Disposition").empty() )
715 {
716 std::string strValue = h.GetValue("Content-Disposition");
717 if (strValue.find("filename=") != std::string::npos &&
718 strValue.find(".flv") != std::string::npos)
719 h.AddParam("Content-Type", "video/flv");
720 }
721 }
722
ParseAndCorrectUrl(CURL & url2)723 void CCurlFile::ParseAndCorrectUrl(CURL &url2)
724 {
725 std::string strProtocol = url2.GetTranslatedProtocol();
726 url2.SetProtocol(strProtocol);
727
728 // lookup host in DNS cache
729 std::string resolvedHost;
730 if (CDNSNameCache::Lookup(url2.GetHostName(), resolvedHost))
731 {
732 struct curl_slist* tempCache;
733 int entryPort = url2.GetPort();
734
735 if (entryPort == 0)
736 {
737 if (strProtocol == "http")
738 entryPort = 80;
739 else if (strProtocol == "https")
740 entryPort = 443;
741 else if (strProtocol == "ftp")
742 entryPort = 21;
743 else if (strProtocol == "ftps")
744 entryPort = 990;
745 }
746
747 std::string entryString =
748 url2.GetHostName() + ":" + std::to_string(entryPort) + ":" + resolvedHost;
749 tempCache = g_curlInterface.slist_append(m_dnsCacheList, entryString.c_str());
750
751 if (tempCache)
752 m_dnsCacheList = tempCache;
753 }
754
755 if( url2.IsProtocol("ftp")
756 || url2.IsProtocol("ftps") )
757 {
758 // we was using url options for urls, keep the old code work and warning
759 if (!url2.GetOptions().empty())
760 {
761 CLog::Log(LOGWARNING, "%s: ftp url option is deprecated, please switch to use protocol option (change '?' to '|'), url: [%s]", __FUNCTION__, url2.GetRedacted().c_str());
762 url2.SetProtocolOptions(url2.GetOptions().substr(1));
763 /* ftp has no options */
764 url2.SetOptions("");
765 }
766
767 /* this is uggly, depending on from where */
768 /* we get the link it may or may not be */
769 /* url encoded. if handed from ftpdirectory */
770 /* it won't be so let's handle that case */
771
772 std::string filename(url2.GetFileName());
773 std::vector<std::string> array;
774
775 // if server sent us the filename in non-utf8, we need send back with same encoding.
776 if (url2.GetProtocolOption("utf8") == "0")
777 g_charsetConverter.utf8ToStringCharset(filename);
778
779 //! @todo create a tokenizer that doesn't skip empty's
780 StringUtils::Tokenize(filename, array, "/");
781 filename.clear();
782 for(std::vector<std::string>::iterator it = array.begin(); it != array.end(); it++)
783 {
784 if(it != array.begin())
785 filename += "/";
786
787 filename += CURL::Encode(*it);
788 }
789
790 /* make sure we keep slashes */
791 if(StringUtils::EndsWith(url2.GetFileName(), "/"))
792 filename += "/";
793
794 url2.SetFileName(filename);
795
796 m_ftpauth.clear();
797 if (url2.HasProtocolOption("auth"))
798 {
799 m_ftpauth = url2.GetProtocolOption("auth");
800 StringUtils::ToLower(m_ftpauth);
801 if(m_ftpauth.empty())
802 m_ftpauth = "any";
803 }
804 m_ftpport = "";
805 if (url2.HasProtocolOption("active"))
806 {
807 m_ftpport = url2.GetProtocolOption("active");
808 if(m_ftpport.empty())
809 m_ftpport = "-";
810 }
811 if (url2.HasProtocolOption("verifypeer"))
812 {
813 if (url2.GetProtocolOption("verifypeer") == "false")
814 m_verifyPeer = false;
815 }
816 m_ftppasvip = url2.HasProtocolOption("pasvip") && url2.GetProtocolOption("pasvip") != "0";
817 }
818 else if(url2.IsProtocol("http") ||
819 url2.IsProtocol("https"))
820 {
821 std::shared_ptr<CSettings> s = CServiceBroker::GetSettingsComponent()->GetSettings();
822 if (!s)
823 return;
824
825 if (!url2.IsLocalHost() &&
826 m_proxyhost.empty() &&
827 s->GetBool(CSettings::SETTING_NETWORK_USEHTTPPROXY) &&
828 !s->GetString(CSettings::SETTING_NETWORK_HTTPPROXYSERVER).empty() &&
829 s->GetInt(CSettings::SETTING_NETWORK_HTTPPROXYPORT) > 0)
830 {
831 m_proxytype = (ProxyType)s->GetInt(CSettings::SETTING_NETWORK_HTTPPROXYTYPE);
832 m_proxyhost = s->GetString(CSettings::SETTING_NETWORK_HTTPPROXYSERVER);
833 m_proxyport = s->GetInt(CSettings::SETTING_NETWORK_HTTPPROXYPORT);
834 m_proxyuser = s->GetString(CSettings::SETTING_NETWORK_HTTPPROXYUSERNAME);
835 m_proxypassword = s->GetString(CSettings::SETTING_NETWORK_HTTPPROXYPASSWORD);
836 CLog::Log(LOGDEBUG, "Using proxy %s, type %d", m_proxyhost.c_str(),
837 proxyType2CUrlProxyType[m_proxytype]);
838 }
839
840 // get username and password
841 m_username = url2.GetUserName();
842 m_password = url2.GetPassWord();
843
844 // handle any protocol options
845 std::map<std::string, std::string> options;
846 url2.GetProtocolOptions(options);
847 if (!options.empty())
848 {
849 // set xbmc headers
850 for (const auto& it : options)
851 {
852 std::string name = it.first;
853 StringUtils::ToLower(name);
854 const std::string& value = it.second;
855
856 if (name == "auth")
857 {
858 m_httpauth = value;
859 StringUtils::ToLower(m_httpauth);
860 if(m_httpauth.empty())
861 m_httpauth = "any";
862 }
863 else if (name == "referer")
864 SetReferer(value);
865 else if (name == "user-agent")
866 SetUserAgent(value);
867 else if (name == "cookie")
868 SetCookie(value);
869 else if (name == "acceptencoding" || name == "encoding")
870 SetAcceptEncoding(value);
871 else if (name == "noshout" && value == "true")
872 m_skipshout = true;
873 else if (name == "seekable" && value == "0")
874 m_seekable = false;
875 else if (name == "accept-charset")
876 SetAcceptCharset(value);
877 else if (name == "sslcipherlist")
878 m_cipherlist = value;
879 else if (name == "connection-timeout")
880 m_connecttimeout = strtol(value.c_str(), NULL, 10);
881 else if (name == "failonerror")
882 m_failOnError = value == "true";
883 else if (name == "redirect-limit")
884 m_redirectlimit = strtol(value.c_str(), NULL, 10);
885 else if (name == "postdata")
886 {
887 m_postdata = Base64::Decode(value);
888 m_postdataset = true;
889 }
890 else if (name == "active-remote")// needed for DACP!
891 {
892 SetRequestHeader(it.first, value);
893 }
894 else if (name == "customrequest")
895 {
896 SetCustomRequest(value);
897 }
898 else if (name == "verifypeer")
899 {
900 if (value == "false")
901 m_verifyPeer = false;
902 }
903 else
904 {
905 if (name.length() > 0 && name[0] == '!')
906 {
907 SetRequestHeader(it.first.substr(1), value);
908 CLog::Log(
909 LOGDEBUG,
910 "CurlFile::ParseAndCorrectUrl() adding custom header option '%s: ***********'",
911 it.first.substr(1).c_str());
912 }
913 else
914 {
915 SetRequestHeader(it.first, value);
916 if (name == "authorization")
917 CLog::Log(
918 LOGDEBUG,
919 "CurlFile::ParseAndCorrectUrl() adding custom header option '%s: ***********'",
920 it.first.c_str());
921 else
922 CLog::Log(LOGDEBUG,
923 "CurlFile::ParseAndCorrectUrl() adding custom header option '%s: %s'",
924 it.first.c_str(), value.c_str());
925 }
926 }
927 }
928 }
929 }
930
931 // Unset the protocol options to have an url without protocol options
932 url2.SetProtocolOptions("");
933
934 if (m_username.length() > 0 && m_password.length() > 0)
935 m_url = url2.GetWithoutUserDetails();
936 else
937 m_url = url2.Get();
938 }
939
Post(const std::string & strURL,const std::string & strPostData,std::string & strHTML)940 bool CCurlFile::Post(const std::string& strURL, const std::string& strPostData, std::string& strHTML)
941 {
942 m_postdata = strPostData;
943 m_postdataset = true;
944 return Service(strURL, strHTML);
945 }
946
Get(const std::string & strURL,std::string & strHTML)947 bool CCurlFile::Get(const std::string& strURL, std::string& strHTML)
948 {
949 m_postdata = "";
950 m_postdataset = false;
951 return Service(strURL, strHTML);
952 }
953
Service(const std::string & strURL,std::string & strHTML)954 bool CCurlFile::Service(const std::string& strURL, std::string& strHTML)
955 {
956 const CURL pathToUrl(strURL);
957 if (Open(pathToUrl))
958 {
959 if (ReadData(strHTML))
960 {
961 Close();
962 return true;
963 }
964 }
965 Close();
966 return false;
967 }
968
ReadData(std::string & strHTML)969 bool CCurlFile::ReadData(std::string& strHTML)
970 {
971 int size_read = 0;
972 int data_size = 0;
973 strHTML = "";
974 char buffer[16384];
975 while( (size_read = Read(buffer, sizeof(buffer)-1) ) > 0 )
976 {
977 buffer[size_read] = 0;
978 strHTML.append(buffer, size_read);
979 data_size += size_read;
980 }
981 if (m_state->m_cancelled)
982 return false;
983 return true;
984 }
985
Download(const std::string & strURL,const std::string & strFileName,unsigned int * pdwSize)986 bool CCurlFile::Download(const std::string& strURL, const std::string& strFileName, unsigned int* pdwSize)
987 {
988 CLog::Log(LOGINFO, "CCurlFile::Download - %s->%s", strURL.c_str(), strFileName.c_str());
989
990 std::string strData;
991 if (!Get(strURL, strData))
992 return false;
993
994 XFILE::CFile file;
995 if (!file.OpenForWrite(strFileName, true))
996 {
997 CLog::Log(LOGERROR, "CCurlFile::Download - Unable to open file %s: %u",
998 strFileName.c_str(), GetLastError());
999 return false;
1000 }
1001 ssize_t written = 0;
1002 if (!strData.empty())
1003 written = file.Write(strData.c_str(), strData.size());
1004
1005 if (pdwSize != NULL)
1006 *pdwSize = written > 0 ? written : 0;
1007
1008 return written == static_cast<ssize_t>(strData.size());
1009 }
1010
1011 // Detect whether we are "online" or not! Very simple and dirty!
IsInternet()1012 bool CCurlFile::IsInternet()
1013 {
1014 CURL url("http://www.msftncsi.com/ncsi.txt");
1015 bool found = Exists(url);
1016 if (!found)
1017 {
1018 // fallback
1019 Close();
1020 url.Parse("http://www.w3.org/");
1021 found = Exists(url);
1022 }
1023 Close();
1024
1025 return found;
1026 }
1027
Cancel()1028 void CCurlFile::Cancel()
1029 {
1030 m_state->m_cancelled = true;
1031 while (m_opened)
1032 KODI::TIME::Sleep(1);
1033 }
1034
Reset()1035 void CCurlFile::Reset()
1036 {
1037 m_state->m_cancelled = false;
1038 }
1039
SetProxy(const std::string & type,const std::string & host,uint16_t port,const std::string & user,const std::string & password)1040 void CCurlFile::SetProxy(const std::string &type, const std::string &host,
1041 uint16_t port, const std::string &user, const std::string &password)
1042 {
1043 m_proxytype = CCurlFile::PROXY_HTTP;
1044 if (type == "http")
1045 m_proxytype = CCurlFile::PROXY_HTTP;
1046 else if (type == "socks4")
1047 m_proxytype = CCurlFile::PROXY_SOCKS4;
1048 else if (type == "socks4a")
1049 m_proxytype = CCurlFile::PROXY_SOCKS4A;
1050 else if (type == "socks5")
1051 m_proxytype = CCurlFile::PROXY_SOCKS5;
1052 else if (type == "socks5-remote")
1053 m_proxytype = CCurlFile::PROXY_SOCKS5_REMOTE;
1054 else
1055 CLog::Log(LOGERROR, "Invalid proxy type \"%s\"", type.c_str());
1056 m_proxyhost = host;
1057 m_proxyport = port;
1058 m_proxyuser = user;
1059 m_proxypassword = password;
1060 }
1061
Open(const CURL & url)1062 bool CCurlFile::Open(const CURL& url)
1063 {
1064 m_opened = true;
1065 m_seekable = true;
1066
1067 CURL url2(url);
1068 ParseAndCorrectUrl(url2);
1069
1070 std::string redactPath = CURL::GetRedacted(m_url);
1071 CLog::Log(LOGDEBUG, "CurlFile::Open(%p) %s", (void*)this, redactPath.c_str());
1072
1073 assert(!(!m_state->m_easyHandle ^ !m_state->m_multiHandle));
1074 if( m_state->m_easyHandle == NULL )
1075 g_curlInterface.easy_acquire(url2.GetProtocol().c_str(),
1076 url2.GetHostName().c_str(),
1077 &m_state->m_easyHandle,
1078 &m_state->m_multiHandle);
1079
1080 // setup common curl options
1081 SetCommonOptions(m_state,
1082 m_failOnError && !CServiceBroker::GetLogging().CanLogComponent(LOGCURL));
1083 SetRequestHeaders(m_state);
1084 m_state->m_sendRange = m_seekable;
1085 m_state->m_bRetry = m_allowRetry;
1086
1087 m_httpresponse = m_state->Connect(m_bufferSize);
1088
1089 if (m_httpresponse <= 0 || (m_failOnError && m_httpresponse >= 400))
1090 {
1091 std::string error;
1092 if (m_httpresponse >= 400 && CServiceBroker::GetLogging().CanLogComponent(LOGCURL))
1093 {
1094 error.resize(4096);
1095 ReadString(&error[0], 4095);
1096 }
1097
1098 CLog::Log(LOGERROR, "CCurlFile::Open failed with code %li for %s:\n%s", m_httpresponse, redactPath.c_str(), error.c_str());
1099
1100 return false;
1101 }
1102
1103 SetCorrectHeaders(m_state);
1104
1105 // since we can't know the stream size up front if we're gzipped/deflated
1106 // flag the stream with an unknown file size rather than the compressed
1107 // file size.
1108 if (!m_state->m_httpheader.GetValue("Content-Encoding").empty() && !StringUtils::EqualsNoCase(m_state->m_httpheader.GetValue("Content-Encoding"), "identity"))
1109 m_state->m_fileSize = 0;
1110
1111 // check if this stream is a shoutcast stream. sometimes checking the protocol line is not enough so examine other headers as well.
1112 // shoutcast streams should be handled by FileShoutcast.
1113 if ((m_state->m_httpheader.GetProtoLine().substr(0, 3) == "ICY" || !m_state->m_httpheader.GetValue("icy-notice1").empty()
1114 || !m_state->m_httpheader.GetValue("icy-name").empty()
1115 || !m_state->m_httpheader.GetValue("icy-br").empty()) && !m_skipshout)
1116 {
1117 CLog::Log(LOGDEBUG,"CCurlFile::Open - File <%s> is a shoutcast stream. Re-opening", redactPath.c_str());
1118 throw new CRedirectException(new CShoutcastFile);
1119 }
1120
1121 m_multisession = false;
1122 if(url2.IsProtocol("http") || url2.IsProtocol("https"))
1123 {
1124 m_multisession = true;
1125 if(m_state->m_httpheader.GetValue("Server").find("Portable SDK for UPnP devices") != std::string::npos)
1126 {
1127 CLog::Log(LOGWARNING, "CCurlFile::Open - Disabling multi session due to broken libupnp server");
1128 m_multisession = false;
1129 }
1130 }
1131
1132 if(StringUtils::EqualsNoCase(m_state->m_httpheader.GetValue("Transfer-Encoding"), "chunked"))
1133 m_state->m_fileSize = 0;
1134
1135 if(m_state->m_fileSize <= 0)
1136 m_seekable = false;
1137 if (m_seekable)
1138 {
1139 if(url2.IsProtocol("http")
1140 || url2.IsProtocol("https"))
1141 {
1142 // if server says explicitly it can't seek, respect that
1143 if(StringUtils::EqualsNoCase(m_state->m_httpheader.GetValue("Accept-Ranges"),"none"))
1144 m_seekable = false;
1145 }
1146 }
1147
1148 std::string efurl = GetInfoString(CURLINFO_EFFECTIVE_URL);
1149 if (!efurl.empty())
1150 {
1151 if (m_url != efurl)
1152 {
1153 std::string redactEfpath = CURL::GetRedacted(efurl);
1154 CLog::Log(LOGDEBUG,"CCurlFile::Open - effective URL: <%s>", redactEfpath.c_str());
1155 }
1156 m_url = efurl;
1157 }
1158
1159 return true;
1160 }
1161
OpenForWrite(const CURL & url,bool bOverWrite)1162 bool CCurlFile::OpenForWrite(const CURL& url, bool bOverWrite)
1163 {
1164 if(m_opened)
1165 return false;
1166
1167 if (Exists(url) && !bOverWrite)
1168 return false;
1169
1170 CURL url2(url);
1171 ParseAndCorrectUrl(url2);
1172
1173 CLog::Log(LOGDEBUG, "CCurlFile::OpenForWrite(%p) %s", (void*)this, CURL::GetRedacted(m_url).c_str());
1174
1175 assert(m_state->m_easyHandle == NULL);
1176 g_curlInterface.easy_acquire(url2.GetProtocol().c_str(),
1177 url2.GetHostName().c_str(),
1178 &m_state->m_easyHandle,
1179 &m_state->m_multiHandle);
1180
1181 // setup common curl options
1182 SetCommonOptions(m_state);
1183 SetRequestHeaders(m_state);
1184
1185 std::string efurl = GetInfoString(CURLINFO_EFFECTIVE_URL);
1186 if (!efurl.empty())
1187 m_url = efurl;
1188
1189 m_opened = true;
1190 m_forWrite = true;
1191 m_inError = false;
1192 m_writeOffset = 0;
1193
1194 assert(m_state->m_multiHandle);
1195
1196 g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_UPLOAD, 1);
1197
1198 g_curlInterface.multi_add_handle(m_state->m_multiHandle, m_state->m_easyHandle);
1199
1200 m_state->SetReadBuffer(NULL, 0);
1201
1202 return true;
1203 }
1204
Write(const void * lpBuf,size_t uiBufSize)1205 ssize_t CCurlFile::Write(const void* lpBuf, size_t uiBufSize)
1206 {
1207 if (!(m_opened && m_forWrite) || m_inError)
1208 return -1;
1209
1210 assert(m_state->m_multiHandle);
1211
1212 m_state->SetReadBuffer(lpBuf, uiBufSize);
1213 m_state->m_isPaused = false;
1214 g_curlInterface.easy_pause(m_state->m_easyHandle, CURLPAUSE_CONT);
1215
1216 CURLMcode result = CURLM_OK;
1217
1218 m_stillRunning = 1;
1219 while (m_stillRunning && !m_state->m_isPaused)
1220 {
1221 while ((result = g_curlInterface.multi_perform(m_state->m_multiHandle, &m_stillRunning)) == CURLM_CALL_MULTI_PERFORM);
1222
1223 if (!m_stillRunning)
1224 break;
1225
1226 if (result != CURLM_OK)
1227 {
1228 long code;
1229 if(g_curlInterface.easy_getinfo(m_state->m_easyHandle, CURLINFO_RESPONSE_CODE, &code) == CURLE_OK )
1230 CLog::Log(LOGERROR, "%s - Unable to write curl resource (%s) - %ld", __FUNCTION__, CURL::GetRedacted(m_url).c_str(), code);
1231 m_inError = true;
1232 return -1;
1233 }
1234 }
1235
1236 m_writeOffset += m_state->m_filePos;
1237 return m_state->m_filePos;
1238 }
1239
ReadString(char * szLine,int iLineLength)1240 bool CCurlFile::CReadState::ReadString(char *szLine, int iLineLength)
1241 {
1242 unsigned int want = (unsigned int)iLineLength;
1243
1244 if((m_fileSize == 0 || m_filePos < m_fileSize) && FillBuffer(want) != FILLBUFFER_OK)
1245 return false;
1246
1247 // ensure only available data is considered
1248 want = std::min(m_buffer.getMaxReadSize(), want);
1249
1250 /* check if we finished prematurely */
1251 if (!m_stillRunning && (m_fileSize == 0 || m_filePos != m_fileSize) && !want)
1252 {
1253 if (m_fileSize != 0)
1254 CLog::Log(LOGWARNING, "%s - Transfer ended before entire file was retrieved pos %" PRId64", size %" PRId64, __FUNCTION__, m_filePos, m_fileSize);
1255
1256 return false;
1257 }
1258
1259 char* pLine = szLine;
1260 do
1261 {
1262 if (!m_buffer.ReadData(pLine, 1))
1263 break;
1264
1265 pLine++;
1266 } while (((pLine - 1)[0] != '\n') && ((unsigned int)(pLine - szLine) < want));
1267 pLine[0] = 0;
1268 m_filePos += (pLine - szLine);
1269 return (pLine - szLine) > 0;
1270 }
1271
ReOpen(const CURL & url)1272 bool CCurlFile::ReOpen(const CURL& url)
1273 {
1274 Close();
1275 return Open(url);
1276 }
1277
Exists(const CURL & url)1278 bool CCurlFile::Exists(const CURL& url)
1279 {
1280 // if file is already running, get info from it
1281 if( m_opened )
1282 {
1283 CLog::Log(LOGWARNING, "CCurlFile::Exists - Exist called on open file %s", url.GetRedacted().c_str());
1284 return true;
1285 }
1286
1287 CURL url2(url);
1288 ParseAndCorrectUrl(url2);
1289
1290 assert(m_state->m_easyHandle == NULL);
1291 g_curlInterface.easy_acquire(url2.GetProtocol().c_str(),
1292 url2.GetHostName().c_str(),
1293 &m_state->m_easyHandle, NULL);
1294
1295 SetCommonOptions(m_state);
1296 SetRequestHeaders(m_state);
1297 g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_TIMEOUT, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_curlconnecttimeout);
1298 g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_NOBODY, 1);
1299 g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_WRITEDATA, NULL); /* will cause write failure*/
1300
1301 if(url2.IsProtocol("ftp") || url2.IsProtocol("ftps"))
1302 {
1303 g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_FILETIME, 1);
1304 // nocwd is less standard, will return empty list for non-existed remote dir on some ftp server, avoid it.
1305 if (StringUtils::EndsWith(url2.GetFileName(), "/"))
1306 g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_FTP_FILEMETHOD, CURLFTPMETHOD_SINGLECWD);
1307 else
1308 g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_FTP_FILEMETHOD, CURLFTPMETHOD_NOCWD);
1309 }
1310
1311 CURLcode result = g_curlInterface.easy_perform(m_state->m_easyHandle);
1312
1313 if (result == CURLE_WRITE_ERROR || result == CURLE_OK)
1314 {
1315 g_curlInterface.easy_release(&m_state->m_easyHandle, NULL);
1316 return true;
1317 }
1318
1319 if (result == CURLE_HTTP_RETURNED_ERROR)
1320 {
1321 long code;
1322 if(g_curlInterface.easy_getinfo(m_state->m_easyHandle, CURLINFO_RESPONSE_CODE, &code) == CURLE_OK && code != 404 )
1323 {
1324 if (code == 405)
1325 {
1326 // If we get a Method Not Allowed response, retry with a GET Request
1327 g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_NOBODY, 0);
1328
1329 g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_FILETIME, 1);
1330 g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_XFERINFOFUNCTION, transfer_abort_callback);
1331 g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_NOPROGRESS, 0);
1332
1333 curl_slist *list = NULL;
1334 list = g_curlInterface.slist_append(list, "Range: bytes=0-1"); /* try to only request 1 byte */
1335 g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_HTTPHEADER, list);
1336
1337 CURLcode result = g_curlInterface.easy_perform(m_state->m_easyHandle);
1338 g_curlInterface.slist_free_all(list);
1339
1340 if (result == CURLE_WRITE_ERROR || result == CURLE_OK)
1341 {
1342 g_curlInterface.easy_release(&m_state->m_easyHandle, NULL);
1343 return true;
1344 }
1345
1346 if (result == CURLE_HTTP_RETURNED_ERROR)
1347 {
1348 if (g_curlInterface.easy_getinfo(m_state->m_easyHandle, CURLINFO_RESPONSE_CODE, &code) == CURLE_OK && code != 404 )
1349 CLog::Log(LOGERROR, "CCurlFile::Exists - Failed: HTTP returned error %ld for %s", code, url.GetRedacted().c_str());
1350 }
1351 }
1352 else
1353 CLog::Log(LOGERROR, "CCurlFile::Exists - Failed: HTTP returned error %ld for %s", code, url.GetRedacted().c_str());
1354 }
1355 }
1356 else if (result != CURLE_REMOTE_FILE_NOT_FOUND && result != CURLE_FTP_COULDNT_RETR_FILE)
1357 {
1358 CLog::Log(LOGERROR, "CCurlFile::Exists - Failed: %s(%d) for %s", g_curlInterface.easy_strerror(result), result, url.GetRedacted().c_str());
1359 }
1360
1361 errno = ENOENT;
1362 g_curlInterface.easy_release(&m_state->m_easyHandle, NULL);
1363 return false;
1364 }
1365
Seek(int64_t iFilePosition,int iWhence)1366 int64_t CCurlFile::Seek(int64_t iFilePosition, int iWhence)
1367 {
1368 int64_t nextPos = m_state->m_filePos;
1369
1370 if(!m_seekable)
1371 return -1;
1372
1373 switch(iWhence)
1374 {
1375 case SEEK_SET:
1376 nextPos = iFilePosition;
1377 break;
1378 case SEEK_CUR:
1379 nextPos += iFilePosition;
1380 break;
1381 case SEEK_END:
1382 if (m_state->m_fileSize)
1383 nextPos = m_state->m_fileSize + iFilePosition;
1384 else
1385 return -1;
1386 break;
1387 default:
1388 return -1;
1389 }
1390
1391 // We can't seek beyond EOF
1392 if (m_state->m_fileSize && nextPos > m_state->m_fileSize) return -1;
1393
1394 if(m_state->Seek(nextPos))
1395 return nextPos;
1396
1397 if (m_multisession)
1398 {
1399 if (!m_oldState)
1400 {
1401 CURL url(m_url);
1402 m_oldState = m_state;
1403 m_state = new CReadState();
1404 m_state->m_fileSize = m_oldState->m_fileSize;
1405 g_curlInterface.easy_acquire(url.GetProtocol().c_str(),
1406 url.GetHostName().c_str(),
1407 &m_state->m_easyHandle,
1408 &m_state->m_multiHandle );
1409 }
1410 else
1411 {
1412 CReadState *tmp;
1413 tmp = m_state;
1414 m_state = m_oldState;
1415 m_oldState = tmp;
1416
1417 if (m_state->Seek(nextPos))
1418 return nextPos;
1419
1420 m_state->Disconnect();
1421 }
1422 }
1423 else
1424 m_state->Disconnect();
1425
1426 // re-setup common curl options
1427 SetCommonOptions(m_state);
1428
1429 /* caller might have changed some headers (needed for daap)*/
1430 //! @todo daap is gone. is this needed for something else?
1431 SetRequestHeaders(m_state);
1432
1433 m_state->m_filePos = nextPos;
1434 m_state->m_sendRange = true;
1435 m_state->m_bRetry = m_allowRetry;
1436
1437 long response = m_state->Connect(m_bufferSize);
1438 if(response < 0 && (m_state->m_fileSize == 0 || m_state->m_fileSize != m_state->m_filePos))
1439 {
1440 if(m_multisession)
1441 {
1442 if (m_oldState)
1443 {
1444 delete m_state;
1445 m_state = m_oldState;
1446 m_oldState = NULL;
1447 }
1448 // Retry without multisession
1449 m_multisession = false;
1450 return Seek(iFilePosition, iWhence);
1451 }
1452 else
1453 {
1454 m_seekable = false;
1455 return -1;
1456 }
1457 }
1458
1459 SetCorrectHeaders(m_state);
1460
1461 return m_state->m_filePos;
1462 }
1463
GetLength()1464 int64_t CCurlFile::GetLength()
1465 {
1466 if (!m_opened) return 0;
1467 return m_state->m_fileSize;
1468 }
1469
GetPosition()1470 int64_t CCurlFile::GetPosition()
1471 {
1472 if (!m_opened) return 0;
1473 return m_state->m_filePos;
1474 }
1475
Stat(const CURL & url,struct __stat64 * buffer)1476 int CCurlFile::Stat(const CURL& url, struct __stat64* buffer)
1477 {
1478 // if file is already running, get info from it
1479 if( m_opened )
1480 {
1481 CLog::Log(LOGWARNING, "CCurlFile::Stat - Stat called on open file %s", url.GetRedacted().c_str());
1482 if (buffer)
1483 {
1484 memset(buffer, 0, sizeof(struct __stat64));
1485 buffer->st_size = GetLength();
1486 buffer->st_mode = _S_IFREG;
1487 }
1488 return 0;
1489 }
1490
1491 CURL url2(url);
1492 ParseAndCorrectUrl(url2);
1493
1494 assert(m_state->m_easyHandle == NULL);
1495 g_curlInterface.easy_acquire(url2.GetProtocol().c_str(),
1496 url2.GetHostName().c_str(),
1497 &m_state->m_easyHandle, NULL);
1498
1499 SetCommonOptions(m_state);
1500 SetRequestHeaders(m_state);
1501 g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_TIMEOUT, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_curlconnecttimeout);
1502 g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_NOBODY, 1);
1503 g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_FILETIME , 1);
1504
1505 if(url2.IsProtocol("ftp"))
1506 {
1507 // nocwd is less standard, will return empty list for non-existed remote dir on some ftp server, avoid it.
1508 if (StringUtils::EndsWith(url2.GetFileName(), "/"))
1509 g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_FTP_FILEMETHOD, CURLFTPMETHOD_SINGLECWD);
1510 else
1511 g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_FTP_FILEMETHOD, CURLFTPMETHOD_NOCWD);
1512 }
1513
1514 CURLcode result = g_curlInterface.easy_perform(m_state->m_easyHandle);
1515
1516 if(result == CURLE_HTTP_RETURNED_ERROR)
1517 {
1518 long code;
1519 if(g_curlInterface.easy_getinfo(m_state->m_easyHandle, CURLINFO_RESPONSE_CODE, &code) == CURLE_OK && code == 404 )
1520 {
1521 g_curlInterface.easy_release(&m_state->m_easyHandle, NULL);
1522 errno = ENOENT;
1523 return -1;
1524 }
1525 }
1526
1527 if(result == CURLE_GOT_NOTHING
1528 || result == CURLE_HTTP_RETURNED_ERROR
1529 || result == CURLE_RECV_ERROR /* some silly shoutcast servers */ )
1530 {
1531 /* some http servers and shoutcast servers don't give us any data on a head request */
1532 /* request normal and just bail out via progress meter callback after we received data */
1533 /* somehow curl doesn't reset CURLOPT_NOBODY properly so reset everything */
1534 SetCommonOptions(m_state);
1535 SetRequestHeaders(m_state);
1536 g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_TIMEOUT, CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_curlconnecttimeout);
1537 g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_FILETIME, 1);
1538 #if LIBCURL_VERSION_NUM >= 0x072000 // 0.7.32
1539 g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_XFERINFOFUNCTION, transfer_abort_callback);
1540 #else
1541 g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_PROGRESSFUNCTION, transfer_abort_callback);
1542 #endif
1543 g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_NOPROGRESS, 0);
1544
1545 result = g_curlInterface.easy_perform(m_state->m_easyHandle);
1546
1547 }
1548
1549 if( result != CURLE_ABORTED_BY_CALLBACK && result != CURLE_OK )
1550 {
1551 g_curlInterface.easy_release(&m_state->m_easyHandle, NULL);
1552 errno = ENOENT;
1553 CLog::Log(LOGERROR, "CCurlFile::Stat - Failed: %s(%d) for %s", g_curlInterface.easy_strerror(result), result, url.GetRedacted().c_str());
1554 return -1;
1555 }
1556
1557 double length;
1558 result = g_curlInterface.easy_getinfo(m_state->m_easyHandle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &length);
1559 if (result != CURLE_OK || length < 0.0)
1560 {
1561 if (url.IsProtocol("ftp"))
1562 {
1563 g_curlInterface.easy_release(&m_state->m_easyHandle, NULL);
1564 CLog::Log(LOGINFO, "CCurlFile::Stat - Content length failed: %s(%d) for %s",
1565 g_curlInterface.easy_strerror(result), result, url.GetRedacted().c_str());
1566 errno = ENOENT;
1567 return -1;
1568 }
1569 else
1570 length = 0.0;
1571 }
1572
1573 SetCorrectHeaders(m_state);
1574
1575 if (buffer)
1576 {
1577 *buffer = {};
1578 buffer->st_size = static_cast<int64_t>(length);
1579
1580 // Note: CURLINFO_CONTENT_TYPE returns the last received content-type response header value.
1581 // In case there is authentication required there might be multiple requests involved and if
1582 // the last request whch actually returns the data does not return a content-type header, but
1583 // one of the preceeding requests, CURLINFO_CONTENT_TYPE returns not the content type of the
1584 // actual resource requested! m_state contains only the values of the last request, which is
1585 // what we want here.
1586 const std::string mimeType = m_state->m_httpheader.GetMimeType();
1587 if (mimeType.find("text/html") != std::string::npos) // consider html files directories
1588 buffer->st_mode = _S_IFDIR;
1589 else
1590 buffer->st_mode = _S_IFREG;
1591
1592 long filetime;
1593 result = g_curlInterface.easy_getinfo(m_state->m_easyHandle, CURLINFO_FILETIME, &filetime);
1594 if (result != CURLE_OK)
1595 {
1596 CLog::Log(LOGINFO, "CCurlFile::Stat - Filetime failed: %s(%d) for %s",
1597 g_curlInterface.easy_strerror(result), result, url.GetRedacted().c_str());
1598 }
1599 else
1600 {
1601 if (filetime != -1)
1602 buffer->st_mtime = filetime;
1603 }
1604 }
1605 g_curlInterface.easy_release(&m_state->m_easyHandle, NULL);
1606 return 0;
1607 }
1608
Read(void * lpBuf,size_t uiBufSize)1609 ssize_t CCurlFile::CReadState::Read(void* lpBuf, size_t uiBufSize)
1610 {
1611 /* only request 1 byte, for truncated reads (only if not eof) */
1612 if (m_fileSize == 0 || m_filePos < m_fileSize)
1613 {
1614 int8_t result = FillBuffer(1);
1615 if (result == FILLBUFFER_FAIL)
1616 return -1; // Fatal error
1617
1618 if (result == FILLBUFFER_NO_DATA)
1619 return 0;
1620 }
1621
1622 /* ensure only available data is considered */
1623 unsigned int want = std::min<unsigned int>(m_buffer.getMaxReadSize(), uiBufSize);
1624
1625 /* xfer data to caller */
1626 if (m_buffer.ReadData((char *)lpBuf, want))
1627 {
1628 m_filePos += want;
1629 return want;
1630 }
1631
1632 /* check if we finished prematurely */
1633 if (!m_stillRunning && (m_fileSize == 0 || m_filePos != m_fileSize))
1634 {
1635 CLog::Log(LOGWARNING, "%s - Transfer ended before entire file was retrieved pos %" PRId64", size %" PRId64, __FUNCTION__, m_filePos, m_fileSize);
1636 return -1;
1637 }
1638
1639 return 0;
1640 }
1641
1642 /* use to attempt to fill the read buffer up to requested number of bytes */
FillBuffer(unsigned int want)1643 int8_t CCurlFile::CReadState::FillBuffer(unsigned int want)
1644 {
1645 int retry = 0;
1646 fd_set fdread;
1647 fd_set fdwrite;
1648 fd_set fdexcep;
1649
1650 // only attempt to fill buffer if transactions still running and buffer
1651 // doesn't exceed required size already
1652 while (m_buffer.getMaxReadSize() < want && m_buffer.getMaxWriteSize() > 0 )
1653 {
1654 if (m_cancelled)
1655 return FILLBUFFER_NO_DATA;
1656
1657 /* if there is data in overflow buffer, try to use that first */
1658 if (m_overflowSize)
1659 {
1660 unsigned amount = std::min(m_buffer.getMaxWriteSize(), m_overflowSize);
1661 m_buffer.WriteData(m_overflowBuffer, amount);
1662
1663 if (amount < m_overflowSize)
1664 memmove(m_overflowBuffer, m_overflowBuffer + amount, m_overflowSize - amount);
1665
1666 m_overflowSize -= amount;
1667 // Shrink memory:
1668 m_overflowBuffer = (char*)realloc_simple(m_overflowBuffer, m_overflowSize);
1669 continue;
1670 }
1671
1672 CURLMcode result = g_curlInterface.multi_perform(m_multiHandle, &m_stillRunning);
1673 if (!m_stillRunning)
1674 {
1675 if (result == CURLM_OK)
1676 {
1677 /* if we still have stuff in buffer, we are fine */
1678 if (m_buffer.getMaxReadSize())
1679 return FILLBUFFER_OK;
1680
1681 // check for errors
1682 int msgs;
1683 CURLMsg* msg;
1684 bool bRetryNow = true;
1685 bool bError = false;
1686 while ((msg = g_curlInterface.multi_info_read(m_multiHandle, &msgs)))
1687 {
1688 if (msg->msg == CURLMSG_DONE)
1689 {
1690 if (msg->data.result == CURLE_OK)
1691 return FILLBUFFER_OK;
1692
1693 long httpCode = 0;
1694 if (msg->data.result == CURLE_HTTP_RETURNED_ERROR)
1695 {
1696 g_curlInterface.easy_getinfo(msg->easy_handle, CURLINFO_RESPONSE_CODE, &httpCode);
1697
1698 // Don't log 404 not-found errors to prevent log-spam
1699 if (httpCode != 404)
1700 CLog::Log(LOGERROR, "CCurlFile::FillBuffer - Failed: HTTP returned error %ld", httpCode);
1701 }
1702 else
1703 {
1704 CLog::Log(LOGERROR, "CCurlFile::FillBuffer - Failed: %s(%d)", g_curlInterface.easy_strerror(msg->data.result), msg->data.result);
1705 }
1706
1707 if ( (msg->data.result == CURLE_OPERATION_TIMEDOUT ||
1708 msg->data.result == CURLE_PARTIAL_FILE ||
1709 msg->data.result == CURLE_COULDNT_CONNECT ||
1710 msg->data.result == CURLE_RECV_ERROR) &&
1711 !m_bFirstLoop)
1712 {
1713 bRetryNow = false; // Leave it to caller whether the operation is retried
1714 bError = true;
1715 }
1716 else if ( (msg->data.result == CURLE_HTTP_RANGE_ERROR ||
1717 httpCode == 416 /* = Requested Range Not Satisfiable */ ||
1718 httpCode == 406 /* = Not Acceptable (fixes issues with non compliant HDHomerun servers */) &&
1719 m_bFirstLoop &&
1720 m_filePos == 0 &&
1721 m_sendRange)
1722 {
1723 // If server returns a (possible) range error, disable range and retry (handled below)
1724 bRetryNow = true;
1725 bError = true;
1726 m_sendRange = false;
1727 }
1728 else
1729 {
1730 // For all other errors, abort the operation
1731 return FILLBUFFER_FAIL;
1732 }
1733 }
1734 }
1735
1736 // Check for an actual error, if not, just return no-data
1737 if (!bError && !m_bLastError)
1738 return FILLBUFFER_NO_DATA;
1739
1740 // Close handle
1741 if (m_multiHandle && m_easyHandle)
1742 g_curlInterface.multi_remove_handle(m_multiHandle, m_easyHandle);
1743
1744 // Reset all the stuff like we would in Disconnect()
1745 m_buffer.Clear();
1746 free(m_overflowBuffer);
1747 m_overflowBuffer = NULL;
1748 m_overflowSize = 0;
1749 m_bLastError = true; // Flag error for the next run
1750
1751 // Retry immediately or leave it up to the caller?
1752 if ((m_bRetry && retry < CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_curlretries) || (bRetryNow && retry == 0))
1753 {
1754 retry++;
1755
1756 // Connect + seek to current position (again)
1757 SetResume();
1758 g_curlInterface.multi_add_handle(m_multiHandle, m_easyHandle);
1759
1760 CLog::Log(LOGWARNING, "CCurlFile::FillBuffer - Reconnect, (re)try %i", retry);
1761
1762 // Return to the beginning of the loop:
1763 continue;
1764 }
1765
1766 return FILLBUFFER_NO_DATA; // We failed but flag no data to caller, so it can retry the operation
1767 }
1768 return FILLBUFFER_FAIL;
1769 }
1770
1771 // We've finished out first loop
1772 if(m_bFirstLoop && m_buffer.getMaxReadSize() > 0)
1773 m_bFirstLoop = false;
1774
1775 // No error this run
1776 m_bLastError = false;
1777
1778 switch (result)
1779 {
1780 case CURLM_OK:
1781 {
1782 int maxfd = -1;
1783 FD_ZERO(&fdread);
1784 FD_ZERO(&fdwrite);
1785 FD_ZERO(&fdexcep);
1786
1787 // get file descriptors from the transfers
1788 g_curlInterface.multi_fdset(m_multiHandle, &fdread, &fdwrite, &fdexcep, &maxfd);
1789
1790 long timeout = 0;
1791 if (CURLM_OK != g_curlInterface.multi_timeout(m_multiHandle, &timeout) || timeout == -1 || timeout < 200)
1792 timeout = 200;
1793
1794 XbmcThreads::EndTime endTime(timeout);
1795 int rc;
1796
1797 do
1798 {
1799 /* On success the value of maxfd is guaranteed to be >= -1. We call
1800 * select(maxfd + 1, ...); specially in case of (maxfd == -1) there are
1801 * no fds ready yet so we call select(0, ...) --or Sleep() on Windows--
1802 * to sleep 100ms, which is the minimum suggested value in the
1803 * curl_multi_fdset() doc.
1804 */
1805 if (maxfd == -1)
1806 {
1807 #ifdef TARGET_WINDOWS
1808 /* Windows does not support using select() for sleeping without a dummy
1809 * socket. Instead use Windows' Sleep() and sleep for 100ms which is the
1810 * minimum suggested value in the curl_multi_fdset() doc.
1811 */
1812 KODI::TIME::Sleep(100);
1813 rc = 0;
1814 #else
1815 /* Portable sleep for platforms other than Windows. */
1816 struct timeval wait = { 0, 100 * 1000 }; /* 100ms */
1817 rc = select(0, NULL, NULL, NULL, &wait);
1818 #endif
1819 }
1820 else
1821 {
1822 unsigned int time_left = endTime.MillisLeft();
1823 struct timeval wait = { (int)time_left / 1000, ((int)time_left % 1000) * 1000 };
1824 rc = select(maxfd + 1, &fdread, &fdwrite, &fdexcep, &wait);
1825 }
1826 #ifdef TARGET_WINDOWS
1827 } while(rc == SOCKET_ERROR && WSAGetLastError() == WSAEINTR);
1828 #else
1829 } while(rc == SOCKET_ERROR && errno == EINTR);
1830 #endif
1831
1832 if(rc == SOCKET_ERROR)
1833 {
1834 #ifdef TARGET_WINDOWS
1835 char buf[256];
1836 strerror_s(buf, 256, WSAGetLastError());
1837 CLog::Log(LOGERROR, "CCurlFile::FillBuffer - Failed with socket error:%s", buf);
1838 #else
1839 char const * str = strerror(errno);
1840 CLog::Log(LOGERROR, "CCurlFile::FillBuffer - Failed with socket error:%s", str);
1841 #endif
1842
1843 return FILLBUFFER_FAIL;
1844 }
1845 }
1846 break;
1847 case CURLM_CALL_MULTI_PERFORM:
1848 {
1849 // we don't keep calling here as that can easily overwrite our buffer which we want to avoid
1850 // docs says we should call it soon after, but aslong as we are reading data somewhere
1851 // this aught to be soon enough. should stay in socket otherwise
1852 continue;
1853 }
1854 break;
1855 default:
1856 {
1857 CLog::Log(LOGERROR, "CCurlFile::FillBuffer - Multi perform failed with code %d, aborting", result);
1858 return FILLBUFFER_FAIL;
1859 }
1860 break;
1861 }
1862 }
1863 return FILLBUFFER_OK;
1864 }
1865
SetReadBuffer(const void * lpBuf,int64_t uiBufSize)1866 void CCurlFile::CReadState::SetReadBuffer(const void* lpBuf, int64_t uiBufSize)
1867 {
1868 m_readBuffer = const_cast<char*>((const char*)lpBuf);
1869 m_fileSize = uiBufSize;
1870 m_filePos = 0;
1871 }
1872
ClearRequestHeaders()1873 void CCurlFile::ClearRequestHeaders()
1874 {
1875 m_requestheaders.clear();
1876 }
1877
SetRequestHeader(const std::string & header,const std::string & value)1878 void CCurlFile::SetRequestHeader(const std::string& header, const std::string& value)
1879 {
1880 m_requestheaders[header] = value;
1881 }
1882
SetRequestHeader(const std::string & header,long value)1883 void CCurlFile::SetRequestHeader(const std::string& header, long value)
1884 {
1885 m_requestheaders[header] = StringUtils::Format("%ld", value);
1886 }
1887
GetURL(void)1888 std::string CCurlFile::GetURL(void)
1889 {
1890 return m_url;
1891 }
1892
GetRedirectURL()1893 std::string CCurlFile::GetRedirectURL()
1894 {
1895 return GetInfoString(CURLINFO_REDIRECT_URL);
1896 }
1897
GetInfoString(int infoType)1898 std::string CCurlFile::GetInfoString(int infoType)
1899 {
1900 char* info{};
1901 CURLcode result = g_curlInterface.easy_getinfo(m_state->m_easyHandle, static_cast<CURLINFO> (infoType), &info);
1902 if (result != CURLE_OK)
1903 {
1904 CLog::Log(LOGERROR, "Info string request for type {} failed with result code {}", infoType, result);
1905 return "";
1906 }
1907 return (info ? info : "");
1908 }
1909
1910 /* STATIC FUNCTIONS */
GetHttpHeader(const CURL & url,CHttpHeader & headers)1911 bool CCurlFile::GetHttpHeader(const CURL &url, CHttpHeader &headers)
1912 {
1913 try
1914 {
1915 CCurlFile file;
1916 if(file.Stat(url, NULL) == 0)
1917 {
1918 headers = file.GetHttpHeader();
1919 return true;
1920 }
1921 return false;
1922 }
1923 catch(...)
1924 {
1925 CLog::Log(LOGERROR, "%s - Exception thrown while trying to retrieve header url: %s", __FUNCTION__, url.GetRedacted().c_str());
1926 return false;
1927 }
1928 }
1929
GetMimeType(const CURL & url,std::string & content,const std::string & useragent)1930 bool CCurlFile::GetMimeType(const CURL &url, std::string &content, const std::string &useragent)
1931 {
1932 CCurlFile file;
1933 if (!useragent.empty())
1934 file.SetUserAgent(useragent);
1935
1936 struct __stat64 buffer;
1937 std::string redactUrl = url.GetRedacted();
1938 if( file.Stat(url, &buffer) == 0 )
1939 {
1940 if (buffer.st_mode == _S_IFDIR)
1941 content = "x-directory/normal";
1942 else
1943 content = file.GetProperty(XFILE::FILE_PROPERTY_MIME_TYPE);
1944 CLog::Log(LOGDEBUG, "CCurlFile::GetMimeType - %s -> %s", redactUrl.c_str(), content.c_str());
1945 return true;
1946 }
1947 CLog::Log(LOGDEBUG, "CCurlFile::GetMimeType - %s -> failed", redactUrl.c_str());
1948 content.clear();
1949 return false;
1950 }
1951
GetContentType(const CURL & url,std::string & content,const std::string & useragent)1952 bool CCurlFile::GetContentType(const CURL &url, std::string &content, const std::string &useragent)
1953 {
1954 CCurlFile file;
1955 if (!useragent.empty())
1956 file.SetUserAgent(useragent);
1957
1958 struct __stat64 buffer;
1959 std::string redactUrl = url.GetRedacted();
1960 if (file.Stat(url, &buffer) == 0)
1961 {
1962 if (buffer.st_mode == _S_IFDIR)
1963 content = "x-directory/normal";
1964 else
1965 content = file.GetProperty(XFILE::FILE_PROPERTY_CONTENT_TYPE, "");
1966 CLog::Log(LOGDEBUG, "CCurlFile::GetContentType - %s -> %s", redactUrl.c_str(), content.c_str());
1967 return true;
1968 }
1969 CLog::Log(LOGDEBUG, "CCurlFile::GetContentType - %s -> failed", redactUrl.c_str());
1970 content.clear();
1971 return false;
1972 }
1973
GetCookies(const CURL & url,std::string & cookies)1974 bool CCurlFile::GetCookies(const CURL &url, std::string &cookies)
1975 {
1976 std::string cookiesStr;
1977 curl_slist* curlCookies;
1978 CURL_HANDLE* easyHandle;
1979 CURLM* multiHandle;
1980
1981 // get the cookies list
1982 g_curlInterface.easy_acquire(url.GetProtocol().c_str(),
1983 url.GetHostName().c_str(),
1984 &easyHandle, &multiHandle);
1985 if (CURLE_OK == g_curlInterface.easy_getinfo(easyHandle, CURLINFO_COOKIELIST, &curlCookies))
1986 {
1987 // iterate over each cookie and format it into an RFC 2109 formatted Set-Cookie string
1988 curl_slist* curlCookieIter = curlCookies;
1989 while(curlCookieIter)
1990 {
1991 // tokenize the CURL cookie string
1992 std::vector<std::string> valuesVec;
1993 StringUtils::Tokenize(curlCookieIter->data, valuesVec, "\t");
1994
1995 // ensure the length is valid
1996 if (valuesVec.size() < 7)
1997 {
1998 CLog::Log(LOGERROR, "CCurlFile::GetCookies - invalid cookie: '%s'", curlCookieIter->data);
1999 curlCookieIter = curlCookieIter->next;
2000 continue;
2001 }
2002
2003 // create a http-header formatted cookie string
2004 std::string cookieStr = valuesVec[5] + "=" + valuesVec[6] +
2005 "; path=" + valuesVec[2] +
2006 "; domain=" + valuesVec[0];
2007
2008 // append this cookie to the string containing all cookies
2009 if (!cookiesStr.empty())
2010 cookiesStr += "\n";
2011 cookiesStr += cookieStr;
2012
2013 // move on to the next cookie
2014 curlCookieIter = curlCookieIter->next;
2015 }
2016
2017 // free the curl cookies
2018 g_curlInterface.slist_free_all(curlCookies);
2019
2020 // release our handles
2021 g_curlInterface.easy_release(&easyHandle, &multiHandle);
2022
2023 // if we have a non-empty cookie string, return it
2024 if (!cookiesStr.empty())
2025 {
2026 cookies = cookiesStr;
2027 return true;
2028 }
2029 }
2030
2031 // no cookies to return
2032 return false;
2033 }
2034
IoControl(EIoControl request,void * param)2035 int CCurlFile::IoControl(EIoControl request, void* param)
2036 {
2037 if (request == IOCTRL_SEEK_POSSIBLE)
2038 return m_seekable ? 1 : 0;
2039
2040 if (request == IOCTRL_SET_RETRY)
2041 {
2042 m_allowRetry = *(bool*) param;
2043 return 0;
2044 }
2045
2046 return -1;
2047 }
2048
GetProperty(XFILE::FileProperty type,const std::string & name) const2049 const std::string CCurlFile::GetProperty(XFILE::FileProperty type, const std::string &name) const
2050 {
2051 switch (type)
2052 {
2053 case FILE_PROPERTY_RESPONSE_PROTOCOL:
2054 return m_state->m_httpheader.GetProtoLine();
2055 case FILE_PROPERTY_RESPONSE_HEADER:
2056 return m_state->m_httpheader.GetValue(name);
2057 case FILE_PROPERTY_CONTENT_TYPE:
2058 return m_state->m_httpheader.GetValue("content-type");
2059 case FILE_PROPERTY_CONTENT_CHARSET:
2060 return m_state->m_httpheader.GetCharset();
2061 case FILE_PROPERTY_MIME_TYPE:
2062 return m_state->m_httpheader.GetMimeType();
2063 case FILE_PROPERTY_EFFECTIVE_URL:
2064 {
2065 char *url = nullptr;
2066 g_curlInterface.easy_getinfo(m_state->m_easyHandle, CURLINFO_EFFECTIVE_URL, &url);
2067 return url ? url : "";
2068 }
2069 default:
2070 return "";
2071 }
2072 }
2073
GetPropertyValues(XFILE::FileProperty type,const std::string & name) const2074 const std::vector<std::string> CCurlFile::GetPropertyValues(XFILE::FileProperty type, const std::string &name) const
2075 {
2076 if (type == FILE_PROPERTY_RESPONSE_HEADER)
2077 {
2078 return m_state->m_httpheader.GetValues(name);
2079 }
2080 std::vector<std::string> values;
2081 std::string value = GetProperty(type, name);
2082 if (!value.empty())
2083 {
2084 values.emplace_back(value);
2085 }
2086 return values;
2087 }
2088
GetDownloadSpeed()2089 double CCurlFile::GetDownloadSpeed()
2090 {
2091 #if LIBCURL_VERSION_NUM >= 0x073a00 // 0.7.58.0
2092 double speed = 0.0;
2093 if (g_curlInterface.easy_getinfo(m_state->m_easyHandle, CURLINFO_SPEED_DOWNLOAD, &speed) == CURLE_OK)
2094 return speed;
2095 #else
2096 double time = 0.0, size = 0.0;
2097 if (g_curlInterface.easy_getinfo(m_state->m_easyHandle, CURLINFO_TOTAL_TIME, &time) == CURLE_OK
2098 && g_curlInterface.easy_getinfo(m_state->m_easyHandle, CURLINFO_SIZE_DOWNLOAD, &size) == CURLE_OK
2099 && time > 0.0)
2100 {
2101 return size / time;
2102 }
2103 #endif
2104 return 0.0;
2105 }
2106