Merge pull request #26220 from 78andyp/blurayfixes
[xbmc.git] / xbmc / utils / FileUtils.cpp
blob5d3d4ccae89835ea4caa4c10994b9b2698bd0b94
1 /*
2 * Copyright (C) 2010-2020 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
9 #include "FileUtils.h"
11 #include "CompileInfo.h"
12 #include "FileOperationJob.h"
13 #include "ServiceBroker.h"
14 #include "StringUtils.h"
15 #include "URIUtils.h"
16 #include "URL.h"
17 #include "Util.h"
18 #include "filesystem/File.h"
19 #include "filesystem/MultiPathDirectory.h"
20 #include "filesystem/SpecialProtocol.h"
21 #include "filesystem/StackDirectory.h"
22 #include "guilib/GUIComponent.h"
23 #include "guilib/GUIKeyboardFactory.h"
24 #include "guilib/LocalizeStrings.h"
25 #include "imagefiles/ImageFileURL.h"
26 #include "settings/MediaSourceSettings.h"
27 #include "settings/Settings.h"
28 #include "settings/SettingsComponent.h"
29 #include "storage/MediaManager.h"
30 #include "utils/Variant.h"
31 #include "utils/log.h"
33 #if defined(TARGET_WINDOWS)
34 #include "platform/win32/WIN32Util.h"
35 #include "utils/CharsetConverter.h"
36 #endif
38 #include <vector>
40 using namespace XFILE;
42 bool CFileUtils::DeleteItem(const std::string &strPath)
44 CFileItemPtr item(new CFileItem(strPath));
45 item->SetPath(strPath);
46 item->m_bIsFolder = URIUtils::HasSlashAtEnd(strPath);
47 item->Select(true);
48 return DeleteItem(item);
51 bool CFileUtils::DeleteItem(const std::shared_ptr<CFileItem>& item)
53 if (!item || item->IsParentFolder())
54 return false;
56 // Create a temporary item list containing the file/folder for deletion
57 CFileItemPtr pItemTemp(new CFileItem(*item));
58 pItemTemp->Select(true);
59 CFileItemList items;
60 items.Add(pItemTemp);
62 // grab the real filemanager window, set up the progress bar,
63 // and process the delete action
64 CFileOperationJob op(CFileOperationJob::ActionDelete, items, "");
66 return op.DoWork();
69 bool CFileUtils::RenameFile(const std::string &strFile)
71 std::string strFileAndPath(strFile);
72 URIUtils::RemoveSlashAtEnd(strFileAndPath);
73 std::string strFileName = URIUtils::GetFileName(strFileAndPath);
74 std::string strPath = URIUtils::GetDirectory(strFileAndPath);
75 if (CGUIKeyboardFactory::ShowAndGetInput(strFileName, CVariant{g_localizeStrings.Get(16013)}, false))
77 strPath = URIUtils::AddFileToFolder(strPath, strFileName);
78 CLog::Log(LOGINFO, "FileUtils: rename {}->{}", strFileAndPath, strPath);
79 if (URIUtils::IsMultiPath(strFileAndPath))
80 { // special case for multipath renames - rename all the paths.
81 std::vector<std::string> paths;
82 CMultiPathDirectory::GetPaths(strFileAndPath, paths);
83 bool success = false;
84 for (unsigned int i = 0; i < paths.size(); ++i)
86 std::string filePath(paths[i]);
87 URIUtils::RemoveSlashAtEnd(filePath);
88 filePath = URIUtils::GetDirectory(filePath);
89 filePath = URIUtils::AddFileToFolder(filePath, strFileName);
90 if (CFile::Rename(paths[i], filePath))
91 success = true;
93 return success;
95 return CFile::Rename(strFileAndPath, strPath);
97 return false;
100 bool CFileUtils::RemoteAccessAllowed(const std::string &strPath)
102 std::string SourceNames[] = { "programs", "files", "video", "music", "pictures" };
104 std::string realPath = URIUtils::GetRealPath(strPath);
105 // for rar:// and zip:// paths we need to extract the path to the archive
106 // instead of using the VFS path
107 while (URIUtils::IsInArchive(realPath))
108 realPath = CURL(realPath).GetHostName();
110 if (StringUtils::StartsWithNoCase(realPath, "virtualpath://upnproot/"))
111 return true;
112 else if (StringUtils::StartsWithNoCase(realPath, "musicdb://"))
113 return true;
114 else if (StringUtils::StartsWithNoCase(realPath, "videodb://"))
115 return true;
116 else if (StringUtils::StartsWithNoCase(realPath, "library://video"))
117 return true;
118 else if (StringUtils::StartsWithNoCase(realPath, "library://music"))
119 return true;
120 else if (StringUtils::StartsWithNoCase(realPath, "sources://video"))
121 return true;
122 else if (StringUtils::StartsWithNoCase(realPath, "special://musicplaylists"))
123 return true;
124 else if (StringUtils::StartsWithNoCase(realPath, "special://profile/playlists"))
125 return true;
126 else if (StringUtils::StartsWithNoCase(realPath, "special://videoplaylists"))
127 return true;
128 else if (StringUtils::StartsWithNoCase(realPath, "special://skin"))
129 return true;
130 else if (StringUtils::StartsWithNoCase(realPath, "special://profile/addon_data"))
131 return true;
132 else if (StringUtils::StartsWithNoCase(realPath, "addons://sources"))
133 return true;
134 else if (StringUtils::StartsWithNoCase(realPath, "upnp://"))
135 return true;
136 else if (StringUtils::StartsWithNoCase(realPath, "plugin://"))
137 return true;
138 else
140 std::string strPlaylistsPath = CServiceBroker::GetSettingsComponent()->GetSettings()->GetString(CSettings::SETTING_SYSTEM_PLAYLISTSPATH);
141 URIUtils::RemoveSlashAtEnd(strPlaylistsPath);
142 if (StringUtils::StartsWithNoCase(realPath, strPlaylistsPath))
143 return true;
145 bool isSource;
146 // Check manually added sources (held in sources.xml)
147 for (const std::string& sourceName : SourceNames)
149 std::vector<CMediaSource>* sources = CMediaSourceSettings::GetInstance().GetSources(sourceName);
150 int sourceIndex = CUtil::GetMatchingSource(realPath, *sources, isSource);
151 if (sourceIndex >= 0 && sourceIndex < static_cast<int>(sources->size()) &&
152 sources->at(sourceIndex).m_iHasLock != LOCK_STATE_LOCKED &&
153 sources->at(sourceIndex).m_allowSharing)
154 return true;
156 // Check auto-mounted sources
157 std::vector<CMediaSource> sources;
158 CServiceBroker::GetMediaManager().GetRemovableDrives(
159 sources); // Sources returned always have m_allowsharing = true
160 //! @todo Make sharing of auto-mounted sources user configurable
161 int sourceIndex = CUtil::GetMatchingSource(realPath, sources, isSource);
162 if (sourceIndex >= 0 && sourceIndex < static_cast<int>(sources.size()) &&
163 sources.at(sourceIndex).m_iHasLock != LOCK_STATE_LOCKED &&
164 sources.at(sourceIndex).m_allowSharing)
165 return true;
167 return false;
170 CDateTime CFileUtils::GetModificationDate(const std::string& strFileNameAndPath,
171 const bool& bUseLatestDate)
173 if (bUseLatestDate)
174 return GetModificationDate(1, strFileNameAndPath);
175 else
176 return GetModificationDate(0, strFileNameAndPath);
179 CDateTime CFileUtils::GetModificationDate(const int& code, const std::string& strFileNameAndPath)
181 CDateTime dateAdded;
182 if (strFileNameAndPath.empty())
184 CLog::Log(LOGDEBUG, "{} empty strFileNameAndPath variable", __FUNCTION__);
185 return dateAdded;
190 std::string file = strFileNameAndPath;
191 if (URIUtils::IsStack(strFileNameAndPath))
192 file = CStackDirectory::GetFirstStackedFile(strFileNameAndPath);
194 if (URIUtils::IsInArchive(file))
195 file = CURL(file).GetHostName();
197 // Try to get ctime (creation on Windows, metadata change on Linux) and mtime (modification)
198 struct __stat64 buffer;
199 if (CFile::Stat(file, &buffer) == 0 && (buffer.st_mtime != 0 || buffer.st_ctime != 0))
201 time_t now = time(NULL);
202 time_t addedTime;
203 // Prefer the modification time if it's valid, fallback to ctime
204 if (code == 0)
206 if (buffer.st_mtime != 0 && static_cast<time_t>(buffer.st_mtime) <= now)
207 addedTime = static_cast<time_t>(buffer.st_mtime);
208 else
209 addedTime = static_cast<time_t>(buffer.st_ctime);
211 // Use the later of the ctime and mtime
212 else if (code == 1)
214 addedTime =
215 std::max(static_cast<time_t>(buffer.st_ctime), static_cast<time_t>(buffer.st_mtime));
216 // if the newer of the two dates is in the future, we try it with the older one
217 if (addedTime > now)
218 addedTime =
219 std::min(static_cast<time_t>(buffer.st_ctime), static_cast<time_t>(buffer.st_mtime));
221 // Prefer the earliest of ctime and mtime, fallback to other
222 else
224 addedTime =
225 std::min(static_cast<time_t>(buffer.st_ctime), static_cast<time_t>(buffer.st_mtime));
226 // if the older of the two dates is invalid, we try it with the newer one
227 if (addedTime == 0)
228 addedTime =
229 std::max(static_cast<time_t>(buffer.st_ctime), static_cast<time_t>(buffer.st_mtime));
233 // make sure the datetime does is not in the future
234 if (addedTime <= now)
236 struct tm* time;
237 #ifdef HAVE_LOCALTIME_R
238 struct tm result = {};
239 time = localtime_r(&addedTime, &result);
240 #else
241 time = localtime(&addedTime);
242 #endif
243 if (time)
244 dateAdded = *time;
248 catch (...)
250 CLog::Log(LOGERROR, "{} unable to extract modification date for file ({})", __FUNCTION__,
251 strFileNameAndPath);
253 return dateAdded;
256 bool CFileUtils::CheckFileAccessAllowed(const std::string &filePath)
258 // DENY access to paths matching
259 const std::vector<std::string> blacklist = {
260 "passwords.xml",
261 "sources.xml",
262 "guisettings.xml",
263 "advancedsettings.xml",
264 "server.key",
265 "/.ssh/",
267 // ALLOW kodi paths
268 std::vector<std::string> whitelist = {
269 CSpecialProtocol::TranslatePath("special://home"),
270 CSpecialProtocol::TranslatePath("special://xbmc"),
271 CSpecialProtocol::TranslatePath("special://musicartistsinfo"),
274 auto kodiExtraWhitelist = CCompileInfo::GetWebserverExtraWhitelist();
275 whitelist.insert(whitelist.end(), kodiExtraWhitelist.begin(), kodiExtraWhitelist.end());
277 bool isImage = false;
278 std::string decodePath = CURL::Decode(filePath);
279 if (URIUtils::IsProtocol(filePath, "image"))
281 isImage = true;
282 decodePath = IMAGE_FILES::CImageFileURL(filePath).GetTargetFile();
285 // check blacklist
286 for (const auto &b : blacklist)
288 if (decodePath.find(b) != std::string::npos)
290 CLog::Log(LOGERROR, "{} denied access to {}", __FUNCTION__, decodePath);
291 return false;
295 #if defined(TARGET_POSIX)
296 std::string whiteEntry;
297 char *fullpath = realpath(decodePath.c_str(), nullptr);
299 // if this is a locally existing file, check access permissions
300 if (fullpath)
302 const std::string realPath = fullpath;
303 free(fullpath);
305 // check whitelist
306 for (const auto &w : whitelist)
308 char *realtemp = realpath(w.c_str(), nullptr);
309 if (realtemp)
311 whiteEntry = realtemp;
312 free(realtemp);
314 if (StringUtils::StartsWith(realPath, whiteEntry))
315 return true;
317 // check sources with realPath
318 return CFileUtils::RemoteAccessAllowed(realPath);
320 #elif defined(TARGET_WINDOWS)
321 CURL url(decodePath);
322 if (url.GetProtocol().empty())
324 std::wstring decodePathW;
325 g_charsetConverter.utf8ToW(decodePath, decodePathW, false);
326 CWIN32Util::AddExtraLongPathPrefix(decodePathW);
327 DWORD bufSize = GetFullPathNameW(decodePathW.c_str(), 0, nullptr, nullptr);
328 if (bufSize > 0)
330 std::wstring fullpathW;
331 fullpathW.resize(bufSize);
332 if (GetFullPathNameW(decodePathW.c_str(), bufSize, const_cast<wchar_t*>(fullpathW.c_str()), nullptr) <= bufSize - 1)
334 CWIN32Util::RemoveExtraLongPathPrefix(fullpathW);
335 std::string fullpath;
336 g_charsetConverter.wToUTF8(fullpathW, fullpath, false);
337 for (const std::string& whiteEntry : whitelist)
339 if (StringUtils::StartsWith(fullpath, whiteEntry))
340 return true;
342 return CFileUtils::RemoteAccessAllowed(fullpath);
346 #endif
347 // if it isn't a local file, it must be a vfs entry
348 if (! isImage)
349 return CFileUtils::RemoteAccessAllowed(decodePath);
350 return true;
353 bool CFileUtils::Exists(const std::string& strFileName, bool bUseCache)
355 return CFile::Exists(strFileName, bUseCache);
358 bool CFileUtils::DeleteItemWithConfirm(const std::shared_ptr<CFileItem>& item)
360 if (CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(
361 CSettings::SETTING_FILELISTS_CONFIRMFILEDELETION))
363 CGUIComponent* gui = CServiceBroker::GetGUI();
365 if (!gui || !gui->ConfirmDelete(item->GetPath()))
366 return false;
369 return CFileUtils::DeleteItem(item);