/* * Copyright (C) 2005-2013 Team XBMC * http://xbmc.org * * This Program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This Program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with XBMC; see the file COPYING. If not, see * . * */ #include "utils/log.h" #include "CurlFile.h" #include "Util.h" #include "utils/URIUtils.h" #include "URL.h" #include "settings/AdvancedSettings.h" #include "settings/GUISettings.h" #include "utils/SystemInfo.h" #include "File.h" #include "Application.h" #include #include #include "DllLibCurl.h" #include "ShoutcastFile.h" #include "SpecialProtocol.h" #include "utils/CharsetConverter.h" using namespace XFILE; using namespace XCURL; #define XMIN(a,b) ((a)<(b)?(a):(b)) #define FITS_INT(a) (((a) <= INT_MAX) && ((a) >= INT_MIN)) extern "C" int __stdcall dllselect(int ntfs, fd_set *readfds, fd_set *writefds, fd_set *errorfds, const timeval *timeout); // curl calls this routine to debug extern "C" int debug_callback(CURL_HANDLE *handle, curl_infotype info, char *output, size_t size, void *data) { if (info == CURLINFO_DATA_IN || info == CURLINFO_DATA_OUT) return 0; // Only shown cURL debug into with loglevel DEBUG_SAMBA or higher if( g_advancedSettings.m_logLevel < LOG_LEVEL_DEBUG_SAMBA ) return 0; CStdString strLine; strLine.append(output, size); std::vector vecLines; CUtil::Tokenize(strLine, vecLines, "\r\n"); std::vector::const_iterator it = vecLines.begin(); char *infotype; switch(info) { case CURLINFO_TEXT : infotype = (char *) "TEXT: "; break; case CURLINFO_HEADER_IN : infotype = (char *) "HEADER_IN: "; break; case CURLINFO_HEADER_OUT : infotype = (char *) "HEADER_OUT: "; break; case CURLINFO_SSL_DATA_IN : infotype = (char *) "SSL_DATA_IN: "; break; case CURLINFO_SSL_DATA_OUT : infotype = (char *) "SSL_DATA_OUT: "; break; case CURLINFO_END : infotype = (char *) "END: "; break; default : infotype = (char *) ""; break; } while (it != vecLines.end()) { CLog::Log(LOGDEBUG, "Curl::Debug - %s%s", infotype, (*it).c_str()); it++; } return 0; } /* curl calls this routine to get more data */ extern "C" size_t write_callback(char *buffer, size_t size, size_t nitems, void *userp) { if(userp == NULL) return 0; CCurlFile::CReadState *state = (CCurlFile::CReadState *)userp; return state->WriteCallback(buffer, size, nitems); } extern "C" size_t read_callback(char *buffer, size_t size, size_t nitems, void *userp) { if(userp == NULL) return 0; CCurlFile::CReadState *state = (CCurlFile::CReadState *)userp; return state->ReadCallback(buffer, size, nitems); } extern "C" size_t header_callback(void *ptr, size_t size, size_t nmemb, void *stream) { CCurlFile::CReadState *state = (CCurlFile::CReadState *)stream; return state->HeaderCallback(ptr, size, nmemb); } /* fix for silly behavior of realloc */ static inline void* realloc_simple(void *ptr, size_t size) { void *ptr2 = realloc(ptr, size); if(ptr && !ptr2 && size > 0) { free(ptr); return NULL; } else return ptr2; } size_t CCurlFile::CReadState::HeaderCallback(void *ptr, size_t size, size_t nmemb) { // clear any previous header if(m_headerdone) { m_httpheader.Clear(); m_headerdone = false; } // libcurl doc says that this info is not always \0 terminated char* strData = (char*)ptr; int iSize = size * nmemb; if (strData[iSize] != 0) { strData = (char*)malloc(iSize + 1); strncpy(strData, (char*)ptr, iSize); strData[iSize] = 0; } else strData = strdup((char*)ptr); if(strcmp(strData, "\r\n") == 0) m_headerdone = true; m_httpheader.Parse(strData); free(strData); return iSize; } size_t CCurlFile::CReadState::ReadCallback(char *buffer, size_t size, size_t nitems) { if (m_fileSize == 0) return 0; if (m_filePos >= m_fileSize) { m_isPaused = true; return CURL_READFUNC_PAUSE; } int64_t retSize = XMIN(m_fileSize - m_filePos, int64_t(nitems * size)); memcpy(buffer, m_readBuffer + m_filePos, retSize); m_filePos += retSize; return retSize; } size_t CCurlFile::CReadState::WriteCallback(char *buffer, size_t size, size_t nitems) { unsigned int amount = size * nitems; // CLog::Log(LOGDEBUG, "CCurlFile::WriteCallback (%p) with %i bytes, readsize = %i, writesize = %i", this, amount, m_buffer.getMaxReadSize(), m_buffer.getMaxWriteSize() - m_overflowSize); if (m_overflowSize) { // we have our overflow buffer - first get rid of as much as we can unsigned int maxWriteable = XMIN((unsigned int)m_buffer.getMaxWriteSize(), m_overflowSize); if (maxWriteable) { if (!m_buffer.WriteData(m_overflowBuffer, maxWriteable)) CLog::Log(LOGERROR, "CCurlFile::WriteCallback - Unable to write to buffer - what's up?"); if (m_overflowSize > maxWriteable) { // still have some more - copy it down memmove(m_overflowBuffer, m_overflowBuffer + maxWriteable, m_overflowSize - maxWriteable); } m_overflowSize -= maxWriteable; } } // ok, now copy the data into our ring buffer unsigned int maxWriteable = XMIN((unsigned int)m_buffer.getMaxWriteSize(), amount); if (maxWriteable) { if (!m_buffer.WriteData(buffer, maxWriteable)) { CLog::Log(LOGERROR, "CCurlFile::WriteCallback - Unable to write to buffer with %i bytes - what's up?", maxWriteable); } else { amount -= maxWriteable; buffer += maxWriteable; } } if (amount) { // CLog::Log(LOGDEBUG, "CCurlFile::WriteCallback(%p) not enough free space for %i bytes", (void*)this, amount); m_overflowBuffer = (char*)realloc_simple(m_overflowBuffer, amount + m_overflowSize); if(m_overflowBuffer == NULL) { CLog::Log(LOGWARNING, "CCurlFile::WriteCallback - Failed to grow overflow buffer from %i bytes to %i bytes", m_overflowSize, amount + m_overflowSize); return 0; } memcpy(m_overflowBuffer + m_overflowSize, buffer, amount); m_overflowSize += amount; } return size * nitems; } CCurlFile::CReadState::CReadState() { m_easyHandle = NULL; m_multiHandle = NULL; m_overflowBuffer = NULL; m_overflowSize = 0; m_filePos = 0; m_fileSize = 0; m_bufferSize = 0; m_cancelled = false; m_bFirstLoop = true; m_sendRange = true; m_headerdone = false; m_readBuffer = 0; m_isPaused = false; } CCurlFile::CReadState::~CReadState() { Disconnect(); if(m_easyHandle) g_curlInterface.easy_release(&m_easyHandle, &m_multiHandle); } bool CCurlFile::CReadState::Seek(int64_t pos) { if(pos == m_filePos) return true; if(FITS_INT(pos - m_filePos) && m_buffer.SkipBytes((int)(pos - m_filePos))) { m_filePos = pos; return true; } if(pos > m_filePos && pos < m_filePos + m_bufferSize) { int len = m_buffer.getMaxReadSize(); m_filePos += len; m_buffer.SkipBytes(len); if(!FillBuffer(m_bufferSize)) { if(!m_buffer.SkipBytes(-len)) CLog::Log(LOGERROR, "%s - Failed to restore position after failed fill", __FUNCTION__); else m_filePos -= len; return false; } if(!FITS_INT(pos - m_filePos) || !m_buffer.SkipBytes((int)(pos - m_filePos))) { CLog::Log(LOGERROR, "%s - Failed to skip to position after having filled buffer", __FUNCTION__); if(!m_buffer.SkipBytes(-len)) CLog::Log(LOGERROR, "%s - Failed to restore position after failed seek", __FUNCTION__); else m_filePos -= len; return false; } m_filePos = pos; return true; } return false; } void CCurlFile::CReadState::SetResume(void) { /* * Explicitly set RANGE header when filepos=0 as some http servers require us to always send the range * request header. If we don't the server may provide different content causing seeking to fail. * This only affects HTTP-like items, for FTP it's a null operation. */ if (m_sendRange && m_filePos == 0) g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_RANGE, "0-"); else { g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_RANGE, NULL); m_sendRange = false; } g_curlInterface.easy_setopt(m_easyHandle, CURLOPT_RESUME_FROM_LARGE, m_filePos); } long CCurlFile::CReadState::Connect(unsigned int size) { SetResume(); g_curlInterface.multi_add_handle(m_multiHandle, m_easyHandle); m_bufferSize = size; m_buffer.Destroy(); m_buffer.Create(size * 3); m_headerdone = false; // read some data in to try and obtain the length // maybe there's a better way to get this info?? m_stillRunning = 1; if (!FillBuffer(1)) { CLog::Log(LOGERROR, "CCurlFile::CReadState::Connect, didn't get any data from stream."); return -1; } double length; if (CURLE_OK == g_curlInterface.easy_getinfo(m_easyHandle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &length)) { if (length < 0) length = 0.0; m_fileSize = m_filePos + (int64_t)length; } long response; if (CURLE_OK == g_curlInterface.easy_getinfo(m_easyHandle, CURLINFO_RESPONSE_CODE, &response)) return response; return -1; } void CCurlFile::CReadState::Disconnect() { if(m_multiHandle && m_easyHandle) g_curlInterface.multi_remove_handle(m_multiHandle, m_easyHandle); m_buffer.Clear(); free(m_overflowBuffer); m_overflowBuffer = NULL; m_overflowSize = 0; m_filePos = 0; m_fileSize = 0; m_bufferSize = 0; m_readBuffer = 0; } CCurlFile::~CCurlFile() { Close(); delete m_state; g_curlInterface.Unload(); } CCurlFile::CCurlFile() { g_curlInterface.Load(); // loads the curl dll and resolves exports etc. m_curlAliasList = NULL; m_curlHeaderList = NULL; m_opened = false; m_forWrite = false; m_inError = false; m_multisession = true; m_seekable = true; m_useOldHttpVersion = false; m_connecttimeout = 0; m_lowspeedtime = 0; m_ftpauth = ""; m_ftpport = ""; m_ftppasvip = false; m_bufferSize = 32768; m_binary = true; m_postdata = ""; m_postdataset = false; m_username = ""; m_password = ""; m_httpauth = ""; m_state = new CReadState(); m_skipshout = false; m_httpresponse = -1; } //Has to be called before Open() void CCurlFile::SetBufferSize(unsigned int size) { m_bufferSize = size; } void CCurlFile::Close() { if (m_opened && m_forWrite && !m_inError) Write(NULL, 0); m_state->Disconnect(); m_url.Empty(); m_referer.Empty(); m_cookie.Empty(); /* cleanup */ if( m_curlAliasList ) g_curlInterface.slist_free_all(m_curlAliasList); if( m_curlHeaderList ) g_curlInterface.slist_free_all(m_curlHeaderList); m_curlAliasList = NULL; m_curlHeaderList = NULL; m_opened = false; m_forWrite = false; m_inError = false; } void CCurlFile::SetCommonOptions(CReadState* state) { CURL_HANDLE* h = state->m_easyHandle; g_curlInterface.easy_reset(h); g_curlInterface.easy_setopt(h, CURLOPT_DEBUGFUNCTION, debug_callback); if( g_advancedSettings.m_logLevel >= LOG_LEVEL_DEBUG ) g_curlInterface.easy_setopt(h, CURLOPT_VERBOSE, TRUE); else g_curlInterface.easy_setopt(h, CURLOPT_VERBOSE, FALSE); g_curlInterface.easy_setopt(h, CURLOPT_WRITEDATA, state); g_curlInterface.easy_setopt(h, CURLOPT_WRITEFUNCTION, write_callback); g_curlInterface.easy_setopt(h, CURLOPT_READDATA, state); g_curlInterface.easy_setopt(h, CURLOPT_READFUNCTION, read_callback); // set username and password for current handle if (m_username.length() > 0 && m_password.length() > 0) { CStdString userpwd = m_username + ":" + m_password; g_curlInterface.easy_setopt(h, CURLOPT_USERPWD, userpwd.c_str()); } // make sure headers are seperated from the data stream g_curlInterface.easy_setopt(h, CURLOPT_WRITEHEADER, state); g_curlInterface.easy_setopt(h, CURLOPT_HEADERFUNCTION, header_callback); g_curlInterface.easy_setopt(h, CURLOPT_HEADER, FALSE); g_curlInterface.easy_setopt(h, CURLOPT_FTP_USE_EPSV, 0); // turn off epsv // Allow us to follow two redirects g_curlInterface.easy_setopt(h, CURLOPT_FOLLOWLOCATION, TRUE); g_curlInterface.easy_setopt(h, CURLOPT_MAXREDIRS, 5); // Enable cookie engine for current handle to re-use them in future requests CStdString strCookieFile; CStdString strTempPath = CSpecialProtocol::TranslatePath(g_advancedSettings.m_cachePath); URIUtils::AddFileToFolder(strTempPath, "cookies.dat", strCookieFile); g_curlInterface.easy_setopt(h, CURLOPT_COOKIEFILE, strCookieFile.c_str()); g_curlInterface.easy_setopt(h, CURLOPT_COOKIEJAR, strCookieFile.c_str()); // Set custom cookie if requested if (!m_cookie.IsEmpty()) g_curlInterface.easy_setopt(h, CURLOPT_COOKIE, m_cookie.c_str()); g_curlInterface.easy_setopt(h, CURLOPT_COOKIELIST, "FLUSH"); // When using multiple threads you should set the CURLOPT_NOSIGNAL option to // TRUE for all handles. Everything will work fine except that timeouts are not // honored during the DNS lookup - which you can work around by building libcurl // with c-ares support. c-ares is a library that provides asynchronous name // resolves. Unfortunately, c-ares does not yet support IPv6. g_curlInterface.easy_setopt(h, CURLOPT_NOSIGNAL, TRUE); // not interested in failed requests g_curlInterface.easy_setopt(h, CURLOPT_FAILONERROR, 1); // enable support for icecast / shoutcast streams if ( NULL == m_curlAliasList ) // m_curlAliasList is used only by this one place, but SetCommonOptions can // be called multiple times, only append to list if it's empty. m_curlAliasList = g_curlInterface.slist_append(m_curlAliasList, "ICY 200 OK"); g_curlInterface.easy_setopt(h, CURLOPT_HTTP200ALIASES, m_curlAliasList); // never verify peer, we don't have any certificates to do this g_curlInterface.easy_setopt(h, CURLOPT_SSL_VERIFYPEER, 0); g_curlInterface.easy_setopt(h, CURLOPT_SSL_VERIFYHOST, 0); g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_URL, m_url.c_str()); g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_TRANSFERTEXT, FALSE); // setup POST data if it is set (and it may be empty) if (m_postdataset) { g_curlInterface.easy_setopt(h, CURLOPT_POST, 1 ); g_curlInterface.easy_setopt(h, CURLOPT_POSTFIELDSIZE, m_postdata.length()); g_curlInterface.easy_setopt(h, CURLOPT_POSTFIELDS, m_postdata.c_str()); } // setup Referer header if needed if (!m_referer.IsEmpty()) g_curlInterface.easy_setopt(h, CURLOPT_REFERER, m_referer.c_str()); else { g_curlInterface.easy_setopt(h, CURLOPT_REFERER, NULL); g_curlInterface.easy_setopt(h, CURLOPT_AUTOREFERER, TRUE); } // setup any requested authentication if( m_ftpauth.length() > 0 ) { g_curlInterface.easy_setopt(h, CURLOPT_FTP_SSL, CURLFTPSSL_TRY); if( m_ftpauth.Equals("any") ) g_curlInterface.easy_setopt(h, CURLOPT_FTPSSLAUTH, CURLFTPAUTH_DEFAULT); else if( m_ftpauth.Equals("ssl") ) g_curlInterface.easy_setopt(h, CURLOPT_FTPSSLAUTH, CURLFTPAUTH_SSL); else if( m_ftpauth.Equals("tls") ) g_curlInterface.easy_setopt(h, CURLOPT_FTPSSLAUTH, CURLFTPAUTH_TLS); } // setup requested http authentication method if(m_httpauth.length() > 0) { if( m_httpauth.Equals("any") ) g_curlInterface.easy_setopt(h, CURLOPT_HTTPAUTH, CURLAUTH_ANY); else if( m_httpauth.Equals("anysafe") ) g_curlInterface.easy_setopt(h, CURLOPT_HTTPAUTH, CURLAUTH_ANYSAFE); else if( m_httpauth.Equals("digest") ) g_curlInterface.easy_setopt(h, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST); else if( m_httpauth.Equals("ntlm") ) g_curlInterface.easy_setopt(h, CURLOPT_HTTPAUTH, CURLAUTH_NTLM); } // allow passive mode for ftp if( m_ftpport.length() > 0 ) g_curlInterface.easy_setopt(h, CURLOPT_FTPPORT, m_ftpport.c_str()); else g_curlInterface.easy_setopt(h, CURLOPT_FTPPORT, NULL); // allow curl to not use the ip address in the returned pasv response if( m_ftppasvip ) g_curlInterface.easy_setopt(h, CURLOPT_FTP_SKIP_PASV_IP, 0); else g_curlInterface.easy_setopt(h, CURLOPT_FTP_SKIP_PASV_IP, 1); // setup Content-Encoding if requested if( m_contentencoding.length() > 0 ) g_curlInterface.easy_setopt(h, CURLOPT_ENCODING, m_contentencoding.c_str()); if (m_userAgent.length() > 0) g_curlInterface.easy_setopt(h, CURLOPT_USERAGENT, m_userAgent.c_str()); else /* set some default agent as shoutcast doesn't return proper stuff otherwise */ g_curlInterface.easy_setopt(h, CURLOPT_USERAGENT, g_sysinfo.GetUserAgent().c_str()); if (m_useOldHttpVersion) g_curlInterface.easy_setopt(h, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0); if (m_proxy.length() > 0) { g_curlInterface.easy_setopt(h, CURLOPT_PROXY, m_proxy.c_str()); if (m_proxyuserpass.length() > 0) g_curlInterface.easy_setopt(h, CURLOPT_PROXYUSERPWD, m_proxyuserpass.c_str()); } if (m_customrequest.length() > 0) g_curlInterface.easy_setopt(h, CURLOPT_CUSTOMREQUEST, m_customrequest.c_str()); if (m_connecttimeout == 0) m_connecttimeout = g_advancedSettings.m_curlconnecttimeout; // set our timeouts, we abort connection after m_timeout, and reads after no data for m_timeout seconds g_curlInterface.easy_setopt(h, CURLOPT_CONNECTTIMEOUT, m_connecttimeout); // We abort in case we transfer less than 1byte/second g_curlInterface.easy_setopt(h, CURLOPT_LOW_SPEED_LIMIT, 1); if (m_lowspeedtime == 0) m_lowspeedtime = g_advancedSettings.m_curllowspeedtime; // Set the lowspeed time very low as it seems Curl takes much longer to detect a lowspeed condition g_curlInterface.easy_setopt(h, CURLOPT_LOW_SPEED_TIME, m_lowspeedtime); if (m_skipshout) // For shoutcast file, content-length should not be set, and in libcurl there is a bug, if the // cast file was 302 redirected then getinfo of CURLINFO_CONTENT_LENGTH_DOWNLOAD will return // the 302 response's body length, which cause the next read request failed, so we ignore // content-length for shoutcast file to workaround this. g_curlInterface.easy_setopt(h, CURLOPT_IGNORE_CONTENT_LENGTH, 1); } void CCurlFile::SetRequestHeaders(CReadState* state) { if(m_curlHeaderList) { g_curlInterface.slist_free_all(m_curlHeaderList); m_curlHeaderList = NULL; } MAPHTTPHEADERS::iterator it; for(it = m_requestheaders.begin(); it != m_requestheaders.end(); it++) { CStdString buffer = it->first + ": " + it->second; m_curlHeaderList = g_curlInterface.slist_append(m_curlHeaderList, buffer.c_str()); } // add user defined headers if (m_curlHeaderList && state->m_easyHandle) g_curlInterface.easy_setopt(state->m_easyHandle, CURLOPT_HTTPHEADER, m_curlHeaderList); } void CCurlFile::SetCorrectHeaders(CReadState* state) { CHttpHeader& h = state->m_httpheader; /* workaround for shoutcast server wich doesn't set content type on standard mp3 */ if( h.GetMimeType().IsEmpty() ) { if( !h.GetValue("icy-notice1").IsEmpty() || !h.GetValue("icy-name").IsEmpty() || !h.GetValue("icy-br").IsEmpty() ) h.Parse("Content-Type: audio/mpeg\r\n"); } /* hack for google video */ if ( h.GetMimeType().Equals("text/html") && !h.GetValue("Content-Disposition").IsEmpty() ) { CStdString strValue = h.GetValue("Content-Disposition"); if (strValue.Find("filename=") > -1 && strValue.Find(".flv") > -1) h.Parse("Content-Type: video/flv\r\n"); } } void CCurlFile::ParseAndCorrectUrl(CURL &url2) { CStdString strProtocol = url2.GetTranslatedProtocol(); url2.SetProtocol(strProtocol); if( strProtocol.Equals("ftp") || strProtocol.Equals("ftps") ) { /* this is uggly, depending on from where */ /* we get the link it may or may not be */ /* url encoded. if handed from ftpdirectory */ /* it won't be so let's handle that case */ CStdString partial, filename(url2.GetFileName()); CStdStringArray array; /* our current client doesn't support utf8 */ g_charsetConverter.utf8ToStringCharset(filename); /* TODO: create a tokenizer that doesn't skip empty's */ CUtil::Tokenize(filename, array, "/"); filename.Empty(); for(CStdStringArray::iterator it = array.begin(); it != array.end(); it++) { if(it != array.begin()) filename += "/"; partial = *it; CURL::Encode(partial); filename += partial; } /* make sure we keep slashes */ if(url2.GetFileName().Right(1) == "/") filename += "/"; url2.SetFileName(filename); CStdString options = url2.GetOptions().Mid(1); options.TrimRight('/'); // hack for trailing slashes being added from source m_ftpauth = ""; m_ftpport = ""; m_ftppasvip = false; /* parse options given */ CUtil::Tokenize(options, array, "&"); for(CStdStringArray::iterator it = array.begin(); it != array.end(); it++) { CStdString name, value; int pos = it->Find('='); if(pos >= 0) { name = it->Left(pos); value = it->Mid(pos+1, it->size()); } else { name = (*it); value = ""; } if(name.Equals("auth")) { m_ftpauth = value; if(m_ftpauth.IsEmpty()) m_ftpauth = "any"; } else if(name.Equals("active")) { m_ftpport = value; if(value.IsEmpty()) m_ftpport = "-"; } else if(name.Equals("pasvip")) { if(value == "0") m_ftppasvip = false; else m_ftppasvip = true; } } /* ftp has no options */ url2.SetOptions(""); } else if( strProtocol.Equals("http") || strProtocol.Equals("https")) { if (g_guiSettings.GetBool("network.usehttpproxy") && !g_guiSettings.GetString("network.httpproxyserver").empty() && !g_guiSettings.GetString("network.httpproxyport").empty() && m_proxy.IsEmpty()) { m_proxy = "http://" + g_guiSettings.GetString("network.httpproxyserver"); m_proxy += ":" + g_guiSettings.GetString("network.httpproxyport"); if (g_guiSettings.GetString("network.httpproxyusername").length() > 0 && m_proxyuserpass.IsEmpty()) { m_proxyuserpass = g_guiSettings.GetString("network.httpproxyusername"); m_proxyuserpass += ":" + g_guiSettings.GetString("network.httpproxypassword"); } CLog::Log(LOGDEBUG, "Using proxy %s", m_proxy.c_str()); } // get username and password m_username = url2.GetUserName(); m_password = url2.GetPassWord(); // handle any protocol options CStdString options = url2.GetProtocolOptions(); options.TrimRight('/'); // hack for trailing slashes being added from source if (options.length() > 0) { // clear protocol options url2.SetProtocolOptions(""); // set xbmc headers CStdStringArray array; CUtil::Tokenize(options, array, "&"); for(CStdStringArray::iterator it = array.begin(); it != array.end(); it++) { // parse name, value CStdString name, value; int pos = it->Find('='); if(pos >= 0) { name = it->Left(pos); value = it->Mid(pos+1, it->size()); } else { name = (*it); value = ""; } // url decode value CURL::Decode(value); if(name.Equals("auth")) { m_httpauth = value; if(m_httpauth.IsEmpty()) m_httpauth = "any"; } else if (name.Equals("Referer")) SetReferer(value); else if (name.Equals("User-Agent")) SetUserAgent(value); else if (name.Equals("Cookie")) SetCookie(value); else if (name.Equals("Encoding")) SetContentEncoding(value); else if (name.Equals("noshout") && value.Equals("true")) m_skipshout = true; else SetRequestHeader(name, value); } } } if (m_username.length() > 0 && m_password.length() > 0) m_url = url2.GetWithoutUserDetails(); else m_url = url2.Get(); } bool CCurlFile::Post(const CStdString& strURL, const CStdString& strPostData, CStdString& strHTML) { m_postdata = strPostData; m_postdataset = true; return Service(strURL, strHTML); } bool CCurlFile::Get(const CStdString& strURL, CStdString& strHTML) { m_postdata = ""; m_postdataset = false; return Service(strURL, strHTML); } bool CCurlFile::Service(const CStdString& strURL, CStdString& strHTML) { if (Open(strURL)) { if (ReadData(strHTML)) { Close(); return true; } } Close(); return false; } bool CCurlFile::ReadData(CStdString& strHTML) { int size_read = 0; int data_size = 0; strHTML = ""; char buffer[16384]; while( (size_read = Read(buffer, sizeof(buffer)-1) ) > 0 ) { buffer[size_read] = 0; strHTML.append(buffer, size_read); data_size += size_read; } if (m_state->m_cancelled) return false; return true; } bool CCurlFile::Download(const CStdString& strURL, const CStdString& strFileName, LPDWORD pdwSize) { CLog::Log(LOGINFO, "CCurlFile::Download - %s->%s", strURL.c_str(), strFileName.c_str()); CStdString strData; if (!Get(strURL, strData)) return false; XFILE::CFile file; if (!file.OpenForWrite(strFileName, true)) { CLog::Log(LOGERROR, "CCurlFile::Download - Unable to open file %s: %u", strFileName.c_str(), GetLastError()); return false; } if (strData.size()) file.Write(strData.c_str(), strData.size()); file.Close(); if (pdwSize != NULL) { *pdwSize = strData.size(); } return true; } // Detect whether we are "online" or not! Very simple and dirty! bool CCurlFile::IsInternet(bool checkDNS /* = true */) { CStdString strURL = "http://www.google.com"; if (!checkDNS) strURL = "http://74.125.19.103"; // www.google.com ip bool found = Exists(strURL); Close(); return found; } void CCurlFile::Cancel() { m_state->m_cancelled = true; while (m_opened) Sleep(1); } void CCurlFile::Reset() { m_state->m_cancelled = false; } bool CCurlFile::Open(const CURL& url) { if (!g_application.getNetwork().IsAvailable()) return false; m_opened = true; CURL url2(url); ParseAndCorrectUrl(url2); CLog::Log(LOGDEBUG, "CurlFile::Open(%p) %s", (void*)this, m_url.c_str()); ASSERT(!(!m_state->m_easyHandle ^ !m_state->m_multiHandle)); if( m_state->m_easyHandle == NULL ) g_curlInterface.easy_aquire(url2.GetProtocol(), url2.GetHostName(), &m_state->m_easyHandle, &m_state->m_multiHandle ); // setup common curl options SetCommonOptions(m_state); SetRequestHeaders(m_state); m_state->m_sendRange = m_seekable; m_httpresponse = m_state->Connect(m_bufferSize); if( m_httpresponse < 0 || m_httpresponse >= 400) return false; SetCorrectHeaders(m_state); // since we can't know the stream size up front if we're gzipped/deflated // flag the stream with an unknown file size rather than the compressed // file size. if (m_contentencoding.size() > 0) m_state->m_fileSize = 0; // check if this stream is a shoutcast stream. sometimes checking the protocol line is not enough so examine other headers as well. // shoutcast streams should be handled by FileShoutcast. if ((m_state->m_httpheader.GetProtoLine().Left(3) == "ICY" || !m_state->m_httpheader.GetValue("icy-notice1").IsEmpty() || !m_state->m_httpheader.GetValue("icy-name").IsEmpty() || !m_state->m_httpheader.GetValue("icy-br").IsEmpty()) && !m_skipshout) { CLog::Log(LOGDEBUG,"CCurlFile::Open - File <%s> is a shoutcast stream. Re-opening", m_url.c_str()); throw new CRedirectException(new CShoutcastFile); } m_multisession = false; if(url2.GetProtocol().Equals("http") || url2.GetProtocol().Equals("https")) { m_multisession = true; if(m_state->m_httpheader.GetValue("Server").Find("Portable SDK for UPnP devices") >= 0) { CLog::Log(LOGWARNING, "CCurlFile::Open - Disabling multi session due to broken libupnp server"); m_multisession = false; } } if(m_state->m_httpheader.GetValue("Transfer-Encoding").Equals("chunked")) m_state->m_fileSize = 0; m_seekable = false; if(m_state->m_fileSize > 0) { m_seekable = true; if(url2.GetProtocol().Equals("http") || url2.GetProtocol().Equals("https")) { // if server says explicitly it can't seek, respect that if(m_state->m_httpheader.GetValue("Accept-Ranges").Equals("none")) m_seekable = false; } } char* efurl; if (CURLE_OK == g_curlInterface.easy_getinfo(m_state->m_easyHandle, CURLINFO_EFFECTIVE_URL,&efurl) && efurl) m_url = efurl; return true; } bool CCurlFile::OpenForWrite(const CURL& url, bool bOverWrite) { if(m_opened) return false; if (Exists(url) && !bOverWrite) return false; CURL url2(url); ParseAndCorrectUrl(url2); CLog::Log(LOGDEBUG, "CCurlFile::OpenForWrite(%p) %s", (void*)this, m_url.c_str()); ASSERT(m_state->m_easyHandle == NULL); g_curlInterface.easy_aquire(url2.GetProtocol(), url2.GetHostName(), &m_state->m_easyHandle, &m_state->m_multiHandle); // setup common curl options SetCommonOptions(m_state); SetRequestHeaders(m_state); char* efurl; if (CURLE_OK == g_curlInterface.easy_getinfo(m_state->m_easyHandle, CURLINFO_EFFECTIVE_URL,&efurl) && efurl) m_url = efurl; m_opened = true; m_forWrite = true; m_inError = false; m_writeOffset = 0; ASSERT(m_state->m_multiHandle); SetCommonOptions(m_state); g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_UPLOAD, 1); g_curlInterface.multi_add_handle(m_state->m_multiHandle, m_state->m_easyHandle); m_state->SetReadBuffer(NULL, 0); return true; } int CCurlFile::Write(const void* lpBuf, int64_t uiBufSize) { if (!(m_opened && m_forWrite) || m_inError) return -1; ASSERT(m_state->m_multiHandle); m_state->SetReadBuffer(lpBuf, uiBufSize); m_state->m_isPaused = false; g_curlInterface.easy_pause(m_state->m_easyHandle, CURLPAUSE_CONT); CURLMcode result = CURLM_OK; m_stillRunning = 1; while (m_stillRunning && !m_state->m_isPaused) { while ((result = g_curlInterface.multi_perform(m_state->m_multiHandle, &m_stillRunning)) == CURLM_CALL_MULTI_PERFORM); if (!m_stillRunning) break; if (result != CURLM_OK) { long code; if(g_curlInterface.easy_getinfo(m_state->m_easyHandle, CURLINFO_RESPONSE_CODE, &code) == CURLE_OK ) CLog::Log(LOGERROR, "%s - Unable to write curl resource (%s) - %ld", __FUNCTION__, m_url.c_str(), code); m_inError = true; return -1; } } m_writeOffset += m_state->m_filePos; return m_state->m_filePos; } bool CCurlFile::CReadState::ReadString(char *szLine, int iLineLength) { unsigned int want = (unsigned int)iLineLength; if((m_fileSize == 0 || m_filePos < m_fileSize) && !FillBuffer(want)) return false; // ensure only available data is considered want = XMIN((unsigned int)m_buffer.getMaxReadSize(), want); /* check if we finished prematurely */ if (!m_stillRunning && (m_fileSize == 0 || m_filePos != m_fileSize) && !want) { if (m_fileSize != 0) CLog::Log(LOGWARNING, "%s - Transfer ended before entire file was retrieved pos %"PRId64", size %"PRId64, __FUNCTION__, m_filePos, m_fileSize); return false; } char* pLine = szLine; do { if (!m_buffer.ReadData(pLine, 1)) break; pLine++; } while (((pLine - 1)[0] != '\n') && ((unsigned int)(pLine - szLine) < want)); pLine[0] = 0; m_filePos += (pLine - szLine); return (bool)((pLine - szLine) > 0); } bool CCurlFile::Exists(const CURL& url) { // if file is already running, get info from it if( m_opened ) { CLog::Log(LOGWARNING, "CCurlFile::Exists - Exist called on open file", __FUNCTION__); return true; } CURL url2(url); ParseAndCorrectUrl(url2); ASSERT(m_state->m_easyHandle == NULL); g_curlInterface.easy_aquire(url2.GetProtocol(), url2.GetHostName(), &m_state->m_easyHandle, NULL); SetCommonOptions(m_state); SetRequestHeaders(m_state); g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_TIMEOUT, 5); g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_NOBODY, 1); g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_WRITEDATA, NULL); /* will cause write failure*/ if(url2.GetProtocol() == "ftp") { g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_FILETIME, 1); // nocwd is less standard, will return empty list for non-existed remote dir on some ftp server, avoid it. if (url2.GetFileName().Right(1).Equals("/")) g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_FTP_FILEMETHOD, CURLFTPMETHOD_SINGLECWD); else g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_FTP_FILEMETHOD, CURLFTPMETHOD_NOCWD); } CURLcode result = g_curlInterface.easy_perform(m_state->m_easyHandle); g_curlInterface.easy_release(&m_state->m_easyHandle, NULL); if (result == CURLE_WRITE_ERROR || result == CURLE_OK) return true; if (result == CURLE_HTTP_RETURNED_ERROR) { long code; if(g_curlInterface.easy_getinfo(m_state->m_easyHandle, CURLINFO_RESPONSE_CODE, &code) == CURLE_OK && code != 404 ) CLog::Log(LOGERROR, "CCurlFile::Exists - Failed: HTTP returned error %ld for %s", code, url.Get().c_str()); } else if (result != CURLE_REMOTE_FILE_NOT_FOUND && result != CURLE_FTP_COULDNT_RETR_FILE) { CLog::Log(LOGERROR, "CCurlFile::Exists - Failed: %s(%d) for %s", g_curlInterface.easy_strerror(result), result, url.Get().c_str()); } errno = ENOENT; return false; } int64_t CCurlFile::Seek(int64_t iFilePosition, int iWhence) { int64_t nextPos = m_state->m_filePos; switch(iWhence) { case SEEK_SET: nextPos = iFilePosition; break; case SEEK_CUR: nextPos += iFilePosition; break; case SEEK_END: if (m_state->m_fileSize) nextPos = m_state->m_fileSize + iFilePosition; else return -1; break; default: return -1; } // We can't seek beyond EOF if (m_state->m_fileSize && nextPos > m_state->m_fileSize) return -1; if(m_state->Seek(nextPos)) return nextPos; if(!m_seekable) return -1; CReadState* oldstate = NULL; if(m_multisession) { CURL url(m_url); oldstate = m_state; m_state = new CReadState(); g_curlInterface.easy_aquire(url.GetProtocol(), url.GetHostName(), &m_state->m_easyHandle, &m_state->m_multiHandle ); // setup common curl options SetCommonOptions(m_state); } else m_state->Disconnect(); /* caller might have changed some headers (needed for daap)*/ SetRequestHeaders(m_state); m_state->m_filePos = nextPos; m_state->m_sendRange = true; if (oldstate) m_state->m_fileSize = oldstate->m_fileSize; long response = m_state->Connect(m_bufferSize); if(response < 0 && (m_state->m_fileSize == 0 || m_state->m_fileSize != m_state->m_filePos)) { m_seekable = false; if(oldstate) { delete m_state; m_state = oldstate; } return -1; } SetCorrectHeaders(m_state); delete oldstate; return m_state->m_filePos; } int64_t CCurlFile::GetLength() { if (!m_opened) return 0; return m_state->m_fileSize; } int64_t CCurlFile::GetPosition() { if (!m_opened) return 0; return m_state->m_filePos; } int CCurlFile::Stat(const CURL& url, struct __stat64* buffer) { // if file is already running, get info from it if( m_opened ) { CLog::Log(LOGWARNING, "CCurlFile::Stat - Stat called on open file %s", __FUNCTION__, url.Get().c_str()); if (buffer) { memset(buffer, 0, sizeof(struct __stat64)); buffer->st_size = GetLength(); buffer->st_mode = _S_IFREG; } return 0; } CURL url2(url); ParseAndCorrectUrl(url2); ASSERT(m_state->m_easyHandle == NULL); g_curlInterface.easy_aquire(url2.GetProtocol(), url2.GetHostName(), &m_state->m_easyHandle, NULL); SetCommonOptions(m_state); SetRequestHeaders(m_state); g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_TIMEOUT, g_advancedSettings.m_curlconnecttimeout); g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_NOBODY, 1); g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_WRITEDATA, NULL); /* will cause write failure*/ g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_FILETIME, 1); if(url2.GetProtocol() == "ftp") { // nocwd is less standard, will return empty list for non-existed remote dir on some ftp server, avoid it. if (url2.GetFileName().Right(1).Equals("/")) g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_FTP_FILEMETHOD, CURLFTPMETHOD_SINGLECWD); else g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_FTP_FILEMETHOD, CURLFTPMETHOD_NOCWD); } CURLcode result = g_curlInterface.easy_perform(m_state->m_easyHandle); if(result == CURLE_HTTP_RETURNED_ERROR) { long code; if(g_curlInterface.easy_getinfo(m_state->m_easyHandle, CURLINFO_RESPONSE_CODE, &code) == CURLE_OK && code == 404 ) return -1; } if(result == CURLE_GOT_NOTHING || result == CURLE_HTTP_RETURNED_ERROR || result == CURLE_RECV_ERROR /* some silly shoutcast servers */ ) { /* some http servers and shoutcast servers don't give us any data on a head request */ /* request normal and just fail out, it's their loss */ /* somehow curl doesn't reset CURLOPT_NOBODY properly so reset everything */ SetCommonOptions(m_state); SetRequestHeaders(m_state); g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_TIMEOUT, g_advancedSettings.m_curlconnecttimeout); g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_RANGE, "0-0"); g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_WRITEDATA, NULL); /* will cause write failure*/ g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_FILETIME, 1); result = g_curlInterface.easy_perform(m_state->m_easyHandle); } if( result == CURLE_HTTP_RANGE_ERROR ) { /* crap can't use the range option, disable it and try again */ g_curlInterface.easy_setopt(m_state->m_easyHandle, CURLOPT_RANGE, NULL); result = g_curlInterface.easy_perform(m_state->m_easyHandle); } if( result != CURLE_WRITE_ERROR && result != CURLE_OK ) { g_curlInterface.easy_release(&m_state->m_easyHandle, NULL); errno = ENOENT; CLog::Log(LOGERROR, "CCurlFile::Stat - Failed: %s(%d) for %s", g_curlInterface.easy_strerror(result), result, url.Get().c_str()); return -1; } double length; result = g_curlInterface.easy_getinfo(m_state->m_easyHandle, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &length); if (result != CURLE_OK || length < 0.0) { if (url.GetProtocol() == "ftp") { g_curlInterface.easy_release(&m_state->m_easyHandle, NULL); CLog::Log(LOGNOTICE, "CCurlFile::Stat - Content length failed: %s(%d) for %s", g_curlInterface.easy_strerror(result), result, url.Get().c_str()); errno = ENOENT; return -1; } else length = 0.0; } SetCorrectHeaders(m_state); if(buffer) { char *content; result = g_curlInterface.easy_getinfo(m_state->m_easyHandle, CURLINFO_CONTENT_TYPE, &content); if (result != CURLE_OK) { CLog::Log(LOGNOTICE, "CCurlFile::Stat - Content type failed: %s(%d) for %s", g_curlInterface.easy_strerror(result), result, url.Get().c_str()); g_curlInterface.easy_release(&m_state->m_easyHandle, NULL); errno = ENOENT; return -1; } else { memset(buffer, 0, sizeof(struct __stat64)); buffer->st_size = (int64_t)length; if(content && strstr(content, "text/html")) //consider html files directories buffer->st_mode = _S_IFDIR; else buffer->st_mode = _S_IFREG; } long filetime; result = g_curlInterface.easy_getinfo(m_state->m_easyHandle, CURLINFO_FILETIME, &filetime); if (result != CURLE_OK) { CLog::Log(LOGNOTICE, "CCurlFile::Stat - Filetime failed: %s(%d) for %s", g_curlInterface.easy_strerror(result), result, url.Get().c_str()); } else { if (filetime != -1) buffer->st_mtime = filetime; } } g_curlInterface.easy_release(&m_state->m_easyHandle, NULL); return 0; } unsigned int CCurlFile::CReadState::Read(void* lpBuf, int64_t uiBufSize) { /* only request 1 byte, for truncated reads (only if not eof) */ if((m_fileSize == 0 || m_filePos < m_fileSize) && !FillBuffer(1)) return 0; /* ensure only available data is considered */ unsigned int want = (unsigned int)XMIN(m_buffer.getMaxReadSize(), uiBufSize); /* xfer data to caller */ if (m_buffer.ReadData((char *)lpBuf, want)) { m_filePos += want; return want; } /* check if we finished prematurely */ if (!m_stillRunning && (m_fileSize == 0 || m_filePos != m_fileSize)) { CLog::Log(LOGWARNING, "%s - Transfer ended before entire file was retrieved pos %"PRId64", size %"PRId64, __FUNCTION__, m_filePos, m_fileSize); return 0; } return 0; } /* use to attempt to fill the read buffer up to requested number of bytes */ bool CCurlFile::CReadState::FillBuffer(unsigned int want) { int retry = 0; fd_set fdread; fd_set fdwrite; fd_set fdexcep; // only attempt to fill buffer if transactions still running and buffer // doesnt exceed required size already while ((unsigned int)m_buffer.getMaxReadSize() < want && m_buffer.getMaxWriteSize() > 0 ) { if (m_cancelled) return false; /* if there is data in overflow buffer, try to use that first */ if (m_overflowSize) { unsigned amount = XMIN((unsigned int)m_buffer.getMaxWriteSize(), m_overflowSize); m_buffer.WriteData(m_overflowBuffer, amount); if (amount < m_overflowSize) memcpy(m_overflowBuffer, m_overflowBuffer+amount,m_overflowSize-amount); m_overflowSize -= amount; m_overflowBuffer = (char*)realloc_simple(m_overflowBuffer, m_overflowSize); continue; } CURLMcode result = g_curlInterface.multi_perform(m_multiHandle, &m_stillRunning); if (!m_stillRunning) { if (result == CURLM_OK) { /* if we still have stuff in buffer, we are fine */ if (m_buffer.getMaxReadSize()) return true; /* verify that we are actually okey */ int msgs; CURLcode CURLresult = CURLE_OK; CURLMsg* msg; while ((msg = g_curlInterface.multi_info_read(m_multiHandle, &msgs))) { if (msg->msg == CURLMSG_DONE) { if (msg->data.result == CURLE_OK) return true; CLog::Log(LOGERROR, "CCurlFile::FillBuffer - Failed: %s(%d)", g_curlInterface.easy_strerror(msg->data.result), msg->data.result); // We need to check the result here as we don't want to retry on every error if ( (msg->data.result == CURLE_OPERATION_TIMEDOUT || msg->data.result == CURLE_PARTIAL_FILE || msg->data.result == CURLE_COULDNT_CONNECT || msg->data.result == CURLE_RECV_ERROR) && !m_bFirstLoop) CURLresult = msg->data.result; else if ( (msg->data.result == CURLE_HTTP_RANGE_ERROR || msg->data.result == CURLE_HTTP_RETURNED_ERROR) && m_bFirstLoop && m_filePos == 0 && m_sendRange) { // If server returns a range or http error, retry with range disabled CURLresult = msg->data.result; m_sendRange = false; } else return false; } } // Don't retry when we didn't "see" any error if (CURLresult == CURLE_OK) return false; // Close handle if (m_multiHandle && m_easyHandle) g_curlInterface.multi_remove_handle(m_multiHandle, m_easyHandle); // Reset all the stuff like we would in Disconnect() m_buffer.Clear(); free(m_overflowBuffer); m_overflowBuffer = NULL; m_overflowSize = 0; // If we got here something is wrong if (++retry > g_advancedSettings.m_curlretries) { CLog::Log(LOGERROR, "CCurlFile::FillBuffer - Reconnect failed!"); // Reset the rest of the variables like we would in Disconnect() m_filePos = 0; m_fileSize = 0; m_bufferSize = 0; return false; } CLog::Log(LOGNOTICE, "CCurlFile::FillBuffer - Reconnect, (re)try %i", retry); // Connect + seek to current position (again) SetResume(); g_curlInterface.multi_add_handle(m_multiHandle, m_easyHandle); // Return to the beginning of the loop: continue; } return false; } // We've finished out first loop if(m_bFirstLoop && m_buffer.getMaxReadSize() > 0) m_bFirstLoop = false; switch (result) { case CURLM_OK: { int maxfd = -1; FD_ZERO(&fdread); FD_ZERO(&fdwrite); FD_ZERO(&fdexcep); // get file descriptors from the transfers g_curlInterface.multi_fdset(m_multiHandle, &fdread, &fdwrite, &fdexcep, &maxfd); long timeout = 0; if (CURLM_OK != g_curlInterface.multi_timeout(m_multiHandle, &timeout) || timeout == -1) timeout = 200; struct timeval t = { timeout / 1000, (timeout % 1000) * 1000 }; /* Wait until data is available or a timeout occurs. We call dllselect(maxfd + 1, ...), specially in case of (maxfd == -1), we call dllselect(0, ...), which is basically equal to sleep. */ if (SOCKET_ERROR == dllselect(maxfd + 1, &fdread, &fdwrite, &fdexcep, &t)) { CLog::Log(LOGERROR, "CCurlFile::FillBuffer - Failed with socket error"); return false; } } break; case CURLM_CALL_MULTI_PERFORM: { // we don't keep calling here as that can easily overwrite our buffer which we want to avoid // docs says we should call it soon after, but aslong as we are reading data somewhere // this aught to be soon enough. should stay in socket otherwise continue; } break; default: { CLog::Log(LOGERROR, "CCurlFile::FillBuffer - Multi perform failed with code %d, aborting", result); return false; } break; } } return true; } void CCurlFile::CReadState::SetReadBuffer(const void* lpBuf, int64_t uiBufSize) { m_readBuffer = (char*)lpBuf; m_fileSize = uiBufSize; m_filePos = 0; } void CCurlFile::ClearRequestHeaders() { m_requestheaders.clear(); } void CCurlFile::SetRequestHeader(CStdString header, CStdString value) { m_requestheaders[header] = value; } void CCurlFile::SetRequestHeader(CStdString header, long value) { CStdString buffer; buffer.Format("%ld", value); m_requestheaders[header] = buffer; } /* STATIC FUNCTIONS */ bool CCurlFile::GetHttpHeader(const CURL &url, CHttpHeader &headers) { try { CCurlFile file; if(file.Stat(url, NULL) == 0) { headers = file.GetHttpHeader(); return true; } return false; } catch(...) { CLog::Log(LOGERROR, "%s - Exception thrown while trying to retrieve header url: %s", __FUNCTION__, url.Get().c_str()); return false; } } bool CCurlFile::GetMimeType(const CURL &url, CStdString &content, CStdString useragent) { CCurlFile file; if (!useragent.IsEmpty()) file.SetUserAgent(useragent); struct __stat64 buffer; if( file.Stat(url, &buffer) == 0 ) { if (buffer.st_mode == _S_IFDIR) content = "x-directory/normal"; else content = file.GetMimeType(); CLog::Log(LOGDEBUG, "CCurlFile::GetMimeType - %s -> %s", url.Get().c_str(), content.c_str()); return true; } CLog::Log(LOGDEBUG, "CCurlFile::GetMimeType - %s -> failed", url.Get().c_str()); content = ""; return false; } int CCurlFile::IoControl(EIoControl request, void* param) { if(request == IOCTRL_SEEK_POSSIBLE) return m_seekable ? 1 : 0; return -1; }