/*
* 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 "system.h"
#include "URL.h"
#include "utils/RegExp.h"
#include "Util.h"
#include "FileSystem/File.h"
#include "FileItem.h"
#include "FileSystem/StackDirectory.h"
#include "utils/URIUtils.h"
#include "utils/log.h"
#include
using namespace std;
CStdString URLEncodeInline(const CStdString& strData)
{
CStdString buffer = strData;
CURL::Encode(buffer);
return buffer;
}
CURL::CURL(const CStdString& strURL1)
{
Parse(strURL1);
}
CURL::CURL()
{
m_iPort = 0;
}
CURL::~CURL()
{
}
void CURL::Reset()
{
m_strHostName.clear();
m_strDomain.clear();
m_strUserName.clear();
m_strPassword.clear();
m_strShareName.clear();
m_strFileName.clear();
m_strProtocol.clear();
m_strFileType.clear();
m_iPort = 0;
}
void CURL::Parse(const CStdString& strURL1)
{
Reset();
// start by validating the path
CStdString strURL = CUtil::ValidatePath(strURL1);
// strURL can be one of the following:
// format 1: protocol://[username:password]@hostname[:port]/directoryandfile
// format 2: protocol://file
// format 3: drive:directoryandfile
//
// first need 2 check if this is a protocol or just a normal drive & path
if (!strURL.size()) return ;
if (strURL.Equals("?", true)) return;
// form is format 1 or 2
// format 1: protocol://[domain;][username:password]@hostname[:port]/directoryandfile
// format 2: protocol://file
// decode protocol
int iPos = strURL.Find("://");
if (iPos < 0)
{
// This is an ugly hack that needs some work.
// example: filename /foo/bar.zip/alice.rar/bob.avi
// This should turn into zip://rar:///foo/bar.zip/alice.rar/bob.avi
iPos = 0;
while (1)
{
iPos = strURL.Find(".zip/", iPos);
int extLen = 3;
if (iPos < 0)
{
/* set filename and update extension*/
SetFileName(strURL);
return ;
}
iPos += extLen + 1;
CStdString archiveName = strURL.Left(iPos);
struct __stat64 s;
if (XFILE::CFile::Stat(archiveName, &s) == 0)
{
#ifdef _LINUX
if (!S_ISDIR(s.st_mode))
#else
if (!(s.st_mode & S_IFDIR))
#endif
{
CURL::Encode(archiveName);
CURL c((CStdString)"zip" + "://" + archiveName + '/' + strURL.Right(strURL.size() - iPos - 1));
*this = c;
return;
}
}
}
}
else
{
SetProtocol(strURL.Left(iPos));
iPos += 3;
}
// virtual protocols
// why not handle all format 2 (protocol://file) style urls here?
// ones that come to mind are iso9660, cdda, musicdb, etc.
// they are all local protocols and have no server part, port number, special options, etc.
// this removes the need for special handling below.
if (
m_strProtocol.Equals("stack") ||
m_strProtocol.Equals("virtualpath") ||
m_strProtocol.Equals("multipath") ||
m_strProtocol.Equals("filereader") ||
m_strProtocol.Equals("special")
)
{
SetFileName(strURL.Mid(iPos));
return;
}
// check for username/password - should occur before first /
if (iPos == -1) iPos = 0;
// for protocols supporting options, chop that part off here
// maybe we should invert this list instead?
int iEnd = strURL.length();
const char* sep = NULL;
CStdString strProtocol2 = GetTranslatedProtocol();
if(m_strProtocol.Equals("rss"))
sep = "?";
else
if(strProtocol2.Equals("http")
|| strProtocol2.Equals("https")
|| strProtocol2.Equals("plugin")
|| strProtocol2.Equals("hdhomerun")
|| strProtocol2.Equals("rtsp")
|| strProtocol2.Equals("zip"))
sep = "?;#|";
else if(strProtocol2.Equals("ftp")
|| strProtocol2.Equals("ftps"))
sep = "?;";
if(sep)
{
int iOptions = strURL.find_first_of(sep, iPos);
if (iOptions >= 0 )
{
// we keep the initial char as it can be any of the above
int iProto = strURL.find_first_of("|",iOptions);
if (iProto >= 0)
{
m_strProtocolOptions = strURL.substr(iProto+1);
m_strOptions = strURL.substr(iOptions,iProto-iOptions);
}
else
m_strOptions = strURL.substr(iOptions);
iEnd = iOptions;
}
}
int iSlash = strURL.Find("/", iPos);
if(iSlash >= iEnd)
iSlash = -1; // was an invalid slash as it was contained in options
if( !m_strProtocol.Equals("iso9660") )
{
int iAlphaSign = strURL.Find("@", iPos);
if (iAlphaSign >= 0 && iAlphaSign < iEnd && (iAlphaSign < iSlash || iSlash < 0))
{
// username/password found
CStdString strUserNamePassword = strURL.Mid(iPos, iAlphaSign - iPos);
// first extract domain, if protocol is smb
if (m_strProtocol.Equals("smb"))
{
int iSemiColon = strUserNamePassword.Find(";");
if (iSemiColon >= 0)
{
m_strDomain = strUserNamePassword.Left(iSemiColon);
strUserNamePassword.Delete(0, iSemiColon + 1);
}
}
// username:password
int iColon = strUserNamePassword.Find(":");
if (iColon >= 0)
{
m_strUserName = strUserNamePassword.Left(iColon);
iColon++;
m_strPassword = strUserNamePassword.Right(strUserNamePassword.size() - iColon);
}
// username
else
{
m_strUserName = strUserNamePassword;
}
iPos = iAlphaSign + 1;
iSlash = strURL.Find("/", iAlphaSign);
if(iSlash >= iEnd)
iSlash = -1;
}
}
// detect hostname:port/
if (iSlash < 0)
{
CStdString strHostNameAndPort = strURL.Mid(iPos, iEnd - iPos);
int iColon = strHostNameAndPort.Find(":");
if (iColon >= 0)
{
m_strHostName = strHostNameAndPort.Left(iColon);
iColon++;
CStdString strPort = strHostNameAndPort.Right(strHostNameAndPort.size() - iColon);
m_iPort = atoi(strPort.c_str());
}
else
{
m_strHostName = strHostNameAndPort;
}
}
else
{
CStdString strHostNameAndPort = strURL.Mid(iPos, iSlash - iPos);
int iColon = strHostNameAndPort.Find(":");
if (iColon >= 0)
{
m_strHostName = strHostNameAndPort.Left(iColon);
iColon++;
CStdString strPort = strHostNameAndPort.Right(strHostNameAndPort.size() - iColon);
m_iPort = atoi(strPort.c_str());
}
else
{
m_strHostName = strHostNameAndPort;
}
iPos = iSlash + 1;
if (iEnd > iPos)
{
m_strFileName = strURL.Mid(iPos, iEnd - iPos);
iSlash = m_strFileName.Find("/");
if(iSlash < 0)
m_strShareName = m_strFileName;
else
m_strShareName = m_strFileName.Left(iSlash);
}
}
// iso9960 doesnt have an hostname;-)
if (m_strProtocol.CompareNoCase("iso9660") == 0
|| m_strProtocol.CompareNoCase("musicdb") == 0
|| m_strProtocol.CompareNoCase("videodb") == 0
|| m_strProtocol.CompareNoCase("lastfm") == 0
|| m_strProtocol.Left(3).CompareNoCase("mem") == 0)
{
if (m_strHostName != "" && m_strFileName != "")
{
CStdString strFileName = m_strFileName;
m_strFileName.Format("%s/%s", m_strHostName.c_str(), strFileName.c_str());
m_strHostName = "";
}
else
{
if (!m_strHostName.IsEmpty() && strURL[iEnd-1]=='/')
m_strFileName = m_strHostName + "/";
else
m_strFileName = m_strHostName;
m_strHostName = "";
}
}
m_strFileName.Replace("\\", "/");
/* update extension */
SetFileName(m_strFileName);
/* decode urlencoding on this stuff */
if( m_strProtocol.Equals("rar") || m_strProtocol.Equals("zip") || m_strProtocol.Equals("musicsearch"))
{
CURL::Decode(m_strHostName);
// Validate it as it is likely to contain a filename
SetHostName(CUtil::ValidatePath(m_strHostName));
}
CURL::Decode(m_strUserName);
CURL::Decode(m_strPassword);
}
void CURL::SetFileName(const CStdString& strFileName)
{
m_strFileName = strFileName;
int slash = m_strFileName.find_last_of(GetDirectorySeparator());
int period = m_strFileName.find_last_of('.');
if(period != -1 && (slash == -1 || period > slash))
m_strFileType = m_strFileName.substr(period+1);
else
m_strFileType = "";
m_strFileType.Normalize();
}
void CURL::SetHostName(const CStdString& strHostName)
{
m_strHostName = strHostName;
}
void CURL::SetUserName(const CStdString& strUserName)
{
m_strUserName = strUserName;
}
void CURL::SetPassword(const CStdString& strPassword)
{
m_strPassword = strPassword;
}
void CURL::SetProtocol(const CStdString& strProtocol)
{
m_strProtocol = strProtocol;
m_strProtocol.ToLower();
}
void CURL::SetOptions(const CStdString& strOptions)
{
m_strOptions.Empty();
if( strOptions.length() > 0)
{
if( strOptions[0] == '?' || strOptions[0] == '#' || strOptions[0] == ';' || strOptions.Find("xml") >=0 )
{
m_strOptions = strOptions;
}
else
CLog::Log(LOGWARNING, "%s - Invalid options specified for url %s", __FUNCTION__, strOptions.c_str());
}
}
void CURL::SetProtocolOptions(const CStdString& strOptions)
{
m_strProtocolOptions = strOptions;
}
void CURL::SetPort(int port)
{
m_iPort = port;
}
bool CURL::HasPort() const
{
return (m_iPort != 0);
}
int CURL::GetPort() const
{
return m_iPort;
}
const CStdString& CURL::GetHostName() const
{
return m_strHostName;
}
const CStdString& CURL::GetShareName() const
{
return m_strShareName;
}
const CStdString& CURL::GetDomain() const
{
return m_strDomain;
}
const CStdString& CURL::GetUserName() const
{
return m_strUserName;
}
const CStdString& CURL::GetPassWord() const
{
return m_strPassword;
}
const CStdString& CURL::GetFileName() const
{
return m_strFileName;
}
const CStdString& CURL::GetProtocol() const
{
return m_strProtocol;
}
const CStdString CURL::GetTranslatedProtocol() const
{
if (m_strProtocol == "ftpx")
return "ftp";
if (m_strProtocol == "shout"
|| m_strProtocol == "daap"
|| m_strProtocol == "dav"
|| m_strProtocol == "tuxbox"
|| m_strProtocol == "lastfm"
|| m_strProtocol == "mms"
|| m_strProtocol == "rss")
return "http";
if (m_strProtocol == "davs")
return "https";
return m_strProtocol;
}
const CStdString& CURL::GetFileType() const
{
return m_strFileType;
}
const CStdString& CURL::GetOptions() const
{
return m_strOptions;
}
const CStdString& CURL::GetProtocolOptions() const
{
return m_strProtocolOptions;
}
const CStdString CURL::GetFileNameWithoutPath() const
{
// *.zip and *.rar store the actual zip/rar path in the hostname of the url
if ((m_strProtocol == "rar" || m_strProtocol == "zip") && m_strFileName.IsEmpty())
return URIUtils::GetFileName(m_strHostName);
// otherwise, we've already got the filepath, so just grab the filename portion
CStdString file(m_strFileName);
URIUtils::RemoveSlashAtEnd(file);
return URIUtils::GetFileName(file);
}
char CURL::GetDirectorySeparator() const
{
if ( IsLocal() )
return '\\';
else
return '/';
}
CStdString CURL::Get() const
{
unsigned int sizeneed = m_strProtocol.length()
+ m_strDomain.length()
+ m_strUserName.length()
+ m_strPassword.length()
+ m_strHostName.length()
+ m_strFileName.length()
+ m_strOptions.length()
+ m_strProtocolOptions.length()
+ 10;
if (m_strProtocol == "")
return m_strFileName;
CStdString strURL;
strURL.reserve(sizeneed);
strURL = GetWithoutFilename();
strURL += m_strFileName;
if( m_strOptions.length() > 0 )
strURL += m_strOptions;
if (m_strProtocolOptions.length() > 0)
strURL += "|"+m_strProtocolOptions;
return strURL;
}
CStdString CURL::GetWithoutUserDetails() const
{
CStdString strURL;
if (m_strProtocol.Equals("stack"))
{
CFileItemList items;
CStdString strURL2;
strURL2 = Get();
XFILE::CStackDirectory dir;
dir.GetDirectory(strURL2,items);
vector newItems;
for (int i=0;iGetPath());
items[i]->SetPath(url.GetWithoutUserDetails());
newItems.push_back(items[i]->GetPath());
}
dir.ConstructStackPath(newItems,strURL);
return strURL;
}
unsigned int sizeneed = m_strProtocol.length()
+ m_strDomain.length()
+ m_strHostName.length()
+ m_strFileName.length()
+ m_strOptions.length()
+ m_strProtocolOptions.length()
+ 10;
strURL.reserve(sizeneed);
if (m_strProtocol == "")
return m_strFileName;
strURL = m_strProtocol;
strURL += "://";
if (m_strHostName != "")
{
if (m_strProtocol.Equals("rar") || m_strProtocol.Equals("zip"))
strURL += CURL(m_strHostName).GetWithoutUserDetails();
else
strURL += m_strHostName;
if ( HasPort() )
{
CStdString strPort;
strPort.Format("%i", m_iPort);
strURL += ":";
strURL += strPort;
}
strURL += "/";
}
strURL += m_strFileName;
if( m_strOptions.length() > 0 )
strURL += m_strOptions;
if( m_strProtocolOptions.length() > 0 )
strURL += "|"+m_strProtocolOptions;
return strURL;
}
CStdString CURL::GetWithoutFilename() const
{
if (m_strProtocol == "")
return "";
unsigned int sizeneed = m_strProtocol.length()
+ m_strDomain.length()
+ m_strUserName.length()
+ m_strPassword.length()
+ m_strHostName.length()
+ 10;
CStdString strURL;
strURL.reserve(sizeneed);
strURL = m_strProtocol;
strURL += "://";
if (m_strDomain != "")
{
strURL += m_strDomain;
strURL += ";";
}
else if (m_strUserName != "")
{
strURL += URLEncodeInline(m_strUserName);
if (m_strPassword != "")
{
strURL += ":";
strURL += URLEncodeInline(m_strPassword);
}
strURL += "@";
}
else if (m_strDomain != "")
strURL += "@";
if (m_strHostName != "")
{
if( m_strProtocol.Equals("rar") || m_strProtocol.Equals("zip") || m_strProtocol.Equals("musicsearch"))
strURL += URLEncodeInline(m_strHostName);
else
strURL += m_strHostName;
if (HasPort())
{
CStdString strPort;
strPort.Format("%i", m_iPort);
strURL += ":";
strURL += strPort;
}
strURL += "/";
}
return strURL;
}
bool CURL::IsLocal() const
{
return m_strProtocol.IsEmpty();
}
bool CURL::IsFileOnly(const CStdString &url)
{
return url.find_first_of("/\\") == CStdString::npos;
}
bool CURL::IsFullPath(const CStdString &url)
{
if (url.size() && url[0] == '/') return true; // /foo/bar.ext
if (url.Find("://") >= 0) return true; // foo://bar.ext
if (url.size() > 1 && url[1] == ':') return true; // c:\\foo\\bar\\bar.ext
return false;
}
void CURL::Decode(CStdString& strURLData)
//modified to be more accomodating - if a non hex value follows a % take the characters directly and don't raise an error.
// However % characters should really be escaped like any other non safe character (www.rfc-editor.org/rfc/rfc1738.txt)
{
CStdString strResult;
/* result will always be less than source */
strResult.reserve( strURLData.length() );
for (unsigned int i = 0; i < strURLData.size(); ++i)
{
int kar = (unsigned char)strURLData[i];
if (kar == '+') strResult += ' ';
else if (kar == '%')
{
if (i < strURLData.size() - 2)
{
CStdString strTmp;
strTmp.assign(strURLData.substr(i + 1, 2));
int dec_num=-1;
sscanf(strTmp,"%x",(unsigned int *)&dec_num);
if (dec_num<0 || dec_num>255)
strResult += kar;
else
{
strResult += (char)dec_num;
i += 2;
}
}
else
strResult += kar;
}
else strResult += kar;
}
strURLData = strResult;
}
void CURL::Encode(CStdString& strURLData)
{
CStdString strResult;
/* wonder what a good value is here is, depends on how often it occurs */
strResult.reserve( strURLData.length() * 2 );
for (int i = 0; i < (int)strURLData.size(); ++i)
{
int kar = (unsigned char)strURLData[i];
//if (kar == ' ') strResult += '+';
if (isalnum(kar)) strResult += kar;
else
{
CStdString strTmp;
strTmp.Format("%%%02.2x", kar);
strResult += strTmp;
}
}
strURLData = strResult;
}