Implement FileSystemEntry.getParent()
[WebKit-https.git] / Source / WebCore / Modules / entriesapi / DOMFileSystem.cpp
1 /*
2  * Copyright (C) 2017 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "DOMFileSystem.h"
28
29 #include "File.h"
30 #include "FileMetadata.h"
31 #include "FileSystem.h"
32 #include "FileSystemDirectoryEntry.h"
33 #include "FileSystemFileEntry.h"
34 #include "ScriptExecutionContext.h"
35 #include <wtf/CrossThreadCopier.h>
36 #include <wtf/UUID.h>
37 #include <wtf/text/StringBuilder.h>
38
39 namespace WebCore {
40
41 struct ListedChild {
42     String filename;
43     FileMetadata::Type type;
44
45     ListedChild isolatedCopy() const { return { filename.isolatedCopy(), type }; }
46 };
47
48 static ExceptionOr<Vector<ListedChild>> listDirectoryWithMetadata(const String& fullPath)
49 {
50     ASSERT(!isMainThread());
51     if (!fileIsDirectory(fullPath, ShouldFollowSymbolicLinks::No))
52         return Exception { NotFoundError, "Path no longer exists or is no longer a directory" };
53
54     auto childPaths = listDirectory(fullPath, "*");
55     Vector<ListedChild> listedChildren;
56     listedChildren.reserveInitialCapacity(childPaths.size());
57     for (auto& childPath : childPaths) {
58         FileMetadata metadata;
59         if (!getFileMetadata(childPath, metadata, ShouldFollowSymbolicLinks::No))
60             continue;
61         listedChildren.uncheckedAppend(ListedChild { pathGetFileName(childPath), metadata.type });
62     }
63     return WTFMove(listedChildren);
64 }
65
66 static ExceptionOr<Vector<Ref<FileSystemEntry>>> toFileSystemEntries(ScriptExecutionContext& context, DOMFileSystem& fileSystem, ExceptionOr<Vector<ListedChild>>&& listedChildren, const String& parentVirtualPath)
67 {
68     ASSERT(isMainThread());
69     if (listedChildren.hasException())
70         return listedChildren.releaseException();
71
72     Vector<Ref<FileSystemEntry>> entries;
73     entries.reserveInitialCapacity(listedChildren.returnValue().size());
74     for (auto& child : listedChildren.returnValue()) {
75         String virtualPath = parentVirtualPath + "/" + child.filename;
76         switch (child.type) {
77         case FileMetadata::TypeFile:
78             entries.uncheckedAppend(FileSystemFileEntry::create(context, fileSystem, virtualPath));
79             break;
80         case FileMetadata::TypeDirectory:
81             entries.uncheckedAppend(FileSystemDirectoryEntry::create(context, fileSystem, virtualPath));
82             break;
83         default:
84             break;
85         }
86     }
87     return WTFMove(entries);
88 }
89
90 static bool isAbsoluteVirtualPath(const String& virtualPath)
91 {
92     return !virtualPath.isEmpty() && virtualPath[0] == '/';
93 }
94
95 DOMFileSystem::DOMFileSystem(Ref<File>&& file)
96     : m_name(createCanonicalUUIDString())
97     , m_file(WTFMove(file))
98     , m_rootPath(directoryName(m_file->path()))
99     , m_workQueue(WorkQueue::create("DOMFileSystem work queue"))
100 {
101     ASSERT(!m_rootPath.endsWith('/'));
102 }
103
104 DOMFileSystem::~DOMFileSystem()
105 {
106 }
107
108 Ref<FileSystemDirectoryEntry> DOMFileSystem::root(ScriptExecutionContext& context)
109 {
110     return FileSystemDirectoryEntry::create(context, *this, ASCIILiteral("/"));
111 }
112
113 Ref<FileSystemEntry> DOMFileSystem::fileAsEntry(ScriptExecutionContext& context)
114 {
115     if (m_file->isDirectory())
116         return FileSystemDirectoryEntry::create(context, *this, "/" + m_file->name());
117     return FileSystemFileEntry::create(context, *this, "/" + m_file->name());
118 }
119
120 // https://wicg.github.io/entries-api/#resolve-a-relative-path
121 static String resolveRelativePath(const String& virtualPath, const String& relativePath)
122 {
123     ASSERT(virtualPath[0] == '/');
124     if (isAbsoluteVirtualPath(relativePath))
125         return relativePath;
126
127     auto virtualPathSegments = virtualPath.split('/');
128     auto relativePathSegments = relativePath.split('/');
129     for (auto& segment : relativePathSegments) {
130         ASSERT(!segment.isEmpty());
131         if (segment == ".")
132             continue;
133         if (segment == "..") {
134             if (!virtualPathSegments.isEmpty())
135                 virtualPathSegments.removeLast();
136             continue;
137         }
138         virtualPathSegments.append(segment);
139     }
140
141     if (virtualPathSegments.isEmpty())
142         return ASCIILiteral("/");
143
144     StringBuilder builder;
145     for (auto& segment : virtualPathSegments) {
146         builder.append('/');
147         builder.append(segment);
148     }
149     return builder.toString();
150 }
151
152 // https://wicg.github.io/entries-api/#evaluate-a-path
153 String DOMFileSystem::evaluatePath(const String& virtualPath)
154 {
155     ASSERT(virtualPath[0] == '/');
156     auto components = virtualPath.split('/');
157
158     Vector<String> resolvedComponents;
159     resolvedComponents.reserveInitialCapacity(components.size());
160     for (auto& component : components) {
161         if (component == ".")
162             continue;
163         if (component == "..") {
164             if (!resolvedComponents.isEmpty())
165                 resolvedComponents.removeLast();
166             continue;
167         }
168         resolvedComponents.uncheckedAppend(component);
169     }
170
171     return pathByAppendingComponents(m_rootPath, resolvedComponents);
172 }
173
174 void DOMFileSystem::listDirectory(ScriptExecutionContext& context, FileSystemDirectoryEntry& directory, DirectoryListingCallback&& completionHandler)
175 {
176     ASSERT(&directory.filesystem() == this);
177
178     String directoryVirtualPath = directory.virtualPath();
179     auto fullPath = evaluatePath(directoryVirtualPath);
180     if (fullPath == m_rootPath) {
181         Vector<Ref<FileSystemEntry>> children;
182         children.append(fileAsEntry(context));
183         completionHandler(WTFMove(children));
184         return;
185     }
186
187     m_workQueue->dispatch([this, context = makeRef(context), completionHandler = WTFMove(completionHandler), fullPath = fullPath.isolatedCopy(), directoryVirtualPath = directoryVirtualPath.isolatedCopy()]() mutable {
188         auto listedChildren = listDirectoryWithMetadata(fullPath);
189         callOnMainThread([this, context = WTFMove(context), completionHandler = WTFMove(completionHandler), listedChildren = crossThreadCopy(listedChildren), directoryVirtualPath = directoryVirtualPath.isolatedCopy()]() mutable {
190             completionHandler(toFileSystemEntries(context, *this, WTFMove(listedChildren), directoryVirtualPath));
191         });
192     });
193 }
194
195 void DOMFileSystem::getParent(ScriptExecutionContext& context, FileSystemEntry& entry, GetParentCallback&& completionCallback)
196 {
197     String virtualPath = resolveRelativePath(entry.virtualPath(), ASCIILiteral(".."));
198     ASSERT(virtualPath[0] == '/');
199     String fullPath = evaluatePath(virtualPath);
200     m_workQueue->dispatch([this, context = makeRef(context), fullPath = crossThreadCopy(fullPath), virtualPath = crossThreadCopy(virtualPath), completionCallback = WTFMove(completionCallback)]() mutable {
201         if (!fileIsDirectory(fullPath, ShouldFollowSymbolicLinks::No)) {
202             callOnMainThread([completionCallback = WTFMove(completionCallback)] {
203                 completionCallback(Exception { NotFoundError, "Path no longer exists or is no longer a directory" });
204             });
205             return;
206         }
207         callOnMainThread([this, context = WTFMove(context), virtualPath = crossThreadCopy(virtualPath), completionCallback = WTFMove(completionCallback)] {
208             completionCallback(FileSystemDirectoryEntry::create(context, *this, virtualPath));
209         });
210     });
211 }
212
213 } // namespace WebCore