[WIN32]: fixed browsing wouldn't show directories below the share
[xbmc:xbmc-antiquated.git] / xbmc / win32 / WINSMBDirectory.cpp
1 /*\r
2  *      Copyright (C) 2005-2008 Team XBMC\r
3  *      http://www.xbmc.org\r
4  *\r
5  *  This Program is free software; you can redistribute it and/or modify\r
6  *  it under the terms of the GNU General Public License as published by\r
7  *  the Free Software Foundation; either version 2, or (at your option)\r
8  *  any later version.\r
9  *\r
10  *  This Program is distributed in the hope that it will be useful,\r
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of\r
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\r
13  *  GNU General Public License for more details.\r
14  *\r
15  *  You should have received a copy of the GNU General Public License\r
16  *  along with XBMC; see the file COPYING.  If not, write to\r
17  *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.\r
18  *  http://www.gnu.org/copyleft/gpl.html\r
19  *\r
20  */\r
21 \r
22 \r
23 #include "stdafx.h"\r
24 #include "WINSMBDirectory.h"\r
25 #include "Util.h"\r
26 #include "DirectoryCache.h"\r
27 #include "URL.h"\r
28 #include "GUISettings.h"\r
29 #include "FileItem.h"\r
30 #include "WNETHelper.h"\r
31 #include "WIN32Util.h"\r
32 \r
33 #ifndef INVALID_FILE_ATTRIBUTES\r
34 #define INVALID_FILE_ATTRIBUTES ((DWORD) -1)\r
35 #endif\r
36 \r
37 \r
38 using namespace AUTOPTR;\r
39 using namespace DIRECTORY;\r
40 \r
41 CWINSMBDirectory::CWINSMBDirectory(void)\r
42 {\r
43   m_bHost=false;\r
44 }\r
45 \r
46 CWINSMBDirectory::~CWINSMBDirectory(void)\r
47 {\r
48 }\r
49 \r
50 CStdString CWINSMBDirectory::GetLocal(const CStdString& strPath)\r
51 {\r
52   CURL url(strPath);\r
53   CStdString path( url.GetFileName() );\r
54   if( url.GetProtocol().Equals("smb", false) )\r
55   {\r
56     CStdString host( url.GetHostName() );\r
57 \r
58     if(host.size() > 0)\r
59     {\r
60       path = "//" + host + "/" + path;\r
61     }\r
62   }\r
63   path.Replace('/', '\\');\r
64   return path;\r
65 }\r
66 \r
67 bool CWINSMBDirectory::GetDirectory(const CStdString& strPath1, CFileItemList &items)\r
68 {\r
69   WIN32_FIND_DATAW wfd;\r
70 \r
71   CStdString strPath=strPath1;\r
72 \r
73   CFileItemList vecCacheItems;\r
74   g_directoryCache.ClearDirectory(strPath1);\r
75 \r
76   CURL url(strPath);\r
77 \r
78   if(url.GetShareName().empty())\r
79   {\r
80     LPNETRESOURCE lpnr = NULL;\r
81     bool ret;\r
82     if(!url.GetHostName().empty())\r
83     {\r
84       lpnr = (LPNETRESOURCE) GlobalAlloc(GPTR, 16384);\r
85       if(lpnr == NULL)\r
86         return false;\r
87 \r
88       CStdString strHost = "\\\\" + url.GetHostName();\r
89       lpnr->lpRemoteName = (char*)strHost.c_str();\r
90       m_bHost = true;\r
91       ret = EnumerateFunc(lpnr, items);\r
92       GlobalFree((HGLOBAL) lpnr);\r
93       m_bHost = false;\r
94     }\r
95     else\r
96       ret = EnumerateFunc(lpnr, items);  \r
97  \r
98     return ret; \r
99   }\r
100 \r
101   memset(&wfd, 0, sizeof(wfd));\r
102   //rebuild the URL\r
103   CStdString strUNCShare = "\\\\" + url.GetHostName() + "\\" + url.GetFileName();\r
104   strUNCShare.Replace("/", "\\");\r
105   if(!CUtil::HasSlashAtEnd(strUNCShare))\r
106     strUNCShare.append("\\");\r
107 \r
108   CStdStringW strSearchMask;\r
109   g_charsetConverter.utf8ToW(strUNCShare, strSearchMask, false); \r
110   strSearchMask += "*";\r
111 \r
112   FILETIME localTime;\r
113   CAutoPtrFind hFind ( FindFirstFileW(strSearchMask.c_str(), &wfd));\r
114   \r
115   // on error, check if path exists at all, this will return true if empty folder\r
116   if (!hFind.isValid()) \r
117   {\r
118     DWORD ret = GetLastError();\r
119     if(ret == ERROR_ACCESS_DENIED)\r
120     {\r
121       if(ConnectToShare(url) == false)\r
122         return false;\r
123       hFind.attach(FindFirstFileW(strSearchMask.c_str(), &wfd));\r
124     }\r
125     else\r
126       return Exists(strPath1);\r
127   }\r
128 \r
129   if (hFind.isValid())\r
130   {\r
131     do\r
132     {\r
133       if (wfd.cFileName[0] != 0)\r
134       {\r
135         CStdString strLabel;\r
136         g_charsetConverter.wToUTF8(wfd.cFileName,strLabel);\r
137         if ( (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) )\r
138         {\r
139           if (strLabel != "." && strLabel != "..")\r
140           {\r
141             CFileItemPtr pItem(new CFileItem(strLabel));\r
142             pItem->m_strPath = strPath;\r
143             CUtil::AddSlashAtEnd(pItem->m_strPath);\r
144             pItem->m_strPath += strLabel;\r
145             pItem->m_bIsFolder = true;\r
146             CUtil::AddSlashAtEnd(pItem->m_strPath);\r
147             FileTimeToLocalFileTime(&wfd.ftLastWriteTime, &localTime);\r
148             pItem->m_dateTime=localTime;\r
149 \r
150             vecCacheItems.Add(pItem);\r
151 \r
152             /* Checks if the file is hidden. If it is then we don't really need to add it */\r
153             if (!(wfd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) || g_guiSettings.GetBool("filelists.showhidden"))\r
154               items.Add(pItem);\r
155           }\r
156         }\r
157         else\r
158         {\r
159           CFileItemPtr pItem(new CFileItem(strLabel));\r
160           pItem->m_strPath = strPath;\r
161           CUtil::AddSlashAtEnd(pItem->m_strPath);\r
162           pItem->m_strPath += strLabel;\r
163           pItem->m_bIsFolder = false;\r
164           pItem->m_dwSize = CUtil::ToInt64(wfd.nFileSizeHigh, wfd.nFileSizeLow);\r
165           FileTimeToLocalFileTime(&wfd.ftLastWriteTime, &localTime);\r
166           pItem->m_dateTime=localTime;\r
167 \r
168           /* Checks if the file is hidden. If it is then we don't really need to add it */\r
169           if ((!(wfd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) || g_guiSettings.GetBool("filelists.showhidden")) && IsAllowed(strLabel))\r
170           {\r
171             vecCacheItems.Add(pItem);\r
172             items.Add(pItem);\r
173           }\r
174           else\r
175             vecCacheItems.Add(pItem);\r
176         }\r
177       }\r
178     }\r
179     while (FindNextFileW((HANDLE)hFind, &wfd));\r
180   }\r
181   if (m_cacheDirectory)\r
182     g_directoryCache.SetDirectory(strPath1, vecCacheItems);\r
183   return true;\r
184 }\r
185 \r
186 bool CWINSMBDirectory::Create(const char* strPath)\r
187 {\r
188   CStdString strPath1 = GetLocal(strPath);\r
189   CStdStringW strWPath1;\r
190   g_charsetConverter.utf8ToW(strPath1, strWPath1, false);\r
191   if(::CreateDirectoryW(strWPath1, NULL))\r
192     return true;\r
193   else if(GetLastError() == ERROR_ALREADY_EXISTS)\r
194     return true;\r
195 \r
196   return false;\r
197 }\r
198 \r
199 bool CWINSMBDirectory::Remove(const char* strPath)\r
200 {\r
201   CStdStringW strWPath;\r
202   CStdString strPath1 = GetLocal(strPath);\r
203   g_charsetConverter.utf8ToW(strPath1, strWPath, false);\r
204   return ::RemoveDirectoryW(strWPath) ? true : false;\r
205 }\r
206 \r
207 bool CWINSMBDirectory::Exists(const char* strPath)\r
208 {\r
209   CStdString strReplaced=GetLocal(strPath);\r
210   CStdStringW strWReplaced;\r
211   g_charsetConverter.utf8ToW(strReplaced, strWReplaced, false);\r
212   // this will fail on shares, needs a subdirectory inside a share\r
213   DWORD attributes = GetFileAttributesW(strWReplaced);\r
214   if(attributes == INVALID_FILE_ATTRIBUTES)\r
215     return false;\r
216   if (FILE_ATTRIBUTE_DIRECTORY & attributes) return true;\r
217   return false;\r
218 }\r
219 \r
220 bool CWINSMBDirectory::EnumerateFunc(LPNETRESOURCE lpnr, CFileItemList &items)\r
221 {\r
222   DWORD dwResult, dwResultEnum;\r
223   HANDLE hEnum;\r
224   DWORD cbBuffer = 16384;     // 16K is a good size\r
225   DWORD cEntries = -1;        // enumerate all possible entries\r
226   LPNETRESOURCE lpnrLocal;    // pointer to enumerated structures\r
227   DWORD i;\r
228   //\r
229   // Call the WNetOpenEnum function to begin the enumeration.\r
230   //\r
231   dwResult = WNetOpenEnum(RESOURCE_GLOBALNET, // all network resources\r
232                           RESOURCETYPE_ANY,   // all resources\r
233                           0,  // enumerate all resources\r
234                           lpnr,       // NULL first time the function is called\r
235                           &hEnum);    // handle to the resource\r
236 \r
237   if (dwResult != NO_ERROR) \r
238   {\r
239     CLog::Log(LOGERROR,"WnetOpenEnum failed with error %d", dwResult);\r
240     return false;\r
241   }\r
242   //\r
243   // Call the GlobalAlloc function to allocate resources.\r
244   //\r
245   lpnrLocal = (LPNETRESOURCE) GlobalAlloc(GPTR, cbBuffer);\r
246   if (lpnrLocal == NULL) \r
247   {\r
248     CLog::Log(LOGERROR,"WnetOpenEnum failed with error %d", dwResult);\r
249     return false;\r
250   }\r
251 \r
252   do \r
253   {\r
254     //\r
255     // Initialize the buffer.\r
256     //\r
257     ZeroMemory(lpnrLocal, cbBuffer);\r
258     //\r
259     // Call the WNetEnumResource function to continue\r
260     //  the enumeration.\r
261     //\r
262     dwResultEnum = WNetEnumResource(hEnum,  // resource handle\r
263                                     &cEntries,      // defined locally as -1\r
264                                     lpnrLocal,      // LPNETRESOURCE\r
265                                     &cbBuffer);     // buffer size\r
266     //\r
267     // If the call succeeds, loop through the structures.\r
268     //\r
269     if (dwResultEnum == NO_ERROR) \r
270     {\r
271       for (i = 0; i < cEntries; i++) \r
272       {\r
273         DWORD dwDisplayType = lpnrLocal[i].dwDisplayType;\r
274         DWORD dwType = lpnrLocal[i].dwType;\r
275 \r
276         if(((dwDisplayType == RESOURCEDISPLAYTYPE_SERVER) || \r
277            (dwDisplayType == RESOURCEDISPLAYTYPE_SHARE)) &&\r
278            (dwType != RESOURCETYPE_PRINT))\r
279         {\r
280           CStdString strurl = "smb:";\r
281           CStdString strName = lpnrLocal[i].lpComment;\r
282           CStdString strRemoteName = lpnrLocal[i].lpRemoteName;\r
283 \r
284           CLog::Log(LOGDEBUG,"Found Server/Share: %s", strRemoteName);\r
285 \r
286           strurl.append(strRemoteName);\r
287           strurl.Replace("\\","/");\r
288           CURL rooturl(strurl);\r
289           rooturl.SetFileName("");\r
290 \r
291           if(strName.empty())\r
292           {\r
293             if(!rooturl.GetShareName().empty())\r
294               strName = rooturl.GetShareName();\r
295             else\r
296               strName = rooturl.GetHostName();\r
297 \r
298             strName.Replace("\\","");\r
299           }\r
300 \r
301           CFileItemPtr pItem(new CFileItem(strName));        \r
302           pItem->m_strPath = strurl;\r
303           pItem->m_bIsFolder = true;\r
304           if(((dwDisplayType == RESOURCEDISPLAYTYPE_SERVER) && (m_bHost == false)) ||\r
305              ((dwDisplayType == RESOURCEDISPLAYTYPE_SHARE) && m_bHost))\r
306             items.Add(pItem);\r
307         }\r
308 \r
309         // If the NETRESOURCE structure represents a container resource, \r
310         //  call the EnumerateFunc function recursively.\r
311         if (RESOURCEUSAGE_CONTAINER == (lpnrLocal[i].dwUsage & RESOURCEUSAGE_CONTAINER))\r
312           EnumerateFunc(&lpnrLocal[i], items);\r
313       }\r
314     }\r
315     // Process errors.\r
316     //\r
317     else if (dwResultEnum != ERROR_NO_MORE_ITEMS) \r
318     {\r
319       CLog::Log(LOGERROR,"WNetEnumResource failed with error %d", dwResultEnum);\r
320       break;\r
321     }\r
322   }\r
323   //\r
324   // End do.\r
325   //\r
326   while (dwResultEnum != ERROR_NO_MORE_ITEMS);\r
327   //\r
328   // Call the GlobalFree function to free the memory.\r
329   //\r
330   GlobalFree((HGLOBAL) lpnrLocal);\r
331   //\r
332   // Call WNetCloseEnum to end the enumeration.\r
333   //\r
334   dwResult = WNetCloseEnum(hEnum);\r
335 \r
336   if (dwResult != NO_ERROR) \r
337   {\r
338       //\r
339       // Process errors.\r
340       //\r
341       CLog::Log(LOGERROR,"WNetCloseEnum failed with error %d", dwResult);\r
342       return false;\r
343   }\r
344 \r
345   return true;\r
346 }\r
347 \r
348 bool CWINSMBDirectory::ConnectToShare(const CURL& url)\r
349 {\r
350   NETRESOURCE nr;\r
351   CURL urlIn(url);\r
352   DWORD dwRet=-1;\r
353   CStdString strUNC("\\\\"+url.GetHostName()+"\\"+url.GetShareName());\r
354   CStdString strPath;\r
355   memset(&nr,0,sizeof(nr));\r
356   nr.dwType = RESOURCETYPE_ANY;\r
357   //nr.lpRemoteName = (char*)m_strUNCShare.c_str();\r
358   nr.lpRemoteName = (char*)strUNC.c_str();\r
359 \r
360   // in general we shouldn't need the password manager as we won't disconnect from shares yet\r
361   IMAPPASSWORDS it = g_passwordManager.m_mapSMBPasswordCache.find(strUNC);\r
362   if(it != g_passwordManager.m_mapSMBPasswordCache.end())\r
363   {\r
364     // if share found in cache use it to supply username and password\r
365     CURL murl(it->second);              // map value contains the full url of the originally authenticated share. map key is just the share\r
366     CStdString strPassword = murl.GetPassWord();\r
367     CStdString strUserName = murl.GetUserName();\r
368     urlIn.SetPassword(strPassword);\r
369     urlIn.SetUserName(strUserName);\r
370   }\r
371 \r
372   CStdString strAuth = URLEncode(urlIn);\r
373 \r
374   while(dwRet != NO_ERROR)\r
375   {\r
376     strPath = URLEncode(urlIn);\r
377     dwRet = WNetAddConnection2(&nr,(LPCTSTR)urlIn.GetUserNameA().c_str(), (LPCTSTR)urlIn.GetPassWord().c_str(), NULL);\r
378     if(dwRet == ERROR_ACCESS_DENIED || dwRet == ERROR_INVALID_PASSWORD)\r
379     {\r
380       if (m_allowPrompting)\r
381       {\r
382         g_passwordManager.SetSMBShare(strPath);\r
383         if (!g_passwordManager.GetSMBShareUserPassword())  // Do this bit via a threadmessage?\r
384                 break;\r
385 \r
386         CURL urlnew( g_passwordManager.GetSMBShare() );\r
387         urlIn.SetUserName(urlnew.GetUserName());\r
388         urlIn.SetPassword(urlnew.GetPassWord());\r
389       }\r
390       else\r
391         break;\r
392     }\r
393     else if(dwRet != NO_ERROR)\r
394     {\r
395       break;\r
396     }\r
397   }\r
398 \r
399   if(dwRet != NO_ERROR)\r
400   {\r
401     CLog::Log(LOGERROR,"Couldn't connect to %s, error code %d", strUNC.c_str(), dwRet);\r
402     return false;\r
403   }\r
404   else if (strPath != strAuth && !strUNC.IsEmpty()) // we succeeded so, if path was changed, return the correct one and cache it\r
405   {\r
406     g_passwordManager.m_mapSMBPasswordCache[strUNC] = strPath;\r
407   }  \r
408   return true;\r
409 }\r
410 \r
411 CStdString CWINSMBDirectory::URLEncode(const CURL &url)\r
412 {\r
413   /* due to smb wanting encoded urls we have to build it manually */\r
414 \r
415   CStdString flat = "smb://";\r
416 \r
417   /* samba messes up of password is set but no username is set. don't know why yet */\r
418   /* probably the url parser that goes crazy */\r
419   if(url.GetUserName().length() > 0 /* || url.GetPassWord().length() > 0 */)\r
420   {\r
421     flat += url.GetUserName();\r
422     flat += ":";\r
423     flat += url.GetPassWord();\r
424     flat += "@";\r
425   }\r
426   else if( !url.GetHostName().IsEmpty() && !g_guiSettings.GetString("smb.username").IsEmpty() )\r
427   {\r
428     /* okey this is abit uggly to do this here, as we don't really only url encode */\r
429     /* but it's the simplest place to do so */\r
430     flat += g_guiSettings.GetString("smb.username");\r
431     flat += ":";\r
432     flat += g_guiSettings.GetString("smb.password");\r
433     flat += "@";\r
434   }\r
435 \r
436   flat += url.GetHostName();\r
437 \r
438   /* okey sadly since a slash is an invalid name we have to tokenize */\r
439   std::vector<CStdString> parts;\r
440   std::vector<CStdString>::iterator it;\r
441   CUtil::Tokenize(url.GetFileName(), parts, "/");\r
442   for( it = parts.begin(); it != parts.end(); it++ )\r
443   {\r
444     flat += "/";\r
445     flat += (*it);\r
446   }\r
447 \r
448   /* okey options should go here, thou current samba doesn't support any */\r
449 \r
450   return flat;\r
451 }