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