2 * Copyright (C) 2005-2013 Team XBMC
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)
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.
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/>.
22 #include "utils/URIUtils.h"
25 #include "settings/AdvancedSettings.h"
26 #include "settings/Settings.h"
28 #include "threads/SystemClock.h"
36 #include "../linux/XFileUtils.h"
37 #include "../linux/XTimeUtils.h"
38 #include "../linux/ConvUtils.h"
41 #include "DllLibCurl.h"
42 #include "ShoutcastFile.h"
43 #include "SpecialProtocol.h"
44 #include "utils/CharsetConverter.h"
45 #include "utils/log.h"
46 #include "utils/StringUtils.h"
48 using namespace XFILE;
49 using namespace XCURL;
51 #define XMIN(a,b) ((a)<(b)?(a):(b))
52 #define FITS_INT(a) (((a) <= INT_MAX) && ((a) >= INT_MIN))
54 curl_proxytype proxyType2CUrlProxyType[] = {
59 CURLPROXY_SOCKS5_HOSTNAME,
62 // curl calls this routine to debug
63 extern "C" int debug_callback(CURL_HANDLE *handle, curl_infotype info, char *output, size_t size, void *data)
65 if (info == CURLINFO_DATA_IN || info == CURLINFO_DATA_OUT)
68 if (!g_advancedSettings.CanLogComponent(LOGCURL))
72 strLine.append(output, size);
73 std::vector<std::string> vecLines;
74 StringUtils::Tokenize(strLine, vecLines, "\r\n");
75 std::vector<std::string>::const_iterator it = vecLines.begin();
80 case CURLINFO_TEXT : infotype = (char *) "TEXT: "; break;
81 case CURLINFO_HEADER_IN : infotype = (char *) "HEADER_IN: "; break;
82 case CURLINFO_HEADER_OUT : infotype = (char *) "HEADER_OUT: "; break;
83 case CURLINFO_SSL_DATA_IN : infotype = (char *) "SSL_DATA_IN: "; break;
84 case CURLINFO_SSL_DATA_OUT : infotype = (char *) "SSL_DATA_OUT: "; break;
85 case CURLINFO_END : infotype = (char *) "END: "; break;
86 default : infotype = (char *) ""; break;
89 while (it != vecLines.end())
91 CLog::Log(LOGDEBUG, "Curl::Debug - %s%s", infotype, (*it).c_str());
97 /* curl calls this routine to get more data */
98 extern "C" size_t write_callback(char *buffer,
103 if(userp == NULL) return 0;
105 CCurlFile::CReadState *state = (CCurlFile::CReadState *)userp;
106 return state->WriteCallback(buffer, size, nitems);
109 extern "C" size_t read_callback(char *buffer,
114 if(userp == NULL) return 0;
116 CCurlFile::CReadState *state = (CCurlFile::CReadState *)userp;
117 return state->ReadCallback(buffer, size, nitems);
120 extern "C" size_t header_callback(void *ptr, size_t size, size_t nmemb, void *stream)
122 CCurlFile::CReadState *state = (CCurlFile::CReadState *)stream;
123 return state->HeaderCallback(ptr, size, nmemb);
126 /* fix for silly behavior of realloc */
127 static inline void* realloc_simple(void *ptr, size_t size)
129 void *ptr2 = realloc(ptr, size);
130 if(ptr && !ptr2 && size > 0)
139 size_t CCurlFile::CReadState::HeaderCallback(void *ptr, size_t size, size_t nmemb)
141 std::string inString;
142 // libcurl doc says that this info is not always \0 terminated
143 const char* strBuf = (const char*)ptr;
144 const size_t iSize = size * nmemb;
145 if (strBuf[iSize - 1] == 0)
146 inString.assign(strBuf, iSize - 1); // skip last char if it's zero
148 inString.append(strBuf, iSize);
150 m_httpheader.Parse(inString);
155 size_t CCurlFile::CReadState::ReadCallback(char *buffer, size_t size, size_t nitems)
160 if (m_filePos >= m_fileSize)
163 return CURL_READFUNC_PAUSE;
166 int64_t retSize = XMIN(m_fileSize - m_filePos, int64_t(nitems * size));
167 memcpy(buffer, m_readBuffer + m_filePos, retSize);
168 m_filePos += retSize;
173 size_t CCurlFile::CReadState::WriteCallback(char *buffer, size_t size, size_t nitems)
175 unsigned int amount = size * nitems;
176 // CLog::Log(LOGDEBUG, "CCurlFile::WriteCallback (%p) with %i bytes, readsize = %i, writesize = %i", this, amount, m_buffer.getMaxReadSize(), m_buffer.getMaxWriteSize() - m_overflowSize);
179 // we have our overflow buffer - first get rid of as much as we can
180 unsigned int maxWriteable = XMIN((unsigned int)m_buffer.getMaxWriteSize(), m_overflowSize);
183 if (!m_buffer.WriteData(m_overflowBuffer, maxWriteable))
184 CLog::Log(LOGERROR, "CCurlFile::WriteCallback - Unable to write to buffer - what's up?");
185 if (m_overflowSize > maxWriteable)
186 { // still have some more - copy it down
187 memmove(m_overflowBuffer, m_overflowBuffer + maxWriteable, m_overflowSize - maxWriteable);
189 m_overflowSize -= maxWriteable;
192 // ok, now copy the data into our ring buffer
193 unsigned int maxWriteable = XMIN((unsigned int)m_buffer.getMaxWriteSize(), amount);
196 if (!m_buffer.WriteData(buffer, maxWriteable))
198 CLog::Log(LOGERROR, "CCurlFile::WriteCallback - Unable to write to buffer with %i bytes - what's up?", maxWriteable);
202 amount -= maxWriteable;
203 buffer += maxWriteable;
208 // CLog::Log(LOGDEBUG, "CCurlFile::WriteCallback(%p) not enough free space for %i bytes", (void*)this, amount);
210 m_overflowBuffer = (char*)realloc_simple(m_overflowBuffer, amount + m_overflowSize);
211 if(m_overflowBuffer == NULL)
213 CLog::Log(LOGWARNING, "CCurlFile::WriteCallback - Failed to grow overflow buffer from %i bytes to %i bytes", m_overflowSize, amount + m_overflowSize);
216 memcpy(m_overflowBuffer + m_overflowSize, buffer, amount);
217 m_overflowSize += amount;
219 return size * nitems;
222 CCurlFile::CReadState::CReadState()
225 m_multiHandle = NULL;
226 m_overflowBuffer = NULL;
236 m_curlHeaderList = NULL;
237 m_curlAliasList = NULL;
240 CCurlFile::CReadState::~CReadState()
245 g_curlInterface.easy_release(&m_easyHandle, &m_multiHandle);
248 bool CCurlFile::CReadState::Seek(int64_t pos)
253 if(FITS_INT(pos - m_filePos) && m_buffer.SkipBytes((int)(pos - m_filePos)))
259 if(pos > m_filePos && pos < m_filePos + m_bufferSize)
261 int len = m_buffer.getMaxReadSize();
263 m_buffer.SkipBytes(len);
264 if(!FillBuffer(m_bufferSize))
266 if(!m_buffer.SkipBytes(-len))
267 CLog::Log(LOGERROR, "%s - Failed to restore position after failed fill", __FUNCTION__);
273 if(!FITS_INT(pos - m_filePos) || !m_buffer.SkipBytes((int)(pos - m_filePos)))
275 CLog::Log(LOGERROR, "%s - Failed to skip to position after having filled buffer", __FUNCTION__);
276 if(!m_buffer.SkipBytes(-len))
277 CLog::Log(LOGERROR, "%s - Failed to restore position after failed seek", __FUNCTION__);
288 void CCurlFile::CReadState::SetResume(void)
291 * Explicitly set RANGE header when filepos=0 as some http servers require us to always send the range
292 * request header. If we don't the server may provide different content causing seeking to fail.
293 * This only affects HTTP-like items, for FTP it's a null operation.
295 if (m_sendRange && m_filePos == 0)
296 g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_RANGE, "0-");
299 g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_RANGE, NULL);
303 g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_RESUME_FROM_LARGE, m_filePos);
306 long CCurlFile::CReadState::Connect(unsigned int size)
309 CLog::Log(LOGDEBUG,"CurlFile::CReadState::Connect - Resume from position %" PRId64, m_filePos);
312 g_curlInterface.multi_add_handle(m_multiHandle, m_easyHandle);
316 m_buffer.Create(size * 3);
317 m_httpheader.Clear();
319 // read some data in to try and obtain the length
320 // maybe there's a better way to get this info??
324 CLog::Log(LOGERROR, "CCurlFile::CReadState::Connect, didn't get any data from stream.");
329 if (CURLE_OK == g_curlInterface.easy_getinfo(m_easyHandle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &length))
333 m_fileSize = m_filePos + (int64_t)length;
337 if (CURLE_OK == g_curlInterface.easy_getinfo(m_easyHandle, CURLINFO_RESPONSE_CODE, &response))
343 void CCurlFile::CReadState::Disconnect()
345 if(m_multiHandle && m_easyHandle)
346 g_curlInterface.multi_remove_handle(m_multiHandle, m_easyHandle);
349 free(m_overflowBuffer);
350 m_overflowBuffer = NULL;
358 if( m_curlHeaderList )
359 g_curlInterface.slist_free_all(m_curlHeaderList);
360 m_curlHeaderList = NULL;
362 if( m_curlAliasList )
363 g_curlInterface.slist_free_all(m_curlAliasList);
364 m_curlAliasList = NULL;
368 CCurlFile::~CCurlFile()
373 g_curlInterface.Unload();
376 CCurlFile::CCurlFile()
378 g_curlInterface.Load(); // loads the curl dll and resolves exports etc.
382 m_multisession = true;
384 m_useOldHttpVersion = false;
385 m_connecttimeout = 0;
390 m_bufferSize = 32768;
392 m_postdataset = false;
397 m_proxytype = PROXY_HTTP;
398 m_state = new CReadState();
402 m_acceptCharset = "UTF-8,*;q=0.8"; /* prefer UTF-8 if available */
405 //Has to be called before Open()
406 void CCurlFile::SetBufferSize(unsigned int size)
411 void CCurlFile::Close()
413 if (m_opened && m_forWrite && !m_inError)
416 m_state->Disconnect();
429 void CCurlFile::SetCommonOptions(CReadState* state)
431 CURL_HANDLE* h = state->m_easyHandle;
433 g_curlInterface.easy_reset(h);
435 g_curlInterface.easy_setopt(h, CURLOPT_DEBUGFUNCTION, debug_callback);
437 if( g_advancedSettings.m_logLevel >= LOG_LEVEL_DEBUG )
438 g_curlInterface.easy_setopt(h, CURLOPT_VERBOSE, TRUE);
440 g_curlInterface.easy_setopt(h, CURLOPT_VERBOSE, FALSE);
442 g_curlInterface.easy_setopt(h, CURLOPT_WRITEDATA, state);
443 g_curlInterface.easy_setopt(h, CURLOPT_WRITEFUNCTION, write_callback);
445 g_curlInterface.easy_setopt(h, CURLOPT_READDATA, state);
446 g_curlInterface.easy_setopt(h, CURLOPT_READFUNCTION, read_callback);
448 // set username and password for current handle
449 if (m_username.length() > 0 && m_password.length() > 0)
451 std::string userpwd = m_username + ':' + m_password;
452 g_curlInterface.easy_setopt(h, CURLOPT_USERPWD, userpwd.c_str());
455 // make sure headers are seperated from the data stream
456 g_curlInterface.easy_setopt(h, CURLOPT_WRITEHEADER, state);
457 g_curlInterface.easy_setopt(h, CURLOPT_HEADERFUNCTION, header_callback);
458 g_curlInterface.easy_setopt(h, CURLOPT_HEADER, FALSE);
460 g_curlInterface.easy_setopt(h, CURLOPT_FTP_USE_EPSV, 0); // turn off epsv
462 // Allow us to follow two redirects
463 g_curlInterface.easy_setopt(h, CURLOPT_FOLLOWLOCATION, TRUE);
464 g_curlInterface.easy_setopt(h, CURLOPT_MAXREDIRS, 5);
466 // Enable cookie engine for current handle to re-use them in future requests
467 std::string strCookieFile;
468 std::string strTempPath = CSpecialProtocol::TranslatePath(g_advancedSettings.m_cachePath);
469 strCookieFile = URIUtils::AddFileToFolder(strTempPath, "cookies.dat");
471 g_curlInterface.easy_setopt(h, CURLOPT_COOKIEFILE, strCookieFile.c_str());
472 g_curlInterface.easy_setopt(h, CURLOPT_COOKIEJAR, strCookieFile.c_str());
474 // Set custom cookie if requested
475 if (!m_cookie.empty())
476 g_curlInterface.easy_setopt(h, CURLOPT_COOKIE, m_cookie.c_str());
478 g_curlInterface.easy_setopt(h, CURLOPT_COOKIELIST, "FLUSH");
480 // When using multiple threads you should set the CURLOPT_NOSIGNAL option to
481 // TRUE for all handles. Everything will work fine except that timeouts are not
482 // honored during the DNS lookup - which you can work around by building libcurl
483 // with c-ares support. c-ares is a library that provides asynchronous name
484 // resolves. Unfortunately, c-ares does not yet support IPv6.
485 g_curlInterface.easy_setopt(h, CURLOPT_NOSIGNAL, TRUE);
487 // not interested in failed requests
488 g_curlInterface.easy_setopt(h, CURLOPT_FAILONERROR, 1);
490 // enable support for icecast / shoutcast streams
491 if ( NULL == state->m_curlAliasList )
492 // m_curlAliasList is used only by this one place, but SetCommonOptions can
493 // be called multiple times, only append to list if it's empty.
494 state->m_curlAliasList = g_curlInterface.slist_append(state->m_curlAliasList, "ICY 200 OK");
495 g_curlInterface.easy_setopt(h, CURLOPT_HTTP200ALIASES, state->m_curlAliasList);
497 // never verify peer, we don't have any certificates to do this
498 g_curlInterface.easy_setopt(h, CURLOPT_SSL_VERIFYPEER, 0);
499 g_curlInterface.easy_setopt(h, CURLOPT_SSL_VERIFYHOST, 0);
501 g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_URL, m_url.c_str());
502 g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_TRANSFERTEXT, FALSE);
504 // setup POST data if it is set (and it may be empty)
507 g_curlInterface.easy_setopt(h, CURLOPT_POST, 1 );
508 g_curlInterface.easy_setopt(h, CURLOPT_POSTFIELDSIZE, m_postdata.length());
509 g_curlInterface.easy_setopt(h, CURLOPT_POSTFIELDS, m_postdata.c_str());
512 // setup Referer header if needed
513 if (!m_referer.empty())
514 g_curlInterface.easy_setopt(h, CURLOPT_REFERER, m_referer.c_str());
517 g_curlInterface.easy_setopt(h, CURLOPT_REFERER, NULL);
518 g_curlInterface.easy_setopt(h, CURLOPT_AUTOREFERER, TRUE);
521 // setup any requested authentication
522 if( !m_ftpauth.empty() )
524 g_curlInterface.easy_setopt(h, CURLOPT_FTP_SSL, CURLFTPSSL_TRY);
525 if( m_ftpauth == "any" )
526 g_curlInterface.easy_setopt(h, CURLOPT_FTPSSLAUTH, CURLFTPAUTH_DEFAULT);
527 else if( m_ftpauth == "ssl" )
528 g_curlInterface.easy_setopt(h, CURLOPT_FTPSSLAUTH, CURLFTPAUTH_SSL);
529 else if( m_ftpauth == "tls" )
530 g_curlInterface.easy_setopt(h, CURLOPT_FTPSSLAUTH, CURLFTPAUTH_TLS);
533 // setup requested http authentication method
534 if(!m_httpauth.empty())
536 if( m_httpauth == "any" )
537 g_curlInterface.easy_setopt(h, CURLOPT_HTTPAUTH, CURLAUTH_ANY);
538 else if( m_httpauth == "anysafe" )
539 g_curlInterface.easy_setopt(h, CURLOPT_HTTPAUTH, CURLAUTH_ANYSAFE);
540 else if( m_httpauth == "digest" )
541 g_curlInterface.easy_setopt(h, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);
542 else if( m_httpauth == "ntlm" )
543 g_curlInterface.easy_setopt(h, CURLOPT_HTTPAUTH, CURLAUTH_NTLM);
546 // allow passive mode for ftp
547 if( m_ftpport.length() > 0 )
548 g_curlInterface.easy_setopt(h, CURLOPT_FTPPORT, m_ftpport.c_str());
550 g_curlInterface.easy_setopt(h, CURLOPT_FTPPORT, NULL);
552 // allow curl to not use the ip address in the returned pasv response
554 g_curlInterface.easy_setopt(h, CURLOPT_FTP_SKIP_PASV_IP, 0);
556 g_curlInterface.easy_setopt(h, CURLOPT_FTP_SKIP_PASV_IP, 1);
558 // setup Content-Encoding if requested
559 if( m_contentencoding.length() > 0 )
560 g_curlInterface.easy_setopt(h, CURLOPT_ENCODING, m_contentencoding.c_str());
562 if (!m_useOldHttpVersion && !m_acceptCharset.empty())
563 SetRequestHeader("Accept-Charset", m_acceptCharset);
565 if (m_userAgent.length() > 0)
566 g_curlInterface.easy_setopt(h, CURLOPT_USERAGENT, m_userAgent.c_str());
567 else /* set some default agent as shoutcast doesn't return proper stuff otherwise */
568 g_curlInterface.easy_setopt(h, CURLOPT_USERAGENT, g_advancedSettings.m_userAgent.c_str());
570 if (m_useOldHttpVersion)
571 g_curlInterface.easy_setopt(h, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
573 if (g_advancedSettings.m_curlDisableIPV6)
574 g_curlInterface.easy_setopt(h, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
576 if (m_proxy.length() > 0)
578 g_curlInterface.easy_setopt(h, CURLOPT_PROXY, m_proxy.c_str());
579 g_curlInterface.easy_setopt(h, CURLOPT_PROXYTYPE, proxyType2CUrlProxyType[m_proxytype]);
580 if (m_proxyuserpass.length() > 0)
581 g_curlInterface.easy_setopt(h, CURLOPT_PROXYUSERPWD, m_proxyuserpass.c_str());
584 if (m_customrequest.length() > 0)
585 g_curlInterface.easy_setopt(h, CURLOPT_CUSTOMREQUEST, m_customrequest.c_str());
587 if (m_connecttimeout == 0)
588 m_connecttimeout = g_advancedSettings.m_curlconnecttimeout;
590 // set our timeouts, we abort connection after m_timeout, and reads after no data for m_timeout seconds
591 g_curlInterface.easy_setopt(h, CURLOPT_CONNECTTIMEOUT, m_connecttimeout);
593 // We abort in case we transfer less than 1byte/second
594 g_curlInterface.easy_setopt(h, CURLOPT_LOW_SPEED_LIMIT, 1);
596 if (m_lowspeedtime == 0)
597 m_lowspeedtime = g_advancedSettings.m_curllowspeedtime;
599 // Set the lowspeed time very low as it seems Curl takes much longer to detect a lowspeed condition
600 g_curlInterface.easy_setopt(h, CURLOPT_LOW_SPEED_TIME, m_lowspeedtime);
603 // For shoutcast file, content-length should not be set, and in libcurl there is a bug, if the
604 // cast file was 302 redirected then getinfo of CURLINFO_CONTENT_LENGTH_DOWNLOAD will return
605 // the 302 response's body length, which cause the next read request failed, so we ignore
606 // content-length for shoutcast file to workaround this.
607 g_curlInterface.easy_setopt(h, CURLOPT_IGNORE_CONTENT_LENGTH, 1);
609 // Setup allowed TLS/SSL ciphers. New versions of cURL may deprecate things that are still in use.
610 if (!m_cipherlist.empty())
611 g_curlInterface.easy_setopt(h, CURLOPT_SSL_CIPHER_LIST, m_cipherlist.c_str());
614 void CCurlFile::SetRequestHeaders(CReadState* state)
616 if(state->m_curlHeaderList)
618 g_curlInterface.slist_free_all(state->m_curlHeaderList);
619 state->m_curlHeaderList = NULL;
622 MAPHTTPHEADERS::iterator it;
623 for(it = m_requestheaders.begin(); it != m_requestheaders.end(); it++)
625 std::string buffer = it->first + ": " + it->second;
626 state->m_curlHeaderList = g_curlInterface.slist_append(state->m_curlHeaderList, buffer.c_str());
629 // add user defined headers
630 if (state->m_easyHandle)
631 g_curlInterface.easy_setopt(state->m_easyHandle, CURLOPT_HTTPHEADER, state->m_curlHeaderList);
634 void CCurlFile::SetCorrectHeaders(CReadState* state)
636 CHttpHeader& h = state->m_httpheader;
637 /* workaround for shoutcast server wich doesn't set content type on standard mp3 */
638 if( h.GetMimeType().empty() )
640 if( !h.GetValue("icy-notice1").empty()
641 || !h.GetValue("icy-name").empty()
642 || !h.GetValue("icy-br").empty() )
643 h.AddParam("Content-Type", "audio/mpeg");
646 /* hack for google video */
647 if (StringUtils::EqualsNoCase(h.GetMimeType(),"text/html")
648 && !h.GetValue("Content-Disposition").empty() )
650 std::string strValue = h.GetValue("Content-Disposition");
651 if (strValue.find("filename=") != std::string::npos &&
652 strValue.find(".flv") != std::string::npos)
653 h.AddParam("Content-Type", "video/flv");
657 void CCurlFile::ParseAndCorrectUrl(CURL &url2)
659 std::string strProtocol = url2.GetTranslatedProtocol();
660 url2.SetProtocol(strProtocol);
662 if( url2.IsProtocol("ftp")
663 || url2.IsProtocol("ftps") )
665 // we was using url optons for urls, keep the old code work and warning
666 if (!url2.GetOptions().empty())
668 CLog::Log(LOGWARNING, "%s: ftp url option is deprecated, please switch to use protocol option (change '?' to '|'), url: [%s]", __FUNCTION__, url2.GetRedacted().c_str());
669 url2.SetProtocolOptions(url2.GetOptions().substr(1));
670 /* ftp has no options */
674 /* this is uggly, depending on from where */
675 /* we get the link it may or may not be */
676 /* url encoded. if handed from ftpdirectory */
677 /* it won't be so let's handle that case */
679 std::string filename(url2.GetFileName());
680 std::vector<std::string> array;
682 // if server sent us the filename in non-utf8, we need send back with same encoding.
683 if (url2.GetProtocolOption("utf8") == "0")
684 g_charsetConverter.utf8ToStringCharset(filename);
686 /* TODO: create a tokenizer that doesn't skip empty's */
687 StringUtils::Tokenize(filename, array, "/");
689 for(std::vector<std::string>::iterator it = array.begin(); it != array.end(); it++)
691 if(it != array.begin())
694 filename += CURL::Encode(*it);
697 /* make sure we keep slashes */
698 if(StringUtils::EndsWith(url2.GetFileName(), "/"))
701 url2.SetFileName(filename);
704 if (url2.HasProtocolOption("auth"))
706 m_ftpauth = url2.GetProtocolOption("auth");
707 StringUtils::ToLower(m_ftpauth);
708 if(m_ftpauth.empty())
712 if (url2.HasProtocolOption("active"))
714 m_ftpport = url2.GetProtocolOption("active");
715 if(m_ftpport.empty())
718 m_ftppasvip = url2.HasProtocolOption("pasvip") && url2.GetProtocolOption("pasvip") != "0";
720 else if( url2.IsProtocol("http")
721 || url2.IsProtocol("https"))
723 if (CSettings::Get().GetBool("network.usehttpproxy")
724 && !CSettings::Get().GetString("network.httpproxyserver").empty()
725 && CSettings::Get().GetInt("network.httpproxyport") > 0
728 m_proxy = CSettings::Get().GetString("network.httpproxyserver");
729 m_proxy += StringUtils::Format(":%d", CSettings::Get().GetInt("network.httpproxyport"));
730 if (CSettings::Get().GetString("network.httpproxyusername").length() > 0 && m_proxyuserpass.empty())
732 m_proxyuserpass = CSettings::Get().GetString("network.httpproxyusername");
733 m_proxyuserpass += ":" + CSettings::Get().GetString("network.httpproxypassword");
735 m_proxytype = (ProxyType)CSettings::Get().GetInt("network.httpproxytype");
736 CLog::Log(LOGDEBUG, "Using proxy %s, type %d", m_proxy.c_str(), proxyType2CUrlProxyType[m_proxytype]);
739 // get username and password
740 m_username = url2.GetUserName();
741 m_password = url2.GetPassWord();
743 // handle any protocol options
744 std::map<std::string, std::string> options;
745 url2.GetProtocolOptions(options);
746 if (options.size() > 0)
749 for (std::map<std::string,std::string>::const_iterator it = options.begin(); it != options.end(); ++it)
751 std::string name = it->first; StringUtils::ToLower(name);
752 const std::string &value = it->second;
757 StringUtils::ToLower(m_httpauth);
758 if(m_httpauth.empty())
761 else if (name == "referer")
763 else if (name == "user-agent")
765 else if (name == "cookie")
767 else if (name == "encoding")
768 SetContentEncoding(value);
769 else if (name == "noshout" && value == "true")
771 else if (name == "seekable" && value == "0")
773 else if (name == "accept-charset")
774 SetAcceptCharset(value);
775 else if (name == "httpproxy")
776 SetStreamProxy(value, PROXY_HTTP);
777 else if (name == "sslcipherlist")
778 m_cipherlist = value;
780 SetRequestHeader(it->first, value);
785 // Unset the protocol options to have an url without protocol options
786 url2.SetProtocolOptions("");
788 if (m_username.length() > 0 && m_password.length() > 0)
789 m_url = url2.GetWithoutUserDetails();
794 void CCurlFile::SetStreamProxy(const std::string &proxy, ProxyType type)
797 m_proxy = url.GetWithoutUserDetails();
798 m_proxyuserpass = url.GetUserName();
799 if (!url.GetPassWord().empty())
800 m_proxyuserpass += ":" + url.GetPassWord();
802 CLog::Log(LOGDEBUG, "Overriding proxy from URL parameter: %s, type %d", m_proxy.c_str(), proxyType2CUrlProxyType[m_proxytype]);
805 bool CCurlFile::Post(const std::string& strURL, const std::string& strPostData, std::string& strHTML)
807 m_postdata = strPostData;
808 m_postdataset = true;
809 return Service(strURL, strHTML);
812 bool CCurlFile::Get(const std::string& strURL, std::string& strHTML)
815 m_postdataset = false;
816 return Service(strURL, strHTML);
819 bool CCurlFile::Service(const std::string& strURL, std::string& strHTML)
821 const CURL pathToUrl(strURL);
824 if (ReadData(strHTML))
834 bool CCurlFile::ReadData(std::string& strHTML)
840 while( (size_read = Read(buffer, sizeof(buffer)-1) ) > 0 )
842 buffer[size_read] = 0;
843 strHTML.append(buffer, size_read);
844 data_size += size_read;
846 if (m_state->m_cancelled)
851 bool CCurlFile::Download(const std::string& strURL, const std::string& strFileName, LPDWORD pdwSize)
853 CLog::Log(LOGINFO, "CCurlFile::Download - %s->%s", strURL.c_str(), strFileName.c_str());
856 if (!Get(strURL, strData))
860 if (!file.OpenForWrite(strFileName, true))
862 CLog::Log(LOGERROR, "CCurlFile::Download - Unable to open file %s: %u",
863 strFileName.c_str(), GetLastError());
867 file.Write(strData.c_str(), strData.size());
872 *pdwSize = strData.size();
878 // Detect whether we are "online" or not! Very simple and dirty!
879 bool CCurlFile::IsInternet()
881 CURL url("http://www.google.com");
882 bool found = Exists(url);
888 void CCurlFile::Cancel()
890 m_state->m_cancelled = true;
895 void CCurlFile::Reset()
897 m_state->m_cancelled = false;
900 bool CCurlFile::Open(const CURL& url)
906 ParseAndCorrectUrl(url2);
908 std::string redactPath = CURL::GetRedacted(m_url);
909 CLog::Log(LOGDEBUG, "CurlFile::Open(%p) %s", (void*)this, redactPath.c_str());
911 assert(!(!m_state->m_easyHandle ^ !m_state->m_multiHandle));
912 if( m_state->m_easyHandle == NULL )
913 g_curlInterface.easy_aquire(url2.GetProtocol().c_str(),
914 url2.GetHostName().c_str(),
915 &m_state->m_easyHandle,
916 &m_state->m_multiHandle);
918 // setup common curl options
919 SetCommonOptions(m_state);
920 SetRequestHeaders(m_state);
921 m_state->m_sendRange = m_seekable;
923 m_httpresponse = m_state->Connect(m_bufferSize);
924 if( m_httpresponse < 0 || m_httpresponse >= 400)
927 SetCorrectHeaders(m_state);
929 // since we can't know the stream size up front if we're gzipped/deflated
930 // flag the stream with an unknown file size rather than the compressed
932 if (m_contentencoding.size() > 0)
933 m_state->m_fileSize = 0;
935 // check if this stream is a shoutcast stream. sometimes checking the protocol line is not enough so examine other headers as well.
936 // shoutcast streams should be handled by FileShoutcast.
937 if ((m_state->m_httpheader.GetProtoLine().substr(0, 3) == "ICY" || !m_state->m_httpheader.GetValue("icy-notice1").empty()
938 || !m_state->m_httpheader.GetValue("icy-name").empty()
939 || !m_state->m_httpheader.GetValue("icy-br").empty()) && !m_skipshout)
941 CLog::Log(LOGDEBUG,"CCurlFile::Open - File <%s> is a shoutcast stream. Re-opening", redactPath.c_str());
942 throw new CRedirectException(new CShoutcastFile);
945 m_multisession = false;
946 if(url2.IsProtocol("http") || url2.IsProtocol("https"))
948 m_multisession = true;
949 if(m_state->m_httpheader.GetValue("Server").find("Portable SDK for UPnP devices") != std::string::npos)
951 CLog::Log(LOGWARNING, "CCurlFile::Open - Disabling multi session due to broken libupnp server");
952 m_multisession = false;
956 if(StringUtils::EqualsNoCase(m_state->m_httpheader.GetValue("Transfer-Encoding"), "chunked"))
957 m_state->m_fileSize = 0;
959 if(m_state->m_fileSize <= 0)
963 if(url2.IsProtocol("http")
964 || url2.IsProtocol("https"))
966 // if server says explicitly it can't seek, respect that
967 if(StringUtils::EqualsNoCase(m_state->m_httpheader.GetValue("Accept-Ranges"),"none"))
973 if (CURLE_OK == g_curlInterface.easy_getinfo(m_state->m_easyHandle, CURLINFO_EFFECTIVE_URL,&efurl) && efurl)
979 bool CCurlFile::OpenForWrite(const CURL& url, bool bOverWrite)
984 if (Exists(url) && !bOverWrite)
988 ParseAndCorrectUrl(url2);
990 CLog::Log(LOGDEBUG, "CCurlFile::OpenForWrite(%p) %s", (void*)this, CURL::GetRedacted(m_url).c_str());
992 assert(m_state->m_easyHandle == NULL);
993 g_curlInterface.easy_aquire(url2.GetProtocol().c_str(),
994 url2.GetHostName().c_str(),
995 &m_state->m_easyHandle,
996 &m_state->m_multiHandle);
998 // setup common curl options
999 SetCommonOptions(m_state);
1000 SetRequestHeaders(m_state);
1003 if (CURLE_OK == g_curlInterface.easy_getinfo(m_state->m_easyHandle, CURLINFO_EFFECTIVE_URL,&efurl) && efurl)
1011 assert(m_state->m_multiHandle);
1013 SetCommonOptions(m_state);
1014 g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_UPLOAD, 1);
1016 g_curlInterface.multi_add_handle(m_state->m_multiHandle, m_state->m_easyHandle);
1018 m_state->SetReadBuffer(NULL, 0);
1023 int CCurlFile::Write(const void* lpBuf, int64_t uiBufSize)
1025 if (!(m_opened && m_forWrite) || m_inError)
1028 assert(m_state->m_multiHandle);
1030 m_state->SetReadBuffer(lpBuf, uiBufSize);
1031 m_state->m_isPaused = false;
1032 g_curlInterface.easy_pause(m_state->m_easyHandle, CURLPAUSE_CONT);
1034 CURLMcode result = CURLM_OK;
1037 while (m_stillRunning && !m_state->m_isPaused)
1039 while ((result = g_curlInterface.multi_perform(m_state->m_multiHandle, &m_stillRunning)) == CURLM_CALL_MULTI_PERFORM);
1041 if (!m_stillRunning)
1044 if (result != CURLM_OK)
1047 if(g_curlInterface.easy_getinfo(m_state->m_easyHandle, CURLINFO_RESPONSE_CODE, &code) == CURLE_OK )
1048 CLog::Log(LOGERROR, "%s - Unable to write curl resource (%s) - %ld", __FUNCTION__, CURL::GetRedacted(m_url).c_str(), code);
1054 m_writeOffset += m_state->m_filePos;
1055 return m_state->m_filePos;
1058 bool CCurlFile::CReadState::ReadString(char *szLine, int iLineLength)
1060 unsigned int want = (unsigned int)iLineLength;
1062 if((m_fileSize == 0 || m_filePos < m_fileSize) && !FillBuffer(want))
1065 // ensure only available data is considered
1066 want = XMIN((unsigned int)m_buffer.getMaxReadSize(), want);
1068 /* check if we finished prematurely */
1069 if (!m_stillRunning && (m_fileSize == 0 || m_filePos != m_fileSize) && !want)
1071 if (m_fileSize != 0)
1072 CLog::Log(LOGWARNING, "%s - Transfer ended before entire file was retrieved pos %" PRId64", size %" PRId64, __FUNCTION__, m_filePos, m_fileSize);
1077 char* pLine = szLine;
1080 if (!m_buffer.ReadData(pLine, 1))
1084 } while (((pLine - 1)[0] != '\n') && ((unsigned int)(pLine - szLine) < want));
1086 m_filePos += (pLine - szLine);
1087 return (bool)((pLine - szLine) > 0);
1090 bool CCurlFile::Exists(const CURL& url)
1092 // if file is already running, get info from it
1095 CLog::Log(LOGWARNING, "CCurlFile::Exists - Exist called on open file %s", url.GetRedacted().c_str());
1100 ParseAndCorrectUrl(url2);
1102 assert(m_state->m_easyHandle == NULL);
1103 g_curlInterface.easy_aquire(url2.GetProtocol().c_str(),
1104 url2.GetHostName().c_str(),
1105 &m_state->m_easyHandle, NULL);
1107 SetCommonOptions(m_state);
1108 SetRequestHeaders(m_state);
1109 g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_TIMEOUT, 5);
1110 g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_NOBODY, 1);
1111 g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_WRITEDATA, NULL); /* will cause write failure*/
1113 if(url2.IsProtocol("ftp"))
1115 g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_FILETIME, 1);
1116 // nocwd is less standard, will return empty list for non-existed remote dir on some ftp server, avoid it.
1117 if (StringUtils::EndsWith(url2.GetFileName(), "/"))
1118 g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_FTP_FILEMETHOD, CURLFTPMETHOD_SINGLECWD);
1120 g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_FTP_FILEMETHOD, CURLFTPMETHOD_NOCWD);
1123 CURLcode result = g_curlInterface.easy_perform(m_state->m_easyHandle);
1124 g_curlInterface.easy_release(&m_state->m_easyHandle, NULL);
1126 if (result == CURLE_WRITE_ERROR || result == CURLE_OK)
1129 if (result == CURLE_HTTP_RETURNED_ERROR)
1132 if(g_curlInterface.easy_getinfo(m_state->m_easyHandle, CURLINFO_RESPONSE_CODE, &code) == CURLE_OK && code != 404 )
1133 CLog::Log(LOGERROR, "CCurlFile::Exists - Failed: HTTP returned error %ld for %s", code, url.GetRedacted().c_str());
1135 else if (result != CURLE_REMOTE_FILE_NOT_FOUND && result != CURLE_FTP_COULDNT_RETR_FILE)
1137 CLog::Log(LOGERROR, "CCurlFile::Exists - Failed: %s(%d) for %s", g_curlInterface.easy_strerror(result), result, url.GetRedacted().c_str());
1144 int64_t CCurlFile::Seek(int64_t iFilePosition, int iWhence)
1146 int64_t nextPos = m_state->m_filePos;
1154 nextPos = iFilePosition;
1157 nextPos += iFilePosition;
1160 if (m_state->m_fileSize)
1161 nextPos = m_state->m_fileSize + iFilePosition;
1169 // We can't seek beyond EOF
1170 if (m_state->m_fileSize && nextPos > m_state->m_fileSize) return -1;
1172 if(m_state->Seek(nextPos))
1180 m_oldState = m_state;
1181 m_state = new CReadState();
1182 m_state->m_fileSize = m_oldState->m_fileSize;
1183 g_curlInterface.easy_aquire(url.GetProtocol().c_str(),
1184 url.GetHostName().c_str(),
1185 &m_state->m_easyHandle,
1186 &m_state->m_multiHandle );
1192 m_state = m_oldState;
1195 if (m_state->Seek(nextPos))
1198 m_state->Disconnect();
1202 m_state->Disconnect();
1204 // re-setup common curl options
1205 SetCommonOptions(m_state);
1207 /* caller might have changed some headers (needed for daap)*/
1208 SetRequestHeaders(m_state);
1210 m_state->m_filePos = nextPos;
1211 m_state->m_sendRange = true;
1213 long response = m_state->Connect(m_bufferSize);
1214 if(response < 0 && (m_state->m_fileSize == 0 || m_state->m_fileSize != m_state->m_filePos))
1221 m_state = m_oldState;
1224 // Retry without mutlisession
1225 m_multisession = false;
1226 return Seek(iFilePosition, iWhence);
1235 SetCorrectHeaders(m_state);
1237 return m_state->m_filePos;
1240 int64_t CCurlFile::GetLength()
1242 if (!m_opened) return 0;
1243 return m_state->m_fileSize;
1246 int64_t CCurlFile::GetPosition()
1248 if (!m_opened) return 0;
1249 return m_state->m_filePos;
1252 int CCurlFile::Stat(const CURL& url, struct __stat64* buffer)
1254 // if file is already running, get info from it
1257 CLog::Log(LOGWARNING, "CCurlFile::Stat - Stat called on open file %s", url.GetRedacted().c_str());
1260 memset(buffer, 0, sizeof(struct __stat64));
1261 buffer->st_size = GetLength();
1262 buffer->st_mode = _S_IFREG;
1268 ParseAndCorrectUrl(url2);
1270 assert(m_state->m_easyHandle == NULL);
1271 g_curlInterface.easy_aquire(url2.GetProtocol().c_str(),
1272 url2.GetHostName().c_str(),
1273 &m_state->m_easyHandle, NULL);
1275 SetCommonOptions(m_state);
1276 SetRequestHeaders(m_state);
1277 g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_TIMEOUT, g_advancedSettings.m_curlconnecttimeout);
1278 g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_NOBODY, 1);
1279 g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_WRITEDATA, NULL); /* will cause write failure*/
1280 g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_FILETIME , 1);
1282 if(url2.IsProtocol("ftp"))
1284 // nocwd is less standard, will return empty list for non-existed remote dir on some ftp server, avoid it.
1285 if (StringUtils::EndsWith(url2.GetFileName(), "/"))
1286 g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_FTP_FILEMETHOD, CURLFTPMETHOD_SINGLECWD);
1288 g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_FTP_FILEMETHOD, CURLFTPMETHOD_NOCWD);
1291 CURLcode result = g_curlInterface.easy_perform(m_state->m_easyHandle);
1293 if(result == CURLE_HTTP_RETURNED_ERROR)
1296 if(g_curlInterface.easy_getinfo(m_state->m_easyHandle, CURLINFO_RESPONSE_CODE, &code) == CURLE_OK && code == 404 )
1300 if(result == CURLE_GOT_NOTHING
1301 || result == CURLE_HTTP_RETURNED_ERROR
1302 || result == CURLE_RECV_ERROR /* some silly shoutcast servers */ )
1304 /* some http servers and shoutcast servers don't give us any data on a head request */
1305 /* request normal and just fail out, it's their loss */
1306 /* somehow curl doesn't reset CURLOPT_NOBODY properly so reset everything */
1307 SetCommonOptions(m_state);
1308 SetRequestHeaders(m_state);
1309 g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_TIMEOUT, g_advancedSettings.m_curlconnecttimeout);
1310 g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_RANGE, "0-0");
1311 g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_WRITEDATA, NULL); /* will cause write failure*/
1312 g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_FILETIME, 1);
1313 result = g_curlInterface.easy_perform(m_state->m_easyHandle);
1316 if( result == CURLE_HTTP_RANGE_ERROR )
1318 /* crap can't use the range option, disable it and try again */
1319 g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_RANGE, NULL);
1320 result = g_curlInterface.easy_perform(m_state->m_easyHandle);
1323 if( result != CURLE_WRITE_ERROR && result != CURLE_OK )
1325 g_curlInterface.easy_release(&m_state->m_easyHandle, NULL);
1327 CLog::Log(LOGERROR, "CCurlFile::Stat - Failed: %s(%d) for %s", g_curlInterface.easy_strerror(result), result, url.GetRedacted().c_str());
1332 result = g_curlInterface.easy_getinfo(m_state->m_easyHandle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &length);
1333 if (result != CURLE_OK || length < 0.0)
1335 if (url.IsProtocol("ftp"))
1337 g_curlInterface.easy_release(&m_state->m_easyHandle, NULL);
1338 CLog::Log(LOGNOTICE, "CCurlFile::Stat - Content length failed: %s(%d) for %s", g_curlInterface.easy_strerror(result), result, url.GetRedacted().c_str());
1346 SetCorrectHeaders(m_state);
1351 result = g_curlInterface.easy_getinfo(m_state->m_easyHandle, CURLINFO_CONTENT_TYPE, &content);
1352 if (result != CURLE_OK)
1354 CLog::Log(LOGNOTICE, "CCurlFile::Stat - Content type failed: %s(%d) for %s", g_curlInterface.easy_strerror(result), result, url.GetRedacted().c_str());
1355 g_curlInterface.easy_release(&m_state->m_easyHandle, NULL);
1361 memset(buffer, 0, sizeof(struct __stat64));
1362 buffer->st_size = (int64_t)length;
1363 if(content && strstr(content, "text/html")) //consider html files directories
1364 buffer->st_mode = _S_IFDIR;
1366 buffer->st_mode = _S_IFREG;
1369 result = g_curlInterface.easy_getinfo(m_state->m_easyHandle, CURLINFO_FILETIME, &filetime);
1370 if (result != CURLE_OK)
1372 CLog::Log(LOGNOTICE, "CCurlFile::Stat - Filetime failed: %s(%d) for %s", g_curlInterface.easy_strerror(result), result, url.GetRedacted().c_str());
1377 buffer->st_mtime = filetime;
1380 g_curlInterface.easy_release(&m_state->m_easyHandle, NULL);
1384 unsigned int CCurlFile::CReadState::Read(void* lpBuf, int64_t uiBufSize)
1386 /* only request 1 byte, for truncated reads (only if not eof) */
1387 if((m_fileSize == 0 || m_filePos < m_fileSize) && !FillBuffer(1))
1390 /* ensure only available data is considered */
1391 unsigned int want = (unsigned int)XMIN(m_buffer.getMaxReadSize(), uiBufSize);
1393 /* xfer data to caller */
1394 if (m_buffer.ReadData((char *)lpBuf, want))
1400 /* check if we finished prematurely */
1401 if (!m_stillRunning && (m_fileSize == 0 || m_filePos != m_fileSize))
1403 CLog::Log(LOGWARNING, "%s - Transfer ended before entire file was retrieved pos %" PRId64", size %" PRId64, __FUNCTION__, m_filePos, m_fileSize);
1410 /* use to attempt to fill the read buffer up to requested number of bytes */
1411 bool CCurlFile::CReadState::FillBuffer(unsigned int want)
1418 // only attempt to fill buffer if transactions still running and buffer
1419 // doesnt exceed required size already
1420 while ((unsigned int)m_buffer.getMaxReadSize() < want && m_buffer.getMaxWriteSize() > 0 )
1425 /* if there is data in overflow buffer, try to use that first */
1428 unsigned amount = XMIN((unsigned int)m_buffer.getMaxWriteSize(), m_overflowSize);
1429 m_buffer.WriteData(m_overflowBuffer, amount);
1431 if (amount < m_overflowSize)
1432 memcpy(m_overflowBuffer, m_overflowBuffer+amount,m_overflowSize-amount);
1434 m_overflowSize -= amount;
1435 m_overflowBuffer = (char*)realloc_simple(m_overflowBuffer, m_overflowSize);
1439 CURLMcode result = g_curlInterface.multi_perform(m_multiHandle, &m_stillRunning);
1440 if (!m_stillRunning)
1442 if (result == CURLM_OK)
1444 /* if we still have stuff in buffer, we are fine */
1445 if (m_buffer.getMaxReadSize())
1448 /* verify that we are actually okey */
1450 CURLcode CURLresult = CURLE_OK;
1452 while ((msg = g_curlInterface.multi_info_read(m_multiHandle, &msgs)))
1454 if (msg->msg == CURLMSG_DONE)
1456 if (msg->data.result == CURLE_OK)
1459 CLog::Log(LOGERROR, "CCurlFile::FillBuffer - Failed: %s(%d)", g_curlInterface.easy_strerror(msg->data.result), msg->data.result);
1461 // We need to check the result here as we don't want to retry on every error
1462 if ( (msg->data.result == CURLE_OPERATION_TIMEDOUT ||
1463 msg->data.result == CURLE_PARTIAL_FILE ||
1464 msg->data.result == CURLE_COULDNT_CONNECT ||
1465 msg->data.result == CURLE_RECV_ERROR) &&
1467 CURLresult = msg->data.result;
1468 else if ( (msg->data.result == CURLE_HTTP_RANGE_ERROR ||
1469 msg->data.result == CURLE_HTTP_RETURNED_ERROR) &&
1474 // If server returns a range or http error, retry with range disabled
1475 CURLresult = msg->data.result;
1476 m_sendRange = false;
1483 // Don't retry when we didn't "see" any error
1484 if (CURLresult == CURLE_OK)
1488 if (m_multiHandle && m_easyHandle)
1489 g_curlInterface.multi_remove_handle(m_multiHandle, m_easyHandle);
1491 // Reset all the stuff like we would in Disconnect()
1493 free(m_overflowBuffer);
1494 m_overflowBuffer = NULL;
1497 // If we got here something is wrong
1498 if (++retry > g_advancedSettings.m_curlretries)
1500 CLog::Log(LOGERROR, "CCurlFile::FillBuffer - Reconnect failed!");
1501 // Reset the rest of the variables like we would in Disconnect()
1509 CLog::Log(LOGNOTICE, "CCurlFile::FillBuffer - Reconnect, (re)try %i", retry);
1511 // Connect + seek to current position (again)
1513 g_curlInterface.multi_add_handle(m_multiHandle, m_easyHandle);
1515 // Return to the beginning of the loop:
1521 // We've finished out first loop
1522 if(m_bFirstLoop && m_buffer.getMaxReadSize() > 0)
1523 m_bFirstLoop = false;
1534 // get file descriptors from the transfers
1535 g_curlInterface.multi_fdset(m_multiHandle, &fdread, &fdwrite, &fdexcep, &maxfd);
1538 if (CURLM_OK != g_curlInterface.multi_timeout(m_multiHandle, &timeout) || timeout == -1)
1541 XbmcThreads::EndTime endTime(timeout);
1546 unsigned int time_left = endTime.MillisLeft();
1547 struct timeval t = { time_left / 1000, (time_left % 1000) * 1000 };
1549 // Wait until data is available or a timeout occurs.
1550 rc = select(maxfd + 1, &fdread, &fdwrite, &fdexcep, &t);
1551 #ifdef TARGET_WINDOWS
1552 } while(rc == SOCKET_ERROR && WSAGetLastError() == WSAEINTR);
1554 } while(rc == SOCKET_ERROR && errno == EINTR);
1557 if(rc == SOCKET_ERROR)
1559 #ifdef TARGET_WINDOWS
1561 strerror_s(buf, 256, WSAGetLastError());
1562 CLog::Log(LOGERROR, "CCurlFile::FillBuffer - Failed with socket error:%s", buf);
1564 char const * str = strerror(errno);
1565 CLog::Log(LOGERROR, "CCurlFile::FillBuffer - Failed with socket error:%s", str);
1572 case CURLM_CALL_MULTI_PERFORM:
1574 // we don't keep calling here as that can easily overwrite our buffer which we want to avoid
1575 // docs says we should call it soon after, but aslong as we are reading data somewhere
1576 // this aught to be soon enough. should stay in socket otherwise
1582 CLog::Log(LOGERROR, "CCurlFile::FillBuffer - Multi perform failed with code %d, aborting", result);
1591 void CCurlFile::CReadState::SetReadBuffer(const void* lpBuf, int64_t uiBufSize)
1593 m_readBuffer = (char*)lpBuf;
1594 m_fileSize = uiBufSize;
1598 void CCurlFile::ClearRequestHeaders()
1600 m_requestheaders.clear();
1603 void CCurlFile::SetRequestHeader(const std::string& header, const std::string& value)
1605 m_requestheaders[header] = value;
1608 void CCurlFile::SetRequestHeader(const std::string& header, long value)
1610 m_requestheaders[header] = StringUtils::Format("%ld", value);
1613 std::string CCurlFile::GetServerReportedCharset(void)
1618 return m_state->m_httpheader.GetCharset();
1621 /* STATIC FUNCTIONS */
1622 bool CCurlFile::GetHttpHeader(const CURL &url, CHttpHeader &headers)
1627 if(file.Stat(url, NULL) == 0)
1629 headers = file.GetHttpHeader();
1636 CLog::Log(LOGERROR, "%s - Exception thrown while trying to retrieve header url: %s", __FUNCTION__, url.GetRedacted().c_str());
1641 bool CCurlFile::GetMimeType(const CURL &url, std::string &content, const std::string &useragent)
1644 if (!useragent.empty())
1645 file.SetUserAgent(useragent);
1647 struct __stat64 buffer;
1648 std::string redactUrl = url.GetRedacted();
1649 if( file.Stat(url, &buffer) == 0 )
1651 if (buffer.st_mode == _S_IFDIR)
1652 content = "x-directory/normal";
1654 content = file.GetMimeType();
1655 CLog::Log(LOGDEBUG, "CCurlFile::GetMimeType - %s -> %s", redactUrl.c_str(), content.c_str());
1658 CLog::Log(LOGDEBUG, "CCurlFile::GetMimeType - %s -> failed", redactUrl.c_str());
1663 bool CCurlFile::GetCookies(const CURL &url, std::string &cookies)
1665 std::string cookiesStr;
1666 struct curl_slist* curlCookies;
1667 XCURL::CURL_HANDLE* easyHandle;
1668 XCURL::CURLM* multiHandle;
1670 // get the cookies list
1671 g_curlInterface.easy_aquire(url.GetProtocol().c_str(),
1672 url.GetHostName().c_str(),
1673 &easyHandle, &multiHandle);
1674 if (CURLE_OK == g_curlInterface.easy_getinfo(easyHandle, CURLINFO_COOKIELIST, &curlCookies))
1676 // iterate over each cookie and format it into an RFC 2109 formatted Set-Cookie string
1677 struct curl_slist* curlCookieIter = curlCookies;
1678 while(curlCookieIter)
1680 // tokenize the CURL cookie string
1681 std::vector<std::string> valuesVec;
1682 StringUtils::Tokenize(curlCookieIter->data, valuesVec, "\t");
1684 // ensure the length is valid
1685 if (valuesVec.size() < 7)
1687 CLog::Log(LOGERROR, "CCurlFile::GetCookies - invalid cookie: '%s'", curlCookieIter->data);
1688 curlCookieIter = curlCookieIter->next;
1692 // create a http-header formatted cookie string
1693 std::string cookieStr = valuesVec[5] + "=" + valuesVec[6] +
1694 "; path=" + valuesVec[2] +
1695 "; domain=" + valuesVec[0];
1697 // append this cookie to the string containing all cookies
1698 if (!cookiesStr.empty())
1700 cookiesStr += cookieStr;
1702 // move on to the next cookie
1703 curlCookieIter = curlCookieIter->next;
1706 // free the curl cookies
1707 g_curlInterface.slist_free_all(curlCookies);
1709 // release our handles
1710 g_curlInterface.easy_release(&easyHandle, &multiHandle);
1712 // if we have a non-empty cookie string, return it
1713 if (!cookiesStr.empty())
1715 cookies = cookiesStr;
1720 // no cookies to return
1724 int CCurlFile::IoControl(EIoControl request, void* param)
1726 if(request == IOCTRL_SEEK_POSSIBLE)
1727 return m_seekable ? 1 : 0;