1 /*
2 * Copyright (C) 2011-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9 #include "WinLibraryFile.h"
10
11 #include "URL.h"
12 #include "WinLibraryDirectory.h"
13 #include "utils/StringUtils.h"
14 #include "utils/URIUtils.h"
15 #include "utils/log.h"
16
17 #include "platform/win10/AsyncHelpers.h"
18 #include "platform/win32/CharsetConverter.h"
19 #include "platform/win32/WIN32Util.h"
20
21 #include <string>
22
23 #include <robuffer.h>
24 #include <winrt/Windows.Foundation.Collections.h>
25 #include <winrt/Windows.Security.Cryptography.h>
26 #include <winrt/Windows.Storage.AccessCache.h>
27 #include <winrt/Windows.Storage.FileProperties.h>
28 #include <winrt/Windows.Storage.Search.h>
29 #include <winrt/Windows.Storage.Streams.h>
30
31 using namespace XFILE;
32 using namespace KODI::PLATFORM::WINDOWS;
33 namespace winrt
34 {
35 using namespace Windows::Foundation;
36 }
37 using namespace winrt::Windows::ApplicationModel;
38 using namespace winrt::Windows::Foundation::Collections;
39 using namespace winrt::Windows::Security::Cryptography;
40 using namespace winrt::Windows::Storage;
41 using namespace winrt::Windows::Storage::AccessCache;
42 using namespace winrt::Windows::Storage::Search;
43 using namespace winrt::Windows::Storage::Streams;
44
45 struct __declspec(uuid("905a0fef-bc53-11df-8c49-001e4fc686da")) IBufferByteAccess : ::IUnknown
46 {
47 virtual HRESULT __stdcall Buffer(void** value) = 0;
48 };
49
50 struct CustomBuffer : winrt::implements<CustomBuffer, IBuffer, IBufferByteAccess>
51 {
52 void *m_address;
53 uint32_t m_capacity;
54 uint32_t m_length;
55
CustomBufferCustomBuffer56 CustomBuffer(void *address, uint32_t capacity) : m_address(address), m_capacity(capacity), m_length(0) { }
CapacityCustomBuffer57 uint32_t Capacity() const { return m_capacity; }
LengthCustomBuffer58 uint32_t Length() const { return m_length; }
LengthCustomBuffer59 void Length(uint32_t length) { m_length = length; }
60
BufferCustomBuffer61 HRESULT __stdcall Buffer(void** value) final
62 {
63 *value = m_address;
64 return S_OK;
65 }
66 };
67
68 CWinLibraryFile::CWinLibraryFile() = default;
69 CWinLibraryFile::~CWinLibraryFile(void) = default;
70
IsValid(const CURL & url)71 bool CWinLibraryFile::IsValid(const CURL & url)
72 {
73 return CWinLibraryDirectory::IsValid(url)
74 && !url.GetFileName().empty();
75 }
76
Open(const CURL & url)77 bool CWinLibraryFile::Open(const CURL& url)
78 {
79 return OpenIntenal(url, FileAccessMode::Read);
80 }
81
OpenForWrite(const CURL & url,bool bOverWrite)82 bool CWinLibraryFile::OpenForWrite(const CURL& url, bool bOverWrite)
83 {
84 return OpenIntenal(url, FileAccessMode::ReadWrite);
85 }
86
Close()87 void CWinLibraryFile::Close()
88 {
89 if (m_fileStream != nullptr)
90 {
91 m_fileStream.Close();
92 m_fileStream = nullptr;
93 }
94 if (m_sFile)
95 m_sFile = nullptr;
96 }
97
Read(void * lpBuf,size_t uiBufSize)98 ssize_t CWinLibraryFile::Read(void* lpBuf, size_t uiBufSize)
99 {
100 if (!m_fileStream)
101 return -1;
102
103 IBuffer buf = winrt::make<CustomBuffer>(lpBuf, static_cast<uint32_t>(uiBufSize));
104 try
105 {
106 Wait(m_fileStream.ReadAsync(buf, buf.Capacity(), InputStreamOptions::None));
107 return static_cast<ssize_t>(buf.Length());
108 }
109 catch (const winrt::hresult_error& ex)
110 {
111 using KODI::PLATFORM::WINDOWS::FromW;
112 CLog::LogF(LOGERROR, "unable to read file ({})", FromW(ex.message().c_str()));
113 return -1;
114 }
115 }
116
Write(const void * lpBuf,size_t uiBufSize)117 ssize_t CWinLibraryFile::Write(const void* lpBuf, size_t uiBufSize)
118 {
119 if (!m_fileStream || !m_allowWrite)
120 return -1;
121
122 uint8_t* buff = (uint8_t*)lpBuf;
123 const auto winrt_buffer = CryptographicBuffer::CreateFromByteArray({ buff, buff + uiBufSize });
124
125 try
126 {
127 const uint32_t result = Wait(m_fileStream.WriteAsync(winrt_buffer));
128 return static_cast<ssize_t>(result);
129 }
130 catch (const winrt::hresult_error& ex)
131 {
132 using KODI::PLATFORM::WINDOWS::FromW;
133 CLog::LogF(LOGERROR, "unable write to file ({})", FromW(ex.message().c_str()));
134 return -1;
135 }
136 }
137
Seek(int64_t iFilePosition,int iWhence)138 int64_t CWinLibraryFile::Seek(int64_t iFilePosition, int iWhence)
139 {
140 if (m_fileStream != nullptr)
141 {
142 int64_t pos = iFilePosition;
143 if (iWhence == SEEK_CUR)
144 pos += m_fileStream.Position();
145 else if (iWhence == SEEK_END)
146 pos += m_fileStream.Size();
147
148 uint64_t seekTo;
149 if (pos < 0)
150 seekTo = 0;
151 else if (static_cast<uint64_t>(pos) > m_fileStream.Size())
152 seekTo = m_fileStream.Size();
153 else
154 seekTo = static_cast<uint64_t>(pos);
155
156 m_fileStream.Seek(seekTo);
157 return GetPosition();
158 }
159 return -1;
160 }
161
Truncate(int64_t toSize)162 int CWinLibraryFile::Truncate(int64_t toSize)
163 {
164 // not allowed
165 return -1;
166 }
167
GetPosition()168 int64_t CWinLibraryFile::GetPosition()
169 {
170 if (m_fileStream != nullptr)
171 return static_cast<int64_t>(m_fileStream.Position());
172
173 return -1;
174 }
175
GetLength()176 int64_t CWinLibraryFile::GetLength()
177 {
178 if (m_fileStream != nullptr)
179 return m_fileStream.Size();
180
181 return 0;
182 }
183
Flush()184 void CWinLibraryFile::Flush()
185 {
186 if (m_fileStream != nullptr)
187 m_fileStream.FlushAsync();
188 }
189
Delete(const CURL & url)190 bool CWinLibraryFile::Delete(const CURL & url)
191 {
192 bool success = false;
193 auto file = GetFile(url);
194 if (file)
195 {
196 try
197 {
198 Wait(file.DeleteAsync());
199 }
200 catch(const winrt::hresult_error&)
201 {
202 return false;
203 }
204 return true;
205 }
206 return false;
207 }
208
Rename(const CURL & urlCurrentName,const CURL & urlNewName)209 bool CWinLibraryFile::Rename(const CURL & urlCurrentName, const CURL & urlNewName)
210 {
211 if (!IsValid(urlNewName))
212 return false;
213
214 auto currFile = GetFile(urlCurrentName);
215 if (currFile)
216 {
217 auto destFile = GetFile(urlNewName);
218 if (destFile)
219 {
220 // replace exiting
221 try
222 {
223 Wait(currFile.MoveAndReplaceAsync(destFile));
224 }
225 catch (const winrt::hresult_error&)
226 {
227 return false;
228 }
229 return true;
230 }
231
232 // move
233 CURL defFolder = CURL(urlNewName.GetWithoutFilename());
234 StorageFolder destFolder = CWinLibraryDirectory::GetFolder(defFolder);
235 if (destFolder != nullptr)
236 {
237 try
238 {
239 Wait(currFile.MoveAsync(destFolder));
240 }
241 catch (const winrt::hresult_error&)
242 {
243 return false;
244 }
245 return true;
246 }
247 }
248 return false;
249 }
250
SetHidden(const CURL & url,bool hidden)251 bool CWinLibraryFile::SetHidden(const CURL& url, bool hidden)
252 {
253 return false;
254 }
255
Exists(const CURL & url)256 bool CWinLibraryFile::Exists(const CURL& url)
257 {
258 return GetFile(url) != nullptr;
259 }
260
Stat(const CURL & url,struct __stat64 * statData)261 int CWinLibraryFile::Stat(const CURL& url, struct __stat64* statData)
262 {
263 if (URIUtils::HasSlashAtEnd(url.GetFileName(), false))
264 {
265 // stat for directory
266 return CWinLibraryDirectory::StatDirectory(url, statData);
267 }
268 else
269 {
270 auto file = GetFile(url);
271 return Stat(file, statData);
272 }
273 }
274
Stat(struct __stat64 * statData)275 int CWinLibraryFile::Stat(struct __stat64* statData)
276 {
277 return Stat(m_sFile, statData);
278 }
279
IsInAccessList(const CURL & url)280 bool CWinLibraryFile::IsInAccessList(const CURL& url)
281 {
282 using KODI::PLATFORM::WINDOWS::FromW;
283 static std::string localPath;
284 static std::string packagePath;
285
286 try
287 {
288 if (localPath.empty())
289 localPath = FromW(ApplicationData::Current().LocalFolder().Path().c_str());
290
291 if (packagePath.empty())
292 packagePath = FromW(Package::Current().InstalledLocation().Path().c_str());
293
294 // don't check files inside local folder and installation folder
295 if ( StringUtils::StartsWithNoCase(url.Get(), localPath)
296 || StringUtils::StartsWithNoCase(url.Get(), packagePath))
297 return false;
298
299 return IsInList(url, StorageApplicationPermissions::FutureAccessList())
300 || IsInList(url, StorageApplicationPermissions::MostRecentlyUsedList());
301 }
302 catch (const winrt::hresult_error& ex)
303 {
304 std::string strError = FromW(ex.message().c_str());
305 CLog::LogF(LOGERROR, "unexpected error occurs during WinRT API call: {}", strError);
306 }
307 return false;
308 }
309
OpenIntenal(const CURL & url,FileAccessMode mode)310 bool CWinLibraryFile::OpenIntenal(const CURL &url, FileAccessMode mode)
311 {
312 // cannot open directories
313 if (URIUtils::HasSlashAtEnd(url.GetFileName(), false))
314 return false;
315
316 try
317 {
318 if (mode == FileAccessMode::Read)
319 {
320 m_sFile = GetFile(url);
321 }
322 else if (mode == FileAccessMode::ReadWrite)
323 {
324 auto destFolder = CURL(URIUtils::GetParentPath(url.Get()));
325 auto folder = CWinLibraryDirectory::GetFolder(destFolder);
326 if (folder)
327 {
328 std::wstring fileNameW = ToW(url.GetFileNameWithoutPath());
329 m_sFile = Wait(folder.CreateFileAsync(fileNameW, CreationCollisionOption::ReplaceExisting));
330 if (m_sFile)
331 m_allowWrite = true;
332 }
333 }
334
335 if (m_sFile)
336 m_fileStream = Wait(m_sFile.OpenAsync(mode));
337 }
338 catch (const winrt::hresult_error& ex)
339 {
340 std::string error = FromW(ex.message().c_str());
341 CLog::LogF(LOGERROR, "an exception occurs while openning a file '%s' (mode: %s) : %s"
342 , url.GetRedacted().c_str()
343 , mode == FileAccessMode::Read ? "r" : "rw"
344 , error.c_str());
345 return false;
346 }
347
348 return m_fileStream != nullptr;
349 }
350
GetFile(const CURL & url)351 StorageFile CWinLibraryFile::GetFile(const CURL& url)
352 {
353 // check that url is library url
354 if (CWinLibraryDirectory::IsValid(url))
355 {
356 StorageFolder rootFolder = CWinLibraryDirectory::GetRootFolder(url);
357 if (rootFolder == nullptr)
358 return nullptr;
359
360 std::string filePath = URIUtils::FixSlashesAndDups(url.GetFileName(), '\\');
361 try
362 {
363 std::wstring wpath = ToW(filePath);
364 auto item = Wait(rootFolder.TryGetItemAsync(wpath));
365 if (item && item.IsOfType(StorageItemTypes::File))
366 return item.as<StorageFile>();
367
368 return nullptr;
369 }
370 catch (const winrt::hresult_error& ex)
371 {
372 std::string error = FromW(ex.message().c_str());
373 CLog::LogF(LOGERROR, "unable to get file '%s' with error %s"
374 , url.GetRedacted().c_str()
375 , error.c_str());
376 }
377 }
378 else if (url.IsProtocol("file") || url.GetProtocol().empty())
379 {
380 // check that a file in feature access list or most rescently used list
381 // search in FAL
382 IStorageItemAccessList list = StorageApplicationPermissions::FutureAccessList();
383 winrt::hstring token = GetTokenFromList(url, list);
384 if (token.empty())
385 {
386 // serach in MRU list
387 list = StorageApplicationPermissions::MostRecentlyUsedList();
388 token = GetTokenFromList(url, list);
389 }
390 if (!token.empty())
391 return Wait(list.GetFileAsync(token));
392 }
393
394 return nullptr;
395 }
396
IsInList(const CURL & url,const IStorageItemAccessList & list)397 bool CWinLibraryFile::IsInList(const CURL& url, const IStorageItemAccessList& list)
398 {
399 auto token = GetTokenFromList(url, list);
400 return !token.empty();
401 }
402
GetTokenFromList(const CURL & url,const IStorageItemAccessList & list)403 winrt::hstring CWinLibraryFile::GetTokenFromList(const CURL& url, const IStorageItemAccessList& list)
404 {
405 AccessListEntryView listview = list.Entries();
406 if (listview.Size() == 0)
407 return winrt::hstring();
408
409 using KODI::PLATFORM::WINDOWS::ToW;
410 std::wstring filePathW = ToW(url.Get());
411
412 for(auto&& listEntry : listview)
413 {
414 if (listEntry.Metadata == filePathW)
415 {
416 return listEntry.Token;
417 }
418 }
419
420 return winrt::hstring();
421 }
422
Stat(const StorageFile & file,struct __stat64 * statData)423 int CWinLibraryFile::Stat(const StorageFile& file, struct __stat64* statData)
424 {
425 if (!statData)
426 return -1;
427
428 if (file == nullptr)
429 return -1;
430
431 /* set st_gid */
432 statData->st_gid = 0; // UNIX group ID is always zero on Win32
433 /* set st_uid */
434 statData->st_uid = 0; // UNIX user ID is always zero on Win32
435 /* set st_ino */
436 statData->st_ino = 0; // inode number is not implemented on Win32
437
438 auto requestedProps = Wait(file.Properties().RetrievePropertiesAsync({
439 L"System.DateAccessed",
440 L"System.DateCreated",
441 L"System.DateModified",
442 L"System.Size"
443 }));
444
445 auto dateAccessed = requestedProps.Lookup(L"System.DateAccessed").as<winrt::IPropertyValue>();
446 if (dateAccessed)
447 {
448 statData->st_atime = winrt::clock::to_time_t(dateAccessed.GetDateTime());
449 }
450 auto dateCreated = requestedProps.Lookup(L"System.DateCreated").as<winrt::IPropertyValue>();
451 if (dateCreated)
452 {
453 statData->st_ctime = winrt::clock::to_time_t(dateCreated.GetDateTime());
454 }
455 auto dateModified = requestedProps.Lookup(L"System.DateModified").as<winrt::IPropertyValue>();
456 if (dateModified)
457 {
458 statData->st_mtime = winrt::clock::to_time_t(dateModified.GetDateTime());
459 }
460 auto fileSize = requestedProps.Lookup(L"System.Size").as<winrt::IPropertyValue>();
461 if (fileSize)
462 {
463 /* set st_size */
464 statData->st_size = fileSize.GetInt64();
465 }
466
467 statData->st_dev = 0;
468 statData->st_rdev = statData->st_dev;
469 /* set st_nlink */
470 statData->st_nlink = 1;
471 /* set st_mode */
472 statData->st_mode = _S_IREAD; // only read permission for file from library
473 // copy user RWX rights to group rights
474 statData->st_mode |= (statData->st_mode & (_S_IREAD | _S_IWRITE | _S_IEXEC)) >> 3;
475 // copy user RWX rights to other rights
476 statData->st_mode |= (statData->st_mode & (_S_IREAD | _S_IWRITE | _S_IEXEC)) >> 6;
477
478 return 0;
479 }
480