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