Merge pull request #4314 from MartijnKaijser/beta1
[vuplus_xbmc] / xbmc / filesystem / windows / WINSMBDirectory.cpp
1 /*
2  *      Copyright (C) 2005-2013 Team XBMC
3  *      http://xbmc.org
4  *
5  *  This Program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2, or (at your option)
8  *  any later version.
9  *
10  *  This Program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  *  GNU General Public License for more details.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with XBMC; see the file COPYING.  If not, see
17  *  <http://www.gnu.org/licenses/>.
18  *
19  */
20
21
22 #include "WINSMBDirectory.h"
23 #include "URL.h"
24 #include "utils/URIUtils.h"
25 #include "settings/Settings.h"
26 #include "FileItem.h"
27 #include "WIN32Util.h"
28 #include "utils/AutoPtrHandle.h"
29 #include "utils/log.h"
30 #include "utils/CharsetConverter.h"
31 #include "PasswordManager.h"
32 #include "Util.h"
33 #include "utils/StringUtils.h"
34
35 #ifndef INVALID_FILE_ATTRIBUTES
36 #define INVALID_FILE_ATTRIBUTES ((DWORD) -1)
37 #endif
38
39
40 using namespace AUTOPTR;
41 using namespace XFILE;
42
43 CWINSMBDirectory::CWINSMBDirectory(void)
44 {
45   m_bHost=false;
46 }
47
48 CWINSMBDirectory::~CWINSMBDirectory(void)
49 {
50 }
51
52 std::string CWINSMBDirectory::GetLocal(const std::string& strPath)
53 {
54   CURL url(strPath);
55   std::string path(url.GetFileName());
56   if (url.GetProtocol().Equals("smb", false) && !url.GetHostName().empty())
57     path = "\\\\?\\UNC\\" + (std::string&)url.GetHostName() + "\\" + path;
58
59   return path;
60 }
61
62 bool CWINSMBDirectory::GetDirectory(const CStdString& strPath1, CFileItemList &items)
63 {
64   WIN32_FIND_DATAW wfd;
65
66   std::string strPath=strPath1;
67
68   CURL url(strPath);
69
70   if(url.GetShareName().empty())
71   {
72     LPNETRESOURCEW lpnr = NULL;
73     bool ret;
74     if(!url.GetHostName().empty())
75     {
76       lpnr = (LPNETRESOURCEW) GlobalAlloc(GPTR, 16384);
77       if(lpnr == NULL)
78         return false;
79
80       ConnectToShare(url);
81       std::string strHost = "\\\\" + url.GetHostName();
82       std::wstring strHostW;
83       g_charsetConverter.utf8ToW(strHost,strHostW, false, false, true);
84       lpnr->lpRemoteName = (LPWSTR)strHostW.c_str();
85       m_bHost = true;
86       ret = EnumerateFunc(lpnr, items);
87       GlobalFree((HGLOBAL) lpnr);
88       m_bHost = false;
89     }
90     else
91       ret = EnumerateFunc(lpnr, items);
92
93     return ret;
94   }
95
96   memset(&wfd, 0, sizeof(wfd));
97   //rebuild the URL
98   std::wstring strSearchMask(CWIN32Util::ConvertPathToWin32Form(GetLocal(strPath)));
99   if (!strSearchMask.empty() && strSearchMask[strSearchMask.length() - 1] == '\\')
100     strSearchMask += L'*';
101   else
102     strSearchMask += L"\\*";
103
104   FILETIME localTime;
105   CAutoPtrFind hFind ( FindFirstFileW(strSearchMask.c_str(), &wfd));
106
107   // on error, check if path exists at all, this will return true if empty folder
108   if (!hFind.isValid())
109   {
110     DWORD ret = GetLastError();
111     if(ret == ERROR_INVALID_PASSWORD || ret == ERROR_LOGON_FAILURE || ret == ERROR_ACCESS_DENIED || ret == ERROR_INVALID_HANDLE)
112     {
113       if(ConnectToShare(url) == false)
114         return false;
115       hFind.attach(FindFirstFileW(strSearchMask.c_str(), &wfd));
116     }
117     else
118       return Exists(strPath1.c_str());
119   }
120
121   if (hFind.isValid())
122   {
123     do
124     {
125       if (wfd.cFileName[0] != 0)
126       {
127         std::string strLabel;
128         g_charsetConverter.wToUTF8(wfd.cFileName,strLabel, true);
129         if ( (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) )
130         {
131           if (strLabel != "." && strLabel != "..")
132           {
133             CFileItemPtr pItem(new CFileItem(strLabel));
134             std::string path = URIUtils::AddFileToFolder(strPath, strLabel);
135             URIUtils::AddSlashAtEnd(path);
136             pItem->SetPath(path);
137             pItem->m_bIsFolder = true;
138             FileTimeToLocalFileTime(&wfd.ftLastWriteTime, &localTime);
139             pItem->m_dateTime=localTime;
140
141             if (wfd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN)
142               pItem->SetProperty("file:hidden", true);
143
144             items.Add(pItem);
145           }
146         }
147         else
148         {
149           CFileItemPtr pItem(new CFileItem(strLabel));
150           pItem->SetPath(URIUtils::AddFileToFolder(strPath, strLabel));
151           pItem->m_bIsFolder = false;
152           pItem->m_dwSize = CUtil::ToInt64(wfd.nFileSizeHigh, wfd.nFileSizeLow);
153           FileTimeToLocalFileTime(&wfd.ftLastWriteTime, &localTime);
154           pItem->m_dateTime=localTime;
155
156           if (wfd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN)
157             pItem->SetProperty("file:hidden", true);
158           items.Add(pItem);
159         }
160       }
161     }
162     while (FindNextFileW((HANDLE)hFind, &wfd));
163   }
164   return true;
165 }
166
167 bool CWINSMBDirectory::Create(const char* strPath)
168 {
169   if(::CreateDirectoryW(CWIN32Util::ConvertPathToWin32Form(GetLocal(strPath)).c_str(), NULL))
170     return true;
171   else if(GetLastError() == ERROR_ALREADY_EXISTS)
172     return true;
173
174   return false;
175 }
176
177 bool CWINSMBDirectory::Remove(const char* strPath)
178 {
179   return ::RemoveDirectoryW(CWIN32Util::ConvertPathToWin32Form(GetLocal(strPath)).c_str()) ? true : false;
180 }
181
182 bool CWINSMBDirectory::Exists(const char* strPath)
183 {
184   DWORD attributes = GetFileAttributesW(CWIN32Util::ConvertPathToWin32Form(GetLocal(strPath)).c_str());
185   if(attributes == INVALID_FILE_ATTRIBUTES)
186     return false;
187   if (FILE_ATTRIBUTE_DIRECTORY & attributes)
188     return true;
189   return false;
190 }
191
192 bool CWINSMBDirectory::EnumerateFunc(LPNETRESOURCEW lpnr, CFileItemList &items)
193 {
194   DWORD dwResult, dwResultEnum;
195   HANDLE hEnum;
196   DWORD cbBuffer = 16384;     // 16K is a good size
197   LPNETRESOURCEW lpnrLocal;   // pointer to enumerated structures
198   DWORD cEntries = -1;        // enumerate all possible entries
199   //
200   // Call the WNetOpenEnum function to begin the enumeration.
201   //
202   dwResult = WNetOpenEnumW( RESOURCE_GLOBALNET,  // all network resources
203                             RESOURCETYPE_DISK,   // all disk resources
204                             0,                   // enumerate all resources
205                             lpnr,                // NULL first time the function is called
206                             &hEnum);             // handle to the resource
207
208   if (dwResult != NO_ERROR)
209   {
210     CLog::Log(LOGERROR,"WnetOpenEnum failed with error %d", dwResult);
211     if(dwResult == ERROR_EXTENDED_ERROR)
212     {
213       DWORD dwWNetResult, dwLastError;
214       CHAR szDescription[256];
215       CHAR szProvider[256];
216       dwWNetResult = WNetGetLastError(&dwLastError, // error code
217                             (LPSTR) szDescription,  // buffer for error description
218                             sizeof(szDescription),  // size of error buffer
219                             (LPSTR) szProvider,     // buffer for provider name
220                             sizeof(szProvider));    // size of name buffer
221       if(dwWNetResult == NO_ERROR)
222         CLog::Log(LOGERROR,"%s failed with code %ld; %s", szProvider, dwLastError, szDescription);
223     }
224     return false;
225   }
226   //
227   // Call the GlobalAlloc function to allocate resources.
228   //
229   lpnrLocal = (LPNETRESOURCEW) GlobalAlloc(GPTR, cbBuffer);
230   if (lpnrLocal == NULL)
231   {
232     CLog::Log(LOGERROR,"Can't allocate buffer %d", cbBuffer);
233     return false;
234   }
235
236   do
237   {
238     //
239     // Initialize the buffer.
240     //
241     ZeroMemory(lpnrLocal, cbBuffer);
242     //
243     // Call the WNetEnumResource function to continue
244     //  the enumeration.
245     //
246     dwResultEnum = WNetEnumResourceW( hEnum,          // resource handle
247                                       &cEntries,      // defined locally as -1
248                                       lpnrLocal,      // LPNETRESOURCE
249                                       &cbBuffer);     // buffer size
250     //
251     // If the call succeeds, loop through the structures.
252     //
253     if (dwResultEnum == NO_ERROR)
254     {
255       for (DWORD i = 0; i < cEntries; i++)
256       {
257         DWORD dwDisplayType = lpnrLocal[i].dwDisplayType;
258         DWORD dwType = lpnrLocal[i].dwType;
259
260         if((((dwDisplayType == RESOURCEDISPLAYTYPE_SERVER) && (m_bHost == false)) ||
261            ((dwDisplayType == RESOURCEDISPLAYTYPE_SHARE) && m_bHost)) &&
262            (dwType != RESOURCETYPE_PRINT))
263         {
264           CStdString strurl = "smb:";
265           CStdStringW strRemoteNameW = lpnrLocal[i].lpRemoteName;
266           CStdString  strName,strRemoteName;
267
268           g_charsetConverter.wToUTF8(strRemoteNameW,strRemoteName, true);
269           CLog::Log(LOGDEBUG,"Found Server/Share: %s", strRemoteName.c_str());
270
271           strurl.append(strRemoteName);
272           StringUtils::Replace(strurl, '\\', '/');
273           CURL rooturl(strurl);
274           rooturl.SetFileName("");
275
276           if(!rooturl.GetShareName().empty())
277             strName = rooturl.GetShareName();
278           else
279             strName = rooturl.GetHostName();
280
281           StringUtils::Replace(strName, "\\", "");
282
283           URIUtils::AddSlashAtEnd(strurl);
284           CFileItemPtr pItem(new CFileItem(strName));
285           pItem->SetPath(strurl);
286           pItem->m_bIsFolder = true;
287           items.Add(pItem);
288         }
289
290         // If the NETRESOURCE structure represents a container resource,
291         //  call the EnumerateFunc function recursively.
292         if(RESOURCEUSAGE_CONTAINER == (lpnrLocal[i].dwUsage & RESOURCEUSAGE_CONTAINER) && lpnrLocal[i].lpRemoteName != NULL)
293           EnumerateFunc(&lpnrLocal[i], items);
294       }
295     }
296     // Process errors.
297     //
298     else if (dwResultEnum != ERROR_NO_MORE_ITEMS)
299     {
300       CLog::Log(LOGERROR,"WNetEnumResource failed with error %d", dwResultEnum);
301       break;
302     }
303   }
304   //
305   // End do.
306   //
307   while (dwResultEnum != ERROR_NO_MORE_ITEMS);
308   //
309   // Call the GlobalFree function to free the memory.
310   //
311   GlobalFree((HGLOBAL) lpnrLocal);
312   //
313   // Call WNetCloseEnum to end the enumeration.
314   //
315   dwResult = WNetCloseEnum(hEnum);
316
317   if (dwResult != NO_ERROR)
318   {
319       //
320       // Process errors.
321       //
322       CLog::Log(LOGERROR,"WNetCloseEnum failed with error %d", dwResult);
323       return false;
324   }
325
326   return true;
327 }
328
329 bool CWINSMBDirectory::ConnectToShare(const CURL& url)
330 {
331   NETRESOURCE nr;
332   CURL urlIn(url);
333   DWORD dwRet=-1;
334   CStdString strUNC("\\\\"+url.GetHostName());
335   if(!url.GetShareName().empty())
336     strUNC.append("\\"+url.GetShareName());
337
338   CStdString strPath;
339   memset(&nr,0,sizeof(nr));
340   nr.dwType = RESOURCETYPE_ANY;
341   nr.lpRemoteName = (char*)strUNC.c_str();
342
343   // in general we shouldn't need the password manager as we won't disconnect from shares yet
344   CPasswordManager::GetInstance().AuthenticateURL(urlIn);
345
346   CStdString strAuth = URLEncode(urlIn);
347
348   while(dwRet != NO_ERROR)
349   {
350     strPath = URLEncode(urlIn);
351     LPCTSTR pUser = urlIn.GetUserNameA().empty() ? NULL : (LPCTSTR)urlIn.GetUserNameA().c_str();
352     LPCTSTR pPass = urlIn.GetPassWord().empty() ? NULL : (LPCTSTR)urlIn.GetPassWord().c_str();
353     dwRet = WNetAddConnection2(&nr, pPass, pUser, CONNECT_TEMPORARY);
354 #ifdef _DEBUG
355     CLog::Log(LOGDEBUG,"Trying to connect to %s with username(%s) and password(%s)", strUNC.c_str(), urlIn.GetUserNameA().c_str(), urlIn.GetPassWord().c_str());
356 #else
357     CLog::Log(LOGDEBUG,"Trying to connect to %s with username(%s) and password(%s)", strUNC.c_str(), urlIn.GetUserNameA().c_str(), "XXXX");
358 #endif
359     if(dwRet == ERROR_ACCESS_DENIED || dwRet == ERROR_INVALID_PASSWORD || dwRet == ERROR_LOGON_FAILURE)
360     {
361       CLog::Log(LOGERROR,"Couldn't connect to %s, access denied", strUNC.c_str());
362       if (m_flags & DIR_FLAG_ALLOW_PROMPT)
363         RequireAuthentication(urlIn.Get());
364       break;
365     }
366     else if(dwRet == ERROR_SESSION_CREDENTIAL_CONFLICT)
367     {
368       DWORD dwRet2=-1;
369       CStdString strRN = nr.lpRemoteName;
370       do
371       {
372         dwRet2 = WNetCancelConnection2((LPCSTR)strRN.c_str(), 0, false);
373         strRN.erase(strRN.find_last_of("\\"),CStdString::npos);
374       }
375       while(dwRet2 == ERROR_NOT_CONNECTED && !strRN.Equals("\\\\"));
376     }
377     else if(dwRet != NO_ERROR)
378     {
379       break;
380     }
381   }
382
383   if(dwRet != NO_ERROR)
384   {
385     CLog::Log(LOGERROR,"Couldn't connect to %s, error code %d", strUNC.c_str(), dwRet);
386     return false;
387   }
388   return true;
389 }
390
391 std::string CWINSMBDirectory::URLEncode(const CURL &url)
392 {
393   /* due to smb wanting encoded urls we have to build it manually */
394
395   std::string flat = "smb://";
396
397   /* samba messes up of password is set but no username is set. don't know why yet */
398   /* probably the url parser that goes crazy */
399   if(url.GetUserName().length() > 0 /* || url.GetPassWord().length() > 0 */)
400   {
401     flat += url.GetUserName();
402     flat += ":";
403     flat += url.GetPassWord();
404     flat += "@";
405   }
406   flat += url.GetHostName();
407
408   /* okey sadly since a slash is an invalid name we have to tokenize */
409   std::vector<std::string> parts;
410   std::vector<std::string>::iterator it;
411   StringUtils::Tokenize(url.GetFileName(), parts, "/");
412   for( it = parts.begin(); it != parts.end(); it++ )
413   {
414     flat += "/";
415     flat += (*it);
416   }
417
418   /* okey options should go here, thou current samba doesn't support any */
419
420   return flat;
421 }