1753e05c93e550cee0a7f40ad204af15caba4dcf
[vuplus_xbmc] / xbmc / filesystem / CurlFile.cpp
1 /*
2  *      Copyright (C) 2005-2013 Team XBMC
3  *      http://xbmc.org
4  *
5  *  This Program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2, or (at your option)
8  *  any later version.
9  *
10  *  This Program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with XBMC; see the file COPYING.  If not, see
17  *  <http://www.gnu.org/licenses/>.
18  *
19  */
20
21 #include "CurlFile.h"
22 #include "utils/URIUtils.h"
23 #include "Util.h"
24 #include "URL.h"
25 #include "settings/AdvancedSettings.h"
26 #include "settings/Settings.h"
27 #include "File.h"
28 #include "threads/SystemClock.h"
29
30 #include <vector>
31 #include <climits>
32
33 #ifdef TARGET_POSIX
34 #include <errno.h>
35 #include <inttypes.h>
36 #include "../linux/XFileUtils.h"
37 #include "../linux/XTimeUtils.h"
38 #include "../linux/ConvUtils.h"
39 #endif
40
41 #include "DllLibCurl.h"
42 #include "ShoutcastFile.h"
43 #include "SpecialProtocol.h"
44 #include "utils/CharsetConverter.h"
45 #include "utils/log.h"
46 #include "utils/StringUtils.h"
47
48 using namespace XFILE;
49 using namespace XCURL;
50
51 #define XMIN(a,b) ((a)<(b)?(a):(b))
52 #define FITS_INT(a) (((a) <= INT_MAX) && ((a) >= INT_MIN))
53
54 curl_proxytype proxyType2CUrlProxyType[] = {
55   CURLPROXY_HTTP,
56   CURLPROXY_SOCKS4,
57   CURLPROXY_SOCKS4A,
58   CURLPROXY_SOCKS5,
59   CURLPROXY_SOCKS5_HOSTNAME,
60 };
61
62 // curl calls this routine to debug
63 extern "C" int debug_callback(CURL_HANDLE *handle, curl_infotype info, char *output, size_t size, void *data)
64 {
65   if (info == CURLINFO_DATA_IN || info == CURLINFO_DATA_OUT)
66     return 0;
67
68   if (!g_advancedSettings.CanLogComponent(LOGCURL))
69     return 0;
70
71   std::string strLine;
72   strLine.append(output, size);
73   std::vector<std::string> vecLines;
74   StringUtils::Tokenize(strLine, vecLines, "\r\n");
75   std::vector<std::string>::const_iterator it = vecLines.begin();
76
77   char *infotype;
78   switch(info)
79   {
80     case CURLINFO_TEXT         : infotype = (char *) "TEXT: "; break;
81     case CURLINFO_HEADER_IN    : infotype = (char *) "HEADER_IN: "; break;
82     case CURLINFO_HEADER_OUT   : infotype = (char *) "HEADER_OUT: "; break;
83     case CURLINFO_SSL_DATA_IN  : infotype = (char *) "SSL_DATA_IN: "; break;
84     case CURLINFO_SSL_DATA_OUT : infotype = (char *) "SSL_DATA_OUT: "; break;
85     case CURLINFO_END          : infotype = (char *) "END: "; break;
86     default                    : infotype = (char *) ""; break;
87   }
88
89   while (it != vecLines.end())
90   {
91     CLog::Log(LOGDEBUG, "Curl::Debug - %s%s", infotype, (*it).c_str());
92     it++;
93   }
94   return 0;
95 }
96
97 /* curl calls this routine to get more data */
98 extern "C" size_t write_callback(char *buffer,
99                size_t size,
100                size_t nitems,
101                void *userp)
102 {
103   if(userp == NULL) return 0;
104
105   CCurlFile::CReadState *state = (CCurlFile::CReadState *)userp;
106   return state->WriteCallback(buffer, size, nitems);
107 }
108
109 extern "C" size_t read_callback(char *buffer,
110                size_t size,
111                size_t nitems,
112                void *userp)
113 {
114   if(userp == NULL) return 0;
115
116   CCurlFile::CReadState *state = (CCurlFile::CReadState *)userp;
117   return state->ReadCallback(buffer, size, nitems);
118 }
119
120 extern "C" size_t header_callback(void *ptr, size_t size, size_t nmemb, void *stream)
121 {
122   CCurlFile::CReadState *state = (CCurlFile::CReadState *)stream;
123   return state->HeaderCallback(ptr, size, nmemb);
124 }
125
126 /* fix for silly behavior of realloc */
127 static inline void* realloc_simple(void *ptr, size_t size)
128 {
129   void *ptr2 = realloc(ptr, size);
130   if(ptr && !ptr2 && size > 0)
131   {
132     free(ptr);
133     return NULL;
134   }
135   else
136     return ptr2;
137 }
138
139 size_t CCurlFile::CReadState::HeaderCallback(void *ptr, size_t size, size_t nmemb)
140 {
141   std::string inString;
142   // libcurl doc says that this info is not always \0 terminated
143   const char* strBuf = (const char*)ptr;
144   const size_t iSize = size * nmemb;
145   if (strBuf[iSize - 1] == 0)
146     inString.assign(strBuf, iSize - 1); // skip last char if it's zero
147   else
148     inString.append(strBuf, iSize);
149
150   m_httpheader.Parse(inString);
151
152   return iSize;
153 }
154
155 size_t CCurlFile::CReadState::ReadCallback(char *buffer, size_t size, size_t nitems)
156 {
157   if (m_fileSize == 0)
158     return 0;
159
160   if (m_filePos >= m_fileSize)
161   {
162     m_isPaused = true;
163     return CURL_READFUNC_PAUSE;
164   }
165
166   int64_t retSize = XMIN(m_fileSize - m_filePos, int64_t(nitems * size));
167   memcpy(buffer, m_readBuffer + m_filePos, retSize);
168   m_filePos += retSize;
169
170   return retSize;
171 }
172
173 size_t CCurlFile::CReadState::WriteCallback(char *buffer, size_t size, size_t nitems)
174 {
175   unsigned int amount = size * nitems;
176 //  CLog::Log(LOGDEBUG, "CCurlFile::WriteCallback (%p) with %i bytes, readsize = %i, writesize = %i", this, amount, m_buffer.getMaxReadSize(), m_buffer.getMaxWriteSize() - m_overflowSize);
177   if (m_overflowSize)
178   {
179     // we have our overflow buffer - first get rid of as much as we can
180     unsigned int maxWriteable = XMIN((unsigned int)m_buffer.getMaxWriteSize(), m_overflowSize);
181     if (maxWriteable)
182     {
183       if (!m_buffer.WriteData(m_overflowBuffer, maxWriteable))
184         CLog::Log(LOGERROR, "CCurlFile::WriteCallback - Unable to write to buffer - what's up?");
185       if (m_overflowSize > maxWriteable)
186       { // still have some more - copy it down
187         memmove(m_overflowBuffer, m_overflowBuffer + maxWriteable, m_overflowSize - maxWriteable);
188       }
189       m_overflowSize -= maxWriteable;
190     }
191   }
192   // ok, now copy the data into our ring buffer
193   unsigned int maxWriteable = XMIN((unsigned int)m_buffer.getMaxWriteSize(), amount);
194   if (maxWriteable)
195   {
196     if (!m_buffer.WriteData(buffer, maxWriteable))
197     {
198       CLog::Log(LOGERROR, "CCurlFile::WriteCallback - Unable to write to buffer with %i bytes - what's up?", maxWriteable);
199     }
200     else
201     {
202       amount -= maxWriteable;
203       buffer += maxWriteable;
204     }
205   }
206   if (amount)
207   {
208 //    CLog::Log(LOGDEBUG, "CCurlFile::WriteCallback(%p) not enough free space for %i bytes", (void*)this,  amount);
209
210     m_overflowBuffer = (char*)realloc_simple(m_overflowBuffer, amount + m_overflowSize);
211     if(m_overflowBuffer == NULL)
212     {
213       CLog::Log(LOGWARNING, "CCurlFile::WriteCallback - Failed to grow overflow buffer from %i bytes to %i bytes", m_overflowSize, amount + m_overflowSize);
214       return 0;
215     }
216     memcpy(m_overflowBuffer + m_overflowSize, buffer, amount);
217     m_overflowSize += amount;
218   }
219   return size * nitems;
220 }
221
222 CCurlFile::CReadState::CReadState()
223 {
224   m_easyHandle = NULL;
225   m_multiHandle = NULL;
226   m_overflowBuffer = NULL;
227   m_overflowSize = 0;
228   m_filePos = 0;
229   m_fileSize = 0;
230   m_bufferSize = 0;
231   m_cancelled = false;
232   m_bFirstLoop = true;
233   m_sendRange = true;
234   m_readBuffer = 0;
235   m_isPaused = false;
236   m_curlHeaderList = NULL;
237   m_curlAliasList = NULL;
238 }
239
240 CCurlFile::CReadState::~CReadState()
241 {
242   Disconnect();
243
244   if(m_easyHandle)
245     g_curlInterface.easy_release(&m_easyHandle, &m_multiHandle);
246 }
247
248 bool CCurlFile::CReadState::Seek(int64_t pos)
249 {
250   if(pos == m_filePos)
251     return true;
252
253   if(FITS_INT(pos - m_filePos) && m_buffer.SkipBytes((int)(pos - m_filePos)))
254   {
255     m_filePos = pos;
256     return true;
257   }
258
259   if(pos > m_filePos && pos < m_filePos + m_bufferSize)
260   {
261     int len = m_buffer.getMaxReadSize();
262     m_filePos += len;
263     m_buffer.SkipBytes(len);
264     if(!FillBuffer(m_bufferSize))
265     {
266       if(!m_buffer.SkipBytes(-len))
267         CLog::Log(LOGERROR, "%s - Failed to restore position after failed fill", __FUNCTION__);
268       else
269         m_filePos -= len;
270       return false;
271     }
272
273     if(!FITS_INT(pos - m_filePos) || !m_buffer.SkipBytes((int)(pos - m_filePos)))
274     {
275       CLog::Log(LOGERROR, "%s - Failed to skip to position after having filled buffer", __FUNCTION__);
276       if(!m_buffer.SkipBytes(-len))
277         CLog::Log(LOGERROR, "%s - Failed to restore position after failed seek", __FUNCTION__);
278       else
279         m_filePos -= len;
280       return false;
281     }
282     m_filePos = pos;
283     return true;
284   }
285   return false;
286 }
287
288 void CCurlFile::CReadState::SetResume(void)
289 {
290   /*
291    * Explicitly set RANGE header when filepos=0 as some http servers require us to always send the range
292    * request header. If we don't the server may provide different content causing seeking to fail.
293    * This only affects HTTP-like items, for FTP it's a null operation.
294    */
295   if (m_sendRange && m_filePos == 0)
296     g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_RANGE, "0-");
297   else
298   {
299     g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_RANGE, NULL);
300     m_sendRange = false;
301   }
302
303   g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_RESUME_FROM_LARGE, m_filePos);
304 }
305
306 long CCurlFile::CReadState::Connect(unsigned int size)
307 {
308   if (m_filePos != 0)
309     CLog::Log(LOGDEBUG,"CurlFile::CReadState::Connect - Resume from position %" PRId64, m_filePos);
310
311   SetResume();
312   g_curlInterface.multi_add_handle(m_multiHandle, m_easyHandle);
313
314   m_bufferSize = size;
315   m_buffer.Destroy();
316   m_buffer.Create(size * 3);
317   m_httpheader.Clear();
318
319   // read some data in to try and obtain the length
320   // maybe there's a better way to get this info??
321   m_stillRunning = 1;
322   if (!FillBuffer(1))
323   {
324     CLog::Log(LOGERROR, "CCurlFile::CReadState::Connect, didn't get any data from stream.");
325     return -1;
326   }
327
328   double length;
329   if (CURLE_OK == g_curlInterface.easy_getinfo(m_easyHandle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &length))
330   {
331     if (length < 0)
332       length = 0.0;
333     m_fileSize = m_filePos + (int64_t)length;
334   }
335
336   long response;
337   if (CURLE_OK == g_curlInterface.easy_getinfo(m_easyHandle, CURLINFO_RESPONSE_CODE, &response))
338     return response;
339
340   return -1;
341 }
342
343 void CCurlFile::CReadState::Disconnect()
344 {
345   if(m_multiHandle && m_easyHandle)
346     g_curlInterface.multi_remove_handle(m_multiHandle, m_easyHandle);
347
348   m_buffer.Clear();
349   free(m_overflowBuffer);
350   m_overflowBuffer = NULL;
351   m_overflowSize = 0;
352   m_filePos = 0;
353   m_fileSize = 0;
354   m_bufferSize = 0;
355   m_readBuffer = 0;
356
357   /* cleanup */
358   if( m_curlHeaderList )
359     g_curlInterface.slist_free_all(m_curlHeaderList);
360   m_curlHeaderList = NULL;
361
362   if( m_curlAliasList )
363     g_curlInterface.slist_free_all(m_curlAliasList);
364   m_curlAliasList = NULL;
365 }
366
367
368 CCurlFile::~CCurlFile()
369 {
370   Close();
371   delete m_state;
372   delete m_oldState;
373   g_curlInterface.Unload();
374 }
375
376 CCurlFile::CCurlFile()
377 {
378   g_curlInterface.Load(); // loads the curl dll and resolves exports etc.
379   m_opened = false;
380   m_forWrite = false;
381   m_inError = false;
382   m_multisession  = true;
383   m_seekable = true;
384   m_useOldHttpVersion = false;
385   m_connecttimeout = 0;
386   m_lowspeedtime = 0;
387   m_ftpauth = "";
388   m_ftpport = "";
389   m_ftppasvip = false;
390   m_bufferSize = 32768;
391   m_postdata = "";
392   m_postdataset = false;
393   m_username = "";
394   m_password = "";
395   m_httpauth = "";
396   m_cipherlist = "";
397   m_proxytype = PROXY_HTTP;
398   m_state = new CReadState();
399   m_oldState = NULL;
400   m_skipshout = false;
401   m_httpresponse = -1;
402   m_acceptCharset = "UTF-8,*;q=0.8"; /* prefer UTF-8 if available */
403 }
404
405 //Has to be called before Open()
406 void CCurlFile::SetBufferSize(unsigned int size)
407 {
408   m_bufferSize = size;
409 }
410
411 void CCurlFile::Close()
412 {
413   if (m_opened && m_forWrite && !m_inError)
414       Write(NULL, 0);
415
416   m_state->Disconnect();
417   delete m_oldState;
418   m_oldState = NULL;
419
420   m_url.clear();
421   m_referer.clear();
422   m_cookie.clear();
423
424   m_opened = false;
425   m_forWrite = false;
426   m_inError = false;
427 }
428
429 void CCurlFile::SetCommonOptions(CReadState* state)
430 {
431   CURL_HANDLE* h = state->m_easyHandle;
432
433   g_curlInterface.easy_reset(h);
434
435   g_curlInterface.easy_setopt(h, CURLOPT_DEBUGFUNCTION, debug_callback);
436
437   if( g_advancedSettings.m_logLevel >= LOG_LEVEL_DEBUG )
438     g_curlInterface.easy_setopt(h, CURLOPT_VERBOSE, TRUE);
439   else
440     g_curlInterface.easy_setopt(h, CURLOPT_VERBOSE, FALSE);
441
442   g_curlInterface.easy_setopt(h, CURLOPT_WRITEDATA, state);
443   g_curlInterface.easy_setopt(h, CURLOPT_WRITEFUNCTION, write_callback);
444
445   g_curlInterface.easy_setopt(h, CURLOPT_READDATA, state);
446   g_curlInterface.easy_setopt(h, CURLOPT_READFUNCTION, read_callback);
447
448   // set username and password for current handle
449   if (m_username.length() > 0 && m_password.length() > 0)
450   {
451     std::string userpwd = m_username + ':' + m_password;
452     g_curlInterface.easy_setopt(h, CURLOPT_USERPWD, userpwd.c_str());
453   }
454
455   // make sure headers are seperated from the data stream
456   g_curlInterface.easy_setopt(h, CURLOPT_WRITEHEADER, state);
457   g_curlInterface.easy_setopt(h, CURLOPT_HEADERFUNCTION, header_callback);
458   g_curlInterface.easy_setopt(h, CURLOPT_HEADER, FALSE);
459
460   g_curlInterface.easy_setopt(h, CURLOPT_FTP_USE_EPSV, 0); // turn off epsv
461
462   // Allow us to follow two redirects
463   g_curlInterface.easy_setopt(h, CURLOPT_FOLLOWLOCATION, TRUE);
464   g_curlInterface.easy_setopt(h, CURLOPT_MAXREDIRS, 5);
465
466   // Enable cookie engine for current handle to re-use them in future requests
467   std::string strCookieFile;
468   std::string strTempPath = CSpecialProtocol::TranslatePath(g_advancedSettings.m_cachePath);
469   strCookieFile = URIUtils::AddFileToFolder(strTempPath, "cookies.dat");
470
471   g_curlInterface.easy_setopt(h, CURLOPT_COOKIEFILE, strCookieFile.c_str());
472   g_curlInterface.easy_setopt(h, CURLOPT_COOKIEJAR, strCookieFile.c_str());
473
474   // Set custom cookie if requested
475   if (!m_cookie.empty())
476     g_curlInterface.easy_setopt(h, CURLOPT_COOKIE, m_cookie.c_str());
477
478   g_curlInterface.easy_setopt(h, CURLOPT_COOKIELIST, "FLUSH");
479
480   // When using multiple threads you should set the CURLOPT_NOSIGNAL option to
481   // TRUE for all handles. Everything will work fine except that timeouts are not
482   // honored during the DNS lookup - which you can work around by building libcurl
483   // with c-ares support. c-ares is a library that provides asynchronous name
484   // resolves. Unfortunately, c-ares does not yet support IPv6.
485   g_curlInterface.easy_setopt(h, CURLOPT_NOSIGNAL, TRUE);
486
487   // not interested in failed requests
488   g_curlInterface.easy_setopt(h, CURLOPT_FAILONERROR, 1);
489
490   // enable support for icecast / shoutcast streams
491   if ( NULL == state->m_curlAliasList )
492     // m_curlAliasList is used only by this one place, but SetCommonOptions can
493     // be called multiple times, only append to list if it's empty.
494     state->m_curlAliasList = g_curlInterface.slist_append(state->m_curlAliasList, "ICY 200 OK");
495   g_curlInterface.easy_setopt(h, CURLOPT_HTTP200ALIASES, state->m_curlAliasList);
496
497   // never verify peer, we don't have any certificates to do this
498   g_curlInterface.easy_setopt(h, CURLOPT_SSL_VERIFYPEER, 0);
499   g_curlInterface.easy_setopt(h, CURLOPT_SSL_VERIFYHOST, 0);
500
501   g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_URL, m_url.c_str());
502   g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_TRANSFERTEXT, FALSE);
503
504   // setup POST data if it is set (and it may be empty)
505   if (m_postdataset)
506   {
507     g_curlInterface.easy_setopt(h, CURLOPT_POST, 1 );
508     g_curlInterface.easy_setopt(h, CURLOPT_POSTFIELDSIZE, m_postdata.length());
509     g_curlInterface.easy_setopt(h, CURLOPT_POSTFIELDS, m_postdata.c_str());
510   }
511
512   // setup Referer header if needed
513   if (!m_referer.empty())
514     g_curlInterface.easy_setopt(h, CURLOPT_REFERER, m_referer.c_str());
515   else
516   {
517     g_curlInterface.easy_setopt(h, CURLOPT_REFERER, NULL);
518     g_curlInterface.easy_setopt(h, CURLOPT_AUTOREFERER, TRUE);
519   }
520
521   // setup any requested authentication
522   if( !m_ftpauth.empty() )
523   {
524     g_curlInterface.easy_setopt(h, CURLOPT_FTP_SSL, CURLFTPSSL_TRY);
525     if( m_ftpauth == "any" )
526       g_curlInterface.easy_setopt(h, CURLOPT_FTPSSLAUTH, CURLFTPAUTH_DEFAULT);
527     else if( m_ftpauth == "ssl" )
528       g_curlInterface.easy_setopt(h, CURLOPT_FTPSSLAUTH, CURLFTPAUTH_SSL);
529     else if( m_ftpauth == "tls" )
530       g_curlInterface.easy_setopt(h, CURLOPT_FTPSSLAUTH, CURLFTPAUTH_TLS);
531   }
532
533   // setup requested http authentication method
534   if(!m_httpauth.empty())
535   {
536     if( m_httpauth == "any" )
537       g_curlInterface.easy_setopt(h, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
538     else if( m_httpauth == "anysafe" )
539       g_curlInterface.easy_setopt(h, CURLOPT_HTTPAUTH, CURLAUTH_ANYSAFE);
540     else if( m_httpauth == "digest" )
541       g_curlInterface.easy_setopt(h, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);
542     else if( m_httpauth == "ntlm" )
543       g_curlInterface.easy_setopt(h, CURLOPT_HTTPAUTH, CURLAUTH_NTLM);
544   }
545
546   // allow passive mode for ftp
547   if( m_ftpport.length() > 0 )
548     g_curlInterface.easy_setopt(h, CURLOPT_FTPPORT, m_ftpport.c_str());
549   else
550     g_curlInterface.easy_setopt(h, CURLOPT_FTPPORT, NULL);
551
552   // allow curl to not use the ip address in the returned pasv response
553   if( m_ftppasvip )
554     g_curlInterface.easy_setopt(h, CURLOPT_FTP_SKIP_PASV_IP, 0);
555   else
556     g_curlInterface.easy_setopt(h, CURLOPT_FTP_SKIP_PASV_IP, 1);
557
558   // setup Content-Encoding if requested
559   if( m_contentencoding.length() > 0 )
560     g_curlInterface.easy_setopt(h, CURLOPT_ENCODING, m_contentencoding.c_str());
561
562   if (!m_useOldHttpVersion && !m_acceptCharset.empty())
563     SetRequestHeader("Accept-Charset", m_acceptCharset);
564
565   if (m_userAgent.length() > 0)
566     g_curlInterface.easy_setopt(h, CURLOPT_USERAGENT, m_userAgent.c_str());
567   else /* set some default agent as shoutcast doesn't return proper stuff otherwise */
568     g_curlInterface.easy_setopt(h, CURLOPT_USERAGENT, g_advancedSettings.m_userAgent.c_str());
569
570   if (m_useOldHttpVersion)
571     g_curlInterface.easy_setopt(h, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
572
573   if (g_advancedSettings.m_curlDisableIPV6)
574     g_curlInterface.easy_setopt(h, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
575
576   if (m_proxy.length() > 0)
577   {
578     g_curlInterface.easy_setopt(h, CURLOPT_PROXY, m_proxy.c_str());
579     g_curlInterface.easy_setopt(h, CURLOPT_PROXYTYPE, proxyType2CUrlProxyType[m_proxytype]);
580     if (m_proxyuserpass.length() > 0)
581       g_curlInterface.easy_setopt(h, CURLOPT_PROXYUSERPWD, m_proxyuserpass.c_str());
582
583   }
584   if (m_customrequest.length() > 0)
585     g_curlInterface.easy_setopt(h, CURLOPT_CUSTOMREQUEST, m_customrequest.c_str());
586
587   if (m_connecttimeout == 0)
588     m_connecttimeout = g_advancedSettings.m_curlconnecttimeout;
589
590   // set our timeouts, we abort connection after m_timeout, and reads after no data for m_timeout seconds
591   g_curlInterface.easy_setopt(h, CURLOPT_CONNECTTIMEOUT, m_connecttimeout);
592
593   // We abort in case we transfer less than 1byte/second
594   g_curlInterface.easy_setopt(h, CURLOPT_LOW_SPEED_LIMIT, 1);
595
596   if (m_lowspeedtime == 0)
597     m_lowspeedtime = g_advancedSettings.m_curllowspeedtime;
598
599   // Set the lowspeed time very low as it seems Curl takes much longer to detect a lowspeed condition
600   g_curlInterface.easy_setopt(h, CURLOPT_LOW_SPEED_TIME, m_lowspeedtime);
601
602   if (m_skipshout)
603     // For shoutcast file, content-length should not be set, and in libcurl there is a bug, if the
604     // cast file was 302 redirected then getinfo of CURLINFO_CONTENT_LENGTH_DOWNLOAD will return
605     // the 302 response's body length, which cause the next read request failed, so we ignore
606     // content-length for shoutcast file to workaround this.
607     g_curlInterface.easy_setopt(h, CURLOPT_IGNORE_CONTENT_LENGTH, 1);
608
609   // Setup allowed TLS/SSL ciphers. New versions of cURL may deprecate things that are still in use.
610   if (!m_cipherlist.empty())
611     g_curlInterface.easy_setopt(h, CURLOPT_SSL_CIPHER_LIST, m_cipherlist.c_str());
612 }
613
614 void CCurlFile::SetRequestHeaders(CReadState* state)
615 {
616   if(state->m_curlHeaderList)
617   {
618     g_curlInterface.slist_free_all(state->m_curlHeaderList);
619     state->m_curlHeaderList = NULL;
620   }
621
622   MAPHTTPHEADERS::iterator it;
623   for(it = m_requestheaders.begin(); it != m_requestheaders.end(); it++)
624   {
625     std::string buffer = it->first + ": " + it->second;
626     state->m_curlHeaderList = g_curlInterface.slist_append(state->m_curlHeaderList, buffer.c_str());
627   }
628
629   // add user defined headers
630   if (state->m_easyHandle)
631     g_curlInterface.easy_setopt(state->m_easyHandle, CURLOPT_HTTPHEADER, state->m_curlHeaderList);
632 }
633
634 void CCurlFile::SetCorrectHeaders(CReadState* state)
635 {
636   CHttpHeader& h = state->m_httpheader;
637   /* workaround for shoutcast server wich doesn't set content type on standard mp3 */
638   if( h.GetMimeType().empty() )
639   {
640     if( !h.GetValue("icy-notice1").empty()
641     || !h.GetValue("icy-name").empty()
642     || !h.GetValue("icy-br").empty() )
643       h.AddParam("Content-Type", "audio/mpeg");
644   }
645
646   /* hack for google video */
647   if (StringUtils::EqualsNoCase(h.GetMimeType(),"text/html")
648   &&  !h.GetValue("Content-Disposition").empty() )
649   {
650     std::string strValue = h.GetValue("Content-Disposition");
651     if (strValue.find("filename=") != std::string::npos &&
652         strValue.find(".flv") != std::string::npos)
653       h.AddParam("Content-Type", "video/flv");
654   }
655 }
656
657 void CCurlFile::ParseAndCorrectUrl(CURL &url2)
658 {
659   std::string strProtocol = url2.GetTranslatedProtocol();
660   url2.SetProtocol(strProtocol);
661
662   if( url2.IsProtocol("ftp")
663    || url2.IsProtocol("ftps") )
664   {
665     // we was using url optons for urls, keep the old code work and warning
666     if (!url2.GetOptions().empty())
667     {
668       CLog::Log(LOGWARNING, "%s: ftp url option is deprecated, please switch to use protocol option (change '?' to '|'), url: [%s]", __FUNCTION__, url2.GetRedacted().c_str());
669       url2.SetProtocolOptions(url2.GetOptions().substr(1));
670       /* ftp has no options */
671       url2.SetOptions("");
672     }
673
674     /* this is uggly, depending on from where   */
675     /* we get the link it may or may not be     */
676     /* url encoded. if handed from ftpdirectory */
677     /* it won't be so let's handle that case    */
678
679     std::string filename(url2.GetFileName());
680     std::vector<std::string> array;
681
682     // if server sent us the filename in non-utf8, we need send back with same encoding.
683     if (url2.GetProtocolOption("utf8") == "0")
684       g_charsetConverter.utf8ToStringCharset(filename);
685
686     /* TODO: create a tokenizer that doesn't skip empty's */
687     StringUtils::Tokenize(filename, array, "/");
688     filename.clear();
689     for(std::vector<std::string>::iterator it = array.begin(); it != array.end(); it++)
690     {
691       if(it != array.begin())
692         filename += "/";
693
694       filename += CURL::Encode(*it);
695     }
696
697     /* make sure we keep slashes */
698     if(StringUtils::EndsWith(url2.GetFileName(), "/"))
699       filename += "/";
700
701     url2.SetFileName(filename);
702
703     m_ftpauth.clear();
704     if (url2.HasProtocolOption("auth"))
705     {
706       m_ftpauth = url2.GetProtocolOption("auth");
707       StringUtils::ToLower(m_ftpauth);
708       if(m_ftpauth.empty())
709         m_ftpauth = "any";
710     }
711     m_ftpport = "";
712     if (url2.HasProtocolOption("active"))
713     {
714       m_ftpport = url2.GetProtocolOption("active");
715       if(m_ftpport.empty())
716         m_ftpport = "-";
717     }
718     m_ftppasvip = url2.HasProtocolOption("pasvip") && url2.GetProtocolOption("pasvip") != "0";
719   }
720   else if( url2.IsProtocol("http")
721        ||  url2.IsProtocol("https"))
722   {
723     if (CSettings::Get().GetBool("network.usehttpproxy")
724         && !CSettings::Get().GetString("network.httpproxyserver").empty()
725         && CSettings::Get().GetInt("network.httpproxyport") > 0
726         && m_proxy.empty())
727     {
728       m_proxy = CSettings::Get().GetString("network.httpproxyserver");
729       m_proxy += StringUtils::Format(":%d", CSettings::Get().GetInt("network.httpproxyport"));
730       if (CSettings::Get().GetString("network.httpproxyusername").length() > 0 && m_proxyuserpass.empty())
731       {
732         m_proxyuserpass = CSettings::Get().GetString("network.httpproxyusername");
733         m_proxyuserpass += ":" + CSettings::Get().GetString("network.httpproxypassword");
734       }
735       m_proxytype = (ProxyType)CSettings::Get().GetInt("network.httpproxytype");
736       CLog::Log(LOGDEBUG, "Using proxy %s, type %d", m_proxy.c_str(), proxyType2CUrlProxyType[m_proxytype]);
737     }
738
739     // get username and password
740     m_username = url2.GetUserName();
741     m_password = url2.GetPassWord();
742
743     // handle any protocol options
744     std::map<std::string, std::string> options;
745     url2.GetProtocolOptions(options);
746     if (options.size() > 0)
747     {
748       // set xbmc headers
749       for (std::map<std::string,std::string>::const_iterator it = options.begin(); it != options.end(); ++it)
750       {
751         std::string name = it->first; StringUtils::ToLower(name);
752         const std::string &value = it->second;
753
754         if (name == "auth")
755         {
756           m_httpauth = value;
757           StringUtils::ToLower(m_httpauth);
758           if(m_httpauth.empty())
759             m_httpauth = "any";
760         }
761         else if (name == "referer")
762           SetReferer(value);
763         else if (name == "user-agent")
764           SetUserAgent(value);
765         else if (name == "cookie")
766           SetCookie(value);
767         else if (name == "encoding")
768           SetContentEncoding(value);
769         else if (name == "noshout" && value == "true")
770           m_skipshout = true;
771         else if (name == "seekable" && value == "0")
772           m_seekable = false;
773         else if (name == "accept-charset")
774           SetAcceptCharset(value);
775         else if (name == "httpproxy")
776           SetStreamProxy(value, PROXY_HTTP);
777         else if (name == "sslcipherlist")
778           m_cipherlist = value;
779         else
780           SetRequestHeader(it->first, value);
781       }
782     }
783   }
784   
785   // Unset the protocol options to have an url without protocol options
786   url2.SetProtocolOptions("");
787
788   if (m_username.length() > 0 && m_password.length() > 0)
789     m_url = url2.GetWithoutUserDetails();
790   else
791     m_url = url2.Get();
792 }
793
794 void CCurlFile::SetStreamProxy(const std::string &proxy, ProxyType type)
795 {
796   CURL url(proxy);
797   m_proxy = url.GetWithoutUserDetails();
798   m_proxyuserpass = url.GetUserName();
799   if (!url.GetPassWord().empty())
800     m_proxyuserpass += ":" + url.GetPassWord();
801   m_proxytype = type;
802   CLog::Log(LOGDEBUG, "Overriding proxy from URL parameter: %s, type %d", m_proxy.c_str(), proxyType2CUrlProxyType[m_proxytype]);
803 }
804
805 bool CCurlFile::Post(const std::string& strURL, const std::string& strPostData, std::string& strHTML)
806 {
807   m_postdata = strPostData;
808   m_postdataset = true;
809   return Service(strURL, strHTML);
810 }
811
812 bool CCurlFile::Get(const std::string& strURL, std::string& strHTML)
813 {
814   m_postdata = "";
815   m_postdataset = false;
816   return Service(strURL, strHTML);
817 }
818
819 bool CCurlFile::Service(const std::string& strURL, std::string& strHTML)
820 {
821   const CURL pathToUrl(strURL);
822   if (Open(pathToUrl))
823   {
824     if (ReadData(strHTML))
825     {
826       Close();
827       return true;
828     }
829   }
830   Close();
831   return false;
832 }
833
834 bool CCurlFile::ReadData(std::string& strHTML)
835 {
836   int size_read = 0;
837   int data_size = 0;
838   strHTML = "";
839   char buffer[16384];
840   while( (size_read = Read(buffer, sizeof(buffer)-1) ) > 0 )
841   {
842     buffer[size_read] = 0;
843     strHTML.append(buffer, size_read);
844     data_size += size_read;
845   }
846   if (m_state->m_cancelled)
847     return false;
848   return true;
849 }
850
851 bool CCurlFile::Download(const std::string& strURL, const std::string& strFileName, LPDWORD pdwSize)
852 {
853   CLog::Log(LOGINFO, "CCurlFile::Download - %s->%s", strURL.c_str(), strFileName.c_str());
854
855   std::string strData;
856   if (!Get(strURL, strData))
857     return false;
858
859   XFILE::CFile file;
860   if (!file.OpenForWrite(strFileName, true))
861   {
862     CLog::Log(LOGERROR, "CCurlFile::Download - Unable to open file %s: %u",
863     strFileName.c_str(), GetLastError());
864     return false;
865   }
866   if (strData.size())
867     file.Write(strData.c_str(), strData.size());
868   file.Close();
869
870   if (pdwSize != NULL)
871   {
872     *pdwSize = strData.size();
873   }
874
875   return true;
876 }
877
878 // Detect whether we are "online" or not! Very simple and dirty!
879 bool CCurlFile::IsInternet()
880 {
881   CURL url("http://www.google.com");
882   bool found = Exists(url);
883   Close();
884
885   return found;
886 }
887
888 void CCurlFile::Cancel()
889 {
890   m_state->m_cancelled = true;
891   while (m_opened)
892     Sleep(1);
893 }
894
895 void CCurlFile::Reset()
896 {
897   m_state->m_cancelled = false;
898 }
899
900 bool CCurlFile::Open(const CURL& url)
901 {
902   m_opened = true;
903   m_seekable = true;
904
905   CURL url2(url);
906   ParseAndCorrectUrl(url2);
907
908   std::string redactPath = CURL::GetRedacted(m_url);
909   CLog::Log(LOGDEBUG, "CurlFile::Open(%p) %s", (void*)this, redactPath.c_str());
910
911   assert(!(!m_state->m_easyHandle ^ !m_state->m_multiHandle));
912   if( m_state->m_easyHandle == NULL )
913     g_curlInterface.easy_aquire(url2.GetProtocol().c_str(),
914                                 url2.GetHostName().c_str(),
915                                 &m_state->m_easyHandle,
916                                 &m_state->m_multiHandle);
917
918   // setup common curl options
919   SetCommonOptions(m_state);
920   SetRequestHeaders(m_state);
921   m_state->m_sendRange = m_seekable;
922
923   m_httpresponse = m_state->Connect(m_bufferSize);
924   if( m_httpresponse < 0 || m_httpresponse >= 400)
925     return false;
926
927   SetCorrectHeaders(m_state);
928
929   // since we can't know the stream size up front if we're gzipped/deflated
930   // flag the stream with an unknown file size rather than the compressed
931   // file size.
932   if (m_contentencoding.size() > 0)
933     m_state->m_fileSize = 0;
934
935   // check if this stream is a shoutcast stream. sometimes checking the protocol line is not enough so examine other headers as well.
936   // shoutcast streams should be handled by FileShoutcast.
937   if ((m_state->m_httpheader.GetProtoLine().substr(0, 3) == "ICY" || !m_state->m_httpheader.GetValue("icy-notice1").empty()
938      || !m_state->m_httpheader.GetValue("icy-name").empty()
939      || !m_state->m_httpheader.GetValue("icy-br").empty()) && !m_skipshout)
940   {
941     CLog::Log(LOGDEBUG,"CCurlFile::Open - File <%s> is a shoutcast stream. Re-opening", redactPath.c_str());
942     throw new CRedirectException(new CShoutcastFile);
943   }
944
945   m_multisession = false;
946   if(url2.IsProtocol("http") || url2.IsProtocol("https"))
947   {
948     m_multisession = true;
949     if(m_state->m_httpheader.GetValue("Server").find("Portable SDK for UPnP devices") != std::string::npos)
950     {
951       CLog::Log(LOGWARNING, "CCurlFile::Open - Disabling multi session due to broken libupnp server");
952       m_multisession = false;
953     }
954   }
955
956   if(StringUtils::EqualsNoCase(m_state->m_httpheader.GetValue("Transfer-Encoding"), "chunked"))
957     m_state->m_fileSize = 0;
958
959   if(m_state->m_fileSize <= 0)
960     m_seekable = false;
961   if (m_seekable)
962   {
963     if(url2.IsProtocol("http")
964     || url2.IsProtocol("https"))
965     {
966       // if server says explicitly it can't seek, respect that
967       if(StringUtils::EqualsNoCase(m_state->m_httpheader.GetValue("Accept-Ranges"),"none"))
968         m_seekable = false;
969     }
970   }
971
972   char* efurl;
973   if (CURLE_OK == g_curlInterface.easy_getinfo(m_state->m_easyHandle, CURLINFO_EFFECTIVE_URL,&efurl) && efurl)
974     m_url = efurl;
975
976   return true;
977 }
978
979 bool CCurlFile::OpenForWrite(const CURL& url, bool bOverWrite)
980 {
981   if(m_opened)
982     return false;
983
984   if (Exists(url) && !bOverWrite)
985     return false;
986
987   CURL url2(url);
988   ParseAndCorrectUrl(url2);
989
990   CLog::Log(LOGDEBUG, "CCurlFile::OpenForWrite(%p) %s", (void*)this, CURL::GetRedacted(m_url).c_str());
991
992   assert(m_state->m_easyHandle == NULL);
993   g_curlInterface.easy_aquire(url2.GetProtocol().c_str(),
994                               url2.GetHostName().c_str(),
995                               &m_state->m_easyHandle,
996                               &m_state->m_multiHandle);
997
998     // setup common curl options
999   SetCommonOptions(m_state);
1000   SetRequestHeaders(m_state);
1001
1002   char* efurl;
1003   if (CURLE_OK == g_curlInterface.easy_getinfo(m_state->m_easyHandle, CURLINFO_EFFECTIVE_URL,&efurl) && efurl)
1004     m_url = efurl;
1005
1006   m_opened = true;
1007   m_forWrite = true;
1008   m_inError = false;
1009   m_writeOffset = 0;
1010
1011   assert(m_state->m_multiHandle);
1012
1013   SetCommonOptions(m_state); 
1014   g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_UPLOAD, 1);
1015
1016   g_curlInterface.multi_add_handle(m_state->m_multiHandle, m_state->m_easyHandle);
1017
1018   m_state->SetReadBuffer(NULL, 0);
1019
1020   return true;
1021 }
1022
1023 int CCurlFile::Write(const void* lpBuf, int64_t uiBufSize)
1024 {
1025   if (!(m_opened && m_forWrite) || m_inError)
1026     return -1;
1027
1028   assert(m_state->m_multiHandle);
1029
1030   m_state->SetReadBuffer(lpBuf, uiBufSize);
1031   m_state->m_isPaused = false;
1032   g_curlInterface.easy_pause(m_state->m_easyHandle, CURLPAUSE_CONT);
1033
1034   CURLMcode result = CURLM_OK;
1035
1036   m_stillRunning = 1;
1037   while (m_stillRunning && !m_state->m_isPaused)
1038   {
1039     while ((result = g_curlInterface.multi_perform(m_state->m_multiHandle, &m_stillRunning)) == CURLM_CALL_MULTI_PERFORM);
1040
1041     if (!m_stillRunning)
1042       break;
1043
1044     if (result != CURLM_OK)
1045     {
1046       long code;
1047       if(g_curlInterface.easy_getinfo(m_state->m_easyHandle, CURLINFO_RESPONSE_CODE, &code) == CURLE_OK )
1048         CLog::Log(LOGERROR, "%s - Unable to write curl resource (%s) - %ld", __FUNCTION__, CURL::GetRedacted(m_url).c_str(), code);
1049       m_inError = true;
1050       return -1;
1051     }
1052   }
1053
1054   m_writeOffset += m_state->m_filePos;
1055   return m_state->m_filePos;
1056 }
1057
1058 bool CCurlFile::CReadState::ReadString(char *szLine, int iLineLength)
1059 {
1060   unsigned int want = (unsigned int)iLineLength;
1061
1062   if((m_fileSize == 0 || m_filePos < m_fileSize) && !FillBuffer(want))
1063     return false;
1064
1065   // ensure only available data is considered
1066   want = XMIN((unsigned int)m_buffer.getMaxReadSize(), want);
1067
1068   /* check if we finished prematurely */
1069   if (!m_stillRunning && (m_fileSize == 0 || m_filePos != m_fileSize) && !want)
1070   {
1071     if (m_fileSize != 0)
1072       CLog::Log(LOGWARNING, "%s - Transfer ended before entire file was retrieved pos %" PRId64", size %" PRId64, __FUNCTION__, m_filePos, m_fileSize);
1073
1074     return false;
1075   }
1076
1077   char* pLine = szLine;
1078   do
1079   {
1080     if (!m_buffer.ReadData(pLine, 1))
1081       break;
1082
1083     pLine++;
1084   } while (((pLine - 1)[0] != '\n') && ((unsigned int)(pLine - szLine) < want));
1085   pLine[0] = 0;
1086   m_filePos += (pLine - szLine);
1087   return (bool)((pLine - szLine) > 0);
1088 }
1089
1090 bool CCurlFile::Exists(const CURL& url)
1091 {
1092   // if file is already running, get info from it
1093   if( m_opened )
1094   {
1095     CLog::Log(LOGWARNING, "CCurlFile::Exists - Exist called on open file %s", url.GetRedacted().c_str());
1096     return true;
1097   }
1098
1099   CURL url2(url);
1100   ParseAndCorrectUrl(url2);
1101
1102   assert(m_state->m_easyHandle == NULL);
1103   g_curlInterface.easy_aquire(url2.GetProtocol().c_str(),
1104                               url2.GetHostName().c_str(),
1105                               &m_state->m_easyHandle, NULL);
1106
1107   SetCommonOptions(m_state);
1108   SetRequestHeaders(m_state);
1109   g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_TIMEOUT, 5);
1110   g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_NOBODY, 1);
1111   g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_WRITEDATA, NULL); /* will cause write failure*/
1112
1113   if(url2.IsProtocol("ftp"))
1114   {
1115     g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_FILETIME, 1);
1116     // nocwd is less standard, will return empty list for non-existed remote dir on some ftp server, avoid it.
1117     if (StringUtils::EndsWith(url2.GetFileName(), "/"))
1118       g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_FTP_FILEMETHOD, CURLFTPMETHOD_SINGLECWD);
1119     else
1120       g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_FTP_FILEMETHOD, CURLFTPMETHOD_NOCWD);
1121   }
1122
1123   CURLcode result = g_curlInterface.easy_perform(m_state->m_easyHandle);
1124   g_curlInterface.easy_release(&m_state->m_easyHandle, NULL);
1125
1126   if (result == CURLE_WRITE_ERROR || result == CURLE_OK)
1127     return true;
1128
1129   if (result == CURLE_HTTP_RETURNED_ERROR)
1130   {
1131     long code;
1132     if(g_curlInterface.easy_getinfo(m_state->m_easyHandle, CURLINFO_RESPONSE_CODE, &code) == CURLE_OK && code != 404 )
1133       CLog::Log(LOGERROR, "CCurlFile::Exists - Failed: HTTP returned error %ld for %s", code, url.GetRedacted().c_str());
1134   }
1135   else if (result != CURLE_REMOTE_FILE_NOT_FOUND && result != CURLE_FTP_COULDNT_RETR_FILE)
1136   {
1137     CLog::Log(LOGERROR, "CCurlFile::Exists - Failed: %s(%d) for %s", g_curlInterface.easy_strerror(result), result, url.GetRedacted().c_str());
1138   }
1139
1140   errno = ENOENT;
1141   return false;
1142 }
1143
1144 int64_t CCurlFile::Seek(int64_t iFilePosition, int iWhence)
1145 {
1146   int64_t nextPos = m_state->m_filePos;
1147   
1148   if(!m_seekable)
1149     return -1;
1150
1151   switch(iWhence)
1152   {
1153     case SEEK_SET:
1154       nextPos = iFilePosition;
1155       break;
1156     case SEEK_CUR:
1157       nextPos += iFilePosition;
1158       break;
1159     case SEEK_END:
1160       if (m_state->m_fileSize)
1161         nextPos = m_state->m_fileSize + iFilePosition;
1162       else
1163         return -1;
1164       break;
1165     default:
1166       return -1;
1167   }
1168
1169   // We can't seek beyond EOF
1170   if (m_state->m_fileSize && nextPos > m_state->m_fileSize) return -1;
1171
1172   if(m_state->Seek(nextPos))
1173     return nextPos;
1174
1175   if (m_multisession)
1176   {
1177     if (!m_oldState)
1178     {
1179       CURL url(m_url);
1180       m_oldState          = m_state;
1181       m_state             = new CReadState();
1182       m_state->m_fileSize = m_oldState->m_fileSize;
1183       g_curlInterface.easy_aquire(url.GetProtocol().c_str(),
1184                                   url.GetHostName().c_str(),
1185                                   &m_state->m_easyHandle,
1186                                   &m_state->m_multiHandle );
1187     }
1188     else
1189     {
1190       CReadState *tmp;
1191       tmp         = m_state;
1192       m_state     = m_oldState;
1193       m_oldState  = tmp;
1194
1195       if (m_state->Seek(nextPos))
1196         return nextPos;
1197       
1198       m_state->Disconnect();
1199     }
1200   }
1201   else
1202     m_state->Disconnect();
1203
1204   // re-setup common curl options
1205   SetCommonOptions(m_state);
1206
1207   /* caller might have changed some headers (needed for daap)*/
1208   SetRequestHeaders(m_state);
1209
1210   m_state->m_filePos = nextPos;
1211   m_state->m_sendRange = true;
1212
1213   long response = m_state->Connect(m_bufferSize);
1214   if(response < 0 && (m_state->m_fileSize == 0 || m_state->m_fileSize != m_state->m_filePos))
1215   {
1216     if(m_multisession)
1217     {
1218       if (m_oldState)
1219       {
1220         delete m_state;
1221         m_state     = m_oldState;
1222         m_oldState  = NULL;
1223       }
1224       // Retry without mutlisession
1225       m_multisession = false;
1226       return Seek(iFilePosition, iWhence);
1227     }
1228     else
1229     {
1230       m_seekable = false;
1231       return -1;
1232     } 
1233   }
1234
1235   SetCorrectHeaders(m_state);
1236
1237   return m_state->m_filePos;
1238 }
1239
1240 int64_t CCurlFile::GetLength()
1241 {
1242   if (!m_opened) return 0;
1243   return m_state->m_fileSize;
1244 }
1245
1246 int64_t CCurlFile::GetPosition()
1247 {
1248   if (!m_opened) return 0;
1249   return m_state->m_filePos;
1250 }
1251
1252 int CCurlFile::Stat(const CURL& url, struct __stat64* buffer)
1253 {
1254   // if file is already running, get info from it
1255   if( m_opened )
1256   {
1257     CLog::Log(LOGWARNING, "CCurlFile::Stat - Stat called on open file %s", url.GetRedacted().c_str());
1258     if (buffer)
1259     {
1260       memset(buffer, 0, sizeof(struct __stat64));
1261       buffer->st_size = GetLength();
1262       buffer->st_mode = _S_IFREG;
1263     }
1264     return 0;
1265   }
1266
1267   CURL url2(url);
1268   ParseAndCorrectUrl(url2);
1269
1270   assert(m_state->m_easyHandle == NULL);
1271   g_curlInterface.easy_aquire(url2.GetProtocol().c_str(),
1272                               url2.GetHostName().c_str(),
1273                               &m_state->m_easyHandle, NULL);
1274
1275   SetCommonOptions(m_state);
1276   SetRequestHeaders(m_state);
1277   g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_TIMEOUT, g_advancedSettings.m_curlconnecttimeout);
1278   g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_NOBODY, 1);
1279   g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_WRITEDATA, NULL); /* will cause write failure*/
1280   g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_FILETIME , 1); 
1281
1282   if(url2.IsProtocol("ftp"))
1283   {
1284     // nocwd is less standard, will return empty list for non-existed remote dir on some ftp server, avoid it.
1285     if (StringUtils::EndsWith(url2.GetFileName(), "/"))
1286       g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_FTP_FILEMETHOD, CURLFTPMETHOD_SINGLECWD);
1287     else
1288       g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_FTP_FILEMETHOD, CURLFTPMETHOD_NOCWD);
1289   }
1290
1291   CURLcode result = g_curlInterface.easy_perform(m_state->m_easyHandle);
1292
1293   if(result == CURLE_HTTP_RETURNED_ERROR)
1294   {
1295     long code;
1296     if(g_curlInterface.easy_getinfo(m_state->m_easyHandle, CURLINFO_RESPONSE_CODE, &code) == CURLE_OK && code == 404 )
1297       return -1;
1298   }
1299
1300   if(result == CURLE_GOT_NOTHING 
1301   || result == CURLE_HTTP_RETURNED_ERROR 
1302   || result == CURLE_RECV_ERROR /* some silly shoutcast servers */ )
1303   {
1304     /* some http servers and shoutcast servers don't give us any data on a head request */
1305     /* request normal and just fail out, it's their loss */
1306     /* somehow curl doesn't reset CURLOPT_NOBODY properly so reset everything */
1307     SetCommonOptions(m_state);
1308     SetRequestHeaders(m_state);
1309     g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_TIMEOUT, g_advancedSettings.m_curlconnecttimeout);
1310     g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_RANGE, "0-0");
1311     g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_WRITEDATA, NULL); /* will cause write failure*/
1312     g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_FILETIME, 1); 
1313     result = g_curlInterface.easy_perform(m_state->m_easyHandle);
1314   }
1315
1316   if( result == CURLE_HTTP_RANGE_ERROR )
1317   {
1318     /* crap can't use the range option, disable it and try again */
1319     g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_RANGE, NULL);
1320     result = g_curlInterface.easy_perform(m_state->m_easyHandle);
1321   }
1322
1323   if( result != CURLE_WRITE_ERROR && result != CURLE_OK )
1324   {
1325     g_curlInterface.easy_release(&m_state->m_easyHandle, NULL);
1326     errno = ENOENT;
1327     CLog::Log(LOGERROR, "CCurlFile::Stat - Failed: %s(%d) for %s", g_curlInterface.easy_strerror(result), result, url.GetRedacted().c_str());
1328     return -1;
1329   }
1330
1331   double length;
1332   result = g_curlInterface.easy_getinfo(m_state->m_easyHandle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &length);
1333   if (result != CURLE_OK || length < 0.0)
1334   {
1335     if (url.IsProtocol("ftp"))
1336     {
1337       g_curlInterface.easy_release(&m_state->m_easyHandle, NULL);
1338       CLog::Log(LOGNOTICE, "CCurlFile::Stat - Content length failed: %s(%d) for %s", g_curlInterface.easy_strerror(result), result, url.GetRedacted().c_str());
1339       errno = ENOENT;
1340       return -1;
1341     }
1342     else
1343       length = 0.0;
1344   }
1345
1346   SetCorrectHeaders(m_state);
1347
1348   if(buffer)
1349   {
1350     char *content;
1351     result = g_curlInterface.easy_getinfo(m_state->m_easyHandle, CURLINFO_CONTENT_TYPE, &content);
1352     if (result != CURLE_OK)
1353     {
1354       CLog::Log(LOGNOTICE, "CCurlFile::Stat - Content type failed: %s(%d) for %s", g_curlInterface.easy_strerror(result), result, url.GetRedacted().c_str());
1355       g_curlInterface.easy_release(&m_state->m_easyHandle, NULL);
1356       errno = ENOENT;
1357       return -1;
1358     }
1359     else
1360     {
1361       memset(buffer, 0, sizeof(struct __stat64));
1362       buffer->st_size = (int64_t)length;
1363       if(content && strstr(content, "text/html")) //consider html files directories
1364         buffer->st_mode = _S_IFDIR;
1365       else
1366         buffer->st_mode = _S_IFREG;
1367     }
1368     long filetime;
1369     result = g_curlInterface.easy_getinfo(m_state->m_easyHandle, CURLINFO_FILETIME, &filetime);
1370     if (result != CURLE_OK)
1371     {
1372       CLog::Log(LOGNOTICE, "CCurlFile::Stat - Filetime failed: %s(%d) for %s", g_curlInterface.easy_strerror(result), result, url.GetRedacted().c_str());
1373     }
1374     else
1375     {
1376       if (filetime != -1)
1377         buffer->st_mtime = filetime;
1378     }
1379   }
1380   g_curlInterface.easy_release(&m_state->m_easyHandle, NULL);
1381   return 0;
1382 }
1383
1384 unsigned int CCurlFile::CReadState::Read(void* lpBuf, int64_t uiBufSize)
1385 {
1386   /* only request 1 byte, for truncated reads (only if not eof) */
1387   if((m_fileSize == 0 || m_filePos < m_fileSize) && !FillBuffer(1))
1388     return 0;
1389
1390   /* ensure only available data is considered */
1391   unsigned int want = (unsigned int)XMIN(m_buffer.getMaxReadSize(), uiBufSize);
1392
1393   /* xfer data to caller */
1394   if (m_buffer.ReadData((char *)lpBuf, want))
1395   {
1396     m_filePos += want;
1397     return want;
1398   }
1399
1400   /* check if we finished prematurely */
1401   if (!m_stillRunning && (m_fileSize == 0 || m_filePos != m_fileSize))
1402   {
1403     CLog::Log(LOGWARNING, "%s - Transfer ended before entire file was retrieved pos %" PRId64", size %" PRId64, __FUNCTION__, m_filePos, m_fileSize);
1404     return 0;
1405   }
1406
1407   return 0;
1408 }
1409
1410 /* use to attempt to fill the read buffer up to requested number of bytes */
1411 bool CCurlFile::CReadState::FillBuffer(unsigned int want)
1412 {
1413   int retry = 0;
1414   fd_set fdread;
1415   fd_set fdwrite;
1416   fd_set fdexcep;
1417
1418   // only attempt to fill buffer if transactions still running and buffer
1419   // doesnt exceed required size already
1420   while ((unsigned int)m_buffer.getMaxReadSize() < want && m_buffer.getMaxWriteSize() > 0 )
1421   {
1422     if (m_cancelled)
1423       return false;
1424
1425     /* if there is data in overflow buffer, try to use that first */
1426     if (m_overflowSize)
1427     {
1428       unsigned amount = XMIN((unsigned int)m_buffer.getMaxWriteSize(), m_overflowSize);
1429       m_buffer.WriteData(m_overflowBuffer, amount);
1430
1431       if (amount < m_overflowSize)
1432         memcpy(m_overflowBuffer, m_overflowBuffer+amount,m_overflowSize-amount);
1433
1434       m_overflowSize -= amount;
1435       m_overflowBuffer = (char*)realloc_simple(m_overflowBuffer, m_overflowSize);
1436       continue;
1437     }
1438
1439     CURLMcode result = g_curlInterface.multi_perform(m_multiHandle, &m_stillRunning);
1440     if (!m_stillRunning)
1441     {
1442       if (result == CURLM_OK)
1443       {
1444         /* if we still have stuff in buffer, we are fine */
1445         if (m_buffer.getMaxReadSize())
1446           return true;
1447
1448         /* verify that we are actually okey */
1449         int msgs;
1450         CURLcode CURLresult = CURLE_OK;
1451         CURLMsg* msg;
1452         while ((msg = g_curlInterface.multi_info_read(m_multiHandle, &msgs)))
1453         {
1454           if (msg->msg == CURLMSG_DONE)
1455           {
1456             if (msg->data.result == CURLE_OK)
1457               return true;
1458
1459             CLog::Log(LOGERROR, "CCurlFile::FillBuffer - Failed: %s(%d)", g_curlInterface.easy_strerror(msg->data.result), msg->data.result);
1460
1461             // We need to check the result here as we don't want to retry on every error
1462             if ( (msg->data.result == CURLE_OPERATION_TIMEDOUT ||
1463                   msg->data.result == CURLE_PARTIAL_FILE       ||
1464                   msg->data.result == CURLE_COULDNT_CONNECT    ||
1465                   msg->data.result == CURLE_RECV_ERROR)        &&
1466                   !m_bFirstLoop)
1467               CURLresult = msg->data.result;
1468             else if ( (msg->data.result == CURLE_HTTP_RANGE_ERROR     ||
1469                        msg->data.result == CURLE_HTTP_RETURNED_ERROR) &&
1470                        m_bFirstLoop                                   &&
1471                        m_filePos == 0                                 &&
1472                        m_sendRange)
1473             {
1474               // If server returns a range or http error, retry with range disabled
1475               CURLresult = msg->data.result;
1476               m_sendRange = false;
1477             }
1478             else
1479               return false;
1480           }
1481         }
1482
1483         // Don't retry when we didn't "see" any error
1484         if (CURLresult == CURLE_OK)
1485           return false;
1486
1487         // Close handle
1488         if (m_multiHandle && m_easyHandle)
1489           g_curlInterface.multi_remove_handle(m_multiHandle, m_easyHandle);
1490
1491         // Reset all the stuff like we would in Disconnect()
1492         m_buffer.Clear();
1493         free(m_overflowBuffer);
1494         m_overflowBuffer = NULL;
1495         m_overflowSize = 0;
1496
1497         // If we got here something is wrong
1498         if (++retry > g_advancedSettings.m_curlretries)
1499         {
1500           CLog::Log(LOGERROR, "CCurlFile::FillBuffer - Reconnect failed!");
1501           // Reset the rest of the variables like we would in Disconnect()
1502           m_filePos = 0;
1503           m_fileSize = 0;
1504           m_bufferSize = 0;
1505
1506           return false;
1507         }
1508
1509         CLog::Log(LOGNOTICE, "CCurlFile::FillBuffer - Reconnect, (re)try %i", retry);
1510
1511         // Connect + seek to current position (again)
1512         SetResume();
1513         g_curlInterface.multi_add_handle(m_multiHandle, m_easyHandle);
1514
1515         // Return to the beginning of the loop:
1516         continue;
1517       }
1518       return false;
1519     }
1520
1521     // We've finished out first loop
1522     if(m_bFirstLoop && m_buffer.getMaxReadSize() > 0)
1523       m_bFirstLoop = false;
1524
1525     switch (result)
1526     {
1527       case CURLM_OK:
1528       {
1529         int maxfd = -1;
1530         FD_ZERO(&fdread);
1531         FD_ZERO(&fdwrite);
1532         FD_ZERO(&fdexcep);
1533
1534         // get file descriptors from the transfers
1535         g_curlInterface.multi_fdset(m_multiHandle, &fdread, &fdwrite, &fdexcep, &maxfd);
1536
1537         long timeout = 0;
1538         if (CURLM_OK != g_curlInterface.multi_timeout(m_multiHandle, &timeout) || timeout == -1)
1539           timeout = 200;
1540
1541         XbmcThreads::EndTime endTime(timeout);
1542         int rc;
1543
1544         do
1545         {
1546           unsigned int time_left = endTime.MillisLeft();
1547           struct timeval t = { time_left / 1000, (time_left % 1000) * 1000 };
1548
1549           // Wait until data is available or a timeout occurs.
1550           rc = select(maxfd + 1, &fdread, &fdwrite, &fdexcep, &t);
1551 #ifdef TARGET_WINDOWS
1552         } while(rc == SOCKET_ERROR && WSAGetLastError() == WSAEINTR);
1553 #else
1554         } while(rc == SOCKET_ERROR && errno == EINTR);
1555 #endif
1556
1557         if(rc == SOCKET_ERROR)
1558         {
1559 #ifdef TARGET_WINDOWS
1560           char buf[256];
1561           strerror_s(buf, 256, WSAGetLastError());
1562           CLog::Log(LOGERROR, "CCurlFile::FillBuffer - Failed with socket error:%s", buf);
1563 #else
1564           char const * str = strerror(errno);
1565           CLog::Log(LOGERROR, "CCurlFile::FillBuffer - Failed with socket error:%s", str);
1566 #endif
1567
1568           return false;
1569         }
1570       }
1571       break;
1572       case CURLM_CALL_MULTI_PERFORM:
1573       {
1574         // we don't keep calling here as that can easily overwrite our buffer which we want to avoid
1575         // docs says we should call it soon after, but aslong as we are reading data somewhere
1576         // this aught to be soon enough. should stay in socket otherwise
1577         continue;
1578       }
1579       break;
1580       default:
1581       {
1582         CLog::Log(LOGERROR, "CCurlFile::FillBuffer - Multi perform failed with code %d, aborting", result);
1583         return false;
1584       }
1585       break;
1586     }
1587   }
1588   return true;
1589 }
1590
1591 void CCurlFile::CReadState::SetReadBuffer(const void* lpBuf, int64_t uiBufSize)
1592 {
1593   m_readBuffer = (char*)lpBuf;
1594   m_fileSize = uiBufSize;
1595   m_filePos = 0;
1596 }
1597
1598 void CCurlFile::ClearRequestHeaders()
1599 {
1600   m_requestheaders.clear();
1601 }
1602
1603 void CCurlFile::SetRequestHeader(const std::string& header, const std::string& value)
1604 {
1605   m_requestheaders[header] = value;
1606 }
1607
1608 void CCurlFile::SetRequestHeader(const std::string& header, long value)
1609 {
1610   m_requestheaders[header] = StringUtils::Format("%ld", value);
1611 }
1612
1613 std::string CCurlFile::GetServerReportedCharset(void)
1614 {
1615   if (!m_state)
1616     return "";
1617
1618   return m_state->m_httpheader.GetCharset();
1619 }
1620
1621 /* STATIC FUNCTIONS */
1622 bool CCurlFile::GetHttpHeader(const CURL &url, CHttpHeader &headers)
1623 {
1624   try
1625   {
1626     CCurlFile file;
1627     if(file.Stat(url, NULL) == 0)
1628     {
1629       headers = file.GetHttpHeader();
1630       return true;
1631     }
1632     return false;
1633   }
1634   catch(...)
1635   {
1636     CLog::Log(LOGERROR, "%s - Exception thrown while trying to retrieve header url: %s", __FUNCTION__, url.GetRedacted().c_str());
1637     return false;
1638   }
1639 }
1640
1641 bool CCurlFile::GetMimeType(const CURL &url, std::string &content, const std::string &useragent)
1642 {
1643   CCurlFile file;
1644   if (!useragent.empty())
1645     file.SetUserAgent(useragent);
1646
1647   struct __stat64 buffer;
1648   std::string redactUrl = url.GetRedacted();
1649   if( file.Stat(url, &buffer) == 0 )
1650   {
1651     if (buffer.st_mode == _S_IFDIR)
1652       content = "x-directory/normal";
1653     else
1654       content = file.GetMimeType();
1655     CLog::Log(LOGDEBUG, "CCurlFile::GetMimeType - %s -> %s", redactUrl.c_str(), content.c_str());
1656     return true;
1657   }
1658   CLog::Log(LOGDEBUG, "CCurlFile::GetMimeType - %s -> failed", redactUrl.c_str());
1659   content.clear();
1660   return false;
1661 }
1662
1663 bool CCurlFile::GetCookies(const CURL &url, std::string &cookies)
1664 {
1665   std::string cookiesStr;
1666   struct curl_slist*     curlCookies;
1667   XCURL::CURL_HANDLE*    easyHandle;
1668   XCURL::CURLM*          multiHandle;
1669
1670   // get the cookies list
1671   g_curlInterface.easy_aquire(url.GetProtocol().c_str(),
1672                               url.GetHostName().c_str(),
1673                               &easyHandle, &multiHandle);
1674   if (CURLE_OK == g_curlInterface.easy_getinfo(easyHandle, CURLINFO_COOKIELIST, &curlCookies))
1675   {
1676     // iterate over each cookie and format it into an RFC 2109 formatted Set-Cookie string
1677     struct curl_slist* curlCookieIter = curlCookies;
1678     while(curlCookieIter)
1679     {
1680       // tokenize the CURL cookie string
1681       std::vector<std::string> valuesVec;
1682       StringUtils::Tokenize(curlCookieIter->data, valuesVec, "\t");
1683
1684       // ensure the length is valid
1685       if (valuesVec.size() < 7)
1686       {
1687         CLog::Log(LOGERROR, "CCurlFile::GetCookies - invalid cookie: '%s'", curlCookieIter->data);
1688         curlCookieIter = curlCookieIter->next;
1689         continue;
1690       }
1691
1692       // create a http-header formatted cookie string
1693       std::string cookieStr = valuesVec[5] + "=" + valuesVec[6] +
1694                               "; path=" + valuesVec[2] +
1695                               "; domain=" + valuesVec[0];
1696
1697       // append this cookie to the string containing all cookies
1698       if (!cookiesStr.empty())
1699         cookiesStr += "\n";
1700       cookiesStr += cookieStr;
1701
1702       // move on to the next cookie
1703       curlCookieIter = curlCookieIter->next;
1704     }
1705
1706     // free the curl cookies
1707     g_curlInterface.slist_free_all(curlCookies);
1708
1709     // release our handles
1710     g_curlInterface.easy_release(&easyHandle, &multiHandle);
1711
1712     // if we have a non-empty cookie string, return it
1713     if (!cookiesStr.empty())
1714     {
1715       cookies = cookiesStr;
1716       return true;
1717     }
1718   }
1719
1720   // no cookies to return
1721   return false;
1722 }
1723
1724 int CCurlFile::IoControl(EIoControl request, void* param)
1725 {
1726   if(request == IOCTRL_SEEK_POSSIBLE)
1727     return m_seekable ? 1 : 0;
1728
1729   return -1;
1730 }