Implement FileSystemDirectoryEntry.getFile()
authorcdumez@apple.com <cdumez@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 2 Sep 2017 00:19:55 +0000 (00:19 +0000)
committercdumez@apple.com <cdumez@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 2 Sep 2017 00:19:55 +0000 (00:19 +0000)
https://bugs.webkit.org/show_bug.cgi?id=176167
<rdar://problem/34187775>

Reviewed by Andreas Kling.

Source/WebCore:

Implement FileSystemDirectoryEntry.getFile():
- https://wicg.github.io/entries-api/#dom-filesystemdirectoryentry-getfile

Test: editing/pasteboard/datatransfer-items-drop-getFile.html

* Modules/entriesapi/DOMFileSystem.cpp:
(WebCore::isValidPathNameCharacter):
(WebCore::isValidPathSegment):
(WebCore::isValidRelativeVirtualPath):
(WebCore::isValidAbsoluteVirtualPath):
(WebCore::isValidVirtualPath):
Implement various path validation functions as per:
- https://wicg.github.io/entries-api/#names-paths

(WebCore::resolveRelativeVirtualPath):
- Use StringView for second parameter for efficiency. Had to keep
  a String for the first parameter because I need the split to
  return a Vector.
- If the input path is absolute, call recursively with '/' as
  base path so that the virtual path gets sanitized (i.e. '..'
  and '.' in the absolute URL get resolved).

(WebCore::DOMFileSystem::listDirectory):
Use String instead of auto. It is not much longer and is clearer.

(WebCore::validatePathIsDirectory):
(WebCore::DOMFileSystem::getParent):
Move logic to validate that the path is a directory from getParent() to a
separate function. This only makes sure the ScriptExecutionContext is only
ref'd / deref'd on the main thread.

(WebCore::validatePathIsFile):
Logic for validating that a path is a file, used by getFile().

(WebCore::DOMFileSystem::getFile):
Implement getFile() as per:
- https://wicg.github.io/entries-api/#dom-filesystemdirectoryentry-getfile

* Modules/entriesapi/DOMFileSystem.h:
* Modules/entriesapi/FileSystemDirectoryEntry.cpp:
(WebCore::FileSystemDirectoryEntry::getFile):
Add implementation of FileSystemDirectoryEntry::getFile() which merely calls
DOMFileSystem::getFile().

LayoutTests:

Add layout test coverage.

* editing/pasteboard/datatransfer-items-drop-getFile-expected.txt: Added.
* editing/pasteboard/datatransfer-items-drop-getFile.html: Added.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@221510 268f45cc-cd09-0410-ab3c-d52691b4dbfc

LayoutTests/ChangeLog
LayoutTests/editing/pasteboard/datatransfer-items-drop-getFile-expected.txt [new file with mode: 0644]
LayoutTests/editing/pasteboard/datatransfer-items-drop-getFile.html [new file with mode: 0644]
LayoutTests/platform/wk2/TestExpectations
Source/WebCore/ChangeLog
Source/WebCore/Modules/entriesapi/DOMFileSystem.cpp
Source/WebCore/Modules/entriesapi/DOMFileSystem.h
Source/WebCore/Modules/entriesapi/FileSystemDirectoryEntry.cpp

index 649aff9..68daa7e 100644 (file)
@@ -1,3 +1,16 @@
+2017-09-01  Chris Dumez  <cdumez@apple.com>
+
+        Implement FileSystemDirectoryEntry.getFile()
+        https://bugs.webkit.org/show_bug.cgi?id=176167
+        <rdar://problem/34187775>
+
+        Reviewed by Andreas Kling.
+
+        Add layout test coverage.
+
+        * editing/pasteboard/datatransfer-items-drop-getFile-expected.txt: Added.
+        * editing/pasteboard/datatransfer-items-drop-getFile.html: Added.
+
 2017-09-01  Matt Lewis  <jlewis3@apple.com>
 
         Skipped multiple webgl tests.
diff --git a/LayoutTests/editing/pasteboard/datatransfer-items-drop-getFile-expected.txt b/LayoutTests/editing/pasteboard/datatransfer-items-drop-getFile-expected.txt
new file mode 100644 (file)
index 0000000..2f1c1ed
--- /dev/null
@@ -0,0 +1,48 @@
+Basic test coverage for fileSystemDirectoryEntry.getFile()
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS dataTransfer.items.length is 1
+* Error case: create flag is set to true
+PASS ex.name is "SecurityError"
+* Regular case: getFile('file1.txt')
+PASS file1Entry.name is "file1.txt"
+PASS file1Entry.fullPath is "/testFiles/file1.txt"
+PASS file1Entry.isFile is true
+* Error case: calling getFile() with path to folder
+PASS ex.name is "TypeMismatchError"
+* Error case: calling getFile() with path containing backslash
+PASS ex.name is "TypeMismatchError"
+* Error case: calling getFile() with path which does not exist
+PASS ex.name is "NotFoundError"
+* Error case: calling getFile() with path containing a NUL character
+PASS ex.name is "TypeMismatchError"
+* Error case: calling getFile() with path to root
+PASS ex.name is "TypeMismatchError"
+* Regular case: calling getFile() with absolute path
+PASS file3Entry.name is "file3.txt"
+PASS file3Entry.fullPath is "/testFiles/subfolder1/file3.txt"
+PASS file3Entry.isFile is true
+* Edge case: calling getFile() with relative path containing '.' and '..'
+PASS file3Entry.name is "file3.txt"
+PASS file3Entry.fullPath is "/testFiles/subfolder1/file3.txt"
+PASS file3Entry.isFile is true
+* Edge case: calling getFile() with relative path containing too many '..'
+PASS file1Entry.name is "file1.txt"
+PASS file1Entry.fullPath is "/testFiles/file1.txt"
+PASS file1Entry.isFile is true
+* Edge case: calling getFile() with absolute path containing '..'
+PASS file1Entry.name is "file1.txt"
+PASS file1Entry.fullPath is "/testFiles/file1.txt"
+PASS file1Entry.isFile is true
+* Edge case: calling getFile() with absolute path containing too many '..'
+PASS file1Entry.name is "file1.txt"
+PASS file1Entry.fullPath is "/testFiles/file1.txt"
+PASS file1Entry.isFile is true
+* Error case: calling getFile() with empty path
+PASS ex.name is "TypeMismatchError"
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/editing/pasteboard/datatransfer-items-drop-getFile.html b/LayoutTests/editing/pasteboard/datatransfer-items-drop-getFile.html
new file mode 100644 (file)
index 0000000..9461f04
--- /dev/null
@@ -0,0 +1,195 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../resources/js-test.js"></script>
+<script src="../editing.js"></script>
+</head>
+<body>
+<div id="dropzone" style="width: 200px; height: 200px; background-color: grey;"></div>
+<script>
+description("Basic test coverage for fileSystemDirectoryEntry.getFile()");
+jsTestIsAsync = true;
+
+function getFileAsPromise(directory, path, flags)
+{
+    return new Promise((resolve, reject) => {
+        directory.getFile(path, flags, resolve, reject);
+    });
+}
+
+function test1() {
+    debug("* Error case: create flag is set to true");
+    return getFileAsPromise(directoryEntry, "file1.txt", { create: true }).then(entry => {
+        testFailed("Should fail when create flag is true");
+    }, e => {
+        ex = e;
+        shouldBeEqualToString("ex.name", "SecurityError");
+    });
+}
+
+function test2() {
+    debug("* Regular case: getFile('file1.txt')");
+    return getFileAsPromise(directoryEntry, "file1.txt", {}).then(entry => {
+        file1Entry = entry;
+        shouldBeEqualToString("file1Entry.name", "file1.txt");
+        shouldBeEqualToString("file1Entry.fullPath", "/testFiles/file1.txt");
+        shouldBeTrue("file1Entry.isFile");
+    }, e => {
+        testFailed("getFile('file1.txt') should succeed");
+    });
+}
+
+function test3() {
+    debug("* Error case: calling getFile() with path to folder");
+    return getFileAsPromise(directoryEntry, "subfolder1", {}).then(entry => {
+        testFailed("Should fail when path points to a folder");
+    }, e => {
+        ex = e;
+        shouldBeEqualToString("ex.name", "TypeMismatchError");
+    });
+}
+
+function test4() {
+    debug("* Error case: calling getFile() with path containing backslash");
+    return getFileAsPromise(directoryEntry, "\\file1.txt", {}).then(entry => {
+        testFailed("Should fail when path is invalid");
+    }, e => {
+        ex = e;
+        shouldBeEqualToString("ex.name", "TypeMismatchError");
+    });
+}
+
+function test5() {
+    debug("* Error case: calling getFile() with path which does not exist");
+    return getFileAsPromise(directoryEntry, "doesnotexist.txt", {}).then(entry => {
+        testFailed("Should fail when path does not exist");
+    }, e => {
+        ex = e;
+        shouldBeEqualToString("ex.name", "NotFoundError");
+    });
+}
+
+function test6() {
+    debug("* Error case: calling getFile() with path containing a NUL character");
+    return getFileAsPromise(directoryEntry, "file1.txt\0", {}).then(entry => {
+        testFailed("Should fail when path is invalid");
+    }, e => {
+        ex = e;
+        shouldBeEqualToString("ex.name", "TypeMismatchError");
+    });
+}
+
+function test7() {
+    debug("* Error case: calling getFile() with path to root");
+    return getFileAsPromise(directoryEntry, "/", {}).then(entry => {
+        testFailed("Should fail when path is not a directory");
+    }, e => {
+        ex = e;
+        shouldBeEqualToString("ex.name", "TypeMismatchError");
+    });
+}
+
+function test8() {
+    debug("* Regular case: calling getFile() with absolute path");
+    return getFileAsPromise(directoryEntry, "/testFiles/subfolder1/file3.txt", {}).then(entry => {
+        file3Entry = entry;
+        shouldBeEqualToString("file3Entry.name", "file3.txt");
+        shouldBeEqualToString("file3Entry.fullPath", "/testFiles/subfolder1/file3.txt");
+        shouldBeTrue("file3Entry.isFile");
+    }, e => {
+        testFailed("getFile('/testFiles/subfolder1/file3.txt') should succeed");
+    });
+}
+
+function test9() {
+    debug("* Edge case: calling getFile() with relative path containing '.' and '..'");
+    return getFileAsPromise(directoryEntry, "../testFiles/././subfolder1/../subfolder1/./file3.txt", {}).then(entry => {
+        file3Entry = entry;
+        shouldBeEqualToString("file3Entry.name", "file3.txt");
+        shouldBeEqualToString("file3Entry.fullPath", "/testFiles/subfolder1/file3.txt");
+        shouldBeTrue("file3Entry.isFile");
+    }, e => {
+        testFailed("getFile('../testFiles/././subfolder1/../subfolder1/./file3.txt') should succeed");
+    });
+}
+
+function test10() {
+    debug("* Edge case: calling getFile() with relative path containing too many '..'");
+    return getFileAsPromise(directoryEntry, "../../../../../testFiles/file1.txt", {}).then(entry => {
+        file1Entry = entry;
+        shouldBeEqualToString("file1Entry.name", "file1.txt");
+        shouldBeEqualToString("file1Entry.fullPath", "/testFiles/file1.txt");
+        shouldBeTrue("file1Entry.isFile");
+    }, e => {
+        testFailed("getFile('../../../../../testFiles/file1.txt') should succeed");
+    });
+}
+
+function test11() {
+    debug("* Edge case: calling getFile() with absolute path containing '..'");
+    return getFileAsPromise(directoryEntry, "/testFiles/../testFiles/file1.txt", {}).then(entry => {
+        file1Entry = entry;
+        shouldBeEqualToString("file1Entry.name", "file1.txt");
+        shouldBeEqualToString("file1Entry.fullPath", "/testFiles/file1.txt");
+        shouldBeTrue("file1Entry.isFile");
+    }, e => {
+        testFailed("getFile('../../../../../testFiles/file1.txt') should succeed");
+    });
+}
+
+function test12() {
+    debug("* Edge case: calling getFile() with absolute path containing too many '..'");
+    return getFileAsPromise(directoryEntry, "/../../../../../testFiles/file1.txt", {}).then(entry => {
+        file1Entry = entry;
+        shouldBeEqualToString("file1Entry.name", "file1.txt");
+        shouldBeEqualToString("file1Entry.fullPath", "/testFiles/file1.txt");
+        shouldBeTrue("file1Entry.isFile");
+    }, e => {
+        testFailed("getFile('../../../../../testFiles/file1.txt') should succeed");
+    });
+}
+
+function test13() {
+    debug("* Error case: calling getFile() with empty path");
+    return getFileAsPromise(directoryEntry, "", {}).then(entry => {
+        testFailed("Should fail when path is invalid");
+    }, e => {
+        ex = e;
+        shouldBeEqualToString("ex.name", "TypeMismatchError");
+    });
+}
+
+var dropzone = document.getElementById('dropzone');
+dropzone.ondrop = function(e) {
+    e.preventDefault();
+    dataTransfer = e.dataTransfer;
+
+    shouldBe("dataTransfer.items.length", "1");
+
+    directoryEntry = dataTransfer.items[0].webkitGetAsEntry();
+    test1()
+    .then(test2)
+    .then(test3)
+    .then(test4)
+    .then(test5)
+    .then(test6)
+    .then(test7)
+    .then(test8)
+    .then(test9)
+    .then(test10)
+    .then(test11)
+    .then(test12)
+    .then(test13)
+    .then(finishJSTest);
+};
+
+dropzone.ondragover = function(ev) {
+    ev.preventDefault();
+}
+
+onload = function() {
+    dragFilesOntoElement(dropzone, ['../../fast/forms/file/resources/testFiles']);
+}
+</script>
+</body>
+</html>
index 6544f65..1a6db1f 100644 (file)
@@ -567,6 +567,7 @@ editing/pasteboard/datatransfer-items-drop-directoryReader.html
 editing/pasteboard/datatransfer-items-drop-directoryReader-error.html
 editing/pasteboard/datatransfer-items-drop-directoryReader-root.html
 editing/pasteboard/datatransfer-items-drop-getAsEntry.html
+editing/pasteboard/datatransfer-items-drop-getFile.html
 editing/pasteboard/datatransfer-items-drop-getParent-root.html
 editing/pasteboard/datatransfer-items-drop-getParent.html
 editing/pasteboard/datatransfer-items-drop-getParent2.html
index 9654b14..c8f74ef 100644 (file)
@@ -1,3 +1,55 @@
+2017-09-01  Chris Dumez  <cdumez@apple.com>
+
+        Implement FileSystemDirectoryEntry.getFile()
+        https://bugs.webkit.org/show_bug.cgi?id=176167
+        <rdar://problem/34187775>
+
+        Reviewed by Andreas Kling.
+
+        Implement FileSystemDirectoryEntry.getFile():
+        - https://wicg.github.io/entries-api/#dom-filesystemdirectoryentry-getfile
+
+        Test: editing/pasteboard/datatransfer-items-drop-getFile.html
+
+        * Modules/entriesapi/DOMFileSystem.cpp:
+        (WebCore::isValidPathNameCharacter):
+        (WebCore::isValidPathSegment):
+        (WebCore::isValidRelativeVirtualPath):
+        (WebCore::isValidAbsoluteVirtualPath):
+        (WebCore::isValidVirtualPath):
+        Implement various path validation functions as per:
+        - https://wicg.github.io/entries-api/#names-paths
+
+        (WebCore::resolveRelativeVirtualPath):
+        - Use StringView for second parameter for efficiency. Had to keep
+          a String for the first parameter because I need the split to
+          return a Vector.
+        - If the input path is absolute, call recursively with '/' as
+          base path so that the virtual path gets sanitized (i.e. '..'
+          and '.' in the absolute URL get resolved).
+
+        (WebCore::DOMFileSystem::listDirectory):
+        Use String instead of auto. It is not much longer and is clearer.
+
+        (WebCore::validatePathIsDirectory):
+        (WebCore::DOMFileSystem::getParent):
+        Move logic to validate that the path is a directory from getParent() to a
+        separate function. This only makes sure the ScriptExecutionContext is only
+        ref'd / deref'd on the main thread.
+
+        (WebCore::validatePathIsFile):
+        Logic for validating that a path is a file, used by getFile().
+
+        (WebCore::DOMFileSystem::getFile):
+        Implement getFile() as per:
+        - https://wicg.github.io/entries-api/#dom-filesystemdirectoryentry-getfile
+
+        * Modules/entriesapi/DOMFileSystem.h:
+        * Modules/entriesapi/FileSystemDirectoryEntry.cpp:
+        (WebCore::FileSystemDirectoryEntry::getFile):
+        Add implementation of FileSystemDirectoryEntry::getFile() which merely calls
+        DOMFileSystem::getFile().
+
 2017-09-01  Brady Eidson  <beidson@apple.com>
 
         ASSERTION FAILED: taken.get() == &job in WebCore::ServiceWorkerContainer::jobDidFinish(WebCore::ServiceWorkerJob &).
index 2550bc3..6b0a3bb 100644 (file)
@@ -87,9 +87,51 @@ static ExceptionOr<Vector<Ref<FileSystemEntry>>> toFileSystemEntries(ScriptExecu
     return WTFMove(entries);
 }
 
-static bool isAbsoluteVirtualPath(const String& virtualPath)
+// https://wicg.github.io/entries-api/#name
+static bool isValidPathNameCharacter(UChar c)
 {
-    return !virtualPath.isEmpty() && virtualPath[0] == '/';
+    return c != '\0' && c != '/' && c != '\\';
+}
+
+// https://wicg.github.io/entries-api/#path-segment
+static bool isValidPathSegment(StringView segment)
+{
+    ASSERT(!segment.isEmpty());
+    if (segment == "." || segment == "..")
+        return true;
+
+    for (unsigned i = 0; i < segment.length(); ++i) {
+        if (!isValidPathNameCharacter(segment[i]))
+            return false;
+    }
+    return true;
+}
+
+// https://wicg.github.io/entries-api/#relative-path
+static bool isValidRelativeVirtualPath(StringView virtualPath)
+{
+    if (virtualPath.isEmpty())
+        return false;
+
+    if (virtualPath[0] == '/')
+        return false;
+
+    auto segments = virtualPath.split('/');
+    for (auto segment : segments) {
+        if (!isValidPathSegment(segment))
+            return false;
+    }
+    return true;
+}
+
+// https://wicg.github.io/entries-api/#valid-path
+static bool isValidVirtualPath(StringView virtualPath)
+{
+    if (virtualPath.isEmpty())
+        return false;
+    if (virtualPath[0] == '/')
+        return isValidRelativeVirtualPath(virtualPath.substring(1));
+    return isValidRelativeVirtualPath(virtualPath);
 }
 
 DOMFileSystem::DOMFileSystem(Ref<File>&& file)
@@ -118,15 +160,15 @@ Ref<FileSystemEntry> DOMFileSystem::fileAsEntry(ScriptExecutionContext& context)
 }
 
 // https://wicg.github.io/entries-api/#resolve-a-relative-path
-static String resolveRelativePath(const String& virtualPath, const String& relativePath)
+static String resolveRelativeVirtualPath(const String& baseVirtualPath, StringView relativeVirtualPath)
 {
-    ASSERT(virtualPath[0] == '/');
-    if (isAbsoluteVirtualPath(relativePath))
-        return relativePath;
+    ASSERT(baseVirtualPath[0] == '/');
+    if (relativeVirtualPath[0] == '/')
+        return resolveRelativeVirtualPath(ASCIILiteral("/"), relativeVirtualPath.substring(1));
 
-    auto virtualPathSegments = virtualPath.split('/');
-    auto relativePathSegments = relativePath.split('/');
-    for (auto& segment : relativePathSegments) {
+    auto virtualPathSegments = baseVirtualPath.split('/');
+    auto relativePathSegments = relativeVirtualPath.split('/');
+    for (auto segment : relativePathSegments) {
         ASSERT(!segment.isEmpty());
         if (segment == ".")
             continue;
@@ -135,7 +177,7 @@ static String resolveRelativePath(const String& virtualPath, const String& relat
                 virtualPathSegments.removeLast();
             continue;
         }
-        virtualPathSegments.append(segment);
+        virtualPathSegments.append(segment.toString());
     }
 
     if (virtualPathSegments.isEmpty())
@@ -176,7 +218,7 @@ void DOMFileSystem::listDirectory(ScriptExecutionContext& context, FileSystemDir
     ASSERT(&directory.filesystem() == this);
 
     String directoryVirtualPath = directory.virtualPath();
-    auto fullPath = evaluatePath(directoryVirtualPath);
+    String fullPath = evaluatePath(directoryVirtualPath);
     if (fullPath == m_rootPath) {
         Vector<Ref<FileSystemEntry>> children;
         children.append(fileAsEntry(context));
@@ -192,20 +234,76 @@ void DOMFileSystem::listDirectory(ScriptExecutionContext& context, FileSystemDir
     });
 }
 
+static ExceptionOr<String> validatePathIsDirectory(const String& fullPath, String&& virtualPath)
+{
+    ASSERT(!isMainThread());
+
+    if (!fileIsDirectory(fullPath, ShouldFollowSymbolicLinks::No))
+        return Exception { NotFoundError, "Path no longer exists or is no longer a directory" };
+    return WTFMove(virtualPath);
+}
+
 void DOMFileSystem::getParent(ScriptExecutionContext& context, FileSystemEntry& entry, GetParentCallback&& completionCallback)
 {
-    String virtualPath = resolveRelativePath(entry.virtualPath(), ASCIILiteral(".."));
+    ASSERT(&entry.filesystem() == this);
+
+    String virtualPath = resolveRelativeVirtualPath(entry.virtualPath(), "..");
     ASSERT(virtualPath[0] == '/');
     String fullPath = evaluatePath(virtualPath);
     m_workQueue->dispatch([this, context = makeRef(context), fullPath = crossThreadCopy(fullPath), virtualPath = crossThreadCopy(virtualPath), completionCallback = WTFMove(completionCallback)]() mutable {
-        if (!fileIsDirectory(fullPath, ShouldFollowSymbolicLinks::No)) {
-            callOnMainThread([completionCallback = WTFMove(completionCallback)] {
-                completionCallback(Exception { NotFoundError, "Path no longer exists or is no longer a directory" });
-            });
-            return;
-        }
-        callOnMainThread([this, context = WTFMove(context), virtualPath = crossThreadCopy(virtualPath), completionCallback = WTFMove(completionCallback)] {
-            completionCallback(FileSystemDirectoryEntry::create(context, *this, virtualPath));
+        auto validatedVirtualPath = validatePathIsDirectory(fullPath, WTFMove(virtualPath));
+        callOnMainThread([this, context = WTFMove(context), validatedVirtualPath = crossThreadCopy(validatedVirtualPath), completionCallback = WTFMove(completionCallback)]() mutable {
+            if (validatedVirtualPath.hasException())
+                completionCallback(validatedVirtualPath.releaseException());
+            else
+                completionCallback(FileSystemDirectoryEntry::create(context, *this, validatedVirtualPath.releaseReturnValue()));
+        });
+    });
+}
+
+static ExceptionOr<String> validatePathIsFile(const String& fullPath, String&& virtualPath)
+{
+    ASSERT(!isMainThread());
+
+    FileMetadata metadata;
+    if (!getFileMetadata(fullPath, metadata, ShouldFollowSymbolicLinks::No))
+        return Exception { NotFoundError, ASCIILiteral("File does not exist") };
+
+    if (metadata.type != FileMetadata::TypeFile)
+        return Exception { TypeMismatchError, ASCIILiteral("Entry at path is not a file") };
+
+    return WTFMove(virtualPath);
+}
+
+// https://wicg.github.io/entries-api/#dom-filesystemdirectoryentry-getfile
+void DOMFileSystem::getFile(ScriptExecutionContext& context, FileSystemDirectoryEntry& directory, const String& virtualPath, const FileSystemDirectoryEntry::Flags& flags, GetFileCallback&& completionCallback)
+{
+    ASSERT(&directory.filesystem() == this);
+
+    if (!isValidVirtualPath(virtualPath)) {
+        callOnMainThread([completionCallback = WTFMove(completionCallback)] {
+            completionCallback(Exception { TypeMismatchError, ASCIILiteral("Path is invalid") });
+        });
+        return;
+    }
+
+    if (flags.create) {
+        callOnMainThread([completionCallback = WTFMove(completionCallback)] {
+            completionCallback(Exception { SecurityError, ASCIILiteral("create flag cannot be true") });
+        });
+        return;
+    }
+
+    String resolvedVirtualPath = resolveRelativeVirtualPath(directory.virtualPath(), virtualPath);
+    ASSERT(resolvedVirtualPath[0] == '/');
+    String fullPath = evaluatePath(resolvedVirtualPath);
+    m_workQueue->dispatch([this, context = makeRef(context), fullPath = crossThreadCopy(fullPath), resolvedVirtualPath = crossThreadCopy(resolvedVirtualPath), completionCallback = WTFMove(completionCallback)]() mutable {
+        auto validatedVirtualPath = validatePathIsFile(fullPath, WTFMove(resolvedVirtualPath));
+        callOnMainThread([this, context = WTFMove(context), validatedVirtualPath = crossThreadCopy(validatedVirtualPath), completionCallback = WTFMove(completionCallback)]() mutable {
+            if (validatedVirtualPath.hasException())
+                completionCallback(validatedVirtualPath.releaseException());
+            else
+                completionCallback(FileSystemFileEntry::create(context, *this, validatedVirtualPath.releaseReturnValue()));
         });
     });
 }
index d5a489c..a83398e 100644 (file)
@@ -26,6 +26,7 @@
 #pragma once
 
 #include "ExceptionOr.h"
+#include "FileSystemDirectoryEntry.h"
 #include "ScriptWrappable.h"
 #include <wtf/RefCounted.h>
 #include <wtf/WorkQueue.h>
@@ -34,7 +35,7 @@
 namespace WebCore {
 
 class File;
-class FileSystemDirectoryEntry;
+class FileSystemFileEntry;
 class FileSystemEntry;
 class ScriptExecutionContext;
 
@@ -57,6 +58,9 @@ public:
     using GetParentCallback = WTF::Function<void(ExceptionOr<Ref<FileSystemDirectoryEntry>>&&)>;
     void getParent(ScriptExecutionContext&, FileSystemEntry&, GetParentCallback&&);
 
+    using GetFileCallback = WTF::Function<void(ExceptionOr<Ref<FileSystemFileEntry>>&&)>;
+    void getFile(ScriptExecutionContext&, FileSystemDirectoryEntry&, const String& virtualPath, const FileSystemDirectoryEntry::Flags&, GetFileCallback&&);
+
 private:
     explicit DOMFileSystem(Ref<File>&&);
 
index 6ae8675..1c28116 100644 (file)
 #include "FileSystemDirectoryEntry.h"
 
 #include "DOMException.h"
+#include "DOMFileSystem.h"
 #include "ErrorCallback.h"
 #include "FileSystemDirectoryReader.h"
+#include "FileSystemEntryCallback.h"
+#include "FileSystemFileEntry.h"
+#include "ScriptExecutionContext.h"
 
 namespace WebCore {
 
@@ -42,10 +46,20 @@ Ref<FileSystemDirectoryReader> FileSystemDirectoryEntry::createReader(ScriptExec
     return FileSystemDirectoryReader::create(context, *this);
 }
 
-void FileSystemDirectoryEntry::getFile(ScriptExecutionContext& context, const String&, const Flags&, RefPtr<FileSystemEntryCallback>&&, RefPtr<ErrorCallback>&& errorCallback)
+void FileSystemDirectoryEntry::getFile(ScriptExecutionContext& context, const String& path, const Flags& flags, RefPtr<FileSystemEntryCallback>&& successCallback, RefPtr<ErrorCallback>&& errorCallback)
 {
-    if (errorCallback)
-        errorCallback->scheduleCallback(context, DOMException::create(NotSupportedError));
+    if (!successCallback && !errorCallback)
+        return;
+
+    filesystem().getFile(context, *this, path, flags, [this, pendingActivity = makePendingActivity(*this), successCallback = WTFMove(successCallback), errorCallback = WTFMove(errorCallback)](auto&& result) {
+        if (result.hasException()) {
+            if (errorCallback)
+                errorCallback->handleEvent(DOMException::create(result.releaseException()));
+            return;
+        }
+        if (successCallback)
+            successCallback->handleEvent(result.releaseReturnValue());
+    });
 }
 
 void FileSystemDirectoryEntry::getDirectory(ScriptExecutionContext& context, const String&, const Flags&, RefPtr<FileSystemEntryCallback>&&, RefPtr<ErrorCallback>&& errorCallback)