diff options
Diffstat (limited to 'xbmc/utils/URIUtils.cpp')
-rw-r--r-- | xbmc/utils/URIUtils.cpp | 1493 |
1 files changed, 1493 insertions, 0 deletions
diff --git a/xbmc/utils/URIUtils.cpp b/xbmc/utils/URIUtils.cpp new file mode 100644 index 0000000..b2b9b23 --- /dev/null +++ b/xbmc/utils/URIUtils.cpp @@ -0,0 +1,1493 @@ +/* + * Copyright (C) 2005-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "network/Network.h" +#include "URIUtils.h" +#include "FileItem.h" +#include "filesystem/MultiPathDirectory.h" +#include "filesystem/SpecialProtocol.h" +#include "filesystem/StackDirectory.h" +#include "network/DNSNameCache.h" +#include "pvr/channels/PVRChannelsPath.h" +#include "settings/AdvancedSettings.h" +#include "URL.h" +#include "utils/FileExtensionProvider.h" +#include "ServiceBroker.h" +#include "StringUtils.h" +#include "utils/log.h" + +#if defined(TARGET_WINDOWS) +#include "platform/win32/CharsetConverter.h" +#endif + +#include <algorithm> +#include <cassert> +#include <netinet/in.h> +#include <arpa/inet.h> + +using namespace PVR; +using namespace XFILE; + +const CAdvancedSettings* URIUtils::m_advancedSettings = nullptr; + +void URIUtils::RegisterAdvancedSettings(const CAdvancedSettings& advancedSettings) +{ + m_advancedSettings = &advancedSettings; +} + +void URIUtils::UnregisterAdvancedSettings() +{ + m_advancedSettings = nullptr; +} + +/* returns filename extension including period of filename */ +std::string URIUtils::GetExtension(const CURL& url) +{ + return URIUtils::GetExtension(url.GetFileName()); +} + +std::string URIUtils::GetExtension(const std::string& strFileName) +{ + if (IsURL(strFileName)) + { + CURL url(strFileName); + return GetExtension(url.GetFileName()); + } + + size_t period = strFileName.find_last_of("./\\"); + if (period == std::string::npos || strFileName[period] != '.') + return std::string(); + + return strFileName.substr(period); +} + +bool URIUtils::HasPluginPath(const CFileItem& item) +{ + return IsPlugin(item.GetPath()) || IsPlugin(item.GetDynPath()); +} + +bool URIUtils::HasExtension(const std::string& strFileName) +{ + if (IsURL(strFileName)) + { + CURL url(strFileName); + return HasExtension(url.GetFileName()); + } + + size_t iPeriod = strFileName.find_last_of("./\\"); + return iPeriod != std::string::npos && strFileName[iPeriod] == '.'; +} + +bool URIUtils::HasExtension(const CURL& url, const std::string& strExtensions) +{ + return HasExtension(url.GetFileName(), strExtensions); +} + +bool URIUtils::HasExtension(const std::string& strFileName, const std::string& strExtensions) +{ + if (IsURL(strFileName)) + { + const CURL url(strFileName); + return HasExtension(url.GetFileName(), strExtensions); + } + + const size_t pos = strFileName.find_last_of("./\\"); + if (pos == std::string::npos || strFileName[pos] != '.') + return false; + + const std::string extensionLower = StringUtils::ToLower(strFileName.substr(pos)); + + const std::vector<std::string> extensionsLower = + StringUtils::Split(StringUtils::ToLower(strExtensions), '|'); + + for (const auto& ext : extensionsLower) + { + if (StringUtils::EndsWith(ext, extensionLower)) + return true; + } + + return false; +} + +void URIUtils::RemoveExtension(std::string& strFileName) +{ + if(IsURL(strFileName)) + { + CURL url(strFileName); + strFileName = url.GetFileName(); + RemoveExtension(strFileName); + url.SetFileName(strFileName); + strFileName = url.Get(); + return; + } + + size_t period = strFileName.find_last_of("./\\"); + if (period != std::string::npos && strFileName[period] == '.') + { + std::string strExtension = strFileName.substr(period); + StringUtils::ToLower(strExtension); + strExtension += "|"; + + std::string strFileMask; + strFileMask = CServiceBroker::GetFileExtensionProvider().GetPictureExtensions(); + strFileMask += "|" + CServiceBroker::GetFileExtensionProvider().GetMusicExtensions(); + strFileMask += "|" + CServiceBroker::GetFileExtensionProvider().GetVideoExtensions(); + strFileMask += "|" + CServiceBroker::GetFileExtensionProvider().GetSubtitleExtensions(); +#if defined(TARGET_DARWIN) + strFileMask += "|.py|.xml|.milk|.xbt|.cdg|.app|.applescript|.workflow"; +#else + strFileMask += "|.py|.xml|.milk|.xbt|.cdg"; +#endif + strFileMask += "|"; + + if (strFileMask.find(strExtension) != std::string::npos) + strFileName.erase(period); + } +} + +std::string URIUtils::ReplaceExtension(const std::string& strFile, + const std::string& strNewExtension) +{ + if(IsURL(strFile)) + { + CURL url(strFile); + url.SetFileName(ReplaceExtension(url.GetFileName(), strNewExtension)); + return url.Get(); + } + + std::string strChangedFile; + std::string strExtension = GetExtension(strFile); + if ( strExtension.size() ) + { + strChangedFile = strFile.substr(0, strFile.size() - strExtension.size()) ; + strChangedFile += strNewExtension; + } + else + { + strChangedFile = strFile; + strChangedFile += strNewExtension; + } + return strChangedFile; +} + +std::string URIUtils::GetFileName(const CURL& url) +{ + return GetFileName(url.GetFileName()); +} + +/* returns a filename given an url */ +/* handles both / and \, and options in urls*/ +std::string URIUtils::GetFileName(const std::string& strFileNameAndPath) +{ + if(IsURL(strFileNameAndPath)) + { + CURL url(strFileNameAndPath); + return GetFileName(url.GetFileName()); + } + + /* find the last slash */ + const size_t slash = strFileNameAndPath.find_last_of("/\\"); + return strFileNameAndPath.substr(slash+1); +} + +void URIUtils::Split(const std::string& strFileNameAndPath, + std::string& strPath, std::string& strFileName) +{ + //Splits a full filename in path and file. + //ex. smb://computer/share/directory/filename.ext -> strPath:smb://computer/share/directory/ and strFileName:filename.ext + //Trailing slash will be preserved + strFileName = ""; + strPath = ""; + int i = strFileNameAndPath.size() - 1; + while (i > 0) + { + char ch = strFileNameAndPath[i]; + // Only break on ':' if it's a drive separator for DOS (ie d:foo) + if (ch == '/' || ch == '\\' || (ch == ':' && i == 1)) break; + else i--; + } + if (i == 0) + i--; + + // take left including the directory separator + strPath = strFileNameAndPath.substr(0, i+1); + // everything to the right of the directory separator + strFileName = strFileNameAndPath.substr(i+1); + + // if actual uri, ignore options + if (IsURL(strFileNameAndPath)) + { + i = strFileName.size() - 1; + while (i > 0) + { + char ch = strFileName[i]; + if (ch == '?' || ch == '|') break; + else i--; + } + if (i > 0) + strFileName = strFileName.substr(0, i); + } +} + +std::vector<std::string> URIUtils::SplitPath(const std::string& strPath) +{ + CURL url(strPath); + + // silly std::string can't take a char in the constructor + std::string sep(1, url.GetDirectorySeparator()); + + // split the filename portion of the URL up into separate dirs + std::vector<std::string> dirs = StringUtils::Split(url.GetFileName(), sep); + + // we start with the root path + std::string dir = url.GetWithoutFilename(); + + if (!dir.empty()) + dirs.insert(dirs.begin(), dir); + + // we don't need empty token on the end + if (dirs.size() > 1 && dirs.back().empty()) + dirs.erase(dirs.end() - 1); + + return dirs; +} + +void URIUtils::GetCommonPath(std::string& strParent, const std::string& strPath) +{ + // find the common path of parent and path + unsigned int j = 1; + while (j <= std::min(strParent.size(), strPath.size()) && + StringUtils::CompareNoCase(strParent, strPath, j) == 0) + j++; + strParent.erase(j - 1); + // they should at least share a / at the end, though for things such as path/cd1 and path/cd2 there won't be + if (!HasSlashAtEnd(strParent)) + { + strParent = GetDirectory(strParent); + AddSlashAtEnd(strParent); + } +} + +bool URIUtils::HasParentInHostname(const CURL& url) +{ + return url.IsProtocol("zip") || url.IsProtocol("apk") || url.IsProtocol("bluray") || + url.IsProtocol("udf") || url.IsProtocol("iso9660") || url.IsProtocol("xbt") || + (CServiceBroker::IsAddonInterfaceUp() && + CServiceBroker::GetFileExtensionProvider().EncodedHostName(url.GetProtocol())); +} + +bool URIUtils::HasEncodedHostname(const CURL& url) +{ + return HasParentInHostname(url) + || url.IsProtocol("musicsearch") + || url.IsProtocol( "image"); +} + +bool URIUtils::HasEncodedFilename(const CURL& url) +{ + const std::string prot2 = url.GetTranslatedProtocol(); + + // For now assume only (quasi) http internet streams use URL encoding + return CURL::IsProtocolEqual(prot2, "http") || + CURL::IsProtocolEqual(prot2, "https"); +} + +std::string URIUtils::GetParentPath(const std::string& strPath) +{ + std::string strReturn; + GetParentPath(strPath, strReturn); + return strReturn; +} + +bool URIUtils::GetParentPath(const std::string& strPath, std::string& strParent) +{ + strParent.clear(); + + CURL url(strPath); + std::string strFile = url.GetFileName(); + if ( URIUtils::HasParentInHostname(url) && strFile.empty()) + { + strFile = url.GetHostName(); + return GetParentPath(strFile, strParent); + } + else if (url.IsProtocol("stack")) + { + CStackDirectory dir; + CFileItemList items; + if (!dir.GetDirectory(url, items)) + return false; + CURL url2(GetDirectory(items[0]->GetPath())); + if (HasParentInHostname(url2)) + GetParentPath(url2.Get(), strParent); + else + strParent = url2.Get(); + for( int i=1;i<items.Size();++i) + { + items[i]->m_strDVDLabel = GetDirectory(items[i]->GetPath()); + if (HasParentInHostname(url2)) + items[i]->SetPath(GetParentPath(items[i]->m_strDVDLabel)); + else + items[i]->SetPath(items[i]->m_strDVDLabel); + + GetCommonPath(strParent,items[i]->GetPath()); + } + return true; + } + else if (url.IsProtocol("multipath")) + { + // get the parent path of the first item + return GetParentPath(CMultiPathDirectory::GetFirstPath(strPath), strParent); + } + else if (url.IsProtocol("plugin")) + { + if (!url.GetOptions().empty()) + { + //! @todo Make a new python call to get the plugin content type and remove this temporary hack + // When a plugin provides multiple types, it has "plugin://addon.id/?content_type=xxx" root URL + if (url.GetFileName().empty() && url.HasOption("content_type") && url.GetOptions().find('&') == std::string::npos) + url.SetHostName(""); + // + url.SetOptions(""); + strParent = url.Get(); + return true; + } + if (!url.GetFileName().empty()) + { + url.SetFileName(""); + strParent = url.Get(); + return true; + } + if (!url.GetHostName().empty()) + { + url.SetHostName(""); + strParent = url.Get(); + return true; + } + return true; // already at root + } + else if (url.IsProtocol("special")) + { + if (HasSlashAtEnd(strFile)) + strFile.erase(strFile.size() - 1); + if(strFile.rfind('/') == std::string::npos) + return false; + } + else if (strFile.empty()) + { + if (!url.GetHostName().empty()) + { + // we have an share with only server or workgroup name + // set hostname to "" and return true to get back to root + url.SetHostName(""); + strParent = url.Get(); + return true; + } + return false; + } + + if (HasSlashAtEnd(strFile) ) + { + strFile.erase(strFile.size() - 1); + } + + size_t iPos = strFile.rfind('/'); +#ifndef TARGET_POSIX + if (iPos == std::string::npos) + { + iPos = strFile.rfind('\\'); + } +#endif + if (iPos == std::string::npos) + { + url.SetFileName(""); + strParent = url.Get(); + return true; + } + + strFile.erase(iPos); + + AddSlashAtEnd(strFile); + + url.SetFileName(strFile); + strParent = url.Get(); + return true; +} + +std::string URIUtils::GetBasePath(const std::string& strPath) +{ + std::string strCheck(strPath); + if (IsStack(strPath)) + strCheck = CStackDirectory::GetFirstStackedFile(strPath); + + std::string strDirectory = GetDirectory(strCheck); + if (IsInRAR(strCheck)) + { + std::string strPath=strDirectory; + GetParentPath(strPath, strDirectory); + } + if (IsStack(strPath)) + { + strCheck = strDirectory; + RemoveSlashAtEnd(strCheck); + if (GetFileName(strCheck).size() == 3 && StringUtils::StartsWithNoCase(GetFileName(strCheck), "cd")) + strDirectory = GetDirectory(strCheck); + } + return strDirectory; +} + +std::string URLEncodePath(const std::string& strPath) +{ + std::vector<std::string> segments = StringUtils::Split(strPath, "/"); + for (std::vector<std::string>::iterator i = segments.begin(); i != segments.end(); ++i) + *i = CURL::Encode(*i); + + return StringUtils::Join(segments, "/"); +} + +std::string URLDecodePath(const std::string& strPath) +{ + std::vector<std::string> segments = StringUtils::Split(strPath, "/"); + for (std::vector<std::string>::iterator i = segments.begin(); i != segments.end(); ++i) + *i = CURL::Decode(*i); + + return StringUtils::Join(segments, "/"); +} + +std::string URIUtils::ChangeBasePath(const std::string &fromPath, const std::string &fromFile, const std::string &toPath, const bool &bAddPath /* = true */) +{ + std::string toFile = fromFile; + + // Convert back slashes to forward slashes, if required + if (IsDOSPath(fromPath) && !IsDOSPath(toPath)) + StringUtils::Replace(toFile, "\\", "/"); + + // Handle difference in URL encoded vs. not encoded + if ( HasEncodedFilename(CURL(fromPath)) + && !HasEncodedFilename(CURL(toPath)) ) + { + toFile = URLDecodePath(toFile); // Decode path + } + else if (!HasEncodedFilename(CURL(fromPath)) + && HasEncodedFilename(CURL(toPath)) ) + { + toFile = URLEncodePath(toFile); // Encode path + } + + // Convert forward slashes to back slashes, if required + if (!IsDOSPath(fromPath) && IsDOSPath(toPath)) + StringUtils::Replace(toFile, "/", "\\"); + + if (bAddPath) + return AddFileToFolder(toPath, toFile); + + return toFile; +} + +CURL URIUtils::SubstitutePath(const CURL& url, bool reverse /* = false */) +{ + const std::string pathToUrl = url.Get(); + return CURL(SubstitutePath(pathToUrl, reverse)); +} + +std::string URIUtils::SubstitutePath(const std::string& strPath, bool reverse /* = false */) +{ + if (!m_advancedSettings) + { + // path substitution not needed / not working during Kodi bootstrap. + return strPath; + } + + for (const auto& pathPair : m_advancedSettings->m_pathSubstitutions) + { + const std::string fromPath = reverse ? pathPair.second : pathPair.first; + std::string toPath = reverse ? pathPair.first : pathPair.second; + + if (strncmp(strPath.c_str(), fromPath.c_str(), HasSlashAtEnd(fromPath) ? fromPath.size() - 1 : fromPath.size()) == 0) + { + if (strPath.size() > fromPath.size()) + { + std::string strSubPathAndFileName = strPath.substr(fromPath.size()); + return ChangeBasePath(fromPath, strSubPathAndFileName, toPath); // Fix encoding + slash direction + } + else + { + return toPath; + } + } + } + return strPath; +} + +bool URIUtils::IsProtocol(const std::string& url, const std::string &type) +{ + return StringUtils::StartsWithNoCase(url, type + "://"); +} + +bool URIUtils::PathHasParent(std::string path, std::string parent, bool translate /* = false */) +{ + if (translate) + { + path = CSpecialProtocol::TranslatePath(path); + parent = CSpecialProtocol::TranslatePath(parent); + } + + if (parent.empty()) + return false; + + if (path == parent) + return true; + + // Make sure parent has a trailing slash + AddSlashAtEnd(parent); + + return StringUtils::StartsWith(path, parent); +} + +bool URIUtils::PathEquals(std::string path1, std::string path2, bool ignoreTrailingSlash /* = false */, bool ignoreURLOptions /* = false */) +{ + if (ignoreURLOptions) + { + path1 = CURL(path1).GetWithoutOptions(); + path2 = CURL(path2).GetWithoutOptions(); + } + + if (ignoreTrailingSlash) + { + RemoveSlashAtEnd(path1); + RemoveSlashAtEnd(path2); + } + + return (path1 == path2); +} + +bool URIUtils::IsRemote(const std::string& strFile) +{ + if (IsCDDA(strFile) || IsISO9660(strFile)) + return false; + + if (IsStack(strFile)) + return IsRemote(CStackDirectory::GetFirstStackedFile(strFile)); + + if (IsSpecial(strFile)) + return IsRemote(CSpecialProtocol::TranslatePath(strFile)); + + if(IsMultiPath(strFile)) + { // virtual paths need to be checked separately + std::vector<std::string> paths; + if (CMultiPathDirectory::GetPaths(strFile, paths)) + { + for (unsigned int i = 0; i < paths.size(); i++) + if (IsRemote(paths[i])) return true; + } + return false; + } + + CURL url(strFile); + if(HasParentInHostname(url)) + return IsRemote(url.GetHostName()); + + if (IsAddonsPath(strFile)) + return false; + + if (IsSourcesPath(strFile)) + return false; + + if (IsVideoDb(strFile) || IsMusicDb(strFile)) + return false; + + if (IsLibraryFolder(strFile)) + return false; + + if (IsPlugin(strFile)) + return false; + + if (IsAndroidApp(strFile)) + return false; + + if (!url.IsLocal()) + return true; + + return false; +} + +bool URIUtils::IsOnDVD(const std::string& strFile) +{ + if (IsProtocol(strFile, "dvd")) + return true; + + if (IsProtocol(strFile, "udf")) + return true; + + if (IsProtocol(strFile, "iso9660")) + return true; + + if (IsProtocol(strFile, "cdda")) + return true; + +#if defined(TARGET_WINDOWS_STORE) + CLog::Log(LOGDEBUG, "{} is not implemented", __FUNCTION__); +#elif defined(TARGET_WINDOWS_DESKTOP) + using KODI::PLATFORM::WINDOWS::ToW; + if (strFile.size() >= 2 && strFile.substr(1, 1) == ":") + return (GetDriveType(ToW(strFile.substr(0, 3)).c_str()) == DRIVE_CDROM); +#endif + return false; +} + +bool URIUtils::IsOnLAN(const std::string& strPath) +{ + if(IsMultiPath(strPath)) + return IsOnLAN(CMultiPathDirectory::GetFirstPath(strPath)); + + if(IsStack(strPath)) + return IsOnLAN(CStackDirectory::GetFirstStackedFile(strPath)); + + if(IsSpecial(strPath)) + return IsOnLAN(CSpecialProtocol::TranslatePath(strPath)); + + if(IsPlugin(strPath)) + return false; + + if(IsUPnP(strPath)) + return true; + + CURL url(strPath); + if (HasParentInHostname(url)) + return IsOnLAN(url.GetHostName()); + + if(!IsRemote(strPath)) + return false; + + const std::string& host = url.GetHostName(); + + return IsHostOnLAN(host); +} + +static bool addr_match(uint32_t addr, const char* target, const char* submask) +{ + uint32_t addr2 = ntohl(inet_addr(target)); + uint32_t mask = ntohl(inet_addr(submask)); + return (addr & mask) == (addr2 & mask); +} + +bool URIUtils::IsHostOnLAN(const std::string& host, bool offLineCheck) +{ + if(host.length() == 0) + return false; + + // assume a hostname without dot's + // is local (smb netbios hostnames) + if(host.find('.') == std::string::npos) + return true; + + uint32_t address = ntohl(inet_addr(host.c_str())); + if(address == INADDR_NONE) + { + std::string ip; + if(CDNSNameCache::Lookup(host, ip)) + address = ntohl(inet_addr(ip.c_str())); + } + + if(address != INADDR_NONE) + { + if (offLineCheck) // check if in private range, ref https://en.wikipedia.org/wiki/Private_network + { + if ( + addr_match(address, "192.168.0.0", "255.255.0.0") || + addr_match(address, "10.0.0.0", "255.0.0.0") || + addr_match(address, "172.16.0.0", "255.240.0.0") + ) + return true; + } + // check if we are on the local subnet + if (!CServiceBroker::GetNetwork().GetFirstConnectedInterface()) + return false; + + if (CServiceBroker::GetNetwork().HasInterfaceForIP(address)) + return true; + } + + return false; +} + +bool URIUtils::IsMultiPath(const std::string& strPath) +{ + return IsProtocol(strPath, "multipath"); +} + +bool URIUtils::IsHD(const std::string& strFileName) +{ + CURL url(strFileName); + + if (IsStack(strFileName)) + return IsHD(CStackDirectory::GetFirstStackedFile(strFileName)); + + if (IsSpecial(strFileName)) + return IsHD(CSpecialProtocol::TranslatePath(strFileName)); + + if (HasParentInHostname(url)) + return IsHD(url.GetHostName()); + + return url.GetProtocol().empty() || url.IsProtocol("file") || url.IsProtocol("win-lib"); +} + +bool URIUtils::IsDVD(const std::string& strFile) +{ + std::string strFileLow = strFile; + StringUtils::ToLower(strFileLow); + if (strFileLow.find("video_ts.ifo") != std::string::npos && IsOnDVD(strFile)) + return true; + +#if defined(TARGET_WINDOWS) + if (IsProtocol(strFile, "dvd")) + return true; + + if(strFile.size() < 2 || (strFile.substr(1) != ":\\" && strFile.substr(1) != ":")) + return false; + +#ifndef TARGET_WINDOWS_STORE + if(GetDriveType(KODI::PLATFORM::WINDOWS::ToW(strFile).c_str()) == DRIVE_CDROM) + return true; +#endif +#else + if (strFileLow == "iso9660://" || strFileLow == "udf://" || strFileLow == "dvd://1" ) + return true; +#endif + + return false; +} + +bool URIUtils::IsStack(const std::string& strFile) +{ + return IsProtocol(strFile, "stack"); +} + +bool URIUtils::IsFavourite(const std::string& strFile) +{ + return IsProtocol(strFile, "favourites"); +} + +bool URIUtils::IsRAR(const std::string& strFile) +{ + std::string strExtension = GetExtension(strFile); + + if (strExtension == ".001" && !StringUtils::EndsWithNoCase(strFile, ".ts.001")) + return true; + + if (StringUtils::EqualsNoCase(strExtension, ".cbr")) + return true; + + if (StringUtils::EqualsNoCase(strExtension, ".rar")) + return true; + + return false; +} + +bool URIUtils::IsInArchive(const std::string &strFile) +{ + CURL url(strFile); + + bool archiveProto = url.IsProtocol("archive") && !url.GetFileName().empty(); + return archiveProto || IsInZIP(strFile) || IsInRAR(strFile) || IsInAPK(strFile); +} + +bool URIUtils::IsInAPK(const std::string& strFile) +{ + CURL url(strFile); + + return url.IsProtocol("apk") && !url.GetFileName().empty(); +} + +bool URIUtils::IsInZIP(const std::string& strFile) +{ + CURL url(strFile); + + if (url.GetFileName().empty()) + return false; + + if (url.IsProtocol("archive")) + return IsZIP(url.GetHostName()); + + return url.IsProtocol("zip"); +} + +bool URIUtils::IsInRAR(const std::string& strFile) +{ + CURL url(strFile); + + if (url.GetFileName().empty()) + return false; + + if (url.IsProtocol("archive")) + return IsRAR(url.GetHostName()); + + return url.IsProtocol("rar"); +} + +bool URIUtils::IsAPK(const std::string& strFile) +{ + return HasExtension(strFile, ".apk"); +} + +bool URIUtils::IsZIP(const std::string& strFile) // also checks for comic books! +{ + return HasExtension(strFile, ".zip|.cbz"); +} + +bool URIUtils::IsArchive(const std::string& strFile) +{ + return HasExtension(strFile, ".zip|.rar|.apk|.cbz|.cbr"); +} + +bool URIUtils::IsSpecial(const std::string& strFile) +{ + if (IsStack(strFile)) + return IsSpecial(CStackDirectory::GetFirstStackedFile(strFile)); + + return IsProtocol(strFile, "special"); +} + +bool URIUtils::IsPlugin(const std::string& strFile) +{ + CURL url(strFile); + return url.IsProtocol("plugin"); +} + +bool URIUtils::IsScript(const std::string& strFile) +{ + CURL url(strFile); + return url.IsProtocol("script"); +} + +bool URIUtils::IsAddonsPath(const std::string& strFile) +{ + CURL url(strFile); + return url.IsProtocol("addons"); +} + +bool URIUtils::IsSourcesPath(const std::string& strPath) +{ + CURL url(strPath); + return url.IsProtocol("sources"); +} + +bool URIUtils::IsCDDA(const std::string& strFile) +{ + return IsProtocol(strFile, "cdda"); +} + +bool URIUtils::IsISO9660(const std::string& strFile) +{ + return IsProtocol(strFile, "iso9660"); +} + +bool URIUtils::IsSmb(const std::string& strFile) +{ + if (IsStack(strFile)) + return IsSmb(CStackDirectory::GetFirstStackedFile(strFile)); + + if (IsSpecial(strFile)) + return IsSmb(CSpecialProtocol::TranslatePath(strFile)); + + CURL url(strFile); + if (HasParentInHostname(url)) + return IsSmb(url.GetHostName()); + + return IsProtocol(strFile, "smb"); +} + +bool URIUtils::IsURL(const std::string& strFile) +{ + return strFile.find("://") != std::string::npos; +} + +bool URIUtils::IsFTP(const std::string& strFile) +{ + if (IsStack(strFile)) + return IsFTP(CStackDirectory::GetFirstStackedFile(strFile)); + + if (IsSpecial(strFile)) + return IsFTP(CSpecialProtocol::TranslatePath(strFile)); + + CURL url(strFile); + if (HasParentInHostname(url)) + return IsFTP(url.GetHostName()); + + return IsProtocol(strFile, "ftp") || + IsProtocol(strFile, "ftps"); +} + +bool URIUtils::IsHTTP(const std::string& strFile, bool bTranslate /* = false */) +{ + if (IsStack(strFile)) + return IsHTTP(CStackDirectory::GetFirstStackedFile(strFile)); + + if (IsSpecial(strFile)) + return IsHTTP(CSpecialProtocol::TranslatePath(strFile)); + + CURL url(strFile); + if (HasParentInHostname(url)) + return IsHTTP(url.GetHostName()); + + const std::string strProtocol = (bTranslate ? url.GetTranslatedProtocol() : url.GetProtocol()); + + return (strProtocol == "http" || strProtocol == "https"); +} + +bool URIUtils::IsUDP(const std::string& strFile) +{ + if (IsStack(strFile)) + return IsUDP(CStackDirectory::GetFirstStackedFile(strFile)); + + return IsProtocol(strFile, "udp"); +} + +bool URIUtils::IsTCP(const std::string& strFile) +{ + if (IsStack(strFile)) + return IsTCP(CStackDirectory::GetFirstStackedFile(strFile)); + + return IsProtocol(strFile, "tcp"); +} + +bool URIUtils::IsPVR(const std::string& strFile) +{ + if (IsStack(strFile)) + return IsPVR(CStackDirectory::GetFirstStackedFile(strFile)); + + return IsProtocol(strFile, "pvr"); +} + +bool URIUtils::IsPVRChannel(const std::string& strFile) +{ + if (IsStack(strFile)) + return IsPVRChannel(CStackDirectory::GetFirstStackedFile(strFile)); + + return IsProtocol(strFile, "pvr") && CPVRChannelsPath(strFile).IsChannel(); +} + +bool URIUtils::IsPVRChannelGroup(const std::string& strFile) +{ + if (IsStack(strFile)) + return IsPVRChannelGroup(CStackDirectory::GetFirstStackedFile(strFile)); + + return IsProtocol(strFile, "pvr") && CPVRChannelsPath(strFile).IsChannelGroup(); +} + +bool URIUtils::IsPVRGuideItem(const std::string& strFile) +{ + if (IsStack(strFile)) + return IsPVRGuideItem(CStackDirectory::GetFirstStackedFile(strFile)); + + return StringUtils::StartsWithNoCase(strFile, "pvr://guide"); +} + +bool URIUtils::IsDAV(const std::string& strFile) +{ + if (IsStack(strFile)) + return IsDAV(CStackDirectory::GetFirstStackedFile(strFile)); + + if (IsSpecial(strFile)) + return IsDAV(CSpecialProtocol::TranslatePath(strFile)); + + CURL url(strFile); + if (HasParentInHostname(url)) + return IsDAV(url.GetHostName()); + + return IsProtocol(strFile, "dav") || + IsProtocol(strFile, "davs"); +} + +bool URIUtils::IsInternetStream(const std::string &path, bool bStrictCheck /* = false */) +{ + const CURL pathToUrl(path); + return IsInternetStream(pathToUrl, bStrictCheck); +} + +bool URIUtils::IsInternetStream(const CURL& url, bool bStrictCheck /* = false */) +{ + if (url.GetProtocol().empty()) + return false; + + // there's nothing to stop internet streams from being stacked + if (url.IsProtocol("stack")) + return IsInternetStream(CStackDirectory::GetFirstStackedFile(url.Get()), bStrictCheck); + + // Only consider "streamed" filesystems internet streams when being strict + if (bStrictCheck && IsStreamedFilesystem(url.Get())) + return true; + + // Check for true internetstreams + const std::string& protocol = url.GetProtocol(); + if (CURL::IsProtocolEqual(protocol, "http") || CURL::IsProtocolEqual(protocol, "https") || + CURL::IsProtocolEqual(protocol, "tcp") || CURL::IsProtocolEqual(protocol, "udp") || + CURL::IsProtocolEqual(protocol, "rtp") || CURL::IsProtocolEqual(protocol, "sdp") || + CURL::IsProtocolEqual(protocol, "mms") || CURL::IsProtocolEqual(protocol, "mmst") || + CURL::IsProtocolEqual(protocol, "mmsh") || CURL::IsProtocolEqual(protocol, "rtsp") || + CURL::IsProtocolEqual(protocol, "rtmp") || CURL::IsProtocolEqual(protocol, "rtmpt") || + CURL::IsProtocolEqual(protocol, "rtmpe") || CURL::IsProtocolEqual(protocol, "rtmpte") || + CURL::IsProtocolEqual(protocol, "rtmps") || CURL::IsProtocolEqual(protocol, "shout") || + CURL::IsProtocolEqual(protocol, "rss") || CURL::IsProtocolEqual(protocol, "rsss")) + return true; + + return false; +} + +bool URIUtils::IsStreamedFilesystem(const std::string& strPath) +{ + CURL url(strPath); + + if (url.GetProtocol().empty()) + return false; + + if (url.IsProtocol("stack")) + return IsStreamedFilesystem(CStackDirectory::GetFirstStackedFile(strPath)); + + if (IsUPnP(strPath) || IsFTP(strPath) || IsHTTP(strPath, true)) + return true; + + //! @todo sftp/ssh special case has to be handled by vfs addon + if (url.IsProtocol("sftp") || url.IsProtocol("ssh")) + return true; + + return false; +} + +bool URIUtils::IsNetworkFilesystem(const std::string& strPath) +{ + CURL url(strPath); + + if (url.GetProtocol().empty()) + return false; + + if (url.IsProtocol("stack")) + return IsNetworkFilesystem(CStackDirectory::GetFirstStackedFile(strPath)); + + if (IsStreamedFilesystem(strPath)) + return true; + + if (IsSmb(strPath) || IsNfs(strPath)) + return true; + + return false; +} + +bool URIUtils::IsUPnP(const std::string& strFile) +{ + return IsProtocol(strFile, "upnp"); +} + +bool URIUtils::IsLiveTV(const std::string& strFile) +{ + std::string strFileWithoutSlash(strFile); + RemoveSlashAtEnd(strFileWithoutSlash); + + if (StringUtils::EndsWithNoCase(strFileWithoutSlash, ".pvr") && + !StringUtils::StartsWith(strFileWithoutSlash, "pvr://recordings")) + return true; + + return false; +} + +bool URIUtils::IsPVRRecording(const std::string& strFile) +{ + std::string strFileWithoutSlash(strFile); + RemoveSlashAtEnd(strFileWithoutSlash); + + return StringUtils::EndsWithNoCase(strFileWithoutSlash, ".pvr") && + StringUtils::StartsWith(strFile, "pvr://recordings"); +} + +bool URIUtils::IsPVRRecordingFileOrFolder(const std::string& strFile) +{ + return StringUtils::StartsWith(strFile, "pvr://recordings"); +} + +bool URIUtils::IsPVRTVRecordingFileOrFolder(const std::string& strFile) +{ + return StringUtils::StartsWith(strFile, "pvr://recordings/tv"); +} + +bool URIUtils::IsPVRRadioRecordingFileOrFolder(const std::string& strFile) +{ + return StringUtils::StartsWith(strFile, "pvr://recordings/radio"); +} + +bool URIUtils::IsMusicDb(const std::string& strFile) +{ + return IsProtocol(strFile, "musicdb"); +} + +bool URIUtils::IsNfs(const std::string& strFile) +{ + if (IsStack(strFile)) + return IsNfs(CStackDirectory::GetFirstStackedFile(strFile)); + + if (IsSpecial(strFile)) + return IsNfs(CSpecialProtocol::TranslatePath(strFile)); + + CURL url(strFile); + if (HasParentInHostname(url)) + return IsNfs(url.GetHostName()); + + return IsProtocol(strFile, "nfs"); +} + +bool URIUtils::IsVideoDb(const std::string& strFile) +{ + return IsProtocol(strFile, "videodb"); +} + +bool URIUtils::IsBluray(const std::string& strFile) +{ + return IsProtocol(strFile, "bluray"); +} + +bool URIUtils::IsAndroidApp(const std::string &path) +{ + return IsProtocol(path, "androidapp"); +} + +bool URIUtils::IsLibraryFolder(const std::string& strFile) +{ + CURL url(strFile); + return url.IsProtocol("library"); +} + +bool URIUtils::IsLibraryContent(const std::string &strFile) +{ + return (IsProtocol(strFile, "library") || + IsProtocol(strFile, "videodb") || + IsProtocol(strFile, "musicdb") || + StringUtils::EndsWith(strFile, ".xsp")); +} + +bool URIUtils::IsDOSPath(const std::string &path) +{ + if (path.size() > 1 && path[1] == ':' && isalpha(path[0])) + return true; + + // windows network drives + if (path.size() > 1 && path[0] == '\\' && path[1] == '\\') + return true; + + return false; +} + +std::string URIUtils::AppendSlash(std::string strFolder) +{ + AddSlashAtEnd(strFolder); + return strFolder; +} + +void URIUtils::AddSlashAtEnd(std::string& strFolder) +{ + if (IsURL(strFolder)) + { + CURL url(strFolder); + std::string file = url.GetFileName(); + if(!file.empty() && file != strFolder) + { + AddSlashAtEnd(file); + url.SetFileName(file); + strFolder = url.Get(); + } + return; + } + + if (!HasSlashAtEnd(strFolder)) + { + if (IsDOSPath(strFolder)) + strFolder += '\\'; + else + strFolder += '/'; + } +} + +bool URIUtils::HasSlashAtEnd(const std::string& strFile, bool checkURL /* = false */) +{ + if (strFile.empty()) return false; + if (checkURL && IsURL(strFile)) + { + CURL url(strFile); + const std::string& file = url.GetFileName(); + return file.empty() || HasSlashAtEnd(file, false); + } + char kar = strFile.c_str()[strFile.size() - 1]; + + if (kar == '/' || kar == '\\') + return true; + + return false; +} + +void URIUtils::RemoveSlashAtEnd(std::string& strFolder) +{ + // performance optimization. pvr guide items are mass objects, uri never has a slash at end, and this method is quite expensive... + if (IsPVRGuideItem(strFolder)) + return; + + if (IsURL(strFolder)) + { + CURL url(strFolder); + std::string file = url.GetFileName(); + if (!file.empty() && file != strFolder) + { + RemoveSlashAtEnd(file); + url.SetFileName(file); + strFolder = url.Get(); + return; + } + if(url.GetHostName().empty()) + return; + } + + while (HasSlashAtEnd(strFolder)) + strFolder.erase(strFolder.size()-1, 1); +} + +bool URIUtils::CompareWithoutSlashAtEnd(const std::string& strPath1, const std::string& strPath2) +{ + std::string strc1 = strPath1, strc2 = strPath2; + RemoveSlashAtEnd(strc1); + RemoveSlashAtEnd(strc2); + return StringUtils::EqualsNoCase(strc1, strc2); +} + + +std::string URIUtils::FixSlashesAndDups(const std::string& path, const char slashCharacter /* = '/' */, const size_t startFrom /*= 0*/) +{ + const size_t len = path.length(); + if (startFrom >= len) + return path; + + std::string result(path, 0, startFrom); + result.reserve(len); + + const char* const str = path.c_str(); + size_t pos = startFrom; + do + { + if (str[pos] == '\\' || str[pos] == '/') + { + result.push_back(slashCharacter); // append one slash + pos++; + // skip any following slashes + while (str[pos] == '\\' || str[pos] == '/') // str is null-terminated, no need to check for buffer overrun + pos++; + } + else + result.push_back(str[pos++]); // append current char and advance pos to next char + + } while (pos < len); + + return result; +} + + +std::string URIUtils::CanonicalizePath(const std::string& path, const char slashCharacter /*= '\\'*/) +{ + assert(slashCharacter == '\\' || slashCharacter == '/'); + + if (path.empty()) + return path; + + const std::string slashStr(1, slashCharacter); + std::vector<std::string> pathVec, resultVec; + StringUtils::Tokenize(path, pathVec, slashStr); + + for (std::vector<std::string>::const_iterator it = pathVec.begin(); it != pathVec.end(); ++it) + { + if (*it == ".") + { /* skip - do nothing */ } + else if (*it == ".." && !resultVec.empty() && resultVec.back() != "..") + resultVec.pop_back(); + else + resultVec.push_back(*it); + } + + std::string result; + if (path[0] == slashCharacter) + result.push_back(slashCharacter); // add slash at the begin + + result += StringUtils::Join(resultVec, slashStr); + + if (path[path.length() - 1] == slashCharacter && !result.empty() && result[result.length() - 1] != slashCharacter) + result.push_back(slashCharacter); // add slash at the end if result isn't empty and result isn't "/" + + return result; +} + +std::string URIUtils::AddFileToFolder(const std::string& strFolder, + const std::string& strFile) +{ + if (IsURL(strFolder)) + { + CURL url(strFolder); + if (url.GetFileName() != strFolder) + { + url.SetFileName(AddFileToFolder(url.GetFileName(), strFile)); + return url.Get(); + } + } + + std::string strResult = strFolder; + if (!strResult.empty()) + AddSlashAtEnd(strResult); + + // Remove any slash at the start of the file + if (strFile.size() && (strFile[0] == '/' || strFile[0] == '\\')) + strResult += strFile.substr(1); + else + strResult += strFile; + + // correct any slash directions + if (!IsDOSPath(strFolder)) + StringUtils::Replace(strResult, '\\', '/'); + else + StringUtils::Replace(strResult, '/', '\\'); + + return strResult; +} + +std::string URIUtils::GetDirectory(const std::string &strFilePath) +{ + // Will from a full filename return the directory the file resides in. + // Keeps the final slash at end and possible |option=foo options. + + size_t iPosSlash = strFilePath.find_last_of("/\\"); + if (iPosSlash == std::string::npos) + return ""; // No slash, so no path (ignore any options) + + size_t iPosBar = strFilePath.rfind('|'); + if (iPosBar == std::string::npos) + return strFilePath.substr(0, iPosSlash + 1); // Only path + + return strFilePath.substr(0, iPosSlash + 1) + strFilePath.substr(iPosBar); // Path + options +} + +CURL URIUtils::CreateArchivePath(const std::string& type, + const CURL& archiveUrl, + const std::string& pathInArchive, + const std::string& password) +{ + CURL url; + url.SetProtocol(type); + if (!password.empty()) + url.SetUserName(password); + url.SetHostName(archiveUrl.Get()); + + /* NOTE: on posix systems, the replacement of \ with / is incorrect. + Ideally this would not be done. We need to check that the ZipManager + code (and elsewhere) doesn't pass in non-posix paths. + */ + std::string strBuffer(pathInArchive); + StringUtils::Replace(strBuffer, '\\', '/'); + StringUtils::TrimLeft(strBuffer, "/"); + url.SetFileName(strBuffer); + + return url; +} + +std::string URIUtils::GetRealPath(const std::string &path) +{ + if (path.empty()) + return path; + + CURL url(path); + url.SetHostName(GetRealPath(url.GetHostName())); + url.SetFileName(resolvePath(url.GetFileName())); + + return url.Get(); +} + +std::string URIUtils::resolvePath(const std::string &path) +{ + if (path.empty()) + return path; + + size_t posSlash = path.find('/'); + size_t posBackslash = path.find('\\'); + std::string delim = posSlash < posBackslash ? "/" : "\\"; + std::vector<std::string> parts = StringUtils::Split(path, delim); + std::vector<std::string> realParts; + + for (std::vector<std::string>::const_iterator part = parts.begin(); part != parts.end(); ++part) + { + if (part->empty() || part->compare(".") == 0) + continue; + + // go one level back up + if (part->compare("..") == 0) + { + if (!realParts.empty()) + realParts.pop_back(); + continue; + } + + realParts.push_back(*part); + } + + std::string realPath; + // re-add any / or \ at the beginning + for (std::string::const_iterator itPath = path.begin(); itPath != path.end(); ++itPath) + { + if (*itPath != delim.at(0)) + break; + + realPath += delim; + } + // put together the path + realPath += StringUtils::Join(realParts, delim); + // re-add any / or \ at the end + if (path.at(path.size() - 1) == delim.at(0) && + realPath.size() > 0 && realPath.at(realPath.size() - 1) != delim.at(0)) + realPath += delim; + + return realPath; +} + +bool URIUtils::UpdateUrlEncoding(std::string &strFilename) +{ + if (strFilename.empty()) + return false; + + CURL url(strFilename); + // if this is a stack:// URL we need to work with its filename + if (URIUtils::IsStack(strFilename)) + { + std::vector<std::string> files; + if (!CStackDirectory::GetPaths(strFilename, files)) + return false; + + for (std::vector<std::string>::iterator file = files.begin(); file != files.end(); ++file) + UpdateUrlEncoding(*file); + + std::string stackPath; + if (!CStackDirectory::ConstructStackPath(files, stackPath)) + return false; + + url.Parse(stackPath); + } + // if the protocol has an encoded hostname we need to work with its hostname + else if (URIUtils::HasEncodedHostname(url)) + { + std::string hostname = url.GetHostName(); + UpdateUrlEncoding(hostname); + url.SetHostName(hostname); + } + else + return false; + + std::string newFilename = url.Get(); + if (newFilename == strFilename) + return false; + + strFilename = newFilename; + return true; +} |