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
43namespace WTF {
44
45namespace FileSystemImpl {
46
47// The following lower-ASCII characters need escaping to be used in a filename
48// across all systems, including Windows:
49// - Unprintable ASCII (00-1F)
50// - Space (20)
51// - Double quote (22)
52// - Percent (25) (escaped because it is our escape character)
53// - Asterisk (2A)
54// - Slash (2F)
55// - Colon (3A)
56// - Less-than (3C)
57// - Greater-than (3E)
58// - Question Mark (3F)
59// - Backslash (5C)
60// - Pipe (7C)
61// - Delete (7F)
62
63static const bool needsEscaping[128] = {
64 true, true, true, true, true, true, true, true, /* 00-07 */
65 true, true, true, true, true, true, true, true, /* 08-0F */
66
67 true, true, true, true, true, true, true, true, /* 10-17 */
68 true, true, true, true, true, true, true, true, /* 18-1F */
69
70 true, false, true, false, false, true, false, false, /* 20-27 */
71 false, false, true, false, false, false, false, true, /* 28-2F */
72
73 false, false, false, false, false, false, false, false, /* 30-37 */
74 false, false, true, false, true, false, true, true, /* 38-3F */
75
76 false, false, false, false, false, false, false, false, /* 40-47 */
77 false, false, false, false, false, false, false, false, /* 48-4F */
78
79 false, false, false, false, false, false, false, false, /* 50-57 */
80 false, false, false, false, true, false, false, false, /* 58-5F */
81
82 false, false, false, false, false, false, false, false, /* 60-67 */
83 false, false, false, false, false, false, false, false, /* 68-6F */
84
85 false, false, false, false, false, false, false, false, /* 70-77 */
86 false, false, false, false, true, false, false, true, /* 78-7F */
87};
88
89static inline bool shouldEscapeUChar(UChar character, UChar previousCharacter, UChar nextCharacter)
90{
91 if (character <= 127)
92 return needsEscaping[character];
93
94 if (U16_IS_LEAD(character) && !U16_IS_TRAIL(nextCharacter))
95 return true;
96
97 if (U16_IS_TRAIL(character) && !U16_IS_LEAD(previousCharacter))
98 return true;
99
100 return false;
101}
102
103String encodeForFileName(const String& inputString)
104{
105 unsigned length = inputString.length();
106 if (!length)
107 return inputString;
108
109 StringBuilder result;
110 result.reserveCapacity(length);
111
112 UChar previousCharacter;
113 UChar character = 0;
114 UChar nextCharacter = inputString[0];
115 for (unsigned i = 0; i < length; ++i) {
116 previousCharacter = character;
117 character = nextCharacter;
118 nextCharacter = i + 1 < length ? inputString[i + 1] : 0;
119
120 if (shouldEscapeUChar(character, previousCharacter, nextCharacter)) {
121 if (character <= 255) {
122 result.append('%');
123 appendByteAsHex(character, result);
124 } else {
125 result.appendLiteral("%+");
126 appendByteAsHex(character >> 8, result);
127 appendByteAsHex(character, result);
128 }
129 } else
130 result.append(character);
131 }
132
133 return result.toString();
134}
135
136String decodeFromFilename(const String& inputString)
137{
138 unsigned length = inputString.length();
139 if (!length)
140 return inputString;
141
142 StringBuilder result;
143 result.reserveCapacity(length);
144
145 for (unsigned i = 0; i < length; ++i) {
146 if (inputString[i] != '%') {
147 result.append(inputString[i]);
148 continue;
149 }
150
151 // If the input string is a valid encoded filename, it must be at least 2 characters longer
152 // than the current index to account for this percent encoded value.
153 if (i + 2 >= length)
154 return { };
155
156 if (inputString[i+1] != '+') {
157 if (!isASCIIHexDigit(inputString[i + 1]))
158 return { };
159 if (!isASCIIHexDigit(inputString[i + 2]))
160 return { };
161 result.append(toASCIIHexValue(inputString[i + 1], inputString[i + 2]));
162 i += 2;
163 continue;
164 }
165
166 // If the input string is a valid encoded filename, it must be at least 5 characters longer
167 // than the current index to account for this percent encoded value.
168 if (i + 5 >= length)
169 return { };
170
171 if (!isASCIIHexDigit(inputString[i + 2]))
172 return { };
173 if (!isASCIIHexDigit(inputString[i + 3]))
174 return { };
175 if (!isASCIIHexDigit(inputString[i + 4]))
176 return { };
177 if (!isASCIIHexDigit(inputString[i + 5]))
178 return { };
179
180 result.append(toASCIIHexValue(inputString[i + 2], inputString[i + 3]) << 8 | toASCIIHexValue(inputString[i + 4], inputString[i + 5]));
181 i += 5;
182 }
183
184 return result.toString();
185}
186
187String lastComponentOfPathIgnoringTrailingSlash(const String& path)
188{
189#if OS(WINDOWS)
190 char pathSeparator = '\\';
191#else
192 char pathSeparator = '/';
193#endif
194
195 auto position = path.reverseFind(pathSeparator);
196 if (position == notFound)
197 return path;
198
199 size_t endOfSubstring = path.length() - 1;
200 if (position == endOfSubstring) {
201 --endOfSubstring;
202 position = path.reverseFind(pathSeparator, endOfSubstring);
203 }
204
205 return path.substring(position + 1, endOfSubstring - position);
206}
207
208bool appendFileContentsToFileHandle(const String& path, PlatformFileHandle& target)
209{
210 auto source = openFile(path, FileOpenMode::Read);
211
212 if (!isHandleValid(source))
213 return false;
214
215 static int bufferSize = 1 << 19;
216 Vector<char> buffer(bufferSize);
217
218 auto fileCloser = WTF::makeScopeExit([source]() {
219 PlatformFileHandle handle = source;
220 closeFile(handle);
221 });
222
223 do {
224 int readBytes = readFromFile(source, buffer.data(), bufferSize);
225
226 if (readBytes < 0)
227 return false;
228
229 if (writeToFile(target, buffer.data(), readBytes) != readBytes)
230 return false;
231
232 if (readBytes < bufferSize)
233 return true;
234 } while (true);
235
236 ASSERT_NOT_REACHED();
237}
238
239
240bool filesHaveSameVolume(const String& fileA, const String& fileB)
241{
242 auto fsRepFileA = fileSystemRepresentation(fileA);
243 auto fsRepFileB = fileSystemRepresentation(fileB);
244
245 if (fsRepFileA.isNull() || fsRepFileB.isNull())
246 return false;
247
248 bool result = false;
249
250 auto fileADev = getFileDeviceId(fsRepFileA);
251 auto fileBDev = getFileDeviceId(fsRepFileB);
252
253 if (fileADev && fileBDev)
254 result = (fileADev == fileBDev);
255
256 return result;
257}
258
259#if !PLATFORM(MAC)
260
261void setMetadataURL(const String&, const String&, const String&)
262{
263}
264
265bool canExcludeFromBackup()
266{
267 return false;
268}
269
270bool excludeFromBackup(const String&)
271{
272 return false;
273}
274
275#endif
276
277MappedFileData::~MappedFileData()
278{
279#if !OS(WINDOWS)
280 if (!m_fileData)
281 return;
282 munmap(m_fileData, m_fileSize);
283#endif
284}
285
286MappedFileData::MappedFileData(const String& filePath, bool& success)
287{
288#if OS(WINDOWS)
289 // FIXME: Implement mapping
290 success = false;
291#else
292 CString fsRep = fileSystemRepresentation(filePath);
293 int fd = !fsRep.isNull() ? open(fsRep.data(), O_RDONLY) : -1;
294 if (fd < 0) {
295 success = false;
296 return;
297 }
298
299 struct stat fileStat;
300 if (fstat(fd, &fileStat)) {
301 close(fd);
302 success = false;
303 return;
304 }
305
306 unsigned size;
307 if (!WTF::convertSafely(fileStat.st_size, size)) {
308 close(fd);
309 success = false;
310 return;
311 }
312
313 if (!size) {
314 close(fd);
315 success = true;
316 return;
317 }
318
319 void* data = mmap(0, size, PROT_READ, MAP_FILE | MAP_SHARED, fd, 0);
320 close(fd);
321
322 if (data == MAP_FAILED) {
323 success = false;
324 return;
325 }
326
327 success = true;
328 m_fileData = data;
329 m_fileSize = size;
330#endif
331}
332
333PlatformFileHandle openAndLockFile(const String& path, FileOpenMode openMode, OptionSet<FileLockMode> lockMode)
334{
335 auto handle = openFile(path, openMode);
336 if (handle == invalidPlatformFileHandle)
337 return invalidPlatformFileHandle;
338
339#if USE(FILE_LOCK)
340 bool locked = lockFile(handle, lockMode);
341 ASSERT_UNUSED(locked, locked);
342#else
343 UNUSED_PARAM(lockMode);
344#endif
345
346 return handle;
347}
348
349void unlockAndCloseFile(PlatformFileHandle handle)
350{
351#if USE(FILE_LOCK)
352 bool unlocked = unlockFile(handle);
353 ASSERT_UNUSED(unlocked, unlocked);
354#endif
355 closeFile(handle);
356}
357
358bool fileIsDirectory(const String& path, ShouldFollowSymbolicLinks shouldFollowSymbolicLinks)
359{
360 auto metadata = shouldFollowSymbolicLinks == ShouldFollowSymbolicLinks::Yes ? fileMetadataFollowingSymlinks(path) : fileMetadata(path);
361 if (!metadata)
362 return false;
363 return metadata.value().type == FileMetadata::Type::Directory;
364}
365
366#if !PLATFORM(IOS_FAMILY)
367bool isSafeToUseMemoryMapForPath(const String&)
368{
369 return true;
370}
371
372void makeSafeToUseMemoryMapForPath(const String&)
373{
374}
375#endif
376
377} // namespace FileSystemImpl
378} // namespace WTF
379