Fixed: Crash when stopping/cancelling a scan that is in the middle of fetching inform...
[xbmc:xbmc-antiquated.git] / xbmc / FileSystem / FileCurl.cpp
1 /*
2  *      Copyright (C) 2005-2008 Team XBMC
3  *      http://www.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, write to
17  *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
18  *  http://www.gnu.org/copyleft/gpl.html
19  *
20  */
21
22 #include "stdafx.h"
23 #include "FileCurl.h"
24 #include "Util.h"
25 #include "URL.h"
26 #include "Settings.h"
27 #include "SystemInfo.h"
28 #include "File.h"
29
30 #include <sys/stat.h>
31 #include <vector>
32 #include <climits>
33
34 #ifdef _LINUX
35 #include <errno.h>
36 #include <inttypes.h>
37 #include "../linux/XFileUtils.h"
38 #endif
39
40 #include "DllLibCurl.h"
41 #include "FileShoutcast.h"
42
43 using namespace XFILE;
44 using namespace XCURL;
45
46 #define XMIN(a,b) ((a)<(b)?(a):(b))
47 #define FITS_INT(a) (((a) <= INT_MAX) && ((a) >= INT_MIN))
48
49 #define dllselect select
50
51 // Maximum retry amount:
52 #define MAX_RETRIES 4
53
54 // curl calls this routine to debug
55 extern "C" int debug_callback(CURL_HANDLE *handle, curl_infotype info, char *output, size_t size, void *data)
56 {
57   if (info == CURLINFO_DATA_IN || info == CURLINFO_DATA_OUT)
58     return 0;
59
60   CStdString strLine;
61   strLine.append(output, size);
62   std::vector<CStdString> vecLines;
63   CUtil::Tokenize(strLine, vecLines, "\r\n");
64   std::vector<CStdString>::const_iterator it = vecLines.begin();
65
66   while (it != vecLines.end()) {
67     CLog::Log(LOGDEBUG, "Curl::Debug %s", (*it).c_str());
68     it++;
69   }
70   return 0;
71 }
72
73 /* curl calls this routine to get more data */
74 extern "C" size_t write_callback(char *buffer,
75                size_t size,
76                size_t nitems,
77                void *userp)
78 {
79   if(userp == NULL) return 0;
80
81   CFileCurl::CReadState *state = (CFileCurl::CReadState *)userp;
82   return state->WriteCallback(buffer, size, nitems);
83 }
84
85 extern "C" size_t header_callback(void *ptr, size_t size, size_t nmemb, void *stream)
86 {
87   CFileCurl::CReadState *state = (CFileCurl::CReadState *)stream;
88   return state->HeaderCallback(ptr, size, nmemb);
89 }
90
91 /* fix for silly behavior of realloc */
92 static inline void* realloc_simple(void *ptr, size_t size)
93 {
94   void *ptr2 = realloc(ptr, size);
95   if(ptr && !ptr2 && size > 0)
96   {
97     free(ptr);
98     return NULL;
99   }
100   else
101     return ptr2;
102 }
103
104 size_t CFileCurl::CReadState::HeaderCallback(void *ptr, size_t size, size_t nmemb)
105 {
106   // libcurl doc says that this info is not always \0 terminated
107   char* strData = (char*)ptr;
108   int iSize = size * nmemb;
109
110   if (strData[iSize] != 0)
111   {
112     strData = (char*)malloc(iSize + 1);
113     strncpy(strData, (char*)ptr, iSize);
114     strData[iSize] = 0;
115   }
116   else strData = strdup((char*)ptr);
117
118   m_httpheader.Parse(strData);
119
120   free(strData);
121
122   return iSize;
123 }
124
125 size_t CFileCurl::CReadState::WriteCallback(char *buffer, size_t size, size_t nitems)
126 {
127   unsigned int amount = size * nitems;
128 //  CLog::Log(LOGDEBUG, "CFileCurl::WriteCallback (%p) with %i bytes, readsize = %i, writesize = %i", this, amount, m_buffer.GetMaxReadSize(), m_buffer.GetMaxWriteSize() - m_overflowSize);
129   if (m_overflowSize)
130   {
131     // we have our overflow buffer - first get rid of as much as we can
132     unsigned int maxWriteable = XMIN((unsigned int)m_buffer.GetMaxWriteSize(), m_overflowSize);
133     if (maxWriteable)
134     {
135       if (!m_buffer.WriteBinary(m_overflowBuffer, maxWriteable))
136         CLog::Log(LOGERROR, "Unable to write to buffer - what's up?");
137       if (m_overflowSize > maxWriteable)
138       { // still have some more - copy it down
139         memmove(m_overflowBuffer, m_overflowBuffer + maxWriteable, m_overflowSize - maxWriteable);
140       }
141       m_overflowSize -= maxWriteable;
142     }
143   }
144   // ok, now copy the data into our ring buffer
145   unsigned int maxWriteable = XMIN((unsigned int)m_buffer.GetMaxWriteSize(), amount);
146   if (maxWriteable)
147   {
148     if (!m_buffer.WriteBinary(buffer, maxWriteable))
149       CLog::Log(LOGERROR, "Unable to write to buffer - what's up?");
150     amount -= maxWriteable;
151     buffer += maxWriteable;
152   }
153   if (amount)
154   {
155     CLog::Log(LOGDEBUG, "CFileCurl::WriteCallback(%p) not enough free space for %i bytes", (void*)this,  amount);
156
157     m_overflowBuffer = (char*)realloc_simple(m_overflowBuffer, amount + m_overflowSize);
158     if(m_overflowBuffer == NULL)
159     {
160       CLog::Log(LOGDEBUG, "%s - Failed to grow overflow buffer", __FUNCTION__);
161       return 0;
162     }
163     memcpy(m_overflowBuffer + m_overflowSize, buffer, amount);
164     m_overflowSize += amount;
165   }
166   return size * nitems;
167 }
168
169 CFileCurl::CReadState::CReadState()
170 {
171   m_easyHandle = NULL;
172   m_multiHandle = NULL;
173   m_overflowBuffer = NULL;
174   m_overflowSize = 0;
175   m_filePos = 0;
176   m_fileSize = 0;
177   m_bufferSize = 0;
178   m_cancelled = false;
179 }
180
181 CFileCurl::CReadState::~CReadState()
182 {
183   Disconnect();
184
185   if(m_easyHandle)
186     g_curlInterface.easy_release(&m_easyHandle, &m_multiHandle);
187 }
188
189 bool CFileCurl::CReadState::Seek(__int64 pos)
190 {
191   if(pos == m_filePos)
192     return true;
193
194   if(FITS_INT(pos - m_filePos) && m_buffer.SkipBytes((int)(pos - m_filePos)))
195   {
196     m_filePos = pos;
197     return true;
198   }
199
200   if(pos > m_filePos && pos < m_filePos + m_bufferSize)
201   {
202     int len = m_buffer.GetMaxReadSize();
203     m_filePos += len;
204     m_buffer.SkipBytes(len);
205     if(!FillBuffer(m_bufferSize))
206     {
207       if(!m_buffer.SkipBytes(-len))
208         CLog::Log(LOGERROR, "%s - Failed to restore position after failed fill", __FUNCTION__);
209       else
210         m_filePos -= len;
211       return false;
212     }
213
214     if(!FITS_INT(pos - m_filePos) || !m_buffer.SkipBytes((int)(pos - m_filePos)))
215     {
216       CLog::Log(LOGERROR, "%s - Failed to skip to position after having filled buffer", __FUNCTION__);
217       if(!m_buffer.SkipBytes(-len))
218         CLog::Log(LOGERROR, "%s - Failed to restore position after failed seek", __FUNCTION__);
219       else
220         m_filePos -= len;
221       return false;
222     }
223     m_filePos = pos;
224     return true;
225   }
226   return false;
227 }
228
229 long CFileCurl::CReadState::Connect(unsigned int size)
230 {
231   g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_RESUME_FROM_LARGE, m_filePos);
232   g_curlInterface.multi_add_handle(m_multiHandle, m_easyHandle);
233
234   m_bufferSize = size;
235   m_buffer.Destroy();
236   m_buffer.Create(size * 3);
237
238   // read some data in to try and obtain the length
239   // maybe there's a better way to get this info??
240   m_stillRunning = 1;
241   if (!FillBuffer(1))
242   {
243     CLog::Log(LOGERROR, "CFileCurl::CReadState::Open, didn't get any data from stream.");
244     return -1;
245   }
246
247   double length;
248   if (CURLE_OK == g_curlInterface.easy_getinfo(m_easyHandle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &length))
249   {
250     if (length < 0)
251       length = 0.0;
252     m_fileSize = m_filePos + (__int64)length;
253   }
254
255   long response;
256   if (CURLE_OK == g_curlInterface.easy_getinfo(m_easyHandle, CURLINFO_RESPONSE_CODE, &response))
257     return response;
258
259   return -1;
260 }
261
262 void CFileCurl::CReadState::Disconnect()
263 {
264   if(m_multiHandle && m_easyHandle)
265     g_curlInterface.multi_remove_handle(m_multiHandle, m_easyHandle);
266
267   m_buffer.Clear();
268   free(m_overflowBuffer);
269   m_overflowBuffer = NULL;
270   m_overflowSize = 0;
271   m_filePos = 0;
272   m_fileSize = 0;
273   m_bufferSize = 0;
274 }
275
276
277 CFileCurl::~CFileCurl()
278 {
279   if (m_opened)
280     Close();
281   delete m_state;
282   g_curlInterface.Unload();
283 }
284
285 CFileCurl::CFileCurl()
286 {
287   g_curlInterface.Load(); // loads the curl dll and resolves exports etc.
288   m_curlAliasList = NULL;
289   m_curlHeaderList = NULL;
290   m_opened = false;
291   m_multisession  = true;
292   m_seekable = true;
293   m_useOldHttpVersion = false;
294   m_connecttimeout = 0;
295   m_lowspeedtime = 0;
296   m_ftpauth = "";
297   m_ftpport = "";
298   m_ftppasvip = false;
299   m_bufferSize = 32768;
300   m_binary = true;
301   m_postdata = "";
302   m_state = new CReadState();
303 }
304
305 //Has to be called before Open()
306 void CFileCurl::SetBufferSize(unsigned int size)
307 {
308   m_bufferSize = size;
309 }
310
311 void CFileCurl::Close()
312 {
313   CLog::Log(LOGDEBUG, "FileCurl::Close(%p) %s", (void*)this, m_url.c_str());
314   m_state->Disconnect();
315
316   m_url.Empty();
317   m_referer.Empty();
318
319   /* cleanup */
320   if( m_curlAliasList )
321     g_curlInterface.slist_free_all(m_curlAliasList);
322   if( m_curlHeaderList )
323     g_curlInterface.slist_free_all(m_curlHeaderList);
324
325   m_curlAliasList = NULL;
326   m_curlHeaderList = NULL;
327   m_opened = false;
328 }
329
330 void CFileCurl::SetCommonOptions(CReadState* state)
331 {
332   CURL_HANDLE* h = state->m_easyHandle;
333
334   g_curlInterface.easy_reset(h);
335
336   g_curlInterface.easy_setopt(h, CURLOPT_DEBUGFUNCTION, debug_callback);
337
338   if( g_advancedSettings.m_logLevel >= LOG_LEVEL_DEBUG )
339     g_curlInterface.easy_setopt(h, CURLOPT_VERBOSE, TRUE);
340   else
341     g_curlInterface.easy_setopt(h, CURLOPT_VERBOSE, FALSE);
342
343   g_curlInterface.easy_setopt(h, CURLOPT_WRITEDATA, state);
344   g_curlInterface.easy_setopt(h, CURLOPT_WRITEFUNCTION, write_callback);
345
346
347   // make sure headers are seperated from the data stream
348   g_curlInterface.easy_setopt(h, CURLOPT_WRITEHEADER, state);
349   g_curlInterface.easy_setopt(h, CURLOPT_HEADERFUNCTION, header_callback);
350   g_curlInterface.easy_setopt(h, CURLOPT_HEADER, FALSE);
351
352   g_curlInterface.easy_setopt(h, CURLOPT_FTP_USE_EPSV, 0); // turn off epsv
353
354   // Allow us to follow two redirects
355   g_curlInterface.easy_setopt(h, CURLOPT_FOLLOWLOCATION, TRUE);
356   g_curlInterface.easy_setopt(h, CURLOPT_MAXREDIRS, 5);
357
358   // Enable cookie engine for current handle to re-use them in future requests
359   g_curlInterface.easy_setopt(h, CURLOPT_COOKIEFILE, "");
360
361   // When using multiple threads you should set the CURLOPT_NOSIGNAL option to
362   // TRUE for all handles. Everything will work fine except that timeouts are not
363   // honored during the DNS lookup - which you can work around by building libcurl
364   // with c-ares support. c-ares is a library that provides asynchronous name
365   // resolves. Unfortunately, c-ares does not yet support IPv6.
366   g_curlInterface.easy_setopt(h, CURLOPT_NOSIGNAL, TRUE);
367
368   // not interested in failed requests
369   g_curlInterface.easy_setopt(h, CURLOPT_FAILONERROR, 1);
370
371   // enable support for icecast / shoutcast streams
372   m_curlAliasList = g_curlInterface.slist_append(m_curlAliasList, "ICY 200 OK");
373   g_curlInterface.easy_setopt(h, CURLOPT_HTTP200ALIASES, m_curlAliasList);
374
375   // never verify peer, we don't have any certificates to do this
376   g_curlInterface.easy_setopt(h, CURLOPT_SSL_VERIFYPEER, 0);
377   g_curlInterface.easy_setopt(h, CURLOPT_SSL_VERIFYHOST, 0);
378
379   g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_URL, m_url.c_str());
380   g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_TRANSFERTEXT, FALSE);
381
382   // setup POST data if it exists
383   if (!m_postdata.IsEmpty())
384   {
385     g_curlInterface.easy_setopt(h, CURLOPT_POST, 1 );
386     g_curlInterface.easy_setopt(h, CURLOPT_POSTFIELDSIZE, m_postdata.length());
387     g_curlInterface.easy_setopt(h, CURLOPT_POSTFIELDS, m_postdata.c_str());
388   }
389
390   // setup Referer header if needed
391   if (!m_referer.IsEmpty())
392     g_curlInterface.easy_setopt(h, CURLOPT_REFERER, m_referer.c_str());
393   else
394     g_curlInterface.easy_setopt(h, CURLOPT_AUTOREFERER, TRUE);
395     
396   // setup any requested authentication
397   if( m_ftpauth.length() > 0 )
398   {
399     g_curlInterface.easy_setopt(h, CURLOPT_FTP_SSL, CURLFTPSSL_TRY);
400     if( m_ftpauth.Equals("any") )
401       g_curlInterface.easy_setopt(h, CURLOPT_FTPSSLAUTH, CURLFTPAUTH_DEFAULT);
402     else if( m_ftpauth.Equals("ssl") )
403       g_curlInterface.easy_setopt(h, CURLOPT_FTPSSLAUTH, CURLFTPAUTH_SSL);
404     else if( m_ftpauth.Equals("tls") )
405       g_curlInterface.easy_setopt(h, CURLOPT_FTPSSLAUTH, CURLFTPAUTH_TLS);
406   }
407
408   // allow passive mode for ftp
409   if( m_ftpport.length() > 0 )
410     g_curlInterface.easy_setopt(h, CURLOPT_FTPPORT, m_ftpport.c_str());
411   else
412     g_curlInterface.easy_setopt(h, CURLOPT_FTPPORT, NULL);
413
414   // allow curl to not use the ip address in the returned pasv response
415   if( m_ftppasvip )
416     g_curlInterface.easy_setopt(h, CURLOPT_FTP_SKIP_PASV_IP, 0);
417   else
418     g_curlInterface.easy_setopt(h, CURLOPT_FTP_SKIP_PASV_IP, 1);
419
420   // always allow gzip compression
421   if( m_contentencoding.length() > 0 )
422     g_curlInterface.easy_setopt(h, CURLOPT_ENCODING, m_contentencoding.c_str());
423
424   if (m_userAgent.length() > 0)
425     g_curlInterface.easy_setopt(h, CURLOPT_USERAGENT, m_userAgent.c_str());
426   else /* set some default agent as shoutcast doesn't return proper stuff otherwise */
427     g_curlInterface.easy_setopt(h, CURLOPT_USERAGENT, g_sysinfo.GetUserAgent().c_str());
428
429   if (m_useOldHttpVersion)
430     g_curlInterface.easy_setopt(h, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
431   else
432     SetRequestHeader("Connection", "keep-alive");
433
434   if (m_proxy.length() > 0)
435   {
436     g_curlInterface.easy_setopt(h, CURLOPT_PROXY, m_proxy.c_str());
437     if (m_proxyuserpass.length() > 0)
438       g_curlInterface.easy_setopt(h, CURLOPT_PROXYUSERPWD, m_proxyuserpass.c_str());
439
440   }
441   if (m_customrequest.length() > 0)
442     g_curlInterface.easy_setopt(h, CURLOPT_CUSTOMREQUEST, m_customrequest.c_str());
443
444   if (m_connecttimeout == 0)
445     m_connecttimeout = g_advancedSettings.m_curlconnecttimeout;
446
447   // set our timeouts, we abort connection after m_timeout, and reads after no data for m_timeout seconds
448   g_curlInterface.easy_setopt(h, CURLOPT_CONNECTTIMEOUT, m_connecttimeout);
449
450   // We abort in case we transfer less than 1byte/second
451   g_curlInterface.easy_setopt(h, CURLOPT_LOW_SPEED_LIMIT, 1);
452
453   if (m_lowspeedtime == 0)
454     m_lowspeedtime = g_advancedSettings.m_curllowspeedtime;
455
456   // Set the lowspeed time very low as it seems Curl takes much longer to detect a lowspeed condition
457   g_curlInterface.easy_setopt(h, CURLOPT_LOW_SPEED_TIME, m_lowspeedtime);
458 }
459
460 void CFileCurl::SetRequestHeaders(CReadState* state)
461 {
462   if(m_curlHeaderList)
463   {
464     g_curlInterface.slist_free_all(m_curlHeaderList);
465     m_curlHeaderList = NULL;
466   }
467
468   MAPHTTPHEADERS::iterator it;
469   for(it = m_requestheaders.begin(); it != m_requestheaders.end(); it++)
470   {
471     CStdString buffer = it->first + ": " + it->second;
472     m_curlHeaderList = g_curlInterface.slist_append(m_curlHeaderList, buffer.c_str());
473   }
474
475   // add user defined headers
476   if (m_curlHeaderList && state->m_easyHandle)
477     g_curlInterface.easy_setopt(state->m_easyHandle, CURLOPT_HTTPHEADER, m_curlHeaderList);
478
479 }
480
481 void CFileCurl::SetCorrectHeaders(CReadState* state)
482 {
483   CHttpHeader& h = state->m_httpheader;
484   /* workaround for shoutcast server wich doesn't set content type on standard mp3 */
485   if( h.GetContentType().IsEmpty() )
486   {
487     if( !h.GetValue("icy-notice1").IsEmpty()
488     || !h.GetValue("icy-name").IsEmpty()
489     || !h.GetValue("icy-br").IsEmpty() )
490     h.Parse("Content-Type: audio/mpeg\r\n");
491   }
492
493   /* hack for google video */
494   if ( h.GetContentType().Equals("text/html")
495   &&  !h.GetValue("Content-Disposition").IsEmpty() )
496   {
497     CStdString strValue = h.GetValue("Content-Disposition");
498     if (strValue.Find("filename=") > -1 && strValue.Find(".flv") > -1)
499       h.Parse("Content-Type: video/flv\r\n");
500   }
501 }
502
503 void CFileCurl::ParseAndCorrectUrl(CURL &url2)
504 {
505   if( url2.GetProtocol().Equals("ftpx") )
506     url2.SetProtocol("ftp");
507   else if( url2.GetProtocol().Equals("shout")
508        ||  url2.GetProtocol().Equals("daap")
509        ||  url2.GetProtocol().Equals("upnp")
510        ||  url2.GetProtocol().Equals("tuxbox")
511        ||  url2.GetProtocol().Equals("lastfm")
512        ||  url2.GetProtocol().Equals("mms"))
513     url2.SetProtocol("http");
514
515   if( url2.GetProtocol().Equals("ftp")
516   ||  url2.GetProtocol().Equals("ftps") )
517   {
518     /* this is uggly, depending on from where   */
519     /* we get the link it may or may not be     */
520     /* url encoded. if handed from ftpdirectory */
521     /* it won't be so let's handle that case    */
522
523     CStdString partial, filename(url2.GetFileName());
524     CStdStringArray array;
525
526     /* our current client doesn't support utf8 */
527     g_charsetConverter.utf8ToStringCharset(filename);
528
529     /* TODO: create a tokenizer that doesn't skip empty's */
530     CUtil::Tokenize(filename, array, "/");
531     filename.Empty();
532     for(CStdStringArray::iterator it = array.begin(); it != array.end(); it++)
533     {
534       if(it != array.begin())
535         filename += "/";
536
537       partial = *it;
538       CUtil::URLEncode(partial);
539       filename += partial;
540     }
541
542     /* make sure we keep slashes */
543     if(url2.GetFileName().Right(1) == "/")
544       filename += "/";
545
546     url2.SetFileName(filename);
547
548     CStdString options = url2.GetOptions().Mid(1);
549     options.TrimRight('/'); // hack for trailing slashes being added from source
550
551     m_ftpauth = "";
552     m_ftpport = "";
553     m_ftppasvip = false;
554
555     /* parse options given */
556     CUtil::Tokenize(options, array, "&");
557     for(CStdStringArray::iterator it = array.begin(); it != array.end(); it++)
558     {
559       CStdString name, value;
560       int pos = it->Find('=');
561       if(pos >= 0)
562       {
563         name = it->Left(pos);
564         value = it->Mid(pos+1, it->size());
565       }
566       else
567       {
568         name = (*it);
569         value = "";
570       }
571
572       if(name.Equals("auth"))
573       {
574         m_ftpauth = value;
575         if(m_ftpauth.IsEmpty())
576           m_ftpauth = "any";
577       }
578       else if(name.Equals("active"))
579       {
580         m_ftpport = value;
581         if(value.IsEmpty())
582           m_ftpport = "-";
583       }
584       else if(name.Equals("pasvip"))
585       {
586         if(value == "0")
587           m_ftppasvip = false;
588         else
589           m_ftppasvip = true;
590       }
591     }
592
593     /* ftp has no options */
594     url2.SetOptions("");
595   }
596   else if( url2.GetProtocol().Equals("http")
597        ||  url2.GetProtocol().Equals("https"))
598   {
599     if (g_guiSettings.GetBool("network.usehttpproxy") && m_proxy.IsEmpty())
600     {
601       m_proxy = "http://" + g_guiSettings.GetString("network.httpproxyserver");
602       m_proxy += ":" + g_guiSettings.GetString("network.httpproxyport");
603       if (g_guiSettings.GetString("network.httpproxyusername").length() > 0 && m_proxyuserpass.IsEmpty())
604       {
605         m_proxyuserpass = g_guiSettings.GetString("network.httpproxyusername");
606         m_proxyuserpass += ":" + g_guiSettings.GetString("network.httpproxypassword");
607       }
608       CLog::Log(LOGDEBUG, "Using proxy %s", m_proxy.c_str());
609     }
610   }
611 }
612
613 bool CFileCurl::Post(const CStdString& strURL, const CStdString& strPostData, CStdString& strHTML)
614 {
615   return Service(strURL, strPostData, strHTML);
616 }
617
618 bool CFileCurl::Get(const CStdString& strURL, CStdString& strHTML)
619 {
620   return Service(strURL, "", strHTML);
621 }
622
623 bool CFileCurl::Service(const CStdString& strURL, const CStdString& strPostData, CStdString& strHTML)
624 {
625   m_postdata = strPostData;
626   if (Open(strURL))
627   {
628     if (ReadData(strHTML))
629     {
630       Close();
631       return true;
632     }
633   }
634   Close();
635   return false;
636 }
637
638 bool CFileCurl::ReadData(CStdString& strHTML)
639 {
640   int size_read = 0;
641   int data_size = 0;
642   strHTML = "";
643   char buffer[16384];
644   while( (size_read = Read(buffer, sizeof(buffer)-1) ) > 0 )
645   {
646     buffer[size_read] = 0;
647     strHTML.append(buffer, size_read);
648     data_size += size_read;
649   }
650   if (m_state->m_cancelled)
651     return false;
652   return true;
653 }
654
655 bool CFileCurl::Download(const CStdString& strURL, const CStdString& strFileName, LPDWORD pdwSize)
656 {
657   CLog::Log(LOGINFO, "Download: %s->%s", strURL.c_str(), strFileName.c_str());
658
659   CStdString strData;
660   if (!Get(strURL, strData))
661     return false;
662
663   XFILE::CFile file;
664   if (!file.OpenForWrite(strFileName, true))
665   {
666     CLog::Log(LOGERROR, "Unable to open file %s: %u",
667     strFileName.c_str(), GetLastError());
668     return false;
669   }
670   if (strData.size())
671     file.Write(strData.c_str(), strData.size());
672   file.Close();
673
674   if (pdwSize != NULL)
675   {
676     *pdwSize = strData.size();
677   }
678
679   return true;
680 }
681
682 // Detect whether we are "online" or not! Very simple and dirty!
683 bool CFileCurl::IsInternet(bool checkDNS /* = true */)
684 {
685   CStdString strURL = "http://www.google.com";
686   if (!checkDNS)
687     strURL = "http://74.125.19.103"; // www.google.com ip
688
689   int result = Stat(strURL, NULL);
690   Close();
691
692   if (result)
693     return false;
694   else
695     return true;
696
697   return false;
698 }
699
700 void CFileCurl::Cancel()
701 {
702   m_state->m_cancelled = true;
703   while (m_opened)
704     Sleep(1);
705 }
706
707 void CFileCurl::Reset()
708 {
709   m_state->m_cancelled = false;
710 }
711
712
713 bool CFileCurl::Open(const CURL& url)
714 {
715
716   CURL url2(url);
717   ParseAndCorrectUrl(url2);
718
719   url2.GetURL(m_url);
720
721   CLog::Log(LOGDEBUG, "FileCurl::Open(%p) %s", (void*)this, m_url.c_str());
722
723   ASSERT(!(!m_state->m_easyHandle ^ !m_state->m_multiHandle));
724   if( m_state->m_easyHandle == NULL )
725     g_curlInterface.easy_aquire(url2.GetProtocol(), url2.GetHostName(), &m_state->m_easyHandle, &m_state->m_multiHandle );
726
727   // setup common curl options
728   SetCommonOptions(m_state);
729   SetRequestHeaders(m_state);
730
731   m_opened = true;
732
733   long response = m_state->Connect(m_bufferSize);
734   if( response < 0 )
735     return false;
736
737   SetCorrectHeaders(m_state);
738
739   // check if this stream is a shoutcast stream. sometimes checking the protocol line is not enough so examine other headers as well.
740   // shoutcast streams should be handled by FileShoutcast.
741   if (m_state->m_httpheader.GetProtoLine().Left(3) == "ICY" || !m_state->m_httpheader.GetValue("icy-notice1").IsEmpty()
742      || !m_state->m_httpheader.GetValue("icy-name").IsEmpty()
743      || !m_state->m_httpheader.GetValue("icy-br").IsEmpty() )
744   {
745     CLog::Log(LOGDEBUG,"FileCurl - file <%s> is a shoutcast stream. re-opening", m_url.c_str());
746     throw new CRedirectException(new CFileShoutcast);
747   }
748
749   m_multisession = false;
750   if(m_url.Left(5).Equals("http:") || m_url.Left(6).Equals("https:"))
751   {
752     m_multisession = true;
753     if(m_state->m_httpheader.GetValue("Server").Find("Portable SDK for UPnP devices") >= 0)
754     {
755       CLog::Log(LOGWARNING, "FileCurl - disabling multi session due to broken libupnp server");
756       m_multisession = false;
757     }
758   }
759
760   if(m_state->m_httpheader.GetValue("Transfer-Encoding").Equals("chunked"))
761     m_state->m_fileSize = 0;
762
763   m_seekable = false;
764   if(m_state->m_fileSize > 0)
765   {
766     m_seekable = true;
767
768     if(url2.GetProtocol().Equals("http")
769     || url2.GetProtocol().Equals("https"))
770     {
771       // if server says explicitly it can't seek, respect that
772       if(m_state->m_httpheader.GetValue("Accept-Ranges").Equals("none"))
773         m_seekable = false;
774     }
775   }
776   return true;
777 }
778
779 bool CFileCurl::CReadState::ReadString(char *szLine, int iLineLength)
780 {
781   unsigned int want = (unsigned int)iLineLength;
782
783   if((m_fileSize == 0 || m_filePos < m_fileSize) && !FillBuffer(want))
784     return false;
785
786   // ensure only available data is considered
787   want = XMIN((unsigned int)m_buffer.GetMaxReadSize(), want);
788
789   /* check if we finished prematurely */
790   if (!m_stillRunning && (m_fileSize == 0 || m_filePos != m_fileSize) && !want)
791   {
792     if (m_fileSize != 0)
793       CLog::Log(LOGWARNING, "%s - Transfer ended before entire file was retrieved pos %"PRId64", size %"PRId64, __FUNCTION__, m_filePos, m_fileSize);
794       
795     return false;
796   }
797
798   char* pLine = szLine;
799   do
800   {
801     if (!m_buffer.ReadBinary(pLine, 1))
802       break;
803
804     pLine++;
805   } while (((pLine - 1)[0] != '\n') && ((unsigned int)(pLine - szLine) < want));
806   pLine[0] = 0;
807   m_filePos += (pLine - szLine);
808   return (bool)((pLine - szLine) > 0);
809 }
810
811 bool CFileCurl::Exists(const CURL& url)
812 {
813   return Stat(url, NULL) == 0;
814 }
815
816
817 __int64 CFileCurl::Seek(__int64 iFilePosition, int iWhence)
818 {
819   __int64 nextPos = m_state->m_filePos;
820   switch(iWhence)
821   {
822     case SEEK_SET:
823       nextPos = iFilePosition;
824       break;
825     case SEEK_CUR:
826       nextPos += iFilePosition;
827       break;
828     case SEEK_END:
829       if (m_state->m_fileSize)
830         nextPos = m_state->m_fileSize + iFilePosition;
831       else
832         return -1;
833       break;
834     case SEEK_POSSIBLE:
835       return m_seekable ? 1 : 0;
836     default:
837       return -1;
838   }
839
840   // We can't seek beyond EOF
841   if (m_state->m_fileSize && nextPos > m_state->m_fileSize) return -1;
842
843   if(m_state->Seek(nextPos))
844     return nextPos;
845
846   if(!m_seekable)
847     return -1;
848
849   CReadState* oldstate = NULL;
850   if(m_multisession)
851   {
852     CURL url(m_url);
853     oldstate = m_state;
854     m_state = new CReadState();
855
856     g_curlInterface.easy_aquire(url.GetProtocol(), url.GetHostName(), &m_state->m_easyHandle, &m_state->m_multiHandle );
857
858     // setup common curl options
859     SetCommonOptions(m_state);
860   }
861   else
862     m_state->Disconnect();
863
864   /* caller might have changed some headers (needed for daap)*/
865   SetRequestHeaders(m_state);
866
867   m_state->m_filePos = nextPos;
868   long response = m_state->Connect(m_bufferSize);
869   if(response < 0)
870   {
871     m_seekable = false;
872     if(oldstate)
873     {
874       delete m_state;
875       m_state = oldstate;
876     }
877     return -1;
878   }
879
880   SetCorrectHeaders(m_state);
881   delete oldstate;
882
883   return m_state->m_filePos;
884 }
885
886 __int64 CFileCurl::GetLength()
887 {
888   if (!m_opened) return 0;
889   return m_state->m_fileSize;
890 }
891
892 __int64 CFileCurl::GetPosition()
893 {
894   if (!m_opened) return 0;
895   return m_state->m_filePos;
896 }
897
898 int CFileCurl::Stat(const CURL& url, struct __stat64* buffer)
899 {
900   // if file is already running, get info from it
901   if( m_opened )
902   {
903     CLog::Log(LOGWARNING, "%s - Stat called on open file", __FUNCTION__);
904     buffer->st_size = GetLength();
905     buffer->st_mode = _S_IFREG;
906     return 0;
907   }
908
909   CURL url2(url);
910   ParseAndCorrectUrl(url2);
911
912   url2.GetURL(m_url);
913
914   ASSERT(m_state->m_easyHandle == NULL);
915   g_curlInterface.easy_aquire(url2.GetProtocol(), url2.GetHostName(), &m_state->m_easyHandle, NULL);
916
917   SetCommonOptions(m_state);
918   SetRequestHeaders(m_state);
919   g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_TIMEOUT, 5);
920   g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_NOBODY, 1);
921   g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_WRITEDATA, NULL); /* will cause write failure*/
922
923   CURLcode result = g_curlInterface.easy_perform(m_state->m_easyHandle);
924
925
926   if(result == CURLE_GOT_NOTHING || result == CURLE_HTTP_RETURNED_ERROR )
927   {
928     /* some http servers and shoutcast servers don't give us any data on a head request */
929     /* request normal and just fail out, it's their loss */
930     /* somehow curl doesn't reset CURLOPT_NOBODY properly so reset everything */
931     SetCommonOptions(m_state);
932     SetRequestHeaders(m_state);
933     g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_TIMEOUT, 5);
934     g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_RANGE, "0-0");
935     g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_WRITEDATA, NULL); /* will cause write failure*/
936     result = g_curlInterface.easy_perform(m_state->m_easyHandle);
937   }
938
939   if( result == CURLE_HTTP_RANGE_ERROR )
940   {
941     /* crap can't use the range option, disable it and try again */
942     g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_RANGE, NULL);
943     result = g_curlInterface.easy_perform(m_state->m_easyHandle);
944   }
945
946   if( result != CURLE_WRITE_ERROR && result != CURLE_OK )
947   {
948     g_curlInterface.easy_release(&m_state->m_easyHandle, NULL);
949     errno = ENOENT;
950     return -1;
951   }
952
953   double length;
954   if (CURLE_OK != g_curlInterface.easy_getinfo(m_state->m_easyHandle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &length) || length < 0.0)
955   {
956     if (url.GetProtocol() == "ftp")
957     {
958       g_curlInterface.easy_release(&m_state->m_easyHandle, NULL);
959       errno = ENOENT;
960       return -1;
961     }
962     else
963       length = 0.0;
964   }
965
966   SetCorrectHeaders(m_state);
967
968   if(buffer)
969   {
970     char content[255];
971     if (CURLE_OK != g_curlInterface.easy_getinfo(m_state->m_easyHandle, CURLINFO_CONTENT_TYPE, content))
972     { 
973       g_curlInterface.easy_release(&m_state->m_easyHandle, NULL); 
974       errno = ENOENT;
975       return -1;
976     }
977     else
978     {
979       buffer->st_size = (__int64)length;
980       if(strstr(content, "text/html")) //consider html files directories
981         buffer->st_mode = _S_IFDIR;
982       else
983         buffer->st_mode = _S_IFREG;
984     }
985   }
986
987   g_curlInterface.easy_release(&m_state->m_easyHandle, NULL);
988   return 0;
989 }
990
991 unsigned int CFileCurl::CReadState::Read(void* lpBuf, __int64 uiBufSize)
992 {
993   /* only request 1 byte, for truncated reads (only if not eof) */
994   if((m_fileSize == 0 || m_filePos < m_fileSize) && !FillBuffer(1))
995     return 0;
996
997   /* ensure only available data is considered */
998   unsigned int want = (unsigned int)XMIN(m_buffer.GetMaxReadSize(), uiBufSize);
999
1000   /* xfer data to caller */
1001   if (m_buffer.ReadBinary((char *)lpBuf, want))
1002   {
1003     m_filePos += want;
1004     return want;
1005   }
1006
1007   /* check if we finished prematurely */
1008   if (!m_stillRunning && (m_fileSize == 0 || m_filePos != m_fileSize))
1009   {
1010     CLog::Log(LOGWARNING, "%s - Transfer ended before entire file was retrieved pos %"PRId64", size %"PRId64, __FUNCTION__, m_filePos, m_fileSize);
1011     return 0;
1012   }
1013
1014   return 0;
1015 }
1016
1017 /* use to attempt to fill the read buffer up to requested number of bytes */
1018 bool CFileCurl::CReadState::FillBuffer(unsigned int want)
1019 {
1020   int retry=0;
1021   int maxfd;
1022   fd_set fdread;
1023   fd_set fdwrite;
1024   fd_set fdexcep;
1025
1026   // only attempt to fill buffer if transactions still running and buffer
1027   // doesnt exceed required size already
1028   while ((unsigned int)m_buffer.GetMaxReadSize() < want && m_buffer.GetMaxWriteSize() > 0 )
1029   {
1030     if (m_cancelled)
1031       return false;
1032     /* if there is data in overflow buffer, try to use that first */
1033     if (m_overflowSize)
1034     {
1035       unsigned amount = XMIN((unsigned int)m_buffer.GetMaxWriteSize(), m_overflowSize);
1036       m_buffer.WriteBinary(m_overflowBuffer, amount);
1037
1038       if (amount < m_overflowSize)
1039         memcpy(m_overflowBuffer, m_overflowBuffer+amount,m_overflowSize-amount);
1040
1041       m_overflowSize -= amount;
1042       m_overflowBuffer = (char*)realloc_simple(m_overflowBuffer, m_overflowSize);
1043       continue;
1044     }
1045
1046     CURLMcode result = g_curlInterface.multi_perform(m_multiHandle, &m_stillRunning);
1047     if (!m_stillRunning)
1048     {
1049       if (result == CURLM_OK)
1050       {
1051         /* if we still have stuff in buffer, we are fine */
1052         if (m_buffer.GetMaxReadSize())
1053           return true;
1054
1055         /* verify that we are actually okey */
1056         int msgs;
1057         CURLcode CURLresult = CURLE_OK;
1058         CURLMsg* msg;
1059         while ((msg = g_curlInterface.multi_info_read(m_multiHandle, &msgs)))
1060         {
1061           if (msg->msg == CURLMSG_DONE)
1062           {
1063             if (msg->data.result == CURLE_OK)
1064               return true;
1065
1066             CLog::Log(LOGDEBUG, "%s: curl failed with code %i", __FUNCTION__, msg->data.result);
1067
1068             // We need to check the data.result here as we don't want to retry on every error
1069             if (msg->data.result == CURLE_OPERATION_TIMEDOUT || msg->data.result == CURLE_PARTIAL_FILE)
1070               CURLresult=msg->data.result;
1071             else
1072               return false;
1073           }
1074         }
1075
1076         // Don't retry, when we didn't "see" any error
1077         if (CURLresult == CURLE_OK)
1078           return false;
1079
1080         // Close handle
1081         if (m_multiHandle && m_easyHandle)
1082           g_curlInterface.multi_remove_handle(m_multiHandle, m_easyHandle);
1083
1084         // Reset all the stuff like we would in Disconnect()
1085         m_buffer.Clear();
1086         if (m_overflowBuffer)
1087           free(m_overflowBuffer);
1088         m_overflowBuffer = NULL;
1089         m_overflowSize = 0;
1090
1091         // If we got here something is wrong
1092         if (++retry > MAX_RETRIES)
1093         {
1094           CLog::Log(LOGDEBUG, "%s: Reconnect failed!", __FUNCTION__);
1095           // Reset the rest of the variables like we would in Disconnect()
1096           m_filePos = 0;
1097           m_fileSize = 0;
1098           m_bufferSize = 0;
1099
1100           return false;
1101         }
1102
1103         CLog::Log(LOGDEBUG, "%s: Reconnect, (re)try %i", __FUNCTION__, retry);
1104         
1105         // On timeout, when we have to retry more than 2 times in a row
1106         // we increase the Curl low speed timeout
1107         if (retry>1 && CURLresult == CURLE_OPERATION_TIMEDOUT)
1108         {
1109           int newlowspeedtime;
1110
1111           if (g_advancedSettings.m_curllowspeedtime<5)
1112             newlowspeedtime = 0;
1113           else
1114             newlowspeedtime = g_advancedSettings.m_curllowspeedtime-5;
1115
1116           newlowspeedtime += (5*retry);
1117           
1118           CLog::Log(LOGDEBUG, "%s: Setting low-speed-time to %i seconds", __FUNCTION__, newlowspeedtime);
1119           g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_LOW_SPEED_TIME, newlowspeedtime);
1120         }
1121
1122         // Connect + seek to current position (again)
1123         g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_RESUME_FROM_LARGE, m_filePos);
1124         g_curlInterface.multi_add_handle(m_multiHandle, m_easyHandle);
1125
1126         // Return to the beginning of the loop:
1127         continue;
1128       }
1129       return false;
1130     }
1131     switch (result)
1132     {
1133       case CURLM_OK:
1134       {
1135         // hack for broken curl, that thinks there is data all the time
1136         // happens especially on ftp during initial connection
1137 #ifndef _LINUX
1138         SwitchToThread();
1139 #elif __APPLE__
1140         sched_yield();
1141 #else
1142         pthread_yield();
1143 #endif
1144
1145         FD_ZERO(&fdread);
1146         FD_ZERO(&fdwrite);
1147         FD_ZERO(&fdexcep);
1148
1149         // get file descriptors from the transfers
1150         if (CURLM_OK != g_curlInterface.multi_fdset(m_multiHandle, &fdread, &fdwrite, &fdexcep, &maxfd) || maxfd == -1)
1151           return false;
1152
1153         long timeout = 0;
1154         if (CURLM_OK != g_curlInterface.multi_timeout(m_multiHandle, &timeout) || timeout == -1)
1155           timeout = 200;
1156
1157         if (maxfd >= 0)
1158         {
1159           struct timeval t = { timeout / 1000, (timeout % 1000) * 1000 };
1160
1161           // wait until data is avialable or a timeout occours
1162           if (SOCKET_ERROR == dllselect(maxfd + 1, &fdread, &fdwrite, &fdexcep, &t))
1163             return false;
1164         }
1165         else
1166           SleepEx(timeout, true);
1167
1168       }
1169       break;
1170       case CURLM_CALL_MULTI_PERFORM:
1171       {
1172         // we don't keep calling here as that can easily overwrite our buffer wich we want to avoid
1173         // docs says we should call it soon after, but aslong as we are reading data somewhere
1174         // this aught to be soon enough. should stay in socket otherwise
1175         continue;
1176       }
1177       break;
1178       default:
1179       {
1180         CLog::Log(LOGERROR, "%s - curl multi perform failed with code %d, aborting", __FUNCTION__, result);
1181         return false;
1182       }
1183       break;
1184     }
1185   }
1186   return true;
1187 }
1188
1189 void CFileCurl::ClearRequestHeaders()
1190 {
1191   m_requestheaders.clear();
1192 }
1193
1194 void CFileCurl::SetRequestHeader(CStdString header, CStdString value)
1195 {
1196   m_requestheaders[header] = value;
1197 }
1198
1199 void CFileCurl::SetRequestHeader(CStdString header, long value)
1200 {
1201   CStdString buffer;
1202   buffer.Format("%ld", value);
1203   m_requestheaders[header] = buffer;
1204 }
1205
1206 /* STATIC FUNCTIONS */
1207 bool CFileCurl::GetHttpHeader(const CURL &url, CHttpHeader &headers)
1208 {
1209   try
1210   {
1211     CFileCurl file;
1212     if(file.Stat(url, NULL) == 0)
1213     {
1214       headers = file.GetHttpHeader();
1215       return true;
1216     }
1217     return false;
1218   }
1219   catch(...)
1220   {
1221     CStdString path;
1222     url.GetURL(path);
1223     CLog::Log(LOGERROR, "%s - Exception thrown while trying to retrieve header url: %s", __FUNCTION__, path.c_str());
1224     return false;
1225   }
1226 }
1227
1228 bool CFileCurl::GetContent(const CURL &url, CStdString &content, CStdString useragent)
1229 {
1230    CFileCurl file;
1231    if (!useragent.IsEmpty())
1232      file.SetUserAgent(useragent);
1233
1234    if( file.Stat(url, NULL) == 0 )
1235    {
1236      content = file.GetContent();
1237      return true;
1238    }
1239
1240    content = "";
1241    return false;
1242 }