2 * Copyright (C) 2005-2013 Team XBMC
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)
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.
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/>.
22 #include "WINSMBDirectory.h"
24 #include "utils/URIUtils.h"
25 #include "settings/Settings.h"
27 #include "WIN32Util.h"
28 #include "utils/AutoPtrHandle.h"
29 #include "utils/log.h"
30 #include "utils/CharsetConverter.h"
31 #include "PasswordManager.h"
33 #include "utils/StringUtils.h"
35 #ifndef INVALID_FILE_ATTRIBUTES
36 #define INVALID_FILE_ATTRIBUTES ((DWORD) -1)
40 using namespace AUTOPTR;
41 using namespace XFILE;
43 CWINSMBDirectory::CWINSMBDirectory(void)
48 CWINSMBDirectory::~CWINSMBDirectory(void)
52 std::string CWINSMBDirectory::GetLocal(const std::string& 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;
62 bool CWINSMBDirectory::GetDirectory(const CStdString& strPath1, CFileItemList &items)
66 std::string strPath=strPath1;
70 if(url.GetShareName().empty())
72 LPNETRESOURCEW lpnr = NULL;
74 if(!url.GetHostName().empty())
76 lpnr = (LPNETRESOURCEW) GlobalAlloc(GPTR, 16384);
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();
86 ret = EnumerateFunc(lpnr, items);
87 GlobalFree((HGLOBAL) lpnr);
91 ret = EnumerateFunc(lpnr, items);
96 memset(&wfd, 0, sizeof(wfd));
98 std::wstring strSearchMask(CWIN32Util::ConvertPathToWin32Form(GetLocal(strPath)));
99 if (!strSearchMask.empty() && strSearchMask[strSearchMask.length() - 1] == '\\')
100 strSearchMask += L'*';
102 strSearchMask += L"\\*";
105 CAutoPtrFind hFind ( FindFirstFileW(strSearchMask.c_str(), &wfd));
107 // on error, check if path exists at all, this will return true if empty folder
108 if (!hFind.isValid())
110 DWORD ret = GetLastError();
111 if(ret == ERROR_INVALID_PASSWORD || ret == ERROR_LOGON_FAILURE || ret == ERROR_ACCESS_DENIED || ret == ERROR_INVALID_HANDLE)
113 if(ConnectToShare(url) == false)
115 hFind.attach(FindFirstFileW(strSearchMask.c_str(), &wfd));
118 return Exists(strPath1.c_str());
125 if (wfd.cFileName[0] != 0)
127 std::string strLabel;
128 g_charsetConverter.wToUTF8(wfd.cFileName,strLabel, true);
129 if ( (wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) )
131 if (strLabel != "." && strLabel != "..")
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;
141 if (wfd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN)
142 pItem->SetProperty("file:hidden", true);
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;
156 if (wfd.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN)
157 pItem->SetProperty("file:hidden", true);
162 while (FindNextFileW((HANDLE)hFind, &wfd));
167 bool CWINSMBDirectory::Create(const char* strPath)
169 if(::CreateDirectoryW(CWIN32Util::ConvertPathToWin32Form(GetLocal(strPath)).c_str(), NULL))
171 else if(GetLastError() == ERROR_ALREADY_EXISTS)
177 bool CWINSMBDirectory::Remove(const char* strPath)
179 return ::RemoveDirectoryW(CWIN32Util::ConvertPathToWin32Form(GetLocal(strPath)).c_str()) ? true : false;
182 bool CWINSMBDirectory::Exists(const char* strPath)
184 DWORD attributes = GetFileAttributesW(CWIN32Util::ConvertPathToWin32Form(GetLocal(strPath)).c_str());
185 if(attributes == INVALID_FILE_ATTRIBUTES)
187 if (FILE_ATTRIBUTE_DIRECTORY & attributes)
192 bool CWINSMBDirectory::EnumerateFunc(LPNETRESOURCEW lpnr, CFileItemList &items)
194 DWORD dwResult, dwResultEnum;
196 DWORD cbBuffer = 16384; // 16K is a good size
197 LPNETRESOURCEW lpnrLocal; // pointer to enumerated structures
198 DWORD cEntries = -1; // enumerate all possible entries
200 // Call the WNetOpenEnum function to begin the enumeration.
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
208 if (dwResult != NO_ERROR)
210 CLog::Log(LOGERROR,"WnetOpenEnum failed with error %d", dwResult);
211 if(dwResult == ERROR_EXTENDED_ERROR)
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);
227 // Call the GlobalAlloc function to allocate resources.
229 lpnrLocal = (LPNETRESOURCEW) GlobalAlloc(GPTR, cbBuffer);
230 if (lpnrLocal == NULL)
232 CLog::Log(LOGERROR,"Can't allocate buffer %d", cbBuffer);
239 // Initialize the buffer.
241 ZeroMemory(lpnrLocal, cbBuffer);
243 // Call the WNetEnumResource function to continue
246 dwResultEnum = WNetEnumResourceW( hEnum, // resource handle
247 &cEntries, // defined locally as -1
248 lpnrLocal, // LPNETRESOURCE
249 &cbBuffer); // buffer size
251 // If the call succeeds, loop through the structures.
253 if (dwResultEnum == NO_ERROR)
255 for (DWORD i = 0; i < cEntries; i++)
257 DWORD dwDisplayType = lpnrLocal[i].dwDisplayType;
258 DWORD dwType = lpnrLocal[i].dwType;
260 if((((dwDisplayType == RESOURCEDISPLAYTYPE_SERVER) && (m_bHost == false)) ||
261 ((dwDisplayType == RESOURCEDISPLAYTYPE_SHARE) && m_bHost)) &&
262 (dwType != RESOURCETYPE_PRINT))
264 CStdString strurl = "smb:";
265 CStdStringW strRemoteNameW = lpnrLocal[i].lpRemoteName;
266 CStdString strName,strRemoteName;
268 g_charsetConverter.wToUTF8(strRemoteNameW,strRemoteName, true);
269 CLog::Log(LOGDEBUG,"Found Server/Share: %s", strRemoteName.c_str());
271 strurl.append(strRemoteName);
272 StringUtils::Replace(strurl, '\\', '/');
273 CURL rooturl(strurl);
274 rooturl.SetFileName("");
276 if(!rooturl.GetShareName().empty())
277 strName = rooturl.GetShareName();
279 strName = rooturl.GetHostName();
281 StringUtils::Replace(strName, "\\", "");
283 URIUtils::AddSlashAtEnd(strurl);
284 CFileItemPtr pItem(new CFileItem(strName));
285 pItem->SetPath(strurl);
286 pItem->m_bIsFolder = true;
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);
298 else if (dwResultEnum != ERROR_NO_MORE_ITEMS)
300 CLog::Log(LOGERROR,"WNetEnumResource failed with error %d", dwResultEnum);
307 while (dwResultEnum != ERROR_NO_MORE_ITEMS);
309 // Call the GlobalFree function to free the memory.
311 GlobalFree((HGLOBAL) lpnrLocal);
313 // Call WNetCloseEnum to end the enumeration.
315 dwResult = WNetCloseEnum(hEnum);
317 if (dwResult != NO_ERROR)
322 CLog::Log(LOGERROR,"WNetCloseEnum failed with error %d", dwResult);
329 bool CWINSMBDirectory::ConnectToShare(const CURL& url)
334 CStdString strUNC("\\\\"+url.GetHostName());
335 if(!url.GetShareName().empty())
336 strUNC.append("\\"+url.GetShareName());
339 memset(&nr,0,sizeof(nr));
340 nr.dwType = RESOURCETYPE_ANY;
341 nr.lpRemoteName = (char*)strUNC.c_str();
343 // in general we shouldn't need the password manager as we won't disconnect from shares yet
344 CPasswordManager::GetInstance().AuthenticateURL(urlIn);
346 CStdString strAuth = URLEncode(urlIn);
348 while(dwRet != NO_ERROR)
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);
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());
357 CLog::Log(LOGDEBUG,"Trying to connect to %s with username(%s) and password(%s)", strUNC.c_str(), urlIn.GetUserNameA().c_str(), "XXXX");
359 if(dwRet == ERROR_ACCESS_DENIED || dwRet == ERROR_INVALID_PASSWORD || dwRet == ERROR_LOGON_FAILURE)
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());
366 else if(dwRet == ERROR_SESSION_CREDENTIAL_CONFLICT)
369 CStdString strRN = nr.lpRemoteName;
372 dwRet2 = WNetCancelConnection2((LPCSTR)strRN.c_str(), 0, false);
373 strRN.erase(strRN.find_last_of("\\"),CStdString::npos);
375 while(dwRet2 == ERROR_NOT_CONNECTED && !strRN.Equals("\\\\"));
377 else if(dwRet != NO_ERROR)
383 if(dwRet != NO_ERROR)
385 CLog::Log(LOGERROR,"Couldn't connect to %s, error code %d", strUNC.c_str(), dwRet);
391 std::string CWINSMBDirectory::URLEncode(const CURL &url)
393 /* due to smb wanting encoded urls we have to build it manually */
395 std::string flat = "smb://";
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 */)
401 flat += url.GetUserName();
403 flat += url.GetPassWord();
406 flat += url.GetHostName();
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++ )
418 /* okey options should go here, thou current samba doesn't support any */