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