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
48namespace WTF {
49
50namespace 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
68static 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
94static 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
108String 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
141String 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
193String 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
214bool 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
246bool 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
267void setMetadataURL(const String&, const String&, const String&)
268{
269}
270
271bool canExcludeFromBackup()
272{
273 return false;
274}
275
276bool excludeFromBackup(const String&)
277{
278 return false;
279}
280
281#endif
282
283MappedFileData::~MappedFileData()
284{
285 if (!m_fileData)
286 return;
287 unmapViewOfFile(m_fileData, m_fileSize);
288}
289
290#if HAVE(MMAP)
291
292MappedFileData::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
300bool 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
338bool unmapViewOfFile(void* buffer, size_t size)
339{
340 return !munmap(buffer, size);
341}
342
343#endif
344
345PlatformFileHandle 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
361void 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
370bool 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)
379bool isSafeToUseMemoryMapForPath(const String&)
380{
381 return true;
382}
383
384void makeSafeToUseMemoryMapForPath(const String&)
385{
386}
387#endif
388
389#if !PLATFORM(COCOA)
390String createTemporaryZipArchive(const String&)
391{
392 return { };
393}
394#endif
395
396} // namespace FileSystemImpl
397} // namespace WTF
398