1/* 2 * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. 3 * Copyright (C) 2008 Collabora, Ltd. All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 3. Neither the name of Apple Inc. ("Apple") nor the names of 15 * its contributors may be used to endorse or promote products derived 16 * from this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 25 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 */ 29 30#include "config.h" 31#include "FileSystem.h" 32 33#include "FileMetadata.h" 34#include "NotImplemented.h" 35#include "PathWalker.h" 36#include <wtf/CryptographicallyRandomNumber.h> 37#include <wtf/HashMap.h> 38#include <wtf/text/CString.h> 39#include <wtf/text/StringBuilder.h> 40 41#include <windows.h> 42#include <shlobj.h> 43#include <shlwapi.h> 44 45namespace WebCore { 46 47static const ULONGLONG kSecondsFromFileTimeToTimet = 11644473600; 48 49static bool getFindData(String path, WIN32_FIND_DATAW& findData) 50{ 51 HANDLE handle = FindFirstFileW(path.charactersWithNullTermination().data(), &findData); 52 if (handle == INVALID_HANDLE_VALUE) 53 return false; 54 FindClose(handle); 55 return true; 56} 57 58static bool getFileSizeFromFindData(const WIN32_FIND_DATAW& findData, long long& size) 59{ 60 ULARGE_INTEGER fileSize; 61 fileSize.HighPart = findData.nFileSizeHigh; 62 fileSize.LowPart = findData.nFileSizeLow; 63 64 if (fileSize.QuadPart > static_cast<ULONGLONG>(std::numeric_limits<long long>::max())) 65 return false; 66 67 size = fileSize.QuadPart; 68 return true; 69} 70 71static void getFileCreationTimeFromFindData(const WIN32_FIND_DATAW& findData, time_t& time) 72{ 73 ULARGE_INTEGER fileTime; 74 fileTime.HighPart = findData.ftCreationTime.dwHighDateTime; 75 fileTime.LowPart = findData.ftCreationTime.dwLowDateTime; 76 77 // Information about converting time_t to FileTime is available at http://msdn.microsoft.com/en-us/library/ms724228%28v=vs.85%29.aspx 78 time = fileTime.QuadPart / 10000000 - kSecondsFromFileTimeToTimet; 79} 80 81 82static void getFileModificationTimeFromFindData(const WIN32_FIND_DATAW& findData, time_t& time) 83{ 84 ULARGE_INTEGER fileTime; 85 fileTime.HighPart = findData.ftLastWriteTime.dwHighDateTime; 86 fileTime.LowPart = findData.ftLastWriteTime.dwLowDateTime; 87 88 // Information about converting time_t to FileTime is available at http://msdn.microsoft.com/en-us/library/ms724228%28v=vs.85%29.aspx 89 time = fileTime.QuadPart / 10000000 - kSecondsFromFileTimeToTimet; 90} 91 92bool getFileSize(const String& path, long long& size) 93{ 94 WIN32_FIND_DATAW findData; 95 if (!getFindData(path, findData)) 96 return false; 97 98 return getFileSizeFromFindData(findData, size); 99} 100 101bool getFileModificationTime(const String& path, time_t& time) 102{ 103 WIN32_FIND_DATAW findData; 104 if (!getFindData(path, findData)) 105 return false; 106 107 getFileModificationTimeFromFindData(findData, time); 108 return true; 109} 110 111bool getFileCreationTime(const String& path, time_t& time) 112{ 113 WIN32_FIND_DATAW findData; 114 if (!getFindData(path, findData)) 115 return false; 116 117 getFileCreationTimeFromFindData(findData, time); 118 return true; 119} 120 121bool getFileMetadata(const String& path, FileMetadata& metadata) 122{ 123 WIN32_FIND_DATAW findData; 124 if (!getFindData(path, findData)) 125 return false; 126 127 if (!getFileSizeFromFindData(findData, metadata.length)) 128 return false; 129 130 time_t modificationTime; 131 getFileModificationTimeFromFindData(findData, modificationTime); 132 metadata.modificationTime = modificationTime; 133 134 metadata.type = (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? FileMetadata::TypeDirectory : FileMetadata::TypeFile; 135 136 return true; 137} 138 139bool fileExists(const String& path) 140{ 141 WIN32_FIND_DATAW findData; 142 return getFindData(path, findData); 143} 144 145bool deleteFile(const String& path) 146{ 147 String filename = path; 148 return !!DeleteFileW(filename.charactersWithNullTermination().data()); 149} 150 151bool deleteEmptyDirectory(const String& path) 152{ 153 String filename = path; 154 return !!RemoveDirectoryW(filename.charactersWithNullTermination().data()); 155} 156 157String pathByAppendingComponent(const String& path, const String& component) 158{ 159#if OS(WINCE) 160 StringBuilder builder; 161 162 builder.append(path); 163 164 UChar lastPathCharacter = path[path.length() - 1]; 165 if (lastPathCharacter != L'\\' && lastPathCharacter != L'/' && component[0] != L'\\' && component[0] != L'/') 166 builder.append(PlatformFilePathSeparator); 167 168 builder.append(component); 169 170 return builder.toString(); 171#else 172 Vector<UChar> buffer(MAX_PATH); 173 174 if (path.length() + 1 > buffer.size()) 175 return String(); 176 177 StringView(path).getCharactersWithUpconvert(buffer.data()); 178 buffer[path.length()] = '\0'; 179 180 if (!PathAppendW(buffer.data(), component.charactersWithNullTermination().data())) 181 return String(); 182 183 buffer.shrink(wcslen(buffer.data())); 184 185 return String::adopt(buffer); 186#endif 187} 188 189#if !USE(CF) 190 191CString fileSystemRepresentation(const String& path) 192{ 193 auto upconvertedCharacters = path.upconvertedCharacters(); 194 195 const UChar* characters = upconvertedCharacters; 196 int size = WideCharToMultiByte(CP_ACP, 0, characters, path.length(), 0, 0, 0, 0) - 1; 197 198 char* buffer; 199 CString string = CString::newUninitialized(size, buffer); 200 201 WideCharToMultiByte(CP_ACP, 0, characters, path.length(), buffer, size, 0, 0); 202 203 return string; 204} 205 206#endif // !USE(CF) 207 208bool makeAllDirectories(const String& path) 209{ 210 String fullPath = path; 211 if (SHCreateDirectoryEx(0, fullPath.charactersWithNullTermination().data(), 0) != ERROR_SUCCESS) { 212 DWORD error = GetLastError(); 213 if (error != ERROR_FILE_EXISTS && error != ERROR_ALREADY_EXISTS) { 214 LOG_ERROR("Failed to create path %s", path.ascii().data()); 215 return false; 216 } 217 } 218 return true; 219} 220 221String homeDirectoryPath() 222{ 223 notImplemented(); 224 return ""; 225} 226 227String pathGetFileName(const String& path) 228{ 229#if OS(WINCE) 230 size_t positionSlash = path.reverseFind('/'); 231 size_t positionBackslash = path.reverseFind('\\'); 232 233 size_t position; 234 if (positionSlash == notFound) 235 position = positionBackslash; 236 else if (positionBackslash == notFound) 237 position = positionSlash; 238 else 239 position = std::max(positionSlash, positionBackslash); 240 241 if (position == notFound) 242 return path; 243 return path.substring(position + 1); 244#else 245 return String(::PathFindFileName(String(path).charactersWithNullTermination().data())); 246#endif 247} 248 249String directoryName(const String& path) 250{ 251 String name = path.left(path.length() - pathGetFileName(path).length()); 252 if (name.characterStartingAt(name.length() - 1) == '\\') { 253 // Remove any trailing "\". 254 name.truncate(name.length() - 1); 255 } 256 return name; 257} 258 259static String bundleName() 260{ 261 DEPRECATED_DEFINE_STATIC_LOCAL(String, name, (ASCIILiteral("WebKit"))); 262 263#if USE(CF) 264 static bool initialized; 265 266 if (!initialized) { 267 initialized = true; 268 269 if (CFBundleRef bundle = CFBundleGetMainBundle()) 270 if (CFTypeRef bundleExecutable = CFBundleGetValueForInfoDictionaryKey(bundle, kCFBundleExecutableKey)) 271 if (CFGetTypeID(bundleExecutable) == CFStringGetTypeID()) 272 name = reinterpret_cast<CFStringRef>(bundleExecutable); 273 } 274#endif 275 276 return name; 277} 278 279static String storageDirectory(DWORD pathIdentifier) 280{ 281#if OS(WINCE) 282 return String(); 283#else 284 Vector<UChar> buffer(MAX_PATH); 285 if (FAILED(SHGetFolderPathW(0, pathIdentifier | CSIDL_FLAG_CREATE, 0, 0, buffer.data()))) 286 return String(); 287 buffer.resize(wcslen(buffer.data())); 288 String directory = String::adopt(buffer); 289 290 DEPRECATED_DEFINE_STATIC_LOCAL(String, companyNameDirectory, (ASCIILiteral("Apple Computer\\"))); 291 directory = pathByAppendingComponent(directory, companyNameDirectory + bundleName()); 292 if (!makeAllDirectories(directory)) 293 return String(); 294 295 return directory; 296#endif 297} 298 299static String cachedStorageDirectory(DWORD pathIdentifier) 300{ 301 static HashMap<DWORD, String> directories; 302 303 HashMap<DWORD, String>::iterator it = directories.find(pathIdentifier); 304 if (it != directories.end()) 305 return it->value; 306 307 String directory = storageDirectory(pathIdentifier); 308 directories.add(pathIdentifier, directory); 309 310 return directory; 311} 312 313String openTemporaryFile(const String&, PlatformFileHandle& handle) 314{ 315 handle = INVALID_HANDLE_VALUE; 316 317 wchar_t tempPath[MAX_PATH]; 318 int tempPathLength = ::GetTempPathW(WTF_ARRAY_LENGTH(tempPath), tempPath); 319 if (tempPathLength <= 0 || tempPathLength > WTF_ARRAY_LENGTH(tempPath)) 320 return String(); 321 322 String proposedPath; 323 do { 324 wchar_t tempFile[] = L"XXXXXXXX.tmp"; // Use 8.3 style name (more characters aren't helpful due to 8.3 short file names) 325 const int randomPartLength = 8; 326 cryptographicallyRandomValues(tempFile, randomPartLength * sizeof(wchar_t)); 327 328 // Limit to valid filesystem characters, also excluding others that could be problematic, like punctuation. 329 // don't include both upper and lowercase since Windows file systems are typically not case sensitive. 330 const char validChars[] = "0123456789abcdefghijklmnopqrstuvwxyz"; 331 for (int i = 0; i < randomPartLength; ++i) 332 tempFile[i] = validChars[tempFile[i] % (sizeof(validChars) - 1)]; 333 334 ASSERT(wcslen(tempFile) == WTF_ARRAY_LENGTH(tempFile) - 1); 335 336 proposedPath = pathByAppendingComponent(tempPath, tempFile); 337 if (proposedPath.isEmpty()) 338 break; 339 340 // use CREATE_NEW to avoid overwriting an existing file with the same name 341 handle = ::CreateFileW(proposedPath.charactersWithNullTermination().data(), GENERIC_READ | GENERIC_WRITE, 0, 0, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, 0); 342 } while (!isHandleValid(handle) && GetLastError() == ERROR_ALREADY_EXISTS); 343 344 if (!isHandleValid(handle)) 345 return String(); 346 347 return proposedPath; 348} 349 350PlatformFileHandle openFile(const String& path, FileOpenMode mode) 351{ 352 DWORD desiredAccess = 0; 353 DWORD creationDisposition = 0; 354 switch (mode) { 355 case OpenForRead: 356 desiredAccess = GENERIC_READ; 357 creationDisposition = OPEN_EXISTING; 358 break; 359 case OpenForWrite: 360 desiredAccess = GENERIC_WRITE; 361 creationDisposition = CREATE_ALWAYS; 362 break; 363 default: 364 ASSERT_NOT_REACHED(); 365 } 366 367 String destination = path; 368 return CreateFile(destination.charactersWithNullTermination().data(), desiredAccess, 0, 0, creationDisposition, FILE_ATTRIBUTE_NORMAL, 0); 369} 370 371void closeFile(PlatformFileHandle& handle) 372{ 373 if (isHandleValid(handle)) { 374 ::CloseHandle(handle); 375 handle = invalidPlatformFileHandle; 376 } 377} 378 379long long seekFile(PlatformFileHandle handle, long long offset, FileSeekOrigin origin) 380{ 381 DWORD moveMethod = FILE_BEGIN; 382 383 if (origin == SeekFromCurrent) 384 moveMethod = FILE_CURRENT; 385 else if (origin == SeekFromEnd) 386 moveMethod = FILE_END; 387 388 LARGE_INTEGER largeOffset; 389 largeOffset.QuadPart = offset; 390 391 largeOffset.LowPart = SetFilePointer(handle, largeOffset.LowPart, &largeOffset.HighPart, moveMethod); 392 393 if (largeOffset.LowPart == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR) 394 return -1; 395 396 return largeOffset.QuadPart; 397} 398 399int writeToFile(PlatformFileHandle handle, const char* data, int length) 400{ 401 if (!isHandleValid(handle)) 402 return -1; 403 404 DWORD bytesWritten; 405 bool success = WriteFile(handle, data, length, &bytesWritten, 0); 406 407 if (!success) 408 return -1; 409 return static_cast<int>(bytesWritten); 410} 411 412int readFromFile(PlatformFileHandle handle, char* data, int length) 413{ 414 if (!isHandleValid(handle)) 415 return -1; 416 417 DWORD bytesRead; 418 bool success = ::ReadFile(handle, data, length, &bytesRead, 0); 419 420 if (!success) 421 return -1; 422 return static_cast<int>(bytesRead); 423} 424 425bool unloadModule(PlatformModule module) 426{ 427 return ::FreeLibrary(module); 428} 429 430String localUserSpecificStorageDirectory() 431{ 432 return cachedStorageDirectory(CSIDL_LOCAL_APPDATA); 433} 434 435String roamingUserSpecificStorageDirectory() 436{ 437 return cachedStorageDirectory(CSIDL_APPDATA); 438} 439 440Vector<String> listDirectory(const String& directory, const String& filter) 441{ 442 Vector<String> entries; 443 444 PathWalker walker(directory, filter); 445 if (!walker.isValid()) 446 return entries; 447 448 do { 449 if (walker.data().dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) 450 continue; 451 452 entries.append(directory + "\\" + reinterpret_cast<const UChar*>(walker.data().cFileName)); 453 } while (walker.step()); 454 455 return entries; 456} 457 458} // namespace WebCore 459