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 Computer, 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/WTFString.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(), &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 getFileModificationTimeFromFindData(const WIN32_FIND_DATAW& findData, time_t& time)
72{
73    ULARGE_INTEGER fileTime;
74    fileTime.HighPart = findData.ftLastWriteTime.dwHighDateTime;
75    fileTime.LowPart = findData.ftLastWriteTime.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
81bool getFileSize(const String& path, long long& size)
82{
83    WIN32_FIND_DATAW findData;
84    if (!getFindData(path, findData))
85        return false;
86
87    return getFileSizeFromFindData(findData, size);
88}
89
90bool getFileModificationTime(const String& path, time_t& time)
91{
92    WIN32_FIND_DATAW findData;
93    if (!getFindData(path, findData))
94        return false;
95
96    getFileModificationTimeFromFindData(findData, time);
97    return true;
98}
99
100bool getFileMetadata(const String& path, FileMetadata& metadata)
101{
102    WIN32_FIND_DATAW findData;
103    if (!getFindData(path, findData))
104        return false;
105
106    if (!getFileSizeFromFindData(findData, metadata.length))
107        return false;
108
109    time_t modificationTime;
110    getFileModificationTimeFromFindData(findData, modificationTime);
111    metadata.modificationTime = modificationTime;
112
113    metadata.type = (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? FileMetadata::TypeDirectory : FileMetadata::TypeFile;
114
115    return true;
116}
117
118bool fileExists(const String& path)
119{
120    WIN32_FIND_DATAW findData;
121    return getFindData(path, findData);
122}
123
124bool deleteFile(const String& path)
125{
126    String filename = path;
127    return !!DeleteFileW(filename.charactersWithNullTermination());
128}
129
130bool deleteEmptyDirectory(const String& path)
131{
132    String filename = path;
133    return !!RemoveDirectoryW(filename.charactersWithNullTermination());
134}
135
136String pathByAppendingComponent(const String& path, const String& component)
137{
138    Vector<UChar> buffer(MAX_PATH);
139
140#if OS(WINCE)
141    buffer.append(path.characters(), path.length());
142
143    UChar lastPathCharacter = path[path.length() - 1];
144    if (lastPathCharacter != L'\\' && lastPathCharacter != L'/' && component[0] != L'\\' && component[0] != L'/')
145        buffer.append(PlatformFilePathSeparator);
146
147    buffer.append(component.characters(), component.length());
148    buffer.shrinkToFit();
149#else
150    if (path.length() + 1 > buffer.size())
151        return String();
152
153    memcpy(buffer.data(), path.characters(), path.length() * sizeof(UChar));
154    buffer[path.length()] = '\0';
155
156    String componentCopy = component;
157    if (!PathAppendW(buffer.data(), componentCopy.charactersWithNullTermination()))
158        return String();
159
160    buffer.resize(wcslen(buffer.data()));
161#endif
162
163    return String::adopt(buffer);
164}
165
166#if !USE(CF)
167
168CString fileSystemRepresentation(const String& path)
169{
170    const UChar* characters = path.characters();
171    int size = WideCharToMultiByte(CP_ACP, 0, characters, path.length(), 0, 0, 0, 0) - 1;
172
173    char* buffer;
174    CString string = CString::newUninitialized(size, buffer);
175
176    WideCharToMultiByte(CP_ACP, 0, characters, path.length(), buffer, size, 0, 0);
177
178    return string;
179}
180
181#endif // !USE(CF)
182
183bool makeAllDirectories(const String& path)
184{
185    String fullPath = path;
186    if (SHCreateDirectoryEx(0, fullPath.charactersWithNullTermination(), 0) != ERROR_SUCCESS) {
187        DWORD error = GetLastError();
188        if (error != ERROR_FILE_EXISTS && error != ERROR_ALREADY_EXISTS) {
189            LOG_ERROR("Failed to create path %s", path.ascii().data());
190            return false;
191        }
192    }
193    return true;
194}
195
196String homeDirectoryPath()
197{
198    notImplemented();
199    return "";
200}
201
202String pathGetFileName(const String& path)
203{
204#if OS(WINCE)
205    size_t positionSlash = path.reverseFind('/');
206    size_t positionBackslash = path.reverseFind('\\');
207
208    size_t position;
209    if (positionSlash == notFound)
210        position = positionBackslash;
211    else if (positionBackslash == notFound)
212        position =  positionSlash;
213    else
214        position = std::max(positionSlash, positionBackslash);
215
216    if (position == notFound)
217        return path;
218    return path.substring(position + 1);
219#else
220    return String(::PathFindFileName(String(path).charactersWithNullTermination()));
221#endif
222}
223
224String directoryName(const String& path)
225{
226    String name = path.left(path.length() - pathGetFileName(path).length());
227    if (name.characterStartingAt(name.length() - 1) == '\\') {
228        // Remove any trailing "\".
229        name.truncate(name.length() - 1);
230    }
231    return name;
232}
233
234static String bundleName()
235{
236    DEFINE_STATIC_LOCAL(String, name, (ASCIILiteral("WebKit")));
237
238#if USE(CF)
239    static bool initialized;
240
241    if (!initialized) {
242        initialized = true;
243
244        if (CFBundleRef bundle = CFBundleGetMainBundle())
245            if (CFTypeRef bundleExecutable = CFBundleGetValueForInfoDictionaryKey(bundle, kCFBundleExecutableKey))
246                if (CFGetTypeID(bundleExecutable) == CFStringGetTypeID())
247                    name = reinterpret_cast<CFStringRef>(bundleExecutable);
248    }
249#endif
250
251    return name;
252}
253
254static String storageDirectory(DWORD pathIdentifier)
255{
256    Vector<UChar> buffer(MAX_PATH);
257    if (FAILED(SHGetFolderPathW(0, pathIdentifier | CSIDL_FLAG_CREATE, 0, 0, buffer.data())))
258        return String();
259    buffer.resize(wcslen(buffer.data()));
260    String directory = String::adopt(buffer);
261
262    DEFINE_STATIC_LOCAL(String, companyNameDirectory, (ASCIILiteral("Apple Computer\\")));
263    directory = pathByAppendingComponent(directory, companyNameDirectory + bundleName());
264    if (!makeAllDirectories(directory))
265        return String();
266
267    return directory;
268}
269
270static String cachedStorageDirectory(DWORD pathIdentifier)
271{
272    static HashMap<DWORD, String> directories;
273
274    HashMap<DWORD, String>::iterator it = directories.find(pathIdentifier);
275    if (it != directories.end())
276        return it->value;
277
278    String directory = storageDirectory(pathIdentifier);
279    directories.add(pathIdentifier, directory);
280
281    return directory;
282}
283
284String openTemporaryFile(const String&, PlatformFileHandle& handle)
285{
286    handle = INVALID_HANDLE_VALUE;
287
288    wchar_t tempPath[MAX_PATH];
289    int tempPathLength = ::GetTempPathW(WTF_ARRAY_LENGTH(tempPath), tempPath);
290    if (tempPathLength <= 0 || tempPathLength > WTF_ARRAY_LENGTH(tempPath))
291        return String();
292
293    String proposedPath;
294    do {
295        wchar_t tempFile[] = L"XXXXXXXX.tmp"; // Use 8.3 style name (more characters aren't helpful due to 8.3 short file names)
296        const int randomPartLength = 8;
297        cryptographicallyRandomValues(tempFile, randomPartLength * sizeof(wchar_t));
298
299        // Limit to valid filesystem characters, also excluding others that could be problematic, like punctuation.
300        // don't include both upper and lowercase since Windows file systems are typically not case sensitive.
301        const char validChars[] = "0123456789abcdefghijklmnopqrstuvwxyz";
302        for (int i = 0; i < randomPartLength; ++i)
303            tempFile[i] = validChars[tempFile[i] % (sizeof(validChars) - 1)];
304
305        ASSERT(wcslen(tempFile) == WTF_ARRAY_LENGTH(tempFile) - 1);
306
307        proposedPath = pathByAppendingComponent(tempPath, tempFile);
308        if (proposedPath.isEmpty())
309            break;
310
311        // use CREATE_NEW to avoid overwriting an existing file with the same name
312        handle = ::CreateFileW(proposedPath.charactersWithNullTermination(), GENERIC_READ | GENERIC_WRITE, 0, 0, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, 0);
313    } while (!isHandleValid(handle) && GetLastError() == ERROR_ALREADY_EXISTS);
314
315    if (!isHandleValid(handle))
316        return String();
317
318    return proposedPath;
319}
320
321PlatformFileHandle openFile(const String& path, FileOpenMode mode)
322{
323    DWORD desiredAccess = 0;
324    DWORD creationDisposition = 0;
325    switch (mode) {
326    case OpenForRead:
327        desiredAccess = GENERIC_READ;
328        creationDisposition = OPEN_EXISTING;
329        break;
330    case OpenForWrite:
331        desiredAccess = GENERIC_WRITE;
332        creationDisposition = CREATE_ALWAYS;
333        break;
334    default:
335        ASSERT_NOT_REACHED();
336    }
337
338    String destination = path;
339    return CreateFile(destination.charactersWithNullTermination(), desiredAccess, 0, 0, creationDisposition, FILE_ATTRIBUTE_NORMAL, 0);
340}
341
342void closeFile(PlatformFileHandle& handle)
343{
344    if (isHandleValid(handle)) {
345        ::CloseHandle(handle);
346        handle = invalidPlatformFileHandle;
347    }
348}
349
350long long seekFile(PlatformFileHandle handle, long long offset, FileSeekOrigin origin)
351{
352    DWORD moveMethod = FILE_BEGIN;
353
354    if (origin == SeekFromCurrent)
355        moveMethod = FILE_CURRENT;
356    else if (origin == SeekFromEnd)
357        moveMethod = FILE_END;
358
359    LARGE_INTEGER largeOffset;
360    largeOffset.QuadPart = offset;
361
362    LARGE_INTEGER newOffset;
363    newOffset.QuadPart = 0;
364
365    SetFilePointerEx(handle, largeOffset, &newOffset, moveMethod);
366
367    return newOffset.QuadPart;
368}
369
370int writeToFile(PlatformFileHandle handle, const char* data, int length)
371{
372    if (!isHandleValid(handle))
373        return -1;
374
375    DWORD bytesWritten;
376    bool success = WriteFile(handle, data, length, &bytesWritten, 0);
377
378    if (!success)
379        return -1;
380    return static_cast<int>(bytesWritten);
381}
382
383bool unloadModule(PlatformModule module)
384{
385    return ::FreeLibrary(module);
386}
387
388String localUserSpecificStorageDirectory()
389{
390    return cachedStorageDirectory(CSIDL_LOCAL_APPDATA);
391}
392
393String roamingUserSpecificStorageDirectory()
394{
395    return cachedStorageDirectory(CSIDL_APPDATA);
396}
397
398Vector<String> listDirectory(const String& directory, const String& filter)
399{
400    Vector<String> entries;
401
402    PathWalker walker(directory, filter);
403    if (!walker.isValid())
404        return entries;
405
406    do {
407        if (walker.data().dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
408            continue;
409
410        entries.append(directory + "\\" + reinterpret_cast<const UChar*>(walker.data().cFileName));
411    } while (walker.step());
412
413    return entries;
414}
415
416} // namespace WebCore
417