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