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 "NotImplemented.h"
34#include "PathWalker.h"
35#include "PlatformString.h"
36#include <wtf/HashMap.h>
37#include <wtf/text/CString.h>
38#include <wtf/text/StringConcatenate.h>
39
40#include <windows.h>
41#include <winbase.h>
42#include <shlobj.h>
43#include <shlwapi.h>
44
45namespace WebCore {
46
47static bool statFile(String path, struct _stat64& st)
48{
49    ASSERT_ARG(path, !path.isNull());
50    return !_wstat64(path.charactersWithNullTermination(), &st) && (st.st_mode & _S_IFMT) == _S_IFREG;
51}
52
53bool getFileSize(const String& path, long long& result)
54{
55    struct _stat64 sb;
56    if (!statFile(path, sb))
57        return false;
58    result = sb.st_size;
59    return true;
60}
61
62bool getFileModificationTime(const String& path, time_t& result)
63{
64    struct _stat64 st;
65    if (!statFile(path, st))
66        return false;
67    result = st.st_mtime;
68    return true;
69}
70
71bool fileExists(const String& path)
72{
73    struct _stat64 st;
74    return statFile(path, st);
75}
76
77bool deleteFile(const String& path)
78{
79    String filename = path;
80    return !!DeleteFileW(filename.charactersWithNullTermination());
81}
82
83bool deleteEmptyDirectory(const String& path)
84{
85    String filename = path;
86    return !!RemoveDirectoryW(filename.charactersWithNullTermination());
87}
88
89String pathByAppendingComponent(const String& path, const String& component)
90{
91    Vector<UChar> buffer(MAX_PATH);
92
93    if (path.length() + 1 > buffer.size())
94        return String();
95
96    memcpy(buffer.data(), path.characters(), path.length() * sizeof(UChar));
97    buffer[path.length()] = '\0';
98
99    String componentCopy = component;
100    if (!PathAppendW(buffer.data(), componentCopy.charactersWithNullTermination()))
101        return String();
102
103    buffer.resize(wcslen(buffer.data()));
104
105    return String::adopt(buffer);
106}
107
108CString fileSystemRepresentation(const String&)
109{
110    return "";
111}
112
113bool makeAllDirectories(const String& path)
114{
115    String fullPath = path;
116    if (SHCreateDirectoryEx(0, fullPath.charactersWithNullTermination(), 0) != ERROR_SUCCESS) {
117        DWORD error = GetLastError();
118        if (error != ERROR_FILE_EXISTS && error != ERROR_ALREADY_EXISTS) {
119            LOG_ERROR("Failed to create path %s", path.ascii().data());
120            return false;
121        }
122    }
123    return true;
124}
125
126String homeDirectoryPath()
127{
128    notImplemented();
129    return "";
130}
131
132String pathGetFileName(const String& path)
133{
134    return String(::PathFindFileName(String(path).charactersWithNullTermination()));
135}
136
137String directoryName(const String& path)
138{
139    String name = path.left(path.length() - pathGetFileName(path).length());
140    if (name.characterStartingAt(name.length() - 1) == '\\') {
141        // Remove any trailing "\".
142        name.truncate(name.length() - 1);
143    }
144    return name;
145}
146
147static String bundleName()
148{
149    static bool initialized;
150    static String name = "WebKit";
151
152    if (!initialized) {
153        initialized = true;
154
155        if (CFBundleRef bundle = CFBundleGetMainBundle())
156            if (CFTypeRef bundleExecutable = CFBundleGetValueForInfoDictionaryKey(bundle, kCFBundleExecutableKey))
157                if (CFGetTypeID(bundleExecutable) == CFStringGetTypeID())
158                    name = reinterpret_cast<CFStringRef>(bundleExecutable);
159    }
160
161    return name;
162}
163
164static String storageDirectory(DWORD pathIdentifier)
165{
166    Vector<UChar> buffer(MAX_PATH);
167    if (FAILED(SHGetFolderPathW(0, pathIdentifier | CSIDL_FLAG_CREATE, 0, 0, buffer.data())))
168        return String();
169    buffer.resize(wcslen(buffer.data()));
170    String directory = String::adopt(buffer);
171
172    static const String companyNameDirectory = "Apple Computer\\";
173    directory = pathByAppendingComponent(directory, companyNameDirectory + bundleName());
174    if (!makeAllDirectories(directory))
175        return String();
176
177    return directory;
178}
179
180static String cachedStorageDirectory(DWORD pathIdentifier)
181{
182    static HashMap<DWORD, String> directories;
183
184    HashMap<DWORD, String>::iterator it = directories.find(pathIdentifier);
185    if (it != directories.end())
186        return it->second;
187
188    String directory = storageDirectory(pathIdentifier);
189    directories.add(pathIdentifier, directory);
190
191    return directory;
192}
193
194String openTemporaryFile(const String&, PlatformFileHandle& handle)
195{
196    handle = INVALID_HANDLE_VALUE;
197
198    char tempPath[MAX_PATH];
199    int tempPathLength = ::GetTempPathA(WTF_ARRAY_LENGTH(tempPath), tempPath);
200    if (tempPathLength <= 0 || tempPathLength > WTF_ARRAY_LENGTH(tempPath))
201        return String();
202
203    HCRYPTPROV hCryptProv = 0;
204    if (!CryptAcquireContext(&hCryptProv, 0, 0, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
205        return String();
206
207    char proposedPath[MAX_PATH];
208    while (1) {
209        char tempFile[] = "XXXXXXXX.tmp"; // Use 8.3 style name (more characters aren't helpful due to 8.3 short file names)
210        const int randomPartLength = 8;
211        if (!CryptGenRandom(hCryptProv, randomPartLength, reinterpret_cast<BYTE*>(tempFile)))
212            break;
213
214        // Limit to valid filesystem characters, also excluding others that could be problematic, like punctuation.
215        // don't include both upper and lowercase since Windows file systems are typically not case sensitive.
216        const char validChars[] = "0123456789abcdefghijklmnopqrstuvwxyz";
217        for (int i = 0; i < randomPartLength; ++i)
218            tempFile[i] = validChars[tempFile[i] % (sizeof(validChars) - 1)];
219
220        ASSERT(strlen(tempFile) == sizeof(tempFile) - 1);
221
222        if (!PathCombineA(proposedPath, tempPath, tempFile))
223            break;
224
225        // use CREATE_NEW to avoid overwriting an existing file with the same name
226        handle = CreateFileA(proposedPath, GENERIC_READ | GENERIC_WRITE, 0, 0, CREATE_NEW, FILE_ATTRIBUTE_NORMAL, 0);
227        if (!isHandleValid(handle) && GetLastError() == ERROR_ALREADY_EXISTS)
228            continue;
229
230        break;
231    }
232
233    CryptReleaseContext(hCryptProv, 0);
234
235    if (!isHandleValid(handle))
236        return String();
237
238    return String::fromUTF8(proposedPath);
239}
240
241PlatformFileHandle openFile(const String& path, FileOpenMode mode)
242{
243    DWORD desiredAccess = 0;
244    DWORD creationDisposition = 0;
245    switch (mode) {
246    case OpenForRead:
247        desiredAccess = GENERIC_READ;
248        creationDisposition = OPEN_EXISTING;
249        break;
250    case OpenForWrite:
251        desiredAccess = GENERIC_WRITE;
252        creationDisposition = CREATE_ALWAYS;
253        break;
254    default:
255        ASSERT_NOT_REACHED();
256    }
257
258    String destination = path;
259    return CreateFile(destination.charactersWithNullTermination(), desiredAccess, 0, 0, creationDisposition, FILE_ATTRIBUTE_NORMAL, 0);
260}
261
262void closeFile(PlatformFileHandle& handle)
263{
264    if (isHandleValid(handle)) {
265        ::CloseHandle(handle);
266        handle = invalidPlatformFileHandle;
267    }
268}
269
270int writeToFile(PlatformFileHandle handle, const char* data, int length)
271{
272    if (!isHandleValid(handle))
273        return -1;
274
275    DWORD bytesWritten;
276    bool success = WriteFile(handle, data, length, &bytesWritten, 0);
277
278    if (!success)
279        return -1;
280    return static_cast<int>(bytesWritten);
281}
282
283bool unloadModule(PlatformModule module)
284{
285    return ::FreeLibrary(module);
286}
287
288String localUserSpecificStorageDirectory()
289{
290    return cachedStorageDirectory(CSIDL_LOCAL_APPDATA);
291}
292
293String roamingUserSpecificStorageDirectory()
294{
295    return cachedStorageDirectory(CSIDL_APPDATA);
296}
297
298bool safeCreateFile(const String& path, CFDataRef data)
299{
300    // Create a temporary file.
301    WCHAR tempDirPath[MAX_PATH];
302    if (!GetTempPathW(WTF_ARRAY_LENGTH(tempDirPath), tempDirPath))
303        return false;
304
305    WCHAR tempPath[MAX_PATH];
306    if (!GetTempFileNameW(tempDirPath, L"WEBKIT", 0, tempPath))
307        return false;
308
309    HANDLE tempFileHandle = CreateFileW(tempPath, GENERIC_READ | GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
310    if (tempFileHandle == INVALID_HANDLE_VALUE)
311        return false;
312
313    // Write the data to this temp file.
314    DWORD written;
315    if (!WriteFile(tempFileHandle, CFDataGetBytePtr(data), static_cast<DWORD>(CFDataGetLength(data)), &written, 0))
316        return false;
317
318    CloseHandle(tempFileHandle);
319
320    // Copy the temp file to the destination file.
321    String destination = path;
322    if (!MoveFileExW(tempPath, destination.charactersWithNullTermination(), MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED))
323        return false;
324
325    return true;
326}
327
328Vector<String> listDirectory(const String& directory, const String& filter)
329{
330    Vector<String> entries;
331
332    PathWalker walker(directory, filter);
333    if (!walker.isValid())
334        return entries;
335
336    do {
337        if (walker.data().dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
338            continue;
339
340        entries.append(makeString(directory, "\\", reinterpret_cast<const UChar*>(walker.data().cFileName)));
341    } while (walker.step());
342
343    return entries;
344}
345
346} // namespace WebCore
347