1/*
2 * Copyright (C) 2007 Apple Inc. All rights reserved.
3 * Copyright (C) 2008 Collabora, Ltd. All rights reserved.
4 * Copyright (C) 2007-2009 Torch Mobile, Inc.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 *
10 * 1.  Redistributions of source code must retain the above copyright
11 *     notice, this list of conditions and the following disclaimer.
12 * 2.  Redistributions in binary form must reproduce the above copyright
13 *     notice, this list of conditions and the following disclaimer in the
14 *     documentation and/or other materials provided with the distribution.
15 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
16 *     its contributors may be used to endorse or promote products derived
17 *     from this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
20 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
23 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31#include "config.h"
32#include "FileSystem.h"
33
34#include "FileMetadata.h"
35#include "NotImplemented.h"
36#include <wincrypt.h>
37#include <windows.h>
38#include <wtf/text/CString.h>
39#include <wtf/text/WTFString.h>
40
41namespace WebCore {
42
43static size_t reverseFindPathSeparator(const String& path, unsigned start = UINT_MAX)
44{
45    size_t positionSlash = path.reverseFind('/', start);
46    size_t positionBackslash = path.reverseFind('\\', start);
47
48    if (positionSlash == notFound)
49        return positionBackslash;
50
51    if (positionBackslash == notFound)
52        return positionSlash;
53
54    return std::max(positionSlash, positionBackslash);
55}
56
57static bool getFileInfo(const String& path, BY_HANDLE_FILE_INFORMATION& fileInfo)
58{
59    String filename = path;
60    HANDLE hFile = CreateFile(filename.charactersWithNullTermination(), GENERIC_READ, FILE_SHARE_READ, 0
61        , OPEN_EXISTING, FILE_FLAG_NO_BUFFERING, 0);
62
63    if (hFile == INVALID_HANDLE_VALUE)
64        return false;
65
66    bool rtn = GetFileInformationByHandle(hFile, &fileInfo) ? true : false;
67
68    CloseHandle(hFile);
69    return rtn;
70}
71
72static void getFileSizeFromFileInfo(const BY_HANDLE_FILE_INFORMATION& fileInfo, long long& size)
73{
74    ULARGE_INTEGER fileSize;
75    fileSize.LowPart = fileInfo.nFileSizeLow;
76    fileSize.HighPart = fileInfo.nFileSizeHigh;
77    size = fileSize.QuadPart;
78}
79
80static void getFileModificationTimeFromFileInfo(const BY_HANDLE_FILE_INFORMATION& fileInfo, time_t& time)
81{
82    ULARGE_INTEGER t;
83    memcpy(&t, &fileInfo.ftLastWriteTime, sizeof(t));
84    time = t.QuadPart * 0.0000001 - 11644473600.0;
85}
86
87bool getFileSize(const String& path, long long& size)
88{
89    BY_HANDLE_FILE_INFORMATION fileInformation;
90    if (!getFileInfo(path, fileInformation))
91        return false;
92
93    getFileSizeFromFileInfo(fileInformation, size);
94    return true;
95}
96
97bool getFileModificationTime(const String& path, time_t& time)
98{
99    BY_HANDLE_FILE_INFORMATION fileInformation;
100    if (!getFileInfo(path, fileInformation))
101        return false;
102
103    getFileModificationTimeFromFileInfo(fileInformation, time);
104    return true;
105}
106
107bool getFileMetadata(const String& path, FileMetadata& metadata)
108{
109    BY_HANDLE_FILE_INFORMATION fileInformation;
110    if (!getFileInfo(path, fileInformation))
111        return false;
112
113    getFileSizeFromFileInfo(fileInformation, metadata.length);
114
115    time_t modificationTime;
116    getFileModificationTimeFromFileInfo(fileInformation, modificationTime);
117    metadata.modificationTime = modificationTime;
118
119    metadata.type = (fileInformation.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ? FileMetadata::TypeDirectory : FileMetadata::TypeFile;
120
121    return true;
122}
123
124bool fileExists(const String& path)
125{
126    String filename = path;
127    HANDLE hFile = CreateFile(filename.charactersWithNullTermination(), 0, FILE_SHARE_READ | FILE_SHARE_WRITE
128        , 0, OPEN_EXISTING, FILE_FLAG_NO_BUFFERING, 0);
129
130    CloseHandle(hFile);
131
132    return hFile != INVALID_HANDLE_VALUE;
133}
134
135bool deleteFile(const String& path)
136{
137    String filename = path;
138    return !!DeleteFileW(filename.charactersWithNullTermination());
139}
140
141
142bool deleteEmptyDirectory(const String& path)
143{
144    String filename = path;
145    return !!RemoveDirectoryW(filename.charactersWithNullTermination());
146}
147
148String pathByAppendingComponent(const String& path, const String& component)
149{
150    if (component.isEmpty())
151        return path;
152
153    Vector<UChar, MAX_PATH> buffer;
154
155    buffer.append(path.characters(), path.length());
156
157    if (buffer.last() != L'\\' && buffer.last() != L'/'
158        && component[0] != L'\\' && component[0] != L'/')
159        buffer.append(L'\\');
160
161    buffer.append(component.characters(), component.length());
162
163    return String(buffer.data(), buffer.size());
164}
165
166CString fileSystemRepresentation(const String&)
167{
168    return "";
169}
170
171bool makeAllDirectories(const String& path)
172{
173    size_t lastDivPos = reverseFindPathSeparator(path);
174    unsigned endPos = path.length();
175    if (lastDivPos == endPos - 1) {
176        --endPos;
177        lastDivPos = reverseFindPathSeparator(path, lastDivPos);
178    }
179
180    if (lastDivPos != notFound) {
181        if (!makeAllDirectories(path.substring(0, lastDivPos)))
182            return false;
183    }
184
185    String folder(path.substring(0, endPos));
186    CreateDirectory(folder.charactersWithNullTermination(), 0);
187
188    DWORD fileAttr = GetFileAttributes(folder.charactersWithNullTermination());
189    return fileAttr != 0xFFFFFFFF && (fileAttr & FILE_ATTRIBUTE_DIRECTORY);
190}
191
192String homeDirectoryPath()
193{
194    notImplemented();
195    return "";
196}
197
198String pathGetFileName(const String& path)
199{
200    size_t pos = reverseFindPathSeparator(path);
201    if (pos == notFound)
202        return path;
203    return path.substring(pos + 1);
204}
205
206String directoryName(const String& path)
207{
208    size_t pos = reverseFindPathSeparator(path);
209    if (pos == notFound)
210        return String();
211    return path.left(pos);
212}
213
214String openTemporaryFile(const String&, PlatformFileHandle& handle)
215{
216    handle = INVALID_HANDLE_VALUE;
217
218    wchar_t tempPath[MAX_PATH];
219    int tempPathLength = ::GetTempPath(WTF_ARRAY_LENGTH(tempPath), tempPath);
220    if (tempPathLength <= 0 || tempPathLength > WTF_ARRAY_LENGTH(tempPath))
221        return String();
222
223    HCRYPTPROV hCryptProv = 0;
224    if (!CryptAcquireContext(&hCryptProv, 0, 0, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
225        return String();
226
227    String proposedPath;
228    while (1) {
229
230        wchar_t tempFile[] = L"XXXXXXXX.tmp"; // Use 8.3 style name (more characters aren't helpful due to 8.3 short file names)
231        const int randomPartLength = 8;
232        if (!CryptGenRandom(hCryptProv, randomPartLength * 2, reinterpret_cast<BYTE*>(tempFile)))
233            break;
234
235        // Limit to valid filesystem characters, also excluding others that could be problematic, like punctuation.
236        // don't include both upper and lowercase since Windows file systems are typically not case sensitive.
237        const char validChars[] = "0123456789abcdefghijklmnopqrstuvwxyz";
238        for (int i = 0; i < randomPartLength; ++i)
239            tempFile[i] = validChars[tempFile[i] % (sizeof(validChars) - 1)];
240
241        ASSERT(wcslen(tempFile) * 2 == sizeof(tempFile) - 2);
242
243        proposedPath = pathByAppendingComponent(String(tempPath), String(tempFile));
244
245        // use CREATE_NEW to avoid overwriting an existing file with the same name
246        handle = CreateFile(proposedPath.charactersWithNullTermination(), GENERIC_READ | GENERIC_WRITE, 0, 0, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, 0);
247        if (!isHandleValid(handle) && GetLastError() == ERROR_ALREADY_EXISTS)
248            continue;
249
250        break;
251    }
252
253    CryptReleaseContext(hCryptProv, 0);
254
255    if (!isHandleValid(handle))
256        return String();
257
258    return proposedPath;
259}
260
261PlatformFileHandle openFile(const String& path, FileOpenMode mode)
262{
263    DWORD desiredAccess = 0;
264    DWORD creationDisposition = 0;
265    switch (mode) {
266        case OpenForRead:
267            desiredAccess = GENERIC_READ;
268            creationDisposition = OPEN_EXISTING;
269        case OpenForWrite:
270            desiredAccess = GENERIC_WRITE;
271            creationDisposition = CREATE_ALWAYS;
272        default:
273            ASSERT_NOT_REACHED();
274    }
275
276    String destination = path;
277    return CreateFile(destination.charactersWithNullTermination(), desiredAccess, 0, 0, creationDisposition, FILE_ATTRIBUTE_NORMAL, 0);
278}
279
280void closeFile(PlatformFileHandle& handle)
281{
282    if (isHandleValid(handle)) {
283        ::CloseHandle(handle);
284        handle = invalidPlatformFileHandle;
285    }
286}
287
288int writeToFile(PlatformFileHandle handle, const char* data, int length)
289{
290    if (!isHandleValid(handle))
291        return -1;
292
293    DWORD bytesWritten;
294    bool success = WriteFile(handle, data, length, &bytesWritten, 0);
295
296    if (!success)
297        return -1;
298    return static_cast<int>(bytesWritten);
299}
300
301bool unloadModule(PlatformModule module)
302{
303    return ::FreeLibrary(module);
304}
305
306String localUserSpecificStorageDirectory()
307{
308    return String(L"\\");
309}
310
311String roamingUserSpecificStorageDirectory()
312{
313    return String(L"\\");
314}
315
316Vector<String> listDirectory(const String& path, const String& filter)
317{
318    Vector<String> entries;
319
320    Vector<UChar, 256> pattern;
321    pattern.append(path.characters(), path.length());
322    if (pattern.last() != L'/' && pattern.last() != L'\\')
323        pattern.append(L'\\');
324
325    String root(pattern.data(), pattern.size());
326    pattern.append(filter.characters(), filter.length());
327    pattern.append(0);
328
329    WIN32_FIND_DATA findData;
330    HANDLE hFind = FindFirstFile(pattern.data(), &findData);
331    if (INVALID_HANDLE_VALUE != hFind) {
332        do {
333            // FIXEME: should we also add the folders? This function
334            // is so far only called by PluginDatabase.cpp to list
335            // all plugins in a folder, where it's not supposed to list sub-folders.
336            if (!(findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
337                entries.append(root + String(findData.cFileName));
338        } while (FindNextFile(hFind, &findData));
339        FindClose(hFind);
340    }
341
342    return entries;
343}
344
345} // namespace WebCore
346