1 /*
2  *  Copyright (C) 2005-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 "Directory.h"
10 
11 #include "Application.h"
12 #include "DirectoryCache.h"
13 #include "DirectoryFactory.h"
14 #include "FileDirectoryFactory.h"
15 #include "FileItem.h"
16 #include "PasswordManager.h"
17 #include "ServiceBroker.h"
18 #include "URL.h"
19 #include "commons/Exception.h"
20 #include "dialogs/GUIDialogBusy.h"
21 #include "guilib/GUIWindowManager.h"
22 #include "settings/Settings.h"
23 #include "settings/SettingsComponent.h"
24 #include "utils/Job.h"
25 #include "utils/JobManager.h"
26 #include "utils/URIUtils.h"
27 #include "utils/log.h"
28 
29 using namespace XFILE;
30 
31 #define TIME_TO_BUSY_DIALOG 500
32 
33 class CGetDirectory
34 {
35 private:
36 
37   struct CResult
38   {
CResultCGetDirectory::CResult39     CResult(const CURL& dir, const CURL& listDir) : m_event(true), m_dir(dir), m_listDir(listDir), m_result(false) {}
40     CEvent        m_event;
41     CFileItemList m_list;
42     CURL          m_dir;
43     CURL          m_listDir;
44     bool          m_result;
45   };
46 
47   struct CGetJob
48     : CJob
49   {
CGetJobCGetDirectory::CGetJob50     CGetJob(std::shared_ptr<IDirectory>& imp
51           , std::shared_ptr<CResult>& result)
52       : m_result(result)
53       , m_imp(imp)
54     {}
55   public:
DoWorkCGetDirectory::CGetJob56     bool DoWork() override
57     {
58       m_result->m_list.SetURL(m_result->m_listDir);
59       m_result->m_result         = m_imp->GetDirectory(m_result->m_dir, m_result->m_list);
60       m_result->m_event.Set();
61       return m_result->m_result;
62     }
63 
64     std::shared_ptr<CResult>    m_result;
65     std::shared_ptr<IDirectory> m_imp;
66   };
67 
68 public:
69 
CGetDirectory(std::shared_ptr<IDirectory> & imp,const CURL & dir,const CURL & listDir)70   CGetDirectory(std::shared_ptr<IDirectory>& imp, const CURL& dir, const CURL& listDir)
71     : m_result(new CResult(dir, listDir))
72   {
73     m_id = CJobManager::GetInstance().AddJob(new CGetJob(imp, m_result)
74                                            , NULL
75                                            , CJob::PRIORITY_HIGH);
76     if (m_id == 0)
77     {
78       CGetJob job(imp, m_result);
79       job.DoWork();
80     }
81   }
~CGetDirectory()82  ~CGetDirectory()
83   {
84     CJobManager::GetInstance().CancelJob(m_id);
85   }
86 
GetEvent()87   CEvent& GetEvent()
88   {
89     return m_result->m_event;
90   }
91 
Wait(unsigned int timeout)92   bool Wait(unsigned int timeout)
93   {
94     return m_result->m_event.WaitMSec(timeout);
95   }
96 
GetDirectory(CFileItemList & list)97   bool GetDirectory(CFileItemList& list)
98   {
99     /* if it was not finished or failed, return failure */
100     if(!m_result->m_event.WaitMSec(0) || !m_result->m_result)
101     {
102       list.Clear();
103       return false;
104     }
105 
106     list.Copy(m_result->m_list);
107     return true;
108   }
109   std::shared_ptr<CResult> m_result;
110   unsigned int               m_id;
111 };
112 
113 
114 CDirectory::CDirectory() = default;
115 
116 CDirectory::~CDirectory() = default;
117 
GetDirectory(const std::string & strPath,CFileItemList & items,const std::string & strMask,int flags)118 bool CDirectory::GetDirectory(const std::string& strPath, CFileItemList &items, const std::string &strMask, int flags)
119 {
120   CHints hints;
121   hints.flags = flags;
122   hints.mask = strMask;
123   const CURL pathToUrl(strPath);
124   return GetDirectory(pathToUrl, items, hints);
125 }
126 
GetDirectory(const std::string & strPath,const std::shared_ptr<IDirectory> & pDirectory,CFileItemList & items,const std::string & strMask,int flags)127 bool CDirectory::GetDirectory(const std::string& strPath,
128                               const std::shared_ptr<IDirectory>& pDirectory,
129                               CFileItemList& items,
130                               const std::string& strMask,
131                               int flags)
132 {
133   CHints hints;
134   hints.flags = flags;
135   hints.mask = strMask;
136   const CURL pathToUrl(strPath);
137   return GetDirectory(pathToUrl, pDirectory, items, hints);
138 }
139 
GetDirectory(const std::string & strPath,CFileItemList & items,const CHints & hints)140 bool CDirectory::GetDirectory(const std::string& strPath, CFileItemList &items, const CHints &hints)
141 {
142   const CURL pathToUrl(strPath);
143   return GetDirectory(pathToUrl, items, hints);
144 }
145 
GetDirectory(const CURL & url,CFileItemList & items,const std::string & strMask,int flags)146 bool CDirectory::GetDirectory(const CURL& url, CFileItemList &items, const std::string &strMask, int flags)
147 {
148   CHints hints;
149   hints.flags = flags;
150   hints.mask = strMask;
151   return GetDirectory(url, items, hints);
152 }
153 
GetDirectory(const CURL & url,CFileItemList & items,const CHints & hints)154 bool CDirectory::GetDirectory(const CURL& url, CFileItemList &items, const CHints &hints)
155 {
156   CURL realURL = URIUtils::SubstitutePath(url);
157   std::shared_ptr<IDirectory> pDirectory(CDirectoryFactory::Create(realURL));
158   return CDirectory::GetDirectory(url, pDirectory, items, hints);
159 }
160 
GetDirectory(const CURL & url,const std::shared_ptr<IDirectory> & pDirectory,CFileItemList & items,const CHints & hints)161 bool CDirectory::GetDirectory(const CURL& url,
162                               const std::shared_ptr<IDirectory>& pDirectory,
163                               CFileItemList& items,
164                               const CHints& hints)
165 {
166   try
167   {
168     CURL realURL = URIUtils::SubstitutePath(url);
169     if (!pDirectory)
170       return false;
171 
172     // check our cache for this path
173     if (g_directoryCache.GetDirectory(realURL.Get(), items, (hints.flags & DIR_FLAG_READ_CACHE) == DIR_FLAG_READ_CACHE))
174       items.SetURL(url);
175     else
176     {
177       // need to clear the cache (in case the directory fetch fails)
178       // and (re)fetch the folder
179       if (!(hints.flags & DIR_FLAG_BYPASS_CACHE))
180         g_directoryCache.ClearDirectory(realURL.Get());
181 
182       pDirectory->SetFlags(hints.flags);
183 
184       bool result = false, cancel = false;
185       CURL authUrl = realURL;
186 
187       while (!result && !cancel)
188       {
189         const std::string pathToUrl(url.Get());
190 
191         // don't change auth if it's set explicitly
192         if (CPasswordManager::GetInstance().IsURLSupported(authUrl) && authUrl.GetUserName().empty())
193           CPasswordManager::GetInstance().AuthenticateURL(authUrl);
194 
195         items.SetURL(url);
196         result = pDirectory->GetDirectory(authUrl, items);
197 
198         if (!result)
199         {
200           if (!cancel)
201           {
202             // @TODO ProcessRequirements() can bring up the keyboard input dialog
203             // filesystem must not depend on GUI
204             if (g_application.IsCurrentThread() && pDirectory->ProcessRequirements())
205             {
206               authUrl.SetDomain("");
207               authUrl.SetUserName("");
208               authUrl.SetPassword("");
209               continue;
210             }
211           }
212           CLog::Log(LOGERROR, "%s - Error getting %s", __FUNCTION__, url.GetRedacted().c_str());
213           return false;
214         }
215       }
216 
217       // hide credentials if necessary
218       if (CPasswordManager::GetInstance().IsURLSupported(realURL))
219       {
220         bool hide = false;
221         // for explicitly credentials
222         if (!realURL.GetUserName().empty())
223         {
224           // credentials was changed i.e. were stored in the password
225           // manager, in this case we can hide them from an item URL,
226           // otherwise we have to keep credentials in an item URL
227           if ( realURL.GetUserName() != authUrl.GetUserName()
228             || realURL.GetPassWord() != authUrl.GetPassWord()
229             || realURL.GetDomain() != authUrl.GetDomain())
230           {
231             hide = true;
232           }
233         }
234         else
235         {
236           // hide credentials in any other cases
237           hide = true;
238         }
239 
240         if (hide)
241         {
242           for (int i = 0; i < items.Size(); ++i)
243           {
244             CFileItemPtr item = items[i];
245             CURL itemUrl = item->GetURL();
246             itemUrl.SetDomain("");
247             itemUrl.SetUserName("");
248             itemUrl.SetPassword("");
249             item->SetPath(itemUrl.Get());
250           }
251         }
252       }
253 
254       // cache the directory, if necessary
255       if (!(hints.flags & DIR_FLAG_BYPASS_CACHE))
256         g_directoryCache.SetDirectory(realURL.Get(), items, pDirectory->GetCacheType(url));
257     }
258 
259     // now filter for allowed files
260     if (!pDirectory->AllowAll())
261     {
262       pDirectory->SetMask(hints.mask);
263       for (int i = 0; i < items.Size(); ++i)
264       {
265         CFileItemPtr item = items[i];
266         if (!item->m_bIsFolder && !pDirectory->IsAllowed(item->GetURL()))
267         {
268           items.Remove(i);
269           i--; // don't confuse loop
270         }
271       }
272     }
273     // filter hidden files
274     //! @todo we shouldn't be checking the gui setting here, callers should use getHidden instead
275     if (!CServiceBroker::GetSettingsComponent()->GetSettings()->GetBool(CSettings::SETTING_FILELISTS_SHOWHIDDEN) && !(hints.flags & DIR_FLAG_GET_HIDDEN))
276     {
277       for (int i = 0; i < items.Size(); ++i)
278       {
279         if (items[i]->GetProperty("file:hidden").asBoolean())
280         {
281           items.Remove(i);
282           i--; // don't confuse loop
283         }
284       }
285     }
286 
287     //  Should any of the files we read be treated as a directory?
288     //  Disable for database folders, as they already contain the extracted items
289     if (!(hints.flags & DIR_FLAG_NO_FILE_DIRS) && !items.IsMusicDb() && !items.IsVideoDb() && !items.IsSmartPlayList())
290       FilterFileDirectories(items, hints.mask);
291 
292     // Correct items for path substitution
293     const std::string pathToUrl(url.Get());
294     const std::string pathToUrl2(realURL.Get());
295     if (pathToUrl != pathToUrl2)
296     {
297       for (int i = 0; i < items.Size(); ++i)
298       {
299         CFileItemPtr item = items[i];
300         item->SetPath(URIUtils::SubstitutePath(item->GetPath(), true));
301       }
302     }
303 
304     return true;
305   }
306   XBMCCOMMONS_HANDLE_UNCHECKED
307   catch (...)
308   {
309     CLog::Log(LOGERROR, "%s - Unhandled exception", __FUNCTION__);
310   }
311   CLog::Log(LOGERROR, "%s - Error getting %s", __FUNCTION__, url.GetRedacted().c_str());
312   return false;
313 }
314 
Create(const std::string & strPath)315 bool CDirectory::Create(const std::string& strPath)
316 {
317   const CURL pathToUrl(strPath);
318   return Create(pathToUrl);
319 }
320 
Create(const CURL & url)321 bool CDirectory::Create(const CURL& url)
322 {
323   try
324   {
325     CURL realURL = URIUtils::SubstitutePath(url);
326 
327     if (CPasswordManager::GetInstance().IsURLSupported(realURL) && realURL.GetUserName().empty())
328       CPasswordManager::GetInstance().AuthenticateURL(realURL);
329 
330     std::unique_ptr<IDirectory> pDirectory(CDirectoryFactory::Create(realURL));
331     if (pDirectory)
332       if(pDirectory->Create(realURL))
333         return true;
334   }
335   XBMCCOMMONS_HANDLE_UNCHECKED
336   catch (...)
337   {
338     CLog::Log(LOGERROR, "%s - Unhandled exception", __FUNCTION__);
339   }
340   CLog::Log(LOGERROR, "%s - Error creating %s", __FUNCTION__, url.GetRedacted().c_str());
341   return false;
342 }
343 
Exists(const std::string & strPath,bool bUseCache)344 bool CDirectory::Exists(const std::string& strPath, bool bUseCache /* = true */)
345 {
346   const CURL pathToUrl(strPath);
347   return Exists(pathToUrl, bUseCache);
348 }
349 
Exists(const CURL & url,bool bUseCache)350 bool CDirectory::Exists(const CURL& url, bool bUseCache /* = true */)
351 {
352   try
353   {
354     CURL realURL = URIUtils::SubstitutePath(url);
355     if (bUseCache)
356     {
357       bool bPathInCache;
358       std::string realPath(realURL.Get());
359       URIUtils::AddSlashAtEnd(realPath);
360       if (g_directoryCache.FileExists(realPath, bPathInCache))
361         return true;
362       if (bPathInCache)
363         return false;
364     }
365 
366     if (CPasswordManager::GetInstance().IsURLSupported(realURL) && realURL.GetUserName().empty())
367       CPasswordManager::GetInstance().AuthenticateURL(realURL);
368 
369     std::unique_ptr<IDirectory> pDirectory(CDirectoryFactory::Create(realURL));
370     if (pDirectory)
371       return pDirectory->Exists(realURL);
372   }
373   XBMCCOMMONS_HANDLE_UNCHECKED
374   catch (...)
375   {
376     CLog::Log(LOGERROR, "%s - Unhandled exception", __FUNCTION__);
377   }
378   CLog::Log(LOGERROR, "%s - Error checking for %s", __FUNCTION__, url.GetRedacted().c_str());
379   return false;
380 }
381 
Remove(const std::string & strPath)382 bool CDirectory::Remove(const std::string& strPath)
383 {
384   const CURL pathToUrl(strPath);
385   return Remove(pathToUrl);
386 }
387 
RemoveRecursive(const std::string & strPath)388 bool CDirectory::RemoveRecursive(const std::string& strPath)
389 {
390   return RemoveRecursive(CURL{ strPath });
391 }
392 
Remove(const CURL & url)393 bool CDirectory::Remove(const CURL& url)
394 {
395   try
396   {
397     CURL realURL = URIUtils::SubstitutePath(url);
398     CURL authUrl = realURL;
399     if (CPasswordManager::GetInstance().IsURLSupported(authUrl) && authUrl.GetUserName().empty())
400       CPasswordManager::GetInstance().AuthenticateURL(authUrl);
401 
402     std::unique_ptr<IDirectory> pDirectory(CDirectoryFactory::Create(realURL));
403     if (pDirectory)
404       if(pDirectory->Remove(authUrl))
405       {
406         g_directoryCache.ClearFile(realURL.Get());
407         return true;
408       }
409   }
410   XBMCCOMMONS_HANDLE_UNCHECKED
411   catch (...)
412   {
413     CLog::Log(LOGERROR, "%s - Unhandled exception", __FUNCTION__);
414   }
415   CLog::Log(LOGERROR, "%s - Error removing %s", __FUNCTION__, url.GetRedacted().c_str());
416   return false;
417 }
418 
RemoveRecursive(const CURL & url)419 bool CDirectory::RemoveRecursive(const CURL& url)
420 {
421   try
422   {
423     CURL realURL = URIUtils::SubstitutePath(url);
424     CURL authUrl = realURL;
425     if (CPasswordManager::GetInstance().IsURLSupported(authUrl) && authUrl.GetUserName().empty())
426       CPasswordManager::GetInstance().AuthenticateURL(authUrl);
427 
428     std::unique_ptr<IDirectory> pDirectory(CDirectoryFactory::Create(realURL));
429     if (pDirectory)
430       if(pDirectory->RemoveRecursive(authUrl))
431       {
432         g_directoryCache.ClearFile(realURL.Get());
433         return true;
434       }
435   }
436   XBMCCOMMONS_HANDLE_UNCHECKED
437   catch (...)
438   {
439     CLog::Log(LOGERROR, "%s - Unhandled exception", __FUNCTION__);
440   }
441   CLog::Log(LOGERROR, "%s - Error removing %s", __FUNCTION__, url.GetRedacted().c_str());
442   return false;
443 }
444 
FilterFileDirectories(CFileItemList & items,const std::string & mask,bool expandImages)445 void CDirectory::FilterFileDirectories(CFileItemList &items, const std::string &mask,
446                                        bool expandImages)
447 {
448   for (int i=0; i< items.Size(); ++i)
449   {
450     CFileItemPtr pItem=items[i];
451     auto mode = expandImages && pItem->IsDiscImage() ? EFILEFOLDER_TYPE_ONBROWSE : EFILEFOLDER_TYPE_ALWAYS;
452     if (!pItem->m_bIsFolder && pItem->IsFileFolder(mode))
453     {
454       std::unique_ptr<IFileDirectory> pDirectory(CFileDirectoryFactory::Create(pItem->GetURL(),pItem.get(),mask));
455       if (pDirectory)
456         pItem->m_bIsFolder = true;
457       else
458         if (pItem->m_bIsFolder)
459         {
460           items.Remove(i);
461           i--; // don't confuse loop
462         }
463     }
464   }
465 }
466