Rename StringBuilder::append(UChar32) to StringBuilder::appendCharacter(UChar32)...
[WebKit-https.git] / Source / WTF / wtf / FileSystem.cpp
1 /*
2  * Copyright (C) 2007-2017 Apple Inc. All rights reserved.
3  * Copyright (C) 2015 Canon Inc. 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  *
15  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
16  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
17  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
19  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
21  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
22  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  */
26
27 #include "config.h"
28 #include <wtf/FileSystem.h>
29
30 #include <wtf/FileMetadata.h>
31 #include <wtf/HexNumber.h>
32 #include <wtf/Scope.h>
33 #include <wtf/text/CString.h>
34 #include <wtf/text/StringBuilder.h>
35
36 #if !OS(WINDOWS)
37 #include <fcntl.h>
38 #include <sys/mman.h>
39 #include <sys/stat.h>
40 #include <unistd.h>
41 #endif
42
43 #if USE(GLIB)
44 #include <gio/gfiledescriptorbased.h>
45 #include <gio/gio.h>
46 #endif
47
48 namespace WTF {
49
50 namespace FileSystemImpl {
51
52 // The following lower-ASCII characters need escaping to be used in a filename
53 // across all systems, including Windows:
54 //     - Unprintable ASCII (00-1F)
55 //     - Space             (20)
56 //     - Double quote      (22)
57 //     - Percent           (25) (escaped because it is our escape character)
58 //     - Asterisk          (2A)
59 //     - Slash             (2F)
60 //     - Colon             (3A)
61 //     - Less-than         (3C)
62 //     - Greater-than      (3E)
63 //     - Question Mark     (3F)
64 //     - Backslash         (5C)
65 //     - Pipe              (7C)
66 //     - Delete            (7F)
67
68 static const bool needsEscaping[128] = {
69     true,  true,  true,  true,  true,  true,  true,  true,  /* 00-07 */
70     true,  true,  true,  true,  true,  true,  true,  true,  /* 08-0F */
71
72     true,  true,  true,  true,  true,  true,  true,  true,  /* 10-17 */
73     true,  true,  true,  true,  true,  true,  true,  true,  /* 18-1F */
74
75     true,  false, true,  false, false, true,  false, false, /* 20-27 */
76     false, false, true,  false, false, false, false, true,  /* 28-2F */
77
78     false, false, false, false, false, false, false, false, /* 30-37 */
79     false, false, true,  false, true,  false, true,  true,  /* 38-3F */
80
81     false, false, false, false, false, false, false, false, /* 40-47 */
82     false, false, false, false, false, false, false, false, /* 48-4F */
83
84     false, false, false, false, false, false, false, false, /* 50-57 */
85     false, false, false, false, true,  false, false, false, /* 58-5F */
86
87     false, false, false, false, false, false, false, false, /* 60-67 */
88     false, false, false, false, false, false, false, false, /* 68-6F */
89
90     false, false, false, false, false, false, false, false, /* 70-77 */
91     false, false, false, false, true,  false, false, true,  /* 78-7F */
92 };
93
94 static inline bool shouldEscapeUChar(UChar character, UChar previousCharacter, UChar nextCharacter)
95 {
96     if (character <= 127)
97         return needsEscaping[character];
98
99     if (U16_IS_LEAD(character) && !U16_IS_TRAIL(nextCharacter))
100         return true;
101
102     if (U16_IS_TRAIL(character) && !U16_IS_LEAD(previousCharacter))
103         return true;
104
105     return false;
106 }
107
108 String encodeForFileName(const String& inputString)
109 {
110     unsigned length = inputString.length();
111     if (!length)
112         return inputString;
113
114     StringBuilder result;
115     result.reserveCapacity(length);
116
117     UChar previousCharacter;
118     UChar character = 0;
119     UChar nextCharacter = inputString[0];
120     for (unsigned i = 0; i < length; ++i) {
121         previousCharacter = character;
122         character = nextCharacter;
123         nextCharacter = i + 1 < length ? inputString[i + 1] : 0;
124
125         if (shouldEscapeUChar(character, previousCharacter, nextCharacter)) {
126             if (character <= 255) {
127                 result.append('%');
128                 appendByteAsHex(character, result);
129             } else {
130                 result.appendLiteral("%+");
131                 appendByteAsHex(character >> 8, result);
132                 appendByteAsHex(character, result);
133             }
134         } else
135             result.append(character);
136     }
137
138     return result.toString();
139 }
140
141 String decodeFromFilename(const String& inputString)
142 {
143     unsigned length = inputString.length();
144     if (!length)
145         return inputString;
146
147     StringBuilder result;
148     result.reserveCapacity(length);
149
150     for (unsigned i = 0; i < length; ++i) {
151         if (inputString[i] != '%') {
152             result.append(inputString[i]);
153             continue;
154         }
155
156         // If the input string is a valid encoded filename, it must be at least 2 characters longer
157         // than the current index to account for this percent encoded value.
158         if (i + 2 >= length)
159             return { };
160
161         if (inputString[i+1] != '+') {
162             if (!isASCIIHexDigit(inputString[i + 1]))
163                 return { };
164             if (!isASCIIHexDigit(inputString[i + 2]))
165                 return { };
166             result.append(toASCIIHexValue(inputString[i + 1], inputString[i + 2]));
167             i += 2;
168             continue;
169         }
170
171         // If the input string is a valid encoded filename, it must be at least 5 characters longer
172         // than the current index to account for this percent encoded value.
173         if (i + 5 >= length)
174             return { };
175
176         if (!isASCIIHexDigit(inputString[i + 2]))
177             return { };
178         if (!isASCIIHexDigit(inputString[i + 3]))
179             return { };
180         if (!isASCIIHexDigit(inputString[i + 4]))
181             return { };
182         if (!isASCIIHexDigit(inputString[i + 5]))
183             return { };
184
185         UChar hexDigit = toASCIIHexValue(inputString[i + 2], inputString[i + 3]) << 8 | toASCIIHexValue(inputString[i + 4], inputString[i + 5]);
186         result.append(hexDigit);
187         i += 5;
188     }
189
190     return result.toString();
191 }
192
193 String lastComponentOfPathIgnoringTrailingSlash(const String& path)
194 {
195 #if OS(WINDOWS)
196     char pathSeparator = '\\';
197 #else
198     char pathSeparator = '/';
199 #endif
200
201     auto position = path.reverseFind(pathSeparator);
202     if (position == notFound)
203         return path;
204
205     size_t endOfSubstring = path.length() - 1;
206     if (position == endOfSubstring) {
207         --endOfSubstring;
208         position = path.reverseFind(pathSeparator, endOfSubstring);
209     }
210
211     return path.substring(position + 1, endOfSubstring - position);
212 }
213
214 bool appendFileContentsToFileHandle(const String& path, PlatformFileHandle& target)
215 {
216     auto source = openFile(path, FileOpenMode::Read);
217
218     if (!isHandleValid(source))
219         return false;
220
221     static int bufferSize = 1 << 19;
222     Vector<char> buffer(bufferSize);
223
224     auto fileCloser = WTF::makeScopeExit([source]() {
225         PlatformFileHandle handle = source;
226         closeFile(handle);
227     });
228
229     do {
230         int readBytes = readFromFile(source, buffer.data(), bufferSize);
231
232         if (readBytes < 0)
233             return false;
234
235         if (writeToFile(target, buffer.data(), readBytes) != readBytes)
236             return false;
237
238         if (readBytes < bufferSize)
239             return true;
240     } while (true);
241
242     ASSERT_NOT_REACHED();
243 }
244
245
246 bool filesHaveSameVolume(const String& fileA, const String& fileB)
247 {
248     auto fsRepFileA = fileSystemRepresentation(fileA);
249     auto fsRepFileB = fileSystemRepresentation(fileB);
250
251     if (fsRepFileA.isNull() || fsRepFileB.isNull())
252         return false;
253
254     bool result = false;
255
256     auto fileADev = getFileDeviceId(fsRepFileA);
257     auto fileBDev = getFileDeviceId(fsRepFileB);
258
259     if (fileADev && fileBDev)
260         result = (fileADev == fileBDev);
261
262     return result;
263 }
264
265 #if !PLATFORM(MAC)
266
267 void setMetadataURL(const String&, const String&, const String&)
268 {
269 }
270
271 bool canExcludeFromBackup()
272 {
273     return false;
274 }
275
276 bool excludeFromBackup(const String&)
277 {
278     return false;
279 }
280
281 #endif
282
283 MappedFileData::~MappedFileData()
284 {
285     if (!m_fileData)
286         return;
287     unmapViewOfFile(m_fileData, m_fileSize);
288 }
289
290 #if HAVE(MMAP)
291
292 MappedFileData::MappedFileData(const String& filePath, MappedFileMode mode, bool& success)
293 {
294     auto fd = openFile(filePath, FileOpenMode::Read);
295
296     success = mapFileHandle(fd, mode);
297     closeFile(fd);
298 }
299
300 bool MappedFileData::mapFileHandle(PlatformFileHandle handle, MappedFileMode mode)
301 {
302     if (!isHandleValid(handle))
303         return false;
304
305     int fd;
306 #if USE(GLIB)
307     auto* inputStream = g_io_stream_get_input_stream(G_IO_STREAM(handle));
308     fd = g_file_descriptor_based_get_fd(G_FILE_DESCRIPTOR_BASED(inputStream));
309 #else
310     fd = handle;
311 #endif
312
313     struct stat fileStat;
314     if (fstat(fd, &fileStat)) {
315         return false;
316     }
317
318     unsigned size;
319     if (!WTF::convertSafely(fileStat.st_size, size)) {
320         return false;
321     }
322
323     if (!size) {
324         return true;
325     }
326
327     void* data = mmap(0, size, PROT_READ, MAP_FILE | (mode == MappedFileMode::Shared ? MAP_SHARED : MAP_PRIVATE), fd, 0);
328
329     if (data == MAP_FAILED) {
330         return false;
331     }
332
333     m_fileData = data;
334     m_fileSize = size;
335     return true;
336 }
337
338 bool unmapViewOfFile(void* buffer, size_t size)
339 {
340     return !munmap(buffer, size);
341 }
342
343 #endif
344
345 PlatformFileHandle openAndLockFile(const String& path, FileOpenMode openMode, OptionSet<FileLockMode> lockMode)
346 {
347     auto handle = openFile(path, openMode);
348     if (handle == invalidPlatformFileHandle)
349         return invalidPlatformFileHandle;
350
351 #if USE(FILE_LOCK)
352     bool locked = lockFile(handle, lockMode);
353     ASSERT_UNUSED(locked, locked);
354 #else
355     UNUSED_PARAM(lockMode);
356 #endif
357
358     return handle;
359 }
360
361 void unlockAndCloseFile(PlatformFileHandle handle)
362 {
363 #if USE(FILE_LOCK)
364     bool unlocked = unlockFile(handle);
365     ASSERT_UNUSED(unlocked, unlocked);
366 #endif
367     closeFile(handle);
368 }
369
370 bool fileIsDirectory(const String& path, ShouldFollowSymbolicLinks shouldFollowSymbolicLinks)
371 {
372     auto metadata = shouldFollowSymbolicLinks == ShouldFollowSymbolicLinks::Yes ? fileMetadataFollowingSymlinks(path) : fileMetadata(path);
373     if (!metadata)
374         return false;
375     return metadata.value().type == FileMetadata::Type::Directory;
376 }
377
378 #if !PLATFORM(IOS_FAMILY)
379 bool isSafeToUseMemoryMapForPath(const String&)
380 {
381     return true;
382 }
383
384 void makeSafeToUseMemoryMapForPath(const String&)
385 {
386 }
387 #endif
388
389 #if !PLATFORM(COCOA)
390 String createTemporaryZipArchive(const String&)
391 {
392     return { };
393 }
394 #endif
395
396 } // namespace FileSystemImpl
397 } // namespace WTF