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