fixed: curl could assert if something threw an exception inside
[xbmc:xbmc-antiquated.git] / xbmc / FileSystem / FileCurl.cpp
1 #include "stdafx.h"
2 #include "FileCurl.h"
3 #include "../Util.h"
4 #include <sys/Stat.h>
5
6 #include "DllLibCurl.h"
7
8 using namespace XFILE;
9 using namespace XCURL;
10
11 extern "C" int __stdcall dllselect(int ntfs, fd_set *readfds, fd_set *writefds, fd_set *errorfds, const timeval *timeout);
12
13 // curl calls this routine to debug
14 extern "C" int debug_callback(CURL_HANDLE *handle, curl_infotype info, char *output, size_t size, void *data)
15 {
16   if (info == CURLINFO_DATA_IN || info == CURLINFO_DATA_OUT)
17     return 0;
18   char *pOut = new char[size + 1];
19   strncpy(pOut, output, size);
20   pOut[size] = '\0';
21   CStdString strOut = pOut;
22   delete[] pOut;
23   strOut.TrimRight("\r\n");
24   CLog::Log(LOGDEBUG, "Curl:: Debug %s", strOut.c_str());
25   return 0;
26 }
27
28 /* curl calls this routine to get more data */
29 extern "C" size_t write_callback(char *buffer,
30                size_t size,
31                size_t nitems,
32                void *userp)
33 {
34   if(userp == NULL) return 0;
35  
36   CFileCurl *file = (CFileCurl *)userp;
37   return file->WriteCallback(buffer, size, nitems);
38 }
39
40 extern "C" size_t header_callback(void *ptr, size_t size, size_t nmemb, void *stream)
41 {
42   CFileCurl *file = (CFileCurl *)stream;
43   return file->HeaderCallback(ptr, size, nmemb);
44 }
45
46 /* fix for silly behavior of realloc */
47 static inline void* realloc_simple(void *ptr, size_t size)
48 {
49   void *ptr2 = realloc(ptr, size);
50   if(ptr && !ptr2 && size > 0)
51   {
52     free(ptr);
53     return NULL;
54   }
55   else
56     return ptr2;
57 }
58
59
60 /* small dummy class to be able to get headers simply */
61 class CDummyHeaders : public IHttpHeaderCallback
62 {
63 public:
64   CDummyHeaders(CHttpHeader *headers)
65   {
66     m_headers = headers;
67   }
68
69   virtual void ParseHeaderData(CStdString strData)
70   {
71     m_headers->Parse(strData);
72   }
73   CHttpHeader *m_headers;
74 };
75
76 size_t CFileCurl::HeaderCallback(void *ptr, size_t size, size_t nmemb)
77 {
78   // libcurl doc says that this info is not always \0 terminated
79   char* strData = (char*)ptr;
80   int iSize = size * nmemb;
81   
82   if (strData[iSize] != 0)
83   {
84     strData = (char*)malloc(iSize + 1);
85     strncpy(strData, (char*)ptr, iSize);
86     strData[iSize] = 0;
87   }
88   else strData = strdup((char*)ptr);
89   
90   if (m_pHeaderCallback) m_pHeaderCallback->ParseHeaderData(strData);
91   
92   m_httpheader.Parse(strData);
93
94   free(strData);
95   
96   return iSize;
97 }
98
99 size_t CFileCurl::WriteCallback(char *buffer, size_t size, size_t nitems)
100 {
101   unsigned int amount = size * nitems;
102 //  CLog::Log(LOGDEBUG, "CFileCurl::WriteCallback (%p) with %i bytes, readsize = %i, writesize = %i", this, amount, m_buffer.GetMaxReadSize(), m_buffer.GetMaxWriteSize() - m_overflowSize);
103   if (m_overflowSize)
104   {
105     // we have our overflow buffer - first get rid of as much as we can
106     unsigned int maxWriteable = min(m_buffer.GetMaxWriteSize(), m_overflowSize);
107     if (maxWriteable)
108     {
109       if (!m_buffer.WriteBinary(m_overflowBuffer, maxWriteable))
110         CLog::Log(LOGERROR, "Unable to write to buffer - what's up?");
111       if (m_overflowSize > maxWriteable)
112       { // still have some more - copy it down
113         memmove(m_overflowBuffer, m_overflowBuffer + maxWriteable, m_overflowSize - maxWriteable);
114       }
115       m_overflowSize -= maxWriteable;
116     }
117   }
118   // ok, now copy the data into our ring buffer
119   unsigned int maxWriteable = min(m_buffer.GetMaxWriteSize(), amount);
120   if (maxWriteable)
121   {
122     if (!m_buffer.WriteBinary(buffer, maxWriteable))
123       CLog::Log(LOGERROR, "Unable to write to buffer - what's up?");
124     amount -= maxWriteable;
125     buffer += maxWriteable;
126   }
127   if (amount)
128   {
129     CLog::Log(LOGDEBUG, "CFileCurl::WriteCallback(%p) not enough free space for %i bytes", this,  amount); 
130     
131     m_overflowBuffer = (char*)realloc_simple(m_overflowBuffer, amount + m_overflowSize);
132     if(m_overflowBuffer == NULL)
133     {
134       CLog::Log(LOGDEBUG, __FUNCTION__" - Failed to grow overflow buffer");
135       return 0;
136     }
137     memcpy(m_overflowBuffer + m_overflowSize, buffer, amount);
138     m_overflowSize += amount;
139   }
140   return size * nitems;
141 }
142
143 CFileCurl::~CFileCurl()
144
145   Close();
146   g_curlInterface.Unload();
147 }
148
149 #define BUFFER_SIZE 32768
150
151 CFileCurl::CFileCurl()
152 {
153   g_curlInterface.Load(); // loads the curl dll and resolves exports etc.
154         m_filePos = 0;
155         m_fileSize = 0;
156   m_easyHandle = NULL;
157   m_multiHandle = NULL;
158   m_curlAliasList = NULL;
159   m_curlHeaderList = NULL;
160   m_opened = false;
161   m_useOldHttpVersion = false;
162   m_overflowBuffer = NULL;
163   m_overflowSize = 0;
164   m_pHeaderCallback = NULL;
165   m_bufferSize = BUFFER_SIZE;
166   m_timeout = 0;
167 }
168
169 //Has to be called before Open()
170 void CFileCurl::SetBufferSize(unsigned int size)
171 {
172   m_bufferSize = size;
173 }
174
175 void CFileCurl::Close()
176 {
177   CLog::Log(LOGDEBUG, "FileCurl::Close(%p) %s", this, m_url.c_str());
178         m_filePos = 0;
179         m_fileSize = 0;
180   m_opened = false;
181
182   if( m_easyHandle )
183   {
184     if(m_multiHandle)
185       g_curlInterface.multi_remove_handle(m_multiHandle, m_easyHandle);
186
187     g_curlInterface.easy_release(m_easyHandle, m_multiHandle);
188     m_multiHandle = NULL;
189     m_easyHandle = NULL;
190   }
191
192   m_url.Empty();
193   
194   /* cleanup */
195   if( m_curlAliasList )
196     g_curlInterface.slist_free_all(m_curlAliasList);
197   if( m_curlHeaderList )
198     g_curlInterface.slist_free_all(m_curlHeaderList);
199   
200   m_multiHandle = NULL;
201   m_curlAliasList = NULL;
202   m_curlHeaderList = NULL;
203   
204   m_buffer.Destroy();
205   if (m_overflowBuffer)
206     free(m_overflowBuffer);
207   m_overflowBuffer = NULL;
208   m_overflowSize = 0;
209 }
210
211 void CFileCurl::SetCommonOptions()
212 {
213   g_curlInterface.easy_reset(m_easyHandle);
214
215   g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_DEBUGFUNCTION, debug_callback);
216
217   if( g_advancedSettings.m_logLevel >= LOG_LEVEL_DEBUG )
218     g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_VERBOSE, TRUE);
219   else
220     g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_VERBOSE, FALSE);
221   
222   g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_WRITEDATA, this);
223   g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_WRITEFUNCTION, write_callback);
224   
225   // make sure headers are seperated from the data stream
226   g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_WRITEHEADER, this);
227   g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_HEADERFUNCTION, header_callback);
228   g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_HEADER, FALSE);
229   
230   g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_FTP_USE_EPSV, 0); // turn off epsv
231   
232   // Allow us to follow two redirects
233   g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_FOLLOWLOCATION, TRUE);
234   g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_MAXREDIRS, 2);
235
236   // When using multiple threads you should set the CURLOPT_NOSIGNAL option to
237   // TRUE for all handles. Everything will work fine except that timeouts are not
238   // honored during the DNS lookup - which you can work around by building libcurl
239   // with c-ares support. c-ares is a library that provides asynchronous name
240   // resolves. Unfortunately, c-ares does not yet support IPv6.
241   g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_NOSIGNAL, TRUE);
242
243   // not interested in failed requests
244   g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_FAILONERROR, 1);
245
246   // enable support for icecast / shoutcast streams
247   m_curlAliasList = g_curlInterface.slist_append(m_curlAliasList, "ICY 200 OK"); 
248   g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_HTTP200ALIASES, m_curlAliasList); 
249
250   // never verify peer, we don't have any certificates to do this
251   g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_SSL_VERIFYPEER, 0);
252
253   // always allow gzip compression
254   if( m_contentencoding.length() > 0 )
255     g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_ENCODING, m_contentencoding.c_str());
256   
257   if (m_userAgent.length() > 0)
258     g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_USERAGENT, m_userAgent.c_str());
259   else /* set some default agent as shoutcast doesn't return proper stuff otherwise */
260     g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_USERAGENT, "XBMC/pre-2.1 (compatible; MSIE 6.0; Windows NT 5.1; WinampMPEG/5.09)");
261   
262   if (m_useOldHttpVersion)
263     g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
264
265   if (m_proxy.length() > 0)
266     g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_PROXY, m_proxy.c_str());
267
268   if (m_customrequest.length() > 0)
269     g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_CUSTOMREQUEST, m_customrequest.c_str());
270
271   if(m_timeout == 0)
272     m_timeout = g_advancedSettings.m_curlclienttimeout;
273
274   // set our timeouts, we abort connection after m_timeout, and reads after no data for m_timeout seconds
275   g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_CONNECTTIMEOUT, m_timeout);
276   g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_LOW_SPEED_LIMIT, 1);
277   g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_LOW_SPEED_TIME, m_timeout);
278   
279   
280   SetRequestHeaders();
281 }
282
283 void CFileCurl::SetRequestHeaders()
284 {
285   if(m_curlHeaderList) 
286   {
287     g_curlInterface.slist_free_all(m_curlHeaderList);
288     m_curlHeaderList = NULL;
289   }
290
291   MAPHTTPHEADERS::iterator it;
292   for(it = m_requestheaders.begin(); it != m_requestheaders.end(); it++)
293   {
294     CStdString buffer = it->first + ": " + it->second;
295     m_curlHeaderList = g_curlInterface.slist_append(m_curlHeaderList, buffer.c_str()); 
296   }
297
298   // add user defined headers
299   if (m_curlHeaderList && m_easyHandle)
300     g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_HTTPHEADER, m_curlHeaderList); 
301
302 }
303
304 bool CFileCurl::Open(const CURL& url, bool bBinary)
305 {
306   m_buffer.Create(m_bufferSize * 3, m_bufferSize);  // 3 times our buffer size (2 in front, 1 behind)
307   m_overflowBuffer = 0;
308   m_overflowSize = 0;
309
310   CURL url2(url);
311   if( url2.GetProtocol().Equals("ftpx") )
312     url2.SetProtocol("ftp");
313   else if (url2.GetProtocol().Equals("shout") || url2.GetProtocol().Equals("daap") || url2.GetProtocol().Equals("upnp") || url2.GetProtocol().Equals("tuxbox") || url2.GetProtocol().Equals("lastfm"))
314     url2.SetProtocol("http");    
315
316   if( url2.GetProtocol().Equals("ftp") )
317   {
318     /* this is uggly, depending on from where   */
319     /* we get the link it may or may not be     */
320     /* url encoded. if handed from ftpdirectory */
321     /* it won't be so let's handle that case    */
322     
323     CStdString partial, filename(url2.GetFileName());
324     CStdStringArray array;
325
326     /* our current client doesn't support utf8 */
327     g_charsetConverter.utf8ToStringCharset(filename);
328
329     /* TODO: create a tokenizer that doesn't skip empty's */
330     CUtil::Tokenize(filename, array, "/");
331     filename.Empty();
332     for(CStdStringArray::iterator it = array.begin(); it != array.end(); it++)
333     {
334       if(it != array.begin())
335         filename += "/";
336
337       partial = *it;      
338       CUtil::URLEncode(partial);      
339       filename += partial;
340     }
341
342     /* make sure we keep slashes */    
343     if(url2.GetFileName().Right(1) == "/")
344       filename += "/";
345
346     url2.SetFileName(filename);
347   }
348   else if( url2.GetProtocol().Equals("http"))
349   {
350     if (g_guiSettings.GetBool("network.usehttpproxy") && m_proxy.IsEmpty())
351     {
352       m_proxy = "http://" + g_guiSettings.GetString("network.httpproxyserver");
353       m_proxy += ":" + g_guiSettings.GetString("network.httpproxyport");
354       CLog::Log(LOGDEBUG, "Using proxy %s", m_proxy.c_str());
355     }
356   }
357
358   url2.GetURL(m_url);
359
360   CLog::Log(LOGDEBUG, "FileCurl::Open(%p) %s", this, m_url.c_str());  
361
362   ASSERT(!(!m_easyHandle ^ !m_multiHandle));
363   if( m_easyHandle == NULL )
364     g_curlInterface.easy_aquire(url2.GetProtocol(), url2.GetHostName(), &m_easyHandle, &m_multiHandle );
365
366
367   // setup common curl options
368   SetCommonOptions();
369
370   g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_URL, m_url.c_str());
371   if (!bBinary) g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_TRANSFERTEXT, TRUE);  
372
373   g_curlInterface.multi_add_handle(m_multiHandle, m_easyHandle);
374
375   m_stillRunning = 1;
376   m_opened = true;
377
378   // read some data in to try and obtain the length
379   // maybe there's a better way to get this info??
380   if (!FillBuffer(1))
381   {
382     CLog::Log(LOGERROR, "CFileCurl:Open, didn't get any data from stream.");    
383     Close();
384     return false;
385   }
386   
387   long response;
388   if (CURLE_OK == g_curlInterface.easy_getinfo(m_easyHandle, CURLINFO_RESPONSE_CODE, &response))
389   {
390     if( url2.GetProtocol().Equals("http") || url2.GetProtocol().Equals("https") )
391     {
392       if( response >= 400 )
393       {
394         CLog::Log(LOGERROR, "CFileCurl:Open, Error code from server %l", response);
395         Close();
396         return false;
397       }
398     }
399   }
400
401   double length;
402   if (CURLE_OK == g_curlInterface.easy_getinfo(m_easyHandle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &length))
403     m_fileSize = (__int64)length;
404
405   /* workaround for shoutcast server wich doesn't set content type on standard mp3 */
406   if( m_httpheader.GetContentType().IsEmpty() )
407   {
408     if( !m_httpheader.GetValue("icy-notice1").IsEmpty()
409      || !m_httpheader.GetValue("icy-name").IsEmpty()
410      || !m_httpheader.GetValue("icy-br").IsEmpty() )
411      m_httpheader.Parse("Content-Type: audio/mpeg\r\n");
412   }
413
414   m_seekable = false;
415   if(m_fileSize > 0)
416   {
417     if(url2.GetProtocol().Equals("http") || url2.GetProtocol().Equals("https"))
418     {
419       // only assume server is seekable if it provides Accept-Ranges
420       if(!m_httpheader.GetValue("Accept-Ranges").IsEmpty())
421         m_seekable = true;
422     }
423     else
424       m_seekable = true;
425   }
426
427   return true;
428 }
429
430 bool CFileCurl::ReadString(char *szLine, int iLineLength)
431 {
432   unsigned int want = (unsigned int)iLineLength;
433
434   if(!FillBuffer(want))
435     return false;
436
437   // ensure only available data is considered 
438   want = min(m_buffer.GetMaxReadSize(), want);
439
440   /* check if we finished prematurely */
441   if (!m_stillRunning && m_fileSize && m_filePos != m_fileSize && !want)
442   {
443     CLog::Log(LOGWARNING, __FUNCTION__" - Transfer ended before entire file was retreived pos %d, size %d", m_filePos, m_fileSize);
444     return false;
445   }
446
447   char* pLine = szLine;
448   do
449   {
450     if (!m_buffer.ReadBinary(pLine, 1))
451     {
452       break;
453     }
454     pLine++;
455   } while (((pLine - 1)[0] != '\n') && ((unsigned int)(pLine - szLine) < want));
456   pLine[0] = 0;
457   m_filePos += (pLine - szLine);
458   return (bool)((pLine - szLine) > 0);
459 }
460
461 bool CFileCurl::Exists(const CURL& url)
462 {
463   return Stat(url, NULL) == 0;
464 }
465
466 __int64 CFileCurl::Seek(__int64 iFilePosition, int iWhence)
467 {
468   /* if we don't have a filesize this is most likely unseekable */
469   if( !m_seekable ) return -1;
470
471   __int64 nextPos = m_filePos;
472         switch(iWhence) 
473         {
474                 case SEEK_SET:
475                         nextPos = iFilePosition;
476                         break;
477                 case SEEK_CUR:
478                         nextPos += iFilePosition;
479                         break;
480                 case SEEK_END:
481                         if (m_fileSize)
482         nextPos = m_fileSize + iFilePosition;
483       else
484         nextPos = 0;
485                         break;
486         }
487 //  CLog::Log(LOGDEBUG, "FileCurl::Seek(%p) - current pos %i, new pos %i", this, (unsigned int)m_filePos, (unsigned int)nextPos);
488   // see if nextPos is within our buffer
489   if (!m_buffer.SkipBytes((int)(nextPos - m_filePos)))
490   {
491     // bummer - seeking outside our buffers
492
493     /* halt transaction */
494     g_curlInterface.multi_remove_handle(m_multiHandle, m_easyHandle);
495
496     /* caller might have changed some headers (needed for daap)*/
497     SetRequestHeaders();
498
499     /* set offset */
500     CURLcode ret = g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_RESUME_FROM_LARGE, nextPos);
501 //    if (CURLE_OK == ret)
502 //      CLog::Log(LOGDEBUG, "FileCurl::Seek(%p) - resetting file fetch to %i (successful)", this, nextPos);
503 //    else
504 //      CLog::Log(LOGDEBUG, "FileCurl::Seek(%p) - resetting file fetch to %i (failed, code %i)", this, nextPos, ret);
505
506
507     /* restart */
508     g_curlInterface.multi_add_handle(m_multiHandle, m_easyHandle);
509
510     /* reset stillrunning as we now are going to reget data */
511     m_stillRunning = 1;
512
513     /* ditch buffer - write will recreate - resets stream pos*/
514     m_buffer.Clear();
515     if (m_overflowBuffer)
516       free(m_overflowBuffer);
517     m_overflowBuffer=NULL;
518     m_overflowSize = 0;
519   }
520   m_filePos = nextPos;
521   return m_filePos;
522 }
523
524 __int64 CFileCurl::GetLength()
525 {
526         if (!m_opened) return 0;
527         return m_fileSize;
528 }
529
530 __int64 CFileCurl::GetPosition()
531 {
532         if (!m_opened) return 0;
533         return m_filePos;
534 }
535
536 int CFileCurl::Stat(const CURL& url, struct __stat64* buffer)
537
538   // if file is already running, get infor from it
539   if( m_opened )
540   {
541     CLog::Log(LOGWARNING, __FUNCTION__" - Stat called on open file");
542     buffer->st_size = GetLength();
543                 buffer->st_mode = _S_IFREG;
544     return 0;
545   }
546
547   CURL url2(url);
548   if( url2.GetProtocol().Equals("ftpx") )
549     url2.SetProtocol("ftp");
550   else if (url2.GetProtocol().Equals("shout") || url2.GetProtocol().Equals("daap") || url2.GetProtocol().Equals("upnp") || url2.GetProtocol().Equals("lastfm"))
551     url2.SetProtocol("http");
552   
553   /* ditch options as it's not supported on ftp */
554   if( url2.GetProtocol().Equals("ftp") )
555     url2.SetOptions("");
556
557   url2.GetURL(m_url);
558
559   ASSERT(m_easyHandle == NULL);
560   g_curlInterface.easy_aquire(url2.GetProtocol(), url2.GetHostName(), &m_easyHandle, NULL);
561
562   SetCommonOptions(); 
563   g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_TIMEOUT, 5);
564   g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_URL, m_url.c_str());
565   g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_NOBODY, 1);
566   g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_WRITEDATA, NULL); /* will cause write failure*/
567
568   CURLcode result = g_curlInterface.easy_perform(m_easyHandle);
569
570   
571   if (result == CURLE_GOT_NOTHING)
572   {
573     /* some http servers and shoutcast servers don't give us any data on a head request */
574     /* request normal and just fail out, it's their loss */
575     /* somehow curl doesn't reset CURLOPT_NOBODY properly so reset everything */    
576     SetCommonOptions(); 
577     g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_TIMEOUT, 5);
578     g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_URL, m_url.c_str());
579     g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_RANGE, "0-0");
580     g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_WRITEDATA, NULL); /* will cause write failure*/    
581     result = g_curlInterface.easy_perform(m_easyHandle);
582   }
583
584   if(result == CURLE_HTTP_RANGE_ERROR )
585   {
586     /* crap can't use the range option, disable it and try again */
587     g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_RANGE, NULL);
588     result = g_curlInterface.easy_perform(m_easyHandle);
589   }
590
591   if( result != CURLE_WRITE_ERROR && result != CURLE_OK )
592   {
593     g_curlInterface.easy_release(m_easyHandle, NULL);
594     m_easyHandle = NULL;
595     errno = ENOENT;
596     return -1;
597   }
598
599   /* workaround for shoutcast server wich doesn't set content type on standard mp3 */
600   if( m_httpheader.GetContentType().IsEmpty() )
601   {
602     if( !m_httpheader.GetValue("icy-notice1").IsEmpty()
603     || !m_httpheader.GetValue("icy-name").IsEmpty()
604     || !m_httpheader.GetValue("icy-br").IsEmpty() )
605     m_httpheader.Parse("Content-Type: audio/mpeg\r\n");
606   }
607
608   if(buffer)
609   {
610
611     double length;
612     char content[255];
613     if (CURLE_OK == g_curlInterface.easy_getinfo(m_easyHandle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &length)
614      && CURLE_OK == g_curlInterface.easy_getinfo(m_easyHandle, CURLINFO_CONTENT_TYPE, content))
615     {
616                   buffer->st_size = (__int64)length;
617       if(strstr(content, "text/html")) //consider html files directories
618         buffer->st_mode = _S_IFDIR;
619       else
620         buffer->st_mode = _S_IFREG;      
621     }
622   }
623   g_curlInterface.easy_release(m_easyHandle, NULL);
624   m_easyHandle = NULL;
625         return 0;
626 }
627
628 unsigned int CFileCurl::Read(void* lpBuf, __int64 uiBufSize)
629 {
630   /* only request 1 byte, for truncated reads */
631   if(!FillBuffer(1))
632     return -1;
633
634   /* ensure only available data is considered */
635   unsigned int want = (unsigned int)min(m_buffer.GetMaxReadSize(), uiBufSize);
636
637   /* xfer data to caller */
638   if (m_buffer.ReadBinary((char *)lpBuf, want))
639   {
640     m_filePos += want;
641     return want;
642   }  
643
644   /* check if we finished prematurely */
645   if (!m_stillRunning && m_fileSize && m_filePos != m_fileSize)
646   {
647     CLog::Log(LOGWARNING, __FUNCTION__" - Transfer ended before entire file was retreived pos %d, size %d", m_filePos, m_fileSize);
648     return -1;
649   }
650
651   return 0;
652 }
653
654 /* use to attempt to fill the read buffer up to requested number of bytes */
655 bool CFileCurl::FillBuffer(unsigned int want)
656 {  
657   int maxfd;  
658   fd_set fdread;
659   fd_set fdwrite;
660   fd_set fdexcep;
661
662   // only attempt to fill buffer if transactions still running and buffer
663   // doesnt exceed required size already
664   while (m_buffer.GetMaxReadSize() < want && m_buffer.GetMaxWriteSize() > 0 )
665   {
666     /* if there is data in overflow buffer, try to use that first */
667     if(m_overflowSize)
668     {
669       unsigned amount = min(m_buffer.GetMaxWriteSize(), m_overflowSize);
670       m_buffer.WriteBinary(m_overflowBuffer, amount);
671
672       if(amount < m_overflowSize)
673         memcpy(m_overflowBuffer, m_overflowBuffer+amount,m_overflowSize-amount);
674
675       m_overflowSize -= amount;
676       m_overflowBuffer = (char*)realloc_simple(m_overflowBuffer, m_overflowSize);
677       continue;
678     }
679
680     CURLMcode result = g_curlInterface.multi_perform(m_multiHandle, &m_stillRunning);
681     if( !m_stillRunning ) 
682       return (CURLM_OK == result);
683
684     switch(result)
685     {
686       case CURLM_OK:
687       {
688         // hack for broken curl, that thinks there is data all the time
689         // happens especially on ftp during initial connection
690         SwitchToThread();
691
692         FD_ZERO(&fdread);
693         FD_ZERO(&fdwrite);
694         FD_ZERO(&fdexcep);
695
696         // get file descriptors from the transfers
697         if( CURLM_OK != g_curlInterface.multi_fdset(m_multiHandle, &fdread, &fdwrite, &fdexcep, &maxfd) )
698           return false;
699
700         long timeout = 0;
701         if(CURLM_OK != g_curlInterface.multi_timeout(m_multiHandle, &timeout) || timeout == -1)
702           timeout = 200;
703         
704         if( maxfd < 0 ) // hack for broken curl
705           maxfd = fdread.fd_count + fdwrite.fd_count + fdexcep.fd_count - 1;
706
707         if( maxfd >= 0  )
708         {
709           struct timeval t = { timeout / 1000, (timeout % 1000) * 1000 };          
710           
711           // wait until data is avialable or a timeout occours
712           if( SOCKET_ERROR == dllselect(maxfd + 1, &fdread, &fdwrite, &fdexcep, &t) )
713             return false;
714         }
715         else
716           SleepEx(timeout, true);
717
718       }
719       break;
720       case CURLM_CALL_MULTI_PERFORM:
721       {
722         // we don't keep calling here as that can easily overwrite our buffer wich we want to avoid
723         // docs says we should call it soon after, but aslong as we are reading data somewhere
724         // this aught to be soon enough. should stay in socket otherwise
725         continue;
726       }
727       break;
728       default:
729       {
730         CLog::Log(LOGERROR, __FUNCTION__" - curl multi perform failed with code %d, aborting", result);
731         return false;
732       }
733       break;
734     }
735   }
736   return true;
737 }
738
739 void CFileCurl::ClearRequestHeaders()
740 {
741   m_requestheaders.clear();
742 }
743
744 void CFileCurl::SetRequestHeader(CStdString header, CStdString value)
745 {
746   m_requestheaders[header] = value;
747 }
748
749 void CFileCurl::SetRequestHeader(CStdString header, long value)
750 {
751   CStdString buffer;
752   buffer.Format("%ld", value);
753   m_requestheaders[header] = buffer;
754 }
755
756 /* STATIC FUNCTIONS */
757 bool CFileCurl::GetHttpHeader(const CURL &url, CHttpHeader &headers)
758 {
759   try
760   {
761     CFileCurl file;
762     CDummyHeaders callback(&headers);
763
764     file.SetHttpHeaderCallback(&callback);
765     
766     /* calling stat should give us all needed data */
767     return file.Stat(url, NULL) == 0;
768   }
769   catch(...)
770   {
771     CStdString path;
772     url.GetURL(path);
773     CLog::Log(LOGERROR, __FUNCTION__" - Exception thrown while trying to retrieve header url: %s", path.c_str());
774     return false;
775   }
776 }
777
778 bool CFileCurl::GetContent(const CURL &url, CStdString &content)
779 {
780    CFileCurl file;
781    if( file.Stat(url, NULL) == 0 )
782    {
783      content = file.GetContent();
784      return true;
785    }
786    
787    content = "";
788    return false;
789 }