d672af6374693cf0521ec3689689cebcc02ede38
[WebKit-https.git] / Source / WTF / wtf / win / FileSystemWin.cpp
1 /*
2  * Copyright (C) 2007-2017 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 <wtf/FileSystem.h>
32
33 #include <io.h>
34 #include <shlobj.h>
35 #include <shlwapi.h>
36 #include <sys/stat.h>
37 #include <windows.h>
38 #include <wtf/CryptographicallyRandomNumber.h>
39 #include <wtf/FileMetadata.h>
40 #include <wtf/HashMap.h>
41 #include <wtf/text/CString.h>
42 #include <wtf/text/StringBuilder.h>
43 #include <wtf/text/win/WCharStringExtras.h>
44 #include <wtf/win/PathWalker.h>
45
46 namespace WTF {
47
48 namespace FileSystemImpl {
49
50 static const ULONGLONG kSecondsFromFileTimeToTimet = 11644473600;
51
52 static bool getFindData(String path, WIN32_FIND_DATAW& findData)
53 {
54     HANDLE handle = FindFirstFileW(stringToNullTerminatedWChar(path).data(), &findData);
55     if (handle == INVALID_HANDLE_VALUE)
56         return false;
57     FindClose(handle);
58     return true;
59 }
60
61 static bool getFileSizeFromFindData(const WIN32_FIND_DATAW& findData, long long& size)
62 {
63     ULARGE_INTEGER fileSize;
64     fileSize.HighPart = findData.nFileSizeHigh;
65     fileSize.LowPart = findData.nFileSizeLow;
66
67     if (fileSize.QuadPart > static_cast<ULONGLONG>(std::numeric_limits<long long>::max()))
68         return false;
69
70     size = fileSize.QuadPart;
71     return true;
72 }
73
74 static bool getFileSizeFromByHandleFileInformationStructure(const BY_HANDLE_FILE_INFORMATION& fileInformation, long long& size)
75 {
76     ULARGE_INTEGER fileSize;
77     fileSize.HighPart = fileInformation.nFileSizeHigh;
78     fileSize.LowPart = fileInformation.nFileSizeLow;
79
80     if (fileSize.QuadPart > static_cast<ULONGLONG>(std::numeric_limits<long long>::max()))
81         return false;
82
83     size = fileSize.QuadPart;
84     return true;
85 }
86
87 static void getFileCreationTimeFromFindData(const WIN32_FIND_DATAW& findData, time_t& time)
88 {
89     ULARGE_INTEGER fileTime;
90     fileTime.HighPart = findData.ftCreationTime.dwHighDateTime;
91     fileTime.LowPart = findData.ftCreationTime.dwLowDateTime;
92
93     // Information about converting time_t to FileTime is available at http://msdn.microsoft.com/en-us/library/ms724228%28v=vs.85%29.aspx
94     time = fileTime.QuadPart / 10000000 - kSecondsFromFileTimeToTimet;
95 }
96
97
98 static void getFileModificationTimeFromFindData(const WIN32_FIND_DATAW& findData, time_t& time)
99 {
100     ULARGE_INTEGER fileTime;
101     fileTime.HighPart = findData.ftLastWriteTime.dwHighDateTime;
102     fileTime.LowPart = findData.ftLastWriteTime.dwLowDateTime;
103
104     // Information about converting time_t to FileTime is available at http://msdn.microsoft.com/en-us/library/ms724228%28v=vs.85%29.aspx
105     time = fileTime.QuadPart / 10000000 - kSecondsFromFileTimeToTimet;
106 }
107
108 bool getFileSize(const String& path, long long& size)
109 {
110     WIN32_FIND_DATAW findData;
111     if (!getFindData(path, findData))
112         return false;
113
114     return getFileSizeFromFindData(findData, size);
115 }
116
117 bool getFileSize(PlatformFileHandle fileHandle, long long& size)
118 {
119     BY_HANDLE_FILE_INFORMATION fileInformation;
120     if (!::GetFileInformationByHandle(fileHandle, &fileInformation))
121         return false;
122
123     return getFileSizeFromByHandleFileInformationStructure(fileInformation, size);
124 }
125
126 Optional<WallTime> getFileModificationTime(const String& path)
127 {
128     WIN32_FIND_DATAW findData;
129     if (!getFindData(path, findData))
130         return WTF::nullopt;
131
132     time_t time = 0;
133     getFileModificationTimeFromFindData(findData, time);
134     return WallTime::fromRawSeconds(time);
135 }
136
137 Optional<WallTime> getFileCreationTime(const String& path)
138 {
139     WIN32_FIND_DATAW findData;
140     if (!getFindData(path, findData))
141         return WTF::nullopt;
142
143     time_t time = 0;
144     getFileCreationTimeFromFindData(findData, time);
145     return WallTime::fromRawSeconds(time);
146 }
147
148 static String getFinalPathName(const String& path)
149 {
150     auto handle = openFile(path, FileOpenMode::Read);
151     if (!isHandleValid(handle))
152         return String();
153
154     Vector<UChar> buffer(MAX_PATH);
155     if (::GetFinalPathNameByHandleW(handle, buffer.data(), buffer.size(), VOLUME_NAME_NT) >= MAX_PATH) {
156         closeFile(handle);
157         return String();
158     }
159     closeFile(handle);
160
161     buffer.shrink(wcslen(buffer.data()));
162     return String::adopt(WTFMove(buffer));
163 }
164
165 static inline bool isSymbolicLink(WIN32_FIND_DATAW findData)
166 {
167     return findData.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT && findData.dwReserved0 == IO_REPARSE_TAG_SYMLINK;
168 }
169
170 static FileMetadata::Type toFileMetadataType(WIN32_FIND_DATAW findData)
171 {
172     if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
173         return FileMetadata::Type::Directory;
174     if (isSymbolicLink(findData))
175         return FileMetadata::Type::SymbolicLink;
176     return FileMetadata::Type::File;
177 }
178
179 static Optional<FileMetadata> findDataToFileMetadata(WIN32_FIND_DATAW findData)
180 {
181     long long length;
182     if (!getFileSizeFromFindData(findData, length))
183         return WTF::nullopt;
184
185     time_t modificationTime;
186     getFileModificationTimeFromFindData(findData, modificationTime);
187
188     return FileMetadata {
189         WallTime::fromRawSeconds(modificationTime),
190         length,
191         static_cast<bool>(findData.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN),
192         toFileMetadataType(findData)
193     };
194 }
195
196 Optional<FileMetadata> fileMetadata(const String& path)
197 {
198     WIN32_FIND_DATAW findData;
199     if (!getFindData(path, findData))
200         return WTF::nullopt;
201
202     return findDataToFileMetadata(findData);
203 }
204
205 Optional<FileMetadata> fileMetadataFollowingSymlinks(const String& path)
206 {
207     WIN32_FIND_DATAW findData;
208     if (!getFindData(path, findData))
209         return WTF::nullopt;
210
211     if (isSymbolicLink(findData)) {
212         String targetPath = getFinalPathName(path);
213         if (targetPath.isNull())
214             return WTF::nullopt;
215         if (!getFindData(targetPath, findData))
216             return WTF::nullopt;
217     }
218
219     return findDataToFileMetadata(findData);
220 }
221
222 bool createSymbolicLink(const String& targetPath, const String& symbolicLinkPath)
223 {
224     return !::CreateSymbolicLinkW(stringToNullTerminatedWChar(symbolicLinkPath).data(), stringToNullTerminatedWChar(targetPath).data(), 0);
225 }
226
227 bool fileExists(const String& path)
228 {
229     WIN32_FIND_DATAW findData;
230     return getFindData(path, findData);
231 }
232
233 bool deleteFile(const String& path)
234 {
235     String filename = path;
236     return !!DeleteFileW(stringToNullTerminatedWChar(filename).data());
237 }
238
239 bool deleteEmptyDirectory(const String& path)
240 {
241     String filename = path;
242     return !!RemoveDirectoryW(stringToNullTerminatedWChar(filename).data());
243 }
244
245 bool moveFile(const String& oldPath, const String& newPath)
246 {
247     String oldFilename = oldPath;
248     String newFilename = newPath;
249     return !!::MoveFileEx(stringToNullTerminatedWChar(oldFilename).data(), stringToNullTerminatedWChar(newFilename).data(), MOVEFILE_COPY_ALLOWED | MOVEFILE_REPLACE_EXISTING);
250 }
251
252 String pathByAppendingComponent(const String& path, const String& component)
253 {
254     Vector<UChar> buffer(MAX_PATH);
255
256     if (path.length() + 1 > buffer.size())
257         return String();
258
259     StringView(path).getCharactersWithUpconvert(buffer.data());
260     buffer[path.length()] = '\0';
261
262     if (!PathAppendW(buffer.data(), stringToNullTerminatedWChar(component).data()))
263         return String();
264
265     buffer.shrink(wcslen(buffer.data()));
266
267     return String::adopt(WTFMove(buffer));
268 }
269
270 String pathByAppendingComponents(StringView path, const Vector<StringView>& components)
271 {
272     String result = path.toString();
273     for (auto& component : components)
274         result = pathByAppendingComponent(result, component.toString());
275     return result;
276 }
277
278 #if !USE(CF)
279
280 CString fileSystemRepresentation(const String& path)
281 {
282     auto upconvertedCharacters = StringView(path).upconvertedCharacters();
283
284     const UChar* characters = upconvertedCharacters;
285     int size = WideCharToMultiByte(CP_ACP, 0, characters, path.length(), 0, 0, 0, 0) - 1;
286
287     char* buffer;
288     CString string = CString::newUninitialized(size, buffer);
289
290     WideCharToMultiByte(CP_ACP, 0, characters, path.length(), buffer, size, 0, 0);
291
292     return string;
293 }
294
295 #endif // !USE(CF)
296
297 bool makeAllDirectories(const String& path)
298 {
299     String fullPath = path;
300     if (SHCreateDirectoryEx(0, stringToNullTerminatedWChar(fullPath).data(), 0) != ERROR_SUCCESS) {
301         DWORD error = GetLastError();
302         if (error != ERROR_FILE_EXISTS && error != ERROR_ALREADY_EXISTS) {
303             LOG_ERROR("Failed to create path %s", path.ascii().data());
304             return false;
305         }
306     }
307     return true;
308 }
309
310 String homeDirectoryPath()
311 {
312     return "";
313 }
314
315 String pathGetFileName(const String& path)
316 {
317     return nullTerminatedWCharToString(::PathFindFileName(stringToNullTerminatedWChar(path).data()));
318 }
319
320 String directoryName(const String& path)
321 {
322     String name = path.left(path.length() - pathGetFileName(path).length());
323     if (name.characterStartingAt(name.length() - 1) == '\\') {
324         // Remove any trailing "\".
325         name.truncate(name.length() - 1);
326     }
327     return name;
328 }
329
330 static String bundleName()
331 {
332     static const NeverDestroyed<String> name = [] {
333         String name { "WebKit"_s };
334
335 #if USE(CF)
336         if (CFBundleRef bundle = CFBundleGetMainBundle()) {
337             if (CFTypeRef bundleExecutable = CFBundleGetValueForInfoDictionaryKey(bundle, kCFBundleExecutableKey)) {
338                 if (CFGetTypeID(bundleExecutable) == CFStringGetTypeID())
339                     name = reinterpret_cast<CFStringRef>(bundleExecutable);
340             }
341         }
342 #endif
343
344         return name;
345     }();
346
347     return name;
348 }
349
350 static String storageDirectory(DWORD pathIdentifier)
351 {
352     Vector<UChar> buffer(MAX_PATH);
353     if (FAILED(SHGetFolderPathW(0, pathIdentifier | CSIDL_FLAG_CREATE, 0, 0, buffer.data())))
354         return String();
355     buffer.resize(wcslen(buffer.data()));
356     String directory = String::adopt(WTFMove(buffer));
357
358     directory = pathByAppendingComponent(directory, "Apple Computer\\" + bundleName());
359     if (!makeAllDirectories(directory))
360         return String();
361
362     return directory;
363 }
364
365 static String cachedStorageDirectory(DWORD pathIdentifier)
366 {
367     static HashMap<DWORD, String> directories;
368
369     HashMap<DWORD, String>::iterator it = directories.find(pathIdentifier);
370     if (it != directories.end())
371         return it->value;
372
373     String directory = storageDirectory(pathIdentifier);
374     directories.add(pathIdentifier, directory);
375
376     return directory;
377 }
378
379 static String generateTemporaryPath(const Function<bool(const String&)>& action)
380 {
381     wchar_t tempPath[MAX_PATH];
382     int tempPathLength = ::GetTempPathW(WTF_ARRAY_LENGTH(tempPath), tempPath);
383     if (tempPathLength <= 0 || tempPathLength > WTF_ARRAY_LENGTH(tempPath))
384         return String();
385
386     String proposedPath;
387     do {
388         wchar_t tempFile[] = L"XXXXXXXX.tmp"; // Use 8.3 style name (more characters aren't helpful due to 8.3 short file names)
389         const int randomPartLength = 8;
390         cryptographicallyRandomValues(tempFile, randomPartLength * sizeof(wchar_t));
391
392         // Limit to valid filesystem characters, also excluding others that could be problematic, like punctuation.
393         // don't include both upper and lowercase since Windows file systems are typically not case sensitive.
394         const char validChars[] = "0123456789abcdefghijklmnopqrstuvwxyz";
395         for (int i = 0; i < randomPartLength; ++i)
396             tempFile[i] = validChars[tempFile[i] % (sizeof(validChars) - 1)];
397
398         ASSERT(wcslen(tempFile) == WTF_ARRAY_LENGTH(tempFile) - 1);
399
400         proposedPath = pathByAppendingComponent(nullTerminatedWCharToString(tempPath), nullTerminatedWCharToString(tempFile));
401         if (proposedPath.isEmpty())
402             break;
403     } while (!action(proposedPath));
404
405     return proposedPath;
406 }
407
408 String openTemporaryFile(const String&, PlatformFileHandle& handle)
409 {
410     handle = INVALID_HANDLE_VALUE;
411
412     String proposedPath = generateTemporaryPath([&handle](const String& proposedPath) {
413         // use CREATE_NEW to avoid overwriting an existing file with the same name
414         handle = ::CreateFileW(stringToNullTerminatedWChar(proposedPath).data(), GENERIC_READ | GENERIC_WRITE, 0, 0, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, 0);
415
416         return isHandleValid(handle) || GetLastError() == ERROR_ALREADY_EXISTS;
417     });
418
419     if (!isHandleValid(handle))
420         return String();
421
422     return proposedPath;
423 }
424
425 PlatformFileHandle openFile(const String& path, FileOpenMode mode)
426 {
427     DWORD desiredAccess = 0;
428     DWORD creationDisposition = 0;
429     DWORD shareMode = 0;
430     switch (mode) {
431     case FileOpenMode::Read:
432         desiredAccess = GENERIC_READ;
433         creationDisposition = OPEN_EXISTING;
434         shareMode = FILE_SHARE_READ;
435         break;
436     case FileOpenMode::Write:
437         desiredAccess = GENERIC_WRITE;
438         creationDisposition = CREATE_ALWAYS;
439         break;
440     default:
441         ASSERT_NOT_REACHED();
442     }
443
444     String destination = path;
445     return CreateFile(stringToNullTerminatedWChar(destination).data(), desiredAccess, shareMode, 0, creationDisposition, FILE_ATTRIBUTE_NORMAL, 0);
446 }
447
448 void closeFile(PlatformFileHandle& handle)
449 {
450     if (isHandleValid(handle)) {
451         ::CloseHandle(handle);
452         handle = invalidPlatformFileHandle;
453     }
454 }
455
456 long long seekFile(PlatformFileHandle handle, long long offset, FileSeekOrigin origin)
457 {
458     DWORD moveMethod = FILE_BEGIN;
459
460     if (origin == FileSeekOrigin::Current)
461         moveMethod = FILE_CURRENT;
462     else if (origin == FileSeekOrigin::End)
463         moveMethod = FILE_END;
464
465     LARGE_INTEGER largeOffset;
466     largeOffset.QuadPart = offset;
467
468     largeOffset.LowPart = SetFilePointer(handle, largeOffset.LowPart, &largeOffset.HighPart, moveMethod);
469
470     if (largeOffset.LowPart == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR)
471         return -1;
472
473     return largeOffset.QuadPart;
474 }
475
476 int writeToFile(PlatformFileHandle handle, const char* data, int length)
477 {
478     if (!isHandleValid(handle))
479         return -1;
480
481     DWORD bytesWritten;
482     bool success = WriteFile(handle, data, length, &bytesWritten, 0);
483
484     if (!success)
485         return -1;
486     return static_cast<int>(bytesWritten);
487 }
488
489 int readFromFile(PlatformFileHandle handle, char* data, int length)
490 {
491     if (!isHandleValid(handle))
492         return -1;
493
494     DWORD bytesRead;
495     bool success = ::ReadFile(handle, data, length, &bytesRead, 0);
496
497     if (!success)
498         return -1;
499     return static_cast<int>(bytesRead);
500 }
501
502 bool hardLinkOrCopyFile(const String& source, const String& destination)
503 {
504     return !!::CopyFile(stringToNullTerminatedWChar(source).data(), stringToNullTerminatedWChar(destination).data(), TRUE);
505 }
506
507 String localUserSpecificStorageDirectory()
508 {
509     return cachedStorageDirectory(CSIDL_LOCAL_APPDATA);
510 }
511
512 String roamingUserSpecificStorageDirectory()
513 {
514     return cachedStorageDirectory(CSIDL_APPDATA);
515 }
516
517 Vector<String> listDirectory(const String& directory, const String& filter)
518 {
519     Vector<String> entries;
520
521     PathWalker walker(directory, filter);
522     if (!walker.isValid())
523         return entries;
524
525     do {
526         if (walker.data().dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY
527             && (!wcscmp(walker.data().cFileName, L".") || !wcscmp(walker.data().cFileName, L"..")))
528             continue;
529
530         entries.append(directory + "\\" + reinterpret_cast<const UChar*>(walker.data().cFileName));
531     } while (walker.step());
532
533     return entries;
534 }
535
536 bool getVolumeFreeSpace(const String&, uint64_t&)
537 {
538     return false;
539 }
540
541 Optional<int32_t> getFileDeviceId(const CString& fsFile)
542 {
543     auto handle = openFile(fsFile.data(), FileOpenMode::Read);
544     if (!isHandleValid(handle))
545         return WTF::nullopt;
546
547     BY_HANDLE_FILE_INFORMATION fileInformation = { };
548     if (!::GetFileInformationByHandle(handle, &fileInformation)) {
549         closeFile(handle);
550         return WTF::nullopt;
551     }
552
553     closeFile(handle);
554
555     return fileInformation.dwVolumeSerialNumber;
556 }
557
558 String realPath(const String& filePath)
559 {
560     return getFinalPathName(filePath);
561 }
562
563 String createTemporaryDirectory()
564 {
565     return generateTemporaryPath([](const String& proposedPath) {
566         return makeAllDirectories(proposedPath);
567     });
568 }
569
570 bool deleteNonEmptyDirectory(const String& directoryPath)
571 {
572     SHFILEOPSTRUCT deleteOperation = {
573         nullptr,
574         FO_DELETE,
575         stringToNullTerminatedWChar(directoryPath).data(),
576         L"",
577         FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT,
578         false,
579         0,
580         L""
581     };
582     return !SHFileOperation(&deleteOperation);
583 }
584
585 } // namespace FileSystemImpl
586 } // namespace WTF