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